From 8d9670ccb756592bddb2a269bf5078d62658537b Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Tue, 9 Nov 2021 23:08:16 -0500 Subject: [PATCH 1/3] Add rawtr() descriptor for P2TR with unknown tweak --- doc/descriptors.md | 3 +- src/script/descriptor.cpp | 35 ++++++++++++++++++++++ test/functional/data/rpc_decodescript.json | 2 +- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/doc/descriptors.md b/doc/descriptors.md index ab2face4f07..60cc36db07f 100644 --- a/doc/descriptors.md +++ b/doc/descriptors.md @@ -77,6 +77,7 @@ Descriptors consist of several types of expressions. The top level expression is - `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. - `addr(ADDR)` (top level only): the script which ADDR expands to. - `raw(HEX)` (top level only): the script whose hex encoding is HEX. +- `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. `KEY` expressions: - Optionally, key origin information, consisting of: @@ -87,7 +88,7 @@ Descriptors consist of several types of expressions. The top level expression is - Followed by the actual key, which is either: - 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). - Inside `wpkh` and `wsh`, only compressed public keys are permitted. - - Inside `tr`, x-only pubkeys are also permitted (64 hex characters). + - Inside `tr` and `rawtr`, x-only pubkeys are also permitted (64 hex characters). - [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. - `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)). - Followed by zero or more `/NUM` unhardened and `/NUM'` hardened BIP32 derivation steps. diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 34a4da74f8b..db386c9ab86 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -1015,6 +1015,24 @@ public: bool IsSingleType() const final { return true; } }; +/** A parsed rawtr(...) descriptor. */ +class RawTRDescriptor final : public DescriptorImpl +{ +protected: + std::vector MakeScripts(const std::vector& keys, Span scripts, FlatSigningProvider& out) const override + { + assert(keys.size() == 1); + XOnlyPubKey xpk(keys[0]); + if (!xpk.IsFullyValid()) return {}; + WitnessV1Taproot output{xpk}; + return Vector(GetScriptForDestination(output)); + } +public: + RawTRDescriptor(std::unique_ptr output_key) : DescriptorImpl(Vector(std::move(output_key)), "rawtr") {} + std::optional GetOutputType() const override { return OutputType::BECH32M; } + bool IsSingleType() const final { return true; } +}; + //////////////////////////////////////////////////////////////////////////// // Parser // //////////////////////////////////////////////////////////////////////////// @@ -1453,6 +1471,16 @@ std::unique_ptr ParseScript(uint32_t& key_exp_index, Span(std::move(output_key)); + } else if (Func("rawtr", expr)) { + error = "Can only have rawtr at top level"; + return nullptr; + } if (ctx == ParseScriptContext::TOP && Func("raw", expr)) { std::string str(expr.begin(), expr.end()); if (!IsHex(str)) { @@ -1626,6 +1654,13 @@ std::unique_ptr InferScript(const CScript& script, ParseScriptCo } } } + // If the above doesn't work, construct a rawtr() descriptor with just the encoded x-only pubkey. + if (pubkey.IsFullyValid()) { + auto key = InferXOnlyPubkey(pubkey, ParseScriptContext::P2TR, provider); + if (key) { + return std::make_unique(std::move(key)); + } + } } if (ctx == ParseScriptContext::P2WSH) { diff --git a/test/functional/data/rpc_decodescript.json b/test/functional/data/rpc_decodescript.json index 8903f5efacd..4a15ae8792f 100644 --- a/test/functional/data/rpc_decodescript.json +++ b/test/functional/data/rpc_decodescript.json @@ -4,7 +4,7 @@ { "asm": "1 eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "address": "bcrt1pamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqz6nvlh", - "desc": "addr(bcrt1pamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqz6nvlh)#v52jnujz", + "desc": "rawtr(eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee)#jk7c6kys", "type": "witness_v1_taproot" } ], From e1e3081200a71b6c9b0dcf236bc2a37ed1aa7552 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Tue, 9 Nov 2021 23:09:00 -0500 Subject: [PATCH 2/3] If P2TR tweaked key is available, sign with it --- src/script/sign.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/script/sign.cpp b/src/script/sign.cpp index a3681d26ccb..ee4e1aeb11c 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -243,6 +243,11 @@ static bool SignTaproot(const SigningProvider& provider, const BaseSignatureCrea sigdata.taproot_key_path_sig = sig; } } + if (sigdata.taproot_key_path_sig.size() == 0) { + if (creator.CreateSchnorrSig(provider, sig, output, nullptr, nullptr, SigVersion::TAPROOT)) { + sigdata.taproot_key_path_sig = sig; + } + } if (sigdata.taproot_key_path_sig.size()) { result = Vector(sigdata.taproot_key_path_sig); return true; From 544b4332f0e122167bdb94dc963405422faa30cb Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Tue, 9 Nov 2021 23:23:49 -0500 Subject: [PATCH 3/3] Add wallet tests for spending rawtr() --- test/functional/wallet_taproot.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/functional/wallet_taproot.py b/test/functional/wallet_taproot.py index c8d4a1da454..3c630ba4332 100755 --- a/test/functional/wallet_taproot.py +++ b/test/functional/wallet_taproot.py @@ -20,6 +20,7 @@ from test_framework.script import ( OP_NUMEQUAL, taproot_construct, ) +from test_framework.segwit_addr import encode_segwit_address # xprvs/xpubs, and m/* derived x-only pubkeys (created using independent implementation) KEYS = [ @@ -182,6 +183,9 @@ def compute_taproot_address(pubkey, scripts): """Compute the address for a taproot output with given inner key and scripts.""" return output_key_to_p2tr(taproot_construct(pubkey, scripts).output_pubkey) +def compute_raw_taproot_address(pubkey): + return encode_segwit_address("bcrt", 1, pubkey) + class WalletTaprootTest(BitcoinTestFramework): """Test generation and spending of P2TR address outputs.""" @@ -216,7 +220,12 @@ class WalletTaprootTest(BitcoinTestFramework): args = [] for j in range(len(keys)): args.append(keys[j]['pubs'][i]) - return compute_taproot_address(*treefn(*args)) + tree = treefn(*args) + if isinstance(tree, tuple): + return compute_taproot_address(*tree) + if isinstance(tree, bytes): + return compute_raw_taproot_address(tree) + assert False def do_test_addr(self, comment, pattern, privmap, treefn, keys): self.log.info("Testing %s address derivation" % comment) @@ -444,6 +453,12 @@ class WalletTaprootTest(BitcoinTestFramework): [True, False], lambda k1, k2: (key(k2), [multi_a(1, ([H_POINT] * rnd_pos) + [k1] + ([H_POINT] * (MAX_PUBKEYS_PER_MULTI_A - 1 - rnd_pos)))]) ) + self.do_test( + "rawtr(XPRV)", + "rawtr($1/*)", + [True], + lambda k1: key(k1) + ) self.log.info("Sending everything back...")