Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions src/policy/policy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
#include <span.h>
#include <chainparams.h> // Peg-out enforcement

extern "C" {
#include <simplicity/bounded.h>
#include <simplicity/elements/cost.h>
#include <simplicity/errorCodes.h>
}

// ELEMENTS:
CAsset policyAsset;

Expand Down Expand Up @@ -274,6 +280,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()) {
Expand All @@ -282,8 +289,16 @@ 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) {
SpanPopBack(stack); // Ignore annex
const auto& control_block = SpanPopBack(stack);
if ((control_block[0] & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSIMPLICITY) {
// Annexes are allowed for Simplicity spends
// checks for zero padding and exact size are done in ExactAnnexPadding
return true;
} else {
// 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)
Expand Down Expand Up @@ -340,3 +355,92 @@ int64_t GetVirtualTransactionInputSize(const CTransaction& tx, const size_t nIn,
{
return GetVirtualTransactionSize(GetTransactionInputWeight(tx, nIn), nSigOpCost, bytes_per_sigop);
}

bool ExactAnnexPadding(const CTransaction& tx, const CCoinsViewCache& mapInputs, std::string& reason)
{
if (tx.IsCoinBase())
return true;

for (unsigned int i = 0; i < tx.vin.size(); i++)
{
if (tx.witness.vtxinwit.size() <= i || tx.witness.vtxinwit[i].scriptWitness.IsNull())
continue;

const CTxOut &prev = tx.vin[i].m_is_pegin ? GetPeginOutputFromWitness(tx.witness.vtxinwit[i].m_pegin_witness) : mapInputs.AccessCoin(tx.vin[i].prevout).out;

CScript prevScript = prev.scriptPubKey;

// Skip P2SH
if (prevScript.IsPayToScriptHash())
continue;

int witnessversion = 0;
std::vector<unsigned char> witnessprogram;
if (!prevScript.IsWitnessProgram(witnessversion, witnessprogram))
continue;

// Only check taproot v1 spends
if (witnessversion != 1 || witnessprogram.size() != WITNESS_V1_TAPROOT_SIZE)
continue;

Span stack{tx.witness.vtxinwit[i].scriptWitness.stack};

// Check for annex
if (stack.size() < 2 || stack.back().empty() || stack.back()[0] != ANNEX_TAG)
continue;

const auto& annex = SpanPopBack(stack);

// Need at least 2 more elements (control block + script) for script path spend
if (stack.size() < 2)
continue;

const auto& control_block = SpanPopBack(stack);
if (control_block.empty())
continue;

// Only check Simplicity spends (leaf version 0xbe)
if ((control_block[0] & TAPROOT_LEAF_MASK) != TAPROOT_LEAF_TAPSIMPLICITY)
continue;

// All annex bytes after 0x50 tag must be zero
std::vector<unsigned char> zero_padding(annex.size(), 0);
zero_padding[0] = ANNEX_TAG;
if (annex != zero_padding) {
reason = "bad-annex-nonzero-padding";
return false;
}

// Annex padding must provide exactly the right budget for the program's cost bound.
// The program is the second-to-last stack item (after popping CMR script and control block).
if (stack.size() < 1)
continue;

SpanPopBack(stack); // drop the CMR
if (stack.size() < 1)
continue;

const auto& simplicity_program = SpanPopBack(stack);

// Compute the program's cost bound (in milli weight units)
simplicity_err error;
ubounded cost_bound;
if (!simplicity_elements_computeCostBound(&error, &cost_bound, simplicity_program.data(), simplicity_program.size())) {
reason = "bad-annex-compute-cost";
return false;
}

// Budget = serialized witness size + VALIDATION_WEIGHT_OFFSET
// cost_bound is in milliWU, budget is in WU
// For exact padding: budget must equal ceil(cost_bound / 1000)
int64_t required_budget = ((int64_t)cost_bound + 999) / 1000;
int64_t actual_budget = ::GetSerializeSize(tx.witness.vtxinwit[i].scriptWitness.stack, PROTOCOL_VERSION) + VALIDATION_WEIGHT_OFFSET;

if (actual_budget != required_budget) {
reason = "bad-annex-padding-size";
return false;
}
}

return true;
}
7 changes: 7 additions & 0 deletions src/policy/policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,13 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
*/
bool IsIssuanceInMoneyRange(const CTransaction& tx);

/**
* Check that Simplicity transactions have exact annex padding:
* - Annex bytes (after 0x50 tag) must all be zero
* - Annex must be exactly the right size for the program's cost bound
*/
bool ExactAnnexPadding(const CTransaction& tx, const CCoinsViewCache& mapInputs, std::string& reason);

/** Compute the virtual transaction size (weight reinterpreted as bytes). */
int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost, unsigned int bytes_per_sigop);
int64_t GetVirtualTransactionSize(const CTransaction& tx, int64_t nSigOpCost, unsigned int bytes_per_sigop);
Expand Down
2 changes: 2 additions & 0 deletions src/simplicity/elements-sources.mk
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ ELEMENTS_SIMPLICITY_INCLUDE_DIR_INT = %reldir%/include
ELEMENTS_SIMPLICITY_DIST_HEADERS_INT =
ELEMENTS_SIMPLICITY_DIST_HEADERS_INT += %reldir%/include/simplicity/errorCodes.h
ELEMENTS_SIMPLICITY_DIST_HEADERS_INT += %reldir%/include/simplicity/elements/cmr.h
ELEMENTS_SIMPLICITY_DIST_HEADERS_INT += %reldir%/include/simplicity/elements/cost.h
ELEMENTS_SIMPLICITY_DIST_HEADERS_INT += %reldir%/include/simplicity/elements/env.h
ELEMENTS_SIMPLICITY_DIST_HEADERS_INT += %reldir%/include/simplicity/elements/exec.h

Expand All @@ -24,6 +25,7 @@ ELEMENTS_SIMPLICITY_LIB_SOURCES_INT += %reldir%/type.c
ELEMENTS_SIMPLICITY_LIB_SOURCES_INT += %reldir%/typeInference.c

ELEMENTS_SIMPLICITY_LIB_SOURCES_INT += %reldir%/elements/cmr.c
ELEMENTS_SIMPLICITY_LIB_SOURCES_INT += %reldir%/elements/cost.c
ELEMENTS_SIMPLICITY_LIB_SOURCES_INT += %reldir%/elements/env.c
ELEMENTS_SIMPLICITY_LIB_SOURCES_INT += %reldir%/elements/exec.c
ELEMENTS_SIMPLICITY_LIB_SOURCES_INT += %reldir%/elements/elementsJets.c
Expand Down
65 changes: 65 additions & 0 deletions src/simplicity/elements/cost.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#include <simplicity/elements/cost.h>

#include "../bounded.h"
#include "../deserialize.h"
#include "../eval.h"
#include "../limitations.h"
#include "../simplicity_alloc.h"
#include "../simplicity_assert.h"
#include "../typeInference.h"
#include "primitive.h"

/* Deserialize a Simplicity 'program', perform type inference, and compute its cost bound.
*
* If at any time malloc fails then '*error' is set to 'SIMPLICITY_ERR_MALLOC' and 'false' is returned.
* Otherwise, 'true' is returned indicating that the result was successfully computed and returned in the '*error' value.
*
* If the operation completes successfully then '*error' is set to 'SIMPLICITY_NO_ERROR',
* and '*cost_bound' is set to the program's cost bound in milli weight units.
*
* Precondition: NULL != error;
* NULL != cost_bound;
* unsigned char program[program_len]
*/
bool simplicity_elements_computeCostBound(simplicity_err* error, ubounded* cost_bound , const unsigned char* program, size_t program_len) {
simplicity_assert(NULL != error);
simplicity_assert(NULL != cost_bound);
simplicity_assert(NULL != program || 0 == program_len);

dag_node* dag = NULL;
type* type_dag = NULL;
combinator_counters census;

bitstream stream = initializeBitstream(program, program_len);
int_fast32_t dag_len = simplicity_decodeMallocDag(&dag, simplicity_elements_decodeJet, &census, &stream);
if (dag_len <= 0) {
simplicity_assert(dag_len < 0);
*error = (simplicity_err)dag_len;
simplicity_free(dag);
return IS_PERMANENT(*error);
}

simplicity_assert(NULL != dag);
simplicity_assert((uint_fast32_t)dag_len <= DAG_LEN_MAX);
*error = simplicity_closeBitstream(&stream);
if (!IS_OK(*error)) {
simplicity_free(dag);
return IS_PERMANENT(*error);
}

*error = simplicity_mallocTypeInference(&type_dag, simplicity_elements_mallocBoundVars, dag, dag_len, &census);
if (!IS_OK(*error)) {
simplicity_free(type_dag);
simplicity_free(dag);
return IS_PERMANENT(*error);
}

simplicity_assert(NULL != type_dag);

ubounded cellsBound, UWORDBound, frameBound;
*error = simplicity_analyseBounds(&cellsBound, &UWORDBound, &frameBound, cost_bound, UBOUNDED_MAX, 0, UBOUNDED_MAX, dag, type_dag, dag_len);

simplicity_free(type_dag);
simplicity_free(dag);
return IS_PERMANENT(*error);
}
22 changes: 22 additions & 0 deletions src/simplicity/include/simplicity/elements/cost.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#ifndef SIMPLICITY_ELEMENTS_COST_H
#define SIMPLICITY_ELEMENTS_COST_H

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <simplicity/errorCodes.h>

/* Deserialize a Simplicity 'program', perform type inference, and compute its cost bound.
*
* If at any time malloc fails then '*error' is set to 'SIMPLICITY_ERR_MALLOC' and 'false' is returned.
* Otherwise, 'true' is returned indicating that the result was successfully computed and returned in the '*error' value.
*
* If the operation completes successfully then '*error' is set to 'SIMPLICITY_NO_ERROR',
* and '*cost_bound' is set to the program's cost bound in milli weight units.
*
* Precondition: NULL != error;
* NULL != cost_bound;
* unsigned char program[program_len]
*/
extern bool simplicity_elements_computeCostBound(simplicity_err* error, uint_least32_t* cost_bound, const unsigned char* program, size_t program_len);
#endif
6 changes: 6 additions & 0 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,12 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
if (tx.HasWitness() && fRequireStandard && !IsWitnessStandard(tx, m_view))
return state.Invalid(TxValidationResult::TX_WITNESS_MUTATED, "bad-witness-nonstandard");

// Check Simplicity transactions for exact annex padding
std::string annex_reason;
if (tx.HasWitness() && fRequireStandard && !ExactAnnexPadding(tx, m_view, annex_reason)) {
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, annex_reason);
}

int64_t nSigOpsCost = GetTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS);

// We only consider policyAsset
Expand Down
Loading