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