aboutsummaryrefslogtreecommitdiff
path: root/src/script/descriptor.cpp
diff options
context:
space:
mode:
authorPieter Wuille <pieter@wuille.net>2021-02-07 19:18:39 -0800
committerPieter Wuille <pieter@wuille.net>2021-05-24 12:14:16 -0700
commit7cedafc5412857404e9a6c3450b100cb8ee4081a (patch)
tree9ec014407feccbe45190dce5fdb4f77c6203708e /src/script/descriptor.cpp
parent90fcac365e1616779b40a69736428435df75fdf2 (diff)
Add tr() descriptor (derivation only, no signing)
This adds a new descriptor with syntax e.g. tr(KEY,{S1,{{S2,S3},S4}) where KEY is a key expression for the internal key and S_i are script expression for the leaves. They have to be organized in nested {A,B} groups, with exactly two elements. tr() only exists at the top level, and inside the script expressions only pk() scripts are allowed for now.
Diffstat (limited to 'src/script/descriptor.cpp')
-rw-r--r--src/script/descriptor.cpp151
1 files changed, 144 insertions, 7 deletions
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp
index d4b43932b9..51cf8a7d62 100644
--- a/src/script/descriptor.cpp
+++ b/src/script/descriptor.cpp
@@ -241,9 +241,10 @@ public:
class ConstPubkeyProvider final : public PubkeyProvider
{
CPubKey m_pubkey;
+ bool m_xonly;
public:
- ConstPubkeyProvider(uint32_t exp_index, const CPubKey& pubkey) : PubkeyProvider(exp_index), m_pubkey(pubkey) {}
+ ConstPubkeyProvider(uint32_t exp_index, const CPubKey& pubkey, bool xonly = false) : PubkeyProvider(exp_index), m_pubkey(pubkey), m_xonly(xonly) {}
bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) override
{
key = m_pubkey;
@@ -254,7 +255,7 @@ public:
}
bool IsRange() const override { return false; }
size_t GetSize() const override { return m_pubkey.size(); }
- std::string ToString() const override { return HexStr(m_pubkey); }
+ std::string ToString() const override { return m_xonly ? HexStr(m_pubkey).substr(2) : HexStr(m_pubkey); }
bool ToPrivateString(const SigningProvider& arg, std::string& ret) const override
{
CKey key;
@@ -505,6 +506,7 @@ protected:
public:
DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_name(name), m_subdescriptor_args() {}
DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, std::unique_ptr<DescriptorImpl> script, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_name(name), m_subdescriptor_args(Vector(std::move(script))) {}
+ DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, std::vector<std::unique_ptr<DescriptorImpl>> scripts, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_name(name), m_subdescriptor_args(std::move(scripts)) {}
bool IsSolvable() const override
{
@@ -693,10 +695,20 @@ public:
/** A parsed pk(P) descriptor. */
class PKDescriptor final : public DescriptorImpl
{
+private:
+ const bool m_xonly;
protected:
- std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider&) const override { return Vector(GetScriptForRawPubKey(keys[0])); }
+ std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider&) const override
+ {
+ if (m_xonly) {
+ CScript script = CScript() << ToByteVector(XOnlyPubKey(keys[0])) << OP_CHECKSIG;
+ return Vector(std::move(script));
+ } else {
+ return Vector(GetScriptForRawPubKey(keys[0]));
+ }
+ }
public:
- PKDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "pk") {}
+ PKDescriptor(std::unique_ptr<PubkeyProvider> prov, bool xonly = false) : DescriptorImpl(Vector(std::move(prov)), "pk"), m_xonly(xonly) {}
bool IsSingleType() const final { return true; }
};
@@ -814,6 +826,56 @@ public:
bool IsSingleType() const final { return true; }
};
+/** A parsed tr(...) descriptor. */
+class TRDescriptor final : public DescriptorImpl
+{
+ std::vector<int> m_depths;
+protected:
+ std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript> scripts, FlatSigningProvider& out) const override
+ {
+ TaprootBuilder builder;
+ assert(m_depths.size() == scripts.size());
+ for (size_t pos = 0; pos < m_depths.size(); ++pos) {
+ builder.Add(m_depths[pos], scripts[pos], TAPROOT_LEAF_TAPSCRIPT);
+ }
+ if (!builder.IsComplete()) return {};
+ assert(keys.size() == 1);
+ XOnlyPubKey xpk(keys[0]);
+ if (!xpk.IsFullyValid()) return {};
+ builder.Finalize(xpk);
+ return Vector(GetScriptForDestination(builder.GetOutput()));
+ }
+ bool ToStringSubScriptHelper(const SigningProvider* arg, std::string& ret, bool priv, bool normalized) const override
+ {
+ if (m_depths.empty()) return true;
+ std::vector<bool> path;
+ for (size_t pos = 0; pos < m_depths.size(); ++pos) {
+ if (pos) ret += ',';
+ while ((int)path.size() <= m_depths[pos]) {
+ if (path.size()) ret += '{';
+ path.push_back(false);
+ }
+ std::string tmp;
+ if (!m_subdescriptor_args[pos]->ToStringHelper(arg, tmp, priv, normalized)) return false;
+ ret += std::move(tmp);
+ while (!path.empty() && path.back()) {
+ if (path.size() > 1) ret += '}';
+ path.pop_back();
+ }
+ if (!path.empty()) path.back() = true;
+ }
+ return true;
+ }
+public:
+ TRDescriptor(std::unique_ptr<PubkeyProvider> internal_key, std::vector<std::unique_ptr<DescriptorImpl>> descs, std::vector<int> depths) :
+ DescriptorImpl(Vector(std::move(internal_key)), std::move(descs), "tr"), m_depths(std::move(depths))
+ {
+ assert(m_subdescriptor_args.size() == m_depths.size());
+ }
+ std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
+ bool IsSingleType() const final { return true; }
+};
+
////////////////////////////////////////////////////////////////////////////
// Parser //
////////////////////////////////////////////////////////////////////////////
@@ -823,6 +885,7 @@ enum class ParseScriptContext {
P2SH, //!< Inside sh() (script becomes P2SH redeemScript)
P2WPKH, //!< Inside wpkh() (no script, pubkey only)
P2WSH, //!< Inside wsh() (script becomes v0 witness script)
+ P2TR, //!< Inside tr() (either internal key, or BIP342 script leaf)
};
/** Parse a key path, being passed a split list of elements (the first element is ignored). */
@@ -871,6 +934,13 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S
error = "Uncompressed keys are not allowed";
return nullptr;
}
+ } else if (data.size() == 32 && ctx == ParseScriptContext::P2TR) {
+ unsigned char fullkey[33] = {0x02};
+ std::copy(data.begin(), data.end(), fullkey + 1);
+ pubkey.Set(std::begin(fullkey), std::end(fullkey));
+ if (pubkey.IsFullyValid()) {
+ return std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey, true);
+ }
}
error = strprintf("Pubkey '%s' is invalid", str);
return nullptr;
@@ -958,13 +1028,16 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
auto pubkey = ParsePubkey(key_exp_index, expr, ctx, out, error);
if (!pubkey) return nullptr;
++key_exp_index;
- return std::make_unique<PKDescriptor>(std::move(pubkey));
+ return std::make_unique<PKDescriptor>(std::move(pubkey), ctx == ParseScriptContext::P2TR);
}
- if (Func("pkh", expr)) {
+ if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH) && Func("pkh", expr)) {
auto pubkey = ParsePubkey(key_exp_index, expr, ctx, out, error);
if (!pubkey) return nullptr;
++key_exp_index;
return std::make_unique<PKHDescriptor>(std::move(pubkey));
+ } else if (Func("pkh", expr)) {
+ error = "Can only have pkh at top level, in sh(), or in wsh()";
+ return nullptr;
}
if (ctx == ParseScriptContext::TOP && Func("combo", expr)) {
auto pubkey = ParsePubkey(key_exp_index, expr, ctx, out, error);
@@ -975,7 +1048,7 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
error = "Can only have combo() at top level";
return nullptr;
}
- if ((sorted_multi = Func("sortedmulti", expr)) || Func("multi", expr)) {
+ if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH) && ((sorted_multi = Func("sortedmulti", expr)) || Func("multi", expr))) {
auto threshold = Expr(expr);
uint32_t thres;
std::vector<std::unique_ptr<PubkeyProvider>> providers;
@@ -1020,6 +1093,9 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
}
}
return std::make_unique<MultisigDescriptor>(thres, std::move(providers), sorted_multi);
+ } else if (Func("sortedmulti", expr) || Func("multi", expr)) {
+ error = "Can only have multi/sortedmulti at top level, in sh(), or in wsh()";
+ return nullptr;
}
if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH) && Func("wpkh", expr)) {
auto pubkey = ParsePubkey(key_exp_index, expr, ParseScriptContext::P2WPKH, out, error);
@@ -1057,6 +1133,67 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
error = "Can only have addr() at top level";
return nullptr;
}
+ if (ctx == ParseScriptContext::TOP && Func("tr", expr)) {
+ auto arg = Expr(expr);
+ auto internal_key = ParsePubkey(key_exp_index, arg, ParseScriptContext::P2TR, out, error);
+ if (!internal_key) return nullptr;
+ ++key_exp_index;
+ std::vector<std::unique_ptr<DescriptorImpl>> subscripts; //!< list of script subexpressions
+ std::vector<int> depths; //!< depth in the tree of each subexpression (same length subscripts)
+ if (expr.size()) {
+ if (!Const(",", expr)) {
+ error = strprintf("tr: expected ',', got '%c'", expr[0]);
+ return nullptr;
+ }
+ /** The path from the top of the tree to what we're currently processing.
+ * branches[i] == false: left branch in the i'th step from the top; true: right branch.
+ */
+ std::vector<bool> branches;
+ // Loop over all provided scripts. In every iteration exactly one script will be processed.
+ // Use a do-loop because inside this if-branch we expect at least one script.
+ do {
+ // First process all open braces.
+ while (Const("{", expr)) {
+ branches.push_back(false); // new left branch
+ if (branches.size() > TAPROOT_CONTROL_MAX_NODE_COUNT) {
+ error = strprintf("tr() supports at most %i nesting levels", TAPROOT_CONTROL_MAX_NODE_COUNT);
+ return nullptr;
+ }
+ }
+ // Process the actual script expression.
+ auto sarg = Expr(expr);
+ subscripts.emplace_back(ParseScript(key_exp_index, sarg, ParseScriptContext::P2TR, out, error));
+ if (!subscripts.back()) return nullptr;
+ depths.push_back(branches.size());
+ // Process closing braces; one is expected for every right branch we were in.
+ while (branches.size() && branches.back()) {
+ if (!Const("}", expr)) {
+ error = strprintf("tr(): expected '}' after script expression");
+ return nullptr;
+ }
+ branches.pop_back(); // move up one level after encountering '}'
+ }
+ // If after that, we're at the end of a left branch, expect a comma.
+ if (branches.size() && !branches.back()) {
+ if (!Const(",", expr)) {
+ error = strprintf("tr(): expected ',' after script expression");
+ return nullptr;
+ }
+ branches.back() = true; // And now we're in a right branch.
+ }
+ } while (branches.size());
+ // After we've explored a whole tree, we must be at the end of the expression.
+ if (expr.size()) {
+ error = strprintf("tr(): expected ')' after script expression");
+ return nullptr;
+ }
+ }
+ assert(TaprootBuilder::ValidDepths(depths));
+ return std::make_unique<TRDescriptor>(std::move(internal_key), std::move(subscripts), std::move(depths));
+ } else if (Func("tr", expr)) {
+ error = "Can only have tr at top level";
+ return nullptr;
+ }
if (ctx == ParseScriptContext::TOP && Func("raw", expr)) {
std::string str(expr.begin(), expr.end());
if (!IsHex(str)) {