1use std::{borrow::Cow, iter};
4
5use super::{ValueVec, constraint::Operand};
6use crate::{
7 ConstraintSystem, Word,
8 error::{ConstraintSystemError, VerificationM4Error},
9};
10
11mod witness;
12
13pub use witness::WitnessM4;
14
15pub trait ChipInstances {
25 fn n_chips(&self) -> usize;
27
28 fn n_instances(&self, chip_id: usize) -> usize;
30
31 fn instance(&self, chip_id: usize, row: usize) -> Cow<'_, ValueVec>;
33}
34
35impl ChipInstances for [Vec<ValueVec>] {
36 fn n_chips(&self) -> usize {
37 self.len()
38 }
39
40 fn n_instances(&self, chip_id: usize) -> usize {
41 self[chip_id].len()
42 }
43
44 fn instance(&self, chip_id: usize, row: usize) -> Cow<'_, ValueVec> {
45 Cow::Borrowed(&self[chip_id][row])
46 }
47}
48
49#[derive(Debug, Clone)]
61pub struct EmbeddedConstraintSystem {
62 pub cs: ConstraintSystem,
64 pub chip_calls: Vec<ChipCall>,
66}
67
68impl EmbeddedConstraintSystem {
69 pub fn validate(&self) -> Result<(), ConstraintSystemError> {
71 self.cs.validate()?;
72
73 for (call_index, chip_call) in self.chip_calls.iter().enumerate() {
74 for (operand_index, operand) in chip_call.inout.iter().enumerate() {
75 if let Some(source) = self.cs.operand_fault(operand) {
76 return Err(ConstraintSystemError::ChipCallOperand {
77 call_index,
78 operand_index,
79 source,
80 });
81 }
82 }
83 }
84
85 Ok(())
86 }
87}
88
89#[derive(Debug, Clone)]
91pub struct ChipCall {
92 pub chip_id: usize,
94 pub first_instance: usize,
104 pub inout: Vec<Operand>,
110}
111
112pub struct ConstraintSystemM4 {
128 pub main: EmbeddedConstraintSystem,
131 pub chips: Vec<(EmbeddedConstraintSystem, usize)>,
137}
138
139impl ConstraintSystemM4 {
140 pub fn validate(&self) -> Result<(), ConstraintSystemError> {
143 self.main.validate()?;
144 self.validate_calls(None, &self.main.chip_calls)?;
145
146 for (chip_index, (chip, _)) in self.chips.iter().enumerate() {
147 chip.validate()?;
148 self.validate_calls(Some(chip_index), &chip.chip_calls)?;
149 }
150
151 self.validate_instances()
152 }
153
154 fn validate_calls(
161 &self,
162 chip_index: Option<usize>,
163 calls: &[ChipCall],
164 ) -> Result<(), ConstraintSystemError> {
165 let n_chips = self.chips.len();
166 for (call_index, call) in calls.iter().enumerate() {
167 if call.chip_id >= n_chips {
168 return Err(ConstraintSystemError::OutOfRangeChipId {
169 chip_index,
170 chip_id: call.chip_id,
171 n_chips,
172 });
173 }
174 if let Some(caller) = chip_index
175 && call.chip_id <= caller
176 {
177 return Err(ConstraintSystemError::CallOutOfOrder {
178 chip_index: caller,
179 callee: call.chip_id,
180 });
181 }
182 let n_inout = self.chips[call.chip_id].0.cs.n_inout;
183 if call.inout.len() > n_inout {
184 return Err(ConstraintSystemError::WrongCallArity {
185 chip_index,
186 call_index,
187 chip_id: call.chip_id,
188 arity: call.inout.len(),
189 n_inout,
190 });
191 }
192 }
193 Ok(())
194 }
195
196 fn validate_instances(&self) -> Result<(), ConstraintSystemError> {
206 let mut n_claimed = vec![0usize; self.chips.len()];
207 let callers = iter::once((None, &self.main, 1)).chain(
208 self.chips
209 .iter()
210 .enumerate()
211 .map(|(chip_index, (chip, n_active))| (Some(chip_index), chip, *n_active)),
212 );
213 for (chip_index, caller, n_active) in callers {
214 for (call_index, call) in caller.chip_calls.iter().enumerate() {
215 let claimed = &mut n_claimed[call.chip_id];
216 if call.first_instance != *claimed {
217 return Err(ConstraintSystemError::WrongCallInstance {
218 chip_index,
219 call_index,
220 first_instance: call.first_instance,
221 expected: *claimed,
222 });
223 }
224
225 *claimed = claimed.checked_add(n_active).ok_or(
230 ConstraintSystemError::TooManyInstances {
231 chip_id: call.chip_id,
232 },
233 )?;
234 }
235 }
236
237 for (chip_id, ((_, n_active), &claimed)) in iter::zip(&self.chips, &n_claimed).enumerate() {
238 if claimed != *n_active {
239 return Err(ConstraintSystemError::WrongActiveInstanceCount {
240 chip_id,
241 declared: *n_active,
242 actual: claimed,
243 });
244 }
245 }
246
247 Ok(())
248 }
249
250 pub fn verify<I: ChipInstances + ?Sized>(
278 &self,
279 main: &ValueVec,
280 chip_instances: &I,
281 ) -> Result<(), VerificationM4Error> {
282 if chip_instances.n_chips() != self.chips.len() {
283 return Err(VerificationM4Error::WrongChipCount {
284 n_witness_chips: chip_instances.n_chips(),
285 n_chips: self.chips.len(),
286 });
287 }
288
289 self.main.cs.verify(main)?;
290 for (chip_id, (chip, n_active)) in self.chips.iter().enumerate() {
291 let n_instances = chip_instances.n_instances(chip_id);
292 if n_instances < *n_active {
293 return Err(VerificationM4Error::MissingInstances {
294 chip_id,
295 n_instances,
296 n_active: *n_active,
297 });
298 }
299 for instance in 0..n_instances {
300 chip.cs
301 .verify(&chip_instances.instance(chip_id, instance))
302 .map_err(|source| VerificationM4Error::ChipInstance {
303 chip_id,
304 instance,
305 source,
306 })?;
307 }
308 }
309
310 self.check_caller(None, &self.main.chip_calls, main, chip_instances)?;
311 for (caller_chip, (chip, n_active)) in self.chips.iter().enumerate() {
312 for caller_instance in 0..*n_active {
313 let values = chip_instances.instance(caller_chip, caller_instance);
314 self.check_caller(
315 Some((caller_chip, caller_instance)),
316 &chip.chip_calls,
317 &values,
318 chip_instances,
319 )?;
320 }
321 }
322
323 Ok(())
324 }
325
326 fn check_caller<I: ChipInstances + ?Sized>(
334 &self,
335 caller: Option<(usize, usize)>,
336 calls: &[ChipCall],
337 values: &ValueVec,
338 chip_instances: &I,
339 ) -> Result<(), VerificationM4Error> {
340 let caller_instance = caller.map_or(0, |(_, instance)| instance);
342 for (call_index, call) in calls.iter().enumerate() {
343 let chip_id = call.chip_id;
344 let chip = &self.chips[chip_id].0;
345 let row = call.first_instance + caller_instance;
346
347 let n_inout = chip.cs.n_inout;
350 let instance = chip_instances.instance(chip_id, row);
351 let served = instance.inout();
352 for word in 0..n_inout {
353 let passed = call
354 .inout
355 .get(word)
356 .map(|operand| values.eval_operand(operand))
357 .unwrap_or(Word::ZERO);
358 if served[word] != passed {
359 return Err(VerificationM4Error::CallMismatch {
360 chip_id,
361 row,
362 caller,
363 call_index,
364 word,
365 passed: passed.as_u64(),
366 served: served[word].as_u64(),
367 });
368 }
369 }
370 }
371 Ok(())
372 }
373}
374
375#[cfg(test)]
376mod tests {
377 use super::{
378 super::{ShiftedValueIndex, ValueIndex, ValueSegment, ValueVecLayout, ZeroConstraint},
379 *,
380 };
381 use crate::error::{OperandFault, VerificationError};
382
383 const LAYOUT: ValueVecLayout = ValueVecLayout {
385 n_const: 0,
386 n_inout: 8,
387 n_witness: 8,
388 n_internal: 0,
389 n_scratch: 0,
390 };
391
392 fn chip(callees: &[usize]) -> EmbeddedConstraintSystem {
397 let cs = LAYOUT.constraint_system_shape(vec![]);
398 let chip_calls = callees
399 .iter()
400 .map(|&chip_id| ChipCall {
401 chip_id,
402 first_instance: 0,
403 inout: vec![],
404 })
405 .collect();
406 EmbeddedConstraintSystem { cs, chip_calls }
407 }
408
409 fn instance() -> ValueVec {
411 ValueVec::new(&LAYOUT)
412 }
413
414 fn system(main_callees: &[usize], chips: Vec<EmbeddedConstraintSystem>) -> ConstraintSystemM4 {
422 let mut cs = ConstraintSystemM4 {
423 main: chip(main_callees),
424 chips: chips.into_iter().map(|chip| (chip, 0)).collect(),
425 };
426
427 let mut n_calls = vec![0usize; cs.chips.len()];
428 for call in &mut cs.main.chip_calls {
429 call.first_instance = n_calls[call.chip_id];
430 n_calls[call.chip_id] += 1;
431 }
432 for chip_index in 0..cs.chips.len() {
433 let n_active = n_calls[chip_index];
434 cs.chips[chip_index].1 = n_active;
435 for call in &mut cs.chips[chip_index].0.chip_calls {
436 call.first_instance = n_calls[call.chip_id];
437 n_calls[call.chip_id] = n_calls[call.chip_id].saturating_add(n_active);
438 }
439 }
440 cs
441 }
442
443 #[test]
444 fn validate_rejects_a_chip_call_operand_past_its_own_segment() {
445 let mut cs = system(&[0], vec![chip(&[])]);
448 cs.main.chip_calls[0].inout = vec![vec![ShiftedValueIndex::plain(ValueIndex::inout(8))]];
449 assert!(matches!(
450 cs.validate(),
451 Err(ConstraintSystemError::ChipCallOperand {
452 call_index: 0,
453 operand_index: 0,
454 source: OperandFault::OutOfRangeValueIndex {
455 segment: ValueSegment::InOut,
456 value_index: 8,
457 segment_len: 8,
458 },
459 })
460 ));
461 }
462
463 #[test]
464 fn validate_rejects_a_call_passing_more_operands_than_the_callee_takes() {
465 let mut cs = system(&[0], vec![chip(&[])]);
467 cs.main.chip_calls[0].inout = vec![vec![]; 9];
468 assert!(matches!(
469 cs.validate(),
470 Err(ConstraintSystemError::WrongCallArity {
471 chip_index: None,
472 call_index: 0,
473 chip_id: 0,
474 arity: 9,
475 n_inout: 8,
476 })
477 ));
478 }
479
480 #[test]
481 fn validate_accepts_calls_running_only_to_later_chips() {
482 let cs = system(&[0], vec![chip(&[1, 2]), chip(&[2]), chip(&[])]);
485 cs.validate().unwrap();
486 }
487
488 #[test]
489 fn validate_rejects_a_chip_that_calls_itself() {
490 let cs = system(&[0], vec![chip(&[0])]);
493 assert!(matches!(
494 cs.validate(),
495 Err(ConstraintSystemError::CallOutOfOrder {
496 chip_index: 0,
497 callee: 0,
498 })
499 ));
500 }
501
502 #[test]
503 fn validate_rejects_a_chip_that_calls_an_earlier_chip() {
504 let cs = system(&[0], vec![chip(&[1]), chip(&[2]), chip(&[1])]);
507 assert!(matches!(
508 cs.validate(),
509 Err(ConstraintSystemError::CallOutOfOrder {
510 chip_index: 2,
511 callee: 1,
512 })
513 ));
514 }
515
516 #[test]
517 fn validate_rejects_a_call_to_a_chip_that_does_not_exist() {
518 let mut cs = system(&[0], vec![chip(&[1]), chip(&[])]);
521 cs.chips[0].0.chip_calls.push(ChipCall {
522 chip_id: 7,
523 first_instance: 0,
524 inout: vec![],
525 });
526 assert!(matches!(
527 cs.validate(),
528 Err(ConstraintSystemError::OutOfRangeChipId {
529 chip_index: Some(0),
530 chip_id: 7,
531 n_chips: 2,
532 })
533 ));
534 }
535
536 #[test]
537 fn validate_rejects_a_main_call_to_a_chip_that_does_not_exist() {
538 let mut cs = system(&[0], vec![chip(&[])]);
539 cs.main.chip_calls[0].chip_id = 7;
540 assert!(matches!(
541 cs.validate(),
542 Err(ConstraintSystemError::OutOfRangeChipId {
543 chip_index: None,
544 chip_id: 7,
545 n_chips: 1,
546 })
547 ));
548 }
549
550 #[test]
553 fn validate_rejects_a_call_naming_the_wrong_instance() {
554 let mut cs = system(&[0, 0], vec![chip(&[])]);
555 cs.main.chip_calls[1].first_instance = 0;
556 assert!(matches!(
557 cs.validate(),
558 Err(ConstraintSystemError::WrongCallInstance {
559 chip_index: None,
560 call_index: 1,
561 first_instance: 0,
562 expected: 1,
563 })
564 ));
565 }
566
567 #[test]
570 fn validate_rejects_more_invocations_than_a_chip_has_active_instances() {
571 let mut cs = system(&[0, 0], vec![chip(&[])]);
573 cs.chips[0].1 = 1;
574 assert!(matches!(
575 cs.validate(),
576 Err(ConstraintSystemError::WrongActiveInstanceCount {
577 chip_id: 0,
578 declared: 1,
579 actual: 2,
580 })
581 ));
582 }
583
584 #[test]
587 fn validate_rejects_an_instance_count_that_outgrows_a_usize() {
588 const DEPTH: usize = 70;
590 let chips = (0..DEPTH)
591 .map(|i| {
592 let callees = if i + 1 < DEPTH {
593 vec![i + 1, i + 1]
594 } else {
595 vec![]
596 };
597 chip(&callees)
598 })
599 .collect();
600
601 let cs = system(&[0], chips);
604 assert!(matches!(
605 cs.validate(),
606 Err(ConstraintSystemError::TooManyInstances { chip_id: 64 })
607 ));
608 }
609
610 #[test]
613 fn verify_constrains_the_inout_values_a_call_passes_no_operand_for() {
614 let cs = system(&[0], vec![chip(&[])]);
615 let main = instance();
616 cs.verify(&main, &[vec![instance()]][..]).unwrap();
617
618 let mut served = instance();
621 served[ValueIndex::inout(3)] = Word::from_u64(0xdead);
622 let err = cs.verify(&main, &[vec![served]][..]).unwrap_err();
623 assert!(
624 matches!(
625 err,
626 VerificationM4Error::CallMismatch {
627 chip_id: 0,
628 row: 0,
629 caller: None,
630 word: 3,
631 passed: 0,
632 served: 0xdead,
633 ..
634 }
635 ),
636 "{err}"
637 );
638 }
639
640 #[test]
641 fn verify_rejects_a_witness_covering_the_wrong_number_of_chips() {
642 let cs = system(&[0], vec![chip(&[])]);
643 let err = cs.verify(&instance(), &[][..]).unwrap_err();
644 assert!(
645 matches!(
646 err,
647 VerificationM4Error::WrongChipCount {
648 n_witness_chips: 0,
649 n_chips: 1,
650 }
651 ),
652 "{err}"
653 );
654 }
655
656 #[test]
657 fn verify_rejects_a_chip_with_fewer_instances_than_active_ones() {
658 let cs = system(&[0], vec![chip(&[])]);
659 let err = cs.verify(&instance(), &[vec![]][..]).unwrap_err();
660 assert!(
661 matches!(
662 err,
663 VerificationM4Error::MissingInstances {
664 chip_id: 0,
665 n_instances: 0,
666 n_active: 1,
667 }
668 ),
669 "{err}"
670 );
671 }
672
673 #[test]
674 fn verify_reports_the_instance_whose_own_constraints_fail() {
675 let mut cs = system(&[0], vec![chip(&[])]);
676 cs.chips[0]
679 .0
680 .cs
681 .zero_constraints
682 .push(ZeroConstraint::plain([ValueIndex::inout(0)]));
683
684 let mut padding = instance();
685 padding[ValueIndex::inout(0)] = Word::ONE;
686 let err = cs
687 .verify(&instance(), &[vec![instance(), padding]][..])
688 .unwrap_err();
689 assert!(
690 matches!(
691 err,
692 VerificationM4Error::ChipInstance {
693 chip_id: 0,
694 instance: 1,
695 source: VerificationError::Unsatisfied { .. },
696 }
697 ),
698 "{err}"
699 );
700 }
701}