Skip to content

Commit dc9d662

Browse files
committed
Merge bitcoin/bitcoin#25235: GetExternalSigner(): fail if multiple signers are found
292b1a3 GetExternalSigner(): fail if multiple signers are found (amadeuszpawlik) Pull request description: If there are multiple external signers, `GetExternalSigner()` will just pick the first one in the list. If the user has two or more hardware wallets connected at the same time, he might not notice this. This PR adds a check and fails with suitable message, forcing the user to disconnect all but one external signer, so that there is no ambiguity as to which external signer was used. ACKs for top commit: Sjors: tACK 292b1a3 achow101: ACK 292b1a3 Tree-SHA512: e2a41d3eecc607d4f94e708614bed0f3545f7abba85f300c5a5f0d3d17d72c815259734accc5ca370953eacd290f27894ba2c18016f5e9584cd50fa1ec2fbb0b
2 parents aca0200 + 292b1a3 commit dc9d662

File tree

7 files changed

+54
-7
lines changed

7 files changed

+54
-7
lines changed

src/qt/walletcontroller.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,10 @@ void CreateWalletActivity::create()
297297
} catch (const std::runtime_error& e) {
298298
QMessageBox::critical(nullptr, tr("Can't list signers"), e.what());
299299
}
300+
if (signers.size() > 1) {
301+
QMessageBox::critical(nullptr, tr("Too many external signers found"), QString::fromStdString("More than one external signer found. Please connect only one at a time."));
302+
signers.clear();
303+
}
300304
m_create_wallet_dialog->setSigners(signers);
301305

302306
m_create_wallet_dialog->setWindowModality(Qt::ApplicationModal);

src/wallet/external_signer_scriptpubkeyman.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ ExternalSigner ExternalSignerScriptPubKeyMan::GetExternalSigner() {
4545
std::vector<ExternalSigner> signers;
4646
ExternalSigner::Enumerate(command, signers, Params().NetworkIDString());
4747
if (signers.empty()) throw std::runtime_error(std::string(__func__) + ": No external signers found");
48-
// TODO: add fingerprint argument in case of multiple signers
48+
// TODO: add fingerprint argument instead of failing in case of multiple signers.
49+
if (signers.size() > 1) throw std::runtime_error(std::string(__func__) + ": More than one external signer found. Please connect only one at a time.");
4950
return signers[0];
5051
}
5152

test/functional/mocks/invalid_signer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def perform_pre_checks():
1818
sys.exit(int(mock_result[0]))
1919

2020
def enumerate(args):
21-
sys.stdout.write(json.dumps([{"fingerprint": "b3c19bfc", "type": "trezor", "model": "trezor_t"}, {"fingerprint": "00000002"}]))
21+
sys.stdout.write(json.dumps([{"fingerprint": "b3c19bfc", "type": "trezor", "model": "trezor_t"}]))
2222

2323
def getdescriptors(args):
2424
xpub_pkh = "xpub6CRhJvXV8x2AKWvqi1ZSMFU6cbxzQiYrv3dxSUXCawjMJ1JzpqVsveH4way1yCmJm29KzH1zrVZmVwes4Qo6oXVE1HFn4fdiKrYJngqFFc6"
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2022 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 argparse
7+
import json
8+
import sys
9+
10+
def enumerate(args):
11+
sys.stdout.write(json.dumps([{"fingerprint": "00000001", "type": "trezor", "model": "trezor_t"},
12+
{"fingerprint": "00000002", "type": "trezor", "model": "trezor_one"}]))
13+
14+
parser = argparse.ArgumentParser(prog='./multi_signers.py', description='External multi-signer mock')
15+
16+
subparsers = parser.add_subparsers(description='Commands', dest='command')
17+
subparsers.required = True
18+
19+
parser_enumerate = subparsers.add_parser('enumerate', help='list available signers')
20+
parser_enumerate.set_defaults(func=enumerate)
21+
22+
23+
if not sys.stdin.isatty():
24+
buffer = sys.stdin.read()
25+
if buffer and buffer.rstrip() != "":
26+
sys.argv.extend(buffer.rstrip().split(" "))
27+
28+
args = parser.parse_args()
29+
30+
args.func(args)

test/functional/mocks/signer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def perform_pre_checks():
1818
sys.exit(int(mock_result[0]))
1919

2020
def enumerate(args):
21-
sys.stdout.write(json.dumps([{"fingerprint": "00000001", "type": "trezor", "model": "trezor_t"}, {"fingerprint": "00000002"}]))
21+
sys.stdout.write(json.dumps([{"fingerprint": "00000001", "type": "trezor", "model": "trezor_t"}]))
2222

2323
def getdescriptors(args):
2424
xpub = "tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B"

test/functional/rpc_signer.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,7 @@ def run_test(self):
7777
)
7878
self.clear_mock_result(self.nodes[1])
7979

80-
result = self.nodes[1].enumeratesigners()
81-
assert_equal(len(result['signers']), 2)
82-
assert_equal(result['signers'][0]["fingerprint"], "00000001")
83-
assert_equal(result['signers'][0]["name"], "trezor_t")
80+
assert_equal({'fingerprint': '00000001', 'name': 'trezor_t'} in self.nodes[1].enumeratesigners()['signers'], True)
8481

8582
if __name__ == '__main__':
8683
RPCSignerTest().main()

test/functional/wallet_signer.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ def mock_invalid_signer_path(self):
3232
else:
3333
return path
3434

35+
def mock_multi_signers_path(self):
36+
path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'mocks', 'multi_signers.py')
37+
if platform.system() == "Windows":
38+
return "py " + path
39+
else:
40+
return path
41+
3542
def set_test_params(self):
3643
self.num_nodes = 2
3744
# The experimental syscall sandbox feature (-sandbox) is not compatible with -signer (which
@@ -58,6 +65,8 @@ def run_test(self):
5865
self.test_valid_signer()
5966
self.restart_node(1, [f"-signer={self.mock_invalid_signer_path()}", "-keypool=10"])
6067
self.test_invalid_signer()
68+
self.restart_node(1, [f"-signer={self.mock_multi_signers_path()}", "-keypool=10"])
69+
self.test_multiple_signers()
6170

6271
def test_valid_signer(self):
6372
self.log.debug(f"-signer={self.mock_signer_path()}")
@@ -212,5 +221,11 @@ def test_invalid_signer(self):
212221
self.log.info('Test invalid external signer')
213222
assert_raises_rpc_error(-1, "Invalid descriptor", self.nodes[1].createwallet, wallet_name='hww_invalid', disable_private_keys=True, descriptors=True, external_signer=True)
214223

224+
def test_multiple_signers(self):
225+
self.log.debug(f"-signer={self.mock_multi_signers_path()}")
226+
self.log.info('Test multiple external signers')
227+
228+
assert_raises_rpc_error(-1, "GetExternalSigner: More than one external signer found", self.nodes[1].createwallet, wallet_name='multi_hww', disable_private_keys=True, descriptors=True, external_signer=True)
229+
215230
if __name__ == '__main__':
216231
WalletSignerTest().main()

0 commit comments

Comments
 (0)