|
| 1 | +/// Cipolla algorithm |
| 2 | +/// |
| 3 | +/// Solving quadratic residue problem: |
| 4 | +/// x^2 = a (mod p) , p is an odd prime |
| 5 | +/// with O(M*log(n)) time complexity, M depends on the complexity of complex numbers multiplication. |
| 6 | +/// |
| 7 | +/// Wikipedia reference: https://en.wikipedia.org/wiki/Cipolla%27s_algorithm |
| 8 | +/// When a is the primitive root modulo n, the answer is unique. |
| 9 | +/// Otherwise it will return the smallest positive solution |
| 10 | +use std::rc::Rc; |
| 11 | +use std::time::{SystemTime, UNIX_EPOCH}; |
| 12 | + |
| 13 | +use super::{fast_power, PCG32}; |
| 14 | + |
| 15 | +#[derive(Debug)] |
| 16 | +struct CustomFiniteFiled { |
| 17 | + modulus: u64, |
| 18 | + i_square: u64, |
| 19 | +} |
| 20 | + |
| 21 | +impl CustomFiniteFiled { |
| 22 | + pub fn new(modulus: u64, i_square: u64) -> Self { |
| 23 | + Self { modulus, i_square } |
| 24 | + } |
| 25 | +} |
| 26 | + |
| 27 | +#[derive(Clone, Debug)] |
| 28 | +struct CustomComplexNumber { |
| 29 | + real: u64, |
| 30 | + imag: u64, |
| 31 | + f: Rc<CustomFiniteFiled>, |
| 32 | +} |
| 33 | + |
| 34 | +impl CustomComplexNumber { |
| 35 | + pub fn new(real: u64, imag: u64, f: Rc<CustomFiniteFiled>) -> Self { |
| 36 | + Self { real, imag, f } |
| 37 | + } |
| 38 | + |
| 39 | + pub fn mult_other(&mut self, rhs: &Self) { |
| 40 | + let tmp = (self.imag * rhs.real + self.real * rhs.imag) % self.f.modulus; |
| 41 | + self.real = (self.real * rhs.real |
| 42 | + + ((self.imag * rhs.imag) % self.f.modulus) * self.f.i_square) |
| 43 | + % self.f.modulus; |
| 44 | + self.imag = tmp; |
| 45 | + } |
| 46 | + |
| 47 | + pub fn mult_self(&mut self) { |
| 48 | + let tmp = (self.imag * self.real + self.real * self.imag) % self.f.modulus; |
| 49 | + self.real = (self.real * self.real |
| 50 | + + ((self.imag * self.imag) % self.f.modulus) * self.f.i_square) |
| 51 | + % self.f.modulus; |
| 52 | + self.imag = tmp; |
| 53 | + } |
| 54 | + |
| 55 | + pub fn fast_power(mut base: Self, mut power: u64) -> Self { |
| 56 | + let mut result = CustomComplexNumber::new(1, 0, base.f.clone()); |
| 57 | + while power != 0 { |
| 58 | + if (power & 1) != 0 { |
| 59 | + result.mult_other(&base); // result *= base; |
| 60 | + } |
| 61 | + base.mult_self(); // base *= base; |
| 62 | + power >>= 1; |
| 63 | + } |
| 64 | + result |
| 65 | + } |
| 66 | +} |
| 67 | + |
| 68 | +fn is_residue(x: u64, modulus: u64) -> bool { |
| 69 | + let power = (modulus - 1) >> 1; |
| 70 | + x != 0 && fast_power(x as usize, power as usize, modulus as usize) == 1 |
| 71 | +} |
| 72 | + |
| 73 | +// return two solutions (x1, x2) for Quadratic Residue problem x^2 = a (mod p), where p is an odd prime |
| 74 | +// if a is Quadratic Nonresidues, return None |
| 75 | +pub fn cipolla(a: u32, p: u32, seed: Option<u64>) -> Option<(u32, u32)> { |
| 76 | + // The params should be kept in u32 range for multiplication overflow issue |
| 77 | + // But inside we use u64 for convenience |
| 78 | + let a = a as u64; |
| 79 | + let p = p as u64; |
| 80 | + if a == 0 { |
| 81 | + return Some((0, 0)); |
| 82 | + } |
| 83 | + if !is_residue(a, p) { |
| 84 | + return None; |
| 85 | + } |
| 86 | + let seed = match seed { |
| 87 | + Some(seed) => seed, |
| 88 | + None => SystemTime::now() |
| 89 | + .duration_since(UNIX_EPOCH) |
| 90 | + .unwrap() |
| 91 | + .as_secs(), |
| 92 | + }; |
| 93 | + let mut rng = PCG32::new_default(seed); |
| 94 | + let r = loop { |
| 95 | + let r = rng.get_u64() % p; |
| 96 | + if r == 0 || !is_residue((p + r * r - a) % p, p) { |
| 97 | + break r; |
| 98 | + } |
| 99 | + }; |
| 100 | + let filed = Rc::new(CustomFiniteFiled::new(p, (p + r * r - a) % p)); |
| 101 | + let comp = CustomComplexNumber::new(r, 1, filed); |
| 102 | + let power = (p + 1) >> 1; |
| 103 | + let x0 = CustomComplexNumber::fast_power(comp, power).real as u32; |
| 104 | + let x1 = p as u32 - x0 as u32; |
| 105 | + if x0 < x1 { |
| 106 | + Some((x0, x1)) |
| 107 | + } else { |
| 108 | + Some((x1, x0)) |
| 109 | + } |
| 110 | +} |
| 111 | + |
| 112 | +#[cfg(test)] |
| 113 | +mod tests { |
| 114 | + use super::*; |
| 115 | + |
| 116 | + #[test] |
| 117 | + fn small_numbers() { |
| 118 | + assert_eq!(cipolla(1, 43, None), Some((1, 42))); |
| 119 | + assert_eq!(cipolla(2, 23, None), Some((5, 18))); |
| 120 | + assert_eq!(cipolla(17, 83, Some(42)), Some((10, 73))); |
| 121 | + } |
| 122 | + |
| 123 | + #[test] |
| 124 | + fn random_numbers() { |
| 125 | + assert_eq!(cipolla(392203, 852167, None), Some((413252, 438915))); |
| 126 | + assert_eq!( |
| 127 | + cipolla(379606557, 425172197, None), |
| 128 | + Some((143417827, 281754370)) |
| 129 | + ); |
| 130 | + assert_eq!( |
| 131 | + cipolla(585251669, 892950901, None), |
| 132 | + Some((192354555, 700596346)) |
| 133 | + ); |
| 134 | + assert_eq!( |
| 135 | + cipolla(404690348, 430183399, Some(19260817)), |
| 136 | + Some((57227138, 372956261)) |
| 137 | + ); |
| 138 | + assert_eq!( |
| 139 | + cipolla(210205747, 625380647, Some(998244353)), |
| 140 | + Some((76810367, 548570280)) |
| 141 | + ); |
| 142 | + } |
| 143 | + |
| 144 | + #[test] |
| 145 | + fn no_answer() { |
| 146 | + assert_eq!(cipolla(650927, 852167, None), None); |
| 147 | + } |
| 148 | +} |
0 commit comments