arnaucube
arnaucube/miden-cryptoPublic Self-hosted repository
miden-crypto/src/dsa/rpo_falcon512/math/mod.rs
src/dsa/rpo_falcon512/math/mod.rsRaw
  1//! Contains different structs and methods related to the Falcon DSA.
  2//!
  3//! It uses and acknowledges the work in:
  4//!
  5//! 1. The [reference](https://falcon-sign.info/impl/README.txt.html) implementation by Thomas Pornin.
  6//! 2. The [Rust](https://github.com/aszepieniec/falcon-rust) implementation by Alan Szepieniec.
  7use super::MODULUS;
  8use alloc::{string::String, vec::Vec};
  9use core::ops::MulAssign;
 10use num::{BigInt, FromPrimitive, One, Zero};
 11use num_complex::Complex64;
 12use rand::Rng;
 13
 14#[cfg(not(feature = "std"))]
 15use num::Float;
 16
 17mod fft;
 18pub use fft::{CyclotomicFourier, FastFft};
 19
 20mod field;
 21pub use field::FalconFelt;
 22
 23mod ffsampling;
 24pub use ffsampling::{ffldl, ffsampling, gram, normalize_tree, LdlTree};
 25
 26mod samplerz;
 27use self::samplerz::sampler_z;
 28
 29mod polynomial;
 30pub use polynomial::Polynomial;
 31
 32pub trait Inverse: Copy + Zero + MulAssign + One {
 33    /// Gets the inverse of a, or zero if it is zero.
 34    fn inverse_or_zero(self) -> Self;
 35
 36    /// Gets the inverses of a batch of elements, and skip over any that are zero.
 37    fn batch_inverse_or_zero(batch: &[Self]) -> Vec<Self> {
 38        let mut acc = Self::one();
 39        let mut rp: Vec<Self> = Vec::with_capacity(batch.len());
 40        for batch_item in batch {
 41            if !batch_item.is_zero() {
 42                rp.push(acc);
 43                acc = *batch_item * acc;
 44            } else {
 45                rp.push(Self::zero());
 46            }
 47        }
 48        let mut inv = Self::inverse_or_zero(acc);
 49        for i in (0..batch.len()).rev() {
 50            if !batch[i].is_zero() {
 51                rp[i] *= inv;
 52                inv *= batch[i];
 53            }
 54        }
 55        rp
 56    }
 57}
 58
 59impl Inverse for Complex64 {
 60    fn inverse_or_zero(self) -> Self {
 61        let modulus = self.re * self.re + self.im * self.im;
 62        Complex64::new(self.re / modulus, -self.im / modulus)
 63    }
 64    fn batch_inverse_or_zero(batch: &[Self]) -> Vec<Self> {
 65        batch.iter().map(|&c| Complex64::new(1.0, 0.0) / c).collect()
 66    }
 67}
 68
 69impl Inverse for f64 {
 70    fn inverse_or_zero(self) -> Self {
 71        1.0 / self
 72    }
 73    fn batch_inverse_or_zero(batch: &[Self]) -> Vec<Self> {
 74        batch.iter().map(|&c| 1.0 / c).collect()
 75    }
 76}
 77
 78/// Samples 4 small polynomials f, g, F, G such that f * G - g * F = q mod (X^n + 1).
 79/// Algorithm 5 (NTRUgen) of the documentation [1, p.34].
 80///
 81/// [1]: https://falcon-sign.info/falcon.pdf
 82pub(crate) fn ntru_gen<R: Rng>(n: usize, rng: &mut R) -> [Polynomial<i16>; 4] {
 83    loop {
 84        let f = gen_poly(n, rng);
 85        let g = gen_poly(n, rng);
 86        let f_ntt = f.map(|&i| FalconFelt::new(i)).fft();
 87        if f_ntt.coefficients.iter().any(|e| e.is_zero()) {
 88            continue;
 89        }
 90        let gamma = gram_schmidt_norm_squared(&f, &g);
 91        if gamma > 1.3689f64 * (MODULUS as f64) {
 92            continue;
 93        }
 94
 95        if let Some((capital_f, capital_g)) =
 96            ntru_solve(&f.map(|&i| i.into()), &g.map(|&i| i.into()))
 97        {
 98            return [
 99                f,
100                g,
101                capital_f.map(|i| i.try_into().unwrap()),
102                capital_g.map(|i| i.try_into().unwrap()),
103            ];
104        }
105    }
106}
107
108/// Solves the NTRU equation. Given f, g in ZZ[X], find F, G in ZZ[X] such that:
109///
110///    f G - g F = q  mod (X^n + 1)
111///
112/// Algorithm 6 of the specification [1, p.35].
113///
114/// [1]: https://falcon-sign.info/falcon.pdf
115fn ntru_solve(
116    f: &Polynomial<BigInt>,
117    g: &Polynomial<BigInt>,
118) -> Option<(Polynomial<BigInt>, Polynomial<BigInt>)> {
119    let n = f.coefficients.len();
120    if n == 1 {
121        let (gcd, u, v) = xgcd(&f.coefficients[0], &g.coefficients[0]);
122        if gcd != BigInt::one() {
123            return None;
124        }
125        return Some((
126            (Polynomial::new(vec![-v * BigInt::from_u32(MODULUS as u32).unwrap()])),
127            Polynomial::new(vec![u * BigInt::from_u32(MODULUS as u32).unwrap()]),
128        ));
129    }
130
131    let f_prime = f.field_norm();
132    let g_prime = g.field_norm();
133
134    let (capital_f_prime, capital_g_prime) = ntru_solve(&f_prime, &g_prime)?;
135    let capital_f_prime_xsq = capital_f_prime.lift_next_cyclotomic();
136    let capital_g_prime_xsq = capital_g_prime.lift_next_cyclotomic();
137
138    let f_minx = f.galois_adjoint();
139    let g_minx = g.galois_adjoint();
140
141    let mut capital_f = (capital_f_prime_xsq.karatsuba(&g_minx)).reduce_by_cyclotomic(n);
142    let mut capital_g = (capital_g_prime_xsq.karatsuba(&f_minx)).reduce_by_cyclotomic(n);
143
144    match babai_reduce(f, g, &mut capital_f, &mut capital_g) {
145        Ok(_) => Some((capital_f, capital_g)),
146        Err(_e) => {
147            #[cfg(test)]
148            {
149                panic!("{}", _e);
150            }
151            #[cfg(not(test))]
152            {
153                None
154            }
155        }
156    }
157}
158
159/// Generates a polynomial of degree at most n-1 whose coefficients are distributed according
160/// to a discrete Gaussian with mu = 0 and sigma = 1.17 * sqrt(Q / (2n)).
161fn gen_poly<R: Rng>(n: usize, rng: &mut R) -> Polynomial<i16> {
162    let mu = 0.0;
163    let sigma_star = 1.43300980528773;
164    Polynomial {
165        coefficients: (0..4096)
166            .map(|_| sampler_z(mu, sigma_star, sigma_star - 0.001, rng))
167            .collect::<Vec<i16>>()
168            .chunks(4096 / n)
169            .map(|ch| ch.iter().sum())
170            .collect(),
171    }
172}
173
174/// Computes the Gram-Schmidt norm of B = [[g, -f], [G, -F]] from f and g.
175/// Corresponds to line 9 in algorithm 5 of the spec [1, p.34]
176///
177/// [1]: https://falcon-sign.info/falcon.pdf
178fn gram_schmidt_norm_squared(f: &Polynomial<i16>, g: &Polynomial<i16>) -> f64 {
179    let n = f.coefficients.len();
180    let norm_f_squared = f.l2_norm_squared();
181    let norm_g_squared = g.l2_norm_squared();
182    let gamma1 = norm_f_squared + norm_g_squared;
183
184    let f_fft = f.map(|i| Complex64::new(*i as f64, 0.0)).fft();
185    let g_fft = g.map(|i| Complex64::new(*i as f64, 0.0)).fft();
186    let f_adj_fft = f_fft.map(|c| c.conj());
187    let g_adj_fft = g_fft.map(|c| c.conj());
188    let ffgg_fft = f_fft.hadamard_mul(&f_adj_fft) + g_fft.hadamard_mul(&g_adj_fft);
189    let ffgg_fft_inverse = ffgg_fft.hadamard_inv();
190    let qf_over_ffgg_fft = f_adj_fft.map(|c| c * (MODULUS as f64)).hadamard_mul(&ffgg_fft_inverse);
191    let qg_over_ffgg_fft = g_adj_fft.map(|c| c * (MODULUS as f64)).hadamard_mul(&ffgg_fft_inverse);
192    let norm_f_over_ffgg_squared =
193        qf_over_ffgg_fft.coefficients.iter().map(|c| (c * c.conj()).re).sum::<f64>() / (n as f64);
194    let norm_g_over_ffgg_squared =
195        qg_over_ffgg_fft.coefficients.iter().map(|c| (c * c.conj()).re).sum::<f64>() / (n as f64);
196
197    let gamma2 = norm_f_over_ffgg_squared + norm_g_over_ffgg_squared;
198
199    f64::max(gamma1, gamma2)
200}
201
202/// Reduces the vector (F,G) relative to (f,g). This method follows the python implementation [1].
203/// Note that this algorithm can end up in an infinite loop. (It's one of the things the author
204/// would like to fix.) When this happens, control returns an error (hence the return type) and
205/// generates another keypair with fresh randomness.
206///
207/// Algorithm 7 in the spec [2, p.35]
208///
209/// [1]: https://github.com/tprest/falcon.py
210///
211/// [2]: https://falcon-sign.info/falcon.pdf
212fn babai_reduce(
213    f: &Polynomial<BigInt>,
214    g: &Polynomial<BigInt>,
215    capital_f: &mut Polynomial<BigInt>,
216    capital_g: &mut Polynomial<BigInt>,
217) -> Result<(), String> {
218    let bitsize = |bi: &BigInt| (bi.bits() + 7) & (u64::MAX ^ 7);
219    let n = f.coefficients.len();
220    let size = [
221        f.map(bitsize).fold(0, |a, &b| u64::max(a, b)),
222        g.map(bitsize).fold(0, |a, &b| u64::max(a, b)),
223        53,
224    ]
225    .into_iter()
226    .max()
227    .unwrap();
228    let shift = (size as i64) - 53;
229    let f_adjusted = f
230        .map(|bi| Complex64::new(i64::try_from(bi >> shift).unwrap() as f64, 0.0))
231        .fft();
232    let g_adjusted = g
233        .map(|bi| Complex64::new(i64::try_from(bi >> shift).unwrap() as f64, 0.0))
234        .fft();
235
236    let f_star_adjusted = f_adjusted.map(|c| c.conj());
237    let g_star_adjusted = g_adjusted.map(|c| c.conj());
238    let denominator_fft =
239        f_adjusted.hadamard_mul(&f_star_adjusted) + g_adjusted.hadamard_mul(&g_star_adjusted);
240
241    let mut counter = 0;
242    loop {
243        let capital_size = [
244            capital_f.map(bitsize).fold(0, |a, &b| u64::max(a, b)),
245            capital_g.map(bitsize).fold(0, |a, &b| u64::max(a, b)),
246            53,
247        ]
248        .into_iter()
249        .max()
250        .unwrap();
251
252        if capital_size < size {
253            break;
254        }
255        let capital_shift = (capital_size as i64) - 53;
256        let capital_f_adjusted = capital_f
257            .map(|bi| Complex64::new(i64::try_from(bi >> capital_shift).unwrap() as f64, 0.0))
258            .fft();
259        let capital_g_adjusted = capital_g
260            .map(|bi| Complex64::new(i64::try_from(bi >> capital_shift).unwrap() as f64, 0.0))
261            .fft();
262
263        let numerator = capital_f_adjusted.hadamard_mul(&f_star_adjusted)
264            + capital_g_adjusted.hadamard_mul(&g_star_adjusted);
265        let quotient = numerator.hadamard_div(&denominator_fft).ifft();
266
267        let k = quotient.map(|f| Into::<BigInt>::into(f.re.round() as i64));
268
269        if k.is_zero() {
270            break;
271        }
272        let kf = (k.clone().karatsuba(f))
273            .reduce_by_cyclotomic(n)
274            .map(|bi| bi << (capital_size - size));
275        let kg = (k.clone().karatsuba(g))
276            .reduce_by_cyclotomic(n)
277            .map(|bi| bi << (capital_size - size));
278        *capital_f -= kf;
279        *capital_g -= kg;
280
281        counter += 1;
282        if counter > 1000 {
283            // If we get here, that means that (with high likelihood) we are in an
284            // infinite loop. We know it happens from time to time -- seldomly, but it
285            // does. It would be nice to fix that! But in order to fix it we need to be
286            // able to reproduce it, and for that we need test vectors. So print them
287            // and hope that one day they circle back to the implementor.
288            return Err(format!("Encountered infinite loop in babai_reduce of falcon-rust.\n\\
289            Please help the developer(s) fix it! You can do this by sending them the inputs to the function that caused the behavior:\n\\
290            f: {:?}\n\\
291            g: {:?}\n\\
292            capital_f: {:?}\n\\
293            capital_g: {:?}\n", f.coefficients, g.coefficients, capital_f.coefficients, capital_g.coefficients));
294        }
295    }
296    Ok(())
297}
298
299/// Extended Euclidean algorithm for computing the greatest common divisor (g) and
300/// Bézout coefficients (u, v) for the relation
301///
302/// $$ u a + v b = g . $$
303///
304/// Implementation adapted from Wikipedia [1].
305///
306/// [1]: https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Pseudocode
307fn xgcd(a: &BigInt, b: &BigInt) -> (BigInt, BigInt, BigInt) {
308    let (mut old_r, mut r) = (a.clone(), b.clone());
309    let (mut old_s, mut s) = (BigInt::one(), BigInt::zero());
310    let (mut old_t, mut t) = (BigInt::zero(), BigInt::one());
311
312    while r != BigInt::zero() {
313        let quotient = old_r.clone() / r.clone();
314        (old_r, r) = (r.clone(), old_r.clone() - quotient.clone() * r);
315        (old_s, s) = (s.clone(), old_s.clone() - quotient.clone() * s);
316        (old_t, t) = (t.clone(), old_t.clone() - quotient * t);
317    }
318
319    (old_r, old_s, old_t)
320}