From fb4644ec5b7b6ea8a6fc8459948a9cf9b4117c77 Mon Sep 17 00:00:00 2001 From: Adam Gibson Date: Thu, 16 Dec 2021 12:13:50 +0000 Subject: [PATCH] Allow utxo address validation with script Prior to this commit, the sendtomany.py script would sometimes fail based on the output of the RPC call gettxout returning only a script, and not an address. After this commit, the validation will still work correctly in these cases using the chosen cryptoengine to convert from script to address. Also notably, the return value of utxo set does not include an 'address' key any more. Tests are updated to reflect this. --- jmclient/jmclient/blockchaininterface.py | 5 ----- jmclient/jmclient/commitment_utils.py | 25 ++++++++++++------------ jmclient/test/test_commitment_utils.py | 16 +++++++-------- jmclient/test/test_wallets.py | 2 +- 4 files changed, 21 insertions(+), 27 deletions(-) diff --git a/jmclient/jmclient/blockchaininterface.py b/jmclient/jmclient/blockchaininterface.py index e15a4f0..40cfca8 100644 --- a/jmclient/jmclient/blockchaininterface.py +++ b/jmclient/jmclient/blockchaininterface.py @@ -387,13 +387,8 @@ class BitcoinCoreInterface(BlockchainInterface): if ret is None: result.append(None) else: - if ret['scriptPubKey'].get('addresses'): - address = ret['scriptPubKey']['addresses'][0] - else: - address = None result_dict = {'value': int(Decimal(str(ret['value'])) * Decimal('1e8')), - 'address': address, 'script': hextobin(ret['scriptPubKey']['hex'])} if includeconf: result_dict['confirms'] = int(ret['confirmations']) diff --git a/jmclient/jmclient/commitment_utils.py b/jmclient/jmclient/commitment_utils.py index 3c9f79c..d2e38dc 100644 --- a/jmclient/jmclient/commitment_utils.py +++ b/jmclient/jmclient/commitment_utils.py @@ -33,16 +33,19 @@ def get_utxo_info(upriv, utxo_binary=False): raise utxo_to_return = utxo_bin if utxo_binary else u return utxo_to_return, priv - + +def print_failed_addr_match(utxostr, addr1, addr2): + jmprint("privkey corresponds to the wrong address for utxo: " + utxostr, "error") + jmprint("blockchain returned address: {}".format(addr2), "error") + jmprint("your privkey gave this address: " + addr1, "error") + return False + def validate_utxo_data(utxo_datas, retrieve=False, utxo_address_type="p2wpkh"): """For each (utxo, privkey), first convert the privkey and convert to address, then use the blockchain instance to look up the utxo and check that its address field matches. If retrieve is True, return the set of utxos and their values. - If segwit is true, assumes a p2sh wrapped p2wpkh, i.e. - native segwit is NOT currently supported here. If segwit - is false, p2pkh is assumed. """ results = [] for u, priv in utxo_datas: @@ -52,10 +55,8 @@ def validate_utxo_data(utxo_datas, retrieve=False, utxo_address_type="p2wpkh"): sys.exit(EXIT_FAILURE) jmprint('validating this utxo: ' + utxostr, "info") # as noted in `ImportWalletMixin` code comments, there is not - # yet a functional auto-detection of key type from WIF, so the - # second argument is ignored; we assume p2sh-p2wpkh if segwit=True, - # p2pkh if segwit=False, and p2wpkh if segwit="native" (slightly - # ugly, just done for backwards compat.). + # yet a functional auto-detection of key type from WIF, hence + # the need for this additional switch: if utxo_address_type == "p2wpkh": engine = BTC_P2WPKH elif utxo_address_type == "p2sh-p2wpkh": @@ -71,11 +72,9 @@ def validate_utxo_data(utxo_datas, retrieve=False, utxo_address_type="p2wpkh"): if len(res) != 1 or None in res: jmprint("utxo not found on blockchain: " + utxostr, "error") return False - if res[0]['address'] != addr: - jmprint("privkey corresponds to the wrong address for utxo: " + utxostr, "error") - jmprint("blockchain returned address: {}".format(res[0]['address']), "error") - jmprint("your privkey gave this address: " + addr, "error") - return False + returned_addr = engine.script_to_address(res[0]['script']) + if returned_addr != addr: + return print_failed_addr_match(utxostr, addr, returned_addr) if retrieve: results.append((u, res[0]['value'])) jmprint('all utxos validated OK', "success") diff --git a/jmclient/test/test_commitment_utils.py b/jmclient/test/test_commitment_utils.py index a7f5d2d..dc39bd1 100644 --- a/jmclient/test/test_commitment_utils.py +++ b/jmclient/test/test_commitment_utils.py @@ -1,8 +1,8 @@ from commontest import DummyBlockchainInterface import pytest -from jmbase import utxostr_to_utxo -from jmclient import (load_test_config, jm_single) +from jmbase import utxostr_to_utxo, hextobin +from jmclient import (load_test_config, jm_single, BTC_P2WPKH) from jmclient.commitment_utils import get_utxo_info, validate_utxo_data from jmbitcoin import select_chain_params @@ -20,7 +20,7 @@ def test_get_utxo_info(): success, fakeutxo_bin = utxostr_to_utxo(fakeutxo) assert success fake_query_results = [{'value': 200000000, - 'address': iaddr, + 'script': BTC_P2WPKH.address_to_script(iaddr), 'utxo': fakeutxo_bin, 'confirms': 20}] dbci.insert_fake_query_results(fake_query_results) @@ -29,15 +29,15 @@ def test_get_utxo_info(): assert u == fakeutxo assert priv == privkey #invalid format - with pytest.raises(Exception) as e_info: + with pytest.raises(Exception): u, priv = get_utxo_info(fakeutxo + privkey) #invalid index fu2 = "ab"*32 + ":-1" - with pytest.raises(Exception) as e_info: + with pytest.raises(Exception): u, priv = get_utxo_info(fu2 + "," + privkey) #invalid privkey p2 = privkey[:-1] + 'j' - with pytest.raises(Exception) as e_info: + with pytest.raises(Exception): u, priv = get_utxo_info(fakeutxo + "," + p2) utxodatas = [(fakeutxo_bin, privkey)] @@ -46,7 +46,7 @@ def test_get_utxo_info(): #try to retrieve retval = validate_utxo_data(utxodatas, True) assert retval[0] == (fakeutxo_bin, 200000000) - fake_query_results[0]['address'] = "fakeaddress" + fake_query_results[0]['script'] = hextobin("76a91479b000887626b294a914501a4cd226b58b23598388ac") dbci.insert_fake_query_results(fake_query_results) #validate should fail for wrong address retval = validate_utxo_data(utxodatas, False) @@ -58,4 +58,4 @@ def test_get_utxo_info(): assert not retval dbci.setQUSFail(False) select_chain_params("bitcoin/regtest") - jm_single().config.set("BLOCKCHAIN", "network", "mainnet") + jm_single().config.set("BLOCKCHAIN", "network", "regtest") diff --git a/jmclient/test/test_wallets.py b/jmclient/test/test_wallets.py index 6450d37..dec25b4 100644 --- a/jmclient/test/test_wallets.py +++ b/jmclient/test/test_wallets.py @@ -50,7 +50,7 @@ def test_query_utxo_set(setup_wallets): includeconf=True, includeunconf=True) assert len(res1) == 1 assert len(res2) == 2 - assert all([x in res1[0] for x in ['script', 'address', 'value']]) + assert all([x in res1[0] for x in ['script', 'value']]) assert not 'confirms' in res1[0] assert 'confirms' in res2[0] assert 'confirms' in res2[1]