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