1use std::iter;
4
5use binius_utils::{
6 checked_arithmetics::log2_ceil_usize,
7 serialization::{DeserializeBytes, SerializationError, SerializeBytes},
8};
9use bytes::{Buf, BufMut};
10
11use super::{
12 AndConstraint, BmulConstraint, Composition, ConstraintKind, ImulConstraint, Operand, Shift,
13 ValueIndex, ValueSegment, ValueVec, ZeroConstraint,
14};
15use crate::{
16 error::{ConstraintSystemError, OperandFault, VerificationError},
17 word::Word,
18};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum InoutSegment {
28 Public,
31 Hidden,
36}
37
38#[derive(Debug, Clone)]
78pub struct ConstraintSystem {
79 pub constants: Vec<Word>,
84 pub n_inout: usize,
86 pub n_private: usize,
88 pub zero_constraints: Vec<ZeroConstraint>,
90 pub and_constraints: Vec<AndConstraint>,
92 pub imul_constraints: Vec<ImulConstraint>,
94 pub bmul_constraints: Vec<BmulConstraint>,
96}
97
98impl ConstraintSystem {
99 pub const SERIALIZATION_VERSION: u32 = 10;
101
102 pub const fn n_const(&self) -> usize {
104 self.constants.len()
105 }
106
107 pub const fn offset_inout(&self) -> usize {
109 self.n_const()
110 }
111
112 pub const fn n_public_values(&self) -> usize {
114 self.n_const() + self.n_inout
115 }
116
117 pub const fn n_public_words(&self, inout: InoutSegment) -> usize {
121 match inout {
122 InoutSegment::Public => self.n_public_values(),
123 InoutSegment::Hidden => self.n_const(),
124 }
125 }
126
127 pub const fn log_public_words(&self, inout: InoutSegment) -> usize {
131 log2_ceil_usize(self.n_public_words(inout))
132 }
133
134 pub const fn n_hidden_words(&self, inout: InoutSegment) -> usize {
138 match inout {
139 InoutSegment::Public => self.n_private,
140 InoutSegment::Hidden => self.n_inout + self.n_private,
141 }
142 }
143
144 pub const fn log_witness_words(&self, inout: InoutSegment) -> usize {
146 log2_ceil_usize(self.n_hidden_words(inout))
147 }
148
149 pub const fn log_segment_words(&self, inout: InoutSegment) -> usize {
155 if self.log_public_words(inout) > self.log_witness_words(inout) {
156 self.log_public_words(inout)
157 } else {
158 self.log_witness_words(inout)
159 }
160 }
161
162 pub const fn segment_len(&self, segment: ValueSegment) -> usize {
167 match segment {
168 ValueSegment::Constant => self.n_const(),
169 ValueSegment::InOut => self.n_inout,
170 ValueSegment::Private => self.n_private,
171 ValueSegment::Scratch => 0,
172 }
173 }
174
175 pub const fn word_offset(&self, index: ValueIndex) -> usize {
183 let segment_start = match index.segment() {
184 ValueSegment::Constant => 0,
185 ValueSegment::InOut => self.offset_inout(),
186 ValueSegment::Private => self.n_public_values(),
187 ValueSegment::Scratch => self.value_vec_len(),
188 };
189 segment_start + index.index() as usize
190 }
191
192 pub fn value_vec_from_data(&self, inout: &[Word], private: &[Word]) -> ValueVec {
197 let public = [self.constants.as_slice(), inout].concat();
198 ValueVec::new_from_data(self.n_const(), &public, private)
199 }
200
201 pub fn validate(&self) -> Result<(), ConstraintSystemError> {
212 tracing::debug_span!("Validating constraint system");
213
214 self.validate_constraints(
215 &self.zero_constraints,
216 ZeroConstraint::KIND,
217 ZeroConstraint::OPERAND_NAMES,
218 )?;
219 self.validate_constraints(
220 &self.and_constraints,
221 AndConstraint::KIND,
222 AndConstraint::OPERAND_NAMES,
223 )?;
224 self.validate_constraints(
225 &self.imul_constraints,
226 ImulConstraint::KIND,
227 ImulConstraint::OPERAND_NAMES,
228 )?;
229 self.validate_constraints(
230 &self.bmul_constraints,
231 BmulConstraint::KIND,
232 BmulConstraint::OPERAND_NAMES,
233 )?;
234
235 Ok(())
236 }
237
238 pub fn verify(&self, values: &ValueVec) -> Result<(), VerificationError> {
253 for (index, &constant) in self.constants.iter().enumerate() {
256 let value_index = index as u32;
257 let actual = values[ValueIndex::constant(value_index)];
258 if actual != constant {
259 return Err(VerificationError::ConstantMismatch {
260 value_index,
261 expected: constant.as_u64(),
262 actual: actual.as_u64(),
263 });
264 }
265 }
266
267 for (constraint_index, constraint) in self.zero_constraints.iter().enumerate() {
270 constraint
271 .verify(values)
272 .map_err(|source| VerificationError::Unsatisfied {
273 constraint_index,
274 source,
275 })?;
276 }
277 for (constraint_index, constraint) in self.and_constraints.iter().enumerate() {
278 constraint
279 .verify(values)
280 .map_err(|source| VerificationError::Unsatisfied {
281 constraint_index,
282 source,
283 })?;
284 }
285 for (constraint_index, constraint) in self.imul_constraints.iter().enumerate() {
286 constraint
287 .verify(values)
288 .map_err(|source| VerificationError::Unsatisfied {
289 constraint_index,
290 source,
291 })?;
292 }
293 for (constraint_index, constraint) in self.bmul_constraints.iter().enumerate() {
294 constraint
295 .verify(values)
296 .map_err(|source| VerificationError::Unsatisfied {
297 constraint_index,
298 source,
299 })?;
300 }
301
302 Ok(())
303 }
304
305 fn validate_constraints<C: AsRef<[Operand; ARITY]>, const ARITY: usize>(
307 &self,
308 constraints: &[C],
309 constraint_kind: ConstraintKind,
310 operand_names: [&'static str; ARITY],
311 ) -> Result<(), ConstraintSystemError> {
312 for (i, constraint) in constraints.iter().enumerate() {
313 for (operand, name) in iter::zip(constraint.as_ref(), operand_names) {
314 self.validate_operand(operand, constraint_kind, i, name)?;
315 }
316 }
317 Ok(())
318 }
319
320 fn validate_operand(
322 &self,
323 operand: &Operand,
324 constraint_kind: ConstraintKind,
325 constraint_index: usize,
326 operand_name: &'static str,
327 ) -> Result<(), ConstraintSystemError> {
328 match self.operand_fault(operand) {
329 None => Ok(()),
330 Some(source) => Err(ConstraintSystemError::ConstraintOperand {
331 constraint_kind,
332 constraint_index,
333 operand_name,
334 source,
335 }),
336 }
337 }
338
339 pub fn operand_fault(&self, operand: &Operand) -> Option<OperandFault> {
345 operand.iter().find_map(|term| {
346 for shift in term.shift_seq {
347 if !shift.is_canonical() {
349 return Some(OperandFault::NonCanonicalShift);
350 }
351 let max_amount = shift.variant.max_amount();
355 if usize::from(shift.amount) >= max_amount {
356 return Some(OperandFault::ShiftAmountTooLarge {
357 shift_amount: shift.amount as usize,
358 max_amount,
359 });
360 }
361 }
362 if term.is_unshifted() && term.is_doubly_shifted() {
366 return Some(OperandFault::NonCanonicalShiftSequence);
367 }
368 if term.is_doubly_shifted() {
372 let composition = Shift::compose(term.inner(), term.outer());
373 if composition != Composition::Pair {
374 return Some(OperandFault::CollapsibleShiftSequence { composition });
375 }
376 }
377 let segment = term.value_index.segment();
380 if !segment.is_referenceable() {
381 return Some(OperandFault::ScratchValueIndex);
382 }
383 let segment_len = self.segment_len(segment);
385 if term.value_index.index() as usize >= segment_len {
386 return Some(OperandFault::OutOfRangeValueIndex {
387 segment,
388 value_index: term.value_index.index(),
389 segment_len,
390 });
391 }
392 None
393 })
394 }
395
396 pub const fn n_zero_constraints(&self) -> usize {
398 self.zero_constraints.len()
399 }
400
401 pub const fn n_and_constraints(&self) -> usize {
403 self.and_constraints.len()
404 }
405
406 pub const fn n_imul_constraints(&self) -> usize {
408 self.imul_constraints.len()
409 }
410
411 pub const fn n_bmul_constraints(&self) -> usize {
413 self.bmul_constraints.len()
414 }
415
416 pub const fn log_zero_constraints(&self) -> Option<usize> {
425 match self.n_zero_constraints() {
426 0 => None,
427 n => Some(log2_ceil_usize(n)),
428 }
429 }
430
431 pub const fn log_and_constraints(&self) -> Option<usize> {
440 match self.n_and_constraints() {
441 0 => None,
442 n => Some(log2_ceil_usize(n)),
443 }
444 }
445
446 pub const fn log_imul_constraints(&self) -> Option<usize> {
454 match self.n_imul_constraints() {
455 0 => None,
456 n => Some(log2_ceil_usize(n)),
457 }
458 }
459
460 pub const fn log_bmul_constraints(&self) -> Option<usize> {
467 match self.n_bmul_constraints() {
468 0 => None,
469 n => Some(log2_ceil_usize(n)),
470 }
471 }
472
473 pub const fn value_vec_len(&self) -> usize {
475 self.n_public_values() + self.n_private
476 }
477}
478
479impl SerializeBytes for ConstraintSystem {
480 fn serialize(&self, mut write_buf: impl BufMut) -> Result<(), SerializationError> {
481 Self::SERIALIZATION_VERSION.serialize(&mut write_buf)?;
482
483 self.constants.serialize(&mut write_buf)?;
484 self.n_inout.serialize(&mut write_buf)?;
485 self.n_private.serialize(&mut write_buf)?;
486 self.zero_constraints.serialize(&mut write_buf)?;
487 self.and_constraints.serialize(&mut write_buf)?;
488 self.imul_constraints.serialize(&mut write_buf)?;
489 self.bmul_constraints.serialize(write_buf)
490 }
491}
492
493impl DeserializeBytes for ConstraintSystem {
494 fn deserialize(mut read_buf: impl Buf) -> Result<Self, SerializationError>
495 where
496 Self: Sized,
497 {
498 let version = u32::deserialize(&mut read_buf)?;
499 if version != Self::SERIALIZATION_VERSION {
500 return Err(SerializationError::InvalidConstruction {
501 name: "ConstraintSystem::version",
502 });
503 }
504
505 let constants = Vec::<Word>::deserialize(&mut read_buf)?;
506 let n_inout = usize::deserialize(&mut read_buf)?;
507 let n_private = usize::deserialize(&mut read_buf)?;
508 let zero_constraints = Vec::<ZeroConstraint>::deserialize(&mut read_buf)?;
509 let and_constraints = Vec::<AndConstraint>::deserialize(&mut read_buf)?;
510 let imul_constraints = Vec::<ImulConstraint>::deserialize(&mut read_buf)?;
511 let bmul_constraints = Vec::<BmulConstraint>::deserialize(read_buf)?;
512
513 Ok(ConstraintSystem {
514 constants,
515 n_inout,
516 n_private,
517 zero_constraints,
518 and_constraints,
519 imul_constraints,
520 bmul_constraints,
521 })
522 }
523}
524
525#[cfg(test)]
526mod tests {
527 use super::*;
528 use crate::{
529 constraint_system::{Shift, ShiftVariant, ShiftedValueIndex, ValuesData, ValuesRef},
530 error::ConstraintViolation,
531 };
532
533 fn test_shape() -> ConstraintSystem {
539 ConstraintSystem {
540 constants: vec![
541 Word::from_u64(1),
542 Word::from_u64(42),
543 Word::from_u64(0xDEADBEEF),
544 ],
545 n_inout: 2,
546 n_private: 6,
547 zero_constraints: vec![],
548 and_constraints: vec![],
549 imul_constraints: vec![],
550 bmul_constraints: vec![],
551 }
552 }
553
554 pub(crate) fn create_test_constraint_system() -> ConstraintSystem {
555 ConstraintSystem {
556 zero_constraints: vec![ZeroConstraint::plain([
557 ValueIndex::constant(0),
558 ValueIndex::inout(0),
559 ValueIndex::private(0),
560 ])],
561 and_constraints: vec![
562 AndConstraint::plain_abc(
563 vec![ValueIndex::constant(0), ValueIndex::constant(1)],
564 vec![ValueIndex::constant(2)],
565 vec![ValueIndex::inout(0), ValueIndex::inout(1)],
566 ),
567 AndConstraint::abc(
568 vec![ShiftedValueIndex::sll(ValueIndex::constant(0), 5)],
569 vec![ShiftedValueIndex::srl(ValueIndex::constant(1), 10)],
570 vec![ShiftedValueIndex::sar(ValueIndex::constant(2), 15)],
571 ),
572 ],
573 imul_constraints: vec![ImulConstraint([
574 vec![ShiftedValueIndex::plain(ValueIndex::constant(0))],
575 vec![ShiftedValueIndex::plain(ValueIndex::constant(1))],
576 vec![ShiftedValueIndex::plain(ValueIndex::constant(2))],
577 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
578 ])],
579 bmul_constraints: vec![BmulConstraint([
580 vec![ShiftedValueIndex::plain(ValueIndex::constant(0))],
581 vec![ShiftedValueIndex::plain(ValueIndex::constant(1))],
582 vec![ShiftedValueIndex::plain(ValueIndex::constant(2))],
583 vec![ShiftedValueIndex::plain(ValueIndex::inout(0))],
584 vec![ShiftedValueIndex::plain(ValueIndex::inout(1))],
585 vec![ShiftedValueIndex::sll(ValueIndex::constant(0), 5)],
586 ])],
587 ..test_shape()
588 }
589 }
590
591 #[test]
592 fn test_constraint_system_serialization_round_trip() {
593 let original = create_test_constraint_system();
594
595 let mut buf = Vec::new();
596 original.serialize(&mut buf).unwrap();
597
598 let deserialized = ConstraintSystem::deserialize(&mut buf.as_slice()).unwrap();
599
600 assert_eq!(ConstraintSystem::SERIALIZATION_VERSION, 10);
602
603 assert_eq!(original.constants, deserialized.constants);
605 assert_eq!(original.n_inout, deserialized.n_inout);
606 assert_eq!(original.n_private, deserialized.n_private);
607
608 assert_eq!(original.zero_constraints.len(), deserialized.zero_constraints.len());
610
611 assert_eq!(original.and_constraints.len(), deserialized.and_constraints.len());
613
614 assert_eq!(original.imul_constraints.len(), deserialized.imul_constraints.len());
616
617 assert_eq!(original.bmul_constraints.len(), deserialized.bmul_constraints.len());
619 }
620
621 #[test]
622 fn test_constraint_system_version_mismatch() {
623 let mut buf = Vec::new();
625 999u32.serialize(&mut buf).unwrap(); let result = ConstraintSystem::deserialize(&mut buf.as_slice());
628 assert!(result.is_err());
629 match result.unwrap_err() {
630 SerializationError::InvalidConstruction { name } => {
631 assert_eq!(name, "ConstraintSystem::version");
632 }
633 _ => panic!("Expected InvalidConstruction error"),
634 }
635 }
636
637 #[test]
638 fn test_serialization_with_different_sources() {
639 let original = create_test_constraint_system();
640
641 let mut vec_buf = Vec::new();
643 original.serialize(&mut vec_buf).unwrap();
644 let deserialized1 = ConstraintSystem::deserialize(&mut vec_buf.as_slice()).unwrap();
645 assert_eq!(original.constants.len(), deserialized1.constants.len());
646
647 let mut bytes_buf = bytes::BytesMut::new();
649 original.serialize(&mut bytes_buf).unwrap();
650 let deserialized2 = ConstraintSystem::deserialize(bytes_buf.freeze()).unwrap();
651 assert_eq!(original.constants.len(), deserialized2.constants.len());
652 }
653
654 #[test]
658 #[ignore] fn create_reference_binary_file() {
660 let constraint_system = create_test_constraint_system();
661
662 let mut buf = Vec::new();
664 constraint_system.serialize(&mut buf).unwrap();
665
666 let test_data_path = std::path::Path::new("test_data/constraint_system_v10.bin");
668
669 if let Some(parent) = test_data_path.parent() {
671 std::fs::create_dir_all(parent).unwrap();
672 }
673
674 std::fs::write(test_data_path, &buf).unwrap();
675
676 println!("Created reference binary file at: {:?}", test_data_path);
677 println!("Binary data length: {} bytes", buf.len());
678 }
679
680 #[test]
683 fn test_deserialize_from_reference_binary_file() {
684 let binary_data = include_bytes!("../../test_data/constraint_system_v10.bin");
688
689 let deserialized = ConstraintSystem::deserialize(&mut binary_data.as_slice()).unwrap();
690
691 assert_eq!(deserialized.n_const(), 3);
692 assert_eq!(deserialized.n_inout, 2);
693 assert_eq!(deserialized.n_private, 6);
694
695 assert_eq!(deserialized.constants[0].as_u64(), 1);
696 assert_eq!(deserialized.constants[1].as_u64(), 42);
697 assert_eq!(deserialized.constants[2].as_u64(), 0xDEADBEEF);
698
699 assert_eq!(deserialized.zero_constraints.len(), 1);
700 assert_eq!(deserialized.and_constraints.len(), 2);
701 assert_eq!(deserialized.imul_constraints.len(), 1);
702 assert_eq!(deserialized.bmul_constraints.len(), 1);
703
704 let version_bytes = &binary_data[0..4]; let expected_version_bytes = 10u32.to_le_bytes(); assert_eq!(
710 version_bytes, expected_version_bytes,
711 "Binary file version mismatch. If you made breaking changes, increment ConstraintSystem::SERIALIZATION_VERSION"
712 );
713 }
714
715 #[test]
716 fn test_log_witness_words() {
717 let cs = |n_private: usize| ConstraintSystem {
718 n_private,
719 ..test_shape()
720 };
721 assert_eq!(cs(60).log_witness_words(InoutSegment::Public), 6);
723 assert_eq!(cs(32).log_witness_words(InoutSegment::Public), 5);
725 }
726
727 #[test]
728 fn segment_lengths_are_the_value_counts() {
729 let cs = test_shape();
732 assert_eq!(cs.n_public_values(), 5);
733 assert_eq!(cs.n_public_words(InoutSegment::Public), 5);
734 assert_eq!(cs.n_hidden_words(InoutSegment::Public), 6);
735 assert_eq!(cs.value_vec_len(), 11);
736
737 assert_eq!(cs.log_public_words(InoutSegment::Public), 3);
739 assert_eq!(cs.log_witness_words(InoutSegment::Public), 3);
740 assert_eq!(cs.log_segment_words(InoutSegment::Public), 3);
741
742 let wide = ConstraintSystem {
744 n_private: 60,
745 ..test_shape()
746 };
747 assert_eq!(wide.log_public_words(InoutSegment::Public), 3);
748 assert_eq!(wide.log_witness_words(InoutSegment::Public), 6);
749 assert_eq!(wide.log_segment_words(InoutSegment::Public), 6);
750
751 let public_heavy = ConstraintSystem {
754 n_inout: 200,
755 n_private: 4,
756 ..test_shape()
757 };
758 assert_eq!(public_heavy.log_public_words(InoutSegment::Public), 8);
759 assert_eq!(public_heavy.log_witness_words(InoutSegment::Public), 2);
760 assert_eq!(public_heavy.log_segment_words(InoutSegment::Public), 8);
761 }
762
763 #[test]
764 fn hidden_inout_moves_the_segment_boundary() {
765 let cs = test_shape();
769 assert_eq!(cs.n_public_words(InoutSegment::Hidden), 3);
770 assert_eq!(cs.n_hidden_words(InoutSegment::Hidden), 8);
771
772 assert_eq!(cs.value_vec_len(), 11);
774 assert_eq!(cs.word_offset(ValueIndex::inout(0)), 3);
775 assert_eq!(cs.word_offset(ValueIndex::private(0)), 5);
776 }
777
778 #[test]
779 fn test_validate_rejects_scratch_references() {
780 let mut cs = test_shape();
781
782 cs.and_constraints.push(AndConstraint::plain_abc(
785 vec![ValueIndex::constant(0)],
786 vec![ValueIndex::scratch(0)], vec![ValueIndex::private(0)],
788 ));
789
790 match cs.validate().unwrap_err() {
791 ConstraintSystemError::ConstraintOperand {
792 constraint_kind,
793 source: OperandFault::ScratchValueIndex,
794 ..
795 } => {
796 assert_eq!(constraint_kind, ConstraintKind::And);
797 }
798 other => panic!("Expected ScratchValueIndex error, got: {:?}", other),
799 }
800 }
801
802 #[test]
803 fn test_validate_checks_each_segment_against_its_own_length() {
804 let mut cs = test_shape();
808 cs.and_constraints.push(AndConstraint::plain_abc(
809 vec![ValueIndex::constant(3)],
810 vec![ValueIndex::inout(0)],
811 vec![ValueIndex::private(3)],
812 ));
813
814 match cs.validate().unwrap_err() {
815 ConstraintSystemError::ConstraintOperand {
816 source:
817 OperandFault::OutOfRangeValueIndex {
818 segment,
819 value_index,
820 segment_len,
821 },
822 ..
823 } => {
824 assert_eq!(segment, ValueSegment::Constant);
825 assert_eq!(value_index, 3);
826 assert_eq!(segment_len, 3);
827 }
828 other => panic!("Expected OutOfRangeValueIndex error, got: {:?}", other),
829 }
830 }
831
832 #[test]
833 fn test_validate_accepts_non_padding_references() {
834 let mut cs = test_shape();
835
836 cs.and_constraints.push(AndConstraint::plain_abc(
838 vec![ValueIndex::constant(0), ValueIndex::constant(1)], vec![ValueIndex::inout(0), ValueIndex::inout(1)], vec![ValueIndex::private(0), ValueIndex::private(1)], ));
842
843 cs.imul_constraints.push(ImulConstraint([
844 vec![ShiftedValueIndex::plain(ValueIndex::private(2))], vec![ShiftedValueIndex::plain(ValueIndex::private(3))], vec![ShiftedValueIndex::plain(ValueIndex::private(4))], vec![ShiftedValueIndex::plain(ValueIndex::private(5))], ]));
849
850 let result = cs.validate();
851 assert!(
852 result.is_ok(),
853 "Should accept constraints with only valid references: {:?}",
854 result
855 );
856 }
857
858 #[test]
859 fn test_validate_keeps_true_constraint_counts() {
860 let cs = create_test_constraint_system();
861 let (n_zero, n_and, n_imul, n_bmul) = (
862 cs.n_zero_constraints(),
863 cs.n_and_constraints(),
864 cs.n_imul_constraints(),
865 cs.n_bmul_constraints(),
866 );
867 cs.validate().unwrap();
868 assert_eq!(cs.n_zero_constraints(), n_zero);
869 assert_eq!(cs.n_and_constraints(), n_and);
870 assert_eq!(cs.n_imul_constraints(), n_imul);
871 assert_eq!(cs.n_bmul_constraints(), n_bmul);
872 }
873
874 #[test]
875 fn test_validate_rejects_out_of_range_in_zero_constraint() {
876 let mut cs = test_shape();
877
878 cs.zero_constraints
879 .push(ZeroConstraint::plain([ValueIndex::constant(0), ValueIndex::private(100)]));
880
881 match cs.validate().unwrap_err() {
882 ConstraintSystemError::ConstraintOperand {
883 constraint_kind,
884 operand_name,
885 source:
886 OperandFault::OutOfRangeValueIndex {
887 value_index,
888 segment_len,
889 ..
890 },
891 ..
892 } => {
893 assert_eq!(constraint_kind, ConstraintKind::Zero);
894 assert_eq!(operand_name, "val");
895 assert_eq!(value_index, 100);
896 assert_eq!(segment_len, 6);
897 }
898 other => panic!("Expected OutOfRangeValueIndex error, got: {:?}", other),
899 }
900 }
901
902 #[test]
903 fn test_log_constraint_counts_round_up() {
904 let mut cs = test_shape();
905 assert_eq!(cs.log_and_constraints(), None);
908 assert_eq!(cs.log_zero_constraints(), None);
909
910 cs.zero_constraints =
911 vec![ZeroConstraint::plain([ValueIndex::constant(0), ValueIndex::private(0)]); 3];
912 assert_eq!(cs.log_zero_constraints(), Some(2));
913
914 let and = AndConstraint::plain_abc(
915 vec![ValueIndex::constant(0)],
916 vec![ValueIndex::inout(0)],
917 vec![ValueIndex::private(0)],
918 );
919 cs.and_constraints = vec![and; 3];
920 assert_eq!(cs.log_and_constraints(), Some(2));
921 cs.and_constraints.push(cs.and_constraints[0].clone());
922 assert_eq!(cs.log_and_constraints(), Some(2));
923 }
924
925 #[test]
926 fn test_validate_rejects_out_of_range_indices() {
927 let mut cs = test_shape();
928
929 cs.and_constraints.push(AndConstraint::plain_abc(
931 vec![ValueIndex::constant(0)], vec![ValueIndex::private(6)], vec![ValueIndex::private(0)], ));
935
936 let result = cs.validate();
937 assert!(result.is_err(), "Should reject constraint with out-of-range index");
938
939 match result.unwrap_err() {
940 ConstraintSystemError::ConstraintOperand {
941 constraint_kind,
942 operand_name,
943 source:
944 OperandFault::OutOfRangeValueIndex {
945 value_index,
946 segment_len,
947 ..
948 },
949 ..
950 } => {
951 assert_eq!(constraint_kind, ConstraintKind::And);
952 assert_eq!(operand_name, "b");
953 assert_eq!(value_index, 6);
954 assert_eq!(segment_len, 6);
955 }
956 other => panic!("Expected OutOfRangeValueIndex error, got: {:?}", other),
957 }
958 }
959
960 #[test]
961 fn test_validate_rejects_out_of_range_in_imul_constraint() {
962 let mut cs = test_shape();
963
964 cs.imul_constraints.push(ImulConstraint([
966 vec![ShiftedValueIndex::plain(ValueIndex::constant(0))], vec![ShiftedValueIndex::plain(ValueIndex::constant(1))], vec![ShiftedValueIndex::plain(ValueIndex::private(0))], vec![ShiftedValueIndex::plain(ValueIndex::private(100))], ]));
971
972 let result = cs.validate();
973 assert!(result.is_err(), "Should reject IMUL constraint with out-of-range index");
974
975 match result.unwrap_err() {
976 ConstraintSystemError::ConstraintOperand {
977 constraint_kind,
978 operand_name,
979 source:
980 OperandFault::OutOfRangeValueIndex {
981 value_index,
982 segment_len,
983 ..
984 },
985 ..
986 } => {
987 assert_eq!(constraint_kind, ConstraintKind::Imul);
988 assert_eq!(operand_name, "hi");
989 assert_eq!(value_index, 100);
990 assert_eq!(segment_len, 6);
991 }
992 other => panic!("Expected OutOfRangeValueIndex error, got: {:?}", other),
993 }
994 }
995
996 #[test]
997 fn test_validate_rejects_out_of_range_in_bmul_constraint() {
998 let mut cs = test_shape();
999
1000 cs.bmul_constraints.push(BmulConstraint([
1002 vec![ShiftedValueIndex::plain(ValueIndex::constant(0))], vec![ShiftedValueIndex::plain(ValueIndex::inout(0))], vec![ShiftedValueIndex::plain(ValueIndex::inout(1))], vec![ShiftedValueIndex::plain(ValueIndex::private(0))], vec![ShiftedValueIndex::plain(ValueIndex::private(1))], vec![ShiftedValueIndex::plain(ValueIndex::private(100))], ]));
1009
1010 let result = cs.validate();
1011 assert!(result.is_err(), "Should reject BMUL constraint with out-of-range index");
1012
1013 match result.unwrap_err() {
1014 ConstraintSystemError::ConstraintOperand {
1015 constraint_kind,
1016 operand_name,
1017 source:
1018 OperandFault::OutOfRangeValueIndex {
1019 value_index,
1020 segment_len,
1021 ..
1022 },
1023 ..
1024 } => {
1025 assert_eq!(constraint_kind, ConstraintKind::Bmul);
1026 assert_eq!(operand_name, "c_hi");
1027 assert_eq!(value_index, 100);
1028 assert_eq!(segment_len, 6);
1029 }
1030 other => panic!("Expected OutOfRangeValueIndex error, got: {:?}", other),
1031 }
1032 }
1033
1034 #[test]
1035 fn test_validate_rejects_half_word_shift_amount_out_of_range() {
1036 let mut cs = test_shape();
1037
1038 cs.and_constraints.push(AndConstraint::abc(
1041 vec![ShiftedValueIndex::single(
1042 ValueIndex::constant(0),
1043 Shift {
1046 variant: ShiftVariant::Sll32,
1047 amount: 32,
1048 },
1049 )],
1050 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1051 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1052 ));
1053
1054 match cs.validate().unwrap_err() {
1055 ConstraintSystemError::ConstraintOperand {
1056 constraint_kind,
1057 constraint_index,
1058 operand_name,
1059 source:
1060 OperandFault::ShiftAmountTooLarge {
1061 shift_amount,
1062 max_amount,
1063 },
1064 } => {
1065 assert_eq!(constraint_kind, ConstraintKind::And);
1066 assert_eq!(constraint_index, 0);
1067 assert_eq!(operand_name, "a");
1068 assert_eq!(shift_amount, 32);
1069 assert_eq!(max_amount, 32);
1070 }
1071 other => panic!("Expected ShiftAmountTooLarge, got: {:?}", other),
1072 }
1073 }
1074
1075 #[test]
1076 fn test_validate_checks_the_outer_shift_slot_too() {
1077 let mut cs = test_shape();
1078
1079 cs.and_constraints.push(AndConstraint::abc(
1082 vec![ShiftedValueIndex::new(
1083 ValueIndex::constant(0),
1084 [
1085 Shift::srl(3),
1086 Shift {
1088 variant: ShiftVariant::Sll32,
1089 amount: 32,
1090 },
1091 ],
1092 )],
1093 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1094 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1095 ));
1096
1097 match cs.validate().unwrap_err() {
1098 ConstraintSystemError::ConstraintOperand {
1099 constraint_kind,
1100 constraint_index,
1101 operand_name,
1102 source:
1103 OperandFault::ShiftAmountTooLarge {
1104 shift_amount,
1105 max_amount,
1106 },
1107 } => {
1108 assert_eq!(constraint_kind, ConstraintKind::And);
1109 assert_eq!(constraint_index, 0);
1110 assert_eq!(operand_name, "a");
1111 assert_eq!(shift_amount, 32);
1112 assert_eq!(max_amount, 32);
1113 }
1114 other => panic!("Expected ShiftAmountTooLarge, got: {:?}", other),
1115 }
1116 }
1117
1118 #[test]
1119 fn test_validate_rejects_a_lone_shift_in_the_outer_slot() {
1120 let mut cs = test_shape();
1121
1122 cs.and_constraints.push(AndConstraint::abc(
1125 vec![ShiftedValueIndex::new(
1126 ValueIndex::constant(0),
1127 [Shift::IDENTITY, Shift::rotr(5)],
1128 )],
1129 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1130 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1131 ));
1132
1133 match cs.validate().unwrap_err() {
1134 ConstraintSystemError::ConstraintOperand {
1135 constraint_kind,
1136 constraint_index,
1137 operand_name,
1138 source: OperandFault::NonCanonicalShiftSequence,
1139 } => {
1140 assert_eq!(constraint_kind, ConstraintKind::And);
1141 assert_eq!(constraint_index, 0);
1142 assert_eq!(operand_name, "a");
1143 }
1144 other => panic!("Expected NonCanonicalShiftSequence, got: {:?}", other),
1145 }
1146 }
1147
1148 #[test]
1149 fn test_validate_rejects_a_pair_that_collapses_to_one_shift() {
1150 let mut cs = test_shape();
1151
1152 cs.and_constraints.push(AndConstraint::abc(
1155 vec![ShiftedValueIndex::new(
1156 ValueIndex::constant(0),
1157 [Shift::rotr(4), Shift::rotr(5)],
1158 )],
1159 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1160 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1161 ));
1162
1163 match cs.validate().unwrap_err() {
1164 ConstraintSystemError::ConstraintOperand {
1165 constraint_kind,
1166 constraint_index,
1167 operand_name,
1168 source: OperandFault::CollapsibleShiftSequence { composition },
1169 } => {
1170 assert_eq!(constraint_kind, ConstraintKind::And);
1171 assert_eq!(constraint_index, 0);
1172 assert_eq!(operand_name, "a");
1173 assert_eq!(composition, Composition::Single(Shift::rotr(9)));
1174 }
1175 other => panic!("Expected CollapsibleShiftSequence, got: {:?}", other),
1176 }
1177 }
1178
1179 #[test]
1180 fn test_validate_rejects_a_pair_that_clears_the_word() {
1181 let mut cs = test_shape();
1182
1183 cs.and_constraints.push(AndConstraint::abc(
1187 vec![ShiftedValueIndex::new(
1188 ValueIndex::constant(0),
1189 [Shift::sll(40), Shift::sll(30)],
1190 )],
1191 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1192 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1193 ));
1194
1195 match cs.validate().unwrap_err() {
1196 ConstraintSystemError::ConstraintOperand {
1197 constraint_kind,
1198 constraint_index,
1199 operand_name,
1200 source: OperandFault::CollapsibleShiftSequence { composition },
1201 } => {
1202 assert_eq!(constraint_kind, ConstraintKind::And);
1203 assert_eq!(constraint_index, 0);
1204 assert_eq!(operand_name, "a");
1205 assert_eq!(composition, Composition::Zero);
1206 }
1207 other => panic!("Expected CollapsibleShiftSequence, got: {:?}", other),
1208 }
1209 }
1210
1211 #[test]
1212 fn test_validate_accepts_a_genuine_shift_pair() {
1213 let mut cs = test_shape();
1214
1215 cs.and_constraints.push(AndConstraint::abc(
1218 vec![ShiftedValueIndex::new(
1219 ValueIndex::constant(0),
1220 [Shift::srl(3), Shift::sll(3)],
1221 )],
1222 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1223 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1224 ));
1225
1226 cs.validate().unwrap();
1227 }
1228
1229 #[test]
1230 fn test_roundtrip_cs_and_witnesses_reconstruct_valuevec() {
1231 let cs = test_shape();
1232
1233 let inout = (0..cs.n_inout)
1236 .map(|i| Word::from_u64(0xA5A5_5A5A ^ (i as u64 * 0x9E37_79B9)))
1237 .collect::<Vec<_>>();
1238 let private = (0..cs.n_private)
1239 .map(|i| Word::from_u64(0x5A5A_A5A5 ^ (i as u64 * 0x9E37_79B9)))
1240 .collect::<Vec<_>>();
1241 let values = cs.value_vec_from_data(&inout, &private);
1242
1243 let mut buf_cs = Vec::new();
1245 cs.serialize(&mut buf_cs).unwrap();
1246
1247 let mut buf_inout = Vec::new();
1248 ValuesRef::new(values.inout())
1249 .serialize(&mut buf_inout)
1250 .unwrap();
1251
1252 let mut buf_non_pub = Vec::new();
1253 ValuesRef::new(values.non_public())
1254 .serialize(&mut buf_non_pub)
1255 .unwrap();
1256
1257 let cs2 = ConstraintSystem::deserialize(&mut buf_cs.as_slice()).unwrap();
1259 let inout2 = ValuesData::deserialize(&mut buf_inout.as_slice()).unwrap();
1260 let non_pub2 = ValuesData::deserialize(&mut buf_non_pub.as_slice()).unwrap();
1261 assert_eq!(cs2.n_inout, inout2.len());
1262 assert_eq!(cs2.n_private, non_pub2.len());
1263
1264 let reconstructed = cs2.value_vec_from_data(&inout2, &non_pub2);
1266
1267 assert_eq!(reconstructed.combined_witness(), values.combined_witness());
1268 }
1269
1270 fn zero_constraint_system(n: usize) -> ConstraintSystem {
1275 ConstraintSystem {
1276 constants: vec![],
1277 n_inout: 0,
1278 n_private: 8,
1279 zero_constraints: (0..n)
1280 .map(|i| ZeroConstraint::plain([ValueIndex::private(i as u32)]))
1281 .collect(),
1282 and_constraints: vec![],
1283 imul_constraints: vec![],
1284 bmul_constraints: vec![],
1285 }
1286 }
1287
1288 #[test]
1289 fn verify_accepts_a_value_vector_satisfying_every_constraint() {
1290 let cs = zero_constraint_system(3);
1291 let values = cs.value_vec_from_data(&[Word::ZERO; 8], &[Word::ZERO; 8]);
1292
1293 assert!(cs.verify(&values).is_ok());
1294 }
1295
1296 #[test]
1297 fn verify_reports_the_index_of_the_first_unsatisfied_constraint() {
1298 let cs = zero_constraint_system(3);
1300 let mut private = [Word::ZERO; 8];
1301 private[1] = Word::from_u64(0xabc);
1302 let values = cs.value_vec_from_data(&[Word::ZERO; 8], &private);
1303
1304 let err = cs.verify(&values).unwrap_err();
1305
1306 assert_eq!(err.to_string(), "zero #1 is unsatisfied: 0000000000000abc != 0");
1309
1310 match err {
1311 VerificationError::Unsatisfied {
1312 constraint_index,
1313 source,
1314 } => {
1315 assert_eq!(constraint_index, 1);
1316 assert_eq!(source.kind(), ConstraintKind::Zero);
1317 match source {
1318 ConstraintViolation::Zero { val } => assert_eq!(val, 0xabc),
1319 other => panic!("wrong violation: {other:?}"),
1320 }
1321 }
1322 other => panic!("wrong error: {other:?}"),
1323 }
1324 }
1325
1326 #[test]
1327 fn verify_rejects_a_value_vector_that_opens_a_constant_to_the_wrong_word() {
1328 let cs = ConstraintSystem {
1331 zero_constraints: vec![],
1332 ..test_shape()
1333 };
1334 let mut public = [Word::ZERO; 8];
1338 public[0] = Word::from_u64(1);
1339 public[1] = Word::from_u64(42);
1340 public[2] = Word::from_u64(0xBAADF00D);
1341 let values = ValueVec::new_from_data(cs.n_const(), &public, &[Word::ZERO; 8]);
1342
1343 match cs.verify(&values).unwrap_err() {
1344 VerificationError::ConstantMismatch {
1345 value_index,
1346 expected,
1347 actual,
1348 } => {
1349 assert_eq!(value_index, 2);
1350 assert_eq!(expected, 0xDEADBEEF);
1351 assert_eq!(actual, 0xBAADF00D);
1352 }
1353 other => panic!("wrong error: {other:?}"),
1354 }
1355 }
1356}