Skip to main content

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}