Skip to content

Commit 386fab7

Browse files
apoelstraroconnor-blockstream
authored andcommitted
wallet: add ability for addhdkey to import a codex32 seed
1 parent 1157b73 commit 386fab7

File tree

2 files changed

+45
-4
lines changed

2 files changed

+45
-4
lines changed

src/rpc/client.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
310310
{ "stop", 0, "wait" },
311311
{ "addnode", 2, "v2transport" },
312312
{ "addconnection", 2, "v2transport" },
313+
{ "addhdkey", 1, "codex32" },
313314
};
314315
// clang-format on
315316

src/wallet/rpc/wallet.cpp

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include <bitcoin-build-config.h> // IWYU pragma: keep
77

8+
#include <codex32.h>
89
#include <core_io.h>
910
#include <key_io.h>
1011
#include <rpc/server.h>
@@ -938,9 +939,15 @@ RPCHelpMan addhdkey()
938939
{
939940
return RPCHelpMan{
940941
"addhdkey",
941-
"\nAdd a BIP 32 HD key to the wallet that can be used with 'createwalletdescriptor'\n",
942+
"Add a BIP 32 HD key to the wallet that can be used with 'createwalletdescriptor'.\n"
943+
"If no argument is provided, a randomly generated one will be added.",
942944
{
943-
{"hdkey", RPCArg::Type::STR, RPCArg::DefaultHint{"Automatically generated new key"}, "The BIP 32 extended private key to add. If none is provided, a randomly generated one will be added."},
945+
{"hdkey", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The BIP 32 extended private key to add."},
946+
{"codex32", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "A codex32 (BIP 93) encoded seed, or list of codex32-encoded shares",
947+
{
948+
{"share 1", RPCArg::Type::STR, RPCArg::Optional::OMITTED, ""},
949+
},
950+
},
944951
},
945952
RPCResult{
946953
RPCResult::Type::OBJ, "", "",
@@ -963,11 +970,13 @@ RPCHelpMan addhdkey()
963970
EnsureWalletIsUnlocked(*wallet);
964971

965972
CExtKey hdkey;
966-
if (request.params[0].isNull()) {
973+
if (request.params[0].isNull() && request.params[1].isNull()) {
967974
CKey seed_key;
968975
seed_key.MakeNewKey(true);
969976
hdkey.SetSeed(seed_key);
970-
} else {
977+
} else if (!request.params[0].isNull() && !request.params[1].isNull()) {
978+
throw JSONRPCError(RPC_WALLET_ERROR, "addhkey takes at most one key type");
979+
} else if (!request.params[0].isNull()) {
971980
hdkey = DecodeExtKey(request.params[0].get_str());
972981
if (!hdkey.key.IsValid()) {
973982
// Check if the user gave us an xpub and give a more descriptive error if so
@@ -978,6 +987,37 @@ RPCHelpMan addhdkey()
978987
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Could not parse HD key");
979988
}
980989
}
990+
} else {
991+
const auto& req_shares = request.params[1].get_array();
992+
std::vector<codex32::Result> shares;
993+
shares.reserve(req_shares.size());
994+
for (size_t j = 0; j < req_shares.size(); ++j) {
995+
if (!req_shares[j].isStr()) {
996+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "codex32 shares must be strings");
997+
}
998+
codex32::Result key_res{req_shares[j].get_str()};
999+
if (!key_res.IsValid()) {
1000+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid codex32 share: " + codex32::ErrorString(key_res.error()));
1001+
}
1002+
shares.push_back(key_res);
1003+
}
1004+
1005+
// Recover seed
1006+
std::vector<unsigned char> seed;
1007+
if (shares.size() == 1) {
1008+
if (shares[0].GetShareIndex() != 's') {
1009+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid codex32: single share must be the S share");
1010+
}
1011+
seed = shares[0].GetPayload();
1012+
} else {
1013+
codex32::Result s{shares, 's'};
1014+
if (!s.IsValid()) {
1015+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Failed to derive codex32 seed: " + codex32::ErrorString(s.error()));
1016+
}
1017+
seed = s.GetPayload();
1018+
}
1019+
1020+
hdkey.SetSeed(std::span{(std::byte*) seed.data(), seed.size()});
9811021
}
9821022

9831023
LOCK(wallet->cs_wallet);

0 commit comments

Comments
 (0)