use ml_dsa::{
Generate, Keypair as _, MlDsa65, Signature as MlSignature, Signer as _, SigningKey,
Verifier as _, VerifyingKey,
};
use crate::core::operation::cryptography::pqc as contracts;
use crate::{Error, Hash, Result};
pub const PUBLIC_KEY_SIZE: usize = contracts::ML_DSA_65_PUBLIC_KEY_SIZE;
pub const SECRET_KEY_SIZE: usize = 4_032;
pub const SIGNATURE_SIZE: usize = contracts::ML_DSA_65_SIGNATURE_SIZE;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MlDsaParameters {
MlDsa44,
MlDsa65,
MlDsa87,
}
impl MlDsaParameters {
#[must_use]
pub const fn public_key_size(self) -> usize {
match self {
Self::MlDsa44 => contracts::ML_DSA_44_PUBLIC_KEY_SIZE,
Self::MlDsa65 => PUBLIC_KEY_SIZE,
Self::MlDsa87 => contracts::ML_DSA_87_PUBLIC_KEY_SIZE,
}
}
#[must_use]
pub const fn signature_size(self) -> usize {
match self {
Self::MlDsa44 => contracts::ML_DSA_44_SIGNATURE_SIZE,
Self::MlDsa65 => SIGNATURE_SIZE,
Self::MlDsa87 => contracts::ML_DSA_87_SIGNATURE_SIZE,
}
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct PublicKey([u8; PUBLIC_KEY_SIZE]);
impl Default for PublicKey {
fn default() -> Self {
Self([0; PUBLIC_KEY_SIZE])
}
}
impl PublicKey {
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
Ok(Self(copy_array(bytes, "ML-DSA-65 public key")?))
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8; PUBLIC_KEY_SIZE] {
&self.0
}
#[must_use]
pub fn to_bytes(&self) -> [u8; PUBLIC_KEY_SIZE] {
self.0
}
#[must_use]
pub fn is_zero(&self) -> bool {
self.0.iter().all(|byte| *byte == 0)
}
#[must_use]
pub fn to_short_hex(&self) -> String {
short_hex(&self.0)
}
}
impl std::fmt::Debug for PublicKey {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_tuple("PublicKey")
.field(&self.to_short_hex())
.finish()
}
}
pub struct SecretKey(SigningKey<MlDsa65>);
impl std::fmt::Debug for SecretKey {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.debug_struct("SecretKey").finish_non_exhaustive()
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct Signature([u8; SIGNATURE_SIZE]);
impl Default for Signature {
fn default() -> Self {
Self([0; SIGNATURE_SIZE])
}
}
impl Signature {
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
Ok(Self(copy_array(bytes, "ML-DSA-65 signature")?))
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8; SIGNATURE_SIZE] {
&self.0
}
#[must_use]
pub fn to_bytes(&self) -> [u8; SIGNATURE_SIZE] {
self.0
}
#[must_use]
pub fn is_zero(&self) -> bool {
self.0.iter().all(|byte| *byte == 0)
}
#[must_use]
pub fn to_short_hex(&self) -> String {
short_hex(&self.0)
}
}
impl std::fmt::Debug for Signature {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_tuple("Signature")
.field(&self.to_short_hex())
.finish()
}
}
pub struct Keypair {
pub public_key: PublicKey,
pub secret_key: SecretKey,
}
impl std::fmt::Debug for Keypair {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("Keypair")
.field("public_key", &self.public_key)
.field("secret_key", &self.secret_key)
.finish()
}
}
pub fn generate_keypair() -> Result<Keypair> {
let signing = SigningKey::<MlDsa65>::try_generate()
.map_err(|_| Error::internal("operating-system random generation failed"))?;
let encoded = signing.verifying_key().encode();
let mut public_key = [0_u8; PUBLIC_KEY_SIZE];
public_key.copy_from_slice(encoded.as_slice());
Ok(Keypair {
public_key: PublicKey(public_key),
secret_key: SecretKey(signing),
})
}
pub fn sign(message: &[u8], secret_key: &SecretKey) -> Result<Signature> {
let signature = secret_key
.0
.try_sign(message)
.map_err(|_| Error::internal("ML-DSA-65 signing failed"))?;
let encoded = signature.encode();
let mut bytes = [0_u8; SIGNATURE_SIZE];
bytes.copy_from_slice(encoded.as_slice());
Ok(Signature(bytes))
}
pub fn sign_hash(hash: &Hash, secret_key: &SecretKey) -> Result<Signature> {
sign(hash.as_bytes(), secret_key)
}
#[must_use]
pub fn verify(message: &[u8], signature: &Signature, public_key: &PublicKey) -> bool {
let Some(signature) = MlSignature::<MlDsa65>::decode((&signature.0).into()) else {
return false;
};
let verifying_key = VerifyingKey::<MlDsa65>::decode((&public_key.0).into());
verifying_key.verify(message, &signature).is_ok()
}
#[must_use]
pub fn verify_hash(hash: &Hash, signature: &Signature, public_key: &PublicKey) -> bool {
verify(hash.as_bytes(), signature, public_key)
}
fn copy_array<const N: usize>(bytes: &[u8], label: &str) -> Result<[u8; N]> {
<[u8; N]>::try_from(bytes)
.map_err(|_| Error::invalid_argument(format!("{label} must contain exactly {N} bytes")))
}
fn short_hex(bytes: &[u8]) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut output = String::with_capacity(32);
for byte in bytes.iter().take(16) {
output.push(char::from(HEX[usize::from(byte >> 4)]));
output.push(char::from(HEX[usize::from(byte & 0x0f)]));
}
output
}