diff options
author | Andrew Chow <achow101-github@achow101.com> | 2022-08-09 16:26:17 -0400 |
---|---|---|
committer | Andrew Chow <achow101-github@achow101.com> | 2022-08-09 16:36:00 -0400 |
commit | ac59112a6a093e641ce2803260dd9de97b1cd961 (patch) | |
tree | 0d5e7e69bd3ac628195cfad542bb95c607160494 | |
parent | c012875b9ded0a5183602f002738ca823d559518 (diff) | |
parent | 544b4332f0e122167bdb94dc963405422faa30cb (diff) | |
download | bitcoin-ac59112a6a093e641ce2803260dd9de97b1cd961.tar.xz |
Merge bitcoin/bitcoin#23480: Add rawtr() descriptor for P2TR with specified (tweaked) output key
544b4332f0e122167bdb94dc963405422faa30cb Add wallet tests for spending rawtr() (Pieter Wuille)
e1e3081200a71b6c9b0dcf236bc2a37ed1aa7552 If P2TR tweaked key is available, sign with it (Pieter Wuille)
8d9670ccb756592bddb2a269bf5078d62658537b 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 544b4332f0e122167bdb94dc963405422faa30cb
sanket1729:
code review ACK 544b4332f0e122167bdb94dc963405422faa30cb
w0xlt:
reACK https://github.com/bitcoin/bitcoin/pull/23480/commits/544b4332f0e122167bdb94dc963405422faa30cb
Tree-SHA512: 0de08de517468bc22ab0c00db471ce33144f5dc211ebc2974c6ea95709f44e830532ec5cdb0128c572513d352120bd651c4559516d4500b5b0a3d257c4b45aca
-rw-r--r-- | doc/descriptors.md | 3 | ||||
-rw-r--r-- | src/script/descriptor.cpp | 35 | ||||
-rw-r--r-- | src/script/sign.cpp | 5 | ||||
-rw-r--r-- | test/functional/data/rpc_decodescript.json | 2 | ||||
-rwxr-xr-x | test/functional/wallet_taproot.py | 17 |
5 files changed, 59 insertions, 3 deletions
diff --git a/doc/descriptors.md b/doc/descriptors.md index ab2face4f0..60cc36db07 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 34a4da74f8..db386c9ab8 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<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript> 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<PubkeyProvider> output_key) : DescriptorImpl(Vector(std::move(output_key)), "rawtr") {} + std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32M; } + bool IsSingleType() const final { return true; } +}; + //////////////////////////////////////////////////////////////////////////// // Parser // //////////////////////////////////////////////////////////////////////////// @@ -1453,6 +1471,16 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const error = "Can only have tr at top level"; return nullptr; } + if (ctx == ParseScriptContext::TOP && Func("rawtr", expr)) { + auto arg = Expr(expr); + auto output_key = ParsePubkey(key_exp_index, arg, ParseScriptContext::P2TR, out, error); + if (!output_key) return nullptr; + ++key_exp_index; + return std::make_unique<RawTRDescriptor>(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<DescriptorImpl> 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<RawTRDescriptor>(std::move(key)); + } + } } if (ctx == ParseScriptContext::P2WSH) { diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 3b8071d9d1..79cf918c9b 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; diff --git a/test/functional/data/rpc_decodescript.json b/test/functional/data/rpc_decodescript.json index 8903f5efac..4a15ae8792 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" } ], diff --git a/test/functional/wallet_taproot.py b/test/functional/wallet_taproot.py index c8d4a1da45..3c630ba433 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...") |