def test_parse_standard_multisig_redeem_script(self): def T(script): return parse_standard_multisig_redeem_script(script) def T2(script, result): info = T(script) self.assertEqual(info.total, result.total) self.assertEqual(info.required, result.required) self.assertEqual(info.pubkeys, result.pubkeys) # NOTE: p2sh_multisig_parse_script does not check validity of pubkeys pubkeys = [CKey.from_secret_bytes(os.urandom(32)).pub for _ in range(15)] T2(CScript([1, pubkeys[0], pubkeys[1], 2, OP_CHECKMULTISIG]), StandardMultisigScriptInfo(total=2, required=1, pubkeys=pubkeys[:2])) T2(CScript([11] + pubkeys[:12] + [12, OP_CHECKMULTISIG]), StandardMultisigScriptInfo(total=12, required=11, pubkeys=pubkeys[:12])) T2(CScript([15] + pubkeys + [15, OP_CHECKMULTISIG]), StandardMultisigScriptInfo(total=15, required=15, pubkeys=pubkeys)) with self.assertRaises(ValueError): # invalid script - extra opcode T(CScript([1, pubkeys[0], pubkeys[1], 2, OP_CHECKMULTISIG, OP_DROP])) with self.assertRaises(ValueError): # invalid pubkey - extra data T(CScript([1, pubkeys[0]+b'abc', pubkeys[1], 2, OP_CHECKMULTISIG])) with self.assertRaises(ValueError): # invalid pubkey - truncated T(CScript([1, pubkeys[0], pubkeys[1][:-1], 2, OP_CHECKMULTISIG])) with self.assertRaises(ValueError): T(CScript([])) with self.assertRaises(ValueError): T(CScript([b'\x01', pubkeys[0], pubkeys[1], 2, OP_CHECKMULTISIG])) with self.assertRaises(ValueError): T(CScript([16, pubkeys[0], pubkeys[1], 2, OP_CHECKMULTISIG])) with self.assertRaises(ValueError): T(CScript([11] + pubkeys[:12] + [b'\x0c', OP_CHECKMULTISIG])) with self.assertRaises(ValueError): T(CScript([11] + pubkeys + [14, OP_CHECKMULTISIG])) with self.assertRaises(ValueError): T(CScript([11] + pubkeys + pubkeys + [15, OP_CHECKMULTISIG])) with self.assertRaises(ValueError): T(CScript([1, pubkeys[0], pubkeys[1], 1, OP_CHECKMULTISIG])) with self.assertRaises(ValueError): T(CScript([11] + pubkeys[:12] + [11, OP_CHECKMULTISIG])) with self.assertRaises(ValueError): T(CScript([11] + pubkeys[:12] + [11, OP_CHECKSIG])) with self.assertRaises(ValueError): T(CScript([11] + pubkeys[:12])) # short script 1 with self.assertRaises(ValueError): T(CScript([11] + pubkeys[:12] + [11])) # short script 2
def test_parse_standard_multisig_redeem_script(self): def T(script, result): self.assertEqual(parse_standard_multisig_redeem_script(script), result) # NOTE: p2sh_multisig_parse_script does not check validity of pubkeys pubkeys = [ CKey.from_secret_bytes(os.urandom(32)).pub for _ in range(15) ] T(CScript([1, pubkeys[0], pubkeys[1], 2, OP_CHECKMULTISIG]), { 'total': 2, 'required': 1, 'pubkeys': pubkeys[:2] }) T(CScript([11] + pubkeys[:12] + [12, OP_CHECKMULTISIG]), { 'total': 12, 'required': 11, 'pubkeys': pubkeys[:12] }) T(CScript([15] + pubkeys + [15, OP_CHECKMULTISIG]), { 'total': 15, 'required': 15, 'pubkeys': pubkeys }) with self.assertRaises(ValueError): T(CScript([]), {}) with self.assertRaises(ValueError): T(CScript([b'\x01', pubkeys[0], pubkeys[1], 2, OP_CHECKMULTISIG]), {}) with self.assertRaises(ValueError): T(CScript([16, pubkeys[0], pubkeys[1], 2, OP_CHECKMULTISIG]), {}) with self.assertRaises(ValueError): T(CScript([11] + pubkeys[:12] + [b'\x0c', OP_CHECKMULTISIG]), {}) with self.assertRaises(ValueError): T(CScript([11] + pubkeys + [14, OP_CHECKMULTISIG]), {}) with self.assertRaises(ValueError): T(CScript([11] + pubkeys + pubkeys + [15, OP_CHECKMULTISIG]), {}) with self.assertRaises(ValueError): T(CScript([1, pubkeys[0], pubkeys[1], 1, OP_CHECKMULTISIG]), {}) with self.assertRaises(ValueError): T(CScript([11] + pubkeys[:12] + [11, OP_CHECKMULTISIG]), {}) with self.assertRaises(ValueError): T(CScript([11] + pubkeys[:12] + [11, OP_CHECKSIG]), {}) with self.assertRaises(ValueError): T( CScript([11] + pubkeys[:12]), # short script 1 {}) with self.assertRaises(ValueError): T( CScript([11] + pubkeys[:12] + [11]), # short script 2 {})
def test_sighash(self) -> None: spent_amount = 1100 pub = CKey.from_secret_bytes(os.urandom(32)).pub spk_legacy = P2PKHCoinAddress.from_pubkey(pub).to_scriptPubKey() spk_segwit = P2WPKHCoinAddress.from_pubkey(pub).to_scriptPubKey() tx = CTransaction([ CTxIn( COutPoint(b'\x00' * 32, 0), CScript([]), nSequence=0xFFFFFFFF) ], [CTxOut(1000, spk_legacy)], nLockTime=0, nVersion=1) # no exceptions should be raised with these two calls spk_legacy.sighash(tx, 0, SIGHASH_ALL, amount=spent_amount, sigversion=SIGVERSION_WITNESS_V0) spk_segwit.sighash(tx, 0, SIGHASH_ALL, amount=spent_amount, sigversion=SIGVERSION_WITNESS_V0) with self.assertRaises(ValueError): # unknown sigversion spk_segwit.sighash(tx, 0, SIGHASH_ALL, amount=spent_amount, sigversion=SIGVERSION_WITNESS_V0 + 1) # type: ignore assert spk_segwit.is_witness_scriptpubkey() with self.assertRaises(ValueError): # incorect sigversion for segwit spk_segwit.sighash(tx, 0, SIGHASH_ALL, amount=spent_amount, sigversion=SIGVERSION_BASE) with self.assertRaises(ValueError): # inIdx > len(tx.vin) - non-raw sighash function should raise # ValueError (raw_sighash can return HASH_ONE) spk_legacy.sighash(tx, 10, SIGHASH_ALL, amount=spent_amount, sigversion=SIGVERSION_BASE)
def T(required: int, total: int, alt_total: Optional[int] = None) -> None: amount = 10000 keys = [ CKey.from_secret_bytes(os.urandom(32)) for _ in range(total) ] pubkeys = [k.pub for k in keys] if alt_total is not None: total = alt_total # for assertRaises checks redeem_script = standard_multisig_redeem_script(total=total, required=required, pubkeys=pubkeys) # Test with P2SH scriptPubKey = redeem_script.to_p2sh_scriptPubKey() (_, tx) = self.create_test_txs(CScript(), scriptPubKey, CScriptWitness([]), amount) tx = tx.to_mutable() sighash = redeem_script.sighash(tx, 0, SIGHASH_ALL, amount=amount, sigversion=SIGVERSION_BASE) sigs = [ k.sign(sighash) + bytes([SIGHASH_ALL]) for k in keys[:required] ] tx.vin[0].scriptSig = CScript( standard_multisig_witness_stack(sigs, redeem_script)) VerifyScript(tx.vin[0].scriptSig, scriptPubKey, tx, 0, (SCRIPT_VERIFY_P2SH, )) # Test with P2WSH scriptPubKey = redeem_script.to_p2wsh_scriptPubKey() (_, tx) = self.create_test_txs(CScript(), scriptPubKey, CScriptWitness([]), amount) tx = tx.to_mutable() sighash = redeem_script.sighash(tx, 0, SIGHASH_ALL, amount=amount, sigversion=SIGVERSION_WITNESS_V0) sigs = [ k.sign(sighash) + bytes([SIGHASH_ALL]) for k in keys[:required] ] witness_stack = standard_multisig_witness_stack( sigs, redeem_script) tx.vin[0].scriptSig = CScript([]) tx.wit.vtxinwit[0] = CTxInWitness( CScriptWitness(witness_stack)).to_mutable() VerifyScript(tx.vin[0].scriptSig, scriptPubKey, tx, 0, flags=(SCRIPT_VERIFY_WITNESS, SCRIPT_VERIFY_P2SH), amount=amount, witness=tx.wit.vtxinwit[0].scriptWitness) # Test with P2SH_P2WSH scriptPubKey = redeem_script.to_p2wsh_scriptPubKey() (_, tx) = self.create_test_txs(CScript(), scriptPubKey, CScriptWitness([]), amount) tx = tx.to_mutable() sighash = redeem_script.sighash(tx, 0, SIGHASH_ALL, amount=amount, sigversion=SIGVERSION_WITNESS_V0) sigs = [ k.sign(sighash) + bytes([SIGHASH_ALL]) for k in keys[:required] ] witness_stack = standard_multisig_witness_stack( sigs, redeem_script) tx.vin[0].scriptSig = CScript([scriptPubKey]) tx.wit.vtxinwit[0] = CTxInWitness( CScriptWitness(witness_stack)).to_mutable() VerifyScript(tx.vin[0].scriptSig, scriptPubKey.to_p2sh_scriptPubKey(), tx, 0, flags=(SCRIPT_VERIFY_WITNESS, SCRIPT_VERIFY_P2SH), amount=amount, witness=tx.wit.vtxinwit[0].scriptWitness)
def test_parse_standard_multisig_redeem_script(self) -> None: def T(script: CScript) -> StandardMultisigScriptInfo: return parse_standard_multisig_redeem_script(script) def T2(script: CScript, result: StandardMultisigScriptInfo) -> None: info = T(script) self.assertEqual(info.total, result.total) self.assertEqual(info.required, result.required) self.assertEqual(info.pubkeys, result.pubkeys) # NOTE: p2sh_multisig_parse_script does not check validity of pubkeys pubkeys = [ CKey.from_secret_bytes(os.urandom(32)).pub for _ in range(15) ] T2( CScript([1, pubkeys[0], pubkeys[1], 2, OP_CHECKMULTISIG]), StandardMultisigScriptInfo(total=2, required=1, pubkeys=pubkeys[:2])) # "list-any" - to workaround to # 'Unsupported operand types for + (List[int] and List[CPubKey]) la: List[Any] = [] T2( CScript(la + [11] + pubkeys[:12] + [12, OP_CHECKMULTISIG]), StandardMultisigScriptInfo(total=12, required=11, pubkeys=pubkeys[:12])) T2(CScript(la + [15] + pubkeys + [15, OP_CHECKMULTISIG]), StandardMultisigScriptInfo(total=15, required=15, pubkeys=pubkeys)) with self.assertRaises(ValueError): # invalid script - extra opcode T( CScript( [1, pubkeys[0], pubkeys[1], 2, OP_CHECKMULTISIG, OP_DROP])) with self.assertRaises(ValueError): # invalid pubkey - extra data T( CScript( [1, pubkeys[0] + b'abc', pubkeys[1], 2, OP_CHECKMULTISIG])) with self.assertRaises(ValueError): # invalid pubkey - truncated T(CScript([1, pubkeys[0], pubkeys[1][:-1], 2, OP_CHECKMULTISIG])) with self.assertRaises(ValueError): T(CScript([])) with self.assertRaises(ValueError): T(CScript([b'\x01', pubkeys[0], pubkeys[1], 2, OP_CHECKMULTISIG])) with self.assertRaises(ValueError): T(CScript([16, pubkeys[0], pubkeys[1], 2, OP_CHECKMULTISIG])) with self.assertRaises(ValueError): T(CScript(la + [11] + pubkeys[:12] + [b'\x0c', OP_CHECKMULTISIG])) with self.assertRaises(ValueError): T(CScript(la + [11] + pubkeys + [14, OP_CHECKMULTISIG])) with self.assertRaises(ValueError): T(CScript(la + [11] + pubkeys + pubkeys + [15, OP_CHECKMULTISIG])) with self.assertRaises(ValueError): T(CScript([1, pubkeys[0], pubkeys[1], 1, OP_CHECKMULTISIG])) with self.assertRaises(ValueError): T(CScript(la + [11] + pubkeys[:12] + [11, OP_CHECKMULTISIG])) with self.assertRaises(ValueError): T(CScript(la + [11] + pubkeys[:12] + [11, OP_CHECKSIG])) with self.assertRaises(ValueError): T(CScript(la + [11] + pubkeys[:12])) # short script 1 with self.assertRaises(ValueError): T(CScript(la + [11] + pubkeys[:12] + [11])) # short script 2
def create_elt_spend_tx(dst_addr, txid, vout_n, elt_contract, die, spend_key=None, contract_key=None, blinding_key=None, blinding_factor=None, asset_blinding_factor=None, branch_condition=True): fee_satoshi = coins_to_satoshi(fixed_fee_amount) out_amount = coins_to_satoshi(pre_agreed_amount) - fee_satoshi # Single blinded output is not allowed, so we add # dummy OP_RETURN output, and we need dummy pubkey for it dummy_key = CKey.from_secret_bytes(os.urandom(32)) tx = CMutableTransaction( vin=[CTxIn(prevout=COutPoint(hash=lx(txid), n=vout_n))], vout=[ CTxOut(nValue=CConfidentialValue(out_amount), nAsset=CConfidentialAsset(bitcoin_asset), scriptPubKey=dst_addr.to_scriptPubKey(), nNonce=CConfidentialNonce(dst_addr.blinding_pubkey)), CTxOut(nValue=CConfidentialValue(0), nAsset=CConfidentialAsset(bitcoin_asset), nNonce=CConfidentialNonce(dummy_key.pub), scriptPubKey=CElementsScript([OP_RETURN])), CTxOut(nValue=CConfidentialValue(fee_satoshi), nAsset=CConfidentialAsset(bitcoin_asset)) ]) output_pubkeys = [dst_addr.blinding_pubkey, dummy_key.pub] in_amount = coins_to_satoshi(pre_agreed_amount) input_descriptors = [ BlindingInputDescriptor(asset=bitcoin_asset, amount=in_amount, blinding_factor=blinding_factor, asset_blinding_factor=asset_blinding_factor) ] blind_result = tx.blind(input_descriptors=input_descriptors, output_pubkeys=output_pubkeys) # The blinding must succeed! if blind_result.error: die('blind failed: {}'.format(blind_result.error)) if branch_condition is False: # Must set nSequence before we calculate signature hash, # because it is included in it tx.vin[0].nSequence = elements_contract_timeout # We used P2SHCoinAddress to create the address that # we sent Elements-BTC to, so we know that we need # to use SIGVERSION_BASE sighash = elt_contract.sighash(tx, 0, SIGHASH_ALL, amount=CConfidentialValue(in_amount), sigversion=SIGVERSION_BASE) spend_sig = spend_key.sign(sighash) + bytes([SIGHASH_ALL]) if branch_condition is True: prepare_elt_spend_reveal_branch(tx, elt_contract, spend_sig, contract_key, blinding_key) else: tx.vin[0].scriptSig = CElementsScript( [spend_sig, OP_FALSE, elt_contract]) return tx
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 :-)')
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!')