diff options
author | Andrew Chow <github@achow101.com> | 2023-10-08 12:01:31 -0400 |
---|---|---|
committer | Andrew Chow <github@achow101.com> | 2023-10-08 12:10:12 -0400 |
commit | db283a6b6f1419291bcd15d74d51c8598aefe06a (patch) | |
tree | c183be4f6dc9a74517483af6a5e2ccc7c4bd91f5 /src | |
parent | d2b8c5e1234cdaff84bd1f60aea598d219cdac5e (diff) | |
parent | ec0fc14a22f38b487929ec21145945966f301eb5 (diff) |
Merge bitcoin/bitcoin#27255: MiniTapscript: port Miniscript to Tapscript
ec0fc14a22f38b487929ec21145945966f301eb5 miniscript: remove P2WSH-specific part of GetStackSize doc comment (Antoine Poinsot)
128bc104ef07e1edaad5378e2ca53e97672a1652 qa: bound testing for TapMiniscript (Antoine Poinsot)
117927bd5f30c8bf09aaf91e62f5244990788084 miniscript: have a custom Node destructor (Antoine Poinsot)
b917c715ace19fb23661b4b245039da48cefc6bf qa: Tapscript Miniscript signing functional tests (Antoine Poinsot)
5dc341dfe672c7195bc71f2834152b86a710c313 qa: list descriptors in Miniscript signing functional tests (Antoine Poinsot)
4f473ea515bc77b9138323dab8a741c063d32e8f script/sign: Miniscript support in Tapscript (Antoine Poinsot)
febe2abc0e3f67b8b0ac9ece1890efb4a0bba83c MOVEONLY: script/sign: move Satisfier declaration above Tapscript signing (Antoine Poinsot)
bd4b11ee06096655b74586413efe40c14d9ef44c qa: functional test Miniscript inside Taproot descriptors (Antoine Poinsot)
8571b89a7fce50229242ef3c6d9f807949f716a3 descriptor: parse Miniscript expressions within Taproot descriptors (Antoine Poinsot)
8ff9489422009284967aeb9ff4232b135f5ddad8 descriptor: Tapscript-specific Miniscript key serialization / parsing (Antoine Poinsot)
5e76f3f0ddbce513c2b626b5ab45242f931d5d60 fuzz: miniscript: higher sensitivity for max stack size limit under Tapscript (Antoine Poinsot)
6f529cbaaf773dd77f3262b1fbd5039856434898 qa: test Miniscript max stack size tracking (Antoine Poinsot)
770ba5b51979b99de513d5a9d15e451b7d29b647 miniscript: check maximum stack size during execution (Antoine Poinsot)
574523dbe030f5fb8aca4d7fd41cdc304bd913d3 fuzz: adapt Miniscript targets to Tapscript (Antoine Poinsot)
84623722ef3a1ff6fc302517adc554ba6cb023a7 qa: Tapscript-Miniscript unit tests (Antoine Poinsot)
fcb6f13f442d6a3f27689a87e3ed2bb9b431a332 pubkey: introduce a GetEvenCorrespondingCPubKey helper (Antoine Poinsot)
ce8845f5dda403461178c08e7363978fda423999 miniscript: account for keys as being 32 bytes under Taproot context (Antoine Poinsot)
f4f978d38ee4920c5cd0de5d93b407ec37bfd9c0 miniscript: adapt resources checks depending on context (Antoine Poinsot)
9cb4c68b89a5715f82026f4aa446b876addd8472 serialize: make GetSizeOfCompactSize constexpr (Antoine Poinsot)
892436c7d575ffdb9bada5fe4e62d6c1f5053c42 miniscript: sanity asserts context in ComputeType (Antoine Poinsot)
e5aaa3d77af7459b37c0c4a37eb22c5fd0cda3e1 miniscript: make 'd:' have the 'u' property under Tapscript context (Antoine Poinsot)
687a0b0fa53ddd5632287b9e00ad8b0550830287 miniscript: introduce a multi_a fragment (Antoine Poinsot)
9164c2eca164d78cbae5351d383f39320711efb9 miniscript: restrict multi() usage to P2WSH context (Antoine Poinsot)
91b4db859023f5cf59f4b27f880484c863ccae66 miniscript: store the script context within the Node structure (Antoine Poinsot)
c3738d0344f589162b9ffb78b8e2d78f612d3786 miniscript: introduce a MsContext() helper to contexts (Antoine Poinsot)
bba9340a947446cd1c70852f58dcd8aee35be9ac miniscript: don't anticipate signature presence in CalcStackSize() (Antoine Poinsot)
a3793f2d1a43624631d6329f6c900a83e7dd0e98 miniscript: add a missing dup key check bypass in Parse() (Antoine Poinsot)
Pull request description:
Miniscript was targeting P2WSH, and as such can currently only be used in `wsh()` descriptors. This pull request introduces support for Tapscript in Miniscript and makes Miniscript available inside `tr()` descriptors. It adds support for both watching *and* signing TapMiniscript descriptors.
The main changes to Miniscript for Tapscript are the following:
- A new `multi_a` fragment is introduced with the same semantics as `multi`. Like in other descriptors `multi` and `multi_a` can exclusively be used in respectively P2WSH and Tapscript.
- The `d:` fragment has the `u` property under Tapscript, since the `MINIMALIF` rule is now consensus. See also https://github.com/bitcoin/bitcoin/pull/24906.
- Keys are now serialized as 32 bytes. (Note this affects the key hashes.)
- The resource consumption checks and calculation changed. Some limits were lifted in Tapscript, and signatures are now 64 bytes long.
The largest amount of complexity probably lies in the last item. Scripts under Taproot can now run into the maximum stack size while executing a fragment. For instance if you've got a stack size of `999` due to the initial witness plus some execution that happened before and try to execute a `hash256` it would `DUP` (increasing the stack size `1000`), `HASH160` and then push the hash on the stack making the script fail.
To make sure this does not happen on any of the spending paths of a sane Miniscript, we introduce a tracking of the maximum stack size during execution of a fragment. See the commits messages for details. Those commits were separated from the resource consumption change, and the fuzz target was tweaked to sometimes pad the witness so the script runs on the brink of the stack size limit to make sure the stack size was not underestimated.
Existing Miniscript unit, functional and fuzz tests are extended with Tapscript logic and test cases. Care was taken for seed stability in the fuzz targets where we cared more about them.
The design of Miniscript for Tapscript is the result of discussions between various people over the past year(s). To the extent of my knowledge at least Pieter Wuille, Sanket Kanjalkar, Andrew Poelstra and Andrew Chow contributed thoughts and ideas.
ACKs for top commit:
sipa:
ACK ec0fc14a22f38b487929ec21145945966f301eb5
achow101:
ACK ec0fc14a22f38b487929ec21145945966f301eb5
Tree-SHA512: f3cf98a3ec8e565650ccf51b7ee7e4b4c2b3949a1168bee16ec03d2942b4d9f20dedc2820457f67a3216161022263573d08419c8346d807a693169ad3a436e07
Diffstat (limited to 'src')
-rw-r--r-- | src/psbt.cpp | 2 | ||||
-rw-r--r-- | src/pubkey.cpp | 7 | ||||
-rw-r--r-- | src/pubkey.h | 2 | ||||
-rw-r--r-- | src/script/descriptor.cpp | 95 | ||||
-rw-r--r-- | src/script/miniscript.cpp | 34 | ||||
-rw-r--r-- | src/script/miniscript.h | 660 | ||||
-rw-r--r-- | src/script/sign.cpp | 272 | ||||
-rw-r--r-- | src/script/sign.h | 1 | ||||
-rw-r--r-- | src/serialize.h | 2 | ||||
-rw-r--r-- | src/test/descriptor_tests.cpp | 18 | ||||
-rw-r--r-- | src/test/fuzz/miniscript.cpp | 305 | ||||
-rw-r--r-- | src/test/miniscript_tests.cpp | 407 |
12 files changed, 1294 insertions, 511 deletions
diff --git a/src/psbt.cpp b/src/psbt.cpp index 7ec9b9c136..76a2fd8241 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -132,6 +132,7 @@ void PSBTInput::FillSignatureData(SignatureData& sigdata) const } for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) { sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin); + sigdata.tap_pubkeys.emplace(Hash160(pubkey), pubkey); } for (const auto& [hash, preimage] : ripemd160_preimages) { sigdata.ripemd160_preimages.emplace(std::vector<unsigned char>(hash.begin(), hash.end()), preimage); @@ -246,6 +247,7 @@ void PSBTOutput::FillSignatureData(SignatureData& sigdata) const } for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) { sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin); + sigdata.tap_pubkeys.emplace(Hash160(pubkey), pubkey); } } diff --git a/src/pubkey.cpp b/src/pubkey.cpp index 05808e4c22..11e1b4abb5 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -204,6 +204,13 @@ std::vector<CKeyID> XOnlyPubKey::GetKeyIDs() const return out; } +CPubKey XOnlyPubKey::GetEvenCorrespondingCPubKey() const +{ + unsigned char full_key[CPubKey::COMPRESSED_SIZE] = {0x02}; + std::copy(begin(), end(), full_key + 1); + return CPubKey{full_key}; +} + bool XOnlyPubKey::IsFullyValid() const { secp256k1_xonly_pubkey pubkey; diff --git a/src/pubkey.h b/src/pubkey.h index 4b34fd829b..2b655c3f73 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -282,6 +282,8 @@ public: */ std::vector<CKeyID> GetKeyIDs() const; + CPubKey GetEvenCorrespondingCPubKey() const; + const unsigned char& operator[](int pos) const { return *(m_keydata.begin() + pos); } const unsigned char* data() const { return m_keydata.begin(); } static constexpr size_t size() { return decltype(m_keydata)::size(); } diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 896fb0b5b3..ba2334df49 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -1114,16 +1114,33 @@ public: class ScriptMaker { //! Keys contained in the Miniscript (the evaluation of DescriptorImpl::m_pubkey_args). const std::vector<CPubKey>& m_keys; + //! The script context we're operating within (Tapscript or P2WSH). + const miniscript::MiniscriptContext m_script_ctx; + + //! Get the ripemd160(sha256()) hash of this key. + //! Any key that is valid in a descriptor serializes as 32 bytes within a Tapscript context. So we + //! must not hash the sign-bit byte in this case. + uint160 GetHash160(uint32_t key) const { + if (miniscript::IsTapscript(m_script_ctx)) { + return Hash160(XOnlyPubKey{m_keys[key]}); + } + return m_keys[key].GetID(); + } public: - ScriptMaker(const std::vector<CPubKey>& keys LIFETIMEBOUND) : m_keys(keys) {} + ScriptMaker(const std::vector<CPubKey>& keys LIFETIMEBOUND, const miniscript::MiniscriptContext script_ctx) : m_keys(keys), m_script_ctx{script_ctx} {} std::vector<unsigned char> ToPKBytes(uint32_t key) const { - return {m_keys[key].begin(), m_keys[key].end()}; + // In Tapscript keys always serialize as x-only, whether an x-only key was used in the descriptor or not. + if (!miniscript::IsTapscript(m_script_ctx)) { + return {m_keys[key].begin(), m_keys[key].end()}; + } + const XOnlyPubKey xonly_pubkey{m_keys[key]}; + return {xonly_pubkey.begin(), xonly_pubkey.end()}; } std::vector<unsigned char> ToPKHBytes(uint32_t key) const { - auto id = m_keys[key].GetID(); + auto id = GetHash160(key); return {id.begin(), id.end()}; } }; @@ -1164,8 +1181,15 @@ protected: std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript> scripts, FlatSigningProvider& provider) const override { - for (const auto& key : keys) provider.pubkeys.emplace(key.GetID(), key); - return Vector(m_node->ToScript(ScriptMaker(keys))); + const auto script_ctx{m_node->GetMsCtx()}; + for (const auto& key : keys) { + if (miniscript::IsTapscript(script_ctx)) { + provider.pubkeys.emplace(Hash160(XOnlyPubKey{key}), key); + } else { + provider.pubkeys.emplace(key.GetID(), key); + } + } + return Vector(m_node->ToScript(ScriptMaker(keys, script_ctx))); } public: @@ -1401,9 +1425,7 @@ std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptCo std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseScriptContext ctx, const SigningProvider& provider) { - unsigned char full_key[CPubKey::COMPRESSED_SIZE] = {0x02}; - std::copy(xkey.begin(), xkey.end(), full_key + 1); - CPubKey pubkey(full_key); + CPubKey pubkey{xkey.GetEvenCorrespondingCPubKey()}; std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, true); KeyOriginInfo info; if (provider.GetKeyOriginByXOnly(xkey, info)) { @@ -1426,18 +1448,32 @@ struct KeyParser { mutable std::vector<std::unique_ptr<PubkeyProvider>> m_keys; //! Used to detect key parsing errors within a Miniscript. mutable std::string m_key_parsing_error; + //! The script context we're operating within (Tapscript or P2WSH). + const miniscript::MiniscriptContext m_script_ctx; + //! The number of keys that were parsed before starting to parse this Miniscript descriptor. + uint32_t m_offset; - KeyParser(FlatSigningProvider* out LIFETIMEBOUND, const SigningProvider* in LIFETIMEBOUND) : m_out(out), m_in(in) {} + KeyParser(FlatSigningProvider* out LIFETIMEBOUND, const SigningProvider* in LIFETIMEBOUND, + miniscript::MiniscriptContext ctx, uint32_t offset = 0) + : m_out(out), m_in(in), m_script_ctx(ctx), m_offset(offset) {} bool KeyCompare(const Key& a, const Key& b) const { return *m_keys.at(a) < *m_keys.at(b); } + ParseScriptContext ParseContext() const { + switch (m_script_ctx) { + case miniscript::MiniscriptContext::P2WSH: return ParseScriptContext::P2WSH; + case miniscript::MiniscriptContext::TAPSCRIPT: return ParseScriptContext::P2TR; + } + assert(false); + } + template<typename I> std::optional<Key> FromString(I begin, I end) const { assert(m_out); Key key = m_keys.size(); - auto pk = ParsePubkey(key, {&*begin, &*end}, ParseScriptContext::P2WSH, *m_out, m_key_parsing_error); + auto pk = ParsePubkey(m_offset + key, {&*begin, &*end}, ParseContext(), *m_out, m_key_parsing_error); if (!pk) return {}; m_keys.push_back(std::move(pk)); return key; @@ -1451,11 +1487,18 @@ struct KeyParser { template<typename I> std::optional<Key> FromPKBytes(I begin, I end) const { assert(m_in); - CPubKey pubkey(begin, end); - if (pubkey.IsValidNonHybrid()) { - Key key = m_keys.size(); - m_keys.push_back(InferPubkey(pubkey, ParseScriptContext::P2WSH, *m_in)); + Key key = m_keys.size(); + if (miniscript::IsTapscript(m_script_ctx) && end - begin == 32) { + XOnlyPubKey pubkey; + std::copy(begin, end, pubkey.begin()); + m_keys.push_back(InferPubkey(pubkey.GetEvenCorrespondingCPubKey(), ParseContext(), *m_in)); return key; + } else if (!miniscript::IsTapscript(m_script_ctx)) { + CPubKey pubkey{begin, end}; + if (pubkey.IsValidNonHybrid()) { + m_keys.push_back(InferPubkey(pubkey, ParseContext(), *m_in)); + return key; + } } return {}; } @@ -1470,11 +1513,15 @@ struct KeyParser { CPubKey pubkey; if (m_in->GetPubKey(keyid, pubkey)) { Key key = m_keys.size(); - m_keys.push_back(InferPubkey(pubkey, ParseScriptContext::P2WSH, *m_in)); + m_keys.push_back(InferPubkey(pubkey, ParseContext(), *m_in)); return key; } return {}; } + + miniscript::MiniscriptContext MsContext() const { + return m_script_ctx; + } }; /** Parse a script in a particular context. */ @@ -1500,8 +1547,9 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const } ++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()"; + } else if (ctx != ParseScriptContext::P2TR && Func("pkh", expr)) { + // Under Taproot, always the Miniscript parser deal with it. + error = "Can only have pkh at top level, in sh(), wsh(), or in tr()"; return nullptr; } if (ctx == ParseScriptContext::TOP && Func("combo", expr)) { @@ -1714,11 +1762,12 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const } // Process miniscript expressions. { - KeyParser parser(&out, nullptr); + const auto script_ctx{ctx == ParseScriptContext::P2WSH ? miniscript::MiniscriptContext::P2WSH : miniscript::MiniscriptContext::TAPSCRIPT}; + KeyParser parser(/*out = */&out, /* in = */nullptr, /* ctx = */script_ctx, key_exp_index); auto node = miniscript::FromString(std::string(expr.begin(), expr.end()), parser); if (node) { - if (ctx != ParseScriptContext::P2WSH) { - error = "Miniscript expressions can only be used in wsh"; + if (ctx != ParseScriptContext::P2WSH && ctx != ParseScriptContext::P2TR) { + error = "Miniscript expressions can only be used in wsh or tr."; return nullptr; } if (parser.m_key_parsing_error != "") { @@ -1753,6 +1802,7 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const // A signature check is required for a miniscript to be sane. Therefore no sane miniscript // may have an empty list of public keys. CHECK_NONFATAL(!parser.m_keys.empty()); + key_exp_index += parser.m_keys.size(); return std::make_unique<MiniscriptDescriptor>(std::move(parser.m_keys), std::move(node)); } } @@ -1886,8 +1936,9 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo } } - if (ctx == ParseScriptContext::P2WSH) { - KeyParser parser(nullptr, &provider); + if (ctx == ParseScriptContext::P2WSH || ctx == ParseScriptContext::P2TR) { + const auto script_ctx{ctx == ParseScriptContext::P2WSH ? miniscript::MiniscriptContext::P2WSH : miniscript::MiniscriptContext::TAPSCRIPT}; + KeyParser parser(/* out = */nullptr, /* in = */&provider, /* ctx = */script_ctx); auto node = miniscript::FromScript(script, parser); if (node && node->IsSane()) { return std::make_unique<MiniscriptDescriptor>(std::move(parser.m_keys), std::move(node)); diff --git a/src/script/miniscript.cpp b/src/script/miniscript.cpp index 19556a9775..344a81bdf0 100644 --- a/src/script/miniscript.cpp +++ b/src/script/miniscript.cpp @@ -6,6 +6,7 @@ #include <vector> #include <script/script.h> #include <script/miniscript.h> +#include <serialize.h> #include <assert.h> @@ -32,7 +33,8 @@ Type SanitizeType(Type e) { return e; } -Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys) { +Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k, + size_t data_size, size_t n_subs, size_t n_keys, MiniscriptContext ms_ctx) { // Sanity check on data if (fragment == Fragment::SHA256 || fragment == Fragment::HASH256) { assert(data_size == 32); @@ -44,7 +46,7 @@ Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Ty // Sanity check on k if (fragment == Fragment::OLDER || fragment == Fragment::AFTER) { assert(k >= 1 && k < 0x80000000UL); - } else if (fragment == Fragment::MULTI) { + } else if (fragment == Fragment::MULTI || fragment == Fragment::MULTI_A) { assert(k >= 1 && k <= n_keys); } else if (fragment == Fragment::THRESH) { assert(k >= 1 && k <= n_subs); @@ -68,7 +70,11 @@ Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Ty if (fragment == Fragment::PK_K || fragment == Fragment::PK_H) { assert(n_keys == 1); } else if (fragment == Fragment::MULTI) { - assert(n_keys >= 1 && n_keys <= 20); + assert(n_keys >= 1 && n_keys <= MAX_PUBKEYS_PER_MULTISIG); + assert(!IsTapscript(ms_ctx)); + } else if (fragment == Fragment::MULTI_A) { + assert(n_keys >= 1 && n_keys <= MAX_PUBKEYS_PER_MULTI_A); + assert(IsTapscript(ms_ctx)); } else { assert(n_keys == 0); } @@ -113,7 +119,8 @@ Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Ty "e"_mst.If(x << "f"_mst) | // e=f_x (x & "ghijk"_mst) | // g=g_x, h=h_x, i=i_x, j=j_x, k=k_x (x & "ms"_mst) | // m=m_x, s=s_x - // NOTE: 'd:' is not 'u' under P2WSH as MINIMALIF is only a policy rule there. + // NOTE: 'd:' is 'u' under Tapscript but not P2WSH as MINIMALIF is only a policy rule there. + "u"_mst.If(IsTapscript(ms_ctx)) | "ndx"_mst; // n, d, x case Fragment::WRAP_V: return "V"_mst.If(x << "B"_mst) | // V=B_x @@ -210,7 +217,12 @@ Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Ty ((x << "h"_mst) && (y << "g"_mst)) || ((x << "i"_mst) && (y << "j"_mst)) || ((x << "j"_mst) && (y << "i"_mst)))); // k=k_x*k_y*k_z* !(g_x*h_y + h_x*g_y + i_x*j_y + j_x*i_y) - case Fragment::MULTI: return "Bnudemsk"_mst; + case Fragment::MULTI: { + return "Bnudemsk"_mst; + } + case Fragment::MULTI_A: { + return "Budemsk"_mst; + } case Fragment::THRESH: { bool all_e = true; bool all_m = true; @@ -246,11 +258,12 @@ Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Ty assert(false); } -size_t ComputeScriptLen(Fragment fragment, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, size_t n_keys) { +size_t ComputeScriptLen(Fragment fragment, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, + size_t n_keys, MiniscriptContext ms_ctx) { switch (fragment) { case Fragment::JUST_1: case Fragment::JUST_0: return 1; - case Fragment::PK_K: return 34; + case Fragment::PK_K: return IsTapscript(ms_ctx) ? 33 : 34; case Fragment::PK_H: return 3 + 21; case Fragment::OLDER: case Fragment::AFTER: return 1 + BuildScript(k).size(); @@ -259,6 +272,7 @@ size_t ComputeScriptLen(Fragment fragment, Type sub0typ, size_t subsize, uint32_ case Fragment::HASH160: case Fragment::RIPEMD160: return 4 + 2 + 21; case Fragment::MULTI: return 1 + BuildScript(n_keys).size() + BuildScript(k).size() + 34 * n_keys; + case Fragment::MULTI_A: return (1 + 32 + 1) * n_keys + BuildScript(k).size() + 1; case Fragment::AND_V: return subsize; case Fragment::WRAP_V: return subsize + (sub0typ << "x"_mst); case Fragment::WRAP_S: @@ -372,9 +386,13 @@ std::optional<std::vector<Opcode>> DecomposeScript(const CScript& script) // Decompose OP_EQUALVERIFY into OP_EQUAL OP_VERIFY out.emplace_back(OP_EQUAL, std::vector<unsigned char>()); opcode = OP_VERIFY; + } else if (opcode == OP_NUMEQUALVERIFY) { + // Decompose OP_NUMEQUALVERIFY into OP_NUMEQUAL OP_VERIFY + out.emplace_back(OP_NUMEQUAL, std::vector<unsigned char>()); + opcode = OP_VERIFY; } else if (IsPushdataOp(opcode)) { if (!CheckMinimalPush(push_data, opcode)) return {}; - } else if (it != itend && (opcode == OP_CHECKSIG || opcode == OP_CHECKMULTISIG || opcode == OP_EQUAL) && (*it == OP_VERIFY)) { + } else if (it != itend && (opcode == OP_CHECKSIG || opcode == OP_CHECKMULTISIG || opcode == OP_EQUAL || opcode == OP_NUMEQUAL) && (*it == OP_VERIFY)) { // Rule out non minimal VERIFY sequences return {}; } diff --git a/src/script/miniscript.h b/src/script/miniscript.h index 4c6bd0bb1d..d6bded959d 100644 --- a/src/script/miniscript.h +++ b/src/script/miniscript.h @@ -20,6 +20,7 @@ #include <primitives/transaction.h> #include <script/script.h> #include <span.h> +#include <util/check.h> #include <util/spanparsing.h> #include <util/strencodings.h> #include <util/string.h> @@ -44,8 +45,8 @@ namespace miniscript { * - When satisfied, pushes nothing. * - Cannot be dissatisfied. * - This can be obtained by adding an OP_VERIFY to a B, modifying the last opcode - * of a B to its -VERIFY version (only for OP_CHECKSIG, OP_CHECKSIGVERIFY - * and OP_EQUAL), or by combining a V fragment under some conditions. + * of a B to its -VERIFY version (only for OP_CHECKSIG, OP_CHECKSIGVERIFY, + * OP_NUMEQUAL and OP_EQUAL), or by combining a V fragment under some conditions. * - For example vc:pk_k(key) = <key> OP_CHECKSIGVERIFY * - "K" Key: * - Takes its inputs from the top of the stack. @@ -216,7 +217,8 @@ enum class Fragment { OR_I, //!< OP_IF [X] OP_ELSE [Y] OP_ENDIF ANDOR, //!< [X] OP_NOTIF [Z] OP_ELSE [Y] OP_ENDIF THRESH, //!< [X1] ([Xn] OP_ADD)* [k] OP_EQUAL - MULTI, //!< [k] [key_n]* [n] OP_CHECKMULTISIG + MULTI, //!< [k] [key_n]* [n] OP_CHECKMULTISIG (only available within P2WSH context) + MULTI_A, //!< [key_0] OP_CHECKSIG ([key_n] OP_CHECKSIGADD)* [k] OP_NUMEQUAL (only within Tapscript ctx) // AND_N(X,Y) is represented as ANDOR(X,Y,0) // WRAP_T(X) is represented as AND_V(X,1) // WRAP_L(X) is represented as OR_I(0,X) @@ -229,13 +231,56 @@ enum class Availability { MAYBE, }; +enum class MiniscriptContext { + P2WSH, + TAPSCRIPT, +}; + +/** Whether the context Tapscript, ensuring the only other possibility is P2WSH. */ +constexpr bool IsTapscript(MiniscriptContext ms_ctx) +{ + switch (ms_ctx) { + case MiniscriptContext::P2WSH: return false; + case MiniscriptContext::TAPSCRIPT: return true; + } + assert(false); +} + namespace internal { +//! The maximum size of a witness item for a Miniscript under Tapscript context. (A BIP340 signature with a sighash type byte.) +static constexpr uint32_t MAX_TAPMINISCRIPT_STACK_ELEM_SIZE{65}; + +//! nVersion + nLockTime +constexpr uint32_t TX_OVERHEAD{4 + 4}; +//! prevout + nSequence + scriptSig +constexpr uint32_t TXIN_BYTES_NO_WITNESS{36 + 4 + 1}; +//! nValue + script len + OP_0 + pushdata 32. +constexpr uint32_t P2WSH_TXOUT_BYTES{8 + 1 + 1 + 33}; +//! Data other than the witness in a transaction. Overhead + vin count + one vin + vout count + one vout + segwit marker +constexpr uint32_t TX_BODY_LEEWAY_WEIGHT{(TX_OVERHEAD + GetSizeOfCompactSize(1) + TXIN_BYTES_NO_WITNESS + GetSizeOfCompactSize(1) + P2WSH_TXOUT_BYTES) * WITNESS_SCALE_FACTOR + 2}; +//! Maximum possible stack size to spend a Taproot output (excluding the script itself). +constexpr uint32_t MAX_TAPSCRIPT_SAT_SIZE{GetSizeOfCompactSize(MAX_STACK_SIZE) + (GetSizeOfCompactSize(MAX_TAPMINISCRIPT_STACK_ELEM_SIZE) + MAX_TAPMINISCRIPT_STACK_ELEM_SIZE) * MAX_STACK_SIZE + GetSizeOfCompactSize(TAPROOT_CONTROL_MAX_SIZE) + TAPROOT_CONTROL_MAX_SIZE}; +/** The maximum size of a script depending on the context. */ +constexpr uint32_t MaxScriptSize(MiniscriptContext ms_ctx) +{ + if (IsTapscript(ms_ctx)) { + // Leaf scripts under Tapscript are not explicitly limited in size. They are only implicitly + // bounded by the maximum standard size of a spending transaction. Let the maximum script + // size conservatively be small enough such that even a maximum sized witness and a reasonably + // sized spending transaction can spend an output paying to this script without running into + // the maximum standard tx size limit. + constexpr auto max_size{MAX_STANDARD_TX_WEIGHT - TX_BODY_LEEWAY_WEIGHT - MAX_TAPSCRIPT_SAT_SIZE}; + return max_size - GetSizeOfCompactSize(max_size); + } + return MAX_STANDARD_P2WSH_SCRIPT_SIZE; +} + //! Helper function for Node::CalcType. -Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys); +Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys, MiniscriptContext ms_ctx); //! Helper function for Node::CalcScriptLen. -size_t ComputeScriptLen(Fragment fragment, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, size_t n_keys); +size_t ComputeScriptLen(Fragment fragment, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, size_t n_keys, MiniscriptContext ms_ctx); //! A helper sanitizer/checker for the output of CalcType. Type SanitizeType(Type x); @@ -328,13 +373,112 @@ struct Ops { Ops(uint32_t in_count, MaxInt<uint32_t> in_sat, MaxInt<uint32_t> in_dsat) : count(in_count), sat(in_sat), dsat(in_dsat) {}; }; +/** A data structure to help the calculation of stack size limits. + * + * Conceptually, every SatInfo object corresponds to a (possibly empty) set of script execution + * traces (sequences of opcodes). + * - SatInfo{} corresponds to the empty set. + * - SatInfo{n, e} corresponds to a single trace whose net effect is removing n elements from the + * stack (may be negative for a net increase), and reaches a maximum of e stack elements more + * than it ends with. + * - operator| is the union operation: (a | b) corresponds to the union of the traces in a and the + * traces in b. + * - operator+ is the concatenation operator: (a + b) corresponds to the set of traces formed by + * concatenating any trace in a with any trace in b. + * + * Its fields are: + * - valid is true if the set is non-empty. + * - netdiff (if valid) is the largest difference between stack size at the beginning and at the + * end of the script across all traces in the set. + * - exec (if valid) is the largest difference between stack size anywhere during execution and at + * the end of the script, across all traces in the set (note that this is not necessarily due + * to the same trace as the one that resulted in the value for netdiff). + * + * This allows us to build up stack size limits for any script efficiently, by starting from the + * individual opcodes miniscripts correspond to, using concatenation to construct scripts, and + * using the union operation to choose between execution branches. Since any top-level script + * satisfaction ends with a single stack element, we know that for a full script: + * - netdiff+1 is the maximal initial stack size (relevant for P2WSH stack limits). + * - exec+1 is the maximal stack size reached during execution (relevant for P2TR stack limits). + * + * Mathematically, SatInfo forms a semiring: + * - operator| is the semiring addition operator, with identity SatInfo{}, and which is commutative + * and associative. + * - operator+ is the semiring multiplication operator, with identity SatInfo{0}, and which is + * associative. + * - operator+ is distributive over operator|, so (a + (b | c)) = (a+b | a+c). This means we do not + * need to actually materialize all possible full execution traces over the whole script (which + * may be exponential in the length of the script); instead we can use the union operation at the + * individual subexpression level, and concatenate the result with subexpressions before and + * after it. + * - It is not a commutative semiring, because a+b can differ from b+a. For example, "OP_1 OP_DROP" + * has exec=1, while "OP_DROP OP_1" has exec=0. + */ +struct SatInfo { + //! Whether a canonical satisfaction/dissatisfaction is possible at all. + const bool valid; + //! How much higher the stack size at start of execution can be compared to at the end. + const int32_t netdiff; + //! Mow much higher the stack size can be during execution compared to at the end. + const int32_t exec; + + /** Empty script set. */ + constexpr SatInfo() noexcept : valid(false), netdiff(0), exec(0) {} + + /** Script set with a single script in it, with specified netdiff and exec. */ + constexpr SatInfo(int32_t in_netdiff, int32_t in_exec) noexcept : + valid{true}, netdiff{in_netdiff}, exec{in_exec} {} + + /** Script set union. */ + constexpr friend SatInfo operator|(const SatInfo& a, const SatInfo& b) noexcept + { + // Union with an empty set is itself. + if (!a.valid) return b; + if (!b.valid) return a; + // Otherwise the netdiff and exec of the union is the maximum of the individual values. + return {std::max(a.netdiff, b.netdiff), std::max(a.exec, b.exec)}; + } + + /** Script set concatenation. */ + constexpr friend SatInfo operator+(const SatInfo& a, const SatInfo& b) noexcept + { + // Concatenation with an empty set yields an empty set. + if (!a.valid || !b.valid) return {}; + // Otherwise, the maximum stack size difference for the combined scripts is the sum of the + // netdiffs, and the maximum stack size difference anywhere is either b.exec (if the + // maximum occurred in b) or b.netdiff+a.exec (if the maximum occurred in a). + return {a.netdiff + b.netdiff, std::max(b.exec, b.netdiff + a.exec)}; + } + + /** The empty script. */ + static constexpr SatInfo Empty() noexcept { return {0, 0}; } + /** A script consisting of a single push opcode. */ + static constexpr SatInfo Push() noexcept { return {-1, 0}; } + /** A script consisting of a single hash opcode. */ + static constexpr SatInfo Hash() noexcept { return {0, 0}; } + /** A script consisting of just a repurposed nop (OP_CHECKLOCKTIMEVERIFY, OP_CHECKSEQUENCEVERIFY). */ + static constexpr SatInfo Nop() noexcept { return {0, 0}; } + /** A script consisting of just OP_IF or OP_NOTIF. Note that OP_ELSE and OP_ENDIF have no stack effect. */ + static constexpr SatInfo If() noexcept { return {1, 1}; } + /** A script consisting of just a binary operator (OP_BOOLAND, OP_BOOLOR, OP_ADD). */ + static constexpr SatInfo BinaryOp() noexcept { return {1, 1}; } + + // Scripts for specific individual opcodes. + static constexpr SatInfo OP_DUP() noexcept { return {-1, 0}; } + static constexpr SatInfo OP_IFDUP(bool nonzero) noexcept { return {nonzero ? -1 : 0, 0}; } + static constexpr SatInfo OP_EQUALVERIFY() noexcept { return {2, 2}; } + static constexpr SatInfo OP_EQUAL() noexcept { return {1, 1}; } + static constexpr SatInfo OP_SIZE() noexcept { return {-1, 0}; } + static constexpr SatInfo OP_CHECKSIG() noexcept { return {1, 1}; } + static constexpr SatInfo OP_0NOTEQUAL() noexcept { return {0, 0}; } + static constexpr SatInfo OP_VERIFY() noexcept { return {1, 1}; } +}; + struct StackSize { - //! Maximum stack size to satisfy; - MaxInt<uint32_t> sat; - //! Maximum stack size to dissatisfy; - MaxInt<uint32_t> dsat; + const SatInfo sat, dsat; - StackSize(MaxInt<uint32_t> in_sat, MaxInt<uint32_t> in_dsat) : sat(in_sat), dsat(in_dsat) {}; + constexpr StackSize(SatInfo in_sat, SatInfo in_dsat) noexcept : sat(in_sat), dsat(in_dsat) {}; + constexpr StackSize(SatInfo in_both) noexcept : sat(in_both), dsat(in_both) {}; }; struct WitnessSize { @@ -362,7 +506,22 @@ struct Node { //! The data bytes in this expression (only for HASH160/HASH256/SHA256/RIPEMD10). const std::vector<unsigned char> data; //! Subexpressions (for WRAP_*/AND_*/OR_*/ANDOR/THRESH) - const std::vector<NodeRef<Key>> subs; + mutable std::vector<NodeRef<Key>> subs; + //! The Script context for this node. Either P2WSH or Tapscript. + const MiniscriptContext m_script_ctx; + + /* Destroy the shared pointers iteratively to avoid a stack-overflow due to recursive calls + * to the subs' destructors. */ + ~Node() { + while (!subs.empty()) { + auto node = std::move(subs.back()); + subs.pop_back(); + while (!node->subs.empty()) { + subs.push_back(std::move(node->subs.back())); + node->subs.pop_back(); + } + } + } private: //! Cached ops counts. @@ -390,7 +549,7 @@ private: subsize += sub->ScriptSize(); } Type sub0type = subs.size() > 0 ? subs[0]->GetType() : ""_mst; - return internal::ComputeScriptLen(fragment, sub0type, subsize, k, subs.size(), keys.size()); + return internal::ComputeScriptLen(fragment, sub0type, subsize, k, subs.size(), keys.size(), m_script_ctx); } /* Apply a recursive algorithm to a Miniscript tree, without actual recursive calls. @@ -557,7 +716,7 @@ private: Type y = subs.size() > 1 ? subs[1]->GetType() : ""_mst; Type z = subs.size() > 2 ? subs[2]->GetType() : ""_mst; - return SanitizeType(ComputeType(fragment, x, y, z, sub_types, k, data.size(), subs.size(), keys.size())); + return SanitizeType(ComputeType(fragment, x, y, z, sub_types, k, data.size(), subs.size(), keys.size(), m_script_ctx)); } public: @@ -578,7 +737,8 @@ public: }; // The upward function computes for a node, given its followed-by-OP_VERIFY status // and the CScripts of its child nodes, the CScript of the node. - auto upfn = [&ctx](bool verify, const Node& node, Span<CScript> subs) -> CScript { + const bool is_tapscript{IsTapscript(m_script_ctx)}; + auto upfn = [&ctx, is_tapscript](bool verify, const Node& node, Span<CScript> subs) -> CScript { switch (node.fragment) { case Fragment::PK_K: return BuildScript(ctx.ToPKBytes(node.keys[0])); case Fragment::PK_H: return BuildScript(OP_DUP, OP_HASH160, ctx.ToPKHBytes(node.keys[0]), OP_EQUALVERIFY); @@ -611,12 +771,21 @@ public: case Fragment::OR_I: return BuildScript(OP_IF, subs[0], OP_ELSE, subs[1], OP_ENDIF); case Fragment::ANDOR: return BuildScript(std::move(subs[0]), OP_NOTIF, subs[2], OP_ELSE, subs[1], OP_ENDIF); case Fragment::MULTI: { + CHECK_NONFATAL(!is_tapscript); CScript script = BuildScript(node.k); for (const auto& key : node.keys) { script = BuildScript(std::move(script), ctx.ToPKBytes(key)); } return BuildScript(std::move(script), node.keys.size(), verify ? OP_CHECKMULTISIGVERIFY : OP_CHECKMULTISIG); } + case Fragment::MULTI_A: { + CHECK_NONFATAL(is_tapscript); + CScript script = BuildScript(ctx.ToPKBytes(*node.keys.begin()), OP_CHECKSIG); + for (auto it = node.keys.begin() + 1; it != node.keys.end(); ++it) { + script = BuildScript(std::move(script), ctx.ToPKBytes(*it), OP_CHECKSIGADD); + } + return BuildScript(std::move(script), node.k, verify ? OP_NUMEQUALVERIFY : OP_NUMEQUAL); + } case Fragment::THRESH: { CScript script = std::move(subs[0]); for (size_t i = 1; i < subs.size(); ++i) { @@ -646,7 +815,8 @@ public: }; // The upward function computes for a node, given whether its parent is a wrapper, // and the string representations of its child nodes, the string representation of the node. - auto upfn = [&ctx](bool wrapped, const Node& node, Span<std::string> subs) -> std::optional<std::string> { + const bool is_tapscript{IsTapscript(m_script_ctx)}; + auto upfn = [&ctx, is_tapscript](bool wrapped, const Node& node, Span<std::string> subs) -> std::optional<std::string> { std::string ret = wrapped ? ":" : ""; switch (node.fragment) { @@ -710,6 +880,7 @@ public: if (node.subs[2]->fragment == Fragment::JUST_0) return std::move(ret) + "and_n(" + std::move(subs[0]) + "," + std::move(subs[1]) + ")"; return std::move(ret) + "andor(" + std::move(subs[0]) + "," + std::move(subs[1]) + "," + std::move(subs[2]) + ")"; case Fragment::MULTI: { + CHECK_NONFATAL(!is_tapscript); auto str = std::move(ret) + "multi(" + ::ToString(node.k); for (const auto& key : node.keys) { auto key_str = ctx.ToString(key); @@ -718,6 +889,16 @@ public: } return std::move(str) + ")"; } + case Fragment::MULTI_A: { + CHECK_NONFATAL(is_tapscript); + auto str = std::move(ret) + "multi_a(" + ::ToString(node.k); + for (const auto& key : node.keys) { + auto key_str = ctx.ToString(key); + if (!key_str) return {}; + str += "," + std::move(*key_str); + } + return std::move(str) + ")"; + } case Fragment::THRESH: { auto str = std::move(ret) + "thresh(" + ::ToString(node.k); for (auto& sub : subs) { @@ -783,6 +964,7 @@ private: return {count, sat, dsat}; } case Fragment::MULTI: return {1, (uint32_t)keys.size(), (uint32_t)keys.size()}; + case Fragment::MULTI_A: return {(uint32_t)keys.size() + 1, 0, 0}; case Fragment::WRAP_S: case Fragment::WRAP_C: case Fragment::WRAP_N: return {1 + subs[0]->ops.count, subs[0]->ops.sat, subs[0]->ops.dsat}; @@ -808,50 +990,115 @@ private: } internal::StackSize CalcStackSize() const { + using namespace internal; switch (fragment) { - case Fragment::JUST_0: return {{}, 0}; - case Fragment::JUST_1: + case Fragment::JUST_0: return {{}, SatInfo::Push()}; + case Fragment::JUST_1: return {SatInfo::Push(), {}}; case Fragment::OLDER: - case Fragment::AFTER: return {0, {}}; - case Fragment::PK_K: return {1, 1}; - case Fragment::PK_H: return {2, 2}; + case Fragment::AFTER: return {SatInfo::Push() + SatInfo::Nop(), {}}; + case Fragment::PK_K: return {SatInfo::Push()}; + case Fragment::PK_H: return {SatInfo::OP_DUP() + SatInfo::Hash() + SatInfo::Push() + SatInfo::OP_EQUALVERIFY()}; case Fragment::SHA256: case Fragment::RIPEMD160: case Fragment::HASH256: - case Fragment::HASH160: return {1, {}}; + case Fragment::HASH160: return { + SatInfo::OP_SIZE() + SatInfo::Push() + SatInfo::OP_EQUALVERIFY() + SatInfo::Hash() + SatInfo::Push() + SatInfo::OP_EQUAL(), + {} + }; case Fragment::ANDOR: { - const auto sat{(subs[0]->ss.sat + subs[1]->ss.sat) | (subs[0]->ss.dsat + subs[2]->ss.sat)}; - const auto dsat{subs[0]->ss.dsat + subs[2]->ss.dsat}; - return {sat, dsat}; + const auto& x{subs[0]->ss}; + const auto& y{subs[1]->ss}; + const auto& z{subs[2]->ss}; + return { + (x.sat + SatInfo::If() + y.sat) | (x.dsat + SatInfo::If() + z.sat), + x.dsat + SatInfo::If() + z.dsat + }; + } + case Fragment::AND_V: { + const auto& x{subs[0]->ss}; + const auto& y{subs[1]->ss}; + return {x.sat + y.sat, {}}; + } + case Fragment::AND_B: { + const auto& x{subs[0]->ss}; + const auto& y{subs[1]->ss}; + return {x.sat + y.sat + SatInfo::BinaryOp(), x.dsat + y.dsat + SatInfo::BinaryOp()}; } - case Fragment::AND_V: return {subs[0]->ss.sat + subs[1]->ss.sat, {}}; - case Fragment::AND_B: return {subs[0]->ss.sat + subs[1]->ss.sat, subs[0]->ss.dsat + subs[1]->ss.dsat}; case Fragment::OR_B: { - const auto sat{(subs[0]->ss.dsat + subs[1]->ss.sat) | (subs[0]->ss.sat + subs[1]->ss.dsat)}; - const auto dsat{subs[0]->ss.dsat + subs[1]->ss.dsat}; - return {sat, dsat}; + const auto& x{subs[0]->ss}; + const auto& y{subs[1]->ss}; + return { + ((x.sat + y.dsat) | (x.dsat + y.sat)) + SatInfo::BinaryOp(), + x.dsat + y.dsat + SatInfo::BinaryOp() + }; } - case Fragment::OR_C: return {subs[0]->ss.sat | (subs[0]->ss.dsat + subs[1]->ss.sat), {}}; - case Fragment::OR_D: return {subs[0]->ss.sat | (subs[0]->ss.dsat + subs[1]->ss.sat), subs[0]->ss.dsat + subs[1]->ss.dsat}; - case Fragment::OR_I: return {(subs[0]->ss.sat + 1) | (subs[1]->ss.sat + 1), (subs[0]->ss.dsat + 1) | (subs[1]->ss.dsat + 1)}; - case Fragment::MULTI: return {k + 1, k + 1}; + case Fragment::OR_C: { + const auto& x{subs[0]->ss}; + const auto& y{subs[1]->ss}; + return {(x.sat + SatInfo::If()) | (x.dsat + SatInfo::If() + y.sat), {}}; + } + case Fragment::OR_D: { + const auto& x{subs[0]->ss}; + const auto& y{subs[1]->ss}; + return { + (x.sat + SatInfo::OP_IFDUP(true) + SatInfo::If()) | (x.dsat + SatInfo::OP_IFDUP(false) + SatInfo::If() + y.sat), + x.dsat + SatInfo::OP_IFDUP(false) + SatInfo::If() + y.dsat + }; + } + case Fragment::OR_I: { + const auto& x{subs[0]->ss}; + const auto& y{subs[1]->ss}; + return {SatInfo::If() + (x.sat | y.sat), SatInfo::If() + (x.dsat | y.dsat)}; + } + // multi(k, key1, key2, ..., key_n) starts off with k+1 stack elements (a 0, plus k + // signatures), then reaches n+k+3 stack elements after pushing the n keys, plus k and + // n itself, and ends with 1 stack element (success or failure). Thus, it net removes + // k elements (from k+1 to 1), while reaching k+n+2 more than it ends with. + case Fragment::MULTI: return {SatInfo(k, k + keys.size() + 2)}; + // multi_a(k, key1, key2, ..., key_n) starts off with n stack elements (the + // signatures), reaches 1 more (after the first key push), and ends with 1. Thus it net + // removes n-1 elements (from n to 1) while reaching n more than it ends with. + case Fragment::MULTI_A: return {SatInfo(keys.size() - 1, keys.size())}; case Fragment::WRAP_A: case Fragment::WRAP_N: - case Fragment::WRAP_S: - case Fragment::WRAP_C: return subs[0]->ss; - case Fragment::WRAP_D: return {1 + subs[0]->ss.sat, 1}; - case Fragment::WRAP_V: return {subs[0]->ss.sat, {}}; - case Fragment::WRAP_J: return {subs[0]->ss.sat, 1}; + case Fragment::WRAP_S: return subs[0]->ss; + case Fragment::WRAP_C: return { + subs[0]->ss.sat + SatInfo::OP_CHECKSIG(), + subs[0]->ss.dsat + SatInfo::OP_CHECKSIG() + }; + case Fragment::WRAP_D: return { + SatInfo::OP_DUP() + SatInfo::If() + subs[0]->ss.sat, + SatInfo::OP_DUP() + SatInfo::If() + }; + case Fragment::WRAP_V: return {subs[0]->ss.sat + SatInfo::OP_VERIFY(), {}}; + case Fragment::WRAP_J: return { + SatInfo::OP_SIZE() + SatInfo::OP_0NOTEQUAL() + SatInfo::If() + subs[0]->ss.sat, + SatInfo::OP_SIZE() + SatInfo::OP_0NOTEQUAL() + SatInfo::If() + }; case Fragment::THRESH: { - auto sats = Vector(internal::MaxInt<uint32_t>(0)); - for (const auto& sub : subs) { - auto next_sats = Vector(sats[0] + sub->ss.dsat); - for (size_t j = 1; j < sats.size(); ++j) next_sats.push_back((sats[j] + sub->ss.dsat) | (sats[j - 1] + sub->ss.sat)); - next_sats.push_back(sats[sats.size() - 1] + sub->ss.sat); + // sats[j] is the SatInfo corresponding to all traces reaching j satisfactions. + auto sats = Vector(SatInfo::Empty()); + for (size_t i = 0; i < subs.size(); ++i) { + // Loop over the subexpressions, processing them one by one. After adding + // element i we need to add OP_ADD (if i>0). + auto add = i ? SatInfo::BinaryOp() : SatInfo::Empty(); + // Construct a variable that will become the next sats, starting with index 0. + auto next_sats = Vector(sats[0] + subs[i]->ss.dsat + add); + // Then loop to construct next_sats[1..i]. + for (size_t j = 1; j < sats.size(); ++j) { + next_sats.push_back(((sats[j] + subs[i]->ss.dsat) | (sats[j - 1] + subs[i]->ss.sat)) + add); + } + // Finally construct next_sats[i+1]. + next_sats.push_back(sats[sats.size() - 1] + subs[i]->ss.sat + add); + // Switch over. sats = std::move(next_sats); } - assert(k <= sats.size()); - return {sats[k], sats[0]}; + // To satisfy thresh we need k satisfactions; to dissatisfy we need 0. In both + // cases a push of k and an OP_EQUAL follow. + return { + sats[k] + SatInfo::Push() + SatInfo::OP_EQUAL(), + sats[0] + SatInfo::Push() + SatInfo::OP_EQUAL() + }; } } assert(false); @@ -885,6 +1132,7 @@ private: case Fragment::OR_D: return {subs[0]->ws.sat | (subs[0]->ws.dsat + subs[1]->ws.sat), subs[0]->ws.dsat + subs[1]->ws.dsat}; case Fragment::OR_I: return {(subs[0]->ws.sat + 1 + 1) | (subs[1]->ws.sat + 1), (subs[0]->ws.dsat + 1 + 1) | (subs[1]->ws.dsat + 1)}; case Fragment::MULTI: return {k * (1 + 72) + 1, k + 1}; + case Fragment::MULTI_A: return {k * (1 + 65) + static_cast<uint32_t>(keys.size()) - k, static_cast<uint32_t>(keys.size())}; case Fragment::WRAP_A: case Fragment::WRAP_N: case Fragment::WRAP_S: @@ -925,6 +1173,34 @@ private: Availability avail = ctx.Sign(node.keys[0], sig); return {ZERO + InputStack(key), (InputStack(std::move(sig)).SetWithSig() + InputStack(key)).SetAvailable(avail)}; } + case Fragment::MULTI_A: { + // sats[j] represents the best stack containing j valid signatures (out of the first i keys). + // In the loop below, these stacks are built up using a dynamic programming approach. + std::vector<InputStack> sats = Vector(EMPTY); + for (size_t i = 0; i < node.keys.size(); ++i) { + // Get the signature for the i'th key in reverse order (the signature for the first key needs to + // be at the top of the stack, contrary to CHECKMULTISIG's satisfaction). + std::vector<unsigned char> sig; + Availability avail = ctx.Sign(node.keys[node.keys.size() - 1 - i], sig); + // Compute signature stack for just this key. + auto sat = InputStack(std::move(sig)).SetWithSig().SetAvailable(avail); + // Compute the next sats vector: next_sats[0] is a copy of sats[0] (no signatures). All further + // next_sats[j] are equal to either the existing sats[j] + ZERO, or sats[j-1] plus a signature + // for the current (i'th) key. The very last element needs all signatures filled. + std::vector<InputStack> next_sats; + next_sats.push_back(sats[0] + ZERO); + for (size_t j = 1; j < sats.size(); ++j) next_sats.push_back((sats[j] + ZERO) | (std::move(sats[j - 1]) + sat)); + next_sats.push_back(std::move(sats[sats.size() - 1]) + std::move(sat)); + // Switch over. + sats = std::move(next_sats); + } + // The dissatisfaction consists of as many empty vectors as there are keys, which is the same as + // satisfying 0 keys. + auto& nsat{sats[0]}; + assert(node.k != 0); + assert(node.k <= sats.size()); + return {std::move(nsat), std::move(sats[node.k])}; + } case Fragment::MULTI: { // sats[j] represents the best stack containing j valid signatures (out of the first i keys). // In the loop below, these stacks are built up using a dynamic programming approach. @@ -1205,19 +1481,36 @@ public: //! Check the ops limit of this script against the consensus limit. bool CheckOpsLimit() const { + if (IsTapscript(m_script_ctx)) return true; if (const auto ops = GetOps()) return *ops <= MAX_OPS_PER_SCRIPT; return true; } - /** Return the maximum number of stack elements needed to satisfy this script non-malleably. - * This does not account for the P2WSH script push. */ + /** Whether this node is of type B, K or W. (That is, anything but V.) */ + bool IsBKW() const { + return !((GetType() & "BKW"_mst) == ""_mst); + } + + /** Return the maximum number of stack elements needed to satisfy this script non-malleably. */ std::optional<uint32_t> GetStackSize() const { if (!ss.sat.valid) return {}; - return ss.sat.value; + return ss.sat.netdiff + static_cast<int32_t>(IsBKW()); + } + + //! Return the maximum size of the stack during execution of this script. + std::optional<uint32_t> GetExecStackSize() const { + if (!ss.sat.valid) return {}; + return ss.sat.exec + static_cast<int32_t>(IsBKW()); } //! Check the maximum stack size for this script against the policy limit. bool CheckStackSize() const { + // Since in Tapscript there is no standardness limit on the script and witness sizes, we may run + // into the maximum stack size while executing the script. Make sure it doesn't happen. + if (IsTapscript(m_script_ctx)) { + if (const auto exec_ss = GetExecStackSize()) return exec_ss <= MAX_STACK_SIZE; + return true; + } if (const auto ss = GetStackSize()) return *ss <= MAX_STANDARD_P2WSH_STACK_ITEMS; return true; } @@ -1235,6 +1528,9 @@ public: //! Return the expression type. Type GetType() const { return typ; } + //! Return the script context for this node. + MiniscriptContext GetMsCtx() const { return m_script_ctx; } + //! Find an insane subnode which has no insane children. Nullptr if there is none. const Node* FindInsaneSub() const { return TreeEval<const Node*>([](const Node& node, Span<const Node*> subs) -> const Node* { @@ -1259,6 +1555,7 @@ public: case Fragment::PK_K: case Fragment::PK_H: case Fragment::MULTI: + case Fragment::MULTI_A: case Fragment::AFTER: case Fragment::OLDER: case Fragment::HASH256: @@ -1286,7 +1583,10 @@ public: } //! Check whether this node is valid at all. - bool IsValid() const { return !(GetType() == ""_mst) && ScriptSize() <= MAX_STANDARD_P2WSH_SCRIPT_SIZE; } + bool IsValid() const { + if (GetType() == ""_mst) return false; + return ScriptSize() <= internal::MaxScriptSize(m_script_ctx); + } //! Check whether this node is valid as a script on its own. bool IsValidTopLevel() const { return IsValid() && GetType() << "B"_mst; } @@ -1328,20 +1628,32 @@ public: bool operator==(const Node<Key>& arg) const { return Compare(*this, arg) == 0; } // Constructors with various argument combinations, which bypass the duplicate key check. - Node(internal::NoDupCheck, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} - Node(internal::NoDupCheck, Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} - Node(internal::NoDupCheck, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} - Node(internal::NoDupCheck, Fragment nt, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} - Node(internal::NoDupCheck, Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) : fragment(nt), k(val), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} - Node(internal::NoDupCheck, Fragment nt, uint32_t val = 0) : fragment(nt), k(val), ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} + Node(internal::NoDupCheck, MiniscriptContext script_ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) + : fragment(nt), k(val), data(std::move(arg)), subs(std::move(sub)), m_script_ctx{script_ctx}, ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} + Node(internal::NoDupCheck, MiniscriptContext script_ctx, Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) + : fragment(nt), k(val), data(std::move(arg)), m_script_ctx{script_ctx}, ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} + Node(internal::NoDupCheck, MiniscriptContext script_ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) + : fragment(nt), k(val), keys(std::move(key)), m_script_ctx{script_ctx}, subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} + Node(internal::NoDupCheck, MiniscriptContext script_ctx, Fragment nt, std::vector<Key> key, uint32_t val = 0) + : fragment(nt), k(val), keys(std::move(key)), m_script_ctx{script_ctx}, ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} + Node(internal::NoDupCheck, MiniscriptContext script_ctx, Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) + : fragment(nt), k(val), subs(std::move(sub)), m_script_ctx{script_ctx}, ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} + Node(internal::NoDupCheck, MiniscriptContext script_ctx, Fragment nt, uint32_t val = 0) + : fragment(nt), k(val), m_script_ctx{script_ctx}, ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {} // Constructors with various argument combinations, which do perform the duplicate key check. - template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, std::move(sub), std::move(arg), val) { DuplicateKeyCheck(ctx); } - template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, std::move(arg), val) { DuplicateKeyCheck(ctx);} - template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, std::move(sub), std::move(key), val) { DuplicateKeyCheck(ctx); } - template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<Key> key, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, std::move(key), val) { DuplicateKeyCheck(ctx); } - template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, std::move(sub), val) { DuplicateKeyCheck(ctx); } - template <typename Ctx> Node(const Ctx& ctx, Fragment nt, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, val) { DuplicateKeyCheck(ctx); } + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) + : Node(internal::NoDupCheck{}, ctx.MsContext(), nt, std::move(sub), std::move(arg), val) { DuplicateKeyCheck(ctx); } + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) + : Node(internal::NoDupCheck{}, ctx.MsContext(), nt, std::move(arg), val) { DuplicateKeyCheck(ctx);} + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) + : Node(internal::NoDupCheck{}, ctx.MsContext(), nt, std::move(sub), std::move(key), val) { DuplicateKeyCheck(ctx); } + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<Key> key, uint32_t val = 0) + : Node(internal::NoDupCheck{}, ctx.MsContext(), nt, std::move(key), val) { DuplicateKeyCheck(ctx); } + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) + : Node(internal::NoDupCheck{}, ctx.MsContext(), nt, std::move(sub), val) { DuplicateKeyCheck(ctx); } + template <typename Ctx> Node(const Ctx& ctx, Fragment nt, uint32_t val = 0) + : Node(internal::NoDupCheck{}, ctx.MsContext(), nt, val) { DuplicateKeyCheck(ctx); } }; namespace internal { @@ -1429,14 +1741,14 @@ std::optional<std::pair<std::vector<unsigned char>, int>> ParseHexStrEnd(Span<co /** BuildBack pops the last two elements off `constructed` and wraps them in the specified Fragment */ template<typename Key> -void BuildBack(Fragment nt, std::vector<NodeRef<Key>>& constructed, const bool reverse = false) +void BuildBack(const MiniscriptContext script_ctx, Fragment nt, std::vector<NodeRef<Key>>& constructed, const bool reverse = false) { NodeRef<Key> child = std::move(constructed.back()); constructed.pop_back(); if (reverse) { - constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, nt, Vector(std::move(child), std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, script_ctx, nt, Vector(std::move(child), std::move(constructed.back()))); } else { - constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, nt, Vector(std::move(constructed.back()), std::move(child))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, script_ctx, nt, Vector(std::move(constructed.back()), std::move(child))); } } @@ -1461,6 +1773,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) // (instead transforming another opcode into its VERIFY form). However, the v: wrapper has // to be interleaved with other fragments to be valid, so this is not a concern. size_t script_size{1}; + size_t max_size{internal::MaxScriptSize(ctx.MsContext())}; // The two integers are used to hold state for thresh() std::vector<std::tuple<ParseContext, int64_t, int64_t>> to_parse; @@ -1468,8 +1781,43 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) to_parse.emplace_back(ParseContext::WRAPPED_EXPR, -1, -1); + // Parses a multi() or multi_a() from its string representation. Returns false on parsing error. + const auto parse_multi_exp = [&](Span<const char>& in, const bool is_multi_a) -> bool { + const auto max_keys{is_multi_a ? MAX_PUBKEYS_PER_MULTI_A : MAX_PUBKEYS_PER_MULTISIG}; + const auto required_ctx{is_multi_a ? MiniscriptContext::TAPSCRIPT : MiniscriptContext::P2WSH}; + if (ctx.MsContext() != required_ctx) return false; + // Get threshold + int next_comma = FindNextChar(in, ','); + if (next_comma < 1) return false; + int64_t k; + if (!ParseInt64(std::string(in.begin(), in.begin() + next_comma), &k)) return false; + in = in.subspan(next_comma + 1); + // Get keys. It is compatible for both compressed and x-only keys. + std::vector<Key> keys; + while (next_comma != -1) { + next_comma = FindNextChar(in, ','); + int key_length = (next_comma == -1) ? FindNextChar(in, ')') : next_comma; + if (key_length < 1) return false; + auto key = ctx.FromString(in.begin(), in.begin() + key_length); + if (!key) return false; + keys.push_back(std::move(*key)); + in = in.subspan(key_length + 1); + } + if (keys.size() < 1 || keys.size() > max_keys) return false; + if (k < 1 || k > (int64_t)keys.size()) return false; + if (is_multi_a) { + // (push + xonly-key + CHECKSIG[ADD]) * n + k + OP_NUMEQUAL(VERIFY), minus one. + script_size += (1 + 32 + 1) * keys.size() + BuildScript(k).size(); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::MULTI_A, std::move(keys), k)); + } else { + script_size += 2 + (keys.size() > 16) + (k > 16) + 34 * keys.size(); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::MULTI, std::move(keys), k)); + } + return true; + }; + while (!to_parse.empty()) { - if (script_size > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {}; + if (script_size > max_size) return {}; // Get the current context we are decoding within auto [cur_context, n, k] = to_parse.back(); @@ -1488,7 +1836,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) // If there is no colon, this loop won't execute bool last_was_v{false}; for (size_t j = 0; colon_index && j < *colon_index; ++j) { - if (script_size > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {}; + if (script_size > max_size) return {}; if (in[j] == 'a') { script_size += 2; to_parse.emplace_back(ParseContext::ALT, -1, -1); @@ -1521,7 +1869,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) } else if (in[j] == 'l') { // The l: wrapper is equivalent to or_i(0,X) script_size += 4; - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_0)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_0)); to_parse.emplace_back(ParseContext::OR_I, -1, -1); } else { return {}; @@ -1534,63 +1882,63 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) } case ParseContext::EXPR: { if (Const("0", in)) { - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_0)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_0)); } else if (Const("1", in)) { - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_1)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_1)); } else if (Const("pk(", in)) { auto res = ParseKeyEnd<Key, Ctx>(in, ctx); if (!res) return {}; auto& [key, key_size] = *res; - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_K, Vector(std::move(key)))))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_C, Vector(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::PK_K, Vector(std::move(key)))))); in = in.subspan(key_size + 1); - script_size += 34; + script_size += IsTapscript(ctx.MsContext()) ? 33 : 34; } else if (Const("pkh(", in)) { auto res = ParseKeyEnd<Key>(in, ctx); if (!res) return {}; auto& [key, key_size] = *res; - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_H, Vector(std::move(key)))))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_C, Vector(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::PK_H, Vector(std::move(key)))))); in = in.subspan(key_size + 1); script_size += 24; } else if (Const("pk_k(", in)) { auto res = ParseKeyEnd<Key>(in, ctx); if (!res) return {}; auto& [key, key_size] = *res; - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_K, Vector(std::move(key)))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::PK_K, Vector(std::move(key)))); in = in.subspan(key_size + 1); - script_size += 33; + script_size += IsTapscript(ctx.MsContext()) ? 32 : 33; } else if (Const("pk_h(", in)) { auto res = ParseKeyEnd<Key>(in, ctx); if (!res) return {}; auto& [key, key_size] = *res; - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_H, Vector(std::move(key)))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::PK_H, Vector(std::move(key)))); in = in.subspan(key_size + 1); script_size += 23; } else if (Const("sha256(", in)) { auto res = ParseHexStrEnd(in, 32, ctx); if (!res) return {}; auto& [hash, hash_size] = *res; - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::SHA256, std::move(hash))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::SHA256, std::move(hash))); in = in.subspan(hash_size + 1); script_size += 38; } else if (Const("ripemd160(", in)) { auto res = ParseHexStrEnd(in, 20, ctx); if (!res) return {}; auto& [hash, hash_size] = *res; - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::RIPEMD160, std::move(hash))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::RIPEMD160, std::move(hash))); in = in.subspan(hash_size + 1); script_size += 26; } else if (Const("hash256(", in)) { auto res = ParseHexStrEnd(in, 32, ctx); if (!res) return {}; auto& [hash, hash_size] = *res; - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH256, std::move(hash))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::HASH256, std::move(hash))); in = in.subspan(hash_size + 1); script_size += 38; } else if (Const("hash160(", in)) { auto res = ParseHexStrEnd(in, 20, ctx); if (!res) return {}; auto& [hash, hash_size] = *res; - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH160, std::move(hash))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::HASH160, std::move(hash))); in = in.subspan(hash_size + 1); script_size += 26; } else if (Const("after(", in)) { @@ -1599,7 +1947,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) int64_t num; if (!ParseInt64(std::string(in.begin(), in.begin() + arg_size), &num)) return {}; if (num < 1 || num >= 0x80000000L) return {}; - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::AFTER, num)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::AFTER, num)); in = in.subspan(arg_size + 1); script_size += 1 + (num > 16) + (num > 0x7f) + (num > 0x7fff) + (num > 0x7fffff); } else if (Const("older(", in)) { @@ -1608,30 +1956,13 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) int64_t num; if (!ParseInt64(std::string(in.begin(), in.begin() + arg_size), &num)) return {}; if (num < 1 || num >= 0x80000000L) return {}; - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::OLDER, num)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::OLDER, num)); in = in.subspan(arg_size + 1); script_size += 1 + (num > 16) + (num > 0x7f) + (num > 0x7fff) + (num > 0x7fffff); } else if (Const("multi(", in)) { - // Get threshold - int next_comma = FindNextChar(in, ','); - if (next_comma < 1) return {}; - if (!ParseInt64(std::string(in.begin(), in.begin() + next_comma), &k)) return {}; - in = in.subspan(next_comma + 1); - // Get keys - std::vector<Key> keys; - while (next_comma != -1) { - next_comma = FindNextChar(in, ','); - int key_length = (next_comma == -1) ? FindNextChar(in, ')') : next_comma; - if (key_length < 1) return {}; - auto key = ctx.FromString(in.begin(), in.begin() + key_length); - if (!key) return {}; - keys.push_back(std::move(*key)); - in = in.subspan(key_length + 1); - } - if (keys.size() < 1 || keys.size() > 20) return {}; - if (k < 1 || k > (int64_t)keys.size()) return {}; - script_size += 2 + (keys.size() > 16) + (k > 16) + 34 * keys.size(); - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::MULTI, std::move(keys), k)); + if (!parse_multi_exp(in, /* is_multi_a = */false)) return {}; + } else if (Const("multi_a(", in)) { + if (!parse_multi_exp(in, /* is_multi_a = */true)) return {}; } else if (Const("thresh(", in)) { int next_comma = FindNextChar(in, ','); if (next_comma < 1) return {}; @@ -1684,70 +2015,70 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) break; } case ParseContext::ALT: { - constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_A, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_A, Vector(std::move(constructed.back()))); break; } case ParseContext::SWAP: { - constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_S, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_S, Vector(std::move(constructed.back()))); break; } case ParseContext::CHECK: { - constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_C, Vector(std::move(constructed.back()))); break; } case ParseContext::DUP_IF: { - constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_D, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_D, Vector(std::move(constructed.back()))); break; } case ParseContext::NON_ZERO: { - constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_J, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_J, Vector(std::move(constructed.back()))); break; } case ParseContext::ZERO_NOTEQUAL: { - constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_N, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_N, Vector(std::move(constructed.back()))); break; } case ParseContext::VERIFY: { script_size += (constructed.back()->GetType() << "x"_mst); - constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_V, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_V, Vector(std::move(constructed.back()))); break; } case ParseContext::WRAP_U: { - constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::OR_I, Vector(std::move(constructed.back()), MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_0))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::OR_I, Vector(std::move(constructed.back()), MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_0))); break; } case ParseContext::WRAP_T: { - constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::AND_V, Vector(std::move(constructed.back()), MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_1))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::AND_V, Vector(std::move(constructed.back()), MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_1))); break; } case ParseContext::AND_B: { - BuildBack(Fragment::AND_B, constructed); + BuildBack(ctx.MsContext(), Fragment::AND_B, constructed); break; } case ParseContext::AND_N: { auto mid = std::move(constructed.back()); constructed.pop_back(); - constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), MakeNodeRef<Key>(ctx, Fragment::JUST_0))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_0))); break; } case ParseContext::AND_V: { - BuildBack(Fragment::AND_V, constructed); + BuildBack(ctx.MsContext(), Fragment::AND_V, constructed); break; } case ParseContext::OR_B: { - BuildBack(Fragment::OR_B, constructed); + BuildBack(ctx.MsContext(), Fragment::OR_B, constructed); break; } case ParseContext::OR_C: { - BuildBack(Fragment::OR_C, constructed); + BuildBack(ctx.MsContext(), Fragment::OR_C, constructed); break; } case ParseContext::OR_D: { - BuildBack(Fragment::OR_D, constructed); + BuildBack(ctx.MsContext(), Fragment::OR_D, constructed); break; } case ParseContext::OR_I: { - BuildBack(Fragment::OR_I, constructed); + BuildBack(ctx.MsContext(), Fragment::OR_I, constructed); break; } case ParseContext::ANDOR: { @@ -1755,7 +2086,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) constructed.pop_back(); auto mid = std::move(constructed.back()); constructed.pop_back(); - constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), std::move(right))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::ANDOR, Vector(std::move(constructed.back()), std::move(mid), std::move(right))); break; } case ParseContext::THRESH: { @@ -1775,7 +2106,7 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) constructed.pop_back(); } std::reverse(subs.begin(), subs.end()); - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::THRESH, std::move(subs), k)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::THRESH, std::move(subs), k)); } else { return {}; } @@ -1808,8 +2139,8 @@ inline NodeRef<Key> Parse(Span<const char> in, const Ctx& ctx) * Construct a vector with one element per opcode in the script, in reverse order. * Each element is a pair consisting of the opcode, as well as the data pushed by * the opcode (including OP_n), if any. OP_CHECKSIGVERIFY, OP_CHECKMULTISIGVERIFY, - * and OP_EQUALVERIFY are decomposed into OP_CHECKSIG, OP_CHECKMULTISIG, OP_EQUAL - * respectively, plus OP_VERIFY. + * OP_NUMEQUALVERIFY and OP_EQUALVERIFY are decomposed into OP_CHECKSIG, OP_CHECKMULTISIG, + * OP_EQUAL and OP_NUMEQUAL respectively, plus OP_VERIFY. */ std::optional<std::vector<Opcode>> DecomposeScript(const CScript& script); @@ -1911,27 +2242,27 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) // Constants if (in[0].first == OP_1) { ++in; - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_1)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_1)); break; } if (in[0].first == OP_0) { ++in; - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::JUST_0)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::JUST_0)); break; } // Public keys - if (in[0].second.size() == 33) { + if (in[0].second.size() == 33 || in[0].second.size() == 32) { auto key = ctx.FromPKBytes(in[0].second.begin(), in[0].second.end()); if (!key) return {}; ++in; - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_K, Vector(std::move(*key)))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::PK_K, Vector(std::move(*key)))); break; } if (last - in >= 5 && in[0].first == OP_VERIFY && in[1].first == OP_EQUAL && in[3].first == OP_HASH160 && in[4].first == OP_DUP && in[2].second.size() == 20) { auto key = ctx.FromPKHBytes(in[2].second.begin(), in[2].second.end()); if (!key) return {}; in += 5; - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::PK_H, Vector(std::move(*key)))); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::PK_H, Vector(std::move(*key)))); break; } // Time locks @@ -1939,37 +2270,38 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) if (last - in >= 2 && in[0].first == OP_CHECKSEQUENCEVERIFY && (num = ParseScriptNumber(in[1]))) { in += 2; if (*num < 1 || *num > 0x7FFFFFFFL) return {}; - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::OLDER, *num)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::OLDER, *num)); break; } if (last - in >= 2 && in[0].first == OP_CHECKLOCKTIMEVERIFY && (num = ParseScriptNumber(in[1]))) { in += 2; if (num < 1 || num > 0x7FFFFFFFL) return {}; - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::AFTER, *num)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::AFTER, *num)); break; } // Hashes if (last - in >= 7 && in[0].first == OP_EQUAL && in[3].first == OP_VERIFY && in[4].first == OP_EQUAL && (num = ParseScriptNumber(in[5])) && num == 32 && in[6].first == OP_SIZE) { if (in[2].first == OP_SHA256 && in[1].second.size() == 32) { - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::SHA256, in[1].second)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::SHA256, in[1].second)); in += 7; break; } else if (in[2].first == OP_RIPEMD160 && in[1].second.size() == 20) { - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::RIPEMD160, in[1].second)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::RIPEMD160, in[1].second)); in += 7; break; } else if (in[2].first == OP_HASH256 && in[1].second.size() == 32) { - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH256, in[1].second)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::HASH256, in[1].second)); in += 7; break; } else if (in[2].first == OP_HASH160 && in[1].second.size() == 20) { - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::HASH160, in[1].second)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::HASH160, in[1].second)); in += 7; break; } } // Multi if (last - in >= 3 && in[0].first == OP_CHECKMULTISIG) { + if (IsTapscript(ctx.MsContext())) return {}; std::vector<Key> keys; const auto n = ParseScriptNumber(in[1]); if (!n || last - in < 3 + *n) return {}; @@ -1984,7 +2316,37 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) if (!k || *k < 1 || *k > *n) return {}; in += 3 + *n; std::reverse(keys.begin(), keys.end()); - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::MULTI, std::move(keys), *k)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::MULTI, std::move(keys), *k)); + break; + } + // Tapscript's equivalent of multi + if (last - in >= 4 && in[0].first == OP_NUMEQUAL) { + if (!IsTapscript(ctx.MsContext())) return {}; + // The necessary threshold of signatures. + const auto k = ParseScriptNumber(in[1]); + if (!k) return {}; + if (*k < 1 || *k > MAX_PUBKEYS_PER_MULTI_A) return {}; + if (last - in < 2 + *k * 2) return {}; + std::vector<Key> keys; + keys.reserve(*k); + // Walk through the expected (pubkey, CHECKSIG[ADD]) pairs. + for (int pos = 2;; pos += 2) { + if (last - in < pos + 2) return {}; + // Make sure it's indeed an x-only pubkey and a CHECKSIG[ADD], then parse the key. + if (in[pos].first != OP_CHECKSIGADD && in[pos].first != OP_CHECKSIG) return {}; + if (in[pos + 1].second.size() != 32) return {}; + auto key = ctx.FromPKBytes(in[pos + 1].second.begin(), in[pos + 1].second.end()); + if (!key) return {}; + keys.push_back(std::move(*key)); + // Make sure early we don't parse an arbitrary large expression. + if (keys.size() > MAX_PUBKEYS_PER_MULTI_A) return {}; + // OP_CHECKSIG means it was the last one to parse. + if (in[pos].first == OP_CHECKSIG) break; + } + if (keys.size() < (size_t)*k) return {}; + in += 2 + keys.size() * 2; + std::reverse(keys.begin(), keys.end()); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::MULTI_A, std::move(keys), *k)); break; } /** In the following wrappers, we only need to push SINGLE_BKV_EXPR rather @@ -2079,63 +2441,63 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) case DecodeContext::SWAP: { if (in >= last || in[0].first != OP_SWAP || constructed.empty()) return {}; ++in; - constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_S, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_S, Vector(std::move(constructed.back()))); break; } case DecodeContext::ALT: { if (in >= last || in[0].first != OP_TOALTSTACK || constructed.empty()) return {}; ++in; - constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_A, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_A, Vector(std::move(constructed.back()))); break; } case DecodeContext::CHECK: { if (constructed.empty()) return {}; - constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_C, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_C, Vector(std::move(constructed.back()))); break; } case DecodeContext::DUP_IF: { if (constructed.empty()) return {}; - constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_D, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_D, Vector(std::move(constructed.back()))); break; } case DecodeContext::VERIFY: { if (constructed.empty()) return {}; - constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_V, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_V, Vector(std::move(constructed.back()))); break; } case DecodeContext::NON_ZERO: { if (constructed.empty()) return {}; - constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_J, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_J, Vector(std::move(constructed.back()))); break; } case DecodeContext::ZERO_NOTEQUAL: { if (constructed.empty()) return {}; - constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::WRAP_N, Vector(std::move(constructed.back()))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::WRAP_N, Vector(std::move(constructed.back()))); break; } case DecodeContext::AND_V: { if (constructed.size() < 2) return {}; - BuildBack(Fragment::AND_V, constructed, /*reverse=*/true); + BuildBack(ctx.MsContext(), Fragment::AND_V, constructed, /*reverse=*/true); break; } case DecodeContext::AND_B: { if (constructed.size() < 2) return {}; - BuildBack(Fragment::AND_B, constructed, /*reverse=*/true); + BuildBack(ctx.MsContext(), Fragment::AND_B, constructed, /*reverse=*/true); break; } case DecodeContext::OR_B: { if (constructed.size() < 2) return {}; - BuildBack(Fragment::OR_B, constructed, /*reverse=*/true); + BuildBack(ctx.MsContext(), Fragment::OR_B, constructed, /*reverse=*/true); break; } case DecodeContext::OR_C: { if (constructed.size() < 2) return {}; - BuildBack(Fragment::OR_C, constructed, /*reverse=*/true); + BuildBack(ctx.MsContext(), Fragment::OR_C, constructed, /*reverse=*/true); break; } case DecodeContext::OR_D: { if (constructed.size() < 2) return {}; - BuildBack(Fragment::OR_D, constructed, /*reverse=*/true); + BuildBack(ctx.MsContext(), Fragment::OR_D, constructed, /*reverse=*/true); break; } case DecodeContext::ANDOR: { @@ -2145,7 +2507,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) NodeRef<Key> right = std::move(constructed.back()); constructed.pop_back(); NodeRef<Key> mid = std::move(constructed.back()); - constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::ANDOR, Vector(std::move(left), std::move(mid), std::move(right))); + constructed.back() = MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::ANDOR, Vector(std::move(left), std::move(mid), std::move(right))); break; } case DecodeContext::THRESH_W: { @@ -2169,7 +2531,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) constructed.pop_back(); subs.push_back(std::move(sub)); } - constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, Fragment::THRESH, std::move(subs), k)); + constructed.push_back(MakeNodeRef<Key>(internal::NoDupCheck{}, ctx.MsContext(), Fragment::THRESH, std::move(subs), k)); break; } case DecodeContext::ENDIF: { @@ -2219,7 +2581,7 @@ inline NodeRef<Key> DecodeScript(I& in, I last, const Ctx& ctx) if (in >= last) return {}; if (in[0].first == OP_IF) { ++in; - BuildBack(Fragment::OR_I, constructed, /*reverse=*/true); + BuildBack(ctx.MsContext(), Fragment::OR_I, constructed, /*reverse=*/true); } else if (in[0].first == OP_NOTIF) { ++in; to_parse.emplace_back(DecodeContext::ANDOR, -1, -1); @@ -2252,7 +2614,7 @@ template<typename Ctx> inline NodeRef<typename Ctx::Key> FromScript(const CScript& script, const Ctx& ctx) { using namespace internal; // A too large Script is necessarily invalid, don't bother parsing it. - if (script.size() > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {}; + if (script.size() > MaxScriptSize(ctx.MsContext())) return {}; auto decomposed = DecomposeScript(script); if (!decomposed) return {}; auto it = decomposed->begin(); diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 92b7ad50b5..248ad780c3 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -114,12 +114,17 @@ static bool GetPubKey(const SigningProvider& provider, const SignatureData& sigd pubkey = it->second.first; return true; } - // Look for pubkey in pubkey list + // Look for pubkey in pubkey lists const auto& pk_it = sigdata.misc_pubkeys.find(address); if (pk_it != sigdata.misc_pubkeys.end()) { pubkey = pk_it->second.first; return true; } + const auto& tap_pk_it = sigdata.tap_pubkeys.find(address); + if (tap_pk_it != sigdata.tap_pubkeys.end()) { + pubkey = tap_pk_it->second.GetEvenCorrespondingCPubKey(); + return true; + } // Query the underlying provider return provider.GetPubKey(address, pubkey); } @@ -171,49 +176,158 @@ static bool CreateTaprootScriptSig(const BaseSignatureCreator& creator, Signatur return false; } -static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatureCreator& creator, SignatureData& sigdata, int leaf_version, Span<const unsigned char> script_bytes, std::vector<valtype>& result) +template<typename M, typename K, typename V> +miniscript::Availability MsLookupHelper(const M& map, const K& key, V& value) { - // Only BIP342 tapscript signing is supported for now. - if (leaf_version != TAPROOT_LEAF_TAPSCRIPT) return false; - SigVersion sigversion = SigVersion::TAPSCRIPT; + auto it = map.find(key); + if (it != map.end()) { + value = it->second; + return miniscript::Availability::YES; + } + return miniscript::Availability::NO; +} - uint256 leaf_hash = ComputeTapleafHash(leaf_version, script_bytes); - CScript script = CScript(script_bytes.begin(), script_bytes.end()); +/** + * Context for solving a Miniscript. + * If enough material (access to keys, hash preimages, ..) is given, produces a valid satisfaction. + */ +template<typename Pk> +struct Satisfier { + using Key = Pk; - // <xonly pubkey> OP_CHECKSIG - if (script.size() == 34 && script[33] == OP_CHECKSIG && script[0] == 0x20) { - XOnlyPubKey pubkey{Span{script}.subspan(1, 32)}; - std::vector<unsigned char> sig; - if (CreateTaprootScriptSig(creator, sigdata, provider, sig, pubkey, leaf_hash, sigversion)) { - result = Vector(std::move(sig)); - return true; - } - return false; + const SigningProvider& m_provider; + SignatureData& m_sig_data; + const BaseSignatureCreator& m_creator; + const CScript& m_witness_script; + //! The context of the script we are satisfying (either P2WSH or Tapscript). + const miniscript::MiniscriptContext m_script_ctx; + + explicit Satisfier(const SigningProvider& provider LIFETIMEBOUND, SignatureData& sig_data LIFETIMEBOUND, + const BaseSignatureCreator& creator LIFETIMEBOUND, + const CScript& witscript LIFETIMEBOUND, + miniscript::MiniscriptContext script_ctx) : m_provider(provider), + m_sig_data(sig_data), + m_creator(creator), + m_witness_script(witscript), + m_script_ctx(script_ctx) {} + + static bool KeyCompare(const Key& a, const Key& b) { + return a < b; } - // multi_a scripts (<key> OP_CHECKSIG <key> OP_CHECKSIGADD <key> OP_CHECKSIGADD <k> OP_NUMEQUAL) - if (auto match = MatchMultiA(script)) { - std::vector<std::vector<unsigned char>> sigs; - int good_sigs = 0; - for (size_t i = 0; i < match->second.size(); ++i) { - XOnlyPubKey pubkey{*(match->second.rbegin() + i)}; - std::vector<unsigned char> sig; - bool good_sig = CreateTaprootScriptSig(creator, sigdata, provider, sig, pubkey, leaf_hash, sigversion); - if (good_sig && good_sigs < match->first) { - ++good_sigs; - sigs.push_back(std::move(sig)); - } else { - sigs.emplace_back(); - } + //! Get a CPubKey from a key hash. Note the key hash may be of an xonly pubkey. + template<typename I> + std::optional<CPubKey> CPubFromPKHBytes(I first, I last) const { + assert(last - first == 20); + CPubKey pubkey; + CKeyID key_id; + std::copy(first, last, key_id.begin()); + if (GetPubKey(m_provider, m_sig_data, key_id, pubkey)) return pubkey; + m_sig_data.missing_pubkeys.push_back(key_id); + return {}; + } + + //! Conversion to raw public key. + std::vector<unsigned char> ToPKBytes(const Key& key) const { return {key.begin(), key.end()}; } + + //! Time lock satisfactions. + bool CheckAfter(uint32_t value) const { return m_creator.Checker().CheckLockTime(CScriptNum(value)); } + bool CheckOlder(uint32_t value) const { return m_creator.Checker().CheckSequence(CScriptNum(value)); } + + //! Hash preimage satisfactions. + miniscript::Availability SatSHA256(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const { + return MsLookupHelper(m_sig_data.sha256_preimages, hash, preimage); + } + miniscript::Availability SatRIPEMD160(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const { + return MsLookupHelper(m_sig_data.ripemd160_preimages, hash, preimage); + } + miniscript::Availability SatHASH256(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const { + return MsLookupHelper(m_sig_data.hash256_preimages, hash, preimage); + } + miniscript::Availability SatHASH160(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const { + return MsLookupHelper(m_sig_data.hash160_preimages, hash, preimage); + } + + miniscript::MiniscriptContext MsContext() const { + return m_script_ctx; + } +}; + +/** Miniscript satisfier specific to P2WSH context. */ +struct WshSatisfier: Satisfier<CPubKey> { + explicit WshSatisfier(const SigningProvider& provider LIFETIMEBOUND, SignatureData& sig_data LIFETIMEBOUND, + const BaseSignatureCreator& creator LIFETIMEBOUND, const CScript& witscript LIFETIMEBOUND) + : Satisfier(provider, sig_data, creator, witscript, miniscript::MiniscriptContext::P2WSH) {} + + //! Conversion from a raw compressed public key. + template <typename I> + std::optional<CPubKey> FromPKBytes(I first, I last) const { + CPubKey pubkey{first, last}; + if (pubkey.IsValid()) return pubkey; + return {}; + } + + //! Conversion from a raw compressed public key hash. + template<typename I> + std::optional<CPubKey> FromPKHBytes(I first, I last) const { + return Satisfier::CPubFromPKHBytes(first, last); + } + + //! Satisfy an ECDSA signature check. + miniscript::Availability Sign(const CPubKey& key, std::vector<unsigned char>& sig) const { + if (CreateSig(m_creator, m_sig_data, m_provider, sig, key, m_witness_script, SigVersion::WITNESS_V0)) { + return miniscript::Availability::YES; } - if (good_sigs == match->first) { - result = std::move(sigs); - return true; + return miniscript::Availability::NO; + } +}; + +/** Miniscript satisfier specific to Tapscript context. */ +struct TapSatisfier: Satisfier<XOnlyPubKey> { + const uint256& m_leaf_hash; + + explicit TapSatisfier(const SigningProvider& provider LIFETIMEBOUND, SignatureData& sig_data LIFETIMEBOUND, + const BaseSignatureCreator& creator LIFETIMEBOUND, const CScript& script LIFETIMEBOUND, + const uint256& leaf_hash LIFETIMEBOUND) + : Satisfier(provider, sig_data, creator, script, miniscript::MiniscriptContext::TAPSCRIPT), + m_leaf_hash(leaf_hash) {} + + //! Conversion from a raw xonly public key. + template <typename I> + std::optional<XOnlyPubKey> FromPKBytes(I first, I last) const { + CHECK_NONFATAL(last - first == 32); + XOnlyPubKey pubkey; + std::copy(first, last, pubkey.begin()); + return pubkey; + } + + //! Conversion from a raw xonly public key hash. + template<typename I> + std::optional<XOnlyPubKey> FromPKHBytes(I first, I last) const { + if (auto pubkey = Satisfier::CPubFromPKHBytes(first, last)) return XOnlyPubKey{*pubkey}; + return {}; + } + + //! Satisfy a BIP340 signature check. + miniscript::Availability Sign(const XOnlyPubKey& key, std::vector<unsigned char>& sig) const { + if (CreateTaprootScriptSig(m_creator, m_sig_data, m_provider, sig, key, m_leaf_hash, SigVersion::TAPSCRIPT)) { + return miniscript::Availability::YES; } - return false; + return miniscript::Availability::NO; } +}; - return false; +static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatureCreator& creator, SignatureData& sigdata, int leaf_version, Span<const unsigned char> script_bytes, std::vector<valtype>& result) +{ + // Only BIP342 tapscript signing is supported for now. + if (leaf_version != TAPROOT_LEAF_TAPSCRIPT) return false; + + uint256 leaf_hash = ComputeTapleafHash(leaf_version, script_bytes); + CScript script = CScript(script_bytes.begin(), script_bytes.end()); + + TapSatisfier ms_satisfier{provider, sigdata, creator, script, leaf_hash}; + const auto ms = miniscript::FromScript(script, ms_satisfier); + return ms && ms->Satisfy(ms_satisfier, result) == miniscript::Availability::YES; } static bool SignTaproot(const SigningProvider& provider, const BaseSignatureCreator& creator, const WitnessV1Taproot& output, SignatureData& sigdata, std::vector<valtype>& result) @@ -382,92 +496,6 @@ static CScript PushAll(const std::vector<valtype>& values) return result; } -template<typename M, typename K, typename V> -miniscript::Availability MsLookupHelper(const M& map, const K& key, V& value) -{ - auto it = map.find(key); - if (it != map.end()) { - value = it->second; - return miniscript::Availability::YES; - } - return miniscript::Availability::NO; -} - -/** - * Context for solving a Miniscript. - * If enough material (access to keys, hash preimages, ..) is given, produces a valid satisfaction. - */ -struct Satisfier { - typedef CPubKey Key; - - const SigningProvider& m_provider; - SignatureData& m_sig_data; - const BaseSignatureCreator& m_creator; - const CScript& m_witness_script; - - explicit Satisfier(const SigningProvider& provider LIFETIMEBOUND, SignatureData& sig_data LIFETIMEBOUND, - const BaseSignatureCreator& creator LIFETIMEBOUND, - const CScript& witscript LIFETIMEBOUND) : m_provider(provider), - m_sig_data(sig_data), - m_creator(creator), - m_witness_script(witscript) {} - - static bool KeyCompare(const Key& a, const Key& b) { - return a < b; - } - - //! Conversion from a raw public key. - template <typename I> - std::optional<Key> FromPKBytes(I first, I last) const - { - Key pubkey{first, last}; - if (pubkey.IsValid()) return pubkey; - return {}; - } - - //! Conversion from a raw public key hash. - template<typename I> - std::optional<Key> FromPKHBytes(I first, I last) const { - assert(last - first == 20); - Key pubkey; - CKeyID key_id; - std::copy(first, last, key_id.begin()); - if (GetPubKey(m_provider, m_sig_data, key_id, pubkey)) return pubkey; - m_sig_data.missing_pubkeys.push_back(key_id); - return {}; - } - - //! Conversion to raw public key. - std::vector<unsigned char> ToPKBytes(const CPubKey& key) const { return {key.begin(), key.end()}; } - - //! Satisfy a signature check. - miniscript::Availability Sign(const CPubKey& key, std::vector<unsigned char>& sig) const { - if (CreateSig(m_creator, m_sig_data, m_provider, sig, key, m_witness_script, SigVersion::WITNESS_V0)) { - return miniscript::Availability::YES; - } - return miniscript::Availability::NO; - } - - //! Time lock satisfactions. - bool CheckAfter(uint32_t value) const { return m_creator.Checker().CheckLockTime(CScriptNum(value)); } - bool CheckOlder(uint32_t value) const { return m_creator.Checker().CheckSequence(CScriptNum(value)); } - - - //! Hash preimage satisfactions. - miniscript::Availability SatSHA256(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const { - return MsLookupHelper(m_sig_data.sha256_preimages, hash, preimage); - } - miniscript::Availability SatRIPEMD160(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const { - return MsLookupHelper(m_sig_data.ripemd160_preimages, hash, preimage); - } - miniscript::Availability SatHASH256(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const { - return MsLookupHelper(m_sig_data.hash256_preimages, hash, preimage); - } - miniscript::Availability SatHASH160(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const { - return MsLookupHelper(m_sig_data.hash160_preimages, hash, preimage); - } -}; - bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreator& creator, const CScript& fromPubKey, SignatureData& sigdata) { if (sigdata.complete) return true; @@ -512,7 +540,7 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato // isn't fully solved. For instance the CHECKMULTISIG satisfaction in SignStep() pushes partial signatures // and the extractor relies on this behaviour to combine witnesses. if (!solved && result.empty()) { - Satisfier ms_satisfier{provider, sigdata, creator, witnessscript}; + WshSatisfier ms_satisfier{provider, sigdata, creator, witnessscript}; const auto ms = miniscript::FromScript(witnessscript, ms_satisfier); solved = ms && ms->Satisfy(ms_satisfier, result) == miniscript::Availability::YES; } diff --git a/src/script/sign.h b/src/script/sign.h index 4d7dade44e..ace2ba7856 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -79,6 +79,7 @@ struct SignatureData { std::vector<unsigned char> taproot_key_path_sig; /// Schnorr signature for key path spending std::map<std::pair<XOnlyPubKey, uint256>, std::vector<unsigned char>> taproot_script_sigs; ///< (Partial) schnorr signatures, indexed by XOnlyPubKey and leaf_hash. std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> taproot_misc_pubkeys; ///< Miscellaneous Taproot pubkeys involved in this input along with their leaf script hashes and key origin data. Also includes the Taproot internal key (may have no leaf script hashes). + std::map<CKeyID, XOnlyPubKey> tap_pubkeys; ///< Misc Taproot pubkeys involved in this input, by hash. (Equivalent of misc_pubkeys but for Taproot.) std::vector<CKeyID> missing_pubkeys; ///< KeyIDs of pubkeys which could not be found std::vector<CKeyID> missing_sigs; ///< KeyIDs of pubkeys for signatures which could not be found uint160 missing_redeem_script; ///< ScriptID of the missing redeemScript (if any) diff --git a/src/serialize.h b/src/serialize.h index e53ff9fa4c..8b15178ec0 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -316,7 +316,7 @@ template <typename Stream> inline void Unserialize(Stream& s, bool& a) { uint8_t * size <= UINT_MAX -- 5 bytes (254 + 4 bytes) * size > UINT_MAX -- 9 bytes (255 + 8 bytes) */ -inline unsigned int GetSizeOfCompactSize(uint64_t nSize) +constexpr inline unsigned int GetSizeOfCompactSize(uint64_t nSize) { if (nSize < 253) return sizeof(unsigned char); else if (nSize <= std::numeric_limits<uint16_t>::max()) return sizeof(unsigned char) + sizeof(uint16_t); diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp index 60e441d457..9b48c48ff8 100644 --- a/src/test/descriptor_tests.cpp +++ b/src/test/descriptor_tests.cpp @@ -538,10 +538,10 @@ BOOST_AUTO_TEST_CASE(descriptor_test) // Invalid checksum CheckUnparsable("wsh(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))#abcdef12", "wsh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))#abcdef12", "Provided checksum 'abcdef12' does not match computed checksum 'tyzp6a7p'"); - // Only p2wsh context is valid - CheckUnparsable("sh(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", "sh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", "Miniscript expressions can only be used in wsh"); + // Only p2wsh or tr contexts are valid + CheckUnparsable("sh(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", "sh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", "Miniscript expressions can only be used in wsh or tr."); CheckUnparsable("tr(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", "tr(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", "tr(): key 'and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10))' is not valid"); - CheckUnparsable("raw(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", "sh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", "Miniscript expressions can only be used in wsh"); + CheckUnparsable("raw(and_v(vc:andor(pk(L4gM1FBdyHNpkzsFh9ipnofLhpZRp2mwobpeULy1a6dBTvw8Ywtd),pk_k(Kx9HCDjGiwFcgVNhTrS5z5NeZdD6veeam61eDxLDCkGWujvL4Gnn),and_v(v:older(1),pk_k(L4o2kDvXXDRH2VS9uBnouScLduWt4dZnM25se7kvEjJeQ285en2A))),after(10)))", "sh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(02aa27e5eb2c185e87cd1dbc3e0efc9cb1175235e0259df1713424941c3cb40402))),after(10)))", "Miniscript expressions can only be used in wsh or tr."); CheckUnparsable("", "tr(034D2224bbbbbbbbbbcbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb40,{{{{{{{{{{{{{{{{{{{{{{multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/967808'/9,xprvA1RpRA33e1JQ7ifknakTFNpgXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/968/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/3/4/5/58/55/2/5/58/58/2/5/5/5/8/5/2/8/5/85/2/8/2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/8/5/8/5/4/5/585/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/8/2/5/8/5/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/58/58/2/0/8/5/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/5/8/5/8/24/5/58/52/5/8/5/2/8/24/5/58/588/246/8/5/2/8/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/5/4/5/58/55/58/2/5/8/55/2/5/8/58/555/58/2/5/8/4//2/5/58/5w/2/5/8/5/2/4/5/58/5558'/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/8/2/5/8/5/5/8/58/2/5/58/58/2/5/8/9/588/2/58/2/5/8/5/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/82/5/8/5/5/58/52/6/8/5/2/8/{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}{{{{{{{{{DDD2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8588/246/8/5/2DLDDDDDDDbbD3DDDD/8/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/3/4/5/58/55/2/5/58/58/2/5/5/5/8/5/2/8/5/85/2/8/2/5/8D)/5/2/5/58/58/2/5/58/58/58/588/2/58/2/5/8/5/25/58/58/2/5/58/58/2/5/8/9/588/2/58/2/6780,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFW/8/5/2/5/58678008')", "'multi(1,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/967808'/9,xprvA1RpRA33e1JQ7ifknakTFNpgXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc/968/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/3/4/5/58/55/2/5/58/58/2/5/5/5/8/5/2/8/5/85/2/8/2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/8/5/8/5/4/5/585/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/8/2/5/8/5/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/58/58/2/0/8/5/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/5/8/5/8/24/5/58/52/5/8/5/2/8/24/5/58/588/246/8/5/2/8/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/5/4/5/58/55/58/2/5/8/55/2/5/8/58/555/58/2/5/8/4//2/5/58/5w/2/5/8/5/2/4/5/58/5558'/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/8/2/5/8/5/5/8/58/2/5/58/58/2/5/8/9/588/2/58/2/5/8/5/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8/5/2/5/58/58/2/5/5/58/588/2/58/2/5/8/5/2/82/5/8/5/5/58/52/6/8/5/2/8/{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}{{{{{{{{{DDD2/5/8/5/2/5/58/58/2/5/58/58/588/2/58/2/8/5/8/5/4/5/58/588/2/6/8/5/2/8/2/5/8588/246/8/5/2DLDDDDDDDbbD3DDDD/8/2/5/8/5/2/5/58/58/2/5/5/5/58/588/2/6/8/5/2/8/2/5/8/2/58/2/5/8/5/2/8/5/8/3/4/5/58/55/2/5/58/58/2/5/5/5/8/5/2/8/5/85/2/8/2/5/8D)/5/2/5/58/58/2/5/58/58/58/588/2/58/2/5/8/5/25/58/58/2/5/58/58/2/5/8/9/588/2/58/2/6780,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFW/8/5/2/5/58678008'' is not a valid descriptor function"); // No uncompressed keys allowed CheckUnparsable("", "wsh(and_v(vc:andor(pk(03cdabb7f2dce7bfbd8a0b9570c6fd1e712e5d64045e9d6b517b3d5072251dc204),pk_k(032707170c71d8f75e4ca4e3fce870b9409dcaf12b051d3bcadff74747fa7619c0),and_v(v:older(1),pk_k(049228de6902abb4f541791f6d7f925b10e2078ccb1298856e5ea5cc5fd667f930eac37a00cc07f9a91ef3c2d17bf7a17db04552ff90ac312a5b8b4caca6c97aa4))),after(10)))", "A function is needed within P2WSH"); @@ -582,6 +582,18 @@ BOOST_AUTO_TEST_CASE(descriptor_test) // Same for hash256 Check("wsh(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),pk(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)))", "wsh(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)))", "wsh(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)))", SIGNABLE_FAILS, {{"0020cf62bf97baf977aec69cbc290c372899f913337a9093e8f066ab59b8657a365c"}}, OutputType::BECH32, /*op_desc_id=*/uint256S("8412ba3ac20ba3a30f81442d10d32e0468fa52814960d04e959bf84a9b813b88"), {{}}, /*spender_nlocktime=*/0, /*spender_nsequence=*/CTxIn::SEQUENCE_FINAL, {}); Check("wsh(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),pk(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)))", "wsh(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)))", "wsh(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),pk(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)))", SIGNABLE, {{"0020cf62bf97baf977aec69cbc290c372899f913337a9093e8f066ab59b8657a365c"}}, OutputType::BECH32, /*op_desc_id=*/uint256S("8412ba3ac20ba3a30f81442d10d32e0468fa52814960d04e959bf84a9b813b88"), {{}}, /*spender_nlocktime=*/0, /*spender_nsequence=*/CTxIn::SEQUENCE_FINAL, {{ParseHex("ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588"), ParseHex("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")}}); + // Can have a Miniscript expression under tr() if it's alone. + Check("tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,thresh(2,pk(L1NKM8dVA1h52mwDrmk1YreTWkAZZTu2vmKLpmLEbFRqGQYjHeEV),s:pk(Kz3iCBy3HNGP5CZWDsAMmnCMFNwqdDohudVN9fvkrN7tAkzKNtM7),adv:older(42)))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,thresh(2,pk(30a6069f344fb784a2b4c99540a91ee727c91e3a25ef6aae867d9c65b5f23529),s:pk(9918d400c1b8c3c478340a40117ced4054b6b58f48cdb3c89b836bdfee1f5766),adv:older(42)))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,thresh(2,pk(30a6069f344fb784a2b4c99540a91ee727c91e3a25ef6aae867d9c65b5f23529),s:pk(9918d400c1b8c3c478340a40117ced4054b6b58f48cdb3c89b836bdfee1f5766),adv:older(42)))", MISSING_PRIVKEYS | XONLY_KEYS | SIGNABLE, {{"512033982eebe204dc66508e4b19cfc31b5ffc6e1bfcbf6e5597dfc2521a52270795"}}, OutputType::BECH32M); + // Can have a pkh() expression alone as tr() script path (because pkh() is valid Miniscript). + Check("tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,pkh(L1NKM8dVA1h52mwDrmk1YreTWkAZZTu2vmKLpmLEbFRqGQYjHeEV))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,pkh(30a6069f344fb784a2b4c99540a91ee727c91e3a25ef6aae867d9c65b5f23529))", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,pkh(30a6069f344fb784a2b4c99540a91ee727c91e3a25ef6aae867d9c65b5f23529))", MISSING_PRIVKEYS | XONLY_KEYS | SIGNABLE, {{"51201e9875f690f5847404e4c5951e2f029887df0525691ee11a682afd37b608aad4"}}, OutputType::BECH32M); + // Can have a Miniscript expression under tr() if it's part of a tree. + Check("tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{{pkh(KykUPmR5967F4URzMUeCv9kNMU9CNRWycrPmx3ZvfkWoQLabbimL),pk(L3Enys1jFgTq4E24b8Uom1kAz6cNkz3Z82XZpBKCE2ztErq9fqvJ)},thresh(1,pk(L1NKM8dVA1h52mwDrmk1YreTWkAZZTu2vmKLpmLEbFRqGQYjHeEV),s:pk(Kz3iCBy3HNGP5CZWDsAMmnCMFNwqdDohudVN9fvkrN7tAkzKNtM7))})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{{pkh(1c9bc926084382e76da33b5a52d17b1fa153c072aae5fb5228ecc2ccf89d79d5),pk(0dd6b52b192ab195558d22dd8437a9ec4519ee5ded496c0d55bc9b1a8b0e8c2b)},thresh(1,pk(30a6069f344fb784a2b4c99540a91ee727c91e3a25ef6aae867d9c65b5f23529),s:pk(9918d400c1b8c3c478340a40117ced4054b6b58f48cdb3c89b836bdfee1f5766))})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{{pkh(1c9bc926084382e76da33b5a52d17b1fa153c072aae5fb5228ecc2ccf89d79d5),pk(0dd6b52b192ab195558d22dd8437a9ec4519ee5ded496c0d55bc9b1a8b0e8c2b)},thresh(1,pk(30a6069f344fb784a2b4c99540a91ee727c91e3a25ef6aae867d9c65b5f23529),s:pk(9918d400c1b8c3c478340a40117ced4054b6b58f48cdb3c89b836bdfee1f5766))})", MISSING_PRIVKEYS | XONLY_KEYS, {{"5120d8ea39b29de2b550b68bd2ada8b075c888c2b2df3290c7a35856482747848934"}}, OutputType::BECH32M); + // Can have two Miniscripts in a Taproot with mixed private and public keys, and mixed ranged extended keys and raw keys. + Check("tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(v:pk(xpub6AGbgdKcAGeUWaGNKH2o3sRvjtvJCGZ1NwrHqMJDwD4bN1QuwPQSsdeAYkPZGPt2FTAyu6nWGsC3fN2nsBELrLPcRNuwwr5k1X7yW5WV4aX/*),pk(02daf6e3477fc3906a1997820ed2940c8f5fa0942946d0368f981b001fdd85afcb)),and_v(v:pk(xprv9wCN7tTqN5ATsmBGEijuNeUgQjma9tv3GmdWLmbYiuArPsAMj6tD1uASiBfm47kdoi7bDBAVxUZNLM2MkeouPK5menDTyCNZtExQrKhVu7C/*),pk(03272c0c1ae2c07528283b91ca57b45d2cc84e7960e1f17f58815372285f35e99a))})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(v:pk(xpub6AGbgdKcAGeUWaGNKH2o3sRvjtvJCGZ1NwrHqMJDwD4bN1QuwPQSsdeAYkPZGPt2FTAyu6nWGsC3fN2nsBELrLPcRNuwwr5k1X7yW5WV4aX/*),pk(02daf6e3477fc3906a1997820ed2940c8f5fa0942946d0368f981b001fdd85afcb)),and_v(v:pk(xpub6ABiXPzjCSim6FFjLkGujnRQxmc4ZMdtdzZ79A1AHEhqGfVWGeCTZhUvZTSf1mNnGUtyNqgfE9eWaYdYReDKbPYqgqi9LLVZSmWnLQRx477/*),pk(03272c0c1ae2c07528283b91ca57b45d2cc84e7960e1f17f58815372285f35e99a))})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(v:pk(xpub6AGbgdKcAGeUWaGNKH2o3sRvjtvJCGZ1NwrHqMJDwD4bN1QuwPQSsdeAYkPZGPt2FTAyu6nWGsC3fN2nsBELrLPcRNuwwr5k1X7yW5WV4aX/*),pk(02daf6e3477fc3906a1997820ed2940c8f5fa0942946d0368f981b001fdd85afcb)),and_v(v:pk(xpub6ABiXPzjCSim6FFjLkGujnRQxmc4ZMdtdzZ79A1AHEhqGfVWGeCTZhUvZTSf1mNnGUtyNqgfE9eWaYdYReDKbPYqgqi9LLVZSmWnLQRx477/*),pk(03272c0c1ae2c07528283b91ca57b45d2cc84e7960e1f17f58815372285f35e99a))})", MISSING_PRIVKEYS | XONLY_KEYS | RANGE | MIXED_PUBKEYS, {{"5120793185cd1a9a0bb710fa57df3845ac4ddf7df63b74beadce2573cbb0b508b3a4"}}, OutputType::BECH32M, /*op_desc_id=*/{}, {{}, {0}}); + // Can sign for a Miniscript expression containing a hash challenge inside a Taproot tree. (Fails without the + // preimages and the sequence, passes with.) + Check("tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),v:pk(KykUPmR5967F4URzMUeCv9kNMU9CNRWycrPmx3ZvfkWoQLabbimL)),older(42)),multi_a(2,adf586a32ad4b0674a86022b000348b681b4c97a811f67eefe4a6e066e55080c,KztMyyi1pXUtuZfJSB7JzVdmJMAz7wfGVFoSRUR5CVZxXxULXuGR)})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),v:pk(1c9bc926084382e76da33b5a52d17b1fa153c072aae5fb5228ecc2ccf89d79d5)),older(42)),multi_a(2,adf586a32ad4b0674a86022b000348b681b4c97a811f67eefe4a6e066e55080c,14fa4ad085cdee1e2fc73d491b36a96c192382b1d9a21108eb3533f630364f9f)})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),v:pk(1c9bc926084382e76da33b5a52d17b1fa153c072aae5fb5228ecc2ccf89d79d5)),older(42)),multi_a(2,adf586a32ad4b0674a86022b000348b681b4c97a811f67eefe4a6e066e55080c,14fa4ad085cdee1e2fc73d491b36a96c192382b1d9a21108eb3533f630364f9f)})", MISSING_PRIVKEYS | XONLY_KEYS | SIGNABLE | SIGNABLE_FAILS, {{"51209a3d79db56fbe3ba4d905d827b62e1ed31cd6df1198b8c759d589c0f4efc27bd"}}, OutputType::BECH32M); + Check("tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),v:pk(KykUPmR5967F4URzMUeCv9kNMU9CNRWycrPmx3ZvfkWoQLabbimL)),older(42)),multi_a(2,adf586a32ad4b0674a86022b000348b681b4c97a811f67eefe4a6e066e55080c,KztMyyi1pXUtuZfJSB7JzVdmJMAz7wfGVFoSRUR5CVZxXxULXuGR)})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),v:pk(1c9bc926084382e76da33b5a52d17b1fa153c072aae5fb5228ecc2ccf89d79d5)),older(42)),multi_a(2,adf586a32ad4b0674a86022b000348b681b4c97a811f67eefe4a6e066e55080c,14fa4ad085cdee1e2fc73d491b36a96c192382b1d9a21108eb3533f630364f9f)})", "tr(a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,{and_v(and_v(v:hash256(ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588),v:pk(1c9bc926084382e76da33b5a52d17b1fa153c072aae5fb5228ecc2ccf89d79d5)),older(42)),multi_a(2,adf586a32ad4b0674a86022b000348b681b4c97a811f67eefe4a6e066e55080c,14fa4ad085cdee1e2fc73d491b36a96c192382b1d9a21108eb3533f630364f9f)})", MISSING_PRIVKEYS | XONLY_KEYS | SIGNABLE, {{"51209a3d79db56fbe3ba4d905d827b62e1ed31cd6df1198b8c759d589c0f4efc27bd"}}, OutputType::BECH32M, /*op_desc_id=*/{}, {{}}, /*spender_nlocktime=*/0, /*spender_nsequence=*/42, /*preimages=*/{{ParseHex("ae253ca2a54debcac7ecf414f6734f48c56421a08bb59182ff9f39a6fffdb588"), ParseHex("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")}}); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/fuzz/miniscript.cpp b/src/test/fuzz/miniscript.cpp index 0246507da1..835b6d8c6b 100644 --- a/src/test/fuzz/miniscript.cpp +++ b/src/test/fuzz/miniscript.cpp @@ -7,6 +7,7 @@ #include <key.h> #include <script/miniscript.h> #include <script/script.h> +#include <script/signingprovider.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> #include <test/fuzz/util.h> @@ -14,6 +15,15 @@ namespace { +using Fragment = miniscript::Fragment; +using NodeRef = miniscript::NodeRef<CPubKey>; +using Node = miniscript::Node<CPubKey>; +using Type = miniscript::Type; +using MsCtx = miniscript::MiniscriptContext; +// https://github.com/llvm/llvm-project/issues/53444 +// NOLINTNEXTLINE(misc-unused-using-decls) +using miniscript::operator"" _mst; + //! Some pre-computed data for more efficient string roundtrips and to simulate challenges. struct TestData { typedef CPubKey Key; @@ -23,6 +33,7 @@ struct TestData { std::map<Key, int> dummy_key_idx_map; std::map<CKeyID, Key> dummy_keys_map; std::map<Key, std::pair<std::vector<unsigned char>, bool>> dummy_sigs; + std::map<XOnlyPubKey, std::pair<std::vector<unsigned char>, bool>> schnorr_sigs; // Precomputed hashes of each kind. std::vector<std::vector<unsigned char>> sha256; @@ -37,6 +48,11 @@ struct TestData { //! Set the precomputed data. void Init() { unsigned char keydata[32] = {1}; + // All our signatures sign (and are required to sign) this constant message. + auto const MESSAGE_HASH{uint256S("f5cd94e18b6fe77dd7aca9e35c2b0c9cbd86356c80a71065")}; + // We don't pass additional randomness when creating a schnorr signature. + auto const EMPTY_AUX{uint256S("")}; + for (size_t i = 0; i < 256; i++) { keydata[31] = i; CKey privkey; @@ -46,11 +62,17 @@ struct TestData { dummy_keys.push_back(pubkey); dummy_key_idx_map.emplace(pubkey, i); dummy_keys_map.insert({pubkey.GetID(), pubkey}); + XOnlyPubKey xonly_pubkey{pubkey}; + dummy_key_idx_map.emplace(xonly_pubkey, i); + uint160 xonly_hash{Hash160(xonly_pubkey)}; + dummy_keys_map.emplace(xonly_hash, pubkey); - std::vector<unsigned char> sig; - privkey.Sign(uint256S(""), sig); + std::vector<unsigned char> sig, schnorr_sig(64); + privkey.Sign(MESSAGE_HASH, sig); sig.push_back(1); // SIGHASH_ALL dummy_sigs.insert({pubkey, {sig, i & 1}}); + assert(privkey.SignSchnorr(MESSAGE_HASH, schnorr_sig, nullptr, EMPTY_AUX)); + schnorr_sigs.emplace(XOnlyPubKey{pubkey}, std::make_pair(std::move(schnorr_sig), i & 1)); std::vector<unsigned char> hash; hash.resize(32); @@ -70,6 +92,19 @@ struct TestData { if (i & 1) hash160_preimages[hash] = std::vector<unsigned char>(keydata, keydata + 32); } } + + //! Get the (Schnorr or ECDSA, depending on context) signature for this pubkey. + const std::pair<std::vector<unsigned char>, bool>* GetSig(const MsCtx script_ctx, const Key& key) const { + if (!miniscript::IsTapscript(script_ctx)) { + const auto it = dummy_sigs.find(key); + if (it == dummy_sigs.end()) return nullptr; + return &it->second; + } else { + const auto it = schnorr_sigs.find(XOnlyPubKey{key}); + if (it == schnorr_sigs.end()) return nullptr; + return &it->second; + } + } } TEST_DATA; /** @@ -80,6 +115,8 @@ struct TestData { struct ParserContext { typedef CPubKey Key; + MsCtx script_ctx{MsCtx::P2WSH}; + bool KeyCompare(const Key& a, const Key& b) const { return a < b; } @@ -92,14 +129,20 @@ struct ParserContext { return HexStr(Span{&idx, 1}); } - std::vector<unsigned char> ToPKBytes(const Key& key) const - { - return {key.begin(), key.end()}; + std::vector<unsigned char> ToPKBytes(const Key& key) const { + if (!miniscript::IsTapscript(script_ctx)) { + return {key.begin(), key.end()}; + } + const XOnlyPubKey xonly_pubkey{key}; + return {xonly_pubkey.begin(), xonly_pubkey.end()}; } - std::vector<unsigned char> ToPKHBytes(const Key& key) const - { - const auto h = Hash160(key); + std::vector<unsigned char> ToPKHBytes(const Key& key) const { + if (!miniscript::IsTapscript(script_ctx)) { + const auto h = Hash160(key); + return {h.begin(), h.end()}; + } + const auto h = Hash160(XOnlyPubKey{key}); return {h.begin(), h.end()}; } @@ -113,10 +156,15 @@ struct ParserContext { template<typename I> std::optional<Key> FromPKBytes(I first, I last) const { - CPubKey key; - key.Set(first, last); - if (!key.IsValid()) return {}; - return key; + if (!miniscript::IsTapscript(script_ctx)) { + Key key{first, last}; + if (key.IsValid()) return key; + return {}; + } + if (last - first != 32) return {}; + XOnlyPubKey xonly_pubkey; + std::copy(first, last, xonly_pubkey.begin()); + return xonly_pubkey.GetEvenCorrespondingCPubKey(); } template<typename I> @@ -128,10 +176,16 @@ struct ParserContext { if (it == TEST_DATA.dummy_keys_map.end()) return {}; return it->second; } + + MsCtx MsContext() const { + return script_ctx; + } } PARSER_CTX; //! Context that implements naive conversion from/to script only, for roundtrip testing. struct ScriptParserContext { + MsCtx script_ctx{MsCtx::P2WSH}; + //! For Script roundtrip we never need the key from a key hash. struct Key { bool is_hash; @@ -172,6 +226,10 @@ struct ScriptParserContext { key.is_hash = true; return key; } + + MsCtx MsContext() const { + return script_ctx; + } } SCRIPT_PARSER_CONTEXT; //! Context to produce a satisfaction for a Miniscript node using the pre-computed data. @@ -183,15 +241,11 @@ struct SatisfierContext: ParserContext { // Signature challenges fulfilled with a dummy signature, if it was one of our dummy keys. miniscript::Availability Sign(const CPubKey& key, std::vector<unsigned char>& sig) const { - const auto it = TEST_DATA.dummy_sigs.find(key); - if (it == TEST_DATA.dummy_sigs.end()) return miniscript::Availability::NO; - if (it->second.second) { - // Key is "available" - sig = it->second.first; - return miniscript::Availability::YES; - } else { - return miniscript::Availability::NO; + bool sig_available{false}; + if (auto res = TEST_DATA.GetSig(script_ctx, key)) { + std::tie(sig, sig_available) = *res; } + return sig_available ? miniscript::Availability::YES : miniscript::Availability::NO; } //! Lookup generalization for all the hash satisfactions below @@ -230,6 +284,13 @@ struct CheckerContext: BaseSignatureChecker { if (it == TEST_DATA.dummy_sigs.end()) return false; return it->second.first == sig; } + bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion, + ScriptExecutionData&, ScriptError*) const override { + XOnlyPubKey pk{pubkey}; + auto it = TEST_DATA.schnorr_sigs.find(pk); + if (it == TEST_DATA.schnorr_sigs.end()) return false; + return it->second.first == sig; + } bool CheckLockTime(const CScriptNum& nLockTime) const override { return nLockTime.GetInt64() & 1; } bool CheckSequence(const CScriptNum& nSequence) const override { return nSequence.GetInt64() & 1; } } CHECKER_CTX; @@ -244,11 +305,8 @@ struct KeyComparator { // A dummy scriptsig to pass to VerifyScript (we always use Segwit v0). const CScript DUMMY_SCRIPTSIG; -using Fragment = miniscript::Fragment; -using NodeRef = miniscript::NodeRef<CPubKey>; -using Node = miniscript::Node<CPubKey>; -using Type = miniscript::Type; -using miniscript::operator"" _mst; +//! Public key to be used as internal key for dummy Taproot spends. +const std::vector<unsigned char> NUMS_PK{ParseHex("50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0")}; //! Construct a miniscript node as a shared_ptr. template<typename... Args> NodeRef MakeNodeRef(Args&&... args) { @@ -321,9 +379,10 @@ std::optional<uint32_t> ConsumeTimeLock(FuzzedDataProvider& provider) { * - For pk_k(), pk_h(), and all hashes, the next byte defines the index of the value in the test data. * - For multi(), the next 2 bytes define respectively the threshold and the number of keys. Then as many * bytes as the number of keys define the index of each key in the test data. + * - For multi_a(), same as for multi() but the threshold and the keys count are encoded on two bytes. * - For thresh(), the next byte defines the threshold value and the following one the number of subs. */ -std::optional<NodeInfo> ConsumeNodeStable(FuzzedDataProvider& provider, Type type_needed) { +std::optional<NodeInfo> ConsumeNodeStable(MsCtx script_ctx, FuzzedDataProvider& provider, Type type_needed) { bool allow_B = (type_needed == ""_mst) || (type_needed << "B"_mst); bool allow_K = (type_needed == ""_mst) || (type_needed << "K"_mst); bool allow_V = (type_needed == ""_mst) || (type_needed << "V"_mst); @@ -367,7 +426,7 @@ std::optional<NodeInfo> ConsumeNodeStable(FuzzedDataProvider& provider, Type typ if (!allow_B) return {}; return {{Fragment::HASH160, ConsumeHash160(provider)}}; case 10: { - if (!allow_B) return {}; + if (!allow_B || IsTapscript(script_ctx)) return {}; const auto k = provider.ConsumeIntegral<uint8_t>(); const auto n_keys = provider.ConsumeIntegral<uint8_t>(); if (n_keys > 20 || k == 0 || k > n_keys) return {}; @@ -428,6 +487,15 @@ std::optional<NodeInfo> ConsumeNodeStable(FuzzedDataProvider& provider, Type typ case 26: if (!allow_B) return {}; return {{{"B"_mst}, Fragment::WRAP_N}}; + case 27: { + if (!allow_B || !IsTapscript(script_ctx)) return {}; + const auto k = provider.ConsumeIntegral<uint16_t>(); + const auto n_keys = provider.ConsumeIntegral<uint16_t>(); + if (n_keys > 999 || k == 0 || k > n_keys) return {}; + std::vector<CPubKey> keys{n_keys}; + for (auto& key: keys) key = ConsumePubKey(provider); + return {{Fragment::MULTI_A, k, std::move(keys)}}; + } default: break; } @@ -444,10 +512,16 @@ std::optional<NodeInfo> ConsumeNodeStable(FuzzedDataProvider& provider, Type typ struct SmartInfo { using recipe = std::pair<Fragment, std::vector<Type>>; - std::map<Type, std::vector<recipe>> table; + std::map<Type, std::vector<recipe>> wsh_table, tap_table; void Init() { + Init(wsh_table, MsCtx::P2WSH); + Init(tap_table, MsCtx::TAPSCRIPT); + } + + void Init(std::map<Type, std::vector<recipe>>& table, MsCtx script_ctx) + { /* Construct a set of interesting type requirements to reason with (sections of BKVWzondu). */ std::vector<Type> types; for (int base = 0; base < 4; ++base) { /* select from B,K,V,W */ @@ -495,7 +569,7 @@ struct SmartInfo std::sort(types.begin(), types.end()); // Iterate over all possible fragments. - for (int fragidx = 0; fragidx <= int(Fragment::MULTI); ++fragidx) { + for (int fragidx = 0; fragidx <= int(Fragment::MULTI_A); ++fragidx) { int sub_count = 0; //!< The minimum number of child nodes this recipe has. int sub_range = 1; //!< The maximum number of child nodes for this recipe is sub_count+sub_range-1. size_t data_size = 0; @@ -503,6 +577,12 @@ struct SmartInfo uint32_t k = 0; Fragment frag{fragidx}; + // Only produce recipes valid in the given context. + if ((!miniscript::IsTapscript(script_ctx) && frag == Fragment::MULTI_A) + || (miniscript::IsTapscript(script_ctx) && frag == Fragment::MULTI)) { + continue; + } + // Based on the fragment, determine #subs/data/k/keys to pass to ComputeType. */ switch (frag) { case Fragment::PK_K: @@ -510,6 +590,7 @@ struct SmartInfo n_keys = 1; break; case Fragment::MULTI: + case Fragment::MULTI_A: n_keys = 1; k = 1; break; @@ -568,7 +649,7 @@ struct SmartInfo if (subs > 0) subt.push_back(x); if (subs > 1) subt.push_back(y); if (subs > 2) subt.push_back(z); - Type res = miniscript::internal::ComputeType(frag, x, y, z, subt, k, data_size, subs, n_keys); + Type res = miniscript::internal::ComputeType(frag, x, y, z, subt, k, data_size, subs, n_keys, script_ctx); // Continue if the result is not a valid node. if ((res << "K"_mst) + (res << "V"_mst) + (res << "B"_mst) + (res << "W"_mst) != 1) continue; @@ -685,10 +766,11 @@ struct SmartInfo * (as improvements to the tables or changes to the typing rules could invalidate * everything). */ -std::optional<NodeInfo> ConsumeNodeSmart(FuzzedDataProvider& provider, Type type_needed) { +std::optional<NodeInfo> ConsumeNodeSmart(MsCtx script_ctx, FuzzedDataProvider& provider, Type type_needed) { /** Table entry for the requested type. */ - auto recipes_it = SMARTINFO.table.find(type_needed); - assert(recipes_it != SMARTINFO.table.end()); + const auto& table{IsTapscript(script_ctx) ? SMARTINFO.tap_table : SMARTINFO.wsh_table}; + auto recipes_it = table.find(type_needed); + assert(recipes_it != table.end()); /** Pick one recipe from the available ones for that type. */ const auto& [frag, subt] = PickValue(provider, recipes_it->second); @@ -704,6 +786,13 @@ std::optional<NodeInfo> ConsumeNodeSmart(FuzzedDataProvider& provider, Type type for (auto& key: keys) key = ConsumePubKey(provider); return {{frag, k, std::move(keys)}}; } + case Fragment::MULTI_A: { + const auto n_keys = provider.ConsumeIntegralInRange<uint16_t>(1, 999); + const auto k = provider.ConsumeIntegralInRange<uint16_t>(1, n_keys); + std::vector<CPubKey> keys{n_keys}; + for (auto& key: keys) key = ConsumePubKey(provider); + return {{frag, k, std::move(keys)}}; + } case Fragment::OLDER: case Fragment::AFTER: return {{frag, provider.ConsumeIntegralInRange<uint32_t>(1, 0x7FFFFFF)}}; @@ -760,7 +849,7 @@ std::optional<NodeInfo> ConsumeNodeSmart(FuzzedDataProvider& provider, Type type * a NodeRef whose Type() matches the type fed to ConsumeNode. */ template<typename F> -NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) { +NodeRef GenNode(MsCtx script_ctx, F ConsumeNode, Type root_type, bool strict_valid = false) { /** A stack of miniscript Nodes being built up. */ std::vector<NodeRef> stack; /** The queue of instructions. */ @@ -781,7 +870,8 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) { // Update predicted resource limits. Since every leaf Miniscript node is at least one // byte long, we move one byte from each child to their parent. A similar technique is // used in the miniscript::internal::Parse function to prevent runaway string parsing. - scriptsize += miniscript::internal::ComputeScriptLen(node_info->fragment, ""_mst, node_info->subtypes.size(), node_info->k, node_info->subtypes.size(), node_info->keys.size()) - 1; + scriptsize += miniscript::internal::ComputeScriptLen(node_info->fragment, ""_mst, node_info->subtypes.size(), node_info->k, node_info->subtypes.size(), + node_info->keys.size(), script_ctx) - 1; if (scriptsize > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {}; switch (node_info->fragment) { case Fragment::JUST_0: @@ -826,6 +916,9 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) { case Fragment::MULTI: ops += 1; break; + case Fragment::MULTI_A: + ops += node_info->keys.size() + 1; + break; case Fragment::WRAP_A: ops += 2; break; @@ -874,11 +967,11 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) { // Construct new NodeRef. NodeRef node; if (info.keys.empty()) { - node = MakeNodeRef(info.fragment, std::move(sub), std::move(info.hash), info.k); + node = MakeNodeRef(script_ctx, info.fragment, std::move(sub), std::move(info.hash), info.k); } else { assert(sub.empty()); assert(info.hash.empty()); - node = MakeNodeRef(info.fragment, std::move(info.keys), info.k); + node = MakeNodeRef(script_ctx, info.fragment, std::move(info.keys), info.k); } // Verify acceptability. if (!node || (node->GetType() & "KVWB"_mst) == ""_mst) { @@ -894,8 +987,10 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) { ops += 1; scriptsize += 1; } - if (ops > MAX_OPS_PER_SCRIPT) return {}; - if (scriptsize > MAX_STANDARD_P2WSH_SCRIPT_SIZE) return {}; + if (!miniscript::IsTapscript(script_ctx) && ops > MAX_OPS_PER_SCRIPT) return {}; + if (scriptsize > miniscript::internal::MaxScriptSize(script_ctx)) { + return {}; + } // Move it to the stack. stack.push_back(std::move(node)); todo.pop_back(); @@ -908,12 +1003,33 @@ NodeRef GenNode(F ConsumeNode, Type root_type, bool strict_valid = false) { return std::move(stack[0]); } +//! The spk for this script under the given context. If it's a Taproot output also record the spend data. +CScript ScriptPubKey(MsCtx ctx, const CScript& script, TaprootBuilder& builder) +{ + if (!miniscript::IsTapscript(ctx)) return CScript() << OP_0 << WitnessV0ScriptHash(script); + + // For Taproot outputs we always use a tree with a single script and a dummy internal key. + builder.Add(0, script, TAPROOT_LEAF_TAPSCRIPT); + builder.Finalize(XOnlyPubKey{NUMS_PK}); + return GetScriptForDestination(builder.GetOutput()); +} + +//! Fill the witness with the data additional to the script satisfaction. +void SatisfactionToWitness(MsCtx ctx, CScriptWitness& witness, const CScript& script, TaprootBuilder& builder) { + // For P2WSH, it's only the witness script. + witness.stack.push_back(std::vector<unsigned char>(script.begin(), script.end())); + if (!miniscript::IsTapscript(ctx)) return; + // For Tapscript we also need the control block. + witness.stack.push_back(*builder.GetSpendData().scripts.begin()->second.begin()); +} + /** Perform various applicable tests on a miniscript Node. */ -void TestNode(const NodeRef& node, FuzzedDataProvider& provider) +void TestNode(const MsCtx script_ctx, const NodeRef& node, FuzzedDataProvider& provider) { if (!node) return; // Check that it roundtrips to text representation + PARSER_CTX.script_ctx = script_ctx; std::optional<std::string> str{node->ToString(PARSER_CTX)}; assert(str); auto parsed = miniscript::FromString(*str, PARSER_CTX); @@ -928,7 +1044,7 @@ void TestNode(const NodeRef& node, FuzzedDataProvider& provider) // with a push of a key, which could match these opcodes). if (!(node->GetType() << "K"_mst)) { bool ends_in_verify = !(node->GetType() << "x"_mst); - assert(ends_in_verify == (script.back() == OP_CHECKSIG || script.back() == OP_CHECKMULTISIG || script.back() == OP_EQUAL)); + assert(ends_in_verify == (script.back() == OP_CHECKSIG || script.back() == OP_CHECKMULTISIG || script.back() == OP_EQUAL || script.back() == OP_NUMEQUAL)); } // The rest of the checks only apply when testing a valid top-level script. @@ -943,9 +1059,11 @@ void TestNode(const NodeRef& node, FuzzedDataProvider& provider) assert(decoded->ToScript(PARSER_CTX) == script); assert(decoded->GetType() == node->GetType()); - const auto node_ops{node->GetOps()}; - if (provider.ConsumeBool() && node_ops && *node_ops < MAX_OPS_PER_SCRIPT && node->ScriptSize() < MAX_STANDARD_P2WSH_SCRIPT_SIZE) { - // Optionally pad the script with OP_NOPs to max op the ops limit of the constructed script. + // Optionally pad the script or the witness in order to increase the sensitivity of the tests of + // the resources limits logic. + CScriptWitness witness_mal, witness_nonmal; + if (provider.ConsumeBool()) { + // Under P2WSH, optionally pad the script with OP_NOPs to max op the ops limit of the constructed script. // This makes the script obviously not actually miniscript-compatible anymore, but the // signatures constructed in this test don't commit to the script anyway, so the same // miniscript satisfier will work. This increases the sensitivity of the test to the ops @@ -954,31 +1072,54 @@ void TestNode(const NodeRef& node, FuzzedDataProvider& provider) // maximal. // Do not pad more than what would cause MAX_STANDARD_P2WSH_SCRIPT_SIZE to be reached, however, // as that also invalidates scripts. - int add = std::min<int>( - MAX_OPS_PER_SCRIPT - *node_ops, - MAX_STANDARD_P2WSH_SCRIPT_SIZE - node->ScriptSize()); - for (int i = 0; i < add; ++i) script.push_back(OP_NOP); + const auto node_ops{node->GetOps()}; + if (!IsTapscript(script_ctx) && node_ops && *node_ops < MAX_OPS_PER_SCRIPT + && node->ScriptSize() < MAX_STANDARD_P2WSH_SCRIPT_SIZE) { + int add = std::min<int>( + MAX_OPS_PER_SCRIPT - *node_ops, + MAX_STANDARD_P2WSH_SCRIPT_SIZE - node->ScriptSize()); + for (int i = 0; i < add; ++i) script.push_back(OP_NOP); + } + + // Under Tapscript, optionally pad the stack up to the limit minus the calculated maximum execution stack + // size to assert a Miniscript would never add more elements to the stack during execution than anticipated. + const auto node_exec_ss{node->GetExecStackSize()}; + if (miniscript::IsTapscript(script_ctx) && node_exec_ss && *node_exec_ss < MAX_STACK_SIZE) { + unsigned add{(unsigned)MAX_STACK_SIZE - *node_exec_ss}; + witness_mal.stack.resize(add); + witness_nonmal.stack.resize(add); + script.reserve(add); + for (unsigned i = 0; i < add; ++i) script.push_back(OP_NIP); + } } + SATISFIER_CTX.script_ctx = script_ctx; + + // Get the ScriptPubKey for this script, filling spend data if it's Taproot. + TaprootBuilder builder; + const CScript script_pubkey{ScriptPubKey(script_ctx, script, builder)}; + // Run malleable satisfaction algorithm. - const CScript script_pubkey = CScript() << OP_0 << WitnessV0ScriptHash(script); - CScriptWitness witness_mal; - const bool mal_success = node->Satisfy(SATISFIER_CTX, witness_mal.stack, false) == miniscript::Availability::YES; - witness_mal.stack.push_back(std::vector<unsigned char>(script.begin(), script.end())); + std::vector<std::vector<unsigned char>> stack_mal; + const bool mal_success = node->Satisfy(SATISFIER_CTX, stack_mal, false) == miniscript::Availability::YES; // Run non-malleable satisfaction algorithm. - CScriptWitness witness_nonmal; - const bool nonmal_success = node->Satisfy(SATISFIER_CTX, witness_nonmal.stack, true) == miniscript::Availability::YES; - witness_nonmal.stack.push_back(std::vector<unsigned char>(script.begin(), script.end())); + std::vector<std::vector<unsigned char>> stack_nonmal; + const bool nonmal_success = node->Satisfy(SATISFIER_CTX, stack_nonmal, true) == miniscript::Availability::YES; if (nonmal_success) { - // Non-malleable satisfactions are bounded by GetStackSize(). - assert(witness_nonmal.stack.size() <= *node->GetStackSize() + 1); + // Non-malleable satisfactions are bounded by the satisfaction size plus: + // - For P2WSH spends, the witness script + // - For Tapscript spends, both the witness script and the control block + const size_t max_stack_size{*node->GetStackSize() + 1 + miniscript::IsTapscript(script_ctx)}; + assert(stack_nonmal.size() <= max_stack_size); // If a non-malleable satisfaction exists, the malleable one must also exist, and be identical to it. assert(mal_success); - assert(witness_nonmal.stack == witness_mal.stack); + assert(stack_nonmal == stack_mal); // Test non-malleable satisfaction. + witness_nonmal.stack.insert(witness_nonmal.stack.end(), std::make_move_iterator(stack_nonmal.begin()), std::make_move_iterator(stack_nonmal.end())); + SatisfactionToWitness(script_ctx, witness_nonmal, script, builder); ScriptError serror; bool res = VerifyScript(DUMMY_SCRIPTSIG, script_pubkey, &witness_nonmal, STANDARD_SCRIPT_VERIFY_FLAGS, CHECKER_CTX, &serror); // Non-malleable satisfactions are guaranteed to be valid if ValidSatisfactions(). @@ -992,6 +1133,8 @@ void TestNode(const NodeRef& node, FuzzedDataProvider& provider) if (mal_success && (!nonmal_success || witness_mal.stack != witness_nonmal.stack)) { // Test malleable satisfaction only if it's different from the non-malleable one. + witness_mal.stack.insert(witness_mal.stack.end(), std::make_move_iterator(stack_mal.begin()), std::make_move_iterator(stack_mal.end())); + SatisfactionToWitness(script_ctx, witness_mal, script, builder); ScriptError serror; bool res = VerifyScript(DUMMY_SCRIPTSIG, script_pubkey, &witness_mal, STANDARD_SCRIPT_VERIFY_FLAGS, CHECKER_CTX, &serror); // Malleable satisfactions are not guaranteed to be valid under any conditions, but they can only @@ -1008,21 +1151,20 @@ void TestNode(const NodeRef& node, FuzzedDataProvider& provider) // algorithm succeeds. Given that under IsSane() both satisfactions // are identical, this implies that for such nodes, the non-malleable // satisfaction will also match the expected policy. - bool satisfiable = node->IsSatisfiable([](const Node& node) -> bool { + const auto is_key_satisfiable = [script_ctx](const CPubKey& pubkey) -> bool { + auto sig_ptr{TEST_DATA.GetSig(script_ctx, pubkey)}; + return sig_ptr != nullptr && sig_ptr->second; + }; + bool satisfiable = node->IsSatisfiable([&](const Node& node) -> bool { switch (node.fragment) { case Fragment::PK_K: - case Fragment::PK_H: { - auto it = TEST_DATA.dummy_sigs.find(node.keys[0]); - assert(it != TEST_DATA.dummy_sigs.end()); - return it->second.second; - } - case Fragment::MULTI: { - size_t sats = 0; - for (const auto& key : node.keys) { - auto it = TEST_DATA.dummy_sigs.find(key); - assert(it != TEST_DATA.dummy_sigs.end()); - sats += it->second.second; - } + case Fragment::PK_H: + return is_key_satisfiable(node.keys[0]); + case Fragment::MULTI: + case Fragment::MULTI_A: { + size_t sats = std::count_if(node.keys.begin(), node.keys.end(), [&](const auto& key) { + return size_t(is_key_satisfiable(key)); + }); return sats >= node.k; } case Fragment::OLDER: @@ -1061,10 +1203,13 @@ void FuzzInitSmart() /** Fuzz target that runs TestNode on nodes generated using ConsumeNodeStable. */ FUZZ_TARGET(miniscript_stable, .init = FuzzInit) { - FuzzedDataProvider provider(buffer.data(), buffer.size()); - TestNode(GenNode([&](Type needed_type) { - return ConsumeNodeStable(provider, needed_type); - }, ""_mst), provider); + // Run it under both P2WSH and Tapscript contexts. + for (const auto script_ctx: {MsCtx::P2WSH, MsCtx::TAPSCRIPT}) { + FuzzedDataProvider provider(buffer.data(), buffer.size()); + TestNode(script_ctx, GenNode(script_ctx, [&](Type needed_type) { + return ConsumeNodeStable(script_ctx, provider, needed_type); + }, ""_mst), provider); + } } /** Fuzz target that runs TestNode on nodes generated using ConsumeNodeSmart. */ @@ -1074,16 +1219,19 @@ FUZZ_TARGET(miniscript_smart, .init = FuzzInitSmart) static constexpr std::array<Type, 4> BASE_TYPES{"B"_mst, "V"_mst, "K"_mst, "W"_mst}; FuzzedDataProvider provider(buffer.data(), buffer.size()); - TestNode(GenNode([&](Type needed_type) { - return ConsumeNodeSmart(provider, needed_type); + const auto script_ctx{(MsCtx)provider.ConsumeBool()}; + TestNode(script_ctx, GenNode(script_ctx, [&](Type needed_type) { + return ConsumeNodeSmart(script_ctx, provider, needed_type); }, PickValue(provider, BASE_TYPES), true), provider); } /* Fuzz tests that test parsing from a string, and roundtripping via string. */ FUZZ_TARGET(miniscript_string, .init = FuzzInit) { + if (buffer.empty()) return; FuzzedDataProvider provider(buffer.data(), buffer.size()); - auto str = provider.ConsumeRemainingBytesAsString(); + auto str = provider.ConsumeBytesAsString(provider.remaining_bytes() - 1); + PARSER_CTX.script_ctx = (MsCtx)provider.ConsumeBool(); auto parsed = miniscript::FromString(str, PARSER_CTX); if (!parsed) return; @@ -1101,6 +1249,7 @@ FUZZ_TARGET(miniscript_script) const std::optional<CScript> script = ConsumeDeserializable<CScript>(fuzzed_data_provider); if (!script) return; + SCRIPT_PARSER_CONTEXT.script_ctx = (MsCtx)fuzzed_data_provider.ConsumeBool(); const auto ms = miniscript::FromScript(*script, SCRIPT_PARSER_CONTEXT); if (!ms) return; diff --git a/src/test/miniscript_tests.cpp b/src/test/miniscript_tests.cpp index b69317c4d9..c8a39076e2 100644 --- a/src/test/miniscript_tests.cpp +++ b/src/test/miniscript_tests.cpp @@ -20,6 +20,7 @@ #include <script/interpreter.h> #include <script/miniscript.h> #include <script/script_error.h> +#include <script/signingprovider.h> namespace { @@ -30,7 +31,9 @@ struct TestData { //! A map from the public keys to their CKeyIDs (faster than hashing every time). std::map<CPubKey, CKeyID> pkhashes; std::map<CKeyID, CPubKey> pkmap; + std::map<XOnlyPubKey, CKeyID> xonly_pkhashes; std::map<CPubKey, std::vector<unsigned char>> signatures; + std::map<XOnlyPubKey, std::vector<unsigned char>> schnorr_signatures; // Various precomputed hashes std::vector<std::vector<unsigned char>> sha256; @@ -46,6 +49,9 @@ struct TestData { { // All our signatures sign (and are required to sign) this constant message. auto const MESSAGE_HASH = uint256S("f5cd94e18b6fe77dd7aca9e35c2b0c9cbd86356c80a71065"); + // We don't pass additional randomness when creating a schnorr signature. + auto const EMPTY_AUX{uint256S("")}; + // We generate 255 public keys and 255 hashes of each type. for (int i = 1; i <= 255; ++i) { // This 32-byte array functions as both private key data and hash preimage (31 zero bytes plus any nonzero byte). @@ -60,12 +66,18 @@ struct TestData { pubkeys.push_back(pubkey); pkhashes.emplace(pubkey, keyid); pkmap.emplace(keyid, pubkey); + XOnlyPubKey xonly_pubkey{pubkey}; + uint160 xonly_hash{Hash160(xonly_pubkey)}; + xonly_pkhashes.emplace(xonly_pubkey, xonly_hash); + pkmap.emplace(xonly_hash, pubkey); // Compute ECDSA signatures on MESSAGE_HASH with the private keys. - std::vector<unsigned char> sig; + std::vector<unsigned char> sig, schnorr_sig(64); BOOST_CHECK(key.Sign(MESSAGE_HASH, sig)); sig.push_back(1); // sighash byte signatures.emplace(pubkey, sig); + BOOST_CHECK(key.SignSchnorr(MESSAGE_HASH, schnorr_sig, nullptr, EMPTY_AUX)); + schnorr_signatures.emplace(XOnlyPubKey{pubkey}, schnorr_sig); // Compute various hashes std::vector<unsigned char> hash; @@ -114,19 +126,30 @@ typedef std::pair<ChallengeType, uint32_t> Challenge; struct KeyConverter { typedef CPubKey Key; + miniscript::MiniscriptContext m_script_ctx{miniscript::MiniscriptContext::P2WSH}; + bool KeyCompare(const Key& a, const Key& b) const { return a < b; } //! Convert a public key to bytes. - std::vector<unsigned char> ToPKBytes(const CPubKey& key) const { return {key.begin(), key.end()}; } + std::vector<unsigned char> ToPKBytes(const CPubKey& key) const { + if (!miniscript::IsTapscript(m_script_ctx)) { + return {key.begin(), key.end()}; + } + const XOnlyPubKey xonly_pubkey{key}; + return {xonly_pubkey.begin(), xonly_pubkey.end()}; + } //! Convert a public key to its Hash160 bytes (precomputed). - std::vector<unsigned char> ToPKHBytes(const CPubKey& key) const - { - auto it = g_testdata->pkhashes.find(key); - assert(it != g_testdata->pkhashes.end()); - return {it->second.begin(), it->second.end()}; + std::vector<unsigned char> ToPKHBytes(const CPubKey& key) const { + if (!miniscript::IsTapscript(m_script_ctx)) { + auto hash = g_testdata->pkhashes.at(key); + return {hash.begin(), hash.end()}; + } + const XOnlyPubKey xonly_key{key}; + auto hash = g_testdata->xonly_pkhashes.at(xonly_key); + return {hash.begin(), hash.end()}; } //! Parse a public key from a range of hex characters. @@ -140,9 +163,15 @@ struct KeyConverter { template<typename I> std::optional<Key> FromPKBytes(I first, I last) const { - Key key{first, last}; - if (key.IsValid()) return key; - return {}; + if (!miniscript::IsTapscript(m_script_ctx)) { + Key key{first, last}; + if (key.IsValid()) return key; + return {}; + } + if (last - first != 32) return {}; + XOnlyPubKey xonly_pubkey; + std::copy(first, last, xonly_pubkey.begin()); + return xonly_pubkey.GetEvenCorrespondingCPubKey(); } template<typename I> @@ -150,14 +179,20 @@ struct KeyConverter { assert(last - first == 20); CKeyID keyid; std::copy(first, last, keyid.begin()); - auto it = g_testdata->pkmap.find(keyid); - assert(it != g_testdata->pkmap.end()); - return it->second; + return g_testdata->pkmap.at(keyid); } std::optional<std::string> ToString(const Key& key) const { return HexStr(ToPKBytes(key)); } + + miniscript::MiniscriptContext MsContext() const { + return m_script_ctx; + } + + void SetContext(miniscript::MiniscriptContext ctx) { + m_script_ctx = ctx; + } }; /** A class that encapsulates all signing/hash revealing operations. */ @@ -178,9 +213,15 @@ struct Satisfier : public KeyConverter { //! Produce a signature for the given key. miniscript::Availability Sign(const CPubKey& key, std::vector<unsigned char>& sig) const { if (supported.count(Challenge(ChallengeType::PK, ChallengeNumber(key)))) { - auto it = g_testdata->signatures.find(key); - if (it == g_testdata->signatures.end()) return miniscript::Availability::NO; - sig = it->second; + if (!miniscript::IsTapscript(m_script_ctx)) { + auto it = g_testdata->signatures.find(key); + if (it == g_testdata->signatures.end()) return miniscript::Availability::NO; + sig = it->second; + } else { + auto it = g_testdata->schnorr_signatures.find(XOnlyPubKey{key}); + if (it == g_testdata->schnorr_signatures.end()) return miniscript::Availability::NO; + sig = it->second; + } return miniscript::Availability::YES; } return miniscript::Availability::NO; @@ -226,6 +267,14 @@ public: return sig == it->second; } + bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion, + ScriptExecutionData&, ScriptError*) const override { + XOnlyPubKey pk{pubkey}; + auto it = g_testdata->schnorr_signatures.find(pk); + if (it == g_testdata->schnorr_signatures.end()) return false; + return sig == it->second; + } + bool CheckLockTime(const CScriptNum& locktime) const override { // Delegate to Satisfier. return ctx.CheckAfter(locktime.GetInt64()); @@ -238,7 +287,10 @@ public: }; //! Singleton instance of KeyConverter. -const KeyConverter CONVERTER{}; +KeyConverter CONVERTER; + +//! Public key to be used as internal key for dummy Taproot spends. +const std::vector<unsigned char> NUMS_PK{ParseHex("50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0")}; using Fragment = miniscript::Fragment; using NodeRef = miniscript::NodeRef<CPubKey>; @@ -271,34 +323,61 @@ std::set<Challenge> FindChallenges(const NodeRef& ref) { return chal; } +//! The spk for this script under the given context. If it's a Taproot output also record the spend data. +CScript ScriptPubKey(miniscript::MiniscriptContext ctx, const CScript& script, TaprootBuilder& builder) +{ + if (!miniscript::IsTapscript(ctx)) return CScript() << OP_0 << WitnessV0ScriptHash(script); + + // For Taproot outputs we always use a tree with a single script and a dummy internal key. + builder.Add(0, script, TAPROOT_LEAF_TAPSCRIPT); + builder.Finalize(XOnlyPubKey{NUMS_PK}); + return GetScriptForDestination(builder.GetOutput()); +} + +//! Fill the witness with the data additional to the script satisfaction. +void SatisfactionToWitness(miniscript::MiniscriptContext ctx, CScriptWitness& witness, const CScript& script, TaprootBuilder& builder) { + // For P2WSH, it's only the witness script. + witness.stack.push_back(std::vector<unsigned char>(script.begin(), script.end())); + if (!miniscript::IsTapscript(ctx)) return; + // For Tapscript we also need the control block. + witness.stack.push_back(*builder.GetSpendData().scripts.begin()->second.begin()); +} + /** Run random satisfaction tests. */ -void TestSatisfy(const std::string& testcase, const NodeRef& node) { - auto script = node->ToScript(CONVERTER); +void TestSatisfy(const KeyConverter& converter, const std::string& testcase, const NodeRef& node) { + auto script = node->ToScript(converter); auto challenges = FindChallenges(node); // Find all challenges in the generated miniscript. std::vector<Challenge> challist(challenges.begin(), challenges.end()); for (int iter = 0; iter < 3; ++iter) { Shuffle(challist.begin(), challist.end(), g_insecure_rand_ctx); Satisfier satisfier; + satisfier.SetContext(converter.MsContext()); TestSignatureChecker checker(satisfier); bool prev_mal_success = false, prev_nonmal_success = false; // Go over all challenges involved in this miniscript in random order. for (int add = -1; add < (int)challist.size(); ++add) { if (add >= 0) satisfier.supported.insert(challist[add]); // The first iteration does not add anything + // Get the ScriptPubKey for this script, filling spend data if it's Taproot. + TaprootBuilder builder; + const CScript script_pubkey{ScriptPubKey(converter.MsContext(), script, builder)}; + // Run malleable satisfaction algorithm. - const CScript script_pubkey = CScript() << OP_0 << WitnessV0ScriptHash(script); CScriptWitness witness_mal; const bool mal_success = node->Satisfy(satisfier, witness_mal.stack, false) == miniscript::Availability::YES; - witness_mal.stack.push_back(std::vector<unsigned char>(script.begin(), script.end())); + SatisfactionToWitness(converter.MsContext(), witness_mal, script, builder); // Run non-malleable satisfaction algorithm. CScriptWitness witness_nonmal; const bool nonmal_success = node->Satisfy(satisfier, witness_nonmal.stack, true) == miniscript::Availability::YES; - witness_nonmal.stack.push_back(std::vector<unsigned char>(script.begin(), script.end())); + SatisfactionToWitness(converter.MsContext(), witness_nonmal, script, builder); if (nonmal_success) { - // Non-malleable satisfactions are bounded by GetStackSize(). - BOOST_CHECK(witness_nonmal.stack.size() <= *node->GetStackSize() + 1); + // Non-malleable satisfactions are bounded by the satisfaction size plus: + // - For P2WSH spends, the witness script + // - For Tapscript spends, both the witness script and the control block + const size_t max_stack_size{*node->GetStackSize() + 1 + miniscript::IsTapscript(converter.MsContext())}; + BOOST_CHECK(witness_nonmal.stack.size() <= max_stack_size); // If a non-malleable satisfaction exists, the malleable one must also exist, and be identical to it. BOOST_CHECK(mal_success); BOOST_CHECK(witness_nonmal.stack == witness_mal.stack); @@ -351,37 +430,57 @@ void TestSatisfy(const std::string& testcase, const NodeRef& node) { } enum TestMode : int { + //! Invalid under any context TESTMODE_INVALID = 0, + //! Valid under any context unless overridden TESTMODE_VALID = 1, TESTMODE_NONMAL = 2, TESTMODE_NEEDSIG = 4, - TESTMODE_TIMELOCKMIX = 8 + TESTMODE_TIMELOCKMIX = 8, + //! Invalid only under P2WSH context + TESTMODE_P2WSH_INVALID = 16, + //! Invalid only under Tapscript context + TESTMODE_TAPSCRIPT_INVALID = 32, }; -void Test(const std::string& ms, const std::string& hexscript, int mode, int opslimit = -1, int stacklimit = -1, std::optional<uint32_t> max_wit_size = std::nullopt) +void Test(const std::string& ms, const std::string& hexscript, int mode, const KeyConverter& converter, + int opslimit = -1, int stacklimit = -1, std::optional<uint32_t> max_wit_size = std::nullopt, + std::optional<uint32_t> stack_exec = {}) { - auto node = miniscript::FromString(ms, CONVERTER); - if (mode == TESTMODE_INVALID) { + auto node = miniscript::FromString(ms, converter); + const bool is_tapscript{miniscript::IsTapscript(converter.MsContext())}; + if (mode == TESTMODE_INVALID || ((mode & TESTMODE_P2WSH_INVALID) && !is_tapscript) || ((mode & TESTMODE_TAPSCRIPT_INVALID) && is_tapscript)) { BOOST_CHECK_MESSAGE(!node || !node->IsValid(), "Unexpectedly valid: " + ms); } else { BOOST_CHECK_MESSAGE(node, "Unparseable: " + ms); BOOST_CHECK_MESSAGE(node->IsValid(), "Invalid: " + ms); BOOST_CHECK_MESSAGE(node->IsValidTopLevel(), "Invalid top level: " + ms); - auto computed_script = node->ToScript(CONVERTER); + auto computed_script = node->ToScript(converter); BOOST_CHECK_MESSAGE(node->ScriptSize() == computed_script.size(), "Script size mismatch: " + ms); if (hexscript != "?") BOOST_CHECK_MESSAGE(HexStr(computed_script) == hexscript, "Script mismatch: " + ms + " (" + HexStr(computed_script) + " vs " + hexscript + ")"); BOOST_CHECK_MESSAGE(node->IsNonMalleable() == !!(mode & TESTMODE_NONMAL), "Malleability mismatch: " + ms); BOOST_CHECK_MESSAGE(node->NeedsSignature() == !!(mode & TESTMODE_NEEDSIG), "Signature necessity mismatch: " + ms); BOOST_CHECK_MESSAGE((node->GetType() << "k"_mst) == !(mode & TESTMODE_TIMELOCKMIX), "Timelock mix mismatch: " + ms); - auto inferred_miniscript = miniscript::FromScript(computed_script, CONVERTER); + auto inferred_miniscript = miniscript::FromScript(computed_script, converter); BOOST_CHECK_MESSAGE(inferred_miniscript, "Cannot infer miniscript from script: " + ms); - BOOST_CHECK_MESSAGE(inferred_miniscript->ToScript(CONVERTER) == computed_script, "Roundtrip failure: miniscript->script != miniscript->script->miniscript->script: " + ms); + BOOST_CHECK_MESSAGE(inferred_miniscript->ToScript(converter) == computed_script, "Roundtrip failure: miniscript->script != miniscript->script->miniscript->script: " + ms); if (opslimit != -1) BOOST_CHECK_MESSAGE((int)*node->GetOps() == opslimit, "Ops limit mismatch: " << ms << " (" << *node->GetOps() << " vs " << opslimit << ")"); if (stacklimit != -1) BOOST_CHECK_MESSAGE((int)*node->GetStackSize() == stacklimit, "Stack limit mismatch: " << ms << " (" << *node->GetStackSize() << " vs " << stacklimit << ")"); if (max_wit_size) BOOST_CHECK_MESSAGE(*node->GetWitnessSize() == *max_wit_size, "Witness size limit mismatch: " << ms << " (" << *node->GetWitnessSize() << " vs " << *max_wit_size << ")"); - TestSatisfy(ms, node); + if (stack_exec) BOOST_CHECK_MESSAGE(*node->GetExecStackSize() == *stack_exec, "Stack execution limit mismatch: " << ms << " (" << *node->GetExecStackSize() << " vs " << *stack_exec << ")"); + TestSatisfy(converter, ms, node); } } + +void Test(const std::string& ms, const std::string& hexscript, const std::string& hextapscript, int mode, + int opslimit = -1, int stacklimit = -1, std::optional<uint32_t> max_wit_size = std::nullopt, + std::optional<uint32_t> stack_exec = {}) +{ + CONVERTER.SetContext(miniscript::MiniscriptContext::P2WSH); + Test(ms, hexscript, mode, CONVERTER, opslimit, stacklimit, max_wit_size, stack_exec); + CONVERTER.SetContext(miniscript::MiniscriptContext::TAPSCRIPT); + Test(ms, hextapscript == "=" ? hexscript : hextapscript, mode, CONVERTER, opslimit, stacklimit, max_wit_size, stack_exec); +} } // namespace BOOST_FIXTURE_TEST_SUITE(miniscript_tests, BasicTestingSetup) @@ -391,94 +490,146 @@ BOOST_AUTO_TEST_CASE(fixed_tests) g_testdata.reset(new TestData()); // Validity rules - Test("l:older(1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // older(1): valid - Test("l:older(0)", "?", TESTMODE_INVALID); // older(0): k must be at least 1 - Test("l:older(2147483647)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // older(2147483647): valid - Test("l:older(2147483648)", "?", TESTMODE_INVALID); // older(2147483648): k must be below 2^31 - Test("u:after(1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // after(1): valid - Test("u:after(0)", "?", TESTMODE_INVALID); // after(0): k must be at least 1 - Test("u:after(2147483647)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // after(2147483647): valid - Test("u:after(2147483648)", "?", TESTMODE_INVALID); // after(2147483648): k must be below 2^31 - Test("andor(0,1,1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // andor(Bdu,B,B): valid - Test("andor(a:0,1,1)", "?", TESTMODE_INVALID); // andor(Wdu,B,B): X must be B - Test("andor(0,a:1,a:1)", "?", TESTMODE_INVALID); // andor(Bdu,W,W): Y and Z must be B/V/K - Test("andor(1,1,1)", "?", TESTMODE_INVALID); // andor(Bu,B,B): X must be d - Test("andor(n:or_i(0,after(1)),1,1)", "?", TESTMODE_VALID); // andor(Bdu,B,B): valid - Test("andor(or_i(0,after(1)),1,1)", "?", TESTMODE_INVALID); // andor(Bd,B,B): X must be u - Test("c:andor(0,pk_k(03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7),pk_k(036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // andor(Bdu,K,K): valid - Test("t:andor(0,v:1,v:1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // andor(Bdu,V,V): valid - Test("and_v(v:1,1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // and_v(V,B): valid - Test("t:and_v(v:1,v:1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // and_v(V,V): valid - Test("c:and_v(v:1,pk_k(036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // and_v(V,K): valid - Test("and_v(1,1)", "?", TESTMODE_INVALID); // and_v(B,B): X must be V - Test("and_v(pk_k(02352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5),1)", "?", TESTMODE_INVALID); // and_v(K,B): X must be V - Test("and_v(v:1,a:1)", "?", TESTMODE_INVALID); // and_v(K,W): Y must be B/V/K - Test("and_b(1,a:1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // and_b(B,W): valid - Test("and_b(1,1)", "?", TESTMODE_INVALID); // and_b(B,B): Y must W - Test("and_b(v:1,a:1)", "?", TESTMODE_INVALID); // and_b(V,W): X must be B - Test("and_b(a:1,a:1)", "?", TESTMODE_INVALID); // and_b(W,W): X must be B - Test("and_b(pk_k(025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),a:1)", "?", TESTMODE_INVALID); // and_b(K,W): X must be B - Test("or_b(0,a:0)", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // or_b(Bd,Wd): valid - Test("or_b(1,a:0)", "?", TESTMODE_INVALID); // or_b(B,Wd): X must be d - Test("or_b(0,a:1)", "?", TESTMODE_INVALID); // or_b(Bd,W): Y must be d - Test("or_b(0,0)", "?", TESTMODE_INVALID); // or_b(Bd,Bd): Y must W - Test("or_b(v:0,a:0)", "?", TESTMODE_INVALID); // or_b(V,Wd): X must be B - Test("or_b(a:0,a:0)", "?", TESTMODE_INVALID); // or_b(Wd,Wd): X must be B - Test("or_b(pk_k(025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),a:0)", "?", TESTMODE_INVALID); // or_b(Kd,Wd): X must be B - Test("t:or_c(0,v:1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // or_c(Bdu,V): valid - Test("t:or_c(a:0,v:1)", "?", TESTMODE_INVALID); // or_c(Wdu,V): X must be B - Test("t:or_c(1,v:1)", "?", TESTMODE_INVALID); // or_c(Bu,V): X must be d - Test("t:or_c(n:or_i(0,after(1)),v:1)", "?", TESTMODE_VALID); // or_c(Bdu,V): valid - Test("t:or_c(or_i(0,after(1)),v:1)", "?", TESTMODE_INVALID); // or_c(Bd,V): X must be u - Test("t:or_c(0,1)", "?", TESTMODE_INVALID); // or_c(Bdu,B): Y must be V - Test("or_d(0,1)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // or_d(Bdu,B): valid - Test("or_d(a:0,1)", "?", TESTMODE_INVALID); // or_d(Wdu,B): X must be B - Test("or_d(1,1)", "?", TESTMODE_INVALID); // or_d(Bu,B): X must be d - Test("or_d(n:or_i(0,after(1)),1)", "?", TESTMODE_VALID); // or_d(Bdu,B): valid - Test("or_d(or_i(0,after(1)),1)", "?", TESTMODE_INVALID); // or_d(Bd,B): X must be u - Test("or_d(0,v:1)", "?", TESTMODE_INVALID); // or_d(Bdu,V): Y must be B - Test("or_i(1,1)", "?", TESTMODE_VALID); // or_i(B,B): valid - Test("t:or_i(v:1,v:1)", "?", TESTMODE_VALID); // or_i(V,V): valid - Test("c:or_i(pk_k(03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7),pk_k(036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // or_i(K,K): valid - Test("or_i(a:1,a:1)", "?", TESTMODE_INVALID); // or_i(W,W): X and Y must be B/V/K - Test("or_b(l:after(100),al:after(1000000000))", "?", TESTMODE_VALID); // or_b(timelock, heighlock) valid - Test("and_b(after(100),a:after(1000000000))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX); // and_b(timelock, heighlock) invalid - Test("pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // alias to c:pk_k - Test("pkh(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)", "76a914fcd35ddacad9f2d5be5e464639441c6065e6955d88ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // alias to c:pk_h + Test("l:older(1)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // older(1): valid + Test("l:older(0)", "?", "?", TESTMODE_INVALID); // older(0): k must be at least 1 + Test("l:older(2147483647)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // older(2147483647): valid + Test("l:older(2147483648)", "?", "?", TESTMODE_INVALID); // older(2147483648): k must be below 2^31 + Test("u:after(1)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // after(1): valid + Test("u:after(0)", "?", "?", TESTMODE_INVALID); // after(0): k must be at least 1 + Test("u:after(2147483647)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // after(2147483647): valid + Test("u:after(2147483648)", "?", "?", TESTMODE_INVALID); // after(2147483648): k must be below 2^31 + Test("andor(0,1,1)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // andor(Bdu,B,B): valid + Test("andor(a:0,1,1)", "?", "?", TESTMODE_INVALID); // andor(Wdu,B,B): X must be B + Test("andor(0,a:1,a:1)", "?", "?", TESTMODE_INVALID); // andor(Bdu,W,W): Y and Z must be B/V/K + Test("andor(1,1,1)", "?", "?", TESTMODE_INVALID); // andor(Bu,B,B): X must be d + Test("andor(n:or_i(0,after(1)),1,1)", "?", "?", TESTMODE_VALID); // andor(Bdu,B,B): valid + Test("andor(or_i(0,after(1)),1,1)", "?", "?", TESTMODE_INVALID); // andor(Bd,B,B): X must be u + Test("c:andor(0,pk_k(03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7),pk_k(036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00))", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // andor(Bdu,K,K): valid + Test("t:andor(0,v:1,v:1)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // andor(Bdu,V,V): valid + Test("and_v(v:1,1)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // and_v(V,B): valid + Test("t:and_v(v:1,v:1)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // and_v(V,V): valid + Test("c:and_v(v:1,pk_k(036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00))", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // and_v(V,K): valid + Test("and_v(1,1)", "?", "?", TESTMODE_INVALID); // and_v(B,B): X must be V + Test("and_v(pk_k(02352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5),1)", "?", "?", TESTMODE_INVALID); // and_v(K,B): X must be V + Test("and_v(v:1,a:1)", "?", "?", TESTMODE_INVALID); // and_v(K,W): Y must be B/V/K + Test("and_b(1,a:1)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // and_b(B,W): valid + Test("and_b(1,1)", "?", "?", TESTMODE_INVALID); // and_b(B,B): Y must W + Test("and_b(v:1,a:1)", "?", "?", TESTMODE_INVALID); // and_b(V,W): X must be B + Test("and_b(a:1,a:1)", "?", "?", TESTMODE_INVALID); // and_b(W,W): X must be B + Test("and_b(pk_k(025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),a:1)", "?", "?", TESTMODE_INVALID); // and_b(K,W): X must be B + Test("or_b(0,a:0)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // or_b(Bd,Wd): valid + Test("or_b(1,a:0)", "?", "?", TESTMODE_INVALID); // or_b(B,Wd): X must be d + Test("or_b(0,a:1)", "?", "?", TESTMODE_INVALID); // or_b(Bd,W): Y must be d + Test("or_b(0,0)", "?", "?", TESTMODE_INVALID); // or_b(Bd,Bd): Y must W + Test("or_b(v:0,a:0)", "?", "?", TESTMODE_INVALID); // or_b(V,Wd): X must be B + Test("or_b(a:0,a:0)", "?", "?", TESTMODE_INVALID); // or_b(Wd,Wd): X must be B + Test("or_b(pk_k(025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),a:0)", "?", "?", TESTMODE_INVALID); // or_b(Kd,Wd): X must be B + Test("t:or_c(0,v:1)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // or_c(Bdu,V): valid + Test("t:or_c(a:0,v:1)", "?", "?", TESTMODE_INVALID); // or_c(Wdu,V): X must be B + Test("t:or_c(1,v:1)", "?", "?", TESTMODE_INVALID); // or_c(Bu,V): X must be d + Test("t:or_c(n:or_i(0,after(1)),v:1)", "?", "?", TESTMODE_VALID); // or_c(Bdu,V): valid + Test("t:or_c(or_i(0,after(1)),v:1)", "?", "?", TESTMODE_INVALID); // or_c(Bd,V): X must be u + Test("t:or_c(0,1)", "?", "?", TESTMODE_INVALID); // or_c(Bdu,B): Y must be V + Test("or_d(0,1)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // or_d(Bdu,B): valid + Test("or_d(a:0,1)", "?", "?", TESTMODE_INVALID); // or_d(Wdu,B): X must be B + Test("or_d(1,1)", "?", "?", TESTMODE_INVALID); // or_d(Bu,B): X must be d + Test("or_d(n:or_i(0,after(1)),1)", "?", "?", TESTMODE_VALID); // or_d(Bdu,B): valid + Test("or_d(or_i(0,after(1)),1)", "?", "?", TESTMODE_INVALID); // or_d(Bd,B): X must be u + Test("or_d(0,v:1)", "?", "?", TESTMODE_INVALID); // or_d(Bdu,V): Y must be B + Test("or_i(1,1)", "?", "?", TESTMODE_VALID); // or_i(B,B): valid + Test("t:or_i(v:1,v:1)", "?", "?", TESTMODE_VALID); // or_i(V,V): valid + Test("c:or_i(pk_k(03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7),pk_k(036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00))", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // or_i(K,K): valid + Test("or_i(a:1,a:1)", "?", "?", TESTMODE_INVALID); // or_i(W,W): X and Y must be B/V/K + Test("or_b(l:after(100),al:after(1000000000))", "?", "?", TESTMODE_VALID); // or_b(timelock, heighlock) valid + Test("and_b(after(100),a:after(1000000000))", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX); // and_b(timelock, heighlock) invalid + Test("pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac", "20d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // alias to c:pk_k + Test("pkh(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65)", "76a914fcd35ddacad9f2d5be5e464639441c6065e6955d88ac", "76a914fd1690c37fa3b0f04395ddc9415b220ab1ccc59588ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG); // alias to c:pk_h // Randomly generated test set that covers the majority of type and node type combinations - Test("lltvln:after(1231488000)", "6300676300676300670400046749b1926869516868", TESTMODE_VALID | TESTMODE_NONMAL, 12, 3, 3); - Test("uuj:and_v(v:multi(2,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a,025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),after(1231488000))", "6363829263522103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a21025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc52af0400046749b168670068670068", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 14, 5, 2 + 2 + 1 + 2 * 73); - Test("or_b(un:multi(2,03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),al:older(16))", "63522103daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee872921024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae926700686b63006760b2686c9b", TESTMODE_VALID, 14, 5, 2 + 1 + 2 * 73 + 2); - Test("j:and_v(vdv:after(1567547623),older(2016))", "829263766304e7e06e5db169686902e007b268", TESTMODE_VALID | TESTMODE_NONMAL, 11, 1, 2); - Test("t:and_v(vu:hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b),v:sha256(ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5))", "6382012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876700686982012088a820ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc58851", TESTMODE_VALID | TESTMODE_NONMAL, 12, 3, 2 + 33 + 33); - Test("t:andor(multi(3,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),v:older(4194305),v:sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2))", "532102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a14602975562102e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd1353ae6482012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2886703010040b2696851", TESTMODE_VALID | TESTMODE_NONMAL, 13, 5, 1 + 3 * 73); - Test("or_d(multi(1,02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9),or_b(multi(3,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a),su:after(500000)))", "512102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f951ae73645321022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a0121032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f2103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a53ae7c630320a107b16700689b68", TESTMODE_VALID | TESTMODE_NONMAL, 15, 7, 2 + 1 + 3 * 73 + 1); - Test("or_d(sha256(38df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b6),and_n(un:after(499999999),older(4194305)))", "82012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68773646304ff64cd1db19267006864006703010040b26868", TESTMODE_VALID, 16, 1, 33); - Test("and_v(or_i(v:multi(2,02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb),v:multi(2,03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)),sha256(d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68))", "63522102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee52103774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb52af67522103e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a21025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc52af6882012088a820d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c6887", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 11, 5, 2 + 1 + 2 * 73 + 33); - Test("j:and_b(multi(2,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),s:or_i(older(1),older(4252898)))", "82926352210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179821024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae7c6351b26703e2e440b2689a68", TESTMODE_VALID | TESTMODE_NEEDSIG, 14, 4, 1 + 2 * 73 + 2); - Test("and_b(older(16),s:or_d(sha256(e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f),n:after(1567547623)))", "60b27c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87736404e7e06e5db192689a", TESTMODE_VALID, 12, 1, 33); - Test("j:and_v(v:hash160(20195b5a3d650c17f0f29f91c33f8f6335193d07),or_d(sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),older(16)))", "82926382012088a91420195b5a3d650c17f0f29f91c33f8f6335193d078882012088a82096de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c4787736460b26868", TESTMODE_VALID, 16, 2, 33 + 33); - Test("and_b(hash256(32ba476771d01e37807990ead8719f08af494723de1d228f2c2c07cc0aa40bac),a:and_b(hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b),a:older(1)))", "82012088aa2032ba476771d01e37807990ead8719f08af494723de1d228f2c2c07cc0aa40bac876b82012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876b51b26c9a6c9a", TESTMODE_VALID | TESTMODE_NONMAL, 15, 2, 33 + 33); - Test("thresh(2,multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),a:multi(1,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),ac:pk_k(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01))", "522103a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c721036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0052ae6b5121036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0051ae6c936b21022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01ac6c935287", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 13, 6, 1 + 2 * 73 + 1 + 73 + 1); - Test("and_n(sha256(d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68),t:or_i(v:older(4252898),v:older(144)))", "82012088a820d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68876400676303e2e440b26967029000b269685168", TESTMODE_VALID, 14, 2, 33 + 2); - Test("or_d(nd:and_v(v:older(4252898),v:older(4252898)),sha256(38df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b6))", "766303e2e440b26903e2e440b2696892736482012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68768", TESTMODE_VALID, 15, 2, 1 + 33); - Test("c:and_v(or_c(sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2),v:multi(1,02c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db)),pk_k(03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))", "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764512102c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db51af682103acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbeac", TESTMODE_VALID | TESTMODE_NEEDSIG, 8, 2, 33 + 73); - Test("c:and_v(or_c(multi(2,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00,02352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5),v:ripemd160(1b0f3c404d12075c68c938f9f60ebea4f74941a0)),pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "5221036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a002102352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d552ae6482012088a6141b0f3c404d12075c68c938f9f60ebea4f74941a088682103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 10, 5, 1 + 2 * 73 + 73); - Test("and_v(andor(hash256(8a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b25),v:hash256(939894f70e6c3a25da75da0cc2071b4076d9b006563cf635986ada2e93c0d735),v:older(50000)),after(499999999))", "82012088aa208a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b2587640350c300b2696782012088aa20939894f70e6c3a25da75da0cc2071b4076d9b006563cf635986ada2e93c0d735886804ff64cd1db1", TESTMODE_VALID, 14, 2, 33 + 33); - Test("andor(hash256(5f8d30e655a7ba0d7596bb3ddfb1d2d20390d23b1845000e1e118b3be1b3f040),j:and_v(v:hash160(3a2bff0da9d96868e66abc4427bea4691cf61ccd),older(4194305)),ripemd160(44d90e2d3714c8663b632fcf0f9d5f22192cc4c8))", "82012088aa205f8d30e655a7ba0d7596bb3ddfb1d2d20390d23b1845000e1e118b3be1b3f040876482012088a61444d90e2d3714c8663b632fcf0f9d5f22192cc4c8876782926382012088a9143a2bff0da9d96868e66abc4427bea4691cf61ccd8803010040b26868", TESTMODE_VALID, 20, 2, 33 + 33); - Test("or_i(c:and_v(v:after(500000),pk_k(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),sha256(d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f946))", "630320a107b1692102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ac6782012088a820d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f9468768", TESTMODE_VALID | TESTMODE_NONMAL, 10, 2, 2 + 73); - Test("thresh(2,c:pk_h(025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc),s:sha256(e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f),a:hash160(dd69735817e0e3f6f826a9238dc2e291184f0131))", "76a9145dedfbf9ea599dd4e3ca6a80b333c472fd0b3f6988ac7c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87936b82012088a914dd69735817e0e3f6f826a9238dc2e291184f0131876c935287", TESTMODE_VALID, 18, 4, 1 + 34 + 33 + 33); - Test("and_n(sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2),uc:and_v(v:older(144),pk_k(03fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ce)))", "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764006763029000b2692103fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ceac67006868", TESTMODE_VALID | TESTMODE_NEEDSIG, 13, 3, 33 + 2 + 73); - Test("and_n(c:pk_k(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),and_b(l:older(4252898),a:older(16)))", "2103daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729ac64006763006703e2e440b2686b60b26c9a68", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TIMELOCKMIX, 12, 2, 73 + 1); - Test("c:or_i(and_v(v:older(16),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)),pk_h(026a245bf6dc698504c89a20cfded60853152b695336c28063b61c65cbd269e6b4))", "6360b26976a9149fc5dbe5efdce10374a4dd4053c93af540211718886776a9142fbd32c8dd59ee7c17e66cb6ebea7e9846c3040f8868ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 12, 3, 2 + 34 + 73); - Test("or_d(c:pk_h(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),andor(c:pk_k(024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),older(2016),after(1567547623)))", "76a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac736421024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97ac6404e7e06e5db16702e007b26868", TESTMODE_VALID | TESTMODE_NONMAL, 13, 3, 1 + 34 + 73); - Test("c:andor(ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e),and_v(v:hash256(8a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b25),pk_h(03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a)))", "82012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba876482012088aa208a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b258876a914dd100be7d9aea5721158ebde6d6a1fd8fff93bb1886776a9149fc5dbe5efdce10374a4dd4053c93af5402117188868ac", TESTMODE_VALID | TESTMODE_NEEDSIG, 18, 3, 33 + 34 + 73); - Test("c:andor(u:ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba),pk_h(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),or_i(pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)))", "6382012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba87670068646376a9149652d86bedf43ad264362e6e6eba6eb764508127886776a914751e76e8199196d454941c45d1b3a323f1433bd688686776a91420d637c1a6404d2227f3561fdbaff5a680dba6488868ac", TESTMODE_VALID | TESTMODE_NEEDSIG, 23, 4, 2 + 33 + 34 + 73); - Test("c:or_i(andor(c:pk_h(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),pk_k(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e))", "6376a914fcd35ddacad9f2d5be5e464639441c6065e6955d88ac6476a91406afd46bcdfd22ef94ac122aa11f241244a37ecc886776a9149652d86bedf43ad264362e6e6eba6eb7645081278868672102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e68ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 17, 5, 2 + 34 + 73 + 34 + 73); - Test("thresh(1,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(1000000000),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670400ca9a3bb16951686c936b6300670164b16951686c935187", TESTMODE_VALID, 18, 3, 73 + 2 + 2); - Test("thresh(2,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),ac:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),altv:after(1000000000),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac6c936b6300670400ca9a3bb16951686c936b6300670164b16951686c935287", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX, 22, 4, 73 + 73 + 2 + 2); + Test("lltvln:after(1231488000)", "6300676300676300670400046749b1926869516868", "=", TESTMODE_VALID | TESTMODE_NONMAL, 12, 3, 3, 3); + Test("uuj:and_v(v:multi(2,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a,025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),after(1231488000))", "6363829263522103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a21025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc52af0400046749b168670068670068", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 14, 5, 2 + 2 + 1 + 2 * 73, 7); + Test("or_b(un:multi(2,03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),al:older(16))", "63522103daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee872921024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae926700686b63006760b2686c9b", "?", TESTMODE_VALID | TESTMODE_TAPSCRIPT_INVALID, 14, 5, 2 + 1 + 2 * 73 + 2, 8); + Test("j:and_v(vdv:after(1567547623),older(2016))", "829263766304e7e06e5db169686902e007b268", "=", TESTMODE_VALID | TESTMODE_NONMAL, 11, 1, 2, 2); + Test("t:and_v(vu:hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b),v:sha256(ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5))", "6382012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876700686982012088a820ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc58851", "6382012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876700686982012088a820ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc58851", TESTMODE_VALID | TESTMODE_NONMAL, 12, 3, 2 + 33 + 33, 4); + Test("t:andor(multi(3,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),v:older(4194305),v:sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2))", "532102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a14602975562102e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd1353ae6482012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2886703010040b2696851", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TAPSCRIPT_INVALID, 13, 5, 1 + 3 * 73, 10); + Test("or_d(multi(1,02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9),or_b(multi(3,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a),su:after(500000)))", "512102f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f951ae73645321022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a0121032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f2103d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a53ae7c630320a107b16700689b68", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TAPSCRIPT_INVALID, 15, 7, 2 + 1 + 3 * 73 + 1, 10); + Test("or_d(sha256(38df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b6),and_n(un:after(499999999),older(4194305)))", "82012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68773646304ff64cd1db19267006864006703010040b26868", "82012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68773646304ff64cd1db19267006864006703010040b26868", TESTMODE_VALID, 16, 1, 33, 3); + Test("and_v(or_i(v:multi(2,02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb),v:multi(2,03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)),sha256(d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68))", "63522102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee52103774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb52af67522103e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a21025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc52af6882012088a820d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c6887", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 11, 5, 2 + 1 + 2 * 73 + 33, 8); + Test("j:and_b(multi(2,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),s:or_i(older(1),older(4252898)))", "82926352210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179821024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c9752ae7c6351b26703e2e440b2689a68", "?", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 14, 4, 1 + 2 * 73 + 2, 8); + Test("and_b(older(16),s:or_d(sha256(e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f),n:after(1567547623)))", "60b27c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87736404e7e06e5db192689a", "=", TESTMODE_VALID, 12, 1, 33, 4); + Test("j:and_v(v:hash160(20195b5a3d650c17f0f29f91c33f8f6335193d07),or_d(sha256(96de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c47),older(16)))", "82926382012088a91420195b5a3d650c17f0f29f91c33f8f6335193d078882012088a82096de8fc8c256fa1e1556d41af431cace7dca68707c78dd88c3acab8b17164c4787736460b26868", "=", TESTMODE_VALID, 16, 2, 33 + 33, 4); + Test("and_b(hash256(32ba476771d01e37807990ead8719f08af494723de1d228f2c2c07cc0aa40bac),a:and_b(hash256(131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b),a:older(1)))", "82012088aa2032ba476771d01e37807990ead8719f08af494723de1d228f2c2c07cc0aa40bac876b82012088aa20131772552c01444cd81360818376a040b7c3b2b7b0a53550ee3edde216cec61b876b51b26c9a6c9a", "=", TESTMODE_VALID | TESTMODE_NONMAL, 15, 2, 33 + 33, 4); + Test("thresh(2,multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),a:multi(1,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),ac:pk_k(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01))", "522103a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c721036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0052ae6b5121036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a0051ae6c936b21022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01ac6c935287", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 13, 6, 1 + 2 * 73 + 1 + 73 + 1, 10); + Test("and_n(sha256(d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68),t:or_i(v:older(4252898),v:older(144)))", "82012088a820d1ec675902ef1633427ca360b290b0b3045a0d9058ddb5e648b4c3c3224c5c68876400676303e2e440b26967029000b269685168", "=", TESTMODE_VALID, 14, 2, 33 + 2, 4); + Test("or_d(nd:and_v(v:older(4252898),v:older(4252898)),sha256(38df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b6))", "766303e2e440b26903e2e440b2696892736482012088a82038df1c1f64a24a77b23393bca50dff872e31edc4f3b5aa3b90ad0b82f4f089b68768", "=", TESTMODE_VALID, 15, 2, 1 + 33, 3); + Test("c:and_v(or_c(sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2),v:multi(1,02c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db)),pk_k(03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))", "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764512102c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db51af682103acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbeac", "?", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 8, 2, 33 + 73, 4); + Test("c:and_v(or_c(multi(2,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00,02352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5),v:ripemd160(1b0f3c404d12075c68c938f9f60ebea4f74941a0)),pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "5221036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a002102352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d552ae6482012088a6141b0f3c404d12075c68c938f9f60ebea4f74941a088682103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TAPSCRIPT_INVALID, 10, 5, 1 + 2 * 73 + 73, 9); + Test("and_v(andor(hash256(8a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b25),v:hash256(939894f70e6c3a25da75da0cc2071b4076d9b006563cf635986ada2e93c0d735),v:older(50000)),after(499999999))", "82012088aa208a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b2587640350c300b2696782012088aa20939894f70e6c3a25da75da0cc2071b4076d9b006563cf635986ada2e93c0d735886804ff64cd1db1", "=", TESTMODE_VALID, 14, 2, 33 + 33, 4); + Test("andor(hash256(5f8d30e655a7ba0d7596bb3ddfb1d2d20390d23b1845000e1e118b3be1b3f040),j:and_v(v:hash160(3a2bff0da9d96868e66abc4427bea4691cf61ccd),older(4194305)),ripemd160(44d90e2d3714c8663b632fcf0f9d5f22192cc4c8))", "82012088aa205f8d30e655a7ba0d7596bb3ddfb1d2d20390d23b1845000e1e118b3be1b3f040876482012088a61444d90e2d3714c8663b632fcf0f9d5f22192cc4c8876782926382012088a9143a2bff0da9d96868e66abc4427bea4691cf61ccd8803010040b26868", "=", TESTMODE_VALID, 20, 2, 33 + 33, 4); + Test("or_i(c:and_v(v:after(500000),pk_k(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),sha256(d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f946))", "630320a107b1692102c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ac6782012088a820d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f9468768", "630320a107b16920c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ac6782012088a820d9147961436944f43cd99d28b2bbddbf452ef872b30c8279e255e7daafc7f9468768", TESTMODE_VALID | TESTMODE_NONMAL, 10, 2, 2 + 73, 3); + Test("thresh(2,c:pk_h(025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc),s:sha256(e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f),a:hash160(dd69735817e0e3f6f826a9238dc2e291184f0131))", "76a9145dedfbf9ea599dd4e3ca6a80b333c472fd0b3f6988ac7c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87936b82012088a914dd69735817e0e3f6f826a9238dc2e291184f0131876c935287", "76a9141a7ac36cfa8431ab2395d701b0050045ae4a37d188ac7c82012088a820e38990d0c7fc009880a9c07c23842e886c6bbdc964ce6bdd5817ad357335ee6f87936b82012088a914dd69735817e0e3f6f826a9238dc2e291184f0131876c935287", TESTMODE_VALID, 18, 4, 1 + 34 + 33 + 33, 6); + Test("and_n(sha256(9267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2),uc:and_v(v:older(144),pk_k(03fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ce)))", "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764006763029000b2692103fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ceac67006868", "82012088a8209267d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed28764006763029000b26920fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ceac67006868", TESTMODE_VALID | TESTMODE_NEEDSIG, 13, 3, 33 + 2 + 73, 5); + Test("and_n(c:pk_k(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),and_b(l:older(4252898),a:older(16)))", "2103daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729ac64006763006703e2e440b2686b60b26c9a68", "20daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729ac64006763006703e2e440b2686b60b26c9a68", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_TIMELOCKMIX, 12, 2, 73 + 1, 3); + Test("c:or_i(and_v(v:older(16),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)),pk_h(026a245bf6dc698504c89a20cfded60853152b695336c28063b61c65cbd269e6b4))", "6360b26976a9149fc5dbe5efdce10374a4dd4053c93af540211718886776a9142fbd32c8dd59ee7c17e66cb6ebea7e9846c3040f8868ac", "6360b26976a9144d4421361c3289bdad06441ffaee8be8e786f1ad886776a91460d4a7bcbd08f58e58bd208d1069837d7adb16ae8868ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 12, 3, 2 + 34 + 73, 4); + Test("or_d(c:pk_h(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),andor(c:pk_k(024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),older(2016),after(1567547623)))", "76a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac736421024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97ac6404e7e06e5db16702e007b26868", "76a91421ab1a140d0d305b8ff62bdb887d9fef82c9899e88ac7364204ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97ac6404e7e06e5db16702e007b26868", TESTMODE_VALID | TESTMODE_NONMAL, 13, 3, 1 + 34 + 73, 5); + Test("c:andor(ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e),and_v(v:hash256(8a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b25),pk_h(03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a)))", "82012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba876482012088aa208a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b258876a914dd100be7d9aea5721158ebde6d6a1fd8fff93bb1886776a9149fc5dbe5efdce10374a4dd4053c93af5402117188868ac", "82012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba876482012088aa208a35d9ca92a48eaade6f53a64985e9e2afeb74dcf8acb4c3721e0dc7e4294b258876a914a63d1e4d2ed109246c600ec8c19cce546b65b1cc886776a9144d4421361c3289bdad06441ffaee8be8e786f1ad8868ac", TESTMODE_VALID | TESTMODE_NEEDSIG, 18, 3, 33 + 34 + 73, 5); + Test("c:andor(u:ripemd160(6ad07d21fd5dfc646f0b30577045ce201616b9ba),pk_h(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),or_i(pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)))", "6382012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba87670068646376a9149652d86bedf43ad264362e6e6eba6eb764508127886776a914751e76e8199196d454941c45d1b3a323f1433bd688686776a91420d637c1a6404d2227f3561fdbaff5a680dba6488868ac", "6382012088a6146ad07d21fd5dfc646f0b30577045ce201616b9ba87670068646376a914ceedcb44b38bdbcb614d872223964fd3dca8a434886776a914f678d9b79045452c8c64e9309d0f0046056e26c588686776a914a2a75e1819afa208f6c89ae0da43021116dfcb0c8868ac", TESTMODE_VALID | TESTMODE_NEEDSIG, 23, 4, 2 + 33 + 34 + 73, 5); + Test("c:or_i(andor(c:pk_h(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),pk_k(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e))", "6376a914fcd35ddacad9f2d5be5e464639441c6065e6955d88ac6476a91406afd46bcdfd22ef94ac122aa11f241244a37ecc886776a9149652d86bedf43ad264362e6e6eba6eb7645081278868672102d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e68ac", "6376a914fd1690c37fa3b0f04395ddc9415b220ab1ccc59588ac6476a9149b652a14674a506079f574d20ca7daef6f9a66bb886776a914ceedcb44b38bdbcb614d872223964fd3dca8a43488686720d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e68ac", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG, 17, 5, 2 + 34 + 73 + 34 + 73, 6); + Test("thresh(1,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(1000000000),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670400ca9a3bb16951686c936b6300670164b16951686c935187", "20d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670400ca9a3bb16951686c936b6300670164b16951686c935187", TESTMODE_VALID, 18, 3, 73 + 2 + 2, 4); + Test("thresh(2,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),ac:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556),altv:after(1000000000),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac6c936b6300670400ca9a3bb16951686c936b6300670164b16951686c935287", "20d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b20fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac6c936b6300670400ca9a3bb16951686c936b6300670164b16951686c935287", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX, 22, 4, 73 + 73 + 2 + 2, 5); + + // Additional Tapscript-related tests + // Edge cases when parsing multi_a from script: + // - no pubkey at all + // - no pubkey before a CHECKSIGADD + // - no pubkey before the CHECKSIG + const auto no_pubkey{ParseHex("ac519c")}; + BOOST_CHECK(miniscript::FromScript({no_pubkey.begin(), no_pubkey.end()}, CONVERTER) == nullptr); + const auto incomplete_multi_a{ParseHex("ba20c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ba519c")}; + BOOST_CHECK(miniscript::FromScript({incomplete_multi_a.begin(), incomplete_multi_a.end()}, CONVERTER) == nullptr); + const auto incomplete_multi_a_2{ParseHex("ac2079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac20c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ba519c")}; + BOOST_CHECK(miniscript::FromScript({incomplete_multi_a_2.begin(), incomplete_multi_a_2.end()}, CONVERTER) == nullptr); + // Can use multi_a under Tapscript but not P2WSH. + Test("and_v(v:multi_a(2,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a,025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),after(1231488000))", "?", "20d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85aac205601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7ccba529d0400046749b1", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_P2WSH_INVALID, 4, 2, {}, 3); + // Can use more than 20 keys in a multi_a. + std::string ms_str_multi_a{"multi_a(1,"}; + for (size_t i = 0; i < 21; ++i) { + ms_str_multi_a += HexStr(g_testdata->pubkeys[i]); + if (i < 20) ms_str_multi_a += ","; + } + ms_str_multi_a += ")"; + Test(ms_str_multi_a, "?", "2079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac20c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5ba20f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9ba20e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13ba202f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4ba20fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ba205cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bcba202f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01ba20acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbeba20a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7ba20774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cbba20d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85aba20f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8ba20499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e4ba20d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080eba20e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0aba20defdea4cdb677750a420fee807eacf21eb9898ae79b9768766e4faa04a2d4a34ba205601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7ccba202b4ea0a797a443d293ef5cff444f4979f06acfebd7e86d277475656138385b6cba204ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97ba20352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5ba519c", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_P2WSH_INVALID, 22, 21, {}, 22); + // Since 'd:' is 'u' we can use it directly inside a thresh. But we can't under P2WSH. + Test("thresh(2,dv:older(42),s:pk(025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc),s:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65))", "?", "7663012ab269687c205cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bcac937c20d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac935287", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_P2WSH_INVALID, 12, 3, {}, 4); + // We can have a script that has more than 201 ops (n = 99), that needs a stack size > 100 (n = 110), or has a + // script that is larger than 3600 bytes (n = 200). All that can't be under P2WSH. + for (const auto pk_count: {99, 110, 200}) { + std::string ms_str_large; + for (auto i = 0; i < pk_count - 1; ++i) { + ms_str_large += "and_b(pk(" + HexStr(g_testdata->pubkeys[i]) + "),a:"; + } + ms_str_large += "pk(" + HexStr(g_testdata->pubkeys[pk_count - 1]) + ")"; + ms_str_large.insert(ms_str_large.end(), pk_count - 1, ')'); + Test(ms_str_large, "?", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_P2WSH_INVALID, pk_count + (pk_count - 1) * 3, pk_count, {}, pk_count + 1); + } + // We can have a script that reaches a stack size of 1000 during execution. + std::string ms_stack_limit; + auto count{998}; + for (auto i = 0; i < count; ++i) { + ms_stack_limit += "and_b(older(1),a:"; + } + ms_stack_limit += "pk(" + HexStr(g_testdata->pubkeys[0]) + ")"; + ms_stack_limit.insert(ms_stack_limit.end(), count, ')'); + const auto ms_stack_ok{miniscript::FromString(ms_stack_limit, CONVERTER)}; + BOOST_CHECK(ms_stack_ok && ms_stack_ok->CheckStackSize()); + Test(ms_stack_limit, "?", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_P2WSH_INVALID, 4 * count + 1, 1, {}, 1 + count + 1); + // But one more element on the stack during execution will make it fail. And we'd detect that. + count++; + ms_stack_limit = "and_b(older(1),a:" + ms_stack_limit + ")"; + const auto ms_stack_nok{miniscript::FromString(ms_stack_limit, CONVERTER)}; + BOOST_CHECK(ms_stack_nok && !ms_stack_nok->CheckStackSize()); + Test(ms_stack_limit, "?", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_NEEDSIG | TESTMODE_P2WSH_INVALID, 4 * count + 1, 1, {}, 1 + count + 1); // Misc unit tests // A Script with a non minimal push is invalid @@ -490,14 +641,15 @@ BOOST_AUTO_TEST_CASE(fixed_tests) const CScript nonminverify_script(nonminverify.begin(), nonminverify.end()); BOOST_CHECK(miniscript::FromScript(nonminverify_script, CONVERTER) == nullptr); // A threshold as large as the number of subs is valid. - Test("thresh(2,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670164b16951686c935287", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_NONMAL); + Test("thresh(2,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(100))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670164b16951686c935287", "20d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac6b6300670164b16951686c935287", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_NONMAL); // A threshold of 1 is valid. - Test("thresh(1,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac7c2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac935187", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_NONMAL); + Test("thresh(1,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac7c2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac935187", "20d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac7c20fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac935187", TESTMODE_VALID | TESTMODE_NEEDSIG | TESTMODE_NONMAL); // A threshold with a k larger than the number of subs is invalid - Test("thresh(3,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac7c2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac935187", TESTMODE_INVALID); + Test("thresh(3,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac7c2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac935187", "=", TESTMODE_INVALID); // A threshold with a k null is invalid - Test("thresh(0,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac7c2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac935187", TESTMODE_INVALID); + Test("thresh(0,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),sc:pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))", "2103d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65ac7c2103fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556ac935187", "=", TESTMODE_INVALID); // For CHECKMULTISIG the OP cost is the number of keys, but the stack size is the number of sigs (+1) + CONVERTER.SetContext(miniscript::MiniscriptContext::P2WSH); const auto ms_multi = miniscript::FromString("multi(1,03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)", CONVERTER); BOOST_CHECK(ms_multi); BOOST_CHECK_EQUAL(*ms_multi->GetOps(), 4); // 3 pubkeys + CMS @@ -538,18 +690,17 @@ BOOST_AUTO_TEST_CASE(fixed_tests) BOOST_CHECK(insane_sub && *insane_sub->ToString(CONVERTER) == "and_b(after(1),a:after(1000000000))"); // Timelock tests - Test("after(100)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // only heightlock - Test("after(1000000000)", "?", TESTMODE_VALID | TESTMODE_NONMAL); // only timelock - Test("or_b(l:after(100),al:after(1000000000))", "?", TESTMODE_VALID); // or_b(timelock, heighlock) valid - Test("and_b(after(100),a:after(1000000000))", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX); // and_b(timelock, heighlock) invalid + Test("after(100)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // only heightlock + Test("after(1000000000)", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL); // only timelock + Test("or_b(l:after(100),al:after(1000000000))", "?", "?", TESTMODE_VALID); // or_b(timelock, heighlock) valid + Test("and_b(after(100),a:after(1000000000))", "?", "?", TESTMODE_VALID | TESTMODE_NONMAL | TESTMODE_TIMELOCKMIX); // and_b(timelock, heighlock) invalid /* This is correctly detected as non-malleable but for the wrong reason. The type system assumes that branches 1 and 2 can be spent together to create a non-malleble witness, but because of mixing of timelocks they cannot be spent together. But since exactly one of the two after's can be satisfied, the witness involving the key cannot be malleated. */ - Test("thresh(2,ltv:after(1000000000),altv:after(100),a:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65))", "?", TESTMODE_VALID | TESTMODE_TIMELOCKMIX | TESTMODE_NONMAL); // thresh with k = 2 + Test("thresh(2,ltv:after(1000000000),altv:after(100),a:pk(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65))", "?", "?", TESTMODE_VALID | TESTMODE_TIMELOCKMIX | TESTMODE_NONMAL); // thresh with k = 2 // This is actually non-malleable in practice, but we cannot detect it in type system. See above rationale - Test("thresh(1,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(1000000000),altv:after(100))", "?", TESTMODE_VALID); // thresh with k = 1 - + Test("thresh(1,c:pk_k(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),altv:after(1000000000),altv:after(100))", "?", "?", TESTMODE_VALID); // thresh with k = 1 g_testdata.reset(); } |