Skip to main content

binius_core/constraint_system/
layout.rs

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