1use std::borrow::Borrow;
8use std::fmt;
9
10use auths_crypto::CryptoProvider;
11use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
12use serde::ser::SerializeMap;
13use serde::{Deserialize, Serialize, Serializer};
14
15#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
19#[error("Invalid KERI {type_name}: {reason}")]
20pub struct KeriTypeError {
21 pub type_name: &'static str,
23 pub reason: String,
25}
26
27fn validate_keri_derivation_code(s: &str, type_label: &'static str) -> Result<(), KeriTypeError> {
31 if s.is_empty() {
32 return Err(KeriTypeError {
33 type_name: type_label,
34 reason: "must not be empty".into(),
35 });
36 }
37 if !s.starts_with('E') {
38 return Err(KeriTypeError {
39 type_name: type_label,
40 reason: format!(
41 "must start with 'E' (Blake3 derivation code), got '{}'",
42 &s[..s.len().min(10)]
43 ),
44 });
45 }
46 Ok(())
47}
48
49#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
63#[repr(transparent)]
64pub struct Prefix(String);
65
66impl Prefix {
67 pub fn new(s: String) -> Result<Self, KeriTypeError> {
69 validate_keri_derivation_code(&s, "Prefix")?;
70 Ok(Self(s))
71 }
72
73 pub fn new_unchecked(s: String) -> Self {
75 Self(s)
76 }
77
78 pub fn as_str(&self) -> &str {
80 &self.0
81 }
82
83 pub fn into_inner(self) -> String {
85 self.0
86 }
87
88 pub fn is_empty(&self) -> bool {
90 self.0.is_empty()
91 }
92}
93
94impl fmt::Display for Prefix {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 f.write_str(&self.0)
97 }
98}
99
100impl AsRef<str> for Prefix {
101 fn as_ref(&self) -> &str {
102 &self.0
103 }
104}
105
106impl Borrow<str> for Prefix {
107 fn borrow(&self) -> &str {
108 &self.0
109 }
110}
111
112impl From<Prefix> for String {
113 fn from(p: Prefix) -> String {
114 p.0
115 }
116}
117
118impl PartialEq<str> for Prefix {
119 fn eq(&self, other: &str) -> bool {
120 self.0 == other
121 }
122}
123
124impl PartialEq<&str> for Prefix {
125 fn eq(&self, other: &&str) -> bool {
126 self.0 == *other
127 }
128}
129
130impl PartialEq<Prefix> for str {
131 fn eq(&self, other: &Prefix) -> bool {
132 self == other.0
133 }
134}
135
136impl PartialEq<Prefix> for &str {
137 fn eq(&self, other: &Prefix) -> bool {
138 *self == other.0
139 }
140}
141
142#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
159#[repr(transparent)]
160pub struct Said(String);
161
162impl Said {
163 pub fn new(s: String) -> Result<Self, KeriTypeError> {
165 validate_keri_derivation_code(&s, "Said")?;
166 Ok(Self(s))
167 }
168
169 pub fn new_unchecked(s: String) -> Self {
171 Self(s)
172 }
173
174 pub fn as_str(&self) -> &str {
176 &self.0
177 }
178
179 pub fn into_inner(self) -> String {
181 self.0
182 }
183
184 pub fn is_empty(&self) -> bool {
186 self.0.is_empty()
187 }
188}
189
190impl fmt::Display for Said {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 f.write_str(&self.0)
193 }
194}
195
196impl AsRef<str> for Said {
197 fn as_ref(&self) -> &str {
198 &self.0
199 }
200}
201
202impl Borrow<str> for Said {
203 fn borrow(&self) -> &str {
204 &self.0
205 }
206}
207
208impl From<Said> for String {
209 fn from(s: Said) -> String {
210 s.0
211 }
212}
213
214impl PartialEq<str> for Said {
215 fn eq(&self, other: &str) -> bool {
216 self.0 == other
217 }
218}
219
220impl PartialEq<&str> for Said {
221 fn eq(&self, other: &&str) -> bool {
222 self.0 == *other
223 }
224}
225
226impl PartialEq<Said> for str {
227 fn eq(&self, other: &Said) -> bool {
228 self == other.0
229 }
230}
231
232impl PartialEq<Said> for &str {
233 fn eq(&self, other: &Said) -> bool {
234 *self == other.0
235 }
236}
237
238#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
242pub enum KeriVerifyError {
243 #[error("Invalid SAID: expected {expected}, got {actual}")]
245 InvalidSaid {
246 expected: Said,
248 actual: Said,
250 },
251 #[error("Broken chain at seq {sequence}: references {referenced}, previous was {actual}")]
253 BrokenChain {
254 sequence: u64,
256 referenced: Said,
258 actual: Said,
260 },
261 #[error("Invalid sequence: expected {expected}, got {actual}")]
263 InvalidSequence {
264 expected: u64,
266 actual: u64,
268 },
269 #[error("Pre-rotation commitment mismatch at sequence {sequence}")]
271 CommitmentMismatch {
272 sequence: u64,
274 },
275 #[error("Signature verification failed at sequence {sequence}")]
277 SignatureFailed {
278 sequence: u64,
280 },
281 #[error("First event must be inception")]
283 NotInception,
284 #[error("Empty KEL")]
286 EmptyKel,
287 #[error("Multiple inception events")]
289 MultipleInceptions,
290 #[error("Serialization error: {0}")]
292 Serialization(String),
293 #[error("Invalid key encoding: {0}")]
295 InvalidKey(String),
296 #[error("Malformed sequence number: {raw:?}")]
298 MalformedSequence {
299 raw: String,
301 },
302}
303
304use auths_crypto::KeriPublicKey;
305
306#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
308#[serde(tag = "t")]
309pub enum KeriEvent {
310 #[serde(rename = "icp")]
312 Inception(IcpEvent),
313 #[serde(rename = "rot")]
315 Rotation(RotEvent),
316 #[serde(rename = "ixn")]
318 Interaction(IxnEvent),
319}
320
321impl Serialize for KeriEvent {
322 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
323 match self {
324 KeriEvent::Inception(e) => e.serialize(serializer),
325 KeriEvent::Rotation(e) => e.serialize(serializer),
326 KeriEvent::Interaction(e) => e.serialize(serializer),
327 }
328 }
329}
330
331impl KeriEvent {
332 pub fn said(&self) -> &Said {
334 match self {
335 KeriEvent::Inception(e) => &e.d,
336 KeriEvent::Rotation(e) => &e.d,
337 KeriEvent::Interaction(e) => &e.d,
338 }
339 }
340
341 pub fn signature(&self) -> &str {
343 match self {
344 KeriEvent::Inception(e) => &e.x,
345 KeriEvent::Rotation(e) => &e.x,
346 KeriEvent::Interaction(e) => &e.x,
347 }
348 }
349
350 pub fn sequence(&self) -> Result<u64, KeriVerifyError> {
352 let s = match self {
353 KeriEvent::Inception(e) => &e.s,
354 KeriEvent::Rotation(e) => &e.s,
355 KeriEvent::Interaction(e) => &e.s,
356 };
357 s.parse::<u64>()
358 .map_err(|_| KeriVerifyError::MalformedSequence { raw: s.clone() })
359 }
360}
361
362#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
364pub struct IcpEvent {
365 pub v: String,
367 #[serde(default)]
369 pub d: Said,
370 pub i: Prefix,
372 pub s: String,
374 #[serde(default)]
376 pub kt: String,
377 pub k: Vec<String>,
379 #[serde(default)]
381 pub nt: String,
382 pub n: Vec<String>,
384 #[serde(default)]
386 pub bt: String,
387 #[serde(default)]
389 pub b: Vec<String>,
390 #[serde(default)]
392 pub a: Vec<Seal>,
393 #[serde(default)]
395 pub x: String,
396}
397
398impl Serialize for IcpEvent {
400 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
401 let field_count = 12 + (!self.d.is_empty() as usize) + (!self.x.is_empty() as usize);
402 let mut map = serializer.serialize_map(Some(field_count))?;
403 map.serialize_entry("v", &self.v)?;
404 map.serialize_entry("t", "icp")?;
405 if !self.d.is_empty() {
406 map.serialize_entry("d", &self.d)?;
407 }
408 map.serialize_entry("i", &self.i)?;
409 map.serialize_entry("s", &self.s)?;
410 map.serialize_entry("kt", &self.kt)?;
411 map.serialize_entry("k", &self.k)?;
412 map.serialize_entry("nt", &self.nt)?;
413 map.serialize_entry("n", &self.n)?;
414 map.serialize_entry("bt", &self.bt)?;
415 map.serialize_entry("b", &self.b)?;
416 map.serialize_entry("a", &self.a)?;
417 if !self.x.is_empty() {
418 map.serialize_entry("x", &self.x)?;
419 }
420 map.end()
421 }
422}
423
424#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
426pub struct RotEvent {
427 pub v: String,
429 #[serde(default)]
431 pub d: Said,
432 pub i: Prefix,
434 pub s: String,
436 pub p: Said,
438 #[serde(default)]
440 pub kt: String,
441 pub k: Vec<String>,
443 #[serde(default)]
445 pub nt: String,
446 pub n: Vec<String>,
448 #[serde(default)]
450 pub bt: String,
451 #[serde(default)]
453 pub b: Vec<String>,
454 #[serde(default)]
456 pub a: Vec<Seal>,
457 #[serde(default)]
459 pub x: String,
460}
461
462impl Serialize for RotEvent {
464 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
465 let field_count = 13 + (!self.d.is_empty() as usize) + (!self.x.is_empty() as usize);
466 let mut map = serializer.serialize_map(Some(field_count))?;
467 map.serialize_entry("v", &self.v)?;
468 map.serialize_entry("t", "rot")?;
469 if !self.d.is_empty() {
470 map.serialize_entry("d", &self.d)?;
471 }
472 map.serialize_entry("i", &self.i)?;
473 map.serialize_entry("s", &self.s)?;
474 map.serialize_entry("p", &self.p)?;
475 map.serialize_entry("kt", &self.kt)?;
476 map.serialize_entry("k", &self.k)?;
477 map.serialize_entry("nt", &self.nt)?;
478 map.serialize_entry("n", &self.n)?;
479 map.serialize_entry("bt", &self.bt)?;
480 map.serialize_entry("b", &self.b)?;
481 map.serialize_entry("a", &self.a)?;
482 if !self.x.is_empty() {
483 map.serialize_entry("x", &self.x)?;
484 }
485 map.end()
486 }
487}
488
489#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
491pub struct IxnEvent {
492 pub v: String,
494 #[serde(default)]
496 pub d: Said,
497 pub i: Prefix,
499 pub s: String,
501 pub p: Said,
503 pub a: Vec<Seal>,
505 #[serde(default)]
507 pub x: String,
508}
509
510impl Serialize for IxnEvent {
512 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
513 let field_count = 7 + (!self.d.is_empty() as usize) + (!self.x.is_empty() as usize);
514 let mut map = serializer.serialize_map(Some(field_count))?;
515 map.serialize_entry("v", &self.v)?;
516 map.serialize_entry("t", "ixn")?;
517 if !self.d.is_empty() {
518 map.serialize_entry("d", &self.d)?;
519 }
520 map.serialize_entry("i", &self.i)?;
521 map.serialize_entry("s", &self.s)?;
522 map.serialize_entry("p", &self.p)?;
523 map.serialize_entry("a", &self.a)?;
524 if !self.x.is_empty() {
525 map.serialize_entry("x", &self.x)?;
526 }
527 map.end()
528 }
529}
530
531#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
533pub struct Seal {
534 pub d: Said,
536 #[serde(rename = "type")]
538 pub seal_type: String,
539}
540
541#[derive(Debug, Clone, Serialize)]
543pub struct KeriKeyState {
544 pub prefix: Prefix,
546
547 #[serde(skip)]
549 pub current_key: Vec<u8>,
550
551 pub current_key_encoded: String,
553
554 pub next_commitment: Option<String>,
556
557 pub sequence: u64,
559
560 pub is_abandoned: bool,
562
563 pub last_event_said: Said,
565}
566
567pub async fn verify_kel(
582 events: &[KeriEvent],
583 provider: &dyn CryptoProvider,
584) -> Result<KeriKeyState, KeriVerifyError> {
585 if events.is_empty() {
586 return Err(KeriVerifyError::EmptyKel);
587 }
588
589 let KeriEvent::Inception(icp) = &events[0] else {
590 return Err(KeriVerifyError::NotInception);
591 };
592
593 verify_event_said(&events[0])?;
594
595 let icp_key = icp
596 .k
597 .first()
598 .ok_or(KeriVerifyError::SignatureFailed { sequence: 0 })?;
599 verify_event_signature(&events[0], icp_key, provider).await?;
600
601 let current_key = decode_key(icp_key)?;
602 let current_key_encoded = icp_key.clone();
603
604 let mut state = KeriKeyState {
605 prefix: icp.i.clone(),
606 current_key,
607 current_key_encoded,
608 next_commitment: icp.n.first().cloned(),
609 sequence: 0,
610 is_abandoned: icp.n.is_empty(),
611 last_event_said: icp.d.clone(),
612 };
613
614 for (idx, event) in events.iter().enumerate().skip(1) {
615 let expected_seq = idx as u64;
616
617 verify_event_said(event)?;
618
619 match event {
620 KeriEvent::Rotation(rot) => {
621 let actual_seq = event.sequence()?;
622 if actual_seq != expected_seq {
623 return Err(KeriVerifyError::InvalidSequence {
624 expected: expected_seq,
625 actual: actual_seq,
626 });
627 }
628
629 if rot.p != state.last_event_said {
630 return Err(KeriVerifyError::BrokenChain {
631 sequence: actual_seq,
632 referenced: rot.p.clone(),
633 actual: state.last_event_said.clone(),
634 });
635 }
636
637 if !rot.k.is_empty() {
638 verify_event_signature(event, &rot.k[0], provider).await?;
639
640 let new_key_bytes = decode_key(&rot.k[0])?;
641
642 if let Some(commitment) = &state.next_commitment
643 && !verify_commitment(&new_key_bytes, commitment)
644 {
645 return Err(KeriVerifyError::CommitmentMismatch {
646 sequence: actual_seq,
647 });
648 }
649
650 state.current_key = new_key_bytes;
651 state.current_key_encoded = rot.k[0].clone();
652 }
653
654 state.next_commitment = rot.n.first().cloned();
655 state.is_abandoned = rot.n.is_empty();
656 state.sequence = actual_seq;
657 state.last_event_said = rot.d.clone();
658 }
659 KeriEvent::Interaction(ixn) => {
660 let actual_seq = event.sequence()?;
661 if actual_seq != expected_seq {
662 return Err(KeriVerifyError::InvalidSequence {
663 expected: expected_seq,
664 actual: actual_seq,
665 });
666 }
667
668 if ixn.p != state.last_event_said {
669 return Err(KeriVerifyError::BrokenChain {
670 sequence: actual_seq,
671 referenced: ixn.p.clone(),
672 actual: state.last_event_said.clone(),
673 });
674 }
675
676 verify_event_signature(event, &state.current_key_encoded, provider).await?;
677
678 state.sequence = actual_seq;
679 state.last_event_said = ixn.d.clone();
680 }
681 KeriEvent::Inception(_) => {
682 return Err(KeriVerifyError::MultipleInceptions);
683 }
684 }
685 }
686
687 Ok(state)
688}
689
690fn serialize_for_signing(event: &KeriEvent) -> Result<Vec<u8>, KeriVerifyError> {
694 match event {
695 KeriEvent::Inception(e) => {
696 let mut copy = e.clone();
697 copy.d = Said::default();
698 copy.i = Prefix::default();
699 copy.x = String::new();
700 serde_json::to_vec(&KeriEvent::Inception(copy))
701 }
702 KeriEvent::Rotation(e) => {
703 let mut copy = e.clone();
704 copy.d = Said::default();
705 copy.x = String::new();
706 serde_json::to_vec(&KeriEvent::Rotation(copy))
707 }
708 KeriEvent::Interaction(e) => {
709 let mut copy = e.clone();
710 copy.d = Said::default();
711 copy.x = String::new();
712 serde_json::to_vec(&KeriEvent::Interaction(copy))
713 }
714 }
715 .map_err(|e| KeriVerifyError::Serialization(e.to_string()))
716}
717
718fn verify_event_said(event: &KeriEvent) -> Result<(), KeriVerifyError> {
720 let json = serialize_for_signing(event)?;
721 let computed = compute_said(&json);
722 let said = event.said();
723
724 if computed != *said {
725 return Err(KeriVerifyError::InvalidSaid {
726 expected: computed,
727 actual: said.clone(),
728 });
729 }
730
731 Ok(())
732}
733
734async fn verify_event_signature(
736 event: &KeriEvent,
737 signing_key: &str,
738 provider: &dyn CryptoProvider,
739) -> Result<(), KeriVerifyError> {
740 let sequence = event.sequence()?;
741
742 let sig_str = event.signature();
743 if sig_str.is_empty() {
744 return Err(KeriVerifyError::SignatureFailed { sequence });
745 }
746 let sig_bytes = URL_SAFE_NO_PAD
747 .decode(sig_str)
748 .map_err(|_| KeriVerifyError::SignatureFailed { sequence })?;
749
750 let key_bytes =
751 decode_key(signing_key).map_err(|_| KeriVerifyError::SignatureFailed { sequence })?;
752
753 let canonical = serialize_for_signing(event)?;
754
755 provider
756 .verify_ed25519(&key_bytes, &canonical, &sig_bytes)
757 .await
758 .map_err(|_| KeriVerifyError::SignatureFailed { sequence })?;
759
760 Ok(())
761}
762
763pub fn compute_said(data: &[u8]) -> Said {
766 let hash = blake3::hash(data);
767 Said::new_unchecked(format!("E{}", URL_SAFE_NO_PAD.encode(hash.as_bytes())))
768}
769
770fn compute_commitment(public_key: &[u8]) -> String {
773 let hash = blake3::hash(public_key);
774 format!("E{}", URL_SAFE_NO_PAD.encode(hash.as_bytes()))
775}
776
777fn verify_commitment(public_key: &[u8], commitment: &str) -> bool {
779 compute_commitment(public_key) == commitment
780}
781
782fn decode_key(key_str: &str) -> Result<Vec<u8>, KeriVerifyError> {
784 KeriPublicKey::parse(key_str)
785 .map(|k| k.into_bytes().to_vec())
786 .map_err(|e| KeriVerifyError::InvalidKey(e.to_string()))
787}
788
789pub fn find_seal_in_kel(events: &[KeriEvent], digest: &str) -> Option<u64> {
793 for event in events {
794 if let KeriEvent::Interaction(ixn) = event {
795 for seal in &ixn.a {
796 if seal.d.as_str() == digest {
797 return ixn.s.parse::<u64>().ok();
798 }
799 }
800 }
801 }
802 None
803}
804
805pub fn parse_kel_json(json: &str) -> Result<Vec<KeriEvent>, KeriVerifyError> {
807 serde_json::from_str(json).map_err(|e| KeriVerifyError::Serialization(e.to_string()))
808}
809
810#[cfg(all(test, not(target_arch = "wasm32")))]
811mod tests {
812 use super::*;
813 use auths_crypto::RingCryptoProvider;
814 use ring::rand::SystemRandom;
815 use ring::signature::{Ed25519KeyPair, KeyPair};
816
817 fn provider() -> RingCryptoProvider {
818 RingCryptoProvider
819 }
820
821 fn finalize_icp(mut icp: IcpEvent) -> IcpEvent {
822 icp.d = Said::default();
823 icp.i = Prefix::default();
824 icp.x = String::new();
825 let json = serde_json::to_vec(&KeriEvent::Inception(icp.clone())).unwrap();
826 let said = compute_said(&json);
827 icp.i = Prefix::new_unchecked(said.as_str().to_string());
828 icp.d = said;
829 icp
830 }
831
832 fn make_signed_icp(keypair: &Ed25519KeyPair, next_commitment: &str) -> IcpEvent {
835 let key_encoded = format!("D{}", URL_SAFE_NO_PAD.encode(keypair.public_key().as_ref()));
836
837 let mut icp = IcpEvent {
838 v: "KERI10JSON".into(),
839 d: Said::default(),
840 i: Prefix::default(),
841 s: "0".into(),
842 kt: "1".into(),
843 k: vec![key_encoded],
844 nt: "1".into(),
845 n: vec![next_commitment.to_string()],
846 bt: "0".into(),
847 b: vec![],
848 a: vec![],
849 x: String::new(),
850 };
851
852 icp = finalize_icp(icp);
854
855 let canonical = serialize_for_signing(&KeriEvent::Inception(icp.clone())).unwrap();
857 let sig = keypair.sign(&canonical);
858 icp.x = URL_SAFE_NO_PAD.encode(sig.as_ref());
859
860 icp
861 }
862
863 fn make_signed_rot(
864 prefix: &str,
865 prev_said: &str,
866 seq: u64,
867 new_keypair: &Ed25519KeyPair,
868 next_commitment: &str,
869 ) -> RotEvent {
870 let key_encoded = format!(
871 "D{}",
872 URL_SAFE_NO_PAD.encode(new_keypair.public_key().as_ref())
873 );
874
875 let mut rot = RotEvent {
876 v: "KERI10JSON".into(),
877 d: Said::default(),
878 i: Prefix::new_unchecked(prefix.to_string()),
879 s: seq.to_string(),
880 p: Said::new_unchecked(prev_said.to_string()),
881 kt: "1".into(),
882 k: vec![key_encoded],
883 nt: "1".into(),
884 n: vec![next_commitment.to_string()],
885 bt: "0".into(),
886 b: vec![],
887 a: vec![],
888 x: String::new(),
889 };
890
891 let json = serialize_for_signing(&KeriEvent::Rotation(rot.clone())).unwrap();
893 rot.d = compute_said(&json);
894
895 let canonical = serialize_for_signing(&KeriEvent::Rotation(rot.clone())).unwrap();
897 let sig = new_keypair.sign(&canonical);
898 rot.x = URL_SAFE_NO_PAD.encode(sig.as_ref());
899
900 rot
901 }
902
903 fn make_signed_ixn(
904 prefix: &str,
905 prev_said: &str,
906 seq: u64,
907 keypair: &Ed25519KeyPair,
908 seals: Vec<Seal>,
909 ) -> IxnEvent {
910 let mut ixn = IxnEvent {
911 v: "KERI10JSON".into(),
912 d: Said::default(),
913 i: Prefix::new_unchecked(prefix.to_string()),
914 s: seq.to_string(),
915 p: Said::new_unchecked(prev_said.to_string()),
916 a: seals,
917 x: String::new(),
918 };
919
920 let json = serialize_for_signing(&KeriEvent::Interaction(ixn.clone())).unwrap();
922 ixn.d = compute_said(&json);
923
924 let canonical = serialize_for_signing(&KeriEvent::Interaction(ixn.clone())).unwrap();
926 let sig = keypair.sign(&canonical);
927 ixn.x = URL_SAFE_NO_PAD.encode(sig.as_ref());
928
929 ixn
930 }
931
932 fn generate_keypair() -> (Ed25519KeyPair, Vec<u8>) {
933 let rng = SystemRandom::new();
934 let pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
935 let pkcs8_bytes = pkcs8.as_ref().to_vec();
936 let keypair = Ed25519KeyPair::from_pkcs8(&pkcs8_bytes).unwrap();
937 (keypair, pkcs8_bytes)
938 }
939
940 #[tokio::test]
943 async fn rejects_empty_kel() {
944 let result = verify_kel(&[], &provider()).await;
945 assert!(matches!(result, Err(KeriVerifyError::EmptyKel)));
946 }
947
948 #[tokio::test]
949 async fn rejects_non_inception_first() {
950 let ixn = KeriEvent::Interaction(IxnEvent {
951 v: "KERI10JSON".into(),
952 d: Said::new_unchecked("EIXN".into()),
953 i: Prefix::new_unchecked("EPrefix".into()),
954 s: "0".into(),
955 p: Said::new_unchecked("EPrev".into()),
956 a: vec![],
957 x: String::new(),
958 });
959
960 let result = verify_kel(&[ixn], &provider()).await;
961 assert!(matches!(result, Err(KeriVerifyError::NotInception)));
962 }
963
964 #[test]
965 fn find_seal_locates_attestation_sync() {
966 let (kp1, _) = generate_keypair();
967 let (kp2, _) = generate_keypair();
968 let next_commitment = compute_commitment(kp2.public_key().as_ref());
969
970 let icp = make_signed_icp(&kp1, &next_commitment);
971
972 let ixn = make_signed_ixn(
974 icp.i.as_str(),
975 icp.d.as_str(),
976 1,
977 &kp1,
978 vec![Seal {
979 d: Said::new_unchecked("EAttDigest".into()),
980 seal_type: "device-attestation".into(),
981 }],
982 );
983
984 let events = vec![KeriEvent::Inception(icp), KeriEvent::Interaction(ixn)];
985
986 let found = find_seal_in_kel(&events, "EAttDigest");
987 assert_eq!(found, Some(1));
988
989 let not_found = find_seal_in_kel(&events, "ENotExist");
990 assert_eq!(not_found, None);
991 }
992
993 #[test]
994 fn decode_key_works() {
995 let key_bytes = [42u8; 32];
996 let encoded = format!("D{}", URL_SAFE_NO_PAD.encode(key_bytes));
997
998 let decoded = decode_key(&encoded).unwrap();
999 assert_eq!(decoded, key_bytes);
1000 }
1001
1002 #[test]
1003 fn decode_key_rejects_unknown_code() {
1004 let result = decode_key("Xsomething");
1005 assert!(matches!(result, Err(KeriVerifyError::InvalidKey(_))));
1006 }
1007
1008 #[tokio::test]
1011 async fn verify_signed_inception() {
1012 let (kp1, _) = generate_keypair();
1013 let (kp2, _) = generate_keypair();
1014 let next_commitment = compute_commitment(kp2.public_key().as_ref());
1015
1016 let icp = make_signed_icp(&kp1, &next_commitment);
1017 let events = vec![KeriEvent::Inception(icp.clone())];
1018
1019 let state = verify_kel(&events, &provider()).await.unwrap();
1020 assert_eq!(state.prefix, icp.i);
1021 assert_eq!(state.current_key, kp1.public_key().as_ref());
1022 assert_eq!(state.sequence, 0);
1023 assert!(!state.is_abandoned);
1024 }
1025
1026 #[tokio::test]
1027 async fn verify_icp_rot_ixn_signed() {
1028 let (kp1, _) = generate_keypair();
1029 let (kp2, _) = generate_keypair();
1030 let (kp3, _) = generate_keypair();
1031
1032 let next1_commitment = compute_commitment(kp2.public_key().as_ref());
1033 let next2_commitment = compute_commitment(kp3.public_key().as_ref());
1034
1035 let icp = make_signed_icp(&kp1, &next1_commitment);
1036 let rot = make_signed_rot(icp.i.as_str(), icp.d.as_str(), 1, &kp2, &next2_commitment);
1037 let ixn = make_signed_ixn(
1038 icp.i.as_str(),
1039 rot.d.as_str(),
1040 2,
1041 &kp2,
1042 vec![Seal {
1043 d: Said::new_unchecked("EAttest".into()),
1044 seal_type: "device-attestation".into(),
1045 }],
1046 );
1047
1048 let events = vec![
1049 KeriEvent::Inception(icp.clone()),
1050 KeriEvent::Rotation(rot),
1051 KeriEvent::Interaction(ixn),
1052 ];
1053
1054 let state = verify_kel(&events, &provider()).await.unwrap();
1055 assert_eq!(state.prefix, icp.i);
1056 assert_eq!(state.current_key, kp2.public_key().as_ref());
1057 assert_eq!(state.sequence, 2);
1058 }
1059
1060 #[tokio::test]
1061 async fn rejects_forged_signature() {
1062 let (kp1, _) = generate_keypair();
1063 let (kp2, _) = generate_keypair();
1064 let next_commitment = compute_commitment(kp2.public_key().as_ref());
1065
1066 let mut icp = make_signed_icp(&kp1, &next_commitment);
1067 icp.x = URL_SAFE_NO_PAD.encode([0u8; 64]);
1068
1069 let events = vec![KeriEvent::Inception(icp)];
1070 let result = verify_kel(&events, &provider()).await;
1071 assert!(matches!(
1072 result,
1073 Err(KeriVerifyError::SignatureFailed { sequence: 0 })
1074 ));
1075 }
1076
1077 #[tokio::test]
1078 async fn rejects_missing_signature() {
1079 let (kp1, _) = generate_keypair();
1080 let (kp2, _) = generate_keypair();
1081 let next_commitment = compute_commitment(kp2.public_key().as_ref());
1082
1083 let mut icp = make_signed_icp(&kp1, &next_commitment);
1084 icp.x = String::new();
1085
1086 let events = vec![KeriEvent::Inception(icp)];
1087 let result = verify_kel(&events, &provider()).await;
1088 assert!(matches!(
1089 result,
1090 Err(KeriVerifyError::SignatureFailed { sequence: 0 })
1091 ));
1092 }
1093
1094 #[tokio::test]
1095 async fn rejects_wrong_key_signature() {
1096 let (kp1, _) = generate_keypair();
1097 let (kp_wrong, _) = generate_keypair();
1098 let (kp2, _) = generate_keypair();
1099 let next_commitment = compute_commitment(kp2.public_key().as_ref());
1100
1101 let key_encoded = format!("D{}", URL_SAFE_NO_PAD.encode(kp1.public_key().as_ref()));
1102 let mut icp = IcpEvent {
1103 v: "KERI10JSON".into(),
1104 d: Said::default(),
1105 i: Prefix::default(),
1106 s: "0".into(),
1107 kt: "1".into(),
1108 k: vec![key_encoded],
1109 nt: "1".into(),
1110 n: vec![next_commitment],
1111 bt: "0".into(),
1112 b: vec![],
1113 a: vec![],
1114 x: String::new(),
1115 };
1116 icp = finalize_icp(icp);
1117
1118 let canonical = serialize_for_signing(&KeriEvent::Inception(icp.clone())).unwrap();
1119 let sig = kp_wrong.sign(&canonical);
1120 icp.x = URL_SAFE_NO_PAD.encode(sig.as_ref());
1121
1122 let events = vec![KeriEvent::Inception(icp)];
1123 let result = verify_kel(&events, &provider()).await;
1124 assert!(matches!(
1125 result,
1126 Err(KeriVerifyError::SignatureFailed { sequence: 0 })
1127 ));
1128 }
1129
1130 #[tokio::test]
1131 async fn rejects_rot_signed_with_old_key() {
1132 let (kp1, _) = generate_keypair();
1133 let (kp2, _) = generate_keypair();
1134 let (kp3, _) = generate_keypair();
1135
1136 let next1_commitment = compute_commitment(kp2.public_key().as_ref());
1137 let next2_commitment = compute_commitment(kp3.public_key().as_ref());
1138
1139 let icp = make_signed_icp(&kp1, &next1_commitment);
1140
1141 let key2_encoded = format!("D{}", URL_SAFE_NO_PAD.encode(kp2.public_key().as_ref()));
1142 let mut rot = RotEvent {
1143 v: "KERI10JSON".into(),
1144 d: Said::default(),
1145 i: icp.i.clone(),
1146 s: "1".into(),
1147 p: icp.d.clone(),
1148 kt: "1".into(),
1149 k: vec![key2_encoded],
1150 nt: "1".into(),
1151 n: vec![next2_commitment],
1152 bt: "0".into(),
1153 b: vec![],
1154 a: vec![],
1155 x: String::new(),
1156 };
1157
1158 let json = serialize_for_signing(&KeriEvent::Rotation(rot.clone())).unwrap();
1159 rot.d = compute_said(&json);
1160
1161 let canonical = serialize_for_signing(&KeriEvent::Rotation(rot.clone())).unwrap();
1162 let sig = kp1.sign(&canonical);
1163 rot.x = URL_SAFE_NO_PAD.encode(sig.as_ref());
1164
1165 let events = vec![KeriEvent::Inception(icp), KeriEvent::Rotation(rot)];
1166 let result = verify_kel(&events, &provider()).await;
1167 assert!(matches!(
1168 result,
1169 Err(KeriVerifyError::SignatureFailed { sequence: 1 })
1170 ));
1171 }
1172
1173 #[tokio::test]
1174 async fn rotation_updates_signing_key_for_ixn() {
1175 let (kp1, _) = generate_keypair();
1176 let (kp2, _) = generate_keypair();
1177 let (kp3, _) = generate_keypair();
1178
1179 let next1_commitment = compute_commitment(kp2.public_key().as_ref());
1180 let next2_commitment = compute_commitment(kp3.public_key().as_ref());
1181
1182 let icp = make_signed_icp(&kp1, &next1_commitment);
1183 let rot = make_signed_rot(icp.i.as_str(), icp.d.as_str(), 1, &kp2, &next2_commitment);
1184 let ixn = make_signed_ixn(icp.i.as_str(), rot.d.as_str(), 2, &kp1, vec![]);
1185
1186 let events = vec![
1187 KeriEvent::Inception(icp),
1188 KeriEvent::Rotation(rot),
1189 KeriEvent::Interaction(ixn),
1190 ];
1191 let result = verify_kel(&events, &provider()).await;
1192 assert!(matches!(
1193 result,
1194 Err(KeriVerifyError::SignatureFailed { sequence: 2 })
1195 ));
1196 }
1197
1198 #[tokio::test]
1199 async fn rejects_wrong_commitment() {
1200 let (kp1, _) = generate_keypair();
1201 let (kp2, _) = generate_keypair();
1202 let (kp_wrong, _) = generate_keypair();
1203 let (kp3, _) = generate_keypair();
1204
1205 let next1_commitment = compute_commitment(kp2.public_key().as_ref());
1206 let next2_commitment = compute_commitment(kp3.public_key().as_ref());
1207
1208 let icp = make_signed_icp(&kp1, &next1_commitment);
1209 let rot = make_signed_rot(
1210 icp.i.as_str(),
1211 icp.d.as_str(),
1212 1,
1213 &kp_wrong,
1214 &next2_commitment,
1215 );
1216
1217 let events = vec![KeriEvent::Inception(icp), KeriEvent::Rotation(rot)];
1218 let result = verify_kel(&events, &provider()).await;
1219 assert!(matches!(
1220 result,
1221 Err(KeriVerifyError::CommitmentMismatch { sequence: 1 })
1222 ));
1223 }
1224
1225 #[tokio::test]
1226 async fn rejects_broken_chain() {
1227 let (kp1, _) = generate_keypair();
1228 let (kp2, _) = generate_keypair();
1229 let next_commitment = compute_commitment(kp2.public_key().as_ref());
1230
1231 let icp = make_signed_icp(&kp1, &next_commitment);
1232
1233 let mut ixn = IxnEvent {
1234 v: "KERI10JSON".into(),
1235 d: Said::default(),
1236 i: icp.i.clone(),
1237 s: "1".into(),
1238 p: Said::new_unchecked("EWrongPrevious".into()),
1239 a: vec![],
1240 x: String::new(),
1241 };
1242
1243 let json = serialize_for_signing(&KeriEvent::Interaction(ixn.clone())).unwrap();
1244 ixn.d = compute_said(&json);
1245
1246 let canonical = serialize_for_signing(&KeriEvent::Interaction(ixn.clone())).unwrap();
1247 let sig = kp1.sign(&canonical);
1248 ixn.x = URL_SAFE_NO_PAD.encode(sig.as_ref());
1249
1250 let events = vec![KeriEvent::Inception(icp), KeriEvent::Interaction(ixn)];
1251 let result = verify_kel(&events, &provider()).await;
1252 assert!(matches!(result, Err(KeriVerifyError::BrokenChain { .. })));
1253 }
1254
1255 #[tokio::test]
1256 async fn verify_kel_with_rotation() {
1257 let (kp1, _) = generate_keypair();
1258 let (kp2, _) = generate_keypair();
1259 let (kp3, _) = generate_keypair();
1260
1261 let next1_commitment = compute_commitment(kp2.public_key().as_ref());
1262 let next2_commitment = compute_commitment(kp3.public_key().as_ref());
1263
1264 let icp = make_signed_icp(&kp1, &next1_commitment);
1265 let rot = make_signed_rot(icp.i.as_str(), icp.d.as_str(), 1, &kp2, &next2_commitment);
1266
1267 let events = vec![KeriEvent::Inception(icp), KeriEvent::Rotation(rot)];
1268
1269 let state = verify_kel(&events, &provider()).await.unwrap();
1270 assert_eq!(state.sequence, 1);
1271 assert_eq!(state.current_key, kp2.public_key().as_ref());
1272 }
1273
1274 #[test]
1275 fn rejects_malformed_sequence_number() {
1276 let icp = IcpEvent {
1278 v: "KERI10JSON".into(),
1279 d: Said::default(),
1280 i: Prefix::default(),
1281 s: "not_a_number".to_string(),
1282 kt: "1".to_string(),
1283 k: vec!["DKey".to_string()],
1284 nt: "1".to_string(),
1285 n: vec!["ENext".to_string()],
1286 bt: "0".to_string(),
1287 b: vec![],
1288 a: vec![],
1289 x: String::new(),
1290 };
1291
1292 let event = KeriEvent::Inception(icp);
1293 let result = event.sequence();
1294 assert!(
1295 matches!(result, Err(KeriVerifyError::MalformedSequence { .. })),
1296 "Expected MalformedSequence error, got: {:?}",
1297 result
1298 );
1299 }
1300}