def test_split_blinding_multi_sign(self) -> None: 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 = CElementsTransaction.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 = CElementsTransaction.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: Optional[CElementsTransaction] if 'signed_hex' in bundle: signed_tx_raw = x(bundle['signed_hex']) signed_tx = CElementsTransaction.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 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 load_test_vectors( name: str ) -> Iterator[Tuple[Dict[str, Any], CElementsTransaction, bytes]]: 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 = CElementsTransaction.deserialize(tx_bytes) yield (tx_decoded, tx, tx_bytes)
def test_immutable_tx_creation_with_mutable_parts_specified(self) -> None: tx = CElementsTransaction( vin=[CMutableTxIn(prevout=COutPoint(hash=b'a' * 32, n=0))], vout=[CMutableTxOut()], witness=CMutableTxWitness( [CMutableTxInWitness(CScriptWitness([CScript([0])]))], [CMutableTxOutWitness()])) self.assertIsInstance(tx, CElementsTransaction) def check_immutable_parts(tx: CElementsTransaction) -> None: self.assertTrue(not tx.vin[0].is_mutable()) self.assertTrue(not tx.vin[0].prevout.is_mutable()) self.assertTrue(not tx.vout[0].is_mutable()) self.assertTrue(not tx.wit.is_mutable()) self.assertTrue(not tx.wit.vtxinwit[0].is_mutable()) self.assertTrue(not tx.wit.vtxoutwit[0].is_mutable()) check_immutable_parts(tx) # Test that if we deserialize with CTransaction, # all the parts are immutable tx = CElementsTransaction.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(not txin.prevout.is_mutable()) wit = CElementsTxWitness((CElementsMutableTxInWitness(), ), (CElementsMutableTxOutWitness(), )) self.assertTrue(not wit.vtxinwit[0].is_mutable()) self.assertTrue(not wit.vtxoutwit[0].is_mutable())
.format(sys.argv[0])) sys.exit(-1) # Switch the chain parameters to Elements select_chain_params('elements/liquidv1') # Read in and decode the blinded transaction. # expected to be hex-encoded as one line. f = sys.argv[1] #with open(sys.argv[1]) as f: # We could use CTransaction here, but if we want to # use mypy to do static checking, we need to use the elements-specific # classes. mypy does cannot know about dynamic class dispatch. #tx = CElementsTransaction.deserialize(x(f.readline().rstrip())) [16/1870] tx = CElementsTransaction.deserialize(x(f)) # Read in the blinding key, expected to be in WIF format. f = sys.argv[2] #with open(sys.argv[2]) as f: #bkey = CCoinKey.from_secret_bytes(x(f.readline().rstrip())) bkey = CCoinKey.from_secret_bytes(x(f)) # Iterate through transaction ouptputs, and unblind what we can. print("") for n, vout in enumerate(tx.vout): # Note that nValue of vout in Elements is not a simple int, # but CConfidentialValue, which can either be explicit, and can be
if len(sys.argv) != 5: sys.stderr.write( "usage: {} <raw-hex-tx-file> <spending-key-wif-file> <unblinding-key-hex-file> <destination-address>\n" .format(sys.argv[0])) sys.exit(-1) # Switch the chain parameters to Elements select_chain_params('elements') # Read in and decode the blinded transaction. # expected to be hex-encoded as one line. with open(sys.argv[1]) as f: # We could use CTransaction here, but if we want to # use mypy to do static checking, we need to use the elements-specific # classes. mypy does cannot know about dynamic class dispatch. input_tx = CElementsTransaction.deserialize(x(f.readline().rstrip())) # Read in the key, expected to be in WIF format. with open(sys.argv[2]) as f: key = CCoinKey(f.readline().rstrip()) # Read in the unblinding key, expected to be in HEX format. with open(sys.argv[3]) as f: bkey = CCoinKey.from_secret_bytes(x(f.readline().rstrip())) dst_addr = CElementsAddress(sys.argv[4]) # Construct P2SH_P2WPKH address from the loaded key spk = CScript([0, Hash160(key.pub)]).to_p2sh_scriptPubKey() src_addr = P2SHCoinAddress.from_scriptPubKey(spk)
def test_blind_unnblind_sign(self) -> None: 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 = CElementsTransaction.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 = CElementsTransaction.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 = CElementsTransaction.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 (lambda v: str(v), 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 = CElementsScript([OP_RETURN]) unblinded_tx = unblinded_tx.to_mutable() unblinded_tx.vout.append( CElementsMutableTxOut( 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 check_blind( self, unblinded_tx: CElementsTransaction, unblinded_tx_raw: bytes, blinded_tx: CElementsTransaction, blinded_tx_raw: bytes, bundle: Dict[str, Any], blinding_derivation_key: CKey, asset_commitments: Sequence[bytes] = () ) -> None: input_descriptors = [] for utxo in bundle['vin_utxo']: amount = -1 if utxo['amount'] == -1 else coins_to_satoshi( utxo['amount']) input_descriptors.append( BlindingInputDescriptor( amount=amount, asset=CAsset(lx(utxo['asset'])), blinding_factor=Uint256(lx(utxo['blinder'])), asset_blinding_factor=Uint256(lx(utxo['assetblinder'])))) num_to_blind = 0 output_pubkeys = [] for vout in unblinded_tx.vout: if not vout.nNonce.is_null() and vout.nValue.is_explicit(): output_pubkeys.append(CPubKey(vout.nNonce.commitment)) num_to_blind += 1 else: output_pubkeys.append(CPubKey()) tx_to_blind = unblinded_tx.to_mutable() blind_issuance_asset_keys: List[Optional[CKey]] = [] blind_issuance_token_keys: List[Optional[CKey]] = [] for vin in blinded_tx.vin: issuance = vin.assetIssuance if not issuance.is_null(): issuance_blinding_script = CElementsScript( [OP_RETURN, vin.prevout.hash, vin.prevout.n]) blind_issuance_key = issuance_blinding_script.derive_blinding_key( blinding_derivation_key) if issuance.nAmount.is_commitment(): blind_issuance_asset_keys.append(blind_issuance_key) num_to_blind += 1 else: blind_issuance_asset_keys.append(None) if issuance.nInflationKeys.is_commitment(): blind_issuance_token_keys.append(blind_issuance_key) num_to_blind += 1 else: blind_issuance_token_keys.append(None) else: blind_issuance_asset_keys.append(None) blind_issuance_token_keys.append(None) # Deterministic random was used when generating test transactions, # to have reproducible results. We need to set the random seed # to the same value that was used when test data was generated. # (see note below on that supplying _rand_func parameter to blind() # is intended only for testing code, not for production) random.seed(bundle['rand_seed']) def rand_func(n: int) -> bytes: return bytes([random.randint(0, 255) for _ in range(n)]) # Auxiliary generators will be be non-empty only for the case # when we are blinding different transaction templates that is # then combined into one common transaction, that is done in # test_split_blinding_multi_sign(). # In this case, you need to supply the asset commitments for # all of the inputs of the final transaction, even if currently # blinded transaction template does not contain these inputs. blind_result = tx_to_blind.blind( input_descriptors=input_descriptors, output_pubkeys=output_pubkeys, blind_issuance_asset_keys=blind_issuance_asset_keys, blind_issuance_token_keys=blind_issuance_token_keys, auxiliary_generators=asset_commitments, # Test data was generated with these params custom_ct_exponent=0, custom_ct_bits=32, # IMPORTANT NOTE: # Specifying custom _rand_func is only required for testing. # Here we use it to supply deterministically generated # pseudo-random bytes, so that blinding results will match the test # data that was generated using deterministically generated random # bytes, with seed values that are saved in 'rand_seed' fields of # test data bunldes. # # In normal code you do should NOT specify _rand_func: # os.urandom will be used by default (os.urandom is suitable for cryptographic use) _rand_func=rand_func) self.assertTrue(blind_result.ok) self.assertFalse(blind_result.error) assert isinstance(blind_result, BlindingSuccess) if all(_k is None for _k in blind_issuance_asset_keys): random.seed(bundle['rand_seed']) tx_to_blind2 = unblinded_tx.to_mutable() blind_result2 = tx_to_blind2.blind( input_descriptors=input_descriptors, output_pubkeys=output_pubkeys, blind_issuance_asset_keys=blind_issuance_asset_keys, blind_issuance_token_keys=blind_issuance_token_keys, auxiliary_generators=asset_commitments, custom_ct_exponent=0, custom_ct_bits=32, # Test data was generated with these params _rand_func=rand_func) self.assertTrue(blind_result2.ok) self.assertFalse(blind_result2.error) self.assertEqual(blind_result, blind_result2) self.assertEqual(tx_to_blind.serialize(), tx_to_blind2.serialize()) self.assertEqual(blind_result.num_successfully_blinded, num_to_blind) self.assertNotEqual(unblinded_tx_raw, tx_to_blind.serialize()) self.assertEqual(blinded_tx_raw, tx_to_blind.serialize())
def check_serialize_deserialize(self, tx: CElementsTransaction, tx_bytes: bytes, tx_decoded: Dict[str, Any]) -> None: 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()) pegout_data = tx.vout[n].scriptPubKey.get_pegout_data() assert pegout_data is not None genesis_hash, pegout_scriptpubkey = 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: assert rpinfo is not None 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 'scriptSig' 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))
if __name__ == '__main__': if len(sys.argv) != 3: print("usage: {} <raw-hex-tx-file> <blinding-key-file>".format( sys.argv[0])) sys.exit(-1) # Switch the chain parameters to Elements select_chain_params('elements') # Read in and decode the blinded transaction. # expected to be hex-encoded as one line. with open(sys.argv[1]) as f: # We could use CTransaction here, but if we want to # use mypy to do static checking, we need to use the elements-specific # classes. mypy does cannot know about dynamic class dispatch. tx = CElementsTransaction.deserialize(x(f.readline().rstrip())) # Read in the blinding key, expected to be in WIF format. with open(sys.argv[2]) as f: bkey = CCoinKey.from_secret_bytes(x(f.readline().rstrip())) # Iterate through transaction ouptputs, and unblind what we can. print("") for n, vout in enumerate(tx.vout): # Note that nValue of vout in Elements is not a simple int, # but CConfidentialValue, which can either be explicit, and can be # converted to satoshis with to_amount(), or it can be blinded, in # which case you need to unblind the output to know its value. if vout.nValue.is_explicit(): # The output is not blinded, we can access the values right away assert vout.nAsset.is_explicit(
def bob(say, recv, send, die, btc_rpc, elt_rpc): """A function that implements the logic of the Bitcoin-side participant of confidential cross-chain atomic swap""" global last_wish_func # Default chain for Bob will be Bitcoin # To handle bitcoin-related objects, either # `with ChainParams(elements_chain_name):` have to be used, or # concrete classes, like CElementsAddress, CElementsTransaction, etc. select_chain_params(bitcoin_chain_name) say('Waiting for blinding key from Alice') alice_btc_pub_raw, alice_elt_exit_pub_raw = recv('pubkeys') blinding_key = CKey.from_secret_bytes(recv('blinding_key')) say("Pubkey for blinding key: {}".format(b2x(blinding_key.pub))) # Let's create the key that would lock the coins on Bitcoin side contract_key = CKey.from_secret_bytes(os.urandom(32)) # And the key for Elements side bob_elt_spend_key = CKey.from_secret_bytes(os.urandom(32)) # And the key for 'timeout' case on btc side bob_btc_exit_key = CKey.from_secret_bytes(os.urandom(32)) key_to_reveal_pub = CPubKey.add(contract_key.pub, blinding_key.pub) say("The pubkey of the combined key to be revealed: {}".format( b2x(key_to_reveal_pub))) say('Sending my pubkeys to Alice') send('pubkeys', (contract_key.pub, bob_elt_spend_key.pub, bob_btc_exit_key.pub)) combined_btc_spend_pubkey = CPubKey.add(contract_key.pub, CPubKey(alice_btc_pub_raw)) say('combined_btc_spend_pubkey: {}'.format(b2x(combined_btc_spend_pubkey))) btc_contract = make_btc_contract(combined_btc_spend_pubkey, bob_btc_exit_key.pub) btc_contract_addr = P2WSHCoinAddress.from_redeemScript(btc_contract) say("Created Bitcoin-side swap contract, size: {}".format( len(btc_contract))) say("Contract address: {}".format(btc_contract_addr)) say('Sending {} to {}'.format(pre_agreed_amount, btc_contract_addr)) btc_txid = btc_rpc.sendtoaddress(str(btc_contract_addr), pre_agreed_amount) def bob_last_wish_func(): try_reclaim_btc(say, btc_rpc, btc_txid, btc_contract, bob_btc_exit_key, die) last_wish_func = bob_last_wish_func wait_confirm(say, 'Bitcoin', btc_txid, die, btc_rpc, num_confirms=6) send('btc_txid', btc_txid) elt_txid = recv('elt_txid') elt_contract = make_elt_cntract(key_to_reveal_pub, bob_elt_spend_key.pub, alice_elt_exit_pub_raw) with ChainParams(elements_chain_name): elt_contract_addr = P2SHCoinAddress.from_redeemScript(elt_contract) say('Got Elements contract address from Alice: {}'.format( elt_contract_addr)) say('Looking for this address in transaction {} in Elements'.format( elt_txid)) tx_json = elt_rpc.getrawtransaction(elt_txid, 1) if tx_json['confirmations'] < 2: die('Transaction does not have enough confirmations') elt_commit_tx = CElementsTransaction.deserialize(x(tx_json['hex'])) vout_n, unblind_result = find_and_unblind_vout(say, elt_commit_tx, elt_contract_addr, blinding_key, die) if unblind_result.amount != 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(unblind_result.amount), pre_agreed_amount)) say('The asset and amount match expected values. lets spend it.') with ChainParams(elements_chain_name): dst_addr = CCoinAddress(elt_rpc.getnewaddress()) assert isinstance(dst_addr, CCoinConfidentialAddress) say('I will claim my Elements-BTC to {}'.format(dst_addr)) elt_claim_tx = create_elt_spend_tx( dst_addr, elt_txid, vout_n, elt_contract, die, spend_key=bob_elt_spend_key, contract_key=contract_key, blinding_key=blinding_key, blinding_factor=unblind_result.blinding_factor, asset_blinding_factor=unblind_result.asset_blinding_factor) # Cannot use VerifyScript for now, # because it does not support CHECKSIGFROMSTACK yet # # VerifyScript(tx.vin[0].scriptSig, # elt_contract_addr.to_scriptPubKey(), # tx, 0, amount=amount) say('Sending my spend-reveal transaction') sr_txid = elt_rpc.sendrawtransaction(b2x(elt_claim_tx.serialize())) wait_confirm(say, 'Elements', sr_txid, die, elt_rpc, num_confirms=2) say('Got my Elements-BTC. Swap successful (at least for me :-)')