1use binius_utils::serialization::{DeserializeBytes, SerializationError, SerializeBytes};
3use bytes::{Buf, BufMut};
4
5use super::{AndConstraint, MulConstraint, Operand, ShiftVariant, ValueVec, ValueVecLayout};
6use crate::{error::ConstraintSystemError, word::Word};
7
8#[derive(Debug, Clone)]
17pub struct ConstraintSystem {
18 pub value_vec_layout: ValueVecLayout,
20 pub constants: Vec<Word>,
25 pub and_constraints: Vec<AndConstraint>,
27 pub mul_constraints: Vec<MulConstraint>,
29}
30
31impl ConstraintSystem {
32 pub const SERIALIZATION_VERSION: u32 = 2;
34}
35
36impl ConstraintSystem {
37 pub fn new(
39 constants: Vec<Word>,
40 value_vec_layout: ValueVecLayout,
41 and_constraints: Vec<AndConstraint>,
42 mul_constraints: Vec<MulConstraint>,
43 ) -> Self {
44 assert_eq!(constants.len(), value_vec_layout.n_const);
45 ConstraintSystem {
46 constants,
47 value_vec_layout,
48 and_constraints,
49 mul_constraints,
50 }
51 }
52
53 pub fn validate(&self) -> Result<(), ConstraintSystemError> {
63 tracing::debug_span!("Validating constraint system");
64
65 self.value_vec_layout.validate()?;
67
68 for i in 0..self.and_constraints.len() {
69 validate_operand(&self.and_constraints[i].a, &self.value_vec_layout, "and", i, "a")?;
70 validate_operand(&self.and_constraints[i].b, &self.value_vec_layout, "and", i, "b")?;
71 validate_operand(&self.and_constraints[i].c, &self.value_vec_layout, "and", i, "c")?;
72 }
73 for i in 0..self.mul_constraints.len() {
74 validate_operand(&self.mul_constraints[i].a, &self.value_vec_layout, "mul", i, "a")?;
75 validate_operand(&self.mul_constraints[i].b, &self.value_vec_layout, "mul", i, "b")?;
76 validate_operand(&self.mul_constraints[i].lo, &self.value_vec_layout, "mul", i, "lo")?;
77 validate_operand(&self.mul_constraints[i].hi, &self.value_vec_layout, "mul", i, "hi")?;
78 }
79
80 return Ok(());
81
82 fn validate_operand(
83 operand: &Operand,
84 value_vec_layout: &ValueVecLayout,
85 constraint_type: &'static str,
86 constraint_index: usize,
87 operand_name: &'static str,
88 ) -> Result<(), ConstraintSystemError> {
89 for term in operand {
90 if term.amount == 0 && term.shift_variant != ShiftVariant::Sll {
92 return Err(ConstraintSystemError::NonCanonicalShift {
93 constraint_type,
94 constraint_index,
95 operand_name,
96 });
97 }
98 if term.amount >= 64 {
99 return Err(ConstraintSystemError::ShiftAmountTooLarge {
100 constraint_type,
101 constraint_index,
102 operand_name,
103 shift_amount: term.amount as usize,
104 });
105 }
106 if value_vec_layout.is_committed_oob(term.value_index) {
108 return Err(ConstraintSystemError::OutOfRangeValueIndex {
109 constraint_type,
110 constraint_index,
111 operand_name,
112 value_index: term.value_index.0,
113 total_len: value_vec_layout.committed_total_len,
114 });
115 }
116 if value_vec_layout.is_padding(term.value_index) {
118 return Err(ConstraintSystemError::PaddingValueIndex {
119 constraint_type,
120 constraint_index,
121 operand_name,
122 });
123 }
124 }
125 Ok(())
126 }
127 }
128
129 pub fn validate_and_prepare(&mut self) -> Result<(), ConstraintSystemError> {
136 self.validate()?;
137
138 let and_target_size = self.and_constraints.len().next_power_of_two();
142 let mul_target_size = if self.mul_constraints.is_empty() {
143 0
144 } else {
145 self.mul_constraints.len().next_power_of_two()
146 };
147
148 self.and_constraints
149 .resize_with(and_target_size, AndConstraint::default);
150 self.mul_constraints
151 .resize_with(mul_target_size, MulConstraint::default);
152
153 Ok(())
154 }
155
156 #[cfg(test)]
157 fn add_and_constraint(&mut self, and_constraint: AndConstraint) {
158 self.and_constraints.push(and_constraint);
159 }
160
161 #[cfg(test)]
162 fn add_mul_constraint(&mut self, mul_constraint: MulConstraint) {
163 self.mul_constraints.push(mul_constraint);
164 }
165
166 pub const fn n_and_constraints(&self) -> usize {
168 self.and_constraints.len()
169 }
170
171 pub const fn n_mul_constraints(&self) -> usize {
173 self.mul_constraints.len()
174 }
175
176 pub const fn value_vec_len(&self) -> usize {
178 self.value_vec_layout.committed_total_len
179 }
180
181 pub fn new_value_vec(&self) -> ValueVec {
183 ValueVec::new(self.value_vec_layout.clone())
184 }
185}
186
187impl SerializeBytes for ConstraintSystem {
188 fn serialize(&self, mut write_buf: impl BufMut) -> Result<(), SerializationError> {
189 Self::SERIALIZATION_VERSION.serialize(&mut write_buf)?;
190
191 self.value_vec_layout.serialize(&mut write_buf)?;
192 self.constants.serialize(&mut write_buf)?;
193 self.and_constraints.serialize(&mut write_buf)?;
194 self.mul_constraints.serialize(write_buf)
195 }
196}
197
198impl DeserializeBytes for ConstraintSystem {
199 fn deserialize(mut read_buf: impl Buf) -> Result<Self, SerializationError>
200 where
201 Self: Sized,
202 {
203 let version = u32::deserialize(&mut read_buf)?;
204 if version != Self::SERIALIZATION_VERSION {
205 return Err(SerializationError::InvalidConstruction {
206 name: "ConstraintSystem::version",
207 });
208 }
209
210 let value_vec_layout = ValueVecLayout::deserialize(&mut read_buf)?;
211 let constants = Vec::<Word>::deserialize(&mut read_buf)?;
212 let and_constraints = Vec::<AndConstraint>::deserialize(&mut read_buf)?;
213 let mul_constraints = Vec::<MulConstraint>::deserialize(read_buf)?;
214
215 if constants.len() != value_vec_layout.n_const {
216 return Err(SerializationError::InvalidConstruction {
217 name: "ConstraintSystem::constants",
218 });
219 }
220
221 Ok(ConstraintSystem {
222 value_vec_layout,
223 constants,
224 and_constraints,
225 mul_constraints,
226 })
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233 use crate::constraint_system::{ShiftedValueIndex, ValueIndex, ValuesData};
234
235 pub(crate) fn create_test_constraint_system() -> ConstraintSystem {
236 let constants = vec![
237 Word::from_u64(1),
238 Word::from_u64(42),
239 Word::from_u64(0xDEADBEEF),
240 ];
241
242 let value_vec_layout = ValueVecLayout {
243 n_const: 3,
244 n_inout: 2,
245 n_witness: 10,
246 n_internal: 3,
247 offset_inout: 4, offset_witness: 8, committed_total_len: 16, n_scratch: 0,
251 };
252
253 let and_constraints = vec![
254 AndConstraint::plain_abc(
255 vec![ValueIndex(0), ValueIndex(1)],
256 vec![ValueIndex(2)],
257 vec![ValueIndex(3), ValueIndex(4)],
258 ),
259 AndConstraint::abc(
260 vec![ShiftedValueIndex::sll(ValueIndex(0), 5)],
261 vec![ShiftedValueIndex::srl(ValueIndex(1), 10)],
262 vec![ShiftedValueIndex::sar(ValueIndex(2), 15)],
263 ),
264 ];
265
266 let mul_constraints = vec![MulConstraint {
267 a: vec![ShiftedValueIndex::plain(ValueIndex(0))],
268 b: vec![ShiftedValueIndex::plain(ValueIndex(1))],
269 hi: vec![ShiftedValueIndex::plain(ValueIndex(2))],
270 lo: vec![ShiftedValueIndex::plain(ValueIndex(3))],
271 }];
272
273 ConstraintSystem::new(constants, value_vec_layout, and_constraints, mul_constraints)
274 }
275
276 #[test]
277 fn test_constraint_system_serialization_round_trip() {
278 let original = create_test_constraint_system();
279
280 let mut buf = Vec::new();
281 original.serialize(&mut buf).unwrap();
282
283 let deserialized = ConstraintSystem::deserialize(&mut buf.as_slice()).unwrap();
284
285 assert_eq!(ConstraintSystem::SERIALIZATION_VERSION, 2);
287
288 assert_eq!(original.value_vec_layout, deserialized.value_vec_layout);
290
291 assert_eq!(original.constants.len(), deserialized.constants.len());
293 for (orig, deser) in original.constants.iter().zip(deserialized.constants.iter()) {
294 assert_eq!(orig, deser);
295 }
296
297 assert_eq!(original.and_constraints.len(), deserialized.and_constraints.len());
299
300 assert_eq!(original.mul_constraints.len(), deserialized.mul_constraints.len());
302 }
303
304 #[test]
305 fn test_constraint_system_version_mismatch() {
306 let mut buf = Vec::new();
308 999u32.serialize(&mut buf).unwrap(); let result = ConstraintSystem::deserialize(&mut buf.as_slice());
311 assert!(result.is_err());
312 match result.unwrap_err() {
313 SerializationError::InvalidConstruction { name } => {
314 assert_eq!(name, "ConstraintSystem::version");
315 }
316 _ => panic!("Expected InvalidConstruction error"),
317 }
318 }
319
320 #[test]
321 fn test_constraint_system_constants_length_mismatch() {
322 let value_vec_layout = ValueVecLayout {
324 n_const: 5, n_inout: 2,
326 n_witness: 10,
327 n_internal: 3,
328 offset_inout: 8,
329 offset_witness: 16,
330 committed_total_len: 32,
331 n_scratch: 0,
332 };
333
334 let constants = vec![Word::from_u64(1), Word::from_u64(2)]; let and_constraints: Vec<AndConstraint> = vec![];
336 let mul_constraints: Vec<MulConstraint> = vec![];
337
338 let mut buf = Vec::new();
340 ConstraintSystem::SERIALIZATION_VERSION
341 .serialize(&mut buf)
342 .unwrap();
343 value_vec_layout.serialize(&mut buf).unwrap();
344 constants.serialize(&mut buf).unwrap();
345 and_constraints.serialize(&mut buf).unwrap();
346 mul_constraints.serialize(&mut buf).unwrap();
347
348 let result = ConstraintSystem::deserialize(&mut buf.as_slice());
349 assert!(result.is_err());
350 match result.unwrap_err() {
351 SerializationError::InvalidConstruction { name } => {
352 assert_eq!(name, "ConstraintSystem::constants");
353 }
354 _ => panic!("Expected InvalidConstruction error"),
355 }
356 }
357
358 #[test]
359 fn test_serialization_with_different_sources() {
360 let original = create_test_constraint_system();
361
362 let mut vec_buf = Vec::new();
364 original.serialize(&mut vec_buf).unwrap();
365 let deserialized1 = ConstraintSystem::deserialize(&mut vec_buf.as_slice()).unwrap();
366 assert_eq!(original.constants.len(), deserialized1.constants.len());
367
368 let mut bytes_buf = bytes::BytesMut::new();
370 original.serialize(&mut bytes_buf).unwrap();
371 let deserialized2 = ConstraintSystem::deserialize(bytes_buf.freeze()).unwrap();
372 assert_eq!(original.constants.len(), deserialized2.constants.len());
373 }
374
375 #[test]
379 #[ignore] fn create_reference_binary_file() {
381 let constraint_system = create_test_constraint_system();
382
383 let mut buf = Vec::new();
385 constraint_system.serialize(&mut buf).unwrap();
386
387 let test_data_path = std::path::Path::new("test_data/constraint_system_v2.bin");
389
390 if let Some(parent) = test_data_path.parent() {
392 std::fs::create_dir_all(parent).unwrap();
393 }
394
395 std::fs::write(test_data_path, &buf).unwrap();
396
397 println!("Created reference binary file at: {:?}", test_data_path);
398 println!("Binary data length: {} bytes", buf.len());
399 }
400
401 #[test]
404 fn test_deserialize_from_reference_binary_file() {
405 let binary_data = include_bytes!("../../test_data/constraint_system_v2.bin");
408
409 let deserialized = ConstraintSystem::deserialize(&mut binary_data.as_slice()).unwrap();
410
411 assert_eq!(deserialized.value_vec_layout.n_const, 3);
412 assert_eq!(deserialized.value_vec_layout.n_inout, 2);
413 assert_eq!(deserialized.value_vec_layout.n_witness, 10);
414 assert_eq!(deserialized.value_vec_layout.n_internal, 3);
415 assert_eq!(deserialized.value_vec_layout.offset_inout, 4);
416 assert_eq!(deserialized.value_vec_layout.offset_witness, 8);
417 assert_eq!(deserialized.value_vec_layout.committed_total_len, 16);
418 assert_eq!(deserialized.value_vec_layout.n_scratch, 0);
419
420 assert_eq!(deserialized.constants.len(), 3);
421 assert_eq!(deserialized.constants[0].as_u64(), 1);
422 assert_eq!(deserialized.constants[1].as_u64(), 42);
423 assert_eq!(deserialized.constants[2].as_u64(), 0xDEADBEEF);
424
425 assert_eq!(deserialized.and_constraints.len(), 2);
426 assert_eq!(deserialized.mul_constraints.len(), 1);
427
428 let version_bytes = &binary_data[0..4]; let expected_version_bytes = 2u32.to_le_bytes(); assert_eq!(
434 version_bytes, expected_version_bytes,
435 "Binary file version mismatch. If you made breaking changes, increment ConstraintSystem::SERIALIZATION_VERSION"
436 );
437 }
438
439 #[test]
440 fn test_validate_rejects_padding_references() {
441 let mut cs = ConstraintSystem::new(
442 vec![Word::from_u64(1)],
443 ValueVecLayout {
444 n_const: 1,
445 n_inout: 1,
446 n_witness: 2,
447 n_internal: 2,
448 offset_inout: 4,
449 offset_witness: 8,
450 committed_total_len: 16,
451 n_scratch: 0,
452 },
453 vec![],
454 vec![],
455 );
456
457 cs.add_and_constraint(AndConstraint::plain_abc(
459 vec![ValueIndex(0)], vec![ValueIndex(2)], vec![ValueIndex(8)], ));
463
464 let result = cs.validate_and_prepare();
465 assert!(result.is_err(), "Should reject constraint referencing padding");
466
467 match result.unwrap_err() {
468 ConstraintSystemError::PaddingValueIndex {
469 constraint_type, ..
470 } => {
471 assert_eq!(constraint_type, "and");
472 }
473 other => panic!("Expected PaddingValueIndex error, got: {:?}", other),
474 }
475 }
476
477 #[test]
478 fn test_validate_accepts_non_padding_references() {
479 let mut cs = ConstraintSystem::new(
480 vec![Word::from_u64(1), Word::from_u64(2)],
481 ValueVecLayout {
482 n_const: 2,
483 n_inout: 2,
484 n_witness: 4,
485 n_internal: 4,
486 offset_inout: 2,
487 offset_witness: 4,
488 committed_total_len: 16,
489 n_scratch: 0,
490 },
491 vec![],
492 vec![],
493 );
494
495 cs.add_and_constraint(AndConstraint::plain_abc(
497 vec![ValueIndex(0), ValueIndex(1)], vec![ValueIndex(2), ValueIndex(3)], vec![ValueIndex(4), ValueIndex(5)], ));
501
502 cs.add_mul_constraint(MulConstraint {
503 a: vec![ShiftedValueIndex::plain(ValueIndex(6))], b: vec![ShiftedValueIndex::plain(ValueIndex(7))], hi: vec![ShiftedValueIndex::plain(ValueIndex(8))], lo: vec![ShiftedValueIndex::plain(ValueIndex(9))], });
508
509 let result = cs.validate_and_prepare();
510 assert!(
511 result.is_ok(),
512 "Should accept constraints with only valid references: {:?}",
513 result
514 );
515 }
516
517 #[test]
518 fn test_validate_rejects_out_of_range_indices() {
519 let mut cs = ConstraintSystem::new(
520 vec![Word::from_u64(1)],
521 ValueVecLayout {
522 n_const: 1,
523 n_inout: 1,
524 n_witness: 2,
525 n_internal: 2,
526 offset_inout: 4,
527 offset_witness: 8,
528 committed_total_len: 16,
529 n_scratch: 0,
530 },
531 vec![],
532 vec![],
533 );
534
535 cs.add_and_constraint(AndConstraint::plain_abc(
537 vec![ValueIndex(0)], vec![ValueIndex(16)], vec![ValueIndex(8)], ));
541
542 let result = cs.validate_and_prepare();
543 assert!(result.is_err(), "Should reject constraint with out-of-range index");
544
545 match result.unwrap_err() {
546 ConstraintSystemError::OutOfRangeValueIndex {
547 constraint_type,
548 operand_name,
549 value_index,
550 total_len,
551 ..
552 } => {
553 assert_eq!(constraint_type, "and");
554 assert_eq!(operand_name, "b");
555 assert_eq!(value_index, 16);
556 assert_eq!(total_len, 16);
557 }
558 other => panic!("Expected OutOfRangeValueIndex error, got: {:?}", other),
559 }
560 }
561
562 #[test]
563 fn test_validate_rejects_out_of_range_in_mul_constraint() {
564 let mut cs = ConstraintSystem::new(
565 vec![Word::from_u64(1), Word::from_u64(2)],
566 ValueVecLayout {
567 n_const: 2,
568 n_inout: 2,
569 n_witness: 4,
570 n_internal: 4,
571 offset_inout: 2,
572 offset_witness: 4,
573 committed_total_len: 16,
574 n_scratch: 0,
575 },
576 vec![],
577 vec![],
578 );
579
580 cs.add_mul_constraint(MulConstraint {
582 a: vec![ShiftedValueIndex::plain(ValueIndex(0))], b: vec![ShiftedValueIndex::plain(ValueIndex(1))], hi: vec![ShiftedValueIndex::plain(ValueIndex(100))], lo: vec![ShiftedValueIndex::plain(ValueIndex(3))], });
587
588 let result = cs.validate_and_prepare();
589 assert!(result.is_err(), "Should reject MUL constraint with out-of-range index");
590
591 match result.unwrap_err() {
592 ConstraintSystemError::OutOfRangeValueIndex {
593 constraint_type,
594 operand_name,
595 value_index,
596 total_len,
597 ..
598 } => {
599 assert_eq!(constraint_type, "mul");
600 assert_eq!(operand_name, "hi");
601 assert_eq!(value_index, 100);
602 assert_eq!(total_len, 16);
603 }
604 other => panic!("Expected OutOfRangeValueIndex error, got: {:?}", other),
605 }
606 }
607
608 #[test]
609 fn test_validate_checks_out_of_range_before_padding() {
610 let mut cs = ConstraintSystem::new(
614 vec![Word::from_u64(1)],
615 ValueVecLayout {
616 n_const: 1,
617 n_inout: 1,
618 n_witness: 2,
619 n_internal: 2,
620 offset_inout: 4,
621 offset_witness: 8,
622 committed_total_len: 16,
623 n_scratch: 0,
624 },
625 vec![],
626 vec![],
627 );
628
629 cs.add_and_constraint(AndConstraint::plain_abc(
632 vec![ValueIndex(0)],
633 vec![ValueIndex(20)], vec![ValueIndex(8)],
635 ));
636
637 let result = cs.validate_and_prepare();
638 assert!(result.is_err());
639
640 match result.unwrap_err() {
642 ConstraintSystemError::OutOfRangeValueIndex { .. } => {
643 }
645 other => panic!(
646 "Expected OutOfRangeValueIndex to be detected before padding check, got: {:?}",
647 other
648 ),
649 }
650 }
651
652 #[test]
653 fn test_roundtrip_cs_and_witnesses_reconstruct_valuevec_with_scratch() {
654 let layout = ValueVecLayout {
656 n_const: 2,
657 n_inout: 3,
658 n_witness: 4,
659 n_internal: 3,
660 offset_inout: 4, offset_witness: 8, committed_total_len: 16,
663 n_scratch: 5, };
665
666 let constants = vec![Word::from_u64(11), Word::from_u64(22)];
667 let cs = ConstraintSystem::new(constants, layout.clone(), vec![], vec![]);
668
669 let mut values = cs.new_value_vec();
671 let full_len = layout.committed_total_len + layout.n_scratch;
672 for i in 0..full_len {
673 let val = Word::from_u64(0xA5A5_5A5A ^ (i as u64 * 0x9E37_79B9));
675 values.set(i, val);
676 }
677
678 let public_data = ValuesData::from(values.public());
680 let non_public_data = ValuesData::from(values.non_public());
681
682 let mut buf_cs = Vec::new();
683 cs.serialize(&mut buf_cs).unwrap();
684
685 let mut buf_pub = Vec::new();
686 public_data.serialize(&mut buf_pub).unwrap();
687
688 let mut buf_non_pub = Vec::new();
689 non_public_data.serialize(&mut buf_non_pub).unwrap();
690
691 let cs2 = ConstraintSystem::deserialize(&mut buf_cs.as_slice()).unwrap();
693 let pub2 = ValuesData::deserialize(&mut buf_pub.as_slice()).unwrap();
694 let non_pub2 = ValuesData::deserialize(&mut buf_non_pub.as_slice()).unwrap();
695
696 let reconstructed =
698 ValueVec::new_from_data(cs2.value_vec_layout, pub2.into_owned(), non_pub2.into_owned())
699 .unwrap();
700
701 assert_eq!(reconstructed.combined_witness(), values.combined_witness());
703
704 let scratch_start = layout.committed_total_len;
706 let scratch_end = scratch_start + layout.n_scratch;
707 for i in scratch_start..scratch_end {
708 assert_eq!(reconstructed.get(i), Word::ZERO, "scratch index {i} should be zero");
709 }
710 }
711}