diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -229,12 +229,13 @@ "range to import."}, }, RPCResult{"[ address ] (array) the derived addresses\n"}, - RPCExamples{"First three native segwit receive addresses\n" + - HelpExampleCli("deriveaddresses", - "\"pkh([d34db33f/84h/0h/0h]" - "xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8P" - "hqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKE" - "u3oaqMSzhSrHMxyyoEAmUHQbY/0/*)\" 0 2")}} + RPCExamples{ + "First three pkh receive addresses\n" + + HelpExampleCli("deriveaddresses", + "\"pkh([d34db33f/84h/0h/0h]" + "xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8P" + "hqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKE" + "u3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#3vhfv5h5\" 0 2")}} .Check(request); RPCTypeCheck(request.params, @@ -263,7 +264,7 @@ } FlatSigningProvider provider; - auto desc = Parse(desc_str, provider); + auto desc = Parse(desc_str, provider, /* require_checksum = */ true); if (!desc) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid descriptor")); diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -1267,7 +1267,7 @@ const std::string &descriptor = data["desc"].get_str(); FlatSigningProvider keys; - auto parsed_desc = Parse(descriptor, keys); + auto parsed_desc = Parse(descriptor, keys, /* require_checksum = */ true); if (!parsed_desc) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Descriptor is invalid"); } diff --git a/test/functional/rpc_deriveaddresses.py b/test/functional/rpc_deriveaddresses.py --- a/test/functional/rpc_deriveaddresses.py +++ b/test/functional/rpc_deriveaddresses.py @@ -4,6 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the deriveaddresses rpc call.""" from test_framework.test_framework import BitcoinTestFramework +from test_framework.descriptors import descsum_create from test_framework.util import assert_equal, assert_raises_rpc_error @@ -16,18 +17,22 @@ assert_raises_rpc_error(-5, "Invalid descriptor", self.nodes[0].deriveaddresses, "a") - descriptor = "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)" + descriptor = "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)#rdfjd0a9" address = "bchreg:qzgrvmwc8vevauc25j86hgfpduz8j98yvvyr0qx0ew" - assert_equal(self.nodes[0].deriveaddresses(descriptor), [address]) - descriptor_pubkey = "pkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0)" - address = "bchreg:qzgrvmwc8vevauc25j86hgfpduz8j98yvvyr0qx0ew" + descriptor = descriptor[:-9] + assert_raises_rpc_error(-5, + "Invalid descriptor", + self.nodes[0].deriveaddresses, + descriptor) + descriptor_pubkey = "pkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0)#7st8eans" + address = "bchreg:qzgrvmwc8vevauc25j86hgfpduz8j98yvvyr0qx0ew" assert_equal(self.nodes[0].deriveaddresses( descriptor_pubkey), [address]) - ranged_descriptor = "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)" + ranged_descriptor = "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)#77vpsvm5" assert_equal( self.nodes[0].deriveaddresses(ranged_descriptor, 0, 2), [address, "bchreg:qz7mjsvr6gglnl389gnfxmqx0asxp0hcvqjx829c6k", "bchreg:qq9q9wefpjzuna7qhuzz7rvck9tuhrzp3gvrzd8kx2"]) @@ -36,7 +41,8 @@ -8, "Range should not be specified for an un-ranged descriptor", self.nodes[0].deriveaddresses, - "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)", + descsum_create( + "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"), 0, 2) @@ -44,20 +50,22 @@ -8, "Range must be specified for a ranged descriptor", self.nodes[0].deriveaddresses, - "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)") + descsum_create("pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)")) assert_raises_rpc_error( -8, "Missing range end parameter", self.nodes[0].deriveaddresses, - "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", + descsum_create( + "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"), 0) assert_raises_rpc_error( -8, "Range end should be equal to or greater than begin", self.nodes[0].deriveaddresses, - "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", + descsum_create( + "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"), 2, 0) @@ -65,21 +73,25 @@ -8, "Range should be greater or equal than 0", self.nodes[0].deriveaddresses, - "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", + descsum_create( + "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)"), -1, 0) - combo_descriptor = "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)" + combo_descriptor = descsum_create( + "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)") assert_equal(self.nodes[0].deriveaddresses( combo_descriptor), [address, address]) - hardened_without_privkey_descriptor = "pkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1'/1/0)" + hardened_without_privkey_descriptor = descsum_create( + "pkh(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1'/1/0)") assert_raises_rpc_error(-5, "Cannot derive script without private keys", self.nodes[0].deriveaddresses, hardened_without_privkey_descriptor) - bare_multisig_descriptor = "multi(1, tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0, tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1)" + bare_multisig_descriptor = descsum_create( + "multi(1, tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0, tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1)") assert_raises_rpc_error(-5, "Descriptor does not have a corresponding address", self.nodes[0].deriveaddresses, diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py --- a/test/functional/wallet_importmulti.py +++ b/test/functional/wallet_importmulti.py @@ -20,6 +20,7 @@ OP_NOP, ) from test_framework.test_framework import BitcoinTestFramework +from test_framework.descriptors import descsum_create from test_framework.util import ( assert_equal, assert_greater_than, @@ -466,13 +467,25 @@ "timestamp": "" }]) + # Test that importing of a P2PKH address via descriptor without + # checksum fails + key = get_key(self.nodes[0]) + self.log.info( + "Should fail to import a p2pkh address from descriptor with no checksum") + self.test_importmulti({"desc": "pkh(" + key.pubkey + ")", + "timestamp": "now", + "label": "Descriptor import test"}, + success=False, + error_code=-5, + error_message='Descriptor is invalid') + # Test importing of a P2PKH address via descriptor key = get_key(self.nodes[0]) self.log.info("Should import a p2pkh address from descriptor") - self.test_importmulti({"desc": "pkh(" + key.pubkey + ")", + self.test_importmulti({"desc": descsum_create("pkh(" + key.pubkey + ")"), "timestamp": "now", "label": "Descriptor import test"}, - True, + success=True, warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) test_address(self.nodes[1], key.p2pkh_addr, @@ -484,7 +497,7 @@ key = get_key(self.nodes[0]) self.log.info( "Import should fail if both scriptPubKey and desc are provided") - self.test_importmulti({"desc": "pkh(" + key.pubkey + ")", + self.test_importmulti({"desc": descsum_create("pkh(" + key.pubkey + ")"), "scriptPubKey": {"address": key.p2pkh_addr}, "timestamp": "now"}, success=False, @@ -504,7 +517,7 @@ key1 = get_key(self.nodes[0]) key2 = get_key(self.nodes[0]) self.log.info("Should import a 1-of-2 bare multisig from descriptor") - self.test_importmulti({"desc": "multi(1," + key1.pubkey + "," + key2.pubkey + ")", + self.test_importmulti({"desc": descsum_create("multi(1," + key1.pubkey + "," + key2.pubkey + ")"), "timestamp": "now"}, success=True, warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) @@ -526,7 +539,7 @@ pub_fpr = info['hdmasterfingerprint'] result = self.nodes[0].importmulti( [{ - 'desc': "pkh([" + pub_fpr + pub_keypath[1:] + "]" + pub + ")", + 'desc': descsum_create("pkh([" + pub_fpr + pub_keypath[1:] + "]" + pub + ")"), "timestamp": "now", }] ) @@ -544,7 +557,7 @@ priv_fpr = info['hdmasterfingerprint'] result = self.nodes[0].importmulti( [{ - 'desc': "pkh([" + priv_fpr + priv_keypath[1:] + "]" + priv + ")", + 'desc': descsum_create("pkh([" + priv_fpr + priv_keypath[1:] + "]" + priv + ")"), "timestamp": "now", }] ) @@ -594,12 +607,12 @@ pub2 = self.nodes[0].getaddressinfo(addr2)['pubkey'] result = wrpc.importmulti( [{ - 'desc': 'pkh(' + pub1 + ')', + 'desc': descsum_create('pkh(' + pub1 + ')'), 'keypool': True, "timestamp": "now", }, { - 'desc': 'pkh(' + pub2 + ')', + 'desc': descsum_create('pkh(' + pub2 + ')'), 'keypool': True, "timestamp": "now", }] @@ -622,13 +635,13 @@ pub2 = self.nodes[0].getaddressinfo(addr2)['pubkey'] result = wrpc.importmulti( [{ - 'desc': 'pkh(' + pub1 + ')', + 'desc': descsum_create('pkh(' + pub1 + ')'), 'keypool': True, 'internal': True, "timestamp": "now", }, { - 'desc': 'pkh(' + pub2 + ')', + 'desc': descsum_create('pkh(' + pub2 + ')'), 'keypool': True, 'internal': True, "timestamp": "now", @@ -651,7 +664,7 @@ pub2 = self.nodes[0].getaddressinfo(addr2)['pubkey'] result = wrpc.importmulti( [{ - 'desc': 'sh(multi(2,' + pub1 + ',' + pub2 + '))', + 'desc': descsum_create('sh(multi(2,' + pub1 + ',' + pub2 + '))'), 'keypool': True, "timestamp": "now", }] @@ -666,7 +679,7 @@ assert wrpc.getwalletinfo()['private_keys_enabled'] result = wrpc.importmulti( [{ - 'desc': 'pkh(' + pub1 + ')', + 'desc': descsum_create('pkh(' + pub1 + ')'), 'keypool': True, "timestamp": "now", }] @@ -691,7 +704,7 @@ ] result = wrpc.importmulti( [{ - 'desc': 'pkh([80002067/0h/0h]' + xpub + '/*)', + 'desc': descsum_create('pkh([80002067/0h/0h]' + xpub + '/*)'), 'keypool': True, 'timestamp': 'now', 'range': {'start': 0, 'end': 4}