miden_lifted_air/statement.rs
1//! Validated runtime inputs for trusted multi-AIR definitions: [`Statement`]
2//! holds per-proof caller inputs, and [`ProverStatement`] adds per-AIR main
3//! traces.
4
5use alloc::vec::Vec;
6use core::marker::PhantomData;
7
8use p3_challenger::CanObserve;
9use p3_field::{ExtensionField, Field};
10use p3_matrix::{Matrix, dense::RowMajorMatrix};
11use thiserror::Error;
12
13use crate::{
14 BaseAir, LiftedAir,
15 air::{MultiAir, ReductionError},
16};
17
18// ============================================================================
19// Statement
20// ============================================================================
21
22/// Validated per-proof inputs over a [`MultiAir`]: `air_inputs` and `aux_inputs`.
23///
24/// Holding one guarantees the caller-input length checks in [`Statement::new`] passed.
25/// The `MultiAir` itself is trusted application code; run
26/// [`crate::debug::assert_multi_air_valid`] in tests/setup to check structural
27/// invariants such as non-empty `airs()`, shared public-value counts, and
28/// periodic-column shape.
29pub struct Statement<F, EF, MA>
30where
31 F: Field,
32 EF: ExtensionField<F>,
33 MA: MultiAir<F, EF>,
34{
35 multi_air: MA,
36 air_inputs: Vec<F>,
37 aux_inputs: Vec<F>,
38 _ef: PhantomData<EF>,
39}
40
41impl<F, EF, MA> Statement<F, EF, MA>
42where
43 F: Field,
44 EF: ExtensionField<F>,
45 MA: MultiAir<F, EF>,
46{
47 /// Construct a [`Statement`], validating caller-supplied input lengths
48 /// against `multi_air`.
49 ///
50 /// This assumes `multi_air` satisfies the structural contract checked by
51 /// [`crate::debug::assert_multi_air_valid`]; malformed AIR definitions (for
52 /// example an empty [`MultiAir::airs`] collection) may panic in trusted
53 /// helper methods such as [`MultiAir::num_air_inputs`].
54 pub fn new(
55 multi_air: MA,
56 air_inputs: Vec<F>,
57 aux_inputs: Vec<F>,
58 ) -> Result<Self, InstanceError> {
59 let expected = multi_air.num_air_inputs();
60 if expected != air_inputs.len() {
61 return Err(InstanceError::PublicValuesLengthMismatch {
62 expected,
63 actual: air_inputs.len(),
64 });
65 }
66 let max = multi_air.max_aux_inputs();
67 if aux_inputs.len() > max {
68 return Err(InstanceError::AuxInputsTooLong { actual: aux_inputs.len(), max });
69 }
70 Ok(Self {
71 multi_air,
72 air_inputs,
73 aux_inputs,
74 _ef: PhantomData,
75 })
76 }
77
78 pub fn multi_air(&self) -> &MA {
79 &self.multi_air
80 }
81
82 pub fn airs(&self) -> &[MA::Air] {
83 self.multi_air.airs()
84 }
85
86 pub fn air_inputs(&self) -> &[F] {
87 &self.air_inputs
88 }
89
90 pub fn aux_inputs(&self) -> &[F] {
91 &self.aux_inputs
92 }
93
94 /// Evaluate cross-AIR assertions via [`MultiAir::eval_external`].
95 pub fn eval_external(
96 &self,
97 challenges: &[EF],
98 aux_values: &[&[EF]],
99 log_trace_heights: &[u8],
100 ) -> Result<Vec<EF>, ReductionError> {
101 self.multi_air.eval_external(
102 challenges,
103 &self.air_inputs,
104 &self.aux_inputs,
105 aux_values,
106 log_trace_heights,
107 )
108 }
109
110 /// Absorb statement-owned data into the Fiat-Shamir challenger via [`MultiAir::observe`].
111 ///
112 /// The protocol separately observes the instance count and
113 /// `log_trace_heights` in instance order after this call. Heights are passed
114 /// here only so custom `MultiAir` bindings can include height-dependent
115 /// statement data.
116 ///
117 /// The default [`MultiAir::observe`] implementation absorbs, in order, the
118 /// `air_inputs` length, `air_inputs`, [`MultiAir::max_aux_inputs`], the
119 /// `aux_inputs` length, and `aux_inputs`.
120 ///
121 /// # Examples
122 ///
123 /// ```
124 /// use miden_field::Felt;
125 /// use miden_lifted_air::{BaseAir, LiftedAir, LiftedAirBuilder, MultiAir, Statement};
126 /// use p3_challenger::CanObserve;
127 /// use p3_matrix::{Matrix, dense::RowMajorMatrix};
128 ///
129 /// #[derive(Clone)]
130 /// struct ExampleAir;
131 ///
132 /// impl BaseAir<Felt> for ExampleAir {
133 /// fn width(&self) -> usize {
134 /// 1
135 /// }
136 ///
137 /// fn num_public_values(&self) -> usize {
138 /// 2
139 /// }
140 /// }
141 ///
142 /// impl LiftedAir<Felt, Felt> for ExampleAir {
143 /// fn num_randomness(&self) -> usize {
144 /// 0
145 /// }
146 ///
147 /// fn aux_width(&self) -> usize {
148 /// 1
149 /// }
150 ///
151 /// fn num_aux_values(&self) -> usize {
152 /// 0
153 /// }
154 ///
155 /// fn build_aux_trace(
156 /// &self,
157 /// main: &RowMajorMatrix<Felt>,
158 /// _air_inputs: &[Felt],
159 /// _aux_inputs: &[Felt],
160 /// _challenges: &[Felt],
161 /// ) -> (RowMajorMatrix<Felt>, Vec<Felt>) {
162 /// (RowMajorMatrix::new(vec![Felt::ZERO; main.height()], 1), Vec::new())
163 /// }
164 ///
165 /// fn eval<AB: LiftedAirBuilder<F = Felt>>(&self, _builder: &mut AB) {}
166 /// }
167 ///
168 /// struct ExampleMultiAir {
169 /// airs: [ExampleAir; 1],
170 /// }
171 ///
172 /// impl MultiAir<Felt, Felt> for ExampleMultiAir {
173 /// type Air = ExampleAir;
174 ///
175 /// fn airs(&self) -> &[Self::Air] {
176 /// &self.airs
177 /// }
178 ///
179 /// fn max_aux_inputs(&self) -> usize {
180 /// 3
181 /// }
182 /// }
183 ///
184 /// #[derive(Default)]
185 /// struct RecordingChallenger(Vec<Felt>);
186 ///
187 /// impl CanObserve<Felt> for RecordingChallenger {
188 /// fn observe(&mut self, value: Felt) {
189 /// self.0.push(value);
190 /// }
191 /// }
192 ///
193 /// let statement = Statement::<Felt, Felt, _>::new(
194 /// ExampleMultiAir { airs: [ExampleAir] },
195 /// vec![Felt::new_unchecked(10), Felt::new_unchecked(11)],
196 /// vec![Felt::new_unchecked(20), Felt::new_unchecked(21)],
197 /// )
198 /// .unwrap();
199 ///
200 /// let mut challenger = RecordingChallenger::default();
201 /// statement.observe(&mut challenger, &[3]);
202 ///
203 /// assert_eq!(
204 /// challenger.0,
205 /// vec![
206 /// Felt::new_unchecked(2),
207 /// Felt::new_unchecked(10),
208 /// Felt::new_unchecked(11),
209 /// Felt::new_unchecked(3),
210 /// Felt::new_unchecked(2),
211 /// Felt::new_unchecked(20),
212 /// Felt::new_unchecked(21),
213 /// ],
214 /// );
215 /// ```
216 pub fn observe<C: CanObserve<F>>(&self, challenger: &mut C, log_trace_heights: &[u8]) {
217 self.multi_air
218 .observe(challenger, &self.air_inputs, &self.aux_inputs, log_trace_heights);
219 }
220}
221
222// ============================================================================
223// ProverStatement
224// ============================================================================
225
226/// A [`Statement`] plus per-AIR main traces.
227///
228/// Holding one guarantees the trace-shape checks in [`ProverStatement::new`] passed.
229pub struct ProverStatement<F, EF, MA>
230where
231 F: Field,
232 EF: ExtensionField<F>,
233 MA: MultiAir<F, EF>,
234{
235 statement: Statement<F, EF, MA>,
236 traces: Vec<RowMajorMatrix<F>>,
237}
238
239impl<F, EF, MA> ProverStatement<F, EF, MA>
240where
241 F: Field,
242 EF: ExtensionField<F>,
243 MA: MultiAir<F, EF>,
244{
245 /// Construct a [`ProverStatement`], validating each trace's count, height, and
246 /// width against its AIR.
247 ///
248 /// This assumes the underlying [`MultiAir`] satisfies the structural contract
249 /// checked by [`crate::debug::assert_multi_air_valid`].
250 pub fn new(
251 statement: Statement<F, EF, MA>,
252 traces: Vec<RowMajorMatrix<F>>,
253 ) -> Result<Self, InstanceError> {
254 // TraceOrder stores instance indices as u8, so it can represent 256
255 // instances: indices 0 through u8::MAX.
256 let max_instances = u8::MAX as usize + 1;
257 if traces.len() > max_instances {
258 return Err(InstanceError::TooManyInstances { count: traces.len() });
259 }
260 let airs = statement.airs();
261 if airs.len() != traces.len() {
262 return Err(InstanceError::TraceCountMismatch {
263 airs: airs.len(),
264 traces: traces.len(),
265 });
266 }
267 for (idx, trace) in traces.iter().enumerate() {
268 let h = trace.height();
269 if h < 2 {
270 return Err(InstanceError::TraceHeightTooSmall { air: idx, height: h });
271 }
272 if !h.is_power_of_two() {
273 return Err(InstanceError::TraceHeightNotPowerOfTwo { air: idx, height: h });
274 }
275 }
276 for (idx, (air, trace)) in airs.iter().zip(traces.iter()).enumerate() {
277 let trace_height = trace.height();
278 let max_period = air.max_periodic_length();
279 if trace_height < max_period {
280 return Err(InstanceError::TraceHeightBelowPeriod {
281 air: idx,
282 trace_height,
283 max_period,
284 });
285 }
286 if trace.width() != air.width() {
287 return Err(InstanceError::TraceWidthMismatch {
288 air: idx,
289 expected: air.width(),
290 actual: trace.width(),
291 });
292 }
293 }
294 Ok(Self { statement, traces })
295 }
296
297 pub fn statement(&self) -> &Statement<F, EF, MA> {
298 &self.statement
299 }
300
301 pub fn traces(&self) -> &[RowMajorMatrix<F>] {
302 &self.traces
303 }
304}
305
306// ============================================================================
307// InstanceError
308// ============================================================================
309
310/// Errors from constructing a [`Statement`] / [`ProverStatement`] — the runtime
311/// trust boundary on caller-supplied inputs.
312#[derive(Debug, Error)]
313pub enum InstanceError {
314 #[error("num_air_inputs() = {expected}, but air_inputs().len() = {actual}")]
315 PublicValuesLengthMismatch { expected: usize, actual: usize },
316
317 #[error("aux_inputs().len() = {actual} exceeds max_aux_inputs() = {max}")]
318 AuxInputsTooLong { actual: usize, max: usize },
319
320 #[error("airs().len() = {airs} does not match traces().len() = {traces}")]
321 TraceCountMismatch { airs: usize, traces: usize },
322
323 #[error(
324 "too many instances ({count}); the per-proof limit is {max} = u8::MAX + 1",
325 max = u8::MAX as usize + 1
326 )]
327 TooManyInstances { count: usize },
328
329 #[error("AIR {air}: trace width = {actual}, but air.width() = {expected}")]
330 TraceWidthMismatch {
331 air: usize,
332 expected: usize,
333 actual: usize,
334 },
335
336 #[error("AIR {air}: trace height = {height} is too small; expected at least 2 rows")]
337 TraceHeightTooSmall { air: usize, height: usize },
338
339 #[error("AIR {air}: trace height = {height} is not a power of two")]
340 TraceHeightNotPowerOfTwo { air: usize, height: usize },
341
342 #[error(
343 "AIR {air}: trace height = {trace_height} is less than max periodic column \
344 length {max_period}"
345 )]
346 TraceHeightBelowPeriod {
347 air: usize,
348 trace_height: usize,
349 max_period: usize,
350 },
351}