From 18096969851f0243a0cd9d4c5b7d1a519edfb4e4 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Wed, 7 Feb 2024 07:29:50 -0500 Subject: [PATCH 01/10] Add ML-KEM implementation --- ml-kem/.gitignore | 2 + ml-kem/Cargo.toml | 29 ++ ml-kem/benches/mlkem.rs | 59 +++ ml-kem/src/algebra.rs | 952 ++++++++++++++++++++++++++++++++++++++ ml-kem/src/compress.rs | 120 +++++ ml-kem/src/crypto.rs | 210 +++++++++ ml-kem/src/encode.rs | 283 ++++++++++++ ml-kem/src/kem.rs | 242 ++++++++++ ml-kem/src/lib.rs | 204 +++++++++ ml-kem/src/param.rs | 266 +++++++++++ ml-kem/src/pke.rs | 185 ++++++++ ml-kem/src/util.rs | 223 +++++++++ ml-kem/tests/nist.rs | 992 ++++++++++++++++++++++++++++++++++++++++ ml-kem/tests/vectors.rs | 60 +++ 14 files changed, 3827 insertions(+) create mode 100644 ml-kem/.gitignore create mode 100644 ml-kem/Cargo.toml create mode 100644 ml-kem/benches/mlkem.rs create mode 100644 ml-kem/src/algebra.rs create mode 100644 ml-kem/src/compress.rs create mode 100644 ml-kem/src/crypto.rs create mode 100644 ml-kem/src/encode.rs create mode 100644 ml-kem/src/kem.rs create mode 100644 ml-kem/src/lib.rs create mode 100644 ml-kem/src/param.rs create mode 100644 ml-kem/src/pke.rs create mode 100644 ml-kem/src/util.rs create mode 100644 ml-kem/tests/nist.rs create mode 100644 ml-kem/tests/vectors.rs diff --git a/ml-kem/.gitignore b/ml-kem/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/ml-kem/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/ml-kem/Cargo.toml b/ml-kem/Cargo.toml new file mode 100644 index 0000000..30f7728 --- /dev/null +++ b/ml-kem/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "ml-kem" +version = "0.1.0" +edition = "2021" + +# TODO(RLB) Selectively enable "deterministic" only for tests and benchmarks +[features] +default = ["deterministic"] +deterministic = [] # Expose deterministic generation and encapsulation functions + +[dependencies] +const-default = "1.0.0" +crypto-common = { version = "0.1.6", features = ["getrandom"] } +generic-array = { version = "1.0.0", features = ["const-default"] } +sha3 = "0.10.8" +typenum = "1.17.0" + +[dev-dependencies] +criterion = "0.5.1" +hex = "0.4.3" +hex-literal = "0.4.1" +rand = "0.8.5" + +[profile.bench] +debug = true + +[[bench]] +name = "mlkem" +harness = false diff --git a/ml-kem/benches/mlkem.rs b/ml-kem/benches/mlkem.rs new file mode 100644 index 0000000..39b15b8 --- /dev/null +++ b/ml-kem/benches/mlkem.rs @@ -0,0 +1,59 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use crypto_common::rand_core::CryptoRngCore; +use generic_array::{ArrayLength, GenericArray}; +use ml_kem::*; + +pub fn rand(rng: &mut impl CryptoRngCore) -> GenericArray { + let mut val: GenericArray = Default::default(); + rng.fill_bytes(&mut val); + val +} + +fn criterion_benchmark(c: &mut Criterion) { + let mut rng = rand::thread_rng(); + let d: B32 = rand(&mut rng); + let z: B32 = rand(&mut rng); + let m: B32 = rand(&mut rng); + + let (dk, ek) = MlKem768::generate_deterministic(&d, &z); + let dk_bytes = dk.as_bytes(); + let ek_bytes = ek.as_bytes(); + let (_sk, ct) = ek.encapsulate(&mut rng); + + // Key generation + c.bench_function("keygen", |b| { + b.iter(|| { + let (dk, ek) = ::generate_deterministic(&d, &z); + let _dk_bytes = dk.as_bytes(); + let _ek_bytes = ek.as_bytes(); + }) + }); + + // Encapsulation + c.bench_function("encapsulate", |b| { + b.iter(|| { + let ek = ::EncapsulationKey::from_bytes(&ek_bytes); + ek.encapsulate_deterministic(&m); + }) + }); + + // Decapsulation + c.bench_function("decapsulate", |b| { + b.iter(|| { + let dk = ::DecapsulationKey::from_bytes(&dk_bytes); + dk.decapsulate(&ct); + }) + }); + + // Round trip + c.bench_function("round_trip", |b| { + b.iter(|| { + let (dk, ek) = ::generate_deterministic(&d, &z); + let (_sk, ct) = ek.encapsulate(&mut rng); + dk.decapsulate(&ct); + }) + }); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/ml-kem/src/algebra.rs b/ml-kem/src/algebra.rs new file mode 100644 index 0000000..fedbddd --- /dev/null +++ b/ml-kem/src/algebra.rs @@ -0,0 +1,952 @@ +use const_default::ConstDefault; +use core::ops::{Add, Mul, Sub}; +use generic_array::{sequence::GenericSequence, GenericArray}; +use sha3::digest::XofReader; +use typenum::consts::U256; + +use crate::crypto::{PrfOutput, PRF, XOF}; +use crate::encode::Encode; +use crate::param::{ArrayLength, CbdSamplingSize}; +use crate::util::{FastClone, FunctionalArray, Truncate, B32}; + +pub type Integer = u16; + +/// An element of GF(q). Although `q` is only 16 bits wide, we use a wider uint type to so that we +/// can defer modular reductions. +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub struct FieldElement(pub Integer); + +impl FieldElement { + pub const Q: Integer = 3329; + pub const Q32: u32 = Self::Q as u32; + const Q64: u64 = Self::Q as u64; + const BARRETT_SHIFT: usize = 24; + const BARRETT_MULTIPLIER: u64 = (1 << Self::BARRETT_SHIFT) / Self::Q64; + + // A fast modular reduction for small numbers `x < 2*q` + // TODO(RLB) Replace with constant-time version (~3-5% performance hit) + fn small_reduce(x: u16) -> u16 { + if x < Self::Q { + x + } else { + x - Self::Q + } + } + + fn barrett_reduce(x: u32) -> u16 { + let product = u64::from(x) * Self::BARRETT_MULTIPLIER; + let quotient = (product >> Self::BARRETT_SHIFT).truncate(); + let remainder = x - quotient * Self::Q32; + Self::small_reduce(remainder.truncate()) + } + + // Algorithm 11. BaseCaseMultiply + // + // This is a hot loop. We promote to u64 so that we can do the absolute minimum number of + // modular reductions, since these are the expensive operation. + fn base_case_multiply(a0: Self, a1: Self, b0: Self, b1: Self, i: usize) -> (Self, Self) { + let a0 = u32::from(a0.0); + let a1 = u32::from(a1.0); + let b0 = u32::from(b0.0); + let b1 = u32::from(b1.0); + let g = u32::from(GAMMA[i].0); + + let b1g = u32::from(Self::barrett_reduce(b1 * g)); + + let c0 = Self::barrett_reduce(a0 * b0 + a1 * b1g); + let c1 = Self::barrett_reduce(a0 * b1 + a1 * b0); + (Self(c0), Self(c1)) + } +} + +impl ConstDefault for FieldElement { + const DEFAULT: Self = Self(0); +} + +impl Add for FieldElement { + type Output = Self; + + fn add(self, rhs: Self) -> Self { + Self(Self::small_reduce(self.0 + rhs.0)) + } +} + +impl Sub for FieldElement { + type Output = Self; + + fn sub(self, rhs: Self) -> Self { + // Guard against underflow if `rhs` is too large + Self(Self::small_reduce(self.0 + Self::Q - rhs.0)) + } +} + +impl Mul for FieldElement { + type Output = FieldElement; + + fn mul(self, rhs: FieldElement) -> FieldElement { + let x = u32::from(self.0); + let y = u32::from(rhs.0); + Self(Self::barrett_reduce(x * y)) + } +} + +/// An element of the ring `R_q`, i.e., a polynomial over `Z_q` of degree 255 +#[derive(Clone, Copy, Default, Debug, PartialEq)] +pub struct Polynomial(pub GenericArray); + +impl ConstDefault for Polynomial { + const DEFAULT: Self = Self(GenericArray::DEFAULT); +} + +impl Add<&Polynomial> for &Polynomial { + type Output = Polynomial; + + fn add(self, rhs: &Polynomial) -> Polynomial { + Polynomial(self.0.zip(&rhs.0, |&x, &y| x + y)) + } +} + +impl Sub<&Polynomial> for &Polynomial { + type Output = Polynomial; + + fn sub(self, rhs: &Polynomial) -> Polynomial { + Polynomial(self.0.zip(&rhs.0, |&x, &y| x - y)) + } +} + +impl Mul<&Polynomial> for FieldElement { + type Output = Polynomial; + + fn mul(self, rhs: &Polynomial) -> Polynomial { + Polynomial(rhs.0.map(|&x| self * x)) + } +} + +impl Polynomial { + // A lookup table for CBD sampling: + // + // ONES[i][j] = i.count_ones() - j.count_ones() mod Q + // + // fn main() { + // let q = 3329; + // let ones: [[u32; 8]; 8] = array::from_fn(|i| { + // array::from_fn(|j| { + // let x = i.count_ones(); + // let y = j.count_ones(); + // if y <= x { + // x - y + // } else { + // x + q - y + // } + // }) + // }); + // println!("ones = {:?}", ones); + // } + // + // XXX(RLB): Empirically, this is not much faster than just doing the calculation inline. But + // it avoids having any branching, and pre-computing seems aesthetically nice. + const ONES: [[u16; 8]; 8] = [ + [0, 3328, 3328, 3327, 3328, 3327, 3327, 3326], + [1, 0, 0, 3328, 0, 3328, 3328, 3327], + [1, 0, 0, 3328, 0, 3328, 3328, 3327], + [2, 1, 1, 0, 1, 0, 0, 3328], + [1, 0, 0, 3328, 0, 3328, 3328, 3327], + [2, 1, 1, 0, 1, 0, 0, 3328], + [2, 1, 1, 0, 1, 0, 0, 3328], + [3, 2, 2, 1, 2, 1, 1, 0], + ]; + + // Algorithm 7. SamplePolyCBD_eta(B) + // + // To avoid all the bitwise manipulation in the algorithm as written, we reuse the logic in + // ByteDecode. We decode the PRF output into integers with eta bits, then use + // `count_ones` to perform the summation described in the algorithm. + pub fn sample_cbd(B: &PrfOutput) -> Self + where + Eta: CbdSamplingSize, + { + let vals: Polynomial = Encode::::decode(B); + Self(vals.0.map(|val| { + // TODO Flatten ONES table to avoid the need for these operations + let x = val.0 & ((1 << Eta::USIZE) - 1); + let y = val.0 >> Eta::USIZE; + FieldElement(Self::ONES[x as usize][y as usize]) + })) + } +} + +/// A vector of polynomials of length `k` +#[derive(Clone, Default, Debug, PartialEq)] +pub struct PolynomialVector(pub GenericArray); + +impl Add> for PolynomialVector { + type Output = PolynomialVector; + + fn add(self, rhs: PolynomialVector) -> PolynomialVector { + PolynomialVector(self.0.zip(&rhs.0, |x, y| x + y)) + } +} + +impl PolynomialVector { + pub fn sample_cbd(sigma: &B32, start_n: u8) -> Self + where + Eta: CbdSamplingSize, + { + Self(GenericArray::generate(|i| { + let N = start_n + i.truncate(); + let prf_output = PRF::(sigma, N); + Polynomial::sample_cbd::(&prf_output) + })) + } +} + +/// An element of the ring `T_q`, i.e., a tuple of 128 elements of the direct sum components of `T_q`. +#[derive(Clone, Default, Debug, PartialEq)] +pub struct NttPolynomial(pub GenericArray); + +impl ConstDefault for NttPolynomial { + const DEFAULT: Self = Self(GenericArray::DEFAULT); +} + +impl Add<&NttPolynomial> for &NttPolynomial { + type Output = NttPolynomial; + + fn add(self, rhs: &NttPolynomial) -> NttPolynomial { + NttPolynomial(self.0.zip(&rhs.0, |&x, &y| x + y)) + } +} + +// Algorithm 6. SampleNTT (lines 4-13) +struct FieldElementReader<'a> { + xof: &'a mut dyn XofReader, + data: [u8; 96], + start: usize, + next: Option, +} + +impl<'a> FieldElementReader<'a> { + fn new(xof: &'a mut impl XofReader) -> Self { + let mut out = Self { + xof, + data: [0u8; 96], + start: 0, + next: None, + }; + + // Fill the buffer + out.xof.read(&mut out.data); + + out + } + + fn next(&mut self) -> FieldElement { + if let Some(val) = self.next { + self.next = None; + return FieldElement(val); + } + + loop { + if self.start == self.data.len() { + self.xof.read(&mut self.data); + self.start = 0; + } + + let end = self.start + 3; + let b = &self.data[self.start..end]; + self.start = end; + + let d1 = Integer::from(b[0]) + ((Integer::from(b[1]) & 0xf) << 8); + let d2 = (Integer::from(b[1]) >> 4) + ((Integer::from(b[2]) as Integer) << 4); + + if d1 < FieldElement::Q { + if d2 < FieldElement::Q { + self.next = Some(d2); + } + return FieldElement(d1); + } + + if d2 < FieldElement::Q { + return FieldElement(d2); + } + } + } +} + +impl NttPolynomial { + // Algorithm 6 SampleNTT(B) + pub fn sample_uniform(B: &mut impl XofReader) -> Self { + let mut reader = FieldElementReader::new(B); + Self(GenericArray::generate(|_| reader.next())) + } +} + +// Since the powers of zeta used in the NTT and MultiplyNTTs are fixed, we use pre-computed tables +// to avoid the need to compute the exponetiations at runtime. +// +// * ZETA_POW_BITREV[i] = zeta^{BitRev_7(i)} +// * GAMMA[i] = zeta^{2 BitRev_7(i) + 1} +// +// The below code was used to generate these tables: +// +// fn bit_reverse(mut x: usize) -> usize { +// let mut out = 0; +// for _i in 0..7 { +// out = (out << 1) + (x % 2); +// x = x >> 1; +// } +// out +// } +// +// fn generate_zeta_gamma() { +// const ZETA: FieldElement = FieldElement(17); +// +// let mut pow = [FieldElement(0); 128]; +// pow[0] = FieldElement(1); +// for i in 1..128 { +// pow[i] = pow[i - 1] * ZETA; +// } +// +// let mut zeta_pow_bitrev = [FieldElement(0); 128]; +// for i in 0..128 { +// zeta_pow_bitrev[i] = pow[bit_reverse(i)]; +// } +// println!("ZETA_POW_BITREV: {:?}", zeta_pow_bitrev); +// +// let mut gamma = [FieldElement(0); 128]; +// for i in 0..128 { +// gamma[i as usize] = (zeta_pow_bitrev[i] * zeta_pow_bitrev[i]) * ZETA; +// } +// println!("GAMMA: {:?}", gamma); +// } +const ZETA_POW_BITREV: [FieldElement; 128] = [ + FieldElement(1), + FieldElement(1729), + FieldElement(2580), + FieldElement(3289), + FieldElement(2642), + FieldElement(630), + FieldElement(1897), + FieldElement(848), + FieldElement(1062), + FieldElement(1919), + FieldElement(193), + FieldElement(797), + FieldElement(2786), + FieldElement(3260), + FieldElement(569), + FieldElement(1746), + FieldElement(296), + FieldElement(2447), + FieldElement(1339), + FieldElement(1476), + FieldElement(3046), + FieldElement(56), + FieldElement(2240), + FieldElement(1333), + FieldElement(1426), + FieldElement(2094), + FieldElement(535), + FieldElement(2882), + FieldElement(2393), + FieldElement(2879), + FieldElement(1974), + FieldElement(821), + FieldElement(289), + FieldElement(331), + FieldElement(3253), + FieldElement(1756), + FieldElement(1197), + FieldElement(2304), + FieldElement(2277), + FieldElement(2055), + FieldElement(650), + FieldElement(1977), + FieldElement(2513), + FieldElement(632), + FieldElement(2865), + FieldElement(33), + FieldElement(1320), + FieldElement(1915), + FieldElement(2319), + FieldElement(1435), + FieldElement(807), + FieldElement(452), + FieldElement(1438), + FieldElement(2868), + FieldElement(1534), + FieldElement(2402), + FieldElement(2647), + FieldElement(2617), + FieldElement(1481), + FieldElement(648), + FieldElement(2474), + FieldElement(3110), + FieldElement(1227), + FieldElement(910), + FieldElement(17), + FieldElement(2761), + FieldElement(583), + FieldElement(2649), + FieldElement(1637), + FieldElement(723), + FieldElement(2288), + FieldElement(1100), + FieldElement(1409), + FieldElement(2662), + FieldElement(3281), + FieldElement(233), + FieldElement(756), + FieldElement(2156), + FieldElement(3015), + FieldElement(3050), + FieldElement(1703), + FieldElement(1651), + FieldElement(2789), + FieldElement(1789), + FieldElement(1847), + FieldElement(952), + FieldElement(1461), + FieldElement(2687), + FieldElement(939), + FieldElement(2308), + FieldElement(2437), + FieldElement(2388), + FieldElement(733), + FieldElement(2337), + FieldElement(268), + FieldElement(641), + FieldElement(1584), + FieldElement(2298), + FieldElement(2037), + FieldElement(3220), + FieldElement(375), + FieldElement(2549), + FieldElement(2090), + FieldElement(1645), + FieldElement(1063), + FieldElement(319), + FieldElement(2773), + FieldElement(757), + FieldElement(2099), + FieldElement(561), + FieldElement(2466), + FieldElement(2594), + FieldElement(2804), + FieldElement(1092), + FieldElement(403), + FieldElement(1026), + FieldElement(1143), + FieldElement(2150), + FieldElement(2775), + FieldElement(886), + FieldElement(1722), + FieldElement(1212), + FieldElement(1874), + FieldElement(1029), + FieldElement(2110), + FieldElement(2935), + FieldElement(885), + FieldElement(2154), +]; +const GAMMA: [FieldElement; 128] = [ + FieldElement(17), + FieldElement(3312), + FieldElement(2761), + FieldElement(568), + FieldElement(583), + FieldElement(2746), + FieldElement(2649), + FieldElement(680), + FieldElement(1637), + FieldElement(1692), + FieldElement(723), + FieldElement(2606), + FieldElement(2288), + FieldElement(1041), + FieldElement(1100), + FieldElement(2229), + FieldElement(1409), + FieldElement(1920), + FieldElement(2662), + FieldElement(667), + FieldElement(3281), + FieldElement(48), + FieldElement(233), + FieldElement(3096), + FieldElement(756), + FieldElement(2573), + FieldElement(2156), + FieldElement(1173), + FieldElement(3015), + FieldElement(314), + FieldElement(3050), + FieldElement(279), + FieldElement(1703), + FieldElement(1626), + FieldElement(1651), + FieldElement(1678), + FieldElement(2789), + FieldElement(540), + FieldElement(1789), + FieldElement(1540), + FieldElement(1847), + FieldElement(1482), + FieldElement(952), + FieldElement(2377), + FieldElement(1461), + FieldElement(1868), + FieldElement(2687), + FieldElement(642), + FieldElement(939), + FieldElement(2390), + FieldElement(2308), + FieldElement(1021), + FieldElement(2437), + FieldElement(892), + FieldElement(2388), + FieldElement(941), + FieldElement(733), + FieldElement(2596), + FieldElement(2337), + FieldElement(992), + FieldElement(268), + FieldElement(3061), + FieldElement(641), + FieldElement(2688), + FieldElement(1584), + FieldElement(1745), + FieldElement(2298), + FieldElement(1031), + FieldElement(2037), + FieldElement(1292), + FieldElement(3220), + FieldElement(109), + FieldElement(375), + FieldElement(2954), + FieldElement(2549), + FieldElement(780), + FieldElement(2090), + FieldElement(1239), + FieldElement(1645), + FieldElement(1684), + FieldElement(1063), + FieldElement(2266), + FieldElement(319), + FieldElement(3010), + FieldElement(2773), + FieldElement(556), + FieldElement(757), + FieldElement(2572), + FieldElement(2099), + FieldElement(1230), + FieldElement(561), + FieldElement(2768), + FieldElement(2466), + FieldElement(863), + FieldElement(2594), + FieldElement(735), + FieldElement(2804), + FieldElement(525), + FieldElement(1092), + FieldElement(2237), + FieldElement(403), + FieldElement(2926), + FieldElement(1026), + FieldElement(2303), + FieldElement(1143), + FieldElement(2186), + FieldElement(2150), + FieldElement(1179), + FieldElement(2775), + FieldElement(554), + FieldElement(886), + FieldElement(2443), + FieldElement(1722), + FieldElement(1607), + FieldElement(1212), + FieldElement(2117), + FieldElement(1874), + FieldElement(1455), + FieldElement(1029), + FieldElement(2300), + FieldElement(2110), + FieldElement(1219), + FieldElement(2935), + FieldElement(394), + FieldElement(885), + FieldElement(2444), + FieldElement(2154), + FieldElement(1175), +]; + +// Algorithm 10. MuliplyNTTs +impl Mul<&NttPolynomial> for &NttPolynomial { + type Output = NttPolynomial; + + fn mul(self, rhs: &NttPolynomial) -> NttPolynomial { + let mut out = NttPolynomial(GenericArray::const_default()); + + for i in 0..128 { + let (c0, c1) = FieldElement::base_case_multiply( + self.0[2 * i], + self.0[2 * i + 1], + rhs.0[2 * i], + rhs.0[2 * i + 1], + i, + ); + + out.0[2 * i] = c0; + out.0[2 * i + 1] = c1; + } + + out + } +} + +impl From> for NttPolynomial { + fn from(f: GenericArray) -> NttPolynomial { + NttPolynomial(f) + } +} + +impl From for GenericArray { + fn from(f_hat: NttPolynomial) -> GenericArray { + f_hat.0 + } +} + +// Algorithm 8. NTT +impl Polynomial { + pub fn ntt(&self) -> NttPolynomial { + let mut k = 1; + + let mut f = self.0; + for len in [128, 64, 32, 16, 8, 4, 2] { + for start in (0..256).step_by(2 * len) { + let zeta = ZETA_POW_BITREV[k]; + k += 1; + + for j in start..(start + len) { + let t = zeta * f[j + len]; + f[j + len] = f[j] - t; + f[j] = f[j] + t; + } + } + } + + f.into() + } +} + +// Algorithm 9. NTT^{-1} +impl NttPolynomial { + pub fn ntt_inverse(&self) -> Polynomial { + let mut f: GenericArray = self.0.fast_clone(); + + let mut k = 127; + for len in [2, 4, 8, 16, 32, 64, 128] { + for start in (0..256).step_by(2 * len) { + let zeta = ZETA_POW_BITREV[k]; + k -= 1; + + for j in start..(start + len) { + let t = f[j]; + f[j] = t + f[j + len]; + f[j + len] = zeta * (f[j + len] - t); + } + } + } + + FieldElement(3303) * &Polynomial(f) + } +} + +/// A vector of K NTT-domain polynomials +#[derive(Clone, Default, Debug, PartialEq)] +pub struct NttVector(pub GenericArray); + +impl NttVector { + // Note the transpose here: Apparently the specification is incorrect, and the proper order + // of indices is reversed. + // + // https://github.com/FiloSottile/mlkem768/blob/main/mlkem768.go#L110C4-L112C51 + pub fn sample_uniform(rho: &B32, i: usize, transpose: bool) -> Self { + Self(GenericArray::generate(|j| { + let (i, j) = if transpose { (i, j) } else { (j, i) }; + let mut xof = XOF(rho, i.truncate(), j.truncate()); + NttPolynomial::sample_uniform(&mut xof) + })) + } +} + +impl Add<&NttVector> for &NttVector { + type Output = NttVector; + + fn add(self, rhs: &NttVector) -> NttVector { + NttVector(self.0.zip(&rhs.0, |x, y| x + y)) + } +} + +impl Mul<&NttVector> for &NttVector { + type Output = NttPolynomial; + + fn mul(self, rhs: &NttVector) -> NttPolynomial { + self.0.zip(&rhs.0, |x, y| x * y).fold(|x, y| x + y) + } +} + +impl PolynomialVector { + pub fn ntt(&self) -> NttVector { + NttVector(self.0.map(Polynomial::ntt)) + } +} + +impl NttVector { + pub fn ntt_inverse(&self) -> PolynomialVector { + PolynomialVector(self.0.map(NttPolynomial::ntt_inverse)) + } +} + +/// A K x K matrix of NTT-domain polynomials. Each vector represents a row of the matrix, so that +/// multiplying on the right just requires iteration. +#[derive(Clone, Default, Debug, PartialEq)] +pub struct NttMatrix(GenericArray, K>); + +impl Mul<&NttVector> for &NttMatrix { + type Output = NttVector; + + fn mul(self, rhs: &NttVector) -> NttVector { + NttVector(self.0.map(|x| x * rhs)) + } +} + +impl NttMatrix { + pub fn sample_uniform(rho: &B32, transpose: bool) -> Self { + Self(GenericArray::generate(|i| { + NttVector::sample_uniform(rho, i, transpose) + })) + } + + pub fn transpose(&self) -> Self { + Self(GenericArray::generate(|i| { + NttVector(GenericArray::generate(|j| self.0[j].0[i].clone())) + })) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::util::Flatten; + use generic_array::arr; + use typenum::consts::{U2, U3, U8}; + + // Multiplication in R_q, modulo X^256 + 1 + impl Mul<&Polynomial> for &Polynomial { + type Output = Polynomial; + + fn mul(self, rhs: &Polynomial) -> Self::Output { + let mut out = Self::Output::DEFAULT; + for (i, x) in self.0.iter().enumerate() { + for (j, y) in rhs.0.iter().enumerate() { + let (sign, index) = if i + j < 256 { + (FieldElement(1), i + j) + } else { + (FieldElement(FieldElement::Q - 1), i + j - 256) + }; + + out.0[index] = out.0[index] + (sign * *x * *y); + } + } + out + } + } + + // A polynomial with only a scalar component, to make simple test cases + fn const_ntt(x: Integer) -> NttPolynomial { + let mut p = Polynomial::DEFAULT; + p.0[0] = FieldElement(x); + p.ntt() + } + + #[test] + fn polynomial_ops() { + let f = Polynomial(GenericArray::generate(|i| FieldElement(i as Integer))); + let g = Polynomial(GenericArray::generate(|i| FieldElement(2 * i as Integer))); + let sum = Polynomial(GenericArray::generate(|i| FieldElement(3 * i as Integer))); + assert_eq!((&f + &g), sum); + assert_eq!((&sum - &g), f); + assert_eq!(FieldElement(3) * &f, sum); + } + + #[test] + fn ntt() { + let f = Polynomial(GenericArray::generate(|i| FieldElement(i as Integer))); + let g = Polynomial(GenericArray::generate(|i| FieldElement(2 * i as Integer))); + let f_hat = f.ntt(); + let g_hat = g.ntt(); + + // Verify that NTT and NTT^-1 are actually inverses + let f_unhat = f_hat.ntt_inverse(); + assert_eq!(f, f_unhat); + + // Verify that NTT is a homomorphism with regard to addition + let fg = &f + &g; + let f_hat_g_hat = &f_hat + &g_hat; + let fg_unhat = f_hat_g_hat.ntt_inverse(); + assert_eq!(fg, fg_unhat); + + // Verify that NTT is a homomorphism with regard to multiplication + let fg = &f * &g; + let f_hat_g_hat = &f_hat * &g_hat; + let fg_unhat = f_hat_g_hat.ntt_inverse(); + assert_eq!(fg, fg_unhat); + } + + #[test] + fn ntt_vector() { + // Verify vector addition + let v1 = NttVector(arr![const_ntt(1), const_ntt(1), const_ntt(1)]); + let v2 = NttVector(arr![const_ntt(2), const_ntt(2), const_ntt(2)]); + let v3 = NttVector(arr![const_ntt(3), const_ntt(3), const_ntt(3)]); + assert_eq!((&v1 + &v2), v3); + + // Verify dot product + assert_eq!((&v1 * &v2), const_ntt(6)); + assert_eq!((&v1 * &v3), const_ntt(9)); + assert_eq!((&v2 * &v3), const_ntt(18)); + } + + #[test] + fn ntt_matrix() { + // Verify matrix multiplication by a vector + let a = NttMatrix(arr![ + NttVector(arr![const_ntt(1), const_ntt(2), const_ntt(3)]), + NttVector(arr![const_ntt(4), const_ntt(5), const_ntt(6)]), + NttVector(arr![const_ntt(7), const_ntt(8), const_ntt(9)]), + ]); + let v_in = NttVector(arr![const_ntt(1), const_ntt(2), const_ntt(3)]); + let v_out = NttVector(arr![const_ntt(14), const_ntt(32), const_ntt(50)]); + assert_eq!(&a * &v_in, v_out); + + // Verify transpose + let aT = NttMatrix(arr![ + NttVector(arr![const_ntt(1), const_ntt(4), const_ntt(7)]), + NttVector(arr![const_ntt(2), const_ntt(5), const_ntt(8)]), + NttVector(arr![const_ntt(3), const_ntt(6), const_ntt(9)]), + ]); + assert_eq!(a.transpose(), aT); + } + + // To verify the accuracy of sampling, we use a theorem related to the law of large numbers, + // which bounds the convergence of the Kullback-Liebler distance between the empirical + // distribution and the hypothesized distribution. + // + // Theorem (Cover & Thomas, 1991, Theorem 12.2.1): Let $X_1, \ldots, X_n$ be i.i.d. $~P(x)$. + // Then: + // + // Pr{ D(P_{x^n} || P) > \epsilon } \leq 2^{ -n ( \epsilon - |X|^{ log(n+1) / n } ) } + // + // So if we test by computing D(P_{x^n} || P) and requiring the value to be below a threshold + // \epsilon, then an unbiased sampling should pass with overwhelming probability 1 - 2^{-k}, + // for some k based on \epsilon, |X|, and n. + // + // If we take k = 256 and n = 256, then we can solve for the required threshold \epsilon: + // + // \epsilon = 1 + |X|^{ 0.03125 } + // + // For the cases we're interested in here: + // + // CBD(eta = 2) => |X| = 5 => epsilon ~= 2.0516 + // CBD(eta = 2) => |X| = 7 => epsilon ~= 2.0627 + // Uniform byte => |X| = 256 => epsilon ~= 2.1892 + // + // Taking epsilon = 2.05 makes us conservative enough in all cases, without significantly + // increasing the probability of false negatives. + const KL_THRESHOLD: f64 = 2.05; + + // The centered binomial distributions are calculated as: + // + // bin_\eta(k) = (2\eta \choose k + \eta) 2^{-2\eta} + // + // for k in $-\eta, \ldots, \eta$. The cases of interest here are \eta = 2, 3. + type Distribution = [f64; Q_SIZE]; + const Q_SIZE: usize = FieldElement::Q as usize; + const CBD2: Distribution = { + let mut dist = [0.0; Q_SIZE]; + dist[Q_SIZE - 2] = 1.0 / 16.0; + dist[Q_SIZE - 1] = 4.0 / 16.0; + dist[0] = 6.0 / 16.0; + dist[1] = 4.0 / 16.0; + dist[2] = 1.0 / 16.0; + dist + }; + const CBD3: Distribution = { + let mut dist = [0.0; Q_SIZE]; + dist[Q_SIZE - 3] = 1.0 / 64.0; + dist[Q_SIZE - 2] = 6.0 / 64.0; + dist[Q_SIZE - 1] = 15.0 / 64.0; + dist[0] = 20.0 / 64.0; + dist[1] = 15.0 / 64.0; + dist[2] = 6.0 / 64.0; + dist[3] = 1.0 / 64.0; + dist + }; + const UNIFORM: Distribution = [1.0 / (FieldElement::Q as f64); Q_SIZE]; + + fn kl_divergence(p: &Distribution, q: &Distribution) -> f64 { + p.iter() + .zip(q.iter()) + .map(|(p, q)| if *p == 0.0 { 0.0 } else { p * (p / q).log2() }) + .sum() + } + + fn test_sample(sample: &[FieldElement], ref_dist: &Distribution) { + // Verify data and compute the empirical distribution + let mut sample_dist: Distribution = [0.0; Q_SIZE]; + let bump: f64 = 1.0 / (sample.len() as f64); + for x in sample { + assert!(x.0 < FieldElement::Q); + assert!(ref_dist[x.0 as usize] > 0.0); + + sample_dist[x.0 as usize] += bump; + } + + let d = kl_divergence(&sample_dist, ref_dist); + assert!(d < KL_THRESHOLD); + } + + #[test] + fn sample_uniform() { + // We require roughly Q/2 samples to verify the uniform distribution. This is because for + // M < N, the uniform distribution over a subset of M elements has KL distance: + // + // M sum(p * log(q / p)) = log(q / p) = log(N / M) + // + // Since Q ~= 2^11 and 256 == 2^8, we need 2^3 == 8 runs of 256 to get out of the bad + // regime and get a meaningful measurement. + let rho = B32::const_default(); + let sample: GenericArray, U8> = + GenericArray::generate(|i| { + let mut xof = XOF(&rho, 0, i as u8); + NttPolynomial::sample_uniform(&mut xof).into() + }); + + test_sample(&sample.flatten(), &UNIFORM); + } + + #[test] + fn sample_cbd() { + // Eta = 2 + let sigma = B32::const_default(); + let prf_output = PRF::(&sigma, 0); + let sample = Polynomial::sample_cbd::(&prf_output).0; + test_sample(&sample, &CBD2); + + // Eta = 3 + let sigma = B32::const_default(); + let prf_output = PRF::(&sigma, 0); + let sample = Polynomial::sample_cbd::(&prf_output).0; + test_sample(&sample, &CBD3); + } +} diff --git a/ml-kem/src/compress.rs b/ml-kem/src/compress.rs new file mode 100644 index 0000000..bb1986f --- /dev/null +++ b/ml-kem/src/compress.rs @@ -0,0 +1,120 @@ +use crate::algebra::{FieldElement, Integer, Polynomial, PolynomialVector}; +use crate::param::{ArrayLength, EncodingSize}; +use crate::util::Truncate; + +// A convenience trait to allow us to associate some constants with a typenum +pub trait CompressionFactor: EncodingSize { + const POW2_HALF: u32; + const MASK: Integer; +} + +impl CompressionFactor for T +where + T: EncodingSize, +{ + const POW2_HALF: u32 = 1 << (T::USIZE - 1); + const MASK: Integer = ((1 as Integer) << T::USIZE) - 1; +} + +// Traits for objects that allow compression / decompression +pub trait Compress { + fn compress(&mut self) -> &Self; + fn decompress(&mut self) -> &Self; +} + +impl Compress for FieldElement { + // Equation 4.5: Compress_d(x) = round((2^d / q) x) + // + // Here and in decompression, we leverage the following fact: + // + // round(a / b) = floor((a + b/2) / b) + fn compress(&mut self) -> &Self { + const Q_HALF: u32 = (FieldElement::Q32 - 1) / 2; + let x = u32::from(self.0); + let y = ((x << D::USIZE) + Q_HALF) / FieldElement::Q32; + self.0 = y.truncate() & D::MASK; + self + } + // Equation 4.6: Decomporess_d(x) = round((q / 2^d) x) + fn decompress(&mut self) -> &Self { + let x = u32::from(self.0); + let y = ((x * FieldElement::Q32) + D::POW2_HALF) >> D::USIZE; + self.0 = y.truncate(); + self + } +} + +impl Compress for Polynomial { + fn compress(&mut self) -> &Self { + for x in &mut self.0 { + x.compress::(); + } + + self + } + + fn decompress(&mut self) -> &Self { + for x in &mut self.0 { + x.decompress::(); + } + + self + } +} + +impl Compress for PolynomialVector { + fn compress(&mut self) -> &Self { + for x in &mut self.0 { + x.compress::(); + } + + self + } + + fn decompress(&mut self) -> &Self { + for x in &mut self.0 { + x.decompress::(); + } + + self + } +} + +#[cfg(test)] +pub(crate) mod test { + use super::*; + use typenum::consts::*; + + // Verify that the integer compression routine produces the same results as rounding with + // floats. + fn compression_known_answer_test() { + let fq: f64 = FieldElement::Q as f64; + let f2d: f64 = 2.0_f64.powi(D::I32); + + for x in 0..FieldElement::Q { + let fx = x as f64; + let mut x = FieldElement(x); + + // Verify equivalence of compression + x.compress::(); + let fcx = ((f2d / fq * fx).round() as Integer) % (1 << D::USIZE); + assert_eq!(x.0, fcx); + + // Verify equivalence of decompression + x.decompress::(); + let fdx = (fq / f2d * (fcx as f64)).round() as Integer; + assert_eq!(x.0, fdx); + } + } + + #[test] + fn compress_decompress() { + compression_known_answer_test::(); + compression_known_answer_test::(); + compression_known_answer_test::(); + compression_known_answer_test::(); + compression_known_answer_test::(); + compression_known_answer_test::(); + compression_known_answer_test::(); + } +} diff --git a/ml-kem/src/crypto.rs b/ml-kem/src/crypto.rs new file mode 100644 index 0000000..3007335 --- /dev/null +++ b/ml-kem/src/crypto.rs @@ -0,0 +1,210 @@ +#![allow(dead_code)] + +use crypto_common::rand_core::CryptoRngCore; +use generic_array::{ArrayLength, GenericArray}; +use sha3::{ + digest::{ExtendableOutput, Update, XofReader}, + Digest, Sha3_256, Sha3_512, Shake128, Shake256, +}; + +use crate::param::{CbdSamplingSize, EncodedPolynomial}; +use crate::util::B32; + +pub fn rand(rng: &mut impl CryptoRngCore) -> GenericArray { + let mut val = GenericArray::default(); + rng.fill_bytes(&mut val); + val +} + +pub fn G(inputs: &[impl AsRef<[u8]>]) -> (B32, B32) { + let mut h = Sha3_512::new(); + for x in inputs { + Digest::update(&mut h, x); + } + let out = h.finalize(); + + let mut a = B32::const_default(); + let mut b = B32::const_default(); + + a.copy_from_slice(&out[..32]); + b.copy_from_slice(&out[32..]); + (a, b) +} + +pub fn H(x: impl AsRef<[u8]>) -> B32 { + let mut h = Sha3_256::new(); + Digest::update(&mut h, x); + + // This odd conversion is needed because the `sha3` crate links against an old version of + // the `generic-array` crate. It should be pretty cheap though, since there's only one + // allocation / no copies. + let mut out = B32::const_default(); + h.finalize_into(out.as_mut_slice().into()); + out +} + +pub fn J(inputs: &[impl AsRef<[u8]>]) -> B32 { + let mut h = Shake256::default(); + for x in inputs { + h.update(x.as_ref()); + } + let mut r = h.finalize_xof(); + + let mut out = B32::default(); + r.read(&mut out); + out +} + +pub type PrfOutput = EncodedPolynomial<::SampleSize>; + +pub fn PRF(s: &B32, b: u8) -> PrfOutput +where + Eta: CbdSamplingSize, +{ + let mut h = Shake256::default(); + h.update(s.as_ref()); + h.update(&[b]); + let mut r = h.finalize_xof(); + + let mut out = PrfOutput::::default(); + r.read(&mut out); + out +} + +pub fn XOF(rho: &B32, i: u8, j: u8) -> impl XofReader { + let mut h = Shake128::default(); + h.update(rho); + h.update(&[i, j]); + h.finalize_xof() +} + +// // A Go script to generate the test vector outputs +// +// package main +// +// import ( +// "fmt" +// "golang.org/x/crypto/sha3" +// ) +// +// func main() { +// // G: B* -> B32 || B32 = SHA3_512(c) +// msgG := []byte("Input to an invocation of G") +// hG := sha3.New512() +// hG.Write(msgG) +// fmt.Printf("G: %x\n", hG.Sum(nil)) +// +// // H: B* -> B32 = SHA3_256(s) +// msgH := []byte("Input to an invocation of H") +// hH := sha3.New256() +// hH.Write(msgH) +// fmt.Printf("H: %x\n", hH.Sum(nil)) +// +// // J: B* -> B32 = SHAKE256(s, 32) +// msgJ := []byte("Input to an invocation of J") +// outJ := make([]byte, 32) +// sha3.ShakeSum256(outJ, msgJ) +// fmt.Printf("J: %x\n", outJ) +// +// // PRF<2>: B32 x B -> B64eta = SHAKE256(s || b, 64 * eta) +// msgPRF2s := []byte("Input s to an invocation of PRF2") +// msgPRF2b := []byte("b") +// msgPRF2 := append(msgPRF2s, msgPRF2b...) +// outPRF2 := make([]byte, 64 * 2) +// sha3.ShakeSum256(outPRF2, msgPRF2) +// fmt.Printf("PRF<2>: %x\n", outPRF2) +// +// // PRF<3>: B33 x B -> B64eta = SHAKE256(s || b, 64 * eta) +// msgPRF3s := []byte("Input s to an invocation of PRF3") +// msgPRF3b := []byte("b") +// msgPRF3 := append(msgPRF3s, msgPRF3b...) +// outPRF3 := make([]byte, 64 * 3) +// sha3.ShakeSum256(outPRF3, msgPRF3) +// fmt.Printf("PRF<3>: %x\n", outPRF3) +// +// // XOF: B32 x B x B -> B* = SHAKE128(rho || i || j) +// msgXOFrho := []byte("Input rho, to an XOF invocation!") +// msgXOFi := []byte("i") +// msgXOFj := []byte("j") +// msgXOF := append(append(msgXOFrho, msgXOFi...), msgXOFj...) +// outXOF := make([]byte, 32) +// sha3.ShakeSum128(outXOF, msgXOF) +// fmt.Printf("XOF: %x\n", outXOF) +// +// } + +#[cfg(test)] +mod test { + use super::*; + use hex_literal::hex; + use typenum::{U2, U3}; + + #[test] + fn g() { + let msg1 = "Input to ".as_bytes(); + let msg2 = "an invocation of G".as_bytes(); + let (actualA, actualB) = G(&[msg1, msg2]); + let expectedA = hex!("07dfced2a3a3feb3277cee1709818828ea6d2f42800152e9c312e848122231c2"); + let expectedB = hex!("272969098a1bbd5a0a9844e2f89f206d8f7f4599e36aecaa4793af400fd880d8"); + assert_eq!(actualA, expectedA.into()); + assert_eq!(actualB, expectedB.into()); + } + + #[test] + fn h() { + let msg = "Input to an invocation of H".as_bytes(); + let actual = H(msg); + let expected = hex!("0ee3ce94213d7dd0069b24b8b15cdd0bcf8eb1c6b3c21c441dc6a19e979cc7eb"); + assert_eq!(actual, expected.into()); + } + + #[test] + fn j() { + let msg1 = "Input to ".as_bytes(); + let msg2 = "an invocation of J".as_bytes(); + let actual = J(&[msg1, msg2]); + let expected = hex!("a5292293d70c8eca049cbb475c48fabd625ed2b20785a18248504d3741196b52"); + assert_eq!(actual, expected.into()); + } + + #[test] + fn prf() { + let s = B32::from_slice("Input s to an invocation of PRF2".as_bytes()); + let b = b'b'; + let actual = PRF::(s, b); + let expected = hex!( + "54c002415c2219b564d5c17b0df0c82f83ddf3fdecc7d814ed5d85457c06c2c3\ + ed0b0584f926dffb1e57c6105f8604e81c4605b93f8284e44585104101042075\ + 568113c861516d91bed227638654fc7f872df205c113b8364091755b62284eec\ + a6124f2cd4c1cdf598cb8324a4f373470a8f81ee618c75cc33f66facee01c213" + ); + assert_eq!(actual, expected.into()); + + let s = B32::from_slice("Input s to an invocation of PRF3".as_bytes()); + let b = b'b'; + let actual = PRF::(s, b); + let expected = hex!( + "5e12028f67479b862a12713cda833e21b8ccd51bff9ddc2bfb9ab2910a9dc2e6\ + c58264a3f51ccc9ef4ff936a15505e016f60c36ffe300be01b9fb12eacd57867\ + 0873c24709d6146b42c42a07873522eac100d61942ae53e73fbf9095b29b1ab7\ + 169e954213c062703dad88c1c5f57f92af143f0364fe057b134b54ea8a55d94c\ + 67764b3fc6b37376453978b8f0caeb6b18c188c28ee8681e28339477e042d5a1\ + b4a12deb1de8b9dad026b4e323e03973ffbe25dd511eed5460d22a9851cfc220" + ); + assert_eq!(actual, expected.into()); + } + + #[test] + fn xof() { + let rho = B32::from_slice("Input rho, to an XOF invocation!".as_bytes()); + let i = b'i'; + let j = b'j'; + + let mut reader = XOF(rho, i, j); + let mut actual = [0u8; 32]; + reader.read(&mut actual); + + let expected = hex!("0d2c3e65f754d074cb366cf1b099ae105cc40f018342509f15f1ba8a1a4144cb"); + assert_eq!(actual, expected); + } +} diff --git a/ml-kem/src/encode.rs b/ml-kem/src/encode.rs new file mode 100644 index 0000000..cffb509 --- /dev/null +++ b/ml-kem/src/encode.rs @@ -0,0 +1,283 @@ +use generic_array::GenericArray; +use typenum::{Unsigned, U256}; + +use crate::algebra::{ + FieldElement, Integer, NttPolynomial, NttVector, Polynomial, PolynomialVector, +}; +use crate::param::{ArrayLength, EncodedPolynomial, EncodingSize, VectorEncodingSize}; +use crate::util::{FunctionalArray, Truncate}; + +type DecodedValue = GenericArray; + +// Algorithm 4 ByteEncode_d(F) +// +// Note: This algorithm performs compression as well as encoding. +fn byte_encode(vals: &DecodedValue) -> EncodedPolynomial { + let val_step = D::ValueStep::USIZE; + let byte_step = D::ByteStep::USIZE; + + let mut bytes = EncodedPolynomial::::default(); + + let vc = vals.chunks(val_step); + let bc = bytes.chunks_mut(byte_step); + for (v, b) in vc.zip(bc) { + let mut x = 0u128; + for (j, vj) in v.iter().enumerate() { + x |= u128::from(vj.0) << (D::USIZE * j); + } + + let xb = x.to_le_bytes(); + b.copy_from_slice(&xb[..byte_step]); + } + + bytes +} + +// Algorithm 5 ByteDecode_d(F) +// +// Note: This function performs decompression as well as decoding. +fn byte_decode(bytes: &EncodedPolynomial) -> DecodedValue { + let val_step = D::ValueStep::USIZE; + let byte_step = D::ByteStep::USIZE; + let mask = (1 << D::USIZE) - 1; + + let mut vals = DecodedValue::default(); + + let vc = vals.chunks_mut(val_step); + let bc = bytes.chunks(byte_step); + for (v, b) in vc.zip(bc) { + let mut xb = [0u8; 16]; + xb[..byte_step].copy_from_slice(b); + + let x = u128::from_le_bytes(xb); + for (j, vj) in v.iter_mut().enumerate() { + let val: Integer = (x >> (D::USIZE * j)).truncate(); + vj.0 = val & mask; + + if D::USIZE == 12 { + vj.0 %= FieldElement::Q; + } + } + } + + vals +} + +pub trait Encode { + type EncodedSize: ArrayLength; + fn encode(&self) -> GenericArray; + fn decode(enc: &GenericArray) -> Self; +} + +impl Encode for Polynomial { + type EncodedSize = D::EncodedPolynomialSize; + + fn encode(&self) -> GenericArray { + byte_encode::(&self.0) + } + + fn decode(enc: &GenericArray) -> Self { + Self(byte_decode::(enc)) + } +} + +impl Encode for PolynomialVector +where + K: ArrayLength, + D: VectorEncodingSize, +{ + type EncodedSize = D::EncodedPolynomialVectorSize; + + fn encode(&self) -> GenericArray { + let polys = self.0.map(|x| Encode::::encode(x)); + >::flatten(polys) + } + + fn decode(enc: &GenericArray) -> Self { + let unfold = >::unflatten(enc); + Self(unfold.map(|&x| >::decode(x))) + } +} + +impl Encode for NttPolynomial { + type EncodedSize = D::EncodedPolynomialSize; + + fn encode(&self) -> GenericArray { + byte_encode::(&self.0) + } + + fn decode(enc: &GenericArray) -> Self { + Self(byte_decode::(enc)) + } +} + +impl Encode for NttVector +where + D: VectorEncodingSize, + K: ArrayLength, +{ + type EncodedSize = D::EncodedPolynomialVectorSize; + + fn encode(&self) -> GenericArray { + let polys = self.0.map(|x| Encode::::encode(x)); + >::flatten(polys) + } + + fn decode(enc: &GenericArray) -> Self { + let unfold = >::unflatten(enc); + Self(unfold.map(|&x| >::decode(x))) + } +} + +#[cfg(test)] +pub(crate) mod test { + use super::*; + use core::cmp::PartialEq; + use core::fmt::Debug; + use core::ops::Rem; + use generic_array::{arr, sequence::GenericSequence, ArrayLength}; + use rand::Rng; + use typenum::{consts::*, marker_traits::Zero, operator_aliases::Mod}; + + use crate::param::EncodedPolynomialVector; + + // A helper trait to construct larger arrays by repeating smaller ones + trait Repeat { + fn repeat(&self) -> GenericArray; + } + + impl Repeat for GenericArray + where + N: ArrayLength, + T: Clone, + D: ArrayLength + Rem, + Mod: Zero, + { + fn repeat(&self) -> GenericArray { + GenericArray::generate(|i| self[i % N::USIZE].clone()) + } + } + + fn byte_codec_test(decoded: DecodedValue, encoded: EncodedPolynomial) + where + D: EncodingSize, + { + // Test known answer + let actual_encoded = byte_encode::(&decoded); + assert_eq!(actual_encoded, encoded); + + let actual_decoded = byte_decode::(&encoded); + assert_eq!(actual_decoded, decoded); + + // Test random decode/encode and encode/decode round trips + let mut rng = rand::thread_rng(); + let mut decoded: GenericArray = Default::default(); + rng.fill(decoded.as_mut_slice()); + let m = match D::USIZE { + 12 => FieldElement::Q, + d => (1 as Integer) << d, + }; + let decoded = decoded.map(|x| FieldElement(x % m)); + + let actual_encoded = byte_encode::(&decoded); + let actual_decoded = byte_decode::(&actual_encoded); + assert_eq!(actual_decoded, decoded); + + let actual_reencoded = byte_encode::(&decoded); + assert_eq!(actual_reencoded, actual_encoded); + } + + #[test] + fn byte_codec() { + // The 1-bit can only represent decoded values equal to 0 or 1. + let decoded: DecodedValue = arr![FieldElement(0), FieldElement(1)].repeat(); + let encoded: EncodedPolynomial = arr![0xaa; 32]; + byte_codec_test::(decoded, encoded); + + // For other codec widths, we use a standard sequence + let decoded: DecodedValue = arr![ + FieldElement(0), + FieldElement(1), + FieldElement(2), + FieldElement(3), + FieldElement(4), + FieldElement(5), + FieldElement(6), + FieldElement(7) + ] + .repeat(); + + let encoded: EncodedPolynomial = arr![0x10, 0x32, 0x54, 0x76].repeat(); + byte_codec_test::(decoded, encoded); + + let encoded: EncodedPolynomial = arr![0x20, 0x88, 0x41, 0x8a, 0x39].repeat(); + byte_codec_test::(decoded, encoded); + + let encoded: EncodedPolynomial = arr![0x40, 0x20, 0x0c, 0x44, 0x61, 0x1c].repeat(); + byte_codec_test::(decoded, encoded); + + let encoded: EncodedPolynomial = + arr![0x00, 0x04, 0x20, 0xc0, 0x00, 0x04, 0x14, 0x60, 0xc0, 0x01].repeat(); + byte_codec_test::(decoded, encoded); + + let encoded: EncodedPolynomial = + arr![0x00, 0x08, 0x80, 0x00, 0x06, 0x40, 0x80, 0x02, 0x18, 0xe0, 0x00].repeat(); + byte_codec_test::(decoded, encoded); + + let encoded: EncodedPolynomial = + arr![0x00, 0x10, 0x00, 0x02, 0x30, 0x00, 0x04, 0x50, 0x00, 0x06, 0x70, 0x00].repeat(); + byte_codec_test::(decoded, encoded); + } + + #[test] + fn byte_codec_12_mod() { + // DecodeBytes_12 is required to reduce mod q + let encoded: EncodedPolynomial = arr![0xff; 384]; + let decoded: DecodedValue = arr![FieldElement(0xfff % FieldElement::Q); 256]; + + let actual_decoded = byte_decode::(&encoded); + assert_eq!(actual_decoded, decoded); + } + + fn vector_codec_known_answer_test(decoded: T, encoded: GenericArray) + where + D: EncodingSize, + T: Encode + PartialEq + Debug, + { + let actual_encoded = decoded.encode(); + assert_eq!(actual_encoded, encoded); + + let actual_decoded: T = Encode::decode(&encoded); + assert_eq!(actual_decoded, decoded); + } + + #[test] + fn vector_codec() { + let poly = Polynomial( + arr![ + FieldElement(0), + FieldElement(1), + FieldElement(2), + FieldElement(3), + FieldElement(4), + FieldElement(5), + FieldElement(6), + FieldElement(7) + ] + .repeat(), + ); + + // The required vector sizes are 2, 3, and 4. + let decoded: PolynomialVector = PolynomialVector(arr![poly, poly]); + let encoded: EncodedPolynomialVector = arr![0x20, 0x88, 0x41, 0x8a, 0x39].repeat(); + vector_codec_known_answer_test::>(decoded, encoded); + + let decoded: PolynomialVector = PolynomialVector(arr![poly, poly, poly]); + let encoded: EncodedPolynomialVector = arr![0x20, 0x88, 0x41, 0x8a, 0x39].repeat(); + vector_codec_known_answer_test::>(decoded, encoded); + + let decoded: PolynomialVector = PolynomialVector(arr![poly, poly, poly, poly]); + let encoded: EncodedPolynomialVector = arr![0x20, 0x88, 0x41, 0x8a, 0x39].repeat(); + vector_codec_known_answer_test::>(decoded, encoded); + } +} diff --git a/ml-kem/src/kem.rs b/ml-kem/src/kem.rs new file mode 100644 index 0000000..2f373a0 --- /dev/null +++ b/ml-kem/src/kem.rs @@ -0,0 +1,242 @@ +use core::marker::PhantomData; +use crypto_common::rand_core::CryptoRngCore; +use typenum::U32; + +use crate::crypto::{rand, G, H, J}; +use crate::param::{DecapsulationKeySize, EncapsulationKeySize, EncodedCiphertext, KemParams}; +use crate::pke::{DecryptionKey, EncryptionKey}; +use crate::util::{FastClone, B32}; +use crate::{Encoded, EncodedSizeUser}; + +/// A shared key resulting from an ML-KEM transaction +pub(crate) type SharedKey = B32; + +/// A `DecapsulationKey` provides the ability to generate a new key pair, and decapsulate an +/// encapsulated shared key. +#[derive(Clone, Debug, PartialEq)] +pub struct DecapsulationKey

