use ed25519_dalek::{SigningKey, VerifyingKey, Signer as DalekSigner};
use rand::rngs::OsRng;
pub trait Signer: Send + Sync {
fn sign(&self, pae: &[u8]) -> Result<Vec<u8>, SignerError>;
fn key_id(&self) -> &str;
fn public_key_bytes(&self) -> Vec<u8>;
}
#[derive(Debug)]
pub struct SignerError(pub String);
impl std::fmt::Display for SignerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "signer error: {}", self.0)
}
}
impl std::error::Error for SignerError {}
pub struct Ed25519Signer {
key_id: String,
signing_key: SigningKey,
}
impl Ed25519Signer {
pub fn from_bytes(key_id: impl Into<String>, bytes: &[u8; 32]) -> Result<Self, SignerError> {
let key_id = key_id.into();
if key_id.is_empty() {
return Err(SignerError("key_id must not be empty".into()));
}
let signing_key = SigningKey::from_bytes(bytes);
Ok(Self { key_id, signing_key })
}
pub fn generate(key_id: impl Into<String>) -> Result<Self, SignerError> {
let key_id = key_id.into();
if key_id.is_empty() {
return Err(SignerError("key_id must not be empty".into()));
}
let signing_key = SigningKey::generate(&mut OsRng);
Ok(Self { key_id, signing_key })
}
pub fn verifying_key(&self) -> VerifyingKey {
self.signing_key.verifying_key()
}
pub fn secret_bytes(&self) -> [u8; 32] {
self.signing_key.to_bytes()
}
}
impl Signer for Ed25519Signer {
fn sign(&self, pae: &[u8]) -> Result<Vec<u8>, SignerError> {
let signature = self.signing_key.sign(pae);
Ok(signature.to_bytes().to_vec())
}
fn key_id(&self) -> &str {
&self.key_id
}
fn public_key_bytes(&self) -> Vec<u8> {
self.signing_key.verifying_key().to_bytes().to_vec()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::attestation::pae;
fn test_pae() -> Vec<u8> {
pae("application/vnd.treeship.action.v1+json", b"{\"actor\":\"agent://test\"}")
}
#[test]
fn generate_succeeds() {
let s = Ed25519Signer::generate("key_test_01").unwrap();
assert_eq!(s.key_id(), "key_test_01");
assert_eq!(s.public_key_bytes().len(), 32);
}
#[test]
fn empty_key_id_errors() {
assert!(Ed25519Signer::generate("").is_err());
}
#[test]
fn sign_produces_64_bytes() {
let signer = Ed25519Signer::generate("key_test").unwrap();
let sig = signer.sign(&test_pae()).unwrap();
assert_eq!(sig.len(), 64, "Ed25519 signatures are always 64 bytes");
}
#[test]
fn sign_is_deterministic_for_same_key_and_message() {
let signer = Ed25519Signer::generate("key_det").unwrap();
let msg = test_pae();
let sig1 = signer.sign(&msg).unwrap();
let sig2 = signer.sign(&msg).unwrap();
assert_eq!(sig1, sig2, "Ed25519 signing must be deterministic");
}
#[test]
fn different_keys_produce_different_signatures() {
let s1 = Ed25519Signer::generate("key_1").unwrap();
let s2 = Ed25519Signer::generate("key_2").unwrap();
let msg = test_pae();
assert_ne!(
s1.sign(&msg).unwrap(),
s2.sign(&msg).unwrap(),
"Different keys must produce different signatures"
);
}
#[test]
fn different_messages_produce_different_signatures() {
let signer = Ed25519Signer::generate("key_test").unwrap();
let pae1 = pae("application/vnd.treeship.action.v1+json", b"{\"a\":1}");
let pae2 = pae("application/vnd.treeship.approval.v1+json", b"{\"a\":1}");
assert_ne!(
signer.sign(&pae1).unwrap(),
signer.sign(&pae2).unwrap()
);
}
#[test]
fn roundtrip_from_bytes() {
let original = Ed25519Signer::generate("key_rt").unwrap();
let secret = original.secret_bytes();
let restored = Ed25519Signer::from_bytes("key_rt", &secret).unwrap();
assert_eq!(original.public_key_bytes(), restored.public_key_bytes());
let msg = test_pae();
let sig_a = original.sign(&msg).unwrap();
let sig_b = restored.sign(&msg).unwrap();
assert_eq!(sig_a, sig_b, "Restored key must produce identical signatures");
}
}