Skip to main content

binius_core/constraint_system/
system.rs

1// Copyright 2025 Irreducible Inc.
2use 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/// The ConstraintSystem is the core data structure in Binius64 that defines the computational
9/// constraints to be proven in zero-knowledge. It represents a system of equations over 64-bit
10/// words that must be satisfied by a valid values vector [`ValueVec`].
11///
12/// # Clone
13///
14/// While this type is cloneable it may be expensive to do so since the constraint systems often
15/// can have millions of constraints.
16#[derive(Debug, Clone)]
17pub struct ConstraintSystem {
18	/// Description of the value vector layout expected by this constraint system.
19	pub value_vec_layout: ValueVecLayout,
20	/// The constants that this constraint system defines.
21	///
22	/// Those constants will be going to be available for constraints in the value vector. Those
23	/// are known to both prover and verifier.
24	pub constants: Vec<Word>,
25	/// List of AND constraints that must be satisfied by the values vector.
26	pub and_constraints: Vec<AndConstraint>,
27	/// List of MUL constraints that must be satisfied by the values vector.
28	pub mul_constraints: Vec<MulConstraint>,
29}
30
31impl ConstraintSystem {
32	/// Serialization format version for compatibility checking
33	pub const SERIALIZATION_VERSION: u32 = 2;
34}
35
36impl ConstraintSystem {
37	/// Creates a new constraint system.
38	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	/// Ensures that this constraint system is well-formed and ready for proving.
54	///
55	/// Specifically checks that:
56	///
57	/// - the value vec layout is [valid][`ValueVecLayout::validate`].
58	/// - every [shifted value index][super::ShiftedValueIndex] is canonical.
59	/// - referenced values indices are in the range.
60	/// - constraints do not reference values in the padding area.
61	/// - shifts amounts are valid.
62	pub fn validate(&self) -> Result<(), ConstraintSystemError> {
63		tracing::debug_span!("Validating constraint system");
64
65		// Validate the value vector layout
66		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				// check canonicity. SLL is the canonical form of the operand.
91				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				// Check if the value index is out of bounds.
107				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				// No value should refer to padding.
117				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	/// [Validates][`Self::validate`] and prepares this constraint system for proving/verifying.
130	///
131	/// This function performs the following:
132	/// 1. Validates the value vector layout (including public input checks)
133	/// 2. Validates the constraints.
134	/// 3. Pads the AND and MUL constraints to the next po2 size
135	pub fn validate_and_prepare(&mut self) -> Result<(), ConstraintSystemError> {
136		self.validate()?;
137
138		// Require all constraint types to have a power-of-two count. An empty MUL constraint set is
139		// kept at zero (rather than padded to a single dummy constraint) so the prover and verifier
140		// can skip the IntMul reduction entirely — see `IOPProver::prove` / `IOPVerifier::verify`.
141		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	/// Returns the number of AND constraints in the system.
167	pub const fn n_and_constraints(&self) -> usize {
168		self.and_constraints.len()
169	}
170
171	/// Returns the number of MUL  constraints in the system.
172	pub const fn n_mul_constraints(&self) -> usize {
173		self.mul_constraints.len()
174	}
175
176	/// The total length of the [`ValueVec`] expected by this constraint system.
177	pub const fn value_vec_len(&self) -> usize {
178		self.value_vec_layout.committed_total_len
179	}
180
181	/// Create a new [`ValueVec`] with the size expected by this constraint system.
182	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,         // Must be power of 2 and >= n_const
248			offset_witness: 8,       // Must be power of 2 and >= offset_inout + n_inout
249			committed_total_len: 16, // Must be power of 2 and >= offset_witness + n_witness
250			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		// Check version
286		assert_eq!(ConstraintSystem::SERIALIZATION_VERSION, 2);
287
288		// Check value_vec_layout
289		assert_eq!(original.value_vec_layout, deserialized.value_vec_layout);
290
291		// Check constants
292		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		// Check and_constraints
298		assert_eq!(original.and_constraints.len(), deserialized.and_constraints.len());
299
300		// Check mul_constraints
301		assert_eq!(original.mul_constraints.len(), deserialized.mul_constraints.len());
302	}
303
304	#[test]
305	fn test_constraint_system_version_mismatch() {
306		// Create a buffer with wrong version
307		let mut buf = Vec::new();
308		999u32.serialize(&mut buf).unwrap(); // Wrong version
309
310		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		// Create valid components but with mismatched constants length
323		let value_vec_layout = ValueVecLayout {
324			n_const: 5, // Expect 5 constants
325			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)]; // Only 2 constants
335		let and_constraints: Vec<AndConstraint> = vec![];
336		let mul_constraints: Vec<MulConstraint> = vec![];
337
338		// Serialize components manually
339		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		// Test with Vec<u8> (memory buffer)
363		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		// Test with bytes::BytesMut (another common buffer type)
369		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	/// Helper function to create or update the reference binary file for version compatibility
376	/// testing. This is not run automatically but can be used to regenerate the reference file
377	/// when needed.
378	#[test]
379	#[ignore] // Use `cargo test -- --ignored create_reference_binary` to run this
380	fn create_reference_binary_file() {
381		let constraint_system = create_test_constraint_system();
382
383		// Serialize to binary data
384		let mut buf = Vec::new();
385		constraint_system.serialize(&mut buf).unwrap();
386
387		// Write to reference file.
388		let test_data_path = std::path::Path::new("test_data/constraint_system_v2.bin");
389
390		// Create directory if it doesn't exist
391		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 deserialization from a reference binary file to ensure version compatibility.
402	/// This test will fail if breaking changes are made without incrementing the version.
403	#[test]
404	fn test_deserialize_from_reference_binary_file() {
405		// We now have v2 format with n_scratch field
406		// The v1 file is no longer compatible, so we test with v2
407		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		// Verify that the version is what we expect
429		// This is implicitly checked during deserialization, but we can also verify
430		// the file starts with the correct version bytes
431		let version_bytes = &binary_data[0..4]; // First 4 bytes should be version
432		let expected_version_bytes = 2u32.to_le_bytes(); // Version 2 in little-endian
433		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		// Add constraint that references padding (index 2 is padding between const and inout)
458		cs.add_and_constraint(AndConstraint::plain_abc(
459			vec![ValueIndex(0)], // valid constant
460			vec![ValueIndex(2)], // PADDING!
461			vec![ValueIndex(8)], // valid witness
462		));
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		// Add constraint that only references valid non-padding indices
496		cs.add_and_constraint(AndConstraint::plain_abc(
497			vec![ValueIndex(0), ValueIndex(1)], // constants
498			vec![ValueIndex(2), ValueIndex(3)], // inout
499			vec![ValueIndex(4), ValueIndex(5)], // witness
500		));
501
502		cs.add_mul_constraint(MulConstraint {
503			a: vec![ShiftedValueIndex::plain(ValueIndex(6))], // witness
504			b: vec![ShiftedValueIndex::plain(ValueIndex(7))], // witness
505			hi: vec![ShiftedValueIndex::plain(ValueIndex(8))], // internal
506			lo: vec![ShiftedValueIndex::plain(ValueIndex(9))], // internal
507		});
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		// Add AND constraint that references an out-of-range index
536		cs.add_and_constraint(AndConstraint::plain_abc(
537			vec![ValueIndex(0)],  // valid constant
538			vec![ValueIndex(16)], // OUT OF RANGE! (total_len is 16, so max valid index is 15)
539			vec![ValueIndex(8)],  // valid witness
540		));
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		// Add MUL constraint with out-of-range index in 'hi' operand
581		cs.add_mul_constraint(MulConstraint {
582			a: vec![ShiftedValueIndex::plain(ValueIndex(0))], // valid
583			b: vec![ShiftedValueIndex::plain(ValueIndex(1))], // valid
584			hi: vec![ShiftedValueIndex::plain(ValueIndex(100))], // WAY out of range!
585			lo: vec![ShiftedValueIndex::plain(ValueIndex(3))], // valid
586		});
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		// This test verifies that out-of-range checking happens before padding checking
611		// by using an index that is both out-of-range AND would be in a padding area if it were
612		// valid
613		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		// Index 20 is out of range (>= 16)
630		// If it were in range, indices 2-3 and 6-7 would be padding
631		cs.add_and_constraint(AndConstraint::plain_abc(
632			vec![ValueIndex(0)],
633			vec![ValueIndex(20)], // out of range
634			vec![ValueIndex(8)],
635		));
636
637		let result = cs.validate_and_prepare();
638		assert!(result.is_err());
639
640		// Should get OutOfRangeValueIndex, not PaddingValueIndex
641		match result.unwrap_err() {
642			ConstraintSystemError::OutOfRangeValueIndex { .. } => {
643				// Good, out-of-range was detected first
644			}
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		// Layout with non-zero scratch. Public = 8, total committed = 16, scratch = 5
655		let layout = ValueVecLayout {
656			n_const: 2,
657			n_inout: 3,
658			n_witness: 4,
659			n_internal: 3,
660			offset_inout: 4,   // >= n_const and power of two
661			offset_witness: 8, // >= offset_inout + n_inout and power of two
662			committed_total_len: 16,
663			n_scratch: 5, // non-zero scratch
664		};
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		// Build a ValueVec and fill both committed and scratch with non-zero data
670		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			// Deterministic pattern
674			let val = Word::from_u64(0xA5A5_5A5A ^ (i as u64 * 0x9E37_79B9));
675			values.set(i, val);
676		}
677
678		// Split into public and non-public witnesses and serialize all artifacts
679		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		// Deserialize everything back
692		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		// Reconstruct ValueVec from deserialized pieces
697		let reconstructed =
698			ValueVec::new_from_data(cs2.value_vec_layout, pub2.into_owned(), non_pub2.into_owned())
699				.unwrap();
700
701		// Ensure committed part matches exactly
702		assert_eq!(reconstructed.combined_witness(), values.combined_witness());
703
704		// Scratch is not serialized; reconstructed scratch should be zero-filled
705		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}