#[cfg(feature = "pq-preview")]
use crate::CryptoError;
#[cfg(feature = "pq-preview")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum PqKemAlgo {
MlKem512,
MlKem768,
MlKem1024,
}
#[cfg(feature = "pq-preview")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum PqSigAlgo {
MlDsa44,
MlDsa65,
MlDsa87,
SlhDsaSha2_128s,
SlhDsaSha2_128f,
SlhDsaSha2_192s,
SlhDsaSha2_192f,
SlhDsaSha2_256s,
SlhDsaSha2_256f,
SlhDsaShake128s,
SlhDsaShake128f,
SlhDsaShake256s,
SlhDsaShake256f,
}
#[cfg(feature = "pq-preview")]
fn pq_os_rng() -> Result<rand_chacha::ChaCha20Rng, crate::CryptoError> {
use rand_core::SeedableRng;
let mut seed = [0u8; 32];
getrandom::fill(&mut seed).map_err(|_| CryptoError::Rng)?;
Ok(rand_chacha::ChaCha20Rng::from_seed(seed))
}
#[cfg(feature = "pq-preview")]
pub fn pq_kem_generate(
algo: PqKemAlgo,
) -> Result<(oxicrypto_core::Vec<u8>, oxicrypto_core::Vec<u8>), CryptoError> {
let mut rng = pq_os_rng()?;
match algo {
PqKemAlgo::MlKem512 => {
let (dk, ek) = oxicrypto_pq::MlKem512::generate(&mut rng);
let dk_bytes = dk.to_bytes()?;
let ek_bytes = ek.to_bytes();
Ok((dk_bytes, ek_bytes))
}
PqKemAlgo::MlKem768 => {
let (dk, ek) = oxicrypto_pq::MlKem768::generate(&mut rng);
let dk_bytes = dk.to_bytes()?;
let ek_bytes = ek.to_bytes();
Ok((dk_bytes, ek_bytes))
}
PqKemAlgo::MlKem1024 => {
let (dk, ek) = oxicrypto_pq::MlKem1024::generate(&mut rng);
let dk_bytes = dk.to_bytes()?;
let ek_bytes = ek.to_bytes();
Ok((dk_bytes, ek_bytes))
}
}
}
#[cfg(feature = "pq-preview")]
pub fn pq_sig_generate(
algo: PqSigAlgo,
) -> Result<(oxicrypto_core::Vec<u8>, oxicrypto_core::Vec<u8>), CryptoError> {
let mut rng = pq_os_rng()?;
match algo {
PqSigAlgo::MlDsa44 => {
let (sk, vk) = oxicrypto_pq::MlDsa44::generate(&mut rng);
Ok((sk.to_bytes(), vk.to_bytes()))
}
PqSigAlgo::MlDsa65 => {
let (sk, vk) = oxicrypto_pq::MlDsa65::generate(&mut rng);
Ok((sk.to_bytes(), vk.to_bytes()))
}
PqSigAlgo::MlDsa87 => {
let (sk, vk) = oxicrypto_pq::MlDsa87::generate(&mut rng);
Ok((sk.to_bytes(), vk.to_bytes()))
}
PqSigAlgo::SlhDsaSha2_128s => {
let (sk, vk) = oxicrypto_pq::SlhDsaSha2_128s::generate(&mut rng);
Ok((sk.to_bytes(), vk.to_bytes()))
}
PqSigAlgo::SlhDsaSha2_128f => {
let (sk, vk) = oxicrypto_pq::SlhDsaSha2_128f::generate(&mut rng);
Ok((sk.to_bytes(), vk.to_bytes()))
}
PqSigAlgo::SlhDsaSha2_256s => {
let (sk, vk) = oxicrypto_pq::SlhDsaSha2_256s::generate(&mut rng);
Ok((sk.to_bytes(), vk.to_bytes()))
}
PqSigAlgo::SlhDsaSha2_256f => {
let (sk, vk) = oxicrypto_pq::SlhDsaSha2_256f::generate(&mut rng);
Ok((sk.to_bytes(), vk.to_bytes()))
}
PqSigAlgo::SlhDsaSha2_192s => {
let (sk, vk) = oxicrypto_pq::SlhDsaSha2_192s::generate(&mut rng);
Ok((sk.to_bytes(), vk.to_bytes()))
}
PqSigAlgo::SlhDsaSha2_192f => {
let (sk, vk) = oxicrypto_pq::SlhDsaSha2_192f::generate(&mut rng);
Ok((sk.to_bytes(), vk.to_bytes()))
}
PqSigAlgo::SlhDsaShake128s => {
let (sk, vk) = oxicrypto_pq::SlhDsaShake128s::generate(&mut rng);
Ok((sk.to_bytes(), vk.to_bytes()))
}
PqSigAlgo::SlhDsaShake128f => {
let (sk, vk) = oxicrypto_pq::SlhDsaShake128f::generate(&mut rng);
Ok((sk.to_bytes(), vk.to_bytes()))
}
PqSigAlgo::SlhDsaShake256s => {
let (sk, vk) = oxicrypto_pq::SlhDsaShake256s::generate(&mut rng);
Ok((sk.to_bytes(), vk.to_bytes()))
}
PqSigAlgo::SlhDsaShake256f => {
let (sk, vk) = oxicrypto_pq::SlhDsaShake256f::generate(&mut rng);
Ok((sk.to_bytes(), vk.to_bytes()))
}
}
}
#[cfg(feature = "pq-preview")]
impl core::fmt::Display for PqKemAlgo {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(match self {
PqKemAlgo::MlKem512 => "ML-KEM-512",
PqKemAlgo::MlKem768 => "ML-KEM-768",
PqKemAlgo::MlKem1024 => "ML-KEM-1024",
})
}
}
#[cfg(feature = "pq-preview")]
impl core::fmt::Display for PqSigAlgo {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(match self {
PqSigAlgo::MlDsa44 => "ML-DSA-44",
PqSigAlgo::MlDsa65 => "ML-DSA-65",
PqSigAlgo::MlDsa87 => "ML-DSA-87",
PqSigAlgo::SlhDsaSha2_128s => "SLH-DSA-SHA2-128s",
PqSigAlgo::SlhDsaSha2_128f => "SLH-DSA-SHA2-128f",
PqSigAlgo::SlhDsaSha2_192s => "SLH-DSA-SHA2-192s",
PqSigAlgo::SlhDsaSha2_192f => "SLH-DSA-SHA2-192f",
PqSigAlgo::SlhDsaSha2_256s => "SLH-DSA-SHA2-256s",
PqSigAlgo::SlhDsaSha2_256f => "SLH-DSA-SHA2-256f",
PqSigAlgo::SlhDsaShake128s => "SLH-DSA-SHAKE-128s",
PqSigAlgo::SlhDsaShake128f => "SLH-DSA-SHAKE-128f",
PqSigAlgo::SlhDsaShake256s => "SLH-DSA-SHAKE-256s",
PqSigAlgo::SlhDsaShake256f => "SLH-DSA-SHAKE-256f",
})
}
}
#[cfg(feature = "pq-preview")]
impl core::str::FromStr for PqKemAlgo {
type Err = CryptoError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"ML-KEM-512" | "ml-kem-512" | "MLKEM512" => Ok(PqKemAlgo::MlKem512),
"ML-KEM-768" | "ml-kem-768" | "MLKEM768" => Ok(PqKemAlgo::MlKem768),
"ML-KEM-1024" | "ml-kem-1024" | "MLKEM1024" => Ok(PqKemAlgo::MlKem1024),
_ => Err(CryptoError::UnsupportedAlgorithm),
}
}
}
#[cfg(feature = "pq-preview")]
impl core::str::FromStr for PqSigAlgo {
type Err = CryptoError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"ML-DSA-44" | "ml-dsa-44" | "MLDSA44" => Ok(PqSigAlgo::MlDsa44),
"ML-DSA-65" | "ml-dsa-65" | "MLDSA65" => Ok(PqSigAlgo::MlDsa65),
"ML-DSA-87" | "ml-dsa-87" | "MLDSA87" => Ok(PqSigAlgo::MlDsa87),
"SLH-DSA-SHA2-128s" | "slh-dsa-sha2-128s" | "SLHDSASHA2128S" => {
Ok(PqSigAlgo::SlhDsaSha2_128s)
}
"SLH-DSA-SHA2-128f" | "slh-dsa-sha2-128f" | "SLHDSASHA2128F" => {
Ok(PqSigAlgo::SlhDsaSha2_128f)
}
"SLH-DSA-SHA2-256s" | "slh-dsa-sha2-256s" | "SLHDSASHA2256S" => {
Ok(PqSigAlgo::SlhDsaSha2_256s)
}
"SLH-DSA-SHA2-256f" | "slh-dsa-sha2-256f" | "SLHDSASHA2256F" => {
Ok(PqSigAlgo::SlhDsaSha2_256f)
}
"SLH-DSA-SHA2-192s" | "slh-dsa-sha2-192s" | "SLHDSASHA2192S" => {
Ok(PqSigAlgo::SlhDsaSha2_192s)
}
"SLH-DSA-SHA2-192f" | "slh-dsa-sha2-192f" | "SLHDSASHA2192F" => {
Ok(PqSigAlgo::SlhDsaSha2_192f)
}
"SLH-DSA-SHAKE-128s" | "slh-dsa-shake-128s" | "SLHDSASHAKE128S" => {
Ok(PqSigAlgo::SlhDsaShake128s)
}
"SLH-DSA-SHAKE-128f" | "slh-dsa-shake-128f" | "SLHDSASHAKE128F" => {
Ok(PqSigAlgo::SlhDsaShake128f)
}
"SLH-DSA-SHAKE-256s" | "slh-dsa-shake-256s" | "SLHDSASHAKE256S" => {
Ok(PqSigAlgo::SlhDsaShake256s)
}
"SLH-DSA-SHAKE-256f" | "slh-dsa-shake-256f" | "SLHDSASHAKE256F" => {
Ok(PqSigAlgo::SlhDsaShake256f)
}
_ => Err(CryptoError::UnsupportedAlgorithm),
}
}
}
#[cfg(feature = "pq-preview")]
impl TryFrom<&str> for PqKemAlgo {
type Error = CryptoError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
s.parse()
}
}
#[cfg(feature = "pq-preview")]
impl TryFrom<&str> for PqSigAlgo {
type Error = CryptoError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
s.parse()
}
}
#[cfg(feature = "pq-preview")]
pub fn pq_sign(
algo: PqSigAlgo,
sk_bytes: &[u8],
msg: &[u8],
) -> Result<oxicrypto_core::Vec<u8>, CryptoError> {
use oxicrypto_pq::mldsa::{SigningKey44, SigningKey65, SigningKey87};
use oxicrypto_pq::slh_dsa::{
SlhDsaSigningKey128f, SlhDsaSigningKey128s, SlhDsaSigningKey192f, SlhDsaSigningKey192s,
SlhDsaSigningKey256f, SlhDsaSigningKey256s, SlhDsaSigningKeyShake128f,
SlhDsaSigningKeyShake128s, SlhDsaSigningKeyShake256f, SlhDsaSigningKeyShake256s,
};
match algo {
PqSigAlgo::MlDsa44 => {
let sk = SigningKey44::from_bytes(sk_bytes)?;
let sig = sk.sign(msg)?;
Ok(sig.to_bytes())
}
PqSigAlgo::MlDsa65 => {
let sk = SigningKey65::from_bytes(sk_bytes)?;
let sig = sk.sign(msg)?;
Ok(sig.to_bytes())
}
PqSigAlgo::MlDsa87 => {
let sk = SigningKey87::from_bytes(sk_bytes)?;
let sig = sk.sign(msg)?;
Ok(sig.to_bytes())
}
PqSigAlgo::SlhDsaSha2_128s => {
let sk = SlhDsaSigningKey128s::from_bytes(sk_bytes)?;
let sig = sk.sign(msg)?;
Ok(sig.to_bytes())
}
PqSigAlgo::SlhDsaSha2_128f => {
let sk = SlhDsaSigningKey128f::from_bytes(sk_bytes)?;
let sig = sk.sign(msg)?;
Ok(sig.to_bytes())
}
PqSigAlgo::SlhDsaSha2_192s => {
let sk = SlhDsaSigningKey192s::from_bytes(sk_bytes)?;
let sig = sk.sign(msg)?;
Ok(sig.to_bytes())
}
PqSigAlgo::SlhDsaSha2_192f => {
let sk = SlhDsaSigningKey192f::from_bytes(sk_bytes)?;
let sig = sk.sign(msg)?;
Ok(sig.to_bytes())
}
PqSigAlgo::SlhDsaSha2_256s => {
let sk = SlhDsaSigningKey256s::from_bytes(sk_bytes)?;
let sig = sk.sign(msg)?;
Ok(sig.to_bytes())
}
PqSigAlgo::SlhDsaSha2_256f => {
let sk = SlhDsaSigningKey256f::from_bytes(sk_bytes)?;
let sig = sk.sign(msg)?;
Ok(sig.to_bytes())
}
PqSigAlgo::SlhDsaShake128s => {
let sk = SlhDsaSigningKeyShake128s::from_bytes(sk_bytes)?;
let sig = sk.sign(msg)?;
Ok(sig.to_bytes())
}
PqSigAlgo::SlhDsaShake128f => {
let sk = SlhDsaSigningKeyShake128f::from_bytes(sk_bytes)?;
let sig = sk.sign(msg)?;
Ok(sig.to_bytes())
}
PqSigAlgo::SlhDsaShake256s => {
let sk = SlhDsaSigningKeyShake256s::from_bytes(sk_bytes)?;
let sig = sk.sign(msg)?;
Ok(sig.to_bytes())
}
PqSigAlgo::SlhDsaShake256f => {
let sk = SlhDsaSigningKeyShake256f::from_bytes(sk_bytes)?;
let sig = sk.sign(msg)?;
Ok(sig.to_bytes())
}
}
}
#[cfg(feature = "pq-preview")]
pub fn pq_verify(
algo: PqSigAlgo,
vk_bytes: &[u8],
msg: &[u8],
sig_bytes: &[u8],
) -> Result<(), CryptoError> {
use oxicrypto_pq::mldsa::{
Signature44, Signature65, Signature87, VerifyingKey44, VerifyingKey65, VerifyingKey87,
};
use oxicrypto_pq::slh_dsa::{
SlhDsaSignature128f, SlhDsaSignature128s, SlhDsaSignature192f, SlhDsaSignature192s,
SlhDsaSignature256f, SlhDsaSignature256s, SlhDsaSignatureShake128f,
SlhDsaSignatureShake128s, SlhDsaSignatureShake256f, SlhDsaSignatureShake256s,
SlhDsaVerifyingKey128f, SlhDsaVerifyingKey128s, SlhDsaVerifyingKey192f,
SlhDsaVerifyingKey192s, SlhDsaVerifyingKey256f, SlhDsaVerifyingKey256s,
SlhDsaVerifyingKeyShake128f, SlhDsaVerifyingKeyShake128s, SlhDsaVerifyingKeyShake256f,
SlhDsaVerifyingKeyShake256s,
};
match algo {
PqSigAlgo::MlDsa44 => {
let vk = VerifyingKey44::from_bytes(vk_bytes)?;
let sig = Signature44::from_bytes(sig_bytes)?;
vk.verify(msg, &sig)
}
PqSigAlgo::MlDsa65 => {
let vk = VerifyingKey65::from_bytes(vk_bytes)?;
let sig = Signature65::from_bytes(sig_bytes)?;
vk.verify(msg, &sig)
}
PqSigAlgo::MlDsa87 => {
let vk = VerifyingKey87::from_bytes(vk_bytes)?;
let sig = Signature87::from_bytes(sig_bytes)?;
vk.verify(msg, &sig)
}
PqSigAlgo::SlhDsaSha2_128s => {
let vk = SlhDsaVerifyingKey128s::from_bytes(vk_bytes)?;
let sig = SlhDsaSignature128s::from_bytes(sig_bytes)?;
vk.verify(msg, &sig)
}
PqSigAlgo::SlhDsaSha2_128f => {
let vk = SlhDsaVerifyingKey128f::from_bytes(vk_bytes)?;
let sig = SlhDsaSignature128f::from_bytes(sig_bytes)?;
vk.verify(msg, &sig)
}
PqSigAlgo::SlhDsaSha2_192s => {
let vk = SlhDsaVerifyingKey192s::from_bytes(vk_bytes)?;
let sig = SlhDsaSignature192s::from_bytes(sig_bytes)?;
vk.verify(msg, &sig)
}
PqSigAlgo::SlhDsaSha2_192f => {
let vk = SlhDsaVerifyingKey192f::from_bytes(vk_bytes)?;
let sig = SlhDsaSignature192f::from_bytes(sig_bytes)?;
vk.verify(msg, &sig)
}
PqSigAlgo::SlhDsaSha2_256s => {
let vk = SlhDsaVerifyingKey256s::from_bytes(vk_bytes)?;
let sig = SlhDsaSignature256s::from_bytes(sig_bytes)?;
vk.verify(msg, &sig)
}
PqSigAlgo::SlhDsaSha2_256f => {
let vk = SlhDsaVerifyingKey256f::from_bytes(vk_bytes)?;
let sig = SlhDsaSignature256f::from_bytes(sig_bytes)?;
vk.verify(msg, &sig)
}
PqSigAlgo::SlhDsaShake128s => {
let vk = SlhDsaVerifyingKeyShake128s::from_bytes(vk_bytes)?;
let sig = SlhDsaSignatureShake128s::from_bytes(sig_bytes)?;
vk.verify(msg, &sig)
}
PqSigAlgo::SlhDsaShake128f => {
let vk = SlhDsaVerifyingKeyShake128f::from_bytes(vk_bytes)?;
let sig = SlhDsaSignatureShake128f::from_bytes(sig_bytes)?;
vk.verify(msg, &sig)
}
PqSigAlgo::SlhDsaShake256s => {
let vk = SlhDsaVerifyingKeyShake256s::from_bytes(vk_bytes)?;
let sig = SlhDsaSignatureShake256s::from_bytes(sig_bytes)?;
vk.verify(msg, &sig)
}
PqSigAlgo::SlhDsaShake256f => {
let vk = SlhDsaVerifyingKeyShake256f::from_bytes(vk_bytes)?;
let sig = SlhDsaSignatureShake256f::from_bytes(sig_bytes)?;
vk.verify(msg, &sig)
}
}
}
#[cfg(all(test, feature = "pq-preview"))]
mod tests {
use super::{pq_sig_generate, pq_sign, pq_verify, PqSigAlgo};
const MLDSA_STACK: usize = 8 * 1024 * 1024;
#[test]
fn pq_sign_verify_round_trip_mldsa65() {
std::thread::Builder::new()
.stack_size(MLDSA_STACK)
.spawn(|| {
let (sk, vk) =
pq_sig_generate(PqSigAlgo::MlDsa65).expect("ML-DSA-65 key generation failed");
let sig = pq_sign(PqSigAlgo::MlDsa65, &sk, b"hello oxicrypto")
.expect("ML-DSA-65 sign failed");
pq_verify(PqSigAlgo::MlDsa65, &vk, b"hello oxicrypto", &sig)
.expect("ML-DSA-65 verify failed on correct message");
assert!(
pq_verify(PqSigAlgo::MlDsa65, &vk, b"tampered message", &sig).is_err(),
"ML-DSA-65 verify must reject a tampered message"
);
})
.expect("thread spawn failed")
.join()
.expect("ML-DSA-65 round-trip thread panicked");
}
#[test]
fn pq_sign_verify_round_trip_slhdsa_sha2_128s() {
std::thread::Builder::new()
.stack_size(MLDSA_STACK)
.spawn(|| {
let (sk, vk) = pq_sig_generate(PqSigAlgo::SlhDsaSha2_128s)
.expect("SLH-DSA-SHA2-128s key generation failed");
let sig = pq_sign(PqSigAlgo::SlhDsaSha2_128s, &sk, b"hello oxicrypto")
.expect("SLH-DSA-SHA2-128s sign failed");
pq_verify(PqSigAlgo::SlhDsaSha2_128s, &vk, b"hello oxicrypto", &sig)
.expect("SLH-DSA-SHA2-128s verify failed on correct message");
assert!(
pq_verify(PqSigAlgo::SlhDsaSha2_128s, &vk, b"tampered message", &sig).is_err(),
"SLH-DSA-SHA2-128s verify must reject a tampered message"
);
})
.expect("thread spawn failed")
.join()
.expect("SLH-DSA-SHA2-128s round-trip thread panicked");
}
#[test]
fn pq_sign_verify_round_trip_slhdsa_shake128s() {
std::thread::Builder::new()
.stack_size(MLDSA_STACK)
.spawn(|| {
let (sk, vk) = pq_sig_generate(PqSigAlgo::SlhDsaShake128s)
.expect("SLH-DSA-SHAKE-128s key generation failed");
let sig = pq_sign(PqSigAlgo::SlhDsaShake128s, &sk, b"hello oxicrypto")
.expect("SLH-DSA-SHAKE-128s sign failed");
pq_verify(PqSigAlgo::SlhDsaShake128s, &vk, b"hello oxicrypto", &sig)
.expect("SLH-DSA-SHAKE-128s verify failed on correct message");
assert!(
pq_verify(PqSigAlgo::SlhDsaShake128s, &vk, b"tampered message", &sig).is_err(),
"SLH-DSA-SHAKE-128s verify must reject a tampered message"
);
})
.expect("thread spawn failed")
.join()
.expect("SLH-DSA-SHAKE-128s round-trip thread panicked");
}
}