miden-ace-codegen 0.31.1

ACE circuit codegen for Plonky3-based Miden AIRs.
Documentation
//! Rendering support for the relation-local MASM ACE evaluator wrapper.

use std::{format, string::String};

use miden_core::{Felt, Word};
use miden_crypto::stark::QuotientRecompositionInputs;

use crate::AceError;

/// Relation-specific inputs to the shared MASM constraint-evaluator renderer.
#[derive(Clone, Debug)]
pub struct MasmConstraintsEvalConfig<'a> {
    /// Command responsible for regenerating the artifact, shown in its header.
    pub generated_by: &'a str,
    /// Relation-local module exposing `auxiliary_ace_inputs_ptr` and
    /// `ace_circuit_stream_ptr`.
    pub layout_module: &'a str,
    /// Number of READ variables in the encoded ACE circuit.
    pub num_inputs: usize,
    /// Number of evaluation gates in the encoded ACE circuit.
    pub num_eval_gates: usize,
    /// Total encoded circuit-stream length, in felts.
    pub stream_len: usize,
    /// Length of the order-dependent stream prefix, in felts.
    pub shuffle_prefix_len: usize,
    /// Log2 of the longest periodic-column cycle across the relation's AIRs.
    pub max_cycle_len_log: u32,
    /// Depth of the relation's ACE registry Merkle tree.
    pub registry_depth: usize,
    /// Number of valid proof-order tags; higher registry slots are padding.
    pub order_tag_count: usize,
    /// Number of AIR instances in the relation.
    pub num_airs: usize,
    /// Relation-local inputs for reconstructing the quotient from its chunks.
    pub quotient_inputs: QuotientRecompositionInputs<Felt>,
    /// Digest of the order-invariant circuit-stream section.
    pub common_commitment: Word,
}

