Skip to content

Commit b7bb56e

Browse files
committed
Merge remote-tracking branch 'sjors/2025/07/bip388-register' into 2025/06/musig2-power
2 parents b1c14ea + 84ef10f commit b7bb56e

File tree

15 files changed

+424
-6
lines changed

15 files changed

+424
-6
lines changed

src/external_signer.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,15 @@ UniValue ExternalSigner::GetDescriptors(const int account)
7171
return RunCommandParseJSON(m_command + " --fingerprint " + m_fingerprint + NetworkArg() + " getdescriptors --account " + strprintf("%d", account));
7272
}
7373

74+
UniValue ExternalSigner::RegisterPolicy(const std::string& name, const std::string& descriptor_template, const std::vector<std::string>& keys_info) const
75+
{
76+
std::string key_args;
77+
for (const std::string& key_info : keys_info) {
78+
key_args.append(" --key " + key_info);
79+
}
80+
return RunCommandParseJSON(m_command + " --fingerprint " + m_fingerprint + NetworkArg() + " register --name " + name + " --desc " + descriptor_template + key_args);
81+
}
82+
7483
bool ExternalSigner::SignTransaction(PartiallySignedTransaction& psbtx, std::string& error)
7584
{
7685
// Serialize the PSBT

src/external_signer.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ class ExternalSigner
5757
//! @returns see doc/external-signer.md
5858
UniValue GetDescriptors(int account);
5959

60+
//! Register BIP388 policy on the device.
61+
//! Calls `<command> register` and passes the name, policy and key info
62+
//! @param[in] name policy name to display on the signer
63+
//! @param[in] descriptor_template BIP388 descriptor template
64+
//! @param[in] keys_info key with origin for each participant
65+
//! @returns hmac provided by the signer
66+
UniValue RegisterPolicy(const std::string& name, const std::string& descriptor_template, const std::vector<std::string>& keys_info) const;
67+
6068
//! Sign PartiallySignedTransaction on the device.
6169
//! Calls `<command> signtransaction` and passes the PSBT via stdin.
6270
//! @param[in,out] psbt PartiallySignedTransaction to be signed

src/interfaces/wallet.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ class Wallet
130130
//! Display address on external signer
131131
virtual util::Result<void> displayAddress(const CTxDestination& dest) = 0;
132132

133+
//! Register BIP388 policy on external signer, store and return hmac
134+
virtual util::Result<std::string> registerPolicy(const std::optional<std::string>& name) = 0;
135+
133136
//! Lock coin.
134137
virtual bool lockCoin(const COutPoint& output, bool write_to_db) = 0;
135138

src/util/string.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,17 @@
88
#include <string>
99

1010
namespace util {
11-
void ReplaceAll(std::string& in_out, const std::string& search, const std::string& substitute)
11+
void ReplaceAll(std::string& in_out, const std::string& search, const std::string& substitute, bool regex)
1212
{
1313
if (search.empty()) return;
14-
in_out = std::regex_replace(in_out, std::regex(search), substitute);
14+
if (regex) {
15+
in_out = std::regex_replace(in_out, std::regex(search), substitute);
16+
return;
17+
}
18+
size_t pos{0};
19+
while ((pos = in_out.find(search, pos)) != std::string::npos) {
20+
in_out.replace(pos, search.size(), substitute);
21+
++pos;
22+
}
1523
}
1624
} // namespace util

src/util/string.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ struct ConstevalFormatString {
9494
consteval ConstevalFormatString(const char* str) : fmt{str} { detail::CheckNumFormatSpecifiers<num_params>(fmt); }
9595
};
9696

97-
void ReplaceAll(std::string& in_out, const std::string& search, const std::string& substitute);
97+
void ReplaceAll(std::string& in_out, const std::string& search, const std::string& substitute, bool regex = true);
9898

9999
/** Split a string on any char found in separators, returning a vector.
100100
*

src/wallet/external_signer_scriptpubkeyman.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <common/system.h>
88
#include <external_signer.h>
99
#include <node/types.h>
10+
#include <util/strencodings.h>
1011
#include <wallet/external_signer_scriptpubkeyman.h>
1112

1213
#include <iostream>
@@ -107,4 +108,23 @@ std::optional<PSBTError> ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySigned
107108
if (options.finalize) FinalizePSBT(psbt); // This won't work in a multisig setup
108109
return {};
109110
}
111+
112+
util::Result<std::string> ExternalSignerScriptPubKeyMan::RegisterPolicy(const ExternalSigner& signer,
113+
const std::string& name,
114+
const std::string& descriptor_template,
115+
const std::vector<std::string>& keys_info) const
116+
{
117+
const UniValue& result{signer.RegisterPolicy(name, descriptor_template, keys_info)};
118+
119+
const UniValue& error = result.find_value("error");
120+
if (error.isStr()) return util::Error{strprintf(_("Signer returned error: %s"), error.getValStr())};
121+
122+
const UniValue& ret_hmac = result.find_value("hmac");
123+
if (!ret_hmac.isStr()) return util::Error{_("Signer did not return hmac")};
124+
const std::string hmac{ret_hmac.getValStr()};
125+
if (!IsHex(hmac)) return util::Error{strprintf(_("Signer return invalid hmac: %s"), hmac)};
126+
127+
return util::Result<std::string>(hmac);
128+
}
129+
110130
} // namespace wallet

src/wallet/external_signer_scriptpubkeyman.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan
3737
util::Result<void> DisplayAddress(const CTxDestination& dest, const ExternalSigner& signer) const;
3838

3939
std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, common::PSBTFillOptions options, int* n_signed = nullptr) const override;
40+
41+
/**
42+
* Register BIP388 wallet policy.
43+
* @param[in] name policy name to display on the signer
44+
* @param[in] descriptor_template BIP388 descriptor template
45+
* @param[in] keys_info key with origin for each participant
46+
* @returns hmac or an error message
47+
*/
48+
util::Result<std::string> RegisterPolicy(const ExternalSigner& signer,
49+
const std::string& name,
50+
const std::string& descriptor_template,
51+
const std::vector<std::string>& keys_info) const;
4052
};
4153
} // namespace wallet
4254
#endif // BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H

