miden_ace_codegen/
encode.rs1use miden_core::{Felt, Word, crypto::hash::Poseidon2};
13use miden_crypto::field::ExtensionField;
14
15use crate::{
16 AceError,
17 circuit::{AceCircuit, AceNode, AceOp, AceOpNode},
18};
19
20const BASE_FELTS_PER_EF: usize = crate::EXT_DEGREE;
25const ACE_READ_ROW_EF_NODES: usize = 2;
27pub(crate) const CONST_EF_ALIGN: usize = 2;
29pub(crate) const ADV_PIPE_BLOCK_FELTS: usize = 8;
32const MAX_NUM_ACE_NODES: usize = (1 << 30) - 1;
37
38#[derive(Debug, Clone)]
45pub struct EncodedCircuit {
46 num_vars: usize,
47 num_ops: usize,
48 instructions: Vec<Felt>,
49}
50
51impl EncodedCircuit {
52 pub fn num_read_rows(&self) -> usize {
54 self.num_vars() / ACE_READ_ROW_EF_NODES
55 }
56
57 pub fn num_eval_rows(&self) -> usize {
59 self.num_ops
60 }
61
62 pub fn num_vars(&self) -> usize {
64 self.num_vars
65 }
66
67 pub fn num_inputs(&self) -> usize {
69 self.num_vars - self.num_constants()
70 }
71
72 pub fn num_constants(&self) -> usize {
74 (self.instructions.len() - self.num_ops) / BASE_FELTS_PER_EF
75 }
76
77 pub fn num_nodes(&self) -> usize {
79 self.num_vars + self.num_ops
80 }
81
82 pub fn instructions(&self) -> &[Felt] {
84 &self.instructions
85 }
86
87 pub fn size_in_felt(&self) -> usize {
89 self.instructions.len()
90 }
91
92 pub fn circuit_hash(&self) -> Word {
97 Poseidon2::hash_elements(self.instructions())
98 }
99}
100
101#[derive(Debug, Clone, Copy)]
108pub(crate) struct StreamGeometry {
109 input_start: usize,
110 constants_start: usize,
111 ops_start: usize,
112}
113
114impl StreamGeometry {
115 pub(crate) fn from_counts(num_inputs: usize, num_constants: usize, num_ops: usize) -> Self {
120 let num_const_nodes = num_constants.next_multiple_of(CONST_EF_ALIGN);
121 let const_felts = num_const_nodes * BASE_FELTS_PER_EF;
122 let num_ops_padded =
123 (const_felts + num_ops).next_multiple_of(ADV_PIPE_BLOCK_FELTS) - const_felts;
124 Self::new(num_inputs, num_const_nodes, num_ops_padded)
125 }
126
127 fn new(num_inputs: usize, num_constants: usize, num_ops: usize) -> Self {
129 let num_nodes = num_inputs + num_constants + num_ops;
130 let input_start = num_nodes - 1;
131 let constants_start = input_start - num_inputs;
132 let ops_start = constants_start - num_constants;
133 Self { input_start, constants_start, ops_start }
134 }
135
136 fn num_inputs(&self) -> usize {
138 self.input_start - self.constants_start
139 }
140
141 pub(crate) fn num_const_nodes(&self) -> usize {
143 self.constants_start - self.ops_start
144 }
145
146 pub(crate) fn num_padded_ops(&self) -> usize {
148 self.ops_start + 1
149 }
150
151 fn num_nodes(&self) -> usize {
153 self.input_start + 1
154 }
155
156 pub(crate) fn validate(&self) -> Result<(), AceError> {
160 if !self.num_inputs().is_multiple_of(ACE_READ_ROW_EF_NODES) {
161 return Err(AceError::InvalidInputLayout {
162 message: "ACE READ layout must be aligned to two EF nodes (use LayoutKind::Masm or pad inputs)"
163 .to_string(),
164 });
165 }
166 if self.num_nodes() > MAX_NUM_ACE_NODES {
167 return Err(AceError::InvalidInputLayout {
168 message: format!(
169 "ACE circuit has {} nodes, must be less than 2^30",
170 self.num_nodes()
171 ),
172 });
173 }
174 Ok(())
175 }
176
177 fn node_id(&self, node: AceNode) -> Result<u64, AceError> {
178 let id = match node {
179 AceNode::Input(idx) => self.input_start.checked_sub(idx),
180 AceNode::Constant(idx) => self.constants_start.checked_sub(idx),
181 AceNode::Operation(idx) => self.ops_start.checked_sub(idx),
182 }
183 .ok_or_else(|| AceError::InvalidInputLayout {
184 message: format!("ACE circuit node index out of range: {node:?}"),
185 })?;
186 Ok(id as u64)
187 }
188
189 pub(crate) fn encode_operation(&self, op: &AceOpNode) -> Result<Felt, AceError> {
191 const RHS_NODE_OFFSET: u64 = 1 << 30;
192 const OP_TAG_OFFSET: u64 = 1 << 60;
193 let tag = match op.op {
194 AceOp::Sub => 0,
195 AceOp::Mul => 1,
196 AceOp::Add => 2,
197 };
198 let lhs_id = self.node_id(op.lhs)?;
199 let rhs_id = self.node_id(op.rhs)?;
200 Ok(Felt::new_unchecked(lhs_id + rhs_id * RHS_NODE_OFFSET + tag * OP_TAG_OFFSET))
201 }
202}
203
204impl<EF> AceCircuit<EF>
205where
206 EF: ExtensionField<Felt>,
207{
208 pub fn to_ace(&self) -> Result<EncodedCircuit, AceError> {
210 let num_input_nodes = self.layout.total_inputs;
211 let num_op_nodes = self.operations.len();
212 if num_op_nodes == 0 {
213 return Err(AceError::InvalidInputLayout {
214 message: "ACE circuit has no operations to encode".to_string(),
215 });
216 }
217 if self.root != AceNode::Operation(num_op_nodes - 1) {
218 return Err(AceError::InvalidInputLayout {
219 message: "ACE circuit root must be the last operation before padding".to_string(),
220 });
221 }
222
223 let geometry =
224 StreamGeometry::from_counts(num_input_nodes, self.constants.len(), num_op_nodes);
225 geometry.validate()?;
226
227 let num_const_nodes = geometry.num_const_nodes();
231 let num_const_felts = num_const_nodes * BASE_FELTS_PER_EF;
232 let len_circuit_padded = num_const_felts + geometry.num_padded_ops();
233
234 let mut instructions = Vec::with_capacity(len_circuit_padded);
235 for constant in &self.constants {
236 let coeffs = constant.as_basis_coefficients_slice();
237 instructions.push(coeffs[0]);
238 instructions.push(coeffs[1]);
239 }
240 instructions.resize(num_const_felts, Felt::ZERO);
241
242 for op in &self.operations {
243 instructions.push(geometry.encode_operation(op)?);
244 }
245
246 let mut last_node_index = num_op_nodes - 1;
249 while instructions.len() < len_circuit_padded {
250 let last_node = AceNode::Operation(last_node_index);
251 let dummy_op = AceOpNode {
252 op: AceOp::Mul,
253 lhs: last_node,
254 rhs: last_node,
255 };
256 instructions.push(geometry.encode_operation(&dummy_op)?);
257 last_node_index += 1;
258 }
259
260 let num_vars = num_input_nodes + num_const_nodes;
261 let num_ops = geometry.num_padded_ops();
262 Ok(EncodedCircuit { num_vars, num_ops, instructions })
263 }
264
265 pub fn is_padded(&self) -> bool {
269 if !self.layout.total_inputs.is_multiple_of(ACE_READ_ROW_EF_NODES) {
270 return false;
271 }
272 if !self.constants.len().is_multiple_of(CONST_EF_ALIGN) {
273 return false;
274 }
275 let const_felts = self.constants.len() * BASE_FELTS_PER_EF;
276 let op_felts = self.operations.len();
277 (const_felts + op_felts).is_multiple_of(ADV_PIPE_BLOCK_FELTS)
278 }
279}