1use midnight_proofs::{circuit::Layouter, plonk::Error};
15use num_bigint::BigUint;
16
17use super::ParserGadget;
18use crate::{
19 field::{native::AssignedBit, AssignedNative},
20 instructions::NativeInstructions,
21 types::{AssignedByte, InnerValue},
22 CircuitField,
23};
24
25#[allow(clippy::upper_case_acronyms)]
27#[derive(Clone, Copy, Debug)]
28pub enum DateFormat {
29 YYYYMMDD,
31 DDMMYYYY,
33 YYMMDD,
36}
37
38#[derive(Clone, Copy, Debug)]
41pub enum Separator {
42 NoSep,
44 Sep(char),
46}
47
48impl<F, N> ParserGadget<F, N>
49where
50 F: CircuitField,
51 N: NativeInstructions<F>,
52{
53 fn ascii_to_digit(
60 &self,
61 layouter: &mut impl Layouter<F>,
62 byte: &AssignedByte<F>,
63 ) -> Result<AssignedNative<F>, Error> {
64 let val = self.native_gadget.add_constant(layouter, &byte.into(), -F::from(48u64))?;
67 self.native_gadget
68 .assert_lower_than_fixed(layouter, &val, &BigUint::from(10u64))?;
69 Ok(val)
70 }
71
72 pub fn ascii_to_int(
86 &self,
87 layouter: &mut impl Layouter<F>,
88 input: &[AssignedByte<F>],
89 ) -> Result<AssignedNative<F>, Error> {
90 let native = &self.native_gadget;
91 let digit_capacity = (F::CAPACITY as f64 / 10f64.log2()) as usize;
92 let n = input.len();
93 assert!(
94 n < digit_capacity,
95 "Cannot parse intgers with more than {digit_capacity} digits"
96 );
97
98 let mut terms = Vec::with_capacity(n);
99 let mut base = F::ONE;
100 for byte in input.iter().rev() {
101 let val = self.ascii_to_digit(layouter, byte)?;
102 terms.push((base, val));
103 base *= F::from(10u64);
104 }
105
106 let res = native.linear_combination(layouter, terms.as_slice(), F::ZERO)?;
107
108 Ok(res)
109 }
110
111 pub fn date_to_int(
125 &self,
126 layouter: &mut impl Layouter<F>,
127 input: &[AssignedByte<F>],
128 format: (DateFormat, Separator),
129 century_base: Option<&AssignedNative<F>>,
130 ) -> Result<AssignedNative<F>, Error> {
131 let native = &self.native_gadget;
132 let n = input.len();
133
134 match format {
135 (DateFormat::YYMMDD, Separator::NoSep) => {
136 assert_eq!(n, 6, "Date format must be 6 characters long: YYMMDD");
137 let century_base =
138 century_base.expect("YYMMDD format requires a century_base parameter");
139 self.date_to_int_short_year(layouter, input, century_base)
140 }
141 (DateFormat::YYMMDD, Separator::Sep(_)) => {
142 panic!("YYMMDD with separator is not supported")
143 }
144 _ => {
145 assert!(
146 century_base.is_none(),
147 "century_base is only used with YYMMDD format"
148 );
149 let indices: (_, _, _) = match format {
151 (DateFormat::DDMMYYYY, Separator::NoSep) => {
152 assert_eq!(n, 8, "Date format must be 8 characters long: DDMMYYYY");
153 ((0..2), (2..4), (4..8))
154 }
155 (DateFormat::DDMMYYYY, Separator::Sep(sep)) => {
156 assert_eq!(
157 n, 10,
158 "Date format must be 10 characters long: DD{sep}MM{sep}YYYY"
159 );
160 native.assert_equal_to_fixed(layouter, &input[2], sep as u8)?;
161 native.assert_equal_to_fixed(layouter, &input[5], sep as u8)?;
162 ((0..2), (3..5), (6..10))
163 }
164 (DateFormat::YYYYMMDD, Separator::NoSep) => {
165 assert_eq!(n, 8, "Date format must be 8 characters long: YYYYMMDD");
166 ((6..8), (4..6), (0..4))
167 }
168 (DateFormat::YYYYMMDD, Separator::Sep(sep)) => {
169 assert_eq!(
170 n, 10,
171 "Date format must be 10 characters long: YYYY{sep}MM{sep}DD"
172 );
173 native.assert_equal_to_fixed(layouter, &input[4], sep as u8)?;
174 native.assert_equal_to_fixed(layouter, &input[7], sep as u8)?;
175 ((8..10), (5..7), (0..4))
176 }
177 (DateFormat::YYMMDD, _) => unreachable!(),
178 };
179 let bytes = [&input[indices.2], &input[indices.1], &input[indices.0]].concat();
180 self.ascii_to_int(layouter, &bytes)
181 }
182 }
183 }
184
185 fn date_to_int_short_year(
189 &self,
190 layouter: &mut impl Layouter<F>,
191 input: &[AssignedByte<F>],
192 century_base: &AssignedNative<F>,
193 ) -> Result<AssignedNative<F>, Error> {
194 let native = &self.native_gadget;
195
196 let yy = self.ascii_to_int(layouter, &input[0..2])?;
197 let mmdd = self.ascii_to_int(layouter, &input[2..6])?;
198
199 let yy_val = input[0]
201 .value()
202 .zip(input[1].value())
203 .map(|(d0, d1)| (d0 - 48) as u64 * 10 + (d1 - 48) as u64);
204 let n_val =
205 InnerValue::value(century_base).map(|be| u64::try_from(be.to_biguint()).unwrap());
206 let is_20xx_val = yy_val.zip(n_val).map(|(yy, n)| yy < n);
207
208 let is_20xx: AssignedBit<F> = native.assign(layouter, is_20xx_val)?;
210 let is_20xx_native: AssignedNative<F> = is_20xx.into();
211
212 let check = native.linear_combination(
215 layouter,
216 &[
217 (F::ONE, yy.clone()),
218 (-F::ONE, century_base.clone()),
219 (F::from(100u64), is_20xx_native.clone()),
220 ],
221 F::ZERO,
222 )?;
223 native.assert_lower_than_fixed(layouter, &check, &BigUint::from(128u64))?;
224
225 native.linear_combination(
227 layouter,
228 &[
229 (F::from(10_000u64), yy),
230 (F::from(1_000_000u64), is_20xx_native),
231 (F::ONE, mmdd),
232 ],
233 F::from(19_000_000u64),
234 )
235 }
236}
237
238#[derive(Clone, Copy, Debug)]
240pub struct Date {
241 pub day: u8,
243 pub month: u8,
245 pub year: u16,
247}
248
249impl Date {
250 pub fn as_yyyymmdd(&self) -> u64 {
252 self.year as u64 * 10_000 + self.month as u64 * 100 + self.day as u64
253 }
254}
255
256impl From<Date> for BigUint {
257 fn from(value: Date) -> Self {
258 value.as_yyyymmdd().into()
259 }
260}
261
262impl<F, N> ParserGadget<F, N>
263where
264 F: CircuitField,
265 N: NativeInstructions<F>,
266{
267 pub fn assert_date_before_fixed(
271 &self,
272 layouter: &mut impl Layouter<F>,
273 date_bytes: &[AssignedByte<F>],
274 format: (DateFormat, Separator),
275 limit_date: Date,
276 ) -> Result<(), Error> {
277 let date = self.date_to_int(layouter, date_bytes, format, None)?;
278 self.native_gadget.assert_lower_than_fixed(layouter, &date, &limit_date.into())
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use std::marker::PhantomData;
285
286 use ff::FromUniformBytes;
287 use midnight_proofs::{
288 circuit::{SimpleFloorPlanner, Value},
289 dev::MockProver,
290 plonk::{Circuit, ConstraintSystem},
291 };
292
293 use super::*;
294 use crate::{
295 field::{decomposition::chip::P2RDecompositionChip, NativeChip, NativeGadget},
296 testing_utils::FromScratch,
297 };
298
299 #[derive(Clone, Copy, Debug)]
300 enum ParseTarget {
301 Int,
302 Date((DateFormat, Separator)),
303 DateShortYear((DateFormat, Separator), u64),
305 }
306
307 #[derive(Clone, Debug)]
308 struct TestCircuit<F, N> {
309 string: Vec<Value<F>>,
310 expected: F,
311 operation: ParseTarget,
312 _marker: PhantomData<N>,
313 }
314
315 impl<F, N> Circuit<F> for TestCircuit<F, N>
316 where
317 F: CircuitField,
318 N: NativeInstructions<F> + FromScratch<F>,
319 {
320 type Config = <N as FromScratch<F>>::Config;
321 type FloorPlanner = SimpleFloorPlanner;
322 type Params = ();
323
324 fn without_witnesses(&self) -> Self {
325 unreachable!()
326 }
327
328 fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
329 let committed_instance_column = meta.instance_column();
330 let instance_column = meta.instance_column();
331 <N as FromScratch<F>>::configure_from_scratch(
332 meta,
333 &mut vec![],
334 &mut vec![],
335 &[committed_instance_column, instance_column],
336 )
337 }
338
339 fn synthesize(
340 &self,
341 config: Self::Config,
342 mut layouter: impl Layouter<F>,
343 ) -> Result<(), Error> {
344 let native_gadget = <N as FromScratch<F>>::new_from_scratch(&config);
345 let parser_gadget = ParserGadget::<F, N>::new(&native_gadget);
346
347 let string = native_gadget.assign_many(&mut layouter, &self.string)?;
348 let bytes = string
349 .iter()
350 .map(|x| native_gadget.convert(&mut layouter, x))
351 .collect::<Result<Vec<AssignedByte<F>>, Error>>()?;
352
353 let res = match self.operation {
354 ParseTarget::Int => parser_gadget.ascii_to_int(&mut layouter, &bytes),
355 ParseTarget::Date(format) => {
356 parser_gadget.date_to_int(&mut layouter, &bytes, format, None)
357 }
358 ParseTarget::DateShortYear(format, n) => {
359 let century_base =
360 native_gadget.assign(&mut layouter, Value::known(F::from(n)))?;
361 parser_gadget.date_to_int(&mut layouter, &bytes, format, Some(¢ury_base))
362 }
363 }?;
364
365 native_gadget.assert_equal_to_fixed(&mut layouter, &res, self.expected)?;
366
367 native_gadget.load_from_scratch(&mut layouter)
368 }
369 }
370
371 fn run<F>(string: &[u8], expected: u64, operation: ParseTarget, must_pass: bool)
372 where
373 F: CircuitField + FromUniformBytes<64> + Ord,
374 {
375 let circuit = TestCircuit::<F, NativeGadget<F, P2RDecompositionChip<F>, NativeChip<F>>> {
376 string: string.iter().map(|x| F::from(*x as u64)).map(Value::known).collect(),
377 expected: F::from(expected),
378 operation,
379 _marker: PhantomData,
380 };
381 let public_inputs = vec![vec![], vec![]];
382 match MockProver::run(&circuit, public_inputs) {
383 Ok(prover) => match prover.verify() {
384 Ok(()) => assert!(must_pass),
385 Err(e) => assert!(!must_pass, "Failed verifier with error {e:?}"),
386 },
387 Err(e) => assert!(!must_pass, "Failed prover with error {e:?}"),
388 }
389 }
390
391 #[test]
392 fn test_parse_int() {
393 type F = midnight_curves::Fq;
394 let test_vecs: Vec<(&[u8], u64, bool)> = vec![
395 (b"987654321", 987654321, true),
396 (b"123456", 123456, true),
397 (b"123", 123, true),
398 (b"0123", 123, true),
399 (b"00123", 123, true),
400 (b"0", 0, true),
401 (b"54321", 54320, false),
402 (b"54321", 54322, false),
403 (b"54321", 0, false),
404 ];
405 test_vecs.iter().for_each(|(input, expected, must_pass)| {
406 run::<F>(input, *expected, ParseTarget::Int, *must_pass)
407 });
408 }
409
410 #[test]
411 fn test_parse_date() {
412 type F = midnight_curves::Fq;
413 let format1 = (DateFormat::DDMMYYYY, Separator::NoSep);
414 let format2 = (DateFormat::DDMMYYYY, Separator::Sep('-'));
415 let format3 = (DateFormat::YYYYMMDD, Separator::Sep('-'));
416 let format4 = (DateFormat::YYYYMMDD, Separator::NoSep);
417
418 let test_vecs: Vec<(&[u8], _, _, _)> = vec![
419 (b"40052025", format1, 20250540, true),
420 (b"01011970", format1, 19700101, true),
421 (b"12121970", format1, 19701212, true),
422 (b"40121970", format1, 19701240, true),
423 (b"01011970", format1, 19700102, false),
424 (b"40-05-2025", format2, 20250540, true),
425 (b"01-01-1970", format2, 19700101, true),
426 (b"12-12-1970", format2, 19701212, true),
427 (b"40-12-1970", format2, 19701240, true),
428 (b"01-01-1970", format2, 19700102, false),
429 (b"02-01-1970", format2, 19700201, false),
430 (b"01/01/1970", format2, 19700102, false),
431 (b"2025-05-40", format3, 20250540, true),
432 (b"1970-01-01", format3, 19700101, true),
433 (b"1970-12-12", format3, 19701212, true),
434 (b"20250540", format4, 20250540, true),
435 (b"19700101", format4, 19700101, true),
436 (b"19701225", format4, 19701225, true),
437 ];
438 test_vecs.iter().for_each(|(input, format, expected, must_pass)| {
439 run::<F>(input, *expected, ParseTarget::Date(*format), *must_pass)
440 });
441 }
442
443 #[test]
444 fn test_parse_date_short_year() {
445 type F = midnight_curves::Fq;
446 let yymmdd = (DateFormat::YYMMDD, Separator::NoSep);
447
448 let test_vecs: Vec<(&[u8], _, u64, _, _)> = vec![
450 (b"000101", yymmdd, 26, 20000101, true), (b"251231", yymmdd, 26, 20251231, true), (b"260101", yymmdd, 26, 19260101, true), (b"911214", yymmdd, 26, 19911214, true), (b"991231", yymmdd, 26, 19991231, true), (b"100812", yymmdd, 26, 20100812, true), (b"911214", yymmdd, 26, 20171214, false), (b"000101", yymmdd, 0, 19000101, true),
459 (b"991231", yymmdd, 0, 19991231, true),
460 (b"990101", yymmdd, 99, 19990101, true),
462 (b"000101", yymmdd, 99, 20000101, true),
463 (b"980101", yymmdd, 99, 20980101, true),
464 ];
465 test_vecs.iter().for_each(|(input, format, n, expected, must_pass)| {
466 run::<F>(
467 input,
468 *expected,
469 ParseTarget::DateShortYear(*format, *n),
470 *must_pass,
471 )
472 });
473 }
474}