1use alloc::vec::Vec;
8use crypto_bigint::{BoxedUint, ConcatenatingMul, Gcd, NonZero, ctutils::CtSelect};
9use crypto_primes::{Flavor, is_prime};
10use rand_core::CryptoRng;
11
12use crate::{
13 arith::{
14 monty::{Modulus, Residue},
15 sbig::{SBig, resized},
16 },
17 error::{Error, Result},
18 params::Params,
19 transcript::{Transcript, uint_from_be, uint_to_be},
20};
21
22pub struct ModWitness<'a> {
24 pub p1: &'a BoxedUint,
25 pub p2: &'a BoxedUint,
26}
27
28#[derive(Clone, Debug, PartialEq, Eq)]
30pub struct ModRep {
31 pub a: bool,
32 pub b: bool,
33 pub x: BoxedUint,
34 pub z: BoxedUint,
35}
36
37#[derive(Clone, Debug, PartialEq, Eq)]
39pub struct ModProof {
40 pub reps: Vec<ModRep>,
41}
42
43fn n_bytes(n_bits: u32) -> usize {
44 (n_bits as usize).div_ceil(8)
45}
46
47fn challenges(
49 tx: &Transcript,
50 params: &Params,
51 mn: &Modulus,
52 n: &BoxedUint,
53) -> Result<Vec<Residue>> {
54 let mut t = tx.clone();
55 t.append("zk", b"mod");
56 t.append_uint("N", n, n_bytes(n.bits()))?;
57 let mut ch = t.challenge("y");
58 let mut ys = Vec::with_capacity(params.r_mod as usize);
59 for _ in 0..params.r_mod {
60 ys.push(ch.unit_mod(mn)?);
61 }
62 Ok(ys)
63}
64
65fn small_w(mn: &Modulus, n: &BoxedUint) -> Result<Residue> {
66 let two = mn.residue(&BoxedUint::from(2u64));
67 mn.pow_vartime(&two, &SBig::from_uint(n.clone()))
68}
69
70fn target(y: &Residue, w: &Residue, a: bool, b: bool) -> Residue {
72 let mut v = y.clone();
73 if a {
74 v = v.neg();
75 }
76 if b {
77 v = v.mul(w);
78 }
79 v
80}
81
82struct PrimeCtx {
84 ring: Modulus,
85 bits: u32,
86 half: SBig,
88 root_exp: SBig,
90 n_inv: SBig,
92}
93
94fn rem(x: &BoxedUint, m: &BoxedUint) -> Result<BoxedUint> {
95 let nz = Option::<NonZero<BoxedUint>>::from(m.to_nz()).ok_or(Error::InvalidModulus)?;
96 Ok(SBig::from_uint(x.clone()).rem_euclid(&nz))
97}
98
99impl PrimeCtx {
100 fn new(p: &BoxedUint, n: &BoxedUint) -> Result<Self> {
101 let prec = p.bits_precision();
102 let one = BoxedUint::one_with_precision(prec);
103 let pm1 = p.wrapping_sub(&one);
104 let half = pm1.shr_vartime(1).ok_or(Error::InvalidModulus)?;
105 let wide = resized(p, prec + 64);
106 let t = wide
107 .wrapping_add(BoxedUint::one_with_precision(prec + 64))
108 .shr_vartime(2)
109 .ok_or(Error::InvalidModulus)?;
110 let t2 = t.concatenating_mul(&t);
111 let root_exp = rem(&t2, &half)?;
112 let pm1_nz =
114 Option::<NonZero<BoxedUint>>::from(pm1.to_nz()).ok_or(Error::InvalidModulus)?;
115 let n_red = rem(n, &pm1)?;
116 let n_inv =
117 Option::<BoxedUint>::from(n_red.invert_mod(&pm1_nz)).ok_or(Error::NotInvertible)?;
118 Ok(Self {
119 ring: Modulus::new(p.clone())?,
120 bits: p.bits(),
121 half: SBig::from_uint(half),
122 root_exp: SBig::from_uint(root_exp),
123 n_inv: SBig::from_uint(n_inv),
124 })
125 }
126
127 fn residue(&self, y: &Residue) -> Residue {
128 self.ring.residue(&y.retrieve())
129 }
130}
131
132fn crt(c1: &PrimeCtx, r1: &Residue, p2: &BoxedUint, r2: &Residue, mn: &Modulus) -> Result<Residue> {
134 let x2 = r2.retrieve();
135 let x2_in_1 = c1.ring.residue(&x2);
136 let inv = c1.ring.invert(&c1.ring.residue(p2))?;
137 let t = r1.sub(&x2_in_1).mul(&inv).retrieve();
138 let x = p2
139 .concatenating_mul(&t)
140 .wrapping_add(resized(&x2, p2.bits_precision() + t.bits_precision()));
141 Ok(mn.residue(&x))
142}
143
144fn random_neg<R: CryptoRng + ?Sized>(rng: &mut R, r: &Residue) -> Residue {
145 let n = r.neg();
146 let flip = crypto_bigint::Choice::from_u8_lsb((rng.next_u32() & 1) as u8);
147 Residue::from_montgomery(
148 r.as_montgomery().ct_select(n.as_montgomery(), flip),
149 r.params(),
150 )
151}
152
153impl ModProof {
154 pub fn prove<R: CryptoRng + ?Sized>(
156 rng: &mut R,
157 tx: &Transcript,
158 params: &Params,
159 n: &BoxedUint,
160 w: &ModWitness<'_>,
161 ) -> Result<Self> {
162 Self::prove_inner(rng, tx, params, n, w, true)
163 }
164
165 fn prove_inner<R: CryptoRng + ?Sized>(
166 rng: &mut R,
167 tx: &Transcript,
168 params: &Params,
169 n: &BoxedUint,
170 wit: &ModWitness<'_>,
171 check: bool,
172 ) -> Result<Self> {
173 if check {
174 let prod = wit.p1.concatenating_mul(wit.p2);
175 if resized(&prod, n.bits_precision()) != resized(n, prod.bits_precision()) {
176 return Err(Error::InvalidModulus);
177 }
178 if wit.p1.as_words()[0] & 7 != 3 || wit.p2.as_words()[0] & 7 != 7 {
179 return Err(Error::InvalidModulus);
180 }
181 }
182 let mn = Modulus::new(n.clone())?;
183 let c1 = PrimeCtx::new(wit.p1, n)?;
184 let c2 = PrimeCtx::new(wit.p2, n)?;
185 let ys = challenges(tx, params, &mn, n)?;
186 let w = small_w(&mn, n)?;
187 let one = BoxedUint::one_with_precision(64);
188 let is_plus = |r: &Residue| resized(&r.retrieve(), 64) == one;
189
190 let mut reps = Vec::with_capacity(ys.len());
191 for y in &ys {
192 let y1 = c1.residue(y);
193 let y2 = c2.residue(y);
194 let l1 = c1.ring.pow_ct(&y1, &c1.half, c1.bits)?;
196 let l2 = c2.ring.pow_ct(&y2, &c2.half, c2.bits)?;
197 let a = !is_plus(&l2);
199 let b = !is_plus(&l1) ^ a;
200 let y0 = target(y, &w, a, b);
201 let y01 = c1.residue(&y0);
202 let y02 = c2.residue(&y0);
203 let r1 = random_neg(rng, &c1.ring.pow_ct(&y01, &c1.root_exp, c1.bits)?);
204 let r2 = random_neg(rng, &c2.ring.pow_ct(&y02, &c2.root_exp, c2.bits)?);
205 let x = crt(&c1, &r1, wit.p2, &r2, &mn)?;
206 let z1 = c1.ring.pow_ct(&y1, &c1.n_inv, c1.bits)?;
207 let z2 = c2.ring.pow_ct(&y2, &c2.n_inv, c2.bits)?;
208 let z = crt(&c1, &z1, wit.p2, &z2, &mn)?;
209 reps.push(ModRep {
210 a,
211 b,
212 x: x.retrieve(),
213 z: z.retrieve(),
214 });
215 }
216 Ok(Self { reps })
217 }
218
219 pub fn verify(
221 &self,
222 tx: &Transcript,
223 params: &Params,
224 n: &BoxedUint,
225 min_bits: u32,
226 ) -> Result<()> {
227 let fail = Error::VerificationFailed;
228 if n.bits() < min_bits || self.reps.len() != params.r_mod as usize {
229 return Err(fail);
230 }
231 if n.as_words()[0] & 3 != 1 {
232 return Err(fail);
233 }
234 let mn = Modulus::new(n.clone()).map_err(|_| fail)?;
235 if mn.jacobi_vartime(&BoxedUint::from(2u64)) != -1 {
236 return Err(fail);
237 }
238 for rep in &self.reps {
240 for v in [&rep.x, &rep.z] {
241 let v = resized(v, n.bits_precision());
242 let nn = resized(n, v.bits_precision());
243 if v.cmp_vartime(&nn) != core::cmp::Ordering::Less {
244 return Err(fail);
245 }
246 if !bool::from(v.gcd(&nn).is_one()) {
247 return Err(fail);
248 }
249 }
250 }
251 if is_prime(Flavor::Any, n) {
252 return Err(fail);
253 }
254 let ys = challenges(tx, params, &mn, n)?;
255 let w = small_w(&mn, n)?;
256 for (rep, y) in self.reps.iter().zip(&ys) {
257 let x = mn.residue(&rep.x);
258 let x4 = x.square().square();
259 if x4 != target(y, &w, rep.a, rep.b) {
260 return Err(fail);
261 }
262 }
263 let n_exp = SBig::from_uint(n.clone());
264 for (rep, y) in self.reps.iter().zip(&ys) {
265 let z = mn.residue(&rep.z);
266 if mn.pow_vartime(&z, &n_exp)? != *y {
267 return Err(fail);
268 }
269 }
270 Ok(())
271 }
272
273 pub fn encoded_len(params: &Params, n_bits: u32) -> usize {
275 params.r_mod as usize * (1 + 2 * n_bytes(n_bits))
276 }
277
278 pub fn encode(&self, n_bits: u32) -> Result<Vec<u8>> {
280 let len = n_bytes(n_bits);
281 let mut out = Vec::with_capacity(self.reps.len() * (1 + 2 * len));
282 for r in &self.reps {
283 out.push(u8::from(r.a) | (u8::from(r.b) << 1));
284 out.extend_from_slice(&uint_to_be(&r.x, len)?);
285 out.extend_from_slice(&uint_to_be(&r.z, len)?);
286 }
287 Ok(out)
288 }
289
290 pub fn decode(bytes: &[u8], params: &Params, n_bits: u32) -> Result<Self> {
291 if bytes.len() != Self::encoded_len(params, n_bits) {
292 return Err(Error::InvalidEncoding);
293 }
294 let len = n_bytes(n_bits);
295 let mut reps = Vec::with_capacity(params.r_mod as usize);
296 for chunk in bytes.chunks_exact(1 + 2 * len) {
297 if chunk[0] > 3 {
298 return Err(Error::InvalidEncoding);
299 }
300 reps.push(ModRep {
301 a: chunk[0] & 1 == 1,
302 b: chunk[0] & 2 == 2,
303 x: uint_from_be(&chunk[1..1 + len], n_bits)?,
304 z: uint_from_be(&chunk[1 + len..], n_bits)?,
305 });
306 }
307 Ok(Self { reps })
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 extern crate std;
314 use super::*;
315 use crate::{
316 params::PARAMS_128,
317 rsa::tough::{ToughSpec, sample_rsa_with, sample_tough_prime},
318 };
319 use rand_chacha::ChaCha8Rng;
320 use rand_core::SeedableRng;
321 use std::{sync::OnceLock, time::Instant};
322
323 const SPEC: ToughSpec = ToughSpec { count: 4, bits: 64 };
324
325 fn params() -> Params {
326 Params {
327 r_mod: 8,
328 ..PARAMS_128
329 }
330 }
331
332 struct Fx {
333 n: BoxedUint,
334 p1: BoxedUint,
335 p2: BoxedUint,
336 }
337
338 fn fx() -> &'static Fx {
339 static F: OnceLock<Fx> = OnceLock::new();
340 F.get_or_init(|| {
341 let mut rng = ChaCha8Rng::seed_from_u64(11);
342 let m = sample_rsa_with(&mut rng, 512, SPEC).unwrap();
343 Fx {
344 n: m.n.clone(),
345 p1: m.secret.p1.p().clone(),
346 p2: m.secret.p2.p().clone(),
347 }
348 })
349 }
350
351 fn tx() -> Transcript {
352 Transcript::new("test/mod")
353 }
354
355 fn honest() -> ModProof {
356 let f = fx();
357 let mut rng = ChaCha8Rng::seed_from_u64(5);
358 ModProof::prove(
359 &mut rng,
360 &tx(),
361 ¶ms(),
362 &f.n,
363 &ModWitness {
364 p1: &f.p1,
365 p2: &f.p2,
366 },
367 )
368 .unwrap()
369 }
370
371 #[test]
372 fn honest_passes() {
373 let f = fx();
374 honest().verify(&tx(), ¶ms(), &f.n, 512).unwrap();
375 let p = Params {
377 r_mod: 64,
378 ..PARAMS_128
379 };
380 let mut rng = ChaCha8Rng::seed_from_u64(6);
381 let pr = ModProof::prove(
382 &mut rng,
383 &tx(),
384 &p,
385 &f.n,
386 &ModWitness {
387 p1: &f.p1,
388 p2: &f.p2,
389 },
390 )
391 .unwrap();
392 pr.verify(&tx(), &p, &f.n, 512).unwrap();
393 for ab in 0..4u8 {
394 assert!(
395 pr.reps
396 .iter()
397 .any(|r| u8::from(r.a) | (u8::from(r.b) << 1) == ab)
398 );
399 }
400 }
401
402 #[test]
403 fn wrong_transcript_or_min_bits() {
404 let f = fx();
405 let pr = honest();
406 assert!(
407 pr.verify(&Transcript::new("other"), ¶ms(), &f.n, 512)
408 .is_err()
409 );
410 assert!(pr.verify(&tx(), ¶ms(), &f.n, 513).is_err());
411 }
412
413 #[test]
414 fn modified_fields_fail() {
415 let f = fx();
416 let one = BoxedUint::one_with_precision(64);
417 for i in [0usize, 3, 7] {
418 let mut p = honest();
419 p.reps[i].a = !p.reps[i].a;
420 assert!(p.verify(&tx(), ¶ms(), &f.n, 512).is_err());
421 let mut p = honest();
422 p.reps[i].b = !p.reps[i].b;
423 assert!(p.verify(&tx(), ¶ms(), &f.n, 512).is_err());
424 let mut p = honest();
425 p.reps[i].x = resized(&p.reps[i].x, 576).wrapping_add(resized(&one, 576));
426 assert!(p.verify(&tx(), ¶ms(), &f.n, 512).is_err());
427 let mut p = honest();
428 p.reps[i].z = resized(&p.reps[i].z, 576).wrapping_add(resized(&one, 576));
429 assert!(p.verify(&tx(), ¶ms(), &f.n, 512).is_err());
430 let mut p = honest();
432 p.reps[i].x = resized(&f.n, 576).wrapping_sub(resized(&p.reps[i].x, 576));
433 p.verify(&tx(), ¶ms(), &f.n, 512).unwrap();
434 let mut p = honest();
436 p.reps[i].x = f.n.clone();
437 assert!(p.verify(&tx(), ¶ms(), &f.n, 512).is_err());
438 let mut p = honest();
439 p.reps[i].z = BoxedUint::zero_with_precision(64);
440 assert!(p.verify(&tx(), ¶ms(), &f.n, 512).is_err());
441 }
442 let mut p = honest();
443 p.reps.pop();
444 assert!(p.verify(&tx(), ¶ms(), &f.n, 512).is_err());
445 }
446
447 #[test]
448 fn bad_moduli_fail() {
449 let f = fx();
450 let pr = honest();
451 let mut rng = ChaCha8Rng::seed_from_u64(9);
452 let n3 = f.n.wrapping_add(BoxedUint::from(2u64));
454 assert!(pr.verify(&tx(), ¶ms(), &n3, 512).is_err());
455 let pm = sample_tough_prime(&mut rng, 512, 3, ToughSpec::SAFE).unwrap();
457 let pn = pm.p().clone();
458 assert!(pr.verify(&tx(), ¶ms(), &pn, 512).is_err());
459 let ne = f.n.wrapping_sub(BoxedUint::one());
461 assert!(pr.verify(&tx(), ¶ms(), &ne, 512).is_err());
462 let p5 = loop {
464 let c = crypto_primes::random_prime::<BoxedUint, _>(&mut rng, Flavor::Any, 256);
465 if c.as_words()[0] & 7 == 5 {
466 break c;
467 }
468 };
469 assert!(pr.verify(&tx(), ¶ms(), &p5, 256).is_err());
470 let a = sample_tough_prime(&mut rng, 256, 3, ToughSpec::SAFE).unwrap();
472 let b = sample_tough_prime(&mut rng, 256, 3, ToughSpec::SAFE).unwrap();
473 let n33 = a.p().concatenating_mul(b.p());
474 let w = ModWitness {
475 p1: a.p(),
476 p2: b.p(),
477 };
478 assert!(ModProof::prove(&mut rng, &tx(), ¶ms(), &n33, &w).is_err());
479 let forged = ModProof::prove_inner(&mut rng, &tx(), ¶ms(), &n33, &w, false).unwrap();
480 assert!(forged.verify(&tx(), ¶ms(), &n33, 512).is_err());
481 let w2 = ModWitness {
483 p1: &f.p2,
484 p2: &f.p1,
485 };
486 assert!(ModProof::prove(&mut rng, &tx(), ¶ms(), &f.n, &w2).is_err());
487 let w3 = ModWitness {
489 p1: &f.p1,
490 p2: a.p(),
491 };
492 assert!(ModProof::prove(&mut rng, &tx(), ¶ms(), &f.n, &w3).is_err());
493 }
494
495 #[test]
496 fn three_factor_modulus_fails() {
497 let mut rng = ChaCha8Rng::seed_from_u64(10);
498 let p1 = sample_tough_prime(&mut rng, 200, 3, ToughSpec::SAFE).unwrap();
499 let p2 = sample_tough_prime(&mut rng, 200, 7, ToughSpec::SAFE).unwrap();
500 let r = loop {
502 let c = crypto_primes::random_prime::<BoxedUint, _>(&mut rng, Flavor::Any, 112);
503 if c.as_words()[0] & 7 == 1 {
504 break c;
505 }
506 };
507 let n = p1.p().concatenating_mul(p2.p()).concatenating_mul(&r);
508 let w = ModWitness {
509 p1: p1.p(),
510 p2: &p2.p().concatenating_mul(&r),
511 };
512 let forged = ModProof::prove_inner(&mut rng, &tx(), ¶ms(), &n, &w, false);
513 if let Ok(f) = forged {
514 assert!(f.verify(&tx(), ¶ms(), &n, 400).is_err());
515 }
516 let junk = ModProof {
518 reps: (0..8)
519 .map(|i| ModRep {
520 a: false,
521 b: false,
522 x: BoxedUint::from(5u64 + i),
523 z: BoxedUint::from(7u64 + i),
524 })
525 .collect(),
526 };
527 assert!(junk.verify(&tx(), ¶ms(), &n, 400).is_err());
528 }
529
530 #[test]
531 fn encode_decode() {
532 let f = fx();
533 let pr = honest();
534 let bytes = pr.encode(f.n.bits()).unwrap();
535 assert_eq!(bytes.len(), ModProof::encoded_len(¶ms(), 512));
536 let back = ModProof::decode(&bytes, ¶ms(), 512).unwrap();
537 back.verify(&tx(), ¶ms(), &f.n, 512).unwrap();
538 assert!(ModProof::decode(&bytes[1..], ¶ms(), 512).is_err());
539 let mut long = bytes.clone();
540 long.push(0);
541 assert!(ModProof::decode(&long, ¶ms(), 512).is_err());
542 let mut bad = bytes.clone();
543 bad[0] = 4;
544 assert!(ModProof::decode(&bad, ¶ms(), 512).is_err());
545 }
546
547 #[test]
548 #[ignore]
549 fn full_size_timings() {
550 let mut rng = ChaCha8Rng::seed_from_u64(77);
551 let spec = ToughSpec::from_params(&PARAMS_128);
552 let t0 = Instant::now();
553 let m = sample_rsa_with(&mut rng, PARAMS_128.n_rsa, spec).unwrap();
554 std::println!("sample_rsa: {:?}", t0.elapsed());
555 let (p1, p2) = (m.secret.p1.p().clone(), m.secret.p2.p().clone());
556 let t0 = Instant::now();
557 let pr = ModProof::prove(
558 &mut rng,
559 &tx(),
560 &PARAMS_128,
561 &m.n,
562 &ModWitness { p1: &p1, p2: &p2 },
563 )
564 .unwrap();
565 std::println!("mod prove: {:?}", t0.elapsed());
566 let t0 = Instant::now();
567 pr.verify(&tx(), &PARAMS_128, &m.n, PARAMS_128.n_rsa)
568 .unwrap();
569 std::println!("mod verify: {:?}", t0.elapsed());
570 let bytes = pr.encode(PARAMS_128.n_rsa).unwrap();
571 std::println!("mod proof size: {} bytes", bytes.len());
572 let mut bad = pr.clone();
573 bad.reps[63].a = !bad.reps[63].a;
574 assert!(
575 bad.verify(&tx(), &PARAMS_128, &m.n, PARAMS_128.n_rsa)
576 .is_err()
577 );
578 }
579}