Skip to content

Commit 7ebc7c0

Browse files
committed
wallet: ExternalSigner: add GetDescriptors method
1 parent fc5da52 commit 7ebc7c0

File tree

7 files changed

+130
-21
lines changed

7 files changed

+130
-21
lines changed

src/wallet/external_signer.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,9 @@ bool ExternalSigner::Enumerate(const std::string& command, std::vector<ExternalS
5555
return true;
5656
}
5757

58+
UniValue ExternalSigner::GetDescriptors(int account)
59+
{
60+
return RunCommandParseJSON(m_command + " --fingerprint \"" + m_fingerprint + "\"" + NetworkArg() + " getdescriptors --account " + strprintf("%d", account));
61+
}
62+
5863
#endif

src/wallet/external_signer.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ class ExternalSigner
4949
//! @param[out] success Boolean
5050
static bool Enumerate(const std::string& command, std::vector<ExternalSigner>& signers, std::string chain, bool ignore_errors = false);
5151

52+
//! Get receive and change Descriptor(s) from device for a given account.
53+
//! Calls `<command> getdescriptors --account <account>`
54+
//! @param[in] account which BIP32 account to use (e.g. `m/44'/0'/account'`)
55+
//! @param[out] UniValue see doc/external-signer.md
56+
UniValue GetDescriptors(int account);
57+
5258
#endif
5359
};
5460

src/wallet/external_signer_scriptpubkeyman.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan
2222
* Returns false if already setup or setup fails, true if setup is successful
2323
*/
2424
bool SetupDescriptor(std::unique_ptr<Descriptor>desc);
25+
26+
static ExternalSigner GetExternalSigner();
27+
2528
};
2629
#endif
2730

src/wallet/scriptpubkeyman.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,11 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
582582
//! Setup descriptors based on the given CExtkey
583583
bool SetupDescriptorGeneration(const CExtKey& master_key, OutputType addr_type);
584584

585+
/** Provide a descriptor at setup time
586+
* Returns false if already setup or setup fails, true if setup is successful
587+
*/
588+
bool SetupDescriptor(std::unique_ptr<Descriptor>desc);
589+
585590
bool HavePrivateKeys() const override;
586591

587592
int64_t GetOldestKeyPoolTime() const override;

src/wallet/wallet.cpp

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <key.h>
1515
#include <key_io.h>
1616
#include <optional.h>
17+
#include <outputtype.h>
1718
#include <policy/fees.h>
1819
#include <policy/policy.h>
1920
#include <primitives/block.h>
@@ -3864,7 +3865,7 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
38643865
walletInstance->SetupLegacyScriptPubKeyMan();
38653866
}
38663867

