Skip to main content

midnight_circuits/vec/
vector_gadget.rs

1// This file is part of MIDNIGHT-ZK.
2// Copyright (C) Midnight Foundation
3// SPDX-License-Identifier: Apache-2.0
4// Licensed under the Apache License, Version 2.0 (the "License");
5// You may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7// http://www.apache.org/licenses/LICENSE-2.0
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14use midnight_proofs::{
15    circuit::{Layouter, Value},
16    plonk::Error,
17};
18use num_bigint::BigUint;
19
20use crate::{
21    field::{
22        decomposition::chip::P2RDecompositionChip, AssignedBounded, AssignedNative, NativeChip,
23        NativeGadget,
24    },
25    instructions::{
26        division::DivisionInstructions,
27        vector::{VectorBounds, VectorInstructions},
28        ArithInstructions, AssertionInstructions, AssignmentInstructions, BinaryInstructions,
29        ComparisonInstructions, ControlFlowInstructions, EqualityInstructions,
30        RangeCheckInstructions,
31    },
32    types::{AssignedBit, AssignedVector, InnerValue, Vectorizable},
33    vec::get_lims,
34    CircuitField,
35};
36
37type NG<F> = NativeGadget<F, P2RDecompositionChip<F>, NativeChip<F>>;
38
39#[derive(Clone, Debug)]
40/// A gadget for vector operations of elements that are or fit within a native
41/// field element:
42pub struct VectorGadget<F: CircuitField> {
43    native_gadget: NG<F>,
44}
45
46impl<F> VectorGadget<F>
47where
48    F: CircuitField,
49{
50    /// Create a new vector gadgets.
51    pub fn new(native_gadget: &NG<F>) -> Self {
52        Self {
53            native_gadget: native_gadget.clone(),
54        }
55    }
56}
57
58impl<F, T, const M: usize, const A: usize> VectorInstructions<F, T, M, A> for VectorGadget<F>
59where
60    F: CircuitField,
61    T: Vectorizable,
62    T::Element: Copy,
63    NG<F>: RangeCheckInstructions<F, AssignedNative<F>>
64        + AssignmentInstructions<F, T>
65        + AssignmentInstructions<F, AssignedNative<F>>
66        + AssignmentInstructions<F, AssignedBit<F>>
67        + EqualityInstructions<F, AssignedNative<F>>
68        + BinaryInstructions<F>
69        + ControlFlowInstructions<F, AssignedNative<F>>
70        + ControlFlowInstructions<F, T>
71        + DivisionInstructions<F, AssignedNative<F>>
72        + AssertionInstructions<F, AssignedBit<F>>
73        + ArithInstructions<F, AssignedNative<F>>,
74{
75    fn resize<const L: usize>(
76        &self,
77        layouter: &mut impl Layouter<F>,
78        input: AssignedVector<F, T, M, A>,
79    ) -> Result<AssignedVector<F, T, L, A>, Error> {
80        assert_eq!(L % A, 0);
81        assert!(L > M);
82
83        let extra_pad = self
84            .native_gadget
85            .assign_many(layouter, &vec![Value::known(T::FILLER); L - M])?;
86
87        let buffer: Box<[T; L]> =
88            Box::new([extra_pad.as_slice(), input.buffer.as_slice()].concat().try_into().unwrap());
89
90        Ok(AssignedVector {
91            buffer,
92            len: input.len.clone(),
93        })
94    }
95
96    fn assign_with_filler(
97        &self,
98        layouter: &mut impl Layouter<F>,
99        value: Value<Vec<T::Element>>,
100        filler: Option<T::Element>,
101    ) -> Result<AssignedVector<F, T, M, A>, Error> {
102        assert!(M >= A, "AssignedVector requires M >= A (got M={M}, A={A})");
103        assert!(A > 0, "AssignedVector requires A positive (A={A})");
104        assert!(
105            M.is_multiple_of(A),
106            "AssignedVector requires M % A == 0 (got M={M}, A={A})"
107        );
108        let ng = &self.native_gadget;
109        let filler = filler.unwrap_or(T::FILLER);
110        let (data_val, len_val) = value
111            .map(|v| {
112                // `v` needs to be at most `M - M % A`, i.e., `M` since we require above that it
113                // is a multiple of `A`.
114                assert!(v.len() <= M);
115                let len = F::from(v.len() as u64);
116                let mut buffer = [filler; M];
117                buffer[get_lims::<M, A>(v.len())].copy_from_slice(v.as_slice());
118                (buffer, len)
119            })
120            .unzip();
121
122        let data: Box<[T; M]> = Box::new(
123            ng.assign_many(layouter, &data_val.transpose_array())?
124                .try_into()
125                .expect("Length mismatch in AssignedVector."),
126        );
127        let len = ng.assign_lower_than_fixed(layouter, len_val, &(M + 1).into())?;
128        Ok(AssignedVector { buffer: data, len })
129    }
130
131    fn padding_flag(
132        &self,
133        layouter: &mut impl Layouter<F>,
134        input: &AssignedVector<F, T, M, A>,
135    ) -> Result<(Box<[AssignedBit<F>; M]>, VectorBounds<F>), Error> {
136        let ng = &self.native_gadget;
137        let limits = self.get_limits(layouter, input)?;
138        let (start, end) = &limits;
139        let mut is_data: AssignedBit<F> = ng.assign_fixed(layouter, true)?;
140
141        let result = (0..=M - A)
142            .map(|i| {
143                let is_start = ng.is_equal_to_fixed(layouter, start, F::from(i as u64))?;
144                is_data = ng.xor(layouter, &[is_data.clone(), is_start])?;
145                Ok(is_data.clone())
146            })
147            .collect::<Result<Vec<_>, Error>>()?;
148
149        let last_chunk = (M - A + 1..M)
150            .map(|i| {
151                let is_end = ng.is_equal_to_fixed(layouter, end, F::from(i as u64))?;
152                is_data = ng.xor(layouter, &[is_data.clone(), is_end])?;
153                Ok(is_data.clone())
154            })
155            .collect::<Result<Vec<_>, Error>>()?;
156
157        let flags: Box<[AssignedBit<F>; M]> =
158            Box::new([result, last_chunk].concat().try_into().expect("Mismatch in vector lengths"));
159        Ok((flags, limits))
160    }
161
162    fn get_limits(
163        &self,
164        layouter: &mut impl Layouter<F>,
165        input: &AssignedVector<F, T, M, A>,
166    ) -> Result<(AssignedNative<F>, AssignedNative<F>), Error> {
167        let ng = &self.native_gadget;
168        let end: AssignedNative<F> = {
169            // The last data position within the last chunk. Value in [0, A);
170            // 0 means the last chunk is full, all its positions are data.
171            let offset = ng.rem(layouter, &input.len, A.into(), Some(M.into()))?;
172
173            // if offset != 0.  End = M - (A - offset).
174            let end1 = ng.add_constant(layouter, &offset, F::from(M as u64 - A as u64))?;
175            // if offset == 0.  End = M - (A - offset) + A = M.
176            let end2 = ng.add_constant(layouter, &end1, F::from(A as u64))?;
177            let is_zero = ng.is_equal_to_fixed(layouter, &offset, F::ZERO)?;
178            ng.select(layouter, &is_zero, &end2, &end1)
179        }?;
180
181        // The index where the data starts.
182        let start: AssignedNative<F> = ng.sub(layouter, &end, &input.len)?;
183
184        Ok((start, end))
185    }
186
187    fn trim_beginning(
188        &self,
189        layouter: &mut impl Layouter<F>,
190        input: &AssignedVector<F, T, M, A>,
191        n_elems: usize,
192    ) -> Result<AssignedVector<F, T, M, A>, Error> {
193        let ng = &self.native_gadget;
194        let a_max_bits = (usize::BITS - A.leading_zeros()) as usize;
195
196        // Assert input.len >= n_elems.
197        let len_complement =
198            ng.linear_combination(layouter, &[(-F::ONE, input.len.clone())], F::from(M as u64))?;
199        ng.assert_lower_than_fixed(layouter, &len_complement, &BigUint::from(M + 1 - n_elems))?;
200
201        // We divide the number of elements to be trimmed in 2 parts.
202        // The A-sized whole chunks, one last <A sized piece.
203        // (1) The A-sized chunks won't modify the alignment, so modifying the value
204        // the vector length is enough to have them trimmed. They will remain
205        // in the buffer but they will be considered padding.
206        // (2) Trimming this last piece may require some realignment of the vector
207        // that ensures the padding at the end remains in [0, A).
208
209        let last_trim = n_elems % A;
210
211        // Length of last chunk ( or 0 if it is full ).
212        let last_len = ng.rem(layouter, &input.len, A.into(), Some(M.into()))?;
213
214        // `modulus` already ensures last_len is in [0, A), so unsafe conversion can be
215        // used here.
216        let bounded_last_len =
217            AssignedBounded::to_assigned_bounded_unsafe(&last_len, a_max_bits as u32);
218
219        // We need to shift right by A if the padding at the end after the left shift is
220        // >= A.
221        let needs_adjust = {
222            let leq_shift = ng.leq_fixed(layouter, &bounded_last_len, F::from(last_trim as u64))?;
223            let full_last = ng.is_equal_to_fixed(layouter, &last_len, F::ZERO)?;
224
225            // Since full_last = 1 => leq_shift = 1:
226            //     let not_full_last = ng.not(layouter, &full_last)?;
227            //     ng.and(layouter, &[not_full_last, leq_shift])
228            // A XOR operation is equivalent to the commented code above.
229            ng.xor(layouter, &[full_last, leq_shift])
230        }?;
231
232        // Shift the original buffer `last_trim` positions to the left.
233        // Then, add A filler elements to the left, in case we need to shift A to the
234        // right to adjust the padding at the end.
235        let buffer = {
236            let filler = ng.assign_many_fixed(layouter, &vec![T::FILLER; A + last_trim])?;
237            [&filler[..A], &input.buffer[last_trim..], &filler[A..]].concat()
238        };
239        debug_assert_eq!(buffer.len(), M + A);
240
241        let buffer: Box<[_; M]> = Box::new(
242            (0..M)
243                .map(|i| ng.select(layouter, &needs_adjust, &buffer[i], &buffer[A + i]))
244                .collect::<Result<Vec<_>, Error>>()?
245                .try_into()
246                .unwrap(),
247        );
248
249        // Compute final length.
250        let len = ng.add_constant(layouter, &input.len, -F::from(n_elems as u64))?;
251
252        Ok(AssignedVector { buffer, len })
253    }
254}
255
256impl<F, const M: usize, T, const A: usize> AssignmentInstructions<F, AssignedVector<F, T, M, A>>
257    for VectorGadget<F>
258where
259    F: CircuitField,
260    T: Vectorizable,
261    T::Element: Copy,
262    Self: VectorInstructions<F, T, M, A>,
263{
264    fn assign_fixed(
265        &self,
266        _layouter: &mut impl Layouter<F>,
267        _constant: <AssignedVector<F, T, M, A> as InnerValue>::Element,
268    ) -> Result<AssignedVector<F, T, M, A>, Error> {
269        unimplemented!("You should not be assigining a fixed `AssignedVector`")
270    }
271
272    fn assign(
273        &self,
274        layouter: &mut impl Layouter<F>,
275        value: Value<<AssignedVector<F, T, M, A> as InnerValue>::Element>,
276    ) -> Result<AssignedVector<F, T, M, A>, Error> {
277        self.assign_with_filler(layouter, value, None)
278    }
279}
280
281impl<F, const M: usize, T, const A: usize> EqualityInstructions<F, AssignedVector<F, T, M, A>>
282    for VectorGadget<F>
283where
284    F: CircuitField,
285    T: Vectorizable,
286    T::Element: Copy,
287    Self: VectorInstructions<F, T, M, A>,
288    NG<F>: ArithInstructions<F, AssignedNative<F>>
289        + EqualityInstructions<F, T>
290        + EqualityInstructions<F, AssignedNative<F>>
291        + BinaryInstructions<F>,
292{
293    fn is_equal(
294        &self,
295        layouter: &mut impl Layouter<F>,
296        x: &AssignedVector<F, T, M, A>,
297        y: &AssignedVector<F, T, M, A>,
298    ) -> Result<AssignedBit<F>, Error> {
299        let ng = &self.native_gadget;
300        // Check all data values are equal.
301        let val_checks = self
302            .padding_flag(layouter, x)?
303            .0
304            .into_iter()
305            .zip(x.buffer.iter().zip(y.buffer.iter()))
306            .map(|(is_padding, (a, b))| {
307                let a_eq_b = ng.is_equal(layouter, a, b)?;
308                ng.or(layouter, &[is_padding, a_eq_b])
309            })
310            .collect::<Result<Vec<_>, Error>>()?;
311
312        // Check lengths are equal.
313        let len_check = ng.is_equal(layouter, &x.len, &y.len)?;
314
315        ng.and(layouter, &[val_checks.as_slice(), &[len_check]].concat())
316    }
317
318    fn is_not_equal(
319        &self,
320        layouter: &mut impl Layouter<F>,
321        x: &AssignedVector<F, T, M, A>,
322        y: &AssignedVector<F, T, M, A>,
323    ) -> Result<AssignedBit<F>, Error> {
324        let b = self.is_equal(layouter, x, y)?;
325        self.native_gadget.not(layouter, &b)
326    }
327
328    fn is_equal_to_fixed(
329        &self,
330        layouter: &mut impl Layouter<F>,
331        x: &AssignedVector<F, T, M, A>,
332        constant: Vec<T::Element>,
333    ) -> Result<AssignedBit<F>, Error> {
334        let ng = &self.native_gadget;
335        let ct_len = constant.len();
336
337        let eq_len = ng.is_equal_to_fixed(layouter, &x.len, F::from(ct_len as u64))?;
338
339        let mut element_checks = x.buffer[get_lims::<M, A>(ct_len)]
340            .iter()
341            .zip(constant.iter())
342            .map(|(a, c)| ng.is_equal_to_fixed(layouter, a, *c))
343            .collect::<Result<Vec<_>, Error>>()?;
344        element_checks.push(eq_len);
345
346        ng.and(layouter, &element_checks)
347    }
348
349    fn is_not_equal_to_fixed(
350        &self,
351        layouter: &mut impl Layouter<F>,
352        x: &AssignedVector<F, T, M, A>,
353        constant: Vec<T::Element>,
354    ) -> Result<AssignedBit<F>, Error> {
355        let b = self.is_equal_to_fixed(layouter, x, constant)?;
356        self.native_gadget.not(layouter, &b)
357    }
358}
359
360impl<F, T, const M: usize, const A: usize> AssertionInstructions<F, AssignedVector<F, T, M, A>>
361    for VectorGadget<F>
362where
363    F: CircuitField,
364    T: Vectorizable,
365    T::Element: Copy,
366    Self: VectorInstructions<F, T, M, A> + EqualityInstructions<F, AssignedVector<F, T, M, A>>,
367    NG<F>: ArithInstructions<F, AssignedNative<F>>
368        + EqualityInstructions<F, T>
369        + EqualityInstructions<F, AssignedNative<F>>
370        + AssertionInstructions<F, AssignedBit<F>>
371        + AssertionInstructions<F, T>
372        + BinaryInstructions<F>,
373{
374    fn assert_equal(
375        &self,
376        layouter: &mut impl Layouter<F>,
377        x: &AssignedVector<F, T, M, A>,
378        y: &AssignedVector<F, T, M, A>,
379    ) -> Result<(), Error> {
380        let is_equal = self.is_equal(layouter, x, y)?;
381        self.native_gadget.assert_equal_to_fixed(layouter, &is_equal, true)
382    }
383
384    fn assert_not_equal(
385        &self,
386        layouter: &mut impl Layouter<F>,
387        x: &AssignedVector<F, T, M, A>,
388        y: &AssignedVector<F, T, M, A>,
389    ) -> Result<(), Error> {
390        let x_eq_y = self.is_equal(layouter, x, y)?;
391        self.native_gadget.assert_equal_to_fixed(layouter, &x_eq_y, false)
392    }
393
394    fn assert_equal_to_fixed(
395        &self,
396        layouter: &mut impl Layouter<F>,
397        x: &AssignedVector<F, T, M, A>,
398        constant: <AssignedVector<F, T, M, A> as InnerValue>::Element,
399    ) -> Result<(), Error> {
400        let ng = &self.native_gadget;
401        let ct_len = constant.len();
402        ng.assert_equal_to_fixed(layouter, &x.len, F::from(ct_len as u64))?;
403
404        x.buffer[get_lims::<M, A>(ct_len)]
405            .iter()
406            .zip(constant.iter())
407            .map(|(a, c)| ng.assert_equal_to_fixed(layouter, a, *c))
408            .collect::<Result<Vec<()>, Error>>()?;
409        Ok(())
410    }
411
412    fn assert_not_equal_to_fixed(
413        &self,
414        layouter: &mut impl Layouter<F>,
415        x: &AssignedVector<F, T, M, A>,
416        constant: <AssignedVector<F, T, M, A> as InnerValue>::Element,
417    ) -> Result<(), Error> {
418        let is_equal = self.is_equal_to_fixed(layouter, x, constant)?;
419        self.native_gadget.assert_equal_to_fixed(layouter, &is_equal, false)
420    }
421}
422
423#[cfg(any(test, feature = "testing"))]
424use midnight_proofs::plonk::{Advice, Column, ConstraintSystem, Fixed, Instance};
425
426#[cfg(any(test, feature = "testing"))]
427use crate::testing_utils::FromScratch;
428
429#[cfg(any(test, feature = "testing"))]
430impl<F: CircuitField> FromScratch<F> for VectorGadget<F> {
431    type Config = <NG<F> as FromScratch<F>>::Config;
432
433    fn new_from_scratch(config: &Self::Config) -> Self {
434        Self {
435            native_gadget: <NG<F> as FromScratch<F>>::new_from_scratch(config),
436        }
437    }
438
439    fn configure_from_scratch(
440        meta: &mut ConstraintSystem<F>,
441        advice_columns: &mut Vec<Column<Advice>>,
442        fixed_columns: &mut Vec<Column<Fixed>>,
443        instance_columns: &[Column<Instance>; 2],
444    ) -> Self::Config {
445        <NG<F>>::configure_from_scratch(meta, advice_columns, fixed_columns, instance_columns)
446    }
447
448    fn load_from_scratch(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
449        self.native_gadget.load_from_scratch(layouter)
450    }
451}
452
453#[cfg(test)]
454mod tests {
455    use ff::{Field, FromUniformBytes};
456    use midnight_proofs::{
457        circuit::{Layouter, SimpleFloorPlanner, Value},
458        dev::MockProver,
459        plonk::{Circuit, ConstraintSystem},
460    };
461    use rand_chacha::{rand_core::SeedableRng, ChaCha12Rng};
462
463    use super::*;
464    use crate::{
465        field::{
466            decomposition::chip::{P2RDecompositionChip, P2RDecompositionConfig},
467            AssignedNative, NativeChip, NativeGadget,
468        },
469        testing_utils::FromScratch,
470        utils::circuit_modeling::{circuit_to_json, cost_measure_end, cost_measure_start},
471    };
472
473    struct TestCircuit<F: CircuitField, const M: usize, const A: usize> {
474        input_1: Value<Vec<F>>,
475        input_2: Vec<F>, // We don't use value here in order to easily mutate the padding.
476        opts: TestOpts,
477    }
478
479    enum TestOpts {
480        // Tests vector equality.
481        Eq { mutate_padding: bool, equal: bool },
482        // Test data limit (indices) on a vector.
483        Limits,
484        // Test padding_flag instruction.
485        Padding,
486        // Test trim.
487        Trim { trim_size: usize },
488    }
489
490    type NG<F> = NativeGadget<F, P2RDecompositionChip<F>, NativeChip<F>>;
491
492    impl<F: CircuitField, const M: usize, const A: usize> Circuit<F> for TestCircuit<F, M, A> {
493        type Config = P2RDecompositionConfig;
494
495        type FloorPlanner = SimpleFloorPlanner;
496
497        type Params = ();
498
499        fn without_witnesses(&self) -> Self {
500            unreachable!();
501        }
502
503        fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
504            let comm_ic = meta.instance_column();
505            let instance_column = meta.instance_column();
506            NativeGadget::configure_from_scratch(
507                meta,
508                &mut vec![],
509                &mut vec![],
510                &[comm_ic, instance_column],
511            )
512        }
513
514        fn synthesize(
515            &self,
516            config: Self::Config,
517            mut layouter: impl Layouter<F>,
518        ) -> Result<(), Error> {
519            let ng = NG::<F>::new_from_scratch(&config);
520            let vg = VectorGadget::new(&ng);
521
522            cost_measure_start(&mut layouter);
523            match self.opts {
524                TestOpts::Eq {
525                    mutate_padding,
526                    equal,
527                } => {
528                    let vec_1: AssignedVector<F, AssignedNative<F>, M, A> =
529                        vg.assign(&mut layouter, self.input_1.clone())?;
530
531                    let mut vec_2: AssignedVector<F, AssignedNative<F>, M, A> =
532                        vg.assign(&mut layouter, Value::known(self.input_2.clone()))?;
533
534                    // Mutate padding
535                    if mutate_padding {
536                        let range = get_lims::<M, A>(self.input_2.len());
537                        for i in 0..range.start {
538                            vec_2.buffer[i] =
539                                ng.add_constant(&mut layouter, &vec_2.buffer[i], F::ONE)?;
540                        }
541                        for i in range.end..M {
542                            vec_2.buffer[i] =
543                                ng.add_constant(&mut layouter, &vec_2.buffer[i], F::ONE)?;
544                        }
545                    }
546
547                    let check = vg.is_equal(&mut layouter, &vec_1, &vec_2)?;
548
549                    ng.assert_equal_to_fixed(&mut layouter, &check, equal)?;
550                }
551                TestOpts::Limits => {
552                    let vec_1: AssignedVector<F, AssignedNative<F>, M, A> =
553                        vg.assign(&mut layouter, self.input_1.clone())?;
554
555                    let limits = vg.get_limits(&mut layouter, &vec_1)?;
556                    let (start, end) = vec_1
557                        .len
558                        .value()
559                        .map(|l| {
560                            let len: usize = l.to_biguint().try_into().unwrap();
561                            let range = get_lims::<M, A>(len);
562                            (F::from(range.start as u64), F::from(range.end as u64))
563                        })
564                        .unzip();
565                    let start = ng.assign(&mut layouter, start)?;
566                    let end = ng.assign(&mut layouter, end)?;
567                    ng.assert_equal(&mut layouter, &limits.0, &start)?;
568                    ng.assert_equal(&mut layouter, &limits.1, &end)?;
569                }
570
571                TestOpts::Padding => {
572                    let vec_1: AssignedVector<F, AssignedNative<F>, M, A> =
573                        vg.assign(&mut layouter, self.input_1.clone())?;
574
575                    let expected: [Value<bool>; M] = vec_1
576                        .len
577                        .value()
578                        .map(|l| {
579                            let len: usize = l.to_biguint().try_into().unwrap();
580                            let range = get_lims::<M, A>(len);
581                            let mut result = vec![true; M];
582                            result[range].iter_mut().for_each(|r| {
583                                *r = false;
584                            });
585                            result.try_into().unwrap()
586                        })
587                        .transpose_array();
588
589                    let (result, _limits) = vg.padding_flag(&mut layouter, &vec_1)?;
590
591                    for (r, e) in result.iter().zip(expected.iter()) {
592                        let e: AssignedBit<F> = ng.assign(&mut layouter, *e)?;
593                        ng.assert_equal(&mut layouter, &e, r)?;
594                    }
595                }
596
597                TestOpts::Trim { trim_size: n_elems } => {
598                    let vec_1: AssignedVector<F, AssignedNative<F>, M, A> =
599                        vg.assign(&mut layouter, self.input_1.clone())?;
600
601                    let result = vg.trim_beginning(&mut layouter, &vec_1, n_elems)?;
602
603                    vg.assert_equal_to_fixed(&mut layouter, &result, self.input_2.clone())?;
604                }
605            }
606            cost_measure_end(&mut layouter);
607
608            ng.load_from_scratch(&mut layouter)
609        }
610    }
611
612    fn run_eq_vec_test<F, const M: usize, const A: usize>(
613        input_1: &[F],
614        input_2: &[F],
615        equal: bool,
616        mutate_padding: bool,
617        cost_model: bool,
618    ) where
619        F: CircuitField + FromUniformBytes<64> + Ord,
620    {
621        let circuit = TestCircuit::<F, M, A> {
622            input_1: Value::known(input_1.to_vec()),
623            input_2: input_2.to_vec(),
624            opts: TestOpts::Eq {
625                equal,
626                mutate_padding,
627            },
628        };
629
630        MockProver::run(&circuit, vec![vec![], vec![]]).unwrap().assert_satisfied();
631
632        if cost_model {
633            circuit_to_json(
634                "Vector equality",
635                format!("Vector equality check with M={M}").as_str(),
636                circuit,
637            );
638        }
639    }
640
641    fn run_limit_vec_test<F, const M: usize, const A: usize>(input_1: &[F], cost_model: bool)
642    where
643        F: CircuitField + FromUniformBytes<64> + Ord,
644    {
645        let circuit = TestCircuit::<F, M, A> {
646            input_1: Value::known(input_1.to_vec()),
647            input_2: vec![],
648            opts: TestOpts::Limits,
649        };
650
651        MockProver::run(&circuit, vec![vec![], vec![]]).unwrap().assert_satisfied();
652
653        if cost_model {
654            circuit_to_json(
655                "Vector limits check",
656                format!("Vector limit check with M={M}").as_str(),
657                circuit,
658            );
659        }
660    }
661
662    fn run_padding_flags_test<F, const M: usize, const A: usize>(input_1: &[F], cost_model: bool)
663    where
664        F: CircuitField + FromUniformBytes<64> + Ord,
665    {
666        let circuit = TestCircuit::<F, M, A> {
667            input_1: Value::known(input_1.to_vec()),
668            input_2: vec![],
669            opts: TestOpts::Padding,
670        };
671
672        MockProver::run(&circuit, vec![vec![], vec![]]).unwrap().assert_satisfied();
673
674        if cost_model {
675            circuit_to_json(
676                "Vector padding flags.",
677                format!("Vector padding flags with M={M}").as_str(),
678                circuit,
679            );
680        }
681    }
682
683    fn run_trim_vec_test<F, const M: usize, const A: usize>(
684        input_1: &[F],
685        trim_size: usize,
686        cost_model: bool,
687    ) where
688        F: CircuitField + FromUniformBytes<64> + Ord,
689    {
690        let input = input_1.to_vec();
691        assert!(trim_size <= input.len());
692        let circuit = TestCircuit::<F, M, A> {
693            input_1: Value::known(input.clone()),
694            input_2: input[trim_size..].to_vec(),
695            opts: TestOpts::Trim { trim_size },
696        };
697
698        MockProver::run(&circuit, vec![vec![], vec![]]).unwrap().assert_satisfied();
699
700        if cost_model {
701            circuit_to_json(
702                "Vector trim beginning.",
703                format!("Vector trim_beginning with M={M}").as_str(),
704                circuit,
705            );
706        }
707    }
708
709    #[test]
710    fn vector_eq() {
711        type F = midnight_curves::Fq;
712
713        // Create a random number generator
714        let mut rng = ChaCha12Rng::seed_from_u64(0xdeadcafe);
715        let inputs = (0..100).map(|_| F::random(&mut rng)).collect::<Vec<_>>();
716
717        // Equal vectors, different padding.
718        run_eq_vec_test::<_, 128, 2>(&inputs, &inputs, true, true, true);
719        run_eq_vec_test::<_, 126, 3>(&inputs, &inputs, true, true, false);
720
721        // Equal vectors, equal padding.
722        run_eq_vec_test::<_, 128, 2>(&inputs, &inputs, true, false, false);
723
724        // Equal data, different length.
725        run_eq_vec_test::<_, 128, 2>(&inputs[..80], &inputs[..81], false, false, false);
726
727        // Different data.
728        run_eq_vec_test::<_, 128, 2>(
729            &[&[F::ZERO], &inputs[..80]].concat(),
730            &[&[F::ONE], &inputs[..80]].concat(),
731            false,
732            false,
733            false,
734        );
735    }
736
737    #[test]
738    fn vector_limits() {
739        type F = midnight_curves::Fq;
740
741        // Create a random number generator
742        let mut rng = ChaCha12Rng::seed_from_u64(0xdeadcafe);
743        let inputs = (0..100).map(|_| F::random(&mut rng)).collect::<Vec<_>>();
744
745        // Test different alignments.
746        run_limit_vec_test::<_, 128, 1>(&inputs, true);
747        run_limit_vec_test::<_, 128, 2>(&inputs, false);
748        run_limit_vec_test::<_, 126, 3>(&inputs, false);
749        run_limit_vec_test::<_, 128, 4>(&inputs, false);
750        run_limit_vec_test::<_, 130, 5>(&inputs, false);
751
752        // Test edge cases.
753        run_limit_vec_test::<_, 64, 2>(&inputs[..64], false);
754        run_limit_vec_test::<F, 64, 2>(&[], false);
755    }
756
757    #[test]
758    fn vector_padding_flags() {
759        type F = midnight_curves::Fq;
760
761        // Create a random number generator
762        let mut rng = ChaCha12Rng::seed_from_u64(0xdeadcafe);
763        let inputs = (0..100).map(|_| F::random(&mut rng)).collect::<Vec<_>>();
764
765        run_padding_flags_test::<_, 128, 1>(&inputs, true);
766        run_padding_flags_test::<_, 128, 2>(&inputs, false);
767        run_padding_flags_test::<_, 126, 3>(&inputs, false);
768        run_padding_flags_test::<_, 128, 64>(&inputs, false);
769        run_padding_flags_test::<F, 128, 64>(&[], false);
770        run_padding_flags_test::<F, 64, 16>(&inputs[..64], false);
771    }
772
773    #[test]
774    fn vector_trim_beginning() {
775        type F = midnight_curves::Fq;
776
777        // Create a random number generator
778        let mut rng = ChaCha12Rng::seed_from_u64(0xdeadcafe);
779        let inputs = (0..100).map(|_| F::random(&mut rng)).collect::<Vec<_>>();
780
781        // Test different alignments (under A).
782        run_trim_vec_test::<_, 128, 64>(&[F::ONE, F::ONE], 1, true);
783        run_trim_vec_test::<_, 128, 32>(&inputs, 0, false);
784        run_trim_vec_test::<_, 128, 32>(&inputs, 1, false);
785        run_trim_vec_test::<_, 128, 32>(&inputs, 2, false);
786        run_trim_vec_test::<_, 128, 32>(&inputs, 3, false);
787        run_trim_vec_test::<_, 128, 32>(&inputs, 4, false);
788        run_trim_vec_test::<_, 128, 32>(&inputs, 5, false);
789        run_trim_vec_test::<_, 128, 32>(&inputs, 30, false);
790        run_trim_vec_test::<_, 128, 32>(&inputs, 31, false);
791
792        // Above or equal to A.
793        run_trim_vec_test::<_, 126, 3>(&inputs, 3, false);
794        run_trim_vec_test::<_, 126, 3>(&inputs, 4, false);
795        run_trim_vec_test::<_, 126, 3>(&inputs, 5, false);
796        run_trim_vec_test::<_, 126, 3>(&inputs, 6, false);
797        run_trim_vec_test::<_, 126, 3>(&inputs, 10, false);
798        run_trim_vec_test::<_, 126, 3>(&inputs, 20, false);
799        run_trim_vec_test::<_, 126, 3>(&inputs, 30, false);
800        run_trim_vec_test::<_, 126, 3>(&inputs, 40, false);
801
802        // Edge case: offset of original vector = 0;
803        run_trim_vec_test::<_, 128, 32>(&inputs[..96], 23, false);
804
805        // Edge case: full vector;
806        run_trim_vec_test::<_, 64, 32>(&inputs[..64], 20, false);
807
808        // Edge case: full vector, trim all elements.
809        run_trim_vec_test::<_, 64, 32>(&inputs[..64], 64, false);
810
811        // The particular case of the credentials:
812        run_trim_vec_test::<_, 128, 64>(&inputs, 39, false);
813    }
814}