1use 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
45pub const NB_BASE64_ADVICE_COLS: usize = 4;
47
48#[derive(Clone, Debug)]
49pub struct Base64Config {
51 advice_cols: [Column<Advice>; NB_BASE64_ADVICE_COLS],
52
53 lookup_sel: Selector,
54 t_char: TableColumn,
56 t_val: TableColumn,
57}
58
59type NG<F> = NativeGadget<F, P2RDecompositionChip<F>, NativeChip<F>>;
61
62type B64Chunk<F> = [AssignedByte<F>; 4];
64
65type AsciiChunk<F> = [AssignedByte<F>; 3];
67
68pub(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)]
78pub 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 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_eq!(A % 4, 0);
215 assert_eq!(M * 3, M_OUT * 4);
216 assert_eq!(A * 3, A_OUT * 4);
217
218 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 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 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 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 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 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 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 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 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 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 self.config.lookup_sel.enable(&mut region, 0)?;
362 self.config.lookup_sel.enable(&mut region, 1)?;
363
364 let positions = [
366 [(0, 0), (1, 0)], [(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], 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 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 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 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>, output: Vec<u8>, 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 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 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 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 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}