#![no_std]
#![deny(clippy::pedantic, warnings, missing_docs, unsafe_code)]
#![deny(absolute_paths_not_starting_with_crate, dead_code)]
#![deny(elided_lifetimes_in_paths, explicit_outlives_requirements, keyword_idents)]
#![deny(let_underscore_drop, macro_use_extern_crate, meta_variable_misuse, missing_abi)]
#![deny(non_ascii_idents, rust_2021_incompatible_closure_captures)]
#![deny(rust_2021_incompatible_or_patterns, rust_2021_prefixes_incompatible_syntax)]
#![deny(rust_2021_prelude_collisions, single_use_lifetimes, trivial_casts)]
#![deny(trivial_numeric_casts, unreachable_pub, unsafe_op_in_unsafe_fn, unstable_features)]
#![deny(unused_extern_crates, unused_import_braces, unused_lifetimes, unused_macro_rules)]
#![deny(unused_qualifications, unused_results, variant_size_differences)]
#![doc = include_str!("../README.md")]
pub use rand_core::{CryptoRng, Error as RngError, RngCore};
mod conversion;
mod encodings;
mod hashing;
mod helpers;
mod high_low;
mod ml_dsa;
mod ntt;
mod types;
pub mod traits;
pub use crate::types::Ph;
const Q: i32 = 8_380_417; const ZETA: i32 = 1753; const D: u32 = 13;
macro_rules! functionality {
() => {
use crate::encodings;
use crate::hashing;
use crate::helpers;
use crate::ml_dsa;
use crate::ntt;
use crate::traits::{KeyGen, SerDes, Signer, Verifier};
use crate::types;
use rand_core::CryptoRngCore;
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::{D, Q};
const BETA: i32 = TAU * ETA;
const LAMBDA_DIV4: usize = LAMBDA / 4;
const W1_LEN: usize = 32 * K * helpers::bit_length((Q - 1) / (2 * GAMMA2) - 1);
const CTEST: bool = false;
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct KG();
pub type PrivateKey = crate::types::PrivateKey<K, L>;
pub type PublicKey = crate::types::PublicKey<K, L>;
#[cfg(feature = "default-rng")]
pub fn try_keygen() -> Result<(PublicKey, PrivateKey), &'static str> { KG::try_keygen() }
pub fn try_keygen_with_rng(rng: &mut impl CryptoRngCore) -> Result<(PublicKey, PrivateKey), &'static str> {
KG::try_keygen_with_rng(rng)
}
impl KeyGen for KG {
type PrivateKey = PrivateKey;
type PublicKey = PublicKey;
fn try_keygen_with_rng(rng: &mut impl CryptoRngCore) -> Result<(PublicKey, PrivateKey), &'static str> {
let (pk, sk) = ml_dsa::key_gen::<CTEST, K, L, PK_LEN, SK_LEN>(rng, ETA)?;
Ok((pk, sk))
}
fn keygen_from_seed(xi: &[u8; 32]) -> (Self::PublicKey, Self::PrivateKey) {
let (pk, sk) = ml_dsa::key_gen_internal::<CTEST, K, L, PK_LEN, SK_LEN>(ETA, xi);
(pk, sk)
}
}
impl Signer for PrivateKey {
type Signature = [u8; SIG_LEN];
type PublicKey = PublicKey;
fn try_sign_with_rng(
&self, rng: &mut impl CryptoRngCore, message: &[u8], ctx: &[u8],
) -> Result<Self::Signature, &'static str> {
helpers::ensure!(ctx.len() < 256, "ML-DSA.Sign: ctx too long");
let mut rnd = [0u8; 32];
rng.try_fill_bytes(&mut rnd).map_err(|_| "ML-DSA.Sign: random number generator failed")?;
let sig = ml_dsa::sign_internal::<CTEST, K, L, LAMBDA_DIV4, SIG_LEN, SK_LEN, W1_LEN>(
BETA, GAMMA1, GAMMA2, OMEGA, TAU, &self, message, ctx, &[], &[], rnd, false
);
Ok(sig)
}
fn try_hash_sign_with_rng(
&self, rng: &mut impl CryptoRngCore, message: &[u8], ctx: &[u8], ph: &types::Ph,
) -> Result<Self::Signature, &'static str> {
helpers::ensure!(ctx.len() < 256, "HashML-DSA.Sign: ctx too long");
let mut rnd = [0u8; 32];
rng.try_fill_bytes(&mut rnd).map_err(|_| "HashML-DSA.Sign: random number generator failed")?;
let mut phm = [0u8; 64]; let (oid, phm_len) = hashing::hash_message(message, ph, &mut phm);
let sig = ml_dsa::sign_internal::<CTEST, K, L, LAMBDA_DIV4, SIG_LEN, SK_LEN, W1_LEN>(
BETA, GAMMA1, GAMMA2, OMEGA, TAU, &self, message, ctx, &oid, &phm[0..phm_len], rnd, false
);
Ok(sig)
}
#[allow(clippy::cast_lossless)]
fn get_public_key(&self) -> Self::PublicKey {
ml_dsa::private_to_public_key(&self)
}
}
impl Verifier for PublicKey {
type Signature = [u8; SIG_LEN];
fn verify(&self, message: &[u8], sig: &Self::Signature, ctx: &[u8]) -> bool {
if ctx.len() > 255 {
return false;
};
ml_dsa::verify_internal::<CTEST, K, L, LAMBDA_DIV4, PK_LEN, SIG_LEN, W1_LEN>(
BETA, GAMMA1, GAMMA2, OMEGA, TAU, &self, &message, &sig, ctx, &[], &[], false
)
}
fn hash_verify(&self, message: &[u8], sig: &Self::Signature, ctx: &[u8], ph: &types::Ph) -> bool {
if ctx.len() > 255 {
return false;
};
let mut phm = [0u8; 64]; let (oid, phm_len) = hashing::hash_message(message, ph, &mut phm);
ml_dsa::verify_internal::<CTEST, K, L, LAMBDA_DIV4, PK_LEN, SIG_LEN, W1_LEN>(
BETA, GAMMA1, GAMMA2, OMEGA, TAU, &self, &message, &sig, ctx, &oid, &phm[0..phm_len], false
)
}
}
impl SerDes for PrivateKey {
type ByteArray = [u8; SK_LEN];
fn try_from_bytes(sk: Self::ByteArray) -> Result<Self, &'static str> {
let esk = ml_dsa::expand_private::<K, L, SK_LEN>(ETA, &sk)?;
Ok(esk)
}
fn into_bytes(self) -> Self::ByteArray {
let PrivateKey {rho, cap_k, tr, s_1_hat_mont: s_hat_1_mont, s_2_hat_mont: s_hat_2_mont, t_0_hat_mont: t_hat_0_mont, ..} = &self;
let s_1: [types::R; L] = ntt::inv_ntt(
&core::array::from_fn(|l|
types::T(core::array::from_fn(|n|
helpers::mont_reduce(i64::from(s_hat_1_mont[l].0[n]))))));
let s_1: [types::R; L] =
core::array::from_fn(|l|
types::R(core::array::from_fn(|n|
if s_1[l].0[n] > (Q / 2) {s_1[l].0[n] - Q} else {s_1[l].0[n]})));
let s_2: [types::R; K] = ntt::inv_ntt(
&core::array::from_fn(|k|
types::T(core::array::from_fn(|n|
helpers::mont_reduce(i64::from(s_hat_2_mont[k].0[n]))))));
let s_2: [types::R; K] =
core::array::from_fn(|k|
types::R(core::array::from_fn(|n|
if s_2[k].0[n] > (Q / 2) {s_2[k].0[n] - Q} else {s_2[k].0[n]})));
let t_0: [types::R; K] = ntt::inv_ntt(
&core::array::from_fn(|k|
types::T(core::array::from_fn(|n|
helpers::mont_reduce(i64::from(t_hat_0_mont[k].0[n]))))));
let t_0: [types::R; K] =
core::array::from_fn(|k|
types::R(core::array::from_fn(|n|
if t_0[k].0[n] > (Q / 2) {t_0[k].0[n] - Q} else {t_0[k].0[n]})));
encodings::sk_encode::<K, L, SK_LEN>(ETA, rho, cap_k, tr, &s_1, &s_2, &t_0)
}
}
impl SerDes for PublicKey {
type ByteArray = [u8; PK_LEN];
fn try_from_bytes(pk: Self::ByteArray) -> Result<Self, &'static str> {
let epk = ml_dsa::expand_public(&pk)?;
Ok(epk)
}
fn into_bytes(self) -> Self::ByteArray {
let PublicKey {rho, tr: _tr, t1_d2_hat_mont} = &self;
let t1_d2: [types::R; K] = ntt::inv_ntt(
&core::array::from_fn(|k|
types::T(core::array::from_fn(|n|
helpers::mont_reduce(i64::from(t1_d2_hat_mont[k].0[n]))))));
let t1: [types::R; K] = core::array::from_fn(|k|
types::R(core::array::from_fn(|n|
t1_d2[k].0[n] >> D)));
encodings::pk_encode(rho, &t1)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Ph;
use rand_chacha::rand_core::SeedableRng;
#[test]
fn smoke_test() {
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(123);
let message1 = [0u8, 1, 2, 3, 4, 5, 6, 7];
let message2 = [7u8, 7, 7, 7, 7, 7, 7, 7];
for _i in 0..32 {
let (pk, sk) = try_keygen_with_rng(&mut rng).unwrap();
let sig = sk.try_sign_with_rng(&mut rng, &message1, &[]).unwrap();
assert!(pk.verify(&message1, &sig, &[]));
assert!(!pk.verify(&message2, &sig, &[]));
for ph in [Ph::SHA256, Ph::SHA512, Ph::SHAKE128] {
let sig = sk.try_hash_sign_with_rng(&mut rng, &message1, &[], &ph).unwrap();
let v = pk.hash_verify(&message1, &sig, &[], &ph);
assert!(v);
}
assert_eq!(pk.clone().into_bytes(), sk.get_public_key().into_bytes());
}
let (pk, sk) = try_keygen().unwrap();
let sig = sk.try_sign(&message1, &[]).unwrap();
assert!(pk.verify(&message1, &sig, &[]));
assert!(!pk.verify(&message2, &sig, &[]));
assert!(!pk.verify(&message1, &sig, &[0u8; 257]));
assert!(sk.try_sign(&message1, &[0u8; 257]).is_err());
for ph in [Ph::SHA256, Ph::SHA512, Ph::SHAKE128] {
let sig = sk.try_hash_sign(&message1, &[], &ph).unwrap();
let v = pk.hash_verify(&message1, &sig, &[], &ph);
assert!(v);
}
assert_eq!(pk.clone().into_bytes(), sk.get_public_key().into_bytes());
let (pk, sk) = KG::keygen_from_seed(&[0x11u8; 32]);
let sig = sk.try_sign_with_seed(&[12u8; 32], &message1, &[]).unwrap();
assert!(pk.verify(&message1, &sig, &[]));
let sig = sk.try_hash_sign_with_seed(&[34u8; 32], &message1, &[], &Ph::SHA256).unwrap();
assert!(pk.hash_verify(&message1, &sig, &[], &Ph::SHA256));
let pk_bytes = pk.into_bytes();
if pk_bytes.len() == 1312 { assert_eq!(pk_bytes[0], 197) }
if pk_bytes.len() == 1952 { assert_eq!(pk_bytes[0], 177) }
if pk_bytes.len() == 2592 { assert_eq!(pk_bytes[0], 16) }
#[cfg(feature = "dudect")]
#[allow(deprecated)] {
assert!(dudect_keygen_sign_with_rng(&mut rng, &[0]).is_ok())
}
}
}
#[deprecated = "Function for constant-time testing; do not use elsewhere"]
#[cfg(feature = "dudect")]
pub fn dudect_keygen_sign_with_rng(
rng: &mut impl CryptoRngCore, message: &[u8],
) -> Result<[u8; SIG_LEN], &'static str> {
let (_pk, sk) = ml_dsa::key_gen::<true, K, L, PK_LEN, SK_LEN>(rng, ETA)?;
let mut rnd = [0u8; 32];
rng.try_fill_bytes(&mut rnd).map_err(|_| "Random number generator failed")?;
let sig = ml_dsa::sign_internal::<true, K, L, LAMBDA_DIV4, SIG_LEN, SK_LEN, W1_LEN>(
BETA, GAMMA1, GAMMA2, OMEGA, TAU, &sk, message, &[1], &[2], &[3], rnd, true
);
Ok(sig)
}
#[deprecated = "Temporary function to allow application of internal nist vectors; will be removed"]
pub fn _internal_sign(
sk: &PrivateKey, message: &[u8], ctx: &[u8], rnd: [u8; 32]
) -> Result<[u8; SIG_LEN], &'static str> {
helpers::ensure!(ctx.len() < 256, "_internal_sign: ctx too long");
let sig = ml_dsa::sign_internal::<CTEST, K, L, LAMBDA_DIV4, SIG_LEN, SK_LEN, W1_LEN>(
BETA, GAMMA1, GAMMA2, OMEGA, TAU, sk, message, ctx, &[], &[], rnd, true
);
Ok(sig)
}
#[deprecated = "Temporary function to allow application of internal nist vectors; will be removed"]
#[must_use]
pub fn _internal_verify(pk: &PublicKey, message: &[u8], sig: &[u8; SIG_LEN], ctx: &[u8]) -> bool {
if ctx.len() > 255 {
return false;
};
ml_dsa::verify_internal::<CTEST, K, L, LAMBDA_DIV4, PK_LEN, SIG_LEN, W1_LEN>(
BETA, GAMMA1, GAMMA2, OMEGA, TAU, pk, &message, &sig, ctx, &[], &[], true
)
}
};
}
#[cfg(feature = "ml-dsa-44")]
pub mod ml_dsa_44 {
const TAU: i32 = 39;
const LAMBDA: usize = 128;
const GAMMA1: i32 = 1 << 17;
const GAMMA2: i32 = (Q - 1) / 88;
const K: usize = 4;
const L: usize = 4;
const ETA: i32 = 2;
const OMEGA: i32 = 80;
pub const SK_LEN: usize = 2560;
pub const PK_LEN: usize = 1312;
pub const SIG_LEN: usize = 2420;
functionality!();
}
#[cfg(feature = "ml-dsa-65")]
pub mod ml_dsa_65 {
const TAU: i32 = 49;
const LAMBDA: usize = 192;
const GAMMA1: i32 = 1 << 19;
const GAMMA2: i32 = (Q - 1) / 32;
const K: usize = 6;
const L: usize = 5;
const ETA: i32 = 4;
const OMEGA: i32 = 55;
pub const SK_LEN: usize = 4032;
pub const PK_LEN: usize = 1952;
pub const SIG_LEN: usize = 3309;
functionality!();
}
#[cfg(feature = "ml-dsa-87")]
pub mod ml_dsa_87 {
const TAU: i32 = 60;
const LAMBDA: usize = 256;
const GAMMA1: i32 = 1 << 19;
const GAMMA2: i32 = (Q - 1) / 32;
const K: usize = 8;
const L: usize = 7;
const ETA: i32 = 2;
const OMEGA: i32 = 75;
pub const SK_LEN: usize = 4896;
pub const PK_LEN: usize = 2592;
pub const SIG_LEN: usize = 4627;
functionality!();
}