use fips204::ml_dsa_65;
use fips204::traits::{SerDes, Signer, Verifier};
use crate::fence::EffectCert;
pub struct CertSigner {
sk: ml_dsa_65::PrivateKey,
pk: ml_dsa_65::PublicKey,
}
#[derive(Clone, PartialEq, Eq)]
pub struct CertSignature(pub Vec<u8>);
impl CertSigner {
pub fn generate() -> Result<Self, &'static str> {
let (pk, sk) = ml_dsa_65::try_keygen()?;
Ok(Self { sk, pk })
}
pub fn public_key_bytes(&self) -> Vec<u8> {
self.pk.clone().into_bytes().to_vec()
}
pub fn sign(&self, cert: &EffectCert) -> Result<CertSignature, &'static str> {
let sig = self.sk.try_sign(cert.hash.as_bytes(), &[])?;
Ok(CertSignature(sig.to_vec()))
}
}
pub fn verify(cert: &EffectCert, sig: &CertSignature, public_key_bytes: &[u8]) -> bool {
if !cert.verify() {
return false; }
let pk_arr: [u8; ml_dsa_65::PK_LEN] = match public_key_bytes.try_into() {
Ok(a) => a,
Err(_) => return false, };
let pk = match ml_dsa_65::PublicKey::try_from_bytes(pk_arr) {
Ok(pk) => pk,
Err(_) => return false,
};
let sig_arr: [u8; ml_dsa_65::SIG_LEN] = match sig.0.as_slice().try_into() {
Ok(a) => a,
Err(_) => return false, };
pk.verify(cert.hash.as_bytes(), &sig_arr, &[])
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fence::{
Admission, EffectFence, EffectRequest, VectorClock, commit_effect_cert,
prepare_effect_fence,
};
use serde_json::json;
fn one_cert() -> EffectCert {
let fence = EffectFence::new();
let req = EffectRequest {
intent: "charge:order-1".into(),
parent: None,
domain: "order:1".into(),
tool: "charge_card".into(),
args: json!({ "amount": 4900 }),
read_set: vec![],
agent: "agent-A".into(),
known_clock: VectorClock::new(),
};
match prepare_effect_fence(&fence, req).unwrap() {
Admission::Fresh(p) => commit_effect_cert(&fence, p, json!({"ok": true})).unwrap(),
_ => panic!("expected fresh"),
}
}
#[test]
fn genuine_signature_verifies() {
let signer = CertSigner::generate().unwrap();
let cert = one_cert();
let sig = signer.sign(&cert).unwrap();
assert!(verify(&cert, &sig, &signer.public_key_bytes()));
}
#[test]
fn a_different_key_does_not_verify() {
let signer = CertSigner::generate().unwrap();
let attacker = CertSigner::generate().unwrap();
let cert = one_cert();
let sig = signer.sign(&cert).unwrap();
assert!(!verify(&cert, &sig, &attacker.public_key_bytes()));
}
#[test]
fn tampering_with_the_cert_breaks_verification() {
let signer = CertSigner::generate().unwrap();
let mut cert = one_cert();
let sig = signer.sign(&cert).unwrap();
cert.result = json!({ "ok": false });
assert!(!verify(&cert, &sig, &signer.public_key_bytes()));
}
#[test]
fn a_garbage_signature_does_not_verify() {
let signer = CertSigner::generate().unwrap();
let cert = one_cert();
let bogus = CertSignature(vec![0u8; 3309]); assert!(!verify(&cert, &bogus, &signer.public_key_bytes()));
}
}