1use serde::ser::SerializeMap;
7use serde::{Deserialize, Serialize, Serializer};
8use std::fmt;
9
10use crate::capability::Capability;
11use crate::types::{CesrKey, ConfigTrait, Prefix, Said, Threshold, VersionString};
12
13pub const KERI_VERSION_PREFIX: &str = "KERI10JSON";
15
16#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
29pub struct KeriSequence(u128);
30
31#[cfg(feature = "schema")]
32impl schemars::JsonSchema for KeriSequence {
33 fn schema_name() -> String {
34 "KeriSequence".to_string()
35 }
36
37 fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
38 schemars::schema::SchemaObject {
39 instance_type: Some(schemars::schema::InstanceType::String.into()),
40 ..Default::default()
41 }
42 .into()
43 }
44}
45
46impl KeriSequence {
47 pub fn new(value: u128) -> Self {
49 Self(value)
50 }
51
52 pub fn value(self) -> u128 {
54 self.0
55 }
56}
57
58impl fmt::Display for KeriSequence {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 write!(f, "{:x}", self.0)
61 }
62}
63
64impl Serialize for KeriSequence {
65 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
66 serializer.serialize_str(&format!("{:x}", self.0))
67 }
68}
69
70impl<'de> Deserialize<'de> for KeriSequence {
71 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
72 let s = String::deserialize(deserializer)?;
73 let value = u128::from_str_radix(&s, 16)
74 .map_err(|_| serde::de::Error::custom(format!("invalid hex sequence: {s:?}")))?;
75 Ok(KeriSequence(value))
76 }
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
93#[non_exhaustive]
94pub enum Seal {
95 Digest {
97 d: Said,
99 },
100 SourceEvent {
102 s: KeriSequence,
104 d: Said,
106 },
107 KeyEvent {
109 i: Prefix,
111 s: KeriSequence,
113 d: Said,
115 },
116 EventLocation {
118 i: Prefix,
120 s: KeriSequence,
122 p: Said,
124 t: String,
126 d: Said,
128 },
129 LatestEstablishment {
131 i: Prefix,
133 },
134 #[cfg(feature = "seal-extensions")]
136 MerkleRoot {
137 rd: Said,
139 },
140 #[cfg(feature = "seal-extensions")]
142 RegistrarBacker {
143 bi: Prefix,
145 d: Said,
147 },
148}
149
150impl Seal {
151 pub fn digest(said: impl Into<String>) -> Self {
153 Self::Digest {
154 d: Said::new_unchecked(said.into()),
155 }
156 }
157
158 pub fn key_event(prefix: Prefix, sequence: KeriSequence, said: Said) -> Self {
160 Self::KeyEvent {
161 i: prefix,
162 s: sequence,
163 d: said,
164 }
165 }
166
167 pub fn digest_value(&self) -> Option<&Said> {
169 match self {
170 Seal::Digest { d } => Some(d),
171 Seal::SourceEvent { d, .. } => Some(d),
172 Seal::KeyEvent { d, .. } => Some(d),
173 Seal::EventLocation { d, .. } => Some(d),
174 Seal::LatestEstablishment { .. } => None,
175 #[cfg(feature = "seal-extensions")]
176 Seal::RegistrarBacker { d, .. } => Some(d),
177 #[cfg(feature = "seal-extensions")]
178 Seal::MerkleRoot { rd } => Some(rd),
179 }
180 }
181}
182
183impl Serialize for Seal {
184 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
185 match self {
186 Seal::Digest { d } => {
187 let mut map = serializer.serialize_map(Some(1))?;
188 map.serialize_entry("d", d)?;
189 map.end()
190 }
191 Seal::SourceEvent { s, d } => {
192 let mut map = serializer.serialize_map(Some(2))?;
193 map.serialize_entry("s", s)?;
194 map.serialize_entry("d", d)?;
195 map.end()
196 }
197 Seal::KeyEvent { i, s, d } => {
198 let mut map = serializer.serialize_map(Some(3))?;
199 map.serialize_entry("i", i)?;
200 map.serialize_entry("s", s)?;
201 map.serialize_entry("d", d)?;
202 map.end()
203 }
204 Seal::EventLocation { i, s, p, t, d } => {
205 let mut map = serializer.serialize_map(Some(5))?;
206 map.serialize_entry("i", i)?;
207 map.serialize_entry("s", s)?;
208 map.serialize_entry("p", p)?;
209 map.serialize_entry("t", t)?;
210 map.serialize_entry("d", d)?;
211 map.end()
212 }
213 Seal::LatestEstablishment { i } => {
214 let mut map = serializer.serialize_map(Some(1))?;
215 map.serialize_entry("i", i)?;
216 map.end()
217 }
218 #[cfg(feature = "seal-extensions")]
219 Seal::MerkleRoot { rd } => {
220 let mut map = serializer.serialize_map(Some(1))?;
221 map.serialize_entry("rd", rd)?;
222 map.end()
223 }
224 #[cfg(feature = "seal-extensions")]
225 Seal::RegistrarBacker { bi, d } => {
226 let mut map = serializer.serialize_map(Some(2))?;
227 map.serialize_entry("bi", bi)?;
228 map.serialize_entry("d", d)?;
229 map.end()
230 }
231 }
232 }
233}
234
235impl<'de> Deserialize<'de> for Seal {
236 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
237 let map: serde_json::Map<String, serde_json::Value> =
238 serde_json::Map::deserialize(deserializer)?;
239
240 let want_str = |k: &str| -> Result<String, D::Error> {
241 map.get(k)
242 .and_then(|v| v.as_str())
243 .map(|v| v.to_string())
244 .ok_or_else(|| {
245 serde::de::Error::custom(format!("seal field `{k}` must be a string"))
246 })
247 };
248 let want_seq =
249 |k: &str| -> Result<KeriSequence, D::Error> {
250 serde_json::from_value(map.get(k).cloned().ok_or_else(|| {
251 serde::de::Error::custom(format!("seal field `{k}` required"))
252 })?)
253 .map_err(serde::de::Error::custom)
254 };
255
256 let mut keys: Vec<&str> = map.keys().map(String::as_str).collect();
258 keys.sort_unstable();
259
260 match keys.as_slice() {
261 ["d"] => Ok(Seal::Digest {
262 d: Said::new_unchecked(want_str("d")?),
263 }),
264 ["d", "s"] => Ok(Seal::SourceEvent {
265 s: want_seq("s")?,
266 d: Said::new_unchecked(want_str("d")?),
267 }),
268 ["d", "i", "s"] => Ok(Seal::KeyEvent {
269 i: Prefix::new_unchecked(want_str("i")?),
270 s: want_seq("s")?,
271 d: Said::new_unchecked(want_str("d")?),
272 }),
273 ["d", "i", "p", "s", "t"] => Ok(Seal::EventLocation {
274 i: Prefix::new_unchecked(want_str("i")?),
275 s: want_seq("s")?,
276 p: Said::new_unchecked(want_str("p")?),
277 t: want_str("t")?,
278 d: Said::new_unchecked(want_str("d")?),
279 }),
280 ["i"] => Ok(Seal::LatestEstablishment {
281 i: Prefix::new_unchecked(want_str("i")?),
282 }),
283 #[cfg(feature = "seal-extensions")]
284 ["rd"] => Ok(Seal::MerkleRoot {
285 rd: Said::new_unchecked(want_str("rd")?),
286 }),
287 #[cfg(feature = "seal-extensions")]
288 ["bi", "d"] => Ok(Seal::RegistrarBacker {
289 bi: Prefix::new_unchecked(want_str("bi")?),
290 d: Said::new_unchecked(want_str("d")?),
291 }),
292 other => Err(serde::de::Error::custom(format!(
293 "unrecognized seal field set: {other:?}"
294 ))),
295 }
296 }
297}
298
299#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
308#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
309pub struct IcpEvent {
310 pub v: VersionString,
312 pub d: Said,
314 pub i: Prefix,
316 pub s: KeriSequence,
318 pub kt: Threshold,
320 pub k: Vec<CesrKey>,
322 pub nt: Threshold,
324 pub n: Vec<Said>,
326 pub bt: Threshold,
328 #[serde(default)]
330 pub b: Vec<Prefix>,
331 #[serde(default)]
333 pub c: Vec<ConfigTrait>,
334 #[serde(default)]
336 pub a: Vec<Seal>,
337}
338
339pub struct IcpEventInit {
345 pub v: VersionString,
347 pub d: Said,
349 pub i: Prefix,
351 pub s: KeriSequence,
353 pub kt: Threshold,
355 pub k: Vec<CesrKey>,
357 pub nt: Threshold,
359 pub n: Vec<Said>,
361 pub bt: Threshold,
363 pub b: Vec<Prefix>,
365 pub c: Vec<ConfigTrait>,
367 pub a: Vec<Seal>,
369}
370
371impl IcpEvent {
372 pub fn new(init: IcpEventInit) -> Self {
374 Self {
375 v: init.v,
376 d: init.d,
377 i: init.i,
378 s: init.s,
379 kt: init.kt,
380 k: init.k,
381 nt: init.nt,
382 n: init.n,
383 bt: init.bt,
384 b: init.b,
385 c: init.c,
386 a: init.a,
387 }
388 }
389}
390
391impl Serialize for IcpEvent {
393 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
394 let field_count = 13;
395 let mut map = serializer.serialize_map(Some(field_count))?;
396 map.serialize_entry("v", &self.v)?;
397 map.serialize_entry("t", "icp")?;
398 map.serialize_entry("d", &self.d)?;
399 map.serialize_entry("i", &self.i)?;
400 map.serialize_entry("s", &self.s)?;
401 map.serialize_entry("kt", &self.kt)?;
402 map.serialize_entry("k", &self.k)?;
403 map.serialize_entry("nt", &self.nt)?;
404 map.serialize_entry("n", &self.n)?;
405 map.serialize_entry("bt", &self.bt)?;
406 map.serialize_entry("b", &self.b)?;
407 map.serialize_entry("c", &self.c)?;
408 map.serialize_entry("a", &self.a)?;
409 map.end()
410 }
411}
412
413#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
417#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
418pub struct RotEvent {
419 pub v: VersionString,
421 pub d: Said,
423 pub i: Prefix,
425 pub s: KeriSequence,
427 pub p: Said,
429 pub kt: Threshold,
431 pub k: Vec<CesrKey>,
433 pub nt: Threshold,
435 pub n: Vec<Said>,
437 pub bt: Threshold,
439 #[serde(default)]
441 pub br: Vec<Prefix>,
442 #[serde(default)]
444 pub ba: Vec<Prefix>,
445 #[serde(default)]
447 pub c: Vec<ConfigTrait>,
448 #[serde(default)]
450 pub a: Vec<Seal>,
451}
452
453pub struct RotEventInit {
456 pub v: VersionString,
458 pub d: Said,
460 pub i: Prefix,
462 pub s: KeriSequence,
464 pub p: Said,
466 pub kt: Threshold,
468 pub k: Vec<CesrKey>,
470 pub nt: Threshold,
472 pub n: Vec<Said>,
474 pub bt: Threshold,
476 pub br: Vec<Prefix>,
478 pub ba: Vec<Prefix>,
480 pub c: Vec<ConfigTrait>,
482 pub a: Vec<Seal>,
484}
485
486impl RotEvent {
487 pub fn new(init: RotEventInit) -> Self {
489 Self {
490 v: init.v,
491 d: init.d,
492 i: init.i,
493 s: init.s,
494 p: init.p,
495 kt: init.kt,
496 k: init.k,
497 nt: init.nt,
498 n: init.n,
499 bt: init.bt,
500 br: init.br,
501 ba: init.ba,
502 c: init.c,
503 a: init.a,
504 }
505 }
506}
507
508impl Serialize for RotEvent {
510 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
511 let field_count = 14;
512 let mut map = serializer.serialize_map(Some(field_count))?;
513 map.serialize_entry("v", &self.v)?;
514 map.serialize_entry("t", "rot")?;
515 map.serialize_entry("d", &self.d)?;
516 map.serialize_entry("i", &self.i)?;
517 map.serialize_entry("s", &self.s)?;
518 map.serialize_entry("p", &self.p)?;
519 map.serialize_entry("kt", &self.kt)?;
520 map.serialize_entry("k", &self.k)?;
521 map.serialize_entry("nt", &self.nt)?;
522 map.serialize_entry("n", &self.n)?;
523 map.serialize_entry("bt", &self.bt)?;
524 map.serialize_entry("br", &self.br)?;
525 map.serialize_entry("ba", &self.ba)?;
526 map.serialize_entry("a", &self.a)?;
527 map.end()
528 }
529}
530
531#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
535#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
536pub struct IxnEvent {
537 pub v: VersionString,
539 pub d: Said,
541 pub i: Prefix,
543 pub s: KeriSequence,
545 pub p: Said,
547 pub a: Vec<Seal>,
549}
550
551pub struct IxnEventInit {
553 pub v: VersionString,
554 pub d: Said,
555 pub i: Prefix,
556 pub s: KeriSequence,
557 pub p: Said,
558 pub a: Vec<Seal>,
559}
560
561impl IxnEvent {
562 pub fn new(init: IxnEventInit) -> Self {
564 Self {
565 v: init.v,
566 d: init.d,
567 i: init.i,
568 s: init.s,
569 p: init.p,
570 a: init.a,
571 }
572 }
573}
574
575impl Serialize for IxnEvent {
577 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
578 let field_count = 7;
579 let mut map = serializer.serialize_map(Some(field_count))?;
580 map.serialize_entry("v", &self.v)?;
581 map.serialize_entry("t", "ixn")?;
582 map.serialize_entry("d", &self.d)?;
583 map.serialize_entry("i", &self.i)?;
584 map.serialize_entry("s", &self.s)?;
585 map.serialize_entry("p", &self.p)?;
586 map.serialize_entry("a", &self.a)?;
587 map.end()
588 }
589}
590
591#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
609#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
610pub struct DipEvent {
611 pub v: VersionString,
613 pub d: Said,
615 pub i: Prefix,
617 pub s: KeriSequence,
619 pub kt: Threshold,
621 pub k: Vec<CesrKey>,
623 pub nt: Threshold,
625 pub n: Vec<Said>,
627 pub bt: Threshold,
629 #[serde(default)]
631 pub b: Vec<Prefix>,
632 #[serde(default)]
634 pub c: Vec<ConfigTrait>,
635 #[serde(default)]
637 pub a: Vec<Seal>,
638 pub di: Prefix,
640 #[serde(skip)]
646 pub source_seal: Option<SourceSeal>,
647}
648
649pub struct DipEventInit {
651 pub v: VersionString,
653 pub d: Said,
655 pub i: Prefix,
657 pub s: KeriSequence,
659 pub kt: Threshold,
661 pub k: Vec<CesrKey>,
663 pub nt: Threshold,
665 pub n: Vec<Said>,
667 pub bt: Threshold,
669 pub b: Vec<Prefix>,
671 pub c: Vec<ConfigTrait>,
673 pub a: Vec<Seal>,
675 pub di: Prefix,
677}
678
679impl DipEvent {
680 pub fn new(init: DipEventInit) -> Self {
682 Self {
683 v: init.v,
684 d: init.d,
685 i: init.i,
686 s: init.s,
687 kt: init.kt,
688 k: init.k,
689 nt: init.nt,
690 n: init.n,
691 bt: init.bt,
692 b: init.b,
693 c: init.c,
694 a: init.a,
695 di: init.di,
696 source_seal: None,
697 }
698 }
699}
700
701impl Serialize for DipEvent {
703 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
704 let field_count = 14;
705 let mut map = serializer.serialize_map(Some(field_count))?;
706 map.serialize_entry("v", &self.v)?;
707 map.serialize_entry("t", "dip")?;
708 map.serialize_entry("d", &self.d)?;
709 map.serialize_entry("i", &self.i)?;
710 map.serialize_entry("s", &self.s)?;
711 map.serialize_entry("kt", &self.kt)?;
712 map.serialize_entry("k", &self.k)?;
713 map.serialize_entry("nt", &self.nt)?;
714 map.serialize_entry("n", &self.n)?;
715 map.serialize_entry("bt", &self.bt)?;
716 map.serialize_entry("b", &self.b)?;
717 map.serialize_entry("c", &self.c)?;
718 map.serialize_entry("a", &self.a)?;
719 map.serialize_entry("di", &self.di)?;
720 map.end()
721 }
722}
723
724#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
730#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
731pub struct DrtEvent {
732 pub v: VersionString,
734 pub d: Said,
736 pub i: Prefix,
738 pub s: KeriSequence,
740 pub p: Said,
742 pub kt: Threshold,
744 pub k: Vec<CesrKey>,
746 pub nt: Threshold,
748 pub n: Vec<Said>,
750 pub bt: Threshold,
752 #[serde(default)]
754 pub br: Vec<Prefix>,
755 #[serde(default)]
757 pub ba: Vec<Prefix>,
758 #[serde(default)]
760 pub c: Vec<ConfigTrait>,
761 #[serde(default)]
763 pub a: Vec<Seal>,
764 pub di: Prefix,
766 #[serde(skip)]
770 pub source_seal: Option<SourceSeal>,
771}
772
773pub struct DrtEventInit {
775 pub v: VersionString,
777 pub d: Said,
779 pub i: Prefix,
781 pub s: KeriSequence,
783 pub p: Said,
785 pub kt: Threshold,
787 pub k: Vec<CesrKey>,
789 pub nt: Threshold,
791 pub n: Vec<Said>,
793 pub bt: Threshold,
795 pub br: Vec<Prefix>,
797 pub ba: Vec<Prefix>,
799 pub c: Vec<ConfigTrait>,
801 pub a: Vec<Seal>,
803 pub di: Prefix,
805}
806
807impl DrtEvent {
808 pub fn new(init: DrtEventInit) -> Self {
810 Self {
811 v: init.v,
812 d: init.d,
813 i: init.i,
814 s: init.s,
815 p: init.p,
816 kt: init.kt,
817 k: init.k,
818 nt: init.nt,
819 n: init.n,
820 bt: init.bt,
821 br: init.br,
822 ba: init.ba,
823 c: init.c,
824 a: init.a,
825 di: init.di,
826 source_seal: None,
827 }
828 }
829}
830
831impl Serialize for DrtEvent {
833 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
834 let field_count = 15;
835 let mut map = serializer.serialize_map(Some(field_count))?;
836 map.serialize_entry("v", &self.v)?;
837 map.serialize_entry("t", "drt")?;
838 map.serialize_entry("d", &self.d)?;
839 map.serialize_entry("i", &self.i)?;
840 map.serialize_entry("s", &self.s)?;
841 map.serialize_entry("p", &self.p)?;
842 map.serialize_entry("kt", &self.kt)?;
843 map.serialize_entry("k", &self.k)?;
844 map.serialize_entry("nt", &self.nt)?;
845 map.serialize_entry("n", &self.n)?;
846 map.serialize_entry("bt", &self.bt)?;
847 map.serialize_entry("br", &self.br)?;
848 map.serialize_entry("ba", &self.ba)?;
849 map.serialize_entry("a", &self.a)?;
850 map.serialize_entry("di", &self.di)?;
851 map.end()
852 }
853}
854
855#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
857#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
858#[serde(tag = "t")]
859pub enum Event {
860 #[serde(rename = "icp")]
862 Icp(IcpEvent),
863 #[serde(rename = "rot")]
865 Rot(RotEvent),
866 #[serde(rename = "ixn")]
868 Ixn(IxnEvent),
869 #[serde(rename = "dip")]
871 Dip(DipEvent),
872 #[serde(rename = "drt")]
874 Drt(DrtEvent),
875}
876
877impl Serialize for Event {
878 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
879 match self {
880 Event::Icp(e) => e.serialize(serializer),
881 Event::Rot(e) => e.serialize(serializer),
882 Event::Ixn(e) => e.serialize(serializer),
883 Event::Dip(e) => e.serialize(serializer),
884 Event::Drt(e) => e.serialize(serializer),
885 }
886 }
887}
888
889impl Event {
890 pub fn said(&self) -> &Said {
892 match self {
893 Event::Icp(e) => &e.d,
894 Event::Rot(e) => &e.d,
895 Event::Ixn(e) => &e.d,
896 Event::Dip(e) => &e.d,
897 Event::Drt(e) => &e.d,
898 }
899 }
900
901 pub fn sequence(&self) -> KeriSequence {
903 match self {
904 Event::Icp(e) => e.s,
905 Event::Rot(e) => e.s,
906 Event::Ixn(e) => e.s,
907 Event::Dip(e) => e.s,
908 Event::Drt(e) => e.s,
909 }
910 }
911
912 pub fn prefix(&self) -> &Prefix {
914 match self {
915 Event::Icp(e) => &e.i,
916 Event::Rot(e) => &e.i,
917 Event::Ixn(e) => &e.i,
918 Event::Dip(e) => &e.i,
919 Event::Drt(e) => &e.i,
920 }
921 }
922
923 pub fn previous(&self) -> Option<&Said> {
925 match self {
926 Event::Icp(_) | Event::Dip(_) => None,
927 Event::Rot(e) => Some(&e.p),
928 Event::Ixn(e) => Some(&e.p),
929 Event::Drt(e) => Some(&e.p),
930 }
931 }
932
933 pub fn keys(&self) -> Option<&[CesrKey]> {
935 match self {
936 Event::Icp(e) => Some(&e.k),
937 Event::Rot(e) => Some(&e.k),
938 Event::Dip(e) => Some(&e.k),
939 Event::Drt(e) => Some(&e.k),
940 Event::Ixn(_) => None,
941 }
942 }
943
944 pub fn next_commitments(&self) -> Option<&[Said]> {
946 match self {
947 Event::Icp(e) => Some(&e.n),
948 Event::Rot(e) => Some(&e.n),
949 Event::Dip(e) => Some(&e.n),
950 Event::Drt(e) => Some(&e.n),
951 Event::Ixn(_) => None,
952 }
953 }
954
955 pub fn anchors(&self) -> &[Seal] {
957 match self {
958 Event::Icp(e) => &e.a,
959 Event::Rot(e) => &e.a,
960 Event::Ixn(e) => &e.a,
961 Event::Dip(e) => &e.a,
962 Event::Drt(e) => &e.a,
963 }
964 }
965
966 pub fn delegator(&self) -> Option<&Prefix> {
968 match self {
969 Event::Dip(e) => Some(&e.di),
970 _ => None,
971 }
972 }
973
974 pub fn is_inception(&self) -> bool {
976 matches!(self, Event::Icp(_) | Event::Dip(_))
977 }
978
979 pub fn is_rotation(&self) -> bool {
981 matches!(self, Event::Rot(_) | Event::Drt(_))
982 }
983
984 pub fn is_interaction(&self) -> bool {
986 matches!(self, Event::Ixn(_))
987 }
988
989 pub fn is_delegated(&self) -> bool {
991 matches!(self, Event::Dip(_) | Event::Drt(_))
992 }
993
994 pub fn source_seal(&self) -> Option<&SourceSeal> {
997 match self {
998 Event::Dip(e) => e.source_seal.as_ref(),
999 Event::Drt(e) => e.source_seal.as_ref(),
1000 _ => None,
1001 }
1002 }
1003}
1004
1005#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1020pub struct IndexedSignature {
1021 pub index: u32,
1023 #[serde(default, skip_serializing_if = "Option::is_none")]
1030 pub prior_index: Option<u32>,
1031 #[serde(with = "hex::serde")]
1033 pub sig: Vec<u8>,
1034}
1035
1036#[derive(Debug, Clone, PartialEq, Eq)]
1045#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1046pub struct SourceSeal {
1047 pub s: KeriSequence,
1049 pub d: Said,
1051}
1052
1053#[derive(Debug, Clone, PartialEq, Eq, Default)]
1062#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1063pub struct AgentScope {
1064 pub capabilities: Vec<Capability>,
1067 pub expires_at: Option<i64>,
1069}
1070
1071const AGENT_SCOPE_MARKER: &str = "agentscope:";
1073
1074pub fn encode_agent_scope(agent_prefix: &str, scope: &AgentScope) -> String {
1090 let expires = scope.expires_at.unwrap_or(0);
1091 let caps = scope
1092 .capabilities
1093 .iter()
1094 .map(Capability::as_str)
1095 .collect::<Vec<_>>()
1096 .join(",");
1097 format!("{AGENT_SCOPE_MARKER}{agent_prefix}:{expires}:{caps}")
1098}
1099
1100pub fn decode_agent_scope(value: &str) -> Option<(String, AgentScope)> {
1107 let rest = value.strip_prefix(AGENT_SCOPE_MARKER)?;
1110 let mut parts = rest.splitn(3, ':');
1111 let prefix = parts.next()?.to_string();
1112 let expires: i64 = parts.next()?.parse().ok()?;
1113 let caps_csv = parts.next().unwrap_or("");
1114 let capabilities = if caps_csv.is_empty() {
1115 Vec::new()
1116 } else {
1117 caps_csv
1118 .split(',')
1119 .map(Capability::parse)
1120 .collect::<Result<Vec<_>, _>>()
1121 .ok()?
1122 };
1123 Some((
1124 prefix,
1125 AgentScope {
1126 capabilities,
1127 expires_at: (expires != 0).then_some(expires),
1128 },
1129 ))
1130}
1131
1132pub fn serialize_source_seal_couples(couples: &[SourceSeal]) -> Result<Vec<u8>, AttachmentError> {
1146 use cesride::{Counter, Matter, Saider, Seqner, counter};
1147
1148 let count = u32::try_from(couples.len())
1149 .map_err(|_| AttachmentError::Encode("too many source seal couples".into()))?;
1150 let mut out = Counter::new_with_code_and_count(counter::Codex::SealSourceCouples, count)
1151 .and_then(|c| c.qb64())
1152 .map_err(|e| AttachmentError::Encode(e.to_string()))?;
1153
1154 for couple in couples {
1155 let seqner = Seqner::new_with_sn(couple.s.value())
1156 .and_then(|s| s.qb64())
1157 .map_err(|e| AttachmentError::Encode(e.to_string()))?;
1158 let saider = Saider::new_with_qb64(couple.d.as_str())
1159 .and_then(|s| s.qb64())
1160 .map_err(|e| AttachmentError::Encode(e.to_string()))?;
1161 out.push_str(&seqner);
1162 out.push_str(&saider);
1163 }
1164
1165 Ok(out.into_bytes())
1166}
1167
1168pub fn parse_source_seal_couples(bytes: &[u8]) -> Result<Vec<SourceSeal>, AttachmentError> {
1176 let s = std::str::from_utf8(bytes)
1177 .map_err(|e| AttachmentError::Decode(format!("non-utf8 source-seal group: {e}")))?;
1178 let (couples, rest) = parse_source_seal_group(s)?;
1179 if !rest.is_empty() {
1180 return Err(AttachmentError::Decode(format!(
1181 "trailing bytes after source-seal group: {rest:?}"
1182 )));
1183 }
1184 Ok(couples)
1185}
1186
1187fn parse_source_seal_group(s: &str) -> Result<(Vec<SourceSeal>, &str), AttachmentError> {
1190 use cesride::{Matter, Saider, Seqner};
1191
1192 if !s.is_ascii() {
1195 return Err(AttachmentError::Decode("non-ASCII CESR attachment".into()));
1196 }
1197 let rest = s.strip_prefix("-G").ok_or_else(|| {
1198 AttachmentError::Decode("source-seal group must start with -G counter code".into())
1199 })?;
1200 if rest.len() < 2 {
1201 return Err(AttachmentError::Decode(
1202 "truncated -G counter header".into(),
1203 ));
1204 }
1205 let (count_b64, mut cursor) = rest.split_at(2);
1206 let count = decode_count_b64(count_b64)?;
1207
1208 let mut out = Vec::with_capacity(count);
1209 for _ in 0..count {
1210 if cursor.len() < SEQNER_QB64_LEN {
1212 return Err(AttachmentError::Decode(
1213 "truncated Seqner in -G couple".into(),
1214 ));
1215 }
1216 let (seqner_qb64, after_seqner) = cursor.split_at(SEQNER_QB64_LEN);
1217 let seqner = Seqner::new_with_qb64(seqner_qb64)
1218 .map_err(|e| AttachmentError::Decode(format!("Seqner: {e}")))?;
1219 let sn = seqner
1220 .sn()
1221 .map_err(|e| AttachmentError::Decode(format!("Seqner sn: {e}")))?;
1222
1223 let first = *after_seqner.as_bytes().first().ok_or_else(|| {
1225 AttachmentError::Decode("truncated -G couple: expected a Saider".into())
1226 })?;
1227 let fs = saider_qb64_len(first).ok_or_else(|| {
1228 AttachmentError::Decode(format!("unsupported Saider code byte {first:?}"))
1229 })?;
1230 if after_seqner.len() < fs {
1231 return Err(AttachmentError::Decode(
1232 "truncated Saider in -G couple".into(),
1233 ));
1234 }
1235 let (saider_qb64, remainder) = after_seqner.split_at(fs);
1236 let saider = Saider::new_with_qb64(saider_qb64)
1237 .and_then(|s| s.qb64())
1238 .map_err(|e| AttachmentError::Decode(format!("Saider: {e}")))?;
1239 cursor = remainder;
1240
1241 out.push(SourceSeal {
1242 s: KeriSequence::new(sn),
1243 d: Said::new_unchecked(saider),
1244 });
1245 }
1246 Ok((out, cursor))
1247}
1248
1249pub fn parse_delegated_attachment(
1258 bytes: &[u8],
1259) -> Result<(Vec<IndexedSignature>, Vec<SourceSeal>), AttachmentError> {
1260 let s = std::str::from_utf8(bytes)
1261 .map_err(|e| AttachmentError::Decode(format!("non-utf8 attachment: {e}")))?;
1262 if s.is_empty() {
1263 return Ok((vec![], vec![]));
1264 }
1265 let (sigs, rest) = parse_sig_group(s)?;
1266 if rest.is_empty() {
1267 return Ok((sigs, vec![]));
1268 }
1269 let (couples, tail) = parse_source_seal_group(rest)?;
1270 if !tail.is_empty() {
1271 return Err(AttachmentError::Decode(format!(
1272 "trailing bytes after delegated attachment: {tail:?}"
1273 )));
1274 }
1275 Ok((sigs, couples))
1276}
1277
1278#[derive(Debug, Clone, Serialize, Deserialize)]
1289pub struct WireSignedDip {
1290 pub event: DipEvent,
1292 pub attachment_b64: String,
1294}
1295
1296pub fn encode_signed_dip(dip: &DipEvent, attachment: &[u8]) -> Result<String, AttachmentError> {
1303 use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
1304 let wire = WireSignedDip {
1305 event: dip.clone(),
1306 attachment_b64: URL_SAFE_NO_PAD.encode(attachment),
1307 };
1308 let json =
1309 serde_json::to_vec(&wire).map_err(|e| AttachmentError::Encode(format!("dip json: {e}")))?;
1310 Ok(URL_SAFE_NO_PAD.encode(json))
1311}
1312
1313pub fn decode_signed_dip(encoded: &str) -> Result<(DipEvent, Vec<u8>), AttachmentError> {
1319 use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
1320 let json = URL_SAFE_NO_PAD
1321 .decode(encoded)
1322 .map_err(|e| AttachmentError::Decode(format!("dip envelope: {e}")))?;
1323 let wire: WireSignedDip = serde_json::from_slice(&json)
1324 .map_err(|e| AttachmentError::Decode(format!("dip json: {e}")))?;
1325 let attachment = URL_SAFE_NO_PAD
1326 .decode(&wire.attachment_b64)
1327 .map_err(|e| AttachmentError::Decode(format!("dip attachment: {e}")))?;
1328 Ok((wire.event, attachment))
1329}
1330
1331#[derive(Debug, Clone, Serialize, Deserialize)]
1339pub struct WireSignedRot {
1340 pub event: RotEvent,
1342 pub attachment_b64: String,
1344}
1345
1346pub fn encode_signed_rot(rot: &RotEvent, attachment: &[u8]) -> Result<String, AttachmentError> {
1353 use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
1354 let wire = WireSignedRot {
1355 event: rot.clone(),
1356 attachment_b64: URL_SAFE_NO_PAD.encode(attachment),
1357 };
1358 let json =
1359 serde_json::to_vec(&wire).map_err(|e| AttachmentError::Encode(format!("rot json: {e}")))?;
1360 Ok(URL_SAFE_NO_PAD.encode(json))
1361}
1362
1363pub fn decode_signed_rot(encoded: &str) -> Result<(RotEvent, Vec<u8>), AttachmentError> {
1369 use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
1370 let json = URL_SAFE_NO_PAD
1371 .decode(encoded)
1372 .map_err(|e| AttachmentError::Decode(format!("rot envelope: {e}")))?;
1373 let wire: WireSignedRot = serde_json::from_slice(&json)
1374 .map_err(|e| AttachmentError::Decode(format!("rot json: {e}")))?;
1375 let attachment = URL_SAFE_NO_PAD
1376 .decode(&wire.attachment_b64)
1377 .map_err(|e| AttachmentError::Decode(format!("rot attachment: {e}")))?;
1378 Ok((wire.event, attachment))
1379}
1380
1381const SEQNER_QB64_LEN: usize = 24;
1383
1384fn saider_qb64_len(first: u8) -> Option<usize> {
1389 matches!(first, b'E' | b'F' | b'G' | b'H' | b'I').then_some(44)
1390}
1391
1392#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1406pub struct SignedEvent {
1407 pub event: Event,
1409 pub signatures: Vec<IndexedSignature>,
1411}
1412
1413impl SignedEvent {
1414 pub fn new(event: Event, signatures: Vec<IndexedSignature>) -> Self {
1416 Self { event, signatures }
1417 }
1418
1419 pub fn said(&self) -> &Said {
1421 self.event.said()
1422 }
1423
1424 pub fn sequence(&self) -> KeriSequence {
1426 self.event.sequence()
1427 }
1428
1429 pub fn prefix(&self) -> &Prefix {
1431 self.event.prefix()
1432 }
1433}
1434
1435pub fn serialize_attachment(signatures: &[IndexedSignature]) -> Result<Vec<u8>, AttachmentError> {
1441 use cesride::{Indexer, Siger, indexer};
1442
1443 let mut out = String::new();
1444 out.push_str("-A");
1445 out.push_str(&encode_count_b64(signatures.len())?);
1446
1447 for sig in signatures {
1448 let (code, ondex) = if sig.prior_index.is_some_and(|j| j != sig.index) {
1453 (indexer::Codex::Ed25519_Big, sig.prior_index)
1454 } else {
1455 (indexer::Codex::Ed25519, None)
1456 };
1457 let siger = Siger::new(
1458 None,
1459 Some(sig.index),
1460 ondex,
1461 Some(code),
1462 Some(&sig.sig),
1463 None,
1464 None,
1465 None,
1466 )
1467 .map_err(|e| AttachmentError::Encode(e.to_string()))?;
1468 let qb64 = siger
1469 .qb64()
1470 .map_err(|e| AttachmentError::Encode(e.to_string()))?;
1471 out.push_str(&qb64);
1472 }
1473
1474 Ok(out.into_bytes())
1475}
1476
1477fn indexer_hard_len(first: u8) -> usize {
1481 if first.is_ascii_digit() { 2 } else { 1 }
1482}
1483
1484fn indexer_sizage(code: &str) -> Option<(usize, usize)> {
1490 Some(match code {
1491 "A" | "B" | "C" | "D" | "E" | "F" => (88, 0),
1493 "2A" | "2B" | "2C" | "2D" | "2E" | "2F" => (92, 2),
1495 "0A" | "0B" => (156, 1),
1497 "3A" | "3B" => (160, 3),
1498 _ => return None,
1499 })
1500}
1501
1502pub fn parse_attachment(bytes: &[u8]) -> Result<Vec<IndexedSignature>, AttachmentError> {
1509 let s = std::str::from_utf8(bytes)
1510 .map_err(|e| AttachmentError::Decode(format!("non-utf8 attachment: {e}")))?;
1511
1512 if s.is_empty() {
1513 return Ok(vec![]);
1514 }
1515
1516 let (sigs, rest) = parse_sig_group(s)?;
1517 if !rest.is_empty() {
1518 return Err(AttachmentError::Decode(format!(
1519 "trailing bytes after signature group: {rest:?}"
1520 )));
1521 }
1522 Ok(sigs)
1523}
1524
1525fn parse_sig_group(s: &str) -> Result<(Vec<IndexedSignature>, &str), AttachmentError> {
1529 use cesride::{Indexer, Siger};
1530
1531 if !s.is_ascii() {
1534 return Err(AttachmentError::Decode("non-ASCII CESR attachment".into()));
1535 }
1536 let rest = s.strip_prefix("-A").ok_or_else(|| {
1537 AttachmentError::Decode("attachment must start with -A counter code".into())
1538 })?;
1539 if rest.len() < 2 {
1540 return Err(AttachmentError::Decode("truncated counter header".into()));
1541 }
1542 let (count_b64, body) = rest.split_at(2);
1543 let count = decode_count_b64(count_b64)?;
1544
1545 let mut out = Vec::with_capacity(count);
1546 let mut cursor = body;
1547 for _ in 0..count {
1548 let first = *cursor.as_bytes().first().ok_or_else(|| {
1549 AttachmentError::Decode("truncated group: expected another signature".into())
1550 })?;
1551 let hs = indexer_hard_len(first);
1552 if cursor.len() < hs {
1553 return Err(AttachmentError::Decode("truncated siger hard code".into()));
1554 }
1555 let code = &cursor[..hs];
1556 let (fs, os) = indexer_sizage(code)
1557 .ok_or_else(|| AttachmentError::Decode(format!("unknown indexer code {code:?}")))?;
1558 if cursor.len() < fs {
1559 return Err(AttachmentError::Decode(format!(
1560 "insufficient bytes for {code} siger: need {fs}, have {}",
1561 cursor.len()
1562 )));
1563 }
1564 let (siger_qb64, remainder) = cursor.split_at(fs);
1565 cursor = remainder;
1566
1567 let siger = Siger::new(None, None, None, None, None, None, Some(siger_qb64), None)
1568 .map_err(|e| AttachmentError::Decode(format!("Siger: {e}")))?;
1569 let prior_index = (os > 0).then(|| siger.ondex());
1572
1573 out.push(IndexedSignature {
1574 index: siger.index(),
1575 prior_index,
1576 sig: siger.raw(),
1577 });
1578 }
1579
1580 Ok((out, cursor))
1581}
1582
1583pub fn pair_kel_attachments(
1606 events: Vec<Event>,
1607 attachments: &[impl AsRef<[u8]>],
1608) -> Result<Vec<SignedEvent>, AttachmentError> {
1609 if attachments.len() != events.len() {
1610 return Err(AttachmentError::CountMismatch {
1611 events: events.len(),
1612 attachments: attachments.len(),
1613 });
1614 }
1615 events
1616 .into_iter()
1617 .zip(attachments)
1618 .map(|(event, att)| {
1619 let (signatures, seals) = parse_delegated_attachment(att.as_ref())?;
1626 let event = rehydrate_source_seal(event, seals.into_iter().next());
1627 Ok(SignedEvent::new(event, signatures))
1628 })
1629 .collect()
1630}
1631
1632fn rehydrate_source_seal(event: Event, seal: Option<SourceSeal>) -> Event {
1635 let Some(seal) = seal else {
1636 return event;
1637 };
1638 match event {
1639 Event::Dip(mut e) => {
1640 e.source_seal = Some(seal);
1641 Event::Dip(e)
1642 }
1643 Event::Drt(mut e) => {
1644 e.source_seal = Some(seal);
1645 Event::Drt(e)
1646 }
1647 other => other,
1648 }
1649}
1650
1651#[derive(Debug, thiserror::Error)]
1653#[non_exhaustive]
1654pub enum AttachmentError {
1655 #[error("attachment encode: {0}")]
1657 Encode(String),
1658 #[error("attachment decode: {0}")]
1660 Decode(String),
1661 #[error(
1665 "KEL/attachment count mismatch ({events} events vs {attachments} attachments): the KEL is unauthenticated, refusing"
1666 )]
1667 CountMismatch {
1668 events: usize,
1670 attachments: usize,
1672 },
1673}
1674
1675const B64_ALPHA: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
1677
1678fn encode_count_b64(count: usize) -> Result<String, AttachmentError> {
1679 if count >= 64 * 64 {
1680 return Err(AttachmentError::Encode(format!(
1681 "count {count} exceeds 2-char base64url max (4095)"
1682 )));
1683 }
1684 let hi = B64_ALPHA[(count >> 6) & 0x3f] as char;
1685 let lo = B64_ALPHA[count & 0x3f] as char;
1686 Ok(format!("{hi}{lo}"))
1687}
1688
1689fn decode_count_b64(s: &str) -> Result<usize, AttachmentError> {
1690 let mut it = s.chars();
1691 let hi = it
1692 .next()
1693 .and_then(b64_index)
1694 .ok_or_else(|| AttachmentError::Decode(format!("invalid count hi char: {s:?}")))?;
1695 let lo = it
1696 .next()
1697 .and_then(b64_index)
1698 .ok_or_else(|| AttachmentError::Decode(format!("invalid count lo char: {s:?}")))?;
1699 Ok((hi << 6) | lo)
1700}
1701
1702fn b64_index(c: char) -> Option<usize> {
1703 B64_ALPHA.iter().position(|&b| b as char == c)
1704}
1705
1706#[cfg(test)]
1707#[allow(clippy::unwrap_used)]
1708mod tests {
1709 use super::*;
1710
1711 #[test]
1712 fn attachment_roundtrip_single_sig() {
1713 let sigs = vec![IndexedSignature {
1714 index: 0,
1715 prior_index: None,
1716 sig: vec![0x42u8; 64],
1717 }];
1718 let bytes = serialize_attachment(&sigs).unwrap();
1719 let s = std::str::from_utf8(&bytes).unwrap();
1720 assert!(s.starts_with("-AAB"), "expected -AAB prefix, got {s:?}");
1721 let back = parse_attachment(&bytes).unwrap();
1722 assert_eq!(back.len(), 1);
1723 assert_eq!(back[0].index, 0);
1724 assert_eq!(back[0].sig, vec![0x42u8; 64]);
1725 }
1726
1727 #[test]
1728 fn attachment_parsers_reject_multibyte_without_panicking() {
1729 assert!(parse_attachment("-AAB\u{42c}".as_bytes()).is_err());
1732 assert!(parse_delegated_attachment("-AAB\u{42c}".as_bytes()).is_err());
1733
1734 let mut seal = String::from("-GAB");
1736 seal.push_str(&"A".repeat(23));
1737 seal.push('\u{42c}');
1738 assert!(parse_source_seal_couples(seal.as_bytes()).is_err());
1739 }
1740
1741 #[test]
1742 fn cesr_length_tables_match_cesride_encoding() {
1743 use cesride::{Diger, Matter, matter};
1744 let digest = Diger::new(
1746 None,
1747 Some(matter::Codex::Blake3_256),
1748 Some(&[0u8; 32]),
1749 None,
1750 None,
1751 None,
1752 )
1753 .and_then(|d| d.qb64())
1754 .unwrap();
1755 assert_eq!(saider_qb64_len(digest.as_bytes()[0]), Some(digest.len()));
1756
1757 for prior_index in [None, Some(1u32)] {
1760 let att = serialize_attachment(&[IndexedSignature {
1761 index: 0,
1762 prior_index,
1763 sig: vec![0u8; 64],
1764 }])
1765 .unwrap();
1766 let att = std::str::from_utf8(&att).unwrap();
1767 let siger = &att[4..];
1768 let code = &siger[..indexer_hard_len(siger.as_bytes()[0])];
1769 assert_eq!(
1770 indexer_sizage(code).map(|(fs, _)| fs),
1771 Some(siger.len()),
1772 "indexer_sizage({code:?}) drifted from cesride's {} chars",
1773 siger.len()
1774 );
1775 }
1776 }
1777
1778 #[test]
1779 fn attachment_roundtrip_three_sigs() {
1780 let sigs = vec![
1781 IndexedSignature {
1782 index: 0,
1783 prior_index: None,
1784 sig: vec![0x01u8; 64],
1785 },
1786 IndexedSignature {
1787 index: 1,
1788 prior_index: None,
1789 sig: vec![0x02u8; 64],
1790 },
1791 IndexedSignature {
1792 index: 2,
1793 prior_index: None,
1794 sig: vec![0x03u8; 64],
1795 },
1796 ];
1797 let bytes = serialize_attachment(&sigs).unwrap();
1798 let s = std::str::from_utf8(&bytes).unwrap();
1799 assert!(s.starts_with("-AAD"), "expected -AAD prefix, got {s:?}");
1800 let back = parse_attachment(&bytes).unwrap();
1801 assert_eq!(back.len(), 3);
1802 for (i, sig) in back.iter().enumerate() {
1803 assert_eq!(sig.index, i as u32);
1804 }
1805 }
1806
1807 #[test]
1808 fn attachment_empty() {
1809 let bytes = serialize_attachment(&[]).unwrap();
1810 assert_eq!(bytes, b"-AAA");
1811 let back = parse_attachment(&bytes).unwrap();
1812 assert!(back.is_empty());
1813 }
1814
1815 #[test]
1816 fn keri_sequence_serializes_as_hex() {
1817 assert_eq!(
1818 serde_json::to_string(&KeriSequence::new(0)).unwrap(),
1819 "\"0\""
1820 );
1821 assert_eq!(
1822 serde_json::to_string(&KeriSequence::new(10)).unwrap(),
1823 "\"a\""
1824 );
1825 assert_eq!(
1826 serde_json::to_string(&KeriSequence::new(255)).unwrap(),
1827 "\"ff\""
1828 );
1829 }
1830
1831 #[test]
1832 fn keri_sequence_deserializes_from_hex() {
1833 let s: KeriSequence = serde_json::from_str("\"0\"").unwrap();
1834 assert_eq!(s.value(), 0);
1835 let s: KeriSequence = serde_json::from_str("\"a\"").unwrap();
1836 assert_eq!(s.value(), 10);
1837 let s: KeriSequence = serde_json::from_str("\"ff\"").unwrap();
1838 assert_eq!(s.value(), 255);
1839 }
1840
1841 #[test]
1842 fn keri_sequence_rejects_invalid_hex() {
1843 assert!(serde_json::from_str::<KeriSequence>("\"not_hex\"").is_err());
1844 }
1845
1846 #[test]
1847 fn icp_event_always_serializes_d_a_c() {
1848 use crate::types::{CesrKey, Threshold, VersionString};
1849 let icp = IcpEvent {
1850 v: VersionString::placeholder(),
1851 d: Said::default(),
1852 i: Prefix::new_unchecked("ETest123".to_string()),
1853 s: KeriSequence::new(0),
1854 kt: Threshold::Simple(1),
1855 k: vec![CesrKey::new_unchecked("DKey123".to_string())],
1856 nt: Threshold::Simple(1),
1857 n: vec![Said::new_unchecked("ENext456".to_string())],
1858 bt: Threshold::Simple(0),
1859 b: vec![],
1860 c: vec![],
1861 a: vec![],
1862 };
1863 let json = serde_json::to_string(&icp).unwrap();
1864 assert!(json.contains("\"d\":"), "d must always be present");
1866 assert!(json.contains("\"a\":"), "a must always be present");
1867 assert!(json.contains("\"c\":"), "c must always be present");
1868 assert!(!json.contains("\"x\":"), "empty x must be omitted");
1870 assert!(json.contains("\"s\":\"0\""), "s must serialize as hex");
1871 }
1872
1873 #[test]
1874 fn event_enum_deserializes_by_t_field() {
1875 let json = r#"{"v":"KERI10JSON000000_","t":"icp","d":"ETestSaid","i":"E123","s":"0","kt":"1","k":["DKey"],"nt":"1","n":["ENext"],"bt":"0","b":[]}"#;
1876 let event: Event = serde_json::from_str(json).unwrap();
1877 assert!(event.is_inception());
1878 assert_eq!(event.sequence().value(), 0);
1879 }
1880
1881 #[test]
1882 fn digest_seal_roundtrips() {
1883 let seal = Seal::digest("EDigest123");
1884 let json = serde_json::to_string(&seal).unwrap();
1885 assert_eq!(json, r#"{"d":"EDigest123"}"#);
1886 let parsed: Seal = serde_json::from_str(&json).unwrap();
1887 assert_eq!(seal, parsed);
1888 }
1889
1890 #[test]
1891 fn seal_event_location_roundtrips() {
1892 let seal = Seal::EventLocation {
1894 i: Prefix::new_unchecked("EPrefix".to_string()),
1895 s: KeriSequence::new(3),
1896 p: Said::new_unchecked("EPrior".to_string()),
1897 t: "ixn".to_string(),
1898 d: Said::new_unchecked("EDigest".to_string()),
1899 };
1900 let json = serde_json::to_string(&seal).unwrap();
1901 let parsed: Seal = serde_json::from_str(&json).unwrap();
1902 assert_eq!(seal, parsed);
1903 assert_eq!(parsed.digest_value().map(|d| d.as_str()), Some("EDigest"));
1904 }
1905
1906 #[test]
1907 fn seal_rejects_malformed_i_s() {
1908 let r: Result<Seal, _> = serde_json::from_str(r#"{"i":"EPrefix","s":"3"}"#);
1911 assert!(r.is_err(), "malformed {{i,s}} seal must be rejected");
1912 let r2: Result<Seal, _> = serde_json::from_str(r#"{"zz":"x"}"#);
1914 assert!(r2.is_err());
1915 }
1916
1917 #[test]
1918 fn key_event_seal_roundtrips() {
1919 let seal = Seal::key_event(
1920 Prefix::new_unchecked("EPrefix".to_string()),
1921 KeriSequence::new(1),
1922 Said::new_unchecked("ESaid".to_string()),
1923 );
1924 let json = serde_json::to_string(&seal).unwrap();
1925 let parsed: Seal = serde_json::from_str(&json).unwrap();
1926 assert_eq!(seal, parsed);
1927 }
1928
1929 #[test]
1930 fn indexed_signature_serde_roundtrip() {
1931 let sig = IndexedSignature {
1932 index: 2,
1933 prior_index: None,
1934 sig: vec![0xab; 64],
1935 };
1936 let json = serde_json::to_string(&sig).unwrap();
1937 assert!(json.contains("\"index\":2"));
1938 assert!(
1939 !json.contains("prior_index"),
1940 "a None prior_index must be omitted from the wire: {json}"
1941 );
1942 let parsed: IndexedSignature = serde_json::from_str(&json).unwrap();
1943 assert_eq!(parsed, sig);
1944 }
1945
1946 #[test]
1947 fn signed_event_accessors() {
1948 use crate::types::{CesrKey, Threshold, VersionString};
1949 let icp = IcpEvent {
1950 v: VersionString::placeholder(),
1951 d: Said::new_unchecked("ESAID123".to_string()),
1952 i: Prefix::new_unchecked("EPrefix".to_string()),
1953 s: KeriSequence::new(0),
1954 kt: Threshold::Simple(1),
1955 k: vec![CesrKey::new_unchecked("DKey".to_string())],
1956 nt: Threshold::Simple(1),
1957 n: vec![Said::new_unchecked("ENext".to_string())],
1958 bt: Threshold::Simple(0),
1959 b: vec![],
1960 c: vec![],
1961 a: vec![],
1962 };
1963 let signed = SignedEvent::new(
1964 Event::Icp(icp),
1965 vec![IndexedSignature {
1966 index: 0,
1967 prior_index: None,
1968 sig: vec![0u8; 64],
1969 }],
1970 );
1971 assert_eq!(signed.said().as_str(), "ESAID123");
1972 assert_eq!(signed.sequence().value(), 0);
1973 assert_eq!(signed.prefix().as_str(), "EPrefix");
1974 assert_eq!(signed.signatures.len(), 1);
1975 assert_eq!(signed.signatures[0].index, 0);
1976 }
1977
1978 #[test]
1979 fn seal_digest_value() {
1980 let seal = Seal::digest("ETest123");
1981 assert_eq!(seal.digest_value().unwrap().as_str(), "ETest123");
1982 let latest = Seal::LatestEstablishment {
1983 i: Prefix::new_unchecked("EPrefix".to_string()),
1984 };
1985 assert!(latest.digest_value().is_none());
1986 }
1987}