diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index d32dc2dd17..fa57174cd7 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -307,6 +307,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) // Check policy limits for Taproot spends: // - MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE limit for stack item size // - No annexes + // ELEMENTS: allow annexes for simplicity transactions if (witnessversion == 1 && witnessprogram.size() == WITNESS_V1_TAPROOT_SIZE && !p2sh) { // Missing witness; invalid by consensus rules if (i >= tx.witness.vtxinwit.size()) { @@ -315,8 +316,14 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) // Taproot spend (non-P2SH-wrapped, version 1, witness program size 32; see BIP 341) Span stack{tx.witness.vtxinwit[i].scriptWitness.stack}; if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) { - // Annexes are nonstandard as long as no semantics are defined for them. - return false; + SpanPopBack(stack); // drop the annex + const auto& control_block = SpanPopBack(stack); + // Annexes are allowed for Simplicity spends only + // checks for zero padding and exact size are done in CheckSimplicity + if (control_block.empty() || (control_block[0] & TAPROOT_LEAF_MASK) != TAPROOT_LEAF_TAPSIMPLICITY) { + // Annexes are nonstandard as long as no semantics are defined for them. + return false; + } } if (stack.size() >= 2) { // Script path spend (2 or more stack elements after removing optional annex) diff --git a/src/policy/policy.h b/src/policy/policy.h index f6534ae4de..2d7fed3601 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -129,7 +129,9 @@ static constexpr unsigned int STANDARD_SCRIPT_VERIFY_FLAGS{MANDATORY_SCRIPT_VERI SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION | SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS | SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE | - SCRIPT_VERIFY_SIMPLICITY}; + SCRIPT_VERIFY_SIMPLICITY | + SCRIPT_VERIFY_ANNEX_PADDING}; + /** For convenience, standard but not mandatory verify flags. */ static constexpr unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS{STANDARD_SCRIPT_VERIFY_FLAGS & ~MANDATORY_SCRIPT_VERIFY_FLAGS}; diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index bd888e4362..2357d02edf 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -3094,14 +3094,14 @@ uint32_t GenericTransactionSignatureChecker::GetnIn() const } template -bool GenericTransactionSignatureChecker::CheckSimplicity(const valtype& program, const valtype& witness, const rawElementsTapEnv& simplicityRawTap, int64_t budget, ScriptError* serror) const +bool GenericTransactionSignatureChecker::CheckSimplicity(const valtype& program, const valtype& witness, const rawElementsTapEnv& simplicityRawTap, int64_t minCost, int64_t budget, ScriptError* serror) const { simplicity_err error; elementsTapEnv* simplicityTapEnv = simplicity_elements_mallocTapEnv(&simplicityRawTap); assert(txdata->m_simplicity_tx_data); assert(simplicityTapEnv); - if (!simplicity_elements_execSimplicity(&error, nullptr, txdata->m_simplicity_tx_data.get(), nIn, simplicityTapEnv, txdata->m_hash_genesis_block.data(), 0, budget, nullptr, program.data(), program.size(), witness.data(), witness.size())) { + if (!simplicity_elements_execSimplicity(&error, nullptr, txdata->m_simplicity_tx_data.get(), nIn, simplicityTapEnv, txdata->m_hash_genesis_block.data(), minCost, budget, nullptr, program.data(), program.size(), witness.data(), witness.size())) { assert(!"simplicity_elements_execSimplicity internal error"); } simplicity_elements_freeTapEnv(simplicityTapEnv); @@ -3262,9 +3262,11 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, // BIP341 Taproot: 32-byte non-P2SH witness v1 program (which encodes a P2C-tweaked pubkey) if (!(flags & SCRIPT_VERIFY_TAPROOT)) return set_success(serror); if (stack.size() == 0) return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY); + valtype annex; if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) { // Drop annex (this is non-standard; see IsWitnessStandard) - const valtype& annex = SpanPopBack(stack); + // ELEMENTS: store the annex for CheckSimplicity + annex = SpanPopBack(stack); execdata.m_annex_hash = (HashWriter{} << annex).GetSHA256(); execdata.m_annex_present = true; } else { @@ -3306,7 +3308,27 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, simplicityRawTap.controlBlock = control.data(); simplicityRawTap.pathLen = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE; simplicityRawTap.scriptCMR = script.data(); - return checker.CheckSimplicity(simplicity_program, simplicity_witness, simplicityRawTap, budget, serror); + // If there is no annex, or we are in consensus checking mode, minCost is set to 0 which effectively disables any overweight cost checks. + int64_t minCost = 0; + if ((flags & SCRIPT_VERIFY_ANNEX_PADDING) && annex.size() > 0) { + valtype zero_padding(annex.size(), 0); + zero_padding[0] = ANNEX_TAG; + if (annex != zero_padding) { + return set_error(serror, SCRIPT_ERR_SIMPLICITY_PADDING_NONZERO); + } + // Compute what the budget would have been without the padding. + // budget includes the padding cost, so subtracting this stack item won't underflow. + minCost = budget - ::GetSerializeSize(annex); + // If the annex exists and is empty (i.e. its size is 1), then the only way the annex could be smaller is by eliminating it entirely. + // So we use the above computed value for minCost. Note: in that case the minCost is 2 WU less than the budget. + + // If the annex exists and is non-empty, then we add to minCost the value of an annex that contains one fewer byte. + if (zero_padding.size() > 1) { + zero_padding.pop_back(); + minCost += ::GetSerializeSize(zero_padding); + } + } + return checker.CheckSimplicity(simplicity_program, simplicity_witness, simplicityRawTap, minCost, budget, serror); } if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION) { return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION); diff --git a/src/script/interpreter.h b/src/script/interpreter.h index b1a510bd62..a749afe176 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -161,6 +161,10 @@ enum : uint32_t { // SCRIPT_VERIFY_SIMPLICITY = (1U << 23), + // Check exact annex padding policy for simplicity spends + // + SCRIPT_VERIFY_ANNEX_PADDING = (1U << 24), + // Constants to point to the highest flag in use. Add new flags above this line. // SCRIPT_VERIFY_END_MARKER @@ -345,7 +349,7 @@ public: return std::numeric_limits::max(); } - virtual bool CheckSimplicity(const std::vector& witness, const std::vector& program, const rawElementsTapEnv& simplicityRawTap, int64_t budget, ScriptError* serror) const + virtual bool CheckSimplicity(const std::vector& witness, const std::vector& program, const rawElementsTapEnv& simplicityRawTap, int64_t minCost, int64_t budget, ScriptError* serror) const { return false; } @@ -393,7 +397,7 @@ public: const PrecomputedTransactionData* GetPrecomputedTransactionData() const override; uint32_t GetnIn() const override; - bool CheckSimplicity(const std::vector& program, const std::vector& witness, const rawElementsTapEnv& simplicityRawTap, int64_t budget, ScriptError* serror) const override; + bool CheckSimplicity(const std::vector& program, const std::vector& witness, const rawElementsTapEnv& simplicityRawTap, int64_t minCost, int64_t budget, ScriptError* serror) const override; }; using TransactionSignatureChecker = GenericTransactionSignatureChecker; diff --git a/src/script/script_error.cpp b/src/script/script_error.cpp index b79c9e7b34..0b61d4e429 100644 --- a/src/script/script_error.cpp +++ b/src/script/script_error.cpp @@ -134,6 +134,8 @@ std::string ScriptErrorString(const ScriptError serror) return "EC scalar mult verify fail"; case SCRIPT_ERR_SIMPLICITY_WRONG_LENGTH: return "Simplicity witness has incorrect length"; + case SCRIPT_ERR_SIMPLICITY_PADDING_NONZERO: + return "Simplicity annex padding must be all zeros"; case SCRIPT_ERR_SIMPLICITY_DATA_OUT_OF_RANGE: return SIMPLICITY_ERR_MSG(SIMPLICITY_ERR_DATA_OUT_OF_RANGE); case SCRIPT_ERR_SIMPLICITY_DATA_OUT_OF_ORDER: diff --git a/src/script/script_error.h b/src/script/script_error.h index cb83adccca..4c5ceb7db1 100644 --- a/src/script/script_error.h +++ b/src/script/script_error.h @@ -99,6 +99,7 @@ typedef enum ScriptError_t /* Elements: Simplicity related errors */ SCRIPT_ERR_SIMPLICITY_WRONG_LENGTH, + SCRIPT_ERR_SIMPLICITY_PADDING_NONZERO, SCRIPT_ERR_SIMPLICITY_NOT_YET_IMPLEMENTED, SCRIPT_ERR_SIMPLICITY_DATA_OUT_OF_RANGE, SCRIPT_ERR_SIMPLICITY_DATA_OUT_OF_ORDER, diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index a4c9d32eb2..6b85571b53 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -71,6 +71,7 @@ static std::map mapFlagNames = { {std::string("DISCOURAGE_OP_SUCCESS"), (unsigned int)SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS}, {std::string("DISCOURAGE_UPGRADABLE_TAPROOT_VERSION"), (unsigned int)SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION}, {std::string("SIMPLICITY"), (unsigned int)SCRIPT_VERIFY_SIMPLICITY}, + {std::string("ANNEX_PADDING"), (unsigned int)SCRIPT_VERIFY_ANNEX_PADDING}, }; unsigned int ParseScriptFlags(std::string strFlags)