Skip to content

Commit 223fc24

Browse files
committed
Merge bitcoin#31603: descriptor: check whitespace in keys within fragments
21e9d39 docs: add release notes for 31603 (brunoerg) a8b548d test: `getdescriptorinfo`/`importdescriptors` with whitespace in pubkeys (brunoerg) c7afca3 test: descriptor: check whitespace into keys (brunoerg) cb722a3 descriptor: check whitespace in ParsePubkeyInner (brunoerg) 5085669 test: fix descriptors in `ismine_tests` (brunoerg) Pull request description: Currently, we successfully parse descriptors which contains spaces in the beginning or end of the public/private key within a fragment (e.g. `pk( KEY)`, `pk(KEY )` or `pk( KEY )`). I have noticed that one of the reasons is that the `DecodeBase58` function simply ignore these whitespaces. This PR changes the `ParsePubkeyInner ` to reject pubkeys that contain a whitespace at the beginning and/or at the end. We will only check the whitespace in some RPCs (e.g. `importdescriptors`), but an already imported descriptor won't be affected by this check, especially because we store descriptors from `ToString`. For context: brunoerg/bitcoinfuzz#72 ACKs for top commit: rkrux: tACK 21e9d39 darosior: re-ACK 21e9d39 sipa: utACK 21e9d39 Tree-SHA512: 54f48a89a235517e5cdc29a46dceeb7dabbee93c7616a166288ff3f90131808eb0ece43b0797a11fe827a5f7bd51d65e3e75c16789b0a42020934cabb684cc8f
2 parents d61a847 + 21e9d39 commit 223fc24

File tree

6 files changed

+41
-5
lines changed

6 files changed

+41
-5
lines changed

doc/release-notes-31603.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Updated RPCs
2+
--------
3+
4+
- Any RPC in which one of the parameters are descriptors will throw an error
5+
if the provided descriptor contains a whitespace at the beginning or the end
6+
of the public key within a fragment - e.g. `pk( KEY)` or `pk(KEY )`.

