use std::fmt;
use std::io;
use bech32::{ToBase32, Variant};
use oqs::sig::{Algorithm, Sig};
use zeroize::{Zeroize, Zeroizing};
use crate::util::parse_bech32;
const SECRET_KEY_PREFIX: &str = "ANUBIS-MLDSA-87-SECRET";
const PUBLIC_KEY_PREFIX: &str = "anubis1mldsa87";
pub const MLDSA87_PUBLIC_KEY_BYTES: usize = 2592;
pub const MLDSA87_SECRET_KEY_BYTES: usize = 4896;
pub const MLDSA87_SIGNATURE_BYTES: usize = 4627;
fn mldsa87() -> Sig {
oqs::init();
Sig::new(Algorithm::MlDsa87).expect("ML-DSA-87 algorithm available")
}
fn invalid_data(msg: &str) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, msg)
}
#[derive(Clone)]
pub struct SigningKey {
secret_key: Zeroizing<Vec<u8>>,
public_key: Vec<u8>,
}
impl SigningKey {
pub fn generate() -> Self {
let sig = mldsa87();
let (pk, sk) = sig.keypair().expect("ML-DSA keypair");
Self {
secret_key: Zeroizing::new(sk.as_ref().to_vec()),
public_key: pk.as_ref().to_vec(),
}
}
pub fn to_string(&self) -> String {
let mut material = Vec::with_capacity(MLDSA87_SECRET_KEY_BYTES + MLDSA87_PUBLIC_KEY_BYTES);
material.extend_from_slice(self.secret_key.as_ref());
material.extend_from_slice(&self.public_key);
let encoded = bech32::encode(SECRET_KEY_PREFIX, material.to_base32(), Variant::Bech32)
.expect("valid HRP");
material.zeroize();
encoded.to_uppercase()
}
pub fn to_public(&self) -> VerificationKey {
VerificationKey {
public_key: self.public_key.clone(),
}
}
pub fn sign(&self, message: &[u8]) -> Result<Signature, io::Error> {
let sig = mldsa87();
let sk = sig
.secret_key_from_bytes(self.secret_key.as_ref())
.ok_or_else(|| invalid_data("invalid ML-DSA secret key"))?;
let signature = sig
.sign(message, sk)
.map_err(|_| invalid_data("failed to sign message"))?;
Ok(Signature {
signature: signature.as_ref().to_vec(),
})
}
}
impl std::str::FromStr for SigningKey {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (hrp, bytes) = parse_bech32(s).ok_or("invalid Bech32 encoding")?;
if !hrp.eq_ignore_ascii_case(SECRET_KEY_PREFIX) {
return Err("incorrect HRP");
}
if bytes.len() != MLDSA87_SECRET_KEY_BYTES + MLDSA87_PUBLIC_KEY_BYTES {
return Err("incorrect signing key length");
}
let secret_key = Zeroizing::new(bytes[..MLDSA87_SECRET_KEY_BYTES].to_vec());
let public_key = bytes[MLDSA87_SECRET_KEY_BYTES..].to_vec();
Ok(Self {
secret_key,
public_key,
})
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct VerificationKey {
public_key: Vec<u8>,
}
impl VerificationKey {
fn ensure_length(bytes: &[u8]) -> Result<(), &'static str> {
if bytes.len() == MLDSA87_PUBLIC_KEY_BYTES {
Ok(())
} else {
Err("incorrect verification key length")
}
}
pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), io::Error> {
let sig = mldsa87();
let pk = sig
.public_key_from_bytes(&self.public_key)
.ok_or_else(|| invalid_data("invalid ML-DSA public key"))?;
let sig_bytes = sig
.signature_from_bytes(&signature.signature)
.ok_or_else(|| invalid_data("invalid ML-DSA signature"))?;
sig.verify(message, sig_bytes, pk)
.map_err(|_| invalid_data("signature verification failed"))
}
}
impl fmt::Display for VerificationKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
bech32::encode(
PUBLIC_KEY_PREFIX,
self.public_key.to_base32(),
Variant::Bech32
)
.expect("valid HRP")
)
}
}
impl fmt::Debug for VerificationKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self)
}
}
impl std::str::FromStr for VerificationKey {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (hrp, bytes) = parse_bech32(s).ok_or("invalid Bech32 encoding")?;
if !hrp.eq_ignore_ascii_case(PUBLIC_KEY_PREFIX) {
return Err("incorrect HRP");
}
Self::ensure_length(&bytes)?;
Ok(Self { public_key: bytes })
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct Signature {
signature: Vec<u8>,
}
impl Signature {
pub fn as_bytes(&self) -> &[u8] {
&self.signature
}
pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, &'static str> {
if bytes.len() == MLDSA87_SIGNATURE_BYTES {
Ok(Self { signature: bytes })
} else {
Err("incorrect signature length")
}
}
}
impl fmt::Debug for Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Signature({} bytes)", self.signature.len())
}
}
#[cfg(test)]
mod tests {
use super::{SigningKey, VerificationKey};
#[test]
fn sign_and_verify() {
let signing_key = SigningKey::generate();
let verification_key = signing_key.to_public();
let message = b"Test message for ML-DSA-87 signature";
let signature = signing_key.sign(message).unwrap();
assert!(verification_key.verify(message, &signature).is_ok());
let wrong_message = b"Different message";
assert!(verification_key.verify(wrong_message, &signature).is_err());
}
#[test]
fn bech32_round_trip() {
let signing_key = SigningKey::generate();
println!("Secret key size: {}", signing_key.secret_key.len());
println!("Public key size: {}", signing_key.public_key.len());
println!(
"Total size: {}",
signing_key.secret_key.len() + signing_key.public_key.len()
);
println!(
"Expected total: {}",
super::MLDSA87_SECRET_KEY_BYTES + super::MLDSA87_PUBLIC_KEY_BYTES
);
let encoded = signing_key.to_string();
let reparsed: SigningKey = encoded.parse().unwrap();
assert_eq!(signing_key.public_key, reparsed.public_key);
let verification_key = signing_key.to_public();
let encoded_vk = verification_key.to_string();
let reparsed_vk: VerificationKey = encoded_vk.parse().unwrap();
assert_eq!(verification_key, reparsed_vk);
}
#[test]
fn signature_size() {
let signing_key = SigningKey::generate();
let message = b"Test";
let signature = signing_key.sign(message).unwrap();
assert_eq!(signature.as_bytes().len(), super::MLDSA87_SIGNATURE_BYTES);
}
}