Skip to main content

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 = self.and_constraints.len().max(1).next_power_of_two();
563		let mul_target_size =
564			cmp::max(consts::MIN_MUL_CONSTRAINTS, self.mul_constraints.len()).next_power_of_two();
565
566		self.and_constraints
567			.resize_with(and_target_size, AndConstraint::default);
568		self.mul_constraints
569			.resize_with(mul_target_size, MulConstraint::default);
570
571		Ok(())
572	}
573
574	#[cfg(test)]
575	fn add_and_constraint(&mut self, and_constraint: AndConstraint) {
576		self.and_constraints.push(and_constraint);
577	}
578
579	#[cfg(test)]
580	fn add_mul_constraint(&mut self, mul_constraint: MulConstraint) {
581		self.mul_constraints.push(mul_constraint);
582	}
583
584	/// Returns the number of AND constraints in the system.
585	pub fn n_and_constraints(&self) -> usize {
586		self.and_constraints.len()
587	}
588
589	/// Returns the number of MUL  constraints in the system.
590	pub fn n_mul_constraints(&self) -> usize {
591		self.mul_constraints.len()
592	}
593
594	/// The total length of the [`ValueVec`] expected by this constraint system.
595	pub fn value_vec_len(&self) -> usize {
596		self.value_vec_layout.committed_total_len
597	}
598
599	/// Create a new [`ValueVec`] with the size expected by this constraint system.
600	pub fn new_value_vec(&self) -> ValueVec {
601		ValueVec::new(self.value_vec_layout.clone())
602	}
603}
604
605impl SerializeBytes for ConstraintSystem {
606	fn serialize(&self, mut write_buf: impl BufMut) -> Result<(), SerializationError> {
607		Self::SERIALIZATION_VERSION.serialize(&mut write_buf)?;
608
609		self.value_vec_layout.serialize(&mut write_buf)?;
610		self.constants.serialize(&mut write_buf)?;
611		self.and_constraints.serialize(&mut write_buf)?;
612		self.mul_constraints.serialize(write_buf)
613	}
614}
615
616impl DeserializeBytes for ConstraintSystem {
617	fn deserialize(mut read_buf: impl Buf) -> Result<Self, SerializationError>
618	where
619		Self: Sized,
620	{
621		let version = u32::deserialize(&mut read_buf)?;
622		if version != Self::SERIALIZATION_VERSION {
623			return Err(SerializationError::InvalidConstruction {
624				name: "ConstraintSystem::version",
625			});
626		}
627
628		let value_vec_layout = ValueVecLayout::deserialize(&mut read_buf)?;
629		let constants = Vec::<Word>::deserialize(&mut read_buf)?;
630		let and_constraints = Vec::<AndConstraint>::deserialize(&mut read_buf)?;
631		let mul_constraints = Vec::<MulConstraint>::deserialize(read_buf)?;
632
633		if constants.len() != value_vec_layout.n_const {
634			return Err(SerializationError::InvalidConstruction {
635				name: "ConstraintSystem::constants",
636			});
637		}
638
639		Ok(ConstraintSystem {
640			value_vec_layout,
641			constants,
642			and_constraints,
643			mul_constraints,
644		})
645	}
646}
647
648/// Description of a layout of the value vector for a particular circuit.
649#[derive(Clone, Debug, PartialEq, Eq)]
650pub struct ValueVecLayout {
651	/// The number of the constants declared by the circuit.
652	pub n_const: usize,
653	/// The number of the input output parameters declared by the circuit.
654	pub n_inout: usize,
655	/// The number of the witness parameters declared by the circuit.
656	pub n_witness: usize,
657	/// The number of the internal values declared by the circuit.
658	///
659	/// Those are outputs and intermediaries created by the gates.
660	pub n_internal: usize,
661
662	/// The offset at which `inout` parameters start.
663	pub offset_inout: usize,
664	/// The offset at which `witness` parameters start.
665	///
666	/// The public section of the value vec has the power-of-two size and is greater than the
667	/// minimum number of words. By public section we mean the constants and the inout values.
668	pub offset_witness: usize,
669	/// The total number of committed values in the values vector. This does not include any
670	/// scratch values.
671	///
672	/// This must be a power-of-two.
673	pub committed_total_len: usize,
674	/// The number of scratch values at the end of the value vec.
675	pub n_scratch: usize,
676}
677
678impl ValueVecLayout {
679	/// Validates that the value vec layout has a correct shape.
680	///
681	/// Specifically checks that:
682	///
683	/// - the total committed length is a power of two.
684	/// - the public segment (constants and inout values) is padded to the power of two.
685	/// - the public segment is not less than the minimum size.
686	pub fn validate(&self) -> Result<(), ConstraintSystemError> {
687		if !self.committed_total_len.is_power_of_two() {
688			return Err(ConstraintSystemError::ValueVecLenNotPowerOfTwo);
689		}
690
691		if !self.offset_witness.is_power_of_two() {
692			return Err(ConstraintSystemError::PublicInputPowerOfTwo);
693		}
694
695		let pub_input_size = self.offset_witness;
696		if pub_input_size < consts::MIN_WORDS_PER_SEGMENT {
697			return Err(ConstraintSystemError::PublicInputTooShort { pub_input_size });
698		}
699
700		Ok(())
701	}
702
703	/// Returns true if the given index points to an area that is considered to be padding.
704	fn is_padding(&self, index: ValueIndex) -> bool {
705		let idx = index.0 as usize;
706
707		// padding 1: between constants and inout section
708		if idx >= self.n_const && idx < self.offset_inout {
709			return true;
710		}
711
712		// padding 2: between the end of inout section and the start of witness section
713		let end_of_inout = self.offset_inout + self.n_inout;
714		if idx >= end_of_inout && idx < self.offset_witness {
715			return true;
716		}
717
718		// padding 3: between the last internal value and the total len
719		let end_of_internal = self.offset_witness + self.n_witness + self.n_internal;
720		if idx >= end_of_internal && idx < self.committed_total_len {
721			return true;
722		}
723
724		false
725	}
726
727	/// Returns true if the given index is out-of-bounds for the committed part of this layout.
728	fn is_committed_oob(&self, index: ValueIndex) -> bool {
729		index.0 as usize >= self.committed_total_len
730	}
731}
732
733impl SerializeBytes for ValueVecLayout {
734	fn serialize(&self, mut write_buf: impl BufMut) -> Result<(), SerializationError> {
735		self.n_const.serialize(&mut write_buf)?;
736		self.n_inout.serialize(&mut write_buf)?;
737		self.n_witness.serialize(&mut write_buf)?;
738		self.n_internal.serialize(&mut write_buf)?;
739		self.offset_inout.serialize(&mut write_buf)?;
740		self.offset_witness.serialize(&mut write_buf)?;
741		self.committed_total_len.serialize(&mut write_buf)?;
742		self.n_scratch.serialize(write_buf)
743	}
744}
745
746impl DeserializeBytes for ValueVecLayout {
747	fn deserialize(mut read_buf: impl Buf) -> Result<Self, SerializationError>
748	where
749		Self: Sized,
750	{
751		let n_const = usize::deserialize(&mut read_buf)?;
752		let n_inout = usize::deserialize(&mut read_buf)?;
753		let n_witness = usize::deserialize(&mut read_buf)?;
754		let n_internal = usize::deserialize(&mut read_buf)?;
755		let offset_inout = usize::deserialize(&mut read_buf)?;
756		let offset_witness = usize::deserialize(&mut read_buf)?;
757		let committed_total_len = usize::deserialize(&mut read_buf)?;
758		let n_scratch = usize::deserialize(read_buf)?;
759
760		Ok(ValueVecLayout {
761			n_const,
762			n_inout,
763			n_witness,
764			n_internal,
765			offset_inout,
766			offset_witness,
767			committed_total_len,
768			n_scratch,
769		})
770	}
771}
772
773/// The vector of values used in constraint evaluation and proof generation.
774///
775/// `ValueVec` is the concrete instantiation of values that satisfy (or should satisfy) a
776/// [`ConstraintSystem`]. It follows the layout defined by [`ValueVecLayout`] and serves
777/// as the primary data structure for both constraint evaluation and polynomial commitment.
778///
779/// Between these sections, there may be padding regions to satisfy alignment requirements.
780/// The total size is always a power of two as required for technical reasons.
781#[derive(Clone, Debug)]
782pub struct ValueVec {
783	layout: ValueVecLayout,
784	data: Vec<Word>,
785}
786
787impl ValueVec {
788	/// Creates a new value vector with the given layout.
789	///
790	/// The values are filled with zeros.
791	pub fn new(layout: ValueVecLayout) -> ValueVec {
792		let size = layout.committed_total_len + layout.n_scratch;
793		ValueVec {
794			layout,
795			data: vec![Word::ZERO; size],
796		}
797	}
798
799	/// Creates a new value vector with the given layout and data.
800	///
801	/// The data is checked to have the correct length.
802	pub fn new_from_data(
803		layout: ValueVecLayout,
804		public: Vec<Word>,
805		private: Vec<Word>,
806	) -> Result<ValueVec, ConstraintSystemError> {
807		let committed_len = public.len() + private.len();
808		if committed_len != layout.committed_total_len {
809			return Err(ConstraintSystemError::ValueVecLenMismatch {
810				expected: layout.committed_total_len,
811				actual: committed_len,
812			});
813		}
814
815		let full_len = layout.committed_total_len + layout.n_scratch;
816		let mut data = public;
817		data.reserve(full_len);
818		data.extend_from_slice(&private);
819		data.resize(full_len, Word::ZERO);
820
821		Ok(ValueVec { layout, data })
822	}
823
824	/// The total size of the committed portion of the vector (excluding scratch).
825	pub fn size(&self) -> usize {
826		self.layout.committed_total_len
827	}
828
829	/// Returns the value stored at the given index.
830	///
831	/// Panics if the index is out of bounds. Will happily return a value from the padding section.
832	pub fn get(&self, index: usize) -> Word {
833		self.data[index]
834	}
835
836	/// Sets the value at the given index.
837	///
838	/// Panics if the index is out of bounds. Will gladly assign a value to the padding section.
839	pub fn set(&mut self, index: usize, value: Word) {
840		self.data[index] = value;
841	}
842
843	/// Returns the public portion of the values vector.
844	pub fn public(&self) -> &[Word] {
845		&self.data[..self.layout.offset_witness]
846	}
847
848	/// Return all non-public values (witness + internal) without scratch space.
849	pub fn non_public(&self) -> &[Word] {
850		&self.data[self.layout.offset_witness..self.layout.committed_total_len]
851	}
852
853	/// Returns the witness portion of the values vector.
854	pub fn witness(&self) -> &[Word] {
855		let start = self.layout.offset_witness;
856		let end = start + self.layout.n_witness;
857		&self.data[start..end]
858	}
859
860	/// Returns the combined values vector.
861	pub fn combined_witness(&self) -> &[Word] {
862		let start = 0;
863		let end = self.layout.committed_total_len;
864		&self.data[start..end]
865	}
866}
867
868impl Index<ValueIndex> for ValueVec {
869	type Output = Word;
870
871	fn index(&self, index: ValueIndex) -> &Self::Output {
872		&self.data[index.0 as usize]
873	}
874}
875
876impl IndexMut<ValueIndex> for ValueVec {
877	fn index_mut(&mut self, index: ValueIndex) -> &mut Self::Output {
878		&mut self.data[index.0 as usize]
879	}
880}
881
882/// Values data for zero-knowledge proofs (either public witness or non-public part - private inputs
883/// and internal values).
884///
885/// It uses `Cow<[Word]>` to avoid unnecessary clones while supporting
886/// both borrowed and owned data.
887#[derive(Clone, Debug, PartialEq, Eq)]
888pub struct ValuesData<'a> {
889	data: Cow<'a, [Word]>,
890}
891
892impl<'a> ValuesData<'a> {
893	/// Serialization format version for compatibility checking
894	pub const SERIALIZATION_VERSION: u32 = 1;
895
896	/// Create a new ValuesData from borrowed data
897	pub fn borrowed(data: &'a [Word]) -> Self {
898		Self {
899			data: Cow::Borrowed(data),
900		}
901	}
902
903	/// Create a new ValuesData from owned data
904	pub fn owned(data: Vec<Word>) -> Self {
905		Self {
906			data: Cow::Owned(data),
907		}
908	}
909
910	/// Get the values data as a slice
911	pub fn as_slice(&self) -> &[Word] {
912		&self.data
913	}
914
915	/// Get the number of words in the values data
916	pub fn len(&self) -> usize {
917		self.data.len()
918	}
919
920	/// Check if the witness is empty
921	pub fn is_empty(&self) -> bool {
922		self.data.is_empty()
923	}
924
925	/// Convert to owned data, consuming self
926	pub fn into_owned(self) -> Vec<Word> {
927		self.data.into_owned()
928	}
929
930	/// Convert to owned version of ValuesData
931	pub fn to_owned(&self) -> ValuesData<'static> {
932		ValuesData {
933			data: Cow::Owned(self.data.to_vec()),
934		}
935	}
936}
937
938impl<'a> SerializeBytes for ValuesData<'a> {
939	fn serialize(&self, mut write_buf: impl BufMut) -> Result<(), SerializationError> {
940		Self::SERIALIZATION_VERSION.serialize(&mut write_buf)?;
941
942		self.data.as_ref().serialize(write_buf)
943	}
944}
945
946impl DeserializeBytes for ValuesData<'static> {
947	fn deserialize(mut read_buf: impl Buf) -> Result<Self, SerializationError>
948	where
949		Self: Sized,
950	{
951		let version = u32::deserialize(&mut read_buf)?;
952		if version != Self::SERIALIZATION_VERSION {
953			return Err(SerializationError::InvalidConstruction {
954				name: "Witness::version",
955			});
956		}
957
958		let data = Vec::<Word>::deserialize(read_buf)?;
959
960		Ok(ValuesData::owned(data))
961	}
962}
963
964impl<'a> From<&'a [Word]> for ValuesData<'a> {
965	fn from(data: &'a [Word]) -> Self {
966		ValuesData::borrowed(data)
967	}
968}
969
970impl From<Vec<Word>> for ValuesData<'static> {
971	fn from(data: Vec<Word>) -> Self {
972		ValuesData::owned(data)
973	}
974}
975
976impl<'a> AsRef<[Word]> for ValuesData<'a> {
977	fn as_ref(&self) -> &[Word] {
978		self.as_slice()
979	}
980}
981
982impl<'a> std::ops::Deref for ValuesData<'a> {
983	type Target = [Word];
984
985	fn deref(&self) -> &Self::Target {
986		self.as_slice()
987	}
988}
989
990impl<'a> From<ValuesData<'a>> for Vec<Word> {
991	fn from(value: ValuesData<'a>) -> Self {
992		value.into_owned()
993	}
994}
995
996/// A zero-knowledge proof that can be serialized for cross-host verification.
997///
998/// This structure contains the complete proof transcript generated by the prover,
999/// along with information about the challenger type needed for verification.
1000/// The proof data represents the Fiat-Shamir transcript that can be deserialized
1001/// by the verifier to recreate the interactive protocol.
1002///
1003/// # Design
1004///
1005/// The proof contains:
1006/// - `data`: The actual proof transcript as bytes (zero-copy with Cow)
1007/// - `challenger_type`: String identifying the challenger used (e.g., `"HasherChallenger<Sha256>"`)
1008///
1009/// This enables complete cross-host verification where a proof generated on one
1010/// machine can be serialized, transmitted, and verified on another machine with
1011/// the correct challenger configuration.
1012#[derive(Clone, Debug, PartialEq, Eq)]
1013pub struct Proof<'a> {
1014	data: Cow<'a, [u8]>,
1015	challenger_type: String,
1016}
1017
1018impl<'a> Proof<'a> {
1019	/// Serialization format version for compatibility checking
1020	pub const SERIALIZATION_VERSION: u32 = 1;
1021
1022	/// Create a new Proof from borrowed transcript data
1023	pub fn borrowed(data: &'a [u8], challenger_type: String) -> Self {
1024		Self {
1025			data: Cow::Borrowed(data),
1026			challenger_type,
1027		}
1028	}
1029
1030	/// Create a new Proof from owned transcript data
1031	pub fn owned(data: Vec<u8>, challenger_type: String) -> Self {
1032		Self {
1033			data: Cow::Owned(data),
1034			challenger_type,
1035		}
1036	}
1037
1038	/// Get the proof transcript data as a slice
1039	pub fn as_slice(&self) -> &[u8] {
1040		&self.data
1041	}
1042
1043	/// Get the challenger type identifier
1044	pub fn challenger_type(&self) -> &str {
1045		&self.challenger_type
1046	}
1047
1048	/// Get the number of bytes in the proof transcript
1049	pub fn len(&self) -> usize {
1050		self.data.len()
1051	}
1052
1053	/// Check if the proof transcript is empty
1054	pub fn is_empty(&self) -> bool {
1055		self.data.is_empty()
1056	}
1057
1058	/// Convert to owned data, consuming self
1059	pub fn into_owned(self) -> (Vec<u8>, String) {
1060		(self.data.into_owned(), self.challenger_type)
1061	}
1062
1063	/// Convert to owned version of Proof
1064	pub fn to_owned(&self) -> Proof<'static> {
1065		Proof {
1066			data: Cow::Owned(self.data.to_vec()),
1067			challenger_type: self.challenger_type.clone(),
1068		}
1069	}
1070}
1071
1072impl<'a> SerializeBytes for Proof<'a> {
1073	fn serialize(&self, mut write_buf: impl BufMut) -> Result<(), SerializationError> {
1074		Self::SERIALIZATION_VERSION.serialize(&mut write_buf)?;
1075
1076		self.challenger_type.serialize(&mut write_buf)?;
1077
1078		self.data.as_ref().serialize(write_buf)
1079	}
1080}
1081
1082impl DeserializeBytes for Proof<'static> {
1083	fn deserialize(mut read_buf: impl Buf) -> Result<Self, SerializationError>
1084	where
1085		Self: Sized,
1086	{
1087		let version = u32::deserialize(&mut read_buf)?;
1088		if version != Self::SERIALIZATION_VERSION {
1089			return Err(SerializationError::InvalidConstruction {
1090				name: "Proof::version",
1091			});
1092		}
1093
1094		let challenger_type = String::deserialize(&mut read_buf)?;
1095		let data = Vec::<u8>::deserialize(read_buf)?;
1096
1097		Ok(Proof::owned(data, challenger_type))
1098	}
1099}
1100
1101impl<'a> From<(&'a [u8], String)> for Proof<'a> {
1102	fn from((data, challenger_type): (&'a [u8], String)) -> Self {
1103		Proof::borrowed(data, challenger_type)
1104	}
1105}
1106
1107impl From<(Vec<u8>, String)> for Proof<'static> {
1108	fn from((data, challenger_type): (Vec<u8>, String)) -> Self {
1109		Proof::owned(data, challenger_type)
1110	}
1111}
1112
1113impl<'a> AsRef<[u8]> for Proof<'a> {
1114	fn as_ref(&self) -> &[u8] {
1115		self.as_slice()
1116	}
1117}
1118
1119impl<'a> std::ops::Deref for Proof<'a> {
1120	type Target = [u8];
1121
1122	fn deref(&self) -> &Self::Target {
1123		self.as_slice()
1124	}
1125}
1126
1127#[cfg(test)]
1128mod serialization_tests {
1129	use rand::{RngCore, SeedableRng, rngs::StdRng};
1130
1131	use super::*;
1132
1133	pub(crate) fn create_test_constraint_system() -> ConstraintSystem {
1134		let constants = vec![
1135			Word::from_u64(1),
1136			Word::from_u64(42),
1137			Word::from_u64(0xDEADBEEF),
1138		];
1139
1140		let value_vec_layout = ValueVecLayout {
1141			n_const: 3,
1142			n_inout: 2,
1143			n_witness: 10,
1144			n_internal: 3,
1145			offset_inout: 4,         // Must be power of 2 and >= n_const
1146			offset_witness: 8,       // Must be power of 2 and >= offset_inout + n_inout
1147			committed_total_len: 16, // Must be power of 2 and >= offset_witness + n_witness
1148			n_scratch: 0,
1149		};
1150
1151		let and_constraints = vec![
1152			AndConstraint::plain_abc(
1153				vec![ValueIndex(0), ValueIndex(1)],
1154				vec![ValueIndex(2)],
1155				vec![ValueIndex(3), ValueIndex(4)],
1156			),
1157			AndConstraint::abc(
1158				vec![ShiftedValueIndex::sll(ValueIndex(0), 5)],
1159				vec![ShiftedValueIndex::srl(ValueIndex(1), 10)],
1160				vec![ShiftedValueIndex::sar(ValueIndex(2), 15)],
1161			),
1162		];
1163
1164		let mul_constraints = vec![MulConstraint {
1165			a: vec![ShiftedValueIndex::plain(ValueIndex(0))],
1166			b: vec![ShiftedValueIndex::plain(ValueIndex(1))],
1167			hi: vec![ShiftedValueIndex::plain(ValueIndex(2))],
1168			lo: vec![ShiftedValueIndex::plain(ValueIndex(3))],
1169		}];
1170
1171		ConstraintSystem::new(constants, value_vec_layout, and_constraints, mul_constraints)
1172	}
1173
1174	#[test]
1175	fn test_word_serialization_round_trip() {
1176		let mut rng = StdRng::seed_from_u64(0);
1177		let word = Word::from_u64(rng.next_u64());
1178
1179		let mut buf = Vec::new();
1180		word.serialize(&mut buf).unwrap();
1181
1182		let deserialized = Word::deserialize(&mut buf.as_slice()).unwrap();
1183		assert_eq!(word, deserialized);
1184	}
1185
1186	#[test]
1187	fn test_shift_variant_serialization_round_trip() {
1188		let variants = [
1189			ShiftVariant::Sll,
1190			ShiftVariant::Slr,
1191			ShiftVariant::Sar,
1192			ShiftVariant::Rotr,
1193		];
1194
1195		for variant in variants {
1196			let mut buf = Vec::new();
1197			variant.serialize(&mut buf).unwrap();
1198
1199			let deserialized = ShiftVariant::deserialize(&mut buf.as_slice()).unwrap();
1200			match (variant, deserialized) {
1201				(ShiftVariant::Sll, ShiftVariant::Sll)
1202				| (ShiftVariant::Slr, ShiftVariant::Slr)
1203				| (ShiftVariant::Sar, ShiftVariant::Sar)
1204				| (ShiftVariant::Rotr, ShiftVariant::Rotr) => {}
1205				_ => panic!("ShiftVariant round trip failed: {:?} != {:?}", variant, deserialized),
1206			}
1207		}
1208	}
1209
1210	#[test]
1211	fn test_shift_variant_unknown_variant() {
1212		// Create invalid variant index
1213		let mut buf = Vec::new();
1214		255u8.serialize(&mut buf).unwrap();
1215
1216		let result = ShiftVariant::deserialize(&mut buf.as_slice());
1217		assert!(result.is_err());
1218		match result.unwrap_err() {
1219			SerializationError::UnknownEnumVariant { name, index } => {
1220				assert_eq!(name, "ShiftVariant");
1221				assert_eq!(index, 255);
1222			}
1223			_ => panic!("Expected UnknownEnumVariant error"),
1224		}
1225	}
1226
1227	#[test]
1228	fn test_value_index_serialization_round_trip() {
1229		let value_index = ValueIndex(12345);
1230
1231		let mut buf = Vec::new();
1232		value_index.serialize(&mut buf).unwrap();
1233
1234		let deserialized = ValueIndex::deserialize(&mut buf.as_slice()).unwrap();
1235		assert_eq!(value_index, deserialized);
1236	}
1237
1238	#[test]
1239	fn test_shifted_value_index_serialization_round_trip() {
1240		let shifted_value_index = ShiftedValueIndex::srl(ValueIndex(42), 23);
1241
1242		let mut buf = Vec::new();
1243		shifted_value_index.serialize(&mut buf).unwrap();
1244
1245		let deserialized = ShiftedValueIndex::deserialize(&mut buf.as_slice()).unwrap();
1246		assert_eq!(shifted_value_index.value_index, deserialized.value_index);
1247		assert_eq!(shifted_value_index.amount, deserialized.amount);
1248		match (shifted_value_index.shift_variant, deserialized.shift_variant) {
1249			(ShiftVariant::Slr, ShiftVariant::Slr) => {}
1250			_ => panic!("ShiftVariant mismatch"),
1251		}
1252	}
1253
1254	#[test]
1255	fn test_shifted_value_index_invalid_amount() {
1256		// Create a buffer with invalid shift amount (>= 64)
1257		let mut buf = Vec::new();
1258		ValueIndex(0).serialize(&mut buf).unwrap();
1259		ShiftVariant::Sll.serialize(&mut buf).unwrap();
1260		64usize.serialize(&mut buf).unwrap(); // Invalid amount
1261
1262		let result = ShiftedValueIndex::deserialize(&mut buf.as_slice());
1263		assert!(result.is_err());
1264		match result.unwrap_err() {
1265			SerializationError::InvalidConstruction { name } => {
1266				assert_eq!(name, "ShiftedValueIndex::amount");
1267			}
1268			_ => panic!("Expected InvalidConstruction error"),
1269		}
1270	}
1271
1272	#[test]
1273	fn test_and_constraint_serialization_round_trip() {
1274		let constraint = AndConstraint::abc(
1275			vec![ShiftedValueIndex::sll(ValueIndex(1), 5)],
1276			vec![ShiftedValueIndex::srl(ValueIndex(2), 10)],
1277			vec![
1278				ShiftedValueIndex::sar(ValueIndex(3), 15),
1279				ShiftedValueIndex::plain(ValueIndex(4)),
1280			],
1281		);
1282
1283		let mut buf = Vec::new();
1284		constraint.serialize(&mut buf).unwrap();
1285
1286		let deserialized = AndConstraint::deserialize(&mut buf.as_slice()).unwrap();
1287		assert_eq!(constraint.a.len(), deserialized.a.len());
1288		assert_eq!(constraint.b.len(), deserialized.b.len());
1289		assert_eq!(constraint.c.len(), deserialized.c.len());
1290
1291		for (orig, deser) in constraint.a.iter().zip(deserialized.a.iter()) {
1292			assert_eq!(orig.value_index, deser.value_index);
1293			assert_eq!(orig.amount, deser.amount);
1294		}
1295	}
1296
1297	#[test]
1298	fn test_mul_constraint_serialization_round_trip() {
1299		let constraint = MulConstraint {
1300			a: vec![ShiftedValueIndex::plain(ValueIndex(0))],
1301			b: vec![ShiftedValueIndex::srl(ValueIndex(1), 32)],
1302			hi: vec![ShiftedValueIndex::plain(ValueIndex(2))],
1303			lo: vec![ShiftedValueIndex::plain(ValueIndex(3))],
1304		};
1305
1306		let mut buf = Vec::new();
1307		constraint.serialize(&mut buf).unwrap();
1308
1309		let deserialized = MulConstraint::deserialize(&mut buf.as_slice()).unwrap();
1310		assert_eq!(constraint.a.len(), deserialized.a.len());
1311		assert_eq!(constraint.b.len(), deserialized.b.len());
1312		assert_eq!(constraint.hi.len(), deserialized.hi.len());
1313		assert_eq!(constraint.lo.len(), deserialized.lo.len());
1314	}
1315
1316	#[test]
1317	fn test_value_vec_layout_serialization_round_trip() {
1318		let layout = ValueVecLayout {
1319			n_const: 5,
1320			n_inout: 3,
1321			n_witness: 12,
1322			n_internal: 7,
1323			offset_inout: 8,
1324			offset_witness: 16,
1325			committed_total_len: 32,
1326			n_scratch: 0,
1327		};
1328
1329		let mut buf = Vec::new();
1330		layout.serialize(&mut buf).unwrap();
1331
1332		let deserialized = ValueVecLayout::deserialize(&mut buf.as_slice()).unwrap();
1333		assert_eq!(layout, deserialized);
1334	}
1335
1336	#[test]
1337	fn test_constraint_system_serialization_round_trip() {
1338		let original = create_test_constraint_system();
1339
1340		let mut buf = Vec::new();
1341		original.serialize(&mut buf).unwrap();
1342
1343		let deserialized = ConstraintSystem::deserialize(&mut buf.as_slice()).unwrap();
1344
1345		// Check version
1346		assert_eq!(ConstraintSystem::SERIALIZATION_VERSION, 2);
1347
1348		// Check value_vec_layout
1349		assert_eq!(original.value_vec_layout, deserialized.value_vec_layout);
1350
1351		// Check constants
1352		assert_eq!(original.constants.len(), deserialized.constants.len());
1353		for (orig, deser) in original.constants.iter().zip(deserialized.constants.iter()) {
1354			assert_eq!(orig, deser);
1355		}
1356
1357		// Check and_constraints
1358		assert_eq!(original.and_constraints.len(), deserialized.and_constraints.len());
1359
1360		// Check mul_constraints
1361		assert_eq!(original.mul_constraints.len(), deserialized.mul_constraints.len());
1362	}
1363
1364	#[test]
1365	fn test_constraint_system_version_mismatch() {
1366		// Create a buffer with wrong version
1367		let mut buf = Vec::new();
1368		999u32.serialize(&mut buf).unwrap(); // Wrong version
1369
1370		let result = ConstraintSystem::deserialize(&mut buf.as_slice());
1371		assert!(result.is_err());
1372		match result.unwrap_err() {
1373			SerializationError::InvalidConstruction { name } => {
1374				assert_eq!(name, "ConstraintSystem::version");
1375			}
1376			_ => panic!("Expected InvalidConstruction error"),
1377		}
1378	}
1379
1380	#[test]
1381	fn test_constraint_system_constants_length_mismatch() {
1382		// Create valid components but with mismatched constants length
1383		let value_vec_layout = ValueVecLayout {
1384			n_const: 5, // Expect 5 constants
1385			n_inout: 2,
1386			n_witness: 10,
1387			n_internal: 3,
1388			offset_inout: 8,
1389			offset_witness: 16,
1390			committed_total_len: 32,
1391			n_scratch: 0,
1392		};
1393
1394		let constants = vec![Word::from_u64(1), Word::from_u64(2)]; // Only 2 constants
1395		let and_constraints: Vec<AndConstraint> = vec![];
1396		let mul_constraints: Vec<MulConstraint> = vec![];
1397
1398		// Serialize components manually
1399		let mut buf = Vec::new();
1400		ConstraintSystem::SERIALIZATION_VERSION
1401			.serialize(&mut buf)
1402			.unwrap();
1403		value_vec_layout.serialize(&mut buf).unwrap();
1404		constants.serialize(&mut buf).unwrap();
1405		and_constraints.serialize(&mut buf).unwrap();
1406		mul_constraints.serialize(&mut buf).unwrap();
1407
1408		let result = ConstraintSystem::deserialize(&mut buf.as_slice());
1409		assert!(result.is_err());
1410		match result.unwrap_err() {
1411			SerializationError::InvalidConstruction { name } => {
1412				assert_eq!(name, "ConstraintSystem::constants");
1413			}
1414			_ => panic!("Expected InvalidConstruction error"),
1415		}
1416	}
1417
1418	#[test]
1419	fn test_serialization_with_different_sources() {
1420		let original = create_test_constraint_system();
1421
1422		// Test with Vec<u8> (memory buffer)
1423		let mut vec_buf = Vec::new();
1424		original.serialize(&mut vec_buf).unwrap();
1425		let deserialized1 = ConstraintSystem::deserialize(&mut vec_buf.as_slice()).unwrap();
1426		assert_eq!(original.constants.len(), deserialized1.constants.len());
1427
1428		// Test with bytes::BytesMut (another common buffer type)
1429		let mut bytes_buf = bytes::BytesMut::new();
1430		original.serialize(&mut bytes_buf).unwrap();
1431		let deserialized2 = ConstraintSystem::deserialize(bytes_buf.freeze()).unwrap();
1432		assert_eq!(original.constants.len(), deserialized2.constants.len());
1433	}
1434
1435	/// Helper function to create or update the reference binary file for version compatibility
1436	/// testing. This is not run automatically but can be used to regenerate the reference file
1437	/// when needed.
1438	#[test]
1439	#[ignore] // Use `cargo test -- --ignored create_reference_binary` to run this
1440	fn create_reference_binary_file() {
1441		let constraint_system = create_test_constraint_system();
1442
1443		// Serialize to binary data
1444		let mut buf = Vec::new();
1445		constraint_system.serialize(&mut buf).unwrap();
1446
1447		// Write to reference file.
1448		let test_data_path = std::path::Path::new("test_data/constraint_system_v2.bin");
1449
1450		// Create directory if it doesn't exist
1451		if let Some(parent) = test_data_path.parent() {
1452			std::fs::create_dir_all(parent).unwrap();
1453		}
1454
1455		std::fs::write(test_data_path, &buf).unwrap();
1456
1457		println!("Created reference binary file at: {:?}", test_data_path);
1458		println!("Binary data length: {} bytes", buf.len());
1459	}
1460
1461	/// Test deserialization from a reference binary file to ensure version compatibility.
1462	/// This test will fail if breaking changes are made without incrementing the version.
1463	#[test]
1464	fn test_deserialize_from_reference_binary_file() {
1465		// We now have v2 format with n_scratch field
1466		// The v1 file is no longer compatible, so we test with v2
1467		let binary_data = include_bytes!("../test_data/constraint_system_v2.bin");
1468
1469		let deserialized = ConstraintSystem::deserialize(&mut binary_data.as_slice()).unwrap();
1470
1471		assert_eq!(deserialized.value_vec_layout.n_const, 3);
1472		assert_eq!(deserialized.value_vec_layout.n_inout, 2);
1473		assert_eq!(deserialized.value_vec_layout.n_witness, 10);
1474		assert_eq!(deserialized.value_vec_layout.n_internal, 3);
1475		assert_eq!(deserialized.value_vec_layout.offset_inout, 4);
1476		assert_eq!(deserialized.value_vec_layout.offset_witness, 8);
1477		assert_eq!(deserialized.value_vec_layout.committed_total_len, 16);
1478		assert_eq!(deserialized.value_vec_layout.n_scratch, 0);
1479
1480		assert_eq!(deserialized.constants.len(), 3);
1481		assert_eq!(deserialized.constants[0].as_u64(), 1);
1482		assert_eq!(deserialized.constants[1].as_u64(), 42);
1483		assert_eq!(deserialized.constants[2].as_u64(), 0xDEADBEEF);
1484
1485		assert_eq!(deserialized.and_constraints.len(), 2);
1486		assert_eq!(deserialized.mul_constraints.len(), 1);
1487
1488		// Verify that the version is what we expect
1489		// This is implicitly checked during deserialization, but we can also verify
1490		// the file starts with the correct version bytes
1491		let version_bytes = &binary_data[0..4]; // First 4 bytes should be version
1492		let expected_version_bytes = 2u32.to_le_bytes(); // Version 2 in little-endian
1493		assert_eq!(
1494			version_bytes, expected_version_bytes,
1495			"Binary file version mismatch. If you made breaking changes, increment ConstraintSystem::SERIALIZATION_VERSION"
1496		);
1497	}
1498
1499	#[test]
1500	fn test_witness_serialization_round_trip_owned() {
1501		let data = vec![
1502			Word::from_u64(1),
1503			Word::from_u64(42),
1504			Word::from_u64(0xDEADBEEF),
1505			Word::from_u64(0x1234567890ABCDEF),
1506		];
1507		let witness = ValuesData::owned(data.clone());
1508
1509		let mut buf = Vec::new();
1510		witness.serialize(&mut buf).unwrap();
1511
1512		let deserialized = ValuesData::deserialize(&mut buf.as_slice()).unwrap();
1513		assert_eq!(witness, deserialized);
1514		assert_eq!(deserialized.as_slice(), data.as_slice());
1515	}
1516
1517	#[test]
1518	fn test_witness_serialization_round_trip_borrowed() {
1519		let data = vec![Word::from_u64(123), Word::from_u64(456)];
1520		let witness = ValuesData::borrowed(&data);
1521
1522		let mut buf = Vec::new();
1523		witness.serialize(&mut buf).unwrap();
1524
1525		let deserialized = ValuesData::deserialize(&mut buf.as_slice()).unwrap();
1526		assert_eq!(witness, deserialized);
1527		assert_eq!(deserialized.as_slice(), data.as_slice());
1528	}
1529
1530	#[test]
1531	fn test_witness_version_mismatch() {
1532		let mut buf = Vec::new();
1533		999u32.serialize(&mut buf).unwrap(); // Wrong version
1534		vec![Word::from_u64(1)].serialize(&mut buf).unwrap(); // Some data
1535
1536		let result = ValuesData::deserialize(&mut buf.as_slice());
1537		assert!(result.is_err());
1538		match result.unwrap_err() {
1539			SerializationError::InvalidConstruction { name } => {
1540				assert_eq!(name, "Witness::version");
1541			}
1542			_ => panic!("Expected version mismatch error"),
1543		}
1544	}
1545
1546	/// Helper function to create or update the reference binary file for Witness version
1547	/// compatibility testing.
1548	#[test]
1549	#[ignore] // Use `cargo test -- --ignored create_witness_reference_binary` to run this
1550	fn create_witness_reference_binary_file() {
1551		let data = vec![
1552			Word::from_u64(1),
1553			Word::from_u64(42),
1554			Word::from_u64(0xDEADBEEF),
1555			Word::from_u64(0x1234567890ABCDEF),
1556		];
1557		let witness = ValuesData::owned(data);
1558
1559		let mut buf = Vec::new();
1560		witness.serialize(&mut buf).unwrap();
1561
1562		let test_data_path = std::path::Path::new("verifier/core/test_data/witness_v1.bin");
1563
1564		if let Some(parent) = test_data_path.parent() {
1565			std::fs::create_dir_all(parent).unwrap();
1566		}
1567
1568		std::fs::write(test_data_path, &buf).unwrap();
1569
1570		println!("Created Witness reference binary file at: {:?}", test_data_path);
1571		println!("Binary data length: {} bytes", buf.len());
1572	}
1573
1574	/// Test deserialization from a reference binary file to ensure Witness version
1575	/// compatibility. This test will fail if breaking changes are made without incrementing the
1576	/// version.
1577	#[test]
1578	fn test_witness_deserialize_from_reference_binary_file() {
1579		let binary_data = include_bytes!("../test_data/witness_v1.bin");
1580
1581		let deserialized = ValuesData::deserialize(&mut binary_data.as_slice()).unwrap();
1582
1583		assert_eq!(deserialized.len(), 4);
1584		assert_eq!(deserialized.as_slice()[0].as_u64(), 1);
1585		assert_eq!(deserialized.as_slice()[1].as_u64(), 42);
1586		assert_eq!(deserialized.as_slice()[2].as_u64(), 0xDEADBEEF);
1587		assert_eq!(deserialized.as_slice()[3].as_u64(), 0x1234567890ABCDEF);
1588
1589		// Verify that the version is what we expect
1590		// This is implicitly checked during deserialization, but we can also verify
1591		// the file starts with the correct version bytes
1592		let version_bytes = &binary_data[0..4]; // First 4 bytes should be version
1593		let expected_version_bytes = 1u32.to_le_bytes(); // Version 1 in little-endian
1594		assert_eq!(
1595			version_bytes, expected_version_bytes,
1596			"WitnessData binary file version mismatch. If you made breaking changes, increment WitnessData::SERIALIZATION_VERSION"
1597		);
1598	}
1599
1600	#[test]
1601	fn test_proof_serialization_round_trip_owned() {
1602		let transcript_data = vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0];
1603		let challenger_type = "HasherChallenger<Sha256>".to_string();
1604		let proof = Proof::owned(transcript_data.clone(), challenger_type.clone());
1605
1606		let mut buf = Vec::new();
1607		proof.serialize(&mut buf).unwrap();
1608
1609		let deserialized = Proof::deserialize(&mut buf.as_slice()).unwrap();
1610		assert_eq!(proof, deserialized);
1611		assert_eq!(deserialized.as_slice(), transcript_data.as_slice());
1612		assert_eq!(deserialized.challenger_type(), &challenger_type);
1613	}
1614
1615	#[test]
1616	fn test_proof_serialization_round_trip_borrowed() {
1617		let transcript_data = vec![0xAA, 0xBB, 0xCC, 0xDD];
1618		let challenger_type = "TestChallenger".to_string();
1619		let proof = Proof::borrowed(&transcript_data, challenger_type.clone());
1620
1621		let mut buf = Vec::new();
1622		proof.serialize(&mut buf).unwrap();
1623
1624		let deserialized = Proof::deserialize(&mut buf.as_slice()).unwrap();
1625		assert_eq!(proof, deserialized);
1626		assert_eq!(deserialized.as_slice(), transcript_data.as_slice());
1627		assert_eq!(deserialized.challenger_type(), &challenger_type);
1628	}
1629
1630	#[test]
1631	fn test_proof_empty_transcript() {
1632		let proof = Proof::owned(vec![], "EmptyProof".to_string());
1633		assert!(proof.is_empty());
1634		assert_eq!(proof.len(), 0);
1635
1636		let mut buf = Vec::new();
1637		proof.serialize(&mut buf).unwrap();
1638
1639		let deserialized = Proof::deserialize(&mut buf.as_slice()).unwrap();
1640		assert_eq!(proof, deserialized);
1641		assert!(deserialized.is_empty());
1642	}
1643
1644	#[test]
1645	fn test_proof_large_transcript() {
1646		let mut rng = StdRng::seed_from_u64(12345);
1647		let mut large_data = vec![0u8; 10000];
1648		rng.fill_bytes(&mut large_data);
1649
1650		let challenger_type = "LargeProofChallenger".to_string();
1651		let proof = Proof::owned(large_data.clone(), challenger_type.clone());
1652
1653		let mut buf = Vec::new();
1654		proof.serialize(&mut buf).unwrap();
1655
1656		let deserialized = Proof::deserialize(&mut buf.as_slice()).unwrap();
1657		assert_eq!(proof, deserialized);
1658		assert_eq!(deserialized.len(), 10000);
1659		assert_eq!(deserialized.challenger_type(), &challenger_type);
1660	}
1661
1662	#[test]
1663	fn test_proof_version_mismatch() {
1664		let mut buf = Vec::new();
1665		999u32.serialize(&mut buf).unwrap(); // Wrong version
1666		"TestChallenger".serialize(&mut buf).unwrap(); // Some challenger type
1667		vec![0xAAu8].serialize(&mut buf).unwrap(); // Some data
1668
1669		let result = Proof::deserialize(&mut buf.as_slice());
1670		assert!(result.is_err());
1671		match result.unwrap_err() {
1672			SerializationError::InvalidConstruction { name } => {
1673				assert_eq!(name, "Proof::version");
1674			}
1675			_ => panic!("Expected version mismatch error"),
1676		}
1677	}
1678
1679	#[test]
1680	fn test_proof_into_owned() {
1681		let original_data = vec![1, 2, 3, 4, 5];
1682		let original_challenger = "TestChallenger".to_string();
1683		let proof = Proof::owned(original_data.clone(), original_challenger.clone());
1684
1685		let (data, challenger_type) = proof.into_owned();
1686		assert_eq!(data, original_data);
1687		assert_eq!(challenger_type, original_challenger);
1688	}
1689
1690	#[test]
1691	fn test_proof_to_owned() {
1692		let data = vec![0xFF, 0xEE, 0xDD];
1693		let challenger_type = "BorrowedChallenger".to_string();
1694		let borrowed_proof = Proof::borrowed(&data, challenger_type.clone());
1695
1696		let owned_proof = borrowed_proof.to_owned();
1697		assert_eq!(owned_proof.as_slice(), data);
1698		assert_eq!(owned_proof.challenger_type(), &challenger_type);
1699		// Verify it's truly owned (not just borrowed)
1700		drop(data); // This would fail if owned_proof was still borrowing
1701		assert_eq!(owned_proof.len(), 3);
1702	}
1703
1704	#[test]
1705	fn test_proof_different_challenger_types() {
1706		let data = vec![0x42];
1707		let challengers = vec![
1708			"HasherChallenger<Sha256>".to_string(),
1709			"HasherChallenger<Blake2b>".to_string(),
1710			"CustomChallenger".to_string(),
1711			"".to_string(), // Empty string should also work
1712		];
1713
1714		for challenger_type in challengers {
1715			let proof = Proof::owned(data.clone(), challenger_type.clone());
1716			let mut buf = Vec::new();
1717			proof.serialize(&mut buf).unwrap();
1718
1719			let deserialized = Proof::deserialize(&mut buf.as_slice()).unwrap();
1720			assert_eq!(deserialized.challenger_type(), &challenger_type);
1721		}
1722	}
1723
1724	#[test]
1725	fn test_proof_serialization_with_different_sources() {
1726		let transcript_data = vec![0x11, 0x22, 0x33, 0x44];
1727		let challenger_type = "MultiSourceChallenger".to_string();
1728		let original = Proof::owned(transcript_data, challenger_type);
1729
1730		// Test with Vec<u8> (memory buffer)
1731		let mut vec_buf = Vec::new();
1732		original.serialize(&mut vec_buf).unwrap();
1733		let deserialized1 = Proof::deserialize(&mut vec_buf.as_slice()).unwrap();
1734		assert_eq!(original, deserialized1);
1735
1736		// Test with bytes::BytesMut (another common buffer type)
1737		let mut bytes_buf = bytes::BytesMut::new();
1738		original.serialize(&mut bytes_buf).unwrap();
1739		let deserialized2 = Proof::deserialize(bytes_buf.freeze()).unwrap();
1740		assert_eq!(original, deserialized2);
1741	}
1742
1743	/// Helper function to create or update the reference binary file for Proof version
1744	/// compatibility testing.
1745	#[test]
1746	#[ignore] // Use `cargo test -- --ignored create_proof_reference_binary` to run this
1747	fn create_proof_reference_binary_file() {
1748		let transcript_data = vec![
1749			0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54,
1750			0x32, 0x10, 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE,
1751		];
1752		let challenger_type = "HasherChallenger<Sha256>".to_string();
1753		let proof = Proof::owned(transcript_data, challenger_type);
1754
1755		let mut buf = Vec::new();
1756		proof.serialize(&mut buf).unwrap();
1757
1758		let test_data_path = std::path::Path::new("verifier/core/test_data/proof_v1.bin");
1759
1760		if let Some(parent) = test_data_path.parent() {
1761			std::fs::create_dir_all(parent).unwrap();
1762		}
1763
1764		std::fs::write(test_data_path, &buf).unwrap();
1765
1766		println!("Created Proof reference binary file at: {:?}", test_data_path);
1767		println!("Binary data length: {} bytes", buf.len());
1768	}
1769
1770	/// Test deserialization from a reference binary file to ensure Proof version
1771	/// compatibility. This test will fail if breaking changes are made without incrementing the
1772	/// version.
1773	#[test]
1774	fn test_proof_deserialize_from_reference_binary_file() {
1775		let binary_data = include_bytes!("../test_data/proof_v1.bin");
1776
1777		let deserialized = Proof::deserialize(&mut binary_data.as_slice()).unwrap();
1778
1779		assert_eq!(deserialized.len(), 24); // 24 bytes of transcript data
1780		assert_eq!(deserialized.challenger_type(), "HasherChallenger<Sha256>");
1781
1782		let expected_data = vec![
1783			0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54,
1784			0x32, 0x10, 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE,
1785		];
1786		assert_eq!(deserialized.as_slice(), expected_data);
1787
1788		// Verify that the version is what we expect
1789		// This is implicitly checked during deserialization, but we can also verify
1790		// the file starts with the correct version bytes
1791		let version_bytes = &binary_data[0..4]; // First 4 bytes should be version
1792		let expected_version_bytes = 1u32.to_le_bytes(); // Version 1 in little-endian
1793		assert_eq!(
1794			version_bytes, expected_version_bytes,
1795			"Proof binary file version mismatch. If you made breaking changes, increment Proof::SERIALIZATION_VERSION"
1796		);
1797	}
1798
1799	#[test]
1800	fn split_values_vec_and_combine() {
1801		let values = ValueVec::new(ValueVecLayout {
1802			n_const: 2,
1803			n_inout: 2,
1804			n_witness: 2,
1805			n_internal: 2,
1806			offset_inout: 2,
1807			offset_witness: 4,
1808			committed_total_len: 8,
1809			n_scratch: 0,
1810		});
1811
1812		let public = values.public();
1813		let non_public = values.non_public();
1814		let combined =
1815			ValueVec::new_from_data(values.layout.clone(), public.to_vec(), non_public.to_vec())
1816				.unwrap();
1817		assert_eq!(combined.combined_witness(), values.combined_witness());
1818	}
1819
1820	#[test]
1821	fn test_roundtrip_cs_and_witnesses_reconstruct_valuevec_with_scratch() {
1822		// Layout with non-zero scratch. Public = 8, total committed = 16, scratch = 5
1823		let layout = ValueVecLayout {
1824			n_const: 2,
1825			n_inout: 3,
1826			n_witness: 4,
1827			n_internal: 3,
1828			offset_inout: 4,   // >= n_const and power of two
1829			offset_witness: 8, // >= offset_inout + n_inout and power of two
1830			committed_total_len: 16,
1831			n_scratch: 5, // non-zero scratch
1832		};
1833
1834		let constants = vec![Word::from_u64(11), Word::from_u64(22)];
1835		let cs = ConstraintSystem::new(constants, layout.clone(), vec![], vec![]);
1836
1837		// Build a ValueVec and fill both committed and scratch with non-zero data
1838		let mut values = cs.new_value_vec();
1839		let full_len = layout.committed_total_len + layout.n_scratch;
1840		for i in 0..full_len {
1841			// Deterministic pattern
1842			let val = Word::from_u64(0xA5A5_5A5A ^ (i as u64 * 0x9E37_79B9));
1843			values.set(i, val);
1844		}
1845
1846		// Split into public and non-public witnesses and serialize all artifacts
1847		let public_data = ValuesData::from(values.public());
1848		let non_public_data = ValuesData::from(values.non_public());
1849
1850		let mut buf_cs = Vec::new();
1851		cs.serialize(&mut buf_cs).unwrap();
1852
1853		let mut buf_pub = Vec::new();
1854		public_data.serialize(&mut buf_pub).unwrap();
1855
1856		let mut buf_non_pub = Vec::new();
1857		non_public_data.serialize(&mut buf_non_pub).unwrap();
1858
1859		// Deserialize everything back
1860		let cs2 = ConstraintSystem::deserialize(&mut buf_cs.as_slice()).unwrap();
1861		let pub2 = ValuesData::deserialize(&mut buf_pub.as_slice()).unwrap();
1862		let non_pub2 = ValuesData::deserialize(&mut buf_non_pub.as_slice()).unwrap();
1863
1864		// Reconstruct ValueVec from deserialized pieces
1865		let reconstructed = ValueVec::new_from_data(
1866			cs2.value_vec_layout.clone(),
1867			pub2.into_owned(),
1868			non_pub2.into_owned(),
1869		)
1870		.unwrap();
1871
1872		// Ensure committed part matches exactly
1873		assert_eq!(reconstructed.combined_witness(), values.combined_witness());
1874
1875		// Scratch is not serialized; reconstructed scratch should be zero-filled
1876		let scratch_start = layout.committed_total_len;
1877		let scratch_end = scratch_start + layout.n_scratch;
1878		for i in scratch_start..scratch_end {
1879			assert_eq!(reconstructed.get(i), Word::ZERO, "scratch index {i} should be zero");
1880		}
1881	}
1882
1883	#[test]
1884	fn test_is_padding_comprehensive() {
1885		// Test layout with all types of padding
1886		let layout = ValueVecLayout {
1887			n_const: 2,              // constants at indices 0-1
1888			n_inout: 3,              // inout at indices 4-6
1889			n_witness: 5,            // witness at indices 16-20
1890			n_internal: 10,          // internal at indices 21-30
1891			offset_inout: 4,         // gap between constants and inout (indices 2-3 are padding)
1892			offset_witness: 16,      // public section is 16 (power of 2), gap 7-15 is padding
1893			committed_total_len: 64, // total must be power of 2, gap 31-63 is padding
1894			n_scratch: 0,
1895		};
1896
1897		// Test constants (indices 0-1): NOT padding
1898		assert!(!layout.is_padding(ValueIndex(0)), "index 0 should be constant");
1899		assert!(!layout.is_padding(ValueIndex(1)), "index 1 should be constant");
1900
1901		// Test padding between constants and inout (indices 2-3): PADDING
1902		assert!(
1903			layout.is_padding(ValueIndex(2)),
1904			"index 2 should be padding between const and inout"
1905		);
1906		assert!(
1907			layout.is_padding(ValueIndex(3)),
1908			"index 3 should be padding between const and inout"
1909		);
1910
1911		// Test inout values (indices 4-6): NOT padding
1912		assert!(!layout.is_padding(ValueIndex(4)), "index 4 should be inout");
1913		assert!(!layout.is_padding(ValueIndex(5)), "index 5 should be inout");
1914		assert!(!layout.is_padding(ValueIndex(6)), "index 6 should be inout");
1915
1916		// Test padding between inout and witness (indices 7-15): PADDING
1917		for i in 7..16 {
1918			assert!(
1919				layout.is_padding(ValueIndex(i)),
1920				"index {} should be padding between inout and witness",
1921				i
1922			);
1923		}
1924
1925		// Test witness values (indices 16-20): NOT padding
1926		for i in 16..21 {
1927			assert!(!layout.is_padding(ValueIndex(i)), "index {} should be witness", i);
1928		}
1929
1930		// Test internal values (indices 21-30): NOT padding
1931		for i in 21..31 {
1932			assert!(!layout.is_padding(ValueIndex(i)), "index {} should be internal", i);
1933		}
1934
1935		// Test padding after internal values (indices 31-63): PADDING
1936		for i in 31..64 {
1937			assert!(
1938				layout.is_padding(ValueIndex(i)),
1939				"index {} should be padding after internal",
1940				i
1941			);
1942		}
1943	}
1944
1945	#[test]
1946	fn test_is_padding_minimal_layout() {
1947		// Test a minimal layout with no gaps except required end padding
1948		let layout = ValueVecLayout {
1949			n_const: 4,              // constants at indices 0-3
1950			n_inout: 4,              // inout at indices 4-7
1951			n_witness: 4,            // witness at indices 8-11
1952			n_internal: 4,           // internal at indices 12-15
1953			offset_inout: 4,         // no gap between constants and inout
1954			offset_witness: 8,       // no gap between inout and witness
1955			committed_total_len: 16, // exactly fits all values
1956			n_scratch: 0,
1957		};
1958
1959		// No padding anywhere in this layout
1960		for i in 0..16 {
1961			assert!(
1962				!layout.is_padding(ValueIndex(i)),
1963				"index {} should not be padding in minimal layout",
1964				i
1965			);
1966		}
1967	}
1968
1969	#[test]
1970	fn test_is_padding_public_section_min_size() {
1971		// Test layout where public section must be padded to meet MIN_WORDS_PER_SEGMENT
1972		let layout = ValueVecLayout {
1973			n_const: 1,              // only 1 constant
1974			n_inout: 1,              // only 1 inout
1975			n_witness: 2,            // 2 witness values
1976			n_internal: 2,           // 2 internal values
1977			offset_inout: 4,         // padding between const and inout to reach min size
1978			offset_witness: 8,       // public section padded to 8 (MIN_WORDS_PER_SEGMENT)
1979			committed_total_len: 16, // power of 2
1980			n_scratch: 0,
1981		};
1982
1983		// Test the single constant
1984		assert!(!layout.is_padding(ValueIndex(0)), "index 0 should be constant");
1985
1986		// Test padding between constant and inout (indices 1-3)
1987		assert!(layout.is_padding(ValueIndex(1)), "index 1 should be padding");
1988		assert!(layout.is_padding(ValueIndex(2)), "index 2 should be padding");
1989		assert!(layout.is_padding(ValueIndex(3)), "index 3 should be padding");
1990
1991		// Test the single inout value
1992		assert!(!layout.is_padding(ValueIndex(4)), "index 4 should be inout");
1993
1994		// Test padding between inout and witness (indices 5-7)
1995		assert!(layout.is_padding(ValueIndex(5)), "index 5 should be padding");
1996		assert!(layout.is_padding(ValueIndex(6)), "index 6 should be padding");
1997		assert!(layout.is_padding(ValueIndex(7)), "index 7 should be padding");
1998
1999		// Test witness values (indices 8-9)
2000		assert!(!layout.is_padding(ValueIndex(8)), "index 8 should be witness");
2001		assert!(!layout.is_padding(ValueIndex(9)), "index 9 should be witness");
2002
2003		// Test internal values (indices 10-11)
2004		assert!(!layout.is_padding(ValueIndex(10)), "index 10 should be internal");
2005		assert!(!layout.is_padding(ValueIndex(11)), "index 11 should be internal");
2006
2007		// Test padding at the end (indices 12-15)
2008		for i in 12..16 {
2009			assert!(layout.is_padding(ValueIndex(i)), "index {} should be end padding", i);
2010		}
2011	}
2012
2013	#[test]
2014	fn test_is_padding_boundary_conditions() {
2015		let layout = ValueVecLayout {
2016			n_const: 2,
2017			n_inout: 2,
2018			n_witness: 4,
2019			n_internal: 4,
2020			offset_inout: 4,
2021			offset_witness: 8,
2022			committed_total_len: 16,
2023			n_scratch: 0,
2024		};
2025
2026		// Test exact boundaries
2027		assert!(!layout.is_padding(ValueIndex(1)), "last constant should not be padding");
2028		assert!(layout.is_padding(ValueIndex(2)), "first padding after const should be padding");
2029
2030		assert!(layout.is_padding(ValueIndex(3)), "last padding before inout should be padding");
2031		assert!(!layout.is_padding(ValueIndex(4)), "first inout should not be padding");
2032
2033		assert!(!layout.is_padding(ValueIndex(5)), "last inout should not be padding");
2034		assert!(layout.is_padding(ValueIndex(6)), "first padding after inout should be padding");
2035
2036		assert!(layout.is_padding(ValueIndex(7)), "last padding before witness should be padding");
2037		assert!(!layout.is_padding(ValueIndex(8)), "first witness should not be padding");
2038
2039		assert!(!layout.is_padding(ValueIndex(11)), "last witness should not be padding");
2040		assert!(!layout.is_padding(ValueIndex(12)), "first internal should not be padding");
2041
2042		assert!(!layout.is_padding(ValueIndex(15)), "last internal should not be padding");
2043		// Note: index 16 would be out of bounds, not tested here
2044	}
2045
2046	#[test]
2047	fn test_validate_rejects_padding_references() {
2048		let mut cs = ConstraintSystem::new(
2049			vec![Word::from_u64(1)],
2050			ValueVecLayout {
2051				n_const: 1,
2052				n_inout: 1,
2053				n_witness: 2,
2054				n_internal: 2,
2055				offset_inout: 4,
2056				offset_witness: 8,
2057				committed_total_len: 16,
2058				n_scratch: 0,
2059			},
2060			vec![],
2061			vec![],
2062		);
2063
2064		// Add constraint that references padding (index 2 is padding between const and inout)
2065		cs.add_and_constraint(AndConstraint::plain_abc(
2066			vec![ValueIndex(0)], // valid constant
2067			vec![ValueIndex(2)], // PADDING!
2068			vec![ValueIndex(8)], // valid witness
2069		));
2070
2071		let result = cs.validate_and_prepare();
2072		assert!(result.is_err(), "Should reject constraint referencing padding");
2073
2074		match result.unwrap_err() {
2075			ConstraintSystemError::PaddingValueIndex {
2076				constraint_type, ..
2077			} => {
2078				assert_eq!(constraint_type, "and");
2079			}
2080			other => panic!("Expected PaddingValueIndex error, got: {:?}", other),
2081		}
2082	}
2083
2084	#[test]
2085	fn test_validate_accepts_non_padding_references() {
2086		let mut cs = ConstraintSystem::new(
2087			vec![Word::from_u64(1), Word::from_u64(2)],
2088			ValueVecLayout {
2089				n_const: 2,
2090				n_inout: 2,
2091				n_witness: 4,
2092				n_internal: 4,
2093				offset_inout: 2,
2094				offset_witness: 4,
2095				committed_total_len: 16,
2096				n_scratch: 0,
2097			},
2098			vec![],
2099			vec![],
2100		);
2101
2102		// Add constraint that only references valid non-padding indices
2103		cs.add_and_constraint(AndConstraint::plain_abc(
2104			vec![ValueIndex(0), ValueIndex(1)], // constants
2105			vec![ValueIndex(2), ValueIndex(3)], // inout
2106			vec![ValueIndex(4), ValueIndex(5)], // witness
2107		));
2108
2109		cs.add_mul_constraint(MulConstraint {
2110			a: vec![ShiftedValueIndex::plain(ValueIndex(6))], // witness
2111			b: vec![ShiftedValueIndex::plain(ValueIndex(7))], // witness
2112			hi: vec![ShiftedValueIndex::plain(ValueIndex(8))], // internal
2113			lo: vec![ShiftedValueIndex::plain(ValueIndex(9))], // internal
2114		});
2115
2116		let result = cs.validate_and_prepare();
2117		assert!(
2118			result.is_ok(),
2119			"Should accept constraints with only valid references: {:?}",
2120			result
2121		);
2122	}
2123
2124	#[test]
2125	fn test_is_padding_matches_compiler_requirements() {
2126		// Test that is_padding correctly handles the MIN_WORDS_PER_SEGMENT requirement
2127		// as seen in the compiler mod.rs:
2128		// cur_index = cur_index.max(MIN_WORDS_PER_SEGMENT as u32);
2129		// cur_index = cur_index.next_power_of_two();
2130
2131		// Case 1: Very small public section (1 const + 1 inout = 2 total)
2132		// Should be padded to MIN_WORDS_PER_SEGMENT (8)
2133		let layout1 = ValueVecLayout {
2134			n_const: 1,
2135			n_inout: 1,
2136			n_witness: 4,
2137			n_internal: 4,
2138			offset_inout: 1,   // right after constants
2139			offset_witness: 8, // padded to MIN_WORDS_PER_SEGMENT
2140			committed_total_len: 16,
2141			n_scratch: 0,
2142		};
2143
2144		// Verify padding between end of inout (index 2) and offset_witness (8)
2145		assert!(!layout1.is_padding(ValueIndex(0)), "const should not be padding");
2146		assert!(!layout1.is_padding(ValueIndex(1)), "inout should not be padding");
2147		for i in 2..8 {
2148			assert!(
2149				layout1.is_padding(ValueIndex(i)),
2150				"index {} should be padding to meet MIN_WORDS_PER_SEGMENT",
2151				i
2152			);
2153		}
2154
2155		// Case 2: Public section exactly MIN_WORDS_PER_SEGMENT (no extra padding needed)
2156		let layout2 = ValueVecLayout {
2157			n_const: 4,
2158			n_inout: 4,
2159			n_witness: 8,
2160			n_internal: 0,
2161			offset_inout: 4,
2162			offset_witness: 8, // exactly MIN_WORDS_PER_SEGMENT, already power of 2
2163			committed_total_len: 16,
2164			n_scratch: 0,
2165		};
2166
2167		// No padding in public section
2168		for i in 0..8 {
2169			assert!(!layout2.is_padding(ValueIndex(i)), "index {} should not be padding", i);
2170		}
2171
2172		// Case 3: Public section between MIN_WORDS_PER_SEGMENT and next power of 2
2173		// e.g., 10 total needs to round up to 16
2174		let layout3 = ValueVecLayout {
2175			n_const: 5,
2176			n_inout: 5,
2177			n_witness: 16,
2178			n_internal: 0,
2179			offset_inout: 5,
2180			offset_witness: 16, // rounded up from 10 to 16 (next power of 2)
2181			committed_total_len: 32,
2182			n_scratch: 0,
2183		};
2184
2185		// Check padding from end of inout (10) to offset_witness (16)
2186		for i in 0..5 {
2187			assert!(!layout3.is_padding(ValueIndex(i)), "const {} should not be padding", i);
2188		}
2189		for i in 5..10 {
2190			assert!(!layout3.is_padding(ValueIndex(i)), "inout {} should not be padding", i);
2191		}
2192		for i in 10..16 {
2193			assert!(
2194				layout3.is_padding(ValueIndex(i)),
2195				"index {} should be padding for power-of-2 alignment",
2196				i
2197			);
2198		}
2199
2200		// Case 4: Test with offsets that show all three padding types
2201		let layout4 = ValueVecLayout {
2202			n_const: 2,              // indices 0-1
2203			n_inout: 2,              // indices 8-9
2204			n_witness: 4,            // indices 16-19
2205			n_internal: 4,           // indices 20-23
2206			offset_inout: 8,         // padding after constants to align
2207			offset_witness: 16,      // padding after inout to reach power of 2
2208			committed_total_len: 32, // padding after internal to reach total
2209			n_scratch: 0,
2210		};
2211
2212		// Constants
2213		assert!(!layout4.is_padding(ValueIndex(0)));
2214		assert!(!layout4.is_padding(ValueIndex(1)));
2215
2216		// Padding between constants and inout (indices 2-7)
2217		for i in 2..8 {
2218			assert!(layout4.is_padding(ValueIndex(i)), "padding between const and inout at {}", i);
2219		}
2220
2221		// Inout values
2222		assert!(!layout4.is_padding(ValueIndex(8)));
2223		assert!(!layout4.is_padding(ValueIndex(9)));
2224
2225		// Padding between inout and witness (indices 10-15)
2226		for i in 10..16 {
2227			assert!(
2228				layout4.is_padding(ValueIndex(i)),
2229				"padding between inout and witness at {}",
2230				i
2231			);
2232		}
2233
2234		// Witness values
2235		for i in 16..20 {
2236			assert!(!layout4.is_padding(ValueIndex(i)), "witness at {}", i);
2237		}
2238
2239		// Internal values
2240		for i in 20..24 {
2241			assert!(!layout4.is_padding(ValueIndex(i)), "internal at {}", i);
2242		}
2243
2244		// Padding after internal to total_len (indices 24-31)
2245		for i in 24..32 {
2246			assert!(layout4.is_padding(ValueIndex(i)), "padding after internal at {}", i);
2247		}
2248	}
2249
2250	#[test]
2251	fn test_validate_rejects_out_of_range_indices() {
2252		let mut cs = ConstraintSystem::new(
2253			vec![Word::from_u64(1)],
2254			ValueVecLayout {
2255				n_const: 1,
2256				n_inout: 1,
2257				n_witness: 2,
2258				n_internal: 2,
2259				offset_inout: 4,
2260				offset_witness: 8,
2261				committed_total_len: 16,
2262				n_scratch: 0,
2263			},
2264			vec![],
2265			vec![],
2266		);
2267
2268		// Add AND constraint that references an out-of-range index
2269		cs.add_and_constraint(AndConstraint::plain_abc(
2270			vec![ValueIndex(0)],  // valid constant
2271			vec![ValueIndex(16)], // OUT OF RANGE! (total_len is 16, so max valid index is 15)
2272			vec![ValueIndex(8)],  // valid witness
2273		));
2274
2275		let result = cs.validate_and_prepare();
2276		assert!(result.is_err(), "Should reject constraint with out-of-range index");
2277
2278		match result.unwrap_err() {
2279			ConstraintSystemError::OutOfRangeValueIndex {
2280				constraint_type,
2281				operand_name,
2282				value_index,
2283				total_len,
2284				..
2285			} => {
2286				assert_eq!(constraint_type, "and");
2287				assert_eq!(operand_name, "b");
2288				assert_eq!(value_index, 16);
2289				assert_eq!(total_len, 16);
2290			}
2291			other => panic!("Expected OutOfRangeValueIndex error, got: {:?}", other),
2292		}
2293	}
2294
2295	#[test]
2296	fn test_validate_rejects_out_of_range_in_mul_constraint() {
2297		let mut cs = ConstraintSystem::new(
2298			vec![Word::from_u64(1), Word::from_u64(2)],
2299			ValueVecLayout {
2300				n_const: 2,
2301				n_inout: 2,
2302				n_witness: 4,
2303				n_internal: 4,
2304				offset_inout: 2,
2305				offset_witness: 4,
2306				committed_total_len: 16,
2307				n_scratch: 0,
2308			},
2309			vec![],
2310			vec![],
2311		);
2312
2313		// Add MUL constraint with out-of-range index in 'hi' operand
2314		cs.add_mul_constraint(MulConstraint {
2315			a: vec![ShiftedValueIndex::plain(ValueIndex(0))], // valid
2316			b: vec![ShiftedValueIndex::plain(ValueIndex(1))], // valid
2317			hi: vec![ShiftedValueIndex::plain(ValueIndex(100))], // WAY out of range!
2318			lo: vec![ShiftedValueIndex::plain(ValueIndex(3))], // valid
2319		});
2320
2321		let result = cs.validate_and_prepare();
2322		assert!(result.is_err(), "Should reject MUL constraint with out-of-range index");
2323
2324		match result.unwrap_err() {
2325			ConstraintSystemError::OutOfRangeValueIndex {
2326				constraint_type,
2327				operand_name,
2328				value_index,
2329				total_len,
2330				..
2331			} => {
2332				assert_eq!(constraint_type, "mul");
2333				assert_eq!(operand_name, "hi");
2334				assert_eq!(value_index, 100);
2335				assert_eq!(total_len, 16);
2336			}
2337			other => panic!("Expected OutOfRangeValueIndex error, got: {:?}", other),
2338		}
2339	}
2340
2341	#[test]
2342	fn test_validate_checks_out_of_range_before_padding() {
2343		// This test verifies that out-of-range checking happens before padding checking
2344		// by using an index that is both out-of-range AND would be in a padding area if it were
2345		// valid
2346		let mut cs = ConstraintSystem::new(
2347			vec![Word::from_u64(1)],
2348			ValueVecLayout {
2349				n_const: 1,
2350				n_inout: 1,
2351				n_witness: 2,
2352				n_internal: 2,
2353				offset_inout: 4,
2354				offset_witness: 8,
2355				committed_total_len: 16,
2356				n_scratch: 0,
2357			},
2358			vec![],
2359			vec![],
2360		);
2361
2362		// Index 20 is out of range (>= 16)
2363		// If it were in range, indices 2-3 and 6-7 would be padding
2364		cs.add_and_constraint(AndConstraint::plain_abc(
2365			vec![ValueIndex(0)],
2366			vec![ValueIndex(20)], // out of range
2367			vec![ValueIndex(8)],
2368		));
2369
2370		let result = cs.validate_and_prepare();
2371		assert!(result.is_err());
2372
2373		// Should get OutOfRangeValueIndex, not PaddingValueIndex
2374		match result.unwrap_err() {
2375			ConstraintSystemError::OutOfRangeValueIndex { .. } => {
2376				// Good, out-of-range was detected first
2377			}
2378			other => panic!(
2379				"Expected OutOfRangeValueIndex to be detected before padding check, got: {:?}",
2380				other
2381			),
2382		}
2383	}
2384}