Skip to content

Commit

Permalink
wallet, rpc: Allow importdescriptors to import multipath descriptors
Browse files Browse the repository at this point in the history
Multipath descriptors will be imported as two separate descriptors with
the first for receiving addresses and the second for change. This
mirrors importmulti.
  • Loading branch information
achow101 committed Jan 10, 2022
1 parent 6be554f commit 2105390
Showing 1 changed file with 70 additions and 54 deletions.
124 changes: 70 additions & 54 deletions src/wallet/rpc/backup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1456,22 +1456,30 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c

const std::string& descriptor = data["desc"].get_str();
const bool active = data.exists("active") ? data["active"].get_bool() : false;
const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
const std::string& label = data.exists("label") ? data["label"].get_str() : "";

// Parse descriptor string
FlatSigningProvider keys;
std::string error;
auto parsed_desc = Parse(descriptor, keys, error, /* require_checksum = */ true).first;
if (!parsed_desc) {
auto parsed_descs = Parse(descriptor, keys, error, /* require_checksum = */ true);
if (!parsed_descs.first) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
}

std::optional<bool> internal;
bool multipath = parsed_descs.second != nullptr;
if (data.exists("internal")) {
if (multipath) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot have multipath descriptor while also specifying \'internal\'");
}
internal = data["internal"].get_bool();
}

// Range check
int64_t range_start = 0, range_end = 1, next_index = 0;
if (!parsed_desc->IsRange() && data.exists("range")) {
if (!parsed_descs.first->IsRange() && data.exists("range")) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor");
} else if (parsed_desc->IsRange()) {
} else if (parsed_descs.first->IsRange()) {
if (data.exists("range")) {
auto range = ParseDescriptorRange(data["range"]);
range_start = range.first;
Expand All @@ -1493,10 +1501,15 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
}

// Active descriptors must be ranged
if (active && !parsed_desc->IsRange()) {
if (active && !parsed_descs.first->IsRange()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Active descriptors must be ranged");
}

// Multipath descriptors should not have a label
if (multipath && data.exists("label")) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Multipath descriptors should not have a label");
}

// Ranged descriptors should not have a label
if (data.exists("range") && data.exists("label")) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptors should not have a label");
Expand All @@ -1508,7 +1521,7 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
}

// Combo descriptor check
if (active && !parsed_desc->IsSingleType()) {
if (active && !parsed_descs.first->IsSingleType()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Combo descriptors cannot be set to active");
}

Expand All @@ -1517,61 +1530,64 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled");
}

// Need to ExpandPrivate to check if private keys are available for all pubkeys
FlatSigningProvider expand_keys;
std::vector<CScript> scripts;
if (!parsed_desc->Expand(0, keys, scripts, expand_keys)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot expand descriptor. Probably because of hardened derivations without private keys provided");
}
parsed_desc->ExpandPrivate(0, keys, expand_keys);

// Check if all private keys are provided
bool have_all_privkeys = !expand_keys.keys.empty();
for (const auto& entry : expand_keys.origins) {
const CKeyID& key_id = entry.first;
CKey key;
if (!expand_keys.GetKey(key_id, key)) {
have_all_privkeys = false;
break;
for (int j = 0; j < (multipath ? 2 : 1); ++j) {
auto parsed_desc = j ? std::move(parsed_descs.second) : std::move(parsed_descs.first);
// Need to ExpandPrivate to check if private keys are available for all pubkeys
FlatSigningProvider expand_keys;
std::vector<CScript> scripts;
if (!parsed_desc->Expand(0, keys, scripts, expand_keys)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot expand descriptor. Probably because of hardened derivations without private keys provided");
}
parsed_desc->ExpandPrivate(0, keys, expand_keys);

// Check if all private keys are provided
bool have_all_privkeys = !expand_keys.keys.empty();
for (const auto& entry : expand_keys.origins) {
const CKeyID& key_id = entry.first;
CKey key;
if (!expand_keys.GetKey(key_id, key)) {
have_all_privkeys = false;
break;
}
}
}

// If private keys are enabled, check some things.
if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
if (keys.keys.empty()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import descriptor without private keys to a wallet with private keys enabled");
}
if (!have_all_privkeys) {
warnings.push_back("Not all private keys provided. Some wallet functionality may return unexpected errors");
}
}
// If private keys are enabled, check some things.
if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
if (keys.keys.empty()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import descriptor without private keys to a wallet with private keys enabled");
}
if (!have_all_privkeys) {
warnings.push_back("Not all private keys provided. Some wallet functionality may return unexpected errors");
}
}

WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index);
WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index);

// Check if the wallet already contains the descriptor
auto existing_spk_manager = wallet.GetDescriptorScriptPubKeyMan(w_desc);
if (existing_spk_manager) {
if (!existing_spk_manager->CanUpdateToWalletDescriptor(w_desc, error)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, error);
// Check if the wallet already contains the descriptor
auto existing_spk_manager = wallet.GetDescriptorScriptPubKeyMan(w_desc);
if (existing_spk_manager) {
if (!existing_spk_manager->CanUpdateToWalletDescriptor(w_desc, error)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, error);
}
}
}

// Add descriptor to the wallet
auto spk_manager = wallet.AddWalletDescriptor(w_desc, keys, label, internal);
if (spk_manager == nullptr) {
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Could not add descriptor '%s'", descriptor));
}
// Add descriptor to the wallet
auto spk_manager = wallet.AddWalletDescriptor(w_desc, keys, label, (internal.has_value() ? internal.value() : j));
if (spk_manager == nullptr) {
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Could not add descriptor '%s'", descriptor));
}

// Set descriptor as active if necessary
if (active) {
if (!w_desc.descriptor->GetOutputType()) {
warnings.push_back("Unknown output type, cannot set descriptor to active.");
// Set descriptor as active if necessary
if (active) {
if (!w_desc.descriptor->GetOutputType()) {
warnings.push_back("Unknown output type, cannot set descriptor to active.");
} else {
wallet.AddActiveScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), (internal.has_value() ? internal.value() : j));
}
} else {
wallet.AddActiveScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal);
}
} else {
if (w_desc.descriptor->GetOutputType()) {
wallet.DeactivateScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal);
if (w_desc.descriptor->GetOutputType()) {
wallet.DeactivateScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), (internal.has_value() ? internal.value() : j));
}
}
}

Expand Down

0 comments on commit 2105390

Please sign in to comment.