1use 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#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct ValueVecLayout {
15 pub n_const: usize,
17 pub n_inout: usize,
19 pub n_witness: usize,
21 pub n_internal: usize,
25
26 pub offset_inout: usize,
28 pub offset_witness: usize,
33 pub n_hidden_words: usize,
37 pub n_scratch: usize,
39}
40
41impl ValueVecLayout {
42 pub const MIN_WORDS_PER_SEGMENT: usize = 2;
46
47 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 pub const fn n_public_words(&self) -> usize {
78 self.offset_witness
79 }
80
81 pub const fn log_public_words(&self) -> usize {
85 self.offset_witness.trailing_zeros() as usize
86 }
87
88 pub const fn combined_len(&self) -> usize {
92 self.offset_witness + self.n_hidden_words
93 }
94
95 pub const fn log_witness_words(&self) -> usize {
100 log2_ceil_usize(self.n_hidden_words)
101 }
102
103 pub(super) const fn is_padding(&self, index: ValueIndex) -> bool {
105 let idx = index.0 as usize;
106
107 if idx >= self.n_const && idx < self.offset_inout {
109 return true;
110 }
111
112 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 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 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 assert_eq!(layout(4, 64).log_witness_words(), 6);
211 assert_eq!(layout(4, 32).log_witness_words(), 5);
213 }
214
215 #[test]
216 fn test_validate_rejects_short_hidden_segment() {
217 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 let layout = ValueVecLayout {
238 n_const: 2, n_inout: 3, n_witness: 5, n_internal: 10, offset_inout: 4, offset_witness: 16, n_hidden_words: 48, n_scratch: 0,
246 };
247
248 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 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 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 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 for i in 16..21 {
278 assert!(!layout.is_padding(ValueIndex(i)), "index {} should be witness", i);
279 }
280
281 for i in 21..31 {
283 assert!(!layout.is_padding(ValueIndex(i)), "index {} should be internal", i);
284 }
285
286 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 let layout = ValueVecLayout {
300 n_const: 4, n_inout: 4, n_witness: 4, n_internal: 4, offset_inout: 4, offset_witness: 8, n_hidden_words: 8, n_scratch: 0,
308 };
309
310 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 let layout = ValueVecLayout {
324 n_const: 1, n_inout: 1, n_witness: 2, n_internal: 2, offset_inout: 4, offset_witness: 8, n_hidden_words: 8, n_scratch: 0,
332 };
333
334 assert!(!layout.is_padding(ValueIndex(0)), "index 0 should be constant");
336
337 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 assert!(!layout.is_padding(ValueIndex(4)), "index 4 should be inout");
344
345 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 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 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 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 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 }
396
397 #[test]
398 fn test_is_padding_matches_compiler_requirements() {
399 let layout1 = ValueVecLayout {
407 n_const: 1,
408 n_inout: 1,
409 n_witness: 4,
410 n_internal: 4,
411 offset_inout: 1, offset_witness: 8, n_hidden_words: 8,
414 n_scratch: 0,
415 };
416
417 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 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, n_hidden_words: 8,
437 n_scratch: 0,
438 };
439
440 for i in 0..8 {
442 assert!(!layout2.is_padding(ValueIndex(i)), "index {} should not be padding", i);
443 }
444
445 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, n_hidden_words: 16,
455 n_scratch: 0,
456 };
457
458 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 let layout4 = ValueVecLayout {
475 n_const: 2, n_inout: 2, n_witness: 4, n_internal: 4, offset_inout: 8, offset_witness: 16, n_hidden_words: 16, n_scratch: 0,
483 };
484
485 assert!(!layout4.is_padding(ValueIndex(0)));
487 assert!(!layout4.is_padding(ValueIndex(1)));
488
489 for i in 2..8 {
491 assert!(layout4.is_padding(ValueIndex(i)), "padding between const and inout at {}", i);
492 }
493
494 assert!(!layout4.is_padding(ValueIndex(8)));
496 assert!(!layout4.is_padding(ValueIndex(9)));
497
498 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 for i in 16..20 {
509 assert!(!layout4.is_padding(ValueIndex(i)), "witness at {}", i);
510 }
511
512 for i in 20..24 {
514 assert!(!layout4.is_padding(ValueIndex(i)), "internal at {}", i);
515 }
516
517 for i in 24..32 {
519 assert!(layout4.is_padding(ValueIndex(i)), "padding after internal at {}", i);
520 }
521 }
522}