binius_core/
constraint_system.rs

1// Copyright 2025 Irreducible Inc.
2//! Constraint system and related definitions.
3
4use std::{
5	borrow::Cow,
6	cmp,
7	ops::{Index, IndexMut},
8};
9
10use binius_utils::serialization::{DeserializeBytes, SerializationError, SerializeBytes};
11use bytes::{Buf, BufMut};
12
13use crate::{consts, error::ConstraintSystemError, word::Word};
14
15/// A type safe wrapper over an index into the [`ValueVec`].
16#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
17pub struct ValueIndex(pub u32);
18
19impl ValueIndex {
20	/// The value index that is not considered to be valid.
21	pub const INVALID: ValueIndex = ValueIndex(u32::MAX);
22}
23
24/// The most sensible default for a value index is invalid.
25impl Default for ValueIndex {
26	fn default() -> Self {
27		Self::INVALID
28	}
29}
30
31impl SerializeBytes for ValueIndex {
32	fn serialize(&self, write_buf: impl BufMut) -> Result<(), SerializationError> {
33		self.0.serialize(write_buf)
34	}
35}
36
37impl DeserializeBytes for ValueIndex {
38	fn deserialize(read_buf: impl Buf) -> Result<Self, SerializationError>
39	where
40		Self: Sized,
41	{
42		Ok(ValueIndex(u32::deserialize(read_buf)?))
43	}
44}
45
46/// A different variants of shifting a value.
47///
48/// Note that there is no shift left arithmetic because it is redundant.
49#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
50pub enum ShiftVariant {
51	/// Shift logical left.
52	Sll,
53	/// Shift logical right.
54	Slr,
55	/// Shift arithmetic right.
56	///
57	/// This is similar to the logical shift right but instead of shifting in 0 bits it will
58	/// replicate the sign bit.
59	Sar,
60	/// Rotate right.
61	///
62	/// Rotates bits to the right, with bits shifted off the right end wrapping around to the left.
63	Rotr,
64	/// Shift logical left on 32-bit halves.
65	///
66	/// Performs independent logical left shifts on the upper and lower 32-bit halves of the word.
67	/// Only uses the lower 5 bits of the shift amount (0-31).
68	Sll32,
69	/// Shift logical right on 32-bit halves.
70	///
71	/// Performs independent logical right shifts on the upper and lower 32-bit halves of the word.
72	/// Only uses the lower 5 bits of the shift amount (0-31).
73	Srl32,
74	/// Shift arithmetic right on 32-bit halves.
75	///
76	/// Performs independent arithmetic right shifts on the upper and lower 32-bit halves of the
77	/// word. Sign extends each 32-bit half independently. Only uses the lower 5 bits of the shift
78	/// amount (0-31).
79	Sra32,
80	/// Rotate right on 32-bit halves.
81	///
82	/// Performs independent rotate right operations on the upper and lower 32-bit halves of the
83	/// word. Bits shifted off the right end wrap around to the left within each 32-bit half.
84	/// Only uses the lower 5 bits of the shift amount (0-31).
85	Rotr32,
86}
87
88impl SerializeBytes for ShiftVariant {
89	fn serialize(&self, write_buf: impl BufMut) -> Result<(), SerializationError> {
90		let index = match self {
91			ShiftVariant::Sll => 0u8,
92			ShiftVariant::Slr => 1u8,
93			ShiftVariant::Sar => 2u8,
94			ShiftVariant::Rotr => 3u8,
95			ShiftVariant::Sll32 => 4u8,
96			ShiftVariant::Srl32 => 5u8,
97			ShiftVariant::Sra32 => 6u8,
98			ShiftVariant::Rotr32 => 7u8,
99		};
100		index.serialize(write_buf)
101	}
102}
103
104impl DeserializeBytes for ShiftVariant {
105	fn deserialize(read_buf: impl Buf) -> Result<Self, SerializationError>
106	where
107		Self: Sized,
108	{
109		let index = u8::deserialize(read_buf)?;
110		match index {
111			0 => Ok(ShiftVariant::Sll),
112			1 => Ok(ShiftVariant::Slr),
113			2 => Ok(ShiftVariant::Sar),
114			3 => Ok(ShiftVariant::Rotr),
115			4 => Ok(ShiftVariant::Sll32),
116			5 => Ok(ShiftVariant::Srl32),
117			6 => Ok(ShiftVariant::Sra32),
118			7 => Ok(ShiftVariant::Rotr32),
119			_ => Err(SerializationError::UnknownEnumVariant {
120				name: "ShiftVariant",
121				index,
122			}),
123		}
124	}
125}
126
127/// Similar to [`ValueIndex`], but represents a value that has been shifted by a certain amount.
128///
129/// This is used in the operands to constraints like [`AndConstraint`].
130///
131/// The canonical formto represent a value without any shifting is [`ShiftVariant::Sll`] with
132/// amount equals 0.
133#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
134pub struct ShiftedValueIndex {
135	/// The index of this value in the input values vector.
136	pub value_index: ValueIndex,
137	/// The flavour of the shift that the value must be shifted by.
138	pub shift_variant: ShiftVariant,
139	/// The number of bits by which the value must be shifted by.
140	///
141	/// Must be less than 64.
142	pub amount: usize,
143}
144
145impl ShiftedValueIndex {
146	/// Create a value index that just uses the specified value. Equivalent to [`Self::sll`] with
147	/// amount equals 0.
148	pub fn plain(value_index: ValueIndex) -> Self {
149		Self {
150			value_index,
151			shift_variant: ShiftVariant::Sll,
152			amount: 0,
153		}
154	}
155
156	/// Shift Left Logical by the given number of bits.
157	///
158	/// # Panics
159	/// Panics if the shift amount is greater than or equal to 64.
160	pub fn sll(value_index: ValueIndex, amount: usize) -> Self {
161		assert!(amount < 64, "shift amount n={amount} out of range");
162		Self {
163			value_index,
164			shift_variant: ShiftVariant::Sll,
165			amount,
166		}
167	}
168
169	/// Shift Right Logical by the given number of bits.
170	///
171	/// # Panics
172	/// Panics if the shift amount is greater than or equal to 64.
173	pub fn srl(value_index: ValueIndex, amount: usize) -> Self {
174		assert!(amount < 64, "shift amount n={amount} out of range");
175		Self {
176			value_index,
177			shift_variant: ShiftVariant::Slr,
178			amount,
179		}
180	}
181
182	/// Shift Right Arithmetic by the given number of bits.
183	///
184	/// This is similar to the Shift Right Logical but instead of shifting in 0 bits it will
185	/// replicate the sign bit.
186	///
187	/// # Panics
188	/// Panics if the shift amount is greater than or equal to 64.
189	pub fn sar(value_index: ValueIndex, amount: usize) -> Self {
190		assert!(amount < 64, "shift amount n={amount} out of range");
191		Self {
192			value_index,
193			shift_variant: ShiftVariant::Sar,
194			amount,
195		}
196	}
197
198	/// Rotate Right by the given number of bits.
199	///
200	/// Rotates bits to the right, with bits shifted off the right end wrapping around to the left.
201	///
202	/// # Panics
203	/// Panics if the shift amount is greater than or equal to 64.
204	pub fn rotr(value_index: ValueIndex, amount: usize) -> Self {
205		assert!(amount < 64, "shift amount n={amount} out of range");
206		Self {
207			value_index,
208			shift_variant: ShiftVariant::Rotr,
209			amount,
210		}
211	}
212
213	/// Shift Left Logical on 32-bit halves by the given number of bits.
214	///
215	/// Performs independent logical left shifts on the upper and lower 32-bit halves.
216	/// Only uses the lower 5 bits of the shift amount (0-31).
217	///
218	/// # Panics
219	/// Panics if the shift amount is greater than or equal to 32.
220	pub fn sll32(value_index: ValueIndex, amount: usize) -> Self {
221		assert!(amount < 32, "shift amount n={amount} out of range for 32-bit shift");
222		Self {
223			value_index,
224			shift_variant: ShiftVariant::Sll32,
225			amount,
226		}
227	}
228
229	/// Shift Right Logical on 32-bit halves by the given number of bits.
230	///
231	/// Performs independent logical right shifts on the upper and lower 32-bit halves.
232	/// Only uses the lower 5 bits of the shift amount (0-31).
233	///
234	/// # Panics
235	/// Panics if the shift amount is greater than or equal to 32.
236	pub fn srl32(value_index: ValueIndex, amount: usize) -> Self {
237		assert!(amount < 32, "shift amount n={amount} out of range for 32-bit shift");
238		Self {
239			value_index,
240			shift_variant: ShiftVariant::Srl32,
241			amount,
242		}
243	}
244
245	/// Shift Right Arithmetic on 32-bit halves by the given number of bits.
246	///
247	/// Performs independent arithmetic right shifts on the upper and lower 32-bit halves.
248	/// Sign extends each 32-bit half independently. Only uses the lower 5 bits of the shift amount
249	/// (0-31).
250	///
251	/// # Panics
252	/// Panics if the shift amount is greater than or equal to 32.
253	pub fn sra32(value_index: ValueIndex, amount: usize) -> Self {
254		assert!(amount < 32, "shift amount n={amount} out of range for 32-bit shift");
255		Self {
256			value_index,
257			shift_variant: ShiftVariant::Sra32,
258			amount,
259		}
260	}
261
262	/// Rotate Right on 32-bit halves by the given number of bits.
263	///
264	/// Performs independent rotate right operations on the upper and lower 32-bit halves.
265	/// Bits shifted off the right end wrap around to the left within each 32-bit half.
266	/// Only uses the lower 5 bits of the shift amount (0-31).
267	///
268	/// # Panics
269	/// Panics if the shift amount is greater than or equal to 32.
270	pub fn rotr32(value_index: ValueIndex, amount: usize) -> Self {
271		assert!(amount < 32, "shift amount n={amount} out of range for 32-bit rotate");
272		Self {
273			value_index,
274			shift_variant: ShiftVariant::Rotr32,
275			amount,
276		}
277	}
278}
279
280impl SerializeBytes for ShiftedValueIndex {
281	fn serialize(&self, mut write_buf: impl BufMut) -> Result<(), SerializationError> {
282		self.value_index.serialize(&mut write_buf)?;
283		self.shift_variant.serialize(&mut write_buf)?;
284		self.amount.serialize(write_buf)
285	}
286}
287
288impl DeserializeBytes for ShiftedValueIndex {
289	fn deserialize(mut read_buf: impl Buf) -> Result<Self, SerializationError>
290	where
291		Self: Sized,
292	{
293		let value_index = ValueIndex::deserialize(&mut read_buf)?;
294		let shift_variant = ShiftVariant::deserialize(&mut read_buf)?;
295		let amount = usize::deserialize(read_buf)?;
296
297		// Validate that amount is within valid range
298		if amount >= 64 {
299			return Err(SerializationError::InvalidConstruction {
300				name: "ShiftedValueIndex::amount",
301			});
302		}
303
304		Ok(ShiftedValueIndex {
305			value_index,
306			shift_variant,
307			amount,
308		})
309	}
310}
311
312/// Operand type.
313///
314/// An operand in Binius64 is a vector of shifted values. Each item in the vector represents a
315/// term in a XOR combination of shifted values.
316///
317/// To give a couple examples:
318///
319/// ```ignore
320/// vec![] == 0
321/// vec![1] == 1
322/// vec![1, 1] == 1 ^ 1
323/// vec![x >> 5, y << 5] = (x >> 5) ^ (y << 5)
324/// ```
325pub type Operand = Vec<ShiftedValueIndex>;
326
327/// AND constraint: `A & B = C`.
328///
329/// This constraint verifies that the bitwise AND of operands A and B equals operand C.
330/// Each operand is computed as the XOR of multiple shifted values from the value vector.
331#[derive(Debug, Clone, Default)]
332pub struct AndConstraint {
333	/// Operand A.
334	pub a: Operand,
335	/// Operand B.
336	pub b: Operand,
337	/// Operand C.
338	pub c: Operand,
339}
340
341impl AndConstraint {
342	/// Creates a new AND constraint from XOR combinations of the given unshifted values.
343	pub fn plain_abc(
344		a: impl IntoIterator<Item = ValueIndex>,
345		b: impl IntoIterator<Item = ValueIndex>,
346		c: impl IntoIterator<Item = ValueIndex>,
347	) -> AndConstraint {
348		AndConstraint {
349			a: a.into_iter().map(ShiftedValueIndex::plain).collect(),
350			b: b.into_iter().map(ShiftedValueIndex::plain).collect(),
351			c: c.into_iter().map(ShiftedValueIndex::plain).collect(),
352		}
353	}
354
355	/// Creates a new AND constraint from XOR combinations of the given shifted values.
356	pub fn abc(
357		a: impl IntoIterator<Item = ShiftedValueIndex>,
358		b: impl IntoIterator<Item = ShiftedValueIndex>,
359		c: impl IntoIterator<Item = ShiftedValueIndex>,
360	) -> AndConstraint {
361		AndConstraint {
362			a: a.into_iter().collect(),
363			b: b.into_iter().collect(),
364			c: c.into_iter().collect(),
365		}
366	}
367}
368
369impl SerializeBytes for AndConstraint {
370	fn serialize(&self, mut write_buf: impl BufMut) -> Result<(), SerializationError> {
371		self.a.serialize(&mut write_buf)?;
372		self.b.serialize(&mut write_buf)?;
373		self.c.serialize(write_buf)
374	}
375}
376
377impl DeserializeBytes for AndConstraint {
378	fn deserialize(mut read_buf: impl Buf) -> Result<Self, SerializationError>
379	where
380		Self: Sized,
381	{
382		let a = Vec::<ShiftedValueIndex>::deserialize(&mut read_buf)?;
383		let b = Vec::<ShiftedValueIndex>::deserialize(&mut read_buf)?;
384		let c = Vec::<ShiftedValueIndex>::deserialize(read_buf)?;
385
386		Ok(AndConstraint { a, b, c })
387	}
388}
389
390/// MUL constraint: `A * B = (HI << 64) | LO`.
391///
392/// 64-bit unsigned integer multiplication producing 128-bit result split into high and low 64-bit
393/// words.
394#[derive(Debug, Clone, Default)]
395pub struct MulConstraint {
396	/// A operand.
397	pub a: Operand,
398	/// B operand.
399	pub b: Operand,
400	/// HI operand.
401	///
402	/// The high 64 bits of the result of the multiplication.
403	pub hi: Operand,
404	/// LO operand.
405	///
406	/// The low 64 bits of the result of the multiplication.
407	pub lo: Operand,
408}
409
410impl SerializeBytes for MulConstraint {
411	fn serialize(&self, mut write_buf: impl BufMut) -> Result<(), SerializationError> {
412		self.a.serialize(&mut write_buf)?;
413		self.b.serialize(&mut write_buf)?;
414		self.hi.serialize(&mut write_buf)?;
415		self.lo.serialize(write_buf)
416	}
417}
418
419impl DeserializeBytes for MulConstraint {
420	fn deserialize(mut read_buf: impl Buf) -> Result<Self, SerializationError>
421	where
422		Self: Sized,
423	{
424		let a = Vec::<ShiftedValueIndex>::deserialize(&mut read_buf)?;
425		let b = Vec::<ShiftedValueIndex>::deserialize(&mut read_buf)?;
426		let hi = Vec::<ShiftedValueIndex>::deserialize(&mut read_buf)?;
427		let lo = Vec::<ShiftedValueIndex>::deserialize(read_buf)?;
428
429		Ok(MulConstraint { a, b, hi, lo })
430	}
431}
432
433/// The ConstraintSystem is the core data structure in Binius64 that defines the computational
434/// constraints to be proven in zero-knowledge. It represents a system of equations over 64-bit
435/// words that must be satisfied by a valid values vector [`ValueVec`].
436///
437/// # Clone
438///
439/// While this type is cloneable it may be expensive to do so since the constraint systems often
440/// can have millions of constraints.
441#[derive(Debug, Clone)]
442pub struct ConstraintSystem {
443	/// Description of the value vector layout expected by this constraint system.
444	pub value_vec_layout: ValueVecLayout,
445	/// The constants that this constraint system defines.
446	///
447	/// Those constants will be going to be available for constraints in the value vector. Those
448	/// are known to both prover and verifier.
449	pub constants: Vec<Word>,
450	/// List of AND constraints that must be satisfied by the values vector.
451	pub and_constraints: Vec<AndConstraint>,
452	/// List of MUL constraints that must be satisfied by the values vector.
453	pub mul_constraints: Vec<MulConstraint>,
454}
455
456impl ConstraintSystem {
457	/// Serialization format version for compatibility checking
458	pub const SERIALIZATION_VERSION: u32 = 2;
459}
460
461impl ConstraintSystem {
462	/// Creates a new constraint system.
463	pub fn new(
464		constants: Vec<Word>,
465		value_vec_layout: ValueVecLayout,
466		and_constraints: Vec<AndConstraint>,
467		mul_constraints: Vec<MulConstraint>,
468	) -> Self {
469		assert_eq!(constants.len(), value_vec_layout.n_const);
470		ConstraintSystem {
471			constants,
472			value_vec_layout,
473			and_constraints,
474			mul_constraints,
475		}
476	}
477
478	/// Ensures that this constraint system is well-formed and ready for proving.
479	///
480	/// Specifically checks that:
481	///
482	/// - the value vec layout is [valid][`ValueVecLayout::validate`].
483	/// - every [shifted value index][`ShiftedValueIndex`] is canonical.
484	/// - referenced values indices are in the range.
485	/// - constraints do not reference values in the padding area.
486	/// - shifts amounts are valid.
487	pub fn validate(&self) -> Result<(), ConstraintSystemError> {
488		// Validate the value vector layout
489		self.value_vec_layout.validate()?;
490
491		for i in 0..self.and_constraints.len() {
492			validate_operand(&self.and_constraints[i].a, &self.value_vec_layout, "and", i, "a")?;
493			validate_operand(&self.and_constraints[i].b, &self.value_vec_layout, "and", i, "b")?;
494			validate_operand(&self.and_constraints[i].c, &self.value_vec_layout, "and", i, "c")?;
495		}
496		for i in 0..self.mul_constraints.len() {
497			validate_operand(&self.mul_constraints[i].a, &self.value_vec_layout, "mul", i, "a")?;
498			validate_operand(&self.mul_constraints[i].b, &self.value_vec_layout, "mul", i, "b")?;
499			validate_operand(&self.mul_constraints[i].lo, &self.value_vec_layout, "mul", i, "lo")?;
500			validate_operand(&self.mul_constraints[i].hi, &self.value_vec_layout, "mul", i, "hi")?;
501		}
502
503		return Ok(());
504
505		fn validate_operand(
506			operand: &Operand,
507			value_vec_layout: &ValueVecLayout,
508			constraint_type: &'static str,
509			constraint_index: usize,
510			operand_name: &'static str,
511		) -> Result<(), ConstraintSystemError> {
512			for term in operand {
513				// check canonicity. SLL is the canonical form of the operand.
514				if term.amount == 0 && term.shift_variant != ShiftVariant::Sll {
515					return Err(ConstraintSystemError::NonCanonicalShift {
516						constraint_type,
517						constraint_index,
518						operand_name,
519					});
520				}
521				if term.amount >= 64 {
522					return Err(ConstraintSystemError::ShiftAmountTooLarge {
523						constraint_type,
524						constraint_index,
525						operand_name,
526						shift_amount: term.amount,
527					});
528				}
529				// Check if the value index is out of bounds.
530				if value_vec_layout.is_committed_oob(term.value_index) {
531					return Err(ConstraintSystemError::OutOfRangeValueIndex {
532						constraint_type,
533						constraint_index,
534						operand_name,
535						value_index: term.value_index.0,
536						total_len: value_vec_layout.committed_total_len,
537					});
538				}
539				// No value should refer to padding.
540				if value_vec_layout.is_padding(term.value_index) {
541					return Err(ConstraintSystemError::PaddingValueIndex {
542						constraint_type,
543						constraint_index,
544						operand_name,
545					});
546				}
547			}
548			Ok(())
549		}
550	}
551
552	/// [Validates][`Self::validate`] and prepares this constraint system for proving/verifying.
553	///
554	/// This function performs the following:
555	/// 1. Validates the value vector layout (including public input checks)
556	/// 2. Validates the constraints.
557	/// 3. Pads the AND and MUL constraints to the next po2 size
558	pub fn validate_and_prepare(&mut self) -> Result<(), ConstraintSystemError> {
559		self.validate()?;
560
561		// Both AND and MUL constraint list have requirements wrt their sizes.
562		let and_target_size =
563			cmp::max(consts::MIN_AND_CONSTRAINTS, self.and_constraints.len()).next_power_of_two();
564		let mul_target_size =
565			cmp::max(consts::MIN_MUL_CONSTRAINTS, self.mul_constraints.len()).next_power_of_two();
566
567		self.and_constraints
568			.resize_with(and_target_size, AndConstraint::default);
569		self.mul_constraints
570			.resize_with(mul_target_size, MulConstraint::default);
571
572		Ok(())
573	}
574
575	#[cfg(test)]
576	fn add_and_constraint(&mut self, and_constraint: AndConstraint) {
577		self.and_constraints.push(and_constraint);
578	}
579
580	#[cfg(test)]
581	fn add_mul_constraint(&mut self, mul_constraint: MulConstraint) {
582		self.mul_constraints.push(mul_constraint);
583	}
584
585	/// Returns the number of AND constraints in the system.
586	pub fn n_and_constraints(&self) -> usize {
587		self.and_constraints.len()
588	}
589
590	/// Returns the number of MUL  constraints in the system.
591	pub fn n_mul_constraints(&self) -> usize {
592		self.mul_constraints.len()
593	}
594
595	/// The total length of the [`ValueVec`] expected by this constraint system.
596	pub fn value_vec_len(&self) -> usize {
597		self.value_vec_layout.committed_total_len
598	}
599
600	/// Create a new [`ValueVec`] with the size expected by this constraint system.
601	pub fn new_value_vec(&self) -> ValueVec {
602		ValueVec::new(self.value_vec_layout.clone())
603	}
604}
605
606impl SerializeBytes for ConstraintSystem {
607	fn serialize(&self, mut write_buf: impl BufMut) -> Result<(), SerializationError> {
608		Self::SERIALIZATION_VERSION.serialize(&mut write_buf)?;
609
610		self.value_vec_layout.serialize(&mut write_buf)?;
611		self.constants.serialize(&mut write_buf)?;
612		self.and_constraints.serialize(&mut write_buf)?;
613		self.mul_constraints.serialize(write_buf)
614	}
615}
616
617impl DeserializeBytes for ConstraintSystem {
618	fn deserialize(mut read_buf: impl Buf) -> Result<Self, SerializationError>
619	where
620		Self: Sized,
621	{
622		let version = u32::deserialize(&mut read_buf)?;
623		if version != Self::SERIALIZATION_VERSION {
624			return Err(SerializationError::InvalidConstruction {
625				name: "ConstraintSystem::version",
626			});
627		}
628
629		let value_vec_layout = ValueVecLayout::deserialize(&mut read_buf)?;
630		let constants = Vec::<Word>::deserialize(&mut read_buf)?;
631		let and_constraints = Vec::<AndConstraint>::deserialize(&mut read_buf)?;
632		let mul_constraints = Vec::<MulConstraint>::deserialize(read_buf)?;
633
634		if constants.len() != value_vec_layout.n_const {
635			return Err(SerializationError::InvalidConstruction {
636				name: "ConstraintSystem::constants",
637			});
638		}
639
640		Ok(ConstraintSystem {
641			value_vec_layout,
642			constants,
643			and_constraints,
644			mul_constraints,
645		})
646	}
647}
648
649/// Description of a layout of the value vector for a particular circuit.
650#[derive(Clone, Debug, PartialEq, Eq)]
651pub struct ValueVecLayout {
652	/// The number of the constants declared by the circuit.
653	pub n_const: usize,
654	/// The number of the input output parameters declared by the circuit.
655	pub n_inout: usize,
656	/// The number of the witness parameters declared by the circuit.
657	pub n_witness: usize,
658	/// The number of the internal values declared by the circuit.
659	///
660	/// Those are outputs and intermediaries created by the gates.
661	pub n_internal: usize,
662
663	/// The offset at which `inout` parameters start.
664	pub offset_inout: usize,
665	/// The offset at which `witness` parameters start.
666	///
667	/// The public section of the value vec has the power-of-two size and is greater than the
668	/// minimum number of words. By public section we mean the constants and the inout values.
669	pub offset_witness: usize,
670	/// The total number of committed values in the values vector. This does not include any
671	/// scratch values.
672	///
673	/// This must be a power-of-two.
674	pub committed_total_len: usize,
675	/// The number of scratch values at the end of the value vec.
676	pub n_scratch: usize,
677}
678
679impl ValueVecLayout {
680	/// Validates that the value vec layout has a correct shape.
681	///
682	/// Specifically checks that:
683	///
684	/// - the total committed length is a power of two.
685	/// - the public segment (constants and inout values) is padded to the power of two.
686	/// - the public segment is not less than the minimum size.
687	pub fn validate(&self) -> Result<(), ConstraintSystemError> {
688		if !self.committed_total_len.is_power_of_two() {
689			return Err(ConstraintSystemError::ValueVecLenNotPowerOfTwo);
690		}
691
692		if !self.offset_witness.is_power_of_two() {
693			return Err(ConstraintSystemError::PublicInputPowerOfTwo);
694		}
695
696		let pub_input_size = self.offset_witness;
697		if pub_input_size < consts::MIN_WORDS_PER_SEGMENT {
698			return Err(ConstraintSystemError::PublicInputTooShort { pub_input_size });
699		}
700
701		Ok(())
702	}
703
704	/// Returns true if the given index points to an area that is considered to be padding.
705	fn is_padding(&self, index: ValueIndex) -> bool {
706		let idx = index.0 as usize;
707
708		// padding 1: between constants and inout section
709		if idx >= self.n_const && idx < self.offset_inout {
710			return true;
711		}
712
713		// padding 2: between the end of inout section and the start of witness section
714		let end_of_inout = self.offset_inout + self.n_inout;
715		if idx >= end_of_inout && idx < self.offset_witness {
716			return true;
717		}
718
719		// padding 3: between the last internal value and the total len
720		let end_of_internal = self.offset_witness + self.n_witness + self.n_internal;
721		if idx >= end_of_internal && idx < self.committed_total_len {
722			return true;
723		}
724
725		false
726	}
727
728	/// Returns true if the given index is out-of-bounds for the committed part of this layout.
729	fn is_committed_oob(&self, index: ValueIndex) -> bool {
730		index.0 as usize >= self.committed_total_len
731	}
732}
733
734impl SerializeBytes for ValueVecLayout {
735	fn serialize(&self, mut write_buf: impl BufMut) -> Result<(), SerializationError> {
736		self.n_const.serialize(&mut write_buf)?;
737		self.n_inout.serialize(&mut write_buf)?;
738		self.n_witness.serialize(&mut write_buf)?;
739		self.n_internal.serialize(&mut write_buf)?;
740		self.offset_inout.serialize(&mut write_buf)?;
741		self.offset_witness.serialize(&mut write_buf)?;
742		self.committed_total_len.serialize(&mut write_buf)?;
743		self.n_scratch.serialize(write_buf)
744	}
745}
746
747impl DeserializeBytes for ValueVecLayout {
748	fn deserialize(mut read_buf: impl Buf) -> Result<Self, SerializationError>
749	where
750		Self: Sized,
751	{
752		let n_const = usize::deserialize(&mut read_buf)?;
753		let n_inout = usize::deserialize(&mut read_buf)?;
754		let n_witness = usize::deserialize(&mut read_buf)?;
755		let n_internal = usize::deserialize(&mut read_buf)?;
756		let offset_inout = usize::deserialize(&mut read_buf)?;
757		let offset_witness = usize::deserialize(&mut read_buf)?;
758		let committed_total_len = usize::deserialize(&mut read_buf)?;
759		let n_scratch = usize::deserialize(read_buf)?;
760
761		Ok(ValueVecLayout {
762			n_const,
763			n_inout,
764			n_witness,
765			n_internal,
766			offset_inout,
767			offset_witness,
768			committed_total_len,
769			n_scratch,
770		})
771	}
772}
773
774/// The vector of values used in constraint evaluation and proof generation.
775///
776/// `ValueVec` is the concrete instantiation of values that satisfy (or should satisfy) a
777/// [`ConstraintSystem`]. It follows the layout defined by [`ValueVecLayout`] and serves
778/// as the primary data structure for both constraint evaluation and polynomial commitment.
779///
780/// Between these sections, there may be padding regions to satisfy alignment requirements.
781/// The total size is always a power of two as required for technical reasons.
782#[derive(Clone, Debug)]
783pub struct ValueVec {
784	layout: ValueVecLayout,
785	data: Vec<Word>,
786}
787
788impl ValueVec {
789	/// Creates a new value vector with the given layout.
790	///
791	/// The values are filled with zeros.
792	pub fn new(layout: ValueVecLayout) -> ValueVec {
793		let size = layout.committed_total_len + layout.n_scratch;
794		ValueVec {
795			layout,
796			data: vec![Word::ZERO; size],
797		}
798	}
799
800	/// Creates a new value vector with the given layout and data.
801	///
802	/// The data is checked to have the correct length.
803	pub fn new_from_data(
804		layout: ValueVecLayout,
805		public: Vec<Word>,
806		private: Vec<Word>,
807	) -> Result<ValueVec, ConstraintSystemError> {
808		let committed_len = public.len() + private.len();
809		if committed_len != layout.committed_total_len {
810			return Err(ConstraintSystemError::ValueVecLenMismatch {
811				expected: layout.committed_total_len,
812				actual: committed_len,
813			});
814		}
815
816		let full_len = layout.committed_total_len + layout.n_scratch;
817		let mut data = public;
818		data.reserve(full_len);
819		data.extend_from_slice(&private);
820		data.resize(full_len, Word::ZERO);
821
822		Ok(ValueVec { layout, data })
823	}
824
825	/// The total size of the committed portion of the vector (excluding scratch).
826	pub fn size(&self) -> usize {
827		self.layout.committed_total_len
828	}
829
830	/// Returns the value stored at the given index.
831	///
832	/// Panics if the index is out of bounds. Will happily return a value from the padding section.
833	pub fn get(&self, index: usize) -> Word {
834		self.data[index]
835	}
836
837	/// Sets the value at the given index.
838	///
839	/// Panics if the index is out of bounds. Will gladly assign a value to the padding section.
840	pub fn set(&mut self, index: usize, value: Word) {
841		self.data[index] = value;
842	}
843
844	/// Returns the public portion of the values vector.
845	pub fn public(&self) -> &[Word] {
846		&self.data[..self.layout.offset_witness]
847	}
848
849	/// Return all non-public values (witness + internal) without scratch space.
850	pub fn non_public(&self) -> &[Word] {
851		&self.data[self.layout.offset_witness..self.layout.committed_total_len]
852	}
853
854	/// Returns the witness portion of the values vector.
855	pub fn witness(&self) -> &[Word] {
856		let start = self.layout.offset_witness;
857		let end = start + self.layout.n_witness;
858		&self.data[start..end]
859	}
860
861	/// Returns the combined values vector.
862	pub fn combined_witness(&self) -> &[Word] {
863		let start = 0;
864		let end = self.layout.committed_total_len;
865		&self.data[start..end]
866	}
867}
868
869impl Index<ValueIndex> for ValueVec {
870	type Output = Word;
871
872	fn index(&self, index: ValueIndex) -> &Self::Output {
873		&self.data[index.0 as usize]
874	}
875}
876
877impl IndexMut<ValueIndex> for ValueVec {
878	fn index_mut(&mut self, index: ValueIndex) -> &mut Self::Output {
879		&mut self.data[index.0 as usize]
880	}
881}
882
883/// Values data for zero-knowledge proofs (either public witness or non-public part - private inputs
884/// and internal values).
885///
886/// It uses `Cow<[Word]>` to avoid unnecessary clones while supporting
887/// both borrowed and owned data.
888#[derive(Clone, Debug, PartialEq, Eq)]
889pub struct ValuesData<'a> {
890	data: Cow<'a, [Word]>,
891}
892
893impl<'a> ValuesData<'a> {
894	/// Serialization format version for compatibility checking
895	pub const SERIALIZATION_VERSION: u32 = 1;
896
897	/// Create a new ValuesData from borrowed data
898	pub fn borrowed(data: &'a [Word]) -> Self {
899		Self {
900			data: Cow::Borrowed(data),
901		}
902	}
903
904	/// Create a new ValuesData from owned data
905	pub fn owned(data: Vec<Word>) -> Self {
906		Self {
907			data: Cow::Owned(data),
908		}
909	}
910
911	/// Get the values data as a slice
912	pub fn as_slice(&self) -> &[Word] {
913		&self.data
914	}
915
916	/// Get the number of words in the values data
917	pub fn len(&self) -> usize {
918		self.data.len()
919	}
920
921	/// Check if the witness is empty
922	pub fn is_empty(&self) -> bool {
923		self.data.is_empty()
924	}
925
926	/// Convert to owned data, consuming self
927	pub fn into_owned(self) -> Vec<Word> {
928		self.data.into_owned()
929	}
930
931	/// Convert to owned version of ValuesData
932	pub fn to_owned(&self) -> ValuesData<'static> {
933		ValuesData {
934			data: Cow::Owned(self.data.to_vec()),
935		}
936	}
937}
938
939impl<'a> SerializeBytes for ValuesData<'a> {
940	fn serialize(&self, mut write_buf: impl BufMut) -> Result<(), SerializationError> {
941		Self::SERIALIZATION_VERSION.serialize(&mut write_buf)?;
942
943		self.data.as_ref().serialize(write_buf)
944	}
945}
946
947impl DeserializeBytes for ValuesData<'static> {
948	fn deserialize(mut read_buf: impl Buf) -> Result<Self, SerializationError>
949	where
950		Self: Sized,
951	{
952		let version = u32::deserialize(&mut read_buf)?;
953		if version != Self::SERIALIZATION_VERSION {
954			return Err(SerializationError::InvalidConstruction {
955				name: "Witness::version",
956			});
957		}
958
959		let data = Vec::<Word>::deserialize(read_buf)?;
960
961		Ok(ValuesData::owned(data))
962	}
963}
964
965impl<'a> From<&'a [Word]> for ValuesData<'a> {
966	fn from(data: &'a [Word]) -> Self {
967		ValuesData::borrowed(data)
968	}
969}
970
971impl From<Vec<Word>> for ValuesData<'static> {
972	fn from(data: Vec<Word>) -> Self {
973		ValuesData::owned(data)
974	}
975}
976
977impl<'a> AsRef<[Word]> for ValuesData<'a> {
978	fn as_ref(&self) -> &[Word] {
979		self.as_slice()
980	}
981}
982
983impl<'a> std::ops::Deref for ValuesData<'a> {
984	type Target = [Word];
985
986	fn deref(&self) -> &Self::Target {
987		self.as_slice()
988	}
989}
990
991impl<'a> From<ValuesData<'a>> for Vec<Word> {
992	fn from(value: ValuesData<'a>) -> Self {
993		value.into_owned()
994	}
995}
996
997/// A zero-knowledge proof that can be serialized for cross-host verification.
998///
999/// This structure contains the complete proof transcript generated by the prover,
1000/// along with information about the challenger type needed for verification.
1001/// The proof data represents the Fiat-Shamir transcript that can be deserialized
1002/// by the verifier to recreate the interactive protocol.
1003///
1004/// # Design
1005///
1006/// The proof contains:
1007/// - `data`: The actual proof transcript as bytes (zero-copy with Cow)
1008/// - `challenger_type`: String identifying the challenger used (e.g., `"HasherChallenger<Sha256>"`)
1009///
1010/// This enables complete cross-host verification where a proof generated on one
1011/// machine can be serialized, transmitted, and verified on another machine with
1012/// the correct challenger configuration.
1013#[derive(Clone, Debug, PartialEq, Eq)]
1014pub struct Proof<'a> {
1015	data: Cow<'a, [u8]>,
1016	challenger_type: String,
1017}
1018
1019impl<'a> Proof<'a> {
1020	/// Serialization format version for compatibility checking
1021	pub const SERIALIZATION_VERSION: u32 = 1;
1022
1023	/// Create a new Proof from borrowed transcript data
1024	pub fn borrowed(data: &'a [u8], challenger_type: String) -> Self {
1025		Self {
1026			data: Cow::Borrowed(data),
1027			challenger_type,
1028		}
1029	}
1030
1031	/// Create a new Proof from owned transcript data
1032	pub fn owned(data: Vec<u8>, challenger_type: String) -> Self {
1033		Self {
1034			data: Cow::Owned(data),
1035			challenger_type,
1036		}
1037	}
1038
1039	/// Get the proof transcript data as a slice
1040	pub fn as_slice(&self) -> &[u8] {
1041		&self.data
1042	}
1043
1044	/// Get the challenger type identifier
1045	pub fn challenger_type(&self) -> &str {
1046		&self.challenger_type
1047	}
1048
1049	/// Get the number of bytes in the proof transcript
1050	pub fn len(&self) -> usize {
1051		self.data.len()
1052	}
1053
1054	/// Check if the proof transcript is empty
1055	pub fn is_empty(&self) -> bool {
1056		self.data.is_empty()
1057	}
1058
1059	/// Convert to owned data, consuming self
1060	pub fn into_owned(self) -> (Vec<u8>, String) {
1061		(self.data.into_owned(), self.challenger_type)
1062	}
1063
1064	/// Convert to owned version of Proof
1065	pub fn to_owned(&self) -> Proof<'static> {
1066		Proof {
1067			data: Cow::Owned(self.data.to_vec()),
1068			challenger_type: self.challenger_type.clone(),
1069		}
1070	}
1071}
1072
1073impl<'a> SerializeBytes for Proof<'a> {
1074	fn serialize(&self, mut write_buf: impl BufMut) -> Result<(), SerializationError> {
1075		Self::SERIALIZATION_VERSION.serialize(&mut write_buf)?;
1076
1077		self.challenger_type.serialize(&mut write_buf)?;
1078
1079		self.data.as_ref().serialize(write_buf)
1080	}
1081}
1082
1083impl DeserializeBytes for Proof<'static> {
1084	fn deserialize(mut read_buf: impl Buf) -> Result<Self, SerializationError>
1085	where
1086		Self: Sized,
1087	{
1088		let version = u32::deserialize(&mut read_buf)?;
1089		if version != Self::SERIALIZATION_VERSION {
1090			return Err(SerializationError::InvalidConstruction {
1091				name: "Proof::version",
1092			});
1093		}
1094
1095		let challenger_type = String::deserialize(&mut read_buf)?;
1096		let data = Vec::<u8>::deserialize(read_buf)?;
1097
1098		Ok(Proof::owned(data, challenger_type))
1099	}
1100}
1101
1102impl<'a> From<(&'a [u8], String)> for Proof<'a> {
1103	fn from((data, challenger_type): (&'a [u8], String)) -> Self {
1104		Proof::borrowed(data, challenger_type)
1105	}
1106}
1107
1108impl From<(Vec<u8>, String)> for Proof<'static> {
1109	fn from((data, challenger_type): (Vec<u8>, String)) -> Self {
1110		Proof::owned(data, challenger_type)
1111	}
1112}
1113
1114impl<'a> AsRef<[u8]> for Proof<'a> {
1115	fn as_ref(&self) -> &[u8] {
1116		self.as_slice()
1117	}
1118}
1119
1120impl<'a> std::ops::Deref for Proof<'a> {
1121	type Target = [u8];
1122
1123	fn deref(&self) -> &Self::Target {
1124		self.as_slice()
1125	}
1126}
1127
1128#[cfg(test)]
1129mod serialization_tests {
1130	use rand::{RngCore, SeedableRng, rngs::StdRng};
1131
1132	use super::*;
1133
1134	pub(crate) fn create_test_constraint_system() -> ConstraintSystem {
1135		let constants = vec![
1136			Word::from_u64(1),
1137			Word::from_u64(42),
1138			Word::from_u64(0xDEADBEEF),
1139		];
1140
1141		let value_vec_layout = ValueVecLayout {
1142			n_const: 3,
1143			n_inout: 2,
1144			n_witness: 10,
1145			n_internal: 3,
1146			offset_inout: 4,         // Must be power of 2 and >= n_const
1147			offset_witness: 8,       // Must be power of 2 and >= offset_inout + n_inout
1148			committed_total_len: 16, // Must be power of 2 and >= offset_witness + n_witness
1149			n_scratch: 0,
1150		};
1151
1152		let and_constraints = vec![
1153			AndConstraint::plain_abc(
1154				vec![ValueIndex(0), ValueIndex(1)],
1155				vec![ValueIndex(2)],
1156				vec![ValueIndex(3), ValueIndex(4)],
1157			),
1158			AndConstraint::abc(
1159				vec![ShiftedValueIndex::sll(ValueIndex(0), 5)],
1160				vec![ShiftedValueIndex::srl(ValueIndex(1), 10)],
1161				vec![ShiftedValueIndex::sar(ValueIndex(2), 15)],
1162			),
1163		];
1164
1165		let mul_constraints = vec![MulConstraint {
1166			a: vec![ShiftedValueIndex::plain(ValueIndex(0))],
1167			b: vec![ShiftedValueIndex::plain(ValueIndex(1))],
1168			hi: vec![ShiftedValueIndex::plain(ValueIndex(2))],
1169			lo: vec![ShiftedValueIndex::plain(ValueIndex(3))],
1170		}];
1171
1172		ConstraintSystem::new(constants, value_vec_layout, and_constraints, mul_constraints)
1173	}
1174
1175	#[test]
1176	fn test_word_serialization_round_trip() {
1177		let mut rng = StdRng::seed_from_u64(0);
1178		let word = Word::from_u64(rng.next_u64());
1179
1180		let mut buf = Vec::new();
1181		word.serialize(&mut buf).unwrap();
1182
1183		let deserialized = Word::deserialize(&mut buf.as_slice()).unwrap();
1184		assert_eq!(word, deserialized);
1185	}
1186
1187	#[test]
1188	fn test_shift_variant_serialization_round_trip() {
1189		let variants = [
1190			ShiftVariant::Sll,
1191			ShiftVariant::Slr,
1192			ShiftVariant::Sar,
1193			ShiftVariant::Rotr,
1194		];
1195
1196		for variant in variants {
1197			let mut buf = Vec::new();
1198			variant.serialize(&mut buf).unwrap();
1199
1200			let deserialized = ShiftVariant::deserialize(&mut buf.as_slice()).unwrap();
1201			match (variant, deserialized) {
1202				(ShiftVariant::Sll, ShiftVariant::Sll)
1203				| (ShiftVariant::Slr, ShiftVariant::Slr)
1204				| (ShiftVariant::Sar, ShiftVariant::Sar)
1205				| (ShiftVariant::Rotr, ShiftVariant::Rotr) => {}
1206				_ => panic!("ShiftVariant round trip failed: {:?} != {:?}", variant, deserialized),
1207			}
1208		}
1209	}
1210
1211	#[test]
1212	fn test_shift_variant_unknown_variant() {
1213		// Create invalid variant index
1214		let mut buf = Vec::new();
1215		255u8.serialize(&mut buf).unwrap();
1216
1217		let result = ShiftVariant::deserialize(&mut buf.as_slice());
1218		assert!(result.is_err());
1219		match result.unwrap_err() {
1220			SerializationError::UnknownEnumVariant { name, index } => {
1221				assert_eq!(name, "ShiftVariant");
1222				assert_eq!(index, 255);
1223			}
1224			_ => panic!("Expected UnknownEnumVariant error"),
1225		}
1226	}
1227
1228	#[test]
1229	fn test_value_index_serialization_round_trip() {
1230		let value_index = ValueIndex(12345);
1231
1232		let mut buf = Vec::new();
1233		value_index.serialize(&mut buf).unwrap();
1234
1235		let deserialized = ValueIndex::deserialize(&mut buf.as_slice()).unwrap();
1236		assert_eq!(value_index, deserialized);
1237	}
1238
1239	#[test]
1240	fn test_shifted_value_index_serialization_round_trip() {
1241		let shifted_value_index = ShiftedValueIndex::srl(ValueIndex(42), 23);
1242
1243		let mut buf = Vec::new();
1244		shifted_value_index.serialize(&mut buf).unwrap();
1245
1246		let deserialized = ShiftedValueIndex::deserialize(&mut buf.as_slice()).unwrap();
1247		assert_eq!(shifted_value_index.value_index, deserialized.value_index);
1248		assert_eq!(shifted_value_index.amount, deserialized.amount);
1249		match (shifted_value_index.shift_variant, deserialized.shift_variant) {
1250			(ShiftVariant::Slr, ShiftVariant::Slr) => {}
1251			_ => panic!("ShiftVariant mismatch"),
1252		}
1253	}
1254
1255	#[test]
1256	fn test_shifted_value_index_invalid_amount() {
1257		// Create a buffer with invalid shift amount (>= 64)
1258		let mut buf = Vec::new();
1259		ValueIndex(0).serialize(&mut buf).unwrap();
1260		ShiftVariant::Sll.serialize(&mut buf).unwrap();
1261		64usize.serialize(&mut buf).unwrap(); // Invalid amount
1262
1263		let result = ShiftedValueIndex::deserialize(&mut buf.as_slice());
1264		assert!(result.is_err());
1265		match result.unwrap_err() {
1266			SerializationError::InvalidConstruction { name } => {
1267				assert_eq!(name, "ShiftedValueIndex::amount");
1268			}
1269			_ => panic!("Expected InvalidConstruction error"),
1270		}
1271	}
1272
1273	#[test]
1274	fn test_and_constraint_serialization_round_trip() {
1275		let constraint = AndConstraint::abc(
1276			vec![ShiftedValueIndex::sll(ValueIndex(1), 5)],
1277			vec![ShiftedValueIndex::srl(ValueIndex(2), 10)],
1278			vec![
1279				ShiftedValueIndex::sar(ValueIndex(3), 15),
1280				ShiftedValueIndex::plain(ValueIndex(4)),
1281			],
1282		);
1283
1284		let mut buf = Vec::new();
1285		constraint.serialize(&mut buf).unwrap();
1286
1287		let deserialized = AndConstraint::deserialize(&mut buf.as_slice()).unwrap();
1288		assert_eq!(constraint.a.len(), deserialized.a.len());
1289		assert_eq!(constraint.b.len(), deserialized.b.len());
1290		assert_eq!(constraint.c.len(), deserialized.c.len());
1291
1292		for (orig, deser) in constraint.a.iter().zip(deserialized.a.iter()) {
1293			assert_eq!(orig.value_index, deser.value_index);
1294			assert_eq!(orig.amount, deser.amount);
1295		}
1296	}
1297
1298	#[test]
1299	fn test_mul_constraint_serialization_round_trip() {
1300		let constraint = MulConstraint {
1301			a: vec![ShiftedValueIndex::plain(ValueIndex(0))],
1302			b: vec![ShiftedValueIndex::srl(ValueIndex(1), 32)],
1303			hi: vec![ShiftedValueIndex::plain(ValueIndex(2))],
1304			lo: vec![ShiftedValueIndex::plain(ValueIndex(3))],
1305		};
1306
1307		let mut buf = Vec::new();
1308		constraint.serialize(&mut buf).unwrap();
1309
1310		let deserialized = MulConstraint::deserialize(&mut buf.as_slice()).unwrap();
1311		assert_eq!(constraint.a.len(), deserialized.a.len());
1312		assert_eq!(constraint.b.len(), deserialized.b.len());
1313		assert_eq!(constraint.hi.len(), deserialized.hi.len());
1314		assert_eq!(constraint.lo.len(), deserialized.lo.len());
1315	}
1316
1317	#[test]
1318	fn test_value_vec_layout_serialization_round_trip() {
1319		let layout = ValueVecLayout {
1320			n_const: 5,
1321			n_inout: 3,
1322			n_witness: 12,
1323			n_internal: 7,
1324			offset_inout: 8,
1325			offset_witness: 16,
1326			committed_total_len: 32,
1327			n_scratch: 0,
1328		};
1329
1330		let mut buf = Vec::new();
1331		layout.serialize(&mut buf).unwrap();
1332
1333		let deserialized = ValueVecLayout::deserialize(&mut buf.as_slice()).unwrap();
1334		assert_eq!(layout, deserialized);
1335	}
1336
1337	#[test]
1338	fn test_constraint_system_serialization_round_trip() {
1339		let original = create_test_constraint_system();
1340
1341		let mut buf = Vec::new();
1342		original.serialize(&mut buf).unwrap();
1343
1344		let deserialized = ConstraintSystem::deserialize(&mut buf.as_slice()).unwrap();
1345
1346		// Check version
1347		assert_eq!(ConstraintSystem::SERIALIZATION_VERSION, 2);
1348
1349		// Check value_vec_layout
1350		assert_eq!(original.value_vec_layout, deserialized.value_vec_layout);
1351
1352		// Check constants
1353		assert_eq!(original.constants.len(), deserialized.constants.len());
1354		for (orig, deser) in original.constants.iter().zip(deserialized.constants.iter()) {
1355			assert_eq!(orig, deser);
1356		}
1357
1358		// Check and_constraints
1359		assert_eq!(original.and_constraints.len(), deserialized.and_constraints.len());
1360
1361		// Check mul_constraints
1362		assert_eq!(original.mul_constraints.len(), deserialized.mul_constraints.len());
1363	}
1364
1365	#[test]
1366	fn test_constraint_system_version_mismatch() {
1367		// Create a buffer with wrong version
1368		let mut buf = Vec::new();
1369		999u32.serialize(&mut buf).unwrap(); // Wrong version
1370
1371		let result = ConstraintSystem::deserialize(&mut buf.as_slice());
1372		assert!(result.is_err());
1373		match result.unwrap_err() {
1374			SerializationError::InvalidConstruction { name } => {
1375				assert_eq!(name, "ConstraintSystem::version");
1376			}
1377			_ => panic!("Expected InvalidConstruction error"),
1378		}
1379	}
1380
1381	#[test]
1382	fn test_constraint_system_constants_length_mismatch() {
1383		// Create valid components but with mismatched constants length
1384		let value_vec_layout = ValueVecLayout {
1385			n_const: 5, // Expect 5 constants
1386			n_inout: 2,
1387			n_witness: 10,
1388			n_internal: 3,
1389			offset_inout: 8,
1390			offset_witness: 16,
1391			committed_total_len: 32,
1392			n_scratch: 0,
1393		};
1394
1395		let constants = vec![Word::from_u64(1), Word::from_u64(2)]; // Only 2 constants
1396		let and_constraints: Vec<AndConstraint> = vec![];
1397		let mul_constraints: Vec<MulConstraint> = vec![];
1398
1399		// Serialize components manually
1400		let mut buf = Vec::new();
1401		ConstraintSystem::SERIALIZATION_VERSION
1402			.serialize(&mut buf)
1403			.unwrap();
1404		value_vec_layout.serialize(&mut buf).unwrap();
1405		constants.serialize(&mut buf).unwrap();
1406		and_constraints.serialize(&mut buf).unwrap();
1407		mul_constraints.serialize(&mut buf).unwrap();
1408
1409		let result = ConstraintSystem::deserialize(&mut buf.as_slice());
1410		assert!(result.is_err());
1411		match result.unwrap_err() {
1412			SerializationError::InvalidConstruction { name } => {
1413				assert_eq!(name, "ConstraintSystem::constants");
1414			}
1415			_ => panic!("Expected InvalidConstruction error"),
1416		}
1417	}
1418
1419	#[test]
1420	fn test_serialization_with_different_sources() {
1421		let original = create_test_constraint_system();
1422
1423		// Test with Vec<u8> (memory buffer)
1424		let mut vec_buf = Vec::new();
1425		original.serialize(&mut vec_buf).unwrap();
1426		let deserialized1 = ConstraintSystem::deserialize(&mut vec_buf.as_slice()).unwrap();
1427		assert_eq!(original.constants.len(), deserialized1.constants.len());
1428
1429		// Test with bytes::BytesMut (another common buffer type)
1430		let mut bytes_buf = bytes::BytesMut::new();
1431		original.serialize(&mut bytes_buf).unwrap();
1432		let deserialized2 = ConstraintSystem::deserialize(bytes_buf.freeze()).unwrap();
1433		assert_eq!(original.constants.len(), deserialized2.constants.len());
1434	}
1435
1436	/// Helper function to create or update the reference binary file for version compatibility
1437	/// testing. This is not run automatically but can be used to regenerate the reference file
1438	/// when needed.
1439	#[test]
1440	#[ignore] // Use `cargo test -- --ignored create_reference_binary` to run this
1441	fn create_reference_binary_file() {
1442		let constraint_system = create_test_constraint_system();
1443
1444		// Serialize to binary data
1445		let mut buf = Vec::new();
1446		constraint_system.serialize(&mut buf).unwrap();
1447
1448		// Write to reference file.
1449		let test_data_path = std::path::Path::new("test_data/constraint_system_v2.bin");
1450
1451		// Create directory if it doesn't exist
1452		if let Some(parent) = test_data_path.parent() {
1453			std::fs::create_dir_all(parent).unwrap();
1454		}
1455
1456		std::fs::write(test_data_path, &buf).unwrap();
1457
1458		println!("Created reference binary file at: {:?}", test_data_path);
1459		println!("Binary data length: {} bytes", buf.len());
1460	}
1461
1462	/// Test deserialization from a reference binary file to ensure version compatibility.
1463	/// This test will fail if breaking changes are made without incrementing the version.
1464	#[test]
1465	fn test_deserialize_from_reference_binary_file() {
1466		// We now have v2 format with n_scratch field
1467		// The v1 file is no longer compatible, so we test with v2
1468		let binary_data = include_bytes!("../test_data/constraint_system_v2.bin");
1469
1470		let deserialized = ConstraintSystem::deserialize(&mut binary_data.as_slice()).unwrap();
1471
1472		assert_eq!(deserialized.value_vec_layout.n_const, 3);
1473		assert_eq!(deserialized.value_vec_layout.n_inout, 2);
1474		assert_eq!(deserialized.value_vec_layout.n_witness, 10);
1475		assert_eq!(deserialized.value_vec_layout.n_internal, 3);
1476		assert_eq!(deserialized.value_vec_layout.offset_inout, 4);
1477		assert_eq!(deserialized.value_vec_layout.offset_witness, 8);
1478		assert_eq!(deserialized.value_vec_layout.committed_total_len, 16);
1479		assert_eq!(deserialized.value_vec_layout.n_scratch, 0);
1480
1481		assert_eq!(deserialized.constants.len(), 3);
1482		assert_eq!(deserialized.constants[0].as_u64(), 1);
1483		assert_eq!(deserialized.constants[1].as_u64(), 42);
1484		assert_eq!(deserialized.constants[2].as_u64(), 0xDEADBEEF);
1485
1486		assert_eq!(deserialized.and_constraints.len(), 2);
1487		assert_eq!(deserialized.mul_constraints.len(), 1);
1488
1489		// Verify that the version is what we expect
1490		// This is implicitly checked during deserialization, but we can also verify
1491		// the file starts with the correct version bytes
1492		let version_bytes = &binary_data[0..4]; // First 4 bytes should be version
1493		let expected_version_bytes = 2u32.to_le_bytes(); // Version 2 in little-endian
1494		assert_eq!(
1495			version_bytes, expected_version_bytes,
1496			"Binary file version mismatch. If you made breaking changes, increment ConstraintSystem::SERIALIZATION_VERSION"
1497		);
1498	}
1499
1500	#[test]
1501	fn test_witness_serialization_round_trip_owned() {
1502		let data = vec![
1503			Word::from_u64(1),
1504			Word::from_u64(42),
1505			Word::from_u64(0xDEADBEEF),
1506			Word::from_u64(0x1234567890ABCDEF),
1507		];
1508		let witness = ValuesData::owned(data.clone());
1509
1510		let mut buf = Vec::new();
1511		witness.serialize(&mut buf).unwrap();
1512
1513		let deserialized = ValuesData::deserialize(&mut buf.as_slice()).unwrap();
1514		assert_eq!(witness, deserialized);
1515		assert_eq!(deserialized.as_slice(), data.as_slice());
1516	}
1517
1518	#[test]
1519	fn test_witness_serialization_round_trip_borrowed() {
1520		let data = vec![Word::from_u64(123), Word::from_u64(456)];
1521		let witness = ValuesData::borrowed(&data);
1522
1523		let mut buf = Vec::new();
1524		witness.serialize(&mut buf).unwrap();
1525
1526		let deserialized = ValuesData::deserialize(&mut buf.as_slice()).unwrap();
1527		assert_eq!(witness, deserialized);
1528		assert_eq!(deserialized.as_slice(), data.as_slice());
1529	}
1530
1531	#[test]
1532	fn test_witness_version_mismatch() {
1533		let mut buf = Vec::new();
1534		999u32.serialize(&mut buf).unwrap(); // Wrong version
1535		vec![Word::from_u64(1)].serialize(&mut buf).unwrap(); // Some data
1536
1537		let result = ValuesData::deserialize(&mut buf.as_slice());
1538		assert!(result.is_err());
1539		match result.unwrap_err() {
1540			SerializationError::InvalidConstruction { name } => {
1541				assert_eq!(name, "Witness::version");
1542			}
1543			_ => panic!("Expected version mismatch error"),
1544		}
1545	}
1546
1547	/// Helper function to create or update the reference binary file for Witness version
1548	/// compatibility testing.
1549	#[test]
1550	#[ignore] // Use `cargo test -- --ignored create_witness_reference_binary` to run this
1551	fn create_witness_reference_binary_file() {
1552		let data = vec![
1553			Word::from_u64(1),
1554			Word::from_u64(42),
1555			Word::from_u64(0xDEADBEEF),
1556			Word::from_u64(0x1234567890ABCDEF),
1557		];
1558		let witness = ValuesData::owned(data);
1559
1560		let mut buf = Vec::new();
1561		witness.serialize(&mut buf).unwrap();
1562
1563		let test_data_path = std::path::Path::new("verifier/core/test_data/witness_v1.bin");
1564
1565		if let Some(parent) = test_data_path.parent() {
1566			std::fs::create_dir_all(parent).unwrap();
1567		}
1568
1569		std::fs::write(test_data_path, &buf).unwrap();
1570
1571		println!("Created Witness reference binary file at: {:?}", test_data_path);
1572		println!("Binary data length: {} bytes", buf.len());
1573	}
1574
1575	/// Test deserialization from a reference binary file to ensure Witness version
1576	/// compatibility. This test will fail if breaking changes are made without incrementing the
1577	/// version.
1578	#[test]
1579	fn test_witness_deserialize_from_reference_binary_file() {
1580		let binary_data = include_bytes!("../test_data/witness_v1.bin");
1581
1582		let deserialized = ValuesData::deserialize(&mut binary_data.as_slice()).unwrap();
1583
1584		assert_eq!(deserialized.len(), 4);
1585		assert_eq!(deserialized.as_slice()[0].as_u64(), 1);
1586		assert_eq!(deserialized.as_slice()[1].as_u64(), 42);
1587		assert_eq!(deserialized.as_slice()[2].as_u64(), 0xDEADBEEF);
1588		assert_eq!(deserialized.as_slice()[3].as_u64(), 0x1234567890ABCDEF);
1589
1590		// Verify that the version is what we expect
1591		// This is implicitly checked during deserialization, but we can also verify
1592		// the file starts with the correct version bytes
1593		let version_bytes = &binary_data[0..4]; // First 4 bytes should be version
1594		let expected_version_bytes = 1u32.to_le_bytes(); // Version 1 in little-endian
1595		assert_eq!(
1596			version_bytes, expected_version_bytes,
1597			"WitnessData binary file version mismatch. If you made breaking changes, increment WitnessData::SERIALIZATION_VERSION"
1598		);
1599	}
1600
1601	#[test]
1602	fn test_proof_serialization_round_trip_owned() {
1603		let transcript_data = vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0];
1604		let challenger_type = "HasherChallenger<Sha256>".to_string();
1605		let proof = Proof::owned(transcript_data.clone(), challenger_type.clone());
1606
1607		let mut buf = Vec::new();
1608		proof.serialize(&mut buf).unwrap();
1609
1610		let deserialized = Proof::deserialize(&mut buf.as_slice()).unwrap();
1611		assert_eq!(proof, deserialized);
1612		assert_eq!(deserialized.as_slice(), transcript_data.as_slice());
1613		assert_eq!(deserialized.challenger_type(), &challenger_type);
1614	}
1615
1616	#[test]
1617	fn test_proof_serialization_round_trip_borrowed() {
1618		let transcript_data = vec![0xAA, 0xBB, 0xCC, 0xDD];
1619		let challenger_type = "TestChallenger".to_string();
1620		let proof = Proof::borrowed(&transcript_data, challenger_type.clone());
1621
1622		let mut buf = Vec::new();
1623		proof.serialize(&mut buf).unwrap();
1624
1625		let deserialized = Proof::deserialize(&mut buf.as_slice()).unwrap();
1626		assert_eq!(proof, deserialized);
1627		assert_eq!(deserialized.as_slice(), transcript_data.as_slice());
1628		assert_eq!(deserialized.challenger_type(), &challenger_type);
1629	}
1630
1631	#[test]
1632	fn test_proof_empty_transcript() {
1633		let proof = Proof::owned(vec![], "EmptyProof".to_string());
1634		assert!(proof.is_empty());
1635		assert_eq!(proof.len(), 0);
1636
1637		let mut buf = Vec::new();
1638		proof.serialize(&mut buf).unwrap();
1639
1640		let deserialized = Proof::deserialize(&mut buf.as_slice()).unwrap();
1641		assert_eq!(proof, deserialized);
1642		assert!(deserialized.is_empty());
1643	}
1644
1645	#[test]
1646	fn test_proof_large_transcript() {
1647		let mut rng = StdRng::seed_from_u64(12345);
1648		let mut large_data = vec![0u8; 10000];
1649		rng.fill_bytes(&mut large_data);
1650
1651		let challenger_type = "LargeProofChallenger".to_string();
1652		let proof = Proof::owned(large_data.clone(), challenger_type.clone());
1653
1654		let mut buf = Vec::new();
1655		proof.serialize(&mut buf).unwrap();
1656
1657		let deserialized = Proof::deserialize(&mut buf.as_slice()).unwrap();
1658		assert_eq!(proof, deserialized);
1659		assert_eq!(deserialized.len(), 10000);
1660		assert_eq!(deserialized.challenger_type(), &challenger_type);
1661	}
1662
1663	#[test]
1664	fn test_proof_version_mismatch() {
1665		let mut buf = Vec::new();
1666		999u32.serialize(&mut buf).unwrap(); // Wrong version
1667		"TestChallenger".serialize(&mut buf).unwrap(); // Some challenger type
1668		vec![0xAAu8].serialize(&mut buf).unwrap(); // Some data
1669
1670		let result = Proof::deserialize(&mut buf.as_slice());
1671		assert!(result.is_err());
1672		match result.unwrap_err() {
1673			SerializationError::InvalidConstruction { name } => {
1674				assert_eq!(name, "Proof::version");
1675			}
1676			_ => panic!("Expected version mismatch error"),
1677		}
1678	}
1679
1680	#[test]
1681	fn test_proof_into_owned() {
1682		let original_data = vec![1, 2, 3, 4, 5];
1683		let original_challenger = "TestChallenger".to_string();
1684		let proof = Proof::owned(original_data.clone(), original_challenger.clone());
1685
1686		let (data, challenger_type) = proof.into_owned();
1687		assert_eq!(data, original_data);
1688		assert_eq!(challenger_type, original_challenger);
1689	}
1690
1691	#[test]
1692	fn test_proof_to_owned() {
1693		let data = vec![0xFF, 0xEE, 0xDD];
1694		let challenger_type = "BorrowedChallenger".to_string();
1695		let borrowed_proof = Proof::borrowed(&data, challenger_type.clone());
1696
1697		let owned_proof = borrowed_proof.to_owned();
1698		assert_eq!(owned_proof.as_slice(), data);
1699		assert_eq!(owned_proof.challenger_type(), &challenger_type);
1700		// Verify it's truly owned (not just borrowed)
1701		drop(data); // This would fail if owned_proof was still borrowing
1702		assert_eq!(owned_proof.len(), 3);
1703	}
1704
1705	#[test]
1706	fn test_proof_different_challenger_types() {
1707		let data = vec![0x42];
1708		let challengers = vec![
1709			"HasherChallenger<Sha256>".to_string(),
1710			"HasherChallenger<Blake2b>".to_string(),
1711			"CustomChallenger".to_string(),
1712			"".to_string(), // Empty string should also work
1713		];
1714
1715		for challenger_type in challengers {
1716			let proof = Proof::owned(data.clone(), challenger_type.clone());
1717			let mut buf = Vec::new();
1718			proof.serialize(&mut buf).unwrap();
1719
1720			let deserialized = Proof::deserialize(&mut buf.as_slice()).unwrap();
1721			assert_eq!(deserialized.challenger_type(), &challenger_type);
1722		}
1723	}
1724
1725	#[test]
1726	fn test_proof_serialization_with_different_sources() {
1727		let transcript_data = vec![0x11, 0x22, 0x33, 0x44];
1728		let challenger_type = "MultiSourceChallenger".to_string();
1729		let original = Proof::owned(transcript_data, challenger_type);
1730
1731		// Test with Vec<u8> (memory buffer)
1732		let mut vec_buf = Vec::new();
1733		original.serialize(&mut vec_buf).unwrap();
1734		let deserialized1 = Proof::deserialize(&mut vec_buf.as_slice()).unwrap();
1735		assert_eq!(original, deserialized1);
1736
1737		// Test with bytes::BytesMut (another common buffer type)
1738		let mut bytes_buf = bytes::BytesMut::new();
1739		original.serialize(&mut bytes_buf).unwrap();
1740		let deserialized2 = Proof::deserialize(bytes_buf.freeze()).unwrap();
1741		assert_eq!(original, deserialized2);
1742	}
1743
1744	/// Helper function to create or update the reference binary file for Proof version
1745	/// compatibility testing.
1746	#[test]
1747	#[ignore] // Use `cargo test -- --ignored create_proof_reference_binary` to run this
1748	fn create_proof_reference_binary_file() {
1749		let transcript_data = vec![
1750			0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54,
1751			0x32, 0x10, 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE,
1752		];
1753		let challenger_type = "HasherChallenger<Sha256>".to_string();
1754		let proof = Proof::owned(transcript_data, challenger_type);
1755
1756		let mut buf = Vec::new();
1757		proof.serialize(&mut buf).unwrap();
1758
1759		let test_data_path = std::path::Path::new("verifier/core/test_data/proof_v1.bin");
1760
1761		if let Some(parent) = test_data_path.parent() {
1762			std::fs::create_dir_all(parent).unwrap();
1763		}
1764
1765		std::fs::write(test_data_path, &buf).unwrap();
1766
1767		println!("Created Proof reference binary file at: {:?}", test_data_path);
1768		println!("Binary data length: {} bytes", buf.len());
1769	}
1770
1771	/// Test deserialization from a reference binary file to ensure Proof version
1772	/// compatibility. This test will fail if breaking changes are made without incrementing the
1773	/// version.
1774	#[test]
1775	fn test_proof_deserialize_from_reference_binary_file() {
1776		let binary_data = include_bytes!("../test_data/proof_v1.bin");
1777
1778		let deserialized = Proof::deserialize(&mut binary_data.as_slice()).unwrap();
1779
1780		assert_eq!(deserialized.len(), 24); // 24 bytes of transcript data
1781		assert_eq!(deserialized.challenger_type(), "HasherChallenger<Sha256>");
1782
1783		let expected_data = vec![
1784			0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54,
1785			0x32, 0x10, 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE,
1786		];
1787		assert_eq!(deserialized.as_slice(), expected_data);
1788
1789		// Verify that the version is what we expect
1790		// This is implicitly checked during deserialization, but we can also verify
1791		// the file starts with the correct version bytes
1792		let version_bytes = &binary_data[0..4]; // First 4 bytes should be version
1793		let expected_version_bytes = 1u32.to_le_bytes(); // Version 1 in little-endian
1794		assert_eq!(
1795			version_bytes, expected_version_bytes,
1796			"Proof binary file version mismatch. If you made breaking changes, increment Proof::SERIALIZATION_VERSION"
1797		);
1798	}
1799
1800	#[test]
1801	fn split_values_vec_and_combine() {
1802		let values = ValueVec::new(ValueVecLayout {
1803			n_const: 2,
1804			n_inout: 2,
1805			n_witness: 2,
1806			n_internal: 2,
1807			offset_inout: 2,
1808			offset_witness: 4,
1809			committed_total_len: 8,
1810			n_scratch: 0,
1811		});
1812
1813		let public = values.public();
1814		let non_public = values.non_public();
1815		let combined =
1816			ValueVec::new_from_data(values.layout.clone(), public.to_vec(), non_public.to_vec())
1817				.unwrap();
1818		assert_eq!(combined.combined_witness(), values.combined_witness());
1819	}
1820
1821	#[test]
1822	fn test_roundtrip_cs_and_witnesses_reconstruct_valuevec_with_scratch() {
1823		// Layout with non-zero scratch. Public = 8, total committed = 16, scratch = 5
1824		let layout = ValueVecLayout {
1825			n_const: 2,
1826			n_inout: 3,
1827			n_witness: 4,
1828			n_internal: 3,
1829			offset_inout: 4,   // >= n_const and power of two
1830			offset_witness: 8, // >= offset_inout + n_inout and power of two
1831			committed_total_len: 16,
1832			n_scratch: 5, // non-zero scratch
1833		};
1834
1835		let constants = vec![Word::from_u64(11), Word::from_u64(22)];
1836		let cs = ConstraintSystem::new(constants, layout.clone(), vec![], vec![]);
1837
1838		// Build a ValueVec and fill both committed and scratch with non-zero data
1839		let mut values = cs.new_value_vec();
1840		let full_len = layout.committed_total_len + layout.n_scratch;
1841		for i in 0..full_len {
1842			// Deterministic pattern
1843			let val = Word::from_u64(0xA5A5_5A5A ^ (i as u64 * 0x9E37_79B9));
1844			values.set(i, val);
1845		}
1846
1847		// Split into public and non-public witnesses and serialize all artifacts
1848		let public_data = ValuesData::from(values.public());
1849		let non_public_data = ValuesData::from(values.non_public());
1850
1851		let mut buf_cs = Vec::new();
1852		cs.serialize(&mut buf_cs).unwrap();
1853
1854		let mut buf_pub = Vec::new();
1855		public_data.serialize(&mut buf_pub).unwrap();
1856
1857		let mut buf_non_pub = Vec::new();
1858		non_public_data.serialize(&mut buf_non_pub).unwrap();
1859
1860		// Deserialize everything back
1861		let cs2 = ConstraintSystem::deserialize(&mut buf_cs.as_slice()).unwrap();
1862		let pub2 = ValuesData::deserialize(&mut buf_pub.as_slice()).unwrap();
1863		let non_pub2 = ValuesData::deserialize(&mut buf_non_pub.as_slice()).unwrap();
1864
1865		// Reconstruct ValueVec from deserialized pieces
1866		let reconstructed = ValueVec::new_from_data(
1867			cs2.value_vec_layout.clone(),
1868			pub2.into_owned(),
1869			non_pub2.into_owned(),
1870		)
1871		.unwrap();
1872
1873		// Ensure committed part matches exactly
1874		assert_eq!(reconstructed.combined_witness(), values.combined_witness());
1875
1876		// Scratch is not serialized; reconstructed scratch should be zero-filled
1877		let scratch_start = layout.committed_total_len;
1878		let scratch_end = scratch_start + layout.n_scratch;
1879		for i in scratch_start..scratch_end {
1880			assert_eq!(reconstructed.get(i), Word::ZERO, "scratch index {i} should be zero");
1881		}
1882	}
1883
1884	#[test]
1885	fn test_is_padding_comprehensive() {
1886		// Test layout with all types of padding
1887		let layout = ValueVecLayout {
1888			n_const: 2,              // constants at indices 0-1
1889			n_inout: 3,              // inout at indices 4-6
1890			n_witness: 5,            // witness at indices 16-20
1891			n_internal: 10,          // internal at indices 21-30
1892			offset_inout: 4,         // gap between constants and inout (indices 2-3 are padding)
1893			offset_witness: 16,      // public section is 16 (power of 2), gap 7-15 is padding
1894			committed_total_len: 64, // total must be power of 2, gap 31-63 is padding
1895			n_scratch: 0,
1896		};
1897
1898		// Test constants (indices 0-1): NOT padding
1899		assert!(!layout.is_padding(ValueIndex(0)), "index 0 should be constant");
1900		assert!(!layout.is_padding(ValueIndex(1)), "index 1 should be constant");
1901
1902		// Test padding between constants and inout (indices 2-3): PADDING
1903		assert!(
1904			layout.is_padding(ValueIndex(2)),
1905			"index 2 should be padding between const and inout"
1906		);
1907		assert!(
1908			layout.is_padding(ValueIndex(3)),
1909			"index 3 should be padding between const and inout"
1910		);
1911
1912		// Test inout values (indices 4-6): NOT padding
1913		assert!(!layout.is_padding(ValueIndex(4)), "index 4 should be inout");
1914		assert!(!layout.is_padding(ValueIndex(5)), "index 5 should be inout");
1915		assert!(!layout.is_padding(ValueIndex(6)), "index 6 should be inout");
1916
1917		// Test padding between inout and witness (indices 7-15): PADDING
1918		for i in 7..16 {
1919			assert!(
1920				layout.is_padding(ValueIndex(i)),
1921				"index {} should be padding between inout and witness",
1922				i
1923			);
1924		}
1925
1926		// Test witness values (indices 16-20): NOT padding
1927		for i in 16..21 {
1928			assert!(!layout.is_padding(ValueIndex(i)), "index {} should be witness", i);
1929		}
1930
1931		// Test internal values (indices 21-30): NOT padding
1932		for i in 21..31 {
1933			assert!(!layout.is_padding(ValueIndex(i)), "index {} should be internal", i);
1934		}
1935
1936		// Test padding after internal values (indices 31-63): PADDING
1937		for i in 31..64 {
1938			assert!(
1939				layout.is_padding(ValueIndex(i)),
1940				"index {} should be padding after internal",
1941				i
1942			);
1943		}
1944	}
1945
1946	#[test]
1947	fn test_is_padding_minimal_layout() {
1948		// Test a minimal layout with no gaps except required end padding
1949		let layout = ValueVecLayout {
1950			n_const: 4,              // constants at indices 0-3
1951			n_inout: 4,              // inout at indices 4-7
1952			n_witness: 4,            // witness at indices 8-11
1953			n_internal: 4,           // internal at indices 12-15
1954			offset_inout: 4,         // no gap between constants and inout
1955			offset_witness: 8,       // no gap between inout and witness
1956			committed_total_len: 16, // exactly fits all values
1957			n_scratch: 0,
1958		};
1959
1960		// No padding anywhere in this layout
1961		for i in 0..16 {
1962			assert!(
1963				!layout.is_padding(ValueIndex(i)),
1964				"index {} should not be padding in minimal layout",
1965				i
1966			);
1967		}
1968	}
1969
1970	#[test]
1971	fn test_is_padding_public_section_min_size() {
1972		// Test layout where public section must be padded to meet MIN_WORDS_PER_SEGMENT
1973		let layout = ValueVecLayout {
1974			n_const: 1,              // only 1 constant
1975			n_inout: 1,              // only 1 inout
1976			n_witness: 2,            // 2 witness values
1977			n_internal: 2,           // 2 internal values
1978			offset_inout: 4,         // padding between const and inout to reach min size
1979			offset_witness: 8,       // public section padded to 8 (MIN_WORDS_PER_SEGMENT)
1980			committed_total_len: 16, // power of 2
1981			n_scratch: 0,
1982		};
1983
1984		// Test the single constant
1985		assert!(!layout.is_padding(ValueIndex(0)), "index 0 should be constant");
1986
1987		// Test padding between constant and inout (indices 1-3)
1988		assert!(layout.is_padding(ValueIndex(1)), "index 1 should be padding");
1989		assert!(layout.is_padding(ValueIndex(2)), "index 2 should be padding");
1990		assert!(layout.is_padding(ValueIndex(3)), "index 3 should be padding");
1991
1992		// Test the single inout value
1993		assert!(!layout.is_padding(ValueIndex(4)), "index 4 should be inout");
1994
1995		// Test padding between inout and witness (indices 5-7)
1996		assert!(layout.is_padding(ValueIndex(5)), "index 5 should be padding");
1997		assert!(layout.is_padding(ValueIndex(6)), "index 6 should be padding");
1998		assert!(layout.is_padding(ValueIndex(7)), "index 7 should be padding");
1999
2000		// Test witness values (indices 8-9)
2001		assert!(!layout.is_padding(ValueIndex(8)), "index 8 should be witness");
2002		assert!(!layout.is_padding(ValueIndex(9)), "index 9 should be witness");
2003
2004		// Test internal values (indices 10-11)
2005		assert!(!layout.is_padding(ValueIndex(10)), "index 10 should be internal");
2006		assert!(!layout.is_padding(ValueIndex(11)), "index 11 should be internal");
2007
2008		// Test padding at the end (indices 12-15)
2009		for i in 12..16 {
2010			assert!(layout.is_padding(ValueIndex(i)), "index {} should be end padding", i);
2011		}
2012	}
2013
2014	#[test]
2015	fn test_is_padding_boundary_conditions() {
2016		let layout = ValueVecLayout {
2017			n_const: 2,
2018			n_inout: 2,
2019			n_witness: 4,
2020			n_internal: 4,
2021			offset_inout: 4,
2022			offset_witness: 8,
2023			committed_total_len: 16,
2024			n_scratch: 0,
2025		};
2026
2027		// Test exact boundaries
2028		assert!(!layout.is_padding(ValueIndex(1)), "last constant should not be padding");
2029		assert!(layout.is_padding(ValueIndex(2)), "first padding after const should be padding");
2030
2031		assert!(layout.is_padding(ValueIndex(3)), "last padding before inout should be padding");
2032		assert!(!layout.is_padding(ValueIndex(4)), "first inout should not be padding");
2033
2034		assert!(!layout.is_padding(ValueIndex(5)), "last inout should not be padding");
2035		assert!(layout.is_padding(ValueIndex(6)), "first padding after inout should be padding");
2036
2037		assert!(layout.is_padding(ValueIndex(7)), "last padding before witness should be padding");
2038		assert!(!layout.is_padding(ValueIndex(8)), "first witness should not be padding");
2039
2040		assert!(!layout.is_padding(ValueIndex(11)), "last witness should not be padding");
2041		assert!(!layout.is_padding(ValueIndex(12)), "first internal should not be padding");
2042
2043		assert!(!layout.is_padding(ValueIndex(15)), "last internal should not be padding");
2044		// Note: index 16 would be out of bounds, not tested here
2045	}
2046
2047	#[test]
2048	fn test_validate_rejects_padding_references() {
2049		let mut cs = ConstraintSystem::new(
2050			vec![Word::from_u64(1)],
2051			ValueVecLayout {
2052				n_const: 1,
2053				n_inout: 1,
2054				n_witness: 2,
2055				n_internal: 2,
2056				offset_inout: 4,
2057				offset_witness: 8,
2058				committed_total_len: 16,
2059				n_scratch: 0,
2060			},
2061			vec![],
2062			vec![],
2063		);
2064
2065		// Add constraint that references padding (index 2 is padding between const and inout)
2066		cs.add_and_constraint(AndConstraint::plain_abc(
2067			vec![ValueIndex(0)], // valid constant
2068			vec![ValueIndex(2)], // PADDING!
2069			vec![ValueIndex(8)], // valid witness
2070		));
2071
2072		let result = cs.validate_and_prepare();
2073		assert!(result.is_err(), "Should reject constraint referencing padding");
2074
2075		match result.unwrap_err() {
2076			ConstraintSystemError::PaddingValueIndex {
2077				constraint_type, ..
2078			} => {
2079				assert_eq!(constraint_type, "and");
2080			}
2081			other => panic!("Expected PaddingValueIndex error, got: {:?}", other),
2082		}
2083	}
2084
2085	#[test]
2086	fn test_validate_accepts_non_padding_references() {
2087		let mut cs = ConstraintSystem::new(
2088			vec![Word::from_u64(1), Word::from_u64(2)],
2089			ValueVecLayout {
2090				n_const: 2,
2091				n_inout: 2,
2092				n_witness: 4,
2093				n_internal: 4,
2094				offset_inout: 2,
2095				offset_witness: 4,
2096				committed_total_len: 16,
2097				n_scratch: 0,
2098			},
2099			vec![],
2100			vec![],
2101		);
2102
2103		// Add constraint that only references valid non-padding indices
2104		cs.add_and_constraint(AndConstraint::plain_abc(
2105			vec![ValueIndex(0), ValueIndex(1)], // constants
2106			vec![ValueIndex(2), ValueIndex(3)], // inout
2107			vec![ValueIndex(4), ValueIndex(5)], // witness
2108		));
2109
2110		cs.add_mul_constraint(MulConstraint {
2111			a: vec![ShiftedValueIndex::plain(ValueIndex(6))], // witness
2112			b: vec![ShiftedValueIndex::plain(ValueIndex(7))], // witness
2113			hi: vec![ShiftedValueIndex::plain(ValueIndex(8))], // internal
2114			lo: vec![ShiftedValueIndex::plain(ValueIndex(9))], // internal
2115		});
2116
2117		let result = cs.validate_and_prepare();
2118		assert!(
2119			result.is_ok(),
2120			"Should accept constraints with only valid references: {:?}",
2121			result
2122		);
2123	}
2124
2125	#[test]
2126	fn test_is_padding_matches_compiler_requirements() {
2127		// Test that is_padding correctly handles the MIN_WORDS_PER_SEGMENT requirement
2128		// as seen in the compiler mod.rs:
2129		// cur_index = cur_index.max(MIN_WORDS_PER_SEGMENT as u32);
2130		// cur_index = cur_index.next_power_of_two();
2131
2132		// Case 1: Very small public section (1 const + 1 inout = 2 total)
2133		// Should be padded to MIN_WORDS_PER_SEGMENT (8)
2134		let layout1 = ValueVecLayout {
2135			n_const: 1,
2136			n_inout: 1,
2137			n_witness: 4,
2138			n_internal: 4,
2139			offset_inout: 1,   // right after constants
2140			offset_witness: 8, // padded to MIN_WORDS_PER_SEGMENT
2141			committed_total_len: 16,
2142			n_scratch: 0,
2143		};
2144
2145		// Verify padding between end of inout (index 2) and offset_witness (8)
2146		assert!(!layout1.is_padding(ValueIndex(0)), "const should not be padding");
2147		assert!(!layout1.is_padding(ValueIndex(1)), "inout should not be padding");
2148		for i in 2..8 {
2149			assert!(
2150				layout1.is_padding(ValueIndex(i)),
2151				"index {} should be padding to meet MIN_WORDS_PER_SEGMENT",
2152				i
2153			);
2154		}
2155
2156		// Case 2: Public section exactly MIN_WORDS_PER_SEGMENT (no extra padding needed)
2157		let layout2 = ValueVecLayout {
2158			n_const: 4,
2159			n_inout: 4,
2160			n_witness: 8,
2161			n_internal: 0,
2162			offset_inout: 4,
2163			offset_witness: 8, // exactly MIN_WORDS_PER_SEGMENT, already power of 2
2164			committed_total_len: 16,
2165			n_scratch: 0,
2166		};
2167
2168		// No padding in public section
2169		for i in 0..8 {
2170			assert!(!layout2.is_padding(ValueIndex(i)), "index {} should not be padding", i);
2171		}
2172
2173		// Case 3: Public section between MIN_WORDS_PER_SEGMENT and next power of 2
2174		// e.g., 10 total needs to round up to 16
2175		let layout3 = ValueVecLayout {
2176			n_const: 5,
2177			n_inout: 5,
2178			n_witness: 16,
2179			n_internal: 0,
2180			offset_inout: 5,
2181			offset_witness: 16, // rounded up from 10 to 16 (next power of 2)
2182			committed_total_len: 32,
2183			n_scratch: 0,
2184		};
2185
2186		// Check padding from end of inout (10) to offset_witness (16)
2187		for i in 0..5 {
2188			assert!(!layout3.is_padding(ValueIndex(i)), "const {} should not be padding", i);
2189		}
2190		for i in 5..10 {
2191			assert!(!layout3.is_padding(ValueIndex(i)), "inout {} should not be padding", i);
2192		}
2193		for i in 10..16 {
2194			assert!(
2195				layout3.is_padding(ValueIndex(i)),
2196				"index {} should be padding for power-of-2 alignment",
2197				i
2198			);
2199		}
2200
2201		// Case 4: Test with offsets that show all three padding types
2202		let layout4 = ValueVecLayout {
2203			n_const: 2,              // indices 0-1
2204			n_inout: 2,              // indices 8-9
2205			n_witness: 4,            // indices 16-19
2206			n_internal: 4,           // indices 20-23
2207			offset_inout: 8,         // padding after constants to align
2208			offset_witness: 16,      // padding after inout to reach power of 2
2209			committed_total_len: 32, // padding after internal to reach total
2210			n_scratch: 0,
2211		};
2212
2213		// Constants
2214		assert!(!layout4.is_padding(ValueIndex(0)));
2215		assert!(!layout4.is_padding(ValueIndex(1)));
2216
2217		// Padding between constants and inout (indices 2-7)
2218		for i in 2..8 {
2219			assert!(layout4.is_padding(ValueIndex(i)), "padding between const and inout at {}", i);
2220		}
2221
2222		// Inout values
2223		assert!(!layout4.is_padding(ValueIndex(8)));
2224		assert!(!layout4.is_padding(ValueIndex(9)));
2225
2226		// Padding between inout and witness (indices 10-15)
2227		for i in 10..16 {
2228			assert!(
2229				layout4.is_padding(ValueIndex(i)),
2230				"padding between inout and witness at {}",
2231				i
2232			);
2233		}
2234
2235		// Witness values
2236		for i in 16..20 {
2237			assert!(!layout4.is_padding(ValueIndex(i)), "witness at {}", i);
2238		}
2239
2240		// Internal values
2241		for i in 20..24 {
2242			assert!(!layout4.is_padding(ValueIndex(i)), "internal at {}", i);
2243		}
2244
2245		// Padding after internal to total_len (indices 24-31)
2246		for i in 24..32 {
2247			assert!(layout4.is_padding(ValueIndex(i)), "padding after internal at {}", i);
2248		}
2249	}
2250
2251	#[test]
2252	fn test_validate_rejects_out_of_range_indices() {
2253		let mut cs = ConstraintSystem::new(
2254			vec![Word::from_u64(1)],
2255			ValueVecLayout {
2256				n_const: 1,
2257				n_inout: 1,
2258				n_witness: 2,
2259				n_internal: 2,
2260				offset_inout: 4,
2261				offset_witness: 8,
2262				committed_total_len: 16,
2263				n_scratch: 0,
2264			},
2265			vec![],
2266			vec![],
2267		);
2268
2269		// Add AND constraint that references an out-of-range index
2270		cs.add_and_constraint(AndConstraint::plain_abc(
2271			vec![ValueIndex(0)],  // valid constant
2272			vec![ValueIndex(16)], // OUT OF RANGE! (total_len is 16, so max valid index is 15)
2273			vec![ValueIndex(8)],  // valid witness
2274		));
2275
2276		let result = cs.validate_and_prepare();
2277		assert!(result.is_err(), "Should reject constraint with out-of-range index");
2278
2279		match result.unwrap_err() {
2280			ConstraintSystemError::OutOfRangeValueIndex {
2281				constraint_type,
2282				operand_name,
2283				value_index,
2284				total_len,
2285				..
2286			} => {
2287				assert_eq!(constraint_type, "and");
2288				assert_eq!(operand_name, "b");
2289				assert_eq!(value_index, 16);
2290				assert_eq!(total_len, 16);
2291			}
2292			other => panic!("Expected OutOfRangeValueIndex error, got: {:?}", other),
2293		}
2294	}
2295
2296	#[test]
2297	fn test_validate_rejects_out_of_range_in_mul_constraint() {
2298		let mut cs = ConstraintSystem::new(
2299			vec![Word::from_u64(1), Word::from_u64(2)],
2300			ValueVecLayout {
2301				n_const: 2,
2302				n_inout: 2,
2303				n_witness: 4,
2304				n_internal: 4,
2305				offset_inout: 2,
2306				offset_witness: 4,
2307				committed_total_len: 16,
2308				n_scratch: 0,
2309			},
2310			vec![],
2311			vec![],
2312		);
2313
2314		// Add MUL constraint with out-of-range index in 'hi' operand
2315		cs.add_mul_constraint(MulConstraint {
2316			a: vec![ShiftedValueIndex::plain(ValueIndex(0))], // valid
2317			b: vec![ShiftedValueIndex::plain(ValueIndex(1))], // valid
2318			hi: vec![ShiftedValueIndex::plain(ValueIndex(100))], // WAY out of range!
2319			lo: vec![ShiftedValueIndex::plain(ValueIndex(3))], // valid
2320		});
2321
2322		let result = cs.validate_and_prepare();
2323		assert!(result.is_err(), "Should reject MUL constraint with out-of-range index");
2324
2325		match result.unwrap_err() {
2326			ConstraintSystemError::OutOfRangeValueIndex {
2327				constraint_type,
2328				operand_name,
2329				value_index,
2330				total_len,
2331				..
2332			} => {
2333				assert_eq!(constraint_type, "mul");
2334				assert_eq!(operand_name, "hi");
2335				assert_eq!(value_index, 100);
2336				assert_eq!(total_len, 16);
2337			}
2338			other => panic!("Expected OutOfRangeValueIndex error, got: {:?}", other),
2339		}
2340	}
2341
2342	#[test]
2343	fn test_validate_checks_out_of_range_before_padding() {
2344		// This test verifies that out-of-range checking happens before padding checking
2345		// by using an index that is both out-of-range AND would be in a padding area if it were
2346		// valid
2347		let mut cs = ConstraintSystem::new(
2348			vec![Word::from_u64(1)],
2349			ValueVecLayout {
2350				n_const: 1,
2351				n_inout: 1,
2352				n_witness: 2,
2353				n_internal: 2,
2354				offset_inout: 4,
2355				offset_witness: 8,
2356				committed_total_len: 16,
2357				n_scratch: 0,
2358			},
2359			vec![],
2360			vec![],
2361		);
2362
2363		// Index 20 is out of range (>= 16)
2364		// If it were in range, indices 2-3 and 6-7 would be padding
2365		cs.add_and_constraint(AndConstraint::plain_abc(
2366			vec![ValueIndex(0)],
2367			vec![ValueIndex(20)], // out of range
2368			vec![ValueIndex(8)],
2369		));
2370
2371		let result = cs.validate_and_prepare();
2372		assert!(result.is_err());
2373
2374		// Should get OutOfRangeValueIndex, not PaddingValueIndex
2375		match result.unwrap_err() {
2376			ConstraintSystemError::OutOfRangeValueIndex { .. } => {
2377				// Good, out-of-range was detected first
2378			}
2379			other => panic!(
2380				"Expected OutOfRangeValueIndex to be detected before padding check, got: {:?}",
2381				other
2382			),
2383		}
2384	}
2385}