Skip to main content

midnight_circuits/parsing/
parser_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 std::{cmp::min, marker::PhantomData};
15
16use midnight_proofs::{circuit::Layouter, plonk::Error};
17use num_bigint::BigUint;
18#[cfg(any(test, feature = "testing"))]
19use {
20    crate::testing_utils::FromScratch,
21    midnight_proofs::plonk::{Advice, Column, ConstraintSystem, Fixed, Instance},
22};
23
24use crate::{
25    field::AssignedNative, instructions::NativeInstructions, types::AssignedByte, CircuitField,
26};
27
28#[derive(Clone, Debug)]
29/// A gadget for parsing json data. It is parametrized by:
30///  - F: the native field,
31///  - N: a set of in-circuit native instructions.
32pub struct ParserGadget<F, N>
33where
34    F: CircuitField,
35    N: NativeInstructions<F>,
36{
37    pub(crate) native_gadget: N,
38    _marker: PhantomData<F>,
39}
40
41impl<F, N> ParserGadget<F, N>
42where
43    F: CircuitField,
44    N: NativeInstructions<F>,
45{
46    /// Create a new parser gadget.
47    pub fn new(native_gadget: &N) -> Self {
48        Self {
49            native_gadget: native_gadget.clone(),
50            _marker: PhantomData,
51        }
52    }
53
54    /// Given a `sequence` of assigned native values, an index `idx`
55    /// (represented with an assigned native value) and a length `len`,
56    /// returns a vector of length `len` that is guaranteed to contain
57    /// consecutive values from `sequence` starting at `idx`. Namely:
58    /// `vec![sequence[idx], sequence[idx+1]..., sequence[idx+len-1]]`.
59    ///
60    /// This is enforced with constraints while keeping `idx` private.
61    ///
62    /// # Unsatisfiable Circuit
63    ///
64    /// If `idx` is not in the range `[0, |sequence| - len]`.
65    fn get_subsequence(
66        &self,
67        layouter: &mut impl Layouter<F>,
68        sequence: &[AssignedNative<F>],
69        idx: &AssignedNative<F>,
70        len: usize,
71    ) -> Result<Vec<AssignedNative<F>>, Error> {
72        let native = &self.native_gadget;
73        let n = sequence.len();
74        native.assert_lower_than_fixed(layouter, idx, &BigUint::from(n - len + 1))?;
75
76        let default = self.native_gadget.assign_fixed(layouter, F::default())?;
77        let mut output = vec![default; len];
78
79        for i in 0..=(n - len) {
80            let b = native.is_equal_to_fixed(layouter, idx, F::from(i as u64))?;
81            for j in 0..len {
82                output[j] = native.select(layouter, &b, &sequence[i + j], &output[j])?;
83            }
84        }
85
86        Ok(output)
87    }
88
89    /// Given a `sequence` of assigned bytes, an index `idx` (represented with
90    /// an assigned native value) and a length `len`, returns a vector of
91    /// length `len` that is guaranteed to contain consecutive bytes from
92    /// `sequence` starting at `idx`. Namely:
93    /// `vec![sequence[idx], sequence[idx+1]..., sequence[idx+len-1]]`.
94    ///
95    /// This is enforced with constraints while keeping `idx` private.
96    ///
97    /// # Unsatisfiable Circuit
98    ///
99    /// If `idx` is not in the range `[0, |sequence| - len]`.
100    pub fn fetch_bytes(
101        &self,
102        layouter: &mut impl Layouter<F>,
103        sequence: &[AssignedByte<F>],
104        idx: &AssignedNative<F>,
105        len: usize,
106    ) -> Result<Vec<AssignedByte<F>>, Error> {
107        let native = &self.native_gadget;
108        let n = sequence.len();
109        native.assert_lower_than_fixed(layouter, idx, &BigUint::from(n - len + 1))?;
110
111        // We will aggregate the bytes while they fit in a single native value.
112        // We then call [get_subsequence] on the aggregated values to get closer
113        // to the region of interest.
114        // Once there, we can expand to bytes again and perform a finer search
115        // over the bytes.
116
117        let nb_bytes_per_chunk = F::CAPACITY as usize / 8;
118        let nb_chunks = n.div_ceil(nb_bytes_per_chunk);
119
120        let mut chunks = Vec::with_capacity(nb_chunks + 1); // A dummy chunk will be added.
121        for chunk_bytes in sequence.chunks(nb_bytes_per_chunk) {
122            let chunk = native.assigned_from_le_bytes(layouter, chunk_bytes)?;
123            chunks.push(chunk);
124        }
125
126        // The idx will be split into chunk_idx and fine_search_idx, where:
127        //   * chunk_idx       := idx / nb_bytes_per_chunk
128        //   * fine_search_idx := idx % nb_bytes_per_chunk
129        //
130        let (chunk_idx, fine_search_idx) = native.div_rem(
131            layouter,
132            idx,
133            nb_bytes_per_chunk.into(),
134            Some((1u64 << 18).into()),
135        )?;
136
137        // Add 1 because the index of interest could be between 2 chunks, even if
138        // the length we are looking for fits in 1 chunk.
139        let len_for_chunks = min(nb_chunks, 1 + len.div_ceil(nb_bytes_per_chunk));
140
141        // Add a dummy chunk before the chunk search, to account for the +1 added to
142        // len_for_chunks. This dummy value will never be read, but it is necessary
143        // for the call to [get_subsequence] to work properly.
144        let dummy = native.assign_fixed(layouter, F::default())?;
145        chunks.push(dummy);
146
147        // The following is implicitly range-checking chunk_idx to be in the range
148        // [0, |chunks| - len_for_chunks]. Note that:
149        //   * |chunks|       := n.div_ceil(nb_bytes_per_chunk)
150        //   * len_for_chunks := min(|chunks|, 1 + len.div_ceil(nb_bytes_per_chunk))
151        //
152        // Thus the above range is equal to [0, 0] or
153        // [0, n.div_ceil(nb_bytes_per_chunk) - len.div_ceil(nb_bytes_per_chunk) - 1],
154        // which is equal or contained in the desired range:
155        // [0, (n - len) / nb_bytes_per_chunk].
156        let selected_chunks =
157            self.get_subsequence(layouter, &chunks, &chunk_idx, len_for_chunks)?;
158
159        // We now convert the selected chunks back to bytes in order to perform the
160        // finer search.
161
162        let mut selected_bytes = Vec::with_capacity(len_for_chunks * 8);
163        for chunk in selected_chunks.iter() {
164            let bytes = native.assigned_to_le_bytes(layouter, chunk, Some(nb_bytes_per_chunk))?;
165            selected_bytes.extend(bytes);
166        }
167
168        let bytes_as_native: Vec<AssignedNative<F>> =
169            selected_bytes.into_iter().map(|byte| byte.into()).collect();
170        let output = self.get_subsequence(layouter, &bytes_as_native, &fine_search_idx, len)?;
171
172        output
173            .iter()
174            .map(|x| native.convert_unsafe(layouter, x))
175            .collect::<Result<Vec<_>, Error>>()
176    }
177}
178
179#[cfg(any(test, feature = "testing"))]
180impl<F, N> FromScratch<F> for ParserGadget<F, N>
181where
182    F: CircuitField,
183    N: NativeInstructions<F> + FromScratch<F>,
184{
185    type Config = <N as FromScratch<F>>::Config;
186
187    fn new_from_scratch(config: &Self::Config) -> Self {
188        let native_gadget = <N as FromScratch<F>>::new_from_scratch(config);
189        ParserGadget::<F, N>::new(&native_gadget)
190    }
191
192    fn configure_from_scratch(
193        meta: &mut ConstraintSystem<F>,
194        advice_columns: &mut Vec<Column<Advice>>,
195        fixed_columns: &mut Vec<Column<Fixed>>,
196        instance_columns: &[Column<Instance>; 2],
197    ) -> Self::Config {
198        <N as FromScratch<F>>::configure_from_scratch(
199            meta,
200            advice_columns,
201            fixed_columns,
202            instance_columns,
203        )
204    }
205
206    fn load_from_scratch(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
207        self.native_gadget.load_from_scratch(layouter)
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use ff::FromUniformBytes;
214    use midnight_proofs::{
215        circuit::{SimpleFloorPlanner, Value},
216        dev::MockProver,
217        plonk::Circuit,
218    };
219
220    use super::*;
221    use crate::field::{decomposition::chip::P2RDecompositionChip, NativeChip, NativeGadget};
222
223    #[derive(Clone, Debug)]
224    enum Operation {
225        GetSubseq,
226        FetchBytes,
227    }
228
229    #[derive(Clone, Debug)]
230    struct TestCircuit<F, N> {
231        sequence: Vec<Value<F>>,
232        idx: Value<F>,
233        expected: Vec<F>,
234        operation: Operation,
235        _marker: PhantomData<N>,
236    }
237
238    impl<F, N> Circuit<F> for TestCircuit<F, N>
239    where
240        F: CircuitField,
241        N: NativeInstructions<F> + FromScratch<F>,
242    {
243        type Config = <N as FromScratch<F>>::Config;
244        type FloorPlanner = SimpleFloorPlanner;
245        type Params = ();
246
247        fn without_witnesses(&self) -> Self {
248            unreachable!()
249        }
250
251        fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
252            let committed_instance_column = meta.instance_column();
253            let instance_column = meta.instance_column();
254            <N as FromScratch<F>>::configure_from_scratch(
255                meta,
256                &mut vec![],
257                &mut vec![],
258                &[committed_instance_column, instance_column],
259            )
260        }
261
262        fn synthesize(
263            &self,
264            config: Self::Config,
265            mut layouter: impl Layouter<F>,
266        ) -> Result<(), Error> {
267            let native_gadget = <N as FromScratch<F>>::new_from_scratch(&config);
268            let parser_gadget = ParserGadget::<F, N>::new(&native_gadget);
269
270            let sequence = native_gadget.assign_many(&mut layouter, &self.sequence)?;
271            let idx = native_gadget.assign(&mut layouter, self.idx)?;
272            let len = self.expected.len();
273
274            let res = match self.operation {
275                Operation::GetSubseq => {
276                    parser_gadget.get_subsequence(&mut layouter, &sequence, &idx, len)
277                }
278                Operation::FetchBytes => {
279                    let bytes = sequence
280                        .iter()
281                        .map(|x| native_gadget.convert(&mut layouter, x))
282                        .collect::<Result<Vec<AssignedByte<F>>, Error>>()?;
283                    let fetched = parser_gadget.fetch_bytes(&mut layouter, &bytes, &idx, len)?;
284                    Ok(fetched.iter().map(|b| b.clone().into()).collect::<Vec<_>>())
285                }
286            }?;
287
288            assert_eq!(res.len(), len);
289            for (resulted, expected) in res.iter().zip(self.expected.iter()) {
290                native_gadget.assert_equal_to_fixed(&mut layouter, resulted, *expected)?;
291            }
292
293            native_gadget.load_from_scratch(&mut layouter)
294        }
295    }
296
297    fn run<F>(sequence: &[u8], idx: usize, expected: &[u8], operation: Operation, must_pass: bool)
298    where
299        F: CircuitField + FromUniformBytes<64> + Ord,
300    {
301        let circuit = TestCircuit::<F, NativeGadget<F, P2RDecompositionChip<F>, NativeChip<F>>> {
302            sequence: sequence.iter().map(|x| F::from(*x as u64)).map(Value::known).collect(),
303            idx: Value::known(F::from(idx as u64)),
304            expected: expected.iter().map(|x| F::from(*x as u64)).collect(),
305            operation,
306            _marker: PhantomData,
307        };
308        let public_inputs = vec![vec![], vec![]];
309        match MockProver::run(&circuit, public_inputs) {
310            Ok(prover) => match prover.verify() {
311                Ok(()) => assert!(must_pass),
312                Err(e) => assert!(!must_pass, "Failed verifier with error {e:?}"),
313            },
314            Err(e) => assert!(!must_pass, "Failed prover with error {e:?}"),
315        }
316    }
317
318    #[test]
319    fn test_get_subsequence() {
320        type F = midnight_curves::Fq;
321        [
322            (vec![1, 2, 3, 4, 5, 6], 0, vec![1, 2, 3], true),
323            (vec![1, 2, 3, 4, 5, 6], 1, vec![2, 3, 4, 5], true),
324            (vec![1, 2, 4, 8, 16], 3, vec![8, 16], true),
325            (vec![1, 2, 4, 8, 16], 3, vec![8], true),
326            (vec![1, 2, 4, 8, 16], 3, vec![], true),
327            (vec![1, 2, 4, 8, 16], 6, vec![], false),
328            (vec![1, 2, 4, 8, 16], 5, vec![0], false),
329            (vec![1, 2, 4, 8, 16], 4, vec![0, 0], false),
330            (vec![1, 2, 4, 8, 16], 4, vec![16], true),
331            (vec![3, 14, 15, 9, 26, 53, 58], 5, vec![26], false),
332            (vec![3, 14, 15, 9, 26, 53, 58], 5, vec![53], true),
333        ]
334        .iter()
335        .for_each(|(sequence, idx, expected, must_pass)| {
336            run::<F>(sequence, *idx, expected, Operation::GetSubseq, *must_pass)
337        });
338    }
339
340    #[test]
341    fn test_fetch_bytes() {
342        type F = midnight_curves::Fq;
343        let short = "L'essentiel est invisible pour les yeux".as_bytes();
344        let long: Vec<u8> = (0..=2000).map(|i| i as u8).collect();
345        [
346            (short, 0, "L".as_bytes(), true),
347            (short, 12, "est".as_bytes(), true),
348            (short, 26, "pour".as_bytes(), true),
349            (short, 27, "our les yeu".as_bytes(), true),
350            (short, 35, "yeu".as_bytes(), true),
351            (short, 35, "yeux".as_bytes(), true),
352            (short, 38, "x".as_bytes(), true),
353            (short, 38, "".as_bytes(), true),
354            (short, 39, "".as_bytes(), true),
355            (short, 40, "".as_bytes(), false),
356            (&long, 0, &[0, 1, 2, 3, 4], true),
357            (&long, 256, &[0, 1, 2], true),
358            (&long, 1000, &[232, 233], true),
359            (
360                &long,
361                1234,
362                &(0..30).map(|i| (1234 + i) as u8).collect::<Vec<_>>(),
363                true,
364            ),
365            (&long, 1995, &[203, 204, 205], true),
366            (&long, 1995, &[203, 204, 205, 206, 207], true),
367            (&long, 1996, &[204, 205, 206, 207, 208], true),
368            (&long, 1997, &[205, 206, 207, 208, 209], false),
369        ]
370        .iter()
371        .for_each(|(bytes, idx, expected, must_pass)| {
372            run::<F>(bytes, *idx, expected, Operation::FetchBytes, *must_pass)
373        });
374    }
375}