src/wallet/interfaces.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,11 @@ class WalletImpl : public Wallet
237237
LOCK(m_wallet->cs_wallet);
238238
return m_wallet->DisplayAddress(dest);
239239
}
240+
util::Result<std::string> registerPolicy(const std::optional<std::string>& name) override
241+
{
242+
LOCK(m_wallet->cs_wallet);
243+
return m_wallet->RegisterPolicy(name);
244+
}
240245
bool lockCoin(const COutPoint& output, const bool write_to_db) override
241246
{
242247
LOCK(m_wallet->cs_wallet);

src/wallet/rpc/wallet.cpp

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ static RPCHelpMan getwalletinfo()
5858
}, /*skip_type_check=*/true},
5959
{RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for output script management"},
6060
{RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"},
61+
{RPCResult::Type::ARR, "bip388", /*optional=*/true, "BIP388 HMACs provided by external signers",
62+
{
63+
{RPCResult::Type::OBJ, "bip388_hmacs", "BIP388 HMACs for the policies registered with the external signer",
64+
{
65+
{RPCResult::Type::STR, "name", "the name of the BIP388 policy"},
66+
{RPCResult::Type::STR, "fingerprint", "the fingerprint of the external signer"},
67+
{RPCResult::Type::STR_HEX, "hmac", "the BIP388 HMAC for the policy and fingerprint"},
68+
},
69+
}}},
6170
{RPCResult::Type::BOOL, "blank", "Whether this wallet intentionally does not contain any keys, scripts, or descriptors"},
6271
{RPCResult::Type::NUM_TIME, "birthtime", /*optional=*/true, "The start time for blocks scanning. It could be modified by (re)importing any descriptor with an earlier timestamp."},
6372
{RPCResult::Type::ARR, "flags", "The flags currently set on the wallet",
@@ -110,6 +119,17 @@ static RPCHelpMan getwalletinfo()
110119
}
111120
obj.pushKV("descriptors", pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
112121
obj.pushKV("external_signer", pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER));
122+
if (pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) {
123+
UniValue bip388_hmacs(UniValue::VARR);
124+
for (const wallet::BIP388& hmac : pwallet->GetHmacs()) {
125+
UniValue bip388(UniValue::VOBJ);
126+
bip388.pushKV("name", hmac.name);
127+
bip388.pushKV("fingerprint", hmac.fingerprint);
128+
bip388.pushKV("hmac", hmac.hmac);
129+
bip388_hmacs.push_back(std::move(bip388));
130+
}
131+
obj.pushKV("bip388", bip388_hmacs);
132+
}
113133
obj.pushKV("blank", pwallet->IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET));
114134
if (int64_t birthtime = pwallet->GetBirthTime(); birthtime != UNKNOWN_TIME) {
115135
obj.pushKV("birthtime", birthtime);
@@ -889,11 +909,11 @@ RPCHelpMan addhdkey()
889909
RPCResult{
890910
RPCResult::Type::OBJ, "", "",
891911
{
892-
{RPCResult::Type::STR, "xpub", "The xpub of the HD key that was added to the wallet"}
912+
{RPCResult::Type::STR, "xpub", "The xpub of the HD key that was added to the wallet"},
893913
},
894914
},
895915
RPCExamples{
896-
HelpExampleCli("addhdkey", "xprv") + HelpExampleRpc("addhdkey", "xprv")
916+
HelpExampleCli("addhdkey", "xprv") + HelpExampleRpc("addhdkey", "xprv"),
897917
},
898918
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
899919
{
@@ -1080,6 +1100,46 @@ RPCHelpMan derivehdkey()
10801100
};
10811101
}
10821102

1103+
#ifdef ENABLE_EXTERNAL_SIGNER
1104+
RPCHelpMan registerpolicy()
1105+
{
1106+
return RPCHelpMan{
1107+
"registerpolicy",
1108+
"Register BIP388 policy on an external signer and store the result.",
1109+
{
1110+
{"policy_name", RPCArg::Type::STR, RPCArg::DefaultHint{"wallet name"}, "Policy name to display on the device"},
1111+
},
1112+
RPCResult{
1113+
RPCResult::Type::OBJ, "", "",
1114+
{
1115+
{RPCResult::Type::STR, "hmac", "The hmac (stored in the wallet)"},
1116+
},
1117+
},
1118+
RPCExamples{""},
1119+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1120+
{
1121+
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
1122+
if (!wallet) return UniValue::VNULL;
1123+
1124+
CWallet* const pwallet = wallet.get();
1125+
LOCK(pwallet->cs_wallet);
1126+
1127+
std::optional<std::string> policy_name;
1128+
if (!request.params[0].isNull()) {
1129+
policy_name = std::string_view{request.params[0].get_str()};
1130+
}
1131+
1132+
util::Result<std::string> res = pwallet->RegisterPolicy(policy_name);
1133+
if (!res) throw JSONRPCError(RPC_MISC_ERROR, util::ErrorString(res).original);
1134+
1135+
UniValue result(UniValue::VOBJ);
1136+
result.pushKV("hmac", *res);
1137+
return result;
1138+
},
1139+
};
1140+
}
1141+
#endif // ENABLE_EXTERNAL_SIGNER
1142+
10831143
// addresses
10841144
RPCHelpMan getaddressinfo();
10851145
RPCHelpMan getnewaddress();
@@ -1200,6 +1260,7 @@ std::span<const CRPCCommand> GetWalletRPCCommands()
12001260
{"wallet", &unloadwallet},
12011261
{"wallet", &walletcreatefundedpsbt},
12021262
#ifdef ENABLE_EXTERNAL_SIGNER
1263+
{"wallet", &registerpolicy},
12031264
{"wallet", &walletdisplayaddress},
12041265
#endif // ENABLE_EXTERNAL_SIGNER
12051266
{"wallet", &walletlock},

0 commit comments

Comments
 (0)