diff --git a/README.rst b/README.rst index 4ee0a80f..ed6c00b6 100644 --- a/README.rst +++ b/README.rst @@ -1,16 +1,16 @@ Python Bitcoin Library ====================== -Bitcoin Crypto Currency Library for Python. +Bitcoin cryptocurrency Library writen in Python. -Includes a fully functional wallet, with multi signature, multi currency and multiple accounts. Use this -library to create and manage transactions, addresses/keys, wallets, mnemonic password phrases and blocks with +Allows you to create a fully functional Bitcoin wallet with a single line of code. +Use this library to create and manage transactions, addresses/keys, wallets, mnemonic password phrases and blocks with simple and straightforward Python code. -You this library at a high level and create and manage wallets for the command line or at a low level -and create your own custom made transactions, keys or wallets. +You can use this library at a high level and create and manage wallets from the command line or at a low level +and create your own custom made transactions, scripts, keys or wallets. -The BitcoinLib connects to various service providers automatically to update wallets, transactions and +The BitcoinLib connects to various service providers automatically to update wallets, transaction and blockchain information. .. image:: https://github.com/1200wd/bitcoinlib/actions/workflows/unittests.yaml/badge.svg diff --git a/bitcoinlib/scripts.py b/bitcoinlib/scripts.py index e8e00a60..3e442f4b 100644 --- a/bitcoinlib/scripts.py +++ b/bitcoinlib/scripts.py @@ -45,6 +45,7 @@ 'sig_pubkey': ('unlocking', ['signature', 'key'], []), # 'p2sh_multisig': ('unlocking', [op.op_0, 'signature', 'op_n', 'key', 'op_n', op.op_checkmultisig], []), 'p2sh_multisig': ('unlocking', [op.op_0, 'signature', 'redeemscript'], []), + 'p2tr_unlock': ('unlocking', ['data'], [64]), 'p2sh_multisig_2?': ('unlocking', [op.op_0, 'signature', op.op_verify, 'redeemscript'], []), 'p2sh_multisig_3?': ('unlocking', [op.op_0, 'signature', op.op_1add, 'redeemscript'], []), 'p2sh_p2wpkh': ('unlocking', [op.op_0, op.op_hash160, 'redeemscript', op.op_equal], []), @@ -155,7 +156,7 @@ def get_data_type(data): elif ((data.startswith(b'\x02') or data.startswith(b'\x03')) and len(data) == 33) or \ (data.startswith(b'\x04') and len(data) == 65): return 'key' - elif len(data) == 20 or len(data) == 32 or 1 < len(data) <= 4: + elif len(data) == 20 or len(data) == 32 or len(data) == 64 or 1 < len(data) <= 4: return 'data-%d' % len(data) else: return 'other' @@ -282,14 +283,17 @@ def parse(cls, script, message=None, tx_data=None, strict=True, _level=0): :return Script: """ + data_length = None if isinstance(script, bytes): + data_length = len(script) // 2 script = BytesIO(script) elif isinstance(script, str): + data_length = len(script) script = BytesIO(bytes.fromhex(script)) - return cls.parse_bytesio(script, message, tx_data, strict, _level) + return cls.parse_bytesio(script, message, tx_data, data_length, strict, _level) @classmethod - def parse_bytesio(cls, script, message=None, tx_data=None, strict=True, _level=0): + def parse_bytesio(cls, script, message=None, tx_data=None, data_length=0, strict=True, _level=0): """ Parse raw script and return Script object. Extracts script commands, keys, signatures and other data. @@ -299,6 +303,8 @@ def parse_bytesio(cls, script, message=None, tx_data=None, strict=True, _level=0 :type message: bytes :param tx_data: Dictionary with extra information needed to verify script. Such as 'redeemscript' for multisignature scripts and 'blockcount' for time locked scripts :type tx_data: dict + :param data_length: Length of script data if known. Supply if you can to increase efficiency and lower change of incorrect parsing + :type data_length: int :param strict: Raise exception when script is malformed, incomplete or not understood. Default is True :type strict: bool :param _level: Internal argument used to avoid recursive depth @@ -317,28 +323,20 @@ def parse_bytesio(cls, script, message=None, tx_data=None, strict=True, _level=0 if not tx_data: tx_data = {} - while script: - chb = script.read(1) - if not chb: - break - ch = int.from_bytes(chb, 'big') - data = None + chb = script.read(1) + ch = int.from_bytes(chb, 'big') + data = None + if chb == b'\x30' and 69 <= data_length <= 74: + data = chb + script.read(data_length - 1) + elif ((chb == b'\x02' or chb == b'\x03') and data_length == 33) or \ + (chb == b'\x04' and data_length == 65): + data = chb + script.read(data_length - 1) + elif data_length == 64: + data = chb + script.read(data_length - 1) + else: data_length = 0 - if 1 <= ch <= 75: # Data` - data_length = ch - elif ch == op.op_pushdata1: - data_length = int.from_bytes(script.read(1), 'little') - elif ch == op.op_pushdata2: - data_length = int.from_bytes(script.read(2), 'little') - if data_length: - data = script.read(data_length) - if len(data) != data_length: - msg = "Malformed script, not enough data found" - if strict: - raise ScriptError(msg) - else: - _logger.warning(msg) + while chb and script: if data: data_type = get_data_type(data) commands.append(data) @@ -384,10 +382,32 @@ def parse_bytesio(cls, script, message=None, tx_data=None, strict=True, _level=0 sigs_required = s2.sigs_required except (ScriptError, IndexError): blueprint.append('data-%d' % len(data)) + data = None + data_length = 0 else: # Other opcode + if 1 <= ch <= 75: # Data` + data_length = ch + elif ch == op.op_pushdata1: + data_length = int.from_bytes(script.read(1), 'little') + elif ch == op.op_pushdata2: + data_length = int.from_bytes(script.read(2), 'little') + if data_length: + data = script.read(data_length) + if len(data) != data_length: + msg = "Malformed script, not enough data found" + if strict: + raise ScriptError(msg) + else: + chb = b'' + _logger.warning(msg) + continue + commands.append(ch) blueprint.append(ch) + chb = script.read(1) + ch = int.from_bytes(chb, 'big') + s = cls(commands, message, keys=keys, signatures=signatures, blueprint=blueprint, tx_data=tx_data, hash_type=hash_type) script.seek(0) @@ -410,6 +430,8 @@ def parse_bytesio(cls, script, message=None, tx_data=None, strict=True, _level=0 (len(s.keys), s.commands[-2] - 80)) elif st in ['p2wpkh', 'p2wsh', 'p2sh', 'p2tr'] and len(s.commands) > 1: s.public_hash = s.commands[1] + elif st == 'p2tr_unlock': + s.public_hash = s.commands[0] elif st == 'p2pkh' and len(s.commands) > 2: s.public_hash = s.commands[2] s.redeemscript = redeemscript if redeemscript else s.redeemscript @@ -443,7 +465,8 @@ def parse_hex(cls, script, message=None, tx_data=None, strict=True, _level=0): :return Script: """ - return cls.parse_bytesio(BytesIO(bytes.fromhex(script)), message, tx_data, strict, _level) + data_length = len(script) // 2 + return cls.parse_bytesio(BytesIO(bytes.fromhex(script)), message, tx_data, data_length, strict, _level) @classmethod def parse_bytes(cls, script, message=None, tx_data=None, strict=True, _level=0): @@ -465,7 +488,8 @@ def parse_bytes(cls, script, message=None, tx_data=None, strict=True, _level=0): :return Script: """ - return cls.parse_bytesio(BytesIO(script), message, tx_data, strict, _level) + data_length = len(script) + return cls.parse_bytesio(BytesIO(script), message, tx_data, data_length, strict, _level) def __repr__(self): s_items = [] diff --git a/bitcoinlib/tools/wallet_multisig_2of3.py b/bitcoinlib/tools/wallet_multisig_2of3.py index 14ee94a0..cd32ba17 100644 --- a/bitcoinlib/tools/wallet_multisig_2of3.py +++ b/bitcoinlib/tools/wallet_multisig_2of3.py @@ -71,7 +71,7 @@ key_lists[w[0]].append(addkey) offline_wallet = Wallet.create(WALLET_NAME, key_lists['Offline PC'], sigs_required=SIGNATURES_REQUIRED, - witness_type=WITNESS_TYPE, network=NETWORK) + witness_type=WITNESS_TYPE, network=NETWORK) offline_wallet.new_key() print("\n\nA multisig wallet has been created on this system") diff --git a/bitcoinlib/transactions.py b/bitcoinlib/transactions.py index fb205cd2..b54166c1 100644 --- a/bitcoinlib/transactions.py +++ b/bitcoinlib/transactions.py @@ -953,7 +953,7 @@ def update_scripts(self, hash_type=SIGHASH_ALL): if b'' in signatures: raise TransactionError("Empty signature found in signature list when signing. " "Is DER encoded version of signature defined?") - if len(signatures) == self.sigs_required: # and not self.unlocking_script + if len(signatures) and len(signatures) >= self.sigs_required: # and not self.unlocking_script unlock_script_obj = Script(script_types=['p2sh_multisig'], keys=[k.public_byte for k in self.keys], signatures=self.signatures[:self.sigs_required], sigs_required=self.sigs_required, redeemscript=self.redeemscript) @@ -984,6 +984,9 @@ def update_scripts(self, hash_type=SIGHASH_ALL): addr_data = self.keys[0].public_byte if self.signatures and not self.unlocking_script: self.unlocking_script = varstr(self.signatures[0].as_der_encoded()) + elif self.script_type == 'p2tr': # segwit_v1 + self.redeemscript = self.witnesses[0] + # FIXME: Address cannot be known without looking at previous transaction elif self.script_type not in ['coinbase', 'unknown'] and self.strict: raise TransactionError("Unknown unlocking script type %s for input %d" % (self.script_type, self.index_n)) if addr_data and not self.address: @@ -1475,25 +1478,40 @@ def parse_bytesio(cls, rawtx, strict=True, network=DEFAULT_NETWORK): if not n_items: continue script = Script() + is_taproot = False for m in range(0, n_items): item_size = read_varbyteint(rawtx) - witness = rawtx.read(item_size) + if item_size == 0: + witness = b'\0' + else: + witness = rawtx.read(item_size) inputs[n].witnesses.append(witness) - s = Script.parse_bytes(varstr(witness), strict=strict) - script += s + if not is_taproot: + s = Script.parse_bytes(witness, strict=strict) + if s.script_types == ['p2tr_unlock']: + # FIXME: Support Taproot unlocking scripts + _logger.warning("Taproot is not supported at the moment, rest of parsing input transaction " + "skipped") + is_taproot = True + script += s inputs[n].script = script if not inputs[n].script else inputs[n].script + script inputs[n].keys = script.keys inputs[n].signatures = script.signatures - if script.script_types[0][:13] == 'p2sh_multisig' or script.script_types[0] == 'signature_multisig': # , 'p2sh_p2wsh' + if script.script_types[0][:13] == 'p2sh_multisig' or script.script_types[0] =='signature_multisig': inputs[n].script_type = 'p2sh_multisig' inputs[n].redeemscript = inputs[n].witnesses[-1] + elif script.script_types[0] == 'p2tr_unlock': + inputs[n].script_type = 'p2tr' + inputs[n].witness_type = 'segwit' elif inputs[n].script_type == 'p2wpkh': inputs[n].script_type = 'p2sh_p2wpkh' inputs[n].witness_type = 'p2sh-segwit' elif inputs[n].script_type == 'p2wpkh' or inputs[n].script_type == 'p2wsh': inputs[n].script_type = 'p2sh_p2wsh' inputs[n].witness_type = 'p2sh-segwit' + elif 'unknown' in script.script_types and not coinbase: + inputs[n].script_type = 'unknown' inputs[n].update_scripts() diff --git a/docs/index.rst b/docs/index.rst index 15266881..6fc68ca1 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,16 +6,16 @@ Welcome to Bitcoinlib's documentation! ====================================== -Bitcoin Crypto Currency Library for Python. +Bitcoin cryptocurrency Library writen in Python. -Includes a fully functional wallet, with multi signature, multi currency and multiple accounts. Use this -library to create and manage transactions, addresses/keys, wallets, mnemonic password phrases and blocks with +Allows you to create a fully functional wallet with a single line of code. +Use this library to create and manage transactions, addresses/keys, wallets, mnemonic password phrases and blocks with simple and straightforward Python code. -You can use this library at a high level and create and manage wallets on the command line or at a low level +You can use this library at a high level and create and manage wallets from the command line or at a low level and create your own custom made transactions, scripts, keys or wallets. -The BitcoinLib connects to various service providers automatically to update wallets, transactions and +The BitcoinLib connects to various service providers automatically to update wallets, transaction and blockchain information. diff --git a/examples/transactions.py b/examples/transactions.py index c52744ba..b3c894dd 100644 --- a/examples/transactions.py +++ b/examples/transactions.py @@ -100,7 +100,7 @@ pk3 = HDKey() t = Transaction() test_txid = 'f3d9b08dbd873631aaca66a1d18342ba24a22437ea107805405f6bedd3851618' -t.add_input(prev_txid=test_txid, output_n=0, +t.add_input(prev_txid=test_txid, output_n=0, value=100000, keys=[pk1.public_byte, pk2.public_byte, pk3.public_byte], script_type='p2sh_multisig', sigs_required=2) t.add_output(100000, '1HsZBGm6nNGG1Moc3TL6S9DSGbnPbsSyW3') diff --git a/tests/test_transactions.py b/tests/test_transactions.py index 0318792a..1745ae0e 100644 --- a/tests/test_transactions.py +++ b/tests/test_transactions.py @@ -105,26 +105,26 @@ def test_transaction_hash_type(self): # TODO: Move and rewrite # def test_transaction_input_locktime(self): - # rawtx = '0200000002f42e4ee59d33dffc39978bd6f7a1fdef42214b7de7d6d2716b2a5ae0a92fbb09000000006a473044' \ - # '022003ea734e54ddc00d4d681e2cac9ecbedb45d24af307aefbc55ecb005c5d2dc13022054d5a0fdb7a0c3ae7b' \ - # '161ffb654be7e89c84de06013d416f708f85afe11845a601210213692eb7eb74a0f86284890885629f2d097733' \ - # '7376868b033029ba49cc64765dfdffffff27a321a0e098276e3dce7aedf33a633db31bf34262bde3fe30106a32' \ - # '7696a70a000000006a47304402207758c05e849310af174ad4d484cdd551d66244d4cf0b5bba84e94d59eb8d3c' \ - # '9b02203e005ef10ede62db1900ed0bc2c72c7edd83ef98a21a3c567b4c6defe8ffca06012103ab51db28d30d3a' \ - # 'c99965a5405c3d473e25dff6447db1368e9191229d6ec0b635fdffffff029b040000000000001976a91406d66a' \ - # 'dea8ca6fcbb4a7a5f18458195c869f4b5488ac307500000000000017a9140614a615ee10d84a1e6d85ec1ff7ff' \ - # 'f527757d5987ffffffff' - # t = Transaction.parse_hex(rawtx) - # t.inputs[0].set_locktime_relative_time(1000) - # self.assertEqual(t.inputs[0].sequence, 4194305) - # t.inputs[0].set_locktime_relative_time(0) - # self.assertEqual(t.inputs[0].sequence, 0xffffffff) - # t.inputs[0].set_locktime_relative_time(100) - # self.assertEqual(t.inputs[0].sequence, 4194305) - # t.inputs[0].set_locktime_relative_blocks(120) - # self.assertEqual(t.inputs[0].sequence, 120) - # t.inputs[0].set_locktime_relative_blocks(0) - # self.assertEqual(t.inputs[0].sequence, 0xffffffff) + # rawtx = '0200000002f42e4ee59d33dffc39978bd6f7a1fdef42214b7de7d6d2716b2a5ae0a92fbb09000000006a473044' \ + # '022003ea734e54ddc00d4d681e2cac9ecbedb45d24af307aefbc55ecb005c5d2dc13022054d5a0fdb7a0c3ae7b' \ + # '161ffb654be7e89c84de06013d416f708f85afe11845a601210213692eb7eb74a0f86284890885629f2d097733' \ + # '7376868b033029ba49cc64765dfdffffff27a321a0e098276e3dce7aedf33a633db31bf34262bde3fe30106a32' \ + # '7696a70a000000006a47304402207758c05e849310af174ad4d484cdd551d66244d4cf0b5bba84e94d59eb8d3c' \ + # '9b02203e005ef10ede62db1900ed0bc2c72c7edd83ef98a21a3c567b4c6defe8ffca06012103ab51db28d30d3a' \ + # 'c99965a5405c3d473e25dff6447db1368e9191229d6ec0b635fdffffff029b040000000000001976a91406d66a' \ + # 'dea8ca6fcbb4a7a5f18458195c869f4b5488ac307500000000000017a9140614a615ee10d84a1e6d85ec1ff7ff' \ + # 'f527757d5987ffffffff' + # t = Transaction.parse_hex(rawtx) + # t.inputs[0].set_locktime_relative_time(1000) + # self.assertEqual(t.inputs[0].sequence, 4194305) + # t.inputs[0].set_locktime_relative_time(0) + # self.assertEqual(t.inputs[0].sequence, 0xffffffff) + # t.inputs[0].set_locktime_relative_time(100) + # self.assertEqual(t.inputs[0].sequence, 4194305) + # t.inputs[0].set_locktime_relative_blocks(120) + # self.assertEqual(t.inputs[0].sequence, 120) + # t.inputs[0].set_locktime_relative_blocks(0) + # self.assertEqual(t.inputs[0].sequence, 0xffffffff) class TestTransactionOutputs(unittest.TestCase): @@ -1197,7 +1197,26 @@ def test_transaction_equal(self): ) self.assertTrue(t1 == t2) - def test_transaction_p2tr(self): + def test_transaction_lightning_force_close(self): + txid = '0018f5e7c12f1ba6b75021f72e5037bf07f7625ab5c97c09d5e32c61d276db90' + network = 'bitcoin' + rawtx = ('0200000000010152d28713dfb0014f739821c4b96fe0a72dab4e893aec1df226ddc5e81e3d7e3d0000000000f000000001' + 'fccb070000000000160014dc754901d5d9299c6b42c315c5e384c89c5eafda03483045022100db8e0480b4f9b44063b635' + 'c89291a2f9cb860fe05c533c59dbcdb014df1fcd02022010936f2acbfd14bcb010cbaee6b13139f6fec8686edb41d2380c' + '5aa42e1be48001004d632103750ff0f31bb30189b1714b4613e0e68c8d9c2fe85b0f5a174f2f8b31aa06c51d6702f000b2' + '752102a3ab7a4b357101f4b77fa7d915b37aeeed0404b2f7de2f0bedc6dfb15386cbfa68acbf270900') + witness_0 = ('3045022100db8e0480b4f9b44063b635c89291a2f9cb860fe05c533c59dbcdb014df1fcd02022010936f2acbfd14bc' + 'b010cbaee6b13139f6fec8686edb41d2380c5aa42e1be48001') + witness_1 = '00' + witness_2 = ('632103750ff0f31bb30189b1714b4613e0e68c8d9c2fe85b0f5a174f2f8b31aa06c51d6702f000b2752102a3ab7a4b3' + '57101f4b77fa7d915b37aeeed0404b2f7de2f0bedc6dfb15386cbfa68ac') + t = Transaction.parse_hex(rawtx, True, network=network) + self.assertEqual(t.txid, txid) + self.assertEqual(t.inputs[0].witnesses[0].hex(), witness_0) + self.assertEqual(t.inputs[0].witnesses[1].hex(), witness_1) + self.assertEqual(t.inputs[0].witnesses[2].hex(), witness_2) + + def test_transaction_p2tr_output(self): rawtx = '01000000000101d1f1c1f8cdf6759167b90f52c9ad358a369f95284e841d7a2536cef31c0549580100000000fdffffff02' \ '0000000000000000316a2f49206c696b65205363686e6f7272207369677320616e6420492063616e6e6f74206c69652e20' \ '4062697462756734329e06010000000000225120a37c3903c8d0db6512e2b40b0dffa05e5a3ab73603ce8c9c4b7771e541' \ @@ -1207,6 +1226,57 @@ def test_transaction_p2tr(self): self.assertEqual(t.outputs[1].address, 'bc1p5d7rjq7g6rdk2yhzks9smlaqtedr4dekq08ge8ztwac72sfr9rusxg3297') self.assertEqual(t.outputs[1].script_type, 'p2tr') + def test_transaction_p2tr_input(self): + txid = '37777defed8717c581b4c0509329550e344bdc14ac38f71fc050096887e535c8' + network = 'bitcoin' + rawtx = ( + '020000000001027bc0bba407bc67178f100e352bf6e047fae4cbf960d783586cb5e430b3b700e70000000000feffffff7bc0bb' + 'a407bc67178f100e352bf6e047fae4cbf960d783586cb5e430b3b700e70100000000feffffff01b4ba0e000000000016001417' + '3fd310e9db2c7e9550ce0f03f1e6c01d833aa90140134896c42cd95680b048845847c8054756861ffab7d4abab72f6508d67d1' + 'ec0c590287ec2161dd7884983286e1cd56ce65c08a24ee0476ede92678a93b1b180c03407b5d614a4610bf9196775791fcc589' + '597ca066dcd10048e004cd4c7341bb4bb90cee4705192f3f7db524e8067a5222c7f09baf29ef6b805b8327ecd1e5ab83ca2220' + 'f5b059b9a72298ccbefff59d9b943f7e0fc91d8a3b944a95e7b6390cc99eb5f4ac41c0d9dfdf0fe3c83e9870095d67fff59a80' + '56dad28c6dfb944bb71cf64b90ace9a7776b22a1185fb2dc9524f6b178e2693189bf01655d7f38f043923668dc5af45bffd30a' + '00') + + witness_0_0 = ('134896c42cd95680b048845847c8054756861ffab7d4abab72f6508d67d1ec0c590287ec2161dd7884983286e1cd' + '56ce65c08a24ee0476ede92678a93b1b180c') + witness_1_0 = ('7b5d614a4610bf9196775791fcc589597ca066dcd10048e004cd4c7341bb4bb90cee4705192f3f7db524e8067a52' + '22c7f09baf29ef6b805b8327ecd1e5ab83ca') + witness_1_1 = '20f5b059b9a72298ccbefff59d9b943f7e0fc91d8a3b944a95e7b6390cc99eb5f4ac' + witness_1_2 = ('c0d9dfdf0fe3c83e9870095d67fff59a8056dad28c6dfb944bb71cf64b90ace9a7776b22a1185fb2dc9524f6b178' + 'e2693189bf01655d7f38f043923668dc5af45b') + t = Transaction.parse_hex(rawtx, True, network=network) + self.assertEqual(t.txid, txid) + self.assertEqual(t.inputs[0].script_type, 'p2tr') + self.assertEqual(t.inputs[1].script_type, 'p2tr') + self.assertEqual(t.inputs[0].witnesses[0].hex(), witness_0_0) + self.assertEqual(t.inputs[1].witnesses[0].hex(), witness_1_0) + self.assertEqual(t.inputs[1].witnesses[1].hex(), witness_1_1) + self.assertEqual(t.inputs[1].witnesses[2].hex(), witness_1_2) + + def test_transaction_p2tr_input_litecoin(self): + txid = '91030689be4cb4a940b6c3a740cede243cbe2793c5405f08b0ed34e22df12430' + network = 'litecoin' + rawtx = ('0100000000010122614318de2d668e53481173347915aa340e48d65088a88e77eb6464f7fc34610000000000fdffffff' + '011027000000000000160014daba82eb57ac200b44fd87030398906cc742233c03402121b2683cc78e2de583dd82e7e2' + 'be76eaf5905dc03af01178bb0f40a309fe1b9669fe491fb3bdcc04c5366fdfac9d2c5b7c1f764b94545ad55a696122e6' + 'd9f14d205029871633eecea9ebe3c12a622f40321135649c30c0a018fd8d2879a0b608cfac0063036f72640101187465' + '78742f706c61696e3b636861727365743d7574662d380007686f742e6c74636821c15029871633eecea9ebe3c12a622f' + '40321135649c30c0a018fd8d2879a0b608cf00000000') + + witness_0 = ('2121b2683cc78e2de583dd82e7e2be76eaf5905dc03af01178bb0f40a309fe1b9669fe491fb3bdcc04c5366fdfac9d' + '2c5b7c1f764b94545ad55a696122e6d9f1') + witness_1 = ('205029871633eecea9ebe3c12a622f40321135649c30c0a018fd8d2879a0b608cfac0063036f726401011874657874' + '2f706c61696e3b636861727365743d7574662d380007686f742e6c746368') + witness_2 = 'c15029871633eecea9ebe3c12a622f40321135649c30c0a018fd8d2879a0b608cf' + t = Transaction.parse_hex(rawtx, True, network=network) + self.assertEqual(t.txid, txid) + self.assertEqual(t.inputs[0].script_type, 'p2tr') + self.assertEqual(t.inputs[0].witnesses[0].hex(), witness_0) + self.assertEqual(t.inputs[0].witnesses[1].hex(), witness_1) + self.assertEqual(t.inputs[0].witnesses[2].hex(), witness_2) + class TestTransactionsMultisigSoroush(unittest.TestCase): # Source: Example from @@ -1537,6 +1607,24 @@ def test_transaction_save_load(self): t3 = Transaction.load(filename='tx_test.tx') self.assertEqual(t, t3) + def test_transaction_save_load_sign(self): + pk1 = HDKey('tprv8ZgxMBicQKsPen95zTdorkDGPi4jHy9xBf4TdVxrB1wTJgSKCZbHpWhmaTGoRXHj2dJRcJQhRkV22Mz3uhg9nThjGLA' + 'JKzrPuZXPmFUgQ42') + pk2 = HDKey('tprv8ZgxMBicQKsPdhv4GxyNcfNK1Wka7QEnQ2c8DNdRL5z3hzf7ufUYNW14fgArjFvLtyg5xmPrkpx6oGBo2dquPf5inH6' + 'Jg6h2D89nsQdY8Ga') + + t = Transaction(network='testnet') + t.add_input('a2c226037d73022ea35af9609c717d98785906ff8b71818cd4095a12872795e7', 1, + [pk1.public_byte, pk2.public_byte], script_type='p2sh_multisig', sigs_required=2) + t.add_output(900000, '2NEgmZU64NjiZsxPULekrFcqdS7YwvYh24r') + self.assertFalse(t.verify()) + t.save() + + t2 = Transaction.load(t.txid) + t2.sign(pk1) + t2.sign(pk2) + self.assertTrue(t2.verify()) + def test_transaction_locktime_cltv(self): # timelock = 533600 # inputs = [