Skip to content

Commit 7cedafc

Browse files
committed
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.
1 parent 90fcac3 commit 7cedafc

File tree

4 files changed

+163
-15
lines changed

4 files changed

+163
-15
lines changed

doc/descriptors.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Output descriptors currently support:
3030
- Pay-to-witness-pubkey-hash scripts (P2WPKH), through the `wpkh` function.
3131
- Pay-to-script-hash scripts (P2SH), through the `sh` function.
3232
- Pay-to-witness-script-hash scripts (P2WSH), through the `wsh` function.
33+
- Pay-to-taproot outputs (P2TR), through the `tr` function.
3334
- Multisig scripts, through the `multi` function.
3435
- Multisig scripts where the public keys are sorted lexicographically, through the `sortedmulti` function.
3536
- Any type of supported address through the `addr` function.
@@ -54,20 +55,22 @@ Output descriptors currently support:
5455
- `pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)` describes a set of P2PKH outputs, but additionally specifies that the specified xpub is a child of a master with fingerprint `d34db33f`, and derived using path `44'/0'/0'`.
5556
- `wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))` describes a set of *1-of-2* P2WSH multisig outputs where the first multisig key is the *1/0/`i`* child of the first specified xpub and the second multisig key is the *0/0/`i`* child of the second specified xpub, and `i` is any number in a configurable range (`0-1000` by default).
5657
- `wsh(sortedmulti(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))` describes a set of *1-of-2* P2WSH multisig outputs where one multisig key is the *1/0/`i`* child of the first specified xpub and the other multisig key is the *0/0/`i`* child of the second specified xpub, and `i` is any number in a configurable range (`0-1000` by default). The order of public keys in the resulting witnessScripts is determined by the lexicographic order of the public keys at that index.
58+
- `tr(c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,{pk(fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),pk(e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)})` describes a P2TR output with the `c6...` x-only pubkey as internal key, and two script paths.
5759

5860
## Reference
5961

6062
Descriptors consist of several types of expressions. The top level expression is either a `SCRIPT`, or `SCRIPT#CHECKSUM` where `CHECKSUM` is an 8-character alphanumeric descriptor checksum.
6163

6264
`SCRIPT` expressions:
6365
- `sh(SCRIPT)` (top level only): P2SH embed the argument.
64-
- `wsh(SCRIPT)` (not inside another 'wsh'): P2WSH embed the argument.
66+
- `wsh(SCRIPT)` (top level or inside `sh` only): P2WSH embed the argument.
6567
- `pk(KEY)` (anywhere): P2PK output for the given public key.
66-
- `pkh(KEY)` (anywhere): P2PKH output for the given public key (use `addr` if you only know the pubkey hash).
67-
- `wpkh(KEY)` (not inside `wsh`): P2WPKH output for the given compressed pubkey.
68+
- `pkh(KEY)` (not inside `tr`): P2PKH output for the given public key (use `addr` if you only know the pubkey hash).
69+
- `wpkh(KEY)` (top level or inside `sh` only): P2WPKH output for the given compressed pubkey.
6870
- `combo(KEY)` (top level only): an alias for the collection of `pk(KEY)` and `pkh(KEY)`. If the key is compressed, it also includes `wpkh(KEY)` and `sh(wpkh(KEY))`.
69-
- `multi(k,KEY_1,KEY_2,...,KEY_n)` (anywhere): k-of-n multisig script.
70-
- `sortedmulti(k,KEY_1,KEY_2,...,KEY_n)` (anywhere): k-of-n multisig script with keys sorted lexicographically in the resulting script.
71+
- `multi(k,KEY_1,KEY_2,...,KEY_n)` (not inside `tr`): k-of-n multisig script.
72+
- `sortedmulti(k,KEY_1,KEY_2,...,KEY_n)` (not inside `tr`): k-of-n multisig script with keys sorted lexicographically in the resulting script.
73+
- `tr(KEY)` or `tr(KEY,TREE)` (top level only): P2TR output with the specified key as internal key, and optionally a tree of script paths.
7174
- `addr(ADDR)` (top level only): the script which ADDR expands to.
7275
- `raw(HEX)` (top level only): the script whose hex encoding is HEX.
7376

