wallet, rpc: Only allow keypool import from single key descriptors

Legacy wallets should only import keys to the keypool if they came in a
single key descriptor. Instead of relying on assumptions about the
descriptor based on how many pubkeys show up after expanding the
descriptor, explicitly mark descriptors as being single key type and use
that for the check.
This commit is contained in:
Ava Chow 2024-01-22 19:00:31 -05:00
parent 99a4ddf5ab
commit 0ff072caa1
4 changed files with 26 additions and 2 deletions

View file

@ -800,6 +800,7 @@ public:
return OutputTypeFromDestination(m_destination);
}
bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return false; }
bool ToPrivateString(const SigningProvider& arg, std::string& out) const final { return false; }
std::optional<int64_t> ScriptSize() const override { return GetScriptForDestination(m_destination).size(); }
@ -827,6 +828,7 @@ public:
return OutputTypeFromDestination(dest);
}
bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return false; }
bool ToPrivateString(const SigningProvider& arg, std::string& out) const final { return false; }
std::optional<int64_t> ScriptSize() const override { return m_script.size(); }
@ -855,6 +857,7 @@ protected:
public:
PKDescriptor(std::unique_ptr<PubkeyProvider> prov, bool xonly = false) : DescriptorImpl(Vector(std::move(prov)), "pk"), m_xonly(xonly) {}
bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return true; }
std::optional<int64_t> ScriptSize() const override {
return 1 + (m_xonly ? 32 : m_pubkey_args[0]->GetSize()) + 1;
@ -891,6 +894,7 @@ public:
PKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "pkh") {}
std::optional<OutputType> GetOutputType() const override { return OutputType::LEGACY; }
bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return true; }
std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 1 + 20 + 1 + 1; }
@ -925,6 +929,7 @@ public:
WPKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "wpkh") {}
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return true; }
std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 20; }
@ -967,6 +972,7 @@ protected:
public:
ComboDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "combo") {}
bool IsSingleType() const final { return false; }
bool IsSingleKey() const final { return true; }
std::unique_ptr<DescriptorImpl> Clone() const override
{
return std::make_unique<ComboDescriptor>(m_pubkey_args.at(0)->Clone());
@ -991,6 +997,7 @@ protected:
public:
MultisigDescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers, bool sorted = false) : DescriptorImpl(std::move(providers), sorted ? "sortedmulti" : "multi"), m_threshold(threshold), m_sorted(sorted) {}
bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return false; }
std::optional<int64_t> ScriptSize() const override {
const auto n_keys = m_pubkey_args.size();
@ -1042,6 +1049,7 @@ protected:
public:
MultiADescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers, bool sorted = false) : DescriptorImpl(std::move(providers), sorted ? "sortedmulti_a" : "multi_a"), m_threshold(threshold), m_sorted(sorted) {}
bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return false; }
std::optional<int64_t> ScriptSize() const override {
const auto n_keys = m_pubkey_args.size();
@ -1088,6 +1096,7 @@ public:
return OutputType::LEGACY;
}
bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return m_subdescriptor_args[0]->IsSingleKey(); }
std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 20 + 1; }
@ -1129,6 +1138,7 @@ public:
WSHDescriptor(std::unique_ptr<DescriptorImpl> desc) : DescriptorImpl({}, std::move(desc), "wsh") {}
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return m_subdescriptor_args[0]->IsSingleKey(); }
std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 32; }
@ -1207,6 +1217,7 @@ public:
}
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32M; }
bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return false; }
std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 32; }
@ -1334,6 +1345,7 @@ public:
bool IsSolvable() const override { return true; }
bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return false; }
std::optional<int64_t> ScriptSize() const override { return m_node->ScriptSize(); }
@ -1373,6 +1385,7 @@ public:
RawTRDescriptor(std::unique_ptr<PubkeyProvider> output_key) : DescriptorImpl(Vector(std::move(output_key)), "rawtr") {}
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32M; }
bool IsSingleType() const final { return true; }
bool IsSingleKey() const final { return false; }
std::optional<int64_t> ScriptSize() const override { return 1 + 1 + 32; }

View file

@ -111,6 +111,11 @@ struct Descriptor {
/** Whether this descriptor will return one scriptPubKey or multiple (aka is or is not combo) */
virtual bool IsSingleType() const = 0;
/** Whether this descriptor only produces single key scripts (i.e. pk(), pkh(), wpkh(), sh() and wsh() nested of those, and combo())
* TODO: Remove this method once legacy wallets are removed as it is only necessary for importmulti.
*/
virtual bool IsSingleKey() const = 0;
/** Convert the descriptor to a private string. This fails if the provided provider does not have the relevant private keys. */
virtual bool ToPrivateString(const SigningProvider& provider, std::string& out) const = 0;

View file

@ -1091,6 +1091,9 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
std::tie(range_start, range_end) = ParseDescriptorRange(data["range"]);
}
// Only single key descriptors are allowed to be imported to a legacy wallet's keypool
bool can_keypool = parsed_descs.at(0)->IsSingleKey();
const UniValue& priv_keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
for (size_t j = 0; j < parsed_descs.size(); ++j) {
@ -1107,9 +1110,11 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
std::vector<CScript> scripts_temp;
parsed_desc->Expand(i, keys, scripts_temp, out_keys);
std::copy(scripts_temp.begin(), scripts_temp.end(), std::inserter(script_pub_keys, script_pub_keys.end()));
if (can_keypool) {
for (const auto& key_pair : out_keys.pubkeys) {
ordered_pubkeys.emplace_back(key_pair.first, desc_internal);
}
}
for (const auto& x : out_keys.scripts) {
import_data.import_scripts.emplace(x.second);

View file

@ -26,6 +26,7 @@ public:
bool IsRange() const override { return false; }
bool IsSolvable() const override { return false; }
bool IsSingleType() const override { return true; }
bool IsSingleKey() const override { return true; }
bool ToPrivateString(const SigningProvider& provider, std::string& out) const override { return false; }
bool ToNormalizedString(const SigningProvider& provider, std::string& out, const DescriptorCache* cache = nullptr) const override { return false; }
bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out, DescriptorCache* write_cache = nullptr) const override { return false; };