1use serde::{Deserialize, Serialize};
22
23use crate::events::Event;
24use crate::types::{ConfigTrait, Prefix, Said, Threshold};
25use crate::witness::StoredReceipt;
26use crate::witness::agreement::{AgreementStatus, WitnessAgreement};
27use crate::{CesrKey, KeyState};
28
29pub const KSN_VERSION: u32 = 1;
31
32pub const KSN_TYPE: &str = "ksn";
34
35pub const KERI_KEY_STATE_VERSION: [u32; 2] = [1, 0];
38
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
45pub struct LatestEstablishmentEvent {
46 pub s: String,
48 pub d: Said,
50 #[serde(default)]
52 pub br: Vec<Prefix>,
53 #[serde(default)]
55 pub ba: Vec<Prefix>,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
74pub struct KeyStateRecord {
75 pub vn: [u32; 2],
77 pub i: Prefix,
79 pub s: String,
81 pub p: String,
83 pub d: Said,
85 pub f: String,
89 pub dt: String,
91 pub et: String,
93 pub kt: Threshold,
95 pub k: Vec<CesrKey>,
97 pub nt: Threshold,
99 pub n: Vec<Said>,
101 pub bt: Threshold,
103 pub b: Vec<Prefix>,
105 pub c: Vec<ConfigTrait>,
107 pub ee: LatestEstablishmentEvent,
109 pub di: String,
111}
112
113impl KeyStateRecord {
114 pub fn from_kel(events: &[Event], state: &KeyState, dt: impl Into<String>) -> Option<Self> {
127 let last = events.last()?;
128 let latest_est = events.iter().rev().find(|e| !e.is_interaction())?;
129 Some(Self {
130 vn: KERI_KEY_STATE_VERSION,
131 i: state.prefix.clone(),
132 s: format!("{:x}", state.sequence),
133 p: last
134 .previous()
135 .map(|s| s.as_str().to_string())
136 .unwrap_or_default(),
137 d: state.last_event_said.clone(),
138 f: format!("{:x}", state.sequence),
139 dt: dt.into(),
140 et: establishment_type(latest_est).to_string(),
141 kt: state.threshold.clone(),
142 k: state.current_keys.clone(),
143 nt: state.next_threshold.clone(),
144 n: state.next_commitment.clone(),
145 bt: state.backer_threshold.clone(),
146 b: state.backers.clone(),
147 c: state.config_traits.clone(),
148 ee: LatestEstablishmentEvent {
149 s: format!("{:x}", latest_est.sequence().value()),
150 d: latest_est.said().clone(),
151 br: Vec::new(),
152 ba: Vec::new(),
153 },
154 di: state
155 .delegator
156 .as_ref()
157 .map(|p| p.as_str().to_string())
158 .unwrap_or_default(),
159 })
160 }
161
162 pub fn sequence(&self) -> u128 {
165 u128::from_str_radix(self.s.trim_start_matches("0x"), 16).unwrap_or(0)
166 }
167
168 pub fn check_not_stale(&self, last_seen_seq: u128) -> Result<(), KsnError> {
179 let got = self.sequence();
180 if got < last_seen_seq {
181 return Err(KsnError::Stale {
182 got,
183 seen: last_seen_seq,
184 });
185 }
186 Ok(())
187 }
188
189 pub fn into_key_state(self) -> KeyState {
195 let sequence = u128::from_str_radix(self.s.trim_start_matches("0x"), 16).unwrap_or(0);
196 let last_est_seq =
197 u128::from_str_radix(self.ee.s.trim_start_matches("0x"), 16).unwrap_or(0);
198 let delegator = if self.di.is_empty() {
199 None
200 } else {
201 Some(Prefix::new_unchecked(self.di))
202 };
203 KeyState {
204 prefix: self.i,
205 current_keys: self.k,
206 next_commitment: self.n.clone(),
207 sequence,
208 last_event_said: self.d,
209 is_abandoned: self.n.is_empty() && self.et != "icp" && self.et != "dip",
210 threshold: self.kt,
211 next_threshold: self.nt,
212 backers: self.b,
213 backer_threshold: self.bt,
214 config_traits: self.c,
215 is_non_transferable: self.n.is_empty(),
216 delegator,
217 last_establishment_sequence: last_est_seq,
218 }
219 }
220}
221
222fn establishment_type(event: &Event) -> &'static str {
226 match event {
227 Event::Icp(_) => "icp",
228 Event::Rot(_) => "rot",
229 Event::Dip(_) => "dip",
230 Event::Drt(_) => "drt",
231 Event::Ixn(_) => "ixn",
232 }
233}
234
235#[derive(Debug, thiserror::Error)]
237#[non_exhaustive]
238pub enum KsnError {
239 #[error("KSN serialization failed: {0}")]
241 Serialize(String),
242
243 #[error("KSN signing failed: {0}")]
245 Signer(String),
246
247 #[error("KSN names no current key")]
250 NoCurrentKey,
251
252 #[error("not a KSN (t = {0:?})")]
254 WrongType(String),
255
256 #[error("KSN signature is invalid")]
258 BadSignature,
259
260 #[error("KSN current key is undecodable: {0}")]
262 UndecodableKey(String),
263
264 #[error("KSN is stale: seq {got} < last-seen {seen}")]
267 Stale {
268 got: u128,
270 seen: u128,
272 },
273}
274
275#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
277pub struct KeyStateNotice {
278 pub version: u32,
280 pub t: String,
282 pub state: KeyState,
285 pub dt: String,
288}
289
290impl KeyStateNotice {
291 pub fn new(state: KeyState, dt: impl Into<String>) -> Self {
297 Self {
298 version: KSN_VERSION,
299 t: KSN_TYPE.to_string(),
300 state,
301 dt: dt.into(),
302 }
303 }
304
305 pub fn canonical_bytes(&self) -> Result<Vec<u8>, KsnError> {
307 serde_json::to_vec(self).map_err(|e| KsnError::Serialize(e.to_string()))
308 }
309
310 pub fn signing_key(&self) -> Option<&CesrKey> {
312 self.state.current_keys.first()
313 }
314
315 pub fn sequence(&self) -> u128 {
317 self.state.sequence
318 }
319
320 pub fn names_delegated_device(&self) -> bool {
325 self.state.delegator.is_some()
326 }
327}
328
329#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
332pub struct SignedKsn {
333 pub notice: KeyStateNotice,
335 #[serde(with = "hex::serde")]
338 pub signature: Vec<u8>,
339 #[serde(default, skip_serializing_if = "Vec::is_empty")]
345 pub receipts: Vec<StoredReceipt>,
346}
347
348impl SignedKsn {
349 pub fn sign_with(
364 notice: KeyStateNotice,
365 signer: impl FnOnce(&[u8]) -> Result<Vec<u8>, String>,
366 ) -> Result<Self, KsnError> {
367 if notice.signing_key().is_none() {
368 return Err(KsnError::NoCurrentKey);
369 }
370 let bytes = notice.canonical_bytes()?;
371 let signature = signer(&bytes).map_err(KsnError::Signer)?;
372 Ok(Self {
373 notice,
374 signature,
375 receipts: Vec::new(),
376 })
377 }
378
379 pub fn with_receipts(mut self, candidates: Vec<StoredReceipt>) -> Self {
396 let state = &self.notice.state;
397 let said = state.last_event_said.clone();
398 let valid: Vec<StoredReceipt> = candidates
399 .into_iter()
400 .filter(|r| {
401 r.signed.receipt.d == said
402 && state.backers.iter().any(|b| b == &r.witness)
403 && receipt_signature_valid(r)
404 })
405 .collect();
406 self.receipts = valid;
407 self
408 }
409
410 pub fn verify(&self) -> Result<VerifiedKsn, KsnError> {
424 if self.notice.t != KSN_TYPE {
425 return Err(KsnError::WrongType(self.notice.t.clone()));
426 }
427 let key_cesr = self.notice.signing_key().ok_or(KsnError::NoCurrentKey)?;
428 let key = crate::KeriPublicKey::parse(key_cesr.as_str())
429 .map_err(|e| KsnError::UndecodableKey(e.to_string()))?;
430 let bytes = self.notice.canonical_bytes()?;
431 key.verify_signature(&bytes, &self.signature)
432 .map_err(|_| KsnError::BadSignature)?;
433 Ok(VerifiedKsn {
434 state: self.notice.state.clone(),
435 trust: self.witness_trust(),
436 })
437 }
438
439 fn witness_trust(&self) -> KsnTrust {
447 let state = &self.notice.state;
448 let required = state.backer_threshold.simple_value().unwrap_or(0) as usize;
449 if state.backers.is_empty() || required == 0 {
450 return KsnTrust::TrustOnFirstSight;
451 }
452
453 let said = &state.last_event_said;
454 let sn = state.sequence as u64;
455 let agreement = WitnessAgreement::new(1);
456 agreement.submit_event(
457 &state.prefix,
458 sn,
459 said,
460 &state.backer_threshold,
461 &state.backers,
462 );
463
464 let mut distinct = std::collections::HashSet::new();
465 for r in &self.receipts {
466 if &r.signed.receipt.d == said && state.backers.iter().any(|b| b == &r.witness) {
469 agreement.add_receipt(&state.prefix, sn, said, r.witness.as_str());
470 distinct.insert(r.witness.as_str());
471 }
472 }
473
474 match agreement.status(&state.prefix, sn, said) {
475 AgreementStatus::Accepted => KsnTrust::Witnessed {
476 receipts: distinct.len(),
477 threshold: required,
478 },
479 AgreementStatus::Pending { .. } => KsnTrust::TrustOnFirstSight,
480 }
481 }
482
483 pub fn check_not_stale(&self, last_seen_seq: u128) -> Result<(), KsnError> {
489 let got = self.notice.sequence();
490 if got < last_seen_seq {
491 return Err(KsnError::Stale {
492 got,
493 seen: last_seen_seq,
494 });
495 }
496 Ok(())
497 }
498}
499
500fn receipt_signature_valid(stored: &StoredReceipt) -> bool {
503 let Ok(key) = crate::KeriPublicKey::parse(stored.witness.as_str()) else {
504 return false;
505 };
506 let Ok(payload) = serde_json::to_vec(&stored.signed.receipt) else {
507 return false;
508 };
509 key.verify_signature(&payload, &stored.signed.signature)
510 .is_ok()
511}
512
513#[derive(Debug, Clone, Copy, PartialEq, Eq)]
519#[non_exhaustive]
520pub enum KsnTrust {
521 TrustOnFirstSight,
525 Witnessed {
531 receipts: usize,
533 threshold: usize,
535 },
536}
537
538#[derive(Debug, Clone, PartialEq, Eq)]
540pub struct VerifiedKsn {
541 pub state: KeyState,
543 pub trust: KsnTrust,
545}
546
547impl VerifiedKsn {
548 pub fn is_authoritative_over_kel(&self) -> bool {
554 false
555 }
556
557 pub fn satisfies_revocation_check(&self) -> bool {
562 false
563 }
564}
565
566#[cfg(test)]
567#[allow(clippy::unwrap_used, clippy::expect_used)]
568mod tests {
569 use super::*;
570 use crate::{KeriPublicKey, Prefix, Said, Threshold};
571 use ring::rand::SystemRandom;
572 use ring::signature::{Ed25519KeyPair, KeyPair};
573
574 fn real_keypair() -> Ed25519KeyPair {
575 let rng = SystemRandom::new();
576 let pkcs8 = Ed25519KeyPair::generate_pkcs8(&rng).unwrap();
577 Ed25519KeyPair::from_pkcs8(pkcs8.as_ref()).unwrap()
578 }
579
580 fn state_for_key(kp: &Ed25519KeyPair, seq: u128) -> KeyState {
582 let cesr = KeriPublicKey::ed25519(kp.public_key().as_ref())
583 .unwrap()
584 .to_qb64()
585 .unwrap();
586 let mut state = key_state(seq, false);
587 state.current_keys = vec![CesrKey::new_unchecked(cesr)];
588 state
589 }
590
591 fn sign_ksn(kp: &Ed25519KeyPair, notice: KeyStateNotice) -> SignedKsn {
592 SignedKsn::sign_with(notice, |bytes| Ok(kp.sign(bytes).as_ref().to_vec())).unwrap()
593 }
594
595 fn key_state(seq: u128, delegated: bool) -> KeyState {
596 let key = KeriPublicKey::ed25519(&[3u8; 32]).unwrap();
597 let mut state = KeyState::from_inception(
598 Prefix::new_unchecked("EksnTestPrefix000000000000000000000000000000".to_string()),
599 vec![CesrKey::new_unchecked(key.to_qb64().unwrap())],
600 vec![Said::new_unchecked(
601 "ENextCommitment0000000000000000000000000000".to_string(),
602 )],
603 Threshold::Simple(1),
604 Threshold::Simple(1),
605 Said::new_unchecked("ELastEvent00000000000000000000000000000000000".to_string()),
606 vec![],
607 Threshold::Simple(0),
608 vec![],
609 );
610 state.sequence = seq;
611 if delegated {
612 state.delegator = Some(Prefix::new_unchecked(
613 "ERootDelegator00000000000000000000000000000".to_string(),
614 ));
615 }
616 state
617 }
618
619 #[test]
620 fn canonical_bytes_is_deterministic() {
621 let notice = KeyStateNotice::new(key_state(2, false), "2026-06-03T00:00:00Z");
622 assert_eq!(
623 notice.canonical_bytes().unwrap(),
624 notice.canonical_bytes().unwrap()
625 );
626 }
627
628 #[test]
629 fn sign_with_and_round_trips() {
630 let notice = KeyStateNotice::new(key_state(0, false), "2026-06-03T00:00:00Z");
631 let signed = SignedKsn::sign_with(notice, |_| Ok(vec![7u8; 64])).unwrap();
632 assert_eq!(signed.signature, vec![7u8; 64]);
633 assert!(signed.receipts.is_empty());
634
635 let json = serde_json::to_string(&signed).unwrap();
636 assert!(!json.contains("receipts"));
638 let parsed: SignedKsn = serde_json::from_str(&json).unwrap();
640 assert_eq!(parsed, signed);
641 }
642
643 #[test]
644 fn sign_with_rejects_no_current_key() {
645 let mut state = key_state(0, false);
646 state.current_keys.clear();
647 let notice = KeyStateNotice::new(state, "2026-06-03T00:00:00Z");
648 let err = SignedKsn::sign_with(notice, |_| Ok(vec![0u8; 64])).unwrap_err();
649 assert!(matches!(err, KsnError::NoCurrentKey));
650 }
651
652 #[test]
653 fn signer_error_propagates() {
654 let notice = KeyStateNotice::new(key_state(0, false), "2026-06-03T00:00:00Z");
655 let err = SignedKsn::sign_with(notice, |_| Err("keychain locked".to_string())).unwrap_err();
656 assert!(matches!(err, KsnError::Signer(_)));
657 }
658
659 #[test]
660 fn delegated_device_is_flagged() {
661 assert!(KeyStateNotice::new(key_state(1, true), "t").names_delegated_device());
662 assert!(!KeyStateNotice::new(key_state(1, false), "t").names_delegated_device());
663 }
664
665 #[test]
666 fn verify_accepts_valid_ksn() {
667 let kp = real_keypair();
668 let notice = KeyStateNotice::new(state_for_key(&kp, 1), "2026-06-03T00:00:00Z");
669 let signed = sign_ksn(&kp, notice);
670 let verified = signed.verify().unwrap();
671 assert_eq!(verified.trust, KsnTrust::TrustOnFirstSight);
672 assert!(!verified.is_authoritative_over_kel());
674 assert!(!verified.satisfies_revocation_check());
675 }
676
677 #[test]
678 fn verify_rejects_tampered_notice() {
679 let kp = real_keypair();
680 let notice = KeyStateNotice::new(state_for_key(&kp, 1), "2026-06-03T00:00:00Z");
681 let mut signed = sign_ksn(&kp, notice);
682 signed.notice.dt = "2099-01-01T00:00:00Z".to_string(); assert!(matches!(signed.verify(), Err(KsnError::BadSignature)));
684 }
685
686 #[test]
687 fn verify_rejects_signature_by_non_current_key() {
688 let signer = real_keypair();
689 let other = real_keypair();
690 let notice = KeyStateNotice::new(state_for_key(&other, 1), "2026-06-03T00:00:00Z");
692 let signed = sign_ksn(&signer, notice);
693 assert!(matches!(signed.verify(), Err(KsnError::BadSignature)));
694 }
695
696 #[test]
697 fn verify_rejects_unsigned_or_garbage_signature() {
698 let kp = real_keypair();
699 let notice = KeyStateNotice::new(state_for_key(&kp, 1), "2026-06-03T00:00:00Z");
700 let mut signed = sign_ksn(&kp, notice);
701 signed.signature = vec![0u8; 64]; assert!(matches!(signed.verify(), Err(KsnError::BadSignature)));
703 }
704
705 #[test]
706 fn verify_rejects_wrong_type() {
707 let kp = real_keypair();
708 let mut notice = KeyStateNotice::new(state_for_key(&kp, 1), "t");
709 notice.t = "rpy".to_string();
710 let signed = sign_ksn(&kp, notice);
711 assert!(matches!(signed.verify(), Err(KsnError::WrongType(_))));
712 }
713
714 #[test]
715 fn check_not_stale_rejects_rollback() {
716 let kp = real_keypair();
717 let signed = sign_ksn(&kp, KeyStateNotice::new(state_for_key(&kp, 2), "t"));
718 assert!(matches!(
719 signed.check_not_stale(3),
720 Err(KsnError::Stale { .. })
721 ));
722 assert!(signed.check_not_stale(2).is_ok());
723 assert!(signed.check_not_stale(1).is_ok());
724 }
725
726 use crate::witness::{Receipt, ReceiptTag, SignedReceipt};
729 use crate::{KeriSequence, VersionString};
730
731 fn witness_kp_and_aid() -> (Ed25519KeyPair, String) {
733 let kp = real_keypair();
734 let aid = KeriPublicKey::ed25519(kp.public_key().as_ref())
735 .unwrap()
736 .to_qb64()
737 .unwrap();
738 (kp, aid)
739 }
740
741 fn witness_receipt(
743 witness_kp: &Ed25519KeyPair,
744 witness_aid: &str,
745 controller: &str,
746 seq: u128,
747 event_said: &str,
748 ) -> StoredReceipt {
749 let receipt = Receipt {
750 v: VersionString::placeholder(),
751 t: ReceiptTag,
752 d: Said::new_unchecked(event_said.to_string()),
753 i: Prefix::new_unchecked(controller.to_string()),
754 s: KeriSequence::new(seq),
755 };
756 let payload = serde_json::to_vec(&receipt).unwrap();
757 let signature = witness_kp.sign(&payload).as_ref().to_vec();
758 StoredReceipt {
759 signed: SignedReceipt { receipt, signature },
760 witness: Prefix::new_unchecked(witness_aid.to_string()),
761 }
762 }
763
764 fn witnessed_state(
766 controller_kp: &Ed25519KeyPair,
767 seq: u128,
768 backers: &[&str],
769 bt: u64,
770 delegated: bool,
771 ) -> KeyState {
772 let mut state = state_for_key(controller_kp, seq);
773 state.backers = backers
774 .iter()
775 .map(|a| Prefix::new_unchecked(a.to_string()))
776 .collect();
777 state.backer_threshold = Threshold::Simple(bt);
778 if delegated {
779 state.delegator = Some(Prefix::new_unchecked(
780 "ERootDelegator00000000000000000000000000000".to_string(),
781 ));
782 }
783 state
784 }
785
786 #[test]
787 fn ksn_witnessed_when_quorum_met() {
788 let ckp = real_keypair();
789 let (w1kp, w1) = witness_kp_and_aid();
790 let (w2kp, w2) = witness_kp_and_aid();
791 let state = witnessed_state(&ckp, 1, &[&w1, &w2], 2, false);
792 let controller = state.prefix.as_str().to_string();
793 let said = state.last_event_said.as_str().to_string();
794 let signed = sign_ksn(&ckp, KeyStateNotice::new(state, "t")).with_receipts(vec![
795 witness_receipt(&w1kp, &w1, &controller, 1, &said),
796 witness_receipt(&w2kp, &w2, &controller, 1, &said),
797 ]);
798 assert_eq!(
799 signed.verify().unwrap().trust,
800 KsnTrust::Witnessed {
801 receipts: 2,
802 threshold: 2
803 }
804 );
805 }
806
807 #[test]
808 fn ksn_stays_tofu_under_quorum() {
809 let ckp = real_keypair();
810 let (w1kp, w1) = witness_kp_and_aid();
811 let (_w2kp, w2) = witness_kp_and_aid();
812 let state = witnessed_state(&ckp, 1, &[&w1, &w2], 2, false);
813 let controller = state.prefix.as_str().to_string();
814 let said = state.last_event_said.as_str().to_string();
815 let signed = sign_ksn(&ckp, KeyStateNotice::new(state, "t"))
816 .with_receipts(vec![witness_receipt(&w1kp, &w1, &controller, 1, &said)]);
817 assert_eq!(signed.verify().unwrap().trust, KsnTrust::TrustOnFirstSight);
818 }
819
820 #[test]
821 fn ksn_ignores_duplicate_witness_receipts() {
822 let ckp = real_keypair();
823 let (w1kp, w1) = witness_kp_and_aid();
824 let (_w2kp, w2) = witness_kp_and_aid();
825 let (_w3kp, w3) = witness_kp_and_aid();
826 let state = witnessed_state(&ckp, 1, &[&w1, &w2, &w3], 2, false);
827 let controller = state.prefix.as_str().to_string();
828 let said = state.last_event_said.as_str().to_string();
829 let signed = sign_ksn(&ckp, KeyStateNotice::new(state, "t")).with_receipts(vec![
831 witness_receipt(&w1kp, &w1, &controller, 1, &said),
832 witness_receipt(&w1kp, &w1, &controller, 1, &said),
833 ]);
834 assert_eq!(signed.verify().unwrap().trust, KsnTrust::TrustOnFirstSight);
835 }
836
837 #[test]
838 fn ksn_ignores_receipt_for_wrong_said() {
839 let ckp = real_keypair();
840 let (w1kp, w1) = witness_kp_and_aid();
841 let (w2kp, w2) = witness_kp_and_aid();
842 let state = witnessed_state(&ckp, 1, &[&w1, &w2], 2, false);
843 let controller = state.prefix.as_str().to_string();
844 let mut signed = sign_ksn(&ckp, KeyStateNotice::new(state, "t"));
847 let wrong = "EWrongEventSaid0000000000000000000000000000";
848 signed.receipts = vec![
849 witness_receipt(&w1kp, &w1, &controller, 1, wrong),
850 witness_receipt(&w2kp, &w2, &controller, 1, wrong),
851 ];
852 assert_eq!(signed.verify().unwrap().trust, KsnTrust::TrustOnFirstSight);
853 }
854
855 #[test]
856 fn ksn_bt_zero_stays_tofu() {
857 let ckp = real_keypair();
859 let signed = sign_ksn(&ckp, KeyStateNotice::new(state_for_key(&ckp, 1), "t"));
860 assert_eq!(signed.verify().unwrap().trust, KsnTrust::TrustOnFirstSight);
861 }
862
863 #[test]
864 fn witnessed_device_ksn_still_refuses_revocation() {
865 let ckp = real_keypair();
866 let (w1kp, w1) = witness_kp_and_aid();
867 let (w2kp, w2) = witness_kp_and_aid();
868 let state = witnessed_state(&ckp, 1, &[&w1, &w2], 2, true); let controller = state.prefix.as_str().to_string();
870 let said = state.last_event_said.as_str().to_string();
871 let signed = sign_ksn(&ckp, KeyStateNotice::new(state, "t")).with_receipts(vec![
872 witness_receipt(&w1kp, &w1, &controller, 1, &said),
873 witness_receipt(&w2kp, &w2, &controller, 1, &said),
874 ]);
875 let v = signed.verify().unwrap();
876 assert!(matches!(v.trust, KsnTrust::Witnessed { .. }));
877 assert!(!v.satisfies_revocation_check());
879 assert!(!v.is_authoritative_over_kel());
880 }
881
882 #[test]
883 fn populating_receipts_preserves_controller_signature() {
884 let ckp = real_keypair();
885 let (w1kp, w1) = witness_kp_and_aid();
886 let (w2kp, w2) = witness_kp_and_aid();
887 let state = witnessed_state(&ckp, 1, &[&w1, &w2], 2, false);
888 let controller = state.prefix.as_str().to_string();
889 let said = state.last_event_said.as_str().to_string();
890 let signed = sign_ksn(&ckp, KeyStateNotice::new(state, "t"));
891 assert!(signed.verify().is_ok()); let published = signed.with_receipts(vec![
894 witness_receipt(&w1kp, &w1, &controller, 1, &said),
895 witness_receipt(&w2kp, &w2, &controller, 1, &said),
896 ]);
897 let v = published
899 .verify()
900 .expect("controller signature must still verify after attaching receipts");
901 assert!(matches!(v.trust, KsnTrust::Witnessed { .. }));
902 }
903
904 const ICP_KEL_JSON: &str = r#"[{
909 "v":"KERI10JSON0000fd_","t":"icp",
910 "d":"EOoC9AuwxiwcyUDsa2yNAaZOVWqfiAt4o3R31_8K2Z1J",
911 "i":"EOoC9AuwxiwcyUDsa2yNAaZOVWqfiAt4o3R31_8K2Z1J",
912 "s":"0","kt":"1",
913 "k":["DAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4f"],
914 "nt":"0","n":[],"bt":"0","b":[],"c":[],"a":[]
915 }]"#;
916
917 #[test]
918 fn key_state_record_emits_keri_wire_shape() {
919 let events = crate::validate::parse_kel_json(ICP_KEL_JSON).unwrap();
920 let state = crate::validate::TrustedKel::from_trusted_source(&events)
921 .replay()
922 .unwrap();
923 let record =
924 KeyStateRecord::from_kel(&events, &state, "2026-06-12T02:49:41.677319+00:00").unwrap();
925
926 let json = serde_json::to_value(&record).unwrap();
927 let obj = json.as_object().unwrap();
928 let keys: Vec<&str> = obj.keys().map(String::as_str).collect();
930 assert_eq!(
931 keys,
932 vec![
933 "vn", "i", "s", "p", "d", "f", "dt", "et", "kt", "k", "nt", "n", "bt", "b", "c",
934 "ee", "di"
935 ]
936 );
937 assert_eq!(obj["vn"], serde_json::json!([1, 0]));
938 assert_eq!(obj["s"], "0");
939 assert_eq!(obj["p"], "");
940 assert_eq!(obj["et"], "icp");
941 assert_eq!(obj["kt"], "1");
942 assert_eq!(obj["di"], "");
943 let ee = obj["ee"].as_object().unwrap();
944 assert_eq!(
945 ee.keys().map(String::as_str).collect::<Vec<_>>(),
946 vec!["s", "d", "br", "ba"]
947 );
948 }
949
950 #[test]
951 fn key_state_record_round_trips_through_key_state() {
952 let events = crate::validate::parse_kel_json(ICP_KEL_JSON).unwrap();
953 let state = crate::validate::TrustedKel::from_trusted_source(&events)
954 .replay()
955 .unwrap();
956 let record = KeyStateRecord::from_kel(&events, &state, "t").unwrap();
957
958 let wire = serde_json::to_string(&record).unwrap();
960 let parsed: KeyStateRecord = serde_json::from_str(&wire).unwrap();
961 assert_eq!(parsed, record);
962
963 let projected = parsed.into_key_state();
964 assert_eq!(projected.prefix, state.prefix);
965 assert_eq!(projected.current_keys, state.current_keys);
966 assert_eq!(projected.sequence, state.sequence);
967 assert_eq!(projected.last_event_said, state.last_event_said);
968 assert_eq!(projected.threshold, state.threshold);
969 assert!(projected.is_non_transferable);
970 }
971
972 #[test]
973 fn key_state_record_check_not_stale() {
974 let events = crate::validate::parse_kel_json(ICP_KEL_JSON).unwrap();
975 let state = crate::validate::TrustedKel::from_trusted_source(&events)
976 .replay()
977 .unwrap();
978 let record = KeyStateRecord::from_kel(&events, &state, "t").unwrap();
979 assert_eq!(record.sequence(), 0);
980 assert!(record.check_not_stale(0).is_ok());
983 assert!(matches!(
984 record.check_not_stale(1),
985 Err(KsnError::Stale { got: 0, seen: 1 })
986 ));
987 }
988
989 #[test]
990 fn key_state_record_ingests_peer_published_record() {
991 let wire = r#"{
994 "vn":[1,0],
995 "i":"EOoC9AuwxiwcyUDsa2yNAaZOVWqfiAt4o3R31_8K2Z1J",
996 "s":"0","p":"",
997 "d":"EOoC9AuwxiwcyUDsa2yNAaZOVWqfiAt4o3R31_8K2Z1J",
998 "f":"0","dt":"2026-06-12T02:49:41.677319+00:00","et":"icp",
999 "kt":"1","k":["DAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4f"],
1000 "nt":"0","n":[],"bt":"0","b":[],"c":[],
1001 "ee":{"s":"0","d":"EOoC9AuwxiwcyUDsa2yNAaZOVWqfiAt4o3R31_8K2Z1J","br":[],"ba":[]},
1002 "di":""
1003 }"#;
1004 let record: KeyStateRecord = serde_json::from_str(wire).unwrap();
1005 let state = record.into_key_state();
1006 assert_eq!(
1007 state.prefix.as_str(),
1008 "EOoC9AuwxiwcyUDsa2yNAaZOVWqfiAt4o3R31_8K2Z1J"
1009 );
1010 assert_eq!(state.sequence, 0);
1011 assert!(state.is_non_transferable);
1012 assert!(state.delegator.is_none());
1013 }
1014}