1use ed25519_dalek::{Signature, Signer as DalekSigner, SigningKey, 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 use rand::TryRng;
47 loop {
48 let mut bytes = zeroize::Zeroizing::new([0u8; 32]);
58 #[allow(clippy::expect_used)]
62 rand::rngs::SysRng
63 .try_fill_bytes(&mut *bytes)
64 .expect("OS random source unavailable");
65 if *bytes == [0u8; 32] || *bytes == [0xFFu8; 32] {
66 continue;
67 }
68 let key = SigningKey::from_bytes(&bytes);
69 let key_id = derive_key_id(&key.verifying_key());
70 return Self { key_id, key };
71 }
72 }
73
74 pub fn from_seed(seed: &[u8; 32]) -> Self {
96 let key = SigningKey::from_bytes(seed);
97 let key_id = derive_key_id(&key.verifying_key());
98 Self { key_id, key }
99 }
100
101 pub fn seed(&self) -> zeroize::Zeroizing<[u8; 32]> {
107 zeroize::Zeroizing::new(self.key.to_bytes())
108 }
109}
110
111fn derive_key_id(vk: &VerifyingKey) -> String {
116 let digest = av_core::digest::sha256_hex(vk.as_bytes());
117 digest.chars().take(32).collect()
118}
119
120impl Signer for Ed25519Signer {
121 fn key_id(&self) -> &str {
122 &self.key_id
123 }
124
125 fn sign(&self, msg: &[u8]) -> [u8; 64] {
126 self.key.sign(msg).to_bytes()
127 }
128
129 fn public_key_bytes(&self) -> [u8; 32] {
130 self.key.verifying_key().to_bytes()
131 }
132}
133
134#[derive(Debug, Default, Clone)]
137pub struct Keyring {
138 keys: HashMap<String, VerifyingKey>,
139}
140
141#[derive(Debug, thiserror::Error)]
143#[non_exhaustive]
144pub enum KeyError {
145 #[error("invalid public key: {0}")]
147 InvalidKey(String),
148 #[error("public key is small-order (weak); refused to add to keyring")]
153 WeakKey,
154 #[error("invalid signature encoding")]
156 InvalidSignature,
157 #[error("unknown key id {0:?}")]
159 UnknownKeyId(String),
160 #[error("signature verification failed for key id {0:?}")]
162 BadSignature(String),
163 #[error("key id {0:?} is already registered to a different public key")]
165 KeyMismatch(String),
166}
167
168impl Keyring {
169 pub fn new() -> Self {
171 Self::default()
172 }
173
174 pub fn add_key_bytes(&mut self, bytes: &[u8; 32]) -> Result<String, KeyError> {
189 let vk = VerifyingKey::from_bytes(bytes).map_err(|e| KeyError::InvalidKey(e.to_string()))?;
190 if vk.is_weak() {
191 return Err(KeyError::WeakKey);
192 }
193 let id = derive_key_id(&vk);
194 if let Some(existing) = self.keys.get(&id) {
195 if existing.as_bytes() != vk.as_bytes() {
196 return Err(KeyError::KeyMismatch(id));
197 }
198 return Ok(id);
199 }
200 self.keys.insert(id.clone(), vk);
201 Ok(id)
202 }
203
204 pub fn add_signer(&mut self, signer: &dyn Signer) -> Result<String, KeyError> {
206 self.add_key_bytes(&signer.public_key_bytes())
207 }
208
209 pub fn verify(&self, key_id: &str, msg: &[u8], sig: &[u8]) -> Result<(), KeyError> {
220 let vk = self
221 .keys
222 .get(key_id)
223 .ok_or_else(|| KeyError::UnknownKeyId(key_id.to_owned()))?;
224 let sig_bytes: [u8; 64] = sig.try_into().map_err(|_| KeyError::InvalidSignature)?;
225 let signature = Signature::from_bytes(&sig_bytes);
226 vk.verify_strict(msg, &signature)
227 .map_err(|_| KeyError::BadSignature(key_id.to_owned()))
228 }
229
230 pub fn len(&self) -> usize {
232 self.keys.len()
233 }
234
235 pub fn is_empty(&self) -> bool {
237 self.keys.is_empty()
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
244
245 use super::*;
246
247 #[test]
248 fn sign_verify_roundtrip() {
249 let signer = Ed25519Signer::generate();
250 let mut ring = Keyring::new();
251 let id = ring.add_signer(&signer).unwrap();
252 assert_eq!(id, signer.key_id());
253 let sig = signer.sign(b"hello");
254 ring.verify(&id, b"hello", &sig).unwrap();
255 }
256
257 #[test]
258 fn wrong_message_fails() {
259 let signer = Ed25519Signer::generate();
260 let mut ring = Keyring::new();
261 let id = ring.add_signer(&signer).unwrap();
262 let sig = signer.sign(b"hello");
263 assert!(matches!(
264 ring.verify(&id, b"HELLO", &sig),
265 Err(KeyError::BadSignature(_))
266 ));
267 }
268
269 #[test]
270 fn wrong_key_fails() {
271 let a = Ed25519Signer::generate();
272 let b = Ed25519Signer::generate();
273 let mut ring = Keyring::new();
274 ring.add_signer(&b).unwrap();
275 let sig = a.sign(b"msg");
276 assert!(matches!(
278 ring.verify(a.key_id(), b"msg", &sig),
279 Err(KeyError::UnknownKeyId(_))
280 ));
281 }
282
283 #[test]
284 fn truncated_signature_rejected() {
285 let signer = Ed25519Signer::generate();
286 let mut ring = Keyring::new();
287 let id = ring.add_signer(&signer).unwrap();
288 let sig = signer.sign(b"msg");
289 assert!(matches!(
290 ring.verify(&id, b"msg", &sig[..63]),
291 Err(KeyError::InvalidSignature)
292 ));
293 }
294
295 #[test]
296 fn seed_roundtrip_preserves_identity() {
297 let a = Ed25519Signer::generate();
298 let b = Ed25519Signer::from_seed(&a.seed());
299 assert_eq!(a.key_id(), b.key_id());
300 assert_eq!(a.public_key_bytes(), b.public_key_bytes());
301 }
302
303 #[test]
310 fn key_id_is_truncated_sha256_of_public_key() {
311 let signer = Ed25519Signer::from_seed(&[0x01; 32]);
312 assert_eq!(
313 hex::encode(signer.public_key_bytes()),
314 "8a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c"
315 );
316 assert_eq!(signer.key_id(), "34750f98bd59fcfc946da45aaabe933b");
317 let recomputed: String = av_core::digest::sha256_hex(&signer.public_key_bytes())
318 .chars()
319 .take(32)
320 .collect();
321 assert_eq!(signer.key_id(), recomputed);
322 }
323
324 #[test]
325 fn rotation_keeps_old_receipts_verifiable() {
326 let old = Ed25519Signer::generate();
327 let new = Ed25519Signer::generate();
328 let mut ring = Keyring::new();
329 ring.add_signer(&old).unwrap();
330 ring.add_signer(&new).unwrap();
331 let old_sig = old.sign(b"old receipt");
332 let new_sig = new.sign(b"new receipt");
333 ring.verify(old.key_id(), b"old receipt", &old_sig).unwrap();
334 ring.verify(new.key_id(), b"new receipt", &new_sig).unwrap();
335 assert_eq!(ring.len(), 2);
336 }
337
338 #[test]
339 fn debug_never_leaks_secret() {
340 let signer = Ed25519Signer::generate();
341 let dbg = format!("{signer:?}");
342 let seed_hex = hex::encode(signer.seed());
343 assert!(!dbg.contains(&seed_hex), "Debug output leaked the seed");
344 assert!(dbg.contains(signer.key_id()));
346 }
347
348 #[test]
349 fn keyring_is_empty_reflects_the_ring_state() {
350 let mut ring = Keyring::new();
351 assert!(ring.is_empty(), "fresh ring must be empty");
352 assert_eq!(ring.len(), 0);
353 let s = Ed25519Signer::generate();
354 ring.add_signer(&s).unwrap();
355 assert!(!ring.is_empty(), "ring with one key must not be empty");
356 assert_eq!(ring.len(), 1);
357 }
358
359 #[test]
360 fn key_id_is_128_bits_wide() {
361 let signer = Ed25519Signer::generate();
362 assert_eq!(
363 signer.key_id().len(),
364 32,
365 "key_id must be 32 hex chars (128 bits) so birthday collisions cost ~2^64"
366 );
367 }
368
369 #[test]
370 fn keyring_refuses_to_overwrite_colliding_id_with_different_key() {
371 let mut ring = Keyring::new();
372 let honest = Ed25519Signer::generate();
373 let id = ring.add_signer(&honest).unwrap();
374 let attacker = Ed25519Signer::generate();
375 assert_ne!(honest.public_key_bytes(), attacker.public_key_bytes());
376 let attacker_vk = VerifyingKey::from_bytes(&attacker.public_key_bytes()).unwrap();
381 let mut fresh = Keyring::new();
382 fresh.keys.insert(id.clone(), attacker_vk);
383 let err = fresh
384 .add_key_bytes(&honest.public_key_bytes())
385 .expect_err("must refuse overwrite of a colliding id");
386 assert!(matches!(err, KeyError::KeyMismatch(_)), "got {err:?}");
387 }
388
389 #[test]
390 fn keyring_add_is_idempotent_for_the_same_key() {
391 let mut ring = Keyring::new();
392 let signer = Ed25519Signer::generate();
393 let id1 = ring.add_signer(&signer).unwrap();
394 let id2 = ring.add_signer(&signer).unwrap();
395 assert_eq!(id1, id2);
396 assert_eq!(ring.len(), 1);
397 }
398}