mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-06-15 09:09:46 +02:00
descriptor: Add unused(KEY) descriptor
unused() descriptors do not have scriptPubKeys. Instead, the wallet uses them to store keys without having any scripts to watch for.
This commit is contained in:
@@ -1038,6 +1038,8 @@ public:
|
||||
|
||||
virtual std::unique_ptr<DescriptorImpl> Clone() const = 0;
|
||||
|
||||
bool HasScripts() const override { return true; }
|
||||
|
||||
// NOLINTNEXTLINE(misc-no-recursion)
|
||||
std::vector<std::string> Warnings() const override {
|
||||
std::vector<std::string> all = m_warnings;
|
||||
@@ -1738,6 +1740,23 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/** A parsed unused(KEY) descriptor */
|
||||
class UnusedDescriptor final : public DescriptorImpl
|
||||
{
|
||||
protected:
|
||||
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, std::span<const CScript> scripts, FlatSigningProvider& out) const override { return {}; }
|
||||
public:
|
||||
UnusedDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "unused") {}
|
||||
bool IsSingleType() const final { return true; }
|
||||
bool HasScripts() const override { return false; }
|
||||
|
||||
std::unique_ptr<DescriptorImpl> Clone() const override
|
||||
{
|
||||
return std::make_unique<UnusedDescriptor>(m_pubkey_args.at(0)->Clone());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Parser //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
@@ -2575,6 +2594,27 @@ std::vector<std::unique_ptr<DescriptorImpl>> ParseScript(uint32_t& key_exp_index
|
||||
error = "Can only have rawtr at top level";
|
||||
return {};
|
||||
}
|
||||
if (ctx == ParseScriptContext::TOP && Func("unused", expr)) {
|
||||
// Check for only one expression, should not find commas, brackets, or parentheses
|
||||
auto arg = Expr(expr);
|
||||
if (expr.size()) {
|
||||
error = strprintf("unused(): only one key expected");
|
||||
return {};
|
||||
}
|
||||
auto keys = ParsePubkey(key_exp_index, arg, ctx, out, error);
|
||||
if (keys.empty()) return {};
|
||||
for (auto& pubkey : keys) {
|
||||
if (pubkey->IsRange()) {
|
||||
error = "unused(): key cannot be ranged";
|
||||
return {};
|
||||
}
|
||||
ret.emplace_back(std::make_unique<UnusedDescriptor>(std::move(pubkey)));
|
||||
}
|
||||
return ret;
|
||||
} else if (Func("unused", expr)) {
|
||||
error = "Can only have unused at top level";
|
||||
return {};
|
||||
}
|
||||
if (ctx == ParseScriptContext::TOP && Func("raw", expr)) {
|
||||
std::string str(expr.begin(), expr.end());
|
||||
if (!IsHex(str)) {
|
||||
|
||||
@@ -108,7 +108,7 @@ struct Descriptor {
|
||||
/** Convert the descriptor back to a string, undoing parsing. */
|
||||
virtual std::string ToString(bool compat_format=false) const = 0;
|
||||
|
||||
/** Whether this descriptor will return one scriptPubKey or multiple (aka is or is not combo) */
|
||||
/** Whether this descriptor will return at most one scriptPubKey or multiple (aka is or is not combo) */
|
||||
virtual bool IsSingleType() const = 0;
|
||||
|
||||
/** Whether the given provider has all private keys required by this descriptor.
|
||||
@@ -179,6 +179,9 @@ struct Descriptor {
|
||||
*/
|
||||
virtual void GetPubKeys(std::set<CPubKey>& pubkeys, std::set<CExtPubKey>& ext_pubs) const = 0;
|
||||
|
||||
/** Whether this descriptor produces any scripts with the Expand functions */
|
||||
virtual bool HasScripts() const = 0;
|
||||
|
||||
/** Semantic/safety warnings (includes subdescriptors). */
|
||||
virtual std::vector<std::string> Warnings() const = 0;
|
||||
|
||||
|
||||
@@ -1334,4 +1334,65 @@ BOOST_AUTO_TEST_CASE(descriptor_older_warnings)
|
||||
}
|
||||
}
|
||||
|
||||
void CheckSingleUnparsable(const std::string& desc, const std::string& expected_error)
|
||||
{
|
||||
FlatSigningProvider keys;
|
||||
std::string error;
|
||||
auto parsed = Parse(desc, keys, error);
|
||||
BOOST_CHECK_MESSAGE(parsed.empty(), desc);
|
||||
BOOST_CHECK_EQUAL(error, expected_error);
|
||||
}
|
||||
|
||||
void CheckUnused(const std::string& prv, const std::string& pub)
|
||||
{
|
||||
FlatSigningProvider keys_priv, keys_pub;
|
||||
std::string error;
|
||||
|
||||
std::unique_ptr<Descriptor> parse_priv;
|
||||
std::unique_ptr<Descriptor> parse_pub;
|
||||
parse_priv = std::move(Parse(prv, keys_priv, error).at(0));
|
||||
parse_pub = std::move(Parse(pub, keys_pub, error).at(0));
|
||||
BOOST_CHECK_MESSAGE(parse_priv, error);
|
||||
BOOST_CHECK_MESSAGE(parse_pub, error);
|
||||
|
||||
BOOST_CHECK(parse_priv->GetOutputType() == std::nullopt);
|
||||
BOOST_CHECK(parse_pub->GetOutputType() == std::nullopt);
|
||||
|
||||
// Check private keys are extracted from the private version but not the public one.
|
||||
BOOST_CHECK(keys_priv.keys.size());
|
||||
BOOST_CHECK(!keys_pub.keys.size());
|
||||
|
||||
// Check that both versions serialize back to the public version.
|
||||
std::string pub1 = parse_priv->ToString();
|
||||
std::string pub2 = parse_pub->ToString();
|
||||
BOOST_CHECK_MESSAGE(EqualDescriptor(pub, pub1), "Private ser: " + pub1 + " Public desc: " + pub);
|
||||
BOOST_CHECK_MESSAGE(EqualDescriptor(pub, pub2), "Public ser: " + pub2 + " Public desc: " + pub);
|
||||
|
||||
// Check both only have one pubkey
|
||||
std::set<CPubKey> prv_pubkeys;
|
||||
std::set<CExtPubKey> prv_extpubs;
|
||||
parse_pub->GetPubKeys(prv_pubkeys, prv_extpubs);
|
||||
BOOST_CHECK_EQUAL(prv_pubkeys.size() + prv_extpubs.size(), 1);
|
||||
std::set<CPubKey> pub_pubkeys;
|
||||
std::set<CExtPubKey> pub_extpubs;
|
||||
parse_pub->GetPubKeys(pub_pubkeys, pub_extpubs);
|
||||
BOOST_CHECK_EQUAL(pub_pubkeys.size() + pub_extpubs.size(), 1);
|
||||
}
|
||||
|
||||
// unused() descriptors don't produce scripts, so these need to be tested separately
|
||||
BOOST_AUTO_TEST_CASE(unused_descriptor_test)
|
||||
{
|
||||
CheckUnparsable("unused(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1,5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss)", "unused(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", "unused(): only one key expected");
|
||||
CheckUnparsable("wsh(unused(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1))", "wsh(unused(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", "Can only have unused at top level");
|
||||
CheckUnparsable("unused(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/*)", "unused(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/*)", "unused(): key cannot be ranged");
|
||||
CheckUnparsable("unused()", "unused()", "No key provided");
|
||||
|
||||
// x-only keys cannot be used in unused()
|
||||
CheckSingleUnparsable("unused(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", "Pubkey 'a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd' is invalid");
|
||||
|
||||
CheckUnused("unused(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "unused(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)");
|
||||
CheckUnused("unused(L4rK1yDtCWekvXuE6oXD9jCYfFNV2cWRpVuPLBcCU2z8TrisoyY1)", "unused(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)");
|
||||
CheckUnused("unused(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/0h/0h/1)", "unused(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0h/0h/1)");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
@@ -37,6 +37,7 @@ public:
|
||||
std::optional<int64_t> MaxSatisfactionWeight(bool) const override { return {}; }
|
||||
std::optional<int64_t> MaxSatisfactionElems() const override { return {}; }
|
||||
void GetPubKeys(std::set<CPubKey>& pubkeys, std::set<CExtPubKey>& ext_pubs) const override {}
|
||||
bool HasScripts() const override { return true; }
|
||||
std::vector<std::string> Warnings() const override { return {}; }
|
||||
uint32_t GetMaxKeyExpr() const override { return 0; }
|
||||
size_t GetKeyCount() const override { return 0; }
|
||||
|
||||
@@ -3813,8 +3813,8 @@ util::Result<std::reference_wrapper<DescriptorScriptPubKeyMan>> CWallet::AddWall
|
||||
}
|
||||
|
||||
// Apply the label if necessary
|
||||
// Note: we disable labels for ranged descriptors
|
||||
if (!desc.descriptor->IsRange()) {
|
||||
// Note: we disable labels for descriptors that are ranged or that don't produce output scripts (i.e. unused())
|
||||
if (!desc.descriptor->IsRange() && desc.descriptor->HasScripts()) {
|
||||
auto script_pub_keys = spk_man->GetScriptPubKeys();
|
||||
if (script_pub_keys.empty()) {
|
||||
return util::Error{_("Could not generate scriptPubKeys (cache is empty)")};
|
||||
|
||||
Reference in New Issue
Block a user