use crate::KP;
use crate::errors::UnknownCryptoError;
use crate::generics::sealed::Sealed;
use crate::generics::{Public, Secret, TypeSpec};
use crate::hazardous::dsa::ml_dsa::internal::{
InternalSignature, InternalSigningKey, InternalVerifyingKey, KeyPairInternal, MlDsa87,
MlDsaParameters,
};
pub use crate::hazardous::dsa::ml_dsa::ExplicitRandom;
pub use crate::hazardous::dsa::ml_dsa::MlDsaExplicitRandom;
pub use crate::hazardous::dsa::ml_dsa::MlDsaSeed;
pub use crate::hazardous::dsa::ml_dsa::RAND_SIZE;
pub use crate::hazardous::dsa::ml_dsa::SEED_SIZE;
pub use crate::hazardous::dsa::ml_dsa::Seed;
pub const SIGNING_KEY_SIZE: usize = MlDsa87::PRIVATE_KEY_SIZE;
pub const VERIFYING_KEY_SIZE: usize = MlDsa87::PUBLIC_KEY_SIZE;
pub const SIGNATURE_SIZE: usize = MlDsa87::SIGNATURE_SIZE;
pub type Signature = Public<MlDsa87Signature>;
pub type SigningKey = Secret<MlDsa87SigningKey>;
pub type VerifyingKey = Public<MlDsa87VerifyingKey>;
#[derive(Debug)]
#[cfg_attr(test, derive(Clone))]
pub struct MlDsa87SigningKey {}
impl Sealed for MlDsa87SigningKey {}
impl TypeSpec for MlDsa87SigningKey {
const NAME: &'static str = stringify!(SigningKey);
type TypeData = InternalSigningKey<
{ MlDsa87::PRIVATE_KEY_SIZE },
{ MlDsa87::SIGNATURE_SIZE },
{ MlDsa87::CLEN },
{ MlDsa87::COMMITMENT_HASH_LEN },
{ MlDsa87::W1_BITPACK_SIZE * MlDsa87::DIM_K },
{ MlDsa87::DIM_K },
{ MlDsa87::DIM_L },
MlDsa87,
>;
}
#[derive(Debug, Clone, Copy)]
pub struct MlDsa87VerifyingKey {}
impl Sealed for MlDsa87VerifyingKey {}
impl TypeSpec for MlDsa87VerifyingKey {
const NAME: &'static str = stringify!(VerifyingKey);
type TypeData = InternalVerifyingKey<
{ MlDsa87::PUBLIC_KEY_SIZE },
{ MlDsa87::SIGNATURE_SIZE },
{ MlDsa87::CLEN },
{ MlDsa87::COMMITMENT_HASH_LEN },
{ MlDsa87::W1_BITPACK_SIZE * MlDsa87::DIM_K },
{ MlDsa87::DIM_K },
{ MlDsa87::DIM_L },
MlDsa87,
>;
fn vartime_partial_eq(lhs: &Self::TypeData, rhs: &[u8]) -> bool {
lhs.pk.as_ref() == rhs
}
}
#[derive(Debug, Clone, Copy)]
pub struct MlDsa87Signature {}
impl Sealed for MlDsa87Signature {}
impl TypeSpec for MlDsa87Signature {
const NAME: &'static str = stringify!(Signature);
type TypeData = InternalSignature<
{ MlDsa87::SIGNATURE_SIZE },
{ MlDsa87::COMMITMENT_HASH_LEN },
{ MlDsa87::DIM_K },
{ MlDsa87::DIM_L },
MlDsa87,
>;
fn vartime_partial_eq(lhs: &Self::TypeData, rhs: &[u8]) -> bool {
lhs.sig.as_ref() == rhs
}
}
impl TryFrom<&Seed> for SigningKey {
type Error = UnknownCryptoError;
fn try_from(value: &Seed) -> Result<Self, Self::Error> {
let kp = KeyPairInternal::<
{ MlDsa87::PRIVATE_KEY_SIZE },
{ MlDsa87::PUBLIC_KEY_SIZE },
{ MlDsa87::SIGNATURE_SIZE },
{ MlDsa87::CLEN },
{ MlDsa87::COMMITMENT_HASH_LEN },
{ MlDsa87::W1_BITPACK_SIZE * MlDsa87::DIM_K },
{ MlDsa87::DIM_K },
{ MlDsa87::DIM_L },
MlDsa87,
>::keygen_internal(value.unprotected_as_ref())?;
Ok(Self::from_data(kp.sk))
}
}
impl SigningKey {
#[cfg(feature = "safe_api")]
#[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))]
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn sign(&self, m: &[u8], ctx: &[u8]) -> Result<Signature, UnknownCryptoError> {
let rnd = ExplicitRandom::generate()?;
self.sign_with_rnd(m, ctx, &rnd)
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn sign_deterministic(
&self,
m: &[u8],
ctx: &[u8],
) -> Result<Signature, UnknownCryptoError> {
self.sign_with_rnd(m, ctx, &ExplicitRandom::deterministic())
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn sign_with_rnd(
&self,
m: &[u8],
ctx: &[u8],
rnd: &ExplicitRandom,
) -> Result<Signature, UnknownCryptoError> {
Ok(Signature::from_data(self.data.sign(
m,
ctx,
rnd.unprotected_as_ref(),
)?))
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn sign_external_mu_with_rnd(
&self,
mu: &[u8],
rnd: &ExplicitRandom,
) -> Result<Signature, UnknownCryptoError> {
if mu.len() != 64 {
return Err(UnknownCryptoError);
}
Ok(Signature::from_data(
self.data
.sign_internal_with_mu(mu, rnd.unprotected_as_ref())?,
))
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn init(&mut self, ctx: &[u8]) -> Result<(), UnknownCryptoError> {
self.data.init(ctx)
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn update(&mut self, m: &[u8]) -> Result<(), UnknownCryptoError> {
self.data.update(m)
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn finalize_with_rnd(
&mut self,
rnd: &ExplicitRandom,
) -> Result<Signature, UnknownCryptoError> {
Ok(Signature::from_data(
self.data.finalize(rnd.unprotected_as_ref())?,
))
}
#[cfg(feature = "safe_api")]
#[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))]
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn finalize(&mut self) -> Result<Signature, UnknownCryptoError> {
let rnd = ExplicitRandom::generate()?;
self.finalize_with_rnd(&rnd)
}
}
impl VerifyingKey {
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn verify(&self, m: &[u8], ctx: &[u8], sig: &Signature) -> Result<(), UnknownCryptoError> {
self.data.verify(m, &sig.data, ctx)
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn verify_external_mu(&self, mu: &[u8], sig: &Signature) -> Result<(), UnknownCryptoError> {
if mu.len() != 64 {
return Err(UnknownCryptoError);
}
self.data.verify_internal_with_mu(mu, &sig.data)
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn init(&mut self, ctx: &[u8]) -> Result<(), UnknownCryptoError> {
self.data.init(ctx)
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn update(&mut self, m: &[u8]) -> Result<(), UnknownCryptoError> {
self.data.update(m)
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn finalize(&mut self, sig: &Signature) -> Result<(), UnknownCryptoError> {
self.data.finalize(&sig.data)
}
}
impl TryFrom<&SigningKey> for VerifyingKey {
type Error = UnknownCryptoError;
fn try_from(value: &SigningKey) -> Result<Self, Self::Error> {
let vk = InternalVerifyingKey::<
{ MlDsa87::PUBLIC_KEY_SIZE },
{ MlDsa87::SIGNATURE_SIZE },
{ MlDsa87::CLEN },
{ MlDsa87::COMMITMENT_HASH_LEN },
{ MlDsa87::W1_BITPACK_SIZE * MlDsa87::DIM_K },
{ MlDsa87::DIM_K },
{ MlDsa87::DIM_L },
MlDsa87,
>::try_from(&value.data)?;
Ok(Self::from_data(vk))
}
}
#[derive(Debug, PartialEq)]
#[cfg_attr(test, derive(Clone))]
pub struct KeyPair {
seed: Seed,
signing_key: SigningKey,
pub(crate) verifying_key: VerifyingKey,
}
impl KP<MlDsa87SigningKey, MlDsa87VerifyingKey> for KeyPair {
fn private(&self) -> &SigningKey {
&self.signing_key
}
fn public(&self) -> &VerifyingKey {
&self.verifying_key
}
}
impl TryFrom<&Seed> for KeyPair {
type Error = UnknownCryptoError;
fn try_from(value: &Seed) -> Result<Self, Self::Error> {
let kp = KeyPairInternal::<
{ MlDsa87::PRIVATE_KEY_SIZE },
{ MlDsa87::PUBLIC_KEY_SIZE },
{ MlDsa87::SIGNATURE_SIZE },
{ MlDsa87::CLEN },
{ MlDsa87::COMMITMENT_HASH_LEN },
{ MlDsa87::W1_BITPACK_SIZE * MlDsa87::DIM_K },
{ MlDsa87::DIM_K },
{ MlDsa87::DIM_L },
MlDsa87,
>::keygen_internal(value.unprotected_as_ref())?;
Ok(Self {
seed: Seed::from_data(value.data.clone()),
signing_key: Secret::<MlDsa87SigningKey>::from_data(kp.sk),
verifying_key: Public::<MlDsa87VerifyingKey>::from_data(kp.pk),
})
}
}
impl KeyPair {
#[cfg(feature = "safe_api")]
#[cfg_attr(docsrs, doc(cfg(feature = "safe_api")))]
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn generate() -> Result<Self, UnknownCryptoError> {
let seed = Seed::generate()?;
Self::new(seed)
}
pub fn seed(&self) -> &Seed {
&self.seed
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn new(seed: Seed) -> Result<Self, UnknownCryptoError> {
Self::try_from(&seed)
}
}
#[cfg(test)]
mod tests {
use crate::hazardous::hash::sha3::shake128::Shake128;
use super::*;
pub(crate) fn c2sp_cctv_accumulated_mldsa87_run(
iter_max: u32,
) -> ([u8; 32], [u8; 32], [u8; 32]) {
let mut s = Shake128::new();
let mut a = Shake128::new();
let mut seed = [0u8; 32];
let mut result_100 = [0u8; 32];
let mut result_10000 = [0u8; 32];
let mut result_60000000 = [0u8; 32];
for n in 1..=iter_max {
s.squeeze(&mut seed).unwrap();
let kp = KeyPair::new(Seed::from(seed)).unwrap();
a.absorb(kp.public().as_ref()).unwrap();
let sig = kp.private().sign_deterministic(b"", b"").unwrap();
a.absorb(sig.as_ref()).unwrap();
assert!(kp.public().verify(b"", b"", &sig).is_ok());
match n {
100 => {
let mut a_ret = a.clone();
a_ret.squeeze(&mut result_100).unwrap();
}
10000 => {
let mut a_ret = a.clone();
a_ret.squeeze(&mut result_10000).unwrap();
}
60000000 => {
let mut a_ret = a.clone();
a_ret.squeeze(&mut result_60000000).unwrap();
}
_ => continue,
}
}
(result_100, result_10000, result_60000000)
}
#[test]
#[ignore = "runs too long - only meant for local development, not CI"] fn c2sp_cctv_accumulated_60000000() {
let mut expected_60000000 = [0u8; 32];
hex::decode_to_slice(
"011166e9d5032c9bdc5c9bbb5dbb6c86df1c3d9bf3570b65ebae942dd9830057",
&mut expected_60000000,
)
.unwrap();
let (_result_100, _result_10000, result_60000000) =
c2sp_cctv_accumulated_mldsa87_run(60000000);
assert_eq!(expected_60000000, result_60000000);
}
#[test]
fn c2sp_cctv_accumulated() {
let mut expected_100 = [0u8; 32];
let mut expected_10000 = [0u8; 32];
hex::decode_to_slice(
"8c3ad714777622b8f21ce31bb35f71394f23bc0fcf3c78ace5d608990f3b061b",
&mut expected_100,
)
.unwrap();
hex::decode_to_slice(
"80a8cf39317f7d0be0e24972c51ac152bd2a3e09bc0c32ce29dd82c4e7385e60",
&mut expected_10000,
)
.unwrap();
let (result_100, result_10000, _result_60000000) = c2sp_cctv_accumulated_mldsa87_run(10000);
assert_eq!(expected_100, result_100);
assert_eq!(expected_10000, result_10000);
}
#[test]
#[cfg(all(feature = "serde", feature = "safe_api"))]
fn test_signature_public_serialization() {
use crate::test_framework::newtypes::public::PublicNewtype;
let kp = KeyPair::new(Seed::from([0u8; 32])).unwrap();
let sig = kp.private().sign(b"", b"").unwrap();
PublicNewtype::test_serialization_non_arbitrary::<MlDsa87Signature>(&sig);
}
#[test]
#[cfg(feature = "serde")]
fn test_verifyingkey_public_serialization() {
use crate::test_framework::newtypes::public::PublicNewtype;
let kp = KeyPair::new(Seed::from([0u8; 32])).unwrap();
PublicNewtype::test_serialization_non_arbitrary::<MlDsa87VerifyingKey>(kp.public());
}
#[cfg(any(feature = "safe_api", feature = "alloc"))]
use crate::test_framework::mldsa_interface::{DsaTester, TestableDsa};
#[cfg(all(feature = "alloc", not(feature = "safe_api")))]
use alloc::vec::*;
#[cfg(any(feature = "safe_api", feature = "alloc"))]
impl TestableDsa for KeyPair {
const SIGNATURE_SIZE: usize = SIGNATURE_SIZE;
fn keygen(seed: &[u8]) -> Result<(Vec<u8>, Vec<u8>), UnknownCryptoError> {
let seed = Seed::try_from(seed)?;
let kp = KeyPair::new(seed)?;
Ok((
kp.signing_key.unprotected_as_ref().to_vec(),
kp.verifying_key.as_ref().to_vec(),
))
}
#[cfg(feature = "safe_api")]
fn keygen_rng() -> Result<(Vec<u8>, Vec<u8>), UnknownCryptoError> {
let seed = Seed::generate()?;
Self::keygen(seed.unprotected_as_ref())
}
fn sign_deterministic(
sk: &[u8],
m: &[u8],
ctx: &[u8],
) -> Result<Vec<u8>, UnknownCryptoError> {
let signing_key = SigningKey::try_from(sk)?;
let sig = signing_key.sign_deterministic(m, ctx)?;
Ok(sig.as_ref().to_vec())
}
#[cfg(feature = "safe_api")]
fn sign_randomized(sk: &[u8], m: &[u8], ctx: &[u8]) -> Result<Vec<u8>, UnknownCryptoError> {
let signing_key = SigningKey::try_from(sk)?;
let sig = signing_key.sign(m, ctx)?;
Ok(sig.as_ref().to_vec())
}
fn verify(vk: &[u8], m: &[u8], ctx: &[u8], sig: &[u8]) -> Result<(), UnknownCryptoError> {
let verifying_key = VerifyingKey::try_from(vk)?;
let sig = Signature::try_from(sig)?;
verifying_key.verify(m, ctx, &sig)
}
#[cfg(feature = "safe_api")]
fn init_sign(&mut self, ctx: &[u8]) -> Result<(), UnknownCryptoError> {
self.signing_key.init(ctx)
}
#[cfg(feature = "safe_api")]
fn init_verify(&mut self, ctx: &[u8]) -> Result<(), UnknownCryptoError> {
self.verifying_key.init(ctx)
}
#[cfg(feature = "safe_api")]
fn update_sign(&mut self, m: &[u8]) -> Result<(), UnknownCryptoError> {
self.signing_key.update(m)
}
#[cfg(feature = "safe_api")]
fn update_verify(&mut self, m: &[u8]) -> Result<(), UnknownCryptoError> {
self.verifying_key.update(m)
}
#[cfg(feature = "safe_api")]
fn finalize_sign(&mut self, rnd: &[u8]) -> Result<Vec<u8>, UnknownCryptoError> {
Ok(self
.signing_key
.finalize_with_rnd(&ExplicitRandom::try_from(rnd)?)?
.as_ref()
.to_vec())
}
#[cfg(feature = "safe_api")]
fn finalize_verify(&mut self, sig: &[u8]) -> Result<(), UnknownCryptoError> {
self.verifying_key.finalize(&Signature::try_from(sig)?)
}
#[cfg(feature = "safe_api")]
fn sign_with_rnd(
sk: &[u8],
m: &[u8],
ctx: &[u8],
rnd: &[u8],
) -> Result<Vec<u8>, UnknownCryptoError> {
let signing_key = SigningKey::try_from(sk)?;
let sig = signing_key.sign_with_rnd(m, ctx, &ExplicitRandom::try_from(rnd)?)?;
Ok(sig.as_ref().to_vec())
}
}
#[test]
#[cfg(any(feature = "safe_api", feature = "alloc"))]
fn run_basic_dsa_tests() {
#[cfg(feature = "safe_api")]
let seed = Seed::generate().unwrap();
#[cfg(not(feature = "safe_api"))]
let seed = Seed::from([123u8; 32]);
let dsa_tester = DsaTester::<KeyPair>::new(KeyPair::new(seed.clone()).unwrap());
DsaTester::<KeyPair>::run_all_tests(
&dsa_tester,
seed.unprotected_as_ref(),
b"This message to sign with ML-DSA.",
);
}
#[test]
fn test_keypair_seed() {
let seed = Seed::from([255u8; 32]);
let kp = KeyPair::new(seed.clone()).unwrap();
assert_eq!(&seed, kp.seed());
}
#[test]
#[cfg(feature = "safe_api")]
fn test_keypair_generate() {
let kp0 = KeyPair::generate().unwrap();
let kp1 = KeyPair::generate().unwrap();
assert_ne!(kp0, kp1);
}
#[test]
fn test_err_on_invalid_mu() {
let seed = Seed::from([255u8; 32]);
let rnd = ExplicitRandom::from([0u8; 32]);
let kp = KeyPair::new(seed.clone()).unwrap();
assert!(
kp.private()
.sign_external_mu_with_rnd(&[1u8; 63], &rnd)
.is_err()
);
assert!(
kp.private()
.sign_external_mu_with_rnd(&[1u8; 65], &rnd)
.is_err()
);
let sig = kp
.private()
.sign_external_mu_with_rnd(&[1u8; 64], &rnd)
.unwrap();
assert!(kp.public().verify_external_mu(&[1u8; 63], &sig).is_err());
assert!(kp.public().verify_external_mu(&[1u8; 65], &sig).is_err());
assert!(kp.public().verify_external_mu(&[1u8; 64], &sig).is_ok());
}
#[test]
fn test_try_from_sk_to_vk() {
let seed = Seed::from([255u8; 32]);
let kp = KeyPair::new(seed).unwrap();
assert_eq!(&VerifyingKey::try_from(kp.private()).unwrap(), kp.public());
}
#[test]
fn test_one_shot_eq_streaming_deterministic() {
let seed = Seed::from([255u8; 32]);
let kp = KeyPair::new(seed.clone()).unwrap();
let oneshot = kp
.private()
.sign_deterministic(b"Message to sign", b"Context")
.unwrap();
let mut sk = SigningKey::try_from(&seed).unwrap();
assert_eq!(kp.seed, seed);
assert_eq!(kp.private(), &sk);
sk.init(b"Context").unwrap();
sk.update(b"Message to ").unwrap();
sk.update(b"sign").unwrap();
let multi = sk
.finalize_with_rnd(&ExplicitRandom::deterministic())
.unwrap();
assert_eq!(oneshot, multi);
let mut vk = VerifyingKey::try_from(&sk).unwrap();
assert_eq!(kp.public(), &vk);
vk.init(b"Context").unwrap();
vk.update(b"Message").unwrap();
vk.update(b" to ").unwrap();
vk.update(b"sign").unwrap();
assert!(vk.clone().finalize(&oneshot).is_ok());
assert!(vk.clone().finalize(&multi).is_ok());
}
#[test]
#[cfg(feature = "safe_api")]
fn test_one_shot_eq_streaming_hedged() {
let seed = Seed::from([255u8; 32]);
let kp = KeyPair::new(seed.clone()).unwrap();
let oneshot = kp.private().sign(b"Message to sign", b"Context").unwrap();
let mut sk = SigningKey::try_from(&seed).unwrap();
assert_eq!(kp.seed, seed);
assert_eq!(kp.private(), &sk);
sk.init(b"Context").unwrap();
sk.update(b"Message to ").unwrap();
sk.update(b"sign").unwrap();
let multi = sk.finalize().unwrap();
assert_ne!(oneshot, multi);
let mut vk = VerifyingKey::try_from(&sk).unwrap();
assert_eq!(kp.public(), &vk);
vk.init(b"Context").unwrap();
vk.update(b"Message").unwrap();
vk.update(b" to ").unwrap();
vk.update(b"sign").unwrap();
assert!(vk.clone().finalize(&oneshot).is_ok());
assert!(vk.clone().finalize(&multi).is_ok());
}
#[test]
#[cfg(feature = "safe_api")]
fn double_keypair_fits_stack() {
const STACKSIZE: usize = 1024 << 10;
std::thread::Builder::new()
.stack_size(STACKSIZE)
.spawn(|| {
let kp1 = KeyPair::new(Seed::from([127u8; 32])).unwrap();
let kp2 = KeyPair::new(Seed::from([255u8; 32])).unwrap();
let sig1 = kp1.signing_key.sign(b"Test", b"").unwrap();
let sig2 = kp2.signing_key.sign(b"Test", b"").unwrap();
assert!(kp1.verifying_key.verify(b"Test", b"", &sig1).is_ok());
assert!(kp2.verifying_key.verify(b"Test", b"", &sig2).is_ok());
})
.unwrap()
.join()
.expect("ML-DSA-87 exceeded the stack size");
}
}