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