holographic_memory/core/provenance/
keri.rs1use anyhow::{anyhow, Result};
5use ed25519_dalek::{Signer, SigningKey, Verifier, VerifyingKey};
6use serde::{Deserialize, Serialize};
7use sha2::{Digest, Sha256};
8
9use super::did;
10
11#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14pub enum EventType {
15 Inception,
16 Rotation,
17 Interaction,
18}
19
20#[derive(Clone, Debug, Serialize, Deserialize)]
22pub struct KeyEvent {
23 #[serde(rename = "v")]
24 pub version: String,
25 #[serde(rename = "t")]
26 pub event_type: EventType,
27 #[serde(rename = "d")]
28 pub digest: String,
29 #[serde(rename = "i")]
30 pub identifier: String,
31 #[serde(rename = "s")]
32 pub sequence: u64,
33 #[serde(rename = "kt")]
34 pub key_threshold: u64,
35 #[serde(rename = "k")]
36 pub keys: Vec<String>,
37 #[serde(rename = "nt", skip_serializing_if = "Option::is_none")]
38 pub next_threshold: Option<u64>,
39 #[serde(rename = "n", skip_serializing_if = "Option::is_none")]
40 pub next_keys_digest: Option<String>,
41 #[serde(rename = "p", skip_serializing_if = "Option::is_none")]
42 pub prior_digest: Option<String>,
43 #[serde(rename = "a", default, skip_serializing_if = "Vec::is_empty")]
44 pub anchors: Vec<serde_json::Value>,
45 pub signature: String,
46}
47
48#[derive(Default)]
50pub struct KeyEventLog {
51 events: Vec<KeyEvent>,
52 path: Option<std::path::PathBuf>,
53}
54
55impl KeyEventLog {
56 pub fn new() -> Self {
57 Self {
58 events: Vec::new(),
59 path: None,
60 }
61 }
62
63 pub fn with_path(path: &std::path::Path) -> Result<Self> {
64 let events = if path.exists() {
65 let data =
66 std::fs::read_to_string(path).map_err(|e| anyhow!("failed to read KEL: {e}"))?;
67 serde_json::from_str(&data).map_err(|e| anyhow!("failed to parse KEL: {e}"))?
68 } else {
69 Vec::new()
70 };
71 Ok(Self {
72 events,
73 path: Some(path.to_path_buf()),
74 })
75 }
76
77 fn persist(&self) -> Result<()> {
78 if let Some(ref path) = self.path {
79 let data = serde_json::to_vec_pretty(&self.events)
80 .map_err(|e| anyhow!("KEL serialization failed: {e}"))?;
81 std::fs::write(path, data).map_err(|e| anyhow!("KEL write failed: {e}"))?;
82 }
83 Ok(())
84 }
85
86 pub fn events(&self) -> &[KeyEvent] {
87 &self.events
88 }
89
90 pub fn current_keys(&self) -> Option<&[String]> {
91 self.events.last().map(|e| e.keys.as_slice())
92 }
93
94 pub fn latest_event(&self) -> Option<&KeyEvent> {
95 self.events.last()
96 }
97
98 pub fn inception(
100 &mut self,
101 signing_key: &SigningKey,
102 next_key_digest: Option<&str>,
103 ) -> Result<&KeyEvent> {
104 if !self.events.is_empty() {
105 return Err(anyhow!("KEL already has an inception event"));
106 }
107
108 let pk_bytes = signing_key.verifying_key().to_bytes();
109 let identifier = did::did_key_from_ed25519(&pk_bytes);
110 let current_key = did::did_key_from_ed25519(&pk_bytes);
111
112 let mut event = KeyEvent {
113 version: "KERI10JSON".to_string(),
114 event_type: EventType::Inception,
115 digest: String::new(),
116 identifier: identifier.clone(),
117 sequence: 0,
118 key_threshold: 1,
119 keys: vec![current_key],
120 next_threshold: next_key_digest.map(|_| 1),
121 next_keys_digest: next_key_digest.map(String::from),
122 prior_digest: None,
123 anchors: Vec::new(),
124 signature: String::new(),
125 };
126
127 event.digest = self_addressing_digest(&event)?;
128 event.signature = sign_event(signing_key, &event)?;
129
130 self.events.push(event);
131 self.persist()?;
132 Ok(self.events.last().unwrap())
133 }
134
135 pub fn rotation(
137 &mut self,
138 current_key: &SigningKey,
139 new_key: &SigningKey,
140 next_key_digest: Option<&str>,
141 ) -> Result<&KeyEvent> {
142 let prior = self
143 .events
144 .last()
145 .ok_or_else(|| anyhow!("no prior event for rotation"))?;
146
147 let new_pk = new_key.verifying_key().to_bytes();
148 let new_did = did::did_key_from_ed25519(&new_pk);
149
150 let mut event = KeyEvent {
151 version: "KERI10JSON".to_string(),
152 event_type: EventType::Rotation,
153 digest: String::new(),
154 identifier: prior.identifier.clone(),
155 sequence: prior.sequence + 1,
156 key_threshold: 1,
157 keys: vec![new_did],
158 next_threshold: next_key_digest.map(|_| 1),
159 next_keys_digest: next_key_digest.map(String::from),
160 prior_digest: Some(prior.digest.clone()),
161 anchors: Vec::new(),
162 signature: String::new(),
163 };
164
165 event.digest = self_addressing_digest(&event)?;
166 event.signature = sign_event(current_key, &event)?;
167
168 self.events.push(event);
169 self.persist()?;
170 Ok(self.events.last().unwrap())
171 }
172
173 pub fn interaction(
175 &mut self,
176 signing_key: &SigningKey,
177 anchors: Vec<serde_json::Value>,
178 ) -> Result<&KeyEvent> {
179 let prior = self
180 .events
181 .last()
182 .ok_or_else(|| anyhow!("no prior event for interaction"))?;
183
184 let mut event = KeyEvent {
185 version: "KERI10JSON".to_string(),
186 event_type: EventType::Interaction,
187 digest: String::new(),
188 identifier: prior.identifier.clone(),
189 sequence: prior.sequence + 1,
190 key_threshold: prior.key_threshold,
191 keys: prior.keys.clone(),
192 next_threshold: prior.next_threshold,
193 next_keys_digest: prior.next_keys_digest.clone(),
194 prior_digest: Some(prior.digest.clone()),
195 anchors,
196 signature: String::new(),
197 };
198
199 event.digest = self_addressing_digest(&event)?;
200 event.signature = sign_event(signing_key, &event)?;
201
202 self.events.push(event);
203 self.persist()?;
204 Ok(self.events.last().unwrap())
205 }
206
207 pub fn verify(&self) -> Result<()> {
209 if self.events.is_empty() {
210 return Err(anyhow!("empty KEL"));
211 }
212
213 if self.events[0].event_type != EventType::Inception {
214 return Err(anyhow!("first event must be inception"));
215 }
216
217 let mut current_keys = Vec::new();
218
219 for (i, event) in self.events.iter().enumerate() {
220 if event.sequence != i as u64 {
221 return Err(anyhow!(
222 "sequence gap: expected {i}, got {}",
223 event.sequence
224 ));
225 }
226
227 if event.key_threshold > 1 {
231 return Err(anyhow!(
232 "multisig key threshold ({}) not supported at event {i}",
233 event.key_threshold
234 ));
235 }
236
237 let computed = self_addressing_digest(event)?;
238 if computed != event.digest {
239 return Err(anyhow!("digest mismatch at event {i}"));
240 }
241
242 if i > 0 {
243 let prior = &self.events[i - 1];
244 if event
245 .prior_digest
246 .as_ref()
247 .is_none_or(|d| d != &prior.digest)
248 {
249 return Err(anyhow!("chain break at event {i}"));
250 }
251
252 if event.event_type == EventType::Rotation {
257 let committed = prior.next_keys_digest.as_ref().ok_or_else(|| {
258 anyhow!("rotation at event {i} but prior event pre-committed no next key")
259 })?;
260 if &next_keys_digest(&event.keys)? != committed {
261 return Err(anyhow!(
262 "rotation at event {i} installs keys not matching the prior commitment"
263 ));
264 }
265 }
266 }
267
268 if i == 0 {
269 verify_event_signature(event, &event.keys)?;
270 } else {
271 verify_event_signature(event, ¤t_keys)?;
272 }
273
274 current_keys.clone_from(&event.keys);
275 }
276
277 Ok(())
278 }
279}
280
281fn next_keys_digest(keys: &[String]) -> Result<String> {
286 let mut hasher = Sha256::new();
287 for key_did in keys {
288 let pk = did::ed25519_from_did_key(key_did)?;
289 hasher.update(pk);
290 }
291 let hash = hasher.finalize();
292 Ok(hash.iter().map(|b| format!("{b:02x}")).collect())
293}
294
295fn self_addressing_digest(event: &KeyEvent) -> Result<String> {
296 let mut e = event.clone();
297 e.digest = String::new();
298 e.signature = String::new();
299 let serialized =
300 serde_json::to_vec(&e).map_err(|e| anyhow!("event serialization failed: {e}"))?;
301 let hash = Sha256::digest(&serialized);
302 Ok(hash.iter().map(|b| format!("{b:02x}")).collect())
303}
304
305fn sign_event(signing_key: &SigningKey, event: &KeyEvent) -> Result<String> {
306 let mut e = event.clone();
307 e.signature = String::new();
308 let serialized =
309 serde_json::to_vec(&e).map_err(|e| anyhow!("event serialization failed: {e}"))?;
310 let signature = signing_key.sign(&serialized);
311 Ok(multibase::encode(
312 multibase::Base::Base58Btc,
313 signature.to_bytes(),
314 ))
315}
316
317fn verify_event_signature(event: &KeyEvent, signer_keys: &[String]) -> Result<()> {
318 let mut e = event.clone();
319 e.signature = String::new();
320 let serialized =
321 serde_json::to_vec(&e).map_err(|e| anyhow!("event serialization failed: {e}"))?;
322
323 let (_, sig_bytes) =
324 multibase::decode(&event.signature).map_err(|e| anyhow!("multibase decode failed: {e}"))?;
325 let signature = ed25519_dalek::Signature::from_slice(&sig_bytes)
326 .map_err(|e| anyhow!("invalid signature: {e}"))?;
327
328 for key_did in signer_keys {
329 if let Ok(pk_bytes) = did::ed25519_from_did_key(key_did) {
330 if let Ok(vk) = VerifyingKey::from_bytes(&pk_bytes) {
331 if vk.verify(&serialized, &signature).is_ok() {
332 return Ok(());
333 }
334 }
335 }
336 }
337
338 Err(anyhow!("no authorized key verified the signature"))
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344
345 fn test_keypair() -> SigningKey {
346 SigningKey::from_bytes(&rand::random())
347 }
348
349 fn next_key_digest(key: &SigningKey) -> String {
350 let pk = key.verifying_key().to_bytes();
351 let hash = Sha256::digest(pk);
352 hash.iter().map(|b| format!("{b:02x}")).collect()
353 }
354
355 #[test]
356 fn inception_and_verify() {
357 let key = test_keypair();
358 let mut kel = KeyEventLog::new();
359 let event = kel.inception(&key, None).unwrap();
360
361 assert_eq!(event.event_type, EventType::Inception);
362 assert_eq!(event.sequence, 0);
363 assert!(!event.digest.is_empty());
364 kel.verify().unwrap();
365 }
366
367 #[test]
368 fn rotation_and_verify() {
369 let key1 = test_keypair();
370 let key2 = test_keypair();
371 let mut kel = KeyEventLog::new();
372
373 let nkd = next_key_digest(&key2);
374 kel.inception(&key1, Some(&nkd)).unwrap();
375 let rot = kel.rotation(&key1, &key2, None).unwrap();
376
377 assert_eq!(rot.event_type, EventType::Rotation);
378 assert_eq!(rot.sequence, 1);
379 assert!(rot.prior_digest.is_some());
380 kel.verify().unwrap();
381 }
382
383 #[test]
384 fn interaction_anchors_data() {
385 let key = test_keypair();
386 let mut kel = KeyEventLog::new();
387 kel.inception(&key, None).unwrap();
388
389 let seal = serde_json::json!({"i": "fact-123", "d": "abc123"});
390 let ixn = kel.interaction(&key, vec![seal.clone()]).unwrap();
391
392 assert_eq!(ixn.event_type, EventType::Interaction);
393 assert_eq!(ixn.anchors.len(), 1);
394 kel.verify().unwrap();
395 }
396
397 #[test]
398 fn tampered_event_rejected() {
399 let key = test_keypair();
400 let mut kel = KeyEventLog::new();
401 kel.inception(&key, None).unwrap();
402 kel.interaction(&key, Vec::new()).unwrap();
403
404 kel.events[1].anchors = vec![serde_json::json!({"tampered": true})];
405 assert!(kel.verify().is_err());
406 }
407
408 #[test]
409 fn rotation_to_uncommitted_key_rejected() {
410 let key1 = test_keypair();
414 let key2 = test_keypair();
415 let key3 = test_keypair();
416 let mut kel = KeyEventLog::new();
417
418 kel.inception(&key1, Some(&next_key_digest(&key2))).unwrap();
419 kel.rotation(&key1, &key3, None).unwrap();
420
421 assert!(kel.verify().is_err());
422 }
423
424 #[test]
425 fn rotation_without_precommitment_rejected() {
426 let key1 = test_keypair();
428 let key2 = test_keypair();
429 let mut kel = KeyEventLog::new();
430
431 kel.inception(&key1, None).unwrap();
432 kel.rotation(&key1, &key2, None).unwrap();
433
434 assert!(kel.verify().is_err());
435 }
436
437 #[test]
438 fn multisig_threshold_rejected() {
439 let key = test_keypair();
440 let mut kel = KeyEventLog::new();
441 kel.inception(&key, None).unwrap();
442 kel.events[0].key_threshold = 2;
443
444 assert!(kel.verify().is_err());
445 }
446
447 #[test]
448 fn double_inception_rejected() {
449 let key = test_keypair();
450 let mut kel = KeyEventLog::new();
451 kel.inception(&key, None).unwrap();
452 assert!(kel.inception(&key, None).is_err());
453 }
454
455 #[test]
456 fn full_lifecycle() {
457 let key1 = test_keypair();
458 let key2 = test_keypair();
459 let key3 = test_keypair();
460 let mut kel = KeyEventLog::new();
461
462 kel.inception(&key1, Some(&next_key_digest(&key2))).unwrap();
463 kel.interaction(&key1, vec![serde_json::json!({"seal": "a"})])
464 .unwrap();
465 kel.rotation(&key1, &key2, Some(&next_key_digest(&key3)))
466 .unwrap();
467 kel.interaction(&key2, vec![serde_json::json!({"seal": "b"})])
468 .unwrap();
469 kel.rotation(&key2, &key3, None).unwrap();
470
471 assert_eq!(kel.events().len(), 5);
472 kel.verify().unwrap();
473
474 let current = kel.current_keys().unwrap();
475 let key3_did = did::did_key_from_ed25519(&key3.verifying_key().to_bytes());
476 assert_eq!(current, &[key3_did]);
477 }
478
479 #[test]
480 fn persistence_roundtrip() {
481 let dir = tempfile::tempdir().unwrap();
482 let kel_path = dir.path().join("kel.json");
483
484 let key1 = test_keypair();
485 let key2 = test_keypair();
486
487 {
488 let mut kel = KeyEventLog::with_path(&kel_path).unwrap();
489 kel.inception(&key1, Some(&next_key_digest(&key2))).unwrap();
490 kel.interaction(&key1, vec![serde_json::json!({"fact": "f1"})])
491 .unwrap();
492 kel.rotation(&key1, &key2, None).unwrap();
493 assert_eq!(kel.events().len(), 3);
494 }
495
496 let loaded = KeyEventLog::with_path(&kel_path).unwrap();
497 assert_eq!(loaded.events().len(), 3);
498 loaded.verify().unwrap();
499 let key2_did = did::did_key_from_ed25519(&key2.verifying_key().to_bytes());
500 assert_eq!(loaded.current_keys().unwrap(), &[key2_did]);
501 }
502}