1use auths_core::crypto::said::{compute_said, verify_commitment};
23use auths_crypto::KeriPublicKey;
24use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
25use ring::signature::UnparsedPublicKey;
26
27use super::types::{Prefix, Said};
28use super::{Event, IcpEvent, IxnEvent, KeyState, RotEvent};
29
30#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
41#[non_exhaustive]
42pub enum ValidationError {
43 #[error("Invalid SAID: expected {expected}, got {actual}")]
48 InvalidSaid { expected: Said, actual: Said },
49
50 #[error("Broken chain: event {sequence} references {referenced}, but previous was {actual}")]
55 BrokenChain {
56 sequence: u64,
57 referenced: Said,
58 actual: Said,
59 },
60
61 #[error("Invalid sequence: expected {expected}, got {actual}")]
66 InvalidSequence { expected: u64, actual: u64 },
67
68 #[error("Pre-rotation commitment mismatch at sequence {sequence}")]
69 CommitmentMismatch { sequence: u64 },
70
71 #[error("Signature verification failed at sequence {sequence}")]
72 SignatureFailed { sequence: u64 },
73
74 #[error("First event must be inception")]
75 NotInception,
76
77 #[error("Empty KEL")]
78 EmptyKel,
79
80 #[error("Multiple inception events in KEL")]
81 MultipleInceptions,
82
83 #[error("Serialization error: {0}")]
84 Serialization(String),
85
86 #[error("Malformed sequence number: {raw:?}")]
87 MalformedSequence { raw: String },
88}
89
90impl auths_core::error::AuthsErrorInfo for ValidationError {
91 fn error_code(&self) -> &'static str {
92 match self {
93 Self::InvalidSaid { .. } => "AUTHS-E4501",
94 Self::BrokenChain { .. } => "AUTHS-E4502",
95 Self::InvalidSequence { .. } => "AUTHS-E4503",
96 Self::CommitmentMismatch { .. } => "AUTHS-E4504",
97 Self::SignatureFailed { .. } => "AUTHS-E4505",
98 Self::NotInception => "AUTHS-E4506",
99 Self::EmptyKel => "AUTHS-E4507",
100 Self::MultipleInceptions => "AUTHS-E4508",
101 Self::Serialization(_) => "AUTHS-E4509",
102 Self::MalformedSequence { .. } => "AUTHS-E4510",
103 }
104 }
105
106 fn suggestion(&self) -> Option<&'static str> {
107 match self {
108 Self::InvalidSaid { .. } => {
109 Some("The KEL may have been tampered with; re-sync from a trusted source")
110 }
111 Self::BrokenChain { .. } => {
112 Some("The KEL chain is broken; re-sync from a trusted source")
113 }
114 Self::InvalidSequence { .. } => {
115 Some("The KEL has sequence gaps; re-sync from a trusted source")
116 }
117 Self::CommitmentMismatch { .. } => {
118 Some("The rotation key does not match the pre-rotation commitment")
119 }
120 Self::SignatureFailed { .. } => {
121 Some("The event signature is invalid; the KEL may be corrupted")
122 }
123 Self::NotInception => Some("The first event in a KEL must be an inception event"),
124 Self::EmptyKel => Some("No events found; initialize the identity first"),
125 Self::MultipleInceptions => Some("A KEL must contain exactly one inception event"),
126 Self::Serialization(_) => None,
127 Self::MalformedSequence { .. } => None,
128 }
129 }
130}
131
132pub fn validate_kel(events: &[Event]) -> Result<KeyState, ValidationError> {
162 if events.is_empty() {
163 return Err(ValidationError::EmptyKel);
164 }
165
166 let Event::Icp(icp) = &events[0] else {
167 return Err(ValidationError::NotInception);
168 };
169
170 verify_event_said(&events[0])?;
171 let mut state = validate_inception(icp)?;
172
173 for (idx, event) in events.iter().enumerate().skip(1) {
174 let expected_seq = idx as u64;
175 verify_event_said(event)?;
176 verify_sequence(event, expected_seq)?;
177 verify_chain_linkage(event, &state)?;
178
179 match event {
180 Event::Rot(rot) => validate_rotation(rot, event, expected_seq, &mut state)?,
181 Event::Ixn(ixn) => validate_interaction(ixn, event, expected_seq, &mut state)?,
182 Event::Icp(_) => return Err(ValidationError::MultipleInceptions),
183 }
184 }
185
186 Ok(state)
187}
188
189fn parse_threshold(raw: &str) -> Result<u64, ValidationError> {
190 raw.parse::<u64>()
191 .map_err(|_| ValidationError::MalformedSequence {
192 raw: raw.to_string(),
193 })
194}
195
196fn validate_inception(icp: &IcpEvent) -> Result<KeyState, ValidationError> {
197 verify_event_signature(
198 &Event::Icp(icp.clone()),
199 icp.k
200 .first()
201 .ok_or(ValidationError::SignatureFailed { sequence: 0 })?,
202 )?;
203
204 let threshold = parse_threshold(&icp.kt)?;
205 let next_threshold = parse_threshold(&icp.nt)?;
206
207 Ok(KeyState::from_inception(
208 icp.i.clone(),
209 icp.k.clone(),
210 icp.n.clone(),
211 threshold,
212 next_threshold,
213 icp.d.clone(),
214 ))
215}
216
217fn verify_sequence(event: &Event, expected: u64) -> Result<(), ValidationError> {
218 let actual = event.sequence().value();
219 if actual != expected {
220 return Err(ValidationError::InvalidSequence { expected, actual });
221 }
222 Ok(())
223}
224
225fn verify_chain_linkage(event: &Event, state: &KeyState) -> Result<(), ValidationError> {
226 let prev_said = event.previous().ok_or(ValidationError::NotInception)?;
227 if *prev_said != state.last_event_said {
228 return Err(ValidationError::BrokenChain {
229 sequence: event.sequence().value(),
230 referenced: prev_said.clone(),
231 actual: state.last_event_said.clone(),
232 });
233 }
234 Ok(())
235}
236
237fn validate_rotation(
238 rot: &RotEvent,
239 event: &Event,
240 sequence: u64,
241 state: &mut KeyState,
242) -> Result<(), ValidationError> {
243 if !rot.k.is_empty() {
244 verify_event_signature(event, &rot.k[0])?;
245 }
246
247 if !state.next_commitment.is_empty() && !rot.k.is_empty() {
248 let key_bytes = KeriPublicKey::parse(&rot.k[0])
249 .map(|k| k.as_bytes().to_vec())
250 .map_err(|_| ValidationError::CommitmentMismatch { sequence })?;
251
252 if !verify_commitment(&key_bytes, &state.next_commitment[0]) {
253 return Err(ValidationError::CommitmentMismatch { sequence });
254 }
255 }
256
257 let threshold = parse_threshold(&rot.kt)?;
258 let next_threshold = parse_threshold(&rot.nt)?;
259
260 state.apply_rotation(
261 rot.k.clone(),
262 rot.n.clone(),
263 threshold,
264 next_threshold,
265 sequence,
266 rot.d.clone(),
267 );
268
269 Ok(())
270}
271
272fn validate_interaction(
273 ixn: &IxnEvent,
274 event: &Event,
275 sequence: u64,
276 state: &mut KeyState,
277) -> Result<(), ValidationError> {
278 let current_key = state
279 .current_key()
280 .ok_or(ValidationError::SignatureFailed { sequence })?;
281 verify_event_signature(event, current_key)?;
282 state.apply_interaction(sequence, ixn.d.clone());
283 Ok(())
284}
285
286pub fn replay_kel(events: &[Event]) -> Result<KeyState, ValidationError> {
293 validate_kel(events)
294}
295
296pub fn verify_event_crypto(
310 event: &Event,
311 current_state: Option<&KeyState>,
312) -> Result<(), ValidationError> {
313 match event {
314 Event::Icp(icp) => {
315 let key = icp
317 .k
318 .first()
319 .ok_or(ValidationError::SignatureFailed { sequence: 0 })?;
320 verify_event_signature(event, key)?;
321
322 if icp.i.as_str() != icp.d.as_str() {
324 return Err(ValidationError::InvalidSaid {
325 expected: icp.d.clone(),
326 actual: Said::new_unchecked(icp.i.as_str().to_string()),
327 });
328 }
329
330 Ok(())
331 }
332 Event::Rot(rot) => {
333 let sequence = event.sequence().value();
334 let state = current_state.ok_or(ValidationError::SignatureFailed { sequence })?;
335
336 if state.is_abandoned || state.next_commitment.is_empty() {
338 return Err(ValidationError::CommitmentMismatch { sequence });
339 }
340
341 if rot.k.is_empty() {
343 return Err(ValidationError::SignatureFailed { sequence });
344 }
345 verify_event_signature(event, &rot.k[0])?;
346
347 let key_str = &rot.k[0];
349 let key_bytes = KeriPublicKey::parse(key_str)
350 .map(|k| k.as_bytes().to_vec())
351 .map_err(|_| ValidationError::CommitmentMismatch { sequence })?;
352
353 if !verify_commitment(&key_bytes, &state.next_commitment[0]) {
354 return Err(ValidationError::CommitmentMismatch { sequence });
355 }
356
357 Ok(())
358 }
359 Event::Ixn(_) => {
360 let sequence = event.sequence().value();
361 let state = current_state.ok_or(ValidationError::SignatureFailed { sequence })?;
362
363 let current_key = state
365 .current_key()
366 .ok_or(ValidationError::SignatureFailed { sequence })?;
367 verify_event_signature(event, current_key)?;
368
369 Ok(())
370 }
371 }
372}
373
374pub fn verify_event_said(event: &Event) -> Result<(), ValidationError> {
378 let json = serialize_for_said(event)?;
380 let computed = compute_said(&json);
381 let actual = event.said();
382
383 if computed != actual.as_str() {
384 return Err(ValidationError::InvalidSaid {
385 expected: computed,
386 actual: actual.clone(),
387 });
388 }
389
390 Ok(())
391}
392
393pub fn validate_for_append(event: &Event, state: &KeyState) -> Result<(), ValidationError> {
403 if matches!(event, Event::Icp(_)) {
404 return Err(ValidationError::MultipleInceptions);
405 }
406
407 verify_event_said(event)?;
408 verify_sequence(event, state.sequence + 1)?;
409 verify_chain_linkage(event, state)?;
410 verify_event_crypto(event, Some(state))?;
411
412 Ok(())
413}
414
415pub fn compute_event_said(event: &Event) -> Result<Said, ValidationError> {
419 let json = serialize_for_said(event)?;
420 Ok(compute_said(&json))
421}
422
423fn serialize_for_said(event: &Event) -> Result<Vec<u8>, ValidationError> {
427 match event {
428 Event::Icp(e) => {
429 let mut e = e.clone();
430 e.d = Said::default();
431 e.i = Prefix::default(); e.x = String::new(); serde_json::to_vec(&Event::Icp(e))
434 }
435 Event::Rot(e) => {
436 let mut e = e.clone();
437 e.d = Said::default();
438 e.x = String::new(); serde_json::to_vec(&Event::Rot(e))
440 }
441 Event::Ixn(e) => {
442 let mut e = e.clone();
443 e.d = Said::default();
444 e.x = String::new(); serde_json::to_vec(&Event::Ixn(e))
446 }
447 }
448 .map_err(|e| ValidationError::Serialization(e.to_string()))
449}
450
451pub fn serialize_for_signing(event: &Event) -> Result<Vec<u8>, ValidationError> {
456 match event {
457 Event::Icp(e) => {
458 let mut e = e.clone();
459 e.d = Said::default();
460 e.i = Prefix::default(); e.x = String::new();
462 serde_json::to_vec(&Event::Icp(e))
463 }
464 Event::Rot(e) => {
465 let mut e = e.clone();
466 e.d = Said::default();
467 e.x = String::new();
468 serde_json::to_vec(&Event::Rot(e))
469 }
470 Event::Ixn(e) => {
471 let mut e = e.clone();
472 e.d = Said::default();
473 e.x = String::new();
474 serde_json::to_vec(&Event::Ixn(e))
475 }
476 }
477 .map_err(|e| ValidationError::Serialization(e.to_string()))
478}
479
480fn verify_event_signature(event: &Event, signing_key: &str) -> Result<(), ValidationError> {
482 let sequence = event.sequence().value();
483
484 let sig_str = event.signature();
486 if sig_str.is_empty() {
487 return Err(ValidationError::SignatureFailed { sequence });
488 }
489 let sig_bytes = URL_SAFE_NO_PAD
490 .decode(sig_str)
491 .map_err(|_| ValidationError::SignatureFailed { sequence })?;
492
493 let key_bytes = KeriPublicKey::parse(signing_key)
495 .map_err(|_| ValidationError::SignatureFailed { sequence })?;
496
497 let canonical = serialize_for_signing(event)?;
499
500 let pk = UnparsedPublicKey::new(&ring::signature::ED25519, key_bytes.as_bytes());
502 pk.verify(&canonical, &sig_bytes)
503 .map_err(|_| ValidationError::SignatureFailed { sequence })?;
504
505 Ok(())
506}
507
508pub fn finalize_icp_event(mut icp: IcpEvent) -> Result<IcpEvent, ValidationError> {
510 icp.d = Said::default();
512 icp.i = Prefix::default();
513
514 let json = serde_json::to_vec(&Event::Icp(icp.clone()))
516 .map_err(|e| ValidationError::Serialization(e.to_string()))?;
517 let said = compute_said(&json);
518
519 icp.d = said.clone();
521 icp.i = Prefix::new_unchecked(said.into_inner());
522
523 Ok(icp)
524}
525
526#[cfg(test)]
527mod tests {
528 use super::*;
529 use crate::keri::{IxnEvent, KERI_VERSION, KeriSequence, Prefix, RotEvent, Said, Seal};
530 use base64::Engine;
531 use base64::engine::general_purpose::URL_SAFE_NO_PAD;
532 use ring::rand::SystemRandom;
533 use ring::signature::{Ed25519KeyPair, KeyPair};
534
535 fn make_raw_icp(key: &str, next: &str) -> IcpEvent {
536 IcpEvent {
537 v: KERI_VERSION.to_string(),
538 d: Said::default(),
539 i: Prefix::default(),
540 s: KeriSequence::new(0),
541 kt: "1".to_string(),
542 k: vec![key.to_string()],
543 nt: "1".to_string(),
544 n: vec![next.to_string()],
545 bt: "0".to_string(),
546 b: vec![],
547 a: vec![],
548 x: String::new(),
549 }
550 }
551
552 fn make_signed_icp() -> (IcpEvent, Ed25519KeyPair) {
554 let rng = SystemRandom::new();
555 let pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
556 let keypair = Ed25519KeyPair::from_pkcs8(pkcs8.as_ref()).unwrap();
557 let key_encoded = format!("D{}", URL_SAFE_NO_PAD.encode(keypair.public_key().as_ref()));
558
559 let icp = IcpEvent {
560 v: KERI_VERSION.to_string(),
561 d: Said::default(),
562 i: Prefix::default(),
563 s: KeriSequence::new(0),
564 kt: "1".to_string(),
565 k: vec![key_encoded],
566 nt: "1".to_string(),
567 n: vec!["ENextCommitment".to_string()],
568 bt: "0".to_string(),
569 b: vec![],
570 a: vec![],
571 x: String::new(),
572 };
573
574 let mut finalized = finalize_icp_event(icp).unwrap();
576
577 let canonical = serialize_for_signing(&Event::Icp(finalized.clone())).unwrap();
579 let sig = keypair.sign(&canonical);
580 finalized.x = URL_SAFE_NO_PAD.encode(sig.as_ref());
581
582 (finalized, keypair)
583 }
584
585 fn make_signed_ixn(
587 prefix: &Prefix,
588 prev_said: &Said,
589 seq: u64,
590 keypair: &Ed25519KeyPair,
591 ) -> IxnEvent {
592 let mut ixn = IxnEvent {
593 v: KERI_VERSION.to_string(),
594 d: Said::default(),
595 i: prefix.clone(),
596 s: KeriSequence::new(seq),
597 p: prev_said.clone(),
598 a: vec![Seal::device_attestation("EAttest")],
599 x: String::new(),
600 };
601
602 let json = serde_json::to_vec(&Event::Ixn(ixn.clone())).unwrap();
604 ixn.d = compute_said(&json);
605
606 let canonical = serialize_for_signing(&Event::Ixn(ixn.clone())).unwrap();
608 let sig = keypair.sign(&canonical);
609 ixn.x = URL_SAFE_NO_PAD.encode(sig.as_ref());
610
611 ixn
612 }
613
614 #[test]
615 fn finalize_icp_sets_said() {
616 let icp = make_raw_icp("DKey1", "ENext1");
617 let finalized = finalize_icp_event(icp).unwrap();
618
619 assert!(!finalized.d.is_empty());
621 assert_eq!(finalized.d.as_str(), finalized.i.as_str());
622 assert!(finalized.d.as_str().starts_with('E'));
623 }
624
625 #[test]
626 fn validates_single_inception() {
627 let (icp, _keypair) = make_signed_icp();
628 let events = vec![Event::Icp(icp.clone())];
629
630 let state = validate_kel(&events).unwrap();
631 assert_eq!(state.prefix, icp.i);
632 assert_eq!(state.sequence, 0);
633 }
634
635 #[test]
636 fn rejects_empty_kel() {
637 let result = validate_kel(&[]);
638 assert!(matches!(result, Err(ValidationError::EmptyKel)));
639 }
640
641 #[test]
642 fn rejects_non_inception_first() {
643 let ixn = IxnEvent {
644 v: KERI_VERSION.to_string(),
645 d: Said::new_unchecked("ETest".to_string()),
646 i: Prefix::new_unchecked("ETest".to_string()),
647 s: KeriSequence::new(0),
648 p: Said::new_unchecked("EPrev".to_string()),
649 a: vec![],
650 x: String::new(),
651 };
652 let events = vec![Event::Ixn(ixn)];
653 let result = validate_kel(&events);
654 assert!(matches!(result, Err(ValidationError::NotInception)));
655 }
656
657 #[test]
658 fn rejects_broken_sequence() {
659 let (icp, keypair) = make_signed_icp();
660
661 let mut ixn = IxnEvent {
663 v: KERI_VERSION.to_string(),
664 d: Said::default(),
665 i: icp.i.clone(),
666 s: KeriSequence::new(5), p: icp.d.clone(),
668 a: vec![],
669 x: String::new(),
670 };
671
672 let json = serde_json::to_vec(&Event::Ixn(ixn.clone())).unwrap();
674 ixn.d = compute_said(&json);
675
676 let canonical = serialize_for_signing(&Event::Ixn(ixn.clone())).unwrap();
678 let sig = keypair.sign(&canonical);
679 ixn.x = URL_SAFE_NO_PAD.encode(sig.as_ref());
680
681 let events = vec![Event::Icp(icp), Event::Ixn(ixn)];
682 let result = validate_kel(&events);
683 assert!(matches!(
684 result,
685 Err(ValidationError::InvalidSequence {
686 expected: 1,
687 actual: 5
688 })
689 ));
690 }
691
692 #[test]
693 fn rejects_broken_chain() {
694 let (icp, keypair) = make_signed_icp();
695
696 let mut ixn = IxnEvent {
698 v: KERI_VERSION.to_string(),
699 d: Said::default(),
700 i: icp.i.clone(),
701 s: KeriSequence::new(1),
702 p: Said::new_unchecked("EWrongPrevious".to_string()),
703 a: vec![],
704 x: String::new(),
705 };
706
707 let json = serde_json::to_vec(&Event::Ixn(ixn.clone())).unwrap();
709 ixn.d = compute_said(&json);
710
711 let canonical = serialize_for_signing(&Event::Ixn(ixn.clone())).unwrap();
713 let sig = keypair.sign(&canonical);
714 ixn.x = URL_SAFE_NO_PAD.encode(sig.as_ref());
715
716 let events = vec![Event::Icp(icp), Event::Ixn(ixn)];
717 let result = validate_kel(&events);
718 assert!(matches!(result, Err(ValidationError::BrokenChain { .. })));
719 }
720
721 #[test]
722 fn rejects_invalid_said() {
723 let icp = make_raw_icp("DKey1", "ENext1");
724 let finalized = finalize_icp_event(icp.clone()).unwrap();
725
726 let mut tampered = finalized.clone();
728 tampered.d = Said::new_unchecked("EWrongSaid".to_string());
729
730 let events = vec![Event::Icp(tampered)];
731 let result = validate_kel(&events);
732 assert!(matches!(result, Err(ValidationError::InvalidSaid { .. })));
733 }
734
735 #[test]
736 fn validates_icp_then_ixn() {
737 let (icp, keypair) = make_signed_icp();
738
739 let ixn = make_signed_ixn(&icp.i, &icp.d, 1, &keypair);
741
742 let events = vec![Event::Icp(icp), Event::Ixn(ixn.clone())];
743 let state = validate_kel(&events).unwrap();
744 assert_eq!(state.sequence, 1);
745 assert_eq!(state.last_event_said, ixn.d);
746 }
747
748 #[test]
749 fn rejects_multiple_inceptions() {
750 let icp1 = finalize_icp_event(make_raw_icp("DKey1", "ENext1")).unwrap();
751 let icp2 = finalize_icp_event(make_raw_icp("DKey2", "ENext2")).unwrap();
752
753 let events = vec![Event::Icp(icp1), Event::Icp(icp2)];
754 let result = validate_kel(&events);
755 assert!(result.is_err());
757 }
758
759 #[test]
760 fn compute_event_said_works() {
761 let icp = make_raw_icp("DKey1", "ENext1");
762 let event = Event::Icp(icp);
763 let said = compute_event_said(&event).unwrap();
764 assert!(said.as_str().starts_with('E'));
765 assert!(!said.is_empty());
766 }
767
768 #[test]
769 fn rejects_forged_signature() {
770 let (mut icp, _keypair) = make_signed_icp();
771
772 icp.x = URL_SAFE_NO_PAD.encode([0u8; 64]);
774
775 let events = vec![Event::Icp(icp)];
776 let result = validate_kel(&events);
777 assert!(matches!(
778 result,
779 Err(ValidationError::SignatureFailed { sequence: 0 })
780 ));
781 }
782
783 #[test]
784 fn rejects_missing_signature() {
785 let (mut icp, _keypair) = make_signed_icp();
786
787 icp.x = String::new();
789
790 let events = vec![Event::Icp(icp)];
791 let result = validate_kel(&events);
792 assert!(matches!(
793 result,
794 Err(ValidationError::SignatureFailed { sequence: 0 })
795 ));
796 }
797
798 #[test]
799 fn rejects_wrong_key_signature() {
800 let rng = SystemRandom::new();
802 let pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
803 let keypair = Ed25519KeyPair::from_pkcs8(pkcs8.as_ref()).unwrap();
804 let key_encoded = format!("D{}", URL_SAFE_NO_PAD.encode(keypair.public_key().as_ref()));
805
806 let mut icp = IcpEvent {
807 v: KERI_VERSION.to_string(),
808 d: Said::default(),
809 i: Prefix::default(),
810 s: KeriSequence::new(0),
811 kt: "1".to_string(),
812 k: vec![key_encoded],
813 nt: "1".to_string(),
814 n: vec!["ENextCommitment".to_string()],
815 bt: "0".to_string(),
816 b: vec![],
817 a: vec![],
818 x: String::new(),
819 };
820
821 icp = finalize_icp_event(icp).unwrap();
823
824 let wrong_pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
826 let wrong_keypair = Ed25519KeyPair::from_pkcs8(wrong_pkcs8.as_ref()).unwrap();
827 let canonical = serialize_for_signing(&Event::Icp(icp.clone())).unwrap();
828 let sig = wrong_keypair.sign(&canonical);
829 icp.x = URL_SAFE_NO_PAD.encode(sig.as_ref());
830
831 let events = vec![Event::Icp(icp)];
832 let result = validate_kel(&events);
833 assert!(matches!(
834 result,
835 Err(ValidationError::SignatureFailed { sequence: 0 })
836 ));
837 }
838
839 #[test]
844 fn crypto_accepts_valid_inception() {
845 let (icp, _keypair) = make_signed_icp();
846 let result = verify_event_crypto(&Event::Icp(icp), None);
847 assert!(result.is_ok());
848 }
849
850 #[test]
851 fn crypto_rejects_forged_inception_signature() {
852 let (mut icp, _keypair) = make_signed_icp();
853 icp.x = URL_SAFE_NO_PAD.encode([0u8; 64]);
854 let result = verify_event_crypto(&Event::Icp(icp), None);
855 assert!(matches!(
856 result,
857 Err(ValidationError::SignatureFailed { sequence: 0 })
858 ));
859 }
860
861 #[test]
862 fn crypto_rejects_inception_with_mismatched_prefix() {
863 let (mut icp, _keypair) = make_signed_icp();
864 icp.i = Prefix::new_unchecked("EWrongPrefix".to_string());
866 let result = verify_event_crypto(&Event::Icp(icp), None);
867 assert!(result.is_err());
869 }
870
871 #[test]
872 fn crypto_accepts_valid_interaction() {
873 let (icp, keypair) = make_signed_icp();
874 let ixn = make_signed_ixn(&icp.i, &icp.d, 1, &keypair);
875
876 let threshold = icp.kt.parse().unwrap();
877 let next_threshold = icp.nt.parse().unwrap();
878 let state = KeyState::from_inception(
879 icp.i.clone(),
880 icp.k.clone(),
881 icp.n.clone(),
882 threshold,
883 next_threshold,
884 icp.d.clone(),
885 );
886 let result = verify_event_crypto(&Event::Ixn(ixn), Some(&state));
887 assert!(result.is_ok());
888 }
889
890 #[test]
891 fn crypto_rejects_interaction_with_forged_signature() {
892 let (icp, keypair) = make_signed_icp();
893 let mut ixn = make_signed_ixn(&icp.i, &icp.d, 1, &keypair);
894
895 ixn.x = URL_SAFE_NO_PAD.encode([0u8; 64]);
897
898 let threshold = icp.kt.parse().unwrap();
899 let next_threshold = icp.nt.parse().unwrap();
900 let state = KeyState::from_inception(
901 icp.i.clone(),
902 icp.k.clone(),
903 icp.n.clone(),
904 threshold,
905 next_threshold,
906 icp.d.clone(),
907 );
908 let result = verify_event_crypto(&Event::Ixn(ixn), Some(&state));
909 assert!(matches!(
910 result,
911 Err(ValidationError::SignatureFailed { sequence: 1 })
912 ));
913 }
914
915 #[test]
916 fn crypto_rejects_interaction_signed_by_wrong_key() {
917 let (icp, _keypair) = make_signed_icp();
918
919 let rng = SystemRandom::new();
921 let wrong_pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
922 let wrong_keypair = Ed25519KeyPair::from_pkcs8(wrong_pkcs8.as_ref()).unwrap();
923 let ixn = make_signed_ixn(&icp.i, &icp.d, 1, &wrong_keypair);
924
925 let threshold = icp.kt.parse().unwrap();
926 let next_threshold = icp.nt.parse().unwrap();
927 let state = KeyState::from_inception(
928 icp.i.clone(),
929 icp.k.clone(),
930 icp.n.clone(),
931 threshold,
932 next_threshold,
933 icp.d.clone(),
934 );
935 let result = verify_event_crypto(&Event::Ixn(ixn), Some(&state));
936 assert!(matches!(
937 result,
938 Err(ValidationError::SignatureFailed { sequence: 1 })
939 ));
940 }
941
942 #[test]
943 fn crypto_rejects_rotation_on_abandoned_identity() {
944 use auths_core::crypto::said::compute_next_commitment;
945
946 let (icp, _keypair) = make_signed_icp();
947
948 let threshold = icp.kt.parse().unwrap();
950 let next_threshold = icp.nt.parse().unwrap();
951 let mut state = KeyState::from_inception(
952 icp.i.clone(),
953 icp.k.clone(),
954 icp.n.clone(),
955 threshold,
956 next_threshold,
957 icp.d.clone(),
958 );
959 state.next_commitment = vec![];
960 state.is_abandoned = true;
961
962 let rng = SystemRandom::new();
964 let new_pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
965 let new_keypair = Ed25519KeyPair::from_pkcs8(new_pkcs8.as_ref()).unwrap();
966 let new_key_encoded = format!(
967 "D{}",
968 URL_SAFE_NO_PAD.encode(new_keypair.public_key().as_ref())
969 );
970 let new_commitment = compute_next_commitment(new_keypair.public_key().as_ref());
971
972 let mut rot = RotEvent {
973 v: KERI_VERSION.to_string(),
974 d: Said::default(),
975 i: icp.i.clone(),
976 s: KeriSequence::new(1),
977 p: icp.d.clone(),
978 kt: "1".to_string(),
979 k: vec![new_key_encoded],
980 nt: "1".to_string(),
981 n: vec![new_commitment],
982 bt: "0".to_string(),
983 b: vec![],
984 a: vec![],
985 x: String::new(),
986 };
987
988 let json = serde_json::to_vec(&Event::Rot(rot.clone())).unwrap();
990 rot.d = compute_said(&json);
991
992 let canonical = serialize_for_signing(&Event::Rot(rot.clone())).unwrap();
994 let sig = new_keypair.sign(&canonical);
995 rot.x = URL_SAFE_NO_PAD.encode(sig.as_ref());
996
997 let result = verify_event_crypto(&Event::Rot(rot), Some(&state));
998 assert!(matches!(
999 result,
1000 Err(ValidationError::CommitmentMismatch { .. })
1001 ));
1002 }
1003
1004 #[test]
1005 fn crypto_rejects_rotation_without_precommitted_key() {
1006 use auths_core::crypto::said::compute_next_commitment;
1007
1008 let rng = SystemRandom::new();
1009
1010 let pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
1012 let keypair = Ed25519KeyPair::from_pkcs8(pkcs8.as_ref()).unwrap();
1013 let key_encoded = format!("D{}", URL_SAFE_NO_PAD.encode(keypair.public_key().as_ref()));
1014
1015 let next_pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
1017 let next_keypair = Ed25519KeyPair::from_pkcs8(next_pkcs8.as_ref()).unwrap();
1018 let next_commitment = compute_next_commitment(next_keypair.public_key().as_ref());
1019
1020 let icp = IcpEvent {
1021 v: KERI_VERSION.to_string(),
1022 d: Said::default(),
1023 i: Prefix::default(),
1024 s: KeriSequence::new(0),
1025 kt: "1".to_string(),
1026 k: vec![key_encoded.clone()],
1027 nt: "1".to_string(),
1028 n: vec![next_commitment.clone()],
1029 bt: "0".to_string(),
1030 b: vec![],
1031 a: vec![],
1032 x: String::new(),
1033 };
1034 let mut icp = finalize_icp_event(icp).unwrap();
1035 let canonical = serialize_for_signing(&Event::Icp(icp.clone())).unwrap();
1036 let sig = keypair.sign(&canonical);
1037 icp.x = URL_SAFE_NO_PAD.encode(sig.as_ref());
1038
1039 let threshold = icp.kt.parse().unwrap();
1040 let next_threshold = icp.nt.parse().unwrap();
1041 let state = KeyState::from_inception(
1042 icp.i.clone(),
1043 icp.k.clone(),
1044 vec![next_commitment],
1045 threshold,
1046 next_threshold,
1047 icp.d.clone(),
1048 );
1049
1050 let wrong_pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
1052 let wrong_keypair = Ed25519KeyPair::from_pkcs8(wrong_pkcs8.as_ref()).unwrap();
1053 let wrong_key_encoded = format!(
1054 "D{}",
1055 URL_SAFE_NO_PAD.encode(wrong_keypair.public_key().as_ref())
1056 );
1057 let new_commitment = compute_next_commitment(wrong_keypair.public_key().as_ref());
1058
1059 let mut rot = RotEvent {
1060 v: KERI_VERSION.to_string(),
1061 d: Said::default(),
1062 i: icp.i.clone(),
1063 s: KeriSequence::new(1),
1064 p: icp.d.clone(),
1065 kt: "1".to_string(),
1066 k: vec![wrong_key_encoded],
1067 nt: "1".to_string(),
1068 n: vec![new_commitment],
1069 bt: "0".to_string(),
1070 b: vec![],
1071 a: vec![],
1072 x: String::new(),
1073 };
1074
1075 let json = serde_json::to_vec(&Event::Rot(rot.clone())).unwrap();
1076 rot.d = compute_said(&json);
1077 let canonical = serialize_for_signing(&Event::Rot(rot.clone())).unwrap();
1078 let sig = wrong_keypair.sign(&canonical);
1079 rot.x = URL_SAFE_NO_PAD.encode(sig.as_ref());
1080
1081 let result = verify_event_crypto(&Event::Rot(rot), Some(&state));
1082 assert!(matches!(
1083 result,
1084 Err(ValidationError::CommitmentMismatch { .. })
1085 ));
1086 }
1087
1088 #[test]
1089 fn crypto_accepts_valid_rotation() {
1090 use auths_core::crypto::said::compute_next_commitment;
1091
1092 let rng = SystemRandom::new();
1093
1094 let pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
1096 let keypair = Ed25519KeyPair::from_pkcs8(pkcs8.as_ref()).unwrap();
1097 let key_encoded = format!("D{}", URL_SAFE_NO_PAD.encode(keypair.public_key().as_ref()));
1098
1099 let next_pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
1101 let next_keypair = Ed25519KeyPair::from_pkcs8(next_pkcs8.as_ref()).unwrap();
1102 let next_commitment = compute_next_commitment(next_keypair.public_key().as_ref());
1103
1104 let icp = IcpEvent {
1105 v: KERI_VERSION.to_string(),
1106 d: Said::default(),
1107 i: Prefix::default(),
1108 s: KeriSequence::new(0),
1109 kt: "1".to_string(),
1110 k: vec![key_encoded],
1111 nt: "1".to_string(),
1112 n: vec![next_commitment.clone()],
1113 bt: "0".to_string(),
1114 b: vec![],
1115 a: vec![],
1116 x: String::new(),
1117 };
1118 let mut icp = finalize_icp_event(icp).unwrap();
1119 let canonical = serialize_for_signing(&Event::Icp(icp.clone())).unwrap();
1120 let sig = keypair.sign(&canonical);
1121 icp.x = URL_SAFE_NO_PAD.encode(sig.as_ref());
1122
1123 let threshold = icp.kt.parse().unwrap();
1124 let next_threshold = icp.nt.parse().unwrap();
1125 let state = KeyState::from_inception(
1126 icp.i.clone(),
1127 icp.k.clone(),
1128 vec![next_commitment],
1129 threshold,
1130 next_threshold,
1131 icp.d.clone(),
1132 );
1133
1134 let next_key_encoded = format!(
1136 "D{}",
1137 URL_SAFE_NO_PAD.encode(next_keypair.public_key().as_ref())
1138 );
1139 let third_pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
1140 let third_keypair = Ed25519KeyPair::from_pkcs8(third_pkcs8.as_ref()).unwrap();
1141 let third_commitment = compute_next_commitment(third_keypair.public_key().as_ref());
1142
1143 let mut rot = RotEvent {
1144 v: KERI_VERSION.to_string(),
1145 d: Said::default(),
1146 i: icp.i.clone(),
1147 s: KeriSequence::new(1),
1148 p: icp.d.clone(),
1149 kt: "1".to_string(),
1150 k: vec![next_key_encoded],
1151 nt: "1".to_string(),
1152 n: vec![third_commitment],
1153 bt: "0".to_string(),
1154 b: vec![],
1155 a: vec![],
1156 x: String::new(),
1157 };
1158
1159 let json = serde_json::to_vec(&Event::Rot(rot.clone())).unwrap();
1160 rot.d = compute_said(&json);
1161 let canonical = serialize_for_signing(&Event::Rot(rot.clone())).unwrap();
1162 let sig = next_keypair.sign(&canonical);
1163 rot.x = URL_SAFE_NO_PAD.encode(sig.as_ref());
1164
1165 let result = verify_event_crypto(&Event::Rot(rot), Some(&state));
1166 assert!(result.is_ok());
1167 }
1168
1169 #[test]
1174 fn parse_threshold_valid() {
1175 assert_eq!(parse_threshold("1").unwrap(), 1);
1176 assert_eq!(parse_threshold("42").unwrap(), 42);
1177 assert_eq!(parse_threshold("0").unwrap(), 0);
1178 }
1179
1180 #[test]
1181 fn parse_threshold_invalid() {
1182 assert!(matches!(
1183 parse_threshold("abc"),
1184 Err(ValidationError::MalformedSequence { .. })
1185 ));
1186 assert!(matches!(
1187 parse_threshold(""),
1188 Err(ValidationError::MalformedSequence { .. })
1189 ));
1190 assert!(matches!(
1191 parse_threshold("-1"),
1192 Err(ValidationError::MalformedSequence { .. })
1193 ));
1194 }
1195
1196 #[test]
1197 fn validate_inception_success() {
1198 let (icp, _keypair) = make_signed_icp();
1199 let state = validate_inception(&icp).unwrap();
1200 assert_eq!(state.prefix, icp.i);
1201 assert_eq!(state.sequence, 0);
1202 assert_eq!(state.last_event_said, icp.d);
1203 }
1204
1205 #[test]
1206 fn validate_inception_bad_signature() {
1207 let (mut icp, _keypair) = make_signed_icp();
1208 icp.x = URL_SAFE_NO_PAD.encode([0u8; 64]);
1209 let result = validate_inception(&icp);
1210 assert!(matches!(
1211 result,
1212 Err(ValidationError::SignatureFailed { sequence: 0 })
1213 ));
1214 }
1215
1216 #[test]
1217 fn verify_sequence_correct() {
1218 let (icp, keypair) = make_signed_icp();
1219 let ixn = make_signed_ixn(&icp.i, &icp.d, 1, &keypair);
1220 assert!(verify_sequence(&Event::Ixn(ixn), 1).is_ok());
1221 }
1222
1223 #[test]
1224 fn verify_sequence_mismatch() {
1225 let (icp, keypair) = make_signed_icp();
1226 let ixn = make_signed_ixn(&icp.i, &icp.d, 5, &keypair);
1227 let result = verify_sequence(&Event::Ixn(ixn), 1);
1228 assert!(matches!(
1229 result,
1230 Err(ValidationError::InvalidSequence {
1231 expected: 1,
1232 actual: 5
1233 })
1234 ));
1235 }
1236
1237 #[test]
1238 fn verify_chain_linkage_correct() {
1239 let (icp, keypair) = make_signed_icp();
1240 let ixn = make_signed_ixn(&icp.i, &icp.d, 1, &keypair);
1241 let state = validate_inception(&icp).unwrap();
1242 assert!(verify_chain_linkage(&Event::Ixn(ixn), &state).is_ok());
1243 }
1244
1245 #[test]
1246 fn verify_chain_linkage_broken() {
1247 let (icp, keypair) = make_signed_icp();
1248 let wrong_said = Said::new_unchecked("EWrongPrevious".to_string());
1249 let ixn = make_signed_ixn(&icp.i, &wrong_said, 1, &keypair);
1250 let state = validate_inception(&icp).unwrap();
1251 let result = verify_chain_linkage(&Event::Ixn(ixn), &state);
1252 assert!(matches!(result, Err(ValidationError::BrokenChain { .. })));
1253 }
1254
1255 #[test]
1256 fn validate_rotation_bad_commitment() {
1257 use auths_core::crypto::said::compute_next_commitment;
1258
1259 let rng = SystemRandom::new();
1260
1261 let pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
1262 let keypair = Ed25519KeyPair::from_pkcs8(pkcs8.as_ref()).unwrap();
1263 let key_encoded = format!("D{}", URL_SAFE_NO_PAD.encode(keypair.public_key().as_ref()));
1264
1265 let next_pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
1266 let next_keypair = Ed25519KeyPair::from_pkcs8(next_pkcs8.as_ref()).unwrap();
1267 let next_commitment = compute_next_commitment(next_keypair.public_key().as_ref());
1268
1269 let icp = IcpEvent {
1270 v: KERI_VERSION.to_string(),
1271 d: Said::default(),
1272 i: Prefix::default(),
1273 s: KeriSequence::new(0),
1274 kt: "1".to_string(),
1275 k: vec![key_encoded],
1276 nt: "1".to_string(),
1277 n: vec![next_commitment],
1278 bt: "0".to_string(),
1279 b: vec![],
1280 a: vec![],
1281 x: String::new(),
1282 };
1283 let mut icp = finalize_icp_event(icp).unwrap();
1284 let canonical = serialize_for_signing(&Event::Icp(icp.clone())).unwrap();
1285 let sig = keypair.sign(&canonical);
1286 icp.x = URL_SAFE_NO_PAD.encode(sig.as_ref());
1287
1288 let mut state = validate_inception(&icp).unwrap();
1289
1290 let wrong_pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
1292 let wrong_keypair = Ed25519KeyPair::from_pkcs8(wrong_pkcs8.as_ref()).unwrap();
1293 let wrong_key_encoded = format!(
1294 "D{}",
1295 URL_SAFE_NO_PAD.encode(wrong_keypair.public_key().as_ref())
1296 );
1297 let wrong_commitment = compute_next_commitment(wrong_keypair.public_key().as_ref());
1298
1299 let mut rot = RotEvent {
1300 v: KERI_VERSION.to_string(),
1301 d: Said::default(),
1302 i: icp.i.clone(),
1303 s: KeriSequence::new(1),
1304 p: icp.d.clone(),
1305 kt: "1".to_string(),
1306 k: vec![wrong_key_encoded],
1307 nt: "1".to_string(),
1308 n: vec![wrong_commitment],
1309 bt: "0".to_string(),
1310 b: vec![],
1311 a: vec![],
1312 x: String::new(),
1313 };
1314
1315 let json = serde_json::to_vec(&Event::Rot(rot.clone())).unwrap();
1316 rot.d = compute_said(&json);
1317 let canonical = serialize_for_signing(&Event::Rot(rot.clone())).unwrap();
1318 let sig = wrong_keypair.sign(&canonical);
1319 rot.x = URL_SAFE_NO_PAD.encode(sig.as_ref());
1320
1321 let result = validate_rotation(&rot, &Event::Rot(rot.clone()), 1, &mut state);
1322 assert!(matches!(
1323 result,
1324 Err(ValidationError::CommitmentMismatch { sequence: 1 })
1325 ));
1326 }
1327
1328 #[test]
1329 fn validate_interaction_wrong_key() {
1330 let (icp, _keypair) = make_signed_icp();
1331 let mut state = validate_inception(&icp).unwrap();
1332
1333 let rng = SystemRandom::new();
1334 let wrong_pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
1335 let wrong_keypair = Ed25519KeyPair::from_pkcs8(wrong_pkcs8.as_ref()).unwrap();
1336 let ixn = make_signed_ixn(&icp.i, &icp.d, 1, &wrong_keypair);
1337
1338 let result = validate_interaction(&ixn, &Event::Ixn(ixn.clone()), 1, &mut state);
1339 assert!(matches!(
1340 result,
1341 Err(ValidationError::SignatureFailed { sequence: 1 })
1342 ));
1343 }
1344}