Skip to content

Commit ac59112

Browse files
committed
Merge bitcoin/bitcoin#23480: Add rawtr() descriptor for P2TR with specified (tweaked) output key
544b433 Add wallet tests for spending rawtr() (Pieter Wuille) e1e3081 If P2TR tweaked key is available, sign with it (Pieter Wuille) 8d9670c Add rawtr() descriptor for P2TR with unknown tweak (Pieter Wuille) Pull request description: It may be useful to be able to represent P2TR outputs in descriptors whose script tree and/or internal key aren't known. This PR does that, by adding a `rawtr(KEY)` descriptor, where the KEY represents the output key directly. If the private key corresponding to that output key is known, it also permits signing with it. I'm not convinced this is desirable, but presumably "tr(KEY)" sounds more intended for direct use than "rawtr(KEY)". ACKs for top commit: achow101: ACK 544b433 sanket1729: code review ACK 544b433 w0xlt: reACK bitcoin/bitcoin@544b433 Tree-SHA512: 0de08de517468bc22ab0c00db471ce33144f5dc211ebc2974c6ea95709f44e830532ec5cdb0128c572513d352120bd651c4559516d4500b5b0a3d257c4b45aca
2 parents c012875 + 544b433 commit ac59112

File tree

5 files changed

+59
-3
lines changed

5 files changed

+59
-3
lines changed

doc/descriptors.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ Descriptors consist of several types of expressions. The top level expression is
7777
- `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.
7878
- `addr(ADDR)` (top level only): the script which ADDR expands to.
7979
- `raw(HEX)` (top level only): the script whose hex encoding is HEX.
80+
- `rawtr(KEY)` (top level only): P2TR output with the specified key as output key. NOTE: while it's possible to use this to construct wallets, it has several downsides, like being unable to prove no hidden script path exists. Use at your own risk.
8081

8182
`KEY` expressions:
8283
- Optionally, key origin information, consisting of:
@@ -87,7 +88,7 @@ Descriptors consist of several types of expressions. The top level expression is
8788
- Followed by the actual key, which is either:
8889
- 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).
8990
- Inside `wpkh` and `wsh`, only compressed public keys are permitted.
90-
- Inside `tr`, x-only pubkeys are also permitted (64 hex characters).
91+
- Inside `tr` and `rawtr`, x-only pubkeys are also permitted (64 hex characters).
9192
- [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.
9293
- `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)).
9394
- Followed by zero or more `/NUM` unhardened and `/NUM'` hardened BIP32 derivation steps.

src/script/descriptor.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,24 @@ class MiniscriptDescriptor final : public DescriptorImpl
10151015
bool IsSingleType() const final { return true; }
10161016
};
10171017

1018+
/** A parsed rawtr(...) descriptor. */
1019+
class RawTRDescriptor final : public DescriptorImpl
1020+
{
1021+
protected:
1022+
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript> scripts, FlatSigningProvider& out) const override
1023+
{
1024+
assert(keys.size() == 1);
1025+
XOnlyPubKey xpk(keys[0]);
1026+
if (!xpk.IsFullyValid()) return {};
1027+
WitnessV1Taproot output{xpk};
1028+
return Vector(GetScriptForDestination(output));
1029+
}
1030+
public:
1031+
RawTRDescriptor(std::unique_ptr<PubkeyProvider> output_key) : DescriptorImpl(Vector(std::move(output_key)), "rawtr") {}
1032+
std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32M; }
1033+
bool IsSingleType() const final { return true; }
1034+
};
1035+
10181036
////////////////////////////////////////////////////////////////////////////
10191037
// Parser //
10201038
////////////////////////////////////////////////////////////////////////////
@@ -1453,6 +1471,16 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
14531471
error = "Can only have tr at top level";
14541472
return nullptr;
14551473
}
1474+
if (ctx == ParseScriptContext::TOP && Func("rawtr", expr)) {
1475+
auto arg = Expr(expr);
1476+
auto output_key = ParsePubkey(key_exp_index, arg, ParseScriptContext::P2TR, out, error);
1477+
if (!output_key) return nullptr;
1478+
++key_exp_index;
1479+
return std::make_unique<RawTRDescriptor>(std::move(output_key));
1480+
} else if (Func("rawtr", expr)) {
1481+
error = "Can only have rawtr at top level";
1482+
return nullptr;
1483+
}
14561484
if (ctx == ParseScriptContext::TOP && Func("raw", expr)) {
14571485
std::string str(expr.begin(), expr.end());
14581486
if (!IsHex(str)) {
@@ -1626,6 +1654,13 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
16261654
}
16271655
}
16281656
}
1657+
// If the above doesn't work, construct a rawtr() descriptor with just the encoded x-only pubkey.
1658+
if (pubkey.IsFullyValid()) {
1659+
auto key = InferXOnlyPubkey(pubkey, ParseScriptContext::P2TR, provider);
1660+
if (key) {
1661+
return std::make_unique<RawTRDescriptor>(std::move(key));
1662+
}
1663+
}
16291664
}
16301665

