1414#include < span.h>
1515#include < chainparams.h> // Peg-out enforcement
1616
17+ extern " C" {
18+ #include < simplicity/bounded.h>
19+ #include < simplicity/elements/cost.h>
20+ #include < simplicity/errorCodes.h>
21+ }
22+
1723// ELEMENTS:
1824CAsset policyAsset;
1925
@@ -274,6 +280,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
274280 // Check policy limits for Taproot spends:
275281 // - MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE limit for stack item size
276282 // - No annexes
283+ // ELEMENTS: allow annexes for simplicity transactions
277284 if (witnessversion == 1 && witnessprogram.size () == WITNESS_V1_TAPROOT_SIZE && !p2sh) {
278285 // Missing witness; invalid by consensus rules
279286 if (i >= tx.witness .vtxinwit .size ()) {
@@ -282,8 +289,16 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
282289 // Taproot spend (non-P2SH-wrapped, version 1, witness program size 32; see BIP 341)
283290 Span stack{tx.witness .vtxinwit [i].scriptWitness .stack };
284291 if (stack.size () >= 2 && !stack.back ().empty () && stack.back ()[0 ] == ANNEX_TAG) {
292+ SpanPopBack (stack); // Ignore annex
293+ const auto & control_block = SpanPopBack (stack);
294+ if ((control_block[0 ] & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSIMPLICITY) {
295+ // Annexes are allowed for Simplicity spends
296+ // checks for zero padding and exact size are done in ExactAnnexPadding
297+ return true ;
298+ } else {
285299 // Annexes are nonstandard as long as no semantics are defined for them.
286300 return false ;
301+ }
287302 }
288303 if (stack.size () >= 2 ) {
289304 // Script path spend (2 or more stack elements after removing optional annex)
@@ -340,3 +355,92 @@ int64_t GetVirtualTransactionInputSize(const CTransaction& tx, const size_t nIn,
340355{
341356 return GetVirtualTransactionSize (GetTransactionInputWeight (tx, nIn), nSigOpCost, bytes_per_sigop);
342357}
358+
359+ bool ExactAnnexPadding (const CTransaction& tx, const CCoinsViewCache& mapInputs, std::string& reason)
360+ {
361+ if (tx.IsCoinBase ())
362+ return true ;
363+
364+ for (unsigned int i = 0 ; i < tx.vin .size (); i++)
365+ {
366+ if (tx.witness .vtxinwit .size () <= i || tx.witness .vtxinwit [i].scriptWitness .IsNull ())
367+ continue ;
368+
369+ const CTxOut &prev = tx.vin [i].m_is_pegin ? GetPeginOutputFromWitness (tx.witness .vtxinwit [i].m_pegin_witness ) : mapInputs.AccessCoin (tx.vin [i].prevout ).out ;
370+
371+ CScript prevScript = prev.scriptPubKey ;
372+
373+ // Skip P2SH
374+ if (prevScript.IsPayToScriptHash ())
375+ continue ;
376+
377+ int witnessversion = 0 ;
378+ std::vector<unsigned char > witnessprogram;
379+ if (!prevScript.IsWitnessProgram (witnessversion, witnessprogram))
380+ continue ;
381+
382+ // Only check taproot v1 spends
383+ if (witnessversion != 1 || witnessprogram.size () != WITNESS_V1_TAPROOT_SIZE)
384+ continue ;
385+
386+ Span stack{tx.witness .vtxinwit [i].scriptWitness .stack };
387+
388+ // Check for annex
389+ if (stack.size () < 2 || stack.back ().empty () || stack.back ()[0 ] != ANNEX_TAG)
390+ continue ;
391+
392+ const auto & annex = SpanPopBack (stack);
393+
394+ // Need at least 2 more elements (control block + script) for script path spend
395+ if (stack.size () < 2 )
396+ continue ;
397+
398+ const auto & control_block = SpanPopBack (stack);
399+ if (control_block.empty ())
400+ continue ;
401+
402+ // Only check Simplicity spends (leaf version 0xbe)
403+ if ((control_block[0 ] & TAPROOT_LEAF_MASK) != TAPROOT_LEAF_TAPSIMPLICITY)
404+ continue ;
405+
406+ // All annex bytes after 0x50 tag must be zero
407+ std::vector<unsigned char > zero_padding (annex.size (), 0 );
408+ zero_padding[0 ] = ANNEX_TAG;
409+ if (annex != zero_padding) {
410+ reason = " bad-annex-nonzero-padding" ;
411+ return false ;
412+ }
413+
414+ // Annex padding must provide exactly the right budget for the program's cost bound.
415+ // The program is the second-to-last stack item (after popping CMR script and control block).
416+ if (stack.size () < 1 )
417+ continue ;
418+
419+ SpanPopBack (stack); // drop the CMR
420+ if (stack.size () < 1 )
421+ continue ;
422+
423+ const auto & simplicity_program = SpanPopBack (stack);
424+
425+ // Compute the program's cost bound (in milli weight units)
426+ simplicity_err error;
427+ ubounded cost_bound;
428+ if (!simplicity_elements_computeCostBound (&error, &cost_bound, simplicity_program.data (), simplicity_program.size ())) {
429+ reason = " bad-annex-compute-cost" ;
430+ return false ;
431+ }
432+
433+ // Budget = serialized witness size + VALIDATION_WEIGHT_OFFSET
434+ // cost_bound is in milliWU, budget is in WU
435+ // For exact padding: budget must equal ceil(cost_bound / 1000)
436+ int64_t required_budget = ((int64_t )cost_bound + 999 ) / 1000 ;
437+ int64_t actual_budget = ::GetSerializeSize (tx.witness .vtxinwit [i].scriptWitness .stack , PROTOCOL_VERSION) + VALIDATION_WEIGHT_OFFSET;
438+
439+ if (actual_budget != required_budget) {
440+ reason = " bad-annex-padding-size" ;
441+ return false ;
442+ }
443+ }
444+
445+ return true ;
446+ }
0 commit comments