1use binius_core::word::Word;
3use binius_frontend::{CircuitBuilder, Wire};
4
5use crate::{
6 multiplexer::rotate_left_dynamic,
7 shift::{var_sll_bytes, var_srl_bytes},
8};
9
10pub fn assert_slice_eq(
24 b: &CircuitBuilder,
25 name: impl Into<String>,
26 len_bytes: Wire,
27 actual: &[Wire],
28 expected: &[Wire],
29) {
30 assert_eq!(
31 actual.len(),
32 expected.len(),
33 "assert_slice_eq: actual and expected must have the same word count"
34 );
35 let name = name.into();
36 let zero = b.add_constant(Word::ZERO);
37 let eight = b.add_constant_64(8);
38 for (i, (&a, &e)) in actual.iter().zip(expected).enumerate() {
39 let start_byte = b.add_constant_64((i * 8) as u64);
40 let (diff_raw, borrow) = b.isub_bin_bout(len_bytes, start_byte, zero);
41 let diff = b.select(borrow, zero, diff_raw);
42
43 let (eight_minus_diff, diff_gt_8) = b.isub_bin_bout(eight, diff, zero);
46 let a_shifted = var_sll_bytes(b, a, eight_minus_diff);
47 let e_shifted = var_sll_bytes(b, e, eight_minus_diff);
48 let a_part = b.select(diff_gt_8, a, a_shifted);
49 let e_part = b.select(diff_gt_8, e, e_shifted);
50
51 let diff_eq_0 = b.icmp_eq(diff, zero);
55 let a_cmp = b.select(diff_eq_0, zero, a_part);
56 let e_cmp = b.select(diff_eq_0, zero, e_part);
57
58 b.assert_eq(format!("{name}[{i}]"), a_cmp, e_cmp);
59 }
60}
61
62pub fn slice(
96 b: &CircuitBuilder,
97 len_input: Wire,
98 len_slice: Wire,
99 input: &[Wire],
100 offset: Wire,
101 max_n_words: usize,
102) -> Vec<Wire> {
103 let max_len_input = input.len() << 3;
105 let max_len_slice = max_n_words << 3;
106
107 assert!(max_len_input <= u32::MAX as usize, "max_n_input must be < 2^32");
108 assert!(max_len_slice <= u32::MAX as usize, "max_n_slice must be < 2^32");
109
110 b.assert_zero("offset_32bit", b.shr(offset, 32));
112 b.assert_zero("len_slice_32bit", b.shr(len_slice, 32));
113 b.assert_zero("len_input_32bit", b.shr(len_input, 32));
114
115 let (offset_plus_len_slice, _) = b.iadd(offset, len_slice);
117 let in_bounds = b.icmp_ule(offset_plus_len_slice, len_input);
118 b.assert_true("bounds_check", in_bounds);
119
120 let sufficient_capacity = b.icmp_ule(len_slice, b.add_constant(Word(max_len_slice as u64)));
121 b.assert_true("max_n_words is sufficient", sufficient_capacity);
122
123 if max_n_words == 0 {
126 Vec::new()
127 } else {
128 let zero = b.add_constant(Word::ZERO);
129
130 let word_offset = b.shr(offset, 3); let byte_offset = b.band(offset, b.add_constant(Word(7))); let (neg_byte_offset, _) = b.isub_bin_bout(b.add_constant(Word(8)), byte_offset, zero);
134 let is_aligned = b.icmp_eq(byte_offset, zero);
135
136 let window = (max_n_words + 1).min(input.len());
144 let rotated = rotate_left_dynamic(b, input, word_offset, window);
145
146 (0..max_n_words)
147 .map(|slice_idx| {
148 let b = b.subcircuit(format!("slice_word[{slice_idx}]"));
149
150 let in_word = rotated[slice_idx % window];
151 let next_word = rotated[(slice_idx + 1) % window];
152
153 let aligned_out_word = in_word;
154 let unaligned_out_word = b.bxor(
155 var_srl_bytes(&b, in_word, byte_offset),
156 var_sll_bytes(&b, next_word, neg_byte_offset),
157 );
158 b.select(is_aligned, aligned_out_word, unaligned_out_word)
159 })
160 .collect()
161 }
162}
163
164#[cfg(test)]
165mod tests {
166
167 use super::{CircuitBuilder, Wire, Word, assert_slice_eq, slice};
168
169 struct SliceTestSetup {
173 builder: CircuitBuilder,
174 len_input: Wire,
175 len_slice: Wire,
176 offset: Wire,
177 input: Vec<Wire>,
178 expected: Vec<Wire>,
179 }
180
181 fn build_slice_check(n_input_words: usize, n_slice_words: usize) -> SliceTestSetup {
182 let builder = CircuitBuilder::new();
183 let len_input = builder.add_inout();
184 let len_slice = builder.add_inout();
185 let offset = builder.add_inout();
186 let input: Vec<Wire> = (0..n_input_words).map(|_| builder.add_inout()).collect();
187 let expected: Vec<Wire> = (0..n_slice_words).map(|_| builder.add_inout()).collect();
188 let actual = slice(&builder, len_input, len_slice, &input, offset, n_slice_words);
189 assert_slice_eq(&builder, "slice_eq", len_slice, &actual, &expected);
190 SliceTestSetup {
191 builder,
192 len_input,
193 len_slice,
194 offset,
195 input,
196 expected,
197 }
198 }
199
200 fn run_slice_success(
203 setup: SliceTestSetup,
204 len_input_val: u64,
205 len_slice_val: u64,
206 offset_val: u64,
207 input_data: &[u8],
208 expected_slice_data: &[u8],
209 ) {
210 let circuit = setup.builder.build();
211 let mut filler = circuit.new_witness_filler();
212 filler[setup.len_input] = Word(len_input_val);
213 filler[setup.len_slice] = Word(len_slice_val);
214 filler[setup.offset] = Word(offset_val);
215 filler.pack_bytes_le(&setup.input, input_data);
216 filler.pack_bytes_le(&setup.expected, expected_slice_data);
217
218 circuit.populate_wire_witness(&mut filler).unwrap();
219 let cs = circuit.constraint_system();
220 cs.verify(&filler.into_value_vec()).unwrap();
221 }
222
223 fn run_slice_failure(
225 setup: SliceTestSetup,
226 len_input_val: u64,
227 len_slice_val: u64,
228 offset_val: u64,
229 input_data: &[u8],
230 expected_slice_data: &[u8],
231 ) {
232 let circuit = setup.builder.build();
233 let mut filler = circuit.new_witness_filler();
234 filler[setup.len_input] = Word(len_input_val);
235 filler[setup.len_slice] = Word(len_slice_val);
236 filler[setup.offset] = Word(offset_val);
237 filler.pack_bytes_le(&setup.input, input_data);
238 filler.pack_bytes_le(&setup.expected, expected_slice_data);
239 assert!(circuit.populate_wire_witness(&mut filler).is_err());
240 }
241
242 #[test]
243 fn test_aligned_slice() {
244 let setup = build_slice_check(2, 1);
246 let input_data = [
247 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
248 0x0e, 0x0f,
249 ];
250 let slice_data = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
251 run_slice_success(setup, 16, 8, 0, &input_data, &slice_data);
252 }
253
254 #[test]
255 fn test_unaligned_slice() {
256 let setup = build_slice_check(2, 1);
258 let input_data = [
259 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
260 0x0e, 0x0f,
261 ];
262 let slice_data = [0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a];
263 run_slice_success(setup, 16, 8, 3, &input_data, &slice_data);
264 }
265
266 #[test]
267 fn test_bounds_check() {
268 let setup = build_slice_check(2, 1);
270 let dummy_input = vec![0u8; 10];
271 let dummy_slice = vec![0u8; 8];
272 run_slice_failure(setup, 10, 8, 5, &dummy_input, &dummy_slice);
273 }
274
275 #[test]
276 fn test_bounds_check_edge_case() {
277 let setup = build_slice_check(2, 1);
279 let input_data = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
280 let slice_data = vec![5, 6, 7, 8, 9];
281 run_slice_success(setup, 10, 5, 5, &input_data, &slice_data);
282 }
283
284 #[test]
285 fn test_empty_slice() {
286 let setup = build_slice_check(2, 1);
288 let input_data = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
289 run_slice_success(setup, 10, 0, 5, &input_data, &[]);
290 }
291
292 #[test]
293 fn test_mismatched_slice_content() {
294 let setup = build_slice_check(2, 1);
297 let input_data = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
298 let wrong_slice_data = vec![0, 1, 2, 3, 4];
300 run_slice_failure(setup, 10, 5, 2, &input_data, &wrong_slice_data);
301 }
302
303 #[test]
304 fn test_offset_at_end() {
305 let setup = build_slice_check(2, 1);
307 let input_data = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
308 run_slice_success(setup, 10, 0, 10, &input_data, &[]);
309 }
310
311 #[test]
312 fn test_multiple_byte_extraction_paths() {
313 for word_idx in 0..3 {
316 for byte_offset in 0..8 {
317 let offset_val = word_idx * 8 + byte_offset;
318 if offset_val + 8 > 24 {
319 continue;
320 }
321 let setup = build_slice_check(3, 1);
322 let input_data: Vec<u8> = (0..24).map(|i| i as u8).collect();
323 let slice_data: Vec<u8> = input_data[offset_val..offset_val + 8].to_vec();
324 run_slice_success(setup, 24, 8, offset_val as u64, &input_data, &slice_data);
325 }
326 }
327 }
328
329 #[test]
330 fn test_partial_word_zero_padding() {
331 let setup = build_slice_check(3, 2);
334 let input_data = vec![
335 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, ];
339 let correct_slice = [
341 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x00, 0x00, 0x00, 0x00, ];
344 run_slice_success(setup, 20, 12, 0, &input_data, &correct_slice);
345 }
346
347 #[test]
348 fn test_partial_word_tolerates_garbage_padding() {
349 let setup = build_slice_check(3, 2);
352 let input_data = vec![
353 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
354 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
355 ];
356 let circuit = setup.builder.build();
357 let mut filler = circuit.new_witness_filler();
358 filler[setup.len_input] = Word(20);
359 filler[setup.len_slice] = Word(12);
360 filler[setup.offset] = Word(0);
361 filler.pack_bytes_le(&setup.input, &input_data);
362 filler[setup.expected[0]] = Word(0x0706050403020100);
364 filler[setup.expected[1]] = Word(0xffffffff0b0a0908);
365 circuit.populate_wire_witness(&mut filler).unwrap();
366 }
367
368 #[test]
369 fn test_large_offset_overflow() {
370 let setup = build_slice_check(2, 1);
372 let circuit = setup.builder.build();
373 let mut filler = circuit.new_witness_filler();
374 filler[setup.len_input] = Word(10);
375 filler[setup.len_slice] = Word(5);
376 filler[setup.offset] = Word(1u64 << 32);
377 filler.pack_bytes_le(&setup.input, &[0u8; 10]);
378 filler.pack_bytes_le(&setup.expected, &[0u8; 5]);
379 assert!(circuit.populate_wire_witness(&mut filler).is_err());
380 }
381
382 #[test]
383 fn test_32bit_validation() {
384 let setup = build_slice_check(2, 1);
386 let circuit = setup.builder.build();
387 let mut filler = circuit.new_witness_filler();
388 filler[setup.len_input] = Word(10);
389 filler[setup.len_slice] = Word(5);
390 filler[setup.offset] = Word(1u64 << 33);
391 filler.pack_bytes_le(&setup.input, &[0u8; 10]);
392 filler.pack_bytes_le(&setup.expected, &[0u8; 5]);
393 assert!(circuit.populate_wire_witness(&mut filler).is_err());
394
395 let setup = build_slice_check(2, 1);
397 let circuit = setup.builder.build();
398 let mut filler = circuit.new_witness_filler();
399 filler[setup.len_input] = Word(0xffffffff00000010);
400 filler[setup.len_slice] = Word(5);
401 filler[setup.offset] = Word(0);
402 filler.pack_bytes_le(&setup.input, &[0u8; 10]);
403 filler.pack_bytes_le(&setup.expected, &[0u8; 5]);
404 assert!(circuit.populate_wire_witness(&mut filler).is_err());
405
406 let setup = build_slice_check(2, 1);
408 let circuit = setup.builder.build();
409 let mut filler = circuit.new_witness_filler();
410 filler[setup.len_input] = Word(10);
411 filler[setup.len_slice] = Word(0x100000005);
412 filler[setup.offset] = Word(0);
413 filler.pack_bytes_le(&setup.input, &[0u8; 10]);
414 filler.pack_bytes_le(&setup.expected, &[0u8; 5]);
415 assert!(circuit.populate_wire_witness(&mut filler).is_err());
416 }
417
418 #[test]
419 fn test_edge_case_len_input_zero() {
420 let setup = build_slice_check(2, 1);
422 run_slice_success(setup, 0, 0, 0, &[], &[]);
423 }
424
425 #[test]
426 fn test_edge_case_len_input_zero_with_nonzero_slice() {
427 let setup = build_slice_check(2, 1);
429 run_slice_failure(setup, 0, 5, 0, &[], &[1, 2, 3, 4, 5]);
430 }
431
432 #[test]
433 fn test_padding_beyond_actual_data() {
434 let setup = build_slice_check(3, 2);
436 let input_data = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
437 let slice_data = vec![2, 3, 4, 5, 6, 7, 8, 9];
439 run_slice_success(setup, 12, 8, 2, &input_data, &slice_data);
440 }
441
442 #[test]
443 fn test_direct_masking_logic() {
444 let slice_word = Word(0xffffffff_0b0a0908);
446 let extracted_word = Word(0x00000000_0b0a0908);
447 let mask = Word(0x00000000_ffffffff);
448
449 let masked_slice = slice_word & mask;
450 let masked_extracted = extracted_word & mask;
451
452 assert_eq!(masked_slice, masked_extracted);
453 assert_eq!(masked_slice ^ masked_extracted, Word::ZERO);
454 }
455}