3867-
if (!(wallet_creation_flags & (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) {
3868+
if ((wallet_creation_flags & WALLET_FLAG_EXTERNAL_SIGNER) || !(wallet_creation_flags & (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) {
38683869
LOCK(walletInstance->cs_wallet);
38693870
if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
38703871
walletInstance->SetupDescriptorScriptPubKeyMans();
@@ -4488,32 +4489,65 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
44884489
{
44894490
AssertLockHeld(cs_wallet);
44904491

4491-
// Make a seed
4492-
CKey seed_key;
4493-
seed_key.MakeNewKey(true);
4494-
CPubKey seed = seed_key.GetPubKey();
4495-
assert(seed_key.VerifyPubKey(seed));
4492+
if (!IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) {
4493+
// Make a seed
4494+
CKey seed_key;
4495+
seed_key.MakeNewKey(true);
4496+
CPubKey seed = seed_key.GetPubKey();
4497+
assert(seed_key.VerifyPubKey(seed));
44964498

4497-
// Get the extended key
4498-
CExtKey master_key;
4499-
master_key.SetSeed(seed_key.begin(), seed_key.size());
4499+
// Get the extended key
4500+
CExtKey master_key;
4501+
master_key.SetSeed(seed_key.begin(), seed_key.size());
45004502

4501-
for (bool internal : {false, true}) {
4502-
for (OutputType t : OUTPUT_TYPES) {
4503-
auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, internal));
4504-
if (IsCrypted()) {
4505-
if (IsLocked()) {
4506-
throw std::runtime_error(std::string(__func__) + ": Wallet is locked, cannot setup new descriptors");
4503+
for (bool internal : {false, true}) {
4504+
for (OutputType t : OUTPUT_TYPES) {
4505+
auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, internal));
4506+
if (IsCrypted()) {
4507+
if (IsLocked()) {
4508+
throw std::runtime_error(std::string(__func__) + ": Wallet is locked, cannot setup new descriptors");
4509+
}
4510+
if (!spk_manager->CheckDecryptionKey(vMasterKey) && !spk_manager->Encrypt(vMasterKey, nullptr)) {
4511+
throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors");
4512+
}
45074513
}
4508-
if (!spk_manager->CheckDecryptionKey(vMasterKey) && !spk_manager->Encrypt(vMasterKey, nullptr)) {
4509-
throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors");
4514+
spk_manager->SetupDescriptorGeneration(master_key, t);
4515+
uint256 id = spk_manager->GetID();
4516+
m_spk_managers[id] = std::move(spk_manager);
4517+
AddActiveScriptPubKeyMan(id, t, internal);
4518+
}
4519+
}
4520+
} else {
4521+
#ifdef ENABLE_EXTERNAL_SIGNER
4522+
ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner();
4523+
4524+
// TODO: add account parameter
4525+
int account = 0;
4526+
UniValue signer_res = signer.GetDescriptors(account);
4527+
4528+
if (!signer_res.isObject()) throw std::runtime_error(std::string(__func__) + ": Unexpected result");
4529+
for (bool internal : {false, true}) {
4530+
const UniValue& descriptor_vals = find_value(signer_res, internal ? "internal" : "receive");
4531+
if (!descriptor_vals.isArray()) throw std::runtime_error(std::string(__func__) + ": Unexpected result");
4532+
for (const UniValue& desc_val : descriptor_vals.get_array().getValues()) {
4533+
std::string desc_str = desc_val.getValStr();
4534+
FlatSigningProvider keys;
4535+
std::string dummy_error;
4536+
std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, dummy_error, false);
4537+
if (!desc->GetOutputType()) {
4538+
continue;
45104539
}
4540+
OutputType t = *desc->GetOutputType();
4541+
auto spk_manager = std::unique_ptr<ExternalSignerScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, internal));
4542+
spk_manager->SetupDescriptor(std::move(desc));
4543+
uint256 id = spk_manager->GetID();
4544+
m_spk_managers[id] = std::move(spk_manager);
4545+
AddActiveScriptPubKeyMan(id, t, internal);
45114546
}
4512-
spk_manager->SetupDescriptorGeneration(master_key, t);
4513-
uint256 id = spk_manager->GetID();
4514-
m_spk_managers[id] = std::move(spk_manager);
4515-
AddActiveScriptPubKeyMan(id, t, internal);
45164547
}
4548+
#else
4549+
throw std::runtime_error(std::string(__func__) + ": Wallets with external signers require Boost::Process library.");
4550+
#endif
45174551
}
45184552
}
45194553

test/functional/mocks/signer.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,37 @@ def perform_pre_checks():
2020
def enumerate(args):
2121
sys.stdout.write(json.dumps([{"fingerprint": "00000001", "type": "trezor", "model": "trezor_t"}, {"fingerprint": "00000002"}]))
2222

23+
def getdescriptors(args):
24+
xpub = "tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B"
25+
26+
sys.stdout.write(json.dumps({
27+
"receive": [
28+
"pkh([00000001/44'/1'/" + args.account + "']" + xpub + "/0/*)#vt6w3l3j",
29+
"sh(wpkh([00000001/49'/1'/" + args.account + "']" + xpub + "/0/*))#r0grqw5x",
30+
"wpkh([00000001/84'/1'/" + args.account + "']" + xpub + "/0/*)#x30uthjs"
31+
],
32+
"internal": [
33+
"pkh([00000001/44'/1'/" + args.account + "']" + xpub + "/1/*)#all0v2p2",
34+
"sh(wpkh([00000001/49'/1'/" + args.account + "']" + xpub + "/1/*))#kwx4c3pe",
35+
"wpkh([00000001/84'/1'/" + args.account + "']" + xpub + "/1/*)#h92akzzg"
36+
]
37+
}))
38+
39+
2340
parser = argparse.ArgumentParser(prog='./signer.py', description='External signer mock')
41+
parser.add_argument('--fingerprint')
42+
parser.add_argument('--chain', default='main')
43+
2444
subparsers = parser.add_subparsers(description='Commands', dest='command')
2545
subparsers.required = True
2646

2747
parser_enumerate = subparsers.add_parser('enumerate', help='list available signers')
2848
parser_enumerate.set_defaults(func=enumerate)
2949

50+
parser_getdescriptors = subparsers.add_parser('getdescriptors')
51+
parser_getdescriptors.set_defaults(func=getdescriptors)
52+
parser_getdescriptors.add_argument('--account', metavar='account')
53+
3054
args = parser.parse_args()
3155

3256
perform_pre_checks()

test/functional/wallet_signer.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,5 +91,37 @@ def run_test(self):
9191
not_hww = self.nodes[1].get_wallet_rpc('not_hww')
9292
assert_raises_rpc_error(-8, "Wallet flag is immutable: external_signer", not_hww.setwalletflag, "external_signer", True)
9393

94+
# assert_raises_rpc_error(-4, "Multiple signers found, please specify which to use", wallet_name='not_hww', disable_private_keys=True, descriptors=True, external_signer=True)
95+
96+
# TODO: Handle error thrown by script
97+
# self.set_mock_result(self.nodes[1], "2")
98+
# assert_raises_rpc_error(-1, 'Unable to parse JSON',
99+
# self.nodes[1].createwallet, wallet_name='not_hww2', disable_private_keys=True, descriptors=True, external_signer=False
100+
# )
101+
# self.clear_mock_result(self.nodes[1])
102+
103+
assert_equal(hww.getwalletinfo()["keypoolsize"], 3)
104+
105+
address1 = hww.getnewaddress(address_type="bech32")
106+
assert_equal(address1, "bcrt1qm90ugl4d48jv8n6e5t9ln6t9zlpm5th68x4f8g")
107+
address_info = hww.getaddressinfo(address1)
108+
assert_equal(address_info['solvable'], True)
109+
assert_equal(address_info['ismine'], True)
110+
assert_equal(address_info['hdkeypath'], "m/84'/1'/0'/0/0")
111+
112+
address2 = hww.getnewaddress(address_type="p2sh-segwit")
113+
assert_equal(address2, "2N2gQKzjUe47gM8p1JZxaAkTcoHPXV6YyVp")
114+
address_info = hww.getaddressinfo(address2)
115+
assert_equal(address_info['solvable'], True)
116+
assert_equal(address_info['ismine'], True)
117+
assert_equal(address_info['hdkeypath'], "m/49'/1'/0'/0/0")
118+
119+
address3 = hww.getnewaddress(address_type="legacy")
120+
assert_equal(address3, "n1LKejAadN6hg2FrBXoU1KrwX4uK16mco9")
121+
address_info = hww.getaddressinfo(address3)
122+
assert_equal(address_info['solvable'], True)
123+
assert_equal(address_info['ismine'], True)
124+
assert_equal(address_info['hdkeypath'], "m/44'/1'/0'/0/0")
125+
94126
if __name__ == '__main__':
95127
SignerTest().main()

0 commit comments

Comments
 (0)