Skip to content

Commit 245b445

Browse files
committed
rpc: signerdisplayaddress
1 parent 7ebc7c0 commit 245b445

9 files changed

+134
-0
lines changed

src/wallet/external_signer.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ bool ExternalSigner::Enumerate(const std::string& command, std::vector<ExternalS
5555
return true;
5656
}
5757

58+
UniValue ExternalSigner::DisplayAddress(const std::string& descriptor) const
59+
{
60+
return RunCommandParseJSON(m_command + " --fingerprint \"" + m_fingerprint + "\"" + NetworkArg() + " displayaddress --desc \"" + descriptor + "\"");
61+
}
62+
5863
UniValue ExternalSigner::GetDescriptors(int account)
5964
{
6065
return RunCommandParseJSON(m_command + " --fingerprint \"" + m_fingerprint + "\"" + NetworkArg() + " getdescriptors --account " + strprintf("%d", account));

src/wallet/external_signer.h

+5
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ 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+
//! Display address on the device. Calls `<command> displayaddress --desc <descriptor>`.
53+
//! @param[in] descriptor Descriptor specifying which address to display.
54+
//! Must include a public key or xpub, as well as key origin.
55+
UniValue DisplayAddress(const std::string& descriptor) const;
56+
5257
//! Get receive and change Descriptor(s) from device for a given account.
5358
//! Calls `<command> getdescriptors --account <account>`
5459
//! @param[in] account which BIP32 account to use (e.g. `m/44'/0'/account'`)

src/wallet/external_signer_scriptpubkeyman.cpp

+35
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,39 @@ ExternalSigner ExternalSignerScriptPubKeyMan::GetExternalSigner() {
4343
return signers[0];
4444
}
4545

46+
bool ExternalSignerScriptPubKeyMan::DisplayAddress(const CScript scriptPubKey, const ExternalSigner &signer) const
47+
{
48+
// TODO: avoid the need to infer a descriptor from inside a descriptor wallet
49+
auto provider = GetSolvingProvider(scriptPubKey);
50+
auto descriptor = InferDescriptor(scriptPubKey, *provider);
51+
52+
signer.DisplayAddress(descriptor->ToString());
53+
// TODO inspect result
54+
return true;
55+
}
56+
57+
// If sign is true, transaction must previously have been filled
58+
TransactionError ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbt, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const
59+
{
60+
if (!sign) {
61+
return DescriptorScriptPubKeyMan::FillPSBT(psbt, sighash_type, false, bip32derivs, n_signed);
62+
}
63+
64+
// Already complete if every input is now signed
65+
bool complete = true;
66+
for (const auto& input : psbt.inputs) {
67+
// TODO: for multisig wallets, we should only care if all _our_ inputs are signed
68+
complete &= PSBTInputSigned(input);
69+
}
70+
if (complete) return TransactionError::OK;
71+
72+
std::string strFailReason;
73+
if(!GetExternalSigner().SignTransaction(psbt, strFailReason)) {
74+
tfm::format(std::cerr, "Failed to sign: %s\n", strFailReason);
75+
return TransactionError::EXTERNAL_SIGNER_FAILED;
76+
}
77+
FinalizePSBT(psbt); // This won't work in a multisig setup
78+
return TransactionError::OK;
79+
}
80+
4681
#endif

src/wallet/external_signer_scriptpubkeyman.h

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan
2525

2626
static ExternalSigner GetExternalSigner();
2727

28+
bool DisplayAddress(const CScript scriptPubKey, const ExternalSigner &signer) const;
2829
};
2930
#endif
3031

src/wallet/rpcsigner.cpp

+37
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

55
#include <chainparamsbase.h>
6+
#include <key_io.h>
67
#include <rpc/server.h>
78
#include <rpc/util.h>
89
#include <util/strencodings.h>
@@ -57,6 +58,41 @@ static RPCHelpMan enumeratesigners()
5758
};
5859
}
5960

61+
static RPCHelpMan signerdisplayaddress()
62+
{
63+
return RPCHelpMan{
64+
"signerdisplayaddress",
65+
"Display address on an external signer for verification.\n",
66+
{
67+
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, /* default_val */ "", "bitcoin address to display"},
68+
},
69+
RPCResult{RPCResult::Type::NONE,"",""},
70+
RPCExamples{""},
71+
[](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
72+
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
73+
if (!wallet) return NullUniValue;
74+
CWallet* const pwallet = wallet.get();
75+
76+
LOCK(pwallet->cs_wallet);
77+
78+
CTxDestination dest = DecodeDestination(request.params[0].get_str());
79+
80+
// Make sure the destination is valid
81+
if (!IsValidDestination(dest)) {
82+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
83+
}
84+
85+
if (!pwallet->DisplayAddress(dest)) {
86+
throw JSONRPCError(RPC_WALLET_ERROR, "Failed to display address");
87+
}
88+
89+
UniValue result(UniValue::VOBJ);
90+
result.pushKV("address", request.params[0].get_str());
91+
return result;
92+
}
93+
};
94+
}
95+
6096
Span<const CRPCCommand> GetSignerRPCCommands()
6197
{
6298

@@ -65,6 +101,7 @@ static const CRPCCommand commands[] =
65101
{ // category actor (function)
66102
// --------------------- ------------------------
67103
{ "signer", &enumeratesigners, },
104+
{ "signer", &signerdisplayaddress, },
68105
};
69106
// clang-format on
70107
return MakeSpan(commands);

src/wallet/wallet.cpp

+19
Original file line numberDiff line numberDiff line change
@@ -3587,6 +3587,25 @@ ExternalSigner CWallet::GetExternalSigner()
35873587
}
35883588
#endif
35893589

