Skip to main content

crystals_rs/poly/
kyber.rs

1use crate::field::kyber::{caddq, fqmul, KyberFq, KYBER_Q, MONT};
2use crate::field::Field;
3use crate::keccak::fips202::{CrystalsPrf, HasParams, Shake128, Shake256, SpongeOps};
4use crate::keccak::KeccakParams;
5use crate::lib::mem::{size_of, transmute};
6use crate::utils::flatten::FlattenArray;
7use crate::utils::split::*;
8
9use super::{Poly, Polynomial, SizedPolynomial};
10
11pub const KYBER_N: usize = 256;
12
13const ROOT_OF_UNITY: i16 = 17; // 2Nth (256-th) root of 1 mod Q
14
15pub type KyberPoly = Poly<KyberFq, { KYBER_N / 2 }>;
16
17pub const POLYBYTES: usize = { KyberPoly::N } * 3;
18
19pub const MSG_BYTES: usize = 32;
20
21pub const NOISE_SEED_BYTES: usize = 32;
22
23pub type Xof = Shake128;
24pub const XOF_BLOCK_BYTES: usize = <Xof as HasParams<_>>::Params::RATE_BYTES;
25
26pub type Prf = Shake256;
27pub const PRF_BLOCK_BYTES: usize = <Prf as HasParams<_>>::Params::RATE_BYTES;
28
29pub(crate) const fn poly_compressed_bytes(d: u8) -> usize {
30    KYBER_N * d as usize / 8 // == 32 * d
31}
32
33pub(crate) const fn poly_compressed_bytes_for_k<const K: usize>() -> usize {
34    match K {
35        2 | 3 => poly_compressed_bytes(4),
36        4 => poly_compressed_bytes(5),
37        _ => unreachable!(),
38    }
39}
40
41pub(crate) const fn polyvec_compressed_bytes_for_k<const K: usize>() -> usize {
42    (match K {
43        2 | 3 => poly_compressed_bytes(10),
44        4 => poly_compressed_bytes(11),
45        _ => unreachable!(),
46    }) * K
47}
48
49pub const fn kyber_ciphertext_bytes<const K: usize>() -> usize {
50    polyvec_compressed_bytes_for_k::<K>() + poly_compressed_bytes_for_k::<K>()
51}
52
53#[inline(always)]
54pub(crate) fn compress_d<const D: usize>(u: i16) -> u16 {
55    const Q: u32 = KYBER_Q as u32;
56    const HALF_Q: u32 = KYBER_Q as u32 / 2;
57
58    let u = caddq(u) as u32;
59
60    ((((u << D as u8) + HALF_Q) / Q) & ((1 << D as u8) - 1)) as u16
61}
62
63#[inline(always)]
64pub(crate) fn decompress_d<const D: usize>(u: u16) -> i16 {
65    debug_assert!(D <= 16);
66
67    const Q: u32 = KYBER_Q as u32;
68
69    let u = u & ((1 << D) - 1);
70
71    ((u as u32 * Q + (1 << (D - 1))) >> D) as i16
72}
73
74const ZETAS: [i16; KyberPoly::N - 1] = {
75    let mut zetas = [0i16; KyberPoly::N - 1];
76    let mut i = 1;
77    let mut omega = MONT;
78    const ROOT_OF_UNITY_MONT: i16 = (MONT * ROOT_OF_UNITY) % KYBER_Q;
79    while i < KyberPoly::N {
80        let br = (i as u8).reverse_bits() as usize >> 1;
81        omega = fqmul(omega, ROOT_OF_UNITY_MONT); //((omega as i32 * ROOT_OF_UNITY as i32) % KYBER_Q as i32) as i16;
82        zetas[br - 1] = omega;
83        i += 1;
84    }
85    zetas
86};
87
88impl Polynomial for KyberPoly {
89    type F = KyberFq;
90}
91
92impl SizedPolynomial<{ KYBER_N / 2 }> for KyberPoly {
93    const INV_NTT_SCALE: <Self::F as Field>::E = 1441; // 512 to convert to non-mongomery form
94
95    #[inline(always)]
96    fn zetas(k: usize) -> <Self::F as Field>::E {
97        ZETAS[k]
98    }
99
100    fn pointwise(&self, other: &Self, result: &mut Self) {
101        for (((tr, ta), tb), zeta) in result
102            .as_mut()
103            .as_array_chunks_mut::<2>()
104            .zip(self.as_ref().as_array_chunks::<2>())
105            .zip(other.as_ref().as_array_chunks::<2>())
106            .zip(ZETAS[63..].iter())
107        {
108            tr[0] = ta[0].basemul(tb[0], *zeta);
109            tr[1] = ta[1].basemul(tb[1], -*zeta);
110        }
111    }
112
113    fn rej_uniform(&mut self, mut ctr: usize, bytes: &[u8; XOF_BLOCK_BYTES]) -> usize {
114        debug_assert!(ctr < Self::NUM_SCALARS);
115        debug_assert!(bytes.len() % 3 == 0);
116
117        // TODO compare performance vs safe
118        let p: &mut [i16; Self::NUM_SCALARS] = self.as_scalar_array_mut();
119
120        for buf in bytes.chunks_exact(3) {
121            // TODO compare with iterator
122            if ctr >= Self::NUM_SCALARS {
123                break;
124            }
125            let val0 = (buf[0] as u16 | (buf[1] as u16) << 8) & 0xFFF;
126            if val0 < KYBER_Q as u16 {
127                p[ctr] = val0 as i16;
128                ctr += 1;
129            }
130
131            if ctr >= Self::NUM_SCALARS {
132                break;
133            }
134            let val1 = ((buf[1] >> 4) as u16 | (buf[2] as u16) << 4) & 0xFFF;
135            if val1 < KYBER_Q as u16 {
136                p[ctr] = val1 as i16;
137                ctr += 1;
138            }
139        }
140        ctr
141    }
142
143    fn pointwise_acc(&self, other: &Self, result: &mut Self) {
144        for (((r, a), b), zeta) in result
145            .as_mut()
146            .as_array_chunks_mut::<2>()
147            .zip(self.as_ref().as_array_chunks::<2>())
148            .zip(other.as_ref().as_array_chunks::<2>())
149            .zip(ZETAS[63..].iter())
150        {
151            r[0] += a[0].basemul(b[0], *zeta);
152            r[1] += a[1].basemul(b[1], -*zeta);
153        }
154    }
155}
156
157impl KyberPoly {
158    #[inline]
159    pub fn serialize(&self, bytes: &mut [u8; POLYBYTES]) {
160        for (f, r) in self.as_ref().iter().zip(bytes.as_array_chunks_mut::<3>()) {
161            // map to positive standard representatives
162            let f = f.freeze();
163            let [t0, t1] = f.0;
164            r[0] = t0 as u8;
165            r[1] = ((t0 >> 8) | (t1 << 4)) as u8;
166            r[2] = (t1 >> 4) as u8;
167        }
168    }
169
170    #[inline]
171    pub fn deserialize(&mut self, bytes: &[u8]) {
172        for (f, a) in self.as_mut().iter_mut().zip(bytes.as_array_chunks::<3>()) {
173            f.0 = [
174                (a[0] as u16 | ((a[1] as u16) << 8) & 0xFFF) as i16,
175                ((a[1] >> 4) as u16 | ((a[2] as u16) << 4) & 0xFFF) as i16,
176            ];
177        }
178    }
179
180    #[inline]
181    pub fn cbd2(&mut self, buf: &[u8; KYBER_N / 2]) {
182        const MASK55: u32 = 0x55_55_55_55;
183
184        for (r, bytes) in self
185            .as_mut()
186            .as_array_chunks_mut::<4>()
187            .zip(buf.as_array_chunks::<4>())
188        {
189            let t = u32::from_le_bytes(*bytes);
190
191            let f0 = t & MASK55;
192            let f1 = (t >> 1) & MASK55;
193            let d = f0.wrapping_add(f1);
194
195            for (j, rj) in r.iter_mut().enumerate() {
196                let a = ((d >> (8 * j)) & 0x3) as i8;
197                let b = ((d >> (8 * j + 2)) & 0x3) as i8;
198                rj.0[0] = (a - b) as i16;
199
200                let a = ((d >> (8 * j + 4)) & 0x3) as i8;
201                let b = ((d >> (8 * j + 6)) & 0x3) as i8;
202                rj.0[1] = (a - b) as i16;
203            }
204        }
205    }
206
207    #[inline]
208    pub fn cbd3(&mut self, buf: &[u8; 3 * KYBER_N / 4]) {
209        const MASK249: u32 = 0x00249249; // 0b001..001001
210
211        #[inline]
212        fn load24_littleendian(x: [u8; 3]) -> u32 {
213            let mut r = x[0] as u32;
214            r |= (x[1] as u32) << 8;
215            r |= (x[2] as u32) << 16;
216            r
217        }
218
219        for (r, bytes) in self
220            .as_mut()
221            .as_array_chunks_mut::<2>()
222            .zip(buf.as_array_chunks::<3>())
223        {
224            let t = load24_littleendian(*bytes);
225            let mut d = t & MASK249;
226            d += (t >> 1) & MASK249;
227            d += (t >> 2) & MASK249;
228
229            for c in r {
230                let a = d as i16 & 0x7;
231                d >>= 3;
232                let b = d as i16 & 0x7;
233                d >>= 3;
234                c.0[0] = a - b;
235
236                let a = d as i16 & 0x7;
237                d >>= 3;
238                let b = d as i16 & 0x7;
239                d >>= 3;
240                c.0[1] = a - b;
241            }
242        }
243    }
244
245    pub fn getnoise_eta1<const K: usize>(
246        &mut self,
247        prf: &mut Prf,
248        seed: &[u8; NOISE_SEED_BYTES],
249        nonce: u8,
250    ) {
251        if K == 2 {
252            const ETA1: usize = 3;
253            let mut buf = [0u8; ETA1 * KYBER_N / 4];
254            prf.absorb_prf(seed, nonce);
255            prf.squeeze(&mut buf);
256            self.cbd3(&buf);
257        } else {
258            self.getnoise_eta2(prf, seed, nonce);
259        }
260    }
261
262    pub fn getnoise_eta2(&mut self, prf: &mut Prf, seed: &[u8; NOISE_SEED_BYTES], nonce: u8) {
263        const ETA2: usize = 2;
264        prf.absorb_prf(seed, nonce);
265        let mut buf = [0u8; ETA2 * KYBER_N / 4];
266        prf.absorb_prf(seed, nonce);
267        prf.squeeze(&mut buf);
268        self.cbd2(&buf);
269    }
270
271    #[inline(always)]
272    pub fn ntt_and_reduce(&mut self) {
273        self.ntt();
274        self.reduce();
275    }
276
277    pub fn into_array(self) -> [<KyberFq as Field>::E; Self::NUM_SCALARS] {
278        *self.0.map(|f| f.0).flatten_array()
279    }
280
281    fn as_scalar_array_mut(
282        &mut self,
283    ) -> &mut [<<Self as Polynomial>::F as Field>::E; Self::NUM_SCALARS] {
284        // FIXME Safety rationale!
285        debug_assert_eq!(
286            size_of::<[<<Self as Polynomial>::F as Field>::E; Self::NUM_SCALARS]>(),
287            size_of::<Self>()
288        );
289
290        #[allow(unsafe_code)]
291        unsafe {
292            transmute(self.as_mut())
293        }
294    }
295
296    fn as_scalar_array(&self) -> &[<<Self as Polynomial>::F as Field>::E; Self::NUM_SCALARS] {
297        // FIXME Safety rationale!
298        debug_assert_eq!(
299            size_of::<[<<Self as Polynomial>::F as Field>::E; Self::NUM_SCALARS]>(),
300            size_of::<Self>()
301        );
302        #[allow(unsafe_code)]
303        unsafe {
304            transmute(self.as_ref())
305        }
306    }
307
308    #[inline]
309    pub fn compress_slice<const D: usize>(&self, ct: &mut [u8]) {
310        assert_eq!(ct.len(), poly_compressed_bytes(D as u8));
311
312        #[inline(always)]
313        fn shift_signed(x: u16, shl: i8) -> u8 {
314            if shl >= 0 {
315                debug_assert!(shl < 8);
316                (x << shl) as u8
317            } else {
318                let shr = (-shl) as u8;
319                (x >> shr) as u8
320            }
321        }
322
323        for (coeffs, bytes) in self
324            .as_scalar_array()
325            .as_array_chunks::<{ 4 * 2 }>()
326            .zip(ct.as_array_chunks_mut::<D>())
327        {
328            let mut shl: i8 = 0;
329            let mut idx = 0;
330            // let mut x = comp_coeffs.next().unwrap(); // array faster?
331            for b in bytes {
332                debug_assert!(shl < 8);
333
334                *b = shift_signed(compress_d::<D>(coeffs[idx]), shl);
335
336                while D as i8 + shl < 8 {
337                    shl += D as i8;
338                    idx += 1;
339                    *b |= shift_signed(compress_d::<D>(coeffs[idx]), shl);
340                }
341                shl -= 8;
342            }
343        }
344    }
345
346    #[inline]
347    pub fn decompress_slice<const D: usize>(&mut self, ct: &[u8]) {
348        assert_eq!(ct.len(), poly_compressed_bytes(D as u8));
349
350        #[inline(always)]
351        fn shift_signed<const D: usize>(x: u8, shl: i8) -> u16 {
352            if shl >= 0 {
353                (x as u16) << shl
354            } else {
355                (x as u16) >> (-shl)
356            }
357        }
358
359        for (coeffs, bytes) in self
360            .as_scalar_array_mut()
361            .as_array_chunks_mut::<{ 4 * 2 }>()
362            .zip(ct.as_array_chunks::<D>())
363        {
364            let mut shl: i8 = 0;
365            let mut idx = 0;
366
367            for c in coeffs {
368                let mut d_bits = shift_signed::<D>(bytes[idx], shl);
369
370                while shl + 8 < D as i8 {
371                    shl += 8;
372                    idx += 1;
373                    d_bits |= shift_signed::<D>(bytes[idx], shl);
374                }
375                shl -= D as i8;
376                *c = decompress_d::<D>(d_bits);
377            }
378        }
379    }
380
381    #[inline]
382    pub fn compress<const D: usize>(&self, ct: &mut [[u8; D]; 32]) {
383        // assert_eq!(ct.len(), poly_compressed_bytes(D as u8));
384        // n: number of bytes, m: number of poly elements (2 * i16)
385        // g = gcd(d,4)
386        // 4*n = d*m
387        // let m: usize = 4 / gcd_u8(D as u8, 4) as usize;
388        // let n: usize = (D as u8 / gcd_u8(D as u8, 4)) as usize;
389
390        #[inline(always)]
391        fn shift_signed(x: u16, shl: i8) -> u8 {
392            if shl >= 0 {
393                debug_assert!(shl < 8);
394                (x << shl) as u8
395            } else {
396                let shr = (-shl) as u8;
397                (x >> shr) as u8
398            }
399        }
400
401        for (coeffs, bytes) in self
402            .as_scalar_array()
403            .as_array_chunks::<{ 4 * 2 }>()
404            .zip(ct)
405        {
406            // let mut comp_coeffs = coeff_pairs
407            // .iter()
408            // .flat_map(|f| f.0.map(|x| compress_d::<D>(x)));
409            // only d bits of t[i] are valid, others set to 0
410            // let mut valid_bits = d as i8;
411            // let mut byte_idx = 0;
412            // let mut shl: i8 = 0;
413            // for x in comp_coeffs {
414            //     while valid_bits > 0 {
415            //         bytes[byte_idx] |= shift_signed(x, shl);
416            //         valid_bits += shl - 8;
417
418            //         byte_idx += 1;
419            //     }
420            //     valid_bits = d as i8;
421            //     shl = 0;
422            // }
423            let mut shl: i8 = 0;
424            let mut idx = 0;
425
426            for b in bytes {
427                debug_assert!(shl < 8);
428
429                *b = shift_signed(compress_d::<D>(coeffs[idx]), shl);
430
431                while D as i8 + shl < 8 {
432                    shl += D as i8;
433                    idx += 1;
434                    *b |= shift_signed(compress_d::<D>(coeffs[idx]), shl);
435                }
436                shl -= 8;
437            }
438        }
439    }
440
441    #[inline]
442    pub fn decompress<const D: usize>(&mut self, ct: &[[u8; D]; 32]) {
443        #[inline(always)]
444        fn shift_signed<const D: usize>(x: u8, shl: i8) -> u16 {
445            if shl >= 0 {
446                (x as u16) << shl
447            } else {
448                (x as u16) >> (-shl)
449            }
450        }
451
452        for (coeff_pairs, bytes) in self.as_mut().chunks_exact_mut(4).zip(ct) {
453            let mut shl: i8 = 0;
454            let mut idx = 0;
455
456            for c in coeff_pairs.iter_mut().flat_map(|f| f.0.iter_mut()) {
457                let mut d_bits = shift_signed::<D>(bytes[idx], shl);
458
459                while shl + 8 < D as i8 {
460                    shl += 8;
461                    idx += 1;
462                    d_bits |= shift_signed::<D>(bytes[idx], shl);
463                }
464                shl -= D as i8;
465                *c = decompress_d::<D>(d_bits);
466            }
467        }
468    }
469
470    pub fn set_from_message(&mut self, msg: &[u8; KYBER_N / 8]) {
471        const ONE_COEFF: i16 = (KYBER_Q + 1) / 2;
472        for (i, byte) in msg.iter().enumerate() {
473            for j in 0..4 {
474                let mask0 = 0i16.wrapping_sub(((*byte) >> (2 * j) & 1) as i16);
475                let mask1 = 0i16.wrapping_sub(((*byte) >> (2 * j + 1) & 1) as i16);
476
477                debug_assert!(mask0 == 0 || mask0 == -1);
478                debug_assert!(mask1 == 0 || mask1 == -1);
479
480                self[4 * i + j].0 = [mask0 & ONE_COEFF, mask1 & ONE_COEFF];
481
482                let x = self[4 * i + j].0;
483                debug_assert!(x[0] == 0 || x[0] == ONE_COEFF);
484                debug_assert!(x[1] == 0 || x[1] == ONE_COEFF);
485            }
486        }
487    }
488
489    #[inline(always)]
490    pub fn from_message(msg: &[u8; KYBER_N / 8]) -> Self {
491        let mut poly = Self::default();
492        poly.set_from_message(msg);
493        poly
494    }
495
496    pub fn compress_to_message(&self, msg: &mut [u8; 32]) {
497        for (coeff_pairs, byte) in self.as_ref().chunks_exact(4).zip(msg.iter_mut()) {
498            *byte = 0;
499            for (j, c) in coeff_pairs
500                .iter()
501                .flat_map(|f| f.reduce().0.map(|u| compress_d::<1>(u) as u8))
502                .enumerate()
503            {
504                *byte |= c << j;
505            }
506        }
507    }
508
509    /// Inplace conversion of all coefficients of a polynomial from normal domain to Montgomery domain
510    /// # Arguments
511    /// * `r` - Input/output polynomial
512    #[inline]
513    pub fn scale_mont(&mut self) {
514        for coeff in self {
515            coeff.scale_mont();
516        }
517    }
518}
519
520use rand::{CryptoRng, Rng, RngCore};
521
522impl KyberPoly {
523    #[doc(hidden)]
524    pub fn new_random<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
525        let mut poly = Self::default();
526        fn frand<R: RngCore + CryptoRng>(rng: &mut R) -> i16 {
527            rng.gen_range(-KYBER_Q / 2..=KYBER_Q / 2)
528        }
529        for c in poly.as_mut() {
530            c.0[0] = frand(rng);
531            c.0[1] = frand(rng);
532        }
533        poly
534    }
535}
536
537#[cfg(test)]
538mod tests {
539    use crate::field::kyber::{KYBER_Q, QINV};
540    use crate::polyvec::KyberPolyVec;
541    use crate::utils::*;
542    extern crate std;
543    use super::*;
544    use crate::poly::SizedPolynomial;
545    use crate::utils::unsafe_utils::flatten::{
546        FlattenArray, FlattenSlice, FlattenSliceMut, FlattenTwice, FlattenTwiceMut,
547    };
548    use crystals_cref::kyber as cref;
549    use std::*;
550
551    const M: usize = KyberPoly::NUM_SCALARS / 8;
552
553    #[test]
554    fn q_inv_test() {
555        assert_eq!(QINV, invm(KYBER_Q as i32, 1 << 16).unwrap() as i16);
556    }
557
558    #[test]
559    fn test_ntt_and_then_invtt() {
560        let mut rng = rand::thread_rng();
561        for testcase in 0..3_000 {
562            let mut poly = KyberPoly::new_random(&mut rng);
563            let poly_copy = poly.clone();
564            poly.ntt();
565            poly.inv_ntt();
566
567            for f in poly.as_mut() {
568                *f = *f * 1; // * R^-1
569            }
570            // poly.reduce();
571
572            assert_eq!(poly, poly_copy, "failed testcase #{}", testcase);
573        }
574    }
575
576    #[test]
577    #[cfg(not(miri))]
578    fn test_ntt_vs_ref() {
579        let mut rng = rand::thread_rng();
580        for _ in 0..3_000 {
581            let mut poly = KyberPoly::new_random(&mut rng);
582            let mut p = poly.into_array();
583
584            crystals_cref::kyber::ntt(&mut p);
585
586            poly.ntt();
587            poly.reduce();
588
589            assert_eq!(poly.into_array(), p);
590        }
591    }
592
593    #[test]
594    #[cfg(not(miri))]
595    fn test_invntt_vs_ref() {
596        let mut rng = rand::thread_rng();
597        for _ in 0..3_000 {
598            let mut poly = KyberPoly::new_random(&mut rng);
599            let mut p = poly.into_array();
600            poly.inv_ntt();
601            poly.reduce();
602            crystals_cref::kyber::inv_ntt(&mut p);
603            crystals_cref::kyber::poly_reduce(&mut p);
604
605            assert_eq!(poly.into_array(), p);
606        }
607    }
608
609    #[test]
610    #[cfg(not(miri))]
611    fn pwm_vs_ref() {
612        let mut rng = rand::thread_rng();
613        for _ in 0..1_000 {
614            let poly_a = KyberPoly::new_random(&mut rng);
615            let poly_b = KyberPoly::new_random(&mut rng);
616            let mut poly_r = KyberPoly::default();
617
618            let a = poly_a.into_array();
619            let b = poly_b.into_array();
620            let mut r = poly_r.into_array();
621
622            poly_a.pointwise(&poly_b, &mut poly_r);
623            poly_r.reduce();
624
625            crystals_cref::kyber::poly_pointwise_montgomery(&mut r, &a, &b);
626            crystals_cref::kyber::poly_reduce(&mut r);
627
628            assert_eq!(poly_r.into_array(), r);
629        }
630    }
631
632    #[test]
633    #[cfg(not(miri))]
634    fn test_cbd2_vs_ref() {
635        let mut rng = rand::thread_rng();
636        for _ in 0..1_000 {
637            let mut poly = KyberPoly::new_random(&mut rng);
638            let mut p = poly.into_array();
639
640            let mut buf = [0u8; 2 * KYBER_N / 4];
641            rng.fill(buf.as_mut());
642
643            poly.cbd2(&buf);
644            crystals_cref::kyber::poly_cbd_eta_eq_2(&mut p, &buf);
645
646            assert_eq!(poly.into_array(), p);
647        }
648    }
649
650    #[test]
651    #[cfg(not(miri))]
652    fn test_cbd3_vs_ref() {
653        let mut rng = rand::thread_rng();
654        for _ in 0..1_000 {
655            let mut poly = KyberPoly::new_random(&mut rng);
656            let mut p = poly.into_array();
657
658            let mut buf = [0u8; 3 * KYBER_N / 4];
659            rng.fill(buf.as_mut());
660
661            poly.cbd3(&buf);
662            crystals_cref::kyber::poly_cbd_eta_eq_3(&mut p, &buf);
663
664            assert_eq!(poly.into_array(), p);
665        }
666    }
667
668    #[test]
669    #[cfg(not(miri))]
670    fn test_getnoise_eta3_vs_ref() {
671        let mut rng = rand::thread_rng();
672        let mut prf = Prf::default();
673        for _ in 0..1_000 {
674            let mut poly = KyberPoly::new_random(&mut rng);
675            let mut p = poly.into_array();
676
677            let mut seed = [0u8; NOISE_SEED_BYTES];
678            rng.fill(seed.as_mut());
679            let nonce = rand::random();
680
681            poly.getnoise_eta1::<2>(&mut prf, &seed, nonce);
682            crystals_cref::kyber::poly_getnoise_eta_eq_3(&mut p, &seed, nonce);
683
684            assert_eq!(poly.into_array(), p);
685        }
686    }
687
688    #[test]
689    #[cfg(not(miri))] // miri does not support calling foreign functions
690    fn test_polycompress_vs_ref() {
691        for _ in 0..1_000 {
692            let poly = KyberPoly::new_random(&mut rand::thread_rng());
693
694            {
695                const D: u8 = 4;
696                let mut ct = [[0u8; { D as usize }]; M];
697                let mut ct_ref = [0u8; poly_compressed_bytes(D)];
698
699                poly.compress::<{ D as usize }>(&mut ct);
700                cref::poly_compress::<2>(&mut ct_ref, &poly.as_scalar_array());
701                assert_eq!(ct.flatten_array(), &ct_ref);
702            }
703
704            {
705                const D: u8 = 4;
706                let mut ct = [[0u8; { D as usize }]; M];
707                let mut ct_ref = [0u8; poly_compressed_bytes(D)];
708
709                poly.compress::<{ D as usize }>(&mut ct);
710                cref::poly_compress::<3>(&mut ct_ref, &poly.as_scalar_array());
711                assert_eq!(ct.flatten_array(), &ct_ref);
712            }
713            {
714                const D: u8 = 5;
715                let mut ct = [[0u8; { D as usize }]; M];
716                let mut ct_ref = [0u8; poly_compressed_bytes(D)];
717
718                poly.compress::<{ D as usize }>(&mut ct);
719                cref::poly_compress::<4>(&mut ct_ref, &poly.as_scalar_array());
720                assert_eq!(ct.flatten_array(), &ct_ref);
721            }
722        }
723    }
724
725    fn test_poly_decompress_vs_ref<const K: usize, const D: usize>() {
726        {
727            let mut rng = rand::thread_rng();
728            let mut poly_ref = [0i16; KYBER_N];
729            let mut poly = KyberPoly::default();
730            let mut ct = [[0u8; D]; M];
731
732            for _ in 0..5_000 {
733                rng.fill_bytes(ct.flatten_slice_mut());
734
735                poly.decompress::<D>(&mut ct);
736                cref::poly_decompress::<K>(&mut poly_ref, ct.flatten_slice());
737                assert_eq!(poly.as_scalar_array(), &poly_ref);
738            }
739        }
740    }
741
742    fn test_polyvec_decompress_vs_ref<const K: usize, const D: usize>() {
743        {
744            let mut rng = rand::thread_rng();
745            let mut pv_ref = [[0i16; KYBER_N]; K];
746            let mut pv = KyberPolyVec::<K>::default();
747            let mut ct = [[[0u8; D]; M]; K];
748
749            for _ in 0..1_000 {
750                rng.fill_bytes(ct.flatten_twice_mut());
751
752                pv.decompress::<D>(&mut ct);
753                cref::polyvec_decompress::<K>(&mut pv_ref, ct.flatten_twice());
754                for (p, p_ref) in pv.into_iter().zip(pv_ref) {
755                    assert_eq!(p.as_scalar_array(), &p_ref);
756                }
757            }
758        }
759    }
760
761    #[test]
762    #[cfg(not(miri))] // miri does not support calling foreign functions
763    fn polyvec_decompress_vs_ref_2() {
764        test_polyvec_decompress_vs_ref::<2, 10>();
765    }
766
767    #[test]
768    #[cfg(not(miri))]
769    fn polyvec_decompress_vs_ref_3() {
770        test_polyvec_decompress_vs_ref::<3, 10>();
771    }
772
773    #[test]
774    #[cfg(not(miri))]
775    fn polyvec_decompress_vs_ref_4() {
776        test_polyvec_decompress_vs_ref::<4, 11>();
777    }
778
779    #[test]
780    #[cfg(not(miri))] // miri does not support calling foreign functions
781    fn poly_decompress_vs_ref_2() {
782        test_poly_decompress_vs_ref::<2, 4>();
783    }
784    #[test]
785    #[cfg(not(miri))]
786    fn poly_decompress_vs_ref_3() {
787        test_poly_decompress_vs_ref::<3, 4>();
788    }
789    #[test]
790    #[cfg(not(miri))]
791    fn poly_decompress_vs_ref_4() {
792        test_poly_decompress_vs_ref::<4, 5>();
793    }
794}