/// Render the MASM wrapper that prepares ACE inputs, authenticates the selected circuit, and
/// executes it.
///
/// Both the Miden VM and PVM use this renderer. Their circuit sizes, registry geometry, quotient
/// inputs, and memory layouts remain relation-local parameters; the authentication and evaluation
/// control flow has one source.
pub fn render_masm_constraints_eval(
    config: &MasmConstraintsEvalConfig<'_>,
) -> Result<String, AceError> {
    if !config.stream_len.is_multiple_of(8) {
        return Err(AceError::InvalidInputLayout {
            message: "ACE stream must be 8-felt aligned".into(),
        });
    }
    if !config.shuffle_prefix_len.is_multiple_of(8)
        || config.shuffle_prefix_len >= config.stream_len
    {
        return Err(AceError::InvalidInputLayout {
            message: "ACE shuffle prefix must be a proper 8-felt-aligned stream prefix".into(),
        });
    }

    let prefix_rows = config.shuffle_prefix_len / 8;
    let common_rows = (config.stream_len - config.shuffle_prefix_len) / 8;
    let common_commitment = config.common_commitment;
    let quotient = config.quotient_inputs;

    Ok(format!(
        concat!(
            "# GENERATED by `{generated_by}` — do not edit by hand.\n",
            "use miden::core::crypto::hashes::poseidon2\n",
            "use miden::core::stark::constants\n",
            "use miden::core::stark::constraints_eval_inputs\n",
            "use {layout_module}\n\n",
            "# CONSTANTS\n",
            "# =================================================================================================\n\n",
            "# Number of READ variables (inputs + constants) for the constraint evaluation circuit.\n",
            "const NUM_INPUTS_CIRCUIT = {num_inputs}\n\n",
            "# Number of evaluation gates in the constraint evaluation circuit\n",
            "const NUM_EVAL_GATES_CIRCUIT = {num_eval_gates}\n\n",
            "# Max cycle length for periodic columns\n",
            "const MAX_CYCLE_LEN_LOG = {max_cycle_len_log}\n\n",
            "# Depth of the ACE circuit registry tree.\n",
            "const ACE_REGISTRY_DEPTH = {registry_depth}\n\n",
            "# Number of valid proof-order tags (n! for n AIRs); higher slots are registry\n",
            "# padding and must never be opened.\n",
            "const ORDER_TAG_COUNT = {order_tag_count}\n\n",
            "# Number of AIR instances in the relation.\n",
            "const NUM_AIRS = {num_airs}\n\n",
            "# Number of 8-felt blocks in each authenticated ACE circuit segment.\n",
            "const ACE_PREFIX_BLOCKS = {prefix_rows}\n",
            "const ACE_COMMON_BLOCKS = {common_rows}\n\n",
            "# Quotient recomposition inputs derived from the circuit's quotient arity and the\n",
            "# relation's PCS configuration. QUOTIENT_SHIFT_RATIO depends on arity;\n",
            "# QUOTIENT_FIRST_SHIFT depends on the canonical LDE shift and blowup; and\n",
            "# QUOTIENT_FIRST_WEIGHT depends on both.\n",
            "const QUOTIENT_SHIFT_RATIO = {quotient_shift_ratio}\n",
            "const QUOTIENT_FIRST_SHIFT = {quotient_first_shift}\n",
            "const QUOTIENT_FIRST_WEIGHT = {quotient_first_weight}\n\n",
            "# Poseidon2 digest of the order-invariant common section of the ACE circuit stream\n",
            "# (common ops + root padding). The registry leaf for each ORDER_TAG is\n",
            "# merge(H(constants | shuffle ops), ACE_COMMON_COMMITMENT).\n",
            "const ACE_COMMON_COMMITMENT_0 = {common_commitment_0}\n",
            "const ACE_COMMON_COMMITMENT_1 = {common_commitment_1}\n",
            "const ACE_COMMON_COMMITMENT_2 = {common_commitment_2}\n",
            "const ACE_COMMON_COMMITMENT_3 = {common_commitment_3}\n\n",
            "# ERRORS\n",
            "# =================================================================================================\n\n",
            "const ERR_CIRCUIT_COMMITMENT_MISMATCH = \"merged ACE circuit segment digests do not match the registry commitment\"\n\n",
            "const ERR_COMMON_SECTION_MISMATCH = \"common ACE circuit section does not match the compiled-in digest\"\n\n",
            "# CONSTRAINT EVALUATION CHECKER\n",
            "# =================================================================================================\n\n",
            "#! Executes the constraints evaluation check for the proof order selected by ORDER_TAG.\n",
            "#!\n",
            "#! Inputs:  []\n",
            "#! Outputs: []\n",
            "pub proc execute_constraint_evaluation_check()\n",
            "    push.ORDER_TAG_COUNT exec.constants::assert_valid_order_tag\n\n",
            "    push.QUOTIENT_SHIFT_RATIO\n",
            "    push.QUOTIENT_FIRST_SHIFT\n",
            "    push.QUOTIENT_FIRST_WEIGHT\n",
            "    exec.layout::auxiliary_ace_inputs_ptr\n",
            "    exec.constants::air_trace_length_logs_ptr\n",
            "    push.NUM_AIRS\n",
            "    push.MAX_CYCLE_LEN_LOG\n",
            "    exec.constraints_eval_inputs::set_up_auxiliary_inputs_ace\n\n",
            "    exec.load_and_authenticate_ace_circuit\n\n",
            "    push.NUM_EVAL_GATES_CIRCUIT\n",
            "    push.NUM_INPUTS_CIRCUIT\n",
            "    exec.constants::public_inputs_address_ptr mem_load\n",
            "    eval_circuit\n",
            "    drop drop drop\n",
            "end\n\n",
            "#! Loads and authenticates the ACE circuit selected by ORDER_TAG.\n",
            "#!\n",
            "#! The circuit stream is factored into two adv_pipe-aligned segments: a per-order\n",
            "#! prefix [constants | shuffle ops] and an order-invariant common section\n",
            "#! [common ops | root padding]. Both are hashed separately; the common digest is\n",
            "#! pinned to the compiled-in ACE_COMMON_COMMITMENT, and the registry leaf\n",
            "#! selected by ORDER_TAG must equal\n",
            "#! merge(PREFIX_COMMITMENT, ACE_COMMON_COMMITMENT).\n",
            "proc load_and_authenticate_ace_circuit\n",
            "    exec.load_ace_registry_commitment\n",
            "    # => [LEAF]\n",
            "    adv.push_mapval\n",
            "    exec.layout::ace_circuit_stream_ptr\n",
            "    padw padw padw\n",
            "    # => [ZERO, ZERO, ZERO, ptr, LEAF]\n",
            "    repeat.ACE_PREFIX_BLOCKS\n",
            "        adv_pipe\n",
            "        exec.poseidon2::permute\n",
            "    end\n",
            "    exec.poseidon2::squeeze_digest\n",
            "    # => [PREFIX_COMMITMENT, ptr, LEAF]\n",
            "    movup.4\n",
            "    # => [ptr, PREFIX_COMMITMENT, LEAF]\n",
            "    padw padw padw\n",
            "    repeat.ACE_COMMON_BLOCKS\n",
            "        adv_pipe\n",
            "        exec.poseidon2::permute\n",
            "    end\n",
            "    exec.poseidon2::squeeze_digest\n",
            "    # => [COMMON_COMMITMENT, ptr, PREFIX_COMMITMENT, LEAF]\n",
            "    movup.4 drop\n",
            "    # => [COMMON_COMMITMENT, PREFIX_COMMITMENT, LEAF]\n",
            "    dupw push.ACE_COMMON_COMMITMENT_3.ACE_COMMON_COMMITMENT_2.ACE_COMMON_COMMITMENT_1.ACE_COMMON_COMMITMENT_0\n",
            "    assert_eqw.err=ERR_COMMON_SECTION_MISMATCH\n",
            "    # => [COMMON_COMMITMENT, PREFIX_COMMITMENT, LEAF]\n",
            "    swapw\n",
            "    # => [PREFIX_COMMITMENT, COMMON_COMMITMENT, LEAF]\n",
            "    exec.poseidon2::merge\n",
            "    # => [CIRCUIT_COMMITMENT, LEAF]\n",
            "    assert_eqw.err=ERR_CIRCUIT_COMMITMENT_MISMATCH\n",
            "end\n\n",
            "#! Loads the ACE circuit commitment selected by ORDER_TAG from the registry tree.\n",
            "proc load_ace_registry_commitment\n",
            "    padw exec.constants::ace_registry_root_ptr mem_loadw_le\n",
            "    exec.constants::get_order_tag\n",
            "    push.ACE_REGISTRY_DEPTH\n",
            "    mtree_get\n",
            "    swapw dropw\n",
            "end\n",
        ),
        generated_by = config.generated_by,
        layout_module = config.layout_module,
        num_inputs = config.num_inputs,
        num_eval_gates = config.num_eval_gates,
        max_cycle_len_log = config.max_cycle_len_log,
        registry_depth = config.registry_depth,
        order_tag_count = config.order_tag_count,
        num_airs = config.num_airs,
        quotient_shift_ratio = quotient.shift_ratio.as_canonical_u64(),
        quotient_first_shift = quotient.first_shift.as_canonical_u64(),
        quotient_first_weight = quotient.first_weight.as_canonical_u64(),
        prefix_rows = prefix_rows,
        common_rows = common_rows,
        common_commitment_0 = common_commitment[0].as_canonical_u64(),
        common_commitment_1 = common_commitment[1].as_canonical_u64(),
        common_commitment_2 = common_commitment[2].as_canonical_u64(),
        common_commitment_3 = common_commitment[3].as_canonical_u64(),
    ))
}