use crate::rng::randombytes;
use crate::{
params::*, poly::*, polyvec::*, symmetric::*, CryptoRng,
KyberLibError, RngCore,
};
#[cfg(feature = "hazmat")]
pub use crate::params::{
KYBER_INDCPA_BYTES, KYBER_INDCPA_PUBLIC_KEY_BYTES,
KYBER_INDCPA_SECRET_KEY_BYTES,
};
fn pack_pk(r: &mut [u8], pk: &mut Polyvec, seed: &[u8]) {
const END: usize = KYBER_SYM_BYTES + KYBER_POLYVEC_BYTES;
polyvec_tobytes(r, pk);
r[KYBER_POLYVEC_BYTES..END]
.copy_from_slice(&seed[..KYBER_SYM_BYTES]);
}
fn unpack_pk(pk: &mut Polyvec, seed: &mut [u8], packedpk: &[u8]) {
const END: usize = KYBER_SYM_BYTES + KYBER_POLYVEC_BYTES;
polyvec_frombytes(pk, packedpk);
seed[..KYBER_SYM_BYTES]
.copy_from_slice(&packedpk[KYBER_POLYVEC_BYTES..END]);
}
fn pack_sk(r: &mut [u8], sk: &mut Polyvec) {
polyvec_tobytes(r, sk);
}
fn unpack_sk(sk: &mut Polyvec, packedsk: &[u8]) {
polyvec_frombytes(sk, packedsk);
}
fn pack_ciphertext(r: &mut [u8], b: &mut Polyvec, v: Poly) {
polyvec_compress(r, *b);
poly_compress(&mut r[KYBER_POLYVEC_COMPRESSED_BYTES..], v);
}
fn unpack_ciphertext(b: &mut Polyvec, v: &mut Poly, c: &[u8]) {
polyvec_decompress(b, c);
poly_decompress(v, &c[KYBER_POLYVEC_COMPRESSED_BYTES..]);
}
fn rej_uniform(
r: &mut [i16],
len: usize,
buf: &[u8],
buflen: usize,
) -> usize {
let (mut ctr, mut pos) = (0usize, 0usize);
let (mut val0, mut val1);
while ctr < len && pos + 3 <= buflen {
val0 = (buf[pos] as u16 | (buf[pos + 1] as u16) << 8) & 0xFFF;
val1 = ((buf[pos + 1] >> 4) as u16
| (buf[pos + 2] as u16) << 4)
& 0xFFF;
pos += 3;
if val0 < KYBER_Q as u16 {
r[ctr] = val0 as i16;
ctr += 1;
}
if ctr < len && val1 < KYBER_Q as u16 {
r[ctr] = val1 as i16;
ctr += 1;
}
}
ctr
}
fn gen_a(a: &mut [Polyvec], b: &[u8]) {
gen_matrix(a, b, false);
}
fn gen_at(a: &mut [Polyvec], b: &[u8]) {
gen_matrix(a, b, true);
}
fn gen_matrix(a: &mut [Polyvec], seed: &[u8], transposed: bool) {
let mut ctr;
const GEN_MATRIX_NBLOCKS: usize =
(12 * KYBER_N / 8 * (1 << 12) / KYBER_Q + XOF_BLOCKBYTES)
/ XOF_BLOCKBYTES;
let mut buf = [0u8; GEN_MATRIX_NBLOCKS * XOF_BLOCKBYTES + 2];
let mut buflen: usize;
let mut off: usize;
let mut state = XofState::new();
#[allow(clippy::needless_range_loop)]
for i in 0..KYBER_SECURITY_PARAMETER {
for j in 0..KYBER_SECURITY_PARAMETER {
if transposed {
xof_absorb(&mut state, seed, i as u8, j as u8);
} else {
xof_absorb(&mut state, seed, j as u8, i as u8);
}
xof_squeezeblocks(&mut buf, GEN_MATRIX_NBLOCKS, &mut state);
buflen = GEN_MATRIX_NBLOCKS * XOF_BLOCKBYTES;
ctr = rej_uniform(
&mut a[i].vec[j].coeffs,
KYBER_N,
&buf,
buflen,
);
while ctr < KYBER_N {
off = buflen % 3;
for k in 0..off {
buf[k] = buf[buflen - off + k];
}
xof_squeezeblocks(&mut buf[off..], 1, &mut state);
buflen = off + XOF_BLOCKBYTES;
ctr += rej_uniform(
&mut a[i].vec[j].coeffs[ctr..],
KYBER_N - ctr,
&buf,
buflen,
);
}
}
}
}
pub(crate) fn indcpa_keypair<R>(
pk: &mut [u8],
sk: &mut [u8],
_seed: Option<(&[u8], &[u8])>,
_rng: &mut R,
) -> Result<(), KyberLibError>
where
R: CryptoRng + RngCore,
{
let mut a = [Polyvec::new(); KYBER_SECURITY_PARAMETER];
let (mut e, mut pkpv, mut skpv) =
(Polyvec::new(), Polyvec::new(), Polyvec::new());
let mut nonce = 0u8;
let mut buf = [0u8; 2 * KYBER_SYM_BYTES];
let mut randbuf = [0u8; 2 * KYBER_SYM_BYTES];
if let Some(s) = _seed {
randbuf[..KYBER_SYM_BYTES].copy_from_slice(s.0);
} else {
randombytes(&mut randbuf, KYBER_SYM_BYTES, _rng)?;
}
let mut g_input = [0u8; KYBER_SYM_BYTES + 1];
g_input[..KYBER_SYM_BYTES]
.copy_from_slice(&randbuf[..KYBER_SYM_BYTES]);
g_input[KYBER_SYM_BYTES] = KYBER_SECURITY_PARAMETER as u8;
hash_g(&mut buf, &g_input, KYBER_SYM_BYTES + 1);
let (publicseed, noiseseed) = buf.split_at(KYBER_SYM_BYTES);
gen_a(&mut a, publicseed);
for i in 0..KYBER_SECURITY_PARAMETER {
poly_getnoise_eta1(&mut skpv.vec[i], noiseseed, nonce);
nonce += 1;
}
for i in 0..KYBER_SECURITY_PARAMETER {
poly_getnoise_eta1(&mut e.vec[i], noiseseed, nonce);
nonce += 1;
}
polyvec_ntt(&mut skpv);
polyvec_ntt(&mut e);
#[allow(clippy::needless_range_loop)]
for i in 0..KYBER_SECURITY_PARAMETER {
polyvec_basemul_acc_montgomery(&mut pkpv.vec[i], &a[i], &skpv);
poly_tomont(&mut pkpv.vec[i]);
}
polyvec_add(&mut pkpv, &e);
polyvec_reduce(&mut pkpv);
pack_sk(sk, &mut skpv);
pack_pk(pk, &mut pkpv, publicseed);
Ok(())
}
pub(crate) fn indcpa_enc(
c: &mut [u8],
m: &[u8],
pk: &[u8],
coins: &[u8],
) {
let mut at = [Polyvec::new(); KYBER_SECURITY_PARAMETER];
let (mut sp, mut pkpv, mut ep, mut b) = (
Polyvec::new(),
Polyvec::new(),
Polyvec::new(),
Polyvec::new(),
);
let (mut v, mut k, mut epp) =
(Poly::new(), Poly::new(), Poly::new());
let mut seed = [0u8; KYBER_SYM_BYTES];
let mut nonce = 0u8;
unpack_pk(&mut pkpv, &mut seed, pk);
poly_frommsg(&mut k, m);
gen_at(&mut at, &seed);
for i in 0..KYBER_SECURITY_PARAMETER {
poly_getnoise_eta1(&mut sp.vec[i], coins, nonce);
nonce += 1;
}
for i in 0..KYBER_SECURITY_PARAMETER {
poly_getnoise_eta2(&mut ep.vec[i], coins, nonce);
nonce += 1;
}
poly_getnoise_eta2(&mut epp, coins, nonce);
polyvec_ntt(&mut sp);
#[allow(clippy::needless_range_loop)]
for i in 0..KYBER_SECURITY_PARAMETER {
polyvec_basemul_acc_montgomery(&mut b.vec[i], &at[i], &sp);
}
polyvec_basemul_acc_montgomery(&mut v, &pkpv, &sp);
polyvec_invntt_tomont(&mut b);
poly_invntt_tomont(&mut v);
polyvec_add(&mut b, &ep);
poly_add(&mut v, &epp);
poly_add(&mut v, &k);
polyvec_reduce(&mut b);
poly_reduce(&mut v);
pack_ciphertext(c, &mut b, v);
}
pub(crate) fn indcpa_dec(m: &mut [u8], c: &[u8], sk: &[u8]) {
let (mut b, mut skpv) = (Polyvec::new(), Polyvec::new());
let (mut v, mut mp) = (Poly::new(), Poly::new());
unpack_ciphertext(&mut b, &mut v, c);
unpack_sk(&mut skpv, sk);
polyvec_ntt(&mut b);
polyvec_basemul_acc_montgomery(&mut mp, &skpv, &b);
poly_invntt_tomont(&mut mp);
poly_sub(&mut mp, &v);
poly_reduce(&mut mp);
poly_tomsg(m, mp);
}
#[allow(dead_code)]
pub(crate) fn gen_matrix_generic<P: crate::paramsets::MlKemParams>(
a: &mut [Poly],
seed: &[u8],
transposed: bool,
) {
debug_assert_eq!(a.len(), P::K * P::K);
let mut ctr;
const GEN_MATRIX_NBLOCKS: usize =
(12 * KYBER_N / 8 * (1 << 12) / KYBER_Q + XOF_BLOCKBYTES)
/ XOF_BLOCKBYTES;
let mut buf = [0u8; GEN_MATRIX_NBLOCKS * XOF_BLOCKBYTES + 2];
let mut buflen: usize;
let mut off: usize;
let mut state = XofState::new();
for i in 0..P::K {
for j in 0..P::K {
if transposed {
xof_absorb(&mut state, seed, i as u8, j as u8);
} else {
xof_absorb(&mut state, seed, j as u8, i as u8);
}
xof_squeezeblocks(&mut buf, GEN_MATRIX_NBLOCKS, &mut state);
buflen = GEN_MATRIX_NBLOCKS * XOF_BLOCKBYTES;
ctr = rej_uniform(
&mut a[i * P::K + j].coeffs,
KYBER_N,
&buf,
buflen,
);
while ctr < KYBER_N {
off = buflen % 3;
for k in 0..off {
buf[k] = buf[buflen - off + k];
}
xof_squeezeblocks(&mut buf[off..], 1, &mut state);
buflen = off + XOF_BLOCKBYTES;
ctr += rej_uniform(
&mut a[i * P::K + j].coeffs[ctr..],
KYBER_N - ctr,
&buf,
buflen,
);
}
}
}
}
const MAX_K: usize = 4;
#[allow(dead_code, clippy::needless_range_loop)]
pub(crate) fn indcpa_keypair_generic<P, R>(
pk: &mut [u8],
sk: &mut [u8],
seed: Option<(&[u8], &[u8])>,
rng: &mut R,
) -> Result<(), KyberLibError>
where
P: crate::paramsets::MlKemParams,
R: CryptoRng + RngCore,
{
debug_assert!(P::K <= MAX_K);
let mut a = [Poly::new(); MAX_K * MAX_K];
let mut skpv = [Poly::new(); MAX_K];
let mut pkpv = [Poly::new(); MAX_K];
let mut e = [Poly::new(); MAX_K];
let mut nonce = 0u8;
let mut buf = [0u8; 2 * KYBER_SYM_BYTES];
let mut randbuf = [0u8; 2 * KYBER_SYM_BYTES];
if let Some(s) = seed {
randbuf[..KYBER_SYM_BYTES].copy_from_slice(s.0);
} else {
randombytes(&mut randbuf, KYBER_SYM_BYTES, rng)?;
}
let mut g_input = [0u8; KYBER_SYM_BYTES + 1];
g_input[..KYBER_SYM_BYTES]
.copy_from_slice(&randbuf[..KYBER_SYM_BYTES]);
g_input[KYBER_SYM_BYTES] = P::K as u8;
hash_g(&mut buf, &g_input, KYBER_SYM_BYTES + 1);
let (publicseed, noiseseed) = buf.split_at(KYBER_SYM_BYTES);
gen_matrix_generic::<P>(&mut a[..P::K * P::K], publicseed, false);
for i in 0..P::K {
poly_getnoise_eta1_generic::<P>(&mut skpv[i], noiseseed, nonce);
nonce += 1;
}
for i in 0..P::K {
poly_getnoise_eta1_generic::<P>(&mut e[i], noiseseed, nonce);
nonce += 1;
}
polyvec_ntt_generic::<P>(&mut skpv[..P::K]);
polyvec_ntt_generic::<P>(&mut e[..P::K]);
for i in 0..P::K {
let row_start = i * P::K;
let row_end = row_start + P::K;
polyvec_basemul_acc_montgomery_generic::<P>(
&mut pkpv[i],
&a[row_start..row_end],
&skpv[..P::K],
);
poly_tomont(&mut pkpv[i]);
}
polyvec_add_generic::<P>(&mut pkpv[..P::K], &e[..P::K]);
polyvec_reduce_generic::<P>(&mut pkpv[..P::K]);
polyvec_tobytes_generic::<P>(sk, &skpv[..P::K]);
polyvec_tobytes_generic::<P>(
&mut pk[..polyvec_bytes_len::<P>()],
&pkpv[..P::K],
);
pk[polyvec_bytes_len::<P>()
..polyvec_bytes_len::<P>() + KYBER_SYM_BYTES]
.copy_from_slice(&publicseed[..KYBER_SYM_BYTES]);
Ok(())
}
#[cfg(test)]
mod indcpa_generic_tests {
#![allow(unused_imports)]
use super::*;
use crate::paramsets::MlKemParams;
#[test]
#[cfg(feature = "kyber768")]
fn gen_matrix_matches_existing_kyber768() {
use crate::MlKem768;
let seed = [0xA5u8; KYBER_SYM_BYTES];
let mut a_existing = [Polyvec::new(); KYBER_SECURITY_PARAMETER];
gen_matrix(&mut a_existing, &seed, false);
let mut a_generic = [Poly::new(); 9];
gen_matrix_generic::<MlKem768>(&mut a_generic, &seed, false);
for i in 0..3 {
for j in 0..3 {
assert_eq!(
a_existing[i].vec[j].coeffs,
a_generic[i * 3 + j].coeffs,
"matrix entry ({i},{j}) diverges"
);
}
}
}
#[test]
#[cfg(feature = "kyber768")]
fn gen_matrix_transposed_matches_existing_kyber768() {
use crate::MlKem768;
let seed = [0xC3u8; KYBER_SYM_BYTES];
let mut a_existing = [Polyvec::new(); KYBER_SECURITY_PARAMETER];
gen_matrix(&mut a_existing, &seed, true);
let mut a_generic = [Poly::new(); 9];
gen_matrix_generic::<MlKem768>(&mut a_generic, &seed, true);
for i in 0..3 {
for j in 0..3 {
assert_eq!(
a_existing[i].vec[j].coeffs,
a_generic[i * 3 + j].coeffs
);
}
}
}
#[test]
#[cfg(feature = "kyber512")]
fn gen_matrix_matches_existing_kyber512() {
use crate::MlKem512;
let seed = [0xA5u8; KYBER_SYM_BYTES];
let mut a_existing = [Polyvec::new(); KYBER_SECURITY_PARAMETER];
gen_matrix(&mut a_existing, &seed, false);
let mut a_generic = [Poly::new(); 4]; gen_matrix_generic::<MlKem512>(&mut a_generic, &seed, false);
for i in 0..2 {
for j in 0..2 {
assert_eq!(
a_existing[i].vec[j].coeffs,
a_generic[i * 2 + j].coeffs
);
}
}
}
#[test]
#[cfg(feature = "kyber1024")]
fn gen_matrix_matches_existing_kyber1024() {
use crate::MlKem1024;
let seed = [0xA5u8; KYBER_SYM_BYTES];
let mut a_existing = [Polyvec::new(); KYBER_SECURITY_PARAMETER];
gen_matrix(&mut a_existing, &seed, false);
let mut a_generic = [Poly::new(); 16]; gen_matrix_generic::<MlKem1024>(&mut a_generic, &seed, false);
for i in 0..4 {
for j in 0..4 {
assert_eq!(
a_existing[i].vec[j].coeffs,
a_generic[i * 4 + j].coeffs
);
}
}
}
}
#[allow(dead_code, clippy::needless_range_loop)]
pub(crate) fn indcpa_enc_generic<P: crate::paramsets::MlKemParams>(
c: &mut [u8],
m: &[u8],
pk: &[u8],
coins: &[u8],
) {
debug_assert!(P::K <= MAX_K);
let mut at = [Poly::new(); MAX_K * MAX_K];
let mut sp = [Poly::new(); MAX_K];
let mut pkpv = [Poly::new(); MAX_K];
let mut ep = [Poly::new(); MAX_K];
let mut b = [Poly::new(); MAX_K];
let (mut v, mut k, mut epp) =
(Poly::new(), Poly::new(), Poly::new());
let mut seed = [0u8; KYBER_SYM_BYTES];
let mut nonce = 0u8;
polyvec_frombytes_generic::<P>(&mut pkpv[..P::K], pk);
let pv_bytes = polyvec_bytes_len::<P>();
seed[..KYBER_SYM_BYTES]
.copy_from_slice(&pk[pv_bytes..pv_bytes + KYBER_SYM_BYTES]);
poly_frommsg(&mut k, m);
gen_matrix_generic::<P>(&mut at[..P::K * P::K], &seed, true);
for i in 0..P::K {
poly_getnoise_eta1_generic::<P>(&mut sp[i], coins, nonce);
nonce += 1;
}
for i in 0..P::K {
poly_getnoise_eta2_generic::<P>(&mut ep[i], coins, nonce);
nonce += 1;
}
poly_getnoise_eta2_generic::<P>(&mut epp, coins, nonce);
polyvec_ntt_generic::<P>(&mut sp[..P::K]);
for i in 0..P::K {
let row_start = i * P::K;
let row_end = row_start + P::K;
polyvec_basemul_acc_montgomery_generic::<P>(
&mut b[i],
&at[row_start..row_end],
&sp[..P::K],
);
}
polyvec_basemul_acc_montgomery_generic::<P>(
&mut v,
&pkpv[..P::K],
&sp[..P::K],
);
polyvec_invntt_tomont_generic::<P>(&mut b[..P::K]);
poly_invntt_tomont(&mut v);
polyvec_add_generic::<P>(&mut b[..P::K], &ep[..P::K]);
poly_add(&mut v, &epp);
poly_add(&mut v, &k);
polyvec_reduce_generic::<P>(&mut b[..P::K]);
poly_reduce(&mut v);
let pv_compressed_bytes = polyvec_compressed_len::<P>();
polyvec_compress_generic::<P>(
&mut c[..pv_compressed_bytes],
&b[..P::K],
);
poly_compress_generic::<P>(&mut c[pv_compressed_bytes..], v);
}
#[allow(dead_code, clippy::needless_range_loop)]
pub(crate) fn indcpa_dec_generic<P: crate::paramsets::MlKemParams>(
m: &mut [u8],
c: &[u8],
sk: &[u8],
) {
debug_assert!(P::K <= MAX_K);
let mut b = [Poly::new(); MAX_K];
let mut skpv = [Poly::new(); MAX_K];
let (mut v, mut mp) = (Poly::new(), Poly::new());
let pv_compressed_bytes = polyvec_compressed_len::<P>();
polyvec_decompress_generic::<P>(
&mut b[..P::K],
&c[..pv_compressed_bytes],
);
poly_decompress_generic::<P>(&mut v, &c[pv_compressed_bytes..]);
polyvec_frombytes_generic::<P>(&mut skpv[..P::K], sk);
polyvec_ntt_generic::<P>(&mut b[..P::K]);
polyvec_basemul_acc_montgomery_generic::<P>(
&mut mp,
&skpv[..P::K],
&b[..P::K],
);
poly_invntt_tomont(&mut mp);
poly_sub(&mut mp, &v);
poly_reduce(&mut mp);
poly_tomsg(m, mp);
}
#[cfg(test)]
mod indcpa_integration_tests {
#![allow(unused_imports)]
use super::*;
use crate::paramsets::MlKemParams;
#[test]
#[cfg(feature = "kyber768")]
fn indcpa_enc_generic_matches_existing_kyber768() {
use crate::MlKem768;
use rand::rngs::StdRng;
use rand::SeedableRng;
let mut rng = StdRng::from_seed([1u8; 32]);
let mut pk = [0u8; KYBER_INDCPA_PUBLIC_KEY_BYTES];
let mut sk = [0u8; KYBER_INDCPA_SECRET_KEY_BYTES];
let kp_seed = [0x42u8; 64];
indcpa_keypair(
&mut pk,
&mut sk,
Some((&kp_seed[..32], &kp_seed[32..])),
&mut rng,
)
.unwrap();
let msg = [0xCDu8; KYBER_SYM_BYTES];
let coins = [0x66u8; KYBER_SYM_BYTES];
let mut c_existing = [0u8; KYBER_INDCPA_BYTES];
indcpa_enc(&mut c_existing, &msg, &pk, &coins);
let mut c_generic = [0u8; KYBER_INDCPA_BYTES];
indcpa_enc_generic::<MlKem768>(
&mut c_generic,
&msg,
&pk,
&coins,
);
assert_eq!(
c_existing.as_slice(),
c_generic.as_slice(),
"indcpa_enc_generic ciphertext diverges from existing"
);
}
#[test]
#[cfg(feature = "kyber768")]
fn indcpa_dec_generic_matches_existing_kyber768() {
use crate::MlKem768;
use rand::rngs::StdRng;
use rand::SeedableRng;
let mut rng = StdRng::from_seed([7u8; 32]);
let mut pk = [0u8; KYBER_INDCPA_PUBLIC_KEY_BYTES];
let mut sk = [0u8; KYBER_INDCPA_SECRET_KEY_BYTES];
let kp_seed = [0x88u8; 64];
indcpa_keypair(
&mut pk,
&mut sk,
Some((&kp_seed[..32], &kp_seed[32..])),
&mut rng,
)
.unwrap();
let msg = [0xEFu8; KYBER_SYM_BYTES];
let coins = [0x99u8; KYBER_SYM_BYTES];
let mut c = [0u8; KYBER_INDCPA_BYTES];
indcpa_enc(&mut c, &msg, &pk, &coins);
let mut m_existing = [0u8; KYBER_SYM_BYTES];
indcpa_dec(&mut m_existing, &c, &sk);
let mut m_generic = [0u8; KYBER_SYM_BYTES];
indcpa_dec_generic::<MlKem768>(&mut m_generic, &c, &sk);
assert_eq!(m_existing, m_generic, "decrypted messages differ");
assert_eq!(m_generic, msg, "IND-CPA correctness violated");
}
#[test]
#[cfg(feature = "kyber768")]
fn indcpa_round_trip_all_generic_kyber768() {
use crate::MlKem768;
use rand::rngs::StdRng;
use rand::SeedableRng;
let mut rng = StdRng::from_seed([0u8; 32]);
let kp_seed = [0xAAu8; 64];
let mut pk = [0u8; KYBER_INDCPA_PUBLIC_KEY_BYTES];
let mut sk = [0u8; KYBER_INDCPA_SECRET_KEY_BYTES];
indcpa_keypair_generic::<MlKem768, _>(
&mut pk,
&mut sk,
Some((&kp_seed[..32], &kp_seed[32..])),
&mut rng,
)
.unwrap();
let msg = [0x55u8; KYBER_SYM_BYTES];
let coins = [0xBBu8; KYBER_SYM_BYTES];
let mut c = [0u8; KYBER_INDCPA_BYTES];
indcpa_enc_generic::<MlKem768>(&mut c, &msg, &pk, &coins);
let mut m_recovered = [0u8; KYBER_SYM_BYTES];
indcpa_dec_generic::<MlKem768>(&mut m_recovered, &c, &sk);
assert_eq!(
m_recovered, msg,
"all-generic IND-CPA round-trip failed"
);
}
#[test]
#[cfg(feature = "kyber768")]
fn indcpa_keypair_generic_matches_existing_kyber768() {
use crate::MlKem768;
use rand::rngs::StdRng;
use rand::SeedableRng;
let seed = [0x77u8; 64];
let mut rng = StdRng::from_seed([0u8; 32]);
let mut pk_existing = [0u8; KYBER_INDCPA_PUBLIC_KEY_BYTES];
let mut sk_existing = [0u8; KYBER_INDCPA_SECRET_KEY_BYTES];
indcpa_keypair(
&mut pk_existing,
&mut sk_existing,
Some((&seed[..32], &seed[32..])),
&mut rng,
)
.unwrap();
let mut rng2 = StdRng::from_seed([0u8; 32]);
let mut pk_generic = [0u8; KYBER_INDCPA_PUBLIC_KEY_BYTES];
let mut sk_generic = [0u8; KYBER_INDCPA_SECRET_KEY_BYTES];
indcpa_keypair_generic::<MlKem768, _>(
&mut pk_generic,
&mut sk_generic,
Some((&seed[..32], &seed[32..])),
&mut rng2,
)
.unwrap();
assert_eq!(
pk_existing.as_slice(),
pk_generic.as_slice(),
"indcpa_keypair_generic pk diverges from existing — \
FIPS 203 compliance regression in the generic port"
);
assert_eq!(
sk_existing.as_slice(),
sk_generic.as_slice(),
"indcpa_keypair_generic sk diverges from existing"
);
}
}