def test_mempool_accept_unconfirmed_ancestors_recovery(self): node = self.nodes[0] unspents = node.listunspent(minconf=1, addresses=[str(depositor_address)], include_unsafe=True, query_options={"minimumAmount": 0.0}) new_deposit_transaction(TEST_DATADIR + "/test_deposit_transaction.pkl", unspents) deposit_tx = load(TEST_DATADIR + "/test_deposit_transaction.pkl") decoded_tx = node.decoderawtransaction(b2x(deposit_tx.serialize())) node.sendrawtransaction(b2x(deposit_tx.serialize())) self.log.info( f"Deposit Transaction with txid {decoded_tx['txid']} accepted.") new_vault_transaction(TEST_DATADIR + "/test_vault_transaction.pkl") vault_tx = load(TEST_DATADIR + "/test_vault_transaction.pkl") decoded_tx = node.decoderawtransaction(b2x(vault_tx.serialize())) node.sendrawtransaction(b2x(vault_tx.serialize())) self.log.info( f"Vault Transaction with txid {decoded_tx['txid']} accepted.") new_p2rw_transaction(TEST_DATADIR + "/test_p2rw_transaction.pkl") p2rw_tx = load(TEST_DATADIR + "/test_p2rw_transaction.pkl") decoded_tx = node.decoderawtransaction(b2x(p2rw_tx.serialize())) node.sendrawtransaction(b2x(p2rw_tx.serialize())) self.log.info( f"P2RW Transaction with txid {decoded_tx['txid']} accepted.") new_recover_transaction(TEST_DATADIR + "/test_recover_transaction.pkl") recover_tx = load(TEST_DATADIR + "/test_recover_transaction.pkl") decoded_tx = node.decoderawtransaction(b2x(recover_tx.serialize())) node.sendrawtransaction(b2x(recover_tx.serialize())) self.log.info( f"Recover Transaction with txid {decoded_tx['txid']} accepted.")
def test_vault_theft_recover(self): node0 = self.nodes[0] # Wallet Owner node2 = self.nodes[2] # Attacker deposit_address_unspents = node0.listunspent( minconf=1, addresses=[str(depositor_address)], include_unsafe=True, query_options={"minimumAmount": 0.0}) new_deposit_transaction(TEST_DATADIR + "/test_deposit_transaction.pkl", deposit_address_unspents) deposit_tx = load(TEST_DATADIR + "/test_deposit_transaction.pkl") decoded_tx = node0.decoderawtransaction(b2x(deposit_tx.serialize())) node0.sendrawtransaction(b2x(deposit_tx.serialize())) self.log.info( f"Deposit Transaction with txid {decoded_tx['txid']} accepted.") new_vault_transaction(TEST_DATADIR + "/test_vault_transaction.pkl") vault_tx = load(TEST_DATADIR + "/test_vault_transaction.pkl") decoded_tx = node0.decoderawtransaction(b2x(vault_tx.serialize())) node0.sendrawtransaction(b2x(vault_tx.serialize())) self.log.info( f"Vault Transaction with txid {decoded_tx['txid']} accepted.") new_p2rw_transaction(TEST_DATADIR + "/test_p2rw_transaction.pkl") p2rw_tx = load(TEST_DATADIR + "/test_p2rw_transaction.pkl") fee_wallet_unspents = node0.listunspent( minconf=1, addresses=[str(fee_wallet_address)], include_unsafe=True, query_options={"minimumAmount": 0.0}) p2rw_tx = update_fee(TEST_DATADIR + "/test_p2rw_transaction.pkl", 0.0001, fee_wallet_unspents) self.log.info("P2RW transaction prepared with high fee.") node0.generate(1) self.log.info(f"Deposit and Vault Transactions mined.") new_unvault_transaction(TEST_DATADIR + "/test_unvault_transaction.pkl") unvault_tx = load(TEST_DATADIR + "/test_unvault_transaction.pkl") decoded_tx = node2.decoderawtransaction(b2x(unvault_tx.serialize())) try: node2.sendrawtransaction(b2x(unvault_tx.serialize())) except (JSONRPCException): self.log.info( f"Un-vault Transaction broadcast early by attacker, rejected since time-lock not expired." ) node2.generate(TIMELOCK - 1) self.log.info( "Time-lock expired, theft transaction broadcast again by attacker." ) node2.sendrawtransaction(b2x(unvault_tx.serialize())) node0.sendrawtransaction(b2x(p2rw_tx.serialize())) self.log.info( f"P2RW Transaction broadcast by wallet owner and replaced attacker's theft transaction." ) node0.generate(1) self.log.info( f"P2RW Transaction with txid {decoded_tx['txid']} mined.")
def find_utxo_for_fee(say, die, rpc): """Find suitable utxo to pay the fee. Retrieve thekey to spend this utxo and add it to the returned dict.""" # Find utxo to use for fee. In our simple example, only Alice pays the fee. # To be on a safe side, include only transactions # that are confirmed (1 as 'minconf' argument of listunspent) # and safe to spend (False as 'include_unsafe' # argument of listunspent) say('Searching for utxo for fee asset') utxo_list = rpc.listunspent(1, 9999999, [], False, {'asset': fee_asset.to_hex()}) utxo_list.sort(key=lambda u: u['amount']) for utxo in utxo_list: # To not deal with possibility of dust outputs, # just require fee utxo to be big enough if coins_to_satoshi(utxo['amount']) >= FIXED_FEE_SATOSHI * 2: utxo['key'] = CCoinKey(rpc.dumpprivkey(utxo['address'])) if 'assetcommitment' not in utxo: # If UTXO is not blinded, Elements daemon will not # give us assetcommitment, so we need to generate it ourselves. asset = CAsset(lx(utxo['asset'])) utxo['assetcommitment'] = b2x(asset.to_commitment()) return utxo else: die('Cannot find utxo for fee that is >= {} satoshi'.format( FIXED_FEE_SATOSHI * 2))
def check_unblind(self, unblinded_tx, unblinded_tx_raw, blinded_tx, blinded_tx_raw, bundle, blinding_derivation_key): for n, bvout in enumerate(blinded_tx.vout): uvout = unblinded_tx.vout[n] if not uvout.nValue.is_explicit(): # skip confidential vouts of partially-blinded txs continue self.assertEqual(bvout.scriptPubKey, uvout.scriptPubKey) if bvout.nAsset.is_explicit(): self.assertTrue(bvout.nValue.is_explicit()) self.assertEqual(bvout.nValue.to_amount(), uvout.nValue.to_amount()) self.assertEqual(bvout.nAsset.to_asset().data, uvout.nAsset.to_asset().data) self.assertEqual(bvout.nNonce.commitment, uvout.nNonce.commitment) else: self.assertFalse(bvout.nValue.is_explicit()) for fbk, spk_set in bundle['foreign_blinding_keys'].items(): if b2x(bvout.scriptPubKey) in spk_set: blinding_key = uvout.scriptPubKey.derive_blinding_key( CKey(lx(fbk))) break else: blinding_key = uvout.scriptPubKey.derive_blinding_key( blinding_derivation_key) unblind_result = bvout.unblind_confidential_pair( blinding_key=blinding_key, rangeproof=blinded_tx.wit.vtxoutwit[n].rangeproof) self.assertFalse(unblind_result.error) self.assertEqual(uvout.nValue.to_amount(), unblind_result.amount) self.assertEqual(uvout.nAsset.to_asset().data, unblind_result.asset.data) descr = unblind_result.get_descriptor() self.assertIsInstance(descr, BlindingInputDescriptor) self.assertEqual(descr.amount, unblind_result.amount) self.assertEqual(descr.asset, unblind_result.asset) self.assertEqual(descr.blinding_factor, unblind_result.blinding_factor) self.assertEqual(descr.asset_blinding_factor, unblind_result.asset_blinding_factor) ub_info = bundle['unblinded_vout_info'][n] if len(ub_info): self.assertEqual(coins_to_satoshi(ub_info['amount']), unblind_result.amount) self.assertEqual(ub_info['asset'], unblind_result.asset.to_hex()) self.assertEqual(ub_info['blinding_factor'], unblind_result.blinding_factor.to_hex()) self.assertEqual( ub_info['asset_blinding_factor'], unblind_result.asset_blinding_factor.to_hex())
def try_reclaim_btc(say, btc_rpc, txid, btc_contract, key, die): ensure_rpc_connected(say, btc_rpc) # we won't return from this function, so we can just # set the chain with select_chain_params select_chain_params(bitcoin_chain_name) def custom_die(msg): say(msg) die('Failed to reclaim my Bitcoin') from_addr = P2WSHCoinAddress.from_redeemScript(btc_contract) say('Will try to reclaim my bitcoin from {}'.format(from_addr)) tx_json = btc_rpc.getrawtransaction(txid, 1) confirmations = int(tx_json['confirmations']) while confirmations < bitcoin_contract_timeout: tx_json = btc_rpc.getrawtransaction(txid, 1) confirmations = int(tx_json['confirmations']) for vout in tx_json['vout']: if 'scriptPubKey' in vout: if str(from_addr) in vout['scriptPubKey']['addresses']: vout_n = int(vout['n']) say('({} at UTXO {}:{})'.format(vout['value'], txid, vout_n)) break else: custom_die( 'Cannot find {} in outputs of tx {} - this must be a bug.'.format( from_addr, txid)) # We should not use CBitcoinAddress directly here, because we might be # in regtest or testnet, and it is treated as different chain. # CBitcoinAddress will not recognize regtest address, you would need # to use CBitcoinTestnetAddress/CBitcoinRegtestAddress. # CCoinAddress is the correct abstraction to use. dst_addr = CCoinAddress(btc_rpc.getnewaddress()) say('Will reclaim my Bitcoin to {}'.format(dst_addr)) reclaim_tx = create_btc_spend_tx(dst_addr, txid, vout_n, btc_contract, spend_key=key, branch_condition=False) say('Sending my Bitcoin-reclaim transaction') new_txid = btc_rpc.sendrawtransaction(b2x(reclaim_tx.serialize())) wait_confirm(say, 'Bitcoin', new_txid, custom_die, btc_rpc, num_confirms=3) say('Reclaimed my Bitcoin. Swap failed.')
def try_reclaim_elt(say, elt_rpc, txid, elt_contract, key, blinding_key, die): ensure_rpc_connected(say, elt_rpc) # we won't return from this function, so we can just # set the chain with select_chain_params select_chain_params(elements_chain_name) from_addr = P2SHCoinAddress.from_redeemScript(elt_contract) say('Will try to reclaim my Elements bitcoin asset from {}'.format( from_addr)) tx_json = elt_rpc.getrawtransaction(txid, 1) confirmations = int(tx_json['confirmations']) while confirmations < elements_contract_timeout: tx_json = elt_rpc.getrawtransaction(txid, 1) confirmations = int(tx_json['confirmations']) commit_tx = CElementsTransaction.deserialize(x(tx_json['hex'])) vout_n, unblind_result = find_and_unblind_vout(say, commit_tx, from_addr, blinding_key, die) dst_addr = CElementsAddress(elt_rpc.getnewaddress()) say('Will reclaim my Elements asset to {}'.format(dst_addr)) reclaim_tx = create_elt_spend_tx( dst_addr, txid, vout_n, elt_contract, die, spend_key=key, blinding_factor=unblind_result.blinding_factor, asset_blinding_factor=unblind_result.asset_blinding_factor, branch_condition=False) say('Sending my Elements-reclaim transaction') new_txid = elt_rpc.sendrawtransaction(b2x(reclaim_tx.serialize())) def custom_die(msg): say(msg) die('Failed to reclaim by Elemets asset') wait_confirm(say, 'Elements', new_txid, custom_die, elt_rpc, num_confirms=3) say('Reclaimed my Elements asset. Swap failed.')
def test_mempool_accept_unconfirmed_ancestors_unvault(self): node = self.nodes[0] unspents = node.listunspent(minconf=1, addresses=[str(depositor_address)], include_unsafe=True, query_options={"minimumAmount": 0.0}) new_deposit_transaction(TEST_DATADIR + "/test_deposit_transaction.pkl", unspents) deposit_tx = load(TEST_DATADIR + "/test_deposit_transaction.pkl") decoded_tx = node.decoderawtransaction(b2x(deposit_tx.serialize())) node.sendrawtransaction(b2x(deposit_tx.serialize())) self.log.info( f"Deposit Transaction with txid {decoded_tx['txid']} accepted.") new_vault_transaction(TEST_DATADIR + "/test_vault_transaction.pkl") vault_tx = load(TEST_DATADIR + "/test_vault_transaction.pkl") decoded_tx = node.decoderawtransaction(b2x(vault_tx.serialize())) node.sendrawtransaction(b2x(vault_tx.serialize())) self.log.info( f"Vault Transaction with txid {decoded_tx['txid']} accepted.") new_unvault_transaction(TEST_DATADIR + "/test_unvault_transaction.pkl") unvault_tx = load(TEST_DATADIR + "/test_unvault_transaction.pkl") decoded_tx = node.decoderawtransaction(b2x(unvault_tx.serialize())) try: node.sendrawtransaction(b2x(unvault_tx.serialize())) except (JSONRPCException): self.log.info( f"Un-vault Transaction with txid {decoded_tx['txid']} rejected (non-BIP68-final)." )
def test(self) -> None: # Test Vectors for RFC 6979 ECDSA, secp256k1, SHA-256 # (private key, message, expected k, expected signature) test_vectors = [ (0x1, "Satoshi Nakamoto", 0x8F8A276C19F4149656B280621E358CCE24F5F52542772691EE69063B74F15D15, "934b1ea10a4b3c1757e2b0c017d0b6143ce3c9a7e6a4a49860d7a6ab210ee3d82442ce9d2b916064108014783e923ec36b49743e2ffa1c4496f01a512aafd9e5"), (0x1, "All those moments will be lost in time, like tears in rain. Time to die...", 0x38AA22D72376B4DBC472E06C3BA403EE0A394DA63FC58D88686C611ABA98D6B3, "8600dbd41e348fe5c9465ab92d23e3db8b98b873beecd930736488696438cb6b547fe64427496db33bf66019dacbf0039c04199abb0122918601db38a72cfc21"), (0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140, "Satoshi Nakamoto", 0x33A19B60E25FB6F4435AF53A3D42D493644827367E6453928554F43E49AA6F90, "fd567d121db66e382991534ada77a6bd3106f0a1098c231e47993447cd6af2d06b39cd0eb1bc8603e159ef5c20a5c8ad685a45b06ce9bebed3f153d10d93bed5"), (0xf8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181, "Alan Turing", 0x525A82B70E67874398067543FD84C83D30C175FDC45FDEEE082FE13B1D7CFDF1, "7063ae83e7f62bbb171798131b4a0564b956930092b33b07b395615d9ec7e15c58dfcc1e00a35e1572f366ffe34ba0fc47db1e7189759b9fb233c5b05ab388ea"), (0xe91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2, "There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!", 0x1F4B84C23A86A221D233F2521BE018D9318639D5B8BBD6374A8A59232D16AD3D, "b552edd27580141f3b2a5463048cb7cd3e047b97c9f98076c32dbdf85a68718b279fa72dd19bfae05577e06c7c0c1900c371fcd5893f7e1d56a37d30174671f6") ] for vector in test_vectors: secret = CBitcoinKey.from_secret_bytes(x('{:064x}'.format(vector[0]))) encoded_sig = secret.sign(hashlib.sha256(vector[1].encode('utf8')).digest()) assert(encoded_sig[0] == 0x30) assert(encoded_sig[1] == len(encoded_sig)-2) assert(encoded_sig[2] == 0x02) rlen = encoded_sig[3] rpos = 4 assert(rlen in (32, 33)) if rlen == 33: assert(encoded_sig[rpos] == 0) rpos += 1 rlen -= 1 rval = encoded_sig[rpos:rpos+rlen] spos = rpos+rlen assert(encoded_sig[spos] == 0x02) spos += 1 slen = encoded_sig[spos] assert(slen in (32, 33)) spos += 1 if slen == 33: assert(encoded_sig[spos] == 0) spos += 1 slen -= 1 sval = encoded_sig[spos:spos+slen] sig = b2x(rval + sval) assert(str(sig) == vector[3])
def __new__(cls, wallet: WatchOnlyWallet): wallet_data = { "wallet_xpub": str(wallet.xpub), "master_fingerprint": b2x(wallet.master_fingerprint), "base_keypath": str(wallet.base_keypath), "current_block": wallet.current_block.to_json(), "external_addresses": [ address.to_json() for address in wallet.external_addresses.values() ], "change_addresses": [ address.to_json() for address in wallet.change_addresses.values() ], } with open(cls.PATH, "w") as wallet_file: json.dump(wallet_data, wallet_file, indent=4)
def test_unvault_tx_flow(self): node = self.nodes[0] unspents = node.listunspent(minconf=1, addresses=[str(depositor_address)], include_unsafe=True, query_options={"minimumAmount": 0.0}) new_deposit_transaction(TEST_DATADIR + "/test_deposit_transaction.pkl", unspents) deposit_tx = load(TEST_DATADIR + "/test_deposit_transaction.pkl") decoded_tx = node.decoderawtransaction(b2x(deposit_tx.serialize())) node.sendrawtransaction(b2x(deposit_tx.serialize())) self.log.info( f"Deposit Transaction with txid {decoded_tx['txid']} accepted.") node.generate(1) self.log.info( f"Deposit Transaction with txid {decoded_tx['txid']} mined.") new_vault_transaction(TEST_DATADIR + "/test_vault_transaction.pkl") vault_tx = load(TEST_DATADIR + "/test_vault_transaction.pkl") decoded_tx = node.decoderawtransaction(b2x(vault_tx.serialize())) node.sendrawtransaction(b2x(vault_tx.serialize())) self.log.info( f"Vault Transaction with txid {decoded_tx['txid']} accepted.") node.generate(1) self.log.info( f"Vault Transaction with txid {decoded_tx['txid']} mined.") new_unvault_transaction(TEST_DATADIR + "/test_unvault_transaction.pkl") unvault_tx = load(TEST_DATADIR + "/test_unvault_transaction.pkl") decoded_tx = node.decoderawtransaction(b2x(unvault_tx.serialize())) try: # Expect transaction rejection (non-BIP68-final) node.sendrawtransaction(b2x(unvault_tx.serialize())) except (JSONRPCException): node.generate(TIMELOCK - 1) # Wait for time-lock to expire node.sendrawtransaction(b2x(unvault_tx.serialize())) self.log.info( f"Un-vault Transaction with txid {decoded_tx['txid']} accepted.") node.generate(1) self.log.info( f"Un-vault Transaction with txid {decoded_tx['txid']} mined.")
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)
for n in path: print("child number: 0x{:08x}".format(n)) xkey = xkey.derive(n) if isinstance(xkey, CBitcoinExtKey): print("xpriv:", xkey) # Note: # if xkey is CBitcoinExtKey, xkey.priv is CBitcoinKey # CBitcoinKey is in WIF format, and compressed # len(bytes(xkey.privkey)) == 33 # if xkey is CExtKey, xkey.priv is CKey # CKey is always 32 bytes # # Standalone CBitcoinKey key can be uncompressed, # and be of 32 bytes length, but this is not the case # with xpriv encapsulated in CBitcoinExtKey - it is # always compressed there. # # you can always use xkey.priv.secret_bytes # to get raw 32-byte secret data from both CBitcoinKey and CKey # print("priv WIF:", xkey.priv) print("raw priv:", b2x(xkey.priv.secret_bytes)) print("xpub: ", xkey.neuter()) print("pub:", b2x(xkey.pub)) else: assert isinstance(xkey, CBitcoinExtPubKey) print("xpub:", xkey) print("pub:", b2x(xkey.pub))
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))
def T(base58_privkey: str, expected_hex_pubkey: str, expected_is_compressed_value: bool) -> None: key = CBitcoinKey(base58_privkey) self.assertEqual(b2x(key.pub), expected_hex_pubkey) self.assertEqual(key.is_compressed(), expected_is_compressed_value)
def T(str_addr: str, expected_scriptPubKey_hexbytes: str) -> None: addr = CBitcoinAddress(str_addr) actual_scriptPubKey = addr.to_scriptPubKey() self.assertEqual(b2x(actual_scriptPubKey), expected_scriptPubKey_hexbytes)
def T(base58_privkey, expected_hex_pubkey, expected_is_compressed_value): key = CBitcoinSecret(base58_privkey) self.assertEqual(b2x(key.pub), expected_hex_pubkey) self.assertEqual(key.is_compressed, expected_is_compressed_value)
) from bitcointx.core.scripteval import VerifyScript from bitcointx.wallet import CBitcoinAddress, CBitcoinKey select_chain_params('bitcoin') COIN = CoreCoinParams.COIN # Create the (in)famous correct brainwallet secret key. h = hashlib.sha256(b'correct horse battery staple').digest() seckey = CBitcoinKey.from_secret_bytes(h) # Create a redeemScript. Similar to a scriptPubKey the redeemScript must be # satisfied for the funds to be spent. txin_redeemScript = CScript([seckey.pub, OP_CHECKSIG]) print(b2x(txin_redeemScript)) # Create the magic P2SH scriptPubKey format from that redeemScript. You should # look at the CScript.to_p2sh_scriptPubKey() function in bitcointx.core.script # to understand what's happening, as well as read BIP16: # https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki txin_scriptPubKey = txin_redeemScript.to_p2sh_scriptPubKey() # Convert the P2SH scriptPubKey to a base58 Bitcoin address and print it. # You'll need to send some funds to it to create a txout to spend. txin_p2sh_address = CBitcoinAddress.from_scriptPubKey(txin_scriptPubKey) print('Pay to:', str(txin_p2sh_address)) # Same as the txid:vout the createrawtransaction RPC call requires # # lx() takes *little-endian* hex and converts it to bytes; in Bitcoin
def test_RBF(self): node = self.nodes[0] deposit_address_unspents = node.listunspent( minconf=1, addresses=[str(depositor_address)], include_unsafe=True, query_options={"minimumAmount": 0.0}) new_deposit_transaction(TEST_DATADIR + "/test_deposit_transaction.pkl", deposit_address_unspents) deposit_tx = load(TEST_DATADIR + "/test_deposit_transaction.pkl") decoded_tx = node.decoderawtransaction(b2x(deposit_tx.serialize())) node.sendrawtransaction(b2x(deposit_tx.serialize())) self.log.info( f"Deposit Transaction with txid {decoded_tx['txid']} accepted.") new_vault_transaction(TEST_DATADIR + "/test_vault_transaction.pkl") vault_tx = load(TEST_DATADIR + "/test_vault_transaction.pkl") decoded_tx = node.decoderawtransaction(b2x(vault_tx.serialize())) node.sendrawtransaction(b2x(vault_tx.serialize())) self.log.info( f"Vault Transaction with txid {decoded_tx['txid']} accepted.") node.generate(TIMELOCK) self.log.info(f"Deposit and Vault Transactions mined.") self.sync_all( ) # Synce node 0 and 1 after mining deposit and vault transactions. disconnect_nodes( self.nodes[0], 1 ) # Now split the network to test transaction types on different nodes. # Use node 0 to test update fee for unvault transaction new_unvault_transaction(TEST_DATADIR + "/test_unvault_transaction.pkl") unvault_tx = load(TEST_DATADIR + "/test_unvault_transaction.pkl") decoded_tx = node.decoderawtransaction(b2x(unvault_tx.serialize())) node.sendrawtransaction(b2x(unvault_tx.serialize())) self.log.info( f"Un-vault Transaction with txid {decoded_tx['txid']} accepted by node 0." ) fee_wallet_unspents = node.listunspent( minconf=1, addresses=[str(fee_wallet_address)], include_unsafe=True, query_options={"minimumAmount": 0.0}) unvault_tx = update_fee(TEST_DATADIR + "/test_unvault_transaction.pkl", 0.0001, fee_wallet_unspents) decoded_tx = node.decoderawtransaction(b2x(unvault_tx.serialize())) node.sendrawtransaction(b2x(unvault_tx.serialize())) self.log.info( f"Updated Un-vault Transaction with txid {decoded_tx['txid']} accepted by node 0." ) node.generate(1) self.log.info( f"Un-vault Transaction with txid {decoded_tx['txid']} mined by node 0." ) # Use node 1 to test update fee for pr2w transaction node = self.nodes[1] new_p2rw_transaction(TEST_DATADIR + "/test_p2rw_transaction.pkl") p2rw_tx = load(TEST_DATADIR + "/test_p2rw_transaction.pkl") decoded_tx = node.decoderawtransaction(b2x(p2rw_tx.serialize())) node.sendrawtransaction(b2x(p2rw_tx.serialize())) self.log.info( f"P2RW Transaction with txid {decoded_tx['txid']} accepted by node 1." ) fee_wallet_unspents = node.listunspent( minconf=1, addresses=[str(fee_wallet_address)], include_unsafe=True, query_options={"minimumAmount": 0.0}) p2rw_tx = update_fee(TEST_DATADIR + "/test_p2rw_transaction.pkl", 0.0001, fee_wallet_unspents) decoded_tx = node.decoderawtransaction(b2x(p2rw_tx.serialize())) node.sendrawtransaction(b2x(p2rw_tx.serialize())) self.log.info( f"Updated P2RW Transaction with txid {decoded_tx['txid']} accepted by node 1." ) node.generate(1) self.log.info( f"P2RW Transaction with txid {decoded_tx['txid']} mined by node 1." )
def T(redeemScript_ops: List[Union[int, bytes]], expected_hex_bytes: str) -> None: redeemScript = CScript(redeemScript_ops) actual_script = redeemScript.to_p2sh_scriptPubKey() self.assertEqual(b2x(actual_script), expected_hex_bytes)
if not sign_result.is_final: print(f'{sign_result.num_inputs_ready} inputs is ready ' f'to be finalized\n') else: assert sign_result.num_inputs_ready == 0 if not sign_result.is_final and sign_result.num_inputs_signed > 0: for index, info in enumerate(sign_result.inputs_info): if info: print(f"Input {index}: added {info.num_new_sigs} sigs, ", end='') print(f"input is now final" if info.is_final else f"{info.num_sigs_missing} is still missing") else: print(f"Input {index}: skipped, cannot be processed") print() if args.finalize: if not sign_result.is_final: print(f'Failed to finalize transaction') sys.exit(-1) print("Signed network transaction:\n") print(b2x(psbt.extract_transaction().serialize())) elif sign_result.num_inputs_signed == 0: print("Could not sign any inputs") sys.exit(-1) else: print("PSBT with added signatures:\n") print(psbt.to_base64())
def T(base58_xpubkey, expected_hex_xpubkey): key = CBitcoinExtPubKey(base58_xpubkey) self.assertEqual(b2x(key), expected_hex_xpubkey) key2 = CBitcoinExtPubKey.from_bytes(x(expected_hex_xpubkey)) self.assertEqual(b2x(key), b2x(key2))
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 T(redeemScript, expected_hex_bytes): redeemScript = CScript(redeemScript) actual_script = redeemScript.to_p2sh_scriptPubKey() self.assertEqual(b2x(actual_script), expected_hex_bytes)
def T(base58_xprivkey, expected_hex_xprivkey): key = CBitcoinExtKey(base58_xprivkey) self.assertEqual(b2x(key), expected_hex_xprivkey)
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 T(str_addr, expected_scriptPubKey_hexbytes): addr = CBitcoinAddress(str_addr) actual_scriptPubKey = addr.to_redeemScript() self.assertEqual(b2x(actual_scriptPubKey), expected_scriptPubKey_hexbytes)
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 T(base58_xprivkey: str, expected_hex_xprivkey: str) -> None: key = CBitcoinExtKey(base58_xprivkey) self.assertEqual(b2x(key), expected_hex_xprivkey) key2 = CBitcoinExtKey.from_bytes(x(expected_hex_xprivkey)) self.assertEqual(b2x(key), b2x(key2))
txin_scriptPubKey = \ P2PKHBitcoinAddress.from_pubkey(seckey.pub).to_scriptPubKey() # Create the txout. This time we create the scriptPubKey from a Bitcoin # address. txout = CMutableTxOut( 0.001 * COIN, CBitcoinAddress('1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8').to_scriptPubKey()) # Create the unsigned transaction. tx = CMutableTransaction([txin], [txout]) # Calculate the signature hash for that transaction. sighash = SignatureHash(txin_scriptPubKey, tx, 0, SIGHASH_ALL) # Now sign it. We have to append the type of signature we want to the end, in # this case the usual SIGHASH_ALL. sig = seckey.sign(sighash) + bytes([SIGHASH_ALL]) # Set the scriptSig of our transaction input appropriately. txin.scriptSig = CScript([sig, seckey.pub]) # Verify the signature worked. This calls EvalScript() and actually executes # the opcodes in the scripts to see if everything worked out. If it doesn't an # exception will be raised. VerifyScript(txin.scriptSig, txin_scriptPubKey, tx, 0) # Done! Print the transaction to standard output with the bytes-to-hex # function. print(b2x(tx.serialize()))
def claim_funds_back(say, utxos, die, rpc): """Try to claim our funds by sending our UTXO to our own addresses""" # The transaction-building code here does not introduce anything new # compared to the code in participant functions, so it will not be # commented too much. input_descriptors = [] # It is better to prepare the claw-back transaction beforehand, to avoid # the possibility of unexpected problems arising at the critical time when # we need to send claw-back tx ASAP, but that would clutter the earlier # part of the example with details that are not very relevant there. tx = CMutableTransaction() for utxo in utxos: tx.vin.append( CTxIn(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'])))) asset_amounts = {} # If some assets are the same, we want them to be sent to one address for idesc in input_descriptors: if idesc.asset == fee_asset: amount = idesc.amount - FIXED_FEE_SATOSHI assert amount >= FIXED_FEE_SATOSHI # enforced at find_utxo_for_fee else: amount = idesc.amount asset_amounts[idesc.asset] = amount output_pubkeys = [] for asset, amount in asset_amounts.items(): dst_addr, _ = get_dst_addr(None, rpc) tx.vout.append( CTxOut(nValue=CConfidentialValue(amount), nAsset=CConfidentialAsset(asset), scriptPubKey=dst_addr.to_scriptPubKey())) output_pubkeys.append(dst_addr.blinding_pubkey) # Add the explicit fee output tx.vout.append( CTxOut(nValue=CConfidentialValue(FIXED_FEE_SATOSHI), nAsset=CConfidentialAsset(fee_asset))) # Add dummy pubkey for non-blinded fee output output_pubkeys.append(CPubKey()) # We used immutable objects for transaction components like CTxIn, # just for our convenience. Convert them all to mutable. tx = tx.to_immutable().to_mutable() # And blind the combined transaction blind_result = tx.blind(input_descriptors=input_descriptors, output_pubkeys=output_pubkeys) assert (not blind_result.error and blind_result.num_successfully_blinded == len(utxos)) for n, utxo in enumerate(utxos): sign_input(tx, n, utxo) # It is possible that Bob has actually sent the swap transaction. # We will get an error if our node has received this transaction. # In real application, we might handle this case, too, but # here we will just ignore it. txid = rpc.sendrawtransaction(b2x(tx.serialize())) rpc.generatetoaddress(1, rpc.getnewaddress()) wait_confirm(say, txid, die, rpc)