Skip to content

Commit 09ad512

Browse files
committed
Merge bitcoin/bitcoin#23628: Check descriptors returned by external signers
5493e92 Check descriptors returned by external signers (sstone) Pull request description: Check that descriptors returned by external signers have been parsed properly when creating a new wallet. See bitcoin/bitcoin#23627 for context. The problem is that parsing an invalid descriptor will return `null` which is not checked for in `CWallet::SetupDescriptorScriptPubKeyMans()`. I'm not completely sure what the best fix is since there several strategies for dealing with errors in the current codebase but the proposed fix is very simple and consistent with other validation checks in `CWallet::SetupDescriptorScriptPubKeyMans()`. ACKs for top commit: jamesob: Code review ACK bitcoin/bitcoin@5493e92 achow101: ACK 5493e92 Tree-SHA512: 63259f4aa519405a86c554b6813efdb741314bdaa18bf005b70ea8bb92a27abc6e2b65f7c584641dc257fc78a6499f42b51b5310c243e611c4663430dccf3d04
2 parents 2f26d8e + 5493e92 commit 09ad512

File tree

3 files changed

+87
-2
lines changed

3 files changed

+87
-2
lines changed

src/wallet/wallet.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3208,8 +3208,11 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
32083208
for (const UniValue& desc_val : descriptor_vals.get_array().getValues()) {
32093209
std::string desc_str = desc_val.getValStr();
32103210
FlatSigningProvider keys;
3211-
std::string dummy_error;
3212-
std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, dummy_error, false);
3211+
std::string desc_error;
3212+
std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, desc_error, false);
3213+
if (desc == nullptr) {
3214+
throw std::runtime_error(std::string(__func__) + ": Invalid descriptor \"" + desc_str + "\" (" + desc_error + ")");
3215+
}
32133216
if (!desc->GetOutputType()) {
32143217
continue;
32153218
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2018-2021 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
6+
import os
7+
import sys
8+
import argparse
9+
import json
10+
11+
def perform_pre_checks():
12+
mock_result_path = os.path.join(os.getcwd(), "mock_result")
13+
if(os.path.isfile(mock_result_path)):
14+
with open(mock_result_path, "r", encoding="utf8") as f:
15+
mock_result = f.read()
16+
if mock_result[0]:
17+
sys.stdout.write(mock_result[2:])
18+
sys.exit(int(mock_result[0]))
19+
20+
def enumerate(args):
21+
sys.stdout.write(json.dumps([{"fingerprint": "b3c19bfc", "type": "trezor", "model": "trezor_t"}, {"fingerprint": "00000002"}]))
22+
23+
def getdescriptors(args):
24+
xpub_pkh = "xpub6CRhJvXV8x2AKWvqi1ZSMFU6cbxzQiYrv3dxSUXCawjMJ1JzpqVsveH4way1yCmJm29KzH1zrVZmVwes4Qo6oXVE1HFn4fdiKrYJngqFFc6"
25+
xpub_sh = "xpub6CoNoq3Tg4tGSpom2BSwL42gy864KHo3TXkHxLxBbhvCkgmdVXADQmiHbLZhX3Me1cYhRx7s25Lpm4LnT5zu395ANHsXB2QvT9tqJDAibTN"
26+
xpub_wpkh = "xpub6DUcLgY1DfgDy2RV6q4djwwsLitaoZDumbribqrR8mP78fEtgZa1XEsqT5MWQ7gwLwKsTQPT28XLoVE5A97rDNTwMXjmzPaNijoCApCbWvp"
27+
28+
sys.stdout.write(json.dumps({
29+
"receive": [
30+
"pkh([b3c19bfc/44'/1'/" + args.account + "']" + xpub_pkh + "/0/*)#h26nxtl9",
31+
"sh(wpkh([b3c19bfc/49'/1'/" + args.account + "']" + xpub_sh + "/0/*))#32ry02yp",
32+
"wpkh([b3c19bfc/84'/1'/" + args.account + "']" + xpub_wpkh + "/0/*)#jftn8ppv"
33+
],
34+
"internal": [
35+
"pkh([b3c19bfc/44'/1'/" + args.account + "']" + xpub_pkh + "/1/*)#x7ljm70a",
36+
"sh(wpkh([b3c19bfc/49'/1'/" + args.account + "']" + xpub_sh + "/1/*))#ytdjh437",
37+
"wpkh([b3c19bfc/84'/1'/" + args.account + "']" + xpub_wpkh + "/1/*)#rawj6535"
38+
]
39+
}))
40+
41+
parser = argparse.ArgumentParser(prog='./invalid_signer.py', description='External invalid signer mock')
42+
parser.add_argument('--fingerprint')
43+
parser.add_argument('--chain', default='main')
44+
parser.add_argument('--stdin', action='store_true')
45+
46+
subparsers = parser.add_subparsers(description='Commands', dest='command')
47+
subparsers.required = True
48+
49+
parser_enumerate = subparsers.add_parser('enumerate', help='list available signers')
50+
parser_enumerate.set_defaults(func=enumerate)
51+
52+
parser_getdescriptors = subparsers.add_parser('getdescriptors')
53+
parser_getdescriptors.set_defaults(func=getdescriptors)
54+
parser_getdescriptors.add_argument('--account', metavar='account')
55+
56+
if not sys.stdin.isatty():
57+
buffer = sys.stdin.read()
58+
if buffer and buffer.rstrip() != "":
59+
sys.argv.extend(buffer.rstrip().split(" "))
60+
61+
args = parser.parse_args()
62+
63+
perform_pre_checks()
64+
65+
args.func(args)

test/functional/wallet_signer.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ def mock_signer_path(self):
2525
else:
2626
return path
2727

28+
def mock_invalid_signer_path(self):
29+
path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'mocks', 'invalid_signer.py')
30+
if platform.system() == "Windows":
31+
return "py " + path
32+
else:
33+
return path
34+
2835
def set_test_params(self):
2936
self.num_nodes = 2
3037
# The experimental syscall sandbox feature (-sandbox) is not compatible with -signer (which
@@ -48,6 +55,11 @@ def clear_mock_result(self, node):
4855
os.remove(os.path.join(node.cwd, "mock_result"))
4956

5057
def run_test(self):
58+
self.test_valid_signer()
59+
self.restart_node(1, [f"-signer={self.mock_invalid_signer_path()}", "-keypool=10"])
60+
self.test_invalid_signer()
61+
62+
def test_valid_signer(self):
5163
self.log.debug(f"-signer={self.mock_signer_path()}")
5264

5365
# Create new wallets for an external signer.
@@ -187,5 +199,10 @@ def run_test(self):
187199
# )
188200
# self.clear_mock_result(self.nodes[4])
189201

202+
def test_invalid_signer(self):
203+
self.log.debug(f"-signer={self.mock_invalid_signer_path()}")
204+
self.log.info('Test invalid external signer')
205+
assert_raises_rpc_error(-1, "Invalid descriptor", self.nodes[1].createwallet, wallet_name='hww_invalid', disable_private_keys=True, descriptors=True, external_signer=True)
206+
190207
if __name__ == '__main__':
191208
WalletSignerTest().main()

0 commit comments

Comments
 (0)