1use alloc::vec::Vec;
18use core::fmt;
19
20use crypto_bigint::modular::{BoxedMontyForm, BoxedMontyParams};
21use crypto_bigint::{
22 BoxedUint, ConcatenatingMul, Gcd, Limb, NonZero, RandomBits, RandomMod, Resize,
23};
24use crypto_primes::{Flavor, is_prime, random_prime};
25use rand_core::CryptoRng;
26use zeroize::{Zeroize, ZeroizeOnDrop};
27
28use crate::params::Params;
29
30const MIN_PRIME_BITS: u32 = 32;
32const SIEVE_LIMIT: u32 = 16384;
34const MAX_PREFIX_ATTEMPTS: u32 = 100_000;
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub enum ToughError {
40 BadSize,
42}
43
44impl fmt::Display for ToughError {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 match self {
47 ToughError::BadSize => f.write_str("inconsistent tough-prime sizes"),
48 }
49 }
50}
51
52#[derive(Clone, Copy, Debug, PartialEq, Eq)]
54pub struct ToughSpec {
55 pub count: u32,
57 pub bits: u32,
59}
60
61impl ToughSpec {
62 pub const fn from_params(params: &Params) -> Self {
64 Self {
65 count: params.tough_count,
66 bits: params.tough_bits,
67 }
68 }
69 pub const SAFE: Self = Self { count: 1, bits: 0 };
71}
72
73#[derive(ZeroizeOnDrop)]
75pub struct ToughPrime {
76 p: BoxedUint,
77 factors: Vec<BoxedUint>,
78}
79
80impl ToughPrime {
81 pub fn p(&self) -> &BoxedUint {
83 &self.p
84 }
85 pub fn factors(&self) -> &[BoxedUint] {
87 &self.factors
88 }
89 pub fn half(&self) -> BoxedUint {
91 self.factors
92 .iter()
93 .fold(BoxedUint::one(), |a, q| a.concatenating_mul(q))
94 }
95}
96
97impl fmt::Debug for ToughPrime {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 f.write_str("ToughPrime(***)")
100 }
101}
102
103#[derive(ZeroizeOnDrop)]
105pub struct RsaSecret {
106 pub p1: ToughPrime,
107 pub p2: ToughPrime,
108}
109
110impl fmt::Debug for RsaSecret {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 f.write_str("RsaSecret(***)")
113 }
114}
115
116pub struct RsaModulus {
118 pub n: BoxedUint,
120 pub secret: RsaSecret,
122}
123
124impl fmt::Debug for RsaModulus {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 write!(f, "RsaModulus {{ n: {:?}, secret: *** }}", self.n.bits())
127 }
128}
129
130#[derive(ZeroizeOnDrop)]
132pub struct DfSecret {
133 pub rsa: RsaSecret,
134 pub lambda1: BoxedUint,
135 pub lambda2: BoxedUint,
136}
137
138impl fmt::Debug for DfSecret {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 f.write_str("DfSecret(***)")
141 }
142}
143
144pub struct DfTuple {
146 pub n_hat: BoxedUint,
147 pub t: BoxedUint,
148 pub s1: BoxedUint,
149 pub s2: BoxedUint,
150 pub secret: DfSecret,
151}
152
153impl fmt::Debug for DfTuple {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 write!(
156 f,
157 "DfTuple {{ n_hat: {:?} bits, secret: *** }}",
158 self.n_hat.bits()
159 )
160 }
161}
162
163fn small_primes() -> Vec<NonZero<Limb>> {
164 let mut sieve = alloc::vec![true; SIEVE_LIMIT as usize + 1];
165 let mut out = Vec::new();
166 for i in 2..=SIEVE_LIMIT {
167 if sieve[i as usize] {
168 if let Some(nz) = Option::from(NonZero::new(Limb::from_u32(i))) {
169 out.push(nz);
170 }
171 let mut j = i * i;
172 while j <= SIEVE_LIMIT {
173 sieve[j as usize] = false;
174 j += i;
175 }
176 }
177 }
178 out
179}
180
181const _: () = assert!(Limb::BITS == 64);
182
183fn mod_small(x: &BoxedUint, s: u64) -> u64 {
185 let a = (1u64 << 32) % s;
186 let r_word = a * a % s; x.as_words()
188 .iter()
189 .rev()
190 .fold(0u64, |r, &w| (r * r_word % s + w % s) % s)
191}
192
193fn divisible_by_small(x: &BoxedUint, primes: &[NonZero<Limb>]) -> bool {
194 primes.iter().any(|q| mod_small(x, q.get().0) == 0)
195}
196
197#[derive(ZeroizeOnDrop)]
199struct Pool(Vec<BoxedUint>);
200
201fn binom_sat(n: u32, k: u32) -> u128 {
203 let mut r: u128 = 1;
204 for i in 0..k {
205 r = r.saturating_mul(u128::from(n - i)) / u128::from(i + 1);
206 }
207 r
208}
209
210fn random_prime_in<R: CryptoRng + ?Sized>(
213 rng: &mut R,
214 qlo: &BoxedUint,
215 qhi: &BoxedUint,
216 low2: u64,
217 sieve: &[NonZero<Limb>],
218) -> Option<BoxedUint> {
219 const W: usize = 2048;
220 let one = BoxedUint::one();
221 let span = BoxedUint::from(4 * W as u64);
222 let width = qhi.wrapping_sub(qlo).wrapping_sub(&span).wrapping_add(&one);
223 let width_nz = Option::<NonZero<BoxedUint>>::from(width.to_nz())?;
224 let mut marks = alloc::vec![false; W];
225 loop {
226 let mut q0 = BoxedUint::random_mod_vartime(rng, &width_nz).wrapping_add(qlo);
227 let cur = q0.as_words()[0] & 3;
228 q0 = q0
229 .wrapping_sub(BoxedUint::from(cur))
230 .wrapping_add(BoxedUint::from(low2));
231 if q0 < *qlo {
232 continue;
233 }
234 marks.iter_mut().for_each(|m| *m = false);
235 for sp in sieve.iter().skip(1) {
236 let s = sp.get().0;
237 let r = mod_small(&q0, s);
238 let inv2 = s.div_ceil(2);
239 let inv4 = inv2 * inv2 % s;
240 let mut i = ((s - r) % s) * inv4 % s;
241 while (i as usize) < W {
242 marks[i as usize] = true;
243 i += s;
244 }
245 }
246 for (i, m) in marks.iter().enumerate() {
247 if *m {
248 continue;
249 }
250 let q = q0.wrapping_add(BoxedUint::from(4 * i as u64));
251 if q > *qhi {
252 break;
253 }
254 if is_prime(Flavor::Any, &q) {
255 return Some(q);
256 }
257 }
258 }
259}
260
261fn sample_from_pool<R: CryptoRng + ?Sized>(
267 rng: &mut R,
268 bits: u32,
269 t: u64,
270 spec: ToughSpec,
271 sieve: &[NonZero<Limb>],
272) -> Result<ToughPrime, ToughError> {
273 let k = spec.count;
274 let b = spec.bits;
275 let ku = k as usize;
276 let prec = bits.div_ceil(64) * 64;
277 let wide = prec + 64;
278 let one = BoxedUint::one_with_precision(wide);
279 let lo = BoxedUint::from(3u8).resize(wide).shl(bits - 2);
280 let hi = one.shl(bits);
281 let mid = lo.wrapping_add(&hi).shr(2);
283
284 let mut pool_n = k + 1;
286 while binom_sat(pool_n, k) < 6 * u128::from(bits) {
287 pool_n += 1;
288 }
289 let pn = pool_n as usize;
290
291 let pow_k = |j: u32| -> BoxedUint {
293 let q0 = BoxedUint::from(256 + j).resize(wide).shl(b - 9);
294 let mut acc = one.clone();
295 for _ in 0..k {
296 acc = acc.concatenating_mul(&q0).resize(wide + 64 * k);
297 }
298 acc
299 };
300 let mid_big = mid.resize(wide + 64 * k);
301 let (mut jl, mut jh) = (0u32, 255u32);
302 while jl < jh {
303 let m = (jl + jh).div_ceil(2);
304 if pow_k(m) <= mid_big {
305 jl = m;
306 } else {
307 jh = m - 1;
308 }
309 }
310 let qlo = BoxedUint::from(256 + jl).resize(wide).shl(b - 9);
311 let qhi = qlo.wrapping_add(qlo.shr(6));
312 let q_max = one.shl(b).wrapping_sub(&one);
313 let qhi = if qhi > q_max { q_max } else { qhi };
314
315 let primes32: Vec<u32> = sieve.iter().map(|x| x.get().0 as u32).collect();
316 let tries_per_pool = 16 * bits;
317 let mut resid = alloc::vec![0u32; primes32.len() * pn];
318
319 for _ in 0..MAX_PREFIX_ATTEMPTS.min(64) {
320 let mut pool = Pool(Vec::with_capacity(pn));
322 while pool.0.len() < pn {
323 let low2 = if t == 1 || pool.0.len() % 2 == 0 {
324 1
325 } else {
326 3
327 };
328 let q = random_prime_in(rng, &qlo, &qhi, low2, sieve).ok_or(ToughError::BadSize)?;
329 if !pool.0.contains(&q) {
330 pool.0.push(q);
331 }
332 }
333 let mod4: Vec<u64> = pool.0.iter().map(|q| q.as_words()[0] & 3).collect();
334 for (i, sp) in sieve.iter().enumerate() {
335 for (j, q) in pool.0.iter().enumerate() {
336 resid[i * pn + j] = mod_small(q, sp.get().0) as u32;
337 }
338 }
339 let mut idx: Vec<usize> = (0..pn).collect();
340 let mut seen: Vec<u64> = Vec::new();
341 for _ in 0..tries_per_pool {
342 for i in 0..ku {
344 let r = rng.next_u32() as usize % (pn - i);
345 idx.swap(i, i + r);
346 }
347 let sub = &idx[..ku];
348 let prod4 = sub.iter().fold(1u64, |a, &j| (a * mod4[j]) & 3);
349 if prod4 != t {
350 continue;
351 }
352 let mut ok = true;
353 for (i, &sp) in primes32.iter().enumerate() {
354 let sp = u64::from(sp);
355 let mut r = 2u64;
356 for &j in sub {
357 r = r * u64::from(resid[i * pn + j]) % sp;
358 }
359 if r + 1 == sp {
360 ok = false;
361 break;
362 }
363 }
364 if !ok {
365 continue;
366 }
367 let mask = sub.iter().fold(0u64, |a, &j| a | (1u64 << (j % 64)));
369 if pn <= 64 {
370 if seen.contains(&mask) {
371 continue;
372 }
373 seen.push(mask);
374 }
375 let m = sub.iter().fold(one.clone(), |a, &j| {
376 a.concatenating_mul(&pool.0[j]).resize(wide)
377 });
378 let p = m.shl(1).wrapping_add(&one);
379 if p < lo || p >= hi {
380 continue;
381 }
382 let p = p.resize(prec);
383 if !is_prime(Flavor::Any, &p) {
384 continue;
385 }
386 let factors = sub
387 .iter()
388 .map(|&j| pool.0[j].clone().resize(prec))
389 .collect();
390 return Ok(ToughPrime { p, factors });
391 }
392 }
393 Err(ToughError::BadSize)
394}
395
396fn sample_tough_prime_inner<R: CryptoRng + ?Sized>(
400 rng: &mut R,
401 bits: u32,
402 residue_mod8: u8,
403 spec: ToughSpec,
404 sieve: &[NonZero<Limb>],
405) -> Result<ToughPrime, ToughError> {
406 if residue_mod8 != 3 && residue_mod8 != 7 {
407 return Err(ToughError::BadSize);
408 }
409 let k = spec.count;
410 let b = spec.bits;
411 if k == 0 || bits < 2 * MIN_PRIME_BITS || (b == 0 && k != 1) || (b != 0 && b < MIN_PRIME_BITS) {
412 return Err(ToughError::BadSize);
413 }
414 if b != 0 && (k * (b - 1) + 1 >= bits || k * b + 1 < bits) {
416 return Err(ToughError::BadSize);
417 }
418 let t: u64 = if residue_mod8 == 3 { 1 } else { 3 };
420 if k >= 2 {
421 return sample_from_pool(rng, bits, t, spec, sieve);
422 }
423
424 let prec = bits.div_ceil(64) * 64;
425 let wide = prec + 64;
426 let one = BoxedUint::one_with_precision(wide);
427 let lo = BoxedUint::from(3u8).resize(wide).shl(bits - 2);
428 let hi = one.shl(bits);
429 let lo_m1 = lo.wrapping_sub(&one);
430 let hi_m2 = hi.wrapping_sub(BoxedUint::from(2u8));
431 let q_floor = if b == 0 {
432 BoxedUint::zero_with_precision(wide)
433 } else {
434 one.shl(b - 1)
435 };
436 let q_ceil = if b == 0 {
437 None
438 } else {
439 Some(one.shl(b).wrapping_sub(&one))
440 };
441
442 'outer: for _ in 0..MAX_PREFIX_ATTEMPTS {
443 let mut factors: Vec<BoxedUint> = Vec::new();
444 for _ in 0..k - 1 {
445 let q: BoxedUint = random_prime(rng, Flavor::Any, b);
446 if factors.contains(&q) {
447 continue 'outer;
448 }
449 factors.push(q);
450 }
451 let m = factors
452 .iter()
453 .fold(one.clone(), |a, q| a.concatenating_mul(q).resize(wide));
454 let two_m = m.shl(1);
455 let Some(two_m_nz) = Option::<NonZero<BoxedUint>>::from(two_m.to_nz()) else {
456 return Err(ToughError::BadSize);
457 };
458 let (qlo, rem) = lo_m1.div_rem_vartime(&two_m_nz);
460 let qlo = if bool::from(rem.is_zero()) {
461 qlo
462 } else {
463 qlo.wrapping_add(&one)
464 };
465 let qhi = hi_m2.div_rem_vartime(&two_m_nz).0;
466 let (qlo, qhi) = match &q_ceil {
467 Some(c) => (
468 if qlo < q_floor { q_floor.clone() } else { qlo },
469 if qhi > *c { c.clone() } else { qhi },
470 ),
471 None => (qlo, qhi),
472 };
473 if qlo > qhi {
474 continue;
475 }
476 let width = qhi.wrapping_sub(&qlo).wrapping_add(&one);
477 let Some(width_nz) = Option::<NonZero<BoxedUint>>::from(width.to_nz()) else {
478 continue;
479 };
480 let m_mod4 = m.as_words()[0] & 3;
481 let low2 = (t * m_mod4) & 3; loop {
483 let r = BoxedUint::random_mod_vartime(rng, &width_nz);
484 let mut q = r.wrapping_add(&qlo);
485 let cur = q.as_words()[0] & 3;
487 q = q
488 .wrapping_sub(BoxedUint::from(cur))
489 .wrapping_add(BoxedUint::from(low2));
490 if q < qlo || q > qhi {
491 continue;
493 }
494 if divisible_by_small(&q, sieve) {
495 continue;
496 }
497 let p = two_m
498 .concatenating_mul(&q)
499 .wrapping_add(BoxedUint::one())
500 .resize(prec);
501 if divisible_by_small(&p, sieve) || factors.contains(&q) {
502 continue;
503 }
504 if !is_prime(Flavor::Any, &p) || !is_prime(Flavor::Any, &q) {
505 continue;
506 }
507 factors.push(q.resize(prec));
508 return Ok(ToughPrime { p, factors });
509 }
510 }
511 Err(ToughError::BadSize)
512}
513
514pub fn sample_tough_prime<R: CryptoRng + ?Sized>(
517 rng: &mut R,
518 bits: u32,
519 residue_mod8: u8,
520 spec: ToughSpec,
521) -> Result<ToughPrime, ToughError> {
522 sample_tough_prime_inner(rng, bits, residue_mod8, spec, &small_primes())
523}
524
525pub fn sample_rsa_with<R: CryptoRng + ?Sized>(
527 rng: &mut R,
528 n_bits: u32,
529 spec: ToughSpec,
530) -> Result<RsaModulus, ToughError> {
531 if n_bits % 2 != 0 || n_bits < 2 * MIN_PRIME_BITS + 8 {
532 return Err(ToughError::BadSize);
533 }
534 let bits = n_bits / 2;
535 let sieve = small_primes();
536 let p1 = sample_tough_prime_inner(rng, bits, 3, spec, &sieve)?;
537 let h1 = p1.half();
538 let p2 = loop {
539 let cand = sample_tough_prime_inner(rng, bits, 7, spec, &sieve)?;
540 let g = h1.gcd(&cand.half());
541 if g.is_one().into() {
542 break cand;
543 }
544 };
545 let n = p1.p().concatenating_mul(p2.p());
547 if n.bits() != n_bits {
548 return Err(ToughError::BadSize);
549 }
550 Ok(RsaModulus {
551 n,
552 secret: RsaSecret { p1, p2 },
553 })
554}
555
556pub fn sample_rsa<R: CryptoRng + ?Sized>(
558 rng: &mut R,
559 params: &Params,
560) -> Result<RsaModulus, ToughError> {
561 sample_rsa_with(rng, params.n_rsa, ToughSpec::from_params(params))
562}
563
564pub fn sample_df_with<R: CryptoRng + ?Sized>(
566 rng: &mut R,
567 n_bits: u32,
568 spec: ToughSpec,
569 ell: u32,
570) -> Result<DfTuple, ToughError> {
571 if ell == 0 {
572 return Err(ToughError::BadSize);
573 }
574 let rsa = sample_rsa_with(rng, n_bits, spec)?;
575 let RsaModulus { n, secret } = rsa;
576 let odd = Option::from(n.to_odd()).ok_or(ToughError::BadSize)?;
577 let mparams = BoxedMontyParams::new(odd);
578 let nz = Option::<NonZero<BoxedUint>>::from(n.to_nz()).ok_or(ToughError::BadSize)?;
579
580 let t_form = loop {
582 let mut r = BoxedUint::random_mod_vartime(rng, &nz);
583 let unit: bool = r.gcd(&n).is_one().into();
584 if unit {
585 let f = BoxedMontyForm::new(r.clone(), &mparams).square();
586 r.zeroize();
587 break f;
588 }
589 r.zeroize();
590 };
591 let t = t_form.retrieve();
592
593 let exp_bits = 2 * ell;
594 let lambda1 = BoxedUint::random_bits(rng, exp_bits);
595 let lambda2 = BoxedUint::random_bits(rng, exp_bits);
596 let s1 = t_form.pow_bounded_exp(&lambda1, exp_bits).retrieve();
597 let s2 = t_form.pow_bounded_exp(&lambda2, exp_bits).retrieve();
598
599 Ok(DfTuple {
600 n_hat: n,
601 t,
602 s1,
603 s2,
604 secret: DfSecret {
605 rsa: secret,
606 lambda1,
607 lambda2,
608 },
609 })
610}
611
612pub fn sample_df<R: CryptoRng + ?Sized>(
614 rng: &mut R,
615 params: &Params,
616) -> Result<DfTuple, ToughError> {
617 sample_df_with(
618 rng,
619 params.n_rsa,
620 ToughSpec::from_params(params),
621 params.ell,
622 )
623}
624
625pub fn sample_df_client<R: CryptoRng + ?Sized>(
629 rng: &mut R,
630 params: &Params,
631) -> Result<DfTuple, ToughError> {
632 let spec = ToughSpec {
634 count: params.n_client.div_ceil(4 * params.ell),
635 bits: 2 * params.ell,
636 };
637 sample_df_with(rng, params.n_client, spec, params.ell)
638}
639
640#[cfg(test)]
641mod tests {
642 #![allow(clippy::unwrap_used)]
643 extern crate std;
644 use super::*;
645 use crate::params::PARAMS_128;
646 use rand_chacha::ChaCha8Rng;
647 use rand_core::SeedableRng;
648
649 const SPEC: ToughSpec = ToughSpec { count: 4, bits: 64 };
650
651 fn bit_is_set(x: &BoxedUint, i: u32) -> bool {
652 bool::from(x.bit(i))
653 }
654
655 fn prime(x: &BoxedUint) -> bool {
656 is_prime(Flavor::Any, x)
657 }
658
659 fn mod8(x: &BoxedUint) -> u64 {
660 x.as_words()[0] & 7
661 }
662
663 fn check_prime_structure(tp: &ToughPrime, bits: u32, spec: ToughSpec, res: u64) {
664 let p = tp.p();
665 assert_eq!(p.bits(), bits);
666 assert!(bit_is_set(p, bits - 1) && bit_is_set(p, bits - 2));
667 assert!(prime(p));
668 assert_eq!(mod8(p), res);
669 assert_eq!(tp.factors().len(), spec.count as usize);
670 for q in tp.factors() {
671 if spec.bits != 0 {
672 assert!(q.bits() + 8 >= spec.bits && q.bits() <= spec.bits);
674 }
675 assert!(prime(q));
676 }
677 let expect = tp.half().shl(1).wrapping_add(BoxedUint::one());
678 assert_eq!(&expect, p);
679 }
680
681 #[test]
682 fn tough_prime_structure_both_residues() {
683 let mut rng = ChaCha8Rng::seed_from_u64(1);
684 for res in [3u8, 7u8] {
685 let tp = sample_tough_prime(&mut rng, 256, res, SPEC).unwrap();
686 check_prime_structure(&tp, 256, SPEC, res as u64);
687 }
688 }
689
690 #[test]
691 fn safe_prime_when_count_one() {
692 let mut rng = ChaCha8Rng::seed_from_u64(2);
693 let tp = sample_tough_prime(&mut rng, 128, 7, ToughSpec::SAFE).unwrap();
694 check_prime_structure(&tp, 128, ToughSpec::SAFE, 7);
695 }
696
697 #[test]
698 fn rejects_bad_sizes() {
699 let mut rng = ChaCha8Rng::seed_from_u64(3);
700 assert!(sample_tough_prime(&mut rng, 256, 5, SPEC).is_err());
701 let big = ToughSpec {
702 count: 6,
703 bits: 256,
704 };
705 assert!(sample_tough_prime(&mut rng, 1024, 3, big).is_err());
706 assert!(sample_rsa_with(&mut rng, 513, SPEC).is_err());
707 }
708
709 #[test]
710 fn rsa_small() {
711 let mut rng = ChaCha8Rng::seed_from_u64(4);
712 let bits = 512;
713 let m = sample_rsa_with(&mut rng, bits, SPEC).unwrap();
714 assert_eq!(m.n.bits(), bits);
715 check_prime_structure(&m.secret.p1, bits / 2, SPEC, 3);
716 check_prime_structure(&m.secret.p2, bits / 2, SPEC, 7);
717 assert_eq!(m.secret.p1.p().concatenating_mul(m.secret.p2.p()), m.n);
718 let one = BoxedUint::one();
720 let a = m.secret.p1.p().wrapping_sub(&one);
721 let b = m.secret.p2.p().wrapping_sub(&one);
722 assert_eq!(a.gcd(&b), BoxedUint::from(2u8));
723 for q in m.secret.p1.factors() {
725 assert!(!m.secret.p2.factors().contains(q));
726 }
727 assert!(!alloc::format!("{:?}", m).contains(&alloc::format!("{:x}", m.secret.p1.p())));
728 assert_eq!(alloc::format!("{:?}", m.secret), "RsaSecret(***)");
729 }
730
731 #[test]
732 fn df_small() {
733 let mut rng = ChaCha8Rng::seed_from_u64(5);
734 let ell = 16;
735 let d = sample_df_with(&mut rng, 512, SPEC, ell).unwrap();
736 assert_eq!(d.n_hat.bits(), 512);
737 assert!(d.secret.lambda1.bits() <= 2 * ell && d.secret.lambda2.bits() <= 2 * ell);
738 let odd = Option::from(d.n_hat.to_odd()).unwrap();
739 let mp = BoxedMontyParams::new(odd);
740 let t = BoxedMontyForm::new(d.t.clone(), &mp);
741 assert_eq!(t.pow(&d.secret.lambda1).retrieve(), d.s1);
742 assert_eq!(t.pow(&d.secret.lambda2).retrieve(), d.s2);
743 for p in [d.secret.rsa.p1.p(), d.secret.rsa.p2.p()] {
745 let pm = Option::from(p.to_odd()).unwrap();
746 let pp = BoxedMontyParams::new(pm);
747 let tp = BoxedMontyForm::new(d.t.rem(&Option::from(p.to_nz()).unwrap()), &pp);
748 let e = p.wrapping_sub(BoxedUint::one()).shr(1);
749 assert!(bool::from(tp.pow(&e).retrieve().is_one()));
750 }
751 }
752
753 #[test]
754 #[ignore = "slow in debug: full-size 3072-bit SampleRSA, run with --release"]
755 fn rsa_full_size() {
756 let mut rng = ChaCha8Rng::seed_from_u64(6);
757 let start = std::time::Instant::now();
758 let m = sample_rsa(&mut rng, &PARAMS_128).unwrap();
759 std::println!("SampleRSA 3072: {:?}", start.elapsed());
760 assert_eq!(m.n.bits(), 3072);
761 let spec = ToughSpec::from_params(&PARAMS_128);
762 check_prime_structure(&m.secret.p1, 1536, spec, 3);
763 check_prime_structure(&m.secret.p2, 1536, spec, 7);
764 let one = BoxedUint::one();
765 let g = m
766 .secret
767 .p1
768 .p()
769 .wrapping_sub(&one)
770 .gcd(&m.secret.p2.p().wrapping_sub(&one));
771 assert_eq!(g, BoxedUint::from(2u8));
772 }
773}
774
775#[cfg(test)]
776mod client_df_tests {
777 #![allow(clippy::unwrap_used)]
778 extern crate std;
779 use super::*;
780 use crate::params::PARAMS_128;
781 use rand_chacha::ChaCha20Rng;
782 use rand_core::SeedableRng;
783
784 #[test]
785 #[ignore = "full-size: run with --release"]
786 fn client_df_full_size() {
787 let mut rng = ChaCha20Rng::seed_from_u64(5);
788 let t = std::time::Instant::now();
789 let df = sample_df_client(&mut rng, &PARAMS_128).unwrap();
790 std::eprintln!("client DF (2048-bit tough): {:?}", t.elapsed());
791 assert_eq!(df.n_hat.bits(), PARAMS_128.n_client);
792 }
793
794 #[test]
795 #[ignore = "benchmark: run with --release --nocapture"]
796 fn bench_median_of_10() {
797 let mut c = std::vec::Vec::new();
798 let mut r = std::vec::Vec::new();
799 for seed in 0..10u64 {
800 let mut rng = ChaCha20Rng::seed_from_u64(100 + seed);
801 let t = std::time::Instant::now();
802 let _ = sample_df_client(&mut rng, &PARAMS_128).unwrap();
803 c.push(t.elapsed());
804 let t = std::time::Instant::now();
805 let _ = sample_rsa(&mut rng, &PARAMS_128).unwrap();
806 r.push(t.elapsed());
807 }
808 c.sort();
809 r.sort();
810 std::eprintln!(
811 "client2048 median {:?} max {:?}; server3072 median {:?} max {:?}",
812 c[5],
813 c[9],
814 r[5],
815 r[9]
816 );
817 }
818}