diff options
Diffstat (limited to 'src/script')
-rw-r--r-- | src/script/bitcoinconsensus.cpp | 42 | ||||
-rw-r--r-- | src/script/bitcoinconsensus.h | 19 | ||||
-rw-r--r-- | src/script/descriptor.cpp | 141 | ||||
-rw-r--r-- | src/script/miniscript.cpp | 34 | ||||
-rw-r--r-- | src/script/miniscript.h | 668 | ||||
-rw-r--r-- | src/script/sign.cpp | 284 | ||||
-rw-r--r-- | src/script/sign.h | 1 | ||||
-rw-r--r-- | src/script/signingprovider.cpp | 4 |
8 files changed, 868 insertions, 325 deletions
diff --git a/src/script/bitcoinconsensus.cpp b/src/script/bitcoinconsensus.cpp index 4fab481b39..71005cfb6e 100644 --- a/src/script/bitcoinconsensus.cpp +++ b/src/script/bitcoinconsensus.cpp @@ -72,14 +72,34 @@ static bool verify_flags(unsigned int flags) static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, CAmount amount, const unsigned char *txTo , unsigned int txToLen, + const UTXO *spentOutputs, unsigned int spentOutputsLen, unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err) { if (!verify_flags(flags)) { return set_error(err, bitcoinconsensus_ERR_INVALID_FLAGS); } + + if (flags & bitcoinconsensus_SCRIPT_FLAGS_VERIFY_TAPROOT && spentOutputs == nullptr) { + return set_error(err, bitcoinconsensus_ERR_SPENT_OUTPUTS_REQUIRED); + } + try { TxInputStream stream(PROTOCOL_VERSION, txTo, txToLen); CTransaction tx(deserialize, stream); + + std::vector<CTxOut> spent_outputs; + if (spentOutputs != nullptr) { + if (spentOutputsLen != tx.vin.size()) { + return set_error(err, bitcoinconsensus_ERR_SPENT_OUTPUTS_MISMATCH); + } + for (size_t i = 0; i < spentOutputsLen; i++) { + CScript spk = CScript(spentOutputs[i].scriptPubKey, spentOutputs[i].scriptPubKey + spentOutputs[i].scriptPubKeySize); + const CAmount& value = spentOutputs[i].value; + CTxOut tx_out = CTxOut(value, spk); + spent_outputs.push_back(tx_out); + } + } + if (nIn >= tx.vin.size()) return set_error(err, bitcoinconsensus_ERR_TX_INDEX); if (GetSerializeSize(tx, PROTOCOL_VERSION) != txToLen) @@ -89,18 +109,34 @@ static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptP set_error(err, bitcoinconsensus_ERR_OK); PrecomputedTransactionData txdata(tx); + + if (spentOutputs != nullptr && flags & bitcoinconsensus_SCRIPT_FLAGS_VERIFY_TAPROOT) { + txdata.Init(tx, std::move(spent_outputs)); + } + return VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), &tx.vin[nIn].scriptWitness, flags, TransactionSignatureChecker(&tx, nIn, amount, txdata, MissingDataBehavior::FAIL), nullptr); } catch (const std::exception&) { return set_error(err, bitcoinconsensus_ERR_TX_DESERIALIZE); // Error deserializing } } +int bitcoinconsensus_verify_script_with_spent_outputs(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, int64_t amount, + const unsigned char *txTo , unsigned int txToLen, + const UTXO *spentOutputs, unsigned int spentOutputsLen, + unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err) +{ + CAmount am(amount); + return ::verify_script(scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, spentOutputs, spentOutputsLen, nIn, flags, err); +} + int bitcoinconsensus_verify_script_with_amount(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, int64_t amount, const unsigned char *txTo , unsigned int txToLen, unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err) { CAmount am(amount); - return ::verify_script(scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, nIn, flags, err); + UTXO *spentOutputs = nullptr; + unsigned int spentOutputsLen = 0; + return ::verify_script(scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, spentOutputs, spentOutputsLen, nIn, flags, err); } @@ -113,7 +149,9 @@ int bitcoinconsensus_verify_script(const unsigned char *scriptPubKey, unsigned i } CAmount am(0); - return ::verify_script(scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, nIn, flags, err); + UTXO *spentOutputs = nullptr; + unsigned int spentOutputsLen = 0; + return ::verify_script(scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, spentOutputs, spentOutputsLen, nIn, flags, err); } unsigned int bitcoinconsensus_version() diff --git a/src/script/bitcoinconsensus.h b/src/script/bitcoinconsensus.h index f2f2ff8686..a202b5ba06 100644 --- a/src/script/bitcoinconsensus.h +++ b/src/script/bitcoinconsensus.h @@ -31,7 +31,7 @@ extern "C" { #endif -#define BITCOINCONSENSUS_API_VER 1 +#define BITCOINCONSENSUS_API_VER 2 typedef enum bitcoinconsensus_error_t { @@ -41,6 +41,8 @@ typedef enum bitcoinconsensus_error_t bitcoinconsensus_ERR_TX_DESERIALIZE, bitcoinconsensus_ERR_AMOUNT_REQUIRED, bitcoinconsensus_ERR_INVALID_FLAGS, + bitcoinconsensus_ERR_SPENT_OUTPUTS_REQUIRED, + bitcoinconsensus_ERR_SPENT_OUTPUTS_MISMATCH } bitcoinconsensus_error; /** Script verification flags */ @@ -53,11 +55,19 @@ enum bitcoinconsensus_SCRIPT_FLAGS_VERIFY_CHECKLOCKTIMEVERIFY = (1U << 9), // enable CHECKLOCKTIMEVERIFY (BIP65) bitcoinconsensus_SCRIPT_FLAGS_VERIFY_CHECKSEQUENCEVERIFY = (1U << 10), // enable CHECKSEQUENCEVERIFY (BIP112) bitcoinconsensus_SCRIPT_FLAGS_VERIFY_WITNESS = (1U << 11), // enable WITNESS (BIP141) + bitcoinconsensus_SCRIPT_FLAGS_VERIFY_TAPROOT = (1U << 17), // enable TAPROOT (BIPs 341 & 342) bitcoinconsensus_SCRIPT_FLAGS_VERIFY_ALL = bitcoinconsensus_SCRIPT_FLAGS_VERIFY_P2SH | bitcoinconsensus_SCRIPT_FLAGS_VERIFY_DERSIG | bitcoinconsensus_SCRIPT_FLAGS_VERIFY_NULLDUMMY | bitcoinconsensus_SCRIPT_FLAGS_VERIFY_CHECKLOCKTIMEVERIFY | - bitcoinconsensus_SCRIPT_FLAGS_VERIFY_CHECKSEQUENCEVERIFY | bitcoinconsensus_SCRIPT_FLAGS_VERIFY_WITNESS + bitcoinconsensus_SCRIPT_FLAGS_VERIFY_CHECKSEQUENCEVERIFY | bitcoinconsensus_SCRIPT_FLAGS_VERIFY_WITNESS | + bitcoinconsensus_SCRIPT_FLAGS_VERIFY_TAPROOT }; +typedef struct { + const unsigned char *scriptPubKey; + unsigned int scriptPubKeySize; + int64_t value; +} UTXO; + /// Returns 1 if the input nIn of the serialized transaction pointed to by /// txTo correctly spends the scriptPubKey pointed to by scriptPubKey under /// the additional constraints specified by flags. @@ -70,6 +80,11 @@ EXPORT_SYMBOL int bitcoinconsensus_verify_script_with_amount(const unsigned char const unsigned char *txTo , unsigned int txToLen, unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err); +EXPORT_SYMBOL int bitcoinconsensus_verify_script_with_spent_outputs(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, int64_t amount, + const unsigned char *txTo , unsigned int txToLen, + const UTXO *spentOutputs, unsigned int spentOutputsLen, + unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err); + EXPORT_SYMBOL unsigned int bitcoinconsensus_version(); #ifdef __cplusplus diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 2f3f2c7a1d..7e62d75583 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: @@ -1290,6 +1314,10 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S if (IsHex(str)) { std::vector<unsigned char> data = ParseHex(str); CPubKey pubkey(data); + if (pubkey.IsValid() && !pubkey.IsValidNonHybrid()) { + error = "Hybrid public keys are not allowed"; + return nullptr; + } if (pubkey.IsFullyValid()) { if (permit_uncompressed || pubkey.IsCompressed()) { return std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey, false); @@ -1385,8 +1413,16 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(uint32_t key_exp_index, const Span<c return std::make_unique<OriginPubkeyProvider>(key_exp_index, std::move(info), std::move(provider), apostrophe); } -std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider) +std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptContext ctx, const SigningProvider& provider) { + // Key cannot be hybrid + if (!pubkey.IsValidNonHybrid()) { + return nullptr; + } + // Uncompressed is only allowed in TOP and P2SH contexts + if (ctx != ParseScriptContext::TOP && ctx != ParseScriptContext::P2SH && !pubkey.IsCompressed()) { + return nullptr; + } std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, false); KeyOriginInfo info; if (provider.GetKeyOrigin(pubkey.GetID(), info)) { @@ -1397,9 +1433,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)) { @@ -1422,18 +1456,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; @@ -1447,11 +1495,20 @@ struct KeyParser { template<typename I> std::optional<Key> FromPKBytes(I begin, I end) const { assert(m_in); - CPubKey pubkey(begin, end); - if (pubkey.IsValid()) { - Key key = m_keys.size(); - m_keys.push_back(InferPubkey(pubkey, ParseScriptContext::P2WSH, *m_in)); - return key; + Key key = m_keys.size(); + if (miniscript::IsTapscript(m_script_ctx) && end - begin == 32) { + XOnlyPubKey pubkey; + std::copy(begin, end, pubkey.begin()); + if (auto pubkey_provider = InferPubkey(pubkey.GetEvenCorrespondingCPubKey(), ParseContext(), *m_in)) { + m_keys.push_back(std::move(pubkey_provider)); + return key; + } + } else if (!miniscript::IsTapscript(m_script_ctx)) { + CPubKey pubkey(begin, end); + if (auto pubkey_provider = InferPubkey(pubkey, ParseContext(), *m_in)) { + m_keys.push_back(std::move(pubkey_provider)); + return key; + } } return {}; } @@ -1465,12 +1522,18 @@ struct KeyParser { CKeyID keyid(hash); CPubKey pubkey; if (m_in->GetPubKey(keyid, pubkey)) { - Key key = m_keys.size(); - m_keys.push_back(InferPubkey(pubkey, ParseScriptContext::P2WSH, *m_in)); - return key; + if (auto pubkey_provider = InferPubkey(pubkey, ParseContext(), *m_in)) { + Key key = m_keys.size(); + m_keys.push_back(std::move(pubkey_provider)); + return key; + } } return {}; } + + miniscript::MiniscriptContext MsContext() const { + return m_script_ctx; + } }; /** Parse a script in a particular context. */ @@ -1496,8 +1559,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)) { @@ -1710,11 +1774,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 != "") { @@ -1749,6 +1814,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)); } } @@ -1795,8 +1861,8 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo if (txntype == TxoutType::PUBKEY && (ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH)) { CPubKey pubkey(data[0]); - if (pubkey.IsValid()) { - return std::make_unique<PKDescriptor>(InferPubkey(pubkey, ctx, provider)); + if (auto pubkey_provider = InferPubkey(pubkey, ctx, provider)) { + return std::make_unique<PKDescriptor>(std::move(pubkey_provider)); } } if (txntype == TxoutType::PUBKEYHASH && (ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH)) { @@ -1804,7 +1870,9 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo CKeyID keyid(hash); CPubKey pubkey; if (provider.GetPubKey(keyid, pubkey)) { - return std::make_unique<PKHDescriptor>(InferPubkey(pubkey, ctx, provider)); + if (auto pubkey_provider = InferPubkey(pubkey, ctx, provider)) { + return std::make_unique<PKHDescriptor>(std::move(pubkey_provider)); + } } } if (txntype == TxoutType::WITNESS_V0_KEYHASH && (ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH)) { @@ -1812,16 +1880,24 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo CKeyID keyid(hash); CPubKey pubkey; if (provider.GetPubKey(keyid, pubkey)) { - return std::make_unique<WPKHDescriptor>(InferPubkey(pubkey, ctx, provider)); + if (auto pubkey_provider = InferPubkey(pubkey, ParseScriptContext::P2WPKH, provider)) { + return std::make_unique<WPKHDescriptor>(std::move(pubkey_provider)); + } } } if (txntype == TxoutType::MULTISIG && (ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH || ctx == ParseScriptContext::P2WSH)) { + bool ok = true; std::vector<std::unique_ptr<PubkeyProvider>> providers; for (size_t i = 1; i + 1 < data.size(); ++i) { CPubKey pubkey(data[i]); - providers.push_back(InferPubkey(pubkey, ctx, provider)); + if (auto pubkey_provider = InferPubkey(pubkey, ctx, provider)) { + providers.push_back(std::move(pubkey_provider)); + } else { + ok = false; + break; + } } - return std::make_unique<MultisigDescriptor>((int)data[0][0], std::move(providers)); + if (ok) return std::make_unique<MultisigDescriptor>((int)data[0][0], std::move(providers)); } if (txntype == TxoutType::SCRIPTHASH && ctx == ParseScriptContext::TOP) { uint160 hash(data[0]); @@ -1882,8 +1958,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..76b952350b 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,63 +990,130 @@ 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); } internal::WitnessSize CalcWitnessSize() const { + const uint32_t sig_size = IsTapscript(m_script_ctx) ? 1 + 65 : 1 + 72; + const uint32_t pubkey_size = IsTapscript(m_script_ctx) ? 1 + 32 : 1 + 33; switch (fragment) { case Fragment::JUST_0: return {{}, 0}; case Fragment::JUST_1: case Fragment::OLDER: case Fragment::AFTER: return {0, {}}; - case Fragment::PK_K: return {1 + 72, 1}; - case Fragment::PK_H: return {1 + 72 + 1 + 33, 1 + 1 + 33}; + case Fragment::PK_K: return {sig_size, 1}; + case Fragment::PK_H: return {sig_size + pubkey_size, 1 + pubkey_size}; case Fragment::SHA256: case Fragment::RIPEMD160: case Fragment::HASH256: @@ -884,7 +1133,8 @@ private: case Fragment::OR_C: return {subs[0]->ws.sat | (subs[0]->ws.dsat + subs[1]->ws.sat), {}}; 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: return {k * sig_size + 1, k + 1}; + case Fragment::MULTI_A: return {k * sig_size + 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 +1175,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 +1483,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 +1530,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 +1557,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 +1585,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 +1630,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 +1743,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 +1775,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 +1783,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 +1838,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 +1871,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 +1884,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 +1949,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 +1958,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 +2017,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 +2088,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 +2108,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 +2141,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 +2244,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 +2272,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 +2318,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 +2443,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 +2509,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 +2533,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 +2583,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 +2616,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..251a8420f7 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) @@ -319,7 +433,7 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator case TxoutType::SCRIPTHASH: { uint160 h160{vSolutions[0]}; if (GetCScript(provider, sigdata, CScriptID{h160}, scriptRet)) { - ret.push_back(std::vector<unsigned char>(scriptRet.begin(), scriptRet.end())); + ret.emplace_back(scriptRet.begin(), scriptRet.end()); return true; } // Could not find redeemScript, add to missing @@ -328,7 +442,7 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator } case TxoutType::MULTISIG: { size_t required = vSolutions.front()[0]; - ret.push_back(valtype()); // workaround CHECKMULTISIG bug + ret.emplace_back(); // workaround CHECKMULTISIG bug for (size_t i = 1; i < vSolutions.size() - 1; ++i) { CPubKey pubkey = CPubKey(vSolutions[i]); // We need to always call CreateSig in order to fill sigdata with all @@ -342,7 +456,7 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator } bool ok = ret.size() == required + 1; for (size_t i = 0; i + ret.size() < required + 1; ++i) { - ret.push_back(valtype()); + ret.emplace_back(); } return ok; } @@ -352,7 +466,7 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator case TxoutType::WITNESS_V0_SCRIPTHASH: if (GetCScript(provider, sigdata, CScriptID{RIPEMD160(vSolutions[0])}, scriptRet)) { - ret.push_back(std::vector<unsigned char>(scriptRet.begin(), scriptRet.end())); + ret.emplace_back(scriptRet.begin(), scriptRet.end()); return true; } // Could not find witnessScript, add to missing @@ -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,11 +540,11 @@ 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; } - result.push_back(std::vector<unsigned char>(witnessscript.begin(), witnessscript.end())); + result.emplace_back(witnessscript.begin(), witnessscript.end()); sigdata.scriptWitness.stack = result; sigdata.witness = true; @@ -533,7 +561,7 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato if (!sigdata.witness) sigdata.scriptWitness.stack.clear(); if (P2SH) { - result.push_back(std::vector<unsigned char>(subscript.begin(), subscript.end())); + result.emplace_back(subscript.begin(), subscript.end()); } sigdata.scriptSig = PushAll(result); 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/script/signingprovider.cpp b/src/script/signingprovider.cpp index 168b3030cc..ff02ab5a12 100644 --- a/src/script/signingprovider.cpp +++ b/src/script/signingprovider.cpp @@ -368,6 +368,8 @@ TaprootBuilder& TaprootBuilder::Add(int depth, Span<const unsigned char> script, /* Construct NodeInfo object with leaf hash and (if track is true) also leaf information. */ NodeInfo node; node.hash = ComputeTapleafHash(leaf_version, script); + // due to bug in clang-tidy-17: + // NOLINTNEXTLINE(modernize-use-emplace) if (track) node.leaves.emplace_back(LeafInfo{std::vector<unsigned char>(script.begin(), script.end()), leaf_version, {}}); /* Insert into the branch. */ Insert(std::move(node), depth); @@ -569,7 +571,7 @@ std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>> TaprootBui assert(leaf.merkle_branch.size() <= TAPROOT_CONTROL_MAX_NODE_COUNT); uint8_t depth = (uint8_t)leaf.merkle_branch.size(); uint8_t leaf_ver = (uint8_t)leaf.leaf_version; - tuples.push_back(std::make_tuple(depth, leaf_ver, leaf.script)); + tuples.emplace_back(depth, leaf_ver, leaf.script); } } return tuples; |