Skip to main content

miden_ace_codegen/
masm.rs

1//! Rendering support for the relation-local MASM ACE evaluator wrapper.
2
3use std::{format, string::String};
4
5use miden_core::{Felt, Word};
6use miden_crypto::stark::QuotientRecompositionInputs;
7
8use crate::AceError;
9
10/// Relation-specific inputs to the shared MASM constraint-evaluator renderer.
11#[derive(Clone, Debug)]
12pub struct MasmConstraintsEvalConfig<'a> {
13    /// Command responsible for regenerating the artifact, shown in its header.
14    pub generated_by: &'a str,
15    /// Relation-local module exposing `auxiliary_ace_inputs_ptr` and
16    /// `ace_circuit_stream_ptr`.
17    pub layout_module: &'a str,
18    /// Number of READ variables in the encoded ACE circuit.
19    pub num_inputs: usize,
20    /// Number of evaluation gates in the encoded ACE circuit.
21    pub num_eval_gates: usize,
22    /// Total encoded circuit-stream length, in felts.
23    pub stream_len: usize,
24    /// Length of the order-dependent stream prefix, in felts.
25    pub shuffle_prefix_len: usize,
26    /// Log2 of the longest periodic-column cycle across the relation's AIRs.
27    pub max_cycle_len_log: u32,
28    /// Depth of the relation's ACE registry Merkle tree.
29    pub registry_depth: usize,
30    /// Number of valid proof-order tags; higher registry slots are padding.
31    pub order_tag_count: usize,
32    /// Number of AIR instances in the relation.
33    pub num_airs: usize,
34    /// Relation-local inputs for reconstructing the quotient from its chunks.
35    pub quotient_inputs: QuotientRecompositionInputs<Felt>,
36    /// Digest of the order-invariant circuit-stream section.
37    pub common_commitment: Word,
38}
39
40/// Render the MASM wrapper that prepares ACE inputs, authenticates the selected circuit, and
41/// executes it.
42///
43/// Both the Miden VM and PVM use this renderer. Their circuit sizes, registry geometry, quotient
44/// inputs, and memory layouts remain relation-local parameters; the authentication and evaluation
45/// control flow has one source.
46pub fn render_masm_constraints_eval(
47    config: &MasmConstraintsEvalConfig<'_>,
48) -> Result<String, AceError> {
49    if !config.stream_len.is_multiple_of(8) {
50        return Err(AceError::InvalidInputLayout {
51            message: "ACE stream must be 8-felt aligned".into(),
52        });
53    }
54    if !config.shuffle_prefix_len.is_multiple_of(8)
55        || config.shuffle_prefix_len >= config.stream_len
56    {
57        return Err(AceError::InvalidInputLayout {
58            message: "ACE shuffle prefix must be a proper 8-felt-aligned stream prefix".into(),
59        });
60    }
61
62    let prefix_rows = config.shuffle_prefix_len / 8;
63    let common_rows = (config.stream_len - config.shuffle_prefix_len) / 8;
64    let common_commitment = config.common_commitment;
65    let quotient = config.quotient_inputs;
66
67    Ok(format!(
68        concat!(
69            "# GENERATED by `{generated_by}` — do not edit by hand.\n",
70            "use miden::core::stark::types\n",
71            "use miden::core::crypto::hashes::poseidon2\n",
72            "use miden::core::stark::constants\n",
73            "use miden::core::stark::constraints_eval_inputs\n",
74            "use {layout_module}\n\n",
75            "# CONSTANTS\n",
76            "# =================================================================================================\n\n",
77            "# Number of READ variables (inputs + constants) for the constraint evaluation circuit.\n",
78            "const NUM_INPUTS_CIRCUIT = {num_inputs}\n\n",
79            "# Number of evaluation gates in the constraint evaluation circuit\n",
80            "const NUM_EVAL_GATES_CIRCUIT = {num_eval_gates}\n\n",
81            "# Max cycle length for periodic columns\n",
82            "const MAX_CYCLE_LEN_LOG = {max_cycle_len_log}\n\n",
83            "# Depth of the ACE circuit registry tree.\n",
84            "const ACE_REGISTRY_DEPTH = {registry_depth}\n\n",
85            "# Number of valid proof-order tags (n! for n AIRs); higher slots are registry\n",
86            "# padding and must never be opened.\n",
87            "const ORDER_TAG_COUNT = {order_tag_count}\n\n",
88            "# Number of AIR instances in the relation.\n",
89            "const NUM_AIRS = {num_airs}\n\n",
90            "# Number of 8-felt blocks in each authenticated ACE circuit segment.\n",
91            "const ACE_PREFIX_BLOCKS = {prefix_rows}\n",
92            "const ACE_COMMON_BLOCKS = {common_rows}\n\n",
93            "# Quotient recomposition inputs derived from the circuit's quotient arity and the\n",
94            "# relation's PCS configuration. QUOTIENT_SHIFT_RATIO depends on arity;\n",
95            "# QUOTIENT_FIRST_SHIFT depends on the canonical LDE shift and blowup; and\n",
96            "# QUOTIENT_FIRST_WEIGHT depends on both.\n",
97            "const QUOTIENT_SHIFT_RATIO = {quotient_shift_ratio}\n",
98            "const QUOTIENT_FIRST_SHIFT = {quotient_first_shift}\n",
99            "const QUOTIENT_FIRST_WEIGHT = {quotient_first_weight}\n\n",
100            "# Poseidon2 digest of the order-invariant common section of the ACE circuit stream\n",
101            "# (common ops + root padding). The registry leaf for each ORDER_TAG is\n",
102            "# merge(H(constants | shuffle ops), ACE_COMMON_COMMITMENT).\n",
103            "const ACE_COMMON_COMMITMENT_0 = {common_commitment_0}\n",
104            "const ACE_COMMON_COMMITMENT_1 = {common_commitment_1}\n",
105            "const ACE_COMMON_COMMITMENT_2 = {common_commitment_2}\n",
106            "const ACE_COMMON_COMMITMENT_3 = {common_commitment_3}\n\n",
107            "# ERRORS\n",
108            "# =================================================================================================\n\n",
109            "const ERR_CIRCUIT_COMMITMENT_MISMATCH = \"merged ACE circuit segment digests do not match the registry commitment\"\n\n",
110            "const ERR_COMMON_SECTION_MISMATCH = \"common ACE circuit section does not match the compiled-in digest\"\n\n",
111            "# CONSTRAINT EVALUATION CHECKER\n",
112            "# =================================================================================================\n\n",
113            "#! Executes the constraints evaluation check for the proof order selected by ORDER_TAG.\n",
114            "#!\n",
115            "#! Inputs:  []\n",
116            "#! Outputs: []\n",
117            "pub proc execute_constraint_evaluation_check()\n",
118            "    push.ORDER_TAG_COUNT exec.constants::assert_valid_order_tag\n\n",
119            "    push.QUOTIENT_SHIFT_RATIO\n",
120            "    push.QUOTIENT_FIRST_SHIFT\n",
121            "    push.QUOTIENT_FIRST_WEIGHT\n",
122            "    exec.layout::auxiliary_ace_inputs_ptr\n",
123            "    exec.constants::air_trace_length_logs_ptr\n",
124            "    push.NUM_AIRS\n",
125            "    push.MAX_CYCLE_LEN_LOG\n",
126            "    exec.constraints_eval_inputs::set_up_auxiliary_inputs_ace\n\n",
127            "    exec.load_and_authenticate_ace_circuit\n\n",
128            "    push.NUM_EVAL_GATES_CIRCUIT\n",
129            "    push.NUM_INPUTS_CIRCUIT\n",
130            "    exec.constants::public_inputs_address_ptr mem_load\n",
131            "    eval_circuit\n",
132            "    drop drop drop\n",
133            "end\n\n",
134            "#! Loads and authenticates the ACE circuit selected by ORDER_TAG.\n",
135            "#!\n",
136            "#! The circuit stream is factored into two adv_pipe-aligned segments: a per-order\n",
137            "#! prefix [constants | shuffle ops] and an order-invariant common section\n",
138            "#! [common ops | root padding]. Both are hashed separately; the common digest is\n",
139            "#! pinned to the compiled-in ACE_COMMON_COMMITMENT, and the registry leaf\n",
140            "#! selected by ORDER_TAG must equal\n",
141            "#! merge(PREFIX_COMMITMENT, ACE_COMMON_COMMITMENT).\n",
142            "proc load_and_authenticate_ace_circuit()\n",
143            "    exec.load_ace_registry_commitment\n",
144            "    # => [LEAF]\n",
145            "    adv.push_mapval\n",
146            "    exec.layout::ace_circuit_stream_ptr\n",
147            "    padw padw padw\n",
148            "    # => [ZERO, ZERO, ZERO, ptr, LEAF]\n",
149            "    repeat.ACE_PREFIX_BLOCKS\n",
150            "        adv_pipe\n",
151            "        exec.poseidon2::permute\n",
152            "    end\n",
153            "    exec.poseidon2::squeeze_digest\n",
154            "    # => [PREFIX_COMMITMENT, ptr, LEAF]\n",
155            "    movup.4\n",
156            "    # => [ptr, PREFIX_COMMITMENT, LEAF]\n",
157            "    padw padw padw\n",
158            "    repeat.ACE_COMMON_BLOCKS\n",
159            "        adv_pipe\n",
160            "        exec.poseidon2::permute\n",
161            "    end\n",
162            "    exec.poseidon2::squeeze_digest\n",
163            "    # => [COMMON_COMMITMENT, ptr, PREFIX_COMMITMENT, LEAF]\n",
164            "    movup.4 drop\n",
165            "    # => [COMMON_COMMITMENT, PREFIX_COMMITMENT, LEAF]\n",
166            "    dupw push.ACE_COMMON_COMMITMENT_3.ACE_COMMON_COMMITMENT_2.ACE_COMMON_COMMITMENT_1.ACE_COMMON_COMMITMENT_0\n",
167            "    assert_eqw.err=ERR_COMMON_SECTION_MISMATCH\n",
168            "    # => [COMMON_COMMITMENT, PREFIX_COMMITMENT, LEAF]\n",
169            "    swapw\n",
170            "    # => [PREFIX_COMMITMENT, COMMON_COMMITMENT, LEAF]\n",
171            "    exec.poseidon2::merge\n",
172            "    # => [CIRCUIT_COMMITMENT, LEAF]\n",
173            "    assert_eqw.err=ERR_CIRCUIT_COMMITMENT_MISMATCH\n",
174            "end\n\n",
175            "#! Loads the ACE circuit commitment selected by ORDER_TAG from the registry tree.\n",
176            "proc load_ace_registry_commitment() -> types::Digest\n",
177            "    padw exec.constants::ace_registry_root_ptr mem_loadw_le\n",
178            "    exec.constants::get_order_tag\n",
179            "    push.ACE_REGISTRY_DEPTH\n",
180            "    mtree_get\n",
181            "    swapw dropw\n",
182            "end\n",
183        ),
184        generated_by = config.generated_by,
185        layout_module = config.layout_module,
186        num_inputs = config.num_inputs,
187        num_eval_gates = config.num_eval_gates,
188        max_cycle_len_log = config.max_cycle_len_log,
189        registry_depth = config.registry_depth,
190        order_tag_count = config.order_tag_count,
191        num_airs = config.num_airs,
192        quotient_shift_ratio = quotient.shift_ratio.as_canonical_u64(),
193        quotient_first_shift = quotient.first_shift.as_canonical_u64(),
194        quotient_first_weight = quotient.first_weight.as_canonical_u64(),
195        prefix_rows = prefix_rows,
196        common_rows = common_rows,
197        common_commitment_0 = common_commitment[0].as_canonical_u64(),
198        common_commitment_1 = common_commitment[1].as_canonical_u64(),
199        common_commitment_2 = common_commitment[2].as_canonical_u64(),
200        common_commitment_3 = common_commitment[3].as_canonical_u64(),
201    ))
202}