1use std::{format, string::String};
4
5use miden_core::{Felt, Word};
6use miden_crypto::stark::QuotientRecompositionInputs;
7
8use crate::AceError;
9
10#[derive(Clone, Debug)]
12pub struct MasmConstraintsEvalConfig<'a> {
13 pub generated_by: &'a str,
15 pub layout_module: &'a str,
18 pub num_inputs: usize,
20 pub num_eval_gates: usize,
22 pub stream_len: usize,
24 pub shuffle_prefix_len: usize,
26 pub max_cycle_len_log: u32,
28 pub registry_depth: usize,
30 pub order_tag_count: usize,
32 pub num_airs: usize,
34 pub quotient_inputs: QuotientRecompositionInputs<Felt>,
36 pub common_commitment: Word,
38}
39
40pub 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::crypto::hashes::poseidon2\n",
71 "use miden::core::stark::constants\n",
72 "use miden::core::stark::constraints_eval_inputs\n",
73 "use {layout_module}\n\n",
74 "# CONSTANTS\n",
75 "# =================================================================================================\n\n",
76 "# Number of READ variables (inputs + constants) for the constraint evaluation circuit.\n",
77 "const NUM_INPUTS_CIRCUIT = {num_inputs}\n\n",
78 "# Number of evaluation gates in the constraint evaluation circuit\n",
79 "const NUM_EVAL_GATES_CIRCUIT = {num_eval_gates}\n\n",
80 "# Max cycle length for periodic columns\n",
81 "const MAX_CYCLE_LEN_LOG = {max_cycle_len_log}\n\n",
82 "# Depth of the ACE circuit registry tree.\n",
83 "const ACE_REGISTRY_DEPTH = {registry_depth}\n\n",
84 "# Number of valid proof-order tags (n! for n AIRs); higher slots are registry\n",
85 "# padding and must never be opened.\n",
86 "const ORDER_TAG_COUNT = {order_tag_count}\n\n",
87 "# Number of AIR instances in the relation.\n",
88 "const NUM_AIRS = {num_airs}\n\n",
89 "# Number of 8-felt blocks in each authenticated ACE circuit segment.\n",
90 "const ACE_PREFIX_BLOCKS = {prefix_rows}\n",
91 "const ACE_COMMON_BLOCKS = {common_rows}\n\n",
92 "# Quotient recomposition inputs derived from the circuit's quotient arity and the\n",
93 "# relation's PCS configuration. QUOTIENT_SHIFT_RATIO depends on arity;\n",
94 "# QUOTIENT_FIRST_SHIFT depends on the canonical LDE shift and blowup; and\n",
95 "# QUOTIENT_FIRST_WEIGHT depends on both.\n",
96 "const QUOTIENT_SHIFT_RATIO = {quotient_shift_ratio}\n",
97 "const QUOTIENT_FIRST_SHIFT = {quotient_first_shift}\n",
98 "const QUOTIENT_FIRST_WEIGHT = {quotient_first_weight}\n\n",
99 "# Poseidon2 digest of the order-invariant common section of the ACE circuit stream\n",
100 "# (common ops + root padding). The registry leaf for each ORDER_TAG is\n",
101 "# merge(H(constants | shuffle ops), ACE_COMMON_COMMITMENT).\n",
102 "const ACE_COMMON_COMMITMENT_0 = {common_commitment_0}\n",
103 "const ACE_COMMON_COMMITMENT_1 = {common_commitment_1}\n",
104 "const ACE_COMMON_COMMITMENT_2 = {common_commitment_2}\n",
105 "const ACE_COMMON_COMMITMENT_3 = {common_commitment_3}\n\n",
106 "# ERRORS\n",
107 "# =================================================================================================\n\n",
108 "const ERR_CIRCUIT_COMMITMENT_MISMATCH = \"merged ACE circuit segment digests do not match the registry commitment\"\n\n",
109 "const ERR_COMMON_SECTION_MISMATCH = \"common ACE circuit section does not match the compiled-in digest\"\n\n",
110 "# CONSTRAINT EVALUATION CHECKER\n",
111 "# =================================================================================================\n\n",
112 "#! Executes the constraints evaluation check for the proof order selected by ORDER_TAG.\n",
113 "#!\n",
114 "#! Inputs: []\n",
115 "#! Outputs: []\n",
116 "pub proc execute_constraint_evaluation_check()\n",
117 " push.ORDER_TAG_COUNT exec.constants::assert_valid_order_tag\n\n",
118 " push.QUOTIENT_SHIFT_RATIO\n",
119 " push.QUOTIENT_FIRST_SHIFT\n",
120 " push.QUOTIENT_FIRST_WEIGHT\n",
121 " exec.layout::auxiliary_ace_inputs_ptr\n",
122 " exec.constants::air_trace_length_logs_ptr\n",
123 " push.NUM_AIRS\n",
124 " push.MAX_CYCLE_LEN_LOG\n",
125 " exec.constraints_eval_inputs::set_up_auxiliary_inputs_ace\n\n",
126 " exec.load_and_authenticate_ace_circuit\n\n",
127 " push.NUM_EVAL_GATES_CIRCUIT\n",
128 " push.NUM_INPUTS_CIRCUIT\n",
129 " exec.constants::public_inputs_address_ptr mem_load\n",
130 " eval_circuit\n",
131 " drop drop drop\n",
132 "end\n\n",
133 "#! Loads and authenticates the ACE circuit selected by ORDER_TAG.\n",
134 "#!\n",
135 "#! The circuit stream is factored into two adv_pipe-aligned segments: a per-order\n",
136 "#! prefix [constants | shuffle ops] and an order-invariant common section\n",
137 "#! [common ops | root padding]. Both are hashed separately; the common digest is\n",
138 "#! pinned to the compiled-in ACE_COMMON_COMMITMENT, and the registry leaf\n",
139 "#! selected by ORDER_TAG must equal\n",
140 "#! merge(PREFIX_COMMITMENT, ACE_COMMON_COMMITMENT).\n",
141 "proc load_and_authenticate_ace_circuit\n",
142 " exec.load_ace_registry_commitment\n",
143 " # => [LEAF]\n",
144 " adv.push_mapval\n",
145 " exec.layout::ace_circuit_stream_ptr\n",
146 " padw padw padw\n",
147 " # => [ZERO, ZERO, ZERO, ptr, LEAF]\n",
148 " repeat.ACE_PREFIX_BLOCKS\n",
149 " adv_pipe\n",
150 " exec.poseidon2::permute\n",
151 " end\n",
152 " exec.poseidon2::squeeze_digest\n",
153 " # => [PREFIX_COMMITMENT, ptr, LEAF]\n",
154 " movup.4\n",
155 " # => [ptr, PREFIX_COMMITMENT, LEAF]\n",
156 " padw padw padw\n",
157 " repeat.ACE_COMMON_BLOCKS\n",
158 " adv_pipe\n",
159 " exec.poseidon2::permute\n",
160 " end\n",
161 " exec.poseidon2::squeeze_digest\n",
162 " # => [COMMON_COMMITMENT, ptr, PREFIX_COMMITMENT, LEAF]\n",
163 " movup.4 drop\n",
164 " # => [COMMON_COMMITMENT, PREFIX_COMMITMENT, LEAF]\n",
165 " dupw push.ACE_COMMON_COMMITMENT_3.ACE_COMMON_COMMITMENT_2.ACE_COMMON_COMMITMENT_1.ACE_COMMON_COMMITMENT_0\n",
166 " assert_eqw.err=ERR_COMMON_SECTION_MISMATCH\n",
167 " # => [COMMON_COMMITMENT, PREFIX_COMMITMENT, LEAF]\n",
168 " swapw\n",
169 " # => [PREFIX_COMMITMENT, COMMON_COMMITMENT, LEAF]\n",
170 " exec.poseidon2::merge\n",
171 " # => [CIRCUIT_COMMITMENT, LEAF]\n",
172 " assert_eqw.err=ERR_CIRCUIT_COMMITMENT_MISMATCH\n",
173 "end\n\n",
174 "#! Loads the ACE circuit commitment selected by ORDER_TAG from the registry tree.\n",
175 "proc load_ace_registry_commitment\n",
176 " padw exec.constants::ace_registry_root_ptr mem_loadw_le\n",
177 " exec.constants::get_order_tag\n",
178 " push.ACE_REGISTRY_DEPTH\n",
179 " mtree_get\n",
180 " swapw dropw\n",
181 "end\n",
182 ),
183 generated_by = config.generated_by,
184 layout_module = config.layout_module,
185 num_inputs = config.num_inputs,
186 num_eval_gates = config.num_eval_gates,
187 max_cycle_len_log = config.max_cycle_len_log,
188 registry_depth = config.registry_depth,
189 order_tag_count = config.order_tag_count,
190 num_airs = config.num_airs,
191 quotient_shift_ratio = quotient.shift_ratio.as_canonical_u64(),
192 quotient_first_shift = quotient.first_shift.as_canonical_u64(),
193 quotient_first_weight = quotient.first_weight.as_canonical_u64(),
194 prefix_rows = prefix_rows,
195 common_rows = common_rows,
196 common_commitment_0 = common_commitment[0].as_canonical_u64(),
197 common_commitment_1 = common_commitment[1].as_canonical_u64(),
198 common_commitment_2 = common_commitment[2].as_canonical_u64(),
199 common_commitment_3 = common_commitment[3].as_canonical_u64(),
200 ))
201}