use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use super::Certificate;
#[derive(Debug)]
pub struct SignatureError {
inner: String,
}
impl core::fmt::Display for SignatureError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Fix: ed25519 signature failed to verify: {}", self.inner)
}
}
impl std::error::Error for SignatureError {}
#[inline]
pub fn canonical_bytes(cert: &Certificate) -> Result<Vec<u8>, serde_json::Error> {
serde_json::to_vec(cert)
}
#[inline]
pub fn sign(cert: &Certificate, signing_key: &SigningKey) -> Result<Signature, serde_json::Error> {
let bytes = canonical_bytes(cert)?;
Ok(signing_key.sign(&bytes))
}
#[inline]
pub fn verify(
cert: &Certificate,
signature: &Signature,
verifying_key: &VerifyingKey,
) -> Result<(), SignatureError> {
let bytes = canonical_bytes(cert).map_err(|err| SignatureError {
inner: err.to_string(),
})?;
verifying_key
.verify(&bytes, signature)
.map_err(|err| SignatureError {
inner: err.to_string(),
})
}
#[cfg(test)]
mod tests {
use super::*;
use ed25519_dalek::SigningKey;
use std::collections::BTreeMap;
fn test_signing_key() -> SigningKey {
SigningKey::from_bytes(&[7u8; 32])
}
fn fixture(backend_name: &str) -> Certificate {
use super::super::{CertificateLevels, CertificateStrength, CoverageMetrics, TrackReport};
let levels = CertificateLevels {
integer: None,
float: None,
approximate: None,
};
let track = TrackReport {
level: None,
ops: Vec::new(),
unsupported_ops: Vec::new(),
coverage: CoverageMetrics::default(),
};
Certificate::new(
backend_name.to_string(),
"ed25519-test".to_string(),
1,
levels,
"2026-04-17T00:00:00Z".to_string(),
0,
CertificateStrength::FastCheck,
0,
BTreeMap::new(),
"EXPLORATORY -- NOT A PROOF".to_string(),
track,
None,
None,
Vec::new(),
Vec::new(),
Vec::new(),
[0u8; 32],
CoverageMetrics::default(),
)
}
#[test]
fn sign_then_verify_round_trips() {
let cert = fixture("ed25519-mirror");
let key = test_signing_key();
let signature = sign(&cert, &key).expect("signing must succeed");
verify(&cert, &signature, &key.verifying_key()).expect("honest signature must verify");
}
#[test]
fn verify_rejects_tampered_certificate() {
let original = fixture("ed25519-mirror");
let key = test_signing_key();
let signature = sign(&original, &key).expect("signing must succeed");
let tampered = fixture("ed25519-tampered");
let err = verify(&tampered, &signature, &key.verifying_key())
.expect_err("signature from original cert must NOT verify the tampered one");
assert!(
err.to_string().to_lowercase().contains("signature"),
"error must name the verification failure, got: {err}"
);
}
#[test]
fn canonical_bytes_are_deterministic() {
let cert = fixture("ed25519-mirror");
let a = canonical_bytes(&cert).expect("serialize a");
let b = canonical_bytes(&cert).expect("serialize b");
assert_eq!(a, b, "canonical serialization must be deterministic");
}
}