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 MAX_VALUES_PER_SEGMENT: usize = 1 << 26;
123
124 pub const fn n_const(&self) -> usize {
126 self.constants.len()
127 }
128
129 pub const fn offset_inout(&self) -> usize {
131 self.n_const()
132 }
133
134 pub const fn n_public_values(&self) -> usize {
136 self.n_const() + self.n_inout
137 }
138
139 pub const fn n_public_words(&self, inout: InoutSegment) -> usize {
143 match inout {
144 InoutSegment::Public => self.n_public_values(),
145 InoutSegment::Hidden => self.n_const(),
146 }
147 }
148
149 pub const fn log_public_words(&self, inout: InoutSegment) -> usize {
153 log2_ceil_usize(self.n_public_words(inout))
154 }
155
156 pub const fn n_hidden_words(&self, inout: InoutSegment) -> usize {
160 match inout {
161 InoutSegment::Public => self.n_private,
162 InoutSegment::Hidden => self.n_inout + self.n_private,
163 }
164 }
165
166 pub const fn log_witness_words(&self, inout: InoutSegment) -> usize {
168 log2_ceil_usize(self.n_hidden_words(inout))
169 }
170
171 pub const fn log_segment_words(&self, inout: InoutSegment) -> usize {
177 if self.log_public_words(inout) > self.log_witness_words(inout) {
178 self.log_public_words(inout)
179 } else {
180 self.log_witness_words(inout)
181 }
182 }
183
184 pub const fn segment_len(&self, segment: ValueSegment) -> usize {
189 match segment {
190 ValueSegment::Constant => self.n_const(),
191 ValueSegment::InOut => self.n_inout,
192 ValueSegment::Private => self.n_private,
193 ValueSegment::Scratch => 0,
194 }
195 }
196
197 pub const fn word_offset(&self, index: ValueIndex) -> usize {
205 let segment_start = match index.segment() {
206 ValueSegment::Constant => 0,
207 ValueSegment::InOut => self.offset_inout(),
208 ValueSegment::Private => self.n_public_values(),
209 ValueSegment::Scratch => self.value_vec_len(),
210 };
211 segment_start + index.index() as usize
212 }
213
214 pub fn value_vec_from_data(&self, inout: &[Word], private: &[Word]) -> ValueVec {
219 let public = [self.constants.as_slice(), inout].concat();
220 ValueVec::new_from_data(self.n_const(), &public, private)
221 }
222
223 pub fn validate(&self) -> Result<(), ConstraintSystemError> {
235 tracing::debug_span!("Validating constraint system");
236
237 for segment in [ValueSegment::InOut, ValueSegment::Private] {
241 let len = self.segment_len(segment);
242 if len > Self::MAX_VALUES_PER_SEGMENT {
243 return Err(ConstraintSystemError::SegmentTooLarge { segment, len });
244 }
245 }
246
247 self.validate_constraints(
248 &self.zero_constraints,
249 ZeroConstraint::KIND,
250 ZeroConstraint::OPERAND_NAMES,
251 )?;
252 self.validate_constraints(
253 &self.and_constraints,
254 AndConstraint::KIND,
255 AndConstraint::OPERAND_NAMES,
256 )?;
257 self.validate_constraints(
258 &self.imul_constraints,
259 ImulConstraint::KIND,
260 ImulConstraint::OPERAND_NAMES,
261 )?;
262 self.validate_constraints(
263 &self.bmul_constraints,
264 BmulConstraint::KIND,
265 BmulConstraint::OPERAND_NAMES,
266 )?;
267
268 Ok(())
269 }
270
271 pub fn verify(&self, values: &ValueVec) -> Result<(), VerificationError> {
286 for (index, &constant) in self.constants.iter().enumerate() {
289 let value_index = index as u32;
290 let actual = values[ValueIndex::constant(value_index)];
291 if actual != constant {
292 return Err(VerificationError::ConstantMismatch {
293 value_index,
294 expected: constant.as_u64(),
295 actual: actual.as_u64(),
296 });
297 }
298 }
299
300 for (constraint_index, constraint) in self.zero_constraints.iter().enumerate() {
303 constraint
304 .verify(values)
305 .map_err(|source| VerificationError::Unsatisfied {
306 constraint_index,
307 source,
308 })?;
309 }
310 for (constraint_index, constraint) in self.and_constraints.iter().enumerate() {
311 constraint
312 .verify(values)
313 .map_err(|source| VerificationError::Unsatisfied {
314 constraint_index,
315 source,
316 })?;
317 }
318 for (constraint_index, constraint) in self.imul_constraints.iter().enumerate() {
319 constraint
320 .verify(values)
321 .map_err(|source| VerificationError::Unsatisfied {
322 constraint_index,
323 source,
324 })?;
325 }
326 for (constraint_index, constraint) in self.bmul_constraints.iter().enumerate() {
327 constraint
328 .verify(values)
329 .map_err(|source| VerificationError::Unsatisfied {
330 constraint_index,
331 source,
332 })?;
333 }
334
335 Ok(())
336 }
337
338 fn validate_constraints<C: AsRef<[Operand; ARITY]>, const ARITY: usize>(
340 &self,
341 constraints: &[C],
342 constraint_kind: ConstraintKind,
343 operand_names: [&'static str; ARITY],
344 ) -> Result<(), ConstraintSystemError> {
345 for (i, constraint) in constraints.iter().enumerate() {
346 for (operand, name) in iter::zip(constraint.as_ref(), operand_names) {
347 self.validate_operand(operand, constraint_kind, i, name)?;
348 }
349 }
350 Ok(())
351 }
352
353 fn validate_operand(
355 &self,
356 operand: &Operand,
357 constraint_kind: ConstraintKind,
358 constraint_index: usize,
359 operand_name: &'static str,
360 ) -> Result<(), ConstraintSystemError> {
361 self.operand_fault(operand).map_or(Ok(()), |source| {
362 Err(ConstraintSystemError::ConstraintOperand {
363 constraint_kind,
364 constraint_index,
365 operand_name,
366 source,
367 })
368 })
369 }
370
371 pub fn operand_fault(&self, operand: &Operand) -> Option<OperandFault> {
377 operand.iter().find_map(|term| {
378 for shift in term.shift_seq {
379 if !shift.is_canonical() {
381 return Some(OperandFault::NonCanonicalShift);
382 }
383 let max_amount = shift.variant.max_amount();
387 if usize::from(shift.amount) >= max_amount {
388 return Some(OperandFault::ShiftAmountTooLarge {
389 shift_amount: shift.amount as usize,
390 max_amount,
391 });
392 }
393 }
394 if term.is_unshifted() && term.is_doubly_shifted() {
398 return Some(OperandFault::NonCanonicalShiftSequence);
399 }
400 if term.is_doubly_shifted() {
404 let composition = Shift::compose(term.inner(), term.outer());
405 if composition != Composition::Pair {
406 return Some(OperandFault::CollapsibleShiftSequence { composition });
407 }
408 }
409 let segment = term.value_index.segment();
412 if !segment.is_referenceable() {
413 return Some(OperandFault::ScratchValueIndex);
414 }
415 let segment_len = self.segment_len(segment);
417 if term.value_index.index() as usize >= segment_len {
418 return Some(OperandFault::OutOfRangeValueIndex {
419 segment,
420 value_index: term.value_index.index(),
421 segment_len,
422 });
423 }
424 None
425 })
426 }
427
428 pub const fn n_zero_constraints(&self) -> usize {
430 self.zero_constraints.len()
431 }
432
433 pub const fn n_and_constraints(&self) -> usize {
435 self.and_constraints.len()
436 }
437
438 pub const fn n_imul_constraints(&self) -> usize {
440 self.imul_constraints.len()
441 }
442
443 pub const fn n_bmul_constraints(&self) -> usize {
445 self.bmul_constraints.len()
446 }
447
448 pub const fn log_zero_constraints(&self) -> Option<usize> {
457 match self.n_zero_constraints() {
458 0 => None,
459 n => Some(log2_ceil_usize(n)),
460 }
461 }
462
463 pub const fn log_and_constraints(&self) -> Option<usize> {
472 match self.n_and_constraints() {
473 0 => None,
474 n => Some(log2_ceil_usize(n)),
475 }
476 }
477
478 pub const fn log_imul_constraints(&self) -> Option<usize> {
486 match self.n_imul_constraints() {
487 0 => None,
488 n => Some(log2_ceil_usize(n)),
489 }
490 }
491
492 pub const fn log_bmul_constraints(&self) -> Option<usize> {
499 match self.n_bmul_constraints() {
500 0 => None,
501 n => Some(log2_ceil_usize(n)),
502 }
503 }
504
505 pub const fn value_vec_len(&self) -> usize {
507 self.n_public_values() + self.n_private
508 }
509}
510
511impl SerializeBytes for ConstraintSystem {
512 fn serialize(&self, mut write_buf: impl BufMut) -> Result<(), SerializationError> {
513 Self::SERIALIZATION_VERSION.serialize(&mut write_buf)?;
514
515 self.constants.serialize(&mut write_buf)?;
516 self.n_inout.serialize(&mut write_buf)?;
517 self.n_private.serialize(&mut write_buf)?;
518 self.zero_constraints.serialize(&mut write_buf)?;
519 self.and_constraints.serialize(&mut write_buf)?;
520 self.imul_constraints.serialize(&mut write_buf)?;
521 self.bmul_constraints.serialize(write_buf)
522 }
523}
524
525impl DeserializeBytes for ConstraintSystem {
526 fn deserialize(mut read_buf: impl Buf) -> Result<Self, SerializationError>
527 where
528 Self: Sized,
529 {
530 let version = u32::deserialize(&mut read_buf)?;
531 if version != Self::SERIALIZATION_VERSION {
532 return Err(SerializationError::InvalidConstruction {
533 name: "ConstraintSystem::version",
534 });
535 }
536
537 let constants = Vec::<Word>::deserialize(&mut read_buf)?;
538 let n_inout = usize::deserialize(&mut read_buf)?;
539 let n_private = usize::deserialize(&mut read_buf)?;
540 let zero_constraints = Vec::<ZeroConstraint>::deserialize(&mut read_buf)?;
541 let and_constraints = Vec::<AndConstraint>::deserialize(&mut read_buf)?;
542 let imul_constraints = Vec::<ImulConstraint>::deserialize(&mut read_buf)?;
543 let bmul_constraints = Vec::<BmulConstraint>::deserialize(read_buf)?;
544
545 Ok(ConstraintSystem {
546 constants,
547 n_inout,
548 n_private,
549 zero_constraints,
550 and_constraints,
551 imul_constraints,
552 bmul_constraints,
553 })
554 }
555}
556
557#[cfg(test)]
558mod tests {
559 use super::*;
560 use crate::{
561 constraint_system::{Shift, ShiftVariant, ShiftedValueIndex, ValuesData, ValuesRef},
562 error::ConstraintViolation,
563 };
564
565 fn test_shape() -> ConstraintSystem {
571 ConstraintSystem {
572 constants: vec![
573 Word::from_u64(1),
574 Word::from_u64(42),
575 Word::from_u64(0xDEADBEEF),
576 ],
577 n_inout: 2,
578 n_private: 6,
579 zero_constraints: vec![],
580 and_constraints: vec![],
581 imul_constraints: vec![],
582 bmul_constraints: vec![],
583 }
584 }
585
586 pub(crate) fn create_test_constraint_system() -> ConstraintSystem {
587 ConstraintSystem {
588 zero_constraints: vec![ZeroConstraint::plain([
589 ValueIndex::constant(0),
590 ValueIndex::inout(0),
591 ValueIndex::private(0),
592 ])],
593 and_constraints: vec![
594 AndConstraint::plain_abc(
595 vec![ValueIndex::constant(0), ValueIndex::constant(1)],
596 vec![ValueIndex::constant(2)],
597 vec![ValueIndex::inout(0), ValueIndex::inout(1)],
598 ),
599 AndConstraint::abc(
600 vec![ShiftedValueIndex::sll(ValueIndex::constant(0), 5)],
601 vec![ShiftedValueIndex::srl(ValueIndex::constant(1), 10)],
602 vec![ShiftedValueIndex::sar(ValueIndex::constant(2), 15)],
603 ),
604 ],
605 imul_constraints: vec![ImulConstraint([
606 vec![ShiftedValueIndex::plain(ValueIndex::constant(0))],
607 vec![ShiftedValueIndex::plain(ValueIndex::constant(1))],
608 vec![ShiftedValueIndex::plain(ValueIndex::constant(2))],
609 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
610 ])],
611 bmul_constraints: vec![BmulConstraint([
612 vec![ShiftedValueIndex::plain(ValueIndex::constant(0))],
613 vec![ShiftedValueIndex::plain(ValueIndex::constant(1))],
614 vec![ShiftedValueIndex::plain(ValueIndex::constant(2))],
615 vec![ShiftedValueIndex::plain(ValueIndex::inout(0))],
616 vec![ShiftedValueIndex::plain(ValueIndex::inout(1))],
617 vec![ShiftedValueIndex::sll(ValueIndex::constant(0), 5)],
618 ])],
619 ..test_shape()
620 }
621 }
622
623 #[test]
624 fn test_constraint_system_serialization_round_trip() {
625 let original = create_test_constraint_system();
626
627 let mut buf = Vec::new();
628 original.serialize(&mut buf).unwrap();
629
630 let deserialized = ConstraintSystem::deserialize(&mut buf.as_slice()).unwrap();
631
632 assert_eq!(ConstraintSystem::SERIALIZATION_VERSION, 10);
634
635 assert_eq!(original.constants, deserialized.constants);
637 assert_eq!(original.n_inout, deserialized.n_inout);
638 assert_eq!(original.n_private, deserialized.n_private);
639
640 assert_eq!(original.zero_constraints.len(), deserialized.zero_constraints.len());
642
643 assert_eq!(original.and_constraints.len(), deserialized.and_constraints.len());
645
646 assert_eq!(original.imul_constraints.len(), deserialized.imul_constraints.len());
648
649 assert_eq!(original.bmul_constraints.len(), deserialized.bmul_constraints.len());
651 }
652
653 #[test]
654 fn test_constraint_system_version_mismatch() {
655 let mut buf = Vec::new();
657 999u32.serialize(&mut buf).unwrap(); let result = ConstraintSystem::deserialize(&mut buf.as_slice());
660 assert!(result.is_err());
661 match result.unwrap_err() {
662 SerializationError::InvalidConstruction { name } => {
663 assert_eq!(name, "ConstraintSystem::version");
664 }
665 _ => panic!("Expected InvalidConstruction error"),
666 }
667 }
668
669 #[test]
670 fn test_serialization_with_different_sources() {
671 let original = create_test_constraint_system();
672
673 let mut vec_buf = Vec::new();
675 original.serialize(&mut vec_buf).unwrap();
676 let deserialized1 = ConstraintSystem::deserialize(&mut vec_buf.as_slice()).unwrap();
677 assert_eq!(original.constants.len(), deserialized1.constants.len());
678
679 let mut bytes_buf = bytes::BytesMut::new();
681 original.serialize(&mut bytes_buf).unwrap();
682 let deserialized2 = ConstraintSystem::deserialize(bytes_buf.freeze()).unwrap();
683 assert_eq!(original.constants.len(), deserialized2.constants.len());
684 }
685
686 #[test]
690 #[ignore] fn create_reference_binary_file() {
692 let constraint_system = create_test_constraint_system();
693
694 let mut buf = Vec::new();
696 constraint_system.serialize(&mut buf).unwrap();
697
698 let test_data_path = std::path::Path::new("test_data/constraint_system_v10.bin");
700
701 if let Some(parent) = test_data_path.parent() {
703 std::fs::create_dir_all(parent).unwrap();
704 }
705
706 std::fs::write(test_data_path, &buf).unwrap();
707
708 println!("Created reference binary file at: {:?}", test_data_path);
709 println!("Binary data length: {} bytes", buf.len());
710 }
711
712 #[test]
715 fn test_deserialize_from_reference_binary_file() {
716 let binary_data = include_bytes!("../../test_data/constraint_system_v10.bin");
720
721 let deserialized = ConstraintSystem::deserialize(&mut binary_data.as_slice()).unwrap();
722
723 assert_eq!(deserialized.n_const(), 3);
724 assert_eq!(deserialized.n_inout, 2);
725 assert_eq!(deserialized.n_private, 6);
726
727 assert_eq!(deserialized.constants[0].as_u64(), 1);
728 assert_eq!(deserialized.constants[1].as_u64(), 42);
729 assert_eq!(deserialized.constants[2].as_u64(), 0xDEADBEEF);
730
731 assert_eq!(deserialized.zero_constraints.len(), 1);
732 assert_eq!(deserialized.and_constraints.len(), 2);
733 assert_eq!(deserialized.imul_constraints.len(), 1);
734 assert_eq!(deserialized.bmul_constraints.len(), 1);
735
736 let version_bytes = &binary_data[0..4]; let expected_version_bytes = 10u32.to_le_bytes(); assert_eq!(
742 version_bytes, expected_version_bytes,
743 "Binary file version mismatch. If you made breaking changes, increment ConstraintSystem::SERIALIZATION_VERSION"
744 );
745 }
746
747 #[test]
748 fn test_log_witness_words() {
749 let cs = |n_private: usize| ConstraintSystem {
750 n_private,
751 ..test_shape()
752 };
753 assert_eq!(cs(60).log_witness_words(InoutSegment::Public), 6);
755 assert_eq!(cs(32).log_witness_words(InoutSegment::Public), 5);
757 }
758
759 #[test]
760 fn segment_lengths_are_the_value_counts() {
761 let cs = test_shape();
764 assert_eq!(cs.n_public_values(), 5);
765 assert_eq!(cs.n_public_words(InoutSegment::Public), 5);
766 assert_eq!(cs.n_hidden_words(InoutSegment::Public), 6);
767 assert_eq!(cs.value_vec_len(), 11);
768
769 assert_eq!(cs.log_public_words(InoutSegment::Public), 3);
771 assert_eq!(cs.log_witness_words(InoutSegment::Public), 3);
772 assert_eq!(cs.log_segment_words(InoutSegment::Public), 3);
773
774 let wide = ConstraintSystem {
776 n_private: 60,
777 ..test_shape()
778 };
779 assert_eq!(wide.log_public_words(InoutSegment::Public), 3);
780 assert_eq!(wide.log_witness_words(InoutSegment::Public), 6);
781 assert_eq!(wide.log_segment_words(InoutSegment::Public), 6);
782
783 let public_heavy = ConstraintSystem {
786 n_inout: 200,
787 n_private: 4,
788 ..test_shape()
789 };
790 assert_eq!(public_heavy.log_public_words(InoutSegment::Public), 8);
791 assert_eq!(public_heavy.log_witness_words(InoutSegment::Public), 2);
792 assert_eq!(public_heavy.log_segment_words(InoutSegment::Public), 8);
793 }
794
795 #[test]
796 fn hidden_inout_moves_the_segment_boundary() {
797 let cs = test_shape();
801 assert_eq!(cs.n_public_words(InoutSegment::Hidden), 3);
802 assert_eq!(cs.n_hidden_words(InoutSegment::Hidden), 8);
803
804 assert_eq!(cs.value_vec_len(), 11);
806 assert_eq!(cs.word_offset(ValueIndex::inout(0)), 3);
807 assert_eq!(cs.word_offset(ValueIndex::private(0)), 5);
808 }
809
810 #[test]
811 fn test_validate_rejects_scratch_references() {
812 let mut cs = test_shape();
813
814 cs.and_constraints.push(AndConstraint::plain_abc(
817 vec![ValueIndex::constant(0)],
818 vec![ValueIndex::scratch(0)], vec![ValueIndex::private(0)],
820 ));
821
822 match cs.validate().unwrap_err() {
823 ConstraintSystemError::ConstraintOperand {
824 constraint_kind,
825 source: OperandFault::ScratchValueIndex,
826 ..
827 } => {
828 assert_eq!(constraint_kind, ConstraintKind::And);
829 }
830 other => panic!("Expected ScratchValueIndex error, got: {:?}", other),
831 }
832 }
833
834 #[test]
835 fn test_validate_checks_each_segment_against_its_own_length() {
836 let mut cs = test_shape();
840 cs.and_constraints.push(AndConstraint::plain_abc(
841 vec![ValueIndex::constant(3)],
842 vec![ValueIndex::inout(0)],
843 vec![ValueIndex::private(3)],
844 ));
845
846 match cs.validate().unwrap_err() {
847 ConstraintSystemError::ConstraintOperand {
848 source:
849 OperandFault::OutOfRangeValueIndex {
850 segment,
851 value_index,
852 segment_len,
853 },
854 ..
855 } => {
856 assert_eq!(segment, ValueSegment::Constant);
857 assert_eq!(value_index, 3);
858 assert_eq!(segment_len, 3);
859 }
860 other => panic!("Expected OutOfRangeValueIndex error, got: {:?}", other),
861 }
862 }
863
864 #[test]
865 fn test_validate_accepts_non_padding_references() {
866 let mut cs = test_shape();
867
868 cs.and_constraints.push(AndConstraint::plain_abc(
870 vec![ValueIndex::constant(0), ValueIndex::constant(1)], vec![ValueIndex::inout(0), ValueIndex::inout(1)], vec![ValueIndex::private(0), ValueIndex::private(1)], ));
874
875 cs.imul_constraints.push(ImulConstraint([
876 vec![ShiftedValueIndex::plain(ValueIndex::private(2))], vec![ShiftedValueIndex::plain(ValueIndex::private(3))], vec![ShiftedValueIndex::plain(ValueIndex::private(4))], vec![ShiftedValueIndex::plain(ValueIndex::private(5))], ]));
881
882 let result = cs.validate();
883 assert!(
884 result.is_ok(),
885 "Should accept constraints with only valid references: {:?}",
886 result
887 );
888 }
889
890 #[test]
891 fn test_validate_keeps_true_constraint_counts() {
892 let cs = create_test_constraint_system();
893 let (n_zero, n_and, n_imul, n_bmul) = (
894 cs.n_zero_constraints(),
895 cs.n_and_constraints(),
896 cs.n_imul_constraints(),
897 cs.n_bmul_constraints(),
898 );
899 cs.validate().unwrap();
900 assert_eq!(cs.n_zero_constraints(), n_zero);
901 assert_eq!(cs.n_and_constraints(), n_and);
902 assert_eq!(cs.n_imul_constraints(), n_imul);
903 assert_eq!(cs.n_bmul_constraints(), n_bmul);
904 }
905
906 #[test]
907 fn test_validate_rejects_out_of_range_in_zero_constraint() {
908 let mut cs = test_shape();
909
910 cs.zero_constraints
911 .push(ZeroConstraint::plain([ValueIndex::constant(0), ValueIndex::private(100)]));
912
913 match cs.validate().unwrap_err() {
914 ConstraintSystemError::ConstraintOperand {
915 constraint_kind,
916 operand_name,
917 source:
918 OperandFault::OutOfRangeValueIndex {
919 value_index,
920 segment_len,
921 ..
922 },
923 ..
924 } => {
925 assert_eq!(constraint_kind, ConstraintKind::Zero);
926 assert_eq!(operand_name, "val");
927 assert_eq!(value_index, 100);
928 assert_eq!(segment_len, 6);
929 }
930 other => panic!("Expected OutOfRangeValueIndex error, got: {:?}", other),
931 }
932 }
933
934 #[test]
935 fn test_log_constraint_counts_round_up() {
936 let mut cs = test_shape();
937 assert_eq!(cs.log_and_constraints(), None);
940 assert_eq!(cs.log_zero_constraints(), None);
941
942 cs.zero_constraints =
943 vec![ZeroConstraint::plain([ValueIndex::constant(0), ValueIndex::private(0)]); 3];
944 assert_eq!(cs.log_zero_constraints(), Some(2));
945
946 let and = AndConstraint::plain_abc(
947 vec![ValueIndex::constant(0)],
948 vec![ValueIndex::inout(0)],
949 vec![ValueIndex::private(0)],
950 );
951 cs.and_constraints = vec![and; 3];
952 assert_eq!(cs.log_and_constraints(), Some(2));
953 cs.and_constraints.push(cs.and_constraints[0].clone());
954 assert_eq!(cs.log_and_constraints(), Some(2));
955 }
956
957 #[test]
958 fn test_validate_rejects_out_of_range_indices() {
959 let mut cs = test_shape();
960
961 cs.and_constraints.push(AndConstraint::plain_abc(
963 vec![ValueIndex::constant(0)], vec![ValueIndex::private(6)], vec![ValueIndex::private(0)], ));
967
968 let result = cs.validate();
969 assert!(result.is_err(), "Should reject constraint with out-of-range index");
970
971 match result.unwrap_err() {
972 ConstraintSystemError::ConstraintOperand {
973 constraint_kind,
974 operand_name,
975 source:
976 OperandFault::OutOfRangeValueIndex {
977 value_index,
978 segment_len,
979 ..
980 },
981 ..
982 } => {
983 assert_eq!(constraint_kind, ConstraintKind::And);
984 assert_eq!(operand_name, "b");
985 assert_eq!(value_index, 6);
986 assert_eq!(segment_len, 6);
987 }
988 other => panic!("Expected OutOfRangeValueIndex error, got: {:?}", other),
989 }
990 }
991
992 #[test]
993 fn test_validate_rejects_out_of_range_in_imul_constraint() {
994 let mut cs = test_shape();
995
996 cs.imul_constraints.push(ImulConstraint([
998 vec![ShiftedValueIndex::plain(ValueIndex::constant(0))], vec![ShiftedValueIndex::plain(ValueIndex::constant(1))], vec![ShiftedValueIndex::plain(ValueIndex::private(0))], vec![ShiftedValueIndex::plain(ValueIndex::private(100))], ]));
1003
1004 let result = cs.validate();
1005 assert!(result.is_err(), "Should reject IMUL constraint with out-of-range index");
1006
1007 match result.unwrap_err() {
1008 ConstraintSystemError::ConstraintOperand {
1009 constraint_kind,
1010 operand_name,
1011 source:
1012 OperandFault::OutOfRangeValueIndex {
1013 value_index,
1014 segment_len,
1015 ..
1016 },
1017 ..
1018 } => {
1019 assert_eq!(constraint_kind, ConstraintKind::Imul);
1020 assert_eq!(operand_name, "hi");
1021 assert_eq!(value_index, 100);
1022 assert_eq!(segment_len, 6);
1023 }
1024 other => panic!("Expected OutOfRangeValueIndex error, got: {:?}", other),
1025 }
1026 }
1027
1028 #[test]
1029 fn test_validate_rejects_out_of_range_in_bmul_constraint() {
1030 let mut cs = test_shape();
1031
1032 cs.bmul_constraints.push(BmulConstraint([
1034 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))], ]));
1041
1042 let result = cs.validate();
1043 assert!(result.is_err(), "Should reject BMUL constraint with out-of-range index");
1044
1045 match result.unwrap_err() {
1046 ConstraintSystemError::ConstraintOperand {
1047 constraint_kind,
1048 operand_name,
1049 source:
1050 OperandFault::OutOfRangeValueIndex {
1051 value_index,
1052 segment_len,
1053 ..
1054 },
1055 ..
1056 } => {
1057 assert_eq!(constraint_kind, ConstraintKind::Bmul);
1058 assert_eq!(operand_name, "c_hi");
1059 assert_eq!(value_index, 100);
1060 assert_eq!(segment_len, 6);
1061 }
1062 other => panic!("Expected OutOfRangeValueIndex error, got: {:?}", other),
1063 }
1064 }
1065
1066 #[test]
1067 fn test_validate_rejects_half_word_shift_amount_out_of_range() {
1068 let mut cs = test_shape();
1069
1070 cs.and_constraints.push(AndConstraint::abc(
1073 vec![ShiftedValueIndex::single(
1074 ValueIndex::constant(0),
1075 Shift {
1078 variant: ShiftVariant::Sll32,
1079 amount: 32,
1080 },
1081 )],
1082 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1083 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1084 ));
1085
1086 match cs.validate().unwrap_err() {
1087 ConstraintSystemError::ConstraintOperand {
1088 constraint_kind,
1089 constraint_index,
1090 operand_name,
1091 source:
1092 OperandFault::ShiftAmountTooLarge {
1093 shift_amount,
1094 max_amount,
1095 },
1096 } => {
1097 assert_eq!(constraint_kind, ConstraintKind::And);
1098 assert_eq!(constraint_index, 0);
1099 assert_eq!(operand_name, "a");
1100 assert_eq!(shift_amount, 32);
1101 assert_eq!(max_amount, 32);
1102 }
1103 other => panic!("Expected ShiftAmountTooLarge, got: {:?}", other),
1104 }
1105 }
1106
1107 #[test]
1108 fn test_validate_checks_the_outer_shift_slot_too() {
1109 let mut cs = test_shape();
1110
1111 cs.and_constraints.push(AndConstraint::abc(
1114 vec![ShiftedValueIndex::new(
1115 ValueIndex::constant(0),
1116 [
1117 Shift::srl(3),
1118 Shift {
1120 variant: ShiftVariant::Sll32,
1121 amount: 32,
1122 },
1123 ],
1124 )],
1125 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1126 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1127 ));
1128
1129 match cs.validate().unwrap_err() {
1130 ConstraintSystemError::ConstraintOperand {
1131 constraint_kind,
1132 constraint_index,
1133 operand_name,
1134 source:
1135 OperandFault::ShiftAmountTooLarge {
1136 shift_amount,
1137 max_amount,
1138 },
1139 } => {
1140 assert_eq!(constraint_kind, ConstraintKind::And);
1141 assert_eq!(constraint_index, 0);
1142 assert_eq!(operand_name, "a");
1143 assert_eq!(shift_amount, 32);
1144 assert_eq!(max_amount, 32);
1145 }
1146 other => panic!("Expected ShiftAmountTooLarge, got: {:?}", other),
1147 }
1148 }
1149
1150 #[test]
1151 fn test_validate_rejects_a_lone_shift_in_the_outer_slot() {
1152 let mut cs = test_shape();
1153
1154 cs.and_constraints.push(AndConstraint::abc(
1157 vec![ShiftedValueIndex::new(
1158 ValueIndex::constant(0),
1159 [Shift::IDENTITY, Shift::rotr(5)],
1160 )],
1161 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1162 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1163 ));
1164
1165 match cs.validate().unwrap_err() {
1166 ConstraintSystemError::ConstraintOperand {
1167 constraint_kind,
1168 constraint_index,
1169 operand_name,
1170 source: OperandFault::NonCanonicalShiftSequence,
1171 } => {
1172 assert_eq!(constraint_kind, ConstraintKind::And);
1173 assert_eq!(constraint_index, 0);
1174 assert_eq!(operand_name, "a");
1175 }
1176 other => panic!("Expected NonCanonicalShiftSequence, got: {:?}", other),
1177 }
1178 }
1179
1180 #[test]
1181 fn test_validate_rejects_a_pair_that_collapses_to_one_shift() {
1182 let mut cs = test_shape();
1183
1184 cs.and_constraints.push(AndConstraint::abc(
1187 vec![ShiftedValueIndex::new(
1188 ValueIndex::constant(0),
1189 [Shift::rotr(4), Shift::rotr(5)],
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::Single(Shift::rotr(9)));
1206 }
1207 other => panic!("Expected CollapsibleShiftSequence, got: {:?}", other),
1208 }
1209 }
1210
1211 #[test]
1212 fn test_validate_rejects_a_pair_that_clears_the_word() {
1213 let mut cs = test_shape();
1214
1215 cs.and_constraints.push(AndConstraint::abc(
1219 vec![ShiftedValueIndex::new(
1220 ValueIndex::constant(0),
1221 [Shift::sll(40), Shift::sll(30)],
1222 )],
1223 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1224 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1225 ));
1226
1227 match cs.validate().unwrap_err() {
1228 ConstraintSystemError::ConstraintOperand {
1229 constraint_kind,
1230 constraint_index,
1231 operand_name,
1232 source: OperandFault::CollapsibleShiftSequence { composition },
1233 } => {
1234 assert_eq!(constraint_kind, ConstraintKind::And);
1235 assert_eq!(constraint_index, 0);
1236 assert_eq!(operand_name, "a");
1237 assert_eq!(composition, Composition::Zero);
1238 }
1239 other => panic!("Expected CollapsibleShiftSequence, got: {:?}", other),
1240 }
1241 }
1242
1243 #[test]
1244 fn test_validate_accepts_a_genuine_shift_pair() {
1245 let mut cs = test_shape();
1246
1247 cs.and_constraints.push(AndConstraint::abc(
1250 vec![ShiftedValueIndex::new(
1251 ValueIndex::constant(0),
1252 [Shift::srl(3), Shift::sll(3)],
1253 )],
1254 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1255 vec![ShiftedValueIndex::plain(ValueIndex::private(0))],
1256 ));
1257
1258 cs.validate().unwrap();
1259 }
1260
1261 #[test]
1262 fn test_roundtrip_cs_and_witnesses_reconstruct_valuevec() {
1263 let cs = test_shape();
1264
1265 let inout = (0..cs.n_inout)
1268 .map(|i| Word::from_u64(0xA5A5_5A5A ^ (i as u64 * 0x9E37_79B9)))
1269 .collect::<Vec<_>>();
1270 let private = (0..cs.n_private)
1271 .map(|i| Word::from_u64(0x5A5A_A5A5 ^ (i as u64 * 0x9E37_79B9)))
1272 .collect::<Vec<_>>();
1273 let values = cs.value_vec_from_data(&inout, &private);
1274
1275 let mut buf_cs = Vec::new();
1277 cs.serialize(&mut buf_cs).unwrap();
1278
1279 let mut buf_inout = Vec::new();
1280 ValuesRef::new(values.inout())
1281 .serialize(&mut buf_inout)
1282 .unwrap();
1283
1284 let mut buf_non_pub = Vec::new();
1285 ValuesRef::new(values.non_public())
1286 .serialize(&mut buf_non_pub)
1287 .unwrap();
1288
1289 let cs2 = ConstraintSystem::deserialize(&mut buf_cs.as_slice()).unwrap();
1291 let inout2 = ValuesData::deserialize(&mut buf_inout.as_slice()).unwrap();
1292 let non_pub2 = ValuesData::deserialize(&mut buf_non_pub.as_slice()).unwrap();
1293 assert_eq!(cs2.n_inout, inout2.len());
1294 assert_eq!(cs2.n_private, non_pub2.len());
1295
1296 let reconstructed = cs2.value_vec_from_data(&inout2, &non_pub2);
1298
1299 assert_eq!(reconstructed.combined_witness(), values.combined_witness());
1300 }
1301
1302 fn zero_constraint_system(n: usize) -> ConstraintSystem {
1307 ConstraintSystem {
1308 constants: vec![],
1309 n_inout: 0,
1310 n_private: 8,
1311 zero_constraints: (0..n)
1312 .map(|i| ZeroConstraint::plain([ValueIndex::private(i as u32)]))
1313 .collect(),
1314 and_constraints: vec![],
1315 imul_constraints: vec![],
1316 bmul_constraints: vec![],
1317 }
1318 }
1319
1320 #[test]
1321 fn verify_accepts_a_value_vector_satisfying_every_constraint() {
1322 let cs = zero_constraint_system(3);
1323 let values = cs.value_vec_from_data(&[Word::ZERO; 8], &[Word::ZERO; 8]);
1324
1325 assert!(cs.verify(&values).is_ok());
1326 }
1327
1328 #[test]
1329 fn verify_reports_the_index_of_the_first_unsatisfied_constraint() {
1330 let cs = zero_constraint_system(3);
1332 let mut private = [Word::ZERO; 8];
1333 private[1] = Word::from_u64(0xabc);
1334 let values = cs.value_vec_from_data(&[Word::ZERO; 8], &private);
1335
1336 let err = cs.verify(&values).unwrap_err();
1337
1338 assert_eq!(err.to_string(), "zero #1 is unsatisfied: 0000000000000abc != 0");
1341
1342 match err {
1343 VerificationError::Unsatisfied {
1344 constraint_index,
1345 source,
1346 } => {
1347 assert_eq!(constraint_index, 1);
1348 assert_eq!(source.kind(), ConstraintKind::Zero);
1349 match source {
1350 ConstraintViolation::Zero { val } => assert_eq!(val, 0xabc),
1351 other => panic!("wrong violation: {other:?}"),
1352 }
1353 }
1354 other => panic!("wrong error: {other:?}"),
1355 }
1356 }
1357
1358 #[test]
1359 fn verify_rejects_a_value_vector_that_opens_a_constant_to_the_wrong_word() {
1360 let cs = ConstraintSystem {
1363 zero_constraints: vec![],
1364 ..test_shape()
1365 };
1366 let mut public = [Word::ZERO; 8];
1370 public[0] = Word::from_u64(1);
1371 public[1] = Word::from_u64(42);
1372 public[2] = Word::from_u64(0xBAADF00D);
1373 let values = ValueVec::new_from_data(cs.n_const(), &public, &[Word::ZERO; 8]);
1374
1375 match cs.verify(&values).unwrap_err() {
1376 VerificationError::ConstantMismatch {
1377 value_index,
1378 expected,
1379 actual,
1380 } => {
1381 assert_eq!(value_index, 2);
1382 assert_eq!(expected, 0xDEADBEEF);
1383 assert_eq!(actual, 0xBAADF00D);
1384 }
1385 other => panic!("wrong error: {other:?}"),
1386 }
1387 }
1388
1389 #[test]
1393 fn an_oversized_inout_segment_is_rejected() {
1394 let mut cs = test_shape();
1395 cs.n_inout = ConstraintSystem::MAX_VALUES_PER_SEGMENT + 1;
1396
1397 assert!(matches!(
1398 cs.validate(),
1399 Err(ConstraintSystemError::SegmentTooLarge {
1400 segment: ValueSegment::InOut,
1401 ..
1402 })
1403 ));
1404 }
1405
1406 #[test]
1407 fn an_oversized_private_segment_is_rejected() {
1408 let mut cs = test_shape();
1409 cs.n_private = ConstraintSystem::MAX_VALUES_PER_SEGMENT + 1;
1410
1411 assert!(matches!(
1412 cs.validate(),
1413 Err(ConstraintSystemError::SegmentTooLarge {
1414 segment: ValueSegment::Private,
1415 ..
1416 })
1417 ));
1418 }
1419
1420 #[test]
1424 fn segments_exactly_at_the_cap_are_accepted() {
1425 let mut cs = test_shape();
1426 cs.n_inout = ConstraintSystem::MAX_VALUES_PER_SEGMENT;
1427 cs.n_private = ConstraintSystem::MAX_VALUES_PER_SEGMENT;
1428
1429 assert!(cs.validate().is_ok());
1430 }
1431}