use core::fmt;
use core::marker::PhantomData;
use core::ops::Range;
use crate::{Error, low};
use crate::{
low::{ct_copy, ct_equal, ct_select_i16},
mid::{
rng::{RandomSource, SystemRandom},
sha3,
},
};
pub struct DecapKey {
ek_pke: EncapKey,
dk_pke: [u8; K * 384],
h_ek: [u8; 32],
z: [u8; 32],
}
impl DecapKey {
pub fn generate() -> Result<Self, Error> {
let mut seed = [0u8; 64];
SystemRandom.fill(&mut seed)?;
Ok(Self::keygen_internal(&seed))
}
pub fn decaps(self, c: &Ciphertext) -> SharedSecret {
self.decaps_internal(c)
}
pub fn encapsulation_key(&self) -> EncapKey {
self.ek_pke.clone()
}
#[doc(hidden)]
pub fn as_bytes(&self) -> [u8; K * 768 + 96] {
let mut out = [0u8; K * 768 + 96];
out[..K * 384].copy_from_slice(&self.dk_pke);
out[K * 384..K * 768].copy_from_slice(&self.ek_pke.t_hat);
out[K * 768..K * 768 + 32].copy_from_slice(&self.ek_pke.rho);
out[K * 768 + 32..K * 768 + 64].copy_from_slice(&self.h_ek);
out[K * 768 + 64..].copy_from_slice(&self.z);
out
}
#[doc(hidden)]
pub fn keygen_internal(seed: &[u8; 64]) -> Self {
let (d, z) = seed.split_at(32);
let d = d.try_into().unwrap();
let z = z.try_into().unwrap();
let (ek_pke, dk_pke) = Self::kpke_keygen(&d);
let mut h = sha3::Sha3_256Context::new();
h.update(&ek_pke.t_hat);
h.update(&ek_pke.rho);
let h_ek = h.finish();
Self {
dk_pke,
ek_pke,
h_ek,
z,
}
}
#[doc(hidden)]
pub fn decaps_internal(&self, c: &Ciphertext) -> SharedSecret {
let m_prime = self.kpke_decrypt(c);
let mut g = sha3::Sha3_512Context::new();
g.update(&m_prime.0);
g.update(&self.h_ek);
let kr_prime = g.finish();
let (k_prime, r_prime) = kr_prime.split_at(32);
let r_prime = KpkeRandomness(r_prime.try_into().unwrap());
let mut k_bar = SharedSecret([0u8; 32]);
sha3::Shake256::new(&[&self.z, &c.0]).read(&mut k_bar.0);
let c_prime = self.ek_pke.kpke_encrypt(m_prime, r_prime);
let mut k_prime = k_prime.try_into().unwrap();
ct_copy(ct_equal(&c.0, &c_prime.0), &mut k_prime, &k_bar.0);
SharedSecret(k_prime)
}
fn kpke_keygen(d: &[u8; 32]) -> (EncapKey, [u8; K * 384]) {
let mut g = sha3::Sha3_512Context::new();
g.update(d);
g.update(&[K_BYTE]);
let g = g.finish();
let (rho, sigma) = g.split_at(32);
let rho: [u8; 32] = rho.try_into().unwrap();
let a_hat = Coeffs::sample_poly(&rho);
let s = Coeffs::sample_poly_cbd(sigma, 0..K_BYTE);
let e = Coeffs::sample_poly_cbd(sigma, K_BYTE..K_BYTE * 2);
let mut s_hat = s.ntt();
let e_hat = e.ntt();
let t_hat = a_hat.mul_add(&s_hat, &e_hat);
let encap = EncapKey {
t_hat: t_hat.to_bytes(),
rho,
transpose_a_hat: a_hat.transpose(),
};
s_hat.reduce_in_place();
let decap = s_hat.to_bytes();
(encap, decap)
}
fn kpke_decrypt(&self, c: &Ciphertext) -> Message {
let (c_1, c_2) = c.0.split_at(32 * DU * K);
let u_prime = Coeffs::decompress_from_bytes_du_10(c_1.try_into().unwrap());
let v_prime = Coeffs::decompress_from_bytes_dv_4(c_2.try_into().unwrap());
let s_hat = Coeffs::from_bytes(self.dk_pke);
let w = v_prime.sub(&s_hat.mul_and_inverse_ntt(&u_prime.ntt()));
w.to_message_bits()
}
}
impl Drop for DecapKey {
fn drop(&mut self) {
low::zeroise(&mut self.dk_pke);
low::zeroise(&mut self.z);
}
}
#[derive(Clone)]
pub struct EncapKey {
t_hat: [u8; K * 384],
rho: [u8; 32],
transpose_a_hat: Coeffs<{ K * K * N }, Ntt>,
}
impl EncapKey {
pub fn from_bytes(input: &[u8; K * 384 + 32]) -> Result<Self, Error> {
let (t_hat, rho) = input.split_at(K * 384);
let t_hat = t_hat.try_into().unwrap();
let rho = rho.try_into().unwrap();
if t_hat != Coeffs::from_bytes(t_hat).to_bytes() {
return Err(Error::OutOfRange);
}
let transpose_a_hat = Coeffs::sample_poly_transposed(&rho);
Ok(Self {
t_hat,
rho,
transpose_a_hat,
})
}
pub fn encaps(self) -> Result<(SharedSecret, Ciphertext), Error> {
let mut m = Message([0; 32]);
SystemRandom.fill(&mut m.0)?;
Ok(self.encaps_internal(m))
}
pub fn as_bytes(&self) -> [u8; K * 384 + 32] {
let mut out = [0u8; K * 384 + 32];
out[..K * 384].copy_from_slice(&self.t_hat);
out[K * 384..].copy_from_slice(&self.rho);
out
}
#[doc(hidden)]
pub fn encaps_internal(self, m: Message) -> (SharedSecret, Ciphertext) {
let mut h = sha3::Sha3_256Context::new();
h.update(&self.t_hat);
h.update(&self.rho);
let h_ek = h.finish();
let mut g = sha3::Sha3_512Context::new();
g.update(&m.0);
g.update(&h_ek);
let kr = g.finish();
let (k, r) = kr.split_at(32);
let k = k.try_into().unwrap();
let r = KpkeRandomness(r.try_into().unwrap());
let c = self.kpke_encrypt(m, r);
(SharedSecret(k), c)
}
fn kpke_encrypt(&self, m: Message, r: KpkeRandomness) -> Ciphertext {
let t_hat = Coeffs::from_bytes(self.t_hat);
let y = Coeffs::sample_poly_cbd(&r.0, 0..K_BYTE);
let e_1 = Coeffs::sample_poly_cbd(&r.0, K_BYTE..K_BYTE * 2);
let mut buf = [0u8; 128];
sha3::Shake256::new(&[&r.0, &[K_BYTE * 2]]).read(&mut buf);
let mut e_2 = Coeffs::zero();
sample_cbd2(&buf, &mut e_2.0);
let y_hat = y.ntt();
let u = self.transpose_a_hat.mul_and_inverse_ntt(&y_hat).add(&e_1);
let mu = Coeffs::from_message_bits(m);
let v = t_hat.mul_and_inverse_ntt(&y_hat).add(&e_2).add(&mu);
let mut c = Ciphertext([0; _]);
let (c_1, c_2) = c.0.split_at_mut(32 * DU * K);
u.compress_into_bytes_du_10(c_1.try_into().unwrap());
v.compress_into_bytes_dv_4(c_2.try_into().unwrap());
c
}
}
#[derive(Debug, Clone)]
pub struct Ciphertext([u8; 32 * (DU * K + DV)]);
impl From<[u8; 1088]> for Ciphertext {
fn from(value: [u8; 1088]) -> Self {
Self(value)
}
}
impl AsRef<[u8; 1088]> for Ciphertext {
fn as_ref(&self) -> &[u8; 1088] {
&self.0
}
}
#[doc(hidden)]
pub struct Message(pub [u8; 32]);
impl Drop for Message {
fn drop(&mut self) {
low::zeroise(&mut self.0);
}
}
struct KpkeRandomness([u8; 32]);
pub struct SharedSecret([u8; 32]);
impl AsRef<[u8; 32]> for SharedSecret {
fn as_ref(&self) -> &[u8; 32] {
&self.0
}
}
impl fmt::Debug for SharedSecret {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("SharedSecret").finish_non_exhaustive()
}
}
impl Drop for SharedSecret {
fn drop(&mut self) {
low::zeroise(&mut self.0);
}
}
trait Domain: Clone {}
#[derive(Clone)]
struct Normal;
impl Domain for Normal {}
#[derive(Clone)]
struct Ntt;
impl Domain for Ntt {}
#[derive(Clone)]
struct MulCache;
impl Domain for MulCache {}
#[derive(Clone, Debug)]
#[repr(align(32))]
struct Coeffs<const C: usize, D: Domain>([i16; C], PhantomData<D>);
impl<const C: usize, D: Domain> Coeffs<C, D> {
const fn zero() -> Self {
Self([0; C], PhantomData)
}
fn reduce_in_place(&mut self) {
for k in self.0.as_chunks_mut().0 {
low::mlkem_reduce(k);
}
}
}
impl Coeffs<{ K * K * N }, Ntt> {
fn sample_poly(rho: &[u8; 32]) -> Self {
Self::_sample_poly_ntt::<false>(rho)
}
fn sample_poly_transposed(rho: &[u8; 32]) -> Self {
Self::_sample_poly_ntt::<true>(rho)
}
fn _sample_poly_ntt<const TRANSPOSED: bool>(rho: &[u8; 32]) -> Self {
let mut r = Coeffs::zero();
for (i, matrix_row) in r.0.chunks_exact_mut(K * N).enumerate() {
for (j, poly) in matrix_row.chunks_exact_mut(N).enumerate() {
let input = match TRANSPOSED {
false => &[j as u8, i as u8],
true => &[i as u8, j as u8],
};
Shake128ForMlKem::new(&[rho, input]).sample_into(poly.try_into().unwrap());
}
}
r.reorder();
r
}
fn mul_add(
&self,
s: &Coeffs<{ K * N }, Ntt>,
e: &Coeffs<{ K * N }, Ntt>,
) -> Coeffs<{ K * N }, Ntt> {
let mut r = e.clone();
let s_precomp = s.mul_cache();
let mut term = Coeffs::<_, Ntt>::zero();
for (aa, rr) in self
.0
.as_chunks()
.0
.iter()
.zip(r.0.as_chunks_mut::<N>().0.iter_mut())
{
low::mlkem_basemul_k3(&mut term.0, aa, &s.0, &s_precomp.0);
low::mlkem_tomont(&mut term.0);
for (r, t) in rr.iter_mut().zip(term.0) {
*r += t;
}
}
r.reduce_in_place();
r
}
fn reorder(&mut self) {
for ch in self.0.as_chunks_mut().0 {
low::mlkem_unpack(ch);
}
}
fn transpose(mut self) -> Self {
let (chunks, _) = self.0.as_chunks_mut::<N>();
for i in 0..K {
for j in (i + 1)..K {
chunks.swap(i * K + j, j * K + i);
}
}
self
}
fn mul_and_inverse_ntt(&self, y: &Coeffs<{ K * N }, Ntt>) -> Coeffs<{ K * N }, Normal> {
let mut r = Coeffs::<_, Ntt>::zero();
let y_precomp = y.mul_cache();
let mut term = Coeffs::<_, Ntt>::zero();
for (aa, rr) in self
.0
.as_chunks()
.0
.iter()
.zip(r.0.as_chunks_mut::<N>().0.iter_mut())
{
low::mlkem_basemul_k3(&mut term.0, aa, &y.0, &y_precomp.0);
for (r, t) in rr.iter_mut().zip(term.0) {
*r += t;
}
}
r.reduce_in_place();
r.inverse_ntt()
}
}
impl Coeffs<{ K * N }, Ntt> {
fn from_bytes(bytes: [u8; 1152]) -> Self {
let mut r = Coeffs::zero();
let (r_chunks, _) = r.0.as_chunks_mut();
let (b_chunks, _) = bytes.as_chunks();
for (rr, bb) in r_chunks.iter_mut().zip(b_chunks.iter()) {
low::mlkem_frombytes(rr, bb);
low::mlkem_reduce(rr);
}
r
}
fn to_bytes(&self) -> [u8; 1152] {
let mut r = [0u8; 1152];
let (c_chunks, _) = self.0.as_chunks();
let (r_chunks, _) = r.as_chunks_mut();
for (rr, cc) in r_chunks.iter_mut().zip(c_chunks.iter()) {
low::mlkem_tobytes(rr, cc);
}
r
}
fn mul_and_inverse_ntt(&self, s: &Coeffs<{ K * N }, Ntt>) -> Coeffs<{ N }, Normal> {
let mut r = Coeffs::zero();
let s_precomp = s.mul_cache();
low::mlkem_basemul_k3(&mut r.0, &self.0, &s.0, &s_precomp.0);
r.inverse_ntt()
}
fn mul_cache(&self) -> Coeffs<{ K * 128 }, MulCache> {
let mut r = Coeffs::zero();
let (m, _) = self.0.as_chunks();
let (rr, _) = r.0.as_chunks_mut();
low::mlkem_mulcache_compute(&mut rr[0], &m[0]);
low::mlkem_mulcache_compute(&mut rr[1], &m[1]);
low::mlkem_mulcache_compute(&mut rr[2], &m[2]);
r
}
}
impl<const C: usize> Coeffs<C, Ntt> {
fn inverse_ntt(mut self) -> Coeffs<C, Normal> {
for ch in self.0.as_chunks_mut().0 {
low::mlkem_intt(ch);
}
Coeffs(self.0, PhantomData)
}
}
fn compress_4(x: i16) -> u8 {
debug_assert!((0..Q).contains(&x));
let c = x as u64;
let c = c << 4;
let c = c + Q_HALF as u64;
let c = c * 80635;
let c = c >> 28;
let c = c & 0xf;
c as u8
}
fn decompress_4(y: u16) -> i16 {
debug_assert!(y < 16);
let d = y as u32;
let d = d * (Q as u32);
let d = d + 8;
let d = d >> 4;
d as i16
}
fn compress_10(x: i16) -> u16 {
debug_assert!((0..Q).contains(&x));
let c = x as u64;
let c = c << 10;
let c = c + Q_HALF as u64;
let c = c * 1290167;
let c = c >> 32;
let c = c & 0x3ff;
c as u16
}
fn decompress_10(y: u16) -> i16 {
debug_assert!(y < 1024);
let d = y as u32;
let d = d * (Q as u32);
let d = d + 512;
let d = d >> 10;
d as i16
}
fn compress_1_x8(coeffs: &[i16; 8]) -> u8 {
let mut r = 0;
for (i, x) in coeffs.iter().enumerate() {
debug_assert!((0..Q).contains(x));
let x = (*x as u64) << 1;
let x = x + Q_HALF as u64;
let x = x * 80635;
let x = x >> 28;
r |= ((x & 1) as u8) << i;
}
r
}
impl Coeffs<{ K * N }, Normal> {
fn sample_poly_cbd(sigma: &[u8], ns: Range<u8>) -> Self {
let mut r = Coeffs::zero();
let (rows, _) = r.0.as_chunks_mut();
for (i, n) in ns.enumerate() {
let mut buf = [0u8; 128];
sha3::Shake256::new(&[sigma, &[n]]).read(&mut buf);
sample_cbd2(&buf, &mut rows[i]);
}
r
}
fn ntt(mut self) -> Coeffs<{ K * N }, Ntt> {
for ch in self.0.as_chunks_mut().0 {
low::mlkem_ntt(ch);
}
Coeffs(self.0, PhantomData)
}
fn compress_into_bytes_du_10(&self, c_1: &mut [u8; 960]) {
let (quads, _) = self.0.as_chunks::<4>();
let (out_chunks, _) = c_1.as_chunks_mut::<5>();
for (quad, out) in quads.iter().zip(out_chunks.iter_mut()) {
let t0 = compress_10(quad[0]);
let t1 = compress_10(quad[1]);
let t2 = compress_10(quad[2]);
let t3 = compress_10(quad[3]);
out[0] = t0 as u8;
out[1] = ((t0 >> 8) | (t1 << 2)) as u8;
out[2] = ((t1 >> 6) | (t2 << 4)) as u8;
out[3] = ((t2 >> 4) | (t3 << 6)) as u8;
out[4] = (t3 >> 2) as u8;
}
}
fn decompress_from_bytes_du_10(c_1: &[u8; 960]) -> Self {
let mut r = Self::zero();
let (in_chunks, _) = c_1.as_chunks::<5>();
let (quads, _) = r.0.as_chunks_mut::<4>();
for (inp, quad) in in_chunks.iter().zip(quads.iter_mut()) {
let t0 = (inp[0] as u16) | ((inp[1] as u16 & 0x3) << 8);
let t1 = ((inp[1] as u16) >> 2) | ((inp[2] as u16 & 0xf) << 6);
let t2 = ((inp[2] as u16) >> 4) | ((inp[3] as u16 & 0x3f) << 4);
let t3 = ((inp[3] as u16) >> 6) | ((inp[4] as u16) << 2);
quad[0] = decompress_10(t0);
quad[1] = decompress_10(t1);
quad[2] = decompress_10(t2);
quad[3] = decompress_10(t3);
}
r
}
}
impl Coeffs<N, Normal> {
fn from_message_bits(m: Message) -> Self {
let mut r = Self::zero();
for (i, byte) in m.0.iter().enumerate() {
for bit in 0..8 {
r.0[i * 8 + bit] = ct_select_i16(byte >> bit & 1, Q_HALF, 0);
}
}
r
}
fn to_message_bits(&self) -> Message {
let mut r = Message([0; 32]);
for (i, r) in r.0.iter_mut().enumerate() {
*r = compress_1_x8(&self.0[i * 8..(i + 1) * 8].try_into().unwrap());
}
r
}
fn compress_into_bytes_dv_4(&self, c_2: &mut [u8; 128]) {
let (pairs, _) = self.0.as_chunks::<2>();
for (pair, byte) in pairs.iter().zip(c_2.iter_mut()) {
*byte = compress_4(pair[0]) | (compress_4(pair[1]) << 4);
}
}
fn decompress_from_bytes_dv_4(c_2: &[u8; 128]) -> Self {
let mut r = Self::zero();
let (pairs, _) = r.0.as_chunks_mut::<2>();
for (byte, pair) in c_2.iter().zip(pairs.iter_mut()) {
pair[0] = decompress_4((byte & 0xf) as u16);
pair[1] = decompress_4((byte >> 4) as u16);
}
r
}
}
impl<const C: usize> Coeffs<C, Normal> {
fn add(mut self, term: &Self) -> Self {
for (ss, tt) in self.0.iter_mut().zip(term.0.iter()) {
*ss += *tt;
}
self.reduce_in_place();
self
}
fn sub(mut self, term: &Self) -> Self {
for (ss, tt) in self.0.iter_mut().zip(term.0.iter()) {
*ss -= *tt;
}
self.reduce_in_place();
self
}
}
fn sample_cbd2(buf: &[u8; 128], out: &mut [i16; 256]) {
for (in_bytes, out_coeffs) in buf.chunks_exact(4).zip(out.chunks_exact_mut(8)) {
let t = u32::from_le_bytes(in_bytes.try_into().unwrap());
let d = (t & 0x5555_5555) + ((t >> 1) & 0x5555_5555);
for (j, coeff) in out_coeffs.iter_mut().enumerate() {
let a = ((d >> (4 * j)) & 0x3) as i16;
let b = ((d >> (4 * j + 2)) & 0x3) as i16;
*coeff = a - b;
}
}
}
pub(crate) struct Shake128ForMlKem {
sponge: sha3::Shake128Sponge,
}
impl Shake128ForMlKem {
pub(crate) fn new(message: &[&[u8]]) -> Self {
Self {
sponge: sha3::Shake128Sponge::new_for_message(message),
}
}
pub(crate) fn sample_into(self, output: &mut [i16; 256]) {
let Self { mut sponge } = self;
let mut initial_bytes = [0; sha3::SHAKE_128_R_BYTES * 3];
sponge.squeeze(&mut initial_bytes);
let used = low::mlkem_rej_uniform_vartime(output, &initial_bytes) as usize;
if used < output.len() {
let output = &mut output[used..];
let tail_iterator = Shake128TwelveBitIterator::new(sponge).filter(|f| *f < Q);
for (out, coeff) in output.iter_mut().zip(tail_iterator) {
*out = coeff;
}
}
}
}
struct Shake128TwelveBitIterator {
sponge: sha3::Shake128Sponge,
samples: [i16; Self::SAMPLE_COUNT],
used: usize,
}
impl Shake128TwelveBitIterator {
fn new(sponge: sha3::Shake128Sponge) -> Self {
Self {
sponge,
samples: [0; Self::SAMPLE_COUNT],
used: Self::SAMPLE_COUNT,
}
}
const SAMPLE_COUNT: usize = sha3::SHAKE_128_R_BYTES / 3 * 2;
}
impl Iterator for Shake128TwelveBitIterator {
type Item = i16;
#[cold]
fn next(&mut self) -> Option<Self::Item> {
if self.used == self.samples.len() {
let mut bytes = [0u8; sha3::SHAKE_128_R_BYTES];
self.sponge.squeeze(&mut bytes);
for (buf, d) in bytes.chunks_exact(3).zip(self.samples.chunks_exact_mut(2)) {
d[0] = (u16::from_le_bytes([buf[0], buf[1]]) & 0xfff) as i16;
d[1] = (u16::from_le_bytes([buf[1], buf[2]]) >> 4) as i16;
}
self.used = 0;
}
let item = self.samples[self.used];
self.used += 1;
Some(item)
}
}
const K: usize = 3;
const K_BYTE: u8 = K as u8;
const DU: usize = 10;
const DV: usize = 4;
const Q: i16 = 3329;
const Q_HALF: i16 = (Q + 1) / 2;
const N: usize = 256;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shared_secret_debug() {
assert_eq!(format!("{:?}", SharedSecret([0u8; 32])), "SharedSecret(..)");
}
#[test]
fn pairwise() {
let d = DecapKey::generate().unwrap();
let (ess, ct) = d.encapsulation_key().encaps().unwrap();
let dss = d.decaps(&ct);
assert_eq!(ess.0, dss.0);
}
#[test]
fn encaps_modulus_test() {
assert_eq!(
EncapKey::from_bytes(&[0xffu8; 1184]).err(),
Some(Error::OutOfRange)
);
}
}