Skip to main content

binius_core/constraint_system/
layout.rs

1// Copyright 2025 Irreducible Inc.
2// Copyright 2026 The Binius Developers
3use binius_utils::serialization::{DeserializeBytes, SerializationError, SerializeBytes};
4use bytes::{Buf, BufMut};
5
6use super::ValueIndex;
7use crate::{consts, error::ConstraintSystemError};
8
9/// Description of a layout of the value vector for a particular circuit.
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct ValueVecLayout {
12	/// The number of the constants declared by the circuit.
13	pub n_const: usize,
14	/// The number of the input output parameters declared by the circuit.
15	pub n_inout: usize,
16	/// The number of the witness parameters declared by the circuit.
17	pub n_witness: usize,
18	/// The number of the internal values declared by the circuit.
19	///
20	/// Those are outputs and intermediaries created by the gates.
21	pub n_internal: usize,
22
23	/// The offset at which `inout` parameters start.
24	pub offset_inout: usize,
25	/// The offset at which `witness` parameters start.
26	///
27	/// The public section of the value vec has the power-of-two size and is greater than the
28	/// minimum number of words. By public section we mean the constants and the inout values.
29	pub offset_witness: usize,
30	/// The total number of committed values in the values vector. This does not include any
31	/// scratch values.
32	pub committed_total_len: usize,
33	/// The number of scratch values at the end of the value vec.
34	pub n_scratch: usize,
35}
36
37impl ValueVecLayout {
38	/// Validates that the value vec layout has a correct shape.
39	///
40	/// Specifically checks that:
41	///
42	/// - the public segment (constants and inout values) is padded to the power of two.
43	/// - the public segment is not less than the minimum size.
44	pub const fn validate(&self) -> Result<(), ConstraintSystemError> {
45		if !self.offset_witness.is_power_of_two() {
46			return Err(ConstraintSystemError::PublicInputPowerOfTwo);
47		}
48
49		let pub_input_size = self.offset_witness;
50		if pub_input_size < consts::MIN_WORDS_PER_SEGMENT {
51			return Err(ConstraintSystemError::PublicInputTooShort { pub_input_size });
52		}
53
54		Ok(())
55	}
56
57	/// Returns the number of words in the public segment: the constants and inout values,
58	/// including padding up to the power-of-two segment length.
59	pub const fn n_public_words(&self) -> usize {
60		self.offset_witness
61	}
62
63	/// Returns the base-2 logarithm of the public segment length in words.
64	///
65	/// [`Self::validate`] guarantees that the public segment length is a power of two.
66	pub const fn log_public_words(&self) -> usize {
67		self.offset_witness.trailing_zeros() as usize
68	}
69
70	/// Returns the number of non-public words: the witness and internal values, including
71	/// padding up to `committed_total_len`.
72	pub const fn n_non_public_words(&self) -> usize {
73		self.committed_total_len - self.offset_witness
74	}
75
76	/// Returns true if the given index points to an area that is considered to be padding.
77	pub(super) const fn is_padding(&self, index: ValueIndex) -> bool {
78		let idx = index.0 as usize;
79
80		// padding 1: between constants and inout section
81		if idx >= self.n_const && idx < self.offset_inout {
82			return true;
83		}
84
85		// padding 2: between the end of inout section and the start of witness section
86		let end_of_inout = self.offset_inout + self.n_inout;
87		if idx >= end_of_inout && idx < self.offset_witness {
88			return true;
89		}
90
91		// padding 3: between the last internal value and the total len
92		let end_of_internal = self.offset_witness + self.n_witness + self.n_internal;
93		if idx >= end_of_internal && idx < self.committed_total_len {
94			return true;
95		}
96
97		false
98	}
99
100	/// Returns true if the given index is out-of-bounds for the committed part of this layout.
101	pub(super) const fn is_committed_oob(&self, index: ValueIndex) -> bool {
102		index.0 as usize >= self.committed_total_len
103	}
104}
105
106impl SerializeBytes for ValueVecLayout {
107	fn serialize(&self, mut write_buf: impl BufMut) -> Result<(), SerializationError> {
108		self.n_const.serialize(&mut write_buf)?;
109		self.n_inout.serialize(&mut write_buf)?;
110		self.n_witness.serialize(&mut write_buf)?;
111		self.n_internal.serialize(&mut write_buf)?;
112		self.offset_inout.serialize(&mut write_buf)?;
113		self.offset_witness.serialize(&mut write_buf)?;
114		self.committed_total_len.serialize(&mut write_buf)?;
115		self.n_scratch.serialize(write_buf)
116	}
117}
118
119impl DeserializeBytes for ValueVecLayout {
120	fn deserialize(mut read_buf: impl Buf) -> Result<Self, SerializationError>
121	where
122		Self: Sized,
123	{
124		let n_const = usize::deserialize(&mut read_buf)?;
125		let n_inout = usize::deserialize(&mut read_buf)?;
126		let n_witness = usize::deserialize(&mut read_buf)?;
127		let n_internal = usize::deserialize(&mut read_buf)?;
128		let offset_inout = usize::deserialize(&mut read_buf)?;
129		let offset_witness = usize::deserialize(&mut read_buf)?;
130		let committed_total_len = usize::deserialize(&mut read_buf)?;
131		let n_scratch = usize::deserialize(read_buf)?;
132
133		Ok(ValueVecLayout {
134			n_const,
135			n_inout,
136			n_witness,
137			n_internal,
138			offset_inout,
139			offset_witness,
140			committed_total_len,
141			n_scratch,
142		})
143	}
144}
145
146#[cfg(test)]
147mod tests {
148	use super::*;
149
150	#[test]
151	fn test_value_vec_layout_serialization_round_trip() {
152		let layout = ValueVecLayout {
153			n_const: 5,
154			n_inout: 3,
155			n_witness: 12,
156			n_internal: 7,
157			offset_inout: 8,
158			offset_witness: 16,
159			committed_total_len: 32,
160			n_scratch: 0,
161		};
162
163		let mut buf = Vec::new();
164		layout.serialize(&mut buf).unwrap();
165
166		let deserialized = ValueVecLayout::deserialize(&mut buf.as_slice()).unwrap();
167		assert_eq!(layout, deserialized);
168	}
169
170	#[test]
171	fn test_is_padding_comprehensive() {
172		// Test layout with all types of padding
173		let layout = ValueVecLayout {
174			n_const: 2,              // constants at indices 0-1
175			n_inout: 3,              // inout at indices 4-6
176			n_witness: 5,            // witness at indices 16-20
177			n_internal: 10,          // internal at indices 21-30
178			offset_inout: 4,         // gap between constants and inout (indices 2-3 are padding)
179			offset_witness: 16,      // public section is 16 (power of 2), gap 7-15 is padding
180			committed_total_len: 64, // total must be power of 2, gap 31-63 is padding
181			n_scratch: 0,
182		};
183
184		// Test constants (indices 0-1): NOT padding
185		assert!(!layout.is_padding(ValueIndex(0)), "index 0 should be constant");
186		assert!(!layout.is_padding(ValueIndex(1)), "index 1 should be constant");
187
188		// Test padding between constants and inout (indices 2-3): PADDING
189		assert!(
190			layout.is_padding(ValueIndex(2)),
191			"index 2 should be padding between const and inout"
192		);
193		assert!(
194			layout.is_padding(ValueIndex(3)),
195			"index 3 should be padding between const and inout"
196		);
197
198		// Test inout values (indices 4-6): NOT padding
199		assert!(!layout.is_padding(ValueIndex(4)), "index 4 should be inout");
200		assert!(!layout.is_padding(ValueIndex(5)), "index 5 should be inout");
201		assert!(!layout.is_padding(ValueIndex(6)), "index 6 should be inout");
202
203		// Test padding between inout and witness (indices 7-15): PADDING
204		for i in 7..16 {
205			assert!(
206				layout.is_padding(ValueIndex(i)),
207				"index {} should be padding between inout and witness",
208				i
209			);
210		}
211
212		// Test witness values (indices 16-20): NOT padding
213		for i in 16..21 {
214			assert!(!layout.is_padding(ValueIndex(i)), "index {} should be witness", i);
215		}
216
217		// Test internal values (indices 21-30): NOT padding
218		for i in 21..31 {
219			assert!(!layout.is_padding(ValueIndex(i)), "index {} should be internal", i);
220		}
221
222		// Test padding after internal values (indices 31-63): PADDING
223		for i in 31..64 {
224			assert!(
225				layout.is_padding(ValueIndex(i)),
226				"index {} should be padding after internal",
227				i
228			);
229		}
230	}
231
232	#[test]
233	fn test_is_padding_minimal_layout() {
234		// Test a minimal layout with no gaps except required end padding
235		let layout = ValueVecLayout {
236			n_const: 4,              // constants at indices 0-3
237			n_inout: 4,              // inout at indices 4-7
238			n_witness: 4,            // witness at indices 8-11
239			n_internal: 4,           // internal at indices 12-15
240			offset_inout: 4,         // no gap between constants and inout
241			offset_witness: 8,       // no gap between inout and witness
242			committed_total_len: 16, // exactly fits all values
243			n_scratch: 0,
244		};
245
246		// No padding anywhere in this layout
247		for i in 0..16 {
248			assert!(
249				!layout.is_padding(ValueIndex(i)),
250				"index {} should not be padding in minimal layout",
251				i
252			);
253		}
254	}
255
256	#[test]
257	fn test_is_padding_public_section_min_size() {
258		// Test layout where public section must be padded to meet MIN_WORDS_PER_SEGMENT
259		let layout = ValueVecLayout {
260			n_const: 1,              // only 1 constant
261			n_inout: 1,              // only 1 inout
262			n_witness: 2,            // 2 witness values
263			n_internal: 2,           // 2 internal values
264			offset_inout: 4,         // padding between const and inout to reach min size
265			offset_witness: 8,       // public section padded to 8 (MIN_WORDS_PER_SEGMENT)
266			committed_total_len: 16, // power of 2
267			n_scratch: 0,
268		};
269
270		// Test the single constant
271		assert!(!layout.is_padding(ValueIndex(0)), "index 0 should be constant");
272
273		// Test padding between constant and inout (indices 1-3)
274		assert!(layout.is_padding(ValueIndex(1)), "index 1 should be padding");
275		assert!(layout.is_padding(ValueIndex(2)), "index 2 should be padding");
276		assert!(layout.is_padding(ValueIndex(3)), "index 3 should be padding");
277
278		// Test the single inout value
279		assert!(!layout.is_padding(ValueIndex(4)), "index 4 should be inout");
280
281		// Test padding between inout and witness (indices 5-7)
282		assert!(layout.is_padding(ValueIndex(5)), "index 5 should be padding");
283		assert!(layout.is_padding(ValueIndex(6)), "index 6 should be padding");
284		assert!(layout.is_padding(ValueIndex(7)), "index 7 should be padding");
285
286		// Test witness values (indices 8-9)
287		assert!(!layout.is_padding(ValueIndex(8)), "index 8 should be witness");
288		assert!(!layout.is_padding(ValueIndex(9)), "index 9 should be witness");
289
290		// Test internal values (indices 10-11)
291		assert!(!layout.is_padding(ValueIndex(10)), "index 10 should be internal");
292		assert!(!layout.is_padding(ValueIndex(11)), "index 11 should be internal");
293
294		// Test padding at the end (indices 12-15)
295		for i in 12..16 {
296			assert!(layout.is_padding(ValueIndex(i)), "index {} should be end padding", i);
297		}
298	}
299
300	#[test]
301	fn test_is_padding_boundary_conditions() {
302		let layout = ValueVecLayout {
303			n_const: 2,
304			n_inout: 2,
305			n_witness: 4,
306			n_internal: 4,
307			offset_inout: 4,
308			offset_witness: 8,
309			committed_total_len: 16,
310			n_scratch: 0,
311		};
312
313		// Test exact boundaries
314		assert!(!layout.is_padding(ValueIndex(1)), "last constant should not be padding");
315		assert!(layout.is_padding(ValueIndex(2)), "first padding after const should be padding");
316
317		assert!(layout.is_padding(ValueIndex(3)), "last padding before inout should be padding");
318		assert!(!layout.is_padding(ValueIndex(4)), "first inout should not be padding");
319
320		assert!(!layout.is_padding(ValueIndex(5)), "last inout should not be padding");
321		assert!(layout.is_padding(ValueIndex(6)), "first padding after inout should be padding");
322
323		assert!(layout.is_padding(ValueIndex(7)), "last padding before witness should be padding");
324		assert!(!layout.is_padding(ValueIndex(8)), "first witness should not be padding");
325
326		assert!(!layout.is_padding(ValueIndex(11)), "last witness should not be padding");
327		assert!(!layout.is_padding(ValueIndex(12)), "first internal should not be padding");
328
329		assert!(!layout.is_padding(ValueIndex(15)), "last internal should not be padding");
330		// Note: index 16 would be out of bounds, not tested here
331	}
332
333	#[test]
334	fn test_is_padding_matches_compiler_requirements() {
335		// Test that is_padding correctly handles the MIN_WORDS_PER_SEGMENT requirement
336		// as seen in the compiler mod.rs:
337		// cur_index = cur_index.max(MIN_WORDS_PER_SEGMENT as u32);
338		// cur_index = cur_index.next_power_of_two();
339
340		// Case 1: Very small public section (1 const + 1 inout = 2 total)
341		// Should be padded to MIN_WORDS_PER_SEGMENT (8)
342		let layout1 = ValueVecLayout {
343			n_const: 1,
344			n_inout: 1,
345			n_witness: 4,
346			n_internal: 4,
347			offset_inout: 1,   // right after constants
348			offset_witness: 8, // padded to MIN_WORDS_PER_SEGMENT
349			committed_total_len: 16,
350			n_scratch: 0,
351		};
352
353		// Verify padding between end of inout (index 2) and offset_witness (8)
354		assert!(!layout1.is_padding(ValueIndex(0)), "const should not be padding");
355		assert!(!layout1.is_padding(ValueIndex(1)), "inout should not be padding");
356		for i in 2..8 {
357			assert!(
358				layout1.is_padding(ValueIndex(i)),
359				"index {} should be padding to meet MIN_WORDS_PER_SEGMENT",
360				i
361			);
362		}
363
364		// Case 2: Public section exactly MIN_WORDS_PER_SEGMENT (no extra padding needed)
365		let layout2 = ValueVecLayout {
366			n_const: 4,
367			n_inout: 4,
368			n_witness: 8,
369			n_internal: 0,
370			offset_inout: 4,
371			offset_witness: 8, // exactly MIN_WORDS_PER_SEGMENT, already power of 2
372			committed_total_len: 16,
373			n_scratch: 0,
374		};
375
376		// No padding in public section
377		for i in 0..8 {
378			assert!(!layout2.is_padding(ValueIndex(i)), "index {} should not be padding", i);
379		}
380
381		// Case 3: Public section between MIN_WORDS_PER_SEGMENT and next power of 2
382		// e.g., 10 total needs to round up to 16
383		let layout3 = ValueVecLayout {
384			n_const: 5,
385			n_inout: 5,
386			n_witness: 16,
387			n_internal: 0,
388			offset_inout: 5,
389			offset_witness: 16, // rounded up from 10 to 16 (next power of 2)
390			committed_total_len: 32,
391			n_scratch: 0,
392		};
393
394		// Check padding from end of inout (10) to offset_witness (16)
395		for i in 0..5 {
396			assert!(!layout3.is_padding(ValueIndex(i)), "const {} should not be padding", i);
397		}
398		for i in 5..10 {
399			assert!(!layout3.is_padding(ValueIndex(i)), "inout {} should not be padding", i);
400		}
401		for i in 10..16 {
402			assert!(
403				layout3.is_padding(ValueIndex(i)),
404				"index {} should be padding for power-of-2 alignment",
405				i
406			);
407		}
408
409		// Case 4: Test with offsets that show all three padding types
410		let layout4 = ValueVecLayout {
411			n_const: 2,              // indices 0-1
412			n_inout: 2,              // indices 8-9
413			n_witness: 4,            // indices 16-19
414			n_internal: 4,           // indices 20-23
415			offset_inout: 8,         // padding after constants to align
416			offset_witness: 16,      // padding after inout to reach power of 2
417			committed_total_len: 32, // padding after internal to reach total
418			n_scratch: 0,
419		};
420
421		// Constants
422		assert!(!layout4.is_padding(ValueIndex(0)));
423		assert!(!layout4.is_padding(ValueIndex(1)));
424
425		// Padding between constants and inout (indices 2-7)
426		for i in 2..8 {
427			assert!(layout4.is_padding(ValueIndex(i)), "padding between const and inout at {}", i);
428		}
429
430		// Inout values
431		assert!(!layout4.is_padding(ValueIndex(8)));
432		assert!(!layout4.is_padding(ValueIndex(9)));
433
434		// Padding between inout and witness (indices 10-15)
435		for i in 10..16 {
436			assert!(
437				layout4.is_padding(ValueIndex(i)),
438				"padding between inout and witness at {}",
439				i
440			);
441		}
442
443		// Witness values
444		for i in 16..20 {
445			assert!(!layout4.is_padding(ValueIndex(i)), "witness at {}", i);
446		}
447
448		// Internal values
449		for i in 20..24 {
450			assert!(!layout4.is_padding(ValueIndex(i)), "internal at {}", i);
451		}
452
453		// Padding after internal to total_len (indices 24-31)
454		for i in 24..32 {
455			assert!(layout4.is_padding(ValueIndex(i)), "padding after internal at {}", i);
456		}
457	}
458}