Skip to main content

crystals_rs/poly/
dilithium.rs

1use crate::{
2    field::{
3        dilithium::{fqmul, DilithiumFq, DILITHIUM_Q, MONT},
4        Field,
5    },
6    keccak::{fips202::Shake128Params, KeccakParams},
7};
8
9use super::{Poly, Polynomial, SizedPolynomial};
10
11pub(crate) const DILITHIUM_N: usize = 256;
12
13const ROOT_OF_UNITY: i32 = 1753; // 2Nth (512-th) root of 1 mod Q
14
15pub type DilithiumPoly = Poly<DilithiumFq, DILITHIUM_N>;
16
17const ZETAS: [i32; DILITHIUM_N - 1] = {
18    let mut zetas = [0i32; DilithiumPoly::N - 1];
19    let mut i = 1;
20    const ROOT_OF_UNITY_MONT: i32 =
21        ((MONT as i64 * ROOT_OF_UNITY as i64) % DILITHIUM_Q as i64) as i32;
22    let mut omega = MONT;
23    while i < DilithiumPoly::N {
24        let br = (i as u8).reverse_bits() as usize;
25        omega = fqmul(omega % DILITHIUM_Q, ROOT_OF_UNITY_MONT); //((omega as i32 * ROOT_OF_UNITY as i32) % KYBER_Q as i32) as i16;
26        zetas[br - 1] = omega;
27        i += 1;
28    }
29    zetas
30};
31
32impl Polynomial for DilithiumPoly {
33    type F = DilithiumFq;
34}
35
36impl SizedPolynomial<DILITHIUM_N> for DilithiumPoly {
37    const INV_NTT_SCALE: <Self::F as Field>::E = 41_978;
38
39    #[inline(always)]
40    fn zetas(k: usize) -> <Self::F as Field>::E {
41        ZETAS[k]
42    }
43
44    fn pointwise(&self, other: &Self, result: &mut Self) {
45        for ((a, b), c) in self
46            .as_ref()
47            .iter()
48            .zip(other.as_ref().iter())
49            .zip(result.as_mut().iter_mut())
50        {
51            c.0 = fqmul(a.0, b.0);
52        }
53        // Unrolled version is significantly slower! (using chunks_exact is even slower, ~ 100% slower!)
54        // const UNROLL: usize = 2;
55        // for ((a, b), c) in self
56        //     .as_ref()
57        //     .into_array_ref_iter::<UNROLL>()
58        //     .zip(other.as_ref().into_array_ref_iter::<UNROLL>())
59        //     .zip(result.as_mut().into_array_mut_iter::<UNROLL>())
60        // {
61        //     c[0].0 = fqmul(a[0].0, b[0].0);
62        //     c[1].0 = fqmul(a[1].0, b[1].0);
63        // }
64    }
65
66    fn rej_uniform(&mut self, mut ctr: usize, bytes: &[u8; Shake128Params::RATE_BYTES]) -> usize {
67        debug_assert!(ctr < Self::N);
68
69        let mut piter = self.0[ctr..].as_mut().iter_mut();
70
71        const_assert_eq!(Shake128Params::RATE_BYTES % 3, 0);
72
73        for buf in bytes.chunks_exact(3) {
74            // let t = (buf[0] as u32 | ((buf[1] as u32) << 8) | ((buf[2] as u32) << 16))
75            // & 0x7FFFFF;
76            let t = (u32::from_le_bytes([buf[0], buf[1], buf[2], 0]) & 0x7FFFFF) as i32;
77            if t < DILITHIUM_Q {
78                match piter.next() {
79                    Some(f) => {
80                        f.0 = t;
81                        ctr += 1;
82                    }
83                    None => break,
84                }
85            }
86        }
87        ctr
88    }
89
90    fn pointwise_acc(&self, other: &Self, result: &mut Self) {
91        for ((a, b), r) in self
92            .as_ref()
93            .iter()
94            .zip(other.as_ref().iter())
95            .zip(result.as_mut().iter_mut())
96        {
97            r.0 += fqmul(a.0, b.0);
98        }
99    }
100}
101
102// #[cfg(test)]
103use rand::{CryptoRng, Rng, RngCore};
104
105// #[cfg(test)]
106impl DilithiumPoly {
107    pub fn new_random<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
108        let mut poly = Self::default();
109        for i in 0..Self::N {
110            poly[i].0 = rng.gen_range(-DILITHIUM_Q / 2..=DILITHIUM_Q / 2);
111        }
112        poly
113    }
114
115    pub fn into_array(self) -> [<<Self as Polynomial>::F as Field>::E; Self::N] {
116        self.0.map(|x| x.0)
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123    use crystals_cref::dilithium as cref;
124
125    #[test]
126    fn test_ntt_and_then_invtt() {
127        let mut rng = rand::thread_rng();
128        for testcase in 0..3_000 {
129            let mut poly = DilithiumPoly::new_random(&mut rng);
130            let poly_copy = poly.clone();
131            poly.ntt();
132            poly.reduce();
133            poly.inv_ntt();
134
135            for f in poly.as_mut() {
136                *f = *f * 1; // * R^-1
137            }
138
139            assert_eq!(poly, poly_copy, "failed testcase #{}", testcase);
140        }
141    }
142
143    // #[test]
144    // fn test_ntt_and_then_invtt_ref() {
145    //     for testcase in 1..1000 {
146    //         let mut poly = Poly::new_random();
147    //         let poly_copy = poly.clone();
148    //         crystals_cref::ntt(&mut poly.0);
149    //         crystals_cref::invntt(&mut poly.0);
150    //         assert_eq!(poly, poly_copy, "failed testcase #{}", testcase);
151    //     }
152    // }
153
154    #[test]
155    fn test_ntt_vs_ref() {
156        let mut rng = rand::thread_rng();
157        for _ in 0..3_000 {
158            let mut poly = DilithiumPoly::new_random(&mut rng);
159            // println!("poly before ntt: {:?}", poly);
160            let mut p = poly.into_array();
161            poly.ntt();
162            // poly.reduce();
163            // println!("poly after ntt: {:?}", poly);
164            // println!("p before ntt: {:?}", p);
165            cref::ntt(&mut p);
166
167            for i in 0..DilithiumPoly::N {
168                assert_eq!(p[i], poly[i].0);
169            }
170            // assert_eq!(poly., poly_copy, "failed testcase #{}", testcase);
171        }
172    }
173
174    #[test]
175    fn test_invntt_vs_ref() {
176        let mut rng = rand::thread_rng();
177        for _ in 0..3_000 {
178            let mut poly = DilithiumPoly::new_random(&mut rng);
179            // println!("poly before ntt: {:?}", poly);
180            let mut p = [0i32; DilithiumPoly::N];
181            for i in 0..DilithiumPoly::N {
182                p[i] = poly[i].0;
183            }
184            poly.inv_ntt();
185            cref::inv_ntt(&mut p);
186
187            for i in 0..DilithiumPoly::N {
188                assert_eq!(p[i], poly[i].0);
189            }
190        }
191    }
192
193    #[test]
194    fn dilithium_poly_uniform() {
195        let mut poly = DilithiumPoly::default();
196        let mut poly_ref = [0i32; DilithiumPoly::N];
197        let mut rng = rand::thread_rng();
198        let mut seed = [0u8; 32];
199
200        const N_TESTS: usize = if cfg!(miri) { 6 } else { 666 };
201
202        for _ in 0..N_TESTS {
203            rng.fill(&mut seed);
204            let i: u8 = rand::random();
205            let j: u8 = rand::random();
206
207            poly.uniform(&seed, i, j);
208
209            let nonce = ((j as u16) << 8) | i as u16;
210            cref::uniform(&mut poly_ref, &seed, nonce);
211
212            assert_eq!(
213                poly_ref,
214                poly.into_array(),
215                "i:{} j:{} seed:{:?}",
216                i,
217                j,
218                seed
219            );
220        }
221    }
222}