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
117 let num_const_felts = num_const_nodes * BASE_FELTS_PER_EF;
119 let num_op_felts = num_op_nodes;
120 let len_circuit = num_const_felts + num_op_felts;
124 let len_circuit_padded = len_circuit.next_multiple_of(ADV_PIPE_BLOCK_FELTS);
125
126 let num_padding_felts = len_circuit_padded - len_circuit;
127 let num_padding_nodes = num_padding_felts;
128 let num_nodes = num_input_nodes + num_const_nodes + num_op_nodes + num_padding_nodes;
129
130 if num_nodes as u64 > MAX_NODE_ID {
131 return Err(AceError::InvalidInputLayout {
132 message: format!("ACE circuit has {num_nodes} nodes, exceeds 2^30-1 limit"),
133 });
134 }
135
136 let mut instructions = Vec::with_capacity(len_circuit_padded);
137 for constant in &self.constants {
138 let coeffs = constant.as_basis_coefficients_slice();
139 instructions.push(coeffs[0]);
140 instructions.push(coeffs[1]);
141 }
142 instructions.resize(num_const_felts, Felt::ZERO);
143
144 let node_id = |node: AceNode| -> Result<u64, AceError> {
145 let input_start = num_nodes - 1;
146 let constants_start = input_start - num_input_nodes;
147 let ops_start = constants_start - num_const_nodes;
148
149 let id = match node {
150 AceNode::Input(idx) => input_start.checked_sub(idx),
151 AceNode::Constant(idx) => constants_start.checked_sub(idx),
152 AceNode::Operation(idx) => ops_start.checked_sub(idx),
153 }
154 .ok_or_else(|| AceError::InvalidInputLayout {
155 message: format!("ACE circuit node index out of range: {node:?}"),
156 })?;
157 Ok(id as u64)
158 };
159
160 let op_tag = |op: AceOp| -> u64 {
161 match op {
162 AceOp::Sub => 0,
163 AceOp::Mul => 1,
164 AceOp::Add => 2,
165 }
166 };
167
168 let encode_operation = |op: &AceOpNode| -> Result<Felt, AceError> {
169 const RHS_NODE_OFFSET: u64 = 1 << 30;
171 const OP_TAG_OFFSET: u64 = 1 << 60;
172 let lhs_id = node_id(op.lhs)?;
173 let rhs_id = node_id(op.rhs)?;
174 let tag = op_tag(op.op);
175 Ok(Felt::new(lhs_id + rhs_id * RHS_NODE_OFFSET + tag * OP_TAG_OFFSET))
176 };
177
178 for op in &self.operations {
179 instructions.push(encode_operation(op)?);
180 }
181
182 let mut last_node_index = num_op_nodes - 1;
183 while instructions.len() < len_circuit_padded {
184 let last_node = AceNode::Operation(last_node_index);
185 let dummy_op = AceOpNode {
186 op: AceOp::Mul,
187 lhs: last_node,
188 rhs: last_node,
189 };
190 instructions.push(encode_operation(&dummy_op)?);
191 last_node_index += 1;
192 }
193
194 let num_vars = num_input_nodes + num_const_nodes;
195 let num_ops = num_op_nodes + num_padding_nodes;
196 Ok(EncodedCircuit { num_vars, num_ops, instructions })
197 }
198
199 pub fn is_padded(&self) -> bool {
203 if !self.layout.total_inputs.is_multiple_of(ACE_READ_ROW_EF_NODES) {
204 return false;
205 }
206 if !self.constants.len().is_multiple_of(CONST_EF_ALIGN) {
207 return false;
208 }
209 let const_felts = self.constants.len() * BASE_FELTS_PER_EF;
210 let op_felts = self.operations.len();
211 (const_felts + op_felts).is_multiple_of(ADV_PIPE_BLOCK_FELTS)
212 }
213}