def getrawtransaction(self, txid, verbose=False): """Return transaction with hash txid Raises IndexError if transaction not found. verbose - If true a dict is returned instead with additional information on the transaction. Note that if all txouts are spent and the transaction index is not enabled the transaction may not be available. """ try: r = self._call('getrawtransaction', b2lx(txid), 1 if verbose else 0) except InvalidAddressOrKeyError as ex: raise IndexError('%s.getrawtransaction(): %s (%d)' % (self.__class__.__name__, ex.error['message'], ex.error['code'])) if verbose: r['tx'] = CTransaction.deserialize(unhexlify(r['hex'])) del r['hex'] del r['txid'] del r['version'] del r['locktime'] del r['vin'] del r['vout'] r['blockhash'] = lx(r['blockhash']) if 'blockhash' in r else None else: r = CTransaction.deserialize(unhexlify(r)) return r
def test_p2wsh_signaturehash2(self): unsigned_tx = x( '0100000002e9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff80e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffff0280969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac80969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac00000000' ) value1 = coins_to_satoshi(0.16777215) scriptcode1 = CScript( x('0063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac' )) value2 = coins_to_satoshi(0.16777215) scriptcode2 = CScript( x('68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac' )) self.assertEqual( SignatureHash(scriptcode1, CTransaction.deserialize(unsigned_tx), 0, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY, value1, SIGVERSION_WITNESS_V0), x('e9071e75e25b8a1e298a72f0d2e9f4f95a0f5cdf86a533cda597eb402ed13b3a' )) self.assertEqual( SignatureHash(scriptcode2, CTransaction.deserialize(unsigned_tx), 1, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY, value2, SIGVERSION_WITNESS_V0), x('cd72f1f1a433ee9df816857fad88d8ebd97e09a75cd481583eb841c330275e54' ))
def test_tx_vsize(self): """simple test to check that tx virtual size calculation works. transaction sizes taken from Bitcoin Core's decoderawtransaction output""" tx_no_witness = CTransaction.deserialize( x('0200000001eab856b5c4de81511cedab916630cf0afa38ea4ed8e0e88c8990eda88773cd47010000006b4830450221008f9ea83b8f4a2d23b07a02f25109aa508a78b85643b0a1f1c8a08c48d32f53e6022053e7028a585c55ba53895e9a8ef8def86b1d109ec057400f4d5f152f5bf302d60121020bcf101930dd54e22344d4ef060561fe68f42426fe01f92c694bd119f308d44effffffff027ef91e82100000001976a914f1ef6b3f14c69cafd75b3a5cd2101114bb411d5088ac12533c72040000002200201f828f01c988a992ef9efb4c77a8e3607df0f97edbc3029fe95d62f6b1c436bb00000000' )) tx_no_witness_vsize = 235 self.assertEqual(tx_no_witness.get_virtual_size(), tx_no_witness_vsize) tx_with_witness = CTransaction.deserialize( x('020000000001025fdeae88276b595be42d440d638a52d3ea0e1e1c820ab305ce438452468d7a2201000000171600149f2ca9bcbfb16f8a5c4f664aa22a2c833545a2b5fefffffffc25d526160911147b11fefeb6598ae97e093590d642265f27a67e7242a2ac31000000001716001482ad37a540c47bbb740596667f472f9d96f6dfb3feffffff02848a1b000000000017a914dc5d78da1cd6b02e08f0aa7bf608b091094415968700e1f5050000000017a9144b8acc9fc4210a5ce3ff417b00b419fd4fb03f8c8702473044022042c7ca216ace58920d6114ad30798a7a0b2b64faf17803034316dd83c90048a002205e37943bc694622128494fa2d9d3d402a58d91c1661c9a3a28124ff0e457d561012103bb79122851602141d7ec63a7342bc23bc51f050808695c141958cf2c222e38ed02483045022100c6841686570b60540b1c5ef620f3159f1f359a12cf30112650e72c44864b3e7202205c565a6cf05578557232e03d1655b73dcbf4e082c6ff0602707f0c0394c86b7601210292f52933e2105dc7410445be9a9d01589e0b9bc09d7a4e1509dc8e094b9ee9e437040000' )) tx_with_witness_vsize = 257 self.assertEqual(tx_with_witness.get_virtual_size(), tx_with_witness_vsize) tx = tx_no_witness.to_mutable() for i in range(260): tx.vout.append(tx.vout[0]) self.assertEqual(tx.get_virtual_size(), 9077) tx = tx_with_witness.to_mutable() for i in range(260): tx.vout.append(tx.vout[0]) self.assertEqual(tx.get_virtual_size(), 8579)
def test_blind_unnblind_sign(self): if not secp256k1_has_zkp: warn_zkp_unavailable() return with open( os.path.dirname(__file__) + '/data/elements_txs_blinding.json', 'r') as fd: for bundle in json.load(fd): blinded_tx_raw = x(bundle['blinded']['hex']) blinded_tx = CTransaction.deserialize(blinded_tx_raw) self.assertEqual(blinded_tx.serialize(), blinded_tx_raw) self.check_serialize_deserialize(blinded_tx, blinded_tx_raw, bundle['blinded']) unblinded_tx_raw = x(bundle['unblinded']['hex']) unblinded_tx = CTransaction.deserialize(unblinded_tx_raw) self.assertEqual(unblinded_tx.serialize(), unblinded_tx_raw) self.check_serialize_deserialize(unblinded_tx, unblinded_tx_raw, bundle['unblinded']) signed_tx_raw = x(bundle['signed_hex']) signed_tx = CTransaction.deserialize(signed_tx_raw) self.assertEqual(signed_tx.serialize(), signed_tx_raw) blinding_derivation_key = CKey( lx(bundle['blinding_derivation_key'])) # ensure that str and repr works for f in (str, repr): f(unblinded_tx) f(blinded_tx) f(signed_tx) if len(blinded_tx.vout) != len(unblinded_tx.vout): assert len(blinded_tx.vout) == len(unblinded_tx.vout) + 1 assert blinded_tx.vout[-1].scriptPubKey == b'\x6a',\ "expected last output of blinded tx to be OP_RETURN" scriptPubKey = CScript([OP_RETURN]) unblinded_tx = unblinded_tx.to_mutable() unblinded_tx.vout.append( CMutableTxOut( nValue=CConfidentialValue(0), nAsset=CConfidentialAsset( unblinded_tx.vout[-1].nAsset.to_asset()), nNonce=CConfidentialNonce( scriptPubKey.derive_blinding_key( blinding_derivation_key).pub), scriptPubKey=scriptPubKey)) unblinded_tx = unblinded_tx.to_immutable() unblinded_tx_raw = unblinded_tx.serialize() self.check_blind(unblinded_tx, unblinded_tx_raw, blinded_tx, blinded_tx_raw, bundle, blinding_derivation_key) self.check_unblind(unblinded_tx, unblinded_tx_raw, blinded_tx, blinded_tx_raw, bundle, blinding_derivation_key) self.check_sign(blinded_tx, signed_tx, bundle)
def test_split_blinding_multi_sign(self): if not secp256k1_has_zkp: warn_zkp_unavailable() return with open( os.path.dirname(__file__) + '/data/elements_txs_split_blinding.json', 'r') as fd: split_blind_txdata = json.load(fd) # we need to supply asset commitments from all inputs of the final # tranaction to the blinding function, even if we are blinding a tx # template that does not contain these inputs asset_commitments = [ x(utxo['assetcommitment']) for utxo in split_blind_txdata['tx2']['vin_utxo'] ] for txlabel in ('tx1', 'tx2'): bundle = split_blind_txdata[txlabel] blinded_tx_raw = x(bundle['blinded']['hex']) blinded_tx = CTransaction.deserialize(blinded_tx_raw) self.assertEqual(blinded_tx.serialize(), blinded_tx_raw) self.check_serialize_deserialize(blinded_tx, blinded_tx_raw, bundle['blinded']) unblinded_tx_raw = x(bundle['unblinded']['hex']) unblinded_tx = CTransaction.deserialize(unblinded_tx_raw) self.assertEqual(unblinded_tx.serialize(), unblinded_tx_raw) self.check_serialize_deserialize(unblinded_tx, unblinded_tx_raw, bundle['unblinded']) if 'signed_hex' in bundle: signed_tx_raw = x(bundle['signed_hex']) signed_tx = CTransaction.deserialize(signed_tx_raw) self.assertEqual(signed_tx.serialize(), signed_tx_raw) else: signed_tx = None blinding_derivation_key = CKey( lx(bundle['blinding_derivation_key'])) self.check_blind(unblinded_tx, unblinded_tx_raw, blinded_tx, blinded_tx_raw, bundle, blinding_derivation_key, asset_commitments=asset_commitments) self.check_unblind(unblinded_tx, unblinded_tx_raw, blinded_tx, blinded_tx_raw, bundle, blinding_derivation_key) if signed_tx is not None: self.check_sign(blinded_tx, signed_tx, bundle)
def test_immutable_tx_creation_with_mutable_parts_specified(self): tx = CTransaction( vin=[CMutableTxIn(prevout=COutPoint(hash=b'a' * 32, n=0))], vout=[CMutableTxOut(nValue=1)], witness=CMutableTxWitness( [CMutableTxInWitness(CScriptWitness([CScript([0])]))])) def check_immutable_parts(tx): self.assertTrue(tx.vin[0].is_immutable()) self.assertTrue(tx.vin[0].is_immutable()) self.assertTrue(tx.vout[0].is_immutable()) self.assertTrue(tx.wit.is_immutable()) self.assertTrue(tx.wit.vtxinwit[0].is_immutable()) check_immutable_parts(tx) # Test that if we deserialize with CTransaction, # all the parts are immutable tx = CTransaction.deserialize(tx.serialize()) check_immutable_parts(tx) # Test some parts separately, because when created via # CMutableTransaction instantiation, they are created with from_* # methods, and not directly txin = CTxIn(prevout=CMutableOutPoint(hash=b'a' * 32, n=0)) self.assertTrue(txin.prevout.is_immutable()) wit = CTxWitness((CMutableTxInWitness(), )) self.assertTrue(wit.vtxinwit[0].is_immutable())
def load_test_vectors(name): with open(os.path.dirname(__file__) + '/data/' + name, 'r') as fd: for tx_decoded in json.load(fd): if isinstance(tx_decoded, str): continue # skip comment tx_bytes = x(tx_decoded['hex']) assert len(tx_bytes) == tx_decoded['size'] tx = CTransaction.deserialize(tx_bytes) yield (tx_decoded, tx, tx_bytes)
def signrawtransaction(self, tx, *args): """Sign inputs for transaction FIXME: implement options """ hextx = hexlify(tx.serialize()) r = self._call('signrawtransaction', hextx, *args) r['tx'] = CTransaction.deserialize(unhexlify(r['hex'])) del r['hex'] return r
def test_p2wpkh_signaturehash(self): unsigned_tx = x( '0100000002fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f0000000000eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac11000000' ) scriptpubkey = CScript( x('00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1')) value = coins_to_satoshi(6) address = CBech32BitcoinAddress.from_scriptPubKey(scriptpubkey) self.assertEqual( SignatureHash(address.to_redeemScript(), CTransaction.deserialize(unsigned_tx), 1, SIGHASH_ALL, value, SIGVERSION_WITNESS_V0), x('c37af31116d1b27caf68aae9e3ac82f1477929014d5b917657d0eb49478cb670' ))
def test_p2sh_p2wpkh_signaturehash(self): unsigned_tx = x( '0100000001db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a54770100000000feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac92040000' ) scriptpubkey = CScript( x('001479091972186c449eb1ded22b78e40d009bdf0089')) value = coins_to_satoshi(10) address = CCoinAddress.from_scriptPubKey(scriptpubkey) self.assertEqual( SignatureHash(address.to_redeemScript(), CTransaction.deserialize(unsigned_tx), 0, SIGHASH_ALL, value, SIGVERSION_WITNESS_V0), x('64f3b0f4dd2bb3aa1ce8566d220cc74dda9df97d8490cc81d89d735c92e59fb6' ))
def test_p2wsh_signaturehash1(self): unsigned_tx = x( '0100000002fe3dc9208094f3ffd12645477b3dc56f60ec4fa8e6f5d67c565d1c6b9216b36e0000000000ffffffff0815cf020f013ed6cf91d29f4202e8a58726b1ac6c79da47c23d1bee0a6925f80000000000ffffffff0100f2052a010000001976a914a30741f8145e5acadf23f751864167f32e0963f788ac00000000' ) value2 = coins_to_satoshi(49) scriptcode1 = CScript( x('21026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac' )) # This is the same script with everything up to the last executed OP_CODESEPARATOR, including that # OP_CODESEPARATOR removed scriptcode2 = CScript( x('210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac' )) self.assertEqual( SignatureHash(scriptcode1, CTransaction.deserialize(unsigned_tx), 1, SIGHASH_SINGLE, value2, SIGVERSION_WITNESS_V0), x('82dde6e4f1e94d02c2b7ad03d2115d691f48d064e9d52f58194a6637e4194391' )) self.assertEqual( SignatureHash(scriptcode2, CTransaction.deserialize(unsigned_tx), 1, SIGHASH_SINGLE, value2, SIGVERSION_WITNESS_V0), x('fef7bd749cce710c5c052bd796df1af0d935e59cea63736268bcbe2d2134fc47' ))
def fundrawtransaction(self, tx, include_watching=False): """Add inputs to a transaction until it has enough in value to meet its out value. include_watching - Also select inputs which are watch only Returns dict: {'tx': Resulting tx, 'fee': Fee the resulting transaction pays, 'changepos': Position of added change output, or -1, } """ hextx = hexlify(tx.serialize()) r = self._call('fundrawtransaction', hextx, include_watching) r['tx'] = CTransaction.deserialize(unhexlify(r['hex'])) del r['hex'] r['fee'] = int(r['fee'] * COIN) return r
def load_test_vectors(name): with open(os.path.dirname(__file__) + '/data/' + name, 'r') as fd: for test_case in json.load(fd): # Comments designated by single length strings if len(test_case) == 1: continue assert len(test_case) == 3 prevouts = {} for json_prevout in test_case[0]: assert len(json_prevout) == 3 n = json_prevout[1] if n == -1: n = 0xffffffff prevout = COutPoint(lx(json_prevout[0]), n) prevouts[prevout] = parse_script(json_prevout[2]) tx_data = x(test_case[1]) tx = CTransaction.deserialize(tx_data) enforceP2SH = test_case[2] yield (prevouts, tx, tx_data, enforceP2SH)
def broadcast_listen(self): pubsub = redis.pubsub() pubsub.subscribe('broadcast') for msg in pubsub.listen(): tx = msg['data'] try: tx = tx.decode() btx = bytes.fromhex(tx) tx = CTransaction.deserialize(btx) # CheckTransaction(tx) # TODO: Fix money supply? msg = msg_tx() msg.tx = tx self.send_message(msg) print('Sent tx %s' % b2lx(msg.tx.GetTxid())) if self.chaindb.tx_is_orphan(msg.tx): self.log.info("MemPool: Ignoring orphan TX %s" % (b2lx(msg.tx.GetHash()), )) else: self.chaindb.mempool_add(msg.tx) except Exception as e: print(e) traceback.print_exc()
def test_tx_valid(self): for prevouts, tx, tx_data, enforceP2SH in load_test_vectors( 'tx_valid.json'): self.assertEqual(tx_data, tx.serialize()) self.assertEqual( tx_data, CTransaction.deserialize(tx.serialize()).serialize()) try: CheckTransaction(tx) except CheckTransactionError: self.fail('tx failed CheckTransaction(): ' + str((prevouts, b2x(tx.serialize()), enforceP2SH))) continue for i in range(len(tx.vin)): flags = set() if enforceP2SH: flags.add(SCRIPT_VERIFY_P2SH) VerifyScript(tx.vin[i].scriptSig, prevouts[tx.vin[i].prevout], tx, i, flags=flags)
def test_p2sh_p2wsh_signaturehash(self): unsigned_tx = x( '010000000136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000000ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac00000000' ) value = coins_to_satoshi(9.87654321) witnessscript = CScript( x('56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae' )) self.assertEqual( SignatureHash(witnessscript, CTransaction.deserialize(unsigned_tx), 0, SIGHASH_ALL, value, SIGVERSION_WITNESS_V0), x('185c0be5263dce5b4bb50a047973c1b6272bfbd0103a89444597dc40b248ee7c' )) self.assertEqual( SignatureHash(witnessscript, CTransaction.deserialize(unsigned_tx), 0, SIGHASH_NONE, value, SIGVERSION_WITNESS_V0), x('e9733bc60ea13c95c6527066bb975a2ff29a925e80aa14c213f686cbae5d2f36' )) self.assertEqual( SignatureHash(witnessscript, CTransaction.deserialize(unsigned_tx), 0, SIGHASH_SINGLE, value, SIGVERSION_WITNESS_V0), x('1e1f1c303dc025bd664acb72e583e933fae4cff9148bf78c157d1e8f78530aea' )) self.assertEqual( SignatureHash(witnessscript, CTransaction.deserialize(unsigned_tx), 0, SIGHASH_ALL | SIGHASH_ANYONECANPAY, value, SIGVERSION_WITNESS_V0), x('2a67f03e63a6a422125878b40b82da593be8d4efaafe88ee528af6e5a9955c6e' )) self.assertEqual( SignatureHash(witnessscript, CTransaction.deserialize(unsigned_tx), 0, SIGHASH_NONE | SIGHASH_ANYONECANPAY, value, SIGVERSION_WITNESS_V0), x('781ba15f3779d5542ce8ecb5c18716733a5ee42a6f51488ec96154934e2c890a' )) self.assertEqual( SignatureHash(witnessscript, CTransaction.deserialize(unsigned_tx), 0, SIGHASH_SINGLE | SIGHASH_ANYONECANPAY, value, SIGVERSION_WITNESS_V0), x('511e8e52ed574121fc1b654970395502128263f62662e076dc6baf05c2e6a99b' ))
value_in = coins_to_satoshi(unspent[-1]['amount']) change_addr = rpc.getnewaddress() change_pubkey_hex = rpc.getaddressinfo(change_addr)['pubkey'] txouts = [ CTxOut(CoreCoinParams.MAX_MONEY, CScript([x(change_pubkey_hex), OP_CHECKSIG])), CTxOut(0, CScript([OP_RETURN, digest])) ] tx_unsigned = CTransaction(txins, txouts).to_mutable() FEE_PER_VBYTE = 0.00025 * CoreCoinParams.COIN / 1000 while True: required_fee = tx_unsigned.get_virtual_size() * FEE_PER_VBYTE tx_unsigned.vout[0].nValue = int(value_in - max(required_fee, 0.00011 * CoreCoinParams.COIN)) r = rpc.signrawtransactionwithwallet(b2x(tx_unsigned.serialize())) assert r['complete'] tx_signed = CTransaction.deserialize(x(r['hex'])) if value_in - tx_signed.vout[0].nValue >= required_fee: tx_bytes = tx_signed.serialize() tx_hex = b2x(tx_bytes) print(tx_hex) print(len(tx_bytes), 'bytes', file=sys.stderr) print(rpc.sendrawtransaction(tx_hex)) break
round(((len(scriptSig) + 41) / 1000 * args.fee_per_kb) * CoreCoinParams.COIN)) for scriptSig, redeemScript in scripts } prevouts_by_scriptPubKey = None if not args.dryrun: txid = rpc.sendmany( '', { adr: float(float(amount) / CoreCoinParams.COIN) for adr, amount in payments.items() }, 0) logging.info('Sent pre-pub tx: %s' % txid) tx = CTransaction.deserialize(x(rpc.getrawtransaction(txid))) prevouts_by_scriptPubKey = { txout.scriptPubKey: COutPoint(lx(txid), i) for i, txout in enumerate(tx.vout) } else: prevouts_by_scriptPubKey = { redeemScript.to_p2sh_scriptPubKey(): COutPoint(b'\x00' * 32, i) for i, (scriptSig, redeemScript) in enumerate(scripts) } logging.debug('Payments: %r' % payments) logging.info( 'Total cost: %s BTC' % str_money_value(sum(amount for addr, amount in payments.items())))
def alice(say, recv, send, die, rpc): """A function that implements the logic of the first participant of an asset atomic swap""" # Issue two asset that we are going to swap to Bob's 1 asset asset1_str, asset1_utxo = issue_asset(say, 1.0, rpc) asset2_str, asset2_utxo = issue_asset(say, 1.0, rpc) # We will need to pay a fee in an asset suitable for this fee_utxo = find_utxo_for_fee(say, die, rpc) say('Getting change address for fee asset') # We don't care for blinding key of change - the node will # have it, anyway, and we don't need to unblind the change. fee_change_addr, _ = get_dst_addr(say, rpc) say('Will use utxo {}:{} (amount: {}) for fee, change will go to {}'. format(fee_utxo['txid'], fee_utxo['vout'], fee_utxo['amount'], fee_change_addr)) say('Setting up communication with Bob') # Tell Bob that we are ready to communicate send('ready') # To avoid mempool synchronization problems, # in our example Alice is the one in charge of generating test blocks. # Bob gives alice txid of his transaction that he wants to be confirmed. bob_txid = recv('wait-txid-confirm') # Make sure asset issuance transactions are confirmed rpc.generatetoaddress(1, rpc.getnewaddress()) wait_confirm(say, asset1_utxo['txid'], die, rpc) wait_confirm(say, asset2_utxo['txid'], die, rpc) wait_confirm(say, bob_txid, die, rpc) # Make sure Bob is alive and ready to communicate, and send # him an offer for two assets say('Sending offer to Bob') my_offers = [ AtomicSwapOffer(asset=asset1_str, amount=coins_to_satoshi(asset1_utxo['amount'])), AtomicSwapOffer(asset=asset2_str, amount=coins_to_satoshi(asset2_utxo['amount'])) ] send('offer', my_offers) bob_offer = recv('offer') print_asset_balances(say, my_offers + [bob_offer], rpc) say('Bob responded with his offer: {}'.format(bob_offer)) # We unconditionally accept Bob's offer - his asset is # equally worthless as ours :-) # Generate an address for Bob to send his asset to. dst_addr, blinding_key = get_dst_addr(say, rpc) say('Sending my address and assetcommitments for my UTXOs to Bob') # Send Bob our address, and the assetcommitments of our UTXOs # (but not any other information about our UTXO), # so he can construct and blind a partial transaction that # will spend his own UTXO, to send his asset to our address. assetcommitments = [ asset1_utxo['assetcommitment'], asset2_utxo['assetcommitment'], fee_utxo['assetcommitment'] ] send('addr_and_assetcommitments', (str(dst_addr), assetcommitments)) partial_tx_bytes = recv('partial_blinded_tx') say('Got partial blinded tx of size {} bytes from Bob'.format( len(partial_tx_bytes))) partial_tx = CTransaction.deserialize(partial_tx_bytes) if len(partial_tx.vout) != 1: die('unexpected number of outputs in tx from Bob: expected 1, got {}'. format(len(partial_tx.vout))) result = partial_tx.vout[0].unblind_confidential_pair( blinding_key, partial_tx.wit.vtxoutwit[0].rangeproof) if result.error: die('cannot unblind output that should have been directed to us: {}'. format(result.error)) if result.asset.to_hex() != bob_offer.asset: die("asset in partial transaction from Bob {} is not the same " "as asset in Bob's initial offer ({})".format( result.asset.to_hex(), bob_offer.asset)) if result.amount != bob_offer.amount: die("amount in partial transaction from Bob {} is not the same " "as amount in Bob's initial offer ({})".format( result.amount, bob_offer.amount)) say("Asset and amount in partial transaction matches Bob's offer") bob_addr_list, bob_assetcommitment = recv('addr_list_and_assetcommitment') if len(bob_addr_list) != len(my_offers): die('unexpected address list lenth from Bob. expected {}, got {}'. format(len(my_offers), len(bob_addr_list))) say("Bob's addresses to receive my assets: {}".format(bob_addr_list)) # Convert Bob's addresses to address objects. # If Bob passes invalid address, we die with with exception. bob_addr_list = [CCoinAddress(a) for a in bob_addr_list] # Add our own inputs and outputs to Bob's partial tx # Create new mutable transaction from partial_tx tx = partial_tx.to_mutable() # We have assetcommitment for the first input, # other data is not needed for it. # initialize first elements of the arrays with empty/negative data. input_descriptors = [ BlindingInputDescriptor(asset=CAsset(), amount=-1, blinding_factor=Uint256(), asset_blinding_factor=Uint256()) ] # First output is already blinded, fill the slot with empty data output_pubkeys = [CPubKey()] # But assetcommitments array should start with Bob's asset commitment assetcommitments = [x(bob_assetcommitment)] # We will add our inputs for asset1 and asset2, and also an input # that will be used to pay the fee. # Note that the order is important: Bob blinded his transaction # with assetcommitments in the order we send them to him, # and we should add our inputs in the same order. utxos_to_add = (asset1_utxo, asset2_utxo, fee_utxo) # Add inputs for asset1 and asset2 and fee_asset and prepare input data # for blinding for utxo in utxos_to_add: # When we create CMutableTransaction and pass CTxIn, # it will be converted to CMutableTxIn. But if we append # to tx.vin or tx.vout, we need to use mutable versions # of the txin/txout classes, or else blinding or signing # will fail with error, unable to modify the instances. # COutPoint is not modified, though, so we can leave it # immutable. tx.vin.append( CMutableTxIn( prevout=COutPoint(hash=lx(utxo['txid']), n=utxo['vout']))) input_descriptors.append( BlindingInputDescriptor( asset=CAsset(lx(utxo['asset'])), amount=coins_to_satoshi(utxo['amount']), blinding_factor=Uint256(lx(utxo['amountblinder'])), asset_blinding_factor=Uint256(lx(utxo['assetblinder'])))) # If we are supplying asset blinders and assetblinders for # particular input, assetcommitment data for that input do # not need to be correct. But if we are supplying assetcommitments # at all (auxiliary_generators argument to tx.blind()), # then all the elements of that array must have correct # type (bytes) and length (33). This is a requirement of the original # Elements Core API, and python-elementstx requires this, too. assetcommitments.append(b'\x00' * 33) # Add outputs to give Bob all our assets, and fill output pubkeys # for blinding the outputs to Bob's addresses for n, offer in enumerate(my_offers): tx.vout.append( CMutableTxOut(nValue=CConfidentialValue(offer.amount), nAsset=CConfidentialAsset(CAsset(lx(offer.asset))), scriptPubKey=bob_addr_list[n].to_scriptPubKey())) output_pubkeys.append(bob_addr_list[n].blinding_pubkey) # Add change output for fee asset fee_change_amount = (coins_to_satoshi(fee_utxo['amount']) - FIXED_FEE_SATOSHI) tx.vout.append( CMutableTxOut(nValue=CConfidentialValue(fee_change_amount), nAsset=CConfidentialAsset(fee_asset), scriptPubKey=fee_change_addr.to_scriptPubKey())) output_pubkeys.append(fee_change_addr.blinding_pubkey) # Add fee output. # Note that while we use CConfidentialAsset and CConfidentialValue # to specify value and asset, they are not in fact confidential here # - they are explicit, because we pass explicit values at creation. # You can check if they are explicit or confidential # with nValue.is_explicit(). If they are explicit, you can access # the unblinded values with nValue.to_amount() and nAsset.to_asset() tx.vout.append( CMutableTxOut(nValue=CConfidentialValue(FIXED_FEE_SATOSHI), nAsset=CConfidentialAsset(fee_asset))) # Add dummy pubkey for non-blinded fee output output_pubkeys.append(CPubKey()) # Our transaction lacks txin witness instances for the added inputs, # and txout witness instances for added outputs. # If transaction already have witness data attached, transaction # serialization code will require in/out witness array length # to be equal to vin/vout array length # Therefore we need to add dummy txin and txout witnesses for each # input and output that we added to transaction # we added one input and one output per asset, and an additional # input/change-output for fee asset. for _ in utxos_to_add: tx.wit.vtxinwit.append(CMutableTxInWitness()) tx.wit.vtxoutwit.append(CMutableTxOutWitness()) # And one extra dummy txout witness for fee output tx.wit.vtxoutwit.append(CMutableTxOutWitness()) # And blind the combined transaction blind_result = tx.blind(input_descriptors=input_descriptors, output_pubkeys=output_pubkeys, auxiliary_generators=assetcommitments) # The blinding must succeed! if blind_result.error: die('blind failed: {}'.format(blind_result.error)) # And must blind exactly three outputs (two to Bob, one fee asset change) if blind_result.num_successfully_blinded != 3: die('blinded {} outputs, expected to be 3'.format( blind_result.num_successfully_blinded)) say('Successfully blinded the combined transaction, will now sign') # Sign two new asset inputs, and fee asset input for n, utxo in enumerate(utxos_to_add): # We specify input_index as 1+n because we skip first (Bob's) input sign_input(tx, 1 + n, utxo) say('Signed my inputs, sending partially-signed transaction to Bob') send('partially_signed_tx', tx.serialize()) # Note that at this point both participants can still opt out of the swap: # Alice by double-spending her inputs to the transaction, # and Bob by not signing or not broadcasting the transaction. # Bob still have tiny advantage, because # he can pretend to have 'difficulties' in broadcasting and try to exploit # Alice's patience. If Alice does not reclaim her funds in the case Bob's # behaviour deviates from expected, then Bob will have free option to # exectute the swap at the time convenient to him. # Get the swap transaction from Bob. # Bob is expected to broadcast this transaction, and could just send txid # here, but then there would be a period of uncertainty: if Alice do not # see the txid at her own node, she does not know if this is because Bob # did not actually broadcast, and is just taking his time watching asset # prices, or the transaction just takes long time to propagate. If the # protocol requires Bob to send the transaction, the timeout required for # Alice to wait can be defined much more certainly. try: signed_tx_raw = recv('final-signed-tx', timeout=ALICE_PATIENCE_LIMIT) signed_tx = CTransaction.deserialize(x(signed_tx_raw)) # Check that this transaction spends the same inputs as the transacton # previously agreed upon for n, vin in enumerate(signed_tx.vin): if vin.prevout != tx.vin[n].prevout: die('Inputs of transaction received from Bob do not match ' 'the agreed-upon transaction') # Send the transaction from our side txid = rpc.sendrawtransaction(b2x(signed_tx.serialize())) except Exception as e: # If there is any problem, including communication timeout or invalid # communication, or invalid transaction encoding, then Alice will try # to claim her funds back, so Bob won't have an option to execute the # swap at the time convenient to him. He should execute it immediately. say('Unexpected problem on receiving final signed transaction ' 'from Bob: {}'.format(e)) say('This is suspicious. I will try to reclaim my funds now') claim_funds_back(say, utxos_to_add, die, rpc) say("Claimed my funds back. Screw Bob!") sys.exit(0) # Make sure the final transaction is confirmed rpc.generatetoaddress(1, rpc.getnewaddress()) wait_confirm(say, txid, die, rpc) # Check that everything went smoothly balance = coins_to_satoshi(rpc.getbalance("*", 1, False, bob_offer.asset)) if balance != bob_offer.amount: die('something went wrong, balance of Bob\'s asset after swap ' 'should be {} satoshi, but it is {} satoshi'.format( balance, bob_offer.amount)) print_asset_balances(say, my_offers + [bob_offer], rpc) # Wait for alice to politely end the conversation send('thanks-goodbye') say('Asset atomic swap completed successfully')
def bob(say, recv, send, die, rpc): """A function that implements the logic of the second participant of an asset atomic swap""" # Issue an asset that we are going to swap asset_str, asset_utxo = issue_asset(say, 1.0, rpc) asset_amount_satoshi = coins_to_satoshi(asset_utxo['amount']) say('Setting up communication with Alice') # Wait for Alice to start communication recv('ready') # To avoid mempool synchronization problems in two-node regtest setup, # in our example Alice is the one in charge of generating test blocks. # Send txid of asset issuance to alice so she can ensure it is confirmed. send('wait-txid-confirm', asset_utxo['txid']) say('Waiting for Alice to send us an offer array') alice_offers = recv('offer') # We unconditionally accept Alice's offer - her assets are # equally worthless as our asset :-) say("Alice's offers are {}, sending my offer".format(alice_offers)) my_offer = AtomicSwapOffer(amount=asset_amount_satoshi, asset=asset_str) send('offer', my_offer) say('Waiting for Alice\'s address and assetcommitments') alice_addr_str, alice_assetcommitments = recv('addr_and_assetcommitments') print_asset_balances(say, alice_offers + [my_offer], rpc) # Convert Alice's address to address object. # If Alice passes invalid address, we die with we die with exception. alice_addr = CCoinAddress(alice_addr_str) say('Alice\'s address: {}'.format(alice_addr)) say('Alice\'s assetcommitments: {}'.format(alice_assetcommitments)) # Create asset commitments array. First goes our own asset commitment, # because our UTXO will be first. assetcommitments = [x(asset_utxo['assetcommitment'])] for ac in alice_assetcommitments: # If Alice sends non-hex data, we will die while converting. assetcommitments.append(x(ac)) # Let's create our part of the transaction. We need to create # mutable transaction, because blind() method only works for mutable. partial_tx = CMutableTransaction( vin=[ CTxIn(prevout=COutPoint(hash=lx(asset_utxo['txid']), n=asset_utxo['vout'])) ], vout=[ CTxOut(nValue=CConfidentialValue(asset_amount_satoshi), nAsset=CConfidentialAsset(CAsset(lx(asset_str))), scriptPubKey=alice_addr.to_scriptPubKey()) ]) # Blind our part of transaction, specifying assetcommitments # (Incliding those received from Alice) as auxiliary_generators. # Note that we could get the blinding factors if we retrieve # the transaction that we spend from, deserialize it, and unblind # the output that we are going to spend. # We could do everything here (besides issuing the asset and sending # the transactions) without using Elements RPC, if we get our data # from files or database, etc. But to simplify our demonstration, # we will use the values we got from RPC. # See 'spend-to-confidential-address.py' example for the code # that does the unblinding itself, and uses the unblinded values # to create a spending transaction. blind_result = partial_tx.blind( input_descriptors=[ BlindingInputDescriptor( asset=CAsset(lx(asset_utxo['asset'])), amount=asset_amount_satoshi, blinding_factor=Uint256(lx(asset_utxo['amountblinder'])), asset_blinding_factor=Uint256(lx(asset_utxo['assetblinder']))) ], output_pubkeys=[alice_addr.blinding_pubkey], auxiliary_generators=assetcommitments) # The blinding must succeed! if blind_result.error: die('blind failed: {}'.format(blind_result.error)) # And must blind exactly one output if blind_result.num_successfully_blinded != 1: die('blinded {} outputs, expected to be 1'.format( blind_result.num_successfully_blinded)) say('Successfully blinded partial transaction, sending it to Alice') send('partial_blinded_tx', partial_tx.serialize()) say("Generating addresses to receive Alice's assets") # Generate as many destination addresses as there are assets # in Alice's offer. Record blinding keys for the addresses. our_addrs = [] blinding_keys = [] for _ in alice_offers: addr, blinding_key = get_dst_addr(say, rpc) our_addrs.append(str(addr)) blinding_keys.append(blinding_key) say("Sending my addresses and assetcommitment to Alice") send('addr_list_and_assetcommitment', (our_addrs, asset_utxo['assetcommitment'])) semi_signed_tx_bytes = recv('partially_signed_tx') say('Got partially signed tx of size {} bytes from Alice'.format( len(semi_signed_tx_bytes))) semi_signed_tx = CTransaction.deserialize(semi_signed_tx_bytes) # Transaction should have 3 extra outputs - one output to Alice, # fee output, and fee asset change output if len(semi_signed_tx.vout) != len(alice_offers) + 3: die('unexpected number of outputs in tx from Alice: ' 'expected {}, got {}'.format( len(alice_offers) + 3, len(semi_signed_tx.vout))) if not semi_signed_tx.vout[-1].is_fee(): die('Last output in tx from Alice ' 'is expected to be fee output, but it is not') # Unblind outputs that should be directed to us and check # that they match the offer. We use n+1 as output index # because we skip our own output, which is at index 0. for n, offer in enumerate(alice_offers): result = semi_signed_tx.vout[n + 1].unblind_confidential_pair( blinding_keys[n], semi_signed_tx.wit.vtxoutwit[n + 1].rangeproof) if result.error: die('cannot unblind output {} that should have been ' 'directed to us: {}'.format(n + 1, result.error)) if result.asset.to_hex() != offer.asset: die("asset at position {} (vout {}) in partial transaction " "from Alice {} is not the same as asset in Alice's " "initial offer ({})".format(n, n + 1, result.asset.to_hex(), offer.asset)) if result.amount != offer.amount: die("amount at position {} (vout {}) in partial transaction " "from Alice {} is not the same as amount in Alice's " "initial offer ({})".format(n, n + 1, result.amount, offer.amount)) say("Assets and amounts in partially signed transaction " "match Alice's offer") # Signing will change the tx, so i tx = semi_signed_tx.to_mutable() # Our input is at index 0 sign_input(tx, 0, asset_utxo) # Note that at this point both participants can still opt out of the swap: # Bob by not broadcasting the transaction, and Alice by double-spending # her inputs to the transaction. Bob still have tiny advantage, because # he can pretend to have 'difficulties' in broadcasting and try to exploit # Alice's patience say('Signed the transaction from my side, ready to send') tx_hex = b2x(tx.serialize()) if bob_be_sneaky: say('Hey! I am now in control of the final transaction. ' 'I have the option to exectue the swap or abort. ') say('Why not wait a bit and watch asset prices, and execute ' 'the swap only if it is profitable') say('I will reduce my risk a bit by doing that.') # Bob takes his time and is not sending the final # transaction to Alice for some time... time.sleep(ALICE_PATIENCE_LIMIT + 2) say('OK, I am willing to execute the swap now') # Send the final transaction to Alice, so she can be sure that # we is not cheating send('final-signed-tx', tx_hex) txid = rpc.sendrawtransaction(tx_hex) say('Sent with txid {}'.format(txid)) # Wait for alice to politely end the conversation recv('thanks-goodbye') print_asset_balances(say, alice_offers + [my_offer], rpc) for i, offer in enumerate(alice_offers): balance = coins_to_satoshi(rpc.getbalance("*", 1, False, offer.asset)) if balance != offer.amount: die('something went wrong, asset{} balance after swap should be ' '{} satoshi, but it is {} satoshi'.format( i, balance, offer.amount)) say('Asset atomic swap completed successfully')
def alice(say, recv, send, die, btc_rpc, elt_rpc): """A function that implements the logic of the Elements-side participant of confidential cross-chain atomic swap""" global last_wish_func # Default chain for Alice will be Elements # To handle bitcoin-related objects, either # `with ChainParams(bitcoin_chain_name):` have to be used, or # concrete classes, like CBitcoinAddress, CBitcoinTransaction, etc. select_chain_params(elements_chain_name) # Let's create the shared blinding key blinding_key = CKey.from_secret_bytes(os.urandom(32)) # And the key for btc spend alice_btc_key = CKey.from_secret_bytes(os.urandom(32)) # And the key for the 'timeout' branch of the contract alice_elt_exit_key = CKey.from_secret_bytes(os.urandom(32)) say('Sending pubkeys to Bob') send('pubkeys', (alice_btc_key.pub, alice_elt_exit_key.pub)) say('Sending the blinding key to Bob') send('blinding_key', blinding_key.secret_bytes) (contract_pubkey_raw, bob_elt_pubkey_raw, bob_btc_exit_pub_raw) = recv('pubkeys') say("Pubkey of the key to be revealed: {}".format( b2x(contract_pubkey_raw))) say("Bob's Elements-side pubkey: {}".format(b2x(bob_elt_pubkey_raw))) contract_pubkey = CPubKey(contract_pubkey_raw) key_to_reveal_pub = CPubKey.add(contract_pubkey, blinding_key.pub) elt_contract = make_elt_cntract(key_to_reveal_pub, bob_elt_pubkey_raw, alice_elt_exit_key.pub) elt_contract_addr = P2SHCoinAddress.from_redeemScript(elt_contract) confidential_contract_addr = P2SHCoinConfidentialAddress.from_unconfidential( elt_contract_addr, blinding_key.pub) assert isinstance(confidential_contract_addr, CElementsConfidentialAddress) say("Created Elemets-side swap contract, size: {}".format( len(elt_contract))) say("Contract address:\n\tconfidential: {}\n\tunconfidential: {}".format( confidential_contract_addr, elt_contract_addr)) btc_txid = recv('btc_txid') combined_btc_spend_pubkey = CPubKey.add(contract_pubkey, alice_btc_key.pub) btc_contract = make_btc_contract(combined_btc_spend_pubkey, bob_btc_exit_pub_raw) tx_json = btc_rpc.getrawtransaction(btc_txid, 1) if tx_json['confirmations'] < 6: die('Transaction does not have enough confirmations') # We use ChainParams, and not P2WSHBitcoinAddress here, # because bitcoin_chain_name might be 'bitcoin/regtest', for example, # and then the address would need to be P2WSHBitcoinRegtestAddress. # with ChainParams we leverage the 'frontend class' magic, P2WSHCoinAddress # will give us appropriate instance. with ChainParams(bitcoin_chain_name): btc_contract_addr = P2WSHCoinAddress.from_redeemScript(btc_contract) say('Looking for this address in transaction {} in Bitcoin'.format( btc_txid)) # CTransaction subclasses do not change between mainnet/testnet/regtest, # so we can directly use CBitcoinTransaction. # That might not be true for other chains, though. # You might also want to use CTransaction within `with ChainParams(...):` btc_tx = CBitcoinTransaction.deserialize(x(tx_json['hex'])) for n, vout in enumerate(btc_tx.vout): if vout.scriptPubKey == btc_contract_addr.to_scriptPubKey(): say("Found the address at output {}".format(n)) btc_vout_n = n break else: die('Did not find contract address in transaction') if vout.nValue != coins_to_satoshi(pre_agreed_amount): die('the amount {} found at the output in the offered transaction ' 'does not match the expected amount {}'.format( satoshi_to_coins(vout.nValue), pre_agreed_amount)) say('Bitcoin amount match expected values') say('Sending {} to {}'.format(pre_agreed_amount, confidential_contract_addr)) contract_txid = elt_rpc.sendtoaddress(str(confidential_contract_addr), pre_agreed_amount) def alice_last_wish_func(): try_reclaim_elt(say, elt_rpc, contract_txid, elt_contract, alice_elt_exit_key, blinding_key, die) last_wish_func = alice_last_wish_func wait_confirm(say, 'Elements', contract_txid, die, elt_rpc, num_confirms=2) send('elt_txid', contract_txid) sr_txid = wait_spend_reveal_transaction(say, contract_txid, die, elt_rpc) say('Got txid for spend-reveal transaction from Bob ({})'.format(sr_txid)) tx_json = elt_rpc.getrawtransaction(sr_txid, 1) wait_confirm(say, 'Elements', sr_txid, die, elt_rpc, num_confirms=2) sr_tx = CTransaction.deserialize(x(tx_json['hex'])) for n, vin in enumerate(sr_tx.vin): if vin.prevout.hash == lx(contract_txid)\ and vin.scriptSig[-(len(elt_contract)):] == elt_contract: say('Transaction input {} seems to contain a script ' 'we can recover the key from'.format(n)) reveal_script_iter = iter(vin.scriptSig) break else: die('Spend-reveal transaction does not have input that spends ' 'the contract output') next(reveal_script_iter) # skip Bob's spend signature try: # 2 skipped bytes are tag and len sig_s = ecdsa.util.string_to_number(next(reveal_script_iter)[2:]) except (ValueError, StopIteration): die('Reveal script is invalid') k, r = get_known_k_r() order = ecdsa.SECP256k1.order mhash = ecdsa.util.string_to_number(hashlib.sha256(b'\x01').digest()) r_inverse = ecdsa.numbertheory.inverse_mod(r, order) for s in (-sig_s, sig_s): secret_exponent = (((s * k - mhash) % order) * r_inverse) % order recovered_key = CKey.from_secret_bytes( ecdsa.util.number_to_string(secret_exponent, order)) if recovered_key.pub == key_to_reveal_pub: break else: die('Key recovery failed. Should not happen - the sig was already ' 'verified when transaction was accepted into mempool. ' 'Must be a bug.') say('recovered key pubkey: {}'.format(b2x(recovered_key.pub))) contract_key = CKey.sub(recovered_key, blinding_key) say('recovered unblined key pubkey: {}'.format(b2x(contract_key.pub))) combined_btc_spend_key = CKey.add(contract_key, alice_btc_key) say('Successfully recovered the key. Can now spend Bitcoin from {}'.format( btc_contract_addr)) with ChainParams(bitcoin_chain_name): dst_addr = CCoinAddress(btc_rpc.getnewaddress()) btc_claim_tx = create_btc_spend_tx(dst_addr, btc_txid, btc_vout_n, btc_contract, spend_key=combined_btc_spend_key) say('Sending my Bitcoin-claim transaction') btc_claim_txid = btc_rpc.sendrawtransaction(b2x(btc_claim_tx.serialize())) wait_confirm(say, 'Bitcoin', btc_claim_txid, die, btc_rpc, num_confirms=3) say('Got my Bitcoin. Swap successful!')
def get_transaction(self, tx_hash: str) -> Transaction: tx_data = self.query_api(self.Query.GET_TX, tx_hash) tx_hex = tx_data["tx_hex"] return Transaction.deserialize(x(tx_hex))
change_addr = rpc.getnewaddress() change_pubkey_hex = rpc.getaddressinfo(change_addr)['pubkey'] change_out = CMutableTxOut( CoreCoinParams.MAX_MONEY, CScript([x(change_pubkey_hex), OP_CHECKSIG])) digest_outs = [CMutableTxOut(0, CScript([OP_RETURN, digest]))] txouts = [change_out] + digest_outs tx = CTransaction(txins, txouts).to_mutable() FEE_PER_VBYTE = 0.00025 * CoreCoinParams.COIN / 1000 while True: required_fee = tx.get_virtual_size() * FEE_PER_VBYTE tx.vout[0].nValue = int(value_in - max(required_fee, 0.00011 * CoreCoinParams.COIN)) r = rpc.signrawtransactionwithwallet(b2x(tx.serialize())) assert r['complete'] tx = CTransaction.deserialize(x(r['hex'])) if value_in - tx.vout[0].nValue >= required_fee: tx_bytes = tx.serialize() tx_hex = b2x(tx_bytes) print(tx_hex) print(len(tx_bytes), 'bytes', file=sys.stderr) print(rpc.sendrawtransaction(tx_hex)) break
def test_clone(self): tx = CTransaction.deserialize( x('020000000001025fdeae88276b595be42d440d638a52d3ea0e1e1c820ab305ce438452468d7a2201000000171600149f2ca9bcbfb16f8a5c4f664aa22a2c833545a2b5fefffffffc25d526160911147b11fefeb6598ae97e093590d642265f27a67e7242a2ac31000000001716001482ad37a540c47bbb740596667f472f9d96f6dfb3feffffff02848a1b000000000017a914dc5d78da1cd6b02e08f0aa7bf608b091094415968700e1f5050000000017a9144b8acc9fc4210a5ce3ff417b00b419fd4fb03f8c8702473044022042c7ca216ace58920d6114ad30798a7a0b2b64faf17803034316dd83c90048a002205e37943bc694622128494fa2d9d3d402a58d91c1661c9a3a28124ff0e457d561012103bb79122851602141d7ec63a7342bc23bc51f050808695c141958cf2c222e38ed02483045022100c6841686570b60540b1c5ef620f3159f1f359a12cf30112650e72c44864b3e7202205c565a6cf05578557232e03d1655b73dcbf4e082c6ff0602707f0c0394c86b7601210292f52933e2105dc7410445be9a9d01589e0b9bc09d7a4e1509dc8e094b9ee9e437040000' )) self.assertEqual(tx.serialize(), tx.clone().serialize())
def check_serialize_deserialize(self, tx, tx_bytes, tx_decoded): self.assertEqual(tx_bytes, tx.serialize()) self.assertEqual(tx_bytes, CTransaction.deserialize(tx.serialize()).serialize()) self.assertEqual(tx_bytes, tx.to_mutable().to_immutable().serialize()) self.assertEqual(tx_decoded['version'], tx.nVersion) self.assertEqual(tx_decoded['locktime'], tx.nLockTime) # we ignore withash field - we do not have ComputeWitnessHash() function # as it is only relevant for blocks, not transactions self.assertEqual(tx_decoded['hash'], b2lx(tx.GetHash())) self.assertEqual(tx_decoded['txid'], b2lx(tx.GetTxid())) for n, vout in enumerate(tx_decoded['vout']): if 'amountcommitment' in vout: self.assertEqual(x(vout['amountcommitment']), tx.vout[n].nValue.commitment) if 'assetcommitment' in vout: self.assertEqual(x(vout['assetcommitment']), tx.vout[n].nAsset.commitment) if 'asset' in vout: self.assertEqual(vout['asset'], tx.vout[n].nAsset.to_asset().to_hex()) if 'scriptPubKey' in vout: spk = vout['scriptPubKey'] self.assertEqual(x(spk['hex']), tx.vout[n].scriptPubKey) if 'pegout_type' in spk: self.assertEqual(spk['type'], 'nulldata') self.assertTrue(tx.vout[n].scriptPubKey.is_pegout()) genesis_hash, pegout_scriptpubkey = tx.vout[ n].scriptPubKey.get_pegout_data() if spk['pegout_type'] != 'nonstandard': assert spk['pegout_type'] in ('pubkeyhash', 'scripthash') addr = CCoinAddress.from_scriptPubKey( pegout_scriptpubkey) self.assertEqual(len(spk['pegout_addresses']), 1) self.assertEqual(spk['pegout_addresses'][0], str(addr)) self.assertEqual(spk['pegout_hex'], b2x(pegout_scriptpubkey)) self.assertEqual(spk['pegout_chain'], b2lx(genesis_hash)) if spk['type'] in ('pubkeyhash', 'scripthash'): self.assertEqual(len(spk['addresses']), 1) addr = CCoinAddress.from_scriptPubKey( tx.vout[n].scriptPubKey) self.assertEqual(spk['addresses'][0], str(addr)) elif spk['type'] == 'nulldata': self.assertEqual(tx.vout[n].scriptPubKey, x(spk['hex'])) else: self.assertEqual(spk['type'], 'fee') self.assertEqual(len(tx.vout[n].scriptPubKey), 0) if secp256k1_has_zkp: if tx.wit.is_null(): rpinfo = None else: rpinfo = tx.wit.vtxoutwit[n].get_rangeproof_info() if 'value-minimum' in vout: self.assertIsNotNone(rpinfo) self.assertEqual(vout['ct-exponent'], rpinfo.exp) self.assertEqual(vout['ct-bits'], rpinfo.mantissa) self.assertEqual( coins_to_satoshi(vout['value-minimum'], check_range=False), rpinfo.value_min) self.assertEqual( coins_to_satoshi(vout['value-maximum'], check_range=False), rpinfo.value_max) else: self.assertTrue(rpinfo is None or rpinfo.exp == -1) if rpinfo is None: value = tx.vout[n].nValue.to_amount() else: value = rpinfo.value_min self.assertEqual(coins_to_satoshi(vout['value']), value) else: warn_zkp_unavailable() if 'value' in vout and tx.vout[n].nValue.is_explicit(): self.assertEqual(coins_to_satoshi(vout['value']), tx.vout[n].nValue.to_amount()) for n, vin in enumerate(tx_decoded['vin']): if 'scripSig' in vin: self.assertEqual( x(vin['scriptSig']['hex'], tx.vin[n].scriptSig)) if 'txid' in vin: self.assertEqual(vin['txid'], b2lx(tx.vin[n].prevout.hash)) if 'vout' in vin: self.assertEqual(vin['vout'], tx.vin[n].prevout.n) if 'is_pegin' in vin: self.assertEqual(vin['is_pegin'], tx.vin[n].is_pegin) if vin['is_pegin'] is False: if 'scriptWitness' in vin: self.assertTrue( tx.wit.vtxinwit[n].scriptWitness.is_null()) if 'pegin_witness' in vin: self.assertTrue( tx.wit.vtxinwit[n].pegin_witness.is_null()) else: for stack_index, stack_item in enumerate( vin['scriptWitness']): self.assertTrue( stack_item, b2x(tx.wit.vtxinwit[n].scriptWitness. stack[stack_index])) for stack_index, stack_item in enumerate( vin['pegin_witness']): self.assertTrue( stack_item, b2x(tx.wit.vtxinwit[n].pegin_witness. stack[stack_index])) if 'sequence' in vin: self.assertEqual(vin['sequence'], tx.vin[n].nSequence) if 'coinbase' in vin: self.assertTrue(tx.is_coinbase()) if 'issuance' in vin: iss = vin['issuance'] self.assertEqual( iss['assetBlindingNonce'], tx.vin[n].assetIssuance.assetBlindingNonce.to_hex()) if 'asset' in iss: if iss['isreissuance']: self.assertTrue(not tx.vin[n].assetIssuance. assetBlindingNonce.is_null()) self.assertEqual( iss['assetEntropy'], tx.vin[n].assetIssuance.assetEntropy.to_hex()) asset = calculate_asset( tx.vin[n].assetIssuance.assetEntropy) else: entropy = generate_asset_entropy( tx.vin[n].prevout, tx.vin[n].assetIssuance.assetEntropy) self.assertEqual(iss['assetEntropy'], entropy.to_hex()) asset = calculate_asset(entropy) reiss_token = calculate_reissuance_token( entropy, tx.vin[n].assetIssuance.nAmount.is_commitment()) self.assertEqual(iss['token'], reiss_token.to_hex()) self.assertEqual(iss['asset'], asset.to_hex()) if 'assetamount' in iss: self.assertEqual( coins_to_satoshi(iss['assetamount']), tx.vin[n].assetIssuance.nAmount.to_amount()) elif 'assetamountcommitment' in iss: self.assertEqual( iss['assetamountcommitment'], b2x(tx.vin[n].assetIssuance.nAmount.commitment)) if 'tokenamount' in iss: self.assertEqual( coins_to_satoshi(iss['tokenamount']), tx.vin[n].assetIssuance.nInflationKeys.to_amount()) elif 'tokenamountcommitment' in iss: self.assertEqual( iss['tokenamountcommitment'], b2x(tx.vin[n].assetIssuance.nInflationKeys.commitment))