#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::{
string::ToString,
vec::Vec,
};
#[cfg(not(feature = "alloc"))]
extern crate alloc;
#[cfg(not(feature = "alloc"))]
use alloc::string::ToString;
#[cfg(feature = "wasm")]
use js_sys::Uint8Array;
use lib_q_core::api::Algorithm;
use lib_q_core::error::{
Error,
Result,
};
use lib_q_core::traits::{
SigKeypair,
SigPublicKey,
SigSecretKey,
Signature,
};
#[cfg(feature = "slh-dsa")]
use lib_q_slh_dsa::{
ParameterSet,
Sha2_128f,
Sha2_192f,
Sha2_256f,
Shake128f,
Shake192f,
Shake256f,
lib_q_integration::SlhDsaSignature,
};
#[cfg(feature = "slh-dsa-std")]
use rand_core::Rng;
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "wasm", wasm_bindgen)]
pub struct SlhDsa;
impl SlhDsa {
pub fn new() -> Self {
Self
}
#[cfg(feature = "alloc")]
pub fn generate_keypair_with_randomness(
&self,
algorithm: Algorithm,
randomness: &[u8],
) -> Result<SigKeypair> {
self.generate_keypair_for_algorithm(algorithm, Some(randomness))
}
#[cfg(feature = "alloc")]
pub fn sign_with_randomness(
&self,
algorithm: Algorithm,
secret_key: &SigSecretKey,
message: &[u8],
randomness: &[u8],
) -> Result<Vec<u8>> {
self.sign_for_algorithm(algorithm, secret_key, message, Some(randomness))
}
}
impl Default for SlhDsa {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "alloc")]
impl Signature for SlhDsa {
fn generate_keypair(&self) -> Result<SigKeypair> {
self.generate_keypair_for_algorithm(Algorithm::SlhDsaShake256128fRobust, None)
}
fn sign(&self, secret_key: &SigSecretKey, message: &[u8]) -> Result<Vec<u8>> {
self.sign_for_algorithm(
Algorithm::SlhDsaShake256128fRobust,
secret_key,
message,
None,
)
}
fn verify(&self, public_key: &SigPublicKey, message: &[u8], signature: &[u8]) -> Result<bool> {
self.verify_for_algorithm(
Algorithm::SlhDsaShake256128fRobust,
public_key,
message,
signature,
)
}
}
impl SlhDsa {
#[cfg(feature = "alloc")]
pub fn generate_keypair_for_algorithm(
&self,
algorithm: Algorithm,
randomness: Option<&[u8]>,
) -> Result<SigKeypair> {
#[cfg(feature = "slh-dsa")]
{
if !matches!(
algorithm,
Algorithm::SlhDsaSha256128fRobust |
Algorithm::SlhDsaSha256192fRobust |
Algorithm::SlhDsaSha256256fRobust |
Algorithm::SlhDsaShake256128fRobust |
Algorithm::SlhDsaShake256192fRobust |
Algorithm::SlhDsaShake256256fRobust
) {
return Err(Error::InvalidAlgorithm {
algorithm: "Algorithm is not an SLH-DSA algorithm",
});
}
let (public_key, secret_key) = match algorithm {
Algorithm::SlhDsaSha256128fRobust => {
self.generate_keypair_impl::<Sha2_128f>(randomness)?
}
Algorithm::SlhDsaSha256192fRobust => {
self.generate_keypair_impl::<Sha2_192f>(randomness)?
}
Algorithm::SlhDsaSha256256fRobust => {
self.generate_keypair_impl::<Sha2_256f>(randomness)?
}
Algorithm::SlhDsaShake256128fRobust => {
self.generate_keypair_impl::<Shake128f>(randomness)?
}
Algorithm::SlhDsaShake256192fRobust => {
self.generate_keypair_impl::<Shake192f>(randomness)?
}
Algorithm::SlhDsaShake256256fRobust => {
self.generate_keypair_impl::<Shake256f>(randomness)?
}
_ => unreachable!(), };
Ok(SigKeypair::new(public_key, secret_key))
}
#[cfg(not(feature = "slh-dsa"))]
{
Err(Error::NotImplemented {
feature: "SLH-DSA implementation requires the 'slh-dsa' feature flag",
})
}
}
#[cfg(feature = "alloc")]
pub fn sign_for_algorithm(
&self,
algorithm: Algorithm,
secret_key: &SigSecretKey,
message: &[u8],
randomness: Option<&[u8]>,
) -> Result<Vec<u8>> {
#[cfg(feature = "slh-dsa")]
{
if !matches!(
algorithm,
Algorithm::SlhDsaSha256128fRobust |
Algorithm::SlhDsaSha256192fRobust |
Algorithm::SlhDsaSha256256fRobust |
Algorithm::SlhDsaShake256128fRobust |
Algorithm::SlhDsaShake256192fRobust |
Algorithm::SlhDsaShake256256fRobust
) {
return Err(Error::InvalidAlgorithm {
algorithm: "Algorithm is not an SLH-DSA algorithm",
});
}
match algorithm {
Algorithm::SlhDsaSha256128fRobust => {
self.sign_impl::<Sha2_128f>(secret_key, message, randomness)
}
Algorithm::SlhDsaSha256192fRobust => {
self.sign_impl::<Sha2_192f>(secret_key, message, randomness)
}
Algorithm::SlhDsaSha256256fRobust => {
self.sign_impl::<Sha2_256f>(secret_key, message, randomness)
}
Algorithm::SlhDsaShake256128fRobust => {
self.sign_impl::<Shake128f>(secret_key, message, randomness)
}
Algorithm::SlhDsaShake256192fRobust => {
self.sign_impl::<Shake192f>(secret_key, message, randomness)
}
Algorithm::SlhDsaShake256256fRobust => {
self.sign_impl::<Shake256f>(secret_key, message, randomness)
}
_ => unreachable!(), }
}
#[cfg(not(feature = "slh-dsa"))]
{
Err(Error::NotImplemented {
feature: "SLH-DSA implementation requires the 'slh-dsa' feature flag",
})
}
}
pub fn verify_for_algorithm(
&self,
algorithm: Algorithm,
public_key: &SigPublicKey,
message: &[u8],
signature: &[u8],
) -> Result<bool> {
#[cfg(feature = "slh-dsa")]
{
if !matches!(
algorithm,
Algorithm::SlhDsaSha256128fRobust |
Algorithm::SlhDsaSha256192fRobust |
Algorithm::SlhDsaSha256256fRobust |
Algorithm::SlhDsaShake256128fRobust |
Algorithm::SlhDsaShake256192fRobust |
Algorithm::SlhDsaShake256256fRobust
) {
return Err(Error::InvalidAlgorithm {
algorithm: "Algorithm is not an SLH-DSA algorithm",
});
}
match algorithm {
Algorithm::SlhDsaSha256128fRobust => {
self.verify_impl::<Sha2_128f>(public_key, message, signature)
}
Algorithm::SlhDsaSha256192fRobust => {
self.verify_impl::<Sha2_192f>(public_key, message, signature)
}
Algorithm::SlhDsaSha256256fRobust => {
self.verify_impl::<Sha2_256f>(public_key, message, signature)
}
Algorithm::SlhDsaShake256128fRobust => {
self.verify_impl::<Shake128f>(public_key, message, signature)
}
Algorithm::SlhDsaShake256192fRobust => {
self.verify_impl::<Shake192f>(public_key, message, signature)
}
Algorithm::SlhDsaShake256256fRobust => {
self.verify_impl::<Shake256f>(public_key, message, signature)
}
_ => unreachable!(), }
}
#[cfg(not(feature = "slh-dsa"))]
{
Err(Error::NotImplemented {
feature: "SLH-DSA implementation requires the 'slh-dsa' feature flag",
})
}
}
#[cfg(all(feature = "slh-dsa", feature = "alloc"))]
fn generate_keypair_impl<P: ParameterSet + 'static>(
&self,
randomness: Option<&[u8]>,
) -> Result<(Vec<u8>, Vec<u8>)> {
let slh_dsa = SlhDsaSignature::<P>::new();
if let Some(rand) = randomness {
let keypair = slh_dsa.generate_keypair_with_randomness(rand)?;
Ok((
keypair.public_key.as_bytes().to_vec(),
keypair.secret_key.as_bytes().to_vec(),
))
} else {
#[cfg(feature = "slh-dsa-std")]
{
use lib_q_random::new_secure_rng;
let mut rng = new_secure_rng().map_err(|_| Error::RandomGenerationFailed {
operation: "Failed to create secure RNG".to_string(),
})?;
let mut key_randomness = vec![0u8; 96]; rng.fill_bytes(&mut key_randomness);
let keypair = slh_dsa.generate_keypair_with_randomness(&key_randomness)?;
Ok((
keypair.public_key.as_bytes().to_vec(),
keypair.secret_key.as_bytes().to_vec(),
))
}
#[cfg(not(feature = "slh-dsa-std"))]
{
Err(Error::RandomGenerationFailed {
operation: "No randomness source available in no_std environment. Use generate_keypair_with_randomness() instead.".to_string(),
})
}
}
}
#[cfg(all(feature = "slh-dsa", feature = "alloc"))]
fn sign_impl<P: ParameterSet + 'static>(
&self,
secret_key: &SigSecretKey,
message: &[u8],
randomness: Option<&[u8]>,
) -> Result<Vec<u8>> {
let slh_dsa = SlhDsaSignature::<P>::new();
if let Some(rand) = randomness {
slh_dsa.sign_with_randomness(secret_key, message, rand)
} else {
#[cfg(feature = "slh-dsa-std")]
{
use lib_q_random::new_secure_rng;
let mut rng = new_secure_rng().map_err(|_| Error::RandomGenerationFailed {
operation: "Failed to create secure RNG".to_string(),
})?;
let mut signing_randomness = vec![0u8; 32]; rng.fill_bytes(&mut signing_randomness);
slh_dsa.sign_with_randomness(secret_key, message, &signing_randomness)
}
#[cfg(not(feature = "slh-dsa-std"))]
{
Err(Error::RandomGenerationFailed {
operation: "No randomness source available in no_std environment. Use sign_with_randomness() instead.".to_string(),
})
}
}
}
#[cfg(all(feature = "slh-dsa", feature = "alloc"))]
fn verify_impl<P: ParameterSet + 'static>(
&self,
public_key: &SigPublicKey,
message: &[u8],
signature: &[u8],
) -> Result<bool> {
let slh_dsa = SlhDsaSignature::<P>::new();
slh_dsa.verify(public_key, message, signature)
}
#[cfg(all(feature = "slh-dsa", not(feature = "alloc")))]
fn verify_impl<P: ParameterSet + 'static>(
&self,
_public_key: &SigPublicKey,
_message: &[u8],
_signature: &[u8],
) -> Result<bool> {
Err(Error::NotImplemented {
feature: "SLH-DSA verification requires alloc feature for no_std compatibility"
.to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_slh_dsa_creation() {
let slh_dsa = SlhDsa::new();
assert_eq!(slh_dsa, SlhDsa);
}
#[cfg(feature = "slh-dsa")]
#[test]
fn test_algorithm_validation() {
let slh_dsa = SlhDsa::new();
let valid_algorithms = [
Algorithm::SlhDsaSha256128fRobust,
Algorithm::SlhDsaSha256192fRobust,
Algorithm::SlhDsaSha256256fRobust,
Algorithm::SlhDsaShake256128fRobust,
Algorithm::SlhDsaShake256192fRobust,
Algorithm::SlhDsaShake256256fRobust,
];
for algorithm in valid_algorithms {
let result = slh_dsa.generate_keypair_for_algorithm(algorithm, None);
assert!(result.is_ok() || matches!(result, Err(Error::RandomGenerationFailed { .. })));
}
let result = slh_dsa.generate_keypair_for_algorithm(Algorithm::MlDsa65, None);
assert!(matches!(result, Err(Error::InvalidAlgorithm { .. })));
}
#[cfg(feature = "slh-dsa")]
#[test]
fn test_explicit_randomness_round_trip() {
let slh_dsa = SlhDsa::new();
let algorithm = Algorithm::SlhDsaShake256128fRobust;
let message = b"slh-dsa explicit randomness test";
let key_randomness = [11u8; 48];
let signing_randomness = [23u8; 16];
let keypair = slh_dsa
.generate_keypair_with_randomness(algorithm, &key_randomness)
.expect("key generation with explicit randomness should succeed");
let signature = slh_dsa
.sign_with_randomness(
algorithm,
keypair.secret_key(),
message,
&signing_randomness,
)
.expect("signing with explicit randomness should succeed");
let is_valid = slh_dsa
.verify_for_algorithm(algorithm, keypair.public_key(), message, &signature)
.expect("verification should succeed");
assert!(is_valid, "signature should verify for original message");
}
#[cfg(feature = "slh-dsa")]
#[test]
fn test_sign_and_verify_reject_non_slh_algorithm() {
let slh_dsa = SlhDsa::new();
let key_randomness = [5u8; 48];
let signing_randomness = [9u8; 16];
let keypair = slh_dsa
.generate_keypair_with_randomness(Algorithm::SlhDsaShake256128fRobust, &key_randomness)
.expect("explicit key generation should succeed");
let sign_result = slh_dsa.sign_for_algorithm(
Algorithm::MlDsa65,
keypair.secret_key(),
b"message",
Some(&signing_randomness),
);
assert!(matches!(sign_result, Err(Error::InvalidAlgorithm { .. })));
let verify_result = slh_dsa.verify_for_algorithm(
Algorithm::MlDsa65,
keypair.public_key(),
b"message",
b"signature",
);
assert!(matches!(verify_result, Err(Error::InvalidAlgorithm { .. })));
}
#[cfg(all(feature = "slh-dsa", feature = "slh-dsa-std"))]
#[test]
fn test_implicit_randomness_round_trip_with_std_rng() {
let slh_dsa = SlhDsa::new();
let algorithm = Algorithm::SlhDsaShake256128fRobust;
let message = b"slh-dsa implicit randomness test";
let keypair = slh_dsa
.generate_keypair_for_algorithm(algorithm, None)
.expect("key generation should use std RNG when slh-dsa-std is enabled");
let signature = slh_dsa
.sign_for_algorithm(algorithm, keypair.secret_key(), message, None)
.expect("signing should use std RNG when slh-dsa-std is enabled");
let is_valid = slh_dsa
.verify_for_algorithm(algorithm, keypair.public_key(), message, &signature)
.expect("verification should succeed");
assert!(is_valid, "signature should verify for original message");
}
#[cfg(all(feature = "slh-dsa", not(feature = "slh-dsa-std")))]
#[test]
fn test_implicit_randomness_requires_std_feature() {
let slh_dsa = SlhDsa::new();
let algorithm = Algorithm::SlhDsaShake256128fRobust;
let keypair_err = slh_dsa.generate_keypair_for_algorithm(algorithm, None);
assert!(
matches!(keypair_err, Err(Error::RandomGenerationFailed { .. })),
"without slh-dsa-std, implicit keygen randomness should fail"
);
let key_randomness = [7u8; 48];
let keypair = slh_dsa
.generate_keypair_with_randomness(algorithm, &key_randomness)
.expect("explicit key generation should succeed");
let sign_err = slh_dsa.sign_for_algorithm(algorithm, keypair.secret_key(), b"msg", None);
assert!(
matches!(sign_err, Err(Error::RandomGenerationFailed { .. })),
"without slh-dsa-std, implicit signing randomness should fail"
);
}
#[cfg(not(feature = "slh-dsa"))]
#[test]
fn test_feature_flag_required() {
let slh_dsa = SlhDsa::new();
let result =
slh_dsa.generate_keypair_for_algorithm(Algorithm::SlhDsaShake256128fRobust, None);
assert!(matches!(result, Err(Error::NotImplemented { .. })));
}
}
#[cfg(feature = "wasm")]
#[wasm_bindgen]
impl SlhDsa {
#[wasm_bindgen]
pub fn generate_keypair_wasm(
&self,
algorithm: &str,
randomness: Option<Uint8Array>,
) -> core::result::Result<WasmSlhDsaKeyPair, JsValue> {
let algorithm = match algorithm {
"SlhDsaSha256128fRobust" => Algorithm::SlhDsaSha256128fRobust,
"SlhDsaSha256192fRobust" => Algorithm::SlhDsaSha256192fRobust,
"SlhDsaSha256256fRobust" => Algorithm::SlhDsaSha256256fRobust,
"SlhDsaShake256128fRobust" => Algorithm::SlhDsaShake256128fRobust,
"SlhDsaShake256192fRobust" => Algorithm::SlhDsaShake256192fRobust,
"SlhDsaShake256256fRobust" => Algorithm::SlhDsaShake256256fRobust,
_ => return Err(JsValue::from_str("Invalid algorithm")),
};
let randomness_vec = randomness.map(|rand| rand.to_vec());
let randomness_slice = randomness_vec.as_deref();
let keypair = match self.generate_keypair_for_algorithm(algorithm, randomness_slice) {
Ok(kp) => kp,
Err(e) => return Err(JsValue::from_str(&e.to_string())),
};
Ok(WasmSlhDsaKeyPair::new(
Uint8Array::from(keypair.public_key().as_bytes()),
Uint8Array::from(keypair.secret_key().as_bytes()),
))
}
#[wasm_bindgen]
pub fn sign_wasm(
&self,
algorithm: &str,
secret_key: Uint8Array,
message: Uint8Array,
randomness: Option<Uint8Array>,
) -> core::result::Result<Uint8Array, JsValue> {
let algorithm = match algorithm {
"SlhDsaSha256128fRobust" => Algorithm::SlhDsaSha256128fRobust,
"SlhDsaSha256192fRobust" => Algorithm::SlhDsaSha256192fRobust,
"SlhDsaSha256256fRobust" => Algorithm::SlhDsaSha256256fRobust,
"SlhDsaShake256128fRobust" => Algorithm::SlhDsaShake256128fRobust,
"SlhDsaShake256192fRobust" => Algorithm::SlhDsaShake256192fRobust,
"SlhDsaShake256256fRobust" => Algorithm::SlhDsaShake256256fRobust,
_ => return Err(JsValue::from_str("Invalid algorithm")),
};
let secret_key = SigSecretKey::new(secret_key.to_vec());
let message = message.to_vec();
let randomness_vec = randomness.map(|rand| rand.to_vec());
let randomness_slice = randomness_vec.as_deref();
let signature =
match self.sign_for_algorithm(algorithm, &secret_key, &message, randomness_slice) {
Ok(sig) => sig,
Err(e) => return Err(JsValue::from_str(&e.to_string())),
};
Ok(Uint8Array::from(signature.as_slice()))
}
#[wasm_bindgen]
pub fn verify_wasm(
&self,
algorithm: &str,
public_key: Uint8Array,
message: Uint8Array,
signature: Uint8Array,
) -> core::result::Result<bool, JsValue> {
let algorithm = match algorithm {
"SlhDsaSha256128fRobust" => Algorithm::SlhDsaSha256128fRobust,
"SlhDsaSha256192fRobust" => Algorithm::SlhDsaSha256192fRobust,
"SlhDsaSha256256fRobust" => Algorithm::SlhDsaSha256256fRobust,
"SlhDsaShake256128fRobust" => Algorithm::SlhDsaShake256128fRobust,
"SlhDsaShake256192fRobust" => Algorithm::SlhDsaShake256192fRobust,
"SlhDsaShake256256fRobust" => Algorithm::SlhDsaShake256256fRobust,
_ => return Err(JsValue::from_str("Invalid algorithm")),
};
let public_key = SigPublicKey::new(public_key.to_vec());
let message = message.to_vec();
let signature = signature.to_vec();
let is_valid = match self.verify_for_algorithm(algorithm, &public_key, &message, &signature)
{
Ok(valid) => valid,
Err(e) => return Err(JsValue::from_str(&e.to_string())),
};
Ok(is_valid)
}
}
#[cfg(feature = "wasm")]
#[wasm_bindgen]
pub struct WasmSlhDsaKeyPair {
public_key: Uint8Array,
secret_key: Uint8Array,
}
#[cfg(feature = "wasm")]
#[wasm_bindgen]
impl WasmSlhDsaKeyPair {
#[wasm_bindgen(constructor)]
pub fn new(public_key: Uint8Array, secret_key: Uint8Array) -> WasmSlhDsaKeyPair {
WasmSlhDsaKeyPair {
public_key,
secret_key,
}
}
#[wasm_bindgen(getter)]
pub fn public_key(&self) -> Uint8Array {
self.public_key.clone()
}
#[wasm_bindgen(getter)]
pub fn secret_key(&self) -> Uint8Array {
self.secret_key.clone()
}
}