16311666
if (ctx == ParseScriptContext::P2WSH) {

src/script/sign.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,11 @@ static bool SignTaproot(const SigningProvider& provider, const BaseSignatureCrea
243243
sigdata.taproot_key_path_sig = sig;
244244
}
245245
}
246+
if (sigdata.taproot_key_path_sig.size() == 0) {
247+
if (creator.CreateSchnorrSig(provider, sig, output, nullptr, nullptr, SigVersion::TAPROOT)) {
248+
sigdata.taproot_key_path_sig = sig;
249+
}
250+
}
246251
if (sigdata.taproot_key_path_sig.size()) {
247252
result = Vector(sigdata.taproot_key_path_sig);
248253
return true;

test/functional/data/rpc_decodescript.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
{
55
"asm": "1 eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
66
"address": "bcrt1pamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqz6nvlh",
7-
"desc": "addr(bcrt1pamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqz6nvlh)#v52jnujz",
7+
"desc": "rawtr(eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee)#jk7c6kys",
88
"type": "witness_v1_taproot"
99
}
1010
],

test/functional/wallet_taproot.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
OP_NUMEQUAL,
2121
taproot_construct,
2222
)
23+
from test_framework.segwit_addr import encode_segwit_address
2324

2425
# xprvs/xpubs, and m/* derived x-only pubkeys (created using independent implementation)
2526
KEYS = [
@@ -182,6 +183,9 @@ def compute_taproot_address(pubkey, scripts):
182183
"""Compute the address for a taproot output with given inner key and scripts."""
183184
return output_key_to_p2tr(taproot_construct(pubkey, scripts).output_pubkey)
184185

186+
def compute_raw_taproot_address(pubkey):
187+
return encode_segwit_address("bcrt", 1, pubkey)
188+
185189
class WalletTaprootTest(BitcoinTestFramework):
186190
"""Test generation and spending of P2TR address outputs."""
187191

@@ -216,7 +220,12 @@ def make_addr(treefn, keys, i):
216220
args = []
217221
for j in range(len(keys)):
218222
args.append(keys[j]['pubs'][i])
219-
return compute_taproot_address(*treefn(*args))
223+
tree = treefn(*args)
224+
if isinstance(tree, tuple):
225+
return compute_taproot_address(*tree)
226+
if isinstance(tree, bytes):
227+
return compute_raw_taproot_address(tree)
228+
assert False
220229

221230
def do_test_addr(self, comment, pattern, privmap, treefn, keys):
222231
self.log.info("Testing %s address derivation" % comment)
@@ -444,6 +453,12 @@ def run_test(self):
444453
[True, False],
445454
lambda k1, k2: (key(k2), [multi_a(1, ([H_POINT] * rnd_pos) + [k1] + ([H_POINT] * (MAX_PUBKEYS_PER_MULTI_A - 1 - rnd_pos)))])
446455
)
456+
self.do_test(
457+
"rawtr(XPRV)",
458+
"rawtr($1/*)",
459+
[True],
460+
lambda k1: key(k1)
461+
)
447462

448463
self.log.info("Sending everything back...")
449464

0 commit comments

Comments
 (0)