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 multiple separate descriptors.
When there are 2 multipath items, the first descriptor will be for receiving
addresses and the second for change. This mirrors importmulti.
  • Loading branch information
achow101 committed Oct 9, 2023
1 parent bdc5174 commit 7ead6fc
Showing 1 changed file with 72 additions and 53 deletions.
125 changes: 72 additions & 53 deletions src/wallet/rpc/backup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1478,7 +1478,6 @@ 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{LabelFromValue(data["label"])};

// Parse descriptor string
Expand All @@ -1488,13 +1487,19 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
if (parsed_descs.empty()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
}
auto& parsed_desc = parsed_descs.at(0);
std::optional<bool> internal;
if (data.exists("internal")) {
if (parsed_descs.size() > 1) {
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.at(0)->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.at(0)->IsRange()) {
if (data.exists("range")) {
auto range = ParseDescriptorRange(data["range"]);
range_start = range.first;
Expand All @@ -1516,10 +1521,15 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
}

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

// Multipath descriptors should not have a label
if (parsed_descs.size() > 1 && 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 @@ -1531,7 +1541,7 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
}

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

Expand All @@ -1540,61 +1550,70 @@ 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 (size_t j = 0; j < parsed_descs.size(); ++j) {
auto parsed_desc = std::move(parsed_descs[j]);
bool desc_internal = internal.has_value() && internal.value();
if (parsed_descs.size() == 2) {
desc_internal = j == 1;
} else if (parsed_descs.size() > 2) {
CHECK_NONFATAL(!desc_internal);
}
// 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, desc_internal);
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(), desc_internal);
}
} 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(), desc_internal);
}
}
}

Expand Down

0 comments on commit 7ead6fc

Please sign in to comment.