p3_uni_stark/
symbolic_builder.rs

1use alloc::vec;
2use alloc::vec::Vec;
3
4use p3_air::{Air, AirBuilder, AirBuilderWithPublicValues, PairBuilder};
5use p3_field::Field;
6use p3_matrix::dense::RowMajorMatrix;
7use p3_util::log2_ceil_usize;
8use tracing::instrument;
9
10use crate::Entry;
11use crate::symbolic_expression::SymbolicExpression;
12use crate::symbolic_variable::SymbolicVariable;
13
14#[instrument(name = "infer log of constraint degree", skip_all)]
15pub fn get_log_quotient_degree<F, A>(
16    air: &A,
17    preprocessed_width: usize,
18    num_public_values: usize,
19    is_zk: usize,
20) -> usize
21where
22    F: Field,
23    A: Air<SymbolicAirBuilder<F>>,
24{
25    assert!(is_zk <= 1, "is_zk must be either 0 or 1");
26    // We pad to at least degree 2, since a quotient argument doesn't make sense with smaller degrees.
27    let constraint_degree =
28        (get_max_constraint_degree(air, preprocessed_width, num_public_values) + is_zk).max(2);
29
30    // The quotient's actual degree is approximately (max_constraint_degree - 1) n,
31    // where subtracting 1 comes from division by the vanishing polynomial.
32    // But we pad it to a power of two so that we can efficiently decompose the quotient.
33    log2_ceil_usize(constraint_degree - 1)
34}
35
36#[instrument(name = "infer constraint degree", skip_all, level = "debug")]
37pub fn get_max_constraint_degree<F, A>(
38    air: &A,
39    preprocessed_width: usize,
40    num_public_values: usize,
41) -> usize
42where
43    F: Field,
44    A: Air<SymbolicAirBuilder<F>>,
45{
46    get_symbolic_constraints(air, preprocessed_width, num_public_values)
47        .iter()
48        .map(|c| c.degree_multiple())
49        .max()
50        .unwrap_or(0)
51}
52
53#[instrument(name = "evaluate constraints symbolically", skip_all, level = "debug")]
54pub fn get_symbolic_constraints<F, A>(
55    air: &A,
56    preprocessed_width: usize,
57    num_public_values: usize,
58) -> Vec<SymbolicExpression<F>>
59where
60    F: Field,
61    A: Air<SymbolicAirBuilder<F>>,
62{
63    let mut builder = SymbolicAirBuilder::new(preprocessed_width, air.width(), num_public_values);
64    air.eval(&mut builder);
65    builder.constraints()
66}
67
68/// An `AirBuilder` for evaluating constraints symbolically, and recording them for later use.
69#[derive(Debug)]
70pub struct SymbolicAirBuilder<F: Field> {
71    preprocessed: RowMajorMatrix<SymbolicVariable<F>>,
72    main: RowMajorMatrix<SymbolicVariable<F>>,
73    public_values: Vec<SymbolicVariable<F>>,
74    constraints: Vec<SymbolicExpression<F>>,
75}
76
77impl<F: Field> SymbolicAirBuilder<F> {
78    pub(crate) fn new(preprocessed_width: usize, width: usize, num_public_values: usize) -> Self {
79        let prep_values = [0, 1]
80            .into_iter()
81            .flat_map(|offset| {
82                (0..preprocessed_width)
83                    .map(move |index| SymbolicVariable::new(Entry::Preprocessed { offset }, index))
84            })
85            .collect();
86        let main_values = [0, 1]
87            .into_iter()
88            .flat_map(|offset| {
89                (0..width).map(move |index| SymbolicVariable::new(Entry::Main { offset }, index))
90            })
91            .collect();
92        let public_values = (0..num_public_values)
93            .map(move |index| SymbolicVariable::new(Entry::Public, index))
94            .collect();
95        Self {
96            preprocessed: RowMajorMatrix::new(prep_values, preprocessed_width),
97            main: RowMajorMatrix::new(main_values, width),
98            public_values,
99            constraints: vec![],
100        }
101    }
102
103    pub(crate) fn constraints(self) -> Vec<SymbolicExpression<F>> {
104        self.constraints
105    }
106}
107
108impl<F: Field> AirBuilder for SymbolicAirBuilder<F> {
109    type F = F;
110    type Expr = SymbolicExpression<F>;
111    type Var = SymbolicVariable<F>;
112    type M = RowMajorMatrix<Self::Var>;
113
114    fn main(&self) -> Self::M {
115        self.main.clone()
116    }
117
118    fn is_first_row(&self) -> Self::Expr {
119        SymbolicExpression::IsFirstRow
120    }
121
122    fn is_last_row(&self) -> Self::Expr {
123        SymbolicExpression::IsLastRow
124    }
125
126    /// # Panics
127    /// This function panics if `size` is not `2`.
128    fn is_transition_window(&self, size: usize) -> Self::Expr {
129        if size == 2 {
130            SymbolicExpression::IsTransition
131        } else {
132            panic!("uni-stark only supports a window size of 2")
133        }
134    }
135
136    fn assert_zero<I: Into<Self::Expr>>(&mut self, x: I) {
137        self.constraints.push(x.into());
138    }
139}
140
141impl<F: Field> AirBuilderWithPublicValues for SymbolicAirBuilder<F> {
142    type PublicVar = SymbolicVariable<F>;
143    fn public_values(&self) -> &[Self::PublicVar] {
144        &self.public_values
145    }
146}
147
148impl<F: Field> PairBuilder for SymbolicAirBuilder<F> {
149    fn preprocessed(&self) -> Self::M {
150        self.preprocessed.clone()
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use alloc::vec;
157    use alloc::vec::Vec;
158
159    use p3_air::BaseAir;
160    use p3_baby_bear::BabyBear;
161
162    use super::*;
163
164    #[derive(Debug)]
165    struct MockAir {
166        constraints: Vec<SymbolicVariable<BabyBear>>,
167        width: usize,
168    }
169
170    impl BaseAir<BabyBear> for MockAir {
171        fn width(&self) -> usize {
172            self.width
173        }
174    }
175
176    impl Air<SymbolicAirBuilder<BabyBear>> for MockAir {
177        fn eval(&self, builder: &mut SymbolicAirBuilder<BabyBear>) {
178            for constraint in &self.constraints {
179                builder.assert_zero(*constraint);
180            }
181        }
182    }
183
184    #[test]
185    fn test_get_log_quotient_degree_no_constraints() {
186        let air = MockAir {
187            constraints: vec![],
188            width: 4,
189        };
190        let log_degree = get_log_quotient_degree(&air, 3, 2, 0);
191        assert_eq!(log_degree, 0);
192    }
193
194    #[test]
195    fn test_get_log_quotient_degree_single_constraint() {
196        let air = MockAir {
197            constraints: vec![SymbolicVariable::new(Entry::Main { offset: 0 }, 0)],
198            width: 4,
199        };
200        let log_degree = get_log_quotient_degree(&air, 3, 2, 0);
201        assert_eq!(log_degree, log2_ceil_usize(1));
202    }
203
204    #[test]
205    fn test_get_log_quotient_degree_multiple_constraints() {
206        let air = MockAir {
207            constraints: vec![
208                SymbolicVariable::new(Entry::Main { offset: 0 }, 0),
209                SymbolicVariable::new(Entry::Main { offset: 1 }, 1),
210                SymbolicVariable::new(Entry::Main { offset: 2 }, 2),
211            ],
212            width: 4,
213        };
214        let log_degree = get_log_quotient_degree(&air, 3, 2, 0);
215        assert_eq!(log_degree, log2_ceil_usize(1));
216    }
217
218    #[test]
219    fn test_get_max_constraint_degree_no_constraints() {
220        let air = MockAir {
221            constraints: vec![],
222            width: 4,
223        };
224        let max_degree = get_max_constraint_degree(&air, 3, 2);
225        assert_eq!(
226            max_degree, 0,
227            "No constraints should result in a degree of 0"
228        );
229    }
230
231    #[test]
232    fn test_get_max_constraint_degree_multiple_constraints() {
233        let air = MockAir {
234            constraints: vec![
235                SymbolicVariable::new(Entry::Main { offset: 0 }, 0),
236                SymbolicVariable::new(Entry::Main { offset: 1 }, 1),
237                SymbolicVariable::new(Entry::Main { offset: 2 }, 2),
238            ],
239            width: 4,
240        };
241        let max_degree = get_max_constraint_degree(&air, 3, 2);
242        assert_eq!(max_degree, 1, "Max constraint degree should be 1");
243    }
244
245    #[test]
246    fn test_get_symbolic_constraints() {
247        let c1 = SymbolicVariable::new(Entry::Main { offset: 0 }, 0);
248        let c2 = SymbolicVariable::new(Entry::Main { offset: 1 }, 1);
249
250        let air = MockAir {
251            constraints: vec![c1, c2],
252            width: 4,
253        };
254
255        let constraints = get_symbolic_constraints(&air, 3, 2);
256
257        assert_eq!(constraints.len(), 2, "Should return exactly 2 constraints");
258
259        assert!(
260            constraints.iter().any(|x| matches!(x, SymbolicExpression::Variable(v) if v.index == c1.index && v.entry == c1.entry)),
261            "Expected constraint {:?} was not found",
262            c1
263        );
264
265        assert!(
266            constraints.iter().any(|x| matches!(x, SymbolicExpression::Variable(v) if v.index == c2.index && v.entry == c2.entry)),
267            "Expected constraint {:?} was not found",
268            c2
269        );
270    }
271
272    #[test]
273    fn test_symbolic_air_builder_initialization() {
274        let builder = SymbolicAirBuilder::<BabyBear>::new(2, 4, 3);
275
276        let expected_main = [
277            SymbolicVariable::<BabyBear>::new(Entry::Main { offset: 0 }, 0),
278            SymbolicVariable::<BabyBear>::new(Entry::Main { offset: 0 }, 1),
279            SymbolicVariable::<BabyBear>::new(Entry::Main { offset: 0 }, 2),
280            SymbolicVariable::<BabyBear>::new(Entry::Main { offset: 0 }, 3),
281            SymbolicVariable::<BabyBear>::new(Entry::Main { offset: 1 }, 0),
282            SymbolicVariable::<BabyBear>::new(Entry::Main { offset: 1 }, 1),
283            SymbolicVariable::<BabyBear>::new(Entry::Main { offset: 1 }, 2),
284            SymbolicVariable::<BabyBear>::new(Entry::Main { offset: 1 }, 3),
285        ];
286
287        let builder_main = builder.main.values;
288
289        assert_eq!(
290            builder_main.len(),
291            expected_main.len(),
292            "Main matrix should have the expected length"
293        );
294
295        for (expected, actual) in expected_main.iter().zip(builder_main.iter()) {
296            assert_eq!(expected.index, actual.index, "Index mismatch");
297            assert_eq!(expected.entry, actual.entry, "Entry mismatch");
298        }
299    }
300
301    #[test]
302    fn test_symbolic_air_builder_is_first_last_row() {
303        let builder = SymbolicAirBuilder::<BabyBear>::new(2, 4, 3);
304
305        assert!(
306            matches!(builder.is_first_row(), SymbolicExpression::IsFirstRow),
307            "First row condition did not match"
308        );
309
310        assert!(
311            matches!(builder.is_last_row(), SymbolicExpression::IsLastRow),
312            "Last row condition did not match"
313        );
314    }
315
316    #[test]
317    fn test_symbolic_air_builder_assert_zero() {
318        let mut builder = SymbolicAirBuilder::<BabyBear>::new(2, 4, 3);
319        let expr = SymbolicExpression::Constant(BabyBear::new(5));
320        builder.assert_zero(expr.clone());
321
322        let constraints = builder.constraints();
323        assert_eq!(constraints.len(), 1, "One constraint should be recorded");
324
325        assert!(
326            constraints.iter().any(
327                |x| matches!(x, SymbolicExpression::Constant(val) if *val == BabyBear::new(5))
328            ),
329            "Constraint should match the asserted one"
330        );
331    }
332}