@@ -80,12 +83,17 @@ Descriptors consist of several types of expressions. The top level expression is
8083
- Followed by the actual key, which is either:
8184
- Hex encoded public keys (either 66 characters starting with `02` or `03` for a compressed pubkey, or 130 characters starting with `04` for an uncompressed pubkey).
8285
- Inside `wpkh` and `wsh`, only compressed public keys are permitted.
86+
- Inside `tr`, x-only pubkeys are also permitted (64 hex characters).
8387
- [WIF](https://en.bitcoin.it/wiki/Wallet_import_format) encoded private keys may be specified instead of the corresponding public key, with the same meaning.
8488
- `xpub` encoded extended public key or `xprv` encoded extended private key (as defined in [BIP 32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)).
8589
- Followed by zero or more `/NUM` unhardened and `/NUM'` hardened BIP32 derivation steps.
8690
- Optionally followed by a single `/*` or `/*'` final step to denote all (direct) unhardened or hardened children.
8791
- The usage of hardened derivation steps requires providing the private key.
8892

93+
`TREE` expressions:
94+
- any `SCRIPT` expression
95+
- An open brace `{`, a `TREE` expression, a comma `,`, a `TREE` expression, and a closing brace `}`
96+
8997
(Anywhere a `'` suffix is permitted to denote hardened derivation, the suffix `h` can be used instead.)
9098

9199
`ADDR` expressions are any type of supported address:

src/pubkey.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,9 @@ class XOnlyPubKey
237237
/** Construct an x-only pubkey from exactly 32 bytes. */
238238
explicit XOnlyPubKey(Span<const unsigned char> bytes);
239239

240+
/** Construct an x-only pubkey from a normal pubkey. */
241+
explicit XOnlyPubKey(const CPubKey& pubkey) : XOnlyPubKey(Span<const unsigned char>(pubkey.begin() + 1, pubkey.begin() + 33)) {}
242+
240243
/** Verify a Schnorr signature against this public key.
241244
*
242245
* sigbytes must be exactly 64 bytes.

src/script/descriptor.cpp

Lines changed: 144 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,10 @@ class OriginPubkeyProvider final : public PubkeyProvider
241241
class ConstPubkeyProvider final : public PubkeyProvider
242242
{
243243
CPubKey m_pubkey;
244+
bool m_xonly;
244245

245246
public:
246-
ConstPubkeyProvider(uint32_t exp_index, const CPubKey& pubkey) : PubkeyProvider(exp_index), m_pubkey(pubkey) {}
247+
ConstPubkeyProvider(uint32_t exp_index, const CPubKey& pubkey, bool xonly = false) : PubkeyProvider(exp_index), m_pubkey(pubkey), m_xonly(xonly) {}
247248
bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& key, KeyOriginInfo& info, const DescriptorCache* read_cache = nullptr, DescriptorCache* write_cache = nullptr) override
248249
{
249250
key = m_pubkey;
@@ -254,7 +255,7 @@ class ConstPubkeyProvider final : public PubkeyProvider
254255
}
255256
bool IsRange() const override { return false; }
256257
size_t GetSize() const override { return m_pubkey.size(); }
257-
std::string ToString() const override { return HexStr(m_pubkey); }
258+
std::string ToString() const override { return m_xonly ? HexStr(m_pubkey).substr(2) : HexStr(m_pubkey); }
258259
bool ToPrivateString(const SigningProvider& arg, std::string& ret) const override
259260
{
260261
CKey key;
@@ -505,6 +506,7 @@ class DescriptorImpl : public Descriptor
505506
public:
506507
DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_name(name), m_subdescriptor_args() {}
507508
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))) {}
509+
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)) {}
508510

509511
bool IsSolvable() const override
510512
{
@@ -693,10 +695,20 @@ class RawDescriptor final : public DescriptorImpl
693695
/** A parsed pk(P) descriptor. */
694696
class PKDescriptor final : public DescriptorImpl
695697
{
698+
private:
699+
const bool m_xonly;
696700
protected:
697-
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider&) const override { return Vector(GetScriptForRawPubKey(keys[0])); }
701+
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider&) const override
702+
{
703+
if (m_xonly) {
704+
CScript script = CScript() << ToByteVector(XOnlyPubKey(keys[0])) << OP_CHECKSIG;
705+
return Vector(std::move(script));
706+
} else {
707+
return Vector(GetScriptForRawPubKey(keys[0]));
708+
}
709+
}
698710
public:
699-
PKDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "pk") {}
711+
PKDescriptor(std::unique_ptr<PubkeyProvider> prov, bool xonly = false) : DescriptorImpl(Vector(std::move(prov)), "pk"), m_xonly(xonly) {}
700712
bool IsSingleType() const final { return true; }
701713
};
702714

@@ -814,6 +826,56 @@ class WSHDescriptor final : public DescriptorImpl
814826
bool IsSingleType() const final { return true; }
815827
};
816828

