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