def test_chain_params_context_manager(self): with ChainParams(BitcoinRegtestParams) as p1: assert isinstance(p1, BitcoinRegtestParams) with ChainParams(BitcoinSignetParams) as p2: assert isinstance(p2, BitcoinSignetParams) assert isinstance(get_current_chain_params(), BitcoinSignetParams) assert isinstance(get_current_chain_params(), BitcoinRegtestParams)
def test_parse_config(self): conf_file_contents = """ listen=1 server=1 rpcpassword=somepass # should be overriden regtest.rpcport = 8123 rpcport = 8888 [main] rpcuser=someuser1 rpcpassword=somepass1 rpcconnect=127.0.0.10 [test] rpcpassword=somepass2 rpcconnect=127.0.0.11 rpcport = 9999 [regtest] rpcuser=someuser3 rpcpassword=somepass3 rpcconnect=127.0.0.12 """ rpc = RPCCaller(conf_file_contents=conf_file_contents) self.assertEqual(rpc._RPCCaller__service_url, 'http://127.0.0.10:8888') authpair = "someuser1:somepass1" authhdr = "Basic " + base64.b64encode( authpair.encode('utf8')).decode('utf8') self.assertEqual(rpc._RPCCaller__auth_header, authhdr) with ChainParams('bitcoin/testnet'): rpc = RPCCaller(conf_file_contents=conf_file_contents) self.assertEqual(rpc._RPCCaller__service_url, 'http://127.0.0.11:9999') authpair = ":somepass2" # no user specified authhdr = "Basic " + base64.b64encode( authpair.encode('utf8')).decode('utf8') self.assertEqual(rpc._RPCCaller__auth_header, authhdr) with ChainParams('bitcoin/regtest'): rpc = RPCCaller(conf_file_contents=conf_file_contents) self.assertEqual(rpc._RPCCaller__service_url, 'http://127.0.0.12:8123') authpair = "someuser3:somepass3" authhdr = "Basic " + base64.b64encode( authpair.encode('utf8')).decode('utf8') self.assertEqual(rpc._RPCCaller__auth_header, authhdr)
def test_MoneyRangeCustomParams(self): class CoreHighMaxClassDispatcher(CoreBitcoinClassDispatcher): ... class CoreHighMaxParams(CoreBitcoinParams, CoreBitcoinClass): @classgetter def MAX_MONEY(self): return 22000000 * self.COIN class WalletHighMaxClassDispatcher(WalletBitcoinClassDispatcher): ... class HighMaxParams(BitcoinMainnetParams): NAME = 'high_maxmoney' WALLET_DISPATCHER = WalletHighMaxClassDispatcher with ChainParams(HighMaxParams): self.assertFalse(MoneyRange(-1)) with self.assertRaises(ValueError): coins_to_satoshi(-1) with self.assertRaises(ValueError): satoshi_to_coins(-1) self.assertTrue(MoneyRange(0)) self.assertTrue(MoneyRange(100000)) max_satoshi = coins_to_satoshi(22000000) self.assertTrue( MoneyRange(max_satoshi)) # Maximum money on Bitcoin network self.assertFalse(MoneyRange(max_satoshi + 1)) with self.assertRaises(ValueError): coins_to_satoshi(max_satoshi + 1) with self.assertRaises(ValueError): satoshi_to_coins(max_satoshi + 1)
def test_legacy_p2sh(self): with ChainParams('litecoin', allow_legacy_p2sh=True): a = CCoinAddress('3F1c6UWAs9RLN2Mbt5bAJue12VhVCorXzs') self.assertIsInstance(a, P2SHLitecoinLegacyAddress) with ChainParams('litecoin'): with self.assertRaises(CCoinAddressError): a = CCoinAddress('3F1c6UWAs9RLN2Mbt5bAJue12VhVCorXzs') l_addr = '3N4DqfrHhStCao4NjwroxoegjydkJk3P9Z' c_addr = 'MUGN9ZGFeZjdPJLGqpr9nSu64gECLRzQrx' self.assertEqual( str( P2SHLitecoinAddress.from_scriptPubKey( P2SHLitecoinLegacyAddress(l_addr).to_scriptPubKey())), c_addr)
def test_valid_bip341_scriptpubkeys_addresses(): with ChainParams("bitcoin"): with open(os.path.join(testdir, "bip341_wallet_test_vectors.json"), "r") as f: json_data = json.loads(f.read()) for x in json_data["scriptPubKey"]: sPK = hextobin(x["expected"]["scriptPubKey"]) addr = x["expected"]["bip350Address"] res, message = validate_address(addr) assert res, message print("address {} was valid bech32m".format(addr)) # test this specific conversion because this is how # our human readable outputs work: assert str(CCoinAddress.from_scriptPubKey( btc.CScript(sPK))) == addr print("and it converts correctly from scriptPubKey: {}".format( btc.CScript(sPK)))
def test_address_implementations(test, paramclasses=None, extra_addr_testfunc=lambda *args: False): pub = CPubKey( x('0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71') ) if paramclasses is None: paramclasses = bitcointx.get_registered_chain_params() for paramclass in paramclasses: with ChainParams(paramclass): def recursive_check(aclass): test.assertTrue(issubclass(aclass, CCoinAddress)) if extra_addr_testfunc(aclass, pub): pass else: a = None if getattr(aclass, 'from_pubkey', None): a = aclass.from_pubkey(pub) elif getattr(aclass, 'from_redeemScript', None): a = aclass.from_redeemScript( CScript(b'\xa9' + Hash160(pub) + b'\x87')) else: assert len(dispatcher_mapped_list(aclass)) > 0,\ ("dispatcher mapped list for {} " "must not be empty".format(aclass)) if a is not None: spk = a.to_scriptPubKey() test.assertEqual(a, aclass.from_scriptPubKey(spk)) a2 = aclass.from_bytes(a) test.assertEqual(bytes(a), bytes(a2)) test.assertEqual(str(a), str(a2)) a3 = aclass(str(a)) test.assertEqual(bytes(a), bytes(a3)) test.assertEqual(str(a), str(a3)) for next_aclass in dispatcher_mapped_list(aclass): recursive_check(next_aclass) recursive_check(CCoinAddress)
def test_MoneyRangeCustomParams(self) -> None: class CoreHighMaxClassDispatcher(CoreBitcoinClassDispatcher): ... class CoreHighMaxClass(CoreBitcoinClass, metaclass=CoreHighMaxClassDispatcher): ... class CoreHighMaxParams(CoreBitcoinParams, CoreHighMaxClass): @classgetter def MAX_MONEY(self) -> int: return 10**100 * self.COIN class WalletHighMaxClassDispatcher(WalletBitcoinClassDispatcher, depends=[ CoreHighMaxClassDispatcher ]): ... class HighMaxParams(BitcoinMainnetParams): NAME = 'high_maxmoney' WALLET_DISPATCHER = WalletHighMaxClassDispatcher with self.assertRaises(ValueError): coins_to_satoshi(10**100) with ChainParams(HighMaxParams): self.assertFalse(MoneyRange(-1)) with self.assertRaises(ValueError): coins_to_satoshi(-1) with self.assertRaises(ValueError): satoshi_to_coins(-1) self.assertTrue(MoneyRange(0)) self.assertTrue(MoneyRange(100000)) max_satoshi = coins_to_satoshi(10**100) self.assertEqual(satoshi_to_coins(max_satoshi), 10**100) self.assertTrue(MoneyRange(max_satoshi)) self.assertFalse(MoneyRange(max_satoshi + 1)) with self.assertRaises(ValueError): coins_to_satoshi(max_satoshi + 1) with self.assertRaises(ValueError): satoshi_to_coins(max_satoshi + 1)
def test_get_output_size(self): pub1 = CPubKey( x('0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71' )) pub2 = CPubKey( x('02546c76587482cd2468b76768da70c0166ecb2aa2eb1038624f4fedc138b042bc' )) for chainparam in get_params_list(): with ChainParams(chainparam): smpl = get_unconfidential_address_samples(pub1, pub2) # 1 byte for 'no asset', 1 byte for 'no nonce', # 9 bytes for explicit value, # minus 8 bytes of len of bitcoin nValue elements_unconfidential_size_extra = 1 + 1 + 9 - 8 # 33 bytes for asset, 33 bytes for nonce, # 33 bytes for confidential value, # minus 8 bytes of len of bitcoin nValue elements_confidential_size_extra = 33 + 33 + 33 - 8 self.assertEqual(smpl.p2pkh.get_output_size(), 34 + elements_unconfidential_size_extra) self.assertEqual(smpl.p2wpkh.get_output_size(), 31 + elements_unconfidential_size_extra) self.assertEqual(smpl.p2sh.get_output_size(), 32 + elements_unconfidential_size_extra) self.assertEqual(smpl.p2wsh.get_output_size(), 43 + elements_unconfidential_size_extra) self.assertEqual(smpl.conf_p2pkh.get_output_size(), 34 + elements_confidential_size_extra) self.assertEqual(smpl.conf_p2wpkh.get_output_size(), 31 + elements_confidential_size_extra) self.assertEqual(smpl.conf_p2sh.get_output_size(), 32 + elements_confidential_size_extra) self.assertEqual(smpl.conf_p2wsh.get_output_size(), 43 + elements_confidential_size_extra)
def test_from_to_unconfidential(self): #noqa pub1 = CPubKey( x('02546c76587482cd2468b76768da70c0166ecb2aa2eb1038624f4fedc138b042bc' )) pub2 = CPubKey( x('0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71' )) params_list = get_params_list() for pl_index, chainparam in enumerate(params_list): next_chainparam = (params_list[0] if pl_index + 1 == len(params_list) else params_list[pl_index + 1]) with ChainParams(chainparam): mapped_cls_list = dispatcher_mapped_list( CCoinConfidentialAddress) assert len(mapped_cls_list) == 1 chain_specific_cls = mapped_cls_list[0] with ChainParams(next_chainparam): mapped_cls_list = dispatcher_mapped_list( CCoinConfidentialAddress) assert len(mapped_cls_list) == 1 next_chain_specific_cls = mapped_cls_list[0] assert next_chain_specific_cls is not chain_specific_cls smpl = get_unconfidential_address_samples(pub1, pub2) for uct in unconf_types: for ct in conf_types: unconf = getattr(smpl, uct) conf = getattr(smpl, ct) with self.assertRaises(TypeError): next_chain_specific_cls.from_unconfidential( unconf, pub2) if ct.endswith(uct): self.assertEqual(str(conf.to_unconfidential()), str(unconf)) self.assertEqual( str(conf.from_unconfidential(unconf, pub2)), str(conf)) self.assertNotEqual( str(conf.from_unconfidential(unconf, pub1)), str(conf)) self.assertEqual( str( CCoinConfidentialAddress. from_unconfidential(unconf, pub2)), str(conf)) self.assertEqual( str( chain_specific_cls.from_unconfidential( unconf, pub2)), str(conf)) if ct.endswith('p2pkh'): self.assertEqual( str( CBase58CoinConfidentialAddress. from_unconfidential(unconf, pub2)), str(conf)) self.assertEqual( str( P2PKHCoinConfidentialAddress. from_unconfidential(unconf, pub2)), str(conf)) elif ct.endswith('p2sh'): self.assertEqual( str( CBase58CoinConfidentialAddress. from_unconfidential(unconf, pub2)), str(conf)) self.assertEqual( str( P2SHCoinConfidentialAddress. from_unconfidential(unconf, pub2)), str(conf)) elif ct.endswith('p2wpkh'): self.assertEqual( str( CBlech32CoinConfidentialAddress. from_unconfidential(unconf, pub2)), str(conf)) self.assertEqual( str( P2WPKHCoinConfidentialAddress. from_unconfidential(unconf, pub2)), str(conf)) elif ct.endswith('p2wsh'): self.assertEqual( str( CBlech32CoinConfidentialAddress. from_unconfidential(unconf, pub2)), str(conf)) self.assertEqual( str( P2WSHCoinConfidentialAddress. from_unconfidential(unconf, pub2)), str(conf)) else: assert 0, "unexpected addr type" if issubclass(conf.__class__, CBlech32CoinConfidentialAddress): with self.assertRaises( CConfidentialAddressError): CBase58CoinConfidentialAddress.from_unconfidential( unconf, pub2) elif issubclass(conf.__class__, CBase58CoinConfidentialAddress): with self.assertRaises( CConfidentialAddressError): CBlech32CoinConfidentialAddress.from_unconfidential( unconf, pub2) else: assert 0, "unexpected conf.__class__" for ct2 in conf_types: if ct != ct2: conf_cls = getattr(smpl, ct2).__class__ with self.assertRaises(TypeError): conf_cls.from_unconfidential( unconf, pub2) else: self.assertNotEqual(str(conf.to_unconfidential()), str(unconf)) with self.assertRaises(TypeError): conf.from_unconfidential(unconf, pub2)
def participant(func, name, pipe, bitcoin_config_path, elements_config_path): """Prepares environment for participants, run their functions, and handles the errors they did not bother to hanlde""" def say(msg): participant_says(name, msg) # Custom exception class to distinguish a case when # participant calss die() from other exceptions class ProtocolFailure(Exception): ... def do_last_wish(msg): global last_wish_func lwf = last_wish_func if lwf: say("Going to die because '{}', " "but I still have something to do before that.".format(msg)) last_wish_func = None lwf() def die(msg, peacefully=False): do_last_wish(msg) if peacefully: sys.exit(-1) raise ProtocolFailure(msg) def recv(expected_type, timeout=60): if not pipe.poll(timeout): die('No messages received in {} seconds'.format(timeout)) msg = pipe.recv() if msg[0] == 'bye!': msg = 'Communication finished unexpectedly' say(msg + ', exiting.') do_last_wish(msg) sys.exit(-1) if msg[0] != expected_type: die("unexpected message type '{}', expected '{}'".format( msg[0], expected_type)) return msg[1] def send(msg_type, data=None): pipe.send([msg_type, data]) # Ignore keyboard interrupt, parent process handles it. signal.signal(signal.SIGINT, signal.SIG_IGN) try: # Connect to Elements or Bitcoin RPC with specified path # Do it with context params switch, so that appropriate values # from config files will be used (there may be regtest.port=N, etc) with ChainParams(bitcoin_chain_name): btc_rpc = connect_rpc(say, bitcoin_config_path) with ChainParams(elements_chain_name): elt_rpc = connect_rpc(say, elements_config_path) # Execute participant's function func(say, recv, send, die, btc_rpc, elt_rpc) except Exception as e: say('FAIL with {}: {}'.format(type(e).__name__, e)) say("Traceback:") print("=" * 80) traceback.print_tb(sys.exc_info()[-1]) print("=" * 80) do_last_wish(e.__class__.__name__) send('bye!') sys.exit(-1)
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!')