Skip to main content

midnight_circuits/parsing/
base64_chip.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
14//! This module contains the `Base64Chip`, which implements Base64 decoding
15//! instructions.
16//!
17//! In particular, this chip will be used to decode credentials. As such, it is
18//! assumed that the input data is signed by a trusted party and therefore it is
19//! well formed. This means that the decoding instructions do not enforce the
20//! validity of the base64 input (i.e. the padding format).
21//!
22//! The instructions ensure that inputs that agree with the base64 specification
23//! decode correctly.
24
25use midnight_proofs::{
26    circuit::{Chip, Layouter, Value},
27    plonk::{Advice, Column, Error, Expression, Selector, TableColumn},
28    poly::Rotation,
29};
30
31use crate::{
32    field::{decomposition::chip::P2RDecompositionChip, AssignedNative, NativeChip, NativeGadget},
33    instructions::{
34        base64::{Base64VarInstructions, Base64Vec},
35        ArithInstructions, AssignmentInstructions, Base64Instructions, ControlFlowInstructions,
36        DecompositionInstructions, EqualityInstructions, RangeCheckInstructions,
37        VectorInstructions, ZeroInstructions,
38    },
39    types::{AssignedByte, AssignedVector, InnerValue},
40    utils::ComposableChip,
41    vec::vector_gadget::VectorGadget,
42    CircuitField,
43};
44
45/// Number of advice columns in [Base64Chip].
46pub const NB_BASE64_ADVICE_COLS: usize = 4;
47
48#[derive(Clone, Debug)]
49/// Config for Base64Chip.
50pub struct Base64Config {
51    advice_cols: [Column<Advice>; NB_BASE64_ADVICE_COLS],
52
53    lookup_sel: Selector,
54    // Base64 table.
55    t_char: TableColumn,
56    t_val: TableColumn,
57}
58
59// Native gadget type abbreviation.
60type NG<F> = NativeGadget<F, P2RDecompositionChip<F>, NativeChip<F>>;
61
62// Contains 4 Base64 characters as bytes.
63type B64Chunk<F> = [AssignedByte<F>; 4];
64
65// Contains 4 ASCII characters as bytes.
66type AsciiChunk<F> = [AssignedByte<F>; 3];
67
68// Table padding for the lookup. This is different from the base64 padding '='.
69// It needs to agree with the value of ASCII_ZERO.
70pub(crate) const ALT_PAD: char = super::table::BASE64_TABLE[0].0;
71
72#[cfg(test)]
73const ASCII_ZERO: char = super::table::BASE64_TABLE[0].1 as char;
74
75const B64_PAD: char = '=';
76
77#[derive(Debug, Clone)]
78/// Base64Chip capable of decoding base64 encoded strings.
79pub struct Base64Chip<F>
80where
81    F: CircuitField,
82{
83    config: Base64Config,
84    vector_gadget: VectorGadget<F>,
85    native_gadget: NG<F>,
86}
87
88impl<F: CircuitField> Base64Instructions<F> for Base64Chip<F> {
89    fn decode_base64url(
90        &self,
91        layouter: &mut impl Layouter<F>,
92        b64url_input: &[AssignedByte<F>],
93        padded: bool,
94    ) -> Result<Vec<AssignedByte<F>>, Error> {
95        let standard_b64 = self.url_to_standard(layouter, b64url_input)?;
96        self.decode_base64(layouter, &standard_b64, padded)
97    }
98
99    fn decode_base64(
100        &self,
101        layouter: &mut impl Layouter<F>,
102        b64_input: &[AssignedByte<F>],
103        padded: bool,
104    ) -> Result<Vec<AssignedByte<F>>, Error> {
105        debug_assert!(
106            b64_input.len().is_multiple_of(4) || !padded,
107            "If pad is selected, the Base64 encoded input length must be a multiple of 4."
108        );
109        let mut last_chunk: B64Chunk<F>;
110        let mut result = Vec::with_capacity(b64_input.len().div_ceil(4) * 3);
111        let mut chunk_iter = b64_input.chunks(4).peekable();
112        while let Some(b64_chunk) = chunk_iter.next() {
113            let chunk_array: &B64Chunk<F> = if chunk_iter.peek().is_none() {
114                last_chunk = if padded {
115                    self.process_padded_chunk(
116                        layouter,
117                        b64_chunk.try_into().expect("Chunk of length 4."),
118                    )?
119                } else {
120                    self.pad(layouter, b64_chunk)?
121                };
122                &last_chunk
123            } else {
124                b64_chunk.try_into().expect("Chunk of length 4")
125            };
126            let values = self.base64_to_val_chunk(layouter, chunk_array)?;
127            let ascii_result = self.val_to_ascii_chunk(layouter, &values)?;
128            result.append(&mut ascii_result.to_vec())
129        }
130
131        Ok(result)
132    }
133}
134
135impl<F: CircuitField, const M: usize, const A: usize> Base64VarInstructions<F, M, A>
136    for Base64Chip<F>
137{
138    fn assign_var_base64(
139        &self,
140        layouter: &mut impl Layouter<F>,
141        value: Value<Vec<u8>>,
142    ) -> Result<Base64Vec<F, M, A>, Error> {
143        let ng = &self.native_gadget;
144
145        let vec =
146            self.vector_gadget
147                .assign_with_filler(layouter, value.clone(), Some(ALT_PAD as u8))?;
148
149        //  A base64 string length must be multiple of 4.
150        let q = {
151            let q = value.map(|v| {
152                assert_eq!(v.len() % 4, 0);
153                F::from(v.len() as u64 / 4)
154            });
155            ng.assign_lower_than_fixed(layouter, q, &((M / 4 + 1) as u128).into())?
156        };
157
158        let check = ng.linear_combination(
159            layouter,
160            &[(F::from(4u64), q), (-F::ONE, vec.len.clone())],
161            F::ZERO,
162        )?;
163        ng.assert_zero(layouter, &check)?;
164
165        Ok(Base64Vec(vec))
166    }
167
168    fn base64_from_vec(
169        &self,
170        layouter: &mut impl Layouter<F>,
171        vec: &AssignedVector<F, AssignedByte<F>, M, A>,
172    ) -> Result<Base64Vec<F, M, A>, Error> {
173        let ng = &self.native_gadget;
174        let vg = &self.vector_gadget;
175        let filler = ng.assign_fixed(layouter, ALT_PAD as u8)?;
176        let (flags, _limits) = vg.padding_flag(layouter, vec)?;
177        let result = vec
178            .buffer
179            .iter()
180            .zip(flags.iter())
181            .map(|(elem, is_padding)| ng.select(layouter, is_padding, &filler, elem))
182            .collect::<Result<Vec<_>, Error>>()?
183            .try_into()
184            .unwrap();
185        Ok(Base64Vec(AssignedVector {
186            buffer: result,
187            len: vec.len.clone(),
188        }))
189    }
190
191    fn var_decode_base64url<const M_OUT: usize, const A_OUT: usize>(
192        &self,
193        layouter: &mut impl Layouter<F>,
194        b64url_input: &Base64Vec<F, M, A>,
195    ) -> Result<AssignedVector<F, AssignedByte<F>, M_OUT, A_OUT>, Error> {
196        let vec = self.url_to_standard(layouter, &*b64url_input.0.buffer)?;
197
198        let b64_input = Base64Vec::<F, M, A>(AssignedVector {
199            buffer: Box::new(vec.try_into().unwrap()),
200            len: b64url_input.0.len.clone(),
201        });
202
203        self.var_decode_base64(layouter, &b64_input)
204    }
205
206    fn var_decode_base64<const M_OUT: usize, const A_OUT: usize>(
207        &self,
208        layouter: &mut impl Layouter<F>,
209        b64_input: &Base64Vec<F, M, A>,
210    ) -> Result<AssignedVector<F, AssignedByte<F>, M_OUT, A_OUT>, Error> {
211        // Assert correct capacity.
212        // This is critical! We are decoding the whole buffer, so we must
213        // be certain that the actual data is correctly aligned.
214        assert_eq!(A % 4, 0);
215        assert_eq!(M * 3, M_OUT * 4);
216        assert_eq!(A * 3, A_OUT * 4);
217
218        // Compute and constrain new length.
219        let three = F::from(3u64);
220        let four = F::from(4u64);
221        let len = &b64_input.0.len;
222
223        let new_len: AssignedNative<F> = {
224            let len_value = len.value().map(|&l| l * four.invert().unwrap() * three);
225            self.native_gadget.assign(layouter, len_value)?
226        };
227
228        let check = self.native_gadget.linear_combination(
229            layouter,
230            &[(four, new_len.clone()), (-three, len.clone())],
231            F::ZERO,
232        )?;
233        self.native_gadget.assert_zero(layouter, &check)?;
234
235        // Compute decoded buffer.
236        let out_buffer = self.decode_base64(layouter, &*b64_input.0.buffer, true)?;
237
238        Ok(AssignedVector::<_, _, M_OUT, A_OUT> {
239            buffer: Box::new(out_buffer.try_into().unwrap()),
240            len: new_len,
241        })
242    }
243}
244
245impl<F: CircuitField> Base64Chip<F> {
246    /// Converts a Base64URL encoded strig into a Base64 sstring.
247    /// It does so by substituting '-' for '+' and '_' for '/',
248    /// leaving the rest of characters unchanged.
249    fn url_to_standard(
250        &self,
251        layouter: &mut impl Layouter<F>,
252        b64url_input: &[AssignedByte<F>],
253    ) -> Result<Vec<AssignedByte<F>>, Error> {
254        let ng = &self.native_gadget;
255        let plus: AssignedByte<F> = ng.assign_fixed(layouter, b'+')?;
256        let slash = ng.assign_fixed(layouter, b'/')?;
257
258        b64url_input
259            .iter()
260            .map(|char| {
261                let is_hyphen = ng.is_equal_to_fixed(layouter, char, b'-')?;
262                let char = self.native_gadget.select(layouter, &is_hyphen, &plus, char)?;
263
264                let is_underscore = ng.is_equal_to_fixed(layouter, &char, b'_')?;
265
266                self.native_gadget.select(layouter, &is_underscore, &slash, &char)
267            })
268            .collect::<Result<Vec<_>, _>>()
269    }
270
271    /// Process the last chunk, which may have 0, 1 or 2 characters of padding.
272    /// The padding characters, if present, are removed and substituted by
273    /// ALT_PAD, the base_64 character that will result in the ASCII_ZERO
274    /// character.
275    fn process_padded_chunk(
276        &self,
277        layouter: &mut impl Layouter<F>,
278        b64_input: &B64Chunk<F>,
279    ) -> Result<B64Chunk<F>, Error> {
280        let ng = &self.native_gadget;
281
282        let pad = ng.assign_fixed(layouter, ALT_PAD as u8)?;
283        let pad_in_3rd = ng.is_equal_to_fixed(layouter, &b64_input[2], B64_PAD as u8)?;
284        let pad_in_4th = ng.is_equal_to_fixed(layouter, &b64_input[3], B64_PAD as u8)?;
285
286        // When the first character of padding is detected, the next must be padding as
287        // well. This disallows padddings such as '=A' or '=?'.
288        ng.cond_assert_equal(layouter, &pad_in_3rd, &pad_in_3rd, &pad_in_4th)?;
289
290        Ok([
291            b64_input[0].clone(),
292            b64_input[1].clone(),
293            ng.select(layouter, &pad_in_3rd, &pad, &b64_input[2])?,
294            ng.select(layouter, &pad_in_4th, &pad, &b64_input[3])?,
295        ])
296    }
297
298    /// Receives an incomplete chunk and returns it filled with circuit padding
299    /// until it reaches full chunk length: 4.
300    fn pad(
301        &self,
302        layouter: &mut impl Layouter<F>,
303        b64_input: &[AssignedByte<F>],
304    ) -> Result<B64Chunk<F>, Error> {
305        let ng = &self.native_gadget;
306        let pad: AssignedByte<F> = ng.assign_fixed(layouter, ALT_PAD as u8)?;
307        let mut res = b64_input.to_vec();
308        res.resize(4, pad);
309        Ok(res.try_into().unwrap())
310    }
311
312    /// Receives 2 12-bit values, where each value represents a pair base64
313    /// characters. Returns a vector of 3 [AssignedByte] with each symbol
314    /// values. These values are guaranteed to be in the range [0, 2^8).
315    fn val_to_ascii_chunk(
316        &self,
317        layouter: &mut impl Layouter<F>,
318        b64_input: &[AssignedNative<F>; 2],
319    ) -> Result<AsciiChunk<F>, Error> {
320        // Sum ( b64_input[i] * 6^i ) = Sum ( byte[i] * 8^i)
321        let terms = vec![
322            (F::from(1u64 << 12), b64_input[0].clone()),
323            (F::ONE, b64_input[1].clone()),
324        ];
325        let total = self.native_gadget.linear_combination(layouter, terms.as_slice(), F::ZERO)?;
326
327        let bytes = self.native_gadget.assigned_to_be_bytes(layouter, &total, Some(3))?;
328
329        Ok(bytes.try_into().unwrap())
330    }
331
332    /// Receives 4 ascii characters as [AssignedByte] representing a base64
333    /// string. Returns a 2 [AssignedNative] with the value of each pair.
334    /// These values are guaranteed to be in the range [0, 2^12).
335    fn base64_to_val_chunk(
336        &self,
337        layouter: &mut impl Layouter<F>,
338        b64_input: &B64Chunk<F>,
339    ) -> Result<[AssignedNative<F>; 2], Error> {
340        let advice_cols = self.config.advice_cols;
341        // |-----|-----|--------|
342        // | A   | B   | 0x0001 |
343        // |-----|-----|--------|
344        // | C   | D   | 0x0203 |
345        // |-----|-----|--------|
346        let decoded = layouter.assign_region(
347            || "Base64 chunk",
348            |mut region| {
349                let decoded_outs: Vec<Value<F>> = b64_input
350                    .chunks_exact(2)
351                    .map(|vs| {
352                        vs[0].value().zip(vs[1].value()).map(|(c0, c1)| {
353                            let v0 = super::table::decode_char(c0 as char) as u64;
354                            let v1 = super::table::decode_char(c1 as char) as u64;
355                            F::from(v0 * (1 << 6) + v1)
356                        })
357                    })
358                    .collect();
359
360                // Enable lookup selector in both rows.
361                self.config.lookup_sel.enable(&mut region, 0)?;
362                self.config.lookup_sel.enable(&mut region, 1)?;
363
364                // Positions of the inputs as: (column, row)
365                let positions = [
366                    [(0, 0), (1, 0)], //
367                    [(0, 1), (1, 1)],
368                ];
369                for (input, pos) in b64_input.iter().zip(positions.as_flattened()) {
370                    let input: AssignedNative<F> = input.clone().into();
371                    input.copy_advice(|| "Base64 char", &mut region, advice_cols[pos.0], pos.1)?;
372                }
373
374                let result: Result<Vec<_>, _> = decoded_outs
375                    .into_iter()
376                    .zip(positions)
377                    .map(|(output, pos)| {
378                        region.assign_advice(
379                            || "Base64 decoded values",
380                            advice_cols[pos[0].0 + 2], /* same position as inputs, just 2
381                                                        * columns right */
382                            pos[0].1,
383                            || output,
384                        )
385                    })
386                    .collect();
387                result
388            },
389        )?;
390
391        Ok(decoded.try_into().unwrap())
392    }
393}
394
395impl<F: CircuitField> Chip<F> for Base64Chip<F> {
396    type Config = Base64Config;
397
398    type Loaded = ();
399
400    fn config(&self) -> &Self::Config {
401        &self.config
402    }
403
404    fn loaded(&self) -> &Self::Loaded {
405        &()
406    }
407}
408
409impl<F: CircuitField> ComposableChip<F> for Base64Chip<F> {
410    type SharedResources = [Column<Advice>; NB_BASE64_ADVICE_COLS];
411    type InstructionDeps = NG<F>;
412
413    fn new(config: &Self::Config, sub_chips: &Self::InstructionDeps) -> Self {
414        Self {
415            config: config.clone(),
416            native_gadget: sub_chips.clone(),
417            vector_gadget: VectorGadget::new(sub_chips),
418        }
419    }
420
421    fn configure(
422        meta: &mut midnight_proofs::plonk::ConstraintSystem<F>,
423        shared_resources: &Self::SharedResources,
424    ) -> Self::Config {
425        let advice_cols = *shared_resources;
426        let lookup_sel = meta.complex_selector();
427
428        // Lookup table columns.
429        let t_char = meta.lookup_table_column();
430        let t_val = meta.lookup_table_column();
431
432        meta.lookup("Base64 lookup", |meta| {
433            let s = meta.query_selector(lookup_sel);
434
435            // Each row decodes 2 characters. The first 2 columns contain
436            // the characters. The third column contains their combined value as:
437            //  char_1 * (2^6) + char_2
438            //
439            // |characters | value |
440            // |-----|-----|-------|
441            // | A   | B   | 0x01  |
442            // |-----|-----|-------|
443
444            let col_1 = meta.query_advice(advice_cols[0], Rotation::cur());
445            let col_2 = meta.query_advice(advice_cols[1], Rotation::cur());
446            let characters = col_1 * Expression::from(1 << 8) + col_2;
447
448            let value = meta.query_advice(advice_cols[2], Rotation::cur());
449
450            // Default value for the deactivated lookup.
451            let default_char = Expression::from(super::table::two_entry_default());
452
453            vec![
454                (
455                    s.clone() * characters + (Expression::from(1) - s.clone()) * default_char,
456                    t_char,
457                ),
458                (s.clone() * value, t_val),
459            ]
460        });
461        Base64Config {
462            advice_cols,
463            lookup_sel,
464            t_char,
465            t_val,
466        }
467    }
468
469    fn load(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
470        layouter.assign_table(
471            || "Base64 table",
472            |mut table| {
473                for (offset, (char, val)) in super::table::two_entry_table().into_iter().enumerate()
474                {
475                    let char = Value::known(F::from(char as u64));
476                    let val = Value::known(F::from(val as u64));
477                    table.assign_cell(|| "t_char", self.config.t_char, offset, || char)?;
478                    table.assign_cell(|| "t_val", self.config.t_val, offset, || val)?;
479                }
480                Ok(())
481            },
482        )
483    }
484}
485
486#[cfg(test)]
487mod tests {
488    use std::marker::PhantomData;
489
490    use midnight_proofs::{
491        circuit::SimpleFloorPlanner,
492        dev::MockProver,
493        plonk::{Circuit, ConstraintSystem},
494    };
495
496    use super::*;
497    use crate::{
498        field::decomposition::chip::P2RDecompositionConfig,
499        instructions::{AssertionInstructions, AssignmentInstructions},
500        testing_utils::FromScratch,
501        vec::vector_gadget::VectorGadget,
502    };
503
504    type Fp = midnight_curves::Fq;
505
506    struct TestCircuit<F: CircuitField> {
507        input: Vec<u8>,  // base64 encoded string
508        output: Vec<u8>, // decoded string
509        options: TestOptions,
510        _marker: PhantomData<F>,
511    }
512
513    #[derive(Clone, Copy, Debug)]
514    struct TestOptions {
515        input_pad: bool,
516        url_safe: bool,
517        variable: bool,
518    }
519
520    impl<F: CircuitField> TestCircuit<F> {
521        fn new(input: &[u8], output: &[u8], options: TestOptions) -> Self {
522            debug_assert_eq!(input.len().is_multiple_of(4), options.input_pad);
523            // Pad output to a multiple of 3.
524            let mut padded_out = output.to_vec();
525
526            match output.len() % 3 {
527                2 => padded_out.append(&mut [ASCII_ZERO as u8].to_vec()),
528                1 => padded_out.append(&mut [ASCII_ZERO as u8, ASCII_ZERO as u8].to_vec()),
529                _ => (),
530            }
531
532            debug_assert_eq!(input.len().div_ceil(4) * 3, padded_out.len());
533            Self {
534                input: input.to_vec(),
535                output: padded_out,
536                options,
537                _marker: PhantomData,
538            }
539        }
540    }
541
542    impl<F: CircuitField> Circuit<F> for TestCircuit<F> {
543        type Config = (P2RDecompositionConfig, Base64Config);
544        type FloorPlanner = SimpleFloorPlanner;
545        type Params = ();
546
547        fn without_witnesses(&self) -> Self {
548            Self {
549                input: vec![],
550                output: vec![],
551                options: TestOptions {
552                    input_pad: true,
553                    url_safe: false,
554                    variable: false,
555                },
556                _marker: PhantomData,
557            }
558        }
559
560        fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
561            let committed_instance_column = meta.instance_column();
562            let instance_column = meta.instance_column();
563            let ng_config = NativeGadget::configure_from_scratch(
564                meta,
565                &mut vec![],
566                &mut vec![],
567                &[committed_instance_column, instance_column],
568            );
569            let sr =
570                &ng_config.native_config.value_cols[..NB_BASE64_ADVICE_COLS].try_into().unwrap();
571            let b64_config = Base64Chip::configure(meta, sr);
572
573            (ng_config, b64_config)
574        }
575
576        fn synthesize(
577            &self,
578            config: Self::Config,
579            mut layouter: impl Layouter<F>,
580        ) -> Result<(), Error> {
581            let options = &self.options;
582
583            // Create chips.
584            let ng: NG<F> = NativeGadget::new_from_scratch(&config.0);
585            let vg = VectorGadget::new(&ng);
586            let b64_chip = Base64Chip::new(&config.1, &ng);
587
588            if options.variable {
589                // Variable length.
590                let assigned_in_var: Base64Vec<F, 1024, 4> =
591                    b64_chip.assign_var_base64(&mut layouter, Value::known(self.input.clone()))?;
592                let ret_var: AssignedVector<F, AssignedByte<F>, 768, 3> = if options.url_safe {
593                    b64_chip.var_decode_base64url(&mut layouter, &assigned_in_var)
594                } else {
595                    b64_chip.var_decode_base64(&mut layouter, &assigned_in_var)
596                }?;
597                vg.assert_equal_to_fixed(&mut layouter, &ret_var, self.output.clone())?;
598            } else {
599                // Fixed length.
600                let input_vals: Vec<Value<u8>> =
601                    self.input.clone().into_iter().map(Value::known).collect();
602
603                let output_vals: Vec<Value<u8>> =
604                    self.output.clone().into_iter().map(Value::known).collect();
605
606                let assigned_in: Vec<AssignedByte<F>> =
607                    ng.assign_many(&mut layouter, &input_vals)?;
608                let assigned_out: Vec<AssignedByte<F>> =
609                    ng.assign_many(&mut layouter, &output_vals)?;
610                let ret = if options.url_safe {
611                    b64_chip.decode_base64url(&mut layouter, &assigned_in, options.input_pad)?
612                } else {
613                    b64_chip.decode_base64(&mut layouter, &assigned_in, options.input_pad)?
614                };
615                assert_eq!(assigned_out.len(), ret.len());
616                for (a, b) in assigned_out.iter().zip(ret.iter()) {
617                    ng.assert_equal(&mut layouter, a, b)?;
618                }
619            }
620
621            ng.load_from_scratch(&mut layouter)?;
622            b64_chip.load(&mut layouter)
623        }
624    }
625
626    #[test]
627    fn test_b64chip() {
628        let b64_input: &[u8] = b"QWxsIHRoYXQgaXMgZ29sZCBkb2VzIG5vdCBnbGl0dGVyLApOb3QgYWxsIHRob3NlIHdobyB3YW5kZXIgYXJlIGxvc3Q7ClRoZSBvbGQgdGhhdCBpcyBzdHJvbmcgZG9lcyBub3Qgd2l0aGVyLApEZWVwIHJvb3RzIGFyZSBub3QgcmVhY2hlZCBieSB0aGUgZnJvc3QuCiAtIEouUi5SLiBUb2xraWVuLCAxOTU0";
629        #[rustfmt::skip]
630        let output: &[u8] =
631          b"All that is gold does not glitter,
632Not all those who wander are lost;
633The old that is strong does not wither,
634Deep roots are not reached by the frost.
635 - J.R.R. Tolkien, 1954";
636
637        let options = TestOptions {
638            input_pad: true,
639            url_safe: false,
640            variable: false,
641        };
642
643        let circuit = TestCircuit::<Fp>::new(b64_input, output, options);
644
645        let public_inputs = vec![vec![], vec![]];
646        let prover = match MockProver::run(&circuit, public_inputs) {
647            Ok(prover) => prover,
648            Err(e) => panic!("{e:#?}"),
649        };
650
651        assert_eq!(prover.verify(), Ok(()));
652    }
653
654    #[test]
655    fn test_urlsafe_b64chip() {
656        let b64_input: &[u8] = b"VVJMU2FmZSB0ZXN0OiA_Pz8gPz8-Lg==";
657        let output: &[u8] = b"URLSafe test: ??? ??>.";
658
659        let options = TestOptions {
660            input_pad: true,
661            url_safe: true,
662            variable: false,
663        };
664
665        let circuit = TestCircuit::<Fp>::new(b64_input, output, options);
666
667        let public_inputs = vec![vec![], vec![]];
668        let prover = match MockProver::run(&circuit, public_inputs) {
669            Ok(prover) => prover,
670            Err(e) => panic!("{e:#?}"),
671        };
672
673        assert_eq!(prover.verify(), Ok(()));
674    }
675
676    #[test]
677    fn test_b64chip_w_padding() {
678        let b64_input: &[u8] = b"QWxsIHRoYXQgaXMgZ29sZCBkb2VzIG5vdCBnbGl0dGVyLA==";
679        let b64_input_bad: &[u8] = b"QWxsIHRoYXQgaXMgZ29sZCBkb2VzIG5vdCBnbGl0dGVyLA=A";
680        let output: &[u8] = b"All that is gold does not glitter,";
681
682        let options = TestOptions {
683            input_pad: true,
684            url_safe: false,
685            variable: false,
686        };
687
688        let circuit = TestCircuit::<Fp>::new(b64_input, output, options);
689        let circuit_bad = TestCircuit::<Fp>::new(b64_input_bad, output, options);
690
691        let public_inputs = vec![vec![], vec![]];
692        let prover = match MockProver::run(&circuit, public_inputs) {
693            Ok(prover) => prover,
694            Err(e) => panic!("{e:#?}"),
695        };
696
697        assert_eq!(prover.verify(), Ok(()));
698
699        let public_inputs = vec![vec![], vec![]];
700        let prover = match MockProver::run(&circuit_bad, public_inputs) {
701            Ok(prover) => prover,
702            Err(e) => panic!("{e:#?}"),
703        };
704        assert!(prover.verify().is_err());
705    }
706
707    #[test]
708    fn test_b64chip_truncated() {
709        let b64_input: &[u8] = b"QWxsIHRoYXQgaXMgZ29sZCBkb2VzIG5vdCBnbGl0dGVyLA";
710        let output: &[u8] = b"All that is gold does not glitter,";
711
712        let options = TestOptions {
713            input_pad: false,
714            url_safe: false,
715            variable: false,
716        };
717        let circuit = TestCircuit::<Fp>::new(b64_input, output, options);
718
719        let public_inputs = vec![vec![], vec![]];
720        let prover = match MockProver::run(&circuit, public_inputs) {
721            Ok(prover) => prover,
722            Err(e) => panic!("{e:#?}"),
723        };
724
725        assert_eq!(prover.verify(), Ok(()));
726    }
727
728    #[test]
729    fn test_b64chip_variable() {
730        let b64_input: &[u8] = b"QWxsIHRoYXQgaXMgZ29sZCBkb2VzIG5vdCBnbGl0dGVyLApOb3QgYWxsIHRob3NlIHdobyB3YW5kZXIgYXJlIGxvc3Q7ClRoZSBvbGQgdGhhdCBpcyBzdHJvbmcgZG9lcyBub3Qgd2l0aGVyLApEZWVwIHJvb3RzIGFyZSBub3QgcmVhY2hlZCBieSB0aGUgZnJvc3QuCiAtIEouUi5SLiBUb2xraWVuLCAxOTU0";
731        #[rustfmt::skip]
732        let output: &[u8] =
733          b"All that is gold does not glitter,
734Not all those who wander are lost;
735The old that is strong does not wither,
736Deep roots are not reached by the frost.
737 - J.R.R. Tolkien, 1954";
738
739        let options = TestOptions {
740            input_pad: true,
741            url_safe: false,
742            variable: true,
743        };
744
745        let circuit = TestCircuit::<Fp>::new(b64_input, output, options);
746
747        let public_inputs = vec![vec![], vec![]];
748        let prover = match MockProver::run(&circuit, public_inputs) {
749            Ok(prover) => prover,
750            Err(e) => panic!("{e:#?}"),
751        };
752
753        assert_eq!(prover.verify(), Ok(()));
754    }
755
756    #[test]
757    fn test_urlsafe_b64chip_variable() {
758        let b64_input: &[u8] = b"VVJMU2FmZSB0ZXN0OiA_Pz8gPz8-Lg==";
759        let output: &[u8] = b"URLSafe test: ??? ??>.";
760
761        let options = TestOptions {
762            input_pad: true,
763            url_safe: true,
764            variable: true,
765        };
766
767        let circuit = TestCircuit::<Fp>::new(b64_input, output, options);
768
769        let public_inputs = vec![vec![], vec![]];
770        let prover = match MockProver::run(&circuit, public_inputs) {
771            Ok(prover) => prover,
772            Err(e) => panic!("{e:#?}"),
773        };
774
775        assert_eq!(prover.verify(), Ok(()));
776    }
777}