3590+
bool CWallet::DisplayAddress(const CTxDestination& dest)
3591+
{
3592+
#ifdef ENABLE_EXTERNAL_SIGNER
3593+
CScript scriptPubKey = GetScriptForDestination(dest);
3594+
const auto spk_man = GetScriptPubKeyMan(scriptPubKey);
3595+
if (spk_man == nullptr) {
3596+
return false;
3597+
}
3598+
auto signer_spk_man = dynamic_cast<ExternalSignerScriptPubKeyMan*>(spk_man);
3599+
if (signer_spk_man == nullptr) {
3600+
return false;
3601+
}
3602+
ExternalSigner signer = GetExternalSigner(); // TODO: move signer in spk_man
3603+
return signer_spk_man->DisplayAddress(scriptPubKey, signer);
3604+
#else
3605+
return false;
3606+
#endif
3607+
}
3608+
35903609
void CWallet::LockCoin(const COutPoint& output)
35913610
{
35923611
AssertLockHeld(cs_wallet);

src/wallet/wallet.h

+3
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,9 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
842842
#ifdef ENABLE_EXTERNAL_SIGNER
843843
ExternalSigner GetExternalSigner() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
844844
#endif
845+
/** Display address on an external signer. Returns false if external signer support is not compiled */
846+
bool DisplayAddress(const CTxDestination& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
847+
845848
bool IsLockedCoin(uint256 hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
846849
void LockCoin(const COutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
847850
void UnlockCoin(const COutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);

test/functional/mocks/signer.py

+18
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,20 @@ def getdescriptors(args):
3737
}))
3838

3939

40+
def displayaddress(args):
41+
# Several descriptor formats are acceptable, so allowing for potential
42+
# changes to InferDescriptor:
43+
if args.fingerprint != "00000001":
44+
return sys.stdout.write(json.dumps({"error": "Unexpected fingerprint", "fingerprint": args.fingerprint}))
45+
46+
expected_desc = [
47+
"wpkh([00000001/84'/1'/0'/0/0]02c97dc3f4420402e01a113984311bf4a1b8de376cac0bdcfaf1b3ac81f13433c7)#0yneg42r"
48+
]
49+
if args.desc not in expected_desc:
50+
return sys.stdout.write(json.dumps({"error": "Unexpected descriptor", "desc": args.desc}))
51+
52+
return sys.stdout.write(json.dumps({"address": "bcrt1qm90ugl4d48jv8n6e5t9ln6t9zlpm5th68x4f8g"}))
53+
4054
parser = argparse.ArgumentParser(prog='./signer.py', description='External signer mock')
4155
parser.add_argument('--fingerprint')
4256
parser.add_argument('--chain', default='main')
@@ -51,6 +65,10 @@ def getdescriptors(args):
5165
parser_getdescriptors.set_defaults(func=getdescriptors)
5266
parser_getdescriptors.add_argument('--account', metavar='account')
5367

68+
parser_displayaddress = subparsers.add_parser('displayaddress', help='display address on signer')
69+
parser_displayaddress.add_argument('--desc', metavar='desc')
70+
parser_displayaddress.set_defaults(func=displayaddress)
71+
5472
args = parser.parse_args()
5573

5674
perform_pre_checks()

test/functional/wallet_signer.py

+11
Original file line numberDiff line numberDiff line change
@@ -123,5 +123,16 @@ def run_test(self):
123123
assert_equal(address_info['ismine'], True)
124124
assert_equal(address_info['hdkeypath'], "m/44'/1'/0'/0/0")
125125

126+
self.log.info('Test signerdisplayaddress')
127+
result = hww.signerdisplayaddress(address1)
128+
assert_equal(result, {"address": address1})
129+
130+
# Handle error thrown by script
131+
self.set_mock_result(self.nodes[1], "2")
132+
assert_raises_rpc_error(-1, 'RunCommandParseJSON error',
133+
hww.signerdisplayaddress, address1
134+
)
135+
self.clear_mock_result(self.nodes[1])
136+
126137
if __name__ == '__main__':
127138
SignerTest().main()

0 commit comments

Comments
 (0)