1use alloc::vec::Vec;
19use core::{fmt, marker::PhantomData};
20
21use miden_core::field::{PrimeCharacteristicRing, QuadFelt};
22use miden_crypto::{
23 rand::random_felt,
24 stark::air::{
25 AirBuilder, PeriodicAirBuilder, PermutationAirBuilder,
26 symbolic::{
27 BaseEntry, BaseLeaf, ExtEntry, ExtLeaf, SymbolicAirBuilder, SymbolicExpr,
28 SymbolicExpression, SymbolicExpressionExt, SymbolicVariable, SymbolicVariableExt,
29 },
30 },
31};
32
33use super::super::{
34 Challenges, Deg, LookupAir, LookupBatch, LookupBuilder, LookupColumn, LookupGroup,
35 LookupMessage,
36};
37use crate::Felt;
38
39type Inner = SymbolicAirBuilder<Felt, QuadFelt>;
40type Expr = SymbolicExpression<Felt>;
41type ExprEF = SymbolicExpressionExt<Felt, QuadFelt>;
42
43#[derive(Clone, Debug)]
49pub enum ValidationError {
50 NumColumnsMismatch { declared: usize, observed: usize },
53 ColumnDegreeMismatch {
57 column_idx: usize,
58 declared: Deg,
59 observed: Deg,
60 },
61 GroupDegreeMismatch {
65 column_idx: usize,
66 group_idx: usize,
67 name: &'static str,
68 declared: Deg,
69 observed: Deg,
70 },
71 EncodingMismatch {
75 column_idx: usize,
76 group_idx: usize,
77 name: &'static str,
78 diff: QuadFelt,
79 },
80 ScopeViolation {
83 column_idx: usize,
84 group_idx: usize,
85 name: &'static str,
86 },
87}
88
89impl fmt::Display for ValidationError {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 match self {
92 Self::NumColumnsMismatch { declared, observed } => {
93 write!(f, "num_columns mismatch: declared {declared}, observed {observed}")
94 },
95 Self::ColumnDegreeMismatch { column_idx, declared, observed } => write!(
96 f,
97 "column[{column_idx}] degree mismatch: declared (v={}, u={}), observed (v={}, u={})",
98 declared.v, declared.u, observed.v, observed.u,
99 ),
100 Self::GroupDegreeMismatch {
101 column_idx,
102 group_idx,
103 name,
104 declared,
105 observed,
106 } => write!(
107 f,
108 "column[{column_idx}] group[{group_idx}] {name:?} degree mismatch: declared (v={}, u={}), observed (v={}, u={})",
109 declared.v, declared.u, observed.v, observed.u,
110 ),
111 Self::EncodingMismatch { column_idx, group_idx, name, diff } => write!(
112 f,
113 "column[{column_idx}] group[{group_idx}] {name:?} cached-encoding mismatch: V_c·U_e − V_e·U_c = {diff:?}",
114 ),
115 Self::ScopeViolation { column_idx, group_idx, name } => write!(
116 f,
117 "column[{column_idx}] group[{group_idx}] {name:?} simple group called insert_encoded",
118 ),
119 }
120 }
121}
122
123#[derive(Clone, Copy, Debug)]
130pub struct ValidateLayout {
131 pub trace_width: usize,
132 pub num_public_values: usize,
133 pub num_periodic_columns: usize,
134 pub permutation_width: usize,
135 pub num_permutation_challenges: usize,
136 pub num_permutation_values: usize,
137}
138
139impl ValidateLayout {
140 fn to_symbolic(self) -> miden_crypto::stark::air::symbolic::AirLayout {
141 miden_crypto::stark::air::symbolic::AirLayout {
142 preprocessed_width: 0,
143 main_width: self.trace_width,
144 num_public_values: self.num_public_values,
145 permutation_width: self.permutation_width,
146 num_permutation_challenges: self.num_permutation_challenges,
147 num_permutation_values: self.num_permutation_values,
148 num_periodic_columns: self.num_periodic_columns,
149 }
150 }
151}
152
153pub fn validate<A>(air: &A, layout: ValidateLayout) -> Result<(), ValidationError>
160where
161 for<'ab, 'r> A: LookupAir<ValidationBuilder<'ab, 'r>>,
162{
163 let current: Vec<Felt> = (0..layout.trace_width).map(|_| random_felt()).collect();
169 let next: Vec<Felt> = (0..layout.trace_width).map(|_| random_felt()).collect();
170 let periodic: Vec<Felt> = (0..layout.num_periodic_columns).map(|_| random_felt()).collect();
171 let alpha = QuadFelt::new([random_felt(), random_felt()]);
172 let beta = QuadFelt::new([random_felt(), random_felt()]);
173
174 let mut sym = SymbolicAirBuilder::<Felt, QuadFelt>::new(layout.to_symbolic());
175 let row_valuation = RowValuation {
176 current: ¤t,
177 next: &next,
178 periodic: &periodic,
179 alpha,
180 beta,
181 };
182 let mut builder = ValidationBuilder::new(&mut sym, air, row_valuation);
183 air.eval(&mut builder);
184 match builder.take_error() {
185 Some(err) => Err(err),
186 None => Ok(()),
187 }
188}
189
190pub trait ValidateLookupAir {
197 fn validate(&self, layout: ValidateLayout) -> Result<(), ValidationError>;
198}
199
200impl<A> ValidateLookupAir for A
201where
202 for<'ab, 'r> A: LookupAir<ValidationBuilder<'ab, 'r>>,
203{
204 fn validate(&self, layout: ValidateLayout) -> Result<(), ValidationError> {
205 validate(self, layout)
206 }
207}
208
209#[derive(Clone, Copy)]
215struct RowValuation<'r> {
216 current: &'r [Felt],
217 next: &'r [Felt],
218 periodic: &'r [Felt],
219 alpha: QuadFelt,
221 beta: QuadFelt,
223}
224
225impl<'r> RowValuation<'r> {
226 fn eval_base(&self, expr: &Expr) -> Felt {
227 match expr {
228 SymbolicExpr::Leaf(leaf) => self.eval_base_leaf(leaf),
229 SymbolicExpr::Add { x, y, .. } => self.eval_base(x) + self.eval_base(y),
230 SymbolicExpr::Sub { x, y, .. } => self.eval_base(x) - self.eval_base(y),
231 SymbolicExpr::Neg { x, .. } => -self.eval_base(x),
232 SymbolicExpr::Mul { x, y, .. } => self.eval_base(x) * self.eval_base(y),
233 }
234 }
235
236 fn eval_base_leaf(&self, leaf: &BaseLeaf<Felt>) -> Felt {
237 match leaf {
238 BaseLeaf::Constant(c) => *c,
239 BaseLeaf::Variable(SymbolicVariable { entry, index, .. }) => match entry {
240 BaseEntry::Main { offset: 0 } => self.current[*index],
241 BaseEntry::Main { offset: 1 } => self.next[*index],
242 BaseEntry::Periodic => self.periodic[*index],
243 BaseEntry::Main { offset } => {
244 panic!("unexpected main offset {offset} in LookupAir::eval")
245 },
246 BaseEntry::Preprocessed { .. } | BaseEntry::Public => {
249 panic!("unexpected {entry:?} leaf in LookupAir::eval")
250 },
251 },
252 BaseLeaf::IsFirstRow | BaseLeaf::IsLastRow | BaseLeaf::IsTransition => {
255 panic!("selector leaf {leaf:?} unexpected in LookupAir::eval")
256 },
257 }
258 }
259
260 fn eval_ext(&self, expr: &ExprEF) -> QuadFelt {
261 match expr {
262 SymbolicExpr::Leaf(leaf) => self.eval_ext_leaf(leaf),
263 SymbolicExpr::Add { x, y, .. } => self.eval_ext(x) + self.eval_ext(y),
264 SymbolicExpr::Sub { x, y, .. } => self.eval_ext(x) - self.eval_ext(y),
265 SymbolicExpr::Neg { x, .. } => -self.eval_ext(x),
266 SymbolicExpr::Mul { x, y, .. } => self.eval_ext(x) * self.eval_ext(y),
267 }
268 }
269
270 fn eval_ext_leaf(&self, leaf: &ExtLeaf<Felt, QuadFelt>) -> QuadFelt {
271 match leaf {
272 ExtLeaf::Base(inner) => self.eval_base(inner).into(),
273 ExtLeaf::ExtConstant(c) => *c,
274 ExtLeaf::ExtVariable(SymbolicVariableExt { entry, index, .. }) => match entry {
275 ExtEntry::Challenge => match *index {
276 0 => self.alpha,
277 1 => self.beta,
278 i => panic!("unexpected challenge index {i} in LookupAir::eval"),
279 },
280 ExtEntry::Permutation { .. } | ExtEntry::PermutationValue => {
283 panic!("unexpected {entry:?} leaf in LookupAir::eval")
284 },
285 },
286 }
287 }
288}
289
290pub struct ValidationBuilder<'ab, 'r> {
298 ab: &'ab mut Inner,
299 sym_challenges: Challenges<ExprEF>,
300 row_valuation: RowValuation<'r>,
301 column_idx: usize,
302 declared_columns: usize,
303 error: Option<ValidationError>,
304}
305
306impl<'ab, 'r> ValidationBuilder<'ab, 'r> {
307 fn new<A>(ab: &'ab mut Inner, air: &A, row_valuation: RowValuation<'r>) -> Self
308 where
309 A: LookupAir<Self>,
310 {
311 let (alpha, beta): (ExprEF, ExprEF) = {
312 let r = ab.permutation_randomness();
313 (r[0].into(), r[1].into())
314 };
315 let sym_challenges =
316 Challenges::<ExprEF>::new(alpha, beta, air.max_message_width(), air.num_bus_ids());
317 Self {
318 ab,
319 sym_challenges,
320 row_valuation,
321 column_idx: 0,
322 declared_columns: air.num_columns(),
323 error: None,
324 }
325 }
326
327 fn take_error(mut self) -> Option<ValidationError> {
328 if self.error.is_none() && self.column_idx != self.declared_columns {
329 self.error = Some(ValidationError::NumColumnsMismatch {
330 declared: self.declared_columns,
331 observed: self.column_idx,
332 });
333 }
334 self.error
335 }
336}
337
338impl<'ab, 'r> LookupBuilder for ValidationBuilder<'ab, 'r> {
339 type F = Felt;
340 type Expr = Expr;
341 type Var = SymbolicVariable<Felt>;
342
343 type EF = QuadFelt;
344 type ExprEF = ExprEF;
345 type VarEF = SymbolicVariableExt<Felt, QuadFelt>;
346
347 type PeriodicVar = SymbolicVariable<Felt>;
348
349 type MainWindow = <Inner as AirBuilder>::MainWindow;
350
351 type Column<'c>
352 = ValidationColumn<'c, 'r>
353 where
354 Self: 'c;
355
356 fn main(&self) -> Self::MainWindow {
357 self.ab.main()
358 }
359
360 fn periodic_values(&self) -> &[Self::PeriodicVar] {
361 self.ab.periodic_values()
362 }
363
364 fn next_column<'c, R>(&'c mut self, f: impl FnOnce(&mut Self::Column<'c>) -> R, deg: Deg) -> R {
365 let column_idx = self.column_idx;
366 self.column_idx += 1;
367
368 let already_errored = self.error.is_some();
369 let mut col = ValidationColumn {
370 challenges: &self.sym_challenges,
371 row_valuation: self.row_valuation,
372 u: ExprEF::ONE,
373 v: ExprEF::ZERO,
374 column_idx,
375 next_group_idx: 0,
376 error: None,
377 _phantom: PhantomData,
378 };
379 let result = f(&mut col);
380
381 if !already_errored {
382 if let Some(err) = col.error.take() {
383 self.error = Some(err);
384 } else {
385 let observed = Deg {
386 v: col.v.degree_multiple(),
387 u: col.u.degree_multiple(),
388 };
389 if observed != deg {
390 self.error = Some(ValidationError::ColumnDegreeMismatch {
391 column_idx,
392 declared: deg,
393 observed,
394 });
395 }
396 }
397 }
398
399 result
400 }
401}
402
403pub struct ValidationColumn<'c, 'r> {
407 challenges: &'c Challenges<ExprEF>,
408 row_valuation: RowValuation<'r>,
409 u: ExprEF,
410 v: ExprEF,
411 column_idx: usize,
412 next_group_idx: usize,
413 error: Option<ValidationError>,
416 _phantom: PhantomData<&'c ()>,
417}
418
419impl<'c, 'r> ValidationColumn<'c, 'r> {
420 fn fold_group(&mut self, u_g: ExprEF, v_g: ExprEF) {
421 self.v = self.v.clone() * u_g.clone() + v_g * self.u.clone();
422 self.u = self.u.clone() * u_g;
423 }
424
425 fn check_group_degree(
426 &mut self,
427 name: &'static str,
428 group_idx: usize,
429 declared: Deg,
430 u: &ExprEF,
431 v: &ExprEF,
432 ) {
433 if self.error.is_some() {
434 return;
435 }
436 let observed = Deg {
437 v: v.degree_multiple(),
438 u: u.degree_multiple(),
439 };
440 if observed != declared {
441 self.error = Some(ValidationError::GroupDegreeMismatch {
442 column_idx: self.column_idx,
443 group_idx,
444 name,
445 declared,
446 observed,
447 });
448 }
449 }
450}
451
452fn fresh_group<'g>(
456 challenges: &'g Challenges<ExprEF>,
457 inside_encoded_closure: bool,
458) -> ValidationGroup<'g> {
459 ValidationGroup {
460 challenges,
461 u: ExprEF::ONE,
462 v: ExprEF::ZERO,
463 inside_encoded_closure,
464 used_insert_encoded: false,
465 }
466}
467
468impl<'c, 'r> LookupColumn for ValidationColumn<'c, 'r> {
469 type Expr = Expr;
470 type ExprEF = ExprEF;
471
472 type Group<'g>
473 = ValidationGroup<'g>
474 where
475 Self: 'g;
476
477 fn group<'g>(&'g mut self, name: &'static str, f: impl FnOnce(&mut Self::Group<'g>), deg: Deg) {
478 let group_idx = self.next_group_idx;
479 self.next_group_idx += 1;
480
481 let mut group = fresh_group(self.challenges, false);
482 f(&mut group);
483 let ValidationGroup { u, v, used_insert_encoded, .. } = group;
484
485 if self.error.is_none() && used_insert_encoded {
486 self.error = Some(ValidationError::ScopeViolation {
487 column_idx: self.column_idx,
488 group_idx,
489 name,
490 });
491 }
492 self.check_group_degree(name, group_idx, deg, &u, &v);
493 self.fold_group(u, v);
494 }
495
496 fn group_with_cached_encoding<'g>(
497 &'g mut self,
498 name: &'static str,
499 canonical: impl FnOnce(&mut Self::Group<'g>),
500 encoded: impl FnOnce(&mut Self::Group<'g>),
501 deg: Deg,
502 ) {
503 let group_idx = self.next_group_idx;
504 self.next_group_idx += 1;
505
506 let mut canon = fresh_group(self.challenges, false);
507 canonical(&mut canon);
508
509 let mut enc = fresh_group(self.challenges, true);
510 encoded(&mut enc);
511
512 if self.error.is_none() {
517 let diff_expr = canon.v.clone() * enc.u.clone() - enc.v.clone() * canon.u;
518 let diff = self.row_valuation.eval_ext(&diff_expr);
519 if diff != QuadFelt::ZERO {
520 self.error = Some(ValidationError::EncodingMismatch {
521 column_idx: self.column_idx,
522 group_idx,
523 name,
524 diff,
525 });
526 }
527 }
528
529 let ValidationGroup { u, v, .. } = enc;
532 self.check_group_degree(name, group_idx, deg, &u, &v);
533 self.fold_group(u, v);
534 }
535}
536
537pub struct ValidationGroup<'g> {
541 challenges: &'g Challenges<ExprEF>,
542 u: ExprEF,
543 v: ExprEF,
544 inside_encoded_closure: bool,
547 used_insert_encoded: bool,
551}
552
553impl<'g> LookupGroup for ValidationGroup<'g> {
554 type Expr = Expr;
555 type ExprEF = ExprEF;
556
557 type Batch<'b>
558 = ValidationBatch<'b>
559 where
560 Self: 'b;
561
562 fn add<M>(&mut self, _name: &'static str, flag: Expr, msg: impl FnOnce() -> M, _deg: Deg)
563 where
564 M: LookupMessage<Expr, ExprEF>,
565 {
566 let v_msg = msg().encode(self.challenges);
567 self.u += (v_msg - ExprEF::ONE) * flag.clone();
568 self.v += flag;
569 }
570
571 fn remove<M>(&mut self, _name: &'static str, flag: Expr, msg: impl FnOnce() -> M, _deg: Deg)
572 where
573 M: LookupMessage<Expr, ExprEF>,
574 {
575 let v_msg = msg().encode(self.challenges);
576 self.u += (v_msg - ExprEF::ONE) * flag.clone();
577 self.v -= flag;
578 }
579
580 fn insert<M>(
581 &mut self,
582 _name: &'static str,
583 flag: Expr,
584 multiplicity: Expr,
585 msg: impl FnOnce() -> M,
586 _deg: Deg,
587 ) where
588 M: LookupMessage<Expr, ExprEF>,
589 {
590 let v_msg = msg().encode(self.challenges);
591 self.u += (v_msg - ExprEF::ONE) * flag.clone();
592 self.v += flag * multiplicity;
593 }
594
595 fn batch<'b>(
596 &'b mut self,
597 _name: &'static str,
598 flag: Expr,
599 build: impl FnOnce(&mut Self::Batch<'b>),
600 _deg: Deg,
601 ) {
602 let mut batch = ValidationBatch {
603 challenges: self.challenges,
604 n: ExprEF::ZERO,
605 d: ExprEF::ONE,
606 };
607 build(&mut batch);
608 let ValidationBatch { n, d, .. } = batch;
609 self.u += (d - ExprEF::ONE) * flag.clone();
610 self.v += n * flag;
611 }
612
613 fn beta_powers(&self) -> &[ExprEF] {
614 &self.challenges.beta_powers[..]
615 }
616
617 fn bus_prefix(&self, bus_id: usize) -> ExprEF {
618 self.challenges.bus_prefix[bus_id].clone()
619 }
620
621 fn insert_encoded(
622 &mut self,
623 _name: &'static str,
624 flag: Expr,
625 multiplicity: Expr,
626 encoded: impl FnOnce() -> ExprEF,
627 _deg: Deg,
628 ) {
629 if !self.inside_encoded_closure {
630 self.used_insert_encoded = true;
631 }
632 let v_msg = encoded();
633 self.u += (v_msg - ExprEF::ONE) * flag.clone();
634 self.v += flag * multiplicity;
635 }
636}
637
638pub struct ValidationBatch<'b> {
642 challenges: &'b Challenges<ExprEF>,
643 n: ExprEF,
644 d: ExprEF,
645}
646
647impl<'b> LookupBatch for ValidationBatch<'b> {
648 type Expr = Expr;
649 type ExprEF = ExprEF;
650
651 fn insert<M>(&mut self, _name: &'static str, multiplicity: Expr, msg: M, _deg: Deg)
652 where
653 M: LookupMessage<Expr, ExprEF>,
654 {
655 let v_msg = msg.encode(self.challenges);
656 let d_prev = self.d.clone();
657 self.n = self.n.clone() * v_msg.clone() + d_prev * multiplicity;
658 self.d = self.d.clone() * v_msg;
659 }
660
661 fn insert_encoded(
662 &mut self,
663 _name: &'static str,
664 multiplicity: Expr,
665 encoded: impl FnOnce() -> ExprEF,
666 _deg: Deg,
667 ) {
668 let v_msg = encoded();
669 let d_prev = self.d.clone();
670 self.n = self.n.clone() * v_msg.clone() + d_prev * multiplicity;
671 self.d = self.d.clone() * v_msg;
672 }
673}