1use miden_crypto::{
9 field::{Algebra, ExtensionField, Field, TwoAdicField},
10 stark::air::{
11 LiftedAir,
12 symbolic::{AirLayout, SymbolicAirBuilder, SymbolicExpressionExt},
13 },
14};
15
16use crate::{
17 AceError,
18 circuit::{AceCircuit, emit_circuit},
19 dag::{AceDag, PeriodicColumnData, build_verifier_dag},
20 layout::{InputCounts, InputLayout},
21};
22
23#[derive(Debug, Clone, Copy)]
29pub enum LayoutKind {
30 Native,
32 Masm,
34}
35
36#[derive(Debug, Clone, Copy)]
38pub struct AceConfig {
39 pub num_quotient_chunks: usize,
41 pub num_vlpi_groups: usize,
46 pub layout: LayoutKind,
48 pub is_multi_air: bool,
53}
54
55#[derive(Debug)]
57pub struct AceArtifacts<EF> {
58 pub layout: InputLayout,
60 pub dag: AceDag<EF>,
62}
63
64pub fn build_ace_circuit_for_air<A, F, EF>(
70 air: &A,
71 config: AceConfig,
72) -> Result<AceCircuit<EF>, AceError>
73where
74 A: LiftedAir<F, EF>,
75 F: TwoAdicField,
76 EF: ExtensionField<F>,
77 SymbolicExpressionExt<F, EF>: Algebra<EF>,
78{
79 let artifacts = build_ace_dag_for_air::<A, F, EF>(air, config)?;
80 emit_circuit(&artifacts.dag, artifacts.layout)
81}
82
83pub fn build_ace_dag_for_air<A, F, EF>(
85 air: &A,
86 config: AceConfig,
87) -> Result<AceArtifacts<EF>, AceError>
88where
89 A: LiftedAir<F, EF>,
90 F: TwoAdicField,
91 EF: ExtensionField<F>,
92 SymbolicExpressionExt<F, EF>: Algebra<EF>,
93{
94 let periodic_columns = air.periodic_columns();
95 let counts = input_counts_for_air::<A, F, EF>(air, config, periodic_columns.len());
96 let layout = match (config.layout, config.is_multi_air) {
97 (LayoutKind::Native, false) => InputLayout::new(counts),
98 (LayoutKind::Masm, false) => InputLayout::new_masm(counts),
99 (LayoutKind::Native, true) => InputLayout::new_multi_air(counts),
100 (LayoutKind::Masm, true) => InputLayout::new_masm_multi_air(counts),
101 };
102 layout.validate();
103
104 let air_layout = AirLayout {
105 preprocessed_width: 0,
106 main_width: counts.width,
107 num_public_values: counts.num_public,
108 permutation_width: counts.aux_width,
109 num_permutation_challenges: counts.num_randomness,
110 num_permutation_values: air.num_aux_values(),
111 num_periodic_columns: counts.num_periodic,
112 };
113 let mut builder = SymbolicAirBuilder::<F, EF>::new(air_layout);
114 air.eval(&mut builder);
115 let constraint_layout = builder.constraint_layout();
116 let base_constraints = builder.base_constraints();
117 let ext_constraints = builder.extension_constraints();
118
119 let periodic_data = (!periodic_columns.is_empty())
120 .then(|| PeriodicColumnData::from_periodic_columns::<F>(periodic_columns.to_vec()));
121 let dag = build_verifier_dag::<F, EF>(
122 &base_constraints,
123 &ext_constraints,
124 &constraint_layout,
125 &layout,
126 periodic_data.as_ref(),
127 );
128
129 Ok(AceArtifacts { layout, dag })
130}
131
132fn input_counts_for_air<A, F, EF>(air: &A, config: AceConfig, num_periodic: usize) -> InputCounts
133where
134 A: LiftedAir<F, EF>,
135 F: Field,
136 EF: ExtensionField<F>,
137{
138 assert!(config.num_quotient_chunks > 0, "num_quotient_chunks must be > 0");
139 assert!(
140 air.preprocessed_trace().is_none(),
141 "preprocessed trace inputs are not supported"
142 );
143
144 let num_randomness = air.num_randomness();
145 assert!(
146 num_randomness == 2,
147 "AIR must declare exactly 2 randomness challenges (alpha, beta), got {num_randomness}"
148 );
149
150 let num_vlpi = match config.layout {
154 LayoutKind::Masm => config.num_vlpi_groups * 2,
155 LayoutKind::Native => config.num_vlpi_groups,
156 };
157
158 InputCounts {
159 width: air.width(),
160 aux_width: air.aux_width(),
161 num_aux_boundary: air.num_aux_values(),
162 num_public: air.num_public_values(),
163 num_vlpi,
164 num_randomness,
165 num_periodic,
166 num_quotient_chunks: config.num_quotient_chunks,
167 }
168}