treeship_core/attestation/
signer.rs1use ed25519_dalek::{SigningKey, VerifyingKey, Signer as DalekSigner};
2use rand::rngs::OsRng;
3
4pub trait Signer: Send + Sync {
13 fn sign(&self, pae: &[u8]) -> Result<Vec<u8>, SignerError>;
16
17 fn key_id(&self) -> &str;
19
20 fn public_key_bytes(&self) -> Vec<u8>;
23}
24
25#[derive(Debug)]
27pub struct SignerError(pub String);
28
29impl std::fmt::Display for SignerError {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 write!(f, "signer error: {}", self.0)
32 }
33}
34
35impl std::error::Error for SignerError {}
36
37pub struct Ed25519Signer {
46 key_id: String,
47 signing_key: SigningKey,
48}
49
50impl Ed25519Signer {
51 pub fn from_bytes(key_id: impl Into<String>, bytes: &[u8; 32]) -> Result<Self, SignerError> {
53 let key_id = key_id.into();
54 if key_id.is_empty() {
55 return Err(SignerError("key_id must not be empty".into()));
56 }
57 let signing_key = SigningKey::from_bytes(bytes);
58 Ok(Self { key_id, signing_key })
59 }
60
61 pub fn generate(key_id: impl Into<String>) -> Result<Self, SignerError> {
66 let key_id = key_id.into();
67 if key_id.is_empty() {
68 return Err(SignerError("key_id must not be empty".into()));
69 }
70 let signing_key = SigningKey::generate(&mut OsRng);
71 Ok(Self { key_id, signing_key })
72 }
73
74 pub fn verifying_key(&self) -> VerifyingKey {
76 self.signing_key.verifying_key()
77 }
78
79 pub fn secret_bytes(&self) -> [u8; 32] {
82 self.signing_key.to_bytes()
83 }
84}
85
86impl Signer for Ed25519Signer {
87 fn sign(&self, pae: &[u8]) -> Result<Vec<u8>, SignerError> {
88 let signature = self.signing_key.sign(pae);
93 Ok(signature.to_bytes().to_vec())
94 }
95
96 fn key_id(&self) -> &str {
97 &self.key_id
98 }
99
100 fn public_key_bytes(&self) -> Vec<u8> {
101 self.signing_key.verifying_key().to_bytes().to_vec()
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::attestation::pae;
109
110 fn test_pae() -> Vec<u8> {
111 pae("application/vnd.treeship.action.v1+json", b"{\"actor\":\"agent://test\"}")
112 }
113
114 #[test]
115 fn generate_succeeds() {
116 let s = Ed25519Signer::generate("key_test_01").unwrap();
117 assert_eq!(s.key_id(), "key_test_01");
118 assert_eq!(s.public_key_bytes().len(), 32);
119 }
120
121 #[test]
122 fn empty_key_id_errors() {
123 assert!(Ed25519Signer::generate("").is_err());
124 }
125
126 #[test]
127 fn sign_produces_64_bytes() {
128 let signer = Ed25519Signer::generate("key_test").unwrap();
129 let sig = signer.sign(&test_pae()).unwrap();
130 assert_eq!(sig.len(), 64, "Ed25519 signatures are always 64 bytes");
131 }
132
133 #[test]
134 fn sign_is_deterministic_for_same_key_and_message() {
135 let signer = Ed25519Signer::generate("key_det").unwrap();
139 let msg = test_pae();
140 let sig1 = signer.sign(&msg).unwrap();
141 let sig2 = signer.sign(&msg).unwrap();
142 assert_eq!(sig1, sig2, "Ed25519 signing must be deterministic");
143 }
144
145 #[test]
146 fn different_keys_produce_different_signatures() {
147 let s1 = Ed25519Signer::generate("key_1").unwrap();
148 let s2 = Ed25519Signer::generate("key_2").unwrap();
149 let msg = test_pae();
150 assert_ne!(
151 s1.sign(&msg).unwrap(),
152 s2.sign(&msg).unwrap(),
153 "Different keys must produce different signatures"
154 );
155 }
156
157 #[test]
158 fn different_messages_produce_different_signatures() {
159 let signer = Ed25519Signer::generate("key_test").unwrap();
160 let pae1 = pae("application/vnd.treeship.action.v1+json", b"{\"a\":1}");
161 let pae2 = pae("application/vnd.treeship.approval.v1+json", b"{\"a\":1}");
162 assert_ne!(
163 signer.sign(&pae1).unwrap(),
164 signer.sign(&pae2).unwrap()
165 );
166 }
167
168 #[test]
169 fn roundtrip_from_bytes() {
170 let original = Ed25519Signer::generate("key_rt").unwrap();
171 let secret = original.secret_bytes();
172 let restored = Ed25519Signer::from_bytes("key_rt", &secret).unwrap();
173
174 assert_eq!(original.public_key_bytes(), restored.public_key_bytes());
175
176 let msg = test_pae();
177 let sig_a = original.sign(&msg).unwrap();
178 let sig_b = restored.sign(&msg).unwrap();
179 assert_eq!(sig_a, sig_b, "Restored key must produce identical signatures");
180 }
181}