1use ed25519_dalek::{Signature, Signer as DalekSigner, SigningKey, Verifier, VerifyingKey};
5use std::collections::HashMap;
6
7pub trait Signer: Send + Sync {
9 fn key_id(&self) -> &str;
11 fn sign(&self, msg: &[u8]) -> [u8; 64];
13 fn public_key_bytes(&self) -> [u8; 32];
15}
16
17pub struct Ed25519Signer {
19 key_id: String,
20 key: SigningKey,
21}
22
23impl std::fmt::Debug for Ed25519Signer {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 f.debug_struct("Ed25519Signer")
27 .field("key_id", &self.key_id)
28 .finish_non_exhaustive()
29 }
30}
31
32impl Ed25519Signer {
33 pub fn generate() -> Self {
46 loop {
47 let key = SigningKey::generate(&mut rand::rngs::OsRng);
48 let bytes = zeroize::Zeroizing::new(key.to_bytes());
56 if *bytes == [0u8; 32] || *bytes == [0xFFu8; 32] {
57 continue;
58 }
59 let key_id = derive_key_id(&key.verifying_key());
60 return Self { key_id, key };
61 }
62 }
63
64 pub fn from_seed(seed: &[u8; 32]) -> Self {
74 let key = SigningKey::from_bytes(seed);
75 let key_id = derive_key_id(&key.verifying_key());
76 Self { key_id, key }
77 }
78
79 pub fn seed(&self) -> zeroize::Zeroizing<[u8; 32]> {
85 zeroize::Zeroizing::new(self.key.to_bytes())
86 }
87}
88
89fn derive_key_id(vk: &VerifyingKey) -> String {
94 let digest = av_core::digest::sha256_hex(vk.as_bytes());
95 digest.chars().take(32).collect()
96}
97
98impl Signer for Ed25519Signer {
99 fn key_id(&self) -> &str {
100 &self.key_id
101 }
102
103 fn sign(&self, msg: &[u8]) -> [u8; 64] {
104 self.key.sign(msg).to_bytes()
105 }
106
107 fn public_key_bytes(&self) -> [u8; 32] {
108 self.key.verifying_key().to_bytes()
109 }
110}
111
112#[derive(Debug, Default, Clone)]
115pub struct Keyring {
116 keys: HashMap<String, VerifyingKey>,
117}
118
119#[derive(Debug, thiserror::Error)]
121#[non_exhaustive]
122pub enum KeyError {
123 #[error("invalid public key: {0}")]
125 InvalidKey(String),
126 #[error("invalid signature encoding")]
128 InvalidSignature,
129 #[error("unknown key id {0:?}")]
131 UnknownKeyId(String),
132 #[error("signature verification failed for key id {0:?}")]
134 BadSignature(String),
135 #[error("key id {0:?} is already registered to a different public key")]
137 KeyMismatch(String),
138}
139
140impl Keyring {
141 pub fn new() -> Self {
143 Self::default()
144 }
145
146 pub fn add_key_bytes(&mut self, bytes: &[u8; 32]) -> Result<String, KeyError> {
153 let vk = VerifyingKey::from_bytes(bytes).map_err(|e| KeyError::InvalidKey(e.to_string()))?;
154 let id = derive_key_id(&vk);
155 if let Some(existing) = self.keys.get(&id) {
156 if existing.as_bytes() != vk.as_bytes() {
157 return Err(KeyError::KeyMismatch(id));
158 }
159 return Ok(id);
160 }
161 self.keys.insert(id.clone(), vk);
162 Ok(id)
163 }
164
165 pub fn add_signer(&mut self, signer: &dyn Signer) -> Result<String, KeyError> {
167 self.add_key_bytes(&signer.public_key_bytes())
168 }
169
170 pub fn verify(&self, key_id: &str, msg: &[u8], sig: &[u8]) -> Result<(), KeyError> {
172 let vk = self
173 .keys
174 .get(key_id)
175 .ok_or_else(|| KeyError::UnknownKeyId(key_id.to_owned()))?;
176 let sig_bytes: [u8; 64] = sig.try_into().map_err(|_| KeyError::InvalidSignature)?;
177 let signature = Signature::from_bytes(&sig_bytes);
178 vk.verify(msg, &signature)
179 .map_err(|_| KeyError::BadSignature(key_id.to_owned()))
180 }
181
182 pub fn len(&self) -> usize {
184 self.keys.len()
185 }
186
187 pub fn is_empty(&self) -> bool {
189 self.keys.is_empty()
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
196
197 use super::*;
198
199 #[test]
200 fn sign_verify_roundtrip() {
201 let signer = Ed25519Signer::generate();
202 let mut ring = Keyring::new();
203 let id = ring.add_signer(&signer).unwrap();
204 assert_eq!(id, signer.key_id());
205 let sig = signer.sign(b"hello");
206 ring.verify(&id, b"hello", &sig).unwrap();
207 }
208
209 #[test]
210 fn wrong_message_fails() {
211 let signer = Ed25519Signer::generate();
212 let mut ring = Keyring::new();
213 let id = ring.add_signer(&signer).unwrap();
214 let sig = signer.sign(b"hello");
215 assert!(matches!(
216 ring.verify(&id, b"HELLO", &sig),
217 Err(KeyError::BadSignature(_))
218 ));
219 }
220
221 #[test]
222 fn wrong_key_fails() {
223 let a = Ed25519Signer::generate();
224 let b = Ed25519Signer::generate();
225 let mut ring = Keyring::new();
226 ring.add_signer(&b).unwrap();
227 let sig = a.sign(b"msg");
228 assert!(matches!(
230 ring.verify(a.key_id(), b"msg", &sig),
231 Err(KeyError::UnknownKeyId(_))
232 ));
233 }
234
235 #[test]
236 fn truncated_signature_rejected() {
237 let signer = Ed25519Signer::generate();
238 let mut ring = Keyring::new();
239 let id = ring.add_signer(&signer).unwrap();
240 let sig = signer.sign(b"msg");
241 assert!(matches!(
242 ring.verify(&id, b"msg", &sig[..63]),
243 Err(KeyError::InvalidSignature)
244 ));
245 }
246
247 #[test]
248 fn seed_roundtrip_preserves_identity() {
249 let a = Ed25519Signer::generate();
250 let b = Ed25519Signer::from_seed(&a.seed());
251 assert_eq!(a.key_id(), b.key_id());
252 assert_eq!(a.public_key_bytes(), b.public_key_bytes());
253 }
254
255 #[test]
256 fn rotation_keeps_old_receipts_verifiable() {
257 let old = Ed25519Signer::generate();
258 let new = Ed25519Signer::generate();
259 let mut ring = Keyring::new();
260 ring.add_signer(&old).unwrap();
261 ring.add_signer(&new).unwrap();
262 let old_sig = old.sign(b"old receipt");
263 let new_sig = new.sign(b"new receipt");
264 ring.verify(old.key_id(), b"old receipt", &old_sig).unwrap();
265 ring.verify(new.key_id(), b"new receipt", &new_sig).unwrap();
266 assert_eq!(ring.len(), 2);
267 }
268
269 #[test]
270 fn debug_never_leaks_secret() {
271 let signer = Ed25519Signer::generate();
272 let dbg = format!("{signer:?}");
273 let seed_hex = hex::encode(signer.seed());
274 assert!(!dbg.contains(&seed_hex), "Debug output leaked the seed");
275 assert!(dbg.contains(signer.key_id()));
277 }
278
279 #[test]
280 fn keyring_is_empty_reflects_the_ring_state() {
281 let mut ring = Keyring::new();
282 assert!(ring.is_empty(), "fresh ring must be empty");
283 assert_eq!(ring.len(), 0);
284 let s = Ed25519Signer::generate();
285 ring.add_signer(&s).unwrap();
286 assert!(!ring.is_empty(), "ring with one key must not be empty");
287 assert_eq!(ring.len(), 1);
288 }
289
290 #[test]
291 fn key_id_is_128_bits_wide() {
292 let signer = Ed25519Signer::generate();
293 assert_eq!(
294 signer.key_id().len(),
295 32,
296 "key_id must be 32 hex chars (128 bits) so birthday collisions cost ~2^64"
297 );
298 }
299
300 #[test]
301 fn keyring_refuses_to_overwrite_colliding_id_with_different_key() {
302 let mut ring = Keyring::new();
303 let honest = Ed25519Signer::generate();
304 let id = ring.add_signer(&honest).unwrap();
305 let attacker = Ed25519Signer::generate();
306 assert_ne!(honest.public_key_bytes(), attacker.public_key_bytes());
307 let attacker_vk = VerifyingKey::from_bytes(&attacker.public_key_bytes()).unwrap();
312 let mut fresh = Keyring::new();
313 fresh.keys.insert(id.clone(), attacker_vk);
314 let err = fresh
315 .add_key_bytes(&honest.public_key_bytes())
316 .expect_err("must refuse overwrite of a colliding id");
317 assert!(matches!(err, KeyError::KeyMismatch(_)), "got {err:?}");
318 }
319
320 #[test]
321 fn keyring_add_is_idempotent_for_the_same_key() {
322 let mut ring = Keyring::new();
323 let signer = Ed25519Signer::generate();
324 let id1 = ring.add_signer(&signer).unwrap();
325 let id2 = ring.add_signer(&signer).unwrap();
326 assert_eq!(id1, id2);
327 assert_eq!(ring.len(), 1);
328 }
329}