mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-20 14:05:23 +01:00
Merge bitcoin/bitcoin#22051: Basic Taproot derivation support for descriptors
2667366aaa
tests: check derivation of P2TR (Pieter Wuille)7cedafc541
Add tr() descriptor (derivation only, no signing) (Pieter Wuille)90fcac365e
Add TaprootBuilder class (Pieter Wuille)5f6cc8daa8
Add XOnlyPubKey::CreateTapTweak (Pieter Wuille)2fbfb1becb
Make consensus checking of tweaks in pubkey.* Taproot-specific (Pieter Wuille)a4bf84039c
Separate WitnessV1Taproot variant in CTxDestination (Pieter Wuille)41839bdb89
Avoid dependence on CTxDestination index order (Pieter Wuille)31df02a070
Change Solver() output for WITNESS_V1_TAPROOT (Pieter Wuille)4b1cc08f9f
Make XOnlyPubKey act like byte container (Pieter Wuille) Pull request description: This is a subset of #21365, to aide review. This adds support `tr(KEY)` or `tr(KEY,SCRIPT)` or `tr(KEY,{{S1,{{S2,S3},...}},...})` descriptors, describing Taproot outputs with specified internal key, and optionally any number of scripts, in nested groups of 2 inside `{`/`}` if there are more than one. While it permits importing `tr(KEY)`, anything beyond that is just laying foundations for more features later. Missing: * Signing support (see #21365) * Support for more interesting scripts inside the tree (only `pk(KEY)` is supported for now). In particular, a multisig policy based on the new `OP_CHECKSIGADD` opcode would be very useful. * Inferring `tr()` descriptors from outputs (given sufficient information). * `getaddressinfo` support. * MuSig support. Standardizing that is still an ongoing effort, and is generally kind of useless without corresponding PSBT support. * Convenient ways of constructing descriptors without spendable internal key (especially ones that arent't trivially recognizable as such). ACKs for top commit: Sjors: utACK2667366
(based on https://github.com/bitcoin/bitcoin/pull/21365#issuecomment-846945215 review, plus the new functional test) achow101: Code Review ACK2667366aaa
lsilva01: Tested ACK2667366aaa
meshcollider: utACK2667366aaa
Tree-SHA512: 61046fef22c561228338cb178422f0b782ef6587ec8208d3ce2bd07afcff29a664b54b35c6b01226eb70b6540b43f6dd245043d09aa6cb6db1381b6042667e75
This commit is contained in:
commit
c7dd9ff71b
16 changed files with 834 additions and 55 deletions
|
@ -30,6 +30,7 @@ Output descriptors currently support:
|
|||
- Pay-to-witness-pubkey-hash scripts (P2WPKH), through the `wpkh` function.
|
||||
- Pay-to-script-hash scripts (P2SH), through the `sh` function.
|
||||
- Pay-to-witness-script-hash scripts (P2WSH), through the `wsh` function.
|
||||
- Pay-to-taproot outputs (P2TR), through the `tr` function.
|
||||
- Multisig scripts, through the `multi` function.
|
||||
- Multisig scripts where the public keys are sorted lexicographically, through the `sortedmulti` function.
|
||||
- Any type of supported address through the `addr` function.
|
||||
|
@ -54,6 +55,7 @@ Output descriptors currently support:
|
|||
- `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'`.
|
||||
- `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).
|
||||
- `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.
|
||||
- `tr(c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,{pk(fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),pk(e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)})` describes a P2TR output with the `c6...` x-only pubkey as internal key, and two script paths.
|
||||
|
||||
## Reference
|
||||
|
||||
|
@ -61,13 +63,14 @@ Descriptors consist of several types of expressions. The top level expression is
|
|||
|
||||
`SCRIPT` expressions:
|
||||
- `sh(SCRIPT)` (top level only): P2SH embed the argument.
|
||||
- `wsh(SCRIPT)` (not inside another 'wsh'): P2WSH embed the argument.
|
||||
- `wsh(SCRIPT)` (top level or inside `sh` only): P2WSH embed the argument.
|
||||
- `pk(KEY)` (anywhere): P2PK output for the given public key.
|
||||
- `pkh(KEY)` (anywhere): P2PKH output for the given public key (use `addr` if you only know the pubkey hash).
|
||||
- `wpkh(KEY)` (not inside `wsh`): P2WPKH output for the given compressed pubkey.
|
||||
- `pkh(KEY)` (not inside `tr`): P2PKH output for the given public key (use `addr` if you only know the pubkey hash).
|
||||
- `wpkh(KEY)` (top level or inside `sh` only): P2WPKH output for the given compressed pubkey.
|
||||
- `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))`.
|
||||
- `multi(k,KEY_1,KEY_2,...,KEY_n)` (anywhere): k-of-n multisig script.
|
||||
- `sortedmulti(k,KEY_1,KEY_2,...,KEY_n)` (anywhere): k-of-n multisig script with keys sorted lexicographically in the resulting script.
|
||||
- `multi(k,KEY_1,KEY_2,...,KEY_n)` (not inside `tr`): k-of-n multisig script.
|
||||
- `sortedmulti(k,KEY_1,KEY_2,...,KEY_n)` (not inside `tr`): k-of-n multisig script with keys sorted lexicographically in the resulting script.
|
||||
- `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.
|
||||
|
||||
|
@ -80,12 +83,17 @@ 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).
|
||||
- [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.
|
||||
- Optionally followed by a single `/*` or `/*'` final step to denote all (direct) unhardened or hardened children.
|
||||
- The usage of hardened derivation steps requires providing the private key.
|
||||
|
||||
`TREE` expressions:
|
||||
- any `SCRIPT` expression
|
||||
- An open brace `{`, a `TREE` expression, a comma `,`, a `TREE` expression, and a closing brace `}`
|
||||
|
||||
(Anywhere a `'` suffix is permitted to denote hardened derivation, the suffix `h` can be used instead.)
|
||||
|
||||
`ADDR` expressions are any type of supported address:
|
||||
|
|
|
@ -54,6 +54,14 @@ public:
|
|||
return bech32::Encode(bech32::Encoding::BECH32, m_params.Bech32HRP(), data);
|
||||
}
|
||||
|
||||
std::string operator()(const WitnessV1Taproot& tap) const
|
||||
{
|
||||
std::vector<unsigned char> data = {1};
|
||||
data.reserve(53);
|
||||
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, tap.begin(), tap.end());
|
||||
return bech32::Encode(bech32::Encoding::BECH32M, m_params.Bech32HRP(), data);
|
||||
}
|
||||
|
||||
std::string operator()(const WitnessUnknown& id) const
|
||||
{
|
||||
if (id.version < 1 || id.version > 16 || id.length < 2 || id.length > 40) {
|
||||
|
@ -135,6 +143,13 @@ CTxDestination DecodeDestination(const std::string& str, const CChainParams& par
|
|||
return CNoDestination();
|
||||
}
|
||||
|
||||
if (version == 1 && data.size() == WITNESS_V1_TAPROOT_SIZE) {
|
||||
static_assert(WITNESS_V1_TAPROOT_SIZE == WitnessV1Taproot::size());
|
||||
WitnessV1Taproot tap;
|
||||
std::copy(data.begin(), data.end(), tap.begin());
|
||||
return tap;
|
||||
}
|
||||
|
||||
if (version > 16) {
|
||||
error_str = "Invalid Bech32 address witness version";
|
||||
return CNoDestination();
|
||||
|
|
|
@ -180,6 +180,12 @@ XOnlyPubKey::XOnlyPubKey(Span<const unsigned char> bytes)
|
|||
std::copy(bytes.begin(), bytes.end(), m_keydata.begin());
|
||||
}
|
||||
|
||||
bool XOnlyPubKey::IsFullyValid() const
|
||||
{
|
||||
secp256k1_xonly_pubkey pubkey;
|
||||
return secp256k1_xonly_pubkey_parse(secp256k1_context_verify, &pubkey, m_keydata.data());
|
||||
}
|
||||
|
||||
bool XOnlyPubKey::VerifySchnorr(const uint256& msg, Span<const unsigned char> sigbytes) const
|
||||
{
|
||||
assert(sigbytes.size() == 64);
|
||||
|
@ -188,13 +194,45 @@ bool XOnlyPubKey::VerifySchnorr(const uint256& msg, Span<const unsigned char> si
|
|||
return secp256k1_schnorrsig_verify(secp256k1_context_verify, sigbytes.data(), msg.begin(), &pubkey);
|
||||
}
|
||||
|
||||
bool XOnlyPubKey::CheckPayToContract(const XOnlyPubKey& base, const uint256& hash, bool parity) const
|
||||
static const CHashWriter HASHER_TAPTWEAK = TaggedHash("TapTweak");
|
||||
|
||||
uint256 XOnlyPubKey::ComputeTapTweakHash(const uint256* merkle_root) const
|
||||
{
|
||||
if (merkle_root == nullptr) {
|
||||
// We have no scripts. The actual tweak does not matter, but follow BIP341 here to
|
||||
// allow for reproducible tweaking.
|
||||
return (CHashWriter(HASHER_TAPTWEAK) << m_keydata).GetSHA256();
|
||||
} else {
|
||||
return (CHashWriter(HASHER_TAPTWEAK) << m_keydata << *merkle_root).GetSHA256();
|
||||
}
|
||||
}
|
||||
|
||||
bool XOnlyPubKey::CheckTapTweak(const XOnlyPubKey& internal, const uint256& merkle_root, bool parity) const
|
||||
{
|
||||
secp256k1_xonly_pubkey internal_key;
|
||||
if (!secp256k1_xonly_pubkey_parse(secp256k1_context_verify, &internal_key, internal.data())) return false;
|
||||
uint256 tweak = internal.ComputeTapTweakHash(&merkle_root);
|
||||
return secp256k1_xonly_pubkey_tweak_add_check(secp256k1_context_verify, m_keydata.begin(), parity, &internal_key, tweak.begin());
|
||||
}
|
||||
|
||||
std::optional<std::pair<XOnlyPubKey, bool>> XOnlyPubKey::CreateTapTweak(const uint256* merkle_root) const
|
||||
{
|
||||
secp256k1_xonly_pubkey base_point;
|
||||
if (!secp256k1_xonly_pubkey_parse(secp256k1_context_verify, &base_point, base.data())) return false;
|
||||
return secp256k1_xonly_pubkey_tweak_add_check(secp256k1_context_verify, m_keydata.begin(), parity, &base_point, hash.begin());
|
||||
if (!secp256k1_xonly_pubkey_parse(secp256k1_context_verify, &base_point, data())) return std::nullopt;
|
||||
secp256k1_pubkey out;
|
||||
uint256 tweak = ComputeTapTweakHash(merkle_root);
|
||||
if (!secp256k1_xonly_pubkey_tweak_add(secp256k1_context_verify, &out, &base_point, tweak.data())) return std::nullopt;
|
||||
int parity = -1;
|
||||
std::pair<XOnlyPubKey, bool> ret;
|
||||
secp256k1_xonly_pubkey out_xonly;
|
||||
if (!secp256k1_xonly_pubkey_from_pubkey(secp256k1_context_verify, &out_xonly, &parity, &out)) return std::nullopt;
|
||||
secp256k1_xonly_pubkey_serialize(secp256k1_context_verify, ret.first.begin(), &out_xonly);
|
||||
assert(parity == 0 || parity == 1);
|
||||
ret.second = parity;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool CPubKey::Verify(const uint256 &hash, const std::vector<unsigned char>& vchSig) const {
|
||||
if (!IsValid())
|
||||
return false;
|
||||
|
|
42
src/pubkey.h
42
src/pubkey.h
|
@ -13,6 +13,7 @@
|
|||
#include <uint256.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
const unsigned int BIP32_EXTKEY_SIZE = 74;
|
||||
|
@ -222,19 +223,56 @@ private:
|
|||
uint256 m_keydata;
|
||||
|
||||
public:
|
||||
/** Construct an empty x-only pubkey. */
|
||||
XOnlyPubKey() = default;
|
||||
|
||||
XOnlyPubKey(const XOnlyPubKey&) = default;
|
||||
XOnlyPubKey& operator=(const XOnlyPubKey&) = default;
|
||||
|
||||
/** Determine if this pubkey is fully valid. This is true for approximately 50% of all
|
||||
* possible 32-byte arrays. If false, VerifySchnorr and CreatePayToContract will always
|
||||
* fail. */
|
||||
bool IsFullyValid() const;
|
||||
|
||||
/** Construct an x-only pubkey from exactly 32 bytes. */
|
||||
explicit XOnlyPubKey(Span<const unsigned char> bytes);
|
||||
|
||||
/** Construct an x-only pubkey from a normal pubkey. */
|
||||
explicit XOnlyPubKey(const CPubKey& pubkey) : XOnlyPubKey(Span<const unsigned char>(pubkey.begin() + 1, pubkey.begin() + 33)) {}
|
||||
|
||||
/** Verify a Schnorr signature against this public key.
|
||||
*
|
||||
* sigbytes must be exactly 64 bytes.
|
||||
*/
|
||||
bool VerifySchnorr(const uint256& msg, Span<const unsigned char> sigbytes) const;
|
||||
bool CheckPayToContract(const XOnlyPubKey& base, const uint256& hash, bool parity) const;
|
||||
|
||||
/** Compute the Taproot tweak as specified in BIP341, with *this as internal
|
||||
* key:
|
||||
* - if merkle_root == nullptr: H_TapTweak(xonly_pubkey)
|
||||
* - otherwise: H_TapTweak(xonly_pubkey || *merkle_root)
|
||||
*
|
||||
* Note that the behavior of this function with merkle_root != nullptr is
|
||||
* consensus critical.
|
||||
*/
|
||||
uint256 ComputeTapTweakHash(const uint256* merkle_root) const;
|
||||
|
||||
/** Verify that this is a Taproot tweaked output point, against a specified internal key,
|
||||
* Merkle root, and parity. */
|
||||
bool CheckTapTweak(const XOnlyPubKey& internal, const uint256& merkle_root, bool parity) const;
|
||||
|
||||
/** Construct a Taproot tweaked output point with this point as internal key. */
|
||||
std::optional<std::pair<XOnlyPubKey, bool>> CreateTapTweak(const uint256* merkle_root) const;
|
||||
|
||||
const unsigned char& operator[](int pos) const { return *(m_keydata.begin() + pos); }
|
||||
const unsigned char* data() const { return m_keydata.begin(); }
|
||||
size_t size() const { return m_keydata.size(); }
|
||||
static constexpr size_t size() { return decltype(m_keydata)::size(); }
|
||||
const unsigned char* begin() const { return m_keydata.begin(); }
|
||||
const unsigned char* end() const { return m_keydata.end(); }
|
||||
unsigned char* begin() { return m_keydata.begin(); }
|
||||
unsigned char* end() { return m_keydata.end(); }
|
||||
bool operator==(const XOnlyPubKey& other) const { return m_keydata == other.m_keydata; }
|
||||
bool operator!=(const XOnlyPubKey& other) const { return m_keydata != other.m_keydata; }
|
||||
bool operator<(const XOnlyPubKey& other) const { return m_keydata < other.m_keydata; }
|
||||
};
|
||||
|
||||
struct CExtPubKey {
|
||||
|
|
|
@ -301,6 +301,16 @@ public:
|
|||
return obj;
|
||||
}
|
||||
|
||||
UniValue operator()(const WitnessV1Taproot& tap) const
|
||||
{
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.pushKV("isscript", true);
|
||||
obj.pushKV("iswitness", true);
|
||||
obj.pushKV("witness_version", 1);
|
||||
obj.pushKV("witness_program", HexStr(tap));
|
||||
return obj;
|
||||
}
|
||||
|
||||
UniValue operator()(const WitnessUnknown& id) const
|
||||
{
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
@ -638,6 +640,20 @@ public:
|
|||
std::optional<OutputType> GetOutputType() const override { return std::nullopt; }
|
||||
};
|
||||
|
||||
static std::optional<OutputType> OutputTypeFromDestination(const CTxDestination& dest) {
|
||||
if (std::holds_alternative<PKHash>(dest) ||
|
||||
std::holds_alternative<ScriptHash>(dest)) {
|
||||
return OutputType::LEGACY;
|
||||
}
|
||||
if (std::holds_alternative<WitnessV0KeyHash>(dest) ||
|
||||
std::holds_alternative<WitnessV0ScriptHash>(dest) ||
|
||||
std::holds_alternative<WitnessV1Taproot>(dest) ||
|
||||
std::holds_alternative<WitnessUnknown>(dest)) {
|
||||
return OutputType::BECH32;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/** A parsed addr(A) descriptor. */
|
||||
class AddressDescriptor final : public DescriptorImpl
|
||||
{
|
||||
|
@ -651,15 +667,7 @@ public:
|
|||
|
||||
std::optional<OutputType> GetOutputType() const override
|
||||
{
|
||||
switch (m_destination.index()) {
|
||||
case 1 /* PKHash */:
|
||||
case 2 /* ScriptHash */: return OutputType::LEGACY;
|
||||
case 3 /* WitnessV0ScriptHash */:
|
||||
case 4 /* WitnessV0KeyHash */:
|
||||
case 5 /* WitnessUnknown */: return OutputType::BECH32;
|
||||
case 0 /* CNoDestination */:
|
||||
default: return std::nullopt;
|
||||
}
|
||||
return OutputTypeFromDestination(m_destination);
|
||||
}
|
||||
bool IsSingleType() const final { return true; }
|
||||
};
|
||||
|
@ -679,15 +687,7 @@ public:
|
|||
{
|
||||
CTxDestination dest;
|
||||
ExtractDestination(m_script, dest);
|
||||
switch (dest.index()) {
|
||||
case 1 /* PKHash */:
|
||||
case 2 /* ScriptHash */: return OutputType::LEGACY;
|
||||
case 3 /* WitnessV0ScriptHash */:
|
||||
case 4 /* WitnessV0KeyHash */:
|
||||
case 5 /* WitnessUnknown */: return OutputType::BECH32;
|
||||
case 0 /* CNoDestination */:
|
||||
default: return std::nullopt;
|
||||
}
|
||||
return OutputTypeFromDestination(dest);
|
||||
}
|
||||
bool IsSingleType() const final { return true; }
|
||||
};
|
||||
|
@ -695,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; }
|
||||
};
|
||||
|
||||
|
@ -816,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 //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -825,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). */
|
||||
|
@ -873,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;
|
||||
|
@ -960,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);
|
||||
|
@ -977,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;
|
||||
|
@ -1022,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);
|
||||
|
@ -1059,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)) {
|
||||
|
|
|
@ -1484,9 +1484,8 @@ template PrecomputedTransactionData::PrecomputedTransactionData(const CTransacti
|
|||
template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo);
|
||||
|
||||
static const CHashWriter HASHER_TAPSIGHASH = TaggedHash("TapSighash");
|
||||
static const CHashWriter HASHER_TAPLEAF = TaggedHash("TapLeaf");
|
||||
static const CHashWriter HASHER_TAPBRANCH = TaggedHash("TapBranch");
|
||||
static const CHashWriter HASHER_TAPTWEAK = TaggedHash("TapTweak");
|
||||
const CHashWriter HASHER_TAPLEAF = TaggedHash("TapLeaf");
|
||||
const CHashWriter HASHER_TAPBRANCH = TaggedHash("TapBranch");
|
||||
|
||||
static bool HandleMissingData(MissingDataBehavior mdb)
|
||||
{
|
||||
|
@ -1869,10 +1868,8 @@ static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, c
|
|||
}
|
||||
k = ss_branch.GetSHA256();
|
||||
}
|
||||
// Compute the tweak from the Merkle root and the internal pubkey.
|
||||
k = (CHashWriter(HASHER_TAPTWEAK) << MakeSpan(p) << k).GetSHA256();
|
||||
// Verify that the output pubkey matches the tweaked internal pubkey, after correcting for parity.
|
||||
return q.CheckPayToContract(p, k, control[0] & 1);
|
||||
return q.CheckTapTweak(p, k, control[0] & 1);
|
||||
}
|
||||
|
||||
static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector<unsigned char>& program, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror, bool is_p2sh)
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#ifndef BITCOIN_SCRIPT_INTERPRETER_H
|
||||
#define BITCOIN_SCRIPT_INTERPRETER_H
|
||||
|
||||
#include <hash.h>
|
||||
#include <script/script_error.h>
|
||||
#include <span.h>
|
||||
#include <primitives/transaction.h>
|
||||
|
@ -218,6 +219,9 @@ static constexpr size_t TAPROOT_CONTROL_NODE_SIZE = 32;
|
|||
static constexpr size_t TAPROOT_CONTROL_MAX_NODE_COUNT = 128;
|
||||
static constexpr size_t TAPROOT_CONTROL_MAX_SIZE = TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * TAPROOT_CONTROL_MAX_NODE_COUNT;
|
||||
|
||||
extern const CHashWriter HASHER_TAPLEAF; //!< Hasher with tag "TapLeaf" pre-fed to it.
|
||||
extern const CHashWriter HASHER_TAPBRANCH; //!< Hasher with tag "TapBranch" pre-fed to it.
|
||||
|
||||
template <class T>
|
||||
uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache = nullptr);
|
||||
|
||||
|
|
|
@ -6,8 +6,11 @@
|
|||
#include <script/standard.h>
|
||||
|
||||
#include <crypto/sha256.h>
|
||||
#include <hash.h>
|
||||
#include <pubkey.h>
|
||||
#include <script/interpreter.h>
|
||||
#include <script/script.h>
|
||||
#include <util/strencodings.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
|
@ -155,15 +158,14 @@ TxoutType Solver(const CScript& scriptPubKey, std::vector<std::vector<unsigned c
|
|||
std::vector<unsigned char> witnessprogram;
|
||||
if (scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
|
||||
if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_KEYHASH_SIZE) {
|
||||
vSolutionsRet.push_back(witnessprogram);
|
||||
vSolutionsRet.push_back(std::move(witnessprogram));
|
||||
return TxoutType::WITNESS_V0_KEYHASH;
|
||||
}
|
||||
if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_SCRIPTHASH_SIZE) {
|
||||
vSolutionsRet.push_back(witnessprogram);
|
||||
vSolutionsRet.push_back(std::move(witnessprogram));
|
||||
return TxoutType::WITNESS_V0_SCRIPTHASH;
|
||||
}
|
||||
if (witnessversion == 1 && witnessprogram.size() == WITNESS_V1_TAPROOT_SIZE) {
|
||||
vSolutionsRet.push_back(std::vector<unsigned char>{(unsigned char)witnessversion});
|
||||
vSolutionsRet.push_back(std::move(witnessprogram));
|
||||
return TxoutType::WITNESS_V1_TAPROOT;
|
||||
}
|
||||
|
@ -242,8 +244,13 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
|
|||
addressRet = hash;
|
||||
return true;
|
||||
}
|
||||
case TxoutType::WITNESS_UNKNOWN:
|
||||
case TxoutType::WITNESS_V1_TAPROOT: {
|
||||
WitnessV1Taproot tap;
|
||||
std::copy(vSolutions[0].begin(), vSolutions[0].end(), tap.begin());
|
||||
addressRet = tap;
|
||||
return true;
|
||||
}
|
||||
case TxoutType::WITNESS_UNKNOWN: {
|
||||
WitnessUnknown unk;
|
||||
unk.version = vSolutions[0][0];
|
||||
std::copy(vSolutions[1].begin(), vSolutions[1].end(), unk.program);
|
||||
|
@ -329,6 +336,11 @@ public:
|
|||
return CScript() << OP_0 << ToByteVector(id);
|
||||
}
|
||||
|
||||
CScript operator()(const WitnessV1Taproot& tap) const
|
||||
{
|
||||
return CScript() << OP_1 << ToByteVector(tap);
|
||||
}
|
||||
|
||||
CScript operator()(const WitnessUnknown& id) const
|
||||
{
|
||||
return CScript() << CScript::EncodeOP_N(id.version) << std::vector<unsigned char>(id.program, id.program + id.length);
|
||||
|
@ -361,3 +373,99 @@ CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys)
|
|||
bool IsValidDestination(const CTxDestination& dest) {
|
||||
return dest.index() != 0;
|
||||
}
|
||||
|
||||
/*static*/ TaprootBuilder::NodeInfo TaprootBuilder::Combine(NodeInfo&& a, NodeInfo&& b)
|
||||
{
|
||||
NodeInfo ret;
|
||||
/* Lexicographically sort a and b's hash, and compute parent hash. */
|
||||
if (a.hash < b.hash) {
|
||||
ret.hash = (CHashWriter(HASHER_TAPBRANCH) << a.hash << b.hash).GetSHA256();
|
||||
} else {
|
||||
ret.hash = (CHashWriter(HASHER_TAPBRANCH) << b.hash << a.hash).GetSHA256();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void TaprootBuilder::Insert(TaprootBuilder::NodeInfo&& node, int depth)
|
||||
{
|
||||
assert(depth >= 0 && (size_t)depth <= TAPROOT_CONTROL_MAX_NODE_COUNT);
|
||||
/* We cannot insert a leaf at a lower depth while a deeper branch is unfinished. Doing
|
||||
* so would mean the Add() invocations do not correspond to a DFS traversal of a
|
||||
* binary tree. */
|
||||
if ((size_t)depth + 1 < m_branch.size()) {
|
||||
m_valid = false;
|
||||
return;
|
||||
}
|
||||
/* As long as an entry in the branch exists at the specified depth, combine it and propagate up.
|
||||
* The 'node' variable is overwritten here with the newly combined node. */
|
||||
while (m_valid && m_branch.size() > (size_t)depth && m_branch[depth].has_value()) {
|
||||
node = Combine(std::move(node), std::move(*m_branch[depth]));
|
||||
m_branch.pop_back();
|
||||
if (depth == 0) m_valid = false; /* Can't propagate further up than the root */
|
||||
--depth;
|
||||
}
|
||||
if (m_valid) {
|
||||
/* Make sure the branch is big enough to place the new node. */
|
||||
if (m_branch.size() <= (size_t)depth) m_branch.resize((size_t)depth + 1);
|
||||
assert(!m_branch[depth].has_value());
|
||||
m_branch[depth] = std::move(node);
|
||||
}
|
||||
}
|
||||
|
||||
/*static*/ bool TaprootBuilder::ValidDepths(const std::vector<int>& depths)
|
||||
{
|
||||
std::vector<bool> branch;
|
||||
for (int depth : depths) {
|
||||
// This inner loop corresponds to effectively the same logic on branch
|
||||
// as what Insert() performs on the m_branch variable. Instead of
|
||||
// storing a NodeInfo object, just remember whether or not there is one
|
||||
// at that depth.
|
||||
if (depth < 0 || (size_t)depth > TAPROOT_CONTROL_MAX_NODE_COUNT) return false;
|
||||
if ((size_t)depth + 1 < branch.size()) return false;
|
||||
while (branch.size() > (size_t)depth && branch[depth]) {
|
||||
branch.pop_back();
|
||||
if (depth == 0) return false;
|
||||
--depth;
|
||||
}
|
||||
if (branch.size() <= (size_t)depth) branch.resize((size_t)depth + 1);
|
||||
assert(!branch[depth]);
|
||||
branch[depth] = true;
|
||||
}
|
||||
// And this check corresponds to the IsComplete() check on m_branch.
|
||||
return branch.size() == 0 || (branch.size() == 1 && branch[0]);
|
||||
}
|
||||
|
||||
TaprootBuilder& TaprootBuilder::Add(int depth, const CScript& script, int leaf_version)
|
||||
{
|
||||
assert((leaf_version & ~TAPROOT_LEAF_MASK) == 0);
|
||||
if (!IsValid()) return *this;
|
||||
/* Construct NodeInfo object with leaf hash. */
|
||||
NodeInfo node;
|
||||
node.hash = (CHashWriter{HASHER_TAPLEAF} << uint8_t(leaf_version) << script).GetSHA256();
|
||||
/* Insert into the branch. */
|
||||
Insert(std::move(node), depth);
|
||||
return *this;
|
||||
}
|
||||
|
||||
TaprootBuilder& TaprootBuilder::AddOmitted(int depth, const uint256& hash)
|
||||
{
|
||||
if (!IsValid()) return *this;
|
||||
/* Construct NodeInfo object with the hash directly, and insert it into the branch. */
|
||||
NodeInfo node;
|
||||
node.hash = hash;
|
||||
Insert(std::move(node), depth);
|
||||
return *this;
|
||||
}
|
||||
|
||||
TaprootBuilder& TaprootBuilder::Finalize(const XOnlyPubKey& internal_key)
|
||||
{
|
||||
/* Can only call this function when IsComplete() is true. */
|
||||
assert(IsComplete());
|
||||
m_internal_key = internal_key;
|
||||
auto ret = m_internal_key.CreateTapTweak(m_branch.size() == 0 ? nullptr : &m_branch[0]->hash);
|
||||
assert(ret.has_value());
|
||||
std::tie(m_output_key, std::ignore) = *ret;
|
||||
return *this;
|
||||
}
|
||||
|
||||
WitnessV1Taproot TaprootBuilder::GetOutput() { return WitnessV1Taproot{m_output_key}; }
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#ifndef BITCOIN_SCRIPT_STANDARD_H
|
||||
#define BITCOIN_SCRIPT_STANDARD_H
|
||||
|
||||
#include <pubkey.h>
|
||||
#include <script/interpreter.h>
|
||||
#include <uint256.h>
|
||||
#include <util/hash_type.h>
|
||||
|
@ -113,6 +114,12 @@ struct WitnessV0KeyHash : public BaseHash<uint160>
|
|||
};
|
||||
CKeyID ToKeyID(const WitnessV0KeyHash& key_hash);
|
||||
|
||||
struct WitnessV1Taproot : public XOnlyPubKey
|
||||
{
|
||||
WitnessV1Taproot() : XOnlyPubKey() {}
|
||||
explicit WitnessV1Taproot(const XOnlyPubKey& xpk) : XOnlyPubKey(xpk) {}
|
||||
};
|
||||
|
||||
//! CTxDestination subtype to encode any future Witness version
|
||||
struct WitnessUnknown
|
||||
{
|
||||
|
@ -142,11 +149,11 @@ struct WitnessUnknown
|
|||
* * ScriptHash: TxoutType::SCRIPTHASH destination (P2SH)
|
||||
* * WitnessV0ScriptHash: TxoutType::WITNESS_V0_SCRIPTHASH destination (P2WSH)
|
||||
* * WitnessV0KeyHash: TxoutType::WITNESS_V0_KEYHASH destination (P2WPKH)
|
||||
* * WitnessUnknown: TxoutType::WITNESS_UNKNOWN/WITNESS_V1_TAPROOT destination (P2W???)
|
||||
* (taproot outputs do not require their own type as long as no wallet support exists)
|
||||
* * WitnessV1Taproot: TxoutType::WITNESS_V1_TAPROOT destination (P2TR)
|
||||
* * WitnessUnknown: TxoutType::WITNESS_UNKNOWN destination (P2W???)
|
||||
* A CTxDestination is the internal data type encoded in a bitcoin address
|
||||
*/
|
||||
using CTxDestination = std::variant<CNoDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessUnknown>;
|
||||
using CTxDestination = std::variant<CNoDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown>;
|
||||
|
||||
/** Check whether a CTxDestination is a CNoDestination. */
|
||||
bool IsValidDestination(const CTxDestination& dest);
|
||||
|
@ -202,4 +209,82 @@ CScript GetScriptForRawPubKey(const CPubKey& pubkey);
|
|||
/** Generate a multisig script. */
|
||||
CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys);
|
||||
|
||||
/** Utility class to construct Taproot outputs from internal key and script tree. */
|
||||
class TaprootBuilder
|
||||
{
|
||||
private:
|
||||
/** Information associated with a node in the Merkle tree. */
|
||||
struct NodeInfo
|
||||
{
|
||||
/** Merkle hash of this node. */
|
||||
uint256 hash;
|
||||
};
|
||||
/** Whether the builder is in a valid state so far. */
|
||||
bool m_valid = true;
|
||||
|
||||
/** The current state of the builder.
|
||||
*
|
||||
* For each level in the tree, one NodeInfo object may be present. m_branch[0]
|
||||
* is information about the root; further values are for deeper subtrees being
|
||||
* explored.
|
||||
*
|
||||
* For every right branch taken to reach the position we're currently
|
||||
* working in, there will be a (non-nullopt) entry in m_branch corresponding
|
||||
* to the left branch at that level.
|
||||
*
|
||||
* For example, imagine this tree: - N0 -
|
||||
* / \
|
||||
* N1 N2
|
||||
* / \ / \
|
||||
* A B C N3
|
||||
* / \
|
||||
* D E
|
||||
*
|
||||
* Initially, m_branch is empty. After processing leaf A, it would become
|
||||
* {nullopt, nullopt, A}. When processing leaf B, an entry at level 2 already
|
||||
* exists, and it would thus be combined with it to produce a level 1 one,
|
||||
* resulting in {nullopt, N1}. Adding C and D takes us to {nullopt, N1, C}
|
||||
* and {nullopt, N1, C, D} respectively. When E is processed, it is combined
|
||||
* with D, and then C, and then N1, to produce the root, resulting in {N0}.
|
||||
*
|
||||
* This structure allows processing with just O(log n) overhead if the leaves
|
||||
* are computed on the fly.
|
||||
*
|
||||
* As an invariant, there can never be nullopt entries at the end. There can
|
||||
* also not be more than 128 entries (as that would mean more than 128 levels
|
||||
* in the tree). The depth of newly added entries will always be at least
|
||||
* equal to the current size of m_branch (otherwise it does not correspond
|
||||
* to a depth-first traversal of a tree). m_branch is only empty if no entries
|
||||
* have ever be processed. m_branch having length 1 corresponds to being done.
|
||||
*/
|
||||
std::vector<std::optional<NodeInfo>> m_branch;
|
||||
|
||||
XOnlyPubKey m_internal_key; //!< The internal key, set when finalizing.
|
||||
XOnlyPubKey m_output_key; //!< The output key, computed when finalizing. */
|
||||
|
||||
/** Combine information about a parent Merkle tree node from its child nodes. */
|
||||
static NodeInfo Combine(NodeInfo&& a, NodeInfo&& b);
|
||||
/** Insert information about a node at a certain depth, and propagate information up. */
|
||||
void Insert(NodeInfo&& node, int depth);
|
||||
|
||||
public:
|
||||
/** Add a new script at a certain depth in the tree. Add() operations must be called
|
||||
* in depth-first traversal order of binary tree. */
|
||||
TaprootBuilder& Add(int depth, const CScript& script, int leaf_version);
|
||||
/** Like Add(), but for a Merkle node with a given hash to the tree. */
|
||||
TaprootBuilder& AddOmitted(int depth, const uint256& hash);
|
||||
/** Finalize the construction. Can only be called when IsComplete() is true.
|
||||
internal_key.IsFullyValid() must be true. */
|
||||
TaprootBuilder& Finalize(const XOnlyPubKey& internal_key);
|
||||
|
||||
/** Return true if so far all input was valid. */
|
||||
bool IsValid() const { return m_valid; }
|
||||
/** Return whether there were either no leaves, or the leaves form a Huffman tree. */
|
||||
bool IsComplete() const { return m_valid && (m_branch.size() == 0 || (m_branch.size() == 1 && m_branch[0].has_value())); }
|
||||
/** Compute scriptPubKey (after Finalize()). */
|
||||
WitnessV1Taproot GetOutput();
|
||||
/** Check if a list of depths is legal (will lead to IsComplete()). */
|
||||
static bool ValidDepths(const std::vector<int>& depths);
|
||||
};
|
||||
|
||||
#endif // BITCOIN_SCRIPT_STANDARD_H
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <key.h>
|
||||
#include <key_io.h>
|
||||
#include <script/script.h>
|
||||
#include <script/signingprovider.h>
|
||||
#include <script/standard.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <util/strencodings.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
|
@ -111,9 +113,8 @@ BOOST_AUTO_TEST_CASE(script_standard_Solver_success)
|
|||
s.clear();
|
||||
s << OP_1 << ToByteVector(uint256::ZERO);
|
||||
BOOST_CHECK_EQUAL(Solver(s, solutions), TxoutType::WITNESS_V1_TAPROOT);
|
||||
BOOST_CHECK_EQUAL(solutions.size(), 2U);
|
||||
BOOST_CHECK(solutions[0] == std::vector<unsigned char>{1});
|
||||
BOOST_CHECK(solutions[1] == ToByteVector(uint256::ZERO));
|
||||
BOOST_CHECK_EQUAL(solutions.size(), 1U);
|
||||
BOOST_CHECK(solutions[0] == ToByteVector(uint256::ZERO));
|
||||
|
||||
// TxoutType::WITNESS_UNKNOWN
|
||||
s.clear();
|
||||
|
@ -379,4 +380,70 @@ BOOST_AUTO_TEST_CASE(script_standard_GetScriptFor_)
|
|||
BOOST_CHECK(result == expected);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(script_standard_taproot_builder)
|
||||
{
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({}), true);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0}), true);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,0}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,1}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,2}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,0}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,1}), true);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,2}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,0}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,1}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,2}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,0,0}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,0,1}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,0,2}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,1,0}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,1,1}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,1,2}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,2,0}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,2,1}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({0,2,2}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,0,0}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,0,1}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,0,2}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,1,0}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,1,1}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,1,2}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,2,0}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,2,1}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({1,2,2}), true);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,0,0}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,0,1}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,0,2}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,1,0}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,1,1}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,1,2}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,2,0}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,2,1}), true);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,2,2}), false);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({2,2,2,3,4,5,6,7,8,9,10,11,12,14,14,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,31,31,31,31,31,31,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,128}), true);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({128,128,127,126,125,124,123,122,121,120,119,118,117,116,115,114,113,112,111,110,109,108,107,106,105,104,103,102,101,100,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1}), true);
|
||||
BOOST_CHECK_EQUAL(TaprootBuilder::ValidDepths({129,129,128,127,126,125,124,123,122,121,120,119,118,117,116,115,114,113,112,111,110,109,108,107,106,105,104,103,102,101,100,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1}), false);
|
||||
|
||||
XOnlyPubKey key_inner{ParseHex("79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")};
|
||||
XOnlyPubKey key_1{ParseHex("c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5")};
|
||||
XOnlyPubKey key_2{ParseHex("f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9")};
|
||||
CScript script_1 = CScript() << ToByteVector(key_1) << OP_CHECKSIG;
|
||||
CScript script_2 = CScript() << ToByteVector(key_2) << OP_CHECKSIG;
|
||||
uint256 hash_3 = uint256S("31fe7061656bea2a36aa60a2f7ef940578049273746935d296426dc0afd86b68");
|
||||
|
||||
TaprootBuilder builder;
|
||||
BOOST_CHECK(builder.IsValid() && builder.IsComplete());
|
||||
builder.Add(2, script_2, 0xc0);
|
||||
BOOST_CHECK(builder.IsValid() && !builder.IsComplete());
|
||||
builder.AddOmitted(2, hash_3);
|
||||
BOOST_CHECK(builder.IsValid() && !builder.IsComplete());
|
||||
builder.Add(1, script_1, 0xc0);
|
||||
BOOST_CHECK(builder.IsValid() && builder.IsComplete());
|
||||
builder.Finalize(key_inner);
|
||||
BOOST_CHECK(builder.IsValid() && builder.IsComplete());
|
||||
BOOST_CHECK_EQUAL(EncodeDestination(builder.GetOutput()), "bc1pj6gaw944fy0xpmzzu45ugqde4rz7mqj5kj0tg8kmr5f0pjq8vnaqgynnge");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
|
@ -75,7 +75,7 @@ public:
|
|||
return &m_data[WIDTH];
|
||||
}
|
||||
|
||||
unsigned int size() const
|
||||
static constexpr unsigned int size()
|
||||
{
|
||||
return sizeof(m_data);
|
||||
}
|
||||
|
|
|
@ -34,11 +34,11 @@ Span<const char> Expr(Span<const char>& sp)
|
|||
int level = 0;
|
||||
auto it = sp.begin();
|
||||
while (it != sp.end()) {
|
||||
if (*it == '(') {
|
||||
if (*it == '(' || *it == '{') {
|
||||
++level;
|
||||
} else if (level && *it == ')') {
|
||||
} else if (level && (*it == ')' || *it == '}')) {
|
||||
--level;
|
||||
} else if (level == 0 && (*it == ')' || *it == ',')) {
|
||||
} else if (level == 0 && (*it == ')' || *it == '}' || *it == ',')) {
|
||||
break;
|
||||
}
|
||||
++it;
|
||||
|
|
|
@ -3735,6 +3735,7 @@ public:
|
|||
return obj;
|
||||
}
|
||||
|
||||
UniValue operator()(const WitnessV1Taproot& id) const { return UniValue(UniValue::VOBJ); }
|
||||
UniValue operator()(const WitnessUnknown& id) const { return UniValue(UniValue::VOBJ); }
|
||||
};
|
||||
|
||||
|
|
|
@ -261,6 +261,7 @@ BASE_SCRIPTS = [
|
|||
'wallet_send.py --legacy-wallet',
|
||||
'wallet_send.py --descriptors',
|
||||
'wallet_create_tx.py --descriptors',
|
||||
'wallet_taproot.py',
|
||||
'p2p_fingerprint.py',
|
||||
'feature_uacomment.py',
|
||||
'wallet_coinbase_category.py --legacy-wallet',
|
||||
|
|
272
test/functional/wallet_taproot.py
Executable file
272
test/functional/wallet_taproot.py
Executable file
|
@ -0,0 +1,272 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2021 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Test generation and spending of P2TR addresses."""
|
||||
|
||||
import random
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal
|
||||
from test_framework.descriptors import descsum_create
|
||||
from test_framework.script import (CScript, OP_CHECKSIG, taproot_construct)
|
||||
from test_framework.segwit_addr import encode_segwit_address
|
||||
|
||||
# xprvs/xpubs, and m/* derived x-only pubkeys (created using independent implementation)
|
||||
KEYS = [
|
||||
{
|
||||
"xprv": "tprv8ZgxMBicQKsPeNLUGrbv3b7qhUk1LQJZAGMuk9gVuKh9sd4BWGp1eMsehUni6qGb8bjkdwBxCbgNGdh2bYGACK5C5dRTaif9KBKGVnSezxV",
|
||||
"xpub": "tpubD6NzVbkrYhZ4XqNGAWGWSzmxGWFwVjVTjZxh2fioKbVYi7Jx8fdbprVWsdW7mHwqjchBVas8TLZG4Xwuz4RKU4iaCqiCvoSkFCzQptqk5Y1",
|
||||
"pubs": [
|
||||
"83d8ee77a0f3a32a5cea96fd1624d623b836c1e5d1ac2dcde46814b619320c18",
|
||||
"a30253b018ea6fca966135bf7dd8026915427f24ccf10d4e03f7870f4128569b",
|
||||
"a61e5749f2f3db9dc871d7b187e30bfd3297eea2557e9be99897ea8ff7a29a21",
|
||||
"8110cf482f66dc37125e619d73075af932521724ffc7108309e88f361efe8c8a",
|
||||
]
|
||||
},
|
||||
{
|
||||
"xprv": "tprv8ZgxMBicQKsPe98QUPieXy5KFPVjuZNpcC9JY7K7buJEm8nWvJogK4kTda7eLjK9U4PnMNbSjEkpjDJazeBZ4rhYNYD7N6GEdaysj1AYSb5",
|
||||
"xpub": "tpubD6NzVbkrYhZ4XcACN3PEwNjRpR1g4tZjBVk5pdMR2B6dbd3HYhdGVZNKofAiFZd9okBserZvv58A6tBX4pE64UpXGNTSesfUW7PpW36HuKz",
|
||||
"pubs": [
|
||||
"f95886b02a84928c5c15bdca32784993105f73de27fa6ad8c1a60389b999267c",
|
||||
"71522134160685eb779857033bfc84c7626f13556154653a51dd42619064e679",
|
||||
"48957b4158b2c5c3f4c000f51fd2cf0fd5ff8868ebfb194256f5e9131fc74bd8",
|
||||
"086dda8139b3a84944010648d2b674b70447be3ae59322c09a4907bc80be62c1",
|
||||
]
|
||||
},
|
||||
{
|
||||
"xprv": "tprv8ZgxMBicQKsPe3ZJmcj9aJ2EPZJYYCh6Lp3v82p75wspgaXmtDZ2RBtkAtWcGnW2VQDzMHQPBkCKMoYTqh1RfJKjv4PcmWVR7KqTpjsdboN",
|
||||
"xpub": "tpubD6NzVbkrYhZ4XWb6fGPjyhgLxapUhXszv7ehQYrQWDgDX4nYWcNcbgWcM2RhYo9s2mbZcfZJ8t5LzYcr24FK79zVybsw5Qj3Rtqug8jpJMy",
|
||||
"pubs": [
|
||||
"9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f7",
|
||||
"8a104c54cd34acba60c97dd8f1f7abc89ba9587afd88dc928e91aca7b1c50d20",
|
||||
"13ba6b252a4eb5ef31d39cb521724cdab19a698323f5c17093f28fb1821d052f",
|
||||
"f6c2b4863fd5ba1ba09e3a890caed8b75ffbe013ebab31a06ab87cd6f72506af",
|
||||
]
|
||||
},
|
||||
{
|
||||
"xprv": "tprv8ZgxMBicQKsPdKziibn63Rm6aNzp7dSjDnufZMStXr71Huz7iihCRpbZZZ6Voy5HyuHCWx6foHMipzMzUq4tZrtkZ24DJwz5EeNWdsuwX5h",
|
||||
"xpub": "tpubD6NzVbkrYhZ4Wo2WcFSgSqRD9QWkGxddo6WSqsVBx7uQ8QEtM7WncKDRjhFEexK119NigyCsFygA4b7sAPQxqebyFGAZ9XVV1BtcgNzbCRR",
|
||||
"pubs": [
|
||||
"03a669ea926f381582ec4a000b9472ba8a17347f5fb159eddd4a07036a6718eb",
|
||||
"bbf56b14b119bccafb686adec2e3d2a6b51b1626213590c3afa815d1fd36f85d",
|
||||
"2994519e31bbc238a07d82f85c9832b831705d2ee4a2dbb477ecec8a3f570fe5",
|
||||
"68991b5c139a4c479f8c89d6254d288c533aefc0c5b91fac6c89019c4de64988",
|
||||
]
|
||||
},
|
||||
{
|
||||
"xprv": "tprv8ZgxMBicQKsPen4PGtDwURYnCtVMDejyE8vVwMGhQWfVqB2FBPdekhTacDW4vmsKTsgC1wsncVqXiZdX2YFGAnKoLXYf42M78fQJFzuDYFN",
|
||||
"xpub": "tpubD6NzVbkrYhZ4YF6BAXtXsqCtmv1HNyvsoSXHDsJzpnTtffH1onTEwC5SnLzCHPKPebh2i7Gxvi9kJNADcpuSmH8oM3rCYcHVtdXHjpYoKnX",
|
||||
"pubs": [
|
||||
"aba457d16a8d59151c387f24d1eb887efbe24644c1ee64b261282e7baebdb247",
|
||||
"c8558b7caf198e892032d91f1a48ee9bdc25462b83b4d0ac62bb7fb2a0df630e",
|
||||
"8a4bcaba0e970685858d133a4d0079c8b55bbc755599e212285691eb779ce3dc",
|
||||
"b0d68ada13e0d954b3921b88160d4453e9c151131c2b7c724e08f538a666ceb3",
|
||||
]
|
||||
},
|
||||
{
|
||||
"xprv": "tprv8ZgxMBicQKsPd91vCgRmbzA13wyip2RimYeVEkAyZvsEN5pUSB3T43SEBxPsytkxb42d64W2EiRE9CewpJQkzR8HKHLV8Uhk4dMF5yRPaTv",
|
||||
"xpub": "tpubD6NzVbkrYhZ4Wc3i6L6N1Pp7cyVeyMcdLrFGXGDGzCfdCa5F4Zs3EY46N72Ws8QDEUYBVwXfDfda2UKSseSdU1fsBegJBhGCZyxkf28bkQ6",
|
||||
"pubs": [
|
||||
"9b4d495b74887815a1ff623c055c6eac6b6b2e07d2a016d6526ebac71dd99744",
|
||||
"8e971b781b7ce7ab742d80278f2dfe7dd330f3efd6d00047f4a2071f2e7553cb",
|
||||
"b811d66739b9f07435ccda907ec5cd225355321c35e0a7c7791232f24cf10632",
|
||||
"4cd27a5552c272bc80ba544e9cc6340bb906969f5e7a1510b6cef9592683fbc9",
|
||||
]
|
||||
},
|
||||
{
|
||||
"xprv": "tprv8ZgxMBicQKsPdEhLRxxwzTv2t18j7ruoffPeqAwVA2qXJ2P66RaMZLUWQ85SjoA7xPxdSgCB9UZ72m65qbnaLPtFTfHVP3MEmkpZk1Bv8RT",
|
||||
"xpub": "tpubD6NzVbkrYhZ4Whj8KcdYPsa9T2efHC6iExzS7gynaJdv8WdripPwjq6NaH5gQJGrLmvUwHY1smhiakUosXNDTEa6qfKUQdLKV6DJBre6XvQ",
|
||||
"pubs": [
|
||||
"d0c19def28bb1b39451c1a814737615983967780d223b79969ba692182c6006b",
|
||||
"cb1d1b1dc62fec1894d4c3d9a1b6738e5ff9c273a64f74e9ab363095f45e9c47",
|
||||
"245be588f41acfaeb9481aa132717db56ee1e23eb289729fe2b8bde8f9a00830",
|
||||
"5bc4ad6d6187fa82728c85a073b428483295288f8aef5722e47305b5872f7169",
|
||||
]
|
||||
},
|
||||
{
|
||||
"xprv": "tprv8ZgxMBicQKsPcxbqxzcMAwQpiCD8x6qaZEJTxdKxw4w9GuMzDACTD9yhEsHGfqQcfYX4LivosLDDngTykYEp9JnTdcqY7cHqU8PpeFFKyV3",
|
||||
"xpub": "tpubD6NzVbkrYhZ4WRddreGwaM4wHDj57S2V8XuFF9NGMLjY7PckqZ23PebZR1wGA4w84uX2vZphdZVsnREjij1ibYjEBTaTVQCEZCLs4xUDapx",
|
||||
"pubs": [
|
||||
"065cc1b92bd99e5a3e626e8296a366b2d132688eb43aea19bc14fd8f43bf07fb",
|
||||
"5b95633a7dda34578b6985e6bfd85d83ec38b7ded892a9b74a3d899c85890562",
|
||||
"dc86d434b9a34495c8e845b969d51f80d19a8df03b400353ffe8036a0c22eb60",
|
||||
"06c8ffde238745b29ae8a97ae533e1f3edf214bba6ec58b5e7b9451d1d61ec19",
|
||||
]
|
||||
},
|
||||
{
|
||||
"xprv": "tprv8ZgxMBicQKsPe6zLoU8MTTXgsdJVNBErrYGpoGwHf5VGvwUzdNc7NHeCSzkJkniCxBhZWujXjmD4HZmBBrnr3URgJjM6GxRgMmEhLdqNTWG",
|
||||
"xpub": "tpubD6NzVbkrYhZ4Xa28h7nwrsBoSepRXWRmRqsc5nyb5MHfmRjmFmRhYnG4d9dC7uxixN5AfsEv1Lz3mCAuWvERyvPgKozHUVjfo8EG6foJGy7",
|
||||
"pubs": [
|
||||
"d826a0a53abb6ffc60df25b9c152870578faef4b2eb5a09bdd672bbe32cdd79b",
|
||||
"939365e0359ff6bc6f6404ee220714c5d4a0d1e36838b9e2081ede217674e2ba",
|
||||
"4e8767edcf7d3d90258cfbbea01b784f4d2de813c4277b51279cf808bac410a2",
|
||||
"d42a2c280940bfc6ede971ae72cde2e1df96c6da7dab06a132900c6751ade208",
|
||||
]
|
||||
},
|
||||
{
|
||||
"xprv": "tprv8ZgxMBicQKsPeB5o5oCsN2dVxM2mtJiYERQEBRc4JNwC1DFGYaEdNkmh8jJYVPU76YhkFoRoWTdh1p3yQGykG8TfDW34dKgrgSx28gswUyL",
|
||||
"xpub": "tpubD6NzVbkrYhZ4Xe7aySsTmSHcXNYi3duSoj11TweMiejaqhW3Ay4DZFPZJses4sfpk4b9VHRhn8v4cKTMjugMM3hqXcqSSmRdiW8QvASXjfY",
|
||||
"pubs": [
|
||||
"e360564b2e0e8d06681b6336a29d0750210e8f34afd9afb5e6fd5fe6dba26c81",
|
||||
"76b4900f00a1dcce463b6d8e02b768518fce4f9ecd6679a13ad78ea1e4815ad3",
|
||||
"5575556e263c8ed52e99ab02147cc05a738869afe0039911b5a60a780f4e43d2",
|
||||
"593b00e2c8d4bd6dda0fd9e238888acf427bb4e128887fd5a40e0e9da78cbc01",
|
||||
]
|
||||
},
|
||||
{
|
||||
"xprv": "tprv8ZgxMBicQKsPfEH6jHemkGDjZRnAaKFJVGH8pQU638E6SdbX9hxit1tK2sfFPfL6KS7v8FfUKxstbfEpzSymbdfBM9Y5UkrxErF9fJaKLK3",
|
||||
"xpub": "tpubD6NzVbkrYhZ4YhJtcwKN9fsr8TJ6jeSD4Zsv6vWPTQ2VH7rHn6nK4WWBCzKK7FkdVVwm3iztCU1UmStY4hX6gRbBmp9UzK9C59dQEzeXS12",
|
||||
"pubs": [
|
||||
"7631cacec3343052d87ef4d0065f61dde82d7d2db0c1cc02ef61ef3c982ea763",
|
||||
"c05e44a9e735d1b1bef62e2c0d886e6fb4923b2649b67828290f5cacc51c71b7",
|
||||
"b33198b20701afe933226c92fd0e3d51d3f266f1113d864dbd026ae3166ef7f2",
|
||||
"f99643ac3f4072ee4a949301e86963a9ca0ad57f2ef29f6b84fda037d7cac85b",
|
||||
]
|
||||
},
|
||||
{
|
||||
"xprv": "tprv8ZgxMBicQKsPdNWU38dT6aGxtqJR4oYS5kPpLVBcuKiiu7gqTYqMMqhUG6DP7pPahzPQu36sWSmeLCP1C4AwqcR5FX2RyRoZfd4B8pAnSdX",
|
||||
"xpub": "tpubD6NzVbkrYhZ4WqYFvnJ3Vyw5TrpME8jLf3zbd1DvKbX7jbwc5wewYLKLSFRzZWV6hZj7XhsXAy7fhE5jB25DiWyNM3ztXbsXHRVCrp5BiPY",
|
||||
"pubs": [
|
||||
"2258b1c3160be0864a541854eec9164a572f094f7562628281a8073bb89173a7",
|
||||
"83df59d0a5c951cdd62b7ab225a62079f48d2a333a86e66c35420d101446e92e",
|
||||
"2a654bf234d819055312f9ca03fad5836f9163b09cdd24d29678f694842b874a",
|
||||
"aa0334ab910047387c912a21ec0dab806a47ffa38365060dbc5d47c18c6e66e7",
|
||||
]
|
||||
},
|
||||
{
|
||||
"xprv": "tprv8mGPkMVz5mZuJDnC2NjjAv7E9Zqa5LCgX4zawbZu5nzTtLb5kGhPwycX4H1gtW1f5ZdTKTNtQJ61hk71F2TdcQ93EFDTpUcPBr98QRji615",
|
||||
"xpub": "tpubDHxRtmYEE9FaBgoyv2QKaKmLibMWEfPb6NbNE7cCW4nripqrNfWz8UEPEPbHCrakwLvwFfsqoaf4pjX4gWStp4nECRf1QwBKPkLqnY8pHbj",
|
||||
"pubs": [
|
||||
"00a9da96087a72258f83b338ef7f0ea8cbbe05da5f18f091eb397d1ecbf7c3d3",
|
||||
"b2749b74d51a78f5fe3ebb3a7c0ff266a468cade143dfa265c57e325177edf00",
|
||||
"6b8747a6bbe4440d7386658476da51f6e49a220508a7ec77fe7bccc3e7baa916",
|
||||
"4674bf4d9ebbe01bf0aceaca2472f63198655ecf2df810f8d69b38421972318e",
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
CHANGE_XPRV = "tprv8ZgxMBicQKsPcyDrWwiecVnTtFmfRwbfFqEfR4ZGWvq5aTTwLBWmAm5zrbMcYtb9gQNFfhRfqhhrBG37U3nhmXxEgeEPBJGHAPrHCrAd1WX"
|
||||
CHANGE_XPUB = "tpubD6NzVbkrYhZ4WSFeQbPF1uSaTHHbbGnZq8qShabZwCdUQwihxaLMMFhs2kidGF2qrRKiQVqw8VoyuTHj1bZqmMXMeciaU1gBjWA1sim2zUB"
|
||||
|
||||
# Point with no known discrete log.
|
||||
H_POINT = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"
|
||||
|
||||
|
||||
def key(hex_key):
|
||||
"""Construct an x-only pubkey from its hex representation."""
|
||||
return bytes.fromhex(hex_key)
|
||||
|
||||
def pk(hex_key):
|
||||
"""Construct a script expression for taproot_construct for pk(hex_key)."""
|
||||
return (None, CScript([bytes.fromhex(hex_key), OP_CHECKSIG]))
|
||||
|
||||
def compute_taproot_address(pubkey, scripts):
|
||||
"""Compute the address for a taproot output with given inner key and scripts."""
|
||||
tap = taproot_construct(pubkey, scripts)
|
||||
assert tap.scriptPubKey[0] == 0x51
|
||||
assert tap.scriptPubKey[1] == 0x20
|
||||
return encode_segwit_address("bcrt", 1, tap.scriptPubKey[2:])
|
||||
|
||||
class WalletTaprootTest(BitcoinTestFramework):
|
||||
"""Test generation and spending of P2TR address outputs."""
|
||||
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 2
|
||||
self.setup_clean_chain = True
|
||||
self.extra_args = [['-keypool=100'], ['-keypool=100']]
|
||||
self.supports_cli = False
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
self.skip_if_no_sqlite()
|
||||
|
||||
def setup_network(self):
|
||||
self.setup_nodes()
|
||||
|
||||
def init_wallet(self, i):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def rand_keys(n):
|
||||
ret = []
|
||||
idxes = set()
|
||||
for _ in range(n):
|
||||
while True:
|
||||
i = random.randrange(len(KEYS))
|
||||
if not i in idxes:
|
||||
break
|
||||
idxes.add(i)
|
||||
ret.append(KEYS[i])
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
def make_desc(pattern, privmap, keys, pub_only = False):
|
||||
pat = pattern.replace("$H", H_POINT)
|
||||
for i in range(len(privmap)):
|
||||
if privmap[i] and not pub_only:
|
||||
pat = pat.replace("$%i" % (i + 1), keys[i]['xprv'])
|
||||
else:
|
||||
pat = pat.replace("$%i" % (i + 1), keys[i]['xpub'])
|
||||
return descsum_create(pat)
|
||||
|
||||
@staticmethod
|
||||
def make_addr(treefn, keys, i):
|
||||
args = []
|
||||
for j in range(len(keys)):
|
||||
args.append(keys[j]['pubs'][i])
|
||||
return compute_taproot_address(*treefn(*args))
|
||||
|
||||
def do_test_addr(self, comment, pattern, privmap, treefn, keys):
|
||||
self.log.info("Testing %s address derivation" % comment)
|
||||
desc = self.make_desc(pattern, privmap, keys, False)
|
||||
desc_pub = self.make_desc(pattern, privmap, keys, True)
|
||||
assert_equal(self.nodes[0].getdescriptorinfo(desc)['descriptor'], desc_pub)
|
||||
result = self.addr_gen.importdescriptors([{"desc": desc_pub, "active": True, "timestamp": "now"}])
|
||||
assert(result[0]['success'])
|
||||
for i in range(4):
|
||||
addr_g = self.addr_gen.getnewaddress(address_type='bech32')
|
||||
if treefn is not None:
|
||||
addr_r = self.make_addr(treefn, keys, i)
|
||||
assert_equal(addr_g, addr_r)
|
||||
|
||||
def do_test(self, comment, pattern, privmap, treefn, nkeys):
|
||||
keys = self.rand_keys(nkeys)
|
||||
self.do_test_addr(comment, pattern, privmap, treefn, keys)
|
||||
|
||||
def run_test(self):
|
||||
self.log.info("Creating wallets...")
|
||||
self.nodes[0].createwallet(wallet_name="addr_gen", descriptors=True, disable_private_keys=True, blank=True)
|
||||
self.addr_gen = self.nodes[0].get_wallet_rpc("addr_gen")
|
||||
|
||||
self.do_test(
|
||||
"tr(XPRV)",
|
||||
"tr($1/*)",
|
||||
[True],
|
||||
lambda k1: (key(k1), []),
|
||||
1
|
||||
)
|
||||
self.do_test(
|
||||
"tr(H,XPRV)",
|
||||
"tr($H,pk($1/*))",
|
||||
[True],
|
||||
lambda k1: (key(H_POINT), [pk(k1)]),
|
||||
1
|
||||
)
|
||||
self.do_test(
|
||||
"tr(XPRV,{H,{H,XPUB}})",
|
||||
"tr($1/*,{pk($H),{pk($H),pk($2/*)}})",
|
||||
[True, False],
|
||||
lambda k1, k2: (key(k1), [pk(H_POINT), [pk(H_POINT), pk(k2)]]),
|
||||
2
|
||||
)
|
||||
self.do_test(
|
||||
"tr(XPUB,{{H,{H,XPUB}},{H,{H,{H,XPRV}}}})",
|
||||
"tr($1/*,{{pk($H),{pk($H),pk($2/*)}},{pk($H),{pk($H),{pk($H),pk($3/*)}}}})",
|
||||
[False, False, True],
|
||||
lambda k1, k2, k3: (key(k1), [[pk(H_POINT), [pk(H_POINT), pk(k2)]], [pk(H_POINT), [pk(H_POINT), [pk(H_POINT), pk(k3)]]]]),
|
||||
3
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
WalletTaprootTest().main()
|
Loading…
Add table
Reference in a new issue