src/script/descriptor.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -1509,6 +1509,10 @@ std::vector<std::unique_ptr<PubkeyProvider>> ParsePubkeyInner(uint32_t key_exp_i
15091509
error = "No key provided";
15101510
return {};
15111511
}
1512+
if (IsSpace(str.front()) || IsSpace(str.back())) {
1513+
error = strprintf("Key '%s' is invalid due to whitespace", str);
1514+
return {};
1515+
}
15121516
if (split.size() == 1) {
15131517
if (IsHex(str)) {
15141518
std::vector<unsigned char> data = ParseHex(str);

src/test/descriptor_tests.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,11 @@ BOOST_AUTO_TEST_CASE(descriptor_test)
938938
CheckUnparsable("sh(sh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "sh(sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "Can only have sh() at top level"); // Cannot embed P2SH inside P2SH
939939
CheckUnparsable("wsh(wsh(pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)))", "wsh(wsh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))", "Can only have wsh() at top level or inside sh()"); // Cannot embed P2WSH inside P2WSH
940940

941+
// Check for whitespace into keys
942+
CheckUnparsable("", "multi(1, L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "Multi: Key ' L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1' is invalid due to whitespace");
943+
CheckUnparsable("", "pk(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1 )", "pk(): Key 'L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1 ' is invalid due to whitespace");
944+
CheckUnparsable("", "pk( L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1 )", "pk(): Key ' L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1 ' is invalid due to whitespace");
945+
941946
// Checksums
942947
Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))#ggrsrxfy", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#tjg09x5t", "sh(multi(2,[00000000/111h/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))#hgmsckna", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, /*op_desc_id=*/uint256{"9339b7bfbe8cfd9d0d55819778ef77f52e5786e85b4c83be8a0d5b976e033f4c"}, {{0x8000006FUL,222},{0}});
943948
Check("sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", "sh(multi(2,[00000000/111h/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}}, OutputType::LEGACY, /*op_desc_id=*/uint256{"9339b7bfbe8cfd9d0d55819778ef77f52e5786e85b4c83be8a0d5b976e033f4c"}, {{0x8000006FUL,222},{0}});

src/wallet/test/ismine_tests.cpp

+5-5
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
406406
// scriptPubKey multisig - Descriptor
407407
{
408408
CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
409-
std::string desc_str = "multi(2, " + EncodeSecret(uncompressedKey) + ", " + EncodeSecret(keys[1]) + ")";
409+
std::string desc_str = "multi(2," + EncodeSecret(uncompressedKey) + "," + EncodeSecret(keys[1]) + ")";
410410

411411
auto spk_manager = CreateDescriptor(keystore, desc_str, true);
412412

@@ -442,7 +442,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
442442
{
443443
CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
444444

445-
std::string desc_str = "sh(multi(2, " + EncodeSecret(uncompressedKey) + ", " + EncodeSecret(keys[1]) + "))";
445+
std::string desc_str = "sh(multi(2," + EncodeSecret(uncompressedKey) + "," + EncodeSecret(keys[1]) + "))";
446446

447447
auto spk_manager = CreateDescriptor(keystore, desc_str, true);
448448

@@ -485,7 +485,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
485485
{
486486
CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
487487

488-
std::string desc_str = "wsh(multi(2, " + EncodeSecret(keys[0]) + ", " + EncodeSecret(keys[1]) + "))";
488+
std::string desc_str = "wsh(multi(2," + EncodeSecret(keys[0]) + "," + EncodeSecret(keys[1]) + "))";
489489

490490
auto spk_manager = CreateDescriptor(keystore, desc_str, true);
491491

@@ -528,7 +528,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
528528
{
529529
CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
530530

531-
std::string desc_str = "wsh(multi(2, " + EncodeSecret(uncompressedKey) + ", " + EncodeSecret(keys[1]) + "))";
531+
std::string desc_str = "wsh(multi(2," + EncodeSecret(uncompressedKey) + "," + EncodeSecret(keys[1]) + "))";
532532

533533
auto spk_manager = CreateDescriptor(keystore, desc_str, false);
534534
BOOST_CHECK_EQUAL(spk_manager, nullptr);
@@ -568,7 +568,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
568568
{
569569
CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
570570

571-
std::string desc_str = "sh(wsh(multi(2, " + EncodeSecret(keys[0]) + ", " + EncodeSecret(keys[1]) + ")))";
571+
std::string desc_str = "sh(wsh(multi(2," + EncodeSecret(keys[0]) + "," + EncodeSecret(keys[1]) + ")))";
572572

573573
auto spk_manager = CreateDescriptor(keystore, desc_str, true);
574574

test/functional/rpc_getdescriptorinfo.py

+7
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
assert_equal,
1212
assert_raises_rpc_error,
1313
)
14+
from test_framework.wallet_util import generate_keypair
1415

1516

1617
class DescriptorTest(BitcoinTestFramework):
@@ -36,6 +37,12 @@ def run_test(self):
3637
assert_raises_rpc_error(-1, 'getdescriptorinfo', self.nodes[0].getdescriptorinfo)
3738
assert_raises_rpc_error(-3, 'JSON value of type number is not of expected type string', self.nodes[0].getdescriptorinfo, 1)
3839
assert_raises_rpc_error(-5, "'' is not a valid descriptor function", self.nodes[0].getdescriptorinfo, "")
40+
assert_raises_rpc_error(-5, "pk(): Key ' 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798' is invalid due to whitespace", self.nodes[0].getdescriptorinfo, "pk( 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)")
41+
assert_raises_rpc_error(-5, "pk(): Key '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 ' is invalid due to whitespace", self.nodes[0].getdescriptorinfo, "pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 )")
42+
assert_raises_rpc_error(-5, "Multi: Key ' 03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7' is invalid due to whitespace", self.nodes[0].getdescriptorinfo, "wsh(multi(2, 03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))")
43+
assert_raises_rpc_error(-5, "Multi: Key ' 03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb' is invalid due to whitespace", self.nodes[0].getdescriptorinfo, "wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7, 03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))")
44+
priv_key = generate_keypair(wif=True)[0]
45+
assert_raises_rpc_error(-5, f"pk(): Key ' {priv_key}' is invalid due to whitespace", self.nodes[0].getdescriptorinfo, f"pk( {priv_key})")
3946

4047
# P2PK output with the specified public key.
4148
self.test_desc('pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)', isrange=False, issolvable=True, hasprivatekeys=False)

test/functional/wallet_importdescriptors.py

+14
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,20 @@ def run_test(self):
128128
assert_equal(info["ismine"], True)
129129
assert_equal(info["ischange"], True)
130130

131+
self.log.info("Should not import a descriptor with an invalid public key due to whitespace")
132+
self.test_importdesc({"desc": descsum_create("pkh( " + key.pubkey + ")"),
133+
"timestamp": "now",
134+
"internal": True},
135+
error_code=-5,
136+
error_message=f"pkh(): Key ' {key.pubkey}' is invalid due to whitespace",
137+
success=False)
138+
self.test_importdesc({"desc": descsum_create("pkh(" + key.pubkey + " )"),
139+
"timestamp": "now",
140+
"internal": True},
141+
error_code=-5,
142+
error_message=f"pkh(): Key '{key.pubkey} ' is invalid due to whitespace",
143+
success=False)
144+
131145
# # Test importing of a P2SH-P2WPKH descriptor
132146
key = get_generate_key()
133147
self.log.info("Should not import a p2sh-p2wpkh descriptor without checksum")

0 commit comments

Comments
 (0)