+where + P: KemParams, +{ + dk_pke: DecryptionKey

, + ek: EncapsulationKey

, + z: B32, +} + +impl

EncodedSizeUser for DecapsulationKey

+where + P: KemParams, +{ + type EncodedSize = DecapsulationKeySize

; + + fn from_bytes(enc: &Encoded) -> Self { + let (dk_pke, ek_pke, h, z) = P::split_dk(enc); + let ek_pke = EncryptionKey::from_bytes(ek_pke); + + // XXX(RLB): The encoding here is redundant, since `h` can be computed from `ek_pke`. + // Should we verify that the provided `h` value is valid? + + Self { + dk_pke: DecryptionKey::from_bytes(dk_pke), + ek: EncapsulationKey { + ek_pke, + h: h.fast_clone(), + }, + z: z.fast_clone(), + } + } + + fn as_bytes(&self) -> Encoded { + let dk_pke = self.dk_pke.as_bytes(); + let ek = self.ek.as_bytes(); + P::concat_dk(dk_pke, ek, self.ek.h.fast_clone(), self.z.fast_clone()) + } +} + +impl

crate::DecapsulationKey> for DecapsulationKey

+where + P: KemParams, +{ + fn decapsulate(&self, ct: &EncodedCiphertext

) -> SharedKey { + let mp = self.dk_pke.decrypt(ct); + let (Kp, rp) = G(&[&mp, &self.ek.h]); + let Kbar = J(&[self.z.as_slice(), ct.as_ref()]); + let cp = self.ek.ek_pke.encrypt(&mp, &rp); + + // TODO(RLB) Replace with constant-time select + if cp == *ct { + Kp + } else { + Kbar + } + } +} + +impl

DecapsulationKey

+where + P: KemParams, +{ + pub(crate) fn generate(rng: &mut impl CryptoRngCore) -> Self { + let d: B32 = rand(rng); + let z: B32 = rand(rng); + Self::generate_deterministic(&d, &z) + } + + pub(crate) fn encapsulation_key(&self) -> &EncapsulationKey

{ + &self.ek + } + + #[must_use] + pub(crate) fn generate_deterministic(d: &B32, z: &B32) -> Self { + let (dk_pke, ek_pke) = DecryptionKey::generate(d); + let ek = EncapsulationKey::new(ek_pke); + let z = z.fast_clone(); + Self { dk_pke, ek, z } + } +} + +/// An `EncapsulationKey` provides the ability to encapsulate a shared key so that it can only be +/// decapsulated by the holder of the corresponding decapsulation key. +#[derive(Clone, Debug, PartialEq)] +pub struct EncapsulationKey

+where + P: KemParams, +{ + ek_pke: EncryptionKey

, + h: B32, +} + +impl

EncapsulationKey

+where + P: KemParams, +{ + fn new(ek_pke: EncryptionKey

) -> Self { + let h = H(ek_pke.as_bytes()); + Self { ek_pke, h } + } + + fn encapsulate_deterministic_inner(&self, m: &B32) -> (SharedKey, EncodedCiphertext

) { + let (K, r) = G(&[m, &self.h]); + let c = self.ek_pke.encrypt(m, &r); + (K, c) + } +} + +impl

EncodedSizeUser for EncapsulationKey

+where + P: KemParams, +{ + type EncodedSize = EncapsulationKeySize

; + + fn from_bytes(enc: &Encoded) -> Self { + Self::new(EncryptionKey::from_bytes(enc)) + } + + fn as_bytes(&self) -> Encoded { + self.ek_pke.as_bytes() + } +} + +impl

crate::EncapsulationKey> for EncapsulationKey

+where + P: KemParams, +{ + fn encapsulate(&self, rng: &mut impl CryptoRngCore) -> (SharedKey, EncodedCiphertext

) { + let m: B32 = rand(rng); + self.encapsulate_deterministic_inner(&m) + } + + #[cfg(feature = "deterministic")] + fn encapsulate_deterministic(&self, m: &B32) -> (SharedKey, EncodedCiphertext

) { + self.encapsulate_deterministic_inner(m) + } +} + +/// An implementation of overall ML-KEM functionality. Generic over parameter sets, but then ties +/// together all of the other related types and sizes. +pub struct Kem

+where + P: KemParams, +{ + _phantom: PhantomData

, +} + +impl

crate::KemCore for Kem

+where + P: KemParams, +{ + type SharedKeySize = U32; + type CiphertextSize = P::CiphertextSize; + type DecapsulationKey = DecapsulationKey

; + type EncapsulationKey = EncapsulationKey

; + + /// Generate a new (decapsulation, encapsulation) key pair + fn generate(rng: &mut impl CryptoRngCore) -> (Self::DecapsulationKey, Self::EncapsulationKey) { + let dk = Self::DecapsulationKey::generate(rng); + let ek = dk.encapsulation_key().clone(); + (dk, ek) + } + + #[cfg(feature = "deterministic")] + fn generate_deterministic( + d: &B32, + z: &B32, + ) -> (Self::DecapsulationKey, Self::EncapsulationKey) { + let dk = Self::DecapsulationKey::generate_deterministic(d, z); + let ek = dk.encapsulation_key().clone(); + (dk, ek) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::DecapsulationKey as _; + use crate::EncapsulationKey as _; + use crate::{MlKem1024Params, MlKem512Params, MlKem768Params}; + + fn round_trip_test

() + where + P: KemParams, + { + let mut rng = rand::thread_rng(); + + let dk = DecapsulationKey::

::generate(&mut rng); + let ek = dk.encapsulation_key(); + + let (k_send, ct) = ek.encapsulate(&mut rng); + let k_recv = dk.decapsulate(&ct); + assert_eq!(k_send, k_recv); + } + + #[test] + fn round_trip() { + round_trip_test::(); + round_trip_test::(); + round_trip_test::(); + } + + fn codec_test

() + where + P: KemParams, + { + let mut rng = rand::thread_rng(); + let dk_original = DecapsulationKey::

::generate(&mut rng); + let ek_original = dk_original.encapsulation_key().clone(); + + let dk_encoded = dk_original.as_bytes(); + let dk_decoded = DecapsulationKey::from_bytes(&dk_encoded); + assert_eq!(dk_original, dk_decoded); + + let ek_encoded = ek_original.as_bytes(); + let ek_decoded = EncapsulationKey::from_bytes(&ek_encoded); + assert_eq!(ek_original, ek_decoded); + } + + #[test] + fn codec() { + codec_test::(); + codec_test::(); + codec_test::(); + } +} diff --git a/ml-kem/src/lib.rs b/ml-kem/src/lib.rs new file mode 100644 index 0000000..5805bc1 --- /dev/null +++ b/ml-kem/src/lib.rs @@ -0,0 +1,204 @@ +//! This crate implements the Module-Latice-based Key Encapsulation Method (ML-KEM) algorithm +//! being standardized by NIST in FIPS 203. ML-KEM is a KEM in the sense that it creates an +//! (decapsulation key, encapsulation key) pair, such that anyone can use the encapsulation key to +//! establish a shared key with the holder of the decapsulation key. ML-KEM is the first KEM +//! algorithm standardized by NIST that is designed to be resistant to attacks using quantum +//! computers. +//! +//! ``` +//! # use ml_kem::*; +//! let mut rng = rand::thread_rng(); +//! +//! // Generate a (decapsulation key, encapsulation key) pair +//! let (dk, ek) = MlKem768::generate(&mut rng); +//! +//! // Encapsulate a shared key to the holder of the decapsulation key, receive the shared +//! // secret `k_send` and the encapsulated form `ct`. +//! let (k_send, ct) = ek.encapsulate(&mut rng); +//! +//! // Decapsulate the shared key and verify that it was faithfully received. +//! let k_recv = dk.decapsulate(&ct); +//! assert_eq!(k_send, k_recv); +//! ``` +//! +//! [RFC 9180]: https://www.rfc-editor.org/info/rfc9180 + +#![no_std] +#![warn(clippy::pedantic)] // Be pedantic by default +#![allow(non_snake_case)] // Allow notation matching the spec +#![allow(clippy::similar_names)] // Allow dk_pke/ek_pke +#![allow(clippy::clone_on_copy)] // Be explicit about moving data +#![deny(missing_docs)] // Require all public interfaces to be documented + +/// The inevitable utility module +mod util; + +/// Section 2.4. Interpreting the Pseudocode +/// Section 4.2.2. Sampling algorithms +/// Section 4.3. The Number-Theoretic Transform +mod algebra; + +/// Section 4.1. Crytographic Functions +mod crypto; + +/// Section 4.2.1. Conversion and Compression Algorithms, Compression and decompression +mod compress; + +/// Section 4.2.1. Conversion and Compression Algorithms, Encoding and decoding +mod encode; + +/// Section 5. The K-PKE Component Scheme +mod pke; + +/// Section 6. The ML-KEM Key-Encapsulation Mechanism +pub mod kem; + +/// Section 7. Parameter Sets +mod param; + +use core::fmt::Debug; +use crypto_common::rand_core::CryptoRngCore; +use generic_array::{ArrayLength, GenericArray}; +use typenum::{U10, U11, U2, U3, U4, U5}; + +#[cfg(feature = "deterministic")] +pub use util::B32; + +pub use param::ParameterSet; + +/// An object that knows what size it is +pub trait EncodedSizeUser { + /// The size of an encoded object + type EncodedSize: ArrayLength; + + /// Parse an object from its encoded form + fn from_bytes(enc: &Encoded) -> Self; + + /// Serialize an object to its encoded form + fn as_bytes(&self) -> Encoded; +} + +/// A byte array encoding a value the indicated size +pub type Encoded = GenericArray::EncodedSize>; + +/// A key that can be used to decapsulated an encapsulated shared key +pub trait DecapsulationKey: EncodedSizeUser { + /// Decapsulate the ciphertext to obtain the encapsulated shared key + fn decapsulate(&self, ciphertext: &Ciphertext) -> SharedKey; +} + +/// A key that can be used to encapsulate a shared key such that it can only be decapsulated +/// by the holder of the corresponding decapsulation key +pub trait EncapsulationKey: EncodedSizeUser { + /// Encapsulate a fresh secret to the holder of the corresponding decapsulation key + fn encapsulate(&self, rng: &mut impl CryptoRngCore) -> (SharedKey, Ciphertext); + + /// Encapsulate a specific shared key to the holder of the corresponding decapsulation key + #[cfg(feature = "deterministic")] + fn encapsulate_deterministic(&self, m: &B32) -> (SharedKey, Ciphertext); +} + +/// A generic interface to a Key Encapsulation Method +pub trait KemCore { + /// The size of a shared key generated by this KEM + type SharedKeySize: ArrayLength; + + /// The size of a ciphertext encapsulating a shared key + type CiphertextSize: ArrayLength; + + /// A decapsulation key for this KEM + type DecapsulationKey: DecapsulationKey, Ciphertext> + Debug + PartialEq; + + /// An encapsulation key for this KEM + type EncapsulationKey: EncapsulationKey, Ciphertext> + Debug + PartialEq; + + /// Generate a new (decapsulation, encapsulation) key pair + fn generate(rng: &mut impl CryptoRngCore) -> (Self::DecapsulationKey, Self::EncapsulationKey); + + /// Generate a new (decapsulation, encapsulation) key pair deterministically + #[cfg(feature = "deterministic")] + fn generate_deterministic(d: &B32, z: &B32) + -> (Self::DecapsulationKey, Self::EncapsulationKey); +} + +/// `MlKem512` is the parameter set for security category 1, corresponding to key search on a block +/// cipher with a 128-bit key. +#[derive(Default, Clone, Debug, PartialEq)] +pub struct MlKem512Params; + +impl ParameterSet for MlKem512Params { + type K = U2; + type Eta1 = U3; + type Eta2 = U2; + type Du = U10; + type Dv = U4; +} + +/// `MlKem768` is the parameter set for security category 3, corresponding to key search on a block +/// cipher with a 192-bit key. +#[derive(Default, Clone, Debug, PartialEq)] +pub struct MlKem768Params; + +impl ParameterSet for MlKem768Params { + type K = U3; + type Eta1 = U2; + type Eta2 = U2; + type Du = U10; + type Dv = U4; +} + +/// `MlKem1024` is the parameter set for security category 5, corresponding to key search on a block +/// cipher with a 256-bit key. +#[derive(Default, Clone, Debug, PartialEq)] +pub struct MlKem1024Params; + +impl ParameterSet for MlKem1024Params { + type K = U4; + type Eta1 = U2; + type Eta2 = U2; + type Du = U11; + type Dv = U5; +} + +/// A shared key produced by the KEM `K` +pub type SharedKey = GenericArray::SharedKeySize>; + +/// A ciphertext produced by the KEM `K` +pub type Ciphertext = GenericArray::CiphertextSize>; + +/// ML-KEM with the parameter set for security category 1, corresponding to key search on a block +/// cipher with a 128-bit key. +pub type MlKem512 = kem::Kem; + +/// ML-KEM with the parameter set for security category 3, corresponding to key search on a block +/// cipher with a 192-bit key. +pub type MlKem768 = kem::Kem; + +/// ML-KEM with the parameter set for security category 5, corresponding to key search on a block +/// cipher with a 256-bit key. +pub type MlKem1024 = kem::Kem; + +#[cfg(test)] +mod test { + use super::*; + + fn round_trip_test() + where + K: KemCore, + { + let mut rng = rand::thread_rng(); + + let (dk, ek) = K::generate(&mut rng); + + let (k_send, ct) = ek.encapsulate(&mut rng); + let k_recv = dk.decapsulate(&ct); + assert_eq!(k_send, k_recv); + } + + #[test] + fn round_trip() { + round_trip_test::(); + round_trip_test::(); + round_trip_test::(); + } +} diff --git a/ml-kem/src/param.rs b/ml-kem/src/param.rs new file mode 100644 index 0000000..81841fc --- /dev/null +++ b/ml-kem/src/param.rs @@ -0,0 +1,266 @@ +//! This module encapsulates all of the compile-time logic related to parameter-set dependent sizes +//! of objects. `ParameterSet` captures the parameters in the form described by the ML-KEM +//! specification. `EncodingSize`, `VectorEncodingSize`, and `CbdSamplingSize` are "upstream" of +//! `ParameterSet`; they provide basic logic about the size of encoded objects. `PkeParams` and +//! `KemParams` are "downstream" of `ParameterSet`; they define derived parameters relevant to +//! K-PKE and ML-KEM. +//! +//! While the primary purpose of these traits is to describe the sizes of objects, in order to +//! avoid leakage of complicated trait bounds, they also need to provide any logic that needs to +//! know any details about object sizes. For example, `VectorEncodingSize::flatten` needs to know +//! that the size of an encoded vector is `K` times the size of an encoded polynomial. + +use core::fmt::Debug; +use core::ops::{Add, Div, Mul, Rem, Sub}; +use generic_array::{ + sequence::{Concat, Split}, + GenericArray, +}; +use typenum::{ + consts::{U0, U12, U2, U32, U384, U8}, + operator_aliases::{Gcf, Prod, Quot, Sum}, + type_operators::Gcd, +}; + +use crate::algebra::NttVector; +use crate::encode::Encode; +use crate::util::{Flatten, Unflatten, B32}; + +/// An array length with other useful properties +pub trait ArrayLength: generic_array::ArrayLength + PartialEq + Debug {} + +impl ArrayLength for T where T: generic_array::ArrayLength + PartialEq + Debug {} + +/// An integer that can be used as a length for encoded values. +pub trait EncodingSize: ArrayLength { + type EncodedPolynomialSize: ArrayLength; + type ValueStep: ArrayLength; + type ByteStep: ArrayLength; +} + +type EncodingUnit = Quot, Gcf>; + +pub type EncodedPolynomialSize = ::EncodedPolynomialSize; +pub type EncodedPolynomial = GenericArray>; + +impl EncodingSize for D +where + D: ArrayLength + Mul + Gcd + Mul, + Prod: ArrayLength, + Prod: Div>, + EncodingUnit: Div + Div, + Quot, D>: ArrayLength, + Quot, U8>: ArrayLength, +{ + type EncodedPolynomialSize = Prod; + type ValueStep = Quot, D>; + type ByteStep = Quot, U8>; +} + +/// An integer that can describe encoded vectors. +pub trait VectorEncodingSize: EncodingSize +where + K: ArrayLength, +{ + type EncodedPolynomialVectorSize: ArrayLength; + + fn flatten(polys: GenericArray, K>) + -> EncodedPolynomialVector; + fn unflatten( + vec: &EncodedPolynomialVector, + ) -> GenericArray<&EncodedPolynomial, K>; +} + +pub type EncodedPolynomialVectorSize = + >::EncodedPolynomialVectorSize; +pub type EncodedPolynomialVector = GenericArray>; + +impl VectorEncodingSize for D +where + D: EncodingSize, + K: ArrayLength, + D::EncodedPolynomialSize: Mul, + Prod: + ArrayLength + Div + Rem, +{ + type EncodedPolynomialVectorSize = Prod; + + fn flatten( + polys: GenericArray, K>, + ) -> EncodedPolynomialVector { + polys.flatten() + } + + fn unflatten( + vec: &EncodedPolynomialVector, + ) -> GenericArray<&EncodedPolynomial, K> { + vec.unflatten() + } +} + +/// An integer that describes a bit length to be used in CBD sampling +pub trait CbdSamplingSize: ArrayLength { + type SampleSize: EncodingSize; +} + +impl CbdSamplingSize for Eta +where + Eta: ArrayLength, + U2: Mul, + Prod: EncodingSize, +{ + type SampleSize = Prod; +} + +/// A `ParameterSet` captures the parameters that describe a particular instance of ML-KEM. There +/// are three variants, corresponding to three different security levels. +pub trait ParameterSet: Default + Clone + Debug + PartialEq { + /// The dimensionality of vectors and arrays + type K: ArrayLength; + + /// The bit width of the centered binary distribution used when sampling random polynomials in + /// key generation and encryption. + type Eta1: CbdSamplingSize; + + /// The bit width of the centered binary distribution used when sampling error vectors during + /// encryption. + type Eta2: CbdSamplingSize; + + /// The bit width of encoded integers in the `u` vector in a ciphertext + type Du: VectorEncodingSize; + + /// The bit width of encoded integers in the `v` polynomial in a ciphertext + type Dv: EncodingSize; +} + +type EncodedUSize

= EncodedPolynomialVectorSize<

::Du,

::K>; +type EncodedVSize

= EncodedPolynomialSize<

::Dv>; + +type EncodedU

= GenericArray>; +type EncodedV

= GenericArray>; + +/// Derived parameter relevant to K-PKE +pub trait PkeParams: ParameterSet { + type NttVectorSize: ArrayLength; + type EncryptionKeySize: ArrayLength; + type CiphertextSize: ArrayLength; + + fn encode_u12(p: &NttVector) -> EncodedNttVector; + fn decode_u12(v: &EncodedNttVector) -> NttVector; + + fn concat_ct(u: EncodedU, v: EncodedV) -> EncodedCiphertext; + fn split_ct(ct: &EncodedCiphertext) -> (&EncodedU, &EncodedV); + + fn concat_ek(t_hat: EncodedNttVector, rho: B32) -> EncodedEncryptionKey; + fn split_ek(ek: &EncodedEncryptionKey) -> (&EncodedNttVector, &B32); +} + +pub type EncodedNttVector

= GenericArray::NttVectorSize>; +pub type EncodedDecryptionKey

= GenericArray::NttVectorSize>; +pub type EncodedEncryptionKey

= GenericArray::EncryptionKeySize>; +pub type EncodedCiphertext

= GenericArray::CiphertextSize>; + +impl

PkeParams for P +where + P: ParameterSet, + U384: Mul, + Prod: ArrayLength + Add + Div + Rem, + EncodedUSize

: Add>, + Sum, EncodedVSize

>: + ArrayLength + Sub, Output = EncodedVSize

>, + EncodedPolynomialVectorSize: Add, + Sum, U32>: + ArrayLength + Sub, Output = U32>, +{ + type NttVectorSize = EncodedPolynomialVectorSize; + type EncryptionKeySize = Sum; + type CiphertextSize = Sum, EncodedVSize

>; + + fn encode_u12(p: &NttVector) -> EncodedNttVector { + Encode::::encode(p) + } + + fn decode_u12(v: &EncodedNttVector) -> NttVector { + Encode::::decode(v) + } + + fn concat_ct(u: EncodedU, v: EncodedV) -> EncodedCiphertext { + u.concat(v) + } + + fn split_ct(ct: &EncodedCiphertext) -> (&EncodedU, &EncodedV) { + ct.split() + } + + fn concat_ek(t_hat: EncodedNttVector, rho: B32) -> EncodedEncryptionKey { + t_hat.concat(rho) + } + + fn split_ek(ek: &EncodedEncryptionKey) -> (&EncodedNttVector, &B32) { + ek.split() + } +} + +/// Derived parameters relevant to ML-KEM +pub trait KemParams: PkeParams { + type DecapsulationKeySize: ArrayLength; + + fn concat_dk( + dk: EncodedDecryptionKey, + ek: EncodedEncryptionKey, + h: B32, + z: B32, + ) -> EncodedDecapsulationKey; + + fn split_dk( + enc: &EncodedDecapsulationKey, + ) -> ( + &EncodedDecryptionKey, + &EncodedEncryptionKey, + &B32, + &B32, + ); +} + +pub type DecapsulationKeySize

=

::DecapsulationKeySize; +pub type EncapsulationKeySize

=

::EncryptionKeySize; + +pub type EncodedDecapsulationKey

= GenericArray::DecapsulationKeySize>; + +impl

KemParams for P +where + P: PkeParams, + P::NttVectorSize: Add, + Sum: + ArrayLength + Add + Sub, + Sum, U32>: + ArrayLength + Add + Sub, Output = U32>, + Sum, U32>, U32>: + ArrayLength + Sub, U32>, Output = U32>, +{ + type DecapsulationKeySize = Sum, U32>, U32>; + + fn concat_dk( + dk: EncodedDecryptionKey, + ek: EncodedEncryptionKey, + h: B32, + z: B32, + ) -> EncodedDecapsulationKey { + dk.concat(ek).concat(h).concat(z) + } + + fn split_dk( + enc: &EncodedDecapsulationKey, + ) -> ( + &EncodedDecryptionKey, + &EncodedEncryptionKey, + &B32, + &B32, + ) { + // We parse from right to left to make it easier to write the trait bounds above + let (enc, z) = enc.split(); + let (enc, h) = enc.split(); + let (dk_pke, ek_pke) = enc.split(); + (dk_pke, ek_pke, h, z) + } +} diff --git a/ml-kem/src/pke.rs b/ml-kem/src/pke.rs new file mode 100644 index 0000000..7faae75 --- /dev/null +++ b/ml-kem/src/pke.rs @@ -0,0 +1,185 @@ +use typenum::{consts::U1, Unsigned}; + +use crate::algebra::{NttMatrix, NttVector, Polynomial, PolynomialVector}; +use crate::compress::Compress; +use crate::crypto::{G, PRF}; +use crate::encode::Encode; +use crate::param::{EncodedCiphertext, EncodedDecryptionKey, EncodedEncryptionKey, PkeParams}; +use crate::util::{FastClone, B32}; + +/// A `DecryptionKey` provides the ability to generate a new key pair, and decrypt an +/// encrypted value. +#[derive(Clone, Default, Debug, PartialEq)] +pub struct DecryptionKey

+where + P: PkeParams, +{ + s_hat: NttVector, +} + +impl

DecryptionKey

+where + P: PkeParams, +{ + /// Generate a new random decryption key according to the `K-PKE.KeyGen` procedure. + // Algorithm 12. K-PKE.KeyGen() + pub fn generate(d: &B32) -> (Self, EncryptionKey

) { + // Generate random seeds + let (rho, sigma) = G(&[d]); + + // Sample pseudo-random matrix and vectors + let A_hat: NttMatrix = NttMatrix::sample_uniform(&rho, false); + let s: PolynomialVector = PolynomialVector::sample_cbd::(&sigma, 0); + let e: PolynomialVector = PolynomialVector::sample_cbd::(&sigma, P::K::U8); + + // NTT the vectors + let s_hat = s.ntt(); + let e_hat = e.ntt(); + + // Compute the public value + let t_hat = &(&A_hat * &s_hat) + &e_hat; + + // Assemble the keys + let dk = DecryptionKey { s_hat }; + let ek = EncryptionKey { t_hat, rho }; + (dk, ek) + } + + /// Decrypt ciphertext to obtain the encrypted value, according to the K-PKE.Decrypt procedure. + // Algorithm 14. kK-PKE.Decrypt(dk_PKE, c) + pub fn decrypt(&self, ciphertext: &EncodedCiphertext

) -> B32 { + let (c1, c2) = P::split_ct(ciphertext); + + let mut u: PolynomialVector = Encode::::decode(c1); + u.decompress::(); + + let mut v: Polynomial = Encode::::decode(c2); + v.decompress::(); + + let u_hat = u.ntt(); + let sTu = (&self.s_hat * &u_hat).ntt_inverse(); + let mut w = &v - &sTu; + Encode::::encode(w.compress::()) + } + + /// Represent this decryption key as a byte array `(s_hat)` + pub fn as_bytes(&self) -> EncodedDecryptionKey

{ + P::encode_u12(&self.s_hat) + } + + /// Parse an decryption key from a byte array `(s_hat)` + pub fn from_bytes(enc: &EncodedDecryptionKey

) -> Self { + let s_hat = P::decode_u12(enc); + Self { s_hat } + } +} + +/// An `EncryptionKey` provides the ability to encrypt a value so that it can only be +/// decrypted by the holder of the corresponding decapsulation key. +#[derive(Clone, Default, Debug, PartialEq)] +pub struct EncryptionKey

+where + P: PkeParams, +{ + t_hat: NttVector, + rho: B32, +} + +impl

EncryptionKey

+where + P: PkeParams, +{ + /// Encrypt the specified message for the holder of the corresponding decryption key, using the + /// provided randomness, according the `K-PKE.Encrypt` procedure. + pub fn encrypt(&self, message: &B32, randomness: &B32) -> EncodedCiphertext

{ + let r = PolynomialVector::::sample_cbd::(randomness, 0); + let e1 = PolynomialVector::::sample_cbd::(randomness, P::K::U8); + + let prf_output = PRF::(randomness, 2 * P::K::U8); + let e2: Polynomial = Polynomial::sample_cbd::(&prf_output); + + let A_hat_t = NttMatrix::::sample_uniform(&self.rho, true); + let r_hat: NttVector = r.ntt(); + let ATr: PolynomialVector = (&A_hat_t * &r_hat).ntt_inverse(); + let mut u = ATr + e1; + + let mut mu: Polynomial = Encode::::decode(message); + mu.decompress::(); + + let tTr: Polynomial = (&self.t_hat * &r_hat).ntt_inverse(); + let mut v = &(&tTr + &e2) + μ + + let c1 = Encode::::encode(u.compress::()); + let c2 = Encode::::encode(v.compress::()); + P::concat_ct(c1, c2) + } + + /// Represent this encryption key as a byte array `(t_hat || rho)` + pub fn as_bytes(&self) -> EncodedEncryptionKey

{ + let t_hat = P::encode_u12(&self.t_hat); + P::concat_ek(t_hat, self.rho.fast_clone()) + } + + /// Parse an encryption key from a byte array `(t_hat || rho)` + pub fn from_bytes(enc: &EncodedEncryptionKey

) -> Self { + let (t_hat, rho) = P::split_ek(enc); + let t_hat = P::decode_u12(t_hat); + Self { + t_hat, + rho: rho.fast_clone(), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::crypto::rand; + use crate::{MlKem1024Params, MlKem512Params, MlKem768Params}; + + fn round_trip_test

() + where + P: PkeParams, + { + let mut rng = rand::thread_rng(); + let d: B32 = rand(&mut rng); + let original = B32::default(); + let randomness = B32::default(); + + let (dk, ek) = DecryptionKey::

::generate(&d); + let encrypted = ek.encrypt(&original, &randomness); + let decrypted = dk.decrypt(&encrypted); + assert_eq!(original, decrypted); + } + + #[test] + fn round_trip() { + round_trip_test::(); + round_trip_test::(); + round_trip_test::(); + } + + fn codec_test

() + where + P: PkeParams, + { + let mut rng = rand::thread_rng(); + let d: B32 = rand(&mut rng); + let (dk_original, ek_original) = DecryptionKey::

::generate(&d); + + let dk_encoded = dk_original.as_bytes(); + let dk_decoded = DecryptionKey::from_bytes(&dk_encoded); + assert_eq!(dk_original, dk_decoded); + + let ek_encoded = ek_original.as_bytes(); + let ek_decoded = EncryptionKey::from_bytes(&ek_encoded); + assert_eq!(ek_original, ek_decoded); + } + + #[test] + fn codec() { + codec_test::(); + codec_test::(); + codec_test::(); + } +} diff --git a/ml-kem/src/util.rs b/ml-kem/src/util.rs new file mode 100644 index 0000000..a7dc2a6 --- /dev/null +++ b/ml-kem/src/util.rs @@ -0,0 +1,223 @@ +use core::mem::ManuallyDrop; +use core::ops::{Div, Mul, Rem}; +use core::ptr; +use generic_array::{sequence::GenericSequence, ArrayLength, GenericArray}; +use typenum::{ + operator_aliases::{Prod, Quot}, + Unsigned, U0, U32, +}; + +/// A 32-byte array, defined here for brevity because it is used several times +pub type B32 = GenericArray; + +/// Benchmarking shows that `GenericArray::clone` does not optimize as well as this alternative +/// implementation. (Obviously, we can't re-implement Clone, so we have a new name.) +pub trait FastClone { + fn fast_clone(&self) -> Self; +} + +impl FastClone for GenericArray +where + T: Copy + Default, + N: ArrayLength, +{ + fn fast_clone(&self) -> Self { + self.map(Clone::clone) + } +} + +/// Benchmarking shows that the `FunctionalSequence` versions of `zip`, `fold`, and `map` do not +/// optimize as well as these alternative implementations. +pub trait FunctionalArray +where + N: ArrayLength, +{ + fn map(&self, f: F) -> GenericArray + where + U: Default, + F: Fn(&T) -> U; + + fn zip(&self, b: &Self, f: F) -> GenericArray + where + U: Default, + F: Fn(&T, &T) -> U; + + fn fold(&self, f: F) -> T + where + T: Clone, + F: Fn(&T, &T) -> T; +} + +impl FunctionalArray for GenericArray +where + N: ArrayLength, +{ + fn map(&self, f: F) -> GenericArray + where + U: Default, + F: Fn(&T) -> U, + { + GenericArray::generate(|i| f(&self[i])) + } + + fn zip(&self, other: &Self, f: F) -> GenericArray + where + U: Default, + F: Fn(&T, &T) -> U, + { + GenericArray::generate(|i| f(&self[i], &other[i])) + } + + fn fold(&self, f: F) -> T + where + T: Clone, + F: Fn(&T, &T) -> T, + { + let mut out = self[0].clone(); + for i in 1..N::USIZE { + out = f(&out, &self[i]); + } + out + } +} + +/// Safely truncate an unsigned integer value to shorter representation +pub trait Truncate { + fn truncate(self) -> T; +} + +macro_rules! define_truncate { + ($from:ident, $to:ident) => { + impl Truncate<$to> for $from { + fn truncate(self) -> $to { + // This line is marked unsafe because the `unwrap_unchecked` call is UB when its + // `self` argument is `Err`. It never will be, because we explicitly zeroize the + // high-order bits before converting. We could have used `unwrap()`, but chose to + // avoid the possibility of panic. + unsafe { (self & $from::from($to::MAX)).try_into().unwrap_unchecked() } + } + } + }; +} + +define_truncate!(u32, u16); +define_truncate!(u64, u32); +define_truncate!(usize, u8); +define_truncate!(u128, u16); +define_truncate!(u128, u8); + +/// Defines a sequence of sequences that can be merged into a bigger overall seequence +pub trait Flatten { + type OutputSize: ArrayLength; + + fn flatten(self) -> GenericArray; +} + +impl Flatten> for GenericArray, N> +where + N: ArrayLength, + M: ArrayLength + Mul, + Prod: ArrayLength, +{ + type OutputSize = Prod; + + // This is the reverse transmute between [T; K*N] and [[T; K], M], which is guaranteed to be + // safe by the Rust memory layout of these types. + fn flatten(self) -> GenericArray { + let whole = ManuallyDrop::new(self); + unsafe { ptr::read(whole.as_ptr().cast()) } + } +} + +/// Defines a sequence that can be split into a sequence of smaller sequences of uniform size +pub trait Unflatten +where + M: ArrayLength, +{ + type Part; + + fn unflatten(self) -> GenericArray; +} + +impl Unflatten for GenericArray +where + T: Default, + N: ArrayLength + Div + Rem, + M: ArrayLength, + Quot: ArrayLength, +{ + type Part = GenericArray>; + + // This requires some unsafeness, but it is the same as what is done in GenericArray::split. + // Basically, this is doing transmute between [T; K*N] and [[T; K], M], which is guaranteed to + // be safe by the Rust memory layout of these types. + fn unflatten(self) -> GenericArray { + let part_size = Quot::::USIZE; + let whole = ManuallyDrop::new(self); + GenericArray::generate(|i| unsafe { ptr::read(whole.as_ptr().add(i * part_size).cast()) }) + } +} + +impl<'a, T, N, M> Unflatten for &'a GenericArray +where + T: Default, + N: ArrayLength + Div + Rem, + M: ArrayLength, + Quot: ArrayLength, +{ + type Part = &'a GenericArray>; + + // This requires some unsafeness, but it is the same as what is done in GenericArray::split. + // Basically, this is doing transmute between [T; K*N] and [[T; K], M], which is guaranteed to + // be safe by the Rust memory layout of these types. + fn unflatten(self) -> GenericArray { + let part_size = Quot::::USIZE; + let mut ptr: *const T = self.as_ptr(); + GenericArray::generate(|_i| unsafe { + let part = &*(ptr.cast()); + ptr = ptr.add(part_size); + part + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use generic_array::arr; + use typenum::consts::*; + + #[test] + fn flatten() { + let flat: GenericArray = arr![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let unflat2: GenericArray, _> = + arr![arr![1, 2], arr![3, 4], arr![5, 6], arr![7, 8], arr![9, 10]]; + let unflat5: GenericArray, _> = + arr![arr![1, 2, 3, 4, 5], arr![6, 7, 8, 9, 10]]; + + // Flatten + let actual = unflat2.flatten(); + assert_eq!(flat, actual); + + let actual = unflat5.flatten(); + assert_eq!(flat, actual); + + // Unflatten + let actual: GenericArray, U5> = flat.unflatten(); + assert_eq!(unflat2, actual); + + let actual: GenericArray, U2> = flat.unflatten(); + assert_eq!(unflat5, actual); + + // Unflatten on references + let actual: GenericArray<&GenericArray, U5> = (&flat).unflatten(); + for (i, part) in actual.iter().enumerate() { + assert_eq!(&unflat2[i], *part); + } + + let actual: GenericArray<&GenericArray, U2> = (&flat).unflatten(); + for (i, part) in actual.iter().enumerate() { + assert_eq!(&unflat5[i], *part); + } + } +} diff --git a/ml-kem/tests/nist.rs b/ml-kem/tests/nist.rs new file mode 100644 index 0000000..f4c45b5 --- /dev/null +++ b/ml-kem/tests/nist.rs @@ -0,0 +1,992 @@ +// TODO(RLB) Disable compilation if not deterministic + +mod vectors; + +use hex_literal::hex; +use ml_kem::*; +use vectors::*; + +pub struct Vector { + pub generate: &'static GenerateVector, + pub encapsulate: &'static EncapsulateVector, + pub decapsulate: &'static DecapsulateVector, +} + +impl Vector { + fn verify(&self) { + self.generate.verify::(); + self.encapsulate.verify::(); + self.decapsulate.verify::(); + } +} + +pub struct Vectors { + pub mlkem512: Vector, + pub mlkem768: Vector, + pub mlkem1024: Vector, +} + +impl Vectors { + pub fn verify(&self) { + self.mlkem512.verify::(); + self.mlkem768.verify::(); + self.mlkem1024.verify::(); + } +} + +#[test] +fn nist() { + let vectors = Vectors { + mlkem512: Vector { + generate: &GEN_VECTORS[0], + encapsulate: &ENC_VECTORS[0], + decapsulate: &DEC_VECTORS[0], + }, + mlkem768: Vector { + generate: &GEN_VECTORS[1], + encapsulate: &ENC_VECTORS[1], + decapsulate: &DEC_VECTORS[1], + }, + mlkem1024: Vector { + generate: &GEN_VECTORS[2], + encapsulate: &ENC_VECTORS[2], + decapsulate: &DEC_VECTORS[2], + }, + }; + + vectors.verify(); +} + +// NIST test vectors, taken from: +// +// https://csrc.nist.gov/Projects/post-quantum-cryptography/post-quantum-cryptography-standardization/example-files +// * Linked file: PQC Intermediate Values -> PQC Intermediate Values.zip +// * Values from individual level / operation files in that ZIP file +const GEN_VECTORS: [GenerateVector; 3] = [ + GenerateVector { + z: hex!("CD119AFDC8559442424A87C13EA101E29FCA11881869077E4092E751BEDCA8BC"), + d: hex!("CD119AFDC8559442424A87C13EA101E29FCA11881869077E4092E751BEDCA8BC"), + dk: &hex!("37EC477E217BFB40384C850E51C1837158BDBC23A31832BC25C91B3121444AD4" + "533733BAFF07CA817B64B2CA4299AA26454CBAFB35B6ABE1185CB47C4CD61AF9" + "8383C4814B20AB8754FC514F23074114C3E5A810A453B855AA7F1310C74B0B01" + "E5AAB2E871738FAC2786C7A05D6B3B32A050D0FB223956C95CA0C2C1D54154A7" + "7BD33737A49A0065D1424A2ABAFD52AA934C9804939208F05CCF8B8B8086316E" + "0943A08710500C918A2B218D37B85AE28022CB0134FB49F5C45D98D3C04B755A" + "60880422668E2B301B18D5194DE991B265BF94697E6A4B8150C8B85203391563" + "5E30665BDA2191DAA505D43344FD29C9FCC1C507691D475B617C948FCC84B1B0" + "8A1C638C3E13580CE359789A9860E5469CC754B08EE33F0921BDEF15A906969F" + "2DC57A25E80CE4C45F11E04A519AB08B9B927C3A13A081CFFA110FACCC5E8DC2" + "9495978B5553104D473A175918AD5B5487BBA69712AE93F615C60A8D387BCE3F" + "651E56880A522B2DB86351CAB65D13B4693DB0B2C80936FAD1CE67925E6BB7C1" + "10C43E83247D22608D8C1023431CB69290A4F8A9593BF1241D737C0CD16D75EB" + "50C6842CE0A21DCE494036824CE63252E9325F05B734452B129132B196084A37" + "88BBB1F20A37D2C2B3F90E0DD7A274C9B1A9F02EC7E721F4A43D409A25FBC99A" + "44D4763107C787620941761ED48C932924BA620986CF277A23471C7B13333D93" + "6C0DD49E0FF34CA3AB8234C42AEBE459C612052B9716E96B20BEC718126040A9" + "091F6BA9445F45806AEB6E3816710F7CBFED1101461284DD962B7B12047C0A0A" + "906A0589B4A9A426469BDA3946091A375B1952A91C231C0FE6B57F7CC97EFED0" + "BC1001367823BE1886308B3A21452B7E455066719CCCEAF6A726FC22BC8399F5" + "4BBFCAF7CA63BA73173C7AA8619A3F485C3E330421006766746F4EF6653E440E" + "5CDC59534018C352C023584CBB374EB7A9B7836832BE53AF272A069755CE2FF2" + "9CD8B394C52422B3470E27415F41B397535959F160003B452CF49697B7A53689" + "852BBE6CCFDFB40B48E9328DE11522D0A431B115A5C0C2F4307D9862C0DD1B40" + "C65A1D9D479777E6905A91A5CB24551C8B1E52A3C77B63313FFC8B5817815259" + "A6ADB59645DC4BB1436D51E62A096834AF43772510C4EDF34CDE0A5B57C145E6" + "87CB87162F001C21C9E1934AC11AAFA70FF810732650B32A3018A7C50CD73679" + "6222C8AB821A9283BE1CC204C3F1630D3CCCDB0A9A3D17552B9158C0664E5D6A" + "04B0FA36DE45862A46A39EC597AE42C311C4AC224A72D6F253BB5235F7A2B8B0" + "F24D1376AF588746F3BB8E0365078761CAB983A4A6A940A3D997047A8F36A731" + "E8965236C37BF200082F821DCA7716C444A90BEC53074BBA58C132BFB9A2ACE2" + "CEC9AA658EAC1232CCCA3C817A92C1195C05C0E1D6639FD2ADE531607D488B74" + "A747CFF47FCA5C8B2163CA03C545ED103278430C60B2381A09427FD130F859BF" + "5DB776DA095DCA5804FA63B0D7D87FA9415C72FB51872A989F466C984BC74C29" + "B8632019CA040C9CA35E22608DAA70357AE2C3AD83631FAA174E0ACDF5DBBF3C" + "F68A05B6543AB6268E1A51B0932C17B00A1371B2DAB241F92A43FFB456D0A8C8" + "860A8E28A61A21307CC0456DA4242905CB1D3D0BBD81BB8EE274A43C76C31001" + "9515FCC140467C33370C86808ECAA58E3BA93A2C1190461C1DFA11302001BBAB" + "4CB1E3642EF8CB26309B60523BC21887B07F898CE562A6CA778EA01505851378" + "CEA8BB7FC09D11961B6C596F93542A9904864EB10CD0A703DBA98921861A87B0" + "56525C71A843553E6400777437C95CCC8085CC0C477D665A4479019D4CD442F7" + "4A3CD8169F4262B8271B5D5A67C8C1611AAE7B3D0534C0859716FDF0BB689490" + "94C06A1B73C9AA1CBDF331543DE002A8C06F94E8810A5CB373832745D720683B" + "574875A666946D0296893F2B59E907488D8C8489D474D929A05A573ED6674903" + "71A46D4556CBB68AAA79CC3EC6653413576C228E379A14CB90B7B7591B19A7BD" + "37A1C4D37859892219442BB0B9B9BA67BA3BC0D095C8803CEBE97AFF0B1C1535" + "78A130CD8157CF745946C2F5726D9C11273575505291346528EE0BAC047CC984" + "538B97BBABFCC357DCB8A98FB857C9C52D1B786749CA61892B09759980520091" + "B9B477C70E6C46586B1CCEBE87BCF6DF03C2B27CB09FA03F63160958383BE636" + "C0ECC8DDAE8B594A14037868BEC0B22300DEFDFAA1D973AC5CEC84AE4386B8FB" + "CD119AFDC8559442424A87C13EA101E29FCA11881869077E4092E751BEDCA8BC"), + ek: &hex!("C65A1D9D479777E6905A91A5CB24551C8B1E52A3C77B63313FFC8B5817815259" + "A6ADB59645DC4BB1436D51E62A096834AF43772510C4EDF34CDE0A5B57C145E6" + "87CB87162F001C21C9E1934AC11AAFA70FF810732650B32A3018A7C50CD73679" + "6222C8AB821A9283BE1CC204C3F1630D3CCCDB0A9A3D17552B9158C0664E5D6A" + "04B0FA36DE45862A46A39EC597AE42C311C4AC224A72D6F253BB5235F7A2B8B0" + "F24D1376AF588746F3BB8E0365078761CAB983A4A6A940A3D997047A8F36A731" + "E8965236C37BF200082F821DCA7716C444A90BEC53074BBA58C132BFB9A2ACE2" + "CEC9AA658EAC1232CCCA3C817A92C1195C05C0E1D6639FD2ADE531607D488B74" + "A747CFF47FCA5C8B2163CA03C545ED103278430C60B2381A09427FD130F859BF" + "5DB776DA095DCA5804FA63B0D7D87FA9415C72FB51872A989F466C984BC74C29" + "B8632019CA040C9CA35E22608DAA70357AE2C3AD83631FAA174E0ACDF5DBBF3C" + "F68A05B6543AB6268E1A51B0932C17B00A1371B2DAB241F92A43FFB456D0A8C8" + "860A8E28A61A21307CC0456DA4242905CB1D3D0BBD81BB8EE274A43C76C31001" + "9515FCC140467C33370C86808ECAA58E3BA93A2C1190461C1DFA11302001BBAB" + "4CB1E3642EF8CB26309B60523BC21887B07F898CE562A6CA778EA01505851378" + "CEA8BB7FC09D11961B6C596F93542A9904864EB10CD0A703DBA98921861A87B0" + "56525C71A843553E6400777437C95CCC8085CC0C477D665A4479019D4CD442F7" + "4A3CD8169F4262B8271B5D5A67C8C1611AAE7B3D0534C0859716FDF0BB689490" + "94C06A1B73C9AA1CBDF331543DE002A8C06F94E8810A5CB373832745D720683B" + "574875A666946D0296893F2B59E907488D8C8489D474D929A05A573ED6674903" + "71A46D4556CBB68AAA79CC3EC6653413576C228E379A14CB90B7B7591B19A7BD" + "37A1C4D37859892219442BB0B9B9BA67BA3BC0D095C8803CEBE97AFF0B1C1535" + "78A130CD8157CF745946C2F5726D9C11273575505291346528EE0BAC047CC984" + "538B97BBABFCC357DCB8A98FB857C9C52D1B786749CA61892B09759980520091" + "B9B477C70E6C46586B1CCEBE87BCF6DF03C2B27CB09FA03F63160958383BE636"), + }, + GenerateVector { + z: hex!("92AC7D1F83BAFAE6EE86FE00F95D813375772434860F5FF7D54FFC37399BC4CC"), + d: hex!("92AC7D1F83BAFAE6EE86FE00F95D813375772434860F5FF7D54FFC37399BC4CC"), + dk: &hex!("19D74AD5472A8B2BAAD2A56702C9B3B5510EF3924858061D57F90DD9A1A01FEC" + "2F57C51A888805341B617C515539597750835C3ED7A033B039D72491332C5DF4" + "A69B6DF26171877AD1E50AC50100BE4728786685DA7A739E843FF0D45922D728" + "1E210D5E82B944652F4862CFB3D902DE60AFD0A164471B26144A1D7A38096503" + "095911762EBA7962C4511D05A128F2781ECB3D1F5BB1244237611ABAB924991F" + "8A2732E27032357920F197C7692D60A9444472258CB457C1B71B77995469F3A9" + "62F3ABA6699614FCCCEA741E21C600C4357BBFAB452927C3D441BF8ED73152F7" + "5C08F540E186ACCA3326F422C84B988D77E61AE61859CF8541F89209E4983040" + "C5617654808852B649B899A399AEC2C8BBA8A542F345ABF2813F65E9A791D32C" + "C2D76026FB8D0C94B657489ABB487DA4A2C0E3868D3CF47F1CBB2FA79C53CFF6" + "264777C09B177C91315484D2B30B0CA21F55ADD23C57E1911C3F086BCAD21798" + "486EB47B7C58577381C09F5252582D1B27A7D5B8E060CE78209CC82BAE4DA606" + "800C8DB1268F7AD2B793A44F34612CCEA31CE7D796A65A2691D61500625F83E7" + "BE57077EE9C1B8C1CAA137CC4B6573308C19668B24B01E966903ABBCB79B67BE" + "0A3E3E058AADA189B9EA80359AC26F4C5C53735FE4FC35247337760CCA3529B8" + "D266BB6C48010654CDBC5A3E9757524675ABC413130CC2701F28933EABB8392B" + "0D6D059CFC3A30326C4FCC810B37A4748C1C53928A4913E48B186697162C33FF" + "FB06DD5161C8639DB195C6CA64829B2B3A2E4C9683B66DF7FB1909904E00020D" + "BA134E02A168D76AC076BB77D4DC8496B4BBE7B4690BA29B62A91ABE72BEF323" + "A44C8903E482B60D99BA61D1BBCF9CB9673534C1D647662374EE2C7C5F0081BA" + "D149F44206717684D9746B2048633AF7A68C6865FB590358D8CF821458369B0C" + "31EB597CF5BE78EB480EA04E35FACC380372C8C0A04DE276B1A72121E596CBB2" + "5EF7536AD3804184A87BDFB5A769160BFBB0CA3C360790E5562BB78EFE0069C7" + "7483AD35CAC237C61DE78A7DB46FC917124CA17510DB7DA218890F448EF63186" + "13A1C97C928E2B7B6A54617BCCB6CDF278AE542B56AD7BB5ECD8C46A66C4FA09" + "50CE41352CB85711890458F299BF40BA6FF2C0713862268B5F08E49845B09443" + "997AB29A62073C0D9818C020167D4749231C059E6F483F976817C90C20A9C937" + "079C2D4BE30DA974A97E4BC53ED96A55169F4A23A3EA24BD8E01B8FAEB95D4E5" + "3FFFECB60802C388A40F4660540B1B1F8176C9811BB26A683CA789564A2940FC" + "EB2CE6A92A1EE45EE4C31857C9B9B8B56A79D95A46CB393A31A2737BAFEA6C81" + "066A672B34C10AA98957C91766B730036A56D940AA4EBCB758B08351E2C4FD19" + "453BF3A6292A993D67C7ECC72F42F782E9EBAA1A8B3B0F567AB39421F6A67A6B" + "8410FD94A721D365F1639E9DDABFD0A6CE1A4605BD2B1C9B977BD1EA32867368" + "D6E639D019AC101853BC153C86F85280FC763BA24FB57A296CB12D32E08AB32C" + "551D5A45A4A28F9ADC28F7A2900E25A40B5190B22AB19DFB246F42B24F97CCA9" + "B09BEAD246E1734F446677B38B7522B780727C117440C9F1A024520C141A69CD" + "D2E69A05534A7232C5F1B766E93A5EE2EA1B26E860A3441ADEA91EDB782CABC8" + "A5D011A21BC388E7F486F0B7993079AE3F1A7C85D27D0F492184D59062142B76" + "A43734A90D556A95DC483DD82104ED58CA1571C39685827951434CC1001AA4C8" + "13261E4F93028E14CD08F768A454310C3B010C83B74D04A57BB977B3D8BCF3AA" + "A78CA12B78F010D95134928A5E5D96A029B442A41888038B29C2F122B0B6B3AF" + "121AEA29A05553BDF1DB607AFB17001860AF1823BCF03DB3B441DA163A28C523" + "A5FB4669A64234A4BCD1217FF2635BD97680FF938DBCF10E9532A9A79A5B073A" + "9E8DB2123D210FAEA200B664838E80071F2BA254AAC890A46E28EC342D92812B" + "01593071657E7A3A4A75CB3D5279CE88405AC5ADACB2051E022EE0AC9BBFE32D" + "EF98667ED347ADCB3930F3CAD031391B709A4E61B8DD4B3FB741B5BD60BF3040" + "15EE7546A24B59EADCA137C7125074726B7686EC551B7BC26BBDB20FC3783534" + "E34EE1F1BC6B77AB49A6667846975778C3C536830450A3FA910259722F3F806E" + "6EB4B9346763FEF0922BC4B6EB3826AFF24EADC6CF6E477C2E055CFB7A90A55C" + "06D0B2A2F5116069E64A5B5078C0577BC8E7900EA71C341C02AD854EA5A01AF2" + "A605CB2068D52438CDDC60B03882CC024D13045F2BA6B0F446AAA59587606179" + "45371FD78C28A40677A6E72F513B9E0667A9BAF446C1BA931BA81834234792A2" + "A2B2B3701F31B7CF467C80F1981141BB457793E1307091C48B5914646A60CE1A" + "301543779D7C3342AD179796C2C440D99DF9D41B52E32625A82AA5F579A9920B" + "FFBA964FA70DB259C85E68C813817B1347BF19814DA5E9364A4645E621923D95" + "5C211A55D355C816DA04730AA324085E622B51D6109B49F673ADD00E414755C8" + "024AA0164F24556DED963D61143856CB4FF0567E3320730DBCBF12F66E2B70B2" + "0054A6DEA42614B50EF72B156F5149FC263DD7E039C55A3EE9827DF92C565D24" + "C55E0A81C6494695344D948748AFBA9F762C0EA90BB724897902000775613949" + "602C48C78A9440678C24086D326D79643BAF7036C66C7E026AAEFDA2807A60BD" + "7FC91363BB0234A590984AA011F11D40268218A1588377B3D7671B8B99789919" + "B86EE82B18EC22D4E80A1F27853D889419D460DEF7567AA4567969C43048C32B" + "8462A9C9386EB3152A6976AA783CDD1A8C57A9B6BBD837A00624B58B4BA3DBB6" + "3BB8200E7BC88881BEBDA925BCA028E291AA1C22539CD04F90090D7F74108C32" + "B8022C1591C881E76304E2408190E20F09A54FC23420E2620E9D87A3108A94FE" + "EA72D5AB7FCFB972E6561B1A7B062F1A682E020AA2562812B296547B917824CD" + "B88C582B5A6890177BC70C91ACAC9ABE290AEB2C34A7E2368955CB456A345368" + "ABE3B91B47FC30B0233A09BA79FB11238AC508CCE61095F854C23204A8D36BFC" + "2C6E05A72AF5244B17C12101E01451570EB110567E850E79C000142441FE4160" + "027545F6290E85451B80234A9406C390B0CEA3C8335D4C6F8550B544C9343E61" + "BA1C8489D1B0399739168AF740A481B0F5C3372530CA06B508ECE838AB78BEE1" + "E597A9B14F6AEC7A3BD1AA8D10BAC23B9802902CD529AB6EF54DB3110CFB561E" + "7E6948E65281250416C349C8100B3B4D3D0F62ACAD8D161175B134F7564937CD" + "ECE9E246AAD11021A67B20EB8F7765AC2823A9D18C93EC282D6DBC53CD6DF575" + "92AC7D1F83BAFAE6EE86FE00F95D813375772434860F5FF7D54FFC37399BC4CC"), + ek: &hex!("D2E69A05534A7232C5F1B766E93A5EE2EA1B26E860A3441ADEA91EDB782CABC8" + "A5D011A21BC388E7F486F0B7993079AE3F1A7C85D27D0F492184D59062142B76" + "A43734A90D556A95DC483DD82104ED58CA1571C39685827951434CC1001AA4C8" + "13261E4F93028E14CD08F768A454310C3B010C83B74D04A57BB977B3D8BCF3AA" + "A78CA12B78F010D95134928A5E5D96A029B442A41888038B29C2F122B0B6B3AF" + "121AEA29A05553BDF1DB607AFB17001860AF1823BCF03DB3B441DA163A28C523" + "A5FB4669A64234A4BCD1217FF2635BD97680FF938DBCF10E9532A9A79A5B073A" + "9E8DB2123D210FAEA200B664838E80071F2BA254AAC890A46E28EC342D92812B" + "01593071657E7A3A4A75CB3D5279CE88405AC5ADACB2051E022EE0AC9BBFE32D" + "EF98667ED347ADCB3930F3CAD031391B709A4E61B8DD4B3FB741B5BD60BF3040" + "15EE7546A24B59EADCA137C7125074726B7686EC551B7BC26BBDB20FC3783534" + "E34EE1F1BC6B77AB49A6667846975778C3C536830450A3FA910259722F3F806E" + "6EB4B9346763FEF0922BC4B6EB3826AFF24EADC6CF6E477C2E055CFB7A90A55C" + "06D0B2A2F5116069E64A5B5078C0577BC8E7900EA71C341C02AD854EA5A01AF2" + "A605CB2068D52438CDDC60B03882CC024D13045F2BA6B0F446AAA59587606179" + "45371FD78C28A40677A6E72F513B9E0667A9BAF446C1BA931BA81834234792A2" + "A2B2B3701F31B7CF467C80F1981141BB457793E1307091C48B5914646A60CE1A" + "301543779D7C3342AD179796C2C440D99DF9D41B52E32625A82AA5F579A9920B" + "FFBA964FA70DB259C85E68C813817B1347BF19814DA5E9364A4645E621923D95" + "5C211A55D355C816DA04730AA324085E622B51D6109B49F673ADD00E414755C8" + "024AA0164F24556DED963D61143856CB4FF0567E3320730DBCBF12F66E2B70B2" + "0054A6DEA42614B50EF72B156F5149FC263DD7E039C55A3EE9827DF92C565D24" + "C55E0A81C6494695344D948748AFBA9F762C0EA90BB724897902000775613949" + "602C48C78A9440678C24086D326D79643BAF7036C66C7E026AAEFDA2807A60BD" + "7FC91363BB0234A590984AA011F11D40268218A1588377B3D7671B8B99789919" + "B86EE82B18EC22D4E80A1F27853D889419D460DEF7567AA4567969C43048C32B" + "8462A9C9386EB3152A6976AA783CDD1A8C57A9B6BBD837A00624B58B4BA3DBB6" + "3BB8200E7BC88881BEBDA925BCA028E291AA1C22539CD04F90090D7F74108C32" + "B8022C1591C881E76304E2408190E20F09A54FC23420E2620E9D87A3108A94FE" + "EA72D5AB7FCFB972E6561B1A7B062F1A682E020AA2562812B296547B917824CD" + "B88C582B5A6890177BC70C91ACAC9ABE290AEB2C34A7E2368955CB456A345368" + "ABE3B91B47FC30B0233A09BA79FB11238AC508CCE61095F854C23204A8D36BFC" + "2C6E05A72AF5244B17C12101E01451570EB110567E850E79C000142441FE4160" + "027545F6290E85451B80234A9406C390B0CEA3C8335D4C6F8550B544C9343E61" + "BA1C8489D1B0399739168AF740A481B0F5C3372530CA06B508ECE838AB78BEE1" + "E597A9B14F6AEC7A3BD1AA8D10BAC23B9802902CD529AB6EF54DB3110CFB561E" + "7E6948E65281250416C349C8100B3B4D3D0F62ACAD8D161175B134F7564937CD"), + }, + GenerateVector { + z: hex!("7AF65022E0A472ED6388638EA29D82DA68B4CF9FFDF2B67CD708EA5A370C6A7C"), + d: hex!("7AF65022E0A472ED6388638EA29D82DA68B4CF9FFDF2B67CD708EA5A370C6A7C"), + + dk: &hex!("8AD0B5F09A25AA935DD9DA34AB82CA75A12D66E99CF48BCA45B9B2DB441BC297" + "1BDC9922B5F8BC3C0678546759073CB88E26BAD1B1B3A4646A6529C632EAA347" + "734A3BE583D47178094C4A670CBC41EC0689765668542E6F15A7D586C9E26A6A" + "03C71469C2C53F7B141B232D86216A25C7A8F36852858C07A9524EE17BA6340A" + "A2A215C1EA852167B6891CC166C2FA13A0270A22983413E0ACC444BF40E28C45" + "E14E07404F62996369597F10FCC180ECACAD1A6719AB9F1B447AE19A2CB02A7D" + "04206172168C4F0A99BAFA932D6649E894A8F0577B81C66483C5B5CF60AE75A4" + "44526A9B3674325FBA38F53296421A785011C1DDB3A6997745DB83CD583C0C41" + "77C797D40A4F699F1F40C5413AC4E42373492B6A2C6A406D437F42570B5E949E" + "F4350DEA790CFEB72D1287517FE3273D3CA65A13CA6E23C57BF07DA04B851CF3" + "AFA18BAF5EF020792857A9E721F01B9FEA7B612E4C6E29079366B0228688BE2A" + "067FBE92842DD280B3C74DFAB761E613A8604C476E15466685C695AC35791A91" + "59942F60170CA214C7C09B1A4B1BCC4F4CC60DF01A101915A9A2BC5531196650" + "32DCD9476FBA7BB07157D33C9C8EFA6BD0AC38C1AC265FB51857D01517615326" + "CA0E08650BA6FA40832C7B4C41B644716022B652B1927D55C9B37FE25F1AB67A" + "9A03C7008C84B07C4926B6381E40CFD441041235187416CEC366CA6FB76FA0AB" + "6E328A2641FC47DCD76E91CA9431E19BFF02CE6228C2336382F8A10E9EE2C8F1" + "759390A20024A15B3B090C1390CA0343797284246BD8943507B7A6B71FC33A03" + "B7A88366E4AFED515739E5C69F8A266E4A1F53D73930E9875569312B27037E5C" + "7F852100C2BA3648B1B9C1B149F6250E0A6B065213134F302569755B8C5C4FFC" + "680BF7811845340035F170B068BA67A4C3B0166D03CC8261840190A20F9A3B1E" + "F465C2F2182DA8DA8D3B3C8CB12915F7D93E04D8840C3567255A7BD6D433CF10" + "68D88452CFC11F991B7CE37927D6CAAE8810742F42148B896EC4EBB534038631" + "5B2C1E2B43915C04549CC8C19AB40E3B7C311B426110A9BBB18D3B992A42C018" + "9290BE673A397C4090443B88C5D5C565A10FEA05603D36244A4AA8E9255CF184" + "AE69535A8399C1C6F76CF2342ADFEA6A447BB4501B9A6C44593EB043E7A5502F" + "586CF3407DEB7A0FC32B3F46F1245C5596E0F1BED937207C4509E1D8985BE745" + "FD69BF448092433028BE2595903311479586A34B2D49107410BC4BD2965317FC" + "76352B638DF3B3A315325026809E3BC4608C0B2CB84DF0C95BC052707FC1A377" + "B2B465EB7A5D644AB4278DDCE5B61E2BB3A71052555CB3BAC693EF025FF00353" + "FB76945B8AA3E9950F9273818791CCAD56884658142A2B4DF3C57ECA13AD44B4" + "9B6346C63EE89078589E9EB9A9804A03BF7A276F86B9676C58D3E71D2C877080" + "4A61592178B449C7955BBE8CF42F316725E3B16D55B527CFB232681B21B2CB2F" + "30AC76015BB5416A0411C1745892412E683A98D736ED1A4CD980617D0821C2AB" + "0282070A611A11D19701FBD55A2127B324E6901D84986C0464DE7120AF4510AF" + "591DD9BB79479C5FA88714C6A99714F76A1E402C8F384A4EE6BCD41500724CC1" + "793ABFD8D3C2F320397134B00F762DBA85A23AF155E6CC037380C1DD64A973DB" + "35B74470448B24212062764787E5A93A48807171A7715CFC89BCBC9E141886F8" + "07A1F9D684C8426F0122887D9C4C27EA69CC151B4D49B51E5A4EAAA5AD06ABA8" + "6DF942E986A5D5792080FC480396B3948668FB382CC8FC154748CB30B7641F02" + "70C83438B4FC3D1901265880405177BC7F44788251ABC474273531212A66279E" + "70337A2309FDF49E059BBDAF4973A5377A4D517BA755702C37CC355685404C95" + "2FB67E0419C78D1584D0949254D04952F7243BF1402803C9FC737325881378CA" + "77EEF5C415FB037D689A5854A1D24B6527A59B9B16959384358C423C79645CCF" + "3133E21B4B64957B14F63F2AA26357B1C262F2A90F7CCC2A15936999B0A1B498" + "AB3B32433032C9CA23081C55D31CAD36E90C1CE0B5FC247CE8C843F2883524B6" + "64FAC1B20BE602A10AAF65738680BB10254426F9CB09A1954DB7655622308FAE" + "F505ACB497554A8FCFA96A85255AD846542013B8415951BDD45C01931EBE583E" + "70E13F301517B5A40D70361F6309416067646D2B7136626BCCCC170C66CED490" + "C735344B6277097CA914212A292DD122FBB69FDECA47FAB4532B8C80CEB77F9C" + "543E0BF1536D1C0CAE077E2CA7862B45A410469CC5B706BAE0051CB2961DB727" + "0B75B711698D2B807040D5628129436FBB58F1203F75561465F54257E44D33F5" + "12D633431D00A2FB0230C9BB9CDDFC83BD65C97445302186A17223AD21332803" + "B909E5E5671970BBB0F1C4837BB84273BA675AC074C5290B411C250065705933" + "9DE392F9CA308952A2201A588767ADC035BDF33024EA3B9A83C5A0B9C5425D14" + "070C81AADA26BAC3FBB8D4B7CFEE0392375C68427351DFEC63609BBB50B463E0" + "4092857009D1E5B81D707D14B833CD4A0B551BAA13EC488A1503B0467EE4023C" + "3FE032C78225063886E2468E00F700072A2EC8DA6AFB206C91904433BBCCB0E7" + "6F42468C40EB5F59CB9AE1B035E521510BF216A1ABCB19033B7A658897C65874" + "D5135183149F979E553CCFBFA3900CDA6F01960B75157F5453AA6E73B3ED902F" + "7D7C9305971BDF722E2937169A1BC0FAEB6C92F7150D2330877C5DC5249AAE20" + "302634C5C5B23053521028122542F485A0EAC869223720633651F5B247C662B3" + "1A10538CA7491B1437AA74F4282D12974D9C934DF214785B6418468B92E52528" + "C8447A1CA422FA6CC88E28B059F04B23597323F72F3E2336F87C47905CBA655B" + "B73FC32E18D4B78705C782EBCB43E2785C82C5AF24B0E1699CFBC0257475799A" + "539B11A50F4DF2B7FAA20BD8827515CA370F89C0D4C60902F6567CD60B0860A5" + "5BC8572C436C246AC276644E7D602AA57C0166201814991C1BD75C7C47C348B6" + "7D77613386908144EA83FF721F9A50076C510164D18E05D05D9884C44146A07C" + "CACF890498ED1A19B2A15431729DC1F12B7EA10F9F928062D1454B4B9F68E599" + "90290BE3728B3289569363AB1005131B2381A08CC2BF943E95D5B21BC6AABC22" + "73348BC72BD093B7B5617AE87F602BB989E6AFC44B81512076A3A876E0E25F97" + "62B462081985502F26B287A2936D5B1ACFFCEC4EEE77A9CBA980EB9B5FDE7553" + "9F650904677DBE29AB8BB918A3494803ECA59A2C32E5B5C83B0B80B1102CD7D9" + "482B459B6B74491EC30C4BE77C2B524AF7B3AD1F71341DF0A76F255C2903C882" + "08079379930A9513F390126E732A2BB094BFA6BF0A432BCD657DAFCB25C8BB15" + "E0955D099B74FF1A4DE6559CD6797C38C48C1134CA2C979243F3152AF4BBE4D7" + "A6BC09872133920CD23B3EF9848CCC6845D647B5387557736513D58560845192" + "F9265159932E572A88C44E6566760C061C67FCB5BF210095E214DA745357E369" + "96D8C066311BBC761A1FD25273D21EAB50010563CD6468A4EA836B6D64BD2BD7" + "6DBE3582D5736A605A5509FC28789B56B884AE9A60415F55674BE601576C7CEE" + "58143BF054806ABCB345A256CBC454E343F3CC7ADE65562FD29EB259737BB3CF" + "9649BDEA283FB07265677C9808D13119C0A2ADF745DE6975F4562CD61557B396" + "5D2B072F000AA7E0A357E1253EAFEA7FDFCC92FA87630DD2276CE42E820B69D1" + "FC2E47D5C498A55B3B29C34E64903D047AB1C04024958F701195F5D13EC6706B" + "8448503A549922A58A24B67C93632756B77D225407316171DEEC56714435CF94" + "CCF4599E00D10E569622BADA820C452F2542ADF08765CA93AE38EB025DE31CFF" + "7974549A7825A831DD054E87B84C5F2547FF47B46F88C99F1548E933A6F4D87F" + "1A4A1B00E39E02D60E51EB603C1C0D807ACDAB08BAA2B99869B75CA2C4B96368" + "B51780BD1EC75B110B9FA66556876C5F48797D090138F754AE30533D36AA44B9" + "B1702A6A8A56626BF0451A37A7AC1A337076E51E0A6B0300C2C790A4437EA28D" + "7EC98C419B37D6AA970417435F91BEDC2B1F4BC8158A51B1F471516FE824287C" + "896B891B49F254DD36359B89C824EB3F6248027FBBAD4CF29118CB50EBB625A3" + "7C537A0223F0EB7085B5C7EC607570DB9185D59902BC26C654A2804C0D946793" + "D8A21482AC4F05E9016260331DCC58BC66AF3CA7585440216AA0263B2A725E08" + "0F6F9C5B6A9C9DA29355189B4B95B137D1225F252AC797B0646CAC52164B5972" + "A99265D347FC7C3591D15FFE681C06D438CCEB60BB6310B7953289720E2C7287" + "30052337ACA7C8521AB44F1E2A049B83E0774C96CD8C876FA675D0923977271B" + "E6E832F2498CA5A3431F40D3187B1ED965FDD6693B37F6EB408A99977AE49644" + "7AF65022E0A472ED6388638EA29D82DA68B4CF9FFDF2B67CD708EA5A370C6A7C"), + ek: &hex!("70E13F301517B5A40D70361F6309416067646D2B7136626BCCCC170C66CED490" + "C735344B6277097CA914212A292DD122FBB69FDECA47FAB4532B8C80CEB77F9C" + "543E0BF1536D1C0CAE077E2CA7862B45A410469CC5B706BAE0051CB2961DB727" + "0B75B711698D2B807040D5628129436FBB58F1203F75561465F54257E44D33F5" + "12D633431D00A2FB0230C9BB9CDDFC83BD65C97445302186A17223AD21332803" + "B909E5E5671970BBB0F1C4837BB84273BA675AC074C5290B411C250065705933" + "9DE392F9CA308952A2201A588767ADC035BDF33024EA3B9A83C5A0B9C5425D14" + "070C81AADA26BAC3FBB8D4B7CFEE0392375C68427351DFEC63609BBB50B463E0" + "4092857009D1E5B81D707D14B833CD4A0B551BAA13EC488A1503B0467EE4023C" + "3FE032C78225063886E2468E00F700072A2EC8DA6AFB206C91904433BBCCB0E7" + "6F42468C40EB5F59CB9AE1B035E521510BF216A1ABCB19033B7A658897C65874" + "D5135183149F979E553CCFBFA3900CDA6F01960B75157F5453AA6E73B3ED902F" + "7D7C9305971BDF722E2937169A1BC0FAEB6C92F7150D2330877C5DC5249AAE20" + "302634C5C5B23053521028122542F485A0EAC869223720633651F5B247C662B3" + "1A10538CA7491B1437AA74F4282D12974D9C934DF214785B6418468B92E52528" + "C8447A1CA422FA6CC88E28B059F04B23597323F72F3E2336F87C47905CBA655B" + "B73FC32E18D4B78705C782EBCB43E2785C82C5AF24B0E1699CFBC0257475799A" + "539B11A50F4DF2B7FAA20BD8827515CA370F89C0D4C60902F6567CD60B0860A5" + "5BC8572C436C246AC276644E7D602AA57C0166201814991C1BD75C7C47C348B6" + "7D77613386908144EA83FF721F9A50076C510164D18E05D05D9884C44146A07C" + "CACF890498ED1A19B2A15431729DC1F12B7EA10F9F928062D1454B4B9F68E599" + "90290BE3728B3289569363AB1005131B2381A08CC2BF943E95D5B21BC6AABC22" + "73348BC72BD093B7B5617AE87F602BB989E6AFC44B81512076A3A876E0E25F97" + "62B462081985502F26B287A2936D5B1ACFFCEC4EEE77A9CBA980EB9B5FDE7553" + "9F650904677DBE29AB8BB918A3494803ECA59A2C32E5B5C83B0B80B1102CD7D9" + "482B459B6B74491EC30C4BE77C2B524AF7B3AD1F71341DF0A76F255C2903C882" + "08079379930A9513F390126E732A2BB094BFA6BF0A432BCD657DAFCB25C8BB15" + "E0955D099B74FF1A4DE6559CD6797C38C48C1134CA2C979243F3152AF4BBE4D7" + "A6BC09872133920CD23B3EF9848CCC6845D647B5387557736513D58560845192" + "F9265159932E572A88C44E6566760C061C67FCB5BF210095E214DA745357E369" + "96D8C066311BBC761A1FD25273D21EAB50010563CD6468A4EA836B6D64BD2BD7" + "6DBE3582D5736A605A5509FC28789B56B884AE9A60415F55674BE601576C7CEE" + "58143BF054806ABCB345A256CBC454E343F3CC7ADE65562FD29EB259737BB3CF" + "9649BDEA283FB07265677C9808D13119C0A2ADF745DE6975F4562CD61557B396" + "5D2B072F000AA7E0A357E1253EAFEA7FDFCC92FA87630DD2276CE42E820B69D1" + "FC2E47D5C498A55B3B29C34E64903D047AB1C04024958F701195F5D13EC6706B" + "8448503A549922A58A24B67C93632756B77D225407316171DEEC56714435CF94" + "CCF4599E00D10E569622BADA820C452F2542ADF08765CA93AE38EB025DE31CFF" + "7974549A7825A831DD054E87B84C5F2547FF47B46F88C99F1548E933A6F4D87F" + "1A4A1B00E39E02D60E51EB603C1C0D807ACDAB08BAA2B99869B75CA2C4B96368" + "B51780BD1EC75B110B9FA66556876C5F48797D090138F754AE30533D36AA44B9" + "B1702A6A8A56626BF0451A37A7AC1A337076E51E0A6B0300C2C790A4437EA28D" + "7EC98C419B37D6AA970417435F91BEDC2B1F4BC8158A51B1F471516FE824287C" + "896B891B49F254DD36359B89C824EB3F6248027FBBAD4CF29118CB50EBB625A3" + "7C537A0223F0EB7085B5C7EC607570DB9185D59902BC26C654A2804C0D946793" + "D8A21482AC4F05E9016260331DCC58BC66AF3CA7585440216AA0263B2A725E08" + "0F6F9C5B6A9C9DA29355189B4B95B137D1225F252AC797B0646CAC52164B5972" + "A99265D347FC7C3591D15FFE681C06D438CCEB60BB6310B7953289720E2C7287" + "30052337ACA7C8521AB44F1E2A049B83E0774C96CD8C876FA675D0923977271B"), + }, +]; + +const ENC_VECTORS: [EncapsulateVector; 3] = [ + EncapsulateVector { + ek: &hex!("A5409718CB72F2438A3555A3C8F18F2671A1F81403DF7B5A4659A51F50827BA6" + "577AA70800D78D8BC5AA86B89E08B58F3480A89E104DC6922EDBC12D06F89102" + "7C654E994A22F91A2AF63404CA98D7B67EEA25911B24C70DEB8146A0821F34A3" + "02551F2D510C0588C8BCA74EB4DC0CFA4603C1C5A3C5537061789068682C4CC3" + "143FBA9BB5542F9778BDF23B3652F2A7524756FA73909DDAC7E532522659218C" + "BA25F33B6B0458CB03DA7935BA59111955312B15CCE2C0F73466A8006283A2AA" + "7CBB61022ABBC2D19F2920BC302472DC97C4A1788C9BD3BBEDC9122B827B279C" + "074C80443141119F4B1629F62F10D4CE2BE3BB343816CAD16A1C87582F2B70E2" + "6635B08BB390C13398FCCDA7E9BB3D9B0B7803750C955C57A028A5D26C270316" + "BB2B815C3B972BA6782DAB02F306821E61285BB072BF79781CABC386142A50C7" + "AAAE66A947585BB0D8288DBCAF4B3B85BB7926987BAF7643AAB5FB02210580A0" + "264352E69C6098989CFB87483395960A3A4F31BEFDA80B5F286ECFDAA555D439" + "0AF6B55D313920929093449CD6729D00218E2D86570ADC0C4F6545FFB5632EFB" + "3AAE2625A6982670FACE8D16126FA607E6D0A1FF616A46ECA642CC6AAC554DBB" + "C43DFCF57F364C190CEA5776C1CEB58B7007505FD79C5F005A4BA218CF0693B0" + "58B510A4CA204324602F59BB8F2281C4D7B0BC8625E7881650F57C89E32CF480" + "9144775C9073B673E39412A27C914321CCB6A7CF7C37C5BCBE7CA51BE0C92846" + "6A458EB778D6466A892A0ACBC09638784A27739C970CA58BC2595AD6BFA4E52E" + "B438AC97C41623802248E110B074838F31A6E7503737704E7AE4AD91299572A8" + "C13603500F3609B625B4E24CAE332B0D7A5BB47A038512A081BC27CDF0F2923C" + "D3479F5307020B77F149584564060E5083CED55312B6A6A465A82B4577D63A4B" + "49C80B07A9367E39778AF76FA8EC2CF528722856CE7813401A8383BDB7151B9B" + "6D2DD6BFF55401D28AC612818C88C9287347B098A966EB9C0A2DB71F0A75555E" + "1757D3AC4E3D802C8DC6A261521255186ABB98C2480301B8C6B31228B54461BC" + "44EA3C2CF94B86C7A5B82C55167A7606CA9DC8253B7604E44A07F3ED55CD5B5E"), + m: hex!("109A248FE8052F84271FF57BAC156B1BA6A509CDCDBCC96CCDB1CCB85CA49315"), + k: hex!("4DDD304E274899BD82971856824B587130927952060121858F9ADEB96AB7F571"), + c: &hex!("597A06DEB88172BA8D7CDE8D82CAA234B8112AF8A72F1AB4CEA1EFCB2D868D53" + "D212E303B70E7E521AB0F4B5DB4F51159248BFB275361BEF883752C78B8D4712" + "275385536A4B0A96E3C23EA6C17EA92B602616E5821E5753A4736C4039C20C92" + "3CCECB579805587C0CE72218BB1AB12452F8E154CB8643328142F9B340A641C6" + "F295E5ECF2E048BC7FC79BC5B94277C868D8E536B50425809DCFA024A3905CBA" + "550AD3BB52B459AC38FABC9BC00EBA03EC0906725B4FE4E976F174320047B31D" + "15891365BA482388F0FB973B85224FB00BA865AFAB3C9A1B7D489F7B982D0BD4" + "70EF948ECB5B3920AF89035960123B1F8630D763681BFD671567EFBB1E6276AA" + "4FB2DFA9C3948DB7F083F28383B77BC514AF9D68D22E2487C20163C02B0BBF23" + "BBCE0650F84FF8CE02C74E9E11D6F30EC5FA8A012ADC3B89627C7DE855C1FBBE" + "B5DCDE84D05E36C5566E5551B58750A411642639B27864F7E005978FFE256B75" + "7D13DA663FC3BB0794A27CF7585D12F22D953B285459FDC9BCDFCDCCB7BF3E4E" + "362D2891D583855F5D9487E6FB217E2E45EE0BD9AFC289F4D564581209A3ACA3" + "1795A124BD1BBAEA846755C8EA7810EAA73060E86FB5FDF3FBE72F806BB1BFBF" + "BAC0C7B16BFE74250277ECF5F541571B8A975050917FDF781FEA17B585E3C6DB" + "FE77B1E48A16504C3A38901156100CAFEC2ED939AE9A9EDFC9C0F8C7F55CC93E" + "5DDD0B3DE1C6EDAE2B7EE34C6101F011B5904F693D286356B54C86CE8BCFEA9D" + "BFEC21C1EF0ECC9105005BAA377D829DCA2CBF5EA5F31B71D446B833E0061981" + "9D7FC6024052499757A2765F19CD2B36C2488599DC5247494FABE81EEBEFD3BE" + "75C4780E43A50418C5DB2FF359C5A6DE286EF5951E2709486EDC9CC49D0724EC" + "A3F2C0B75F8A36CE862388F00B3C593D1C8C6AC45D73A72FF6B4F805B131ED4E" + "AF5601D7B73B0E3724E75D58DD50F5871C54A37C1481331759F4BE86FB58A2EE" + "003130F66E187C8BA5015BE713296589ACAFBF6596897E03D4920C91F26333B7" + "BF1798AF815C93D4DF55BD47A08249BF113063FBB39503E9B6D43EAC7B0C305A"), + }, + EncapsulateVector { + ek: &hex!("1456A2EE8C3556054ABC79B4882C3190E5CA726AB402E5B09728C0F4F79C9FC2" + "ADD828ABE432B1501B60F46CCBC86A3378C34895708A13671B20B389479AAA01" + "C69D6B3B7D07D1C3AB54B91C580F5A336B30069A4F134FFD3764CE73A047E284" + "4771742BF4710B972D4F6590A1C53A975368C271B670F1A4036441054A66E881" + "5997512288552FD7149FFB705AAE133F8414060D0092FA8A1627D78AB2ABC669" + "6288BAF5C60EF370827A7EFA72AE5C6741A5DA043D5940F121485372A98F472D" + "60F05F74D95F01A1991E73A3E0A9536467A4738AB4CF385BA772827EB8CC058B" + "3572E40B598444C181C7F6D9B760A7B907092E9C3351EA234E4449BD9B61A134" + "654E2DA191FF0793961569D3594448BBC2586999A6671EFCA957F3A6699A4A1B" + "2F4707ABA0B2DB20114FE68A4E2815AF3AAC4B8C6BE5648C50CC35C27C572880" + "28D361708D302EEBB860BEE691F656A2550CB321E9293D7516C599817B766BA9" + "28B108779A1C8712E74C76841AC58B8C515BF4749BF715984445B2B530633840" + "01E55F68867B1AF46CA70CA8EA74172DB80B5218BDE4F00A0E658DB5A18D94E1" + "427AF7AE358CCEB238772FCC83F10828A4A367D42C4CB6933FDD1C1C7B86AD8B" + "009657A96222D7BA92F527AF877970A83247F47A23FC2285118B577177152046" + "74DA9C94B62BC7838CF87200156B26BA4671159931C49322D80671A0F332EAA2" + "BBF893BE408B9EAC6A505483AA9075BD1368B51F99211F480A9C542A75B5BE08" + "E43ADAF301DD729A85954010E64892A2AA4F15C0BD70B3D856494FF9BA0FE4CE" + "12991CA06B5E3D0B2AF1F797B7A2B760910AE9F833D0D4267A58052C2990F161" + "B886E251711C09D085C3D958B144192C9CC3224A460715B6784EB0B26F237187" + "507D85C5110ACC71CE47198F254553356DAB448C38D243A7C02BE40C908C828D" + "05C081DFAB8FC6B5CFE7D56E7317157DC053B2B3489986B081288871818585E0" + "9931095E3274A084115BE276438254A796270A7B4306F08B98D9C2AAECF7065E" + "74446B7C696DBAAF8B4625A10B07827B4A8BABAB09B64AE1C375BB785441F319" + "FB9AC2F14C95FFB252ABBB809C6909CD97706E40691CBA61C9252BD38A04311C" + "A5BB2CA79578347505D0888851E082648BD003BE97C0F8F66759EC96A96A081C" + "6822C4510559537042FC15F069A649B74A10961B354A1F625B04E25B293CF65F" + "B4F53A80CC733D7A175775BF8A9ABB9201620E83A7F3E724D1287DBC44BDD5D8" + "5FC71545A927BEEDE537A7768735CC1486C7C3F31104DB67343F435D2D45554B" + "AAC9CDB5822E8422AE8321C78ABE9F261FD4810A79E33E94E63B3341872C9225" + "3521997C084FBC060B8B125CCC88AC85AC5FE3168ACB059B3F119C4E050A2073" + "2F501BB9B3E687C846B5C2653F8886373E1004A2AB8D1BB970A7E571D8A46EE8" + "1B782F26942DD394FDD9A5E4C5631D985528604B1CC976275B6AC8A67CEEC10F" + "FACBBA3D3BB141321DFC3C9231FC96E448B9AB847021E2C8D90C6BCAF2B12407" + "83B62C79DEDC072A5763E660AF2C27C3F0C3C09207CAD990BB41A7BFCEC99F51" + "596A0E83778F85C006AC6D1FE981B4C4BA1CB575A7D07AE2D31BA760095F74BC" + "163841CF8FF77F894ABC6D261ED87A4530363B949C4AD24EFB3A56809478DDA2"), + m: hex!("40BE9DCAC16E9CA73D49D0C83F9D3D89BB71574A4219A0F393DFECE2988394C4"), + k: hex!("616E0B753A3B7F40FEF9A389F58F16BFBB04622941D2464BDAE767820DFAC38E"), + c: &hex!("778D6B03791ACAF56CAAFCC78CEE5CBCA1DE8737E9C7FF4AE5F384D344E08223" + "C74C824CB5848520517C7F0EA0645EB6F889517AE5216B0CF41DDC3F0D1DF9BC" + "6E4DECB236A5EA8B214F64266D3CDE08E0CB00E5D91F586706B1EE533D20476F" + "4423B78F916B1726EEEA959FFB9AC634D04A94D09923CB0D4E730CCA4144E7C4" + "884921652DA4928C68E644F673CFC57D3E87CF5BE581A89F9CB8F0FCE2782D68" + "1E5CE88AF58458C3D63D807572DE5AA8E1FAF2DCD14EDB7349565B7D3271DDBE" + "B0B6CC7AFE08635784311159733C46E5FDC5E0CD36CE5685ACFB1AFE50ABB46F" + "447521E60D9C8F0E4CA28C190ABB40C365F412471E95A8EA396D4BD8070EEB1F" + "02B07C825367AA1EC0F10C3862416BB21AD6CA748A86E9829EFC1A0499093C85" + "176D37F574C75CF5EDFA8D920D3268CB34C6A4BB0002869BC05D7C8FCC0658D4" + "A01EACD74557A37D98A763074752DFDD6429881CAFF577D3A048031BD52C4E97" + "26398590F9519FD59405D6B3C307AFCB168A985785D954A6D1DC1EA92E1EB6F9" + "46A4D99DD6CA307ABFD8362FABA98BB264C69C5F555D60883CC56019FEB4E800" + "0C48B7E68CD667F00B5250CEF293A4A9E778726E62F120361E21AB3140464CDC" + "6ABDE9EA05198D8B3BB671B9111A2F317582847CA5015664F22CDB08C143187B" + "DE2129B54F34160295D75FE9A494FD7E67AAA76B57AAFFD89D01A71DF5C81586" + "20298D582BBEFA6D09AC412A99AA3BE9C383504948C43DD5AF4127B1435804F4" + "4BAFA142BFC2A95D95FB2EF0641ABE71064DE51D6B9EC50857B8EEF7F4803631" + "3D0E936763B8F7BDE69B064DD5761D80EA6F1A8B37565753C579BBB895EFB9FC" + "B3FC5FA3362E3774F0F77140B973CAE587BAD2F3B566A9C25A969347E5C54F87" + "F1105E9C074867D94077CCAE3ABEA54520EDB51D9DAABE7848E78FDF66E07E2E" + "22B30251931E890BAF1F5E177D4D9CEC9E4969481FD7C1335A0ED5879F34EF4B" + "B4F66C28803CEA162BA461506D52EB3AE16951922B06825186C3D4CE1B51F3C9" + "2F3C52F2D04D1F13B2B17C9EEB882CCE0EB88B7EA9A1CE4E37415CC84C7BC436" + "A4628386CC77D9AFD207911BD9BFD8A7FA05C275BE0C4C6A8FC0A61BDA1D67AE" + "33B5310BE1290DC71C1418EB5744BF2842C1652173A49A692E71FE43258A205B" + "3CAAB90C0304A51E77D01B404A01FAE2F83AB80C5DBF6CF518C001F46A633FA1" + "69B1BDB77A9D0B1E0C007835C09F6ABBA96F3F53564DA508EE8861A483A81749" + "D4A44672B1EF1605F29D168B74B736B4F13501D7AD1213118A7832E666A50BE8" + "010D54322A526CF7A4E543A79D0D98E004FBEC76EA3F7E887BDBAF50DADFDDDF" + "3FFECF6D3F77EA4B9B16DC754F4A68E5EF32F6A137E7C9E3C3E8C2E236C7EBC4" + "5D46EC1677A5A8BB2668443B0BE8693DC257F13D8B9A90100B92B4D1761B8196" + "73832C32020671BFB3D0220A363E4BED6D649D3F7368CFE081E196A43D470879" + "8E31BB2A2F61824674ABA2FC9DCD05DB84B8627AE11488886F921BC79AE1FD03"), + }, + EncapsulateVector { + ek: &hex!("27669A667667B8D5466858602260115B6209BC2C45DF7A4E64932B75C78B9F70" + "83F131BCD4E20EFF8CCF69736BDBC88406F9B69AD3CE356A0F5E676DD0A7C4AB" + "B1A1C9D62021BB384A4014FB04CD2F821890D90427C49F4A628ECEC2731FAC02" + "5237360D582CD06647B1109AA6C2AC5D433758C1CAA53555FFF577EBB521FBE3" + "2D10F790604C53C2F82C17B08EF3625674214844906DB3FB9520031422A13BD7" + "612D4201C27D15B9D194830CC3669BB8BA34C2523764413971C40D84AEE65675" + "D5215309DA8367F001497546ECE07CBF002D781B830682484080AD6F9558B36B" + "6BF610917130B7419B39F85029621264CF2C8AE4D808387B20CC5AA0B969C39B" + "C80E6CB9CA0351A3F60ACEAF12BD41FA0996E39906A9B61697B747C2031C7602" + "88364457425BBBB40F4898AD085876608A77A5EB9D124BC9922651B763958815" + "58CAD06F3C4BCF08E45B67BA516038A364B7740E9740EE2B93C5C65F49020AD4" + "2B3C0AEA5BF242A4F1B089B5A3458BE8A371CA1F293C53F2780ECE281293D991" + "E6E579042BABC169724F10681FD1C7D2FB1648B0BF80818A7DD3B709734D3897" + "2E3E44875AF0927A9AADE82613FCA05EE5B3210647A5632AA170D09E70B56A2F" + "04337A337EE952383A1A8AEEA6CDB90CCD86A818D1BB39465BA313D266BBB105" + "81FA187D926AC3A8B749F64445FAB56C9927555793FB4ACFB039B1AA543B1B87" + "AE6A49AB562933C4C97BD74C07BF29851A469851A982595596FE7ACAE0DB2353" + "3028AA34676F7A9B29263E7AA27900104B1BA1B5674739B2FC4ED8A330BBA5A0" + "B6247C63F1153DA01DC8F616F10483A693A634C1BA6AE1AB2F163400BB5771E7" + "0171FCB54155ABFCB2044FCB30BAD67F742183861819EDB1AA6C771FC8E11A92" + "E08B71F40D036C15D2896A204725BA90A03B478D98C49084382F1D223FE12980" + "E947A415E55FE67B85DA40441342445B46C2FC42020D04769A2A1C64641F0C36" + "636BA6C4652B267A4B9219E333A06817B5817B6E6CC485E352614169ABC20E18" + "91B7A000C52AF15A7B904C976C1BFD3A2377EB76B55033C7C4C69E7174AAF277" + "15756316CACCCE63A5A22435C7D1020443AA71693BF062303D13331F795424C2" + "0D266C1D90305FC8C2536684A93D506DE6329B6162405999BD5CAA7DDB9613C8" + "238CC6D335A1EB4082E7710D079F87A4BFF6478B5F0C587786AF427192D9A34A" + "4FA33BF0D3CC58FB463B4838CA2C337E65397DA15690C52AC0E5468BDC03DF5A" + "62F7020934E267E0F7CF95599435F952FAB74CFEB4308B173F12E073F7F040DB" + "4C63C1C48A7B7A41F4779A6B57A922C970771180008493D4C76805400B7C664D" + "0B92B22C49551B1247E62C85E1E540C82093371013C4676CEAD77C5F3064A373" + "49C7165EB3AA7DEF8731E9D66A56368F195C045B2A50E59786161A630D280089" + "801298C130E4483150CA9152C2A0F247750C062259B84C28236C3FB54625D5CD" + "BECC68DBA22FB1558055FB9B243501C75851E76ABE4847B9B972A73411A6B428" + "2BF5983A82DA7413E54BA35BAB37A9B3C62884B643C134165C9870C6BB390F6B" + "7A1E5745158FB251D6909433551FEBD30BA575A1E2F10958498D9F147ED95313" + "22A16097F55D811795457912912B1C65F38025429B3E764A2E1ABC4E30C28808" + "2742995590981C43DBB365966BCB9720B178C5EB963B82934C02814B7525546D" + "B7C96D65822E4942E4A4AC13C99490E7AB4A702371F21316A57906B192584288" + "01192567C2045BF8775CF58C5DB28BA1B05E042A1859E64286B5B114F39FCACC" + "127BE63DFF590BC184B83B168C30199890374100E40D2FC7752B1430355022F3" + "D58925D1991BF3B98A90395F8579646C8413BAB3C0C0707A238A27D09FA57A32" + "FF85392FD08C2F2286ABDB2B6936B9D3503802C6B51E415B81673CC78054F1B2" + "C4BDFA733E5264C55A7C4DA5B73944402462033D08AE620BD05644B477AB315E" + "936D3F25B5BA7AC19EB559A5C1195F568B313C2675092E6DF58FF399C42CAB63" + "63AA033691CB8CE06699E701F2B92597CB8FC23516E9F40CE75B7BC1E0520A5A" + "3895EB7D8D474009A0CB0ADC2DF476B5164112C3B600B6776DAB49B20381A401" + "4691652A3C3161AAC6616CFAA265638C6C665A8454F36780B789CFA35D2AF49E" + "6D5F482BFA3C864B0EF29E18D2EFFF92DB1876A22076AB1AAC0A7393ED9E5A48"), + m: hex!("034FF14A56249C2521D4279EBA3D04931CC892BBC45002B5B33D9F0188ACBAF6"), + k: hex!("46C200F3F6EE8E11D47653801E3482241CB783B9D794EB116A4BDA085AEB6BB7"), + c: &hex!("8D4E2CB39FFDE4311AEEDB2338BF58CE11FADABDC9813A321930F46756DD13A8" + "E7919FAC4F59CC9F8B91C833B3B3F91ADC6F9FBDBDE2F7DAE8841BE5238B9850" + "A5EEBE675DDEF42A9314F690595D51523E8117F22266034F09B77D991EE57580" + "2AFE446374EB3D9E1BEB8F25049C6EFA96327366C024CDFBE8DC27EF56492C90" + "409E87139C6088488E17B82D1556C25131ACEE7DAFFE2D437CEC3441BBBBAB80" + "C4BF177E653AE0831C9B4CEB70505727D63C4D474FEDC52019BE411C9A43B871" + "70F5893F06ECD8D782063DF893A1B682246D1C64F8F5A8C6FCDF07927F4D5B7A" + "397FBCBD075045DF2C4A36F5304C95F44AF927AE9166420B39448794F5B3C352" + "27C3C9DF925602A1AC98F851AADB65C93FDD6327AED8AE4129724436A33AA08A" + "A56608855FF80AAA42ACA4562B2D78DBBD2F91AEF251566B8C6F98213784C99D" + "D7D71F495564C908501E35E3BFBB675CCB66635287CB6466E6E38EA8AB11CE7E" + "C60BED8620B3DCD6943D1279A41F93A87FA359E513C81DE918DA88322B1B0881" + "40E074BE39BC17E3C51AB719DF6E426D64FF94B8662B9DD26A32A3C3687BF929" + "4C537A2268F9DED380CC8A0F1127EE5A322B4DF24D87FBCE76F560B037C659B6" + "FB15C156071AEDC26EF11140DE88D08D463EA0EAF080A0B2E627D9FF1D56C502" + "335524269727A032DACD16543ADA8342CD6CB40E7228592C3574D982E0B9145E" + "B865DB2EE7810726A916B837CA4F14C2CB9E951BDE76BE16B8B1CDC2EECDC069" + "49B8BEB11786B8F25F4C9AFA5597CEB1D85FC9B9C91DC61966F396091E54C96C" + "97A4300E99FD9F752C0BEF5D88CAFBDCB3993FCF6C7A8C5519FCECB6A79117E9" + "B521680197D8A91AB75F1814DBC58075EF4F07987ABC56A75DA4416EDB9D6F3D" + "771AD340D5CBCFC0E571FA70AAC1C7DBBB5F5C5E1D8B1036F5A6FCFD0625AB5B" + "BDA571839C5835DD6979778F59D348684FA6CFC2A62535B47FAD7F97B5218872" + "D52DCACE9D3C1B11628D352AD821900F44E14B647F6BFA70F646B5C7AF531317" + "7A10954944229153A449FCF89A6263BDBF8556E981E5D6251340F9F43C669203" + "0FB9605BB99F33E96F06D1E4E6ABBE65E14696D530F1B525FFF87D54C1AC2F5E" + "964D46EE37F4045B54E6098F76B28EAF69E998888D25E021A538FD1956A7FC30" + "AE83F8BA9947F864FD59731A6FBB402AF2990E1ED2D56BF62AA6CEAE6F769D2D" + "0C6C313D7AAF974E69DC02CC4318B9457B8CC40656AB7B6134DE3F9801CE0196" + "99CE855EBE9C6C02FD08506F004A4EED2CA166C954C7DB8810700CA671EF372A" + "290B00E1BFBB97E3E674D3DCCC57CE59F465B1488FF76F6239008BE3E761EF9C" + "113DF0107B8EEAE3FEBA55B35E4C1DA3B6C87A8D20110E1CD771CCBC30DFF761" + "E603D488E55B853AAE7DAADF2A007B8393DF08AF534F9F53A73757BABE21C864" + "26CF058ECA817EF237BFC58AC298FBF2A1481C4D12DCF1B737FD639769A2531E" + "F931A362A44456EE2CA48598B46259FCC977076C59FA4E2954E9967DA45DA7CB" + "F78633EC59C463FE48A83B801A54DB3FEAB445A357E418B0653F2940B2B71381" + "B2DF9ECF8100848E2912F4BD503AF075AAAF36C136A413C95BE2F25A6D291976" + "CD66A27643537E35E1DF89B1E494B36B08F3D0196CD7E90BA5BB21009F37A843" + "199E08DD95CA4948C533CB263B5D405AF2FA119981A8536EB71C88226C41534C" + "2687BF1EED3475E8488BDE909A93D4DB55B6E834B5E7860AA98FD8BCB13AB077" + "B7BFD75B35FA393E93E3BFB4B9BA1DAA7465FD5B23A5B4CD1716D4BDF7B8D557" + "4B156DB87D8DE1E526C97F8EB287BD97EEEEEF074DBCB2C4DB51A4EFF1FA7FFF" + "328A572D7270017108ACE2ED25093DA535C7A26D3B912AA57FB322E53BB222E9" + "4E7CF68CD8A21AD7C06A4AF978ED1DEB10E3F2412AC6543C182068EFFBD87F31" + "765F5AE681EE8B2E9AEB5BC940A94EC0EEF5BEF74874169EABECF1512565C51E" + "A58721DD3AF1690365DB22E1877F2A5C01723F69B7725277AE4E9EFACD3AFA5A" + "DCAF385777E7CE10F956B4642C6FC1C97808993EFD994CA65C75F459AC5872F8" + "2488C57FB7AF9AB969D5E369C16D0B2BF7800B938D6784C7F64D0C55CA779465" + "4938949E14217055D34101F9417D370A8ADD72FC0B5766EC1D8ADDD702334A2A" + "C27709C5AC5AE5601DBA952BE258D9336DF3E0F65878A858613258FB5E47941B"), + }, +]; + +const DEC_VECTORS: [DecapsulateVector; 3] = [ + DecapsulateVector { + dk: &hex!("174313EFA93520E28A7076C888096E02B0BDD86830497B61FDEAB6209C6CF71C" + "625C4680775C3477581C427A6FE1B0356EAB048BCA434F83B542C8B860010696" + "A57299BB262268891FFC72142CA1A866185CA82D05406695BA57D4C930F9C17D" + "6223523CF5A4F2A433A364459AC0ACDE7254481329288B1BE187CC25219F48C2" + "443C532199859355320D04F0B80DE969F169A3D2BA3411B4ADBC01B66271824C" + "D9543C78BA4804AE81F3AF00336C5CC3698354C0E01873A2A17D6A95A312689A" + "99DC89084150A8D52BB31C3FF3D4215FA3C4111B401992866E513E5128A20ED9" + "5FDEE61485DC937E099D76F79B92734DC4CBB9A7A413FEA6285BC0C27C961E47" + "D1983644C4BF913D72F4B030D34738427263E87AB4C0B7DF0B72CA8AA0BAA67B" + "079939D587801D60C87A20405E5C52603C072FDB63E2E1C2A95CC26F5ABEF608" + "8333800886D093CA01A76F57005E053569542E0A076B98736D4D39B00FC1653F" + "BC2D12EA32A94B9B92C68BA4B68A4E7B370A23B03FE8221639B01244806C2706" + "7A58031DB80D2D03661A017BB46BB3711ACB568A4FABEBAFC5FA06F7CA0E4D96" + "2E3170CB11C0A8D18A09CE27A6A9763E123885450224DE07CC17546C17951FDE" + "476E083583EF10BF76A98AFFF9B12DB5401CD3673495392D741291C3AA78420C" + "8A7CB5FFE65012997C4DA4322EA90B5014B5B4D0180100247047341E4C24B96B" + "8D7C0020524B7C1D66C3E08CB299EB4EC6FA0EE8EA05FD430F57605E892B232D" + "2047CA9B4ECAD9BDD09C9951196916525D1EC921B6E3CE0EE692EBA728B4DB10" + "F3381FBF584ABB7B6A9210C7C424CE4A369370CB48D608634ABA0BFF91C5620A" + "1189D0CA97421D423429FB663952DC1231B4362B7162FE3A42111C91D76A964C" + "B4154194209EDBAA1F481BD126C325D15678E39BCCE4C704EA487246648A6C6C" + "2540B5F680A35EE2824246450A7293F21A90CFD14EFAF78FA3D7322251C641A5" + "0E95BB5EC5CA0B60E89D7C18B7A44A0FAFB4BCADE9B588D1B7FCF12BA1E1084D" + "56B197EA90A79A3D83927A2307603BC211C0830CB7062C04254824575B226CAD" + "9A27C2A45519AE39546467690485498A320AD56993B15A9D22C6191446CB40AA" + "7547401681DCC7E36596B10C07FA2A20B43C4B0124401F8A0E744878C7296623" + "C7395B6994D18C4787A289DBB05CB1827451D83F072904537594F515CA101799" + "1620A33E096EE0DC091AE4CA960603B101B5B4E23E9A5B65E1F6C2A8CC893413" + "83B706725ED5B3485769181B8F76439C05636A0C3436FFBA8B86A5306FA111F6" + "FC71EB779B25707CFAE0A6DA7B0AD5D94B10F21E4FCA92893B9FFE7321076340" + "1377837A10CA9625346C42ADC705BD92DB3426D926CE4B5EC24A5CDF27CB91E5" + "A7E7164D1BDC99D75679FBC93A58F647DAC1086CE931BC089233E9487E0867BC" + "58472B01BF2895C323B64DBE4A17A9E841B053CADB5C76D035724C321BBC1366" + "6F0A35DFDA0721E8987623256A994D95FA1C05F57C1E15A30C4A0C8318A0D83C" + "410C362862E817DD6ABBAA4BBE75B736CCCBB4AF2A188402BD4CE59793200886" + "2865332562F324C7A424151FB59D0AE1821F2864C7E698127AAD92C33B313988" + "C29A09E260449BCA7BEE360862314E47519EF3918DDDE403E7B92AC9908F93C6" + "369CC5C47B8CB1DC3A3479C762F62A18FE05A9B0645A5311A01828723AEB51FA" + "505E96B29E3D2B6E5B1327DE3A61AB0C50BE0124B64B33314B32D6122510E464" + "45857AA0E2C4B0D256955620A8681D1E555126D00509E35BF59683DDAA40E82C" + "519B855852C366CB54452BF910B001692330345708653F511800B10E009D9F7D" + "10A53B8B30BF13B06F254EC8A6BA539700F6358DE0463A019540C9873F3F4680" + "E2113A7CCC55FF754D85AA67E9E55F887424E0B2625682A5DDA218F03C3C10A2" + "46CDB0CC91D19D8F024DB9B1415F50ACD8F65DE2787B9103C575B687765572CF" + "FA59026C2BCEE77423BCAFD3054BF8E2713FB85B0BF6A46E716152F5C9A3011E" + "C90114C76B01516799BD5911415B704544077F188806755EEC4131E55556DB90" + "3F4284C1F90086FF431B68F51F629812F320B55F219D72A1928F38C9A1EC823B" + "A198BA9ABBACF62902B3CA0AFC95EA8AC303FB8BDD29BB9D18A03BA44E58B1B0" + "B85A2A1662E6A31DA7545511A478A18177889061EF76631264239ADEBD04A8C5" + "2B72E2B1F3A2DFBBD8C054E70CC2A742E7B7D417DFED314422187DE1B2954481" + "195755EC04BB7671C4331446BBE8952514905321A2176E935B5420C0D5EA4465"), + k: hex!("224B9C051213EF46549243796532282973FA7CF97E8913C339C1940AC17E05E0"), + c: &hex!("84A188A072E4D4F449A4BE170274DD2A5F3E356E95B96E40AD3FF1455E36C6A7" + "1E909DD2C0DFF8AD2C9F503BAC9065716248083BDA40CECB38E3B3058BAF51A7" + "572384FF8406A8136A4FC6D912A54B2EB5B9D598FB689E72ED3DEFD2FF8355ED" + "9E9CCA53E82C0886E094C592C392311F04FEC68F9A1C531CF3419030892B5BDC" + "ACEEF6A0E7F1BD44903F49DE8E37B02BA3FC5121D99F8CC3040F66832F77021B" + "4CA35F7A4825038936564CA2E673FF9CC0519C25F6A52D87EDD965B2464AA365" + "D2BF068B72FC68B65E88515E2C832BBDB27D61BF512B5FC2D8590FB35F49500C" + "AFE70E7D0776B5C4E4503A7189ADBAFF5D5B515CC68B2F81D993C6D7FA7D3D1D" + "90EBFF51DA3FBBB4430E5BBEDBCA8DA078DCE8EC815B168BFC09AB4A20678870" + "F4868B1FAE28D209C75368A799317DFA08C2B651FAC72DCA2A1B4CBB75E873F1" + "5C51B6D0B5E6F5E60E2AF6C40D2CABCBF3588F44BCEA6D72D359F40F9CF5E0EC" + "40A5215E5ACEEAF0DA00D923D4CEFF5C3A3AB1E46C754F4AE052C2BC49FDB452" + "1AE44DF634D56E433DAD3DF3C07115406FF8BFD0D7C93B4941D0F09213C1681C" + "FD5C8663DF02041A3CBD162F5C4D80CB1DC7D4A501AD06FE96EB348B6E331C82" + "96FE904EB97C087456328D703B85BDAC2FB43C728D0B05FC54B8C155C010EF0D" + "B14CC668D1B1BC727AF8864076736B898BABA1C81DCA2053F58587D3C4E33C69" + "4A264BE2897E7D2EEFADDA9FF88D70BF3731F1228CB3E131EB0CB76FDBD2CCB1" + "CBC18D1450AC7A16349E7129CAB720D5CB70B56E855E8305DCDA730BBD0EA33E" + "F0815D02190BB98E30F73BF7789CDD673C613B0C57CB2EF32E670A98D2D63067" + "0773C59D8A6A2CFCFF1C7CA1BB55C17A32CB65A2EA19C7B8E295C6898CF32FEE" + "1DEB01472BE76C3A78CB242EDFE21D961FCB85C3CF6CEE218986C1BD932BF97B" + "C6DECAABF8C62940C0A58E87C6EDDCD74B7F715D8C22520546239F3AAA10A435" + "820103B4E3295311D992C9C8771A3CE849868F36F31214F9639C028F4A5F4945" + "F2BEC9585077BF2F637D2549F8348C00ECBF19C470DF255EFF6232813429F853"), + }, + DecapsulateVector { + dk: &hex!("3456859BF707E672AC712B7E70F5427574597502B81DE8931C92A9C0D22A8E17" + "73CB87472205A31C32206BA4BCF42259533CB3A19C0200860244A6C3F6921845" + "B0A05850187A4310B3D5223AAAA0C79B9BBCFCCB3F751214EB0CFAC1A29ED884" + "8A5A49BA84BA68E6B6F5057D493105FF38A9F44B4E7F6CBE7D216408F7B48605" + "B270B253B001A5401C0C9127CC185B1B0CF92B99FBA0D95A295F873515520C86" + "321B8C966C837AAB34B2BFFAB2A2A4301B356B26CDC4563802901B4762F28428" + "1A382E5F762BEF47B519A81A108657EBE962BE120B5FB3B9ED338CCF47B3A039" + "52A16633F6E6B534E6B63D05706EFA0F94C03A2B856AE551422F9011F2589A41" + "B96A2CD213C6999B09E91FF423CB106A1A920B84B811469497154223987F005C" + "72F8AF388B090C639F8C774FC5A294C74A212C91A86C328AEBEA558AB43F8B87" + "3534FA2EF9E66CEF3C52CD471AB78375E745B9D0AA65D2278B9275AE5348B16C" + "F62AC8065734E4BD77B80CCF897605EB76F485AF8A0B466557A83C0292CCF903" + "EE7AA57C3B51AD660189B86139E380425B31A92689DF2431BFA7B69EAB172745" + "1B29DA8B8BF851E1BC2D3A63134CA9663C57AEC6985CEBD56DB0447B136B017A" + "974761C3C67D33772F9964E5434D643504332A3027294A078C599CB29163109C" + "E3B56CE698B4D3F59E2956A1F03A4B955593F2D2457FFAAE9624A0711045B3F5" + "5292F20CC9D0CD791A21597B0F2CD980F3510F0B0239022000D735586EE6A73F" + "3A3DCBD6BD1A85C86512ABF3C51CE00A0331F65360462C022329597A81C3F92F" + "C17938C9138F4111387979C28F0334F90119221374DAB045929B49E43A9646A2" + "43F4464DAF811AB00630C75961BCD4AF5D99115A3749191BA8FD41CE0B3C89A6" + "95B4BB85064FD3AF95C9B4AEE09AC7B0CC69ECA36A004B6CD662A6D32795053E" + "F0A03ADA3B98BFE3B46A79723E3A45AB3C31950669AD77072062CC3B504DF133" + "4FD6909EAC7915F1D5AD16639F5FB564416454259134D565882CB381CBA58B76" + "880767B50AC1B85795D7268433B371230ED4C72F99AB1AD1E595A459CF0A2334" + "AA1463ADE4BDC9249605381857BB98095B41132946CA2457DFAA9149582AA199" + "27B63689E2929AA41027BEF4921970BAD4A55490D91ABE251DEF4552CA880341" + "06A02CE4B058F8B59624B67E063BF178B015E4281EB114A2BC2454943A4B4647" + "122C42CBEA4E94154FD3E4B791F6290B782994206853D67000A633F320A8A374" + "CA5D4038F9CA4244DCB02E9A84E1F7C8A821132B32B9A840557B347806653017" + "24BA2606681D945E34D7CF941B8963CAA1001A491B8B2E43570E9AB95C0A57C5" + "03F0AB960B4856D0251574710FE5CB474284FC1049AA2A7B03694A1C763E99DA" + "C6AD0BA8038B138A64432E349116A031E8C792781751BA473CBDF55720005ABD" + "AA13D50182F0E633776BB0675C40472BAD1F9672769183D0CCC810BC25A85732" + "20569F6AC4BAC22A1354D8B36C0580D0E5299E629C506CC7655546FF27810C97" + "B51BA056BBF86ED9CB7C0A537F72D0CF9AD2C231E29EBF553F613CBB15B3721A" + "20077E505FD390CB19F6488A107DEE1CAC58AB7034BA690300219595B3695C12" + "34E8B57E33C8D3A048454A616DF3C9B56A6FF2026AF997725FC95579043BAE93" + "99B6790D637B4FA820B0B2D2CAB607BAF6A372734C31EE0026F3C076D14A8E3E" + "E66AAD8BBBCCEB9DC70C7B6BB0BB76C200C231601CA0873EC8710F4B18D57290" + "B033727C601EDB71C2B0F0C21D553E0E7A4F77716839C7C8448ABB9F66A54E8A" + "4B08A79D9A392CA1270031388BAD56217E32AEF55411974906A245C00712B3CB" + "B1170685193FE25ACD7AC13D32073F3879A5D78375F0052CF79175BAB46D2237" + "0597BD06789EDD0711CC4243507A02B4FAADBB62250CC997AE0327AEB00DEB52" + "9192A64B1096A86B19674D0B0AF05C4AAE178C2C9A6442E94ED0A56033A11EE4" + "2632C0B4AA51D42150790F41062B77253C25BA4DE559761F0A90068389728BC9" + "77F70CF7BCCFBD883DF13C79F5F2C34312CB1D5A55D78C1B242096A8C0593CFB" + "2753460BD30ABA306C74173995748385D00B3670E61324D87DE8A14450DC4937" + "68777FF0CE6810937A711229561A5EF2BB69861074E00BD93266E4B86269E18E" + "EA2CAACB60A1358636CD7A7CA6BB682130241784B101EA5BFD6C3A0715862161" + "4736F6996D5A4E14963A12D836E533A0C8912DB7E11685A4A53D8285F08750DF" + "F66DA27C23B97542DEFB99E470ACD5E647C940CB57301B43CC3E68E64E28B067" + "70695EF609265E06C60F22CB875849E62BAB88CC10ECF622C379CB54F13D8B2B" + "AC902B9AB02BB330B45AC8B741C2647AC45B5BF48A6D3FE039986CC940C60A94" + "E66CF644531016A5272450824314B5662A0A909ABFB46FD27BAED3ABA8259361" + "596882B08B2AC7233930FC3786738ED2F81EE638C45C3B9CFD1951DB5BCC1445" + "C2C1625D57D57B53904B6A1AB681580755E89FA79775A657CD62B4426304BC0C" + "711E2807A2C9E852D4B4359EE6B53E4675F523C90782572DC7368FB400C328C7" + "0FC846B5E98A4330BBB627BDD784B4DAF0B1F645944942B4C2B6225C8B31E989" + "545522BA6F10396034CB1CA745977844D570894C611A5608A757416D6DE59963" + "C32798C493EFD2264C231910E9A30090CA7B5384F231B89BA68A238190EF1A2A" + "43CB01703470A0F061A70738944BCD9B7004F24797AECB88B1091CFED0590B04" + "15453C39B6EC45B66305FAEA6B55A4B7967505FE3862A267ADBFE05B9181A065" + "01893391650EAAA4A6D16853349276F98E0F44CD726615C61C16713094D8AB09" + "3CAC71F2803E7D39109EF5009C9C2CDAF7B7A6B37A33A49881F4BB5D7245A14C" + "5042280C76A84E63F49D0D619D46D723BAA747A3BA90A6FB637A9A1DC02268FD" + "5C043D18CBA1528AC8E225C1F923D1CC84F2E78E25DC3CCE9353C9DAC2AD726A" + "79F64940801DD5701EFBDCB80A98A25993CD7F80591320B63172718647B976A9" + "8A771686F0120A053B0C4474604305890FECAF23475DDCC11BC08A9C5F592ABB" + "1A153DB1B883C0507EB68F78E0A14DEBBFEEC621E10A69B6DAAFAA916B539533" + "E508007C4188CE05C862D101D4DB1DF3C4502B8C8AE1457488A36EAD2665BFAC" + "B321760281DB9CA72C7614363404A0A8EABC058A23A346875FA96BB18AC2CCF0" + "93B8A855673811CED47CBE1EE81D2CF07E43FC4872090853743108865F02C561" + "2AA87166707EE90FFD5B8021F0AA016E5DBCD91F57B3562D3A2BCFA20A4C0301" + "0B8AA144E6482804B474FEC1F5E138BE632A3B9C82483DC6890A13B1E8EE6AF7" + "14EC5EFAC3B1976B29DADB605B14D3732B5DE118596516858117E2634C4EA0CC"), + k: hex!("BD7256B242F404869D662F80BF677A16C0C6FC1568CCA5B64582A01A6A142D71"), + c: &hex!("DFA6B9D72A63B420B89DDE50F7E0D56ECF876BFEF991FCE91C8D286FA6EABAC1" + "730FD87741FE4AD717B282A21E235A55C3757D88D4CE62F414EB77EB9D357EE2" + "9D00087BF8110E5BBBC7C90419072EAE044BF7E183D43A94B2632AA14649619B" + "70649521BC19370942EF70F36C34C8C23591EE0CA71A12D279E0F52D39ED0F91" + "3F8C262621FB242E680DEB307B0749C6B393A8EF66F8B04AAFA877B951AB93F5" + "98B4B2FAB04F88AC803984FF37E3FE74F3A616D5314EB3A826F874F8ECD3A564" + "7D04942A57EFC09638470DC0A9DF40B317571D3984A78CF7D11751090722B305" + "9E07591CC4A2ED9BA0DCE99BE9E5EE5DB8D698CDEB5814759BA977C90079CF2A" + "FDE478069C513A60091A3A5D0111E22DE06CB145C14E22A214CB278C8152B068" + "1BCAFF54D552B54A671C0DFEF775E7C54FEFC4853868C955971ABDAC2A76292C" + "CCD4FD1C706B7D3614159673E9D7B29A2D3F63363129E7A21E803A460F2714E3" + "E25922780AF38257CD1495ACD1E01980638DF58A153DAB07EFB5C7E78ADACF63" + "1956D69CCDA070459568BD9D11A2934BCF1643BC99468238910B1F742EBB3C03" + "D39FD45CFB85BA309E29DD9B5CD560819EC729FCAC8B9D725E3E8ABEDE4B5298" + "A8658EE3F781B0CE683CBB7335CD57EFE2204A8F197446D7314CDBF4C5D08CCC" + "41F80857CC9571FBFB906060F7E17C8CEF0F274AFF83E393B15F2F9589A13AF4" + "BC78E16CDDE62361D63B8DC903B70C01A43419CD2052150BD28719F61FF31F4A" + "9BEC4DDBCEC1F8FB2EFBF37DFFFA4C7FECA8CE6D626BFDA16EE708D9206814A2" + "EF988525615D4AC9BE608C4B03ABEE95B32A5DB74A96119A7E159AF99CD98E88" + "EAF09F0D780E7C7E814B8E88B4F4E15FA54995D0ECBAD3EF046A4947F3E8B9E7" + "44241489B806FE9401E78BAFC8E882E9D6D0700F720C0024E7DA49061C5D18A6" + "2074040ABC0003200ED465231797930A2E2AA501F64862DDA13014A99F9D3270" + "AA907EEB3FDBFF291600DF1F6B39684B11E396B70D86F90492E82B09BA25607B" + "0C286FBC070182AC76FA7C859AAFEA87016AED22C3605A2789A1D439FD8D9333" + "42DAB745A3E550E7D77C01A6234BDA7D6BB19D495E6560FCE8396FC3C6E088ED" + "60F5F2771416EA3BE5BE472B6404906C91E71D9A8672F390083655AB7D0EC6ED" + "FE86789CE20BE2EA90CA5CC31416FB24CBAF94DA1468FE696BCDF5247CF117CB" + "E9334076CA6896B2F6A016B1F7C73728807898D8B199756C2B0AA2457E1B4F77" + "54C4576CE5645614EA15C1AE28B094EB217C7A7A41239576CBDA380EE6878343" + "2730AD5EBE7F51D6BE7FB02AB37BE0C96AAC9F3C790A18D159E6BABA71EC88C1" + "10FD84C336DF630F271CF79328B6C879DF7CDE0F70712220B1FBB9ACB48248D9" + "1F0E2B6E3BE40C2B221E626E7E330D9D83CC0668F7308591E14C7D72B841A6F0" + "5F3FDC139EECC1536765650B55A9CEC6BBF54CCEC5C3AC9A0E39F48F237BD4C6" + "60CB1A8D250BB6C8C010FEC34CC3D91599271C7531330F12A3E44FAFD905D2C6"), + }, + DecapsulateVector { + dk: &hex!("0FEA26C4A544A514444A971B5C5A825827C09D42469E59344CF2AC06A28D33E9" + "A012CAA3717B2C3B290A0715821109C4CCEAC49F341DADD377D42A37261916AC" + "7BB9E41C096CA8181CF58350573F605684A1BCA53D88257453C535165C4ED72A" + "9FF05645712901F66C10D04F5EB4A2EC3772E9498E9DC44BBDAB71BBDBBCFC85" + "B801363089EA60EFE586E1E2180C38B2E7B4A63ED607490BC5BA7A58AC3B1C0E" + "43967200C7980290EBF411828439EE8C8E6129B258E13D127CB15A00CB7B468D" + "4023B5097B9B2E509B50E890B63B4707487961A29E18656DD2D09E6A3B8843E2" + "843CB4854F18116E717DDB0355A75135B2026A752C8E7FF18E0F4A391CA37F5B" + "2BCC88C999B4E47750C46547EC076AC21530722CFAF9679961C98688C3562B17" + "CC808146A12572C9B5FF151AAB54410901840E26423987C5E0D28EF2EA53EAE5" + "951E62AC7BD518B9830A4DBCCE6A936591EA8EF275078A0973852A4D130495D0" + "0B3F21851599901CFDF9368344C810422FFEA08AEDCB1A7FD3625F26B034812F" + "A307AB2C20945465546D31A341A4013D8189B4F50FE860A668DAC7B103441E96" + "1FCEB0C5B1F34DF2E598C6D8CF60B864150C703D2BBEAC9B001AA2108147AE6B" + "8AAE2C7791DBE956C1F9B2047A1576094387064C3A801B0D89C996A5CFA3B012" + "C14438B9F3530C0C5FA9389F10FB3EF1E2013338415F7B1DB411ADF91C73B645" + "6B68AB7CFC7BC929E44E58EB34CA10AE31F03B2C3BA6CCA27EB35CB1379A130A" + "AC87E3B875CFE253AF03C4BD783F18C5A2F8492BBF7C56875598B1B63FE6CB06" + "94D0480CA1C8F8867C11B8BF33A32C20B79F9CA486858610B19783BEF784BF6B" + "0F858C1A791130DA6957F212234EC98679814BE839BF110B45C1C883ECDC3DB3" + "F822A4F7C125566ED1663568C8413CD01C22467AD5201A0ADC763435A2CB05CD" + "C47072A94370F5B434F75C078B415993E854DDE17BBF86C0C6C9A3248532D9C2" + "139EF3C75A9BC693781060DCAE2FFA58D9CC548F19C1CE5364880C7FB50CC7BE" + "405312D6CC94037618F388C490AF8F61B9B4044CF75A5CD71A15853B5FD6224C" + "6B9590E58501D2814200C919F283CC2B49AD8BFA5BAAA2977F03823F609EFB24" + "26F936C30287097BD6B7BDC67862858883DB5954080429B9CD02CA96BC1CCBDB" + "5121DFF805B0824AEE999E2BBB2D82353E6D3A300792781058C56EF7098AB358" + "4EA0621E20337D3A975D93CF32586D6A71A2C4BBB202B853FF09C407B43B1C19" + "B1C4CCB821482DDD27378177AA7F6178497C3FBA797153848C5D0B1F40B54E9D" + "5193904A303F725F0CCC66C6CCB158850605346DB42B877DD9CEA5F69C12B221" + "C7EC5100F76587B9834BC0C641538F83E85BB3090DBAFBCB0B7118FF7C97E952" + "63157041F8AC4052D0403500CC4F689455974CEB5B076790A050E0B3F6772A77" + "67541FF6B67B2A1D5407820647688F360A2B01473767712909B227658BE64578" + "48C440757168061888589CB05A999E55496791B11AF2066BB8CA746051C4680A" + "0BC07382412AB8B8A319DBC794DDC694BFDB813F80B58B72218DD64DFCDBA1AB" + "48A94F7A8DCA9266CD15A42D9BA5FB6767A955526C050DE2598B112A2B103AA2" + "D1F0606FE68A55191EF53B302F7C1922C301CEEA989A62134090A86076776FA4" + "4627B7316386576A678175B218E6F482B52BC6027BBEB34698B9802FD67634C1" + "A94DD4C5CD49EC6E2D665F727781D1EC10AAF66AD8279B9BF24C99E875EC9435" + "2D9605FA30CB3D8B2686B03971A760B3053B34346D0D71B44D8B7D2EA61A5C10" + "A933D38BA48336711174546147D44B2914F85689D9C1BF0037C7F7377CD930CF" + "F60F84B0A2005D3EFE55C7311B1B6132768B5290D836B82BC443C32B4FEC9602" + "19DB2132F7990AD684A3729F3D1A2CEA3A1FE4B12675C489EF33198F01A10680" + "6EFCE8921DC46E971C0A0A564AF9E56CA727A7641C568C95AA5956910B288429" + "F80EE7226E9DC4067E34944F06926D44B2CF8764F713593B4429F82B8FCC6077" + "98916B815B9098330EC334290DB8C04B083DF3CA10CE3575073028E994A25BE7" + "2878492FE1B696BA5CB1A773193A3B28A4F440AE582DC7C24FE7451D6676232B" + "B961C5040C9E5201AAF3CD4DE40AD5A9578AF52810B593E9815E23F63F564061" + "A48407213AA1B0908F4B174F86D573FA04386498BE68398E8D720D278111D8B1" + "7303602A96E35F56FB25173C4F4A03CA2AC9BF79DCAB764BCE4410401E1013E6" + "528CCC5113358577DA8375E02343108C2924D2551E5CC5A1B04DEF88324D854F" + "C92C4ADF7C2301337E4520BFC365566F66092E367AE60612744653C1EB47F082" + "0951A2A14C425909340D8727188EAA08E48678984876D0008DAE99015B3663FD" + "CB725741530BC3895B11620CE3B417A320E18813B99C235AC06F55600F983882" + "BFF00236107B5042545B6B775868AEFB79B595596902C69B9ECA3D358C61FEE0" + "36D218AC43BA3F52C06A8F881A7ED70386142CBAC5CC04FCC31E16277651CE2D" + "CC5014F6BA5A915C1338834EF474B6715913BC7A4E593C688766ADD70698B37E" + "06E53915F385388C25C4265E1CB44FE3D019D121AE4C32434F37B0A4CB69C7CC" + "95707350C3493D0FB11CD4D09F29DC56C07BC8EB0BD0082B41442145663C21AB" + "433467B95EC2478423C18BF2EC703EFBA28CDABD42B7B833150D6DA25EB00A83" + "28902E2D089B55D69AAD9A94D818264C54B04D614D147A30ABFC03D9929D96BA" + "7F81865DA353C454BA7AA7881AB974C1B8F0831E79C4418664E953A54DE93213" + "697281341D37F508E8CBAE3D8185054567DEFC8E3BBCAA4247907C483B8F1B84" + "B324C1A7CA8442DB6B7B128C8313BE1FE25791209B864A3E1A618D56D710D6F3" + "BF559510167C464C6B9B8BC490B8E03925D03D0EEB5D78179428BB80D3FB1488" + "40709C41147A686FC9BCBDCDF7C7EA7C30FB640FF05B7539ABAB70892908E93C" + "C9C347F8AC889E56468A135B99754738E15F4E677DF375BF1B43606A2C47380B" + "10A0C14C28583C83311A2854B2A9931FD66086C10749F334577FD70B51B95060" + "075199319B3F7CB5B237302C370A23175E4E013C56281BAFE2BE9F825A3066AB" + "8BBA5793E21E7A48978CF60C091B1F80C0C23814A30F7760601ACEABB1215200" + "940FFA152272096D458D00DD039F236B2727B588C62204E79C451681DFE410EE" + "C42B74945AEC0313A391942AE1B122174DBE59AB1E390CD64941436C75A9323C" + "69A641880870FBB280B3B37B3BD982B82955620B0783B82E8961A4043BC7F66C" + "0EF25A5ED15326F8816E5EA4167EE8BF6666451D315B2C751441172C27830026" + "8261C78C6F0C46562779B3A1196F87835F79FCB7E0CBA15336CC83E156C50228" + "87A80986B49C1B576594A23142624ABF524822418C6101905262806572494D37" + "53C06281E7F17E0D796CD7767FDCE901FE1712A00A3D36EB423E29868846932A" + "9431B8CA660FC1975E23A75B4A51DE1069D3A59F6EEB2A5CE72A8916B5E86347" + "6E6AC572929F2C29BC5627BA994163CED35AB7031C00490724555ACDE613AEB4" + "C3E99981C62B5DC6A9B35BA79220243689E0594996857C045D67193D9E411B4F" + "F39D0F8C3C0A70ADB72A7021E36D64FB294D932B24E1A2BC0BC41C4AA3B5EC3C" + "F0E672DE140F484733FD82BF082934B540A635C44898E8AB8E0645705AA58171" + "8B4132C427927FAE75BF9616A5424C2020EBC5CFC1BC0ED1653AE5005A175418" + "1620B7F06D716313033BB72A40647ADB2E667370F2C74FDB94420DA48DD1379D" + "BA59AA22F857E231C5C083290066C548761BDF385F2F85817B212066D39F03B7" + "7F8EF41219E4BFB9C12E4FC98800571D223AA92A32C7A3C2A7CF9C995AE0A7B5" + "9391FE9A4F0D633BFB798C34B72BBA6A9F16C4132E88B570758BD551C91BD2AD" + "EB53A72AC6AA03689DD64B035709A8AF468543CB1736DBC9C72B529E70596D18" + "B19CA68E617A147C189D283A77688CAF94DA5A0E9B63181A40BBE7BD4168A24D" + "274319A993BCEA8ABF505FE862129692B5BDE849F36AC92F7171E53859313604" + "EAC10BE2786FF385B9C718154818772FA7B899C04EFD18A8019A79B6F64D5B9A" + "2C55E784CB47CA294856689AA6A70CC27B6C20D4D1C729C409D0B925C40C30C0" + "777815077749488B8DF0390695ABDB048C7CE1853602A54D153CF2A51617847B" + "11E63C4C761966D5AD93350DBADA4A15C124BD808871993FC775B6E410C38659" + "0F730A8EC9475EEE915039E91B6FE425B90668C6AC5258B7AF103B9F5E230B71" + "9BBB09871DC1621517BA2A839C96AAA6440A875EAC90B298D61BD3F3AC89B405" + "DB394232686A2BE0F3C75F15E64E61F070791EB4BB97B7019825F117C7D73A12" + "FD3DCC22D581B0E41B786374A461EA0D88DAA89B659F0DC82443423515B633B0" + "05C958EC26561B6DB818F4B8CB2E28990E748417587FEC38A1284BBB4FF9E478"), + k: hex!("C61F73D2BFB18594E1BA5D3B58B4C934206D3A6F8EC91395AB7779C61FA1DD6F"), + c: &hex!("61FF1A8B6117EF118328E88B3227993014DCD075B8A1A7F9801893EEE6405BB9" + "60B6B7F6A1A27518A3409139A48B859681CC758F2BCC3EEFB04394A375A5CD71" + "316490938ABFD194B20BCD31B3980261C9ED69BF9B1D7D7659A8040DB1E25D2B" + "A6F703486624B73CACDCA27DB0F7E2408C9448E38873280F5E9950D7CCE252A6" + "47580C19904FAD62AEC300BC8E38F05948B63BAD5CE7C90E40C4BC65117761F5" + "F8868F8025D6CEB2C5DF60DE38C3232922087EFCF2CD95DE5E87B6888B88C86C" + "C78315585B2CC688A71B477BFA388DC2334DFA8AA95503D5397E2AE0352903EA" + "6A0AE8B649A914B3525FE58F564BF19CC09F54E105D19BD81054E57001F70BBD" + "D7719449687E9A53B16CA5366A19105A8BA08589AD08DF1300EF4F923BA9E762" + "A82FB09B76E125F2F274D617BF30EAB465ECF24D3707AD300D9AFC1CF1DC40EE" + "7D4EEA6D150E6F0A31DB9F8F92BA8EEEB35D7445589B046BA79EFE231106CF0A" + "75712AB392724C53EFF9F5733BEE0D6A44D0B6F515D0F5E40B1B1E17E67AED3C" + "81D00AC468A28F8453D4B0DA809E57D823F28D61ED0B59A08C622972D99179DA" + "8636C45F1CE8F6252AC86D91B5E92997014E3F5089E68BC52CED5DAE6D5B175F" + "E2D61928465059724C835902D7612CDB69CDAC664FC1C9CB11203A8C7B71486E" + "97B7D1BC6A98F493DCBEC8E629558ED361091293D1B5D2096CEB9FC7AFEE71DB" + "7CCFE482B68A196429FF04D15903E7A75C7BB5F622C36971694559FF07DFAA79" + "E41C362B22643CD39BD9E1D3D6C2A306B5F1102C266EEE67DCDACF36697A836F" + "203838EC110308C90A3D01570CB3668ABA50340E40F54CFA6A9E8862532F5F19" + "848AA11FD34FC86B7FCB1637F4E5A1D03AFCE44124E4E460B84C63496ADED558" + "01DF2517A90AB061C8E63AB6B14BE1694D6F389DD85F5639C5783AFCA0146E6A" + "1EB0C40563C137010DB60BBC3D6374D6F3A892DEBC064701C64BECCB8E2C33B7" + "40CC7ED49D108A8C4656818DF5F7D91EAAA446AC6CCDE30C6D3D1BF66E4E3B7B" + "6B81E3CB17227F80DB0096E6BE7D859C09713749FCA21530FE1A716EBE325504" + "319BD0EA2A7D7713607CB679B0A0B2268D493B67C0481872177FFD2593F3ACF6" + "91CEE99A36ECA722579EFAA59ACC59EF8CEA9108E620B06056C19D3C1EB91E86" + "34DE4957706DFA8F9D0A9E0CD4094F6B95A83F118A513EBFE5E99AEB88A268E0" + "097FCC3C7AE250B681933BBC2A8F5381F94D156434A87E9EE37E78C27A0CDAEE" + "A9814BCB43DF538DBE628C802C1A94E0CDDCD0CD5A0F8220DA97C2383936A339" + "19FCDC11D70ED4437DD2D7C73CD0C3BB90CA7070228FE8D64A1C9D56E6B34830" + "EF300B5AA6EC6C78A5425AE6F7AD0EFDD527CF0AF8E09B56E495BE66F665C64B" + "0A42C5C4B24680480AD2E5C11D991F7E3DA759AEC802F176DDF11EF71469DC13" + "B3A3E03699519858AC6FC65C27FA4CEFDA09C82E8F958E018DD5255CA2F628E0" + "DA7391ABED6D37705528AB22EC71DC8836D7FD4645944703A51CC74D297092FC" + "E139E8976F8BE9C5F86390B74D401A8C8153112201133D0C517C6CE7A38C0860" + "69CE3971F1AD28F3E5D01B56A480B417A016AEA46394CDF764812918D8AB0501" + "D5D18CE13FBD3DE91F504215CCD0E2D17B7E963C867F6F132114E36459FC5AF7" + "CEE99B789673E524131F7DC71360951A997A9CE50DD5FAFC4521144441C06BB4" + "1C79E8ED53285D137D54F325A6C2F2EF74E34C0F877A614CE45DC0AEDDF95A0E" + "2E4EDAE29AF411C9CC2AF95C9EA9A94A7961C8246E654FA28F3D568D5FEE9335" + "2C2E0D60CCAF5B00090AB6E7A53AA06A8CD3737EBF1B65D625BCF220F74DE22D" + "9871EFC376BF082D4B872A303C32427A0C98BECF58959C9F9E2E887DBC42AAB1" + "656AD15637A6A8F4BF9634095491F8C99242913891437E6C5B50A213DDE80D21" + "96BE12C3937FE3239BF6759ABB8C1C9466F42FBD53894AE52FB533321429FCE4" + "FEC1DB352C49583A7D817EAF62000888ECB0EBFFEF69FF8E590CFA25BEAB2160" + "5B635ABC2CA23680789725CF700F553C88352F31616154873D18B6C6EB519FC6" + "39B070FD67F86AAB62349DBFFA89F93051A7C7B7BD161FCD73672CEEF59A9BB7" + "F571EABE2570C5BF31ECAA1F9CA7A9C6D31EA5FB7C979CDD2613897E7D1503FB" + "0C19ADDCFB3A63E2185FC4101838DA66CCE2D3D9FFB47746C2003EDD86C2F8C3"), + }, +]; diff --git a/ml-kem/tests/vectors.rs b/ml-kem/tests/vectors.rs new file mode 100644 index 0000000..e2bc970 --- /dev/null +++ b/ml-kem/tests/vectors.rs @@ -0,0 +1,60 @@ +use generic_array::GenericArray; +use ml_kem::*; + +pub struct GenerateVector { + pub z: [u8; 32], + pub d: [u8; 32], + pub dk: &'static [u8], + pub ek: &'static [u8], +} + +impl GenerateVector { + pub fn verify(&self) { + let d = GenericArray::from_slice(&self.d); + let z = GenericArray::from_slice(&self.z); + let (dk, ek) = K::generate_deterministic(d, z); + assert_eq!(dk.as_bytes().as_slice(), self.dk); + assert_eq!(ek.as_bytes().as_slice(), self.ek); + + let dk_bytes = Encoded::::from_slice(self.dk); + assert_eq!(dk, K::DecapsulationKey::from_bytes(dk_bytes)); + + let ek_bytes = Encoded::::from_slice(self.ek); + assert_eq!(ek, K::EncapsulationKey::from_bytes(ek_bytes)); + } +} + +pub struct EncapsulateVector { + pub ek: &'static [u8], + pub m: [u8; 32], + pub k: [u8; 32], + pub c: &'static [u8], +} + +impl EncapsulateVector { + pub fn verify(&self) { + let m = GenericArray::from_slice(&self.m); + let ek_bytes = Encoded::::from_slice(self.ek); + let ek = K::EncapsulationKey::from_bytes(ek_bytes); + let (k, c) = ek.encapsulate_deterministic(m); + assert_eq!(k.as_slice(), &self.k); + assert_eq!(c.as_slice(), self.c); + } +} + +pub struct DecapsulateVector { + pub dk: &'static [u8], + pub c: &'static [u8], + pub k: [u8; 32], +} + +impl DecapsulateVector { + pub fn verify(&self) { + let dk_bytes = Encoded::::from_slice(self.dk); + let dk = K::DecapsulationKey::from_bytes(dk_bytes); + + let c_bytes = Ciphertext::::from_slice(self.c); + let k = dk.decapsulate(c_bytes); + assert_eq!(k.as_slice(), &self.k); + } +} From 60c48903023b290018db0de8838f68a0c06e897c Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Thu, 8 Feb 2024 12:44:10 -0500 Subject: [PATCH 02/10] Pre-compute constants using const fn --- ml-kem/src/algebra.rs | 382 ++++++------------------------------------ ml-kem/src/param.rs | 54 +++++- 2 files changed, 96 insertions(+), 340 deletions(-) diff --git a/ml-kem/src/algebra.rs b/ml-kem/src/algebra.rs index fedbddd..e4e367f 100644 --- a/ml-kem/src/algebra.rs +++ b/ml-kem/src/algebra.rs @@ -24,7 +24,6 @@ impl FieldElement { const BARRETT_MULTIPLIER: u64 = (1 << Self::BARRETT_SHIFT) / Self::Q64; // A fast modular reduction for small numbers `x < 2*q` - // TODO(RLB) Replace with constant-time version (~3-5% performance hit) fn small_reduce(x: u16) -> u16 { if x < Self::Q { x @@ -123,39 +122,6 @@ impl Mul<&Polynomial> for FieldElement { } impl Polynomial { - // A lookup table for CBD sampling: - // - // ONES[i][j] = i.count_ones() - j.count_ones() mod Q - // - // fn main() { - // let q = 3329; - // let ones: [[u32; 8]; 8] = array::from_fn(|i| { - // array::from_fn(|j| { - // let x = i.count_ones(); - // let y = j.count_ones(); - // if y <= x { - // x - y - // } else { - // x + q - y - // } - // }) - // }); - // println!("ones = {:?}", ones); - // } - // - // XXX(RLB): Empirically, this is not much faster than just doing the calculation inline. But - // it avoids having any branching, and pre-computing seems aesthetically nice. - const ONES: [[u16; 8]; 8] = [ - [0, 3328, 3328, 3327, 3328, 3327, 3327, 3326], - [1, 0, 0, 3328, 0, 3328, 3328, 3327], - [1, 0, 0, 3328, 0, 3328, 3328, 3327], - [2, 1, 1, 0, 1, 0, 0, 3328], - [1, 0, 0, 3328, 0, 3328, 3328, 3327], - [2, 1, 1, 0, 1, 0, 0, 3328], - [2, 1, 1, 0, 1, 0, 0, 3328], - [3, 2, 2, 1, 2, 1, 1, 0], - ]; - // Algorithm 7. SamplePolyCBD_eta(B) // // To avoid all the bitwise manipulation in the algorithm as written, we reuse the logic in @@ -166,12 +132,7 @@ impl Polynomial { Eta: CbdSamplingSize, { let vals: Polynomial = Encode::::decode(B); - Self(vals.0.map(|val| { - // TODO Flatten ONES table to avoid the need for these operations - let x = val.0 & ((1 << Eta::USIZE) - 1); - let y = val.0 >> Eta::USIZE; - FieldElement(Self::ONES[x as usize][y as usize]) - })) + Self(vals.0.map(|val| Eta::ONES[val.0 as usize])) } } @@ -286,298 +247,55 @@ impl NttPolynomial { // * ZETA_POW_BITREV[i] = zeta^{BitRev_7(i)} // * GAMMA[i] = zeta^{2 BitRev_7(i) + 1} // -// The below code was used to generate these tables: -// -// fn bit_reverse(mut x: usize) -> usize { -// let mut out = 0; -// for _i in 0..7 { -// out = (out << 1) + (x % 2); -// x = x >> 1; -// } -// out -// } -// -// fn generate_zeta_gamma() { -// const ZETA: FieldElement = FieldElement(17); -// -// let mut pow = [FieldElement(0); 128]; -// pow[0] = FieldElement(1); -// for i in 1..128 { -// pow[i] = pow[i - 1] * ZETA; -// } -// -// let mut zeta_pow_bitrev = [FieldElement(0); 128]; -// for i in 0..128 { -// zeta_pow_bitrev[i] = pow[bit_reverse(i)]; -// } -// println!("ZETA_POW_BITREV: {:?}", zeta_pow_bitrev); -// -// let mut gamma = [FieldElement(0); 128]; -// for i in 0..128 { -// gamma[i as usize] = (zeta_pow_bitrev[i] * zeta_pow_bitrev[i]) * ZETA; -// } -// println!("GAMMA: {:?}", gamma); -// } -const ZETA_POW_BITREV: [FieldElement; 128] = [ - FieldElement(1), - FieldElement(1729), - FieldElement(2580), - FieldElement(3289), - FieldElement(2642), - FieldElement(630), - FieldElement(1897), - FieldElement(848), - FieldElement(1062), - FieldElement(1919), - FieldElement(193), - FieldElement(797), - FieldElement(2786), - FieldElement(3260), - FieldElement(569), - FieldElement(1746), - FieldElement(296), - FieldElement(2447), - FieldElement(1339), - FieldElement(1476), - FieldElement(3046), - FieldElement(56), - FieldElement(2240), - FieldElement(1333), - FieldElement(1426), - FieldElement(2094), - FieldElement(535), - FieldElement(2882), - FieldElement(2393), - FieldElement(2879), - FieldElement(1974), - FieldElement(821), - FieldElement(289), - FieldElement(331), - FieldElement(3253), - FieldElement(1756), - FieldElement(1197), - FieldElement(2304), - FieldElement(2277), - FieldElement(2055), - FieldElement(650), - FieldElement(1977), - FieldElement(2513), - FieldElement(632), - FieldElement(2865), - FieldElement(33), - FieldElement(1320), - FieldElement(1915), - FieldElement(2319), - FieldElement(1435), - FieldElement(807), - FieldElement(452), - FieldElement(1438), - FieldElement(2868), - FieldElement(1534), - FieldElement(2402), - FieldElement(2647), - FieldElement(2617), - FieldElement(1481), - FieldElement(648), - FieldElement(2474), - FieldElement(3110), - FieldElement(1227), - FieldElement(910), - FieldElement(17), - FieldElement(2761), - FieldElement(583), - FieldElement(2649), - FieldElement(1637), - FieldElement(723), - FieldElement(2288), - FieldElement(1100), - FieldElement(1409), - FieldElement(2662), - FieldElement(3281), - FieldElement(233), - FieldElement(756), - FieldElement(2156), - FieldElement(3015), - FieldElement(3050), - FieldElement(1703), - FieldElement(1651), - FieldElement(2789), - FieldElement(1789), - FieldElement(1847), - FieldElement(952), - FieldElement(1461), - FieldElement(2687), - FieldElement(939), - FieldElement(2308), - FieldElement(2437), - FieldElement(2388), - FieldElement(733), - FieldElement(2337), - FieldElement(268), - FieldElement(641), - FieldElement(1584), - FieldElement(2298), - FieldElement(2037), - FieldElement(3220), - FieldElement(375), - FieldElement(2549), - FieldElement(2090), - FieldElement(1645), - FieldElement(1063), - FieldElement(319), - FieldElement(2773), - FieldElement(757), - FieldElement(2099), - FieldElement(561), - FieldElement(2466), - FieldElement(2594), - FieldElement(2804), - FieldElement(1092), - FieldElement(403), - FieldElement(1026), - FieldElement(1143), - FieldElement(2150), - FieldElement(2775), - FieldElement(886), - FieldElement(1722), - FieldElement(1212), - FieldElement(1874), - FieldElement(1029), - FieldElement(2110), - FieldElement(2935), - FieldElement(885), - FieldElement(2154), -]; -const GAMMA: [FieldElement; 128] = [ - FieldElement(17), - FieldElement(3312), - FieldElement(2761), - FieldElement(568), - FieldElement(583), - FieldElement(2746), - FieldElement(2649), - FieldElement(680), - FieldElement(1637), - FieldElement(1692), - FieldElement(723), - FieldElement(2606), - FieldElement(2288), - FieldElement(1041), - FieldElement(1100), - FieldElement(2229), - FieldElement(1409), - FieldElement(1920), - FieldElement(2662), - FieldElement(667), - FieldElement(3281), - FieldElement(48), - FieldElement(233), - FieldElement(3096), - FieldElement(756), - FieldElement(2573), - FieldElement(2156), - FieldElement(1173), - FieldElement(3015), - FieldElement(314), - FieldElement(3050), - FieldElement(279), - FieldElement(1703), - FieldElement(1626), - FieldElement(1651), - FieldElement(1678), - FieldElement(2789), - FieldElement(540), - FieldElement(1789), - FieldElement(1540), - FieldElement(1847), - FieldElement(1482), - FieldElement(952), - FieldElement(2377), - FieldElement(1461), - FieldElement(1868), - FieldElement(2687), - FieldElement(642), - FieldElement(939), - FieldElement(2390), - FieldElement(2308), - FieldElement(1021), - FieldElement(2437), - FieldElement(892), - FieldElement(2388), - FieldElement(941), - FieldElement(733), - FieldElement(2596), - FieldElement(2337), - FieldElement(992), - FieldElement(268), - FieldElement(3061), - FieldElement(641), - FieldElement(2688), - FieldElement(1584), - FieldElement(1745), - FieldElement(2298), - FieldElement(1031), - FieldElement(2037), - FieldElement(1292), - FieldElement(3220), - FieldElement(109), - FieldElement(375), - FieldElement(2954), - FieldElement(2549), - FieldElement(780), - FieldElement(2090), - FieldElement(1239), - FieldElement(1645), - FieldElement(1684), - FieldElement(1063), - FieldElement(2266), - FieldElement(319), - FieldElement(3010), - FieldElement(2773), - FieldElement(556), - FieldElement(757), - FieldElement(2572), - FieldElement(2099), - FieldElement(1230), - FieldElement(561), - FieldElement(2768), - FieldElement(2466), - FieldElement(863), - FieldElement(2594), - FieldElement(735), - FieldElement(2804), - FieldElement(525), - FieldElement(1092), - FieldElement(2237), - FieldElement(403), - FieldElement(2926), - FieldElement(1026), - FieldElement(2303), - FieldElement(1143), - FieldElement(2186), - FieldElement(2150), - FieldElement(1179), - FieldElement(2775), - FieldElement(554), - FieldElement(886), - FieldElement(2443), - FieldElement(1722), - FieldElement(1607), - FieldElement(1212), - FieldElement(2117), - FieldElement(1874), - FieldElement(1455), - FieldElement(1029), - FieldElement(2300), - FieldElement(2110), - FieldElement(1219), - FieldElement(2935), - FieldElement(394), - FieldElement(885), - FieldElement(2444), - FieldElement(2154), - FieldElement(1175), -]; +// Note that the const environment here imposes some annoying conditions. Because operator +// overloading can't be const, we have to do all the reductions here manually. Because `for` loops +// are forbidden in `const` functions, we do them manually with `while` loops. +#[allow(clippy::cast_possible_truncation)] +const ZETA_POW_BITREV: [FieldElement; 128] = { + const ZETA: u64 = 17; + const fn bitrev7(x: usize) -> usize { + ((x >> 6) % 2) + | (((x >> 5) % 2) << 1) + | (((x >> 4) % 2) << 2) + | (((x >> 3) % 2) << 3) + | (((x >> 2) % 2) << 4) + | (((x >> 1) % 2) << 5) + | ((x % 2) << 6) + } + + // Compute the powers of zeta + let mut pow = [FieldElement(0); 128]; + let mut i = 0; + let mut curr = 1u64; + while i < 128 { + pow[i] = FieldElement(curr as u16); + i += 1; + curr = (curr * ZETA) % FieldElement::Q64; + } + + // Reorder the powers according to bitrev7 + let mut pow_bitrev = [FieldElement(0); 128]; + let mut i = 0; + while i < 128 { + pow_bitrev[i] = pow[bitrev7(i)]; + i += 1; + } + pow_bitrev +}; + +#[allow(clippy::cast_possible_truncation)] +const GAMMA: [FieldElement; 128] = { + const ZETA: u64 = 17; + let mut gamma = [FieldElement(0); 128]; + let mut i = 0; + while i < 128 { + let zpr = ZETA_POW_BITREV[i].0 as u64; + let g = (zpr * zpr * ZETA) % FieldElement::Q64; + gamma[i] = FieldElement(g as u16); + i += 1; + } + gamma +}; // Algorithm 10. MuliplyNTTs impl Mul<&NttPolynomial> for &NttPolynomial { diff --git a/ml-kem/src/param.rs b/ml-kem/src/param.rs index 81841fc..103b660 100644 --- a/ml-kem/src/param.rs +++ b/ml-kem/src/param.rs @@ -14,15 +14,16 @@ use core::fmt::Debug; use core::ops::{Add, Div, Mul, Rem, Sub}; use generic_array::{ sequence::{Concat, Split}, - GenericArray, + GenericArray, IntoArrayLength, }; use typenum::{ - consts::{U0, U12, U2, U32, U384, U8}, + consts::{U0, U12, U16, U2, U3, U32, U384, U4, U6, U64, U8}, operator_aliases::{Gcf, Prod, Quot, Sum}, type_operators::Gcd, + Const, }; -use crate::algebra::NttVector; +use crate::algebra::{FieldElement, NttVector}; use crate::encode::Encode; use crate::util::{Flatten, Unflatten, B32}; @@ -101,15 +102,52 @@ where /// An integer that describes a bit length to be used in CBD sampling pub trait CbdSamplingSize: ArrayLength { type SampleSize: EncodingSize; + type OnesSize: ArrayLength; + const ONES: GenericArray; } -impl CbdSamplingSize for Eta +// To speed up CBD sampling, we pre-compute all the bit-manipulations: +// +// * Splitting a sampled integer into two parts +// * Counting the ones in each part +// * Taking the difference between the two counts mod q +// +// We have to allow the use of `as` here because we can't use our nice Truncate trait, because +// const functions don't support traits. +#[allow(clippy::cast_possible_truncation)] +const fn ones_array() -> GenericArray where - Eta: ArrayLength, - U2: Mul, - Prod: EncodingSize, + U: ArrayLength, + Const: IntoArrayLength, { - type SampleSize = Prod; + let max = 1 << B; + let mut out = [FieldElement(0); N]; + let mut x = 0usize; + while x < max { + let mut y = 0usize; + while y < max { + let x_ones = x.count_ones() as u16; + let y_ones = y.count_ones() as u16; + let i = x + (y << B); + out[i] = FieldElement((x_ones + FieldElement::Q - y_ones) % FieldElement::Q); + + y += 1; + } + x += 1; + } + GenericArray::from_array(out) +} + +impl CbdSamplingSize for U2 { + type SampleSize = U4; + type OnesSize = U16; + const ONES: GenericArray = ones_array::<2, 16, U16>(); +} + +impl CbdSamplingSize for U3 { + type SampleSize = U6; + type OnesSize = U64; + const ONES: GenericArray = ones_array::<3, 64, U64>(); } /// A `ParameterSet` captures the parameters that describe a particular instance of ML-KEM. There From 9fe6e2f445360600a0803f0f3ea705b1363ca143 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Thu, 8 Feb 2024 13:58:49 -0500 Subject: [PATCH 03/10] Use constant-time selection in decapsulation --- ml-kem/src/kem.rs | 26 +++++++++++++++++++------- ml-kem/src/lib.rs | 2 +- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/ml-kem/src/kem.rs b/ml-kem/src/kem.rs index 2f373a0..6b8eb3e 100644 --- a/ml-kem/src/kem.rs +++ b/ml-kem/src/kem.rs @@ -5,7 +5,7 @@ use typenum::U32; use crate::crypto::{rand, G, H, J}; use crate::param::{DecapsulationKeySize, EncapsulationKeySize, EncodedCiphertext, KemParams}; use crate::pke::{DecryptionKey, EncryptionKey}; -use crate::util::{FastClone, B32}; +use crate::util::{FastClone, FunctionalArray, B32}; use crate::{Encoded, EncodedSizeUser}; /// A shared key resulting from an ML-KEM transaction @@ -53,6 +53,13 @@ where } } +// 0xff if x == y, 0x00 otherwise +fn constant_time_eq(x: u8, y: u8) -> u8 { + let diff = x ^ y; + let is_zero = !diff & diff.wrapping_sub(1); + 0u8.wrapping_sub(is_zero >> 7) +} + impl

crate::DecapsulationKey> for DecapsulationKey

where P: KemParams, @@ -63,12 +70,17 @@ where let Kbar = J(&[self.z.as_slice(), ct.as_ref()]); let cp = self.ek.ek_pke.encrypt(&mp, &rp); - // TODO(RLB) Replace with constant-time select - if cp == *ct { - Kp - } else { - Kbar - } + // Constant-time version of: + // + // if cp == *ct { + // Kp + // } else { + // Kbar + // } + let equal = cp + .zip(ct, |&x, &y| constant_time_eq(x, y)) + .fold(|x, y| x & y); + Kp.zip(&Kbar, |x, y| (equal & x) | (!equal & y)) } } diff --git a/ml-kem/src/lib.rs b/ml-kem/src/lib.rs index 5805bc1..b9083f9 100644 --- a/ml-kem/src/lib.rs +++ b/ml-kem/src/lib.rs @@ -23,7 +23,7 @@ //! //! [RFC 9180]: https://www.rfc-editor.org/info/rfc9180 -#![no_std] +//#![no_std] #![warn(clippy::pedantic)] // Be pedantic by default #![allow(non_snake_case)] // Allow notation matching the spec #![allow(clippy::similar_names)] // Allow dk_pke/ek_pke From d7ceaf9e7a071470e4b72b05d28489eda054d7a6 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Thu, 8 Feb 2024 14:01:10 -0500 Subject: [PATCH 04/10] Make determinstic feature non-default --- ml-kem/Cargo.toml | 3 +-- ml-kem/tests/nist.rs | 2 +- ml-kem/tests/vectors.rs | 2 ++ 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ml-kem/Cargo.toml b/ml-kem/Cargo.toml index 30f7728..beadae1 100644 --- a/ml-kem/Cargo.toml +++ b/ml-kem/Cargo.toml @@ -3,9 +3,8 @@ name = "ml-kem" version = "0.1.0" edition = "2021" -# TODO(RLB) Selectively enable "deterministic" only for tests and benchmarks [features] -default = ["deterministic"] +default = [] deterministic = [] # Expose deterministic generation and encapsulation functions [dependencies] diff --git a/ml-kem/tests/nist.rs b/ml-kem/tests/nist.rs index f4c45b5..89c95f9 100644 --- a/ml-kem/tests/nist.rs +++ b/ml-kem/tests/nist.rs @@ -1,4 +1,4 @@ -// TODO(RLB) Disable compilation if not deterministic +#![cfg(feature = "deterministic")] mod vectors; diff --git a/ml-kem/tests/vectors.rs b/ml-kem/tests/vectors.rs index e2bc970..a5255c0 100644 --- a/ml-kem/tests/vectors.rs +++ b/ml-kem/tests/vectors.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "deterministic")] + use generic_array::GenericArray; use ml_kem::*; From 20089d779d2066ef2ebadf4747051cb4962fb5b2 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Thu, 15 Feb 2024 17:16:38 -0500 Subject: [PATCH 05/10] Remove redundant .gitignore --- ml-kem/.gitignore | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 ml-kem/.gitignore diff --git a/ml-kem/.gitignore b/ml-kem/.gitignore deleted file mode 100644 index 4fffb2f..0000000 --- a/ml-kem/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target -/Cargo.lock From 3bad71837dc91182295dee2987d0f7774fc9a795 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Fri, 1 Mar 2024 11:19:31 -0500 Subject: [PATCH 06/10] Add workspace-level Cargo.toml --- Cargo.toml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Cargo.toml diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f35cc86 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +resolver = "2" +members = [ + "ml-kem", +] From 4e5404ec6f5d63709f99ca82a701b1a38abe06a7 Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Fri, 1 Mar 2024 12:07:02 -0500 Subject: [PATCH 07/10] Convert from generic-array to hybrid-array --- ml-kem/Cargo.toml | 2 +- ml-kem/benches/mlkem.rs | 6 +- ml-kem/src/algebra.rs | 190 ++++++++++++++++++++++------------------ ml-kem/src/compress.rs | 6 +- ml-kem/src/crypto.rs | 26 +++--- ml-kem/src/encode.rs | 138 +++++++++++++++++------------ ml-kem/src/kem.rs | 23 +++-- ml-kem/src/lib.rs | 20 +++-- ml-kem/src/param.rs | 128 +++++++++++++-------------- ml-kem/src/pke.rs | 8 +- ml-kem/src/util.rs | 114 ++++++++++++------------ 11 files changed, 353 insertions(+), 308 deletions(-) diff --git a/ml-kem/Cargo.toml b/ml-kem/Cargo.toml index beadae1..de1bb8e 100644 --- a/ml-kem/Cargo.toml +++ b/ml-kem/Cargo.toml @@ -11,8 +11,8 @@ deterministic = [] # Expose deterministic generation and encapsulation functions const-default = "1.0.0" crypto-common = { version = "0.1.6", features = ["getrandom"] } generic-array = { version = "1.0.0", features = ["const-default"] } +hybrid-array = { path = "../../hybrid-array" } sha3 = "0.10.8" -typenum = "1.17.0" [dev-dependencies] criterion = "0.5.1" diff --git a/ml-kem/benches/mlkem.rs b/ml-kem/benches/mlkem.rs index 39b15b8..7c8e8cb 100644 --- a/ml-kem/benches/mlkem.rs +++ b/ml-kem/benches/mlkem.rs @@ -1,10 +1,10 @@ use criterion::{criterion_group, criterion_main, Criterion}; use crypto_common::rand_core::CryptoRngCore; -use generic_array::{ArrayLength, GenericArray}; +use hybrid_array::{Array, ArraySize}; use ml_kem::*; -pub fn rand(rng: &mut impl CryptoRngCore) -> GenericArray { - let mut val: GenericArray = Default::default(); +pub fn rand(rng: &mut impl CryptoRngCore) -> Array { + let mut val = Array::::default(); rng.fill_bytes(&mut val); val } diff --git a/ml-kem/src/algebra.rs b/ml-kem/src/algebra.rs index e4e367f..5882cac 100644 --- a/ml-kem/src/algebra.rs +++ b/ml-kem/src/algebra.rs @@ -1,13 +1,11 @@ -use const_default::ConstDefault; use core::ops::{Add, Mul, Sub}; -use generic_array::{sequence::GenericSequence, GenericArray}; +use hybrid_array::{typenum::U256, Array}; use sha3::digest::XofReader; -use typenum::consts::U256; use crate::crypto::{PrfOutput, PRF, XOF}; use crate::encode::Encode; -use crate::param::{ArrayLength, CbdSamplingSize}; -use crate::util::{FastClone, FunctionalArray, Truncate, B32}; +use crate::param::{ArraySize, CbdSamplingSize}; +use crate::util::{Truncate, B32}; pub type Integer = u16; @@ -58,10 +56,6 @@ impl FieldElement { } } -impl ConstDefault for FieldElement { - const DEFAULT: Self = Self(0); -} - impl Add for FieldElement { type Output = Self; @@ -91,17 +85,19 @@ impl Mul for FieldElement { /// An element of the ring `R_q`, i.e., a polynomial over `Z_q` of degree 255 #[derive(Clone, Copy, Default, Debug, PartialEq)] -pub struct Polynomial(pub GenericArray); - -impl ConstDefault for Polynomial { - const DEFAULT: Self = Self(GenericArray::DEFAULT); -} +pub struct Polynomial(pub Array); impl Add<&Polynomial> for &Polynomial { type Output = Polynomial; fn add(self, rhs: &Polynomial) -> Polynomial { - Polynomial(self.0.zip(&rhs.0, |&x, &y| x + y)) + Polynomial( + self.0 + .iter() + .zip(rhs.0.iter()) + .map(|(&x, &y)| x + y) + .collect(), + ) } } @@ -109,7 +105,13 @@ impl Sub<&Polynomial> for &Polynomial { type Output = Polynomial; fn sub(self, rhs: &Polynomial) -> Polynomial { - Polynomial(self.0.zip(&rhs.0, |&x, &y| x - y)) + Polynomial( + self.0 + .iter() + .zip(rhs.0.iter()) + .map(|(&x, &y)| x - y) + .collect(), + ) } } @@ -117,7 +119,7 @@ impl Mul<&Polynomial> for FieldElement { type Output = Polynomial; fn mul(self, rhs: &Polynomial) -> Polynomial { - Polynomial(rhs.0.map(|&x| self * x)) + Polynomial(rhs.0.iter().map(|&x| self * x).collect()) } } @@ -132,28 +134,34 @@ impl Polynomial { Eta: CbdSamplingSize, { let vals: Polynomial = Encode::::decode(B); - Self(vals.0.map(|val| Eta::ONES[val.0 as usize])) + Self(vals.0.iter().map(|val| Eta::ONES[val.0 as usize]).collect()) } } /// A vector of polynomials of length `k` #[derive(Clone, Default, Debug, PartialEq)] -pub struct PolynomialVector(pub GenericArray); +pub struct PolynomialVector(pub Array); -impl Add> for PolynomialVector { +impl Add> for PolynomialVector { type Output = PolynomialVector; fn add(self, rhs: PolynomialVector) -> PolynomialVector { - PolynomialVector(self.0.zip(&rhs.0, |x, y| x + y)) + PolynomialVector( + self.0 + .iter() + .zip(rhs.0.iter()) + .map(|(x, y)| x + y) + .collect(), + ) } } -impl PolynomialVector { +impl PolynomialVector { pub fn sample_cbd(sigma: &B32, start_n: u8) -> Self where Eta: CbdSamplingSize, { - Self(GenericArray::generate(|i| { + Self(Array::from_fn(|i| { let N = start_n + i.truncate(); let prf_output = PRF::(sigma, N); Polynomial::sample_cbd::(&prf_output) @@ -163,17 +171,19 @@ impl PolynomialVector { /// An element of the ring `T_q`, i.e., a tuple of 128 elements of the direct sum components of `T_q`. #[derive(Clone, Default, Debug, PartialEq)] -pub struct NttPolynomial(pub GenericArray); - -impl ConstDefault for NttPolynomial { - const DEFAULT: Self = Self(GenericArray::DEFAULT); -} +pub struct NttPolynomial(pub Array); impl Add<&NttPolynomial> for &NttPolynomial { type Output = NttPolynomial; fn add(self, rhs: &NttPolynomial) -> NttPolynomial { - NttPolynomial(self.0.zip(&rhs.0, |&x, &y| x + y)) + NttPolynomial( + self.0 + .iter() + .zip(rhs.0.iter()) + .map(|(&x, &y)| x + y) + .collect(), + ) } } @@ -237,7 +247,7 @@ impl NttPolynomial { // Algorithm 6 SampleNTT(B) pub fn sample_uniform(B: &mut impl XofReader) -> Self { let mut reader = FieldElementReader::new(B); - Self(GenericArray::generate(|_| reader.next())) + Self(Array::from_fn(|_| reader.next())) } } @@ -302,7 +312,7 @@ impl Mul<&NttPolynomial> for &NttPolynomial { type Output = NttPolynomial; fn mul(self, rhs: &NttPolynomial) -> NttPolynomial { - let mut out = NttPolynomial(GenericArray::const_default()); + let mut out = NttPolynomial(Array::default()); for i in 0..128 { let (c0, c1) = FieldElement::base_case_multiply( @@ -321,14 +331,14 @@ impl Mul<&NttPolynomial> for &NttPolynomial { } } -impl From> for NttPolynomial { - fn from(f: GenericArray) -> NttPolynomial { +impl From> for NttPolynomial { + fn from(f: Array) -> NttPolynomial { NttPolynomial(f) } } -impl From for GenericArray { - fn from(f_hat: NttPolynomial) -> GenericArray { +impl From for Array { + fn from(f_hat: NttPolynomial) -> Array { f_hat.0 } } @@ -359,7 +369,7 @@ impl Polynomial { // Algorithm 9. NTT^{-1} impl NttPolynomial { pub fn ntt_inverse(&self) -> Polynomial { - let mut f: GenericArray = self.0.fast_clone(); + let mut f: Array = self.0.clone(); let mut k = 127; for len in [2, 4, 8, 16, 32, 64, 128] { @@ -381,15 +391,15 @@ impl NttPolynomial { /// A vector of K NTT-domain polynomials #[derive(Clone, Default, Debug, PartialEq)] -pub struct NttVector(pub GenericArray); +pub struct NttVector(pub Array); -impl NttVector { +impl NttVector { // Note the transpose here: Apparently the specification is incorrect, and the proper order // of indices is reversed. // // https://github.com/FiloSottile/mlkem768/blob/main/mlkem768.go#L110C4-L112C51 pub fn sample_uniform(rho: &B32, i: usize, transpose: bool) -> Self { - Self(GenericArray::generate(|j| { + Self(Array::from_fn(|j| { let (i, j) = if transpose { (i, j) } else { (j, i) }; let mut xof = XOF(rho, i.truncate(), j.truncate()); NttPolynomial::sample_uniform(&mut xof) @@ -397,57 +407,67 @@ impl NttVector { } } -impl Add<&NttVector> for &NttVector { +impl Add<&NttVector> for &NttVector { type Output = NttVector; fn add(self, rhs: &NttVector) -> NttVector { - NttVector(self.0.zip(&rhs.0, |x, y| x + y)) + NttVector( + self.0 + .iter() + .zip(rhs.0.iter()) + .map(|(x, y)| x + y) + .collect(), + ) } } -impl Mul<&NttVector> for &NttVector { +impl Mul<&NttVector> for &NttVector { type Output = NttPolynomial; fn mul(self, rhs: &NttVector) -> NttPolynomial { - self.0.zip(&rhs.0, |x, y| x * y).fold(|x, y| x + y) + self.0 + .iter() + .zip(rhs.0.iter()) + .map(|(x, y)| x * y) + .fold(NttPolynomial::default(), |x, y| &x + &y) } } -impl PolynomialVector { +impl PolynomialVector { pub fn ntt(&self) -> NttVector { - NttVector(self.0.map(Polynomial::ntt)) + NttVector(self.0.iter().map(Polynomial::ntt).collect()) } } -impl NttVector { +impl NttVector { pub fn ntt_inverse(&self) -> PolynomialVector { - PolynomialVector(self.0.map(NttPolynomial::ntt_inverse)) + PolynomialVector(self.0.iter().map(NttPolynomial::ntt_inverse).collect()) } } /// A K x K matrix of NTT-domain polynomials. Each vector represents a row of the matrix, so that /// multiplying on the right just requires iteration. #[derive(Clone, Default, Debug, PartialEq)] -pub struct NttMatrix(GenericArray, K>); +pub struct NttMatrix(Array, K>); -impl Mul<&NttVector> for &NttMatrix { +impl Mul<&NttVector> for &NttMatrix { type Output = NttVector; fn mul(self, rhs: &NttVector) -> NttVector { - NttVector(self.0.map(|x| x * rhs)) + NttVector(self.0.iter().map(|x| x * rhs).collect()) } } -impl NttMatrix { +impl NttMatrix { pub fn sample_uniform(rho: &B32, transpose: bool) -> Self { - Self(GenericArray::generate(|i| { + Self(Array::from_fn(|i| { NttVector::sample_uniform(rho, i, transpose) })) } pub fn transpose(&self) -> Self { - Self(GenericArray::generate(|i| { - NttVector(GenericArray::generate(|j| self.0[j].0[i].clone())) + Self(Array::from_fn(|i| { + NttVector(Array::from_fn(|j| self.0[j].0[i].clone())) })) } } @@ -456,15 +476,14 @@ impl NttMatrix { mod test { use super::*; use crate::util::Flatten; - use generic_array::arr; - use typenum::consts::{U2, U3, U8}; + use hybrid_array::typenum::{U2, U3, U8}; // Multiplication in R_q, modulo X^256 + 1 impl Mul<&Polynomial> for &Polynomial { type Output = Polynomial; fn mul(self, rhs: &Polynomial) -> Self::Output { - let mut out = Self::Output::DEFAULT; + let mut out = Self::Output::default(); for (i, x) in self.0.iter().enumerate() { for (j, y) in rhs.0.iter().enumerate() { let (sign, index) = if i + j < 256 { @@ -482,16 +501,16 @@ mod test { // A polynomial with only a scalar component, to make simple test cases fn const_ntt(x: Integer) -> NttPolynomial { - let mut p = Polynomial::DEFAULT; + let mut p = Polynomial::default(); p.0[0] = FieldElement(x); p.ntt() } #[test] fn polynomial_ops() { - let f = Polynomial(GenericArray::generate(|i| FieldElement(i as Integer))); - let g = Polynomial(GenericArray::generate(|i| FieldElement(2 * i as Integer))); - let sum = Polynomial(GenericArray::generate(|i| FieldElement(3 * i as Integer))); + let f = Polynomial(Array::from_fn(|i| FieldElement(i as Integer))); + let g = Polynomial(Array::from_fn(|i| FieldElement(2 * i as Integer))); + let sum = Polynomial(Array::from_fn(|i| FieldElement(3 * i as Integer))); assert_eq!((&f + &g), sum); assert_eq!((&sum - &g), f); assert_eq!(FieldElement(3) * &f, sum); @@ -499,8 +518,8 @@ mod test { #[test] fn ntt() { - let f = Polynomial(GenericArray::generate(|i| FieldElement(i as Integer))); - let g = Polynomial(GenericArray::generate(|i| FieldElement(2 * i as Integer))); + let f = Polynomial(Array::from_fn(|i| FieldElement(i as Integer))); + let g = Polynomial(Array::from_fn(|i| FieldElement(2 * i as Integer))); let f_hat = f.ntt(); let g_hat = g.ntt(); @@ -524,9 +543,9 @@ mod test { #[test] fn ntt_vector() { // Verify vector addition - let v1 = NttVector(arr![const_ntt(1), const_ntt(1), const_ntt(1)]); - let v2 = NttVector(arr![const_ntt(2), const_ntt(2), const_ntt(2)]); - let v3 = NttVector(arr![const_ntt(3), const_ntt(3), const_ntt(3)]); + let v1: NttVector = NttVector(Array([const_ntt(1), const_ntt(1), const_ntt(1)])); + let v2: NttVector = NttVector(Array([const_ntt(2), const_ntt(2), const_ntt(2)])); + let v3: NttVector = NttVector(Array([const_ntt(3), const_ntt(3), const_ntt(3)])); assert_eq!((&v1 + &v2), v3); // Verify dot product @@ -538,21 +557,21 @@ mod test { #[test] fn ntt_matrix() { // Verify matrix multiplication by a vector - let a = NttMatrix(arr![ - NttVector(arr![const_ntt(1), const_ntt(2), const_ntt(3)]), - NttVector(arr![const_ntt(4), const_ntt(5), const_ntt(6)]), - NttVector(arr![const_ntt(7), const_ntt(8), const_ntt(9)]), - ]); - let v_in = NttVector(arr![const_ntt(1), const_ntt(2), const_ntt(3)]); - let v_out = NttVector(arr![const_ntt(14), const_ntt(32), const_ntt(50)]); + let a: NttMatrix = NttMatrix(Array([ + NttVector(Array([const_ntt(1), const_ntt(2), const_ntt(3)])), + NttVector(Array([const_ntt(4), const_ntt(5), const_ntt(6)])), + NttVector(Array([const_ntt(7), const_ntt(8), const_ntt(9)])), + ])); + let v_in: NttVector = NttVector(Array([const_ntt(1), const_ntt(2), const_ntt(3)])); + let v_out: NttVector = NttVector(Array([const_ntt(14), const_ntt(32), const_ntt(50)])); assert_eq!(&a * &v_in, v_out); // Verify transpose - let aT = NttMatrix(arr![ - NttVector(arr![const_ntt(1), const_ntt(4), const_ntt(7)]), - NttVector(arr![const_ntt(2), const_ntt(5), const_ntt(8)]), - NttVector(arr![const_ntt(3), const_ntt(6), const_ntt(9)]), - ]); + let aT = NttMatrix(Array([ + NttVector(Array([const_ntt(1), const_ntt(4), const_ntt(7)])), + NttVector(Array([const_ntt(2), const_ntt(5), const_ntt(8)])), + NttVector(Array([const_ntt(3), const_ntt(6), const_ntt(9)])), + ])); assert_eq!(a.transpose(), aT); } @@ -643,12 +662,11 @@ mod test { // // Since Q ~= 2^11 and 256 == 2^8, we need 2^3 == 8 runs of 256 to get out of the bad // regime and get a meaningful measurement. - let rho = B32::const_default(); - let sample: GenericArray, U8> = - GenericArray::generate(|i| { - let mut xof = XOF(&rho, 0, i as u8); - NttPolynomial::sample_uniform(&mut xof).into() - }); + let rho = B32::default(); + let sample: Array, U8> = Array::from_fn(|i| { + let mut xof = XOF(&rho, 0, i as u8); + NttPolynomial::sample_uniform(&mut xof).into() + }); test_sample(&sample.flatten(), &UNIFORM); } @@ -656,13 +674,13 @@ mod test { #[test] fn sample_cbd() { // Eta = 2 - let sigma = B32::const_default(); + let sigma = B32::default(); let prf_output = PRF::(&sigma, 0); let sample = Polynomial::sample_cbd::(&prf_output).0; test_sample(&sample, &CBD2); // Eta = 3 - let sigma = B32::const_default(); + let sigma = B32::default(); let prf_output = PRF::(&sigma, 0); let sample = Polynomial::sample_cbd::(&prf_output).0; test_sample(&sample, &CBD3); diff --git a/ml-kem/src/compress.rs b/ml-kem/src/compress.rs index bb1986f..35cff76 100644 --- a/ml-kem/src/compress.rs +++ b/ml-kem/src/compress.rs @@ -1,5 +1,5 @@ use crate::algebra::{FieldElement, Integer, Polynomial, PolynomialVector}; -use crate::param::{ArrayLength, EncodingSize}; +use crate::param::{ArraySize, EncodingSize}; use crate::util::Truncate; // A convenience trait to allow us to associate some constants with a typenum @@ -62,7 +62,7 @@ impl Compress for Polynomial { } } -impl Compress for PolynomialVector { +impl Compress for PolynomialVector { fn compress(&mut self) -> &Self { for x in &mut self.0 { x.compress::(); @@ -83,7 +83,7 @@ impl Compress for PolynomialVector { #[cfg(test)] pub(crate) mod test { use super::*; - use typenum::consts::*; + use hybrid_array::typenum::{U1, U10, U11, U12, U4, U5, U6}; // Verify that the integer compression routine produces the same results as rounding with // floats. diff --git a/ml-kem/src/crypto.rs b/ml-kem/src/crypto.rs index 3007335..9d6c343 100644 --- a/ml-kem/src/crypto.rs +++ b/ml-kem/src/crypto.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] use crypto_common::rand_core::CryptoRngCore; -use generic_array::{ArrayLength, GenericArray}; +use hybrid_array::{Array, ArraySize}; use sha3::{ digest::{ExtendableOutput, Update, XofReader}, Digest, Sha3_256, Sha3_512, Shake128, Shake256, @@ -10,8 +10,8 @@ use sha3::{ use crate::param::{CbdSamplingSize, EncodedPolynomial}; use crate::util::B32; -pub fn rand(rng: &mut impl CryptoRngCore) -> GenericArray { - let mut val = GenericArray::default(); +pub fn rand(rng: &mut impl CryptoRngCore) -> Array { + let mut val = Array::default(); rng.fill_bytes(&mut val); val } @@ -23,8 +23,8 @@ pub fn G(inputs: &[impl AsRef<[u8]>]) -> (B32, B32) { } let out = h.finalize(); - let mut a = B32::const_default(); - let mut b = B32::const_default(); + let mut a = B32::default(); + let mut b = B32::default(); a.copy_from_slice(&out[..32]); b.copy_from_slice(&out[32..]); @@ -38,7 +38,7 @@ pub fn H(x: impl AsRef<[u8]>) -> B32 { // This odd conversion is needed because the `sha3` crate links against an old version of // the `generic-array` crate. It should be pretty cheap though, since there's only one // allocation / no copies. - let mut out = B32::const_default(); + let mut out = B32::default(); h.finalize_into(out.as_mut_slice().into()); out } @@ -137,7 +137,7 @@ pub fn XOF(rho: &B32, i: u8, j: u8) -> impl XofReader { mod test { use super::*; use hex_literal::hex; - use typenum::{U2, U3}; + use hybrid_array::typenum::{U2, U3}; #[test] fn g() { @@ -146,8 +146,8 @@ mod test { let (actualA, actualB) = G(&[msg1, msg2]); let expectedA = hex!("07dfced2a3a3feb3277cee1709818828ea6d2f42800152e9c312e848122231c2"); let expectedB = hex!("272969098a1bbd5a0a9844e2f89f206d8f7f4599e36aecaa4793af400fd880d8"); - assert_eq!(actualA, expectedA.into()); - assert_eq!(actualB, expectedB.into()); + assert_eq!(actualA, expectedA); + assert_eq!(actualB, expectedB); } #[test] @@ -155,7 +155,7 @@ mod test { let msg = "Input to an invocation of H".as_bytes(); let actual = H(msg); let expected = hex!("0ee3ce94213d7dd0069b24b8b15cdd0bcf8eb1c6b3c21c441dc6a19e979cc7eb"); - assert_eq!(actual, expected.into()); + assert_eq!(actual, expected); } #[test] @@ -164,7 +164,7 @@ mod test { let msg2 = "an invocation of J".as_bytes(); let actual = J(&[msg1, msg2]); let expected = hex!("a5292293d70c8eca049cbb475c48fabd625ed2b20785a18248504d3741196b52"); - assert_eq!(actual, expected.into()); + assert_eq!(actual, expected); } #[test] @@ -178,7 +178,7 @@ mod test { 568113c861516d91bed227638654fc7f872df205c113b8364091755b62284eec\ a6124f2cd4c1cdf598cb8324a4f373470a8f81ee618c75cc33f66facee01c213" ); - assert_eq!(actual, expected.into()); + assert_eq!(actual, expected); let s = B32::from_slice("Input s to an invocation of PRF3".as_bytes()); let b = b'b'; @@ -191,7 +191,7 @@ mod test { 67764b3fc6b37376453978b8f0caeb6b18c188c28ee8681e28339477e042d5a1\ b4a12deb1de8b9dad026b4e323e03973ffbe25dd511eed5460d22a9851cfc220" ); - assert_eq!(actual, expected.into()); + assert_eq!(actual, expected); } #[test] diff --git a/ml-kem/src/encode.rs b/ml-kem/src/encode.rs index cffb509..285e393 100644 --- a/ml-kem/src/encode.rs +++ b/ml-kem/src/encode.rs @@ -1,13 +1,15 @@ -use generic_array::GenericArray; -use typenum::{Unsigned, U256}; +use hybrid_array::{ + typenum::{Unsigned, U256}, + Array, +}; use crate::algebra::{ FieldElement, Integer, NttPolynomial, NttVector, Polynomial, PolynomialVector, }; -use crate::param::{ArrayLength, EncodedPolynomial, EncodingSize, VectorEncodingSize}; -use crate::util::{FunctionalArray, Truncate}; +use crate::param::{ArraySize, EncodedPolynomial, EncodingSize, VectorEncodingSize}; +use crate::util::Truncate; -type DecodedValue = GenericArray; +type DecodedValue = Array; // Algorithm 4 ByteEncode_d(F) // @@ -64,49 +66,54 @@ fn byte_decode(bytes: &EncodedPolynomial) -> DecodedValue { } pub trait Encode { - type EncodedSize: ArrayLength; - fn encode(&self) -> GenericArray; - fn decode(enc: &GenericArray) -> Self; + type EncodedSize: ArraySize; + fn encode(&self) -> Array; + fn decode(enc: &Array) -> Self; } impl Encode for Polynomial { type EncodedSize = D::EncodedPolynomialSize; - fn encode(&self) -> GenericArray { + fn encode(&self) -> Array { byte_encode::(&self.0) } - fn decode(enc: &GenericArray) -> Self { + fn decode(enc: &Array) -> Self { Self(byte_decode::(enc)) } } impl Encode for PolynomialVector where - K: ArrayLength, + K: ArraySize, D: VectorEncodingSize, { type EncodedSize = D::EncodedPolynomialVectorSize; - fn encode(&self) -> GenericArray { - let polys = self.0.map(|x| Encode::::encode(x)); + fn encode(&self) -> Array { + let polys = self.0.iter().map(|x| Encode::::encode(x)).collect(); >::flatten(polys) } - fn decode(enc: &GenericArray) -> Self { + fn decode(enc: &Array) -> Self { let unfold = >::unflatten(enc); - Self(unfold.map(|&x| >::decode(x))) + Self( + unfold + .iter() + .map(|&x| >::decode(x)) + .collect(), + ) } } impl Encode for NttPolynomial { type EncodedSize = D::EncodedPolynomialSize; - fn encode(&self) -> GenericArray { + fn encode(&self) -> Array { byte_encode::(&self.0) } - fn decode(enc: &GenericArray) -> Self { + fn decode(enc: &Array) -> Self { Self(byte_decode::(enc)) } } @@ -114,18 +121,23 @@ impl Encode for NttPolynomial { impl Encode for NttVector where D: VectorEncodingSize, - K: ArrayLength, + K: ArraySize, { type EncodedSize = D::EncodedPolynomialVectorSize; - fn encode(&self) -> GenericArray { - let polys = self.0.map(|x| Encode::::encode(x)); + fn encode(&self) -> Array { + let polys = self.0.iter().map(|x| Encode::::encode(x)).collect(); >::flatten(polys) } - fn decode(enc: &GenericArray) -> Self { + fn decode(enc: &Array) -> Self { let unfold = >::unflatten(enc); - Self(unfold.map(|&x| >::decode(x))) + Self( + unfold + .iter() + .map(|&x| >::decode(x)) + .collect(), + ) } } @@ -135,26 +147,27 @@ pub(crate) mod test { use core::cmp::PartialEq; use core::fmt::Debug; use core::ops::Rem; - use generic_array::{arr, sequence::GenericSequence, ArrayLength}; + use hybrid_array::typenum::{ + marker_traits::Zero, operator_aliases::Mod, U1, U10, U11, U12, U2, U3, U4, U5, U6, U8, + }; use rand::Rng; - use typenum::{consts::*, marker_traits::Zero, operator_aliases::Mod}; use crate::param::EncodedPolynomialVector; // A helper trait to construct larger arrays by repeating smaller ones - trait Repeat { - fn repeat(&self) -> GenericArray; + trait Repeat { + fn repeat(&self) -> Array; } - impl Repeat for GenericArray + impl Repeat for Array where - N: ArrayLength, + N: ArraySize, T: Clone, - D: ArrayLength + Rem, + D: ArraySize + Rem, Mod: Zero, { - fn repeat(&self) -> GenericArray { - GenericArray::generate(|i| self[i % N::USIZE].clone()) + fn repeat(&self) -> Array { + Array::from_fn(|i| self[i % N::USIZE].clone()) } } @@ -171,13 +184,13 @@ pub(crate) mod test { // Test random decode/encode and encode/decode round trips let mut rng = rand::thread_rng(); - let mut decoded: GenericArray = Default::default(); + let mut decoded: Array = Default::default(); rng.fill(decoded.as_mut_slice()); let m = match D::USIZE { 12 => FieldElement::Q, d => (1 as Integer) << d, }; - let decoded = decoded.map(|x| FieldElement(x % m)); + let decoded = decoded.iter().map(|x| FieldElement(x % m)).collect(); let actual_encoded = byte_encode::(&decoded); let actual_decoded = byte_decode::(&actual_encoded); @@ -190,12 +203,12 @@ pub(crate) mod test { #[test] fn byte_codec() { // The 1-bit can only represent decoded values equal to 0 or 1. - let decoded: DecodedValue = arr![FieldElement(0), FieldElement(1)].repeat(); - let encoded: EncodedPolynomial = arr![0xaa; 32]; + let decoded: DecodedValue = Array::<_, U2>([FieldElement(0), FieldElement(1)]).repeat(); + let encoded: EncodedPolynomial = Array([0xaa; 32]); byte_codec_test::(decoded, encoded); // For other codec widths, we use a standard sequence - let decoded: DecodedValue = arr![ + let decoded: DecodedValue = Array::<_, U8>([ FieldElement(0), FieldElement(1), FieldElement(2), @@ -203,43 +216,49 @@ pub(crate) mod test { FieldElement(4), FieldElement(5), FieldElement(6), - FieldElement(7) - ] + FieldElement(7), + ]) .repeat(); - let encoded: EncodedPolynomial = arr![0x10, 0x32, 0x54, 0x76].repeat(); + let encoded: EncodedPolynomial = Array::<_, U4>([0x10, 0x32, 0x54, 0x76]).repeat(); byte_codec_test::(decoded, encoded); - let encoded: EncodedPolynomial = arr![0x20, 0x88, 0x41, 0x8a, 0x39].repeat(); + let encoded: EncodedPolynomial = + Array::<_, U5>([0x20, 0x88, 0x41, 0x8a, 0x39]).repeat(); byte_codec_test::(decoded, encoded); - let encoded: EncodedPolynomial = arr![0x40, 0x20, 0x0c, 0x44, 0x61, 0x1c].repeat(); + let encoded: EncodedPolynomial = + Array::<_, U6>([0x40, 0x20, 0x0c, 0x44, 0x61, 0x1c]).repeat(); byte_codec_test::(decoded, encoded); let encoded: EncodedPolynomial = - arr![0x00, 0x04, 0x20, 0xc0, 0x00, 0x04, 0x14, 0x60, 0xc0, 0x01].repeat(); + Array::<_, U10>([0x00, 0x04, 0x20, 0xc0, 0x00, 0x04, 0x14, 0x60, 0xc0, 0x01]).repeat(); byte_codec_test::(decoded, encoded); - let encoded: EncodedPolynomial = - arr![0x00, 0x08, 0x80, 0x00, 0x06, 0x40, 0x80, 0x02, 0x18, 0xe0, 0x00].repeat(); + let encoded: EncodedPolynomial = Array::<_, U11>([ + 0x00, 0x08, 0x80, 0x00, 0x06, 0x40, 0x80, 0x02, 0x18, 0xe0, 0x00, + ]) + .repeat(); byte_codec_test::(decoded, encoded); - let encoded: EncodedPolynomial = - arr![0x00, 0x10, 0x00, 0x02, 0x30, 0x00, 0x04, 0x50, 0x00, 0x06, 0x70, 0x00].repeat(); + let encoded: EncodedPolynomial = Array::<_, U12>([ + 0x00, 0x10, 0x00, 0x02, 0x30, 0x00, 0x04, 0x50, 0x00, 0x06, 0x70, 0x00, + ]) + .repeat(); byte_codec_test::(decoded, encoded); } #[test] fn byte_codec_12_mod() { // DecodeBytes_12 is required to reduce mod q - let encoded: EncodedPolynomial = arr![0xff; 384]; - let decoded: DecodedValue = arr![FieldElement(0xfff % FieldElement::Q); 256]; + let encoded: EncodedPolynomial = Array([0xff; 384]); + let decoded: DecodedValue = Array([FieldElement(0xfff % FieldElement::Q); 256]); let actual_decoded = byte_decode::(&encoded); assert_eq!(actual_decoded, decoded); } - fn vector_codec_known_answer_test(decoded: T, encoded: GenericArray) + fn vector_codec_known_answer_test(decoded: T, encoded: Array) where D: EncodingSize, T: Encode + PartialEq + Debug, @@ -254,7 +273,7 @@ pub(crate) mod test { #[test] fn vector_codec() { let poly = Polynomial( - arr![ + Array::<_, U8>([ FieldElement(0), FieldElement(1), FieldElement(2), @@ -262,22 +281,25 @@ pub(crate) mod test { FieldElement(4), FieldElement(5), FieldElement(6), - FieldElement(7) - ] + FieldElement(7), + ]) .repeat(), ); // The required vector sizes are 2, 3, and 4. - let decoded: PolynomialVector = PolynomialVector(arr![poly, poly]); - let encoded: EncodedPolynomialVector = arr![0x20, 0x88, 0x41, 0x8a, 0x39].repeat(); + let decoded: PolynomialVector = PolynomialVector(Array([poly, poly])); + let encoded: EncodedPolynomialVector = + Array::<_, U5>([0x20, 0x88, 0x41, 0x8a, 0x39]).repeat(); vector_codec_known_answer_test::>(decoded, encoded); - let decoded: PolynomialVector = PolynomialVector(arr![poly, poly, poly]); - let encoded: EncodedPolynomialVector = arr![0x20, 0x88, 0x41, 0x8a, 0x39].repeat(); + let decoded: PolynomialVector = PolynomialVector(Array([poly, poly, poly])); + let encoded: EncodedPolynomialVector = + Array::<_, U5>([0x20, 0x88, 0x41, 0x8a, 0x39]).repeat(); vector_codec_known_answer_test::>(decoded, encoded); - let decoded: PolynomialVector = PolynomialVector(arr![poly, poly, poly, poly]); - let encoded: EncodedPolynomialVector = arr![0x20, 0x88, 0x41, 0x8a, 0x39].repeat(); + let decoded: PolynomialVector = PolynomialVector(Array([poly, poly, poly, poly])); + let encoded: EncodedPolynomialVector = + Array::<_, U5>([0x20, 0x88, 0x41, 0x8a, 0x39]).repeat(); vector_codec_known_answer_test::>(decoded, encoded); } } diff --git a/ml-kem/src/kem.rs b/ml-kem/src/kem.rs index 6b8eb3e..cb6d28a 100644 --- a/ml-kem/src/kem.rs +++ b/ml-kem/src/kem.rs @@ -1,11 +1,11 @@ use core::marker::PhantomData; use crypto_common::rand_core::CryptoRngCore; -use typenum::U32; +use hybrid_array::typenum::U32; use crate::crypto::{rand, G, H, J}; use crate::param::{DecapsulationKeySize, EncapsulationKeySize, EncodedCiphertext, KemParams}; use crate::pke::{DecryptionKey, EncryptionKey}; -use crate::util::{FastClone, FunctionalArray, B32}; +use crate::util::B32; use crate::{Encoded, EncodedSizeUser}; /// A shared key resulting from an ML-KEM transaction @@ -40,16 +40,16 @@ where dk_pke: DecryptionKey::from_bytes(dk_pke), ek: EncapsulationKey { ek_pke, - h: h.fast_clone(), + h: h.clone(), }, - z: z.fast_clone(), + z: z.clone(), } } fn as_bytes(&self) -> Encoded { let dk_pke = self.dk_pke.as_bytes(); let ek = self.ek.as_bytes(); - P::concat_dk(dk_pke, ek, self.ek.h.fast_clone(), self.z.fast_clone()) + P::concat_dk(dk_pke, ek, self.ek.h.clone(), self.z.clone()) } } @@ -78,9 +78,14 @@ where // Kbar // } let equal = cp - .zip(ct, |&x, &y| constant_time_eq(x, y)) - .fold(|x, y| x & y); - Kp.zip(&Kbar, |x, y| (equal & x) | (!equal & y)) + .iter() + .zip(ct.iter()) + .map(|(&x, &y)| constant_time_eq(x, y)) + .fold(0xff, |x, y| x & y); + Kp.iter() + .zip(Kbar.iter()) + .map(|(x, y)| (equal & x) | (!equal & y)) + .collect() } } @@ -102,7 +107,7 @@ where pub(crate) fn generate_deterministic(d: &B32, z: &B32) -> Self { let (dk_pke, ek_pke) = DecryptionKey::generate(d); let ek = EncapsulationKey::new(ek_pke); - let z = z.fast_clone(); + let z = z.clone(); Self { dk_pke, ek, z } } } diff --git a/ml-kem/src/lib.rs b/ml-kem/src/lib.rs index b9083f9..779d25f 100644 --- a/ml-kem/src/lib.rs +++ b/ml-kem/src/lib.rs @@ -58,18 +58,20 @@ mod param; use core::fmt::Debug; use crypto_common::rand_core::CryptoRngCore; -use generic_array::{ArrayLength, GenericArray}; -use typenum::{U10, U11, U2, U3, U4, U5}; +use hybrid_array::{ + typenum::{U10, U11, U2, U3, U4, U5}, + Array, +}; #[cfg(feature = "deterministic")] pub use util::B32; -pub use param::ParameterSet; +pub use param::{ArraySize, ParameterSet}; /// An object that knows what size it is pub trait EncodedSizeUser { /// The size of an encoded object - type EncodedSize: ArrayLength; + type EncodedSize: ArraySize; /// Parse an object from its encoded form fn from_bytes(enc: &Encoded) -> Self; @@ -79,7 +81,7 @@ pub trait EncodedSizeUser { } /// A byte array encoding a value the indicated size -pub type Encoded = GenericArray::EncodedSize>; +pub type Encoded = Array::EncodedSize>; /// A key that can be used to decapsulated an encapsulated shared key pub trait DecapsulationKey: EncodedSizeUser { @@ -101,10 +103,10 @@ pub trait EncapsulationKey: EncodedSizeUser { /// A generic interface to a Key Encapsulation Method pub trait KemCore { /// The size of a shared key generated by this KEM - type SharedKeySize: ArrayLength; + type SharedKeySize: ArraySize; /// The size of a ciphertext encapsulating a shared key - type CiphertextSize: ArrayLength; + type CiphertextSize: ArraySize; /// A decapsulation key for this KEM type DecapsulationKey: DecapsulationKey, Ciphertext> + Debug + PartialEq; @@ -161,10 +163,10 @@ impl ParameterSet for MlKem1024Params { } /// A shared key produced by the KEM `K` -pub type SharedKey = GenericArray::SharedKeySize>; +pub type SharedKey = Array::SharedKeySize>; /// A ciphertext produced by the KEM `K` -pub type Ciphertext = GenericArray::CiphertextSize>; +pub type Ciphertext = Array::CiphertextSize>; /// ML-KEM with the parameter set for security category 1, corresponding to key search on a block /// cipher with a 128-bit key. diff --git a/ml-kem/src/param.rs b/ml-kem/src/param.rs index 103b660..1563969 100644 --- a/ml-kem/src/param.rs +++ b/ml-kem/src/param.rs @@ -12,15 +12,14 @@ use core::fmt::Debug; use core::ops::{Add, Div, Mul, Rem, Sub}; -use generic_array::{ - sequence::{Concat, Split}, - GenericArray, IntoArrayLength, -}; -use typenum::{ - consts::{U0, U12, U16, U2, U3, U32, U384, U4, U6, U64, U8}, - operator_aliases::{Gcf, Prod, Quot, Sum}, - type_operators::Gcd, - Const, + +use hybrid_array::{ + typenum::{ + operator_aliases::{Gcf, Prod, Quot, Sum}, + type_operators::Gcd, + Const, ToUInt, U0, U12, U16, U2, U3, U32, U384, U4, U6, U64, U8, + }, + Array, }; use crate::algebra::{FieldElement, NttVector}; @@ -28,30 +27,30 @@ use crate::encode::Encode; use crate::util::{Flatten, Unflatten, B32}; /// An array length with other useful properties -pub trait ArrayLength: generic_array::ArrayLength + PartialEq + Debug {} +pub trait ArraySize: hybrid_array::ArraySize + PartialEq + Debug {} -impl ArrayLength for T where T: generic_array::ArrayLength + PartialEq + Debug {} +impl ArraySize for T where T: hybrid_array::ArraySize + PartialEq + Debug {} /// An integer that can be used as a length for encoded values. -pub trait EncodingSize: ArrayLength { - type EncodedPolynomialSize: ArrayLength; - type ValueStep: ArrayLength; - type ByteStep: ArrayLength; +pub trait EncodingSize: ArraySize { + type EncodedPolynomialSize: ArraySize; + type ValueStep: ArraySize; + type ByteStep: ArraySize; } type EncodingUnit = Quot, Gcf>; pub type EncodedPolynomialSize = ::EncodedPolynomialSize; -pub type EncodedPolynomial = GenericArray>; +pub type EncodedPolynomial = Array>; impl EncodingSize for D where - D: ArrayLength + Mul + Gcd + Mul, - Prod: ArrayLength, + D: ArraySize + Mul + Gcd + Mul, + Prod: ArraySize, Prod: Div>, EncodingUnit: Div + Div, - Quot, D>: ArrayLength, - Quot, U8>: ArrayLength, + Quot, D>: ArraySize, + Quot, U8>: ArraySize, { type EncodedPolynomialSize = Prod; type ValueStep = Quot, D>; @@ -61,49 +60,42 @@ where /// An integer that can describe encoded vectors. pub trait VectorEncodingSize: EncodingSize where - K: ArrayLength, + K: ArraySize, { - type EncodedPolynomialVectorSize: ArrayLength; + type EncodedPolynomialVectorSize: ArraySize; - fn flatten(polys: GenericArray, K>) - -> EncodedPolynomialVector; - fn unflatten( - vec: &EncodedPolynomialVector, - ) -> GenericArray<&EncodedPolynomial, K>; + fn flatten(polys: Array, K>) -> EncodedPolynomialVector; + fn unflatten(vec: &EncodedPolynomialVector) -> Array<&EncodedPolynomial, K>; } pub type EncodedPolynomialVectorSize = >::EncodedPolynomialVectorSize; -pub type EncodedPolynomialVector = GenericArray>; +pub type EncodedPolynomialVector = Array>; impl VectorEncodingSize for D where D: EncodingSize, - K: ArrayLength, + K: ArraySize, D::EncodedPolynomialSize: Mul, Prod: - ArrayLength + Div + Rem, + ArraySize + Div + Rem, { type EncodedPolynomialVectorSize = Prod; - fn flatten( - polys: GenericArray, K>, - ) -> EncodedPolynomialVector { + fn flatten(polys: Array, K>) -> EncodedPolynomialVector { polys.flatten() } - fn unflatten( - vec: &EncodedPolynomialVector, - ) -> GenericArray<&EncodedPolynomial, K> { + fn unflatten(vec: &EncodedPolynomialVector) -> Array<&EncodedPolynomial, K> { vec.unflatten() } } /// An integer that describes a bit length to be used in CBD sampling -pub trait CbdSamplingSize: ArrayLength { +pub trait CbdSamplingSize: ArraySize { type SampleSize: EncodingSize; - type OnesSize: ArrayLength; - const ONES: GenericArray; + type OnesSize: ArraySize; + const ONES: Array; } // To speed up CBD sampling, we pre-compute all the bit-manipulations: @@ -115,10 +107,10 @@ pub trait CbdSamplingSize: ArrayLength { // We have to allow the use of `as` here because we can't use our nice Truncate trait, because // const functions don't support traits. #[allow(clippy::cast_possible_truncation)] -const fn ones_array() -> GenericArray +const fn ones_array() -> Array where - U: ArrayLength, - Const: IntoArrayLength, + U: ArraySize = [FieldElement; N]>, + Const: ToUInt, { let max = 1 << B; let mut out = [FieldElement(0); N]; @@ -135,26 +127,26 @@ where } x += 1; } - GenericArray::from_array(out) + Array(out) } impl CbdSamplingSize for U2 { type SampleSize = U4; type OnesSize = U16; - const ONES: GenericArray = ones_array::<2, 16, U16>(); + const ONES: Array = ones_array::<2, 16, U16>(); } impl CbdSamplingSize for U3 { type SampleSize = U6; type OnesSize = U64; - const ONES: GenericArray = ones_array::<3, 64, U64>(); + const ONES: Array = ones_array::<3, 64, U64>(); } /// A `ParameterSet` captures the parameters that describe a particular instance of ML-KEM. There /// are three variants, corresponding to three different security levels. pub trait ParameterSet: Default + Clone + Debug + PartialEq { /// The dimensionality of vectors and arrays - type K: ArrayLength; + type K: ArraySize; /// The bit width of the centered binary distribution used when sampling random polynomials in /// key generation and encryption. @@ -174,14 +166,14 @@ pub trait ParameterSet: Default + Clone + Debug + PartialEq { type EncodedUSize

= EncodedPolynomialVectorSize<

::Du,

::K>; type EncodedVSize

= EncodedPolynomialSize<

::Dv>; -type EncodedU

= GenericArray>; -type EncodedV

= GenericArray>; +type EncodedU

= Array>; +type EncodedV

= Array>; /// Derived parameter relevant to K-PKE pub trait PkeParams: ParameterSet { - type NttVectorSize: ArrayLength; - type EncryptionKeySize: ArrayLength; - type CiphertextSize: ArrayLength; + type NttVectorSize: ArraySize; + type EncryptionKeySize: ArraySize; + type CiphertextSize: ArraySize; fn encode_u12(p: &NttVector) -> EncodedNttVector; fn decode_u12(v: &EncodedNttVector) -> NttVector; @@ -193,22 +185,22 @@ pub trait PkeParams: ParameterSet { fn split_ek(ek: &EncodedEncryptionKey) -> (&EncodedNttVector, &B32); } -pub type EncodedNttVector

= GenericArray::NttVectorSize>; -pub type EncodedDecryptionKey

= GenericArray::NttVectorSize>; -pub type EncodedEncryptionKey

= GenericArray::EncryptionKeySize>; -pub type EncodedCiphertext

= GenericArray::CiphertextSize>; +pub type EncodedNttVector

= Array::NttVectorSize>; +pub type EncodedDecryptionKey

= Array::NttVectorSize>; +pub type EncodedEncryptionKey

= Array::EncryptionKeySize>; +pub type EncodedCiphertext

= Array::CiphertextSize>; impl

PkeParams for P where P: ParameterSet, U384: Mul, - Prod: ArrayLength + Add + Div + Rem, + Prod: ArraySize + Add + Div + Rem, EncodedUSize

: Add>, Sum, EncodedVSize

>: - ArrayLength + Sub, Output = EncodedVSize

>, + ArraySize + Sub, Output = EncodedVSize

>, EncodedPolynomialVectorSize: Add, Sum, U32>: - ArrayLength + Sub, Output = U32>, + ArraySize + Sub, Output = U32>, { type NttVectorSize = EncodedPolynomialVectorSize; type EncryptionKeySize = Sum; @@ -227,7 +219,7 @@ where } fn split_ct(ct: &EncodedCiphertext) -> (&EncodedU, &EncodedV) { - ct.split() + ct.split_ref() } fn concat_ek(t_hat: EncodedNttVector, rho: B32) -> EncodedEncryptionKey { @@ -235,13 +227,13 @@ where } fn split_ek(ek: &EncodedEncryptionKey) -> (&EncodedNttVector, &B32) { - ek.split() + ek.split_ref() } } /// Derived parameters relevant to ML-KEM pub trait KemParams: PkeParams { - type DecapsulationKeySize: ArrayLength; + type DecapsulationKeySize: ArraySize; fn concat_dk( dk: EncodedDecryptionKey, @@ -263,18 +255,18 @@ pub trait KemParams: PkeParams { pub type DecapsulationKeySize

=

::DecapsulationKeySize; pub type EncapsulationKeySize

=

::EncryptionKeySize; -pub type EncodedDecapsulationKey

= GenericArray::DecapsulationKeySize>; +pub type EncodedDecapsulationKey

= Array::DecapsulationKeySize>; impl

KemParams for P where P: PkeParams, P::NttVectorSize: Add, Sum: - ArrayLength + Add + Sub, + ArraySize + Add + Sub, Sum, U32>: - ArrayLength + Add + Sub, Output = U32>, + ArraySize + Add + Sub, Output = U32>, Sum, U32>, U32>: - ArrayLength + Sub, U32>, Output = U32>, + ArraySize + Sub, U32>, Output = U32>, { type DecapsulationKeySize = Sum, U32>, U32>; @@ -296,9 +288,9 @@ where &B32, ) { // We parse from right to left to make it easier to write the trait bounds above - let (enc, z) = enc.split(); - let (enc, h) = enc.split(); - let (dk_pke, ek_pke) = enc.split(); + let (enc, z) = enc.split_ref(); + let (enc, h) = enc.split_ref(); + let (dk_pke, ek_pke) = enc.split_ref(); (dk_pke, ek_pke, h, z) } } diff --git a/ml-kem/src/pke.rs b/ml-kem/src/pke.rs index 7faae75..e4977b9 100644 --- a/ml-kem/src/pke.rs +++ b/ml-kem/src/pke.rs @@ -1,11 +1,11 @@ -use typenum::{consts::U1, Unsigned}; +use hybrid_array::typenum::{Unsigned, U1}; use crate::algebra::{NttMatrix, NttVector, Polynomial, PolynomialVector}; use crate::compress::Compress; use crate::crypto::{G, PRF}; use crate::encode::Encode; use crate::param::{EncodedCiphertext, EncodedDecryptionKey, EncodedEncryptionKey, PkeParams}; -use crate::util::{FastClone, B32}; +use crate::util::B32; /// A `DecryptionKey` provides the ability to generate a new key pair, and decrypt an /// encrypted value. @@ -117,7 +117,7 @@ where /// Represent this encryption key as a byte array `(t_hat || rho)` pub fn as_bytes(&self) -> EncodedEncryptionKey

{ let t_hat = P::encode_u12(&self.t_hat); - P::concat_ek(t_hat, self.rho.fast_clone()) + P::concat_ek(t_hat, self.rho.clone()) } /// Parse an encryption key from a byte array `(t_hat || rho)` @@ -126,7 +126,7 @@ where let t_hat = P::decode_u12(t_hat); Self { t_hat, - rho: rho.fast_clone(), + rho: rho.clone(), } } } diff --git a/ml-kem/src/util.rs b/ml-kem/src/util.rs index a7dc2a6..b0690e8 100644 --- a/ml-kem/src/util.rs +++ b/ml-kem/src/util.rs @@ -1,25 +1,27 @@ use core::mem::ManuallyDrop; use core::ops::{Div, Mul, Rem}; use core::ptr; -use generic_array::{sequence::GenericSequence, ArrayLength, GenericArray}; -use typenum::{ - operator_aliases::{Prod, Quot}, - Unsigned, U0, U32, +use hybrid_array::{ + typenum::{ + operator_aliases::{Prod, Quot}, + Unsigned, U0, U32, + }, + Array, ArraySize, }; /// A 32-byte array, defined here for brevity because it is used several times -pub type B32 = GenericArray; +pub type B32 = Array; -/// Benchmarking shows that `GenericArray::clone` does not optimize as well as this alternative +/// Benchmarking shows that `Array::clone` does not optimize as well as this alternative /// implementation. (Obviously, we can't re-implement Clone, so we have a new name.) pub trait FastClone { fn fast_clone(&self) -> Self; } -impl FastClone for GenericArray +impl FastClone for Array where T: Copy + Default, - N: ArrayLength, + N: ArraySize, { fn fast_clone(&self) -> Self { self.map(Clone::clone) @@ -30,14 +32,14 @@ where /// optimize as well as these alternative implementations. pub trait FunctionalArray where - N: ArrayLength, + N: ArraySize, { - fn map(&self, f: F) -> GenericArray + fn map(&self, f: F) -> Array where U: Default, F: Fn(&T) -> U; - fn zip(&self, b: &Self, f: F) -> GenericArray + fn zip(&self, b: &Self, f: F) -> Array where U: Default, F: Fn(&T, &T) -> U; @@ -48,24 +50,24 @@ where F: Fn(&T, &T) -> T; } -impl FunctionalArray for GenericArray +impl FunctionalArray for Array where - N: ArrayLength, + N: ArraySize, { - fn map(&self, f: F) -> GenericArray + fn map(&self, f: F) -> Array where U: Default, F: Fn(&T) -> U, { - GenericArray::generate(|i| f(&self[i])) + Array::from_fn(|i| f(&self[i])) } - fn zip(&self, other: &Self, f: F) -> GenericArray + fn zip(&self, other: &Self, f: F) -> Array where U: Default, F: Fn(&T, &T) -> U, { - GenericArray::generate(|i| f(&self[i], &other[i])) + Array::from_fn(|i| f(&self[i], &other[i])) } fn fold(&self, f: F) -> T @@ -107,23 +109,23 @@ define_truncate!(u128, u16); define_truncate!(u128, u8); /// Defines a sequence of sequences that can be merged into a bigger overall seequence -pub trait Flatten { - type OutputSize: ArrayLength; +pub trait Flatten { + type OutputSize: ArraySize; - fn flatten(self) -> GenericArray; + fn flatten(self) -> Array; } -impl Flatten> for GenericArray, N> +impl Flatten> for Array, N> where - N: ArrayLength, - M: ArrayLength + Mul, - Prod: ArrayLength, + N: ArraySize, + M: ArraySize + Mul, + Prod: ArraySize, { type OutputSize = Prod; // This is the reverse transmute between [T; K*N] and [[T; K], M], which is guaranteed to be // safe by the Rust memory layout of these types. - fn flatten(self) -> GenericArray { + fn flatten(self) -> Array { let whole = ManuallyDrop::new(self); unsafe { ptr::read(whole.as_ptr().cast()) } } @@ -132,48 +134,48 @@ where /// Defines a sequence that can be split into a sequence of smaller sequences of uniform size pub trait Unflatten where - M: ArrayLength, + M: ArraySize, { type Part; - fn unflatten(self) -> GenericArray; + fn unflatten(self) -> Array; } -impl Unflatten for GenericArray +impl Unflatten for Array where T: Default, - N: ArrayLength + Div + Rem, - M: ArrayLength, - Quot: ArrayLength, + N: ArraySize + Div + Rem, + M: ArraySize, + Quot: ArraySize, { - type Part = GenericArray>; + type Part = Array>; - // This requires some unsafeness, but it is the same as what is done in GenericArray::split. + // This requires some unsafeness, but it is the same as what is done in Array::split. // Basically, this is doing transmute between [T; K*N] and [[T; K], M], which is guaranteed to // be safe by the Rust memory layout of these types. - fn unflatten(self) -> GenericArray { + fn unflatten(self) -> Array { let part_size = Quot::::USIZE; let whole = ManuallyDrop::new(self); - GenericArray::generate(|i| unsafe { ptr::read(whole.as_ptr().add(i * part_size).cast()) }) + Array::from_fn(|i| unsafe { ptr::read(whole.as_ptr().add(i * part_size).cast()) }) } } -impl<'a, T, N, M> Unflatten for &'a GenericArray +impl<'a, T, N, M> Unflatten for &'a Array where T: Default, - N: ArrayLength + Div + Rem, - M: ArrayLength, - Quot: ArrayLength, + N: ArraySize + Div + Rem, + M: ArraySize, + Quot: ArraySize, { - type Part = &'a GenericArray>; + type Part = &'a Array>; - // This requires some unsafeness, but it is the same as what is done in GenericArray::split. + // This requires some unsafeness, but it is the same as what is done in Array::split. // Basically, this is doing transmute between [T; K*N] and [[T; K], M], which is guaranteed to // be safe by the Rust memory layout of these types. - fn unflatten(self) -> GenericArray { + fn unflatten(self) -> Array { let part_size = Quot::::USIZE; let mut ptr: *const T = self.as_ptr(); - GenericArray::generate(|_i| unsafe { + Array::from_fn(|_i| unsafe { let part = &*(ptr.cast()); ptr = ptr.add(part_size); part @@ -184,16 +186,20 @@ where #[cfg(test)] mod test { use super::*; - use generic_array::arr; - use typenum::consts::*; + use hybrid_array::typenum::consts::*; #[test] fn flatten() { - let flat: GenericArray = arr![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - let unflat2: GenericArray, _> = - arr![arr![1, 2], arr![3, 4], arr![5, 6], arr![7, 8], arr![9, 10]]; - let unflat5: GenericArray, _> = - arr![arr![1, 2, 3, 4, 5], arr![6, 7, 8, 9, 10]]; + let flat: Array = Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + let unflat2: Array, _> = Array([ + Array([1, 2]), + Array([3, 4]), + Array([5, 6]), + Array([7, 8]), + Array([9, 10]), + ]); + let unflat5: Array, _> = + Array([Array([1, 2, 3, 4, 5]), Array([6, 7, 8, 9, 10])]); // Flatten let actual = unflat2.flatten(); @@ -203,19 +209,19 @@ mod test { assert_eq!(flat, actual); // Unflatten - let actual: GenericArray, U5> = flat.unflatten(); + let actual: Array, U5> = flat.unflatten(); assert_eq!(unflat2, actual); - let actual: GenericArray, U2> = flat.unflatten(); + let actual: Array, U2> = flat.unflatten(); assert_eq!(unflat5, actual); // Unflatten on references - let actual: GenericArray<&GenericArray, U5> = (&flat).unflatten(); + let actual: Array<&Array, U5> = (&flat).unflatten(); for (i, part) in actual.iter().enumerate() { assert_eq!(&unflat2[i], *part); } - let actual: GenericArray<&GenericArray, U2> = (&flat).unflatten(); + let actual: Array<&Array, U2> = (&flat).unflatten(); for (i, part) in actual.iter().enumerate() { assert_eq!(&unflat5[i], *part); } From 48e9e2f1b16dea7bb29dbfbc3d9a3024fdcc169c Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Fri, 1 Mar 2024 12:54:02 -0500 Subject: [PATCH 08/10] Use released version of hybrid-array --- ml-kem/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ml-kem/Cargo.toml b/ml-kem/Cargo.toml index de1bb8e..07ea6b7 100644 --- a/ml-kem/Cargo.toml +++ b/ml-kem/Cargo.toml @@ -11,7 +11,7 @@ deterministic = [] # Expose deterministic generation and encapsulation functions const-default = "1.0.0" crypto-common = { version = "0.1.6", features = ["getrandom"] } generic-array = { version = "1.0.0", features = ["const-default"] } -hybrid-array = { path = "../../hybrid-array" } +hybrid-array = { version = "0.2.0-rc.6" } sha3 = "0.10.8" [dev-dependencies] From d0945d459b22402946cece88552e66d840729d7d Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Fri, 1 Mar 2024 15:55:56 -0500 Subject: [PATCH 09/10] Use standard KEM interfaces --- ml-kem/benches/mlkem.rs | 10 +++---- ml-kem/src/kem.rs | 62 ++++++++++++++++++++++++++++------------- ml-kem/src/lib.rs | 54 +++++++++++++++++++++++------------ 3 files changed, 84 insertions(+), 42 deletions(-) diff --git a/ml-kem/benches/mlkem.rs b/ml-kem/benches/mlkem.rs index 7c8e8cb..8edd8f0 100644 --- a/ml-kem/benches/mlkem.rs +++ b/ml-kem/benches/mlkem.rs @@ -18,7 +18,7 @@ fn criterion_benchmark(c: &mut Criterion) { let (dk, ek) = MlKem768::generate_deterministic(&d, &z); let dk_bytes = dk.as_bytes(); let ek_bytes = ek.as_bytes(); - let (_sk, ct) = ek.encapsulate(&mut rng); + let (ct, _sk) = ek.encapsulate(&mut rng).unwrap(); // Key generation c.bench_function("keygen", |b| { @@ -33,7 +33,7 @@ fn criterion_benchmark(c: &mut Criterion) { c.bench_function("encapsulate", |b| { b.iter(|| { let ek = ::EncapsulationKey::from_bytes(&ek_bytes); - ek.encapsulate_deterministic(&m); + ek.encapsulate_deterministic(&m).unwrap(); }) }); @@ -41,7 +41,7 @@ fn criterion_benchmark(c: &mut Criterion) { c.bench_function("decapsulate", |b| { b.iter(|| { let dk = ::DecapsulationKey::from_bytes(&dk_bytes); - dk.decapsulate(&ct); + dk.decapsulate(&ct).unwrap(); }) }); @@ -49,8 +49,8 @@ fn criterion_benchmark(c: &mut Criterion) { c.bench_function("round_trip", |b| { b.iter(|| { let (dk, ek) = ::generate_deterministic(&d, &z); - let (_sk, ct) = ek.encapsulate(&mut rng); - dk.decapsulate(&ct); + let (ct, _sk) = ek.encapsulate(&mut rng).unwrap(); + dk.decapsulate(&ct).unwrap(); }) }); } diff --git a/ml-kem/src/kem.rs b/ml-kem/src/kem.rs index cb6d28a..10937a3 100644 --- a/ml-kem/src/kem.rs +++ b/ml-kem/src/kem.rs @@ -60,14 +60,19 @@ fn constant_time_eq(x: u8, y: u8) -> u8 { 0u8.wrapping_sub(is_zero >> 7) } -impl

crate::DecapsulationKey> for DecapsulationKey

+impl

crate::Decapsulate, SharedKey> for DecapsulationKey

where P: KemParams, { - fn decapsulate(&self, ct: &EncodedCiphertext

) -> SharedKey { - let mp = self.dk_pke.decrypt(ct); + // Decapsulation is infallible + // XXX(RLB): Maybe we should reflect decryption failure as an error? + // TODO(RLB) Make Infallible + type Error = (); + + fn decapsulate(&self, encapsulated_key: &EncodedCiphertext

) -> Result { + let mp = self.dk_pke.decrypt(encapsulated_key); let (Kp, rp) = G(&[&mp, &self.ek.h]); - let Kbar = J(&[self.z.as_slice(), ct.as_ref()]); + let Kbar = J(&[self.z.as_slice(), encapsulated_key.as_ref()]); let cp = self.ek.ek_pke.encrypt(&mp, &rp); // Constant-time version of: @@ -79,13 +84,14 @@ where // } let equal = cp .iter() - .zip(ct.iter()) + .zip(encapsulated_key.iter()) .map(|(&x, &y)| constant_time_eq(x, y)) .fold(0xff, |x, y| x & y); - Kp.iter() + Ok(Kp + .iter() .zip(Kbar.iter()) .map(|(x, y)| (equal & x) | (!equal & y)) - .collect() + .collect()) } } @@ -132,10 +138,10 @@ where Self { ek_pke, h } } - fn encapsulate_deterministic_inner(&self, m: &B32) -> (SharedKey, EncodedCiphertext

) { + fn encapsulate_deterministic_inner(&self, m: &B32) -> (EncodedCiphertext

, SharedKey) { let (K, r) = G(&[m, &self.h]); let c = self.ek_pke.encrypt(m, &r); - (K, c) + (c, K) } } @@ -154,18 +160,36 @@ where } } -impl

crate::EncapsulationKey> for EncapsulationKey

+impl

crate::Encapsulate, SharedKey> for EncapsulationKey

where P: KemParams, { - fn encapsulate(&self, rng: &mut impl CryptoRngCore) -> (SharedKey, EncodedCiphertext

) { + // TODO(RLB) Make Infallible + // TODO(RLB) Swap the order of the + type Error = (); + + fn encapsulate( + &self, + rng: &mut impl CryptoRngCore, + ) -> Result<(EncodedCiphertext

, SharedKey), Self::Error> { let m: B32 = rand(rng); - self.encapsulate_deterministic_inner(&m) + Ok(self.encapsulate_deterministic_inner(&m)) } +} - #[cfg(feature = "deterministic")] - fn encapsulate_deterministic(&self, m: &B32) -> (SharedKey, EncodedCiphertext

) { - self.encapsulate_deterministic_inner(m) +#[cfg(feature = "deterministic")] +impl

crate::EncapsulateDeterministic, SharedKey> for EncapsulationKey

+where + P: KemParams, +{ + // TODO(RLB) Make Infallible + type Error = (); + + fn encapsulate_deterministic( + &self, + m: &B32, + ) -> Result<(EncodedCiphertext

, SharedKey), Self::Error> { + Ok(self.encapsulate_deterministic_inner(&m)) } } @@ -208,8 +232,8 @@ where #[cfg(test)] mod test { use super::*; - use crate::DecapsulationKey as _; - use crate::EncapsulationKey as _; + use crate::Decapsulate as _; + use crate::Encapsulate as _; use crate::{MlKem1024Params, MlKem512Params, MlKem768Params}; fn round_trip_test

() @@ -221,8 +245,8 @@ mod test { let dk = DecapsulationKey::

::generate(&mut rng); let ek = dk.encapsulation_key(); - let (k_send, ct) = ek.encapsulate(&mut rng); - let k_recv = dk.decapsulate(&ct); + let (ct, k_send) = ek.encapsulate(&mut rng).unwrap(); + let k_recv = dk.decapsulate(&ct).unwrap(); assert_eq!(k_send, k_recv); } diff --git a/ml-kem/src/lib.rs b/ml-kem/src/lib.rs index 779d25f..7cfad33 100644 --- a/ml-kem/src/lib.rs +++ b/ml-kem/src/lib.rs @@ -14,10 +14,10 @@ //! //! // Encapsulate a shared key to the holder of the decapsulation key, receive the shared //! // secret `k_send` and the encapsulated form `ct`. -//! let (k_send, ct) = ek.encapsulate(&mut rng); +//! let (ct, k_send) = ek.encapsulate(&mut rng).unwrap(); //! //! // Decapsulate the shared key and verify that it was faithfully received. -//! let k_recv = dk.decapsulate(&ct); +//! let k_recv = dk.decapsulate(&ct).unwrap(); //! assert_eq!(k_send, k_recv); //! ``` //! @@ -83,21 +83,39 @@ pub trait EncodedSizeUser { /// A byte array encoding a value the indicated size pub type Encoded = Array::EncodedSize>; -/// A key that can be used to decapsulated an encapsulated shared key -pub trait DecapsulationKey: EncodedSizeUser { - /// Decapsulate the ciphertext to obtain the encapsulated shared key - fn decapsulate(&self, ciphertext: &Ciphertext) -> SharedKey; +// XXX(RLB) Copy/pasted from https://github.com/RustCrypto/traits/pull/1509 +/// A value that can be encapsulated to. Often, this will just be a public key. However, it can +/// also be a bundle of public keys, or it can include a sender's private key for authenticated +/// encapsulation. +pub trait Encapsulate { + /// Encapsulation error + type Error: Debug; + + /// Encapsulates a fresh shared secret + fn encapsulate(&self, rng: &mut impl CryptoRngCore) -> Result<(EK, SS), Self::Error>; } -/// A key that can be used to encapsulate a shared key such that it can only be decapsulated -/// by the holder of the corresponding decapsulation key -pub trait EncapsulationKey: EncodedSizeUser { - /// Encapsulate a fresh secret to the holder of the corresponding decapsulation key - fn encapsulate(&self, rng: &mut impl CryptoRngCore) -> (SharedKey, Ciphertext); +/// A value that can be encapsulated to. Note that this interface is not safe: In order for the +/// KEM to be secure, the `m` input must be randomly generated. +#[cfg(feature = "deterministic")] +pub trait EncapsulateDeterministic { + /// Encapsulation error + type Error: Debug; - /// Encapsulate a specific shared key to the holder of the corresponding decapsulation key - #[cfg(feature = "deterministic")] - fn encapsulate_deterministic(&self, m: &B32) -> (SharedKey, Ciphertext); + /// Encapsulates a fresh shared secret + fn encapsulate_deterministic(&self, m: &B32) -> Result<(EK, SS), Self::Error>; +} + +// XXX(RLB) Copy/pasted from https://github.com/RustCrypto/traits/pull/1509 +/// A value that can be used to decapsulate an encapsulated key. Often, this will just be a secret +/// key. But, as with [`Encapsulate`], it can be a bundle of secret keys, or it can include a +/// sender's private key for authenticated encapsulation. +pub trait Decapsulate { + /// Decapsulation error + type Error: Debug; + + /// Decapsulates the given encapsulated key + fn decapsulate(&self, encapsulated_key: &EK) -> Result; } /// A generic interface to a Key Encapsulation Method @@ -109,10 +127,10 @@ pub trait KemCore { type CiphertextSize: ArraySize; /// A decapsulation key for this KEM - type DecapsulationKey: DecapsulationKey, Ciphertext> + Debug + PartialEq; + type DecapsulationKey: Decapsulate, SharedKey> + Debug + PartialEq; /// An encapsulation key for this KEM - type EncapsulationKey: EncapsulationKey, Ciphertext> + Debug + PartialEq; + type EncapsulationKey: Encapsulate, SharedKey> + Debug + PartialEq; /// Generate a new (decapsulation, encapsulation) key pair fn generate(rng: &mut impl CryptoRngCore) -> (Self::DecapsulationKey, Self::EncapsulationKey); @@ -192,8 +210,8 @@ mod test { let (dk, ek) = K::generate(&mut rng); - let (k_send, ct) = ek.encapsulate(&mut rng); - let k_recv = dk.decapsulate(&ct); + let (ct, k_send) = ek.encapsulate(&mut rng).unwrap(); + let k_recv = dk.decapsulate(&ct).unwrap(); assert_eq!(k_send, k_recv); } From 4d83d5d0a58d5a0ad38e774b55a0c2ad234beb2c Mon Sep 17 00:00:00 2001 From: Richard Barnes Date: Fri, 1 Mar 2024 17:54:55 -0500 Subject: [PATCH 10/10] Cargo clippy cleanup --- ml-kem/src/kem.rs | 2 ++ ml-kem/src/lib.rs | 5 +++-- ml-kem/src/param.rs | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ml-kem/src/kem.rs b/ml-kem/src/kem.rs index 10937a3..c1ee71c 100644 --- a/ml-kem/src/kem.rs +++ b/ml-kem/src/kem.rs @@ -29,6 +29,7 @@ where { type EncodedSize = DecapsulationKeySize

; + #[allow(clippy::similar_names)] // allow dk_pke, ek_pke, following the spec fn from_bytes(enc: &Encoded) -> Self { let (dk_pke, ek_pke, h, z) = P::split_dk(enc); let ek_pke = EncryptionKey::from_bytes(ek_pke); @@ -110,6 +111,7 @@ where } #[must_use] + #[allow(clippy::similar_names)] // allow dk_pke, ek_pke, following the spec pub(crate) fn generate_deterministic(d: &B32, z: &B32) -> Self { let (dk_pke, ek_pke) = DecryptionKey::generate(d); let ek = EncapsulationKey::new(ek_pke); diff --git a/ml-kem/src/lib.rs b/ml-kem/src/lib.rs index 7cfad33..326bc0a 100644 --- a/ml-kem/src/lib.rs +++ b/ml-kem/src/lib.rs @@ -23,10 +23,9 @@ //! //! [RFC 9180]: https://www.rfc-editor.org/info/rfc9180 -//#![no_std] +#![no_std] #![warn(clippy::pedantic)] // Be pedantic by default #![allow(non_snake_case)] // Allow notation matching the spec -#![allow(clippy::similar_names)] // Allow dk_pke/ek_pke #![allow(clippy::clone_on_copy)] // Be explicit about moving data #![deny(missing_docs)] // Require all public interfaces to be documented @@ -91,6 +90,7 @@ pub trait Encapsulate { /// Encapsulation error type Error: Debug; + #[allow(clippy::missing_errors_doc)] /// Encapsulates a fresh shared secret fn encapsulate(&self, rng: &mut impl CryptoRngCore) -> Result<(EK, SS), Self::Error>; } @@ -114,6 +114,7 @@ pub trait Decapsulate { /// Decapsulation error type Error: Debug; + #[allow(clippy::missing_errors_doc)] /// Decapsulates the given encapsulated key fn decapsulate(&self, encapsulated_key: &EK) -> Result; } diff --git a/ml-kem/src/param.rs b/ml-kem/src/param.rs index 1563969..0af99bb 100644 --- a/ml-kem/src/param.rs +++ b/ml-kem/src/param.rs @@ -279,6 +279,7 @@ where dk.concat(ek).concat(h).concat(z) } + #[allow(clippy::similar_names)] // allow dk_pke, ek_pke, following the spec fn split_dk( enc: &EncodedDecapsulationKey, ) -> (