829+
/** A parsed tr(...) descriptor. */
830+
class TRDescriptor final : public DescriptorImpl
831+
{
832+
std::vector<int> m_depths;
833+
protected:
834+
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript> scripts, FlatSigningProvider& out) const override
835+
{
836+
TaprootBuilder builder;
837+
assert(m_depths.size() == scripts.size());
838+
for (size_t pos = 0; pos < m_depths.size(); ++pos) {
839+
builder.Add(m_depths[pos], scripts[pos], TAPROOT_LEAF_TAPSCRIPT);
840+
}
841+
if (!builder.IsComplete()) return {};
842+
assert(keys.size() == 1);
843+
XOnlyPubKey xpk(keys[0]);
844+
if (!xpk.IsFullyValid()) return {};
845+
builder.Finalize(xpk);
846+
return Vector(GetScriptForDestination(builder.GetOutput()));
847+
}
848+
bool ToStringSubScriptHelper(const SigningProvider* arg, std::string& ret, bool priv, bool normalized) const override
849+
{
850+
if (m_depths.empty()) return true;
851+
std::vector<bool> path;
852+
for (size_t pos = 0; pos < m_depths.size(); ++pos) {
853+
if (pos) ret += ',';
854+
while ((int)path.size() <= m_depths[pos]) {
855+
if (path.size()) ret += '{';
856+
path.push_back(false);
857+
}
858+
std::string tmp;
859+
if (!m_subdescriptor_args[pos]->ToStringHelper(arg, tmp, priv, normalized)) return false;
860+
ret += std::move(tmp);
861+
while (!path.empty() && path.back()) {
862+
if (path.size() > 1) ret += '}';
863+
path.pop_back();
864+
}
865+
if (!path.empty()) path.back() = true;
866+
}
867+
return true;
868+
}
869+
public:
870+
TRDescriptor(std::unique_ptr<PubkeyProvider> internal_key, std::vector<std::unique_ptr<DescriptorImpl>> descs, std::vector<int> depths) :
871+
DescriptorImpl(Vector(std::move(internal_key)), std::move(descs), "tr"), m_depths(std::move(depths))
872+
{
873+
assert(m_subdescriptor_args.size() == m_depths.size());
874+
}
875+
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
876+
bool IsSingleType() const final { return true; }
877+
};
878+
817879
////////////////////////////////////////////////////////////////////////////
818880
// Parser //
819881
////////////////////////////////////////////////////////////////////////////
@@ -823,6 +885,7 @@ enum class ParseScriptContext {
823885
P2SH, //!< Inside sh() (script becomes P2SH redeemScript)
824886
P2WPKH, //!< Inside wpkh() (no script, pubkey only)
825887
P2WSH, //!< Inside wsh() (script becomes v0 witness script)
888+
P2TR, //!< Inside tr() (either internal key, or BIP342 script leaf)
826889
};
827890

