use super::MlDsaError;
use super::decompose;
use super::encode;
use super::ntt::{self, mod_q};
use super::params::{D, MAX_K, MAX_L, N, Params, Q};
use super::rng::CryptoRng;
use super::sample;
use super::sha3;
use alloc::vec::Vec;
#[cfg(any(feature = "compressed-poly", feature = "compressed-challenge"))]
use super::compressed;
#[cfg(feature = "sca-protected")]
use super::masked::{self, MaskedPoly};
#[cfg(all(feature = "sca-protected", feature = "compressed-challenge"))]
compile_error!(
"features `sca-protected` and `compressed-challenge` are mutually exclusive: masking requires NTT-domain multiplication, schoolbook operates in time domain"
);
#[cfg(all(feature = "sca-protected", feature = "small-secret"))]
compile_error!("features `sca-protected` and `small-secret` are mutually exclusive: masking operates in i32 domain");
#[cfg(all(feature = "sca-protected", feature = "union-buffer"))]
compile_error!("features `sca-protected` and `union-buffer` are mutually exclusive");
#[cfg(feature = "sca-protected")]
use super::sha3::KeccakState;
#[cfg(feature = "sca-protected")]
use super::shuffle;
#[cfg(feature = "small-secret")]
use super::smallpoly::{self, SmallPoly};
#[cfg(feature = "sca-protected")]
struct ScaRng {
state: KeccakState,
}
#[cfg(feature = "sca-protected")]
impl ScaRng {
fn from_seed(seed: &[u8]) -> Self {
let mut s = sha3::shake256();
s.absorb(b"quantica-mldsa-sca-v1");
s.absorb(seed);
Self { state: s }
}
}
#[cfg(feature = "sca-protected")]
impl CryptoRng for ScaRng {
fn fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), MlDsaError> {
self.state.squeeze(dest);
Ok(())
}
}
#[cfg(not(feature = "low-mem"))]
fn mat_vec_mul(a_hat: &[[[i32; N]; MAX_L]; MAX_K], s: &[[i32; N]], k: usize, l: usize, result: &mut [[i32; N]]) {
for i in 0..k {
result[i] = [0i32; N];
for j in 0..l {
let prod = ntt::pointwise_mul(&a_hat[i][j], &s[j]);
result[i] = ntt::poly_add(&result[i], &prod);
}
}
}
#[cfg(feature = "low-mem")]
fn mat_vec_mul_lazy(rho: &[u8; 32], s: &[[i32; N]], k: usize, l: usize, result: &mut [[i32; N]]) {
for i in 0..k {
result[i] = [0i32; N];
for j in 0..l {
let a_ij = sample::rej_ntt_poly(rho, j as u8, i as u8);
let prod = ntt::pointwise_mul(&a_ij, &s[j]);
result[i] = ntt::poly_add(&result[i], &prod);
}
}
}
fn ntt_vec(v: &mut [[i32; N]], len: usize) {
for poly in v[..len].iter_mut() {
ntt::ntt(poly);
}
}
fn ntt_inv_vec(v: &mut [[i32; N]], len: usize) {
for poly in v[..len].iter_mut() {
ntt::ntt_inv(poly);
}
}
fn vec_add(a: &[[i32; N]], b: &[[i32; N]], out: &mut [[i32; N]], len: usize) {
for i in 0..len {
out[i] = ntt::poly_add(&a[i], &b[i]);
}
}
fn vec_sub(a: &[[i32; N]], b: &[[i32; N]], out: &mut [[i32; N]], len: usize) {
for i in 0..len {
out[i] = ntt::poly_sub(&a[i], &b[i]);
}
}
#[cfg(feature = "low-stack")]
fn poly_vec(len: usize) -> Vec<[i32; N]> {
vec![[0i32; N]; len]
}
fn check_norm(v: &[i32; N], bound: i32) -> bool {
for &c in v.iter() {
let mut val = mod_q(c);
if val > Q / 2 {
val -= Q;
}
if val >= bound || val <= -bound {
return false;
}
}
true
}
fn check_norm_vec(v: &[[i32; N]], bound: i32, len: usize) -> bool {
for poly in v[..len].iter() {
if !check_norm(poly, bound) {
return false;
}
}
true
}
pub fn keygen_internal<P: Params>(xi: &[u8; 32]) -> (Vec<u8>, Vec<u8>) {
let k = P::K;
let l = P::L;
let mut h_input = [0u8; 34];
h_input[..32].copy_from_slice(xi);
h_input[32] = k as u8;
h_input[33] = l as u8;
let mut hash_out = [0u8; 128]; let mut state = sha3::shake256();
state.absorb(&h_input);
state.squeeze(&mut hash_out);
let mut rho = [0u8; 32];
rho.copy_from_slice(&hash_out[..32]);
let mut rho_prime = [0u8; 64];
rho_prime.copy_from_slice(&hash_out[32..96]);
let mut k_seed = [0u8; 32];
k_seed.copy_from_slice(&hash_out[96..128]);
let (mut s1, mut s2) = sample::expand_s::<P>(&rho_prime);
let mut s1_hat = s1;
ntt_vec(&mut s1_hat, l);
let mut t = [[0i32; N]; MAX_K];
#[cfg(not(feature = "low-mem"))]
{
let a_hat = sample::expand_a::<P>(&rho);
mat_vec_mul(&a_hat, &s1_hat, k, l, &mut t);
}
#[cfg(feature = "low-mem")]
mat_vec_mul_lazy(&rho, &s1_hat, k, l, &mut t);
ntt_inv_vec(&mut t, k);
{
let mut tmp = [[0i32; N]; MAX_K];
vec_add(&t, &s2, &mut tmp, k);
t = tmp;
}
let (t1, t0) = encode::power2round_vec(&t, k);
let pk = encode::pk_encode::<P>(&rho, &t1);
let mut tr = [0u8; 64];
sha3::shake256_digest(&pk, &mut tr);
let sk = encode::sk_encode::<P>(&rho, &k_seed, &tr, &s1, &s2, &t0);
for poly in s1[..l].iter_mut() {
for c in poly.iter_mut() {
*c = 0;
}
}
for poly in s2[..k].iter_mut() {
for c in poly.iter_mut() {
*c = 0;
}
}
for byte in rho_prime.iter_mut() {
*byte = 0;
}
for byte in k_seed.iter_mut() {
*byte = 0;
}
(pk, sk)
}
pub fn sign_internal<P: Params>(sk: &[u8], m_prime: &[u8], rnd: &[u8; 32]) -> Result<Vec<u8>, MlDsaError> {
let k = P::K;
let l = P::L;
let gamma1 = P::GAMMA1;
let gamma2 = P::GAMMA2;
let beta = P::BETA;
let omega = P::OMEGA;
let c_tilde_len = P::LAMBDA / 4;
#[cfg(feature = "indexed-sk")]
let (rho, k_seed, tr) = encode::sk_decode_seeds::<P>(sk);
#[cfg(not(feature = "indexed-sk"))]
let (rho, k_seed, tr, s1, s2, t0) = encode::sk_decode::<P>(sk);
#[cfg(not(feature = "low-stack"))]
let mut s1_hat = {
#[cfg(feature = "indexed-sk")]
{
let mut v = [[0i32; N]; MAX_L];
for i in 0..l {
encode::sk_decode_s1::<P>(sk, i, &mut v[i]);
}
v
}
#[cfg(not(feature = "indexed-sk"))]
{
s1
}
};
#[cfg(feature = "low-stack")]
let mut s1_hat = {
let mut v = poly_vec(l);
#[cfg(feature = "indexed-sk")]
for i in 0..l {
encode::sk_decode_s1::<P>(sk, i, &mut v[i]);
}
#[cfg(not(feature = "indexed-sk"))]
for i in 0..l {
v[i] = s1[i];
}
v
};
#[cfg(not(feature = "low-stack"))]
let mut s2_hat = {
#[cfg(feature = "indexed-sk")]
{
let mut v = [[0i32; N]; MAX_K];
for i in 0..k {
encode::sk_decode_s2::<P>(sk, i, &mut v[i]);
}
v
}
#[cfg(not(feature = "indexed-sk"))]
{
s2
}
};
#[cfg(feature = "low-stack")]
let mut s2_hat = {
let mut v = poly_vec(k);
#[cfg(feature = "indexed-sk")]
for i in 0..k {
encode::sk_decode_s2::<P>(sk, i, &mut v[i]);
}
#[cfg(not(feature = "indexed-sk"))]
for i in 0..k {
v[i] = s2[i];
}
v
};
#[cfg(not(feature = "low-stack"))]
let mut t0_hat = {
#[cfg(feature = "indexed-sk")]
{
let mut v = [[0i32; N]; MAX_K];
for i in 0..k {
encode::sk_decode_t0::<P>(sk, i, &mut v[i]);
}
v
}
#[cfg(not(feature = "indexed-sk"))]
{
t0
}
};
#[cfg(feature = "low-stack")]
let mut t0_hat = {
let mut v = poly_vec(k);
#[cfg(feature = "indexed-sk")]
for i in 0..k {
encode::sk_decode_t0::<P>(sk, i, &mut v[i]);
}
#[cfg(not(feature = "indexed-sk"))]
for i in 0..k {
v[i] = t0[i];
}
v
};
#[cfg(feature = "sca-protected")]
let (mut s1_hat_m, mut s2_hat_m, mut t0_hat_m, mut sca_rng) = {
let mut sca_seed = [0u8; 64];
{
let mut h = sha3::shake256();
h.absorb(b"quantica-mldsa-sca-seed-v1");
h.absorb(&k_seed);
h.absorb(rnd);
h.absorb(&tr);
h.absorb(m_prime);
h.squeeze(&mut sca_seed);
}
let mut rng = ScaRng::from_seed(&sca_seed);
for i in 0..l {
shuffle::ntt_shuffled(&mut s1_hat[i], &mut rng)?;
}
for i in 0..k {
shuffle::ntt_shuffled(&mut s2_hat[i], &mut rng)?;
}
for i in 0..k {
shuffle::ntt_shuffled(&mut t0_hat[i], &mut rng)?;
}
let mut s1m: [MaskedPoly; MAX_L] = core::array::from_fn(|_| MaskedPoly::zero());
let mut s2m: [MaskedPoly; MAX_K] = core::array::from_fn(|_| MaskedPoly::zero());
let mut t0m: [MaskedPoly; MAX_K] = core::array::from_fn(|_| MaskedPoly::zero());
for i in 0..l {
s1m[i] = MaskedPoly::mask(&s1_hat[i], &mut rng)?;
}
for i in 0..k {
s2m[i] = MaskedPoly::mask(&s2_hat[i], &mut rng)?;
}
for i in 0..k {
t0m[i] = MaskedPoly::mask(&t0_hat[i], &mut rng)?;
}
for i in 0..l {
s1_hat[i] = [0i32; N];
}
for i in 0..k {
s2_hat[i] = [0i32; N];
}
for i in 0..k {
t0_hat[i] = [0i32; N];
}
(s1m, s2m, t0m, rng)
};
#[cfg(not(feature = "sca-protected"))]
{
#[cfg(not(any(feature = "compressed-challenge", feature = "small-secret")))]
{
ntt_vec(&mut s1_hat, l);
ntt_vec(&mut s2_hat, k);
ntt_vec(&mut t0_hat, k);
}
#[cfg(all(feature = "small-secret", not(feature = "compressed-challenge")))]
{
ntt_vec(&mut t0_hat, k);
}
}
#[cfg(feature = "small-secret")]
let (s1_small, s2_small) = {
let mut s1s: [SmallPoly; MAX_L] = core::array::from_fn(|_| SmallPoly::zero());
let mut s2s: [SmallPoly; MAX_K] = core::array::from_fn(|_| SmallPoly::zero());
for i in 0..l {
s1s[i] = SmallPoly::from_i32(&s1_hat[i]);
smallpoly::small_ntt(&mut s1s[i]);
}
for i in 0..k {
s2s[i] = SmallPoly::from_i32(&s2_hat[i]);
smallpoly::small_ntt(&mut s2s[i]);
}
(s1s, s2s)
};
#[cfg(not(feature = "low-mem"))]
let a_hat = sample::expand_a::<P>(&rho);
let mut mu = [0u8; 64];
{
let mut state = sha3::shake256();
state.absorb(&tr);
state.absorb(m_prime);
state.squeeze(&mut mu);
}
let mut rho_double_prime = [0u8; 64];
{
let mut state = sha3::shake256();
state.absorb(&k_seed);
state.absorb(rnd);
state.absorb(&mu);
state.squeeze(&mut rho_double_prime);
}
let mut kappa: u16 = 0;
loop {
#[cfg(feature = "sca-protected")]
{
for i in 0..l {
s1_hat_m[i].refresh(&mut sca_rng)?;
}
for i in 0..k {
s2_hat_m[i].refresh(&mut sca_rng)?;
}
for i in 0..k {
t0_hat_m[i].refresh(&mut sca_rng)?;
}
}
#[cfg(not(feature = "sca-masked-y"))]
let y = sample::expand_mask::<P>(&rho_double_prime, kappa);
#[cfg(feature = "sca-masked-y")]
let (y, w_precomputed) = {
let mut y_m: [masked::MaskedPoly; MAX_L] = core::array::from_fn(|_| masked::MaskedPoly::zero());
for r in 0..l {
y_m[r] = masked::MaskedPoly::sample_expand_mask(
&rho_double_prime,
kappa + r as u16,
gamma1,
P::BITLEN_GAMMA1_MINUS1,
);
}
let mut y_hat_m: [masked::MaskedPoly; MAX_L] = core::array::from_fn(|_| masked::MaskedPoly::zero());
for r in 0..l {
y_hat_m[r].share0 = y_m[r].share0;
y_hat_m[r].share1 = y_m[r].share1;
masked::masked_ntt(&mut y_hat_m[r]);
}
let mut w_m: [masked::MaskedPoly; MAX_K] = core::array::from_fn(|_| masked::MaskedPoly::zero());
#[cfg(not(feature = "low-mem"))]
masked::masked_mat_vec_mul(&a_hat, &y_hat_m, k, l, &mut w_m);
#[cfg(feature = "low-mem")]
masked::masked_mat_vec_mul_lazy(&rho, &y_hat_m, k, l, &mut w_m);
for r in 0..l {
y_hat_m[r].zeroize();
}
for i in 0..k {
masked::masked_ntt_inv(&mut w_m[i]);
}
let mut w_tmp = [[0i32; N]; MAX_K];
for i in 0..k {
w_tmp[i] = w_m[i].unmask();
w_m[i].zeroize();
}
let mut y_out = [[0i32; N]; MAX_L];
for r in 0..l {
let um = y_m[r].unmask();
for n in 0..N {
let mut v = um[n];
if v > Q / 2 {
v -= Q;
}
y_out[r][n] = v;
}
y_m[r].zeroize();
}
(y_out, w_tmp)
};
#[cfg(not(feature = "compressed-poly"))]
{
#[cfg(not(feature = "low-stack"))]
let mut _w_full = [[0i32; N]; MAX_K];
#[cfg(feature = "low-stack")]
let mut _w_full = poly_vec(k);
}
#[cfg(not(feature = "sca-masked-y"))]
let mut w_tmp = {
let mut y_hat = y;
ntt_vec(&mut y_hat, l);
let mut wt = [[0i32; N]; MAX_K];
#[cfg(not(feature = "low-mem"))]
mat_vec_mul(&a_hat, &y_hat, k, l, &mut wt);
#[cfg(feature = "low-mem")]
mat_vec_mul_lazy(&rho, &y_hat, k, l, &mut wt);
ntt_inv_vec(&mut wt, k);
wt
};
#[cfg(feature = "sca-masked-y")]
let mut w_tmp = w_precomputed;
#[cfg(feature = "compressed-poly")]
let w_comp = {
let mut wc = compressed::CompressedVecK::new(k);
for i in 0..k {
for c in w_tmp[i].iter_mut() {
*c = mod_q(*c);
}
wc.pack(i, &w_tmp[i]);
}
wc
};
#[cfg(not(feature = "low-stack"))]
let mut w1 = [[0i32; N]; MAX_K];
#[cfg(feature = "low-stack")]
let mut w1 = poly_vec(k);
decompose::high_bits_vec(&w_tmp, gamma2, &mut w1, k);
#[cfg(not(feature = "compressed-poly"))]
let w = w_tmp;
#[cfg(feature = "compressed-poly")]
drop(w_tmp);
let w1_encoded = encode::w1_encode::<P>(&w1);
#[cfg(feature = "low-stack")]
drop(w1);
let mut c_tilde_buf = [0u8; 64];
{
let mut state = sha3::shake256();
state.absorb(&mu);
state.absorb(&w1_encoded);
state.squeeze(&mut c_tilde_buf[..c_tilde_len]);
}
let c_tilde = &c_tilde_buf[..c_tilde_len];
let c = sample::sample_in_ball::<P>(c_tilde);
#[cfg(not(feature = "compressed-challenge"))]
let c_hat = {
let mut ch = c;
for coeff in ch.iter_mut() {
*coeff = mod_q(*coeff);
}
ntt::ntt(&mut ch);
ch
};
#[cfg(feature = "compressed-challenge")]
let c_comp = {
let mut cc = [0u8; compressed::COMPRESSED_CHALLENGE_BYTES];
compressed::challenge_compress(&mut cc, &c, P::TAU);
cc
};
#[cfg(feature = "small-secret")]
let c_small_ntt = {
let mut cs = SmallPoly::from_i32(&c);
smallpoly::small_ntt(&mut cs);
cs
};
#[cfg(feature = "union-buffer")]
{
let mut z = [[0i32; N]; MAX_L];
let mut tmp = [0i32; N];
let mut rejected = false;
for l_idx in 0..l {
#[cfg(all(not(feature = "compressed-challenge"), not(feature = "small-secret")))]
{
tmp = ntt::pointwise_mul(&c_hat, &s1_hat[l_idx]);
ntt::ntt_inv(&mut tmp);
}
#[cfg(feature = "compressed-challenge")]
{
tmp = [0i32; N];
compressed::schoolbook_mul_add(&mut tmp, &c_comp, &s1_hat[l_idx], P::TAU);
}
#[cfg(all(feature = "small-secret", not(feature = "compressed-challenge")))]
{
tmp = smallpoly::small_basemul_invntt_widen(&c_small_ntt, &s1_small[l_idx]);
}
z[l_idx] = ntt::poly_add(&y[l_idx], &tmp);
}
if !check_norm_vec(&z, gamma1 - beta, l) {
kappa += l as u16;
continue;
}
let mut h = [[0i32; N]; MAX_K];
let mut total_hints = 0usize;
let mut wbuf = [0i32; N];
for k_idx in 0..k {
if rejected {
break;
}
#[cfg(all(not(feature = "compressed-challenge"), not(feature = "small-secret")))]
{
tmp = ntt::pointwise_mul(&c_hat, &s2_hat[k_idx]);
ntt::ntt_inv(&mut tmp);
}
#[cfg(feature = "compressed-challenge")]
{
tmp = [0i32; N];
compressed::schoolbook_mul_add(&mut tmp, &c_comp, &s2_hat[k_idx], P::TAU);
}
#[cfg(all(feature = "small-secret", not(feature = "compressed-challenge")))]
{
tmp = smallpoly::small_basemul_invntt_widen(&c_small_ntt, &s2_small[k_idx]);
}
#[cfg(not(feature = "compressed-poly"))]
for j in 0..N {
wbuf[j] = w[k_idx][j] - tmp[j];
}
#[cfg(feature = "compressed-poly")]
w_comp.sub_into(k_idx, &tmp, &mut wbuf);
for j in 0..N {
tmp[j] = decompose::low_bits(wbuf[j], gamma2);
}
if !check_norm(&tmp, gamma2 - beta) {
rejected = true;
continue;
}
#[cfg(not(feature = "compressed-challenge"))]
{
tmp = ntt::pointwise_mul(&c_hat, &t0_hat[k_idx]);
ntt::ntt_inv(&mut tmp);
}
#[cfg(feature = "compressed-challenge")]
{
tmp = [0i32; N];
compressed::schoolbook_mul_add(&mut tmp, &c_comp, &t0_hat[k_idx], P::TAU);
}
if !check_norm(&tmp, gamma2) {
rejected = true;
continue;
}
for j in 0..N {
h[k_idx][j] = decompose::make_hint(mod_q(-tmp[j]), wbuf[j] + tmp[j], gamma2);
if h[k_idx][j] == 1 {
total_hints += 1;
}
}
}
if rejected || total_hints > omega {
kappa += l as u16;
continue;
}
for poly in z[..l].iter_mut() {
for c in poly.iter_mut() {
*c = mod_q(*c);
if *c > Q / 2 {
*c -= Q;
}
}
}
let sig = encode::sig_encode::<P>(c_tilde, &z, &h);
return Ok(sig);
}
#[cfg(not(feature = "union-buffer"))]
{
#[cfg(not(feature = "low-stack"))]
let mut cs1 = [[0i32; N]; MAX_L];
#[cfg(feature = "low-stack")]
let mut cs1 = poly_vec(l);
#[cfg(feature = "sca-protected")]
for i in 0..l {
let prod = masked::masked_pointwise_mul_public(&s1_hat_m[i], &c_hat);
cs1[i] = prod.unmask();
ntt::ntt_inv(&mut cs1[i]);
}
#[cfg(all(
not(feature = "sca-protected"),
not(feature = "compressed-challenge"),
not(feature = "small-secret")
))]
for i in 0..l {
cs1[i] = ntt::pointwise_mul(&c_hat, &s1_hat[i]);
ntt::ntt_inv(&mut cs1[i]);
}
#[cfg(all(not(feature = "sca-protected"), feature = "compressed-challenge"))]
for i in 0..l {
cs1[i] = [0i32; N];
compressed::schoolbook_mul_add(&mut cs1[i], &c_comp, &s1_hat[i], P::TAU);
}
#[cfg(all(
not(feature = "sca-protected"),
feature = "small-secret",
not(feature = "compressed-challenge")
))]
for i in 0..l {
cs1[i] = smallpoly::small_basemul_invntt_widen(&c_small_ntt, &s1_small[i]);
}
#[cfg(not(feature = "low-stack"))]
let mut z = [[0i32; N]; MAX_L];
#[cfg(feature = "low-stack")]
let mut z = poly_vec(l);
vec_add(&y, &cs1, &mut z, l);
#[cfg(feature = "low-stack")]
drop(cs1);
#[cfg(not(feature = "low-stack"))]
let mut cs2 = [[0i32; N]; MAX_K];
#[cfg(feature = "low-stack")]
let mut cs2 = poly_vec(k);
#[cfg(feature = "sca-protected")]
for i in 0..k {
let prod = masked::masked_pointwise_mul_public(&s2_hat_m[i], &c_hat);
cs2[i] = prod.unmask();
ntt::ntt_inv(&mut cs2[i]);
}
#[cfg(all(
not(feature = "sca-protected"),
not(feature = "compressed-challenge"),
not(feature = "small-secret")
))]
for i in 0..k {
cs2[i] = ntt::pointwise_mul(&c_hat, &s2_hat[i]);
ntt::ntt_inv(&mut cs2[i]);
}
#[cfg(all(not(feature = "sca-protected"), feature = "compressed-challenge"))]
for i in 0..k {
cs2[i] = [0i32; N];
compressed::schoolbook_mul_add(&mut cs2[i], &c_comp, &s2_hat[i], P::TAU);
}
#[cfg(all(
not(feature = "sca-protected"),
feature = "small-secret",
not(feature = "compressed-challenge")
))]
for i in 0..k {
cs2[i] = smallpoly::small_basemul_invntt_widen(&c_small_ntt, &s2_small[i]);
}
#[cfg(not(feature = "low-stack"))]
let mut w_minus_cs2 = [[0i32; N]; MAX_K];
#[cfg(feature = "low-stack")]
let mut w_minus_cs2 = poly_vec(k);
#[cfg(not(feature = "compressed-poly"))]
vec_sub(&w, &cs2, &mut w_minus_cs2, k);
#[cfg(feature = "compressed-poly")]
for i in 0..k {
w_comp.sub_into(i, &cs2[i], &mut w_minus_cs2[i]);
}
#[cfg(feature = "low-stack")]
drop(cs2);
#[cfg(all(feature = "low-stack", not(feature = "compressed-poly")))]
drop(w);
#[cfg(feature = "compressed-poly")]
drop(w_comp);
#[cfg(not(feature = "low-stack"))]
let mut r0 = [[0i32; N]; MAX_K];
#[cfg(feature = "low-stack")]
let mut r0 = poly_vec(k);
decompose::low_bits_vec(&w_minus_cs2, gamma2, &mut r0, k);
#[cfg(not(feature = "sca-ct-rejection"))]
{
if !check_norm_vec(&z, gamma1 - beta, l) {
kappa += l as u16;
continue;
}
if !check_norm_vec(&r0, gamma2 - beta, k) {
kappa += l as u16;
continue;
}
}
#[cfg(feature = "sca-ct-rejection")]
let mut _reject_flag = {
let z_ok = check_norm_vec(&z, gamma1 - beta, l);
let r0_ok = check_norm_vec(&r0, gamma2 - beta, k);
!(z_ok & r0_ok)
};
#[cfg(feature = "low-stack")]
drop(r0);
#[cfg(not(feature = "low-stack"))]
let mut ct0 = [[0i32; N]; MAX_K];
#[cfg(feature = "low-stack")]
let mut ct0 = poly_vec(k);
#[cfg(feature = "sca-protected")]
for i in 0..k {
let prod = masked::masked_pointwise_mul_public(&t0_hat_m[i], &c_hat);
ct0[i] = prod.unmask();
ntt::ntt_inv(&mut ct0[i]);
}
#[cfg(all(not(feature = "sca-protected"), not(feature = "compressed-challenge")))]
for i in 0..k {
ct0[i] = ntt::pointwise_mul(&c_hat, &t0_hat[i]);
ntt::ntt_inv(&mut ct0[i]);
}
#[cfg(all(not(feature = "sca-protected"), feature = "compressed-challenge"))]
for i in 0..k {
ct0[i] = [0i32; N];
compressed::schoolbook_mul_add(&mut ct0[i], &c_comp, &t0_hat[i], P::TAU);
}
#[cfg(not(feature = "sca-ct-rejection"))]
{
if !check_norm_vec(&ct0, gamma2, k) {
kappa += l as u16;
continue;
}
}
#[cfg(feature = "sca-ct-rejection")]
{
_reject_flag |= !check_norm_vec(&ct0, gamma2, k);
}
#[cfg(not(feature = "low-stack"))]
let mut w_cs2_ct0 = [[0i32; N]; MAX_K];
#[cfg(feature = "low-stack")]
let mut w_cs2_ct0 = poly_vec(k);
vec_add(&w_minus_cs2, &ct0, &mut w_cs2_ct0, k);
#[cfg(not(feature = "low-stack"))]
let mut neg_ct0 = [[0i32; N]; MAX_K];
#[cfg(feature = "low-stack")]
let mut neg_ct0 = poly_vec(k);
for i in 0..k {
for j in 0..N {
neg_ct0[i][j] = mod_q(-ct0[i][j]);
}
}
#[cfg(feature = "low-stack")]
{
drop(ct0);
drop(w_minus_cs2);
}
let (h, num_ones) = decompose::make_hint_vec(&neg_ct0, &w_cs2_ct0, gamma2, k);
#[cfg(feature = "low-stack")]
{
drop(neg_ct0);
drop(w_cs2_ct0);
}
#[cfg(not(feature = "sca-ct-rejection"))]
{
if num_ones > omega {
kappa += l as u16;
continue;
}
}
#[cfg(feature = "sca-ct-rejection")]
{
_reject_flag |= num_ones > omega;
if _reject_flag {
kappa += l as u16;
continue;
}
}
for poly in z[..l].iter_mut() {
for c in poly.iter_mut() {
*c = mod_q(*c);
if *c > Q / 2 {
*c -= Q;
}
}
}
let sig = encode::sig_encode::<P>(c_tilde, &z, &h);
return Ok(sig);
} }
}
pub fn verify_internal<P: Params>(pk: &[u8], m_prime: &[u8], sig: &[u8]) -> Result<bool, MlDsaError> {
let k = P::K;
let l = P::L;
let gamma1 = P::GAMMA1;
let gamma2 = P::GAMMA2;
let beta = P::BETA;
let omega = P::OMEGA;
let c_tilde_len = P::LAMBDA / 4;
if pk.len() != P::PK_LEN {
return Err(MlDsaError::InvalidPublicKey);
}
if sig.len() != P::SIG_LEN {
return Err(MlDsaError::InvalidSignature);
}
let (rho, t1) = encode::pk_decode::<P>(pk);
let mut tr = [0u8; 64];
sha3::shake256_digest(pk, &mut tr);
let (c_tilde, z, h) = match encode::sig_decode::<P>(sig) {
Some(x) => x,
None => return Ok(false),
};
if !check_norm_vec(&z, gamma1 - beta, l) {
return Ok(false);
}
#[cfg(not(feature = "low-mem"))]
let a_hat = sample::expand_a::<P>(&rho);
let mut mu = [0u8; 64];
{
let mut state = sha3::shake256();
state.absorb(&tr);
state.absorb(m_prime);
state.squeeze(&mut mu);
}
let mut c = sample::sample_in_ball::<P>(&c_tilde);
for coeff in c.iter_mut() {
*coeff = mod_q(*coeff);
}
let mut c_hat = c;
ntt::ntt(&mut c_hat);
let mut z_hat = z;
ntt_vec(&mut z_hat, l);
let mut t1_2d_hat = [[0i32; N]; MAX_K];
for i in 0..k {
for j in 0..N {
t1_2d_hat[i][j] = mod_q(t1[i][j] * (1 << D));
}
ntt::ntt(&mut t1_2d_hat[i]);
}
let mut az = [[0i32; N]; MAX_K];
#[cfg(not(feature = "low-mem"))]
mat_vec_mul(&a_hat, &z_hat, k, l, &mut az);
#[cfg(feature = "low-mem")]
mat_vec_mul_lazy(&rho, &z_hat, k, l, &mut az);
let mut ct1 = [[0i32; N]; MAX_K];
for i in 0..k {
ct1[i] = ntt::pointwise_mul(&c_hat, &t1_2d_hat[i]);
}
let mut w_approx = [[0i32; N]; MAX_K];
vec_sub(&az, &ct1, &mut w_approx, k);
ntt_inv_vec(&mut w_approx, k);
let w1_prime = decompose::use_hint_vec(&h, &w_approx, gamma2, k);
let w1_encoded = encode::w1_encode::<P>(&w1_prime);
let mut c_tilde_prime = vec![0u8; c_tilde_len];
{
let mut state = sha3::shake256();
state.absorb(&mu);
state.absorb(&w1_encoded);
state.squeeze(&mut c_tilde_prime);
}
let mut hint_count = 0usize;
for i in 0..k {
for &c in h[i].iter() {
hint_count += c as usize;
}
}
if hint_count > omega {
return Ok(false);
}
Ok(c_tilde == c_tilde_prime)
}
pub fn keygen<P: Params>(rng: &mut dyn CryptoRng) -> Result<(Vec<u8>, Vec<u8>), MlDsaError> {
let mut xi = [0u8; 32];
rng.fill_bytes(&mut xi)?;
let result = keygen_internal::<P>(&xi);
Ok(result)
}
pub fn sign<P: Params>(sk: &[u8], msg: &[u8], ctx: &[u8], rng: &mut dyn CryptoRng) -> Result<Vec<u8>, MlDsaError> {
if ctx.len() > 255 {
return Err(MlDsaError::ContextTooLong);
}
if sk.len() != P::SK_LEN {
return Err(MlDsaError::InvalidSecretKey);
}
let mut m_prime = Vec::with_capacity(1 + 1 + ctx.len() + msg.len());
m_prime.push(0x00);
m_prime.push(ctx.len() as u8);
m_prime.extend_from_slice(ctx);
m_prime.extend_from_slice(msg);
let mut rnd = [0u8; 32];
rng.fill_bytes(&mut rnd)?;
sign_internal::<P>(sk, &m_prime, &rnd)
}
pub fn verify<P: Params>(pk: &[u8], msg: &[u8], ctx: &[u8], sig: &[u8]) -> Result<bool, MlDsaError> {
if ctx.len() > 255 {
return Err(MlDsaError::ContextTooLong);
}
if pk.len() != P::PK_LEN {
return Err(MlDsaError::InvalidPublicKey);
}
if sig.len() != P::SIG_LEN {
return Err(MlDsaError::InvalidSignature);
}
let mut m_prime = Vec::with_capacity(1 + 1 + ctx.len() + msg.len());
m_prime.push(0x00);
m_prime.push(ctx.len() as u8);
m_prime.extend_from_slice(ctx);
m_prime.extend_from_slice(msg);
verify_internal::<P>(pk, &m_prime, sig)
}