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;
27const CONST_EF_ALIGN: usize = 2;
29const ADV_PIPE_BLOCK_FELTS: usize = 8;
32
33#[derive(Debug, Clone)]
40pub struct EncodedCircuit {
41 num_vars: usize,
42 num_ops: usize,
43 instructions: Vec<Felt>,
44}
45
46impl EncodedCircuit {
47 pub fn num_read_rows(&self) -> usize {
49 self.num_vars() / ACE_READ_ROW_EF_NODES
50 }
51
52 pub fn num_eval_rows(&self) -> usize {
54 self.num_ops
55 }
56
57 pub fn num_vars(&self) -> usize {
59 self.num_vars
60 }
61
62 pub fn num_inputs(&self) -> usize {
64 self.num_vars - self.num_constants()
65 }
66
67 pub fn num_constants(&self) -> usize {
69 (self.instructions.len() - self.num_ops) / BASE_FELTS_PER_EF
70 }
71
72 pub fn num_nodes(&self) -> usize {
74 self.num_vars + self.num_ops
75 }
76
77 pub fn instructions(&self) -> &[Felt] {
79 &self.instructions
80 }
81
82 pub fn size_in_felt(&self) -> usize {
84 self.instructions.len()
85 }
86
87 pub fn circuit_hash(&self) -> Word {
89 Poseidon2::hash_elements(self.instructions())
90 }
91}
92
93impl<EF> AceCircuit<EF>
94where
95 EF: ExtensionField<Felt>,
96{
97 pub fn to_ace(&self) -> Result<EncodedCircuit, AceError> {
99 const MAX_NODE_ID: u64 = (1 << 30) - 1;
100
101 if !self.layout.total_inputs.is_multiple_of(ACE_READ_ROW_EF_NODES) {
102 return Err(AceError::InvalidInputLayout {
103 message: "ACE READ layout must be aligned to two EF nodes (use LayoutKind::Masm or pad inputs)"
104 .to_string(),
105 });
106 }
107
108 let num_input_nodes = self.layout.total_inputs;
109 let num_const_nodes = self.constants.len().next_multiple_of(CONST_EF_ALIGN);
110 let num_op_nodes = self.operations.len();
111 if num_op_nodes == 0 {
112 return Err(AceError::InvalidInputLayout {
113 message: "ACE circuit has no operations to encode".to_string(),
114 });
115 }
116 if self.root != AceNode::Operation(num_op_nodes - 1) {
117 return Err(AceError::InvalidInputLayout {
118 message: "ACE circuit root must be the last operation before padding".to_string(),
119 });
120 }
121
122 let num_const_felts = num_const_nodes * BASE_FELTS_PER_EF;
124 let num_op_felts = num_op_nodes;
125 let len_circuit = num_const_felts + num_op_felts;
129 let len_circuit_padded = len_circuit.next_multiple_of(ADV_PIPE_BLOCK_FELTS);
130
131 let num_padding_felts = len_circuit_padded - len_circuit;
132 let num_padding_nodes = num_padding_felts;
133 let num_nodes = num_input_nodes + num_const_nodes + num_op_nodes + num_padding_nodes;
134
135 if num_nodes as u64 > MAX_NODE_ID {
136 return Err(AceError::InvalidInputLayout {
137 message: format!("ACE circuit has {num_nodes} nodes, exceeds 2^30-1 limit"),
138 });
139 }
140
141 let mut instructions = Vec::with_capacity(len_circuit_padded);
142 for constant in &self.constants {
143 let coeffs = constant.as_basis_coefficients_slice();
144 instructions.push(coeffs[0]);
145 instructions.push(coeffs[1]);
146 }
147 instructions.resize(num_const_felts, Felt::ZERO);
148
149 let node_id = |node: AceNode| -> Result<u64, AceError> {
150 let input_start = num_nodes - 1;
151 let constants_start = input_start - num_input_nodes;
152 let ops_start = constants_start - num_const_nodes;
153
154 let id = match node {
155 AceNode::Input(idx) => input_start.checked_sub(idx),
156 AceNode::Constant(idx) => constants_start.checked_sub(idx),
157 AceNode::Operation(idx) => ops_start.checked_sub(idx),
158 }
159 .ok_or_else(|| AceError::InvalidInputLayout {
160 message: format!("ACE circuit node index out of range: {node:?}"),
161 })?;
162 Ok(id as u64)
163 };
164
165 let op_tag = |op: AceOp| -> u64 {
166 match op {
167 AceOp::Sub => 0,
168 AceOp::Mul => 1,
169 AceOp::Add => 2,
170 }
171 };
172
173 let encode_operation = |op: &AceOpNode| -> Result<Felt, AceError> {
174 const RHS_NODE_OFFSET: u64 = 1 << 30;
176 const OP_TAG_OFFSET: u64 = 1 << 60;
177 let lhs_id = node_id(op.lhs)?;
178 let rhs_id = node_id(op.rhs)?;
179 let tag = op_tag(op.op);
180 Ok(Felt::new_unchecked(lhs_id + rhs_id * RHS_NODE_OFFSET + tag * OP_TAG_OFFSET))
181 };
182
183 for op in &self.operations {
184 instructions.push(encode_operation(op)?);
185 }
186
187 let mut last_node_index = num_op_nodes - 1;
190 while instructions.len() < len_circuit_padded {
191 let last_node = AceNode::Operation(last_node_index);
192 let dummy_op = AceOpNode {
193 op: AceOp::Mul,
194 lhs: last_node,
195 rhs: last_node,
196 };
197 instructions.push(encode_operation(&dummy_op)?);
198 last_node_index += 1;
199 }
200
201 let num_vars = num_input_nodes + num_const_nodes;
202 let num_ops = num_op_nodes + num_padding_nodes;
203 Ok(EncodedCircuit { num_vars, num_ops, instructions })
204 }
205
206 pub fn is_padded(&self) -> bool {
210 if !self.layout.total_inputs.is_multiple_of(ACE_READ_ROW_EF_NODES) {
211 return false;
212 }
213 if !self.constants.len().is_multiple_of(CONST_EF_ALIGN) {
214 return false;
215 }
216 let const_felts = self.constants.len() * BASE_FELTS_PER_EF;
217 let op_felts = self.operations.len();
218 (const_felts + op_felts).is_multiple_of(ADV_PIPE_BLOCK_FELTS)
219 }
220}