828891
/** 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
871934
error = "Uncompressed keys are not allowed";
872935
return nullptr;
873936
}
937+
} else if (data.size() == 32 && ctx == ParseScriptContext::P2TR) {
938+
unsigned char fullkey[33] = {0x02};
939+
std::copy(data.begin(), data.end(), fullkey + 1);
940+
pubkey.Set(std::begin(fullkey), std::end(fullkey));
941+
if (pubkey.IsFullyValid()) {
942+
return std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey, true);
943+
}
874944
}
875945
error = strprintf("Pubkey '%s' is invalid", str);
876946
return nullptr;
@@ -958,13 +1028,16 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
9581028
auto pubkey = ParsePubkey(key_exp_index, expr, ctx, out, error);
9591029
if (!pubkey) return nullptr;
9601030
++key_exp_index;
961-
return std::make_unique<PKDescriptor>(std::move(pubkey));
1031+
return std::make_unique<PKDescriptor>(std::move(pubkey), ctx == ParseScriptContext::P2TR);
9621032
}
963-
if (Func("pkh", expr)) {
1033+
if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH) && Func("pkh", expr)) {
9641034
auto pubkey = ParsePubkey(key_exp_index, expr, ctx, out, error);
9651035
if (!pubkey) return nullptr;
9661036
++key_exp_index;
9671037
return std::make_unique<PKHDescriptor>(std::move(pubkey));
1038+
} else if (Func("pkh", expr)) {
1039+
error = "Can only have pkh at top level, in sh(), or in wsh()";
1040+
return nullptr;
9681041
}
9691042
if (ctx == ParseScriptContext::TOP && Func("combo", expr)) {
9701043
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
9751048
error = "Can only have combo() at top level";
9761049
return nullptr;
9771050
}
978-
if ((sorted_multi = Func("sortedmulti", expr)) || Func("multi", expr)) {
1051+
if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH) && ((sorted_multi = Func("sortedmulti", expr)) || Func("multi", expr))) {
9791052
auto threshold = Expr(expr);
9801053
uint32_t thres;
9811054
std::vector<std::unique_ptr<PubkeyProvider>> providers;
@@ -1020,6 +1093,9 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
10201093
}
10211094
}
10221095
return std::make_unique<MultisigDescriptor>(thres, std::move(providers), sorted_multi);
1096+
} else if (Func("sortedmulti", expr) || Func("multi", expr)) {
1097+
error = "Can only have multi/sortedmulti at top level, in sh(), or in wsh()";
1098+
return nullptr;
10231099
}
10241100
if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH) && Func("wpkh", expr)) {
10251101
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
10571133
error = "Can only have addr() at top level";
10581134
return nullptr;
10591135
}
1136+
if (ctx == ParseScriptContext::TOP && Func("tr", expr)) {
1137+
auto arg = Expr(expr);
1138+
auto internal_key = ParsePubkey(key_exp_index, arg, ParseScriptContext::P2TR, out, error);
1139+
if (!internal_key) return nullptr;
1140+
++key_exp_index;
1141+
std::vector<std::unique_ptr<DescriptorImpl>> subscripts; //!< list of script subexpressions
1142+
std::vector<int> depths; //!< depth in the tree of each subexpression (same length subscripts)
1143+
if (expr.size()) {
1144+
if (!Const(",", expr)) {
1145+
error = strprintf("tr: expected ',', got '%c'", expr[0]);
1146+
return nullptr;
1147+
}
1148+
/** The path from the top of the tree to what we're currently processing.
1149+
* branches[i] == false: left branch in the i'th step from the top; true: right branch.
1150+
*/
1151+
std::vector<bool> branches;
1152+
// Loop over all provided scripts. In every iteration exactly one script will be processed.
1153+
// Use a do-loop because inside this if-branch we expect at least one script.
1154+
do {
1155+
// First process all open braces.
1156+
while (Const("{", expr)) {
1157+
branches.push_back(false); // new left branch
1158+
if (branches.size() > TAPROOT_CONTROL_MAX_NODE_COUNT) {
1159+
error = strprintf("tr() supports at most %i nesting levels", TAPROOT_CONTROL_MAX_NODE_COUNT);
1160+
return nullptr;
1161+
}
1162+
}
1163+
// Process the actual script expression.
1164+
auto sarg = Expr(expr);
1165+
subscripts.emplace_back(ParseScript(key_exp_index, sarg, ParseScriptContext::P2TR, out, error));
1166+
if (!subscripts.back()) return nullptr;
1167+
depths.push_back(branches.size());
1168+
// Process closing braces; one is expected for every right branch we were in.
1169+
while (branches.size() && branches.back()) {
1170+
if (!Const("}", expr)) {
1171+
error = strprintf("tr(): expected '}' after script expression");
1172+
return nullptr;
1173+
}
1174+
branches.pop_back(); // move up one level after encountering '}'
1175+
}
1176+
// If after that, we're at the end of a left branch, expect a comma.
1177+
if (branches.size() && !branches.back()) {
1178+
if (!Const(",", expr)) {
1179+
error = strprintf("tr(): expected ',' after script expression");
1180+
return nullptr;
1181+
}
1182+
branches.back() = true; // And now we're in a right branch.
1183+
}
1184+
} while (branches.size());
1185+
// After we've explored a whole tree, we must be at the end of the expression.
1186+
if (expr.size()) {
1187+
error = strprintf("tr(): expected ')' after script expression");
1188+
return nullptr;
1189+
}
1190+
}
1191+
assert(TaprootBuilder::ValidDepths(depths));
1192+
return std::make_unique<TRDescriptor>(std::move(internal_key), std::move(subscripts), std::move(depths));
1193+
} else if (Func("tr", expr)) {
1194+
error = "Can only have tr at top level";
1195+
return nullptr;
1196+
}
10601197
if (ctx == ParseScriptContext::TOP && Func("raw", expr)) {
10611198
std::string str(expr.begin(), expr.end());
10621199
if (!IsHex(str)) {

src/util/spanparsing.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ Span<const char> Expr(Span<const char>& sp)
3434
int level = 0;
3535
auto it = sp.begin();
3636
while (it != sp.end()) {
37-
if (*it == '(') {
37+
if (*it == '(' || *it == '{') {
3838
++level;
39-
} else if (level && *it == ')') {
39+
} else if (level && (*it == ')' || *it == '}')) {
4040
--level;
41-
} else if (level == 0 && (*it == ')' || *it == ',')) {
41+
} else if (level == 0 && (*it == ')' || *it == '}' || *it == ',')) {
4242
break;
4343
}
4444
++it;

0 commit comments

Comments
 (0)