#![no_std]
#![deny(clippy::pedantic, 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)]
#![cfg_attr(
not(any(feature = "ml-dsa-44", feature = "ml-dsa-65", feature = "ml-dsa-87")),
allow(dead_code, unreachable_pub, unused_macros)
)]
#![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::pre_hash;
const MAX_PREHASH_LEN: usize = 1024;
fn check_pre_hash(hash: &[u8], hash_oid: &[u8]) -> Result<(), &'static str> {
helpers::ensure!(!hash_oid.is_empty(), "HashML-DSA.Sign: OID is empty");
helpers::ensure!(
hash_oid.len() >= 3
&& hash_oid[0] == 0x06
&& hash_oid[1] < 0x80
&& usize::from(hash_oid[1]) == hash_oid.len() - 2,
"HashML-DSA.Sign: OID is not DER encoded"
);
helpers::ensure!(
hash.len() <= MAX_PREHASH_LEN,
"Hash of message is too long, should not be more than 1KiB"
);
if let Some(len) = pre_hash::digest_len(hash_oid) {
helpers::ensure!(
hash.len() == len,
"HashML-DSA.Sign: digest length does not match the OID"
);
}
Ok(())
}
const Q: i32 = 8_380_417; const ZETA: i32 = 1753; const D: u32 = 13;
macro_rules! functionality {
() => {
use crate::encodings;
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 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;
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 = zeroize::Zeroizing::new([0u8; 32]);
rng.try_fill_bytes(&mut rnd[..]).map_err(|_| "ML-DSA.Sign: random number generator failed")?;
let mu = ml_dsa::mu(&[&self.tr, &[0u8], &[ctx.len().to_le_bytes()[0]], ctx, message]);
let sig = ml_dsa::sign_internal::<CTEST, K, L, LAMBDA_DIV4, SIG_LEN, SK_LEN, W1_LEN>(
BETA, GAMMA1, GAMMA2, OMEGA, TAU, &self, &mu, &rnd
)?;
Ok(sig)
}
fn try_hash_sign_with_rng(
&self, rng: &mut impl CryptoRngCore, hash: &[u8], ctx: &[u8], hash_oid: &[u8],
) -> Result<Self::Signature, &'static str> {
helpers::ensure!(ctx.len() < 256, "HashML-DSA.Sign: ctx too long");
crate::check_pre_hash(hash, hash_oid)?;
let mut rnd = zeroize::Zeroizing::new([0u8; 32]);
rng.try_fill_bytes(&mut rnd[..]).map_err(|_| "HashML-DSA.Sign: random number generator failed")?;
let mu = ml_dsa::mu(&[&self.tr, &[1u8], &[ctx.len().to_le_bytes()[0]], ctx, hash_oid, hash]);
let sig = ml_dsa::sign_internal::<CTEST, K, L, LAMBDA_DIV4, SIG_LEN, SK_LEN, W1_LEN>(
BETA, GAMMA1, GAMMA2, OMEGA, TAU, &self, &mu, &rnd
)?;
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;
};
let mu = ml_dsa::mu(&[&self.tr, &[0u8], &[ctx.len().to_le_bytes()[0]], ctx, message]);
ml_dsa::verify_internal::<CTEST, K, L, LAMBDA_DIV4, PK_LEN, SIG_LEN, W1_LEN>(
BETA, GAMMA1, GAMMA2, OMEGA, TAU, &self, &mu, &sig
)
}
fn hash_verify(&self, hash: &[u8], sig: &Self::Signature, ctx: &[u8], hash_oid: &[u8]) -> bool {
if ctx.len() > 255 {
return false;
};
if crate::check_pre_hash(hash, hash_oid).is_err() {
return false;
}
let mu = ml_dsa::mu(&[&self.tr, &[1u8], &[ctx.len().to_le_bytes()[0]], ctx, hash_oid, hash]);
ml_dsa::verify_internal::<CTEST, K, L, LAMBDA_DIV4, PK_LEN, SIG_LEN, W1_LEN>(
BETA, GAMMA1, GAMMA2, OMEGA, TAU, &self, &mu, &sig
)
}
}
#[cfg(feature = "acvp-internal")]
impl PrivateKey {
#[deprecated = "Hook for the NIST ACVP internal test groups; do not use elsewhere"]
#[doc(hidden)]
pub fn sign_internal(
&self, rng: &mut impl CryptoRngCore, m: &[u8], hedged: bool,
) -> Result<[u8; SIG_LEN], &'static str> {
let mu = ml_dsa::mu(&[&self.tr, m]);
#[allow(deprecated)]
self.sign_mu(rng, &mu, hedged)
}
#[deprecated = "Hook for the NIST ACVP internal test groups; do not use elsewhere"]
#[doc(hidden)]
pub fn sign_mu(
&self, rng: &mut impl CryptoRngCore, mu: &[u8; 64], hedged: bool,
) -> Result<[u8; SIG_LEN], &'static str> {
let mut rnd = zeroize::Zeroizing::new([0u8; 32]);
if hedged {
rng.try_fill_bytes(&mut rnd[..])
.map_err(|_| "ML-DSA.Sign_internal: random number generator failed")?;
}
ml_dsa::sign_internal::<CTEST, K, L, LAMBDA_DIV4, SIG_LEN, SK_LEN, W1_LEN>(
BETA, GAMMA1, GAMMA2, OMEGA, TAU, &self, mu, &rnd
)
}
}
#[cfg(feature = "acvp-internal")]
impl PublicKey {
#[deprecated = "Hook for the NIST ACVP internal test groups; do not use elsewhere"]
#[doc(hidden)]
#[must_use]
pub fn verify_internal(&self, m: &[u8], sig: &[u8; SIG_LEN]) -> bool {
let mu = ml_dsa::mu(&[&self.tr, m]);
#[allow(deprecated)]
self.verify_mu(&mu, sig)
}
#[deprecated = "Hook for the NIST ACVP internal test groups; do not use elsewhere"]
#[doc(hidden)]
#[must_use]
pub fn verify_mu(&self, mu: &[u8; 64], sig: &[u8; SIG_LEN]) -> bool {
ml_dsa::verify_internal::<CTEST, K, L, LAMBDA_DIV4, PK_LEN, SIG_LEN, W1_LEN>(
BETA, GAMMA1, GAMMA2, OMEGA, TAU, &self, mu, sig
)
}
}
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 rand_chacha::rand_core::SeedableRng;
use crate::types::pre_hash;
use sha2::{Digest, Sha256, Sha512};
use sha3::{Shake128,digest::{Update,ExtendableOutput,XofReader}};
fn digest(message: &[u8], hash_oid: &[u8], hash: &mut [u8; 64]) -> Result<usize, &'static str> {
if *hash_oid == pre_hash::SHA2_256 {
let mut hasher = Sha256::new();
Digest::update(&mut hasher, message);
hash[0..32].copy_from_slice(&hasher.finalize());
Ok(32)
} else if *hash_oid == pre_hash::SHA2_512 {
let mut hasher = Sha512::new();
Digest::update(&mut hasher, message);
hash.copy_from_slice(&hasher.finalize());
Ok(64)
} else if *hash_oid == pre_hash::SHAKE_128 {
let mut hasher = Shake128::default();
hasher.update(message);
let mut reader = hasher.finalize_xof();
reader.read(&mut hash[0..32]);
Ok(32)
} else {
Err("Did not recognize hash_oid")
}
}
#[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..4 {
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 [pre_hash::SHA2_256, pre_hash::SHA2_512, pre_hash::SHAKE_128] {
let mut hash = [ 0u8;64];
let hashlen = digest(&message1, &ph, &mut hash).unwrap();
let sig = sk.try_hash_sign_with_rng(&mut rng, &hash[0..hashlen], &[], &ph).unwrap();
let v1 = pk.hash_verify(&hash[0..hashlen], &sig, &[], &ph);
assert!(v1);
}
assert_eq!(pk.clone().into_bytes(), sk.get_public_key().into_bytes());
}
#[cfg(feature = "default-rng")]
{
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 [pre_hash::SHA2_256, pre_hash::SHA2_512, pre_hash::SHAKE_128] {
let mut hash = [ 0u8;64];
let hashlen = digest(&message1, &ph, &mut hash).unwrap();
let sig = sk.try_hash_sign(&hash[0..hashlen], &[], &ph).unwrap();
let v2 = pk.hash_verify(&hash[0..hashlen], &sig, &[], &ph);
assert!(v2);
assert!(sk.try_hash_sign(&hash[0..hashlen], &[], &[]).is_err());
assert!(!pk.hash_verify(&hash[0..hashlen], &sig, &[], &[]));
}
let too_long = [0u8; crate::MAX_PREHASH_LEN + 1];
assert!(sk.try_hash_sign(&too_long, &[], &pre_hash::SHA2_256).is_err());
assert!(!pk.hash_verify(&too_long, &sig, &[], &pre_hash::SHA2_256));
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 mut hash = [ 0u8; 64 ];
if let Ok(hashlen) = digest(&message1, &pre_hash::SHA2_256, &mut hash) {
let sig = sk.try_hash_sign_with_seed(&[34u8; 32], &hash[0..hashlen], &[], &pre_hash::SHA2_256).unwrap();
assert!(pk.hash_verify(&hash[0..hashlen], &sig, &[], &pre_hash::SHA2_256));
}
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"]
#[doc(hidden)]
#[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 = zeroize::Zeroizing::new([0u8; 32]);
rng.try_fill_bytes(&mut rnd[..]).map_err(|_| "Random number generator failed")?;
let mu = ml_dsa::mu(&[&sk.tr, &[0u8], &[0u8], message]);
ml_dsa::sign_internal::<true, K, L, LAMBDA_DIV4, SIG_LEN, SK_LEN, W1_LEN>(
BETA, GAMMA1, GAMMA2, OMEGA, TAU, &sk, &mu, &rnd
)
}
};
}
#[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!();
}