mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-16 13:01:19 +02:00
simplicity: allow exact annex padding in policy
This commit is contained in:
parent
af697df271
commit
27c0cf5949
7 changed files with 48 additions and 9 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -3094,14 +3094,14 @@ uint32_t GenericTransactionSignatureChecker<T>::GetnIn() const
|
|||
}
|
||||
|
||||
template <class T>
|
||||
bool GenericTransactionSignatureChecker<T>::CheckSimplicity(const valtype& program, const valtype& witness, const rawElementsTapEnv& simplicityRawTap, int64_t budget, ScriptError* serror) const
|
||||
bool GenericTransactionSignatureChecker<T>::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);
|
||||
|
|
|
|||
|
|
@ -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<uint32_t>::max();
|
||||
}
|
||||
|
||||
virtual bool CheckSimplicity(const std::vector<unsigned char>& witness, const std::vector<unsigned char>& program, const rawElementsTapEnv& simplicityRawTap, int64_t budget, ScriptError* serror) const
|
||||
virtual bool CheckSimplicity(const std::vector<unsigned char>& witness, const std::vector<unsigned char>& 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<unsigned char>& program, const std::vector<unsigned char>& witness, const rawElementsTapEnv& simplicityRawTap, int64_t budget, ScriptError* serror) const override;
|
||||
bool CheckSimplicity(const std::vector<unsigned char>& program, const std::vector<unsigned char>& witness, const rawElementsTapEnv& simplicityRawTap, int64_t minCost, int64_t budget, ScriptError* serror) const override;
|
||||
};
|
||||
|
||||
using TransactionSignatureChecker = GenericTransactionSignatureChecker<CTransaction>;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ static std::map<std::string, unsigned int> 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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue