Skip to main content

auths_keri/
events.rs

1//! Canonical KERI event types: Inception (ICP), Rotation (ROT), Interaction (IXN).
2//!
3//! These types are the single authoritative definition of KERI events for the
4//! entire workspace. All other crates import from here.
5
6use serde::ser::SerializeMap;
7use serde::{Deserialize, Serialize, Serializer};
8use std::fmt;
9
10use crate::types::{CesrKey, ConfigTrait, Prefix, Said, Threshold, VersionString};
11
12/// KERI protocol version prefix string.
13pub const KERI_VERSION_PREFIX: &str = "KERI10JSON";
14
15// ── KeriSequence ─────────────────────────────────────────────────────────────
16
17/// A KERI sequence number, stored internally as u64 and serialized as a hex string.
18///
19/// Sequence numbers are spec-compliant hex strings: "0", "1", "a", "ff", etc.
20///
21/// Usage:
22/// ```ignore
23/// let seq = KeriSequence::new(10);
24/// assert_eq!(seq.value(), 10);
25/// assert_eq!(serde_json::to_string(&seq).unwrap(), "\"a\"");
26/// ```
27#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
28pub struct KeriSequence(u128);
29
30#[cfg(feature = "schema")]
31impl schemars::JsonSchema for KeriSequence {
32    fn schema_name() -> String {
33        "KeriSequence".to_string()
34    }
35
36    fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
37        schemars::schema::SchemaObject {
38            instance_type: Some(schemars::schema::InstanceType::String.into()),
39            ..Default::default()
40        }
41        .into()
42    }
43}
44
45impl KeriSequence {
46    /// Create a new sequence number.
47    pub fn new(value: u128) -> Self {
48        Self(value)
49    }
50
51    /// Return the full u128 value.
52    pub fn value(self) -> u128 {
53        self.0
54    }
55}
56
57impl fmt::Display for KeriSequence {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        write!(f, "{:x}", self.0)
60    }
61}
62
63impl Serialize for KeriSequence {
64    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
65        serializer.serialize_str(&format!("{:x}", self.0))
66    }
67}
68
69impl<'de> Deserialize<'de> for KeriSequence {
70    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
71        let s = String::deserialize(deserializer)?;
72        let value = u128::from_str_radix(&s, 16)
73            .map_err(|_| serde::de::Error::custom(format!("invalid hex sequence: {s:?}")))?;
74        Ok(KeriSequence(value))
75    }
76}
77
78// ── Seal ─────────────────────────────────────────────────────────────────────
79
80/// KERI seal — anchors external data in an event's `a` field.
81///
82/// Variants are distinguished by field shape (untagged), not by a "type" discriminator.
83/// Per the spec, seal fields MUST appear in the specified order.
84///
85/// Usage:
86/// ```
87/// use auths_keri::Seal;
88/// let seal = Seal::digest("EDigest123");
89/// assert!(seal.digest_value().is_some());
90/// ```
91#[derive(Debug, Clone, PartialEq, Eq)]
92#[non_exhaustive]
93pub enum Seal {
94    /// Digest seal: `{"d": "<SAID>"}`
95    Digest {
96        /// SAID of the anchored data.
97        d: Said,
98    },
99    /// Source event seal: `{"s": "<hex-sn>", "d": "<SAID>"}`
100    SourceEvent {
101        /// Sequence number.
102        s: KeriSequence,
103        /// Event SAID.
104        d: Said,
105    },
106    /// Key event seal: `{"i": "<AID>", "s": "<hex-sn>", "d": "<SAID>"}`
107    KeyEvent {
108        /// AID.
109        i: Prefix,
110        /// Sequence number.
111        s: KeriSequence,
112        /// Event SAID.
113        d: Said,
114    },
115    /// Event-location seal (KERI v1.1 §7): `{"i","s","p","t","d"}`.
116    EventLocation {
117        /// AID.
118        i: Prefix,
119        /// Sequence number.
120        s: KeriSequence,
121        /// Prior event SAID.
122        p: Said,
123        /// Event type (ilk).
124        t: String,
125        /// Event SAID.
126        d: Said,
127    },
128    /// Latest establishment event seal: `{"i": "<AID>"}`
129    LatestEstablishment {
130        /// AID.
131        i: Prefix,
132    },
133    /// Merkle tree root digest seal: `{"rd": "<digest>"}` (non-spec extension).
134    #[cfg(feature = "seal-extensions")]
135    MerkleRoot {
136        /// Root digest.
137        rd: Said,
138    },
139    /// Registrar backer seal: `{"bi": "<AID>", "d": "<SAID>"}` (non-spec extension).
140    #[cfg(feature = "seal-extensions")]
141    RegistrarBacker {
142        /// Backer AID.
143        bi: Prefix,
144        /// Metadata SAID.
145        d: Said,
146    },
147}
148
149impl Seal {
150    /// Create a digest seal from a SAID.
151    pub fn digest(said: impl Into<String>) -> Self {
152        Self::Digest {
153            d: Said::new_unchecked(said.into()),
154        }
155    }
156
157    /// Create a key event seal.
158    pub fn key_event(prefix: Prefix, sequence: KeriSequence, said: Said) -> Self {
159        Self::KeyEvent {
160            i: prefix,
161            s: sequence,
162            d: said,
163        }
164    }
165
166    /// Get the digest from this seal, if it has one.
167    pub fn digest_value(&self) -> Option<&Said> {
168        match self {
169            Seal::Digest { d } => Some(d),
170            Seal::SourceEvent { d, .. } => Some(d),
171            Seal::KeyEvent { d, .. } => Some(d),
172            Seal::EventLocation { d, .. } => Some(d),
173            Seal::LatestEstablishment { .. } => None,
174            #[cfg(feature = "seal-extensions")]
175            Seal::RegistrarBacker { d, .. } => Some(d),
176            #[cfg(feature = "seal-extensions")]
177            Seal::MerkleRoot { rd } => Some(rd),
178        }
179    }
180}
181
182impl Serialize for Seal {
183    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
184        match self {
185            Seal::Digest { d } => {
186                let mut map = serializer.serialize_map(Some(1))?;
187                map.serialize_entry("d", d)?;
188                map.end()
189            }
190            Seal::SourceEvent { s, d } => {
191                let mut map = serializer.serialize_map(Some(2))?;
192                map.serialize_entry("s", s)?;
193                map.serialize_entry("d", d)?;
194                map.end()
195            }
196            Seal::KeyEvent { i, s, d } => {
197                let mut map = serializer.serialize_map(Some(3))?;
198                map.serialize_entry("i", i)?;
199                map.serialize_entry("s", s)?;
200                map.serialize_entry("d", d)?;
201                map.end()
202            }
203            Seal::EventLocation { i, s, p, t, d } => {
204                let mut map = serializer.serialize_map(Some(5))?;
205                map.serialize_entry("i", i)?;
206                map.serialize_entry("s", s)?;
207                map.serialize_entry("p", p)?;
208                map.serialize_entry("t", t)?;
209                map.serialize_entry("d", d)?;
210                map.end()
211            }
212            Seal::LatestEstablishment { i } => {
213                let mut map = serializer.serialize_map(Some(1))?;
214                map.serialize_entry("i", i)?;
215                map.end()
216            }
217            #[cfg(feature = "seal-extensions")]
218            Seal::MerkleRoot { rd } => {
219                let mut map = serializer.serialize_map(Some(1))?;
220                map.serialize_entry("rd", rd)?;
221                map.end()
222            }
223            #[cfg(feature = "seal-extensions")]
224            Seal::RegistrarBacker { bi, d } => {
225                let mut map = serializer.serialize_map(Some(2))?;
226                map.serialize_entry("bi", bi)?;
227                map.serialize_entry("d", d)?;
228                map.end()
229            }
230        }
231    }
232}
233
234impl<'de> Deserialize<'de> for Seal {
235    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
236        let map: serde_json::Map<String, serde_json::Value> =
237            serde_json::Map::deserialize(deserializer)?;
238
239        let want_str = |k: &str| -> Result<String, D::Error> {
240            map.get(k)
241                .and_then(|v| v.as_str())
242                .map(|v| v.to_string())
243                .ok_or_else(|| {
244                    serde::de::Error::custom(format!("seal field `{k}` must be a string"))
245                })
246        };
247        let want_seq =
248            |k: &str| -> Result<KeriSequence, D::Error> {
249                serde_json::from_value(map.get(k).cloned().ok_or_else(|| {
250                    serde::de::Error::custom(format!("seal field `{k}` required"))
251                })?)
252                .map_err(serde::de::Error::custom)
253            };
254
255        // Match on the EXACT sorted field set — no silent field-dropping (F-37).
256        let mut keys: Vec<&str> = map.keys().map(String::as_str).collect();
257        keys.sort_unstable();
258
259        match keys.as_slice() {
260            ["d"] => Ok(Seal::Digest {
261                d: Said::new_unchecked(want_str("d")?),
262            }),
263            ["d", "s"] => Ok(Seal::SourceEvent {
264                s: want_seq("s")?,
265                d: Said::new_unchecked(want_str("d")?),
266            }),
267            ["d", "i", "s"] => Ok(Seal::KeyEvent {
268                i: Prefix::new_unchecked(want_str("i")?),
269                s: want_seq("s")?,
270                d: Said::new_unchecked(want_str("d")?),
271            }),
272            ["d", "i", "p", "s", "t"] => Ok(Seal::EventLocation {
273                i: Prefix::new_unchecked(want_str("i")?),
274                s: want_seq("s")?,
275                p: Said::new_unchecked(want_str("p")?),
276                t: want_str("t")?,
277                d: Said::new_unchecked(want_str("d")?),
278            }),
279            ["i"] => Ok(Seal::LatestEstablishment {
280                i: Prefix::new_unchecked(want_str("i")?),
281            }),
282            #[cfg(feature = "seal-extensions")]
283            ["rd"] => Ok(Seal::MerkleRoot {
284                rd: Said::new_unchecked(want_str("rd")?),
285            }),
286            #[cfg(feature = "seal-extensions")]
287            ["bi", "d"] => Ok(Seal::RegistrarBacker {
288                bi: Prefix::new_unchecked(want_str("bi")?),
289                d: Said::new_unchecked(want_str("d")?),
290            }),
291            other => Err(serde::de::Error::custom(format!(
292                "unrecognized seal field set: {other:?}"
293            ))),
294        }
295    }
296}
297
298// ── Event Types ───────────────────────────────────────────────────────────────
299
300/// Inception event — creates a new KERI identity.
301///
302/// The inception event establishes the identifier prefix and commits
303/// to the first rotation key via the `n` (next) field.
304///
305/// Spec field order: `[v, t, d, i, s, kt, k, nt, n, bt, b, c, a]`
306#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
307#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
308pub struct IcpEvent {
309    /// Version string
310    pub v: VersionString,
311    /// SAID (Self-Addressing Identifier) — Blake3 hash of event
312    pub d: Said,
313    /// Identifier prefix (same as `d` for self-addressing inception)
314    pub i: Prefix,
315    /// Sequence number (always 0 for inception)
316    pub s: KeriSequence,
317    /// Key signing threshold (hex integer or fractional weight list)
318    pub kt: Threshold,
319    /// Current public key(s), CESR-encoded
320    pub k: Vec<CesrKey>,
321    /// Next key signing threshold
322    pub nt: Threshold,
323    /// Next key commitment(s) — Blake3 digests of next public key(s)
324    pub n: Vec<Said>,
325    /// Witness/backer threshold
326    pub bt: Threshold,
327    /// Witness/backer list (ordered AIDs)
328    #[serde(default)]
329    pub b: Vec<Prefix>,
330    /// Configuration traits (e.g., EstablishmentOnly, DoNotDelegate)
331    #[serde(default)]
332    pub c: Vec<ConfigTrait>,
333    /// Anchored seals
334    #[serde(default)]
335    pub a: Vec<Seal>,
336}
337
338/// Parameter struct for [`IcpEvent::new`]. Mirrors the existing
339/// field set 1-for-1 so call-site migration is a mechanical prefix/
340/// suffix wrap. Future wire-format additions land on `IcpEvent` via
341/// a `with_*` method, not by widening this `Init` struct — that's
342/// what makes the pattern future-proof.
343pub struct IcpEventInit {
344    /// Version string (`KERI10JSON…`); a placeholder until finalized.
345    pub v: VersionString,
346    /// Self-addressing identifier (the event SAID); blank until finalized.
347    pub d: Said,
348    /// Identifier prefix; for inception this equals the SAID (self-addressing).
349    pub i: Prefix,
350    /// Sequence number (0 for inception).
351    pub s: KeriSequence,
352    /// Signing threshold over the current keys `k`.
353    pub kt: Threshold,
354    /// Current signing keys (CESR-encoded verkeys).
355    pub k: Vec<CesrKey>,
356    /// Next-key threshold over the pre-rotation commitments `n`.
357    pub nt: Threshold,
358    /// Pre-rotation commitments (digests of the next keys).
359    pub n: Vec<Said>,
360    /// Backer (witness) threshold.
361    pub bt: Threshold,
362    /// Backer (witness) prefixes.
363    pub b: Vec<Prefix>,
364    /// Configuration traits (e.g. establishment-only, do-not-delegate).
365    pub c: Vec<ConfigTrait>,
366    /// Anchored seals.
367    pub a: Vec<Seal>,
368}
369
370impl IcpEvent {
371    /// Construct an `IcpEvent` from its required fields.
372    pub fn new(init: IcpEventInit) -> Self {
373        Self {
374            v: init.v,
375            d: init.d,
376            i: init.i,
377            s: init.s,
378            kt: init.kt,
379            k: init.k,
380            nt: init.nt,
381            n: init.n,
382            bt: init.bt,
383            b: init.b,
384            c: init.c,
385            a: init.a,
386        }
387    }
388}
389
390/// Spec field order: v, t, d, i, s, kt, k, nt, n, bt, b, c, a
391impl Serialize for IcpEvent {
392    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
393        let field_count = 13;
394        let mut map = serializer.serialize_map(Some(field_count))?;
395        map.serialize_entry("v", &self.v)?;
396        map.serialize_entry("t", "icp")?;
397        map.serialize_entry("d", &self.d)?;
398        map.serialize_entry("i", &self.i)?;
399        map.serialize_entry("s", &self.s)?;
400        map.serialize_entry("kt", &self.kt)?;
401        map.serialize_entry("k", &self.k)?;
402        map.serialize_entry("nt", &self.nt)?;
403        map.serialize_entry("n", &self.n)?;
404        map.serialize_entry("bt", &self.bt)?;
405        map.serialize_entry("b", &self.b)?;
406        map.serialize_entry("c", &self.c)?;
407        map.serialize_entry("a", &self.a)?;
408        map.end()
409    }
410}
411
412/// Rotation event — rotates to pre-committed key.
413///
414/// Spec field order: `[v, t, d, i, s, p, kt, k, nt, n, bt, br, ba, a]`
415#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
416#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
417pub struct RotEvent {
418    /// Version string
419    pub v: VersionString,
420    /// SAID of this event
421    pub d: Said,
422    /// Identifier prefix
423    pub i: Prefix,
424    /// Sequence number (increments with each event)
425    pub s: KeriSequence,
426    /// Previous event SAID (creates the hash chain)
427    pub p: Said,
428    /// Key signing threshold
429    pub kt: Threshold,
430    /// New current key(s), CESR-encoded
431    pub k: Vec<CesrKey>,
432    /// Next key signing threshold
433    pub nt: Threshold,
434    /// New next key commitment(s) — Blake3 digests
435    pub n: Vec<Said>,
436    /// Witness/backer threshold
437    pub bt: Threshold,
438    /// List of backers to remove (processed first)
439    #[serde(default)]
440    pub br: Vec<Prefix>,
441    /// List of backers to add (processed after removals)
442    #[serde(default)]
443    pub ba: Vec<Prefix>,
444    /// Configuration traits
445    #[serde(default)]
446    pub c: Vec<ConfigTrait>,
447    /// Anchored seals
448    #[serde(default)]
449    pub a: Vec<Seal>,
450}
451
452/// Parameter struct for [`RotEvent::new`]. See [`IcpEventInit`] for
453/// the pattern rationale.
454pub struct RotEventInit {
455    /// Version string (`KERI10JSON…`); a placeholder until finalized.
456    pub v: VersionString,
457    /// Self-addressing identifier (the event SAID); blank until finalized.
458    pub d: Said,
459    /// Identifier prefix of the rotating identity.
460    pub i: Prefix,
461    /// Sequence number (strictly greater than the prior event's).
462    pub s: KeriSequence,
463    /// Prior event SAID (the back-link establishing the chain).
464    pub p: Said,
465    /// Signing threshold over the new current keys `k`.
466    pub kt: Threshold,
467    /// New current signing keys (CESR-encoded verkeys) revealed by this rotation.
468    pub k: Vec<CesrKey>,
469    /// Next-key threshold over the new pre-rotation commitments `n`.
470    pub nt: Threshold,
471    /// New pre-rotation commitments (digests of the next keys).
472    pub n: Vec<Said>,
473    /// Backer (witness) threshold after applying the backer deltas.
474    pub bt: Threshold,
475    /// Backer (witness) prefixes to remove.
476    pub br: Vec<Prefix>,
477    /// Backer (witness) prefixes to add.
478    pub ba: Vec<Prefix>,
479    /// Configuration traits (e.g. `RB`/`NRB`) updated by this rotation.
480    pub c: Vec<ConfigTrait>,
481    /// Anchored seals carried in this event's `a[]`.
482    pub a: Vec<Seal>,
483}
484
485impl RotEvent {
486    /// Construct a `RotEvent` from its required fields.
487    pub fn new(init: RotEventInit) -> Self {
488        Self {
489            v: init.v,
490            d: init.d,
491            i: init.i,
492            s: init.s,
493            p: init.p,
494            kt: init.kt,
495            k: init.k,
496            nt: init.nt,
497            n: init.n,
498            bt: init.bt,
499            br: init.br,
500            ba: init.ba,
501            c: init.c,
502            a: init.a,
503        }
504    }
505}
506
507/// Spec field order: v, t, d, i, s, p, kt, k, nt, n, bt, br, ba, a
508impl Serialize for RotEvent {
509    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
510        let field_count = 14;
511        let mut map = serializer.serialize_map(Some(field_count))?;
512        map.serialize_entry("v", &self.v)?;
513        map.serialize_entry("t", "rot")?;
514        map.serialize_entry("d", &self.d)?;
515        map.serialize_entry("i", &self.i)?;
516        map.serialize_entry("s", &self.s)?;
517        map.serialize_entry("p", &self.p)?;
518        map.serialize_entry("kt", &self.kt)?;
519        map.serialize_entry("k", &self.k)?;
520        map.serialize_entry("nt", &self.nt)?;
521        map.serialize_entry("n", &self.n)?;
522        map.serialize_entry("bt", &self.bt)?;
523        map.serialize_entry("br", &self.br)?;
524        map.serialize_entry("ba", &self.ba)?;
525        map.serialize_entry("a", &self.a)?;
526        map.end()
527    }
528}
529
530/// Interaction event — anchors data without key rotation.
531///
532/// Spec field order: `[v, t, d, i, s, p, a]`
533#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
534#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
535pub struct IxnEvent {
536    /// Version string
537    pub v: VersionString,
538    /// SAID of this event
539    pub d: Said,
540    /// Identifier prefix
541    pub i: Prefix,
542    /// Sequence number
543    pub s: KeriSequence,
544    /// Previous event SAID
545    pub p: Said,
546    /// Anchored seals (the main purpose of IXN events)
547    pub a: Vec<Seal>,
548}
549
550/// Parameter struct for [`IxnEvent::new`].
551pub struct IxnEventInit {
552    pub v: VersionString,
553    pub d: Said,
554    pub i: Prefix,
555    pub s: KeriSequence,
556    pub p: Said,
557    pub a: Vec<Seal>,
558}
559
560impl IxnEvent {
561    /// Construct an `IxnEvent` from its required fields.
562    pub fn new(init: IxnEventInit) -> Self {
563        Self {
564            v: init.v,
565            d: init.d,
566            i: init.i,
567            s: init.s,
568            p: init.p,
569            a: init.a,
570        }
571    }
572}
573
574/// Spec field order: v, t, d, i, s, p, a
575impl Serialize for IxnEvent {
576    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
577        let field_count = 7;
578        let mut map = serializer.serialize_map(Some(field_count))?;
579        map.serialize_entry("v", &self.v)?;
580        map.serialize_entry("t", "ixn")?;
581        map.serialize_entry("d", &self.d)?;
582        map.serialize_entry("i", &self.i)?;
583        map.serialize_entry("s", &self.s)?;
584        map.serialize_entry("p", &self.p)?;
585        map.serialize_entry("a", &self.a)?;
586        map.end()
587    }
588}
589
590/// Unified event enum for processing any KERI event type.
591///
592/// Uses serde's tagged enum feature to deserialize based on the `t` field.
593///
594/// Usage:
595/// ```ignore
596/// let event: Event = serde_json::from_str(json)?;
597/// match event {
598///     Event::Icp(icp) => { /* inception */ }
599///     Event::Rot(rot) => { /* rotation */ }
600///     Event::Ixn(ixn) => { /* interaction */ }
601/// }
602/// ```
603/// Delegated inception event — creates a delegated KERI identity.
604///
605/// Same as ICP plus the `di` (delegator identifier prefix) field.
606/// Spec field order: `[v, t, d, i, s, kt, k, nt, n, bt, b, c, a, di]`
607#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
608#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
609pub struct DipEvent {
610    /// Version string
611    pub v: VersionString,
612    /// SAID
613    pub d: Said,
614    /// Identifier prefix (same as `d` for self-addressing)
615    pub i: Prefix,
616    /// Sequence number (always 0)
617    pub s: KeriSequence,
618    /// Key signing threshold
619    pub kt: Threshold,
620    /// Current public key(s)
621    pub k: Vec<CesrKey>,
622    /// Next key signing threshold
623    pub nt: Threshold,
624    /// Next key commitment(s)
625    pub n: Vec<Said>,
626    /// Witness/backer threshold
627    pub bt: Threshold,
628    /// Witness/backer list
629    #[serde(default)]
630    pub b: Vec<Prefix>,
631    /// Configuration traits
632    #[serde(default)]
633    pub c: Vec<ConfigTrait>,
634    /// Anchored seals
635    #[serde(default)]
636    pub a: Vec<Seal>,
637    /// Delegator identifier prefix
638    pub di: Prefix,
639    /// Delegate-side source seal (`-G` `SealSourceCouple`): a back-reference to
640    /// the delegator's anchoring event. Carried in the CESR **attachment**, never
641    /// in the event body — so it is `#[serde(skip)]` (absent from the JSON and from
642    /// SAID computation) and populated after the delegator anchors (the cooperative
643    /// double-anchor). `None` until/unless the anchoring event is known.
644    #[serde(skip)]
645    pub source_seal: Option<SourceSeal>,
646}
647
648/// Parameter struct for [`DipEvent::new`].
649pub struct DipEventInit {
650    /// Version string (KERI version + serialization + byte count).
651    pub v: VersionString,
652    /// Self-addressing identifier (SAID) of this event.
653    pub d: Said,
654    /// Identifier prefix (AID); self-addressing for a delegated AID (`i == d`).
655    pub i: Prefix,
656    /// Sequence number (0 for inception).
657    pub s: KeriSequence,
658    /// Current signing threshold.
659    pub kt: Threshold,
660    /// Current signing public keys (CESR-encoded).
661    pub k: Vec<CesrKey>,
662    /// Next (rotation) threshold.
663    pub nt: Threshold,
664    /// Next-key commitments (pre-rotation digests).
665    pub n: Vec<Said>,
666    /// Backer (witness) threshold.
667    pub bt: Threshold,
668    /// Backer (witness) list.
669    pub b: Vec<Prefix>,
670    /// Configuration traits.
671    pub c: Vec<ConfigTrait>,
672    /// Anchored seals.
673    pub a: Vec<Seal>,
674    /// Delegator prefix — the AID delegating this identifier.
675    pub di: Prefix,
676}
677
678impl DipEvent {
679    /// Construct a `DipEvent` from its required fields.
680    pub fn new(init: DipEventInit) -> Self {
681        Self {
682            v: init.v,
683            d: init.d,
684            i: init.i,
685            s: init.s,
686            kt: init.kt,
687            k: init.k,
688            nt: init.nt,
689            n: init.n,
690            bt: init.bt,
691            b: init.b,
692            c: init.c,
693            a: init.a,
694            di: init.di,
695            source_seal: None,
696        }
697    }
698}
699
700/// Spec field order: v, t, d, i, s, kt, k, nt, n, bt, b, c, a, di
701impl Serialize for DipEvent {
702    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
703        let field_count = 14;
704        let mut map = serializer.serialize_map(Some(field_count))?;
705        map.serialize_entry("v", &self.v)?;
706        map.serialize_entry("t", "dip")?;
707        map.serialize_entry("d", &self.d)?;
708        map.serialize_entry("i", &self.i)?;
709        map.serialize_entry("s", &self.s)?;
710        map.serialize_entry("kt", &self.kt)?;
711        map.serialize_entry("k", &self.k)?;
712        map.serialize_entry("nt", &self.nt)?;
713        map.serialize_entry("n", &self.n)?;
714        map.serialize_entry("bt", &self.bt)?;
715        map.serialize_entry("b", &self.b)?;
716        map.serialize_entry("c", &self.c)?;
717        map.serialize_entry("a", &self.a)?;
718        map.serialize_entry("di", &self.di)?;
719        map.end()
720    }
721}
722
723/// Delegated rotation event — rotates keys for a delegated identity.
724///
725/// Same field set as ROT but type `drt`. Validation requires checking the
726/// delegator's KEL for an anchoring seal.
727/// Spec field order: `[v, t, d, i, s, p, kt, k, nt, n, bt, br, ba, a]`
728#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
729#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
730pub struct DrtEvent {
731    /// Version string
732    pub v: VersionString,
733    /// SAID
734    pub d: Said,
735    /// Identifier prefix
736    pub i: Prefix,
737    /// Sequence number
738    pub s: KeriSequence,
739    /// Previous event SAID
740    pub p: Said,
741    /// Key signing threshold
742    pub kt: Threshold,
743    /// New current key(s)
744    pub k: Vec<CesrKey>,
745    /// Next key signing threshold
746    pub nt: Threshold,
747    /// New next key commitment(s)
748    pub n: Vec<Said>,
749    /// Witness/backer threshold
750    pub bt: Threshold,
751    /// Backers to remove
752    #[serde(default)]
753    pub br: Vec<Prefix>,
754    /// Backers to add
755    #[serde(default)]
756    pub ba: Vec<Prefix>,
757    /// Configuration traits
758    #[serde(default)]
759    pub c: Vec<ConfigTrait>,
760    /// Anchored seals
761    #[serde(default)]
762    pub a: Vec<Seal>,
763    /// Delegator identifier prefix (KERI §11).
764    pub di: Prefix,
765    /// Delegate-side source seal (`-G` `SealSourceCouple`) — see
766    /// [`DipEvent::source_seal`]. Carried in the attachment, set after the
767    /// delegator anchors this rotation.
768    #[serde(skip)]
769    pub source_seal: Option<SourceSeal>,
770}
771
772/// Parameter struct for [`DrtEvent::new`].
773pub struct DrtEventInit {
774    /// Version string (KERI version + serialization + byte count).
775    pub v: VersionString,
776    /// Self-addressing identifier (SAID) of this event.
777    pub d: Said,
778    /// Identifier prefix (AID) — the existing delegated AID (not self-addressing).
779    pub i: Prefix,
780    /// Sequence number.
781    pub s: KeriSequence,
782    /// Prior event SAID (chains to the previous event).
783    pub p: Said,
784    /// Current signing threshold.
785    pub kt: Threshold,
786    /// Current signing public keys (CESR-encoded) — the revealed pre-rotated keys.
787    pub k: Vec<CesrKey>,
788    /// Next (rotation) threshold.
789    pub nt: Threshold,
790    /// Next-key commitments (pre-rotation digests).
791    pub n: Vec<Said>,
792    /// Backer (witness) threshold.
793    pub bt: Threshold,
794    /// Backers (witnesses) to remove.
795    pub br: Vec<Prefix>,
796    /// Backers (witnesses) to add.
797    pub ba: Vec<Prefix>,
798    /// Configuration traits.
799    pub c: Vec<ConfigTrait>,
800    /// Anchored seals.
801    pub a: Vec<Seal>,
802    /// Delegator prefix — the AID delegating this identifier.
803    pub di: Prefix,
804}
805
806impl DrtEvent {
807    /// Construct a `DrtEvent` from its required fields.
808    pub fn new(init: DrtEventInit) -> Self {
809        Self {
810            v: init.v,
811            d: init.d,
812            i: init.i,
813            s: init.s,
814            p: init.p,
815            kt: init.kt,
816            k: init.k,
817            nt: init.nt,
818            n: init.n,
819            bt: init.bt,
820            br: init.br,
821            ba: init.ba,
822            c: init.c,
823            a: init.a,
824            di: init.di,
825            source_seal: None,
826        }
827    }
828}
829
830/// Spec field order (KERI §11): v, t, d, i, s, p, kt, k, nt, n, bt, br, ba, a, di
831impl Serialize for DrtEvent {
832    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
833        let field_count = 15;
834        let mut map = serializer.serialize_map(Some(field_count))?;
835        map.serialize_entry("v", &self.v)?;
836        map.serialize_entry("t", "drt")?;
837        map.serialize_entry("d", &self.d)?;
838        map.serialize_entry("i", &self.i)?;
839        map.serialize_entry("s", &self.s)?;
840        map.serialize_entry("p", &self.p)?;
841        map.serialize_entry("kt", &self.kt)?;
842        map.serialize_entry("k", &self.k)?;
843        map.serialize_entry("nt", &self.nt)?;
844        map.serialize_entry("n", &self.n)?;
845        map.serialize_entry("bt", &self.bt)?;
846        map.serialize_entry("br", &self.br)?;
847        map.serialize_entry("ba", &self.ba)?;
848        map.serialize_entry("a", &self.a)?;
849        map.serialize_entry("di", &self.di)?;
850        map.end()
851    }
852}
853
854/// Unified event enum for processing any KERI event type.
855#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
856#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
857#[serde(tag = "t")]
858pub enum Event {
859    /// Inception event
860    #[serde(rename = "icp")]
861    Icp(IcpEvent),
862    /// Rotation event
863    #[serde(rename = "rot")]
864    Rot(RotEvent),
865    /// Interaction event
866    #[serde(rename = "ixn")]
867    Ixn(IxnEvent),
868    /// Delegated inception event
869    #[serde(rename = "dip")]
870    Dip(DipEvent),
871    /// Delegated rotation event
872    #[serde(rename = "drt")]
873    Drt(DrtEvent),
874}
875
876impl Serialize for Event {
877    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
878        match self {
879            Event::Icp(e) => e.serialize(serializer),
880            Event::Rot(e) => e.serialize(serializer),
881            Event::Ixn(e) => e.serialize(serializer),
882            Event::Dip(e) => e.serialize(serializer),
883            Event::Drt(e) => e.serialize(serializer),
884        }
885    }
886}
887
888impl Event {
889    /// Get the SAID of this event.
890    pub fn said(&self) -> &Said {
891        match self {
892            Event::Icp(e) => &e.d,
893            Event::Rot(e) => &e.d,
894            Event::Ixn(e) => &e.d,
895            Event::Dip(e) => &e.d,
896            Event::Drt(e) => &e.d,
897        }
898    }
899
900    /// Get the sequence number of this event.
901    pub fn sequence(&self) -> KeriSequence {
902        match self {
903            Event::Icp(e) => e.s,
904            Event::Rot(e) => e.s,
905            Event::Ixn(e) => e.s,
906            Event::Dip(e) => e.s,
907            Event::Drt(e) => e.s,
908        }
909    }
910
911    /// Get the identifier prefix.
912    pub fn prefix(&self) -> &Prefix {
913        match self {
914            Event::Icp(e) => &e.i,
915            Event::Rot(e) => &e.i,
916            Event::Ixn(e) => &e.i,
917            Event::Dip(e) => &e.i,
918            Event::Drt(e) => &e.i,
919        }
920    }
921
922    /// Get the previous event SAID (None for inception/delegated inception).
923    pub fn previous(&self) -> Option<&Said> {
924        match self {
925            Event::Icp(_) | Event::Dip(_) => None,
926            Event::Rot(e) => Some(&e.p),
927            Event::Ixn(e) => Some(&e.p),
928            Event::Drt(e) => Some(&e.p),
929        }
930    }
931
932    /// Get the current keys (only for establishment events).
933    pub fn keys(&self) -> Option<&[CesrKey]> {
934        match self {
935            Event::Icp(e) => Some(&e.k),
936            Event::Rot(e) => Some(&e.k),
937            Event::Dip(e) => Some(&e.k),
938            Event::Drt(e) => Some(&e.k),
939            Event::Ixn(_) => None,
940        }
941    }
942
943    /// Get the next key commitments (only for establishment events).
944    pub fn next_commitments(&self) -> Option<&[Said]> {
945        match self {
946            Event::Icp(e) => Some(&e.n),
947            Event::Rot(e) => Some(&e.n),
948            Event::Dip(e) => Some(&e.n),
949            Event::Drt(e) => Some(&e.n),
950            Event::Ixn(_) => None,
951        }
952    }
953
954    /// Get the anchored seals.
955    pub fn anchors(&self) -> &[Seal] {
956        match self {
957            Event::Icp(e) => &e.a,
958            Event::Rot(e) => &e.a,
959            Event::Ixn(e) => &e.a,
960            Event::Dip(e) => &e.a,
961            Event::Drt(e) => &e.a,
962        }
963    }
964
965    /// Get the delegator AID (only for delegated inception).
966    pub fn delegator(&self) -> Option<&Prefix> {
967        match self {
968            Event::Dip(e) => Some(&e.di),
969            _ => None,
970        }
971    }
972
973    /// Check if this is an inception event (including delegated).
974    pub fn is_inception(&self) -> bool {
975        matches!(self, Event::Icp(_) | Event::Dip(_))
976    }
977
978    /// Check if this is a rotation event (including delegated).
979    pub fn is_rotation(&self) -> bool {
980        matches!(self, Event::Rot(_) | Event::Drt(_))
981    }
982
983    /// Check if this is an interaction event.
984    pub fn is_interaction(&self) -> bool {
985        matches!(self, Event::Ixn(_))
986    }
987
988    /// Check if this is a delegated event.
989    pub fn is_delegated(&self) -> bool {
990        matches!(self, Event::Dip(_) | Event::Drt(_))
991    }
992
993    /// Get the delegate-side source seal (`-G` back-reference to the delegator's
994    /// anchoring event), if this is a delegated event that has been anchored.
995    pub fn source_seal(&self) -> Option<&SourceSeal> {
996        match self {
997            Event::Dip(e) => e.source_seal.as_ref(),
998            Event::Drt(e) => e.source_seal.as_ref(),
999            _ => None,
1000        }
1001    }
1002}
1003
1004// ── Signed Event (externalized signatures) ──────────────────────────────────
1005
1006/// A single indexed controller signature.
1007///
1008/// The `index` maps to the position in the key list (`k` field) of the
1009/// signing key. Per the CESR spec, indexed signatures carry their key
1010/// index in the derivation code.
1011///
1012/// Usage:
1013/// ```
1014/// use auths_keri::IndexedSignature;
1015/// let sig = IndexedSignature { index: 0, prior_index: None, sig: vec![0u8; 64] };
1016/// assert_eq!(sig.index, 0);
1017/// ```
1018#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1019pub struct IndexedSignature {
1020    /// Index into the new key list `k[]` (which current key signed).
1021    pub index: u32,
1022    /// For a rotation signature, the index into the prior event's next-key
1023    /// commitment list `n[]` that this signature reveals (CESR *ondex*).
1024    ///
1025    /// `Some(j)` emits a dual-index ("Big", `2A`) siger; `None` emits the
1026    /// single-index code (`A`) with ondex == index — preserving the wire
1027    /// bytes of every single-index signer (inception, symmetric rotation).
1028    #[serde(default, skip_serializing_if = "Option::is_none")]
1029    pub prior_index: Option<u32>,
1030    /// Raw signature bytes (64 bytes for Ed25519).
1031    #[serde(with = "hex::serde")]
1032    pub sig: Vec<u8>,
1033}
1034
1035/// A delegate-side source seal — the parsed form of a CESR `-G` `SealSourceCouple`.
1036///
1037/// A delegated event (`dip`/`drt`) carries one of these as an **attachment** (never
1038/// in the body) to bind itself back to the *specific* delegator event that anchored
1039/// it: `s` is that anchoring event's sequence number (CESR `Seqner`), `d` is its
1040/// SAID (CESR `Saider`). Together with the delegator-side `Seal::KeyEvent` (which
1041/// points the other way) this forms the **bilateral** delegation binding keripy
1042/// requires. Byte-aligned with keripy 1.3.4's `-G` couple.
1043#[derive(Debug, Clone, PartialEq, Eq)]
1044#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1045pub struct SourceSeal {
1046    /// Sequence number of the delegator's anchoring event.
1047    pub s: KeriSequence,
1048    /// SAID of the delegator's anchoring event.
1049    pub d: Said,
1050}
1051
1052/// Delegator-anchored scope/expiry for a delegated agent (Epic E.7).
1053///
1054/// Carried as a `Seal::Digest` in the **delegator's** anchoring `ixn` — authority
1055/// comes from the party that controls the delegator key, never agent-self-asserted.
1056/// The digest value is a structured marker string (not a SAID) namespaced under
1057/// `agentscope:`, so it never collides with a real digest, a revocation (bare
1058/// prefix), or an `agent:` role marker. Advisory authorization; the principled
1059/// upgrade is a targeted ACDC (Epic F).
1060#[derive(Debug, Clone, PartialEq, Eq, Default)]
1061#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1062pub struct AgentScope {
1063    /// Capabilities granted to the agent (empty = unrestricted). Capability strings
1064    /// may contain `:` (e.g. `repo:foo`) but not `,` (the list separator).
1065    pub capabilities: Vec<String>,
1066    /// Expiry as Unix epoch seconds; `None` = never expires.
1067    pub expires_at: Option<i64>,
1068}
1069
1070/// Marker prefix for an agent-scope `Seal::Digest` value.
1071const AGENT_SCOPE_MARKER: &str = "agentscope:";
1072
1073/// Encode an agent-scope seal value: `agentscope:{prefix}:{expires_or_0}:{caps_csv}`.
1074///
1075/// Args:
1076/// * `agent_prefix`: The agent's KEL prefix the scope applies to.
1077/// * `scope`: The granted capabilities + optional expiry.
1078///
1079/// Usage:
1080/// ```
1081/// use auths_keri::{AgentScope, encode_agent_scope};
1082/// let s = AgentScope { capabilities: vec!["sign_commit".into()], expires_at: Some(99) };
1083/// assert_eq!(encode_agent_scope("Eabc", &s), "agentscope:Eabc:99:sign_commit");
1084/// ```
1085pub fn encode_agent_scope(agent_prefix: &str, scope: &AgentScope) -> String {
1086    let expires = scope.expires_at.unwrap_or(0);
1087    let caps = scope.capabilities.join(",");
1088    format!("{AGENT_SCOPE_MARKER}{agent_prefix}:{expires}:{caps}")
1089}
1090
1091/// Decode an agent-scope seal value into `(agent_prefix, AgentScope)`, or `None` if
1092/// the value is not an `agentscope:` marker. Inverse of [`encode_agent_scope`].
1093///
1094/// Args:
1095/// * `value`: A `Seal::Digest` value to interpret.
1096pub fn decode_agent_scope(value: &str) -> Option<(String, AgentScope)> {
1097    // {prefix}:{expires}:{caps_csv} — prefix is a `:`-free KERI prefix; caps may
1098    // contain `:`, so the 3-way split keeps the caps tail intact.
1099    let rest = value.strip_prefix(AGENT_SCOPE_MARKER)?;
1100    let mut parts = rest.splitn(3, ':');
1101    let prefix = parts.next()?.to_string();
1102    let expires: i64 = parts.next()?.parse().ok()?;
1103    let caps_csv = parts.next().unwrap_or("");
1104    let capabilities = if caps_csv.is_empty() {
1105        Vec::new()
1106    } else {
1107        caps_csv.split(',').map(|c| c.to_string()).collect()
1108    };
1109    Some((
1110        prefix,
1111        AgentScope {
1112            capabilities,
1113            expires_at: (expires != 0).then_some(expires),
1114        },
1115    ))
1116}
1117
1118/// Serialize source seals to a CESR text-domain `-G##<Seqner><Saider>…`
1119/// `SealSourceCouples` group, byte-aligned with keripy 1.3.4.
1120///
1121/// Each couple is a `Seqner` (`0A`-coded 128-bit sequence) followed by a `Saider`
1122/// (the anchoring event's SAID). Reads back via [`parse_source_seal_couples`].
1123///
1124/// Args:
1125/// * `couples`: The delegate-side source seals to encode.
1126///
1127/// Usage:
1128/// ```ignore
1129/// let bytes = serialize_source_seal_couples(&[SourceSeal { s, d }])?;
1130/// ```
1131pub fn serialize_source_seal_couples(couples: &[SourceSeal]) -> Result<Vec<u8>, AttachmentError> {
1132    use cesride::{Counter, Matter, Saider, Seqner, counter};
1133
1134    let count = u32::try_from(couples.len())
1135        .map_err(|_| AttachmentError::Encode("too many source seal couples".into()))?;
1136    let mut out = Counter::new_with_code_and_count(counter::Codex::SealSourceCouples, count)
1137        .and_then(|c| c.qb64())
1138        .map_err(|e| AttachmentError::Encode(e.to_string()))?;
1139
1140    for couple in couples {
1141        let seqner = Seqner::new_with_sn(couple.s.value())
1142            .and_then(|s| s.qb64())
1143            .map_err(|e| AttachmentError::Encode(e.to_string()))?;
1144        let saider = Saider::new_with_qb64(couple.d.as_str())
1145            .and_then(|s| s.qb64())
1146            .map_err(|e| AttachmentError::Encode(e.to_string()))?;
1147        out.push_str(&seqner);
1148        out.push_str(&saider);
1149    }
1150
1151    Ok(out.into_bytes())
1152}
1153
1154/// Parse a CESR `-G##` `SealSourceCouples` group into [`SourceSeal`]s.
1155///
1156/// Inverse of [`serialize_source_seal_couples`]; code-directed so each `Seqner`
1157/// and `Saider` width is read from its CESR hard code.
1158///
1159/// Args:
1160/// * `bytes`: The `-G`-prefixed couple-group bytes (e.g. from an attachment tail).
1161pub fn parse_source_seal_couples(bytes: &[u8]) -> Result<Vec<SourceSeal>, AttachmentError> {
1162    let s = std::str::from_utf8(bytes)
1163        .map_err(|e| AttachmentError::Decode(format!("non-utf8 source-seal group: {e}")))?;
1164    let (couples, rest) = parse_source_seal_group(s)?;
1165    if !rest.is_empty() {
1166        return Err(AttachmentError::Decode(format!(
1167            "trailing bytes after source-seal group: {rest:?}"
1168        )));
1169    }
1170    Ok(couples)
1171}
1172
1173/// Parse one `-G` `SealSourceCouples` group at the head of `s`, returning the
1174/// couples and the unconsumed remainder.
1175fn parse_source_seal_group(s: &str) -> Result<(Vec<SourceSeal>, &str), AttachmentError> {
1176    use cesride::{Matter, Saider, Seqner};
1177
1178    let rest = s.strip_prefix("-G").ok_or_else(|| {
1179        AttachmentError::Decode("source-seal group must start with -G counter code".into())
1180    })?;
1181    if rest.len() < 2 {
1182        return Err(AttachmentError::Decode(
1183            "truncated -G counter header".into(),
1184        ));
1185    }
1186    let (count_b64, mut cursor) = rest.split_at(2);
1187    let count = decode_count_b64(count_b64)?;
1188
1189    let mut out = Vec::with_capacity(count);
1190    for _ in 0..count {
1191        // Seqner is fixed-width (`0A` + 22 base64 = 24 chars; a 128-bit number).
1192        if cursor.len() < SEQNER_QB64_LEN {
1193            return Err(AttachmentError::Decode(
1194                "truncated Seqner in -G couple".into(),
1195            ));
1196        }
1197        let (seqner_qb64, after_seqner) = cursor.split_at(SEQNER_QB64_LEN);
1198        let seqner = Seqner::new_with_qb64(seqner_qb64)
1199            .map_err(|e| AttachmentError::Decode(format!("Seqner: {e}")))?;
1200        let sn = seqner
1201            .sn()
1202            .map_err(|e| AttachmentError::Decode(format!("Seqner sn: {e}")))?;
1203
1204        // Saider is code-directed; the leading char fixes its full width.
1205        let first = *after_seqner.as_bytes().first().ok_or_else(|| {
1206            AttachmentError::Decode("truncated -G couple: expected a Saider".into())
1207        })?;
1208        let fs = saider_qb64_len(first).ok_or_else(|| {
1209            AttachmentError::Decode(format!("unsupported Saider code byte {first:?}"))
1210        })?;
1211        if after_seqner.len() < fs {
1212            return Err(AttachmentError::Decode(
1213                "truncated Saider in -G couple".into(),
1214            ));
1215        }
1216        let (saider_qb64, remainder) = after_seqner.split_at(fs);
1217        let saider = Saider::new_with_qb64(saider_qb64)
1218            .and_then(|s| s.qb64())
1219            .map_err(|e| AttachmentError::Decode(format!("Saider: {e}")))?;
1220        cursor = remainder;
1221
1222        out.push(SourceSeal {
1223            s: KeriSequence::new(sn),
1224            d: Said::new_unchecked(saider),
1225        });
1226    }
1227    Ok((out, cursor))
1228}
1229
1230/// Parse a delegated event's combined attachment — the controller signature group
1231/// (`-A`) followed by an optional source-seal group (`-G`).
1232///
1233/// Returns the indexed signatures and any source seals. Used by storage read paths
1234/// to re-attach a delegated event's `-G` back-reference after a JSON round-trip.
1235///
1236/// Args:
1237/// * `bytes`: The full attachment bytes (`-A…` then optional `-G…`).
1238pub fn parse_delegated_attachment(
1239    bytes: &[u8],
1240) -> Result<(Vec<IndexedSignature>, Vec<SourceSeal>), AttachmentError> {
1241    let s = std::str::from_utf8(bytes)
1242        .map_err(|e| AttachmentError::Decode(format!("non-utf8 attachment: {e}")))?;
1243    if s.is_empty() {
1244        return Ok((vec![], vec![]));
1245    }
1246    let (sigs, rest) = parse_sig_group(s)?;
1247    if rest.is_empty() {
1248        return Ok((sigs, vec![]));
1249    }
1250    let (couples, tail) = parse_source_seal_group(rest)?;
1251    if !tail.is_empty() {
1252        return Err(AttachmentError::Decode(format!(
1253            "trailing bytes after delegated attachment: {tail:?}"
1254        )));
1255    }
1256    Ok((sigs, couples))
1257}
1258
1259/// CESR `Seqner` qb64 width: `0A` hard code (2) + 22 base64 chars = 24.
1260const SEQNER_QB64_LEN: usize = 24;
1261
1262/// qb64 full width of a 32-byte digest `Saider` from its leading code char.
1263/// Blake3-256 (`E`), Blake2b-256 (`F`), Blake2s-256 (`G`), SHA3-256 (`H`),
1264/// SHA2-256 (`I`) are all single-char-coded 44-char primitives. Mirrors cesride
1265/// `matter` sizage for the digest family auths emits.
1266fn saider_qb64_len(first: u8) -> Option<usize> {
1267    matches!(first, b'E' | b'F' | b'G' | b'H' | b'I').then_some(44)
1268}
1269
1270/// An event paired with its detached signature(s).
1271///
1272/// Per the KERI spec, signatures are NOT part of the event body. They are
1273/// attached externally (CESR attachment codes in streams, or stored alongside
1274/// in databases). The event body is what gets hashed for the SAID.
1275///
1276/// Usage:
1277/// ```ignore
1278/// use auths_keri::{SignedEvent, IndexedSignature, Event};
1279///
1280/// // After creating and finalizing an event:
1281/// let signed = SignedEvent::new(event, vec![IndexedSignature { index: 0, sig: sig_bytes }]);
1282/// ```
1283#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1284pub struct SignedEvent {
1285    /// The event body (no signature data).
1286    pub event: Event,
1287    /// Controller-indexed signatures (detached from body).
1288    pub signatures: Vec<IndexedSignature>,
1289}
1290
1291impl SignedEvent {
1292    /// Create a new signed event from an event and its signatures.
1293    pub fn new(event: Event, signatures: Vec<IndexedSignature>) -> Self {
1294        Self { event, signatures }
1295    }
1296
1297    /// Get the SAID of the inner event.
1298    pub fn said(&self) -> &Said {
1299        self.event.said()
1300    }
1301
1302    /// Get the sequence number of the inner event.
1303    pub fn sequence(&self) -> KeriSequence {
1304        self.event.sequence()
1305    }
1306
1307    /// Get the identifier prefix of the inner event.
1308    pub fn prefix(&self) -> &Prefix {
1309        self.event.prefix()
1310    }
1311}
1312
1313/// Serialize a list of externalized signatures to CESR text-domain
1314/// `-A##<siger1><siger2>…` indexed-signature group bytes.
1315///
1316/// Wire format: `-A` prefix + 2-char base64url count + each signature as
1317/// a CESR `Siger` (qb64). Reads back via `parse_attachment`.
1318pub fn serialize_attachment(signatures: &[IndexedSignature]) -> Result<Vec<u8>, AttachmentError> {
1319    use cesride::{Indexer, Siger, indexer};
1320
1321    let mut out = String::new();
1322    out.push_str("-A");
1323    out.push_str(&encode_count_b64(signatures.len())?);
1324
1325    for sig in signatures {
1326        // Ed25519 indexed signature (auths' sign paths produce Ed25519). A
1327        // distinct prior-commitment index (`prior_index != index`) needs the
1328        // dual-index "Big" code `2A`, which carries both indices; otherwise the
1329        // single-index `A` (ondex == index) keeps the legacy 88-char bytes.
1330        let (code, ondex) = if sig.prior_index.is_some_and(|j| j != sig.index) {
1331            (indexer::Codex::Ed25519_Big, sig.prior_index)
1332        } else {
1333            (indexer::Codex::Ed25519, None)
1334        };
1335        let siger = Siger::new(
1336            None,
1337            Some(sig.index),
1338            ondex,
1339            Some(code),
1340            Some(&sig.sig),
1341            None,
1342            None,
1343            None,
1344        )
1345        .map_err(|e| AttachmentError::Encode(e.to_string()))?;
1346        let qb64 = siger
1347            .qb64()
1348            .map_err(|e| AttachmentError::Encode(e.to_string()))?;
1349        out.push_str(&qb64);
1350    }
1351
1352    Ok(out.into_bytes())
1353}
1354
1355/// CESR Indexer hard-code length: codes whose first char is a digit (`0`–`4`)
1356/// are multi-char selectors; all others are single-char. Mirrors cesride's
1357/// `indexer` hardage (whose tables are `pub(crate)`).
1358fn indexer_hard_len(first: u8) -> usize {
1359    if first.is_ascii_digit() { 2 } else { 1 }
1360}
1361
1362/// `(full qb64 size, ondex size)` for a CESR Indexer signature code, mirroring
1363/// cesride `indexer/tables.rs` (its `sizage` is `pub(crate)`, not callable here).
1364///
1365/// `os > 0` marks a dual-index ("Big") code carrying a *distinct* prior-commitment
1366/// index (ondex); single-index codes have `os == 0` (ondex echoes index).
1367fn indexer_sizage(code: &str) -> Option<(usize, usize)> {
1368    Some(match code {
1369        // single-index: Ed25519 A/B, secp256k1 C/D, P-256 E/F (+ Crt variants)
1370        "A" | "B" | "C" | "D" | "E" | "F" => (88, 0),
1371        // dual-index ("Big"): Ed25519 2A/2B, secp256k1 2C/2D, P-256 2E/2F
1372        "2A" | "2B" | "2C" | "2D" | "2E" | "2F" => (92, 2),
1373        // large/huge index variants (completeness; not emitted by auths)
1374        "0A" | "0B" => (156, 1),
1375        "3A" | "3B" => (160, 3),
1376        _ => return None,
1377    })
1378}
1379
1380/// Parse a CESR `-A##` indexed-signature group into the constituent
1381/// `IndexedSignature`s.
1382///
1383/// Code-directed: each siger's width is read from its CESR hard code (so a group
1384/// may mix single-index `A` (88 ch) and dual-index `2A` (92 ch), or curves). A
1385/// signature under a dual-index code populates `prior_index` from its ondex.
1386pub fn parse_attachment(bytes: &[u8]) -> Result<Vec<IndexedSignature>, AttachmentError> {
1387    let s = std::str::from_utf8(bytes)
1388        .map_err(|e| AttachmentError::Decode(format!("non-utf8 attachment: {e}")))?;
1389
1390    if s.is_empty() {
1391        return Ok(vec![]);
1392    }
1393
1394    let (sigs, rest) = parse_sig_group(s)?;
1395    if !rest.is_empty() {
1396        return Err(AttachmentError::Decode(format!(
1397            "trailing bytes after signature group: {rest:?}"
1398        )));
1399    }
1400    Ok(sigs)
1401}
1402
1403/// Parse one `-A` indexed-signature group at the head of `s`, returning the
1404/// signatures and the unconsumed remainder (e.g. a trailing `-G` source-seal
1405/// group on a delegated event).
1406fn parse_sig_group(s: &str) -> Result<(Vec<IndexedSignature>, &str), AttachmentError> {
1407    use cesride::{Indexer, Siger};
1408
1409    let rest = s.strip_prefix("-A").ok_or_else(|| {
1410        AttachmentError::Decode("attachment must start with -A counter code".into())
1411    })?;
1412    if rest.len() < 2 {
1413        return Err(AttachmentError::Decode("truncated counter header".into()));
1414    }
1415    let (count_b64, body) = rest.split_at(2);
1416    let count = decode_count_b64(count_b64)?;
1417
1418    let mut out = Vec::with_capacity(count);
1419    let mut cursor = body;
1420    for _ in 0..count {
1421        let first = *cursor.as_bytes().first().ok_or_else(|| {
1422            AttachmentError::Decode("truncated group: expected another signature".into())
1423        })?;
1424        let hs = indexer_hard_len(first);
1425        if cursor.len() < hs {
1426            return Err(AttachmentError::Decode("truncated siger hard code".into()));
1427        }
1428        let code = &cursor[..hs];
1429        let (fs, os) = indexer_sizage(code)
1430            .ok_or_else(|| AttachmentError::Decode(format!("unknown indexer code {code:?}")))?;
1431        if cursor.len() < fs {
1432            return Err(AttachmentError::Decode(format!(
1433                "insufficient bytes for {code} siger: need {fs}, have {}",
1434                cursor.len()
1435            )));
1436        }
1437        let (siger_qb64, remainder) = cursor.split_at(fs);
1438        cursor = remainder;
1439
1440        let siger = Siger::new(None, None, None, None, None, None, Some(siger_qb64), None)
1441            .map_err(|e| AttachmentError::Decode(format!("Siger: {e}")))?;
1442        // A distinct prior-commitment index is carried only by dual-index codes
1443        // (os > 0); single-index codes report ondex == index, which is not a binding.
1444        let prior_index = (os > 0).then(|| siger.ondex());
1445
1446        out.push(IndexedSignature {
1447            index: siger.index(),
1448            prior_index,
1449            sig: siger.raw(),
1450        });
1451    }
1452
1453    Ok((out, cursor))
1454}
1455
1456/// Error shape for attachment encode/decode.
1457#[derive(Debug, thiserror::Error)]
1458#[non_exhaustive]
1459pub enum AttachmentError {
1460    /// Encoding an indexed signature into CESR failed.
1461    #[error("attachment encode: {0}")]
1462    Encode(String),
1463    /// Decoding a CESR attachment stream failed (bad counter, malformed Siger, etc.).
1464    #[error("attachment decode: {0}")]
1465    Decode(String),
1466}
1467
1468/// CESR base64url alphabet, ordered so `B64_ALPHA[n]` is the char for n.
1469const B64_ALPHA: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
1470
1471fn encode_count_b64(count: usize) -> Result<String, AttachmentError> {
1472    if count >= 64 * 64 {
1473        return Err(AttachmentError::Encode(format!(
1474            "count {count} exceeds 2-char base64url max (4095)"
1475        )));
1476    }
1477    let hi = B64_ALPHA[(count >> 6) & 0x3f] as char;
1478    let lo = B64_ALPHA[count & 0x3f] as char;
1479    Ok(format!("{hi}{lo}"))
1480}
1481
1482fn decode_count_b64(s: &str) -> Result<usize, AttachmentError> {
1483    let mut it = s.chars();
1484    let hi = it
1485        .next()
1486        .and_then(b64_index)
1487        .ok_or_else(|| AttachmentError::Decode(format!("invalid count hi char: {s:?}")))?;
1488    let lo = it
1489        .next()
1490        .and_then(b64_index)
1491        .ok_or_else(|| AttachmentError::Decode(format!("invalid count lo char: {s:?}")))?;
1492    Ok((hi << 6) | lo)
1493}
1494
1495fn b64_index(c: char) -> Option<usize> {
1496    B64_ALPHA.iter().position(|&b| b as char == c)
1497}
1498
1499#[cfg(test)]
1500#[allow(clippy::unwrap_used)]
1501mod tests {
1502    use super::*;
1503
1504    #[test]
1505    fn attachment_roundtrip_single_sig() {
1506        let sigs = vec![IndexedSignature {
1507            index: 0,
1508            prior_index: None,
1509            sig: vec![0x42u8; 64],
1510        }];
1511        let bytes = serialize_attachment(&sigs).unwrap();
1512        let s = std::str::from_utf8(&bytes).unwrap();
1513        assert!(s.starts_with("-AAB"), "expected -AAB prefix, got {s:?}");
1514        let back = parse_attachment(&bytes).unwrap();
1515        assert_eq!(back.len(), 1);
1516        assert_eq!(back[0].index, 0);
1517        assert_eq!(back[0].sig, vec![0x42u8; 64]);
1518    }
1519
1520    #[test]
1521    fn attachment_roundtrip_three_sigs() {
1522        let sigs = vec![
1523            IndexedSignature {
1524                index: 0,
1525                prior_index: None,
1526                sig: vec![0x01u8; 64],
1527            },
1528            IndexedSignature {
1529                index: 1,
1530                prior_index: None,
1531                sig: vec![0x02u8; 64],
1532            },
1533            IndexedSignature {
1534                index: 2,
1535                prior_index: None,
1536                sig: vec![0x03u8; 64],
1537            },
1538        ];
1539        let bytes = serialize_attachment(&sigs).unwrap();
1540        let s = std::str::from_utf8(&bytes).unwrap();
1541        assert!(s.starts_with("-AAD"), "expected -AAD prefix, got {s:?}");
1542        let back = parse_attachment(&bytes).unwrap();
1543        assert_eq!(back.len(), 3);
1544        for (i, sig) in back.iter().enumerate() {
1545            assert_eq!(sig.index, i as u32);
1546        }
1547    }
1548
1549    #[test]
1550    fn attachment_empty() {
1551        let bytes = serialize_attachment(&[]).unwrap();
1552        assert_eq!(bytes, b"-AAA");
1553        let back = parse_attachment(&bytes).unwrap();
1554        assert!(back.is_empty());
1555    }
1556
1557    #[test]
1558    fn keri_sequence_serializes_as_hex() {
1559        assert_eq!(
1560            serde_json::to_string(&KeriSequence::new(0)).unwrap(),
1561            "\"0\""
1562        );
1563        assert_eq!(
1564            serde_json::to_string(&KeriSequence::new(10)).unwrap(),
1565            "\"a\""
1566        );
1567        assert_eq!(
1568            serde_json::to_string(&KeriSequence::new(255)).unwrap(),
1569            "\"ff\""
1570        );
1571    }
1572
1573    #[test]
1574    fn keri_sequence_deserializes_from_hex() {
1575        let s: KeriSequence = serde_json::from_str("\"0\"").unwrap();
1576        assert_eq!(s.value(), 0);
1577        let s: KeriSequence = serde_json::from_str("\"a\"").unwrap();
1578        assert_eq!(s.value(), 10);
1579        let s: KeriSequence = serde_json::from_str("\"ff\"").unwrap();
1580        assert_eq!(s.value(), 255);
1581    }
1582
1583    #[test]
1584    fn keri_sequence_rejects_invalid_hex() {
1585        assert!(serde_json::from_str::<KeriSequence>("\"not_hex\"").is_err());
1586    }
1587
1588    #[test]
1589    fn icp_event_always_serializes_d_a_c() {
1590        use crate::types::{CesrKey, Threshold, VersionString};
1591        let icp = IcpEvent {
1592            v: VersionString::placeholder(),
1593            d: Said::default(),
1594            i: Prefix::new_unchecked("ETest123".to_string()),
1595            s: KeriSequence::new(0),
1596            kt: Threshold::Simple(1),
1597            k: vec![CesrKey::new_unchecked("DKey123".to_string())],
1598            nt: Threshold::Simple(1),
1599            n: vec![Said::new_unchecked("ENext456".to_string())],
1600            bt: Threshold::Simple(0),
1601            b: vec![],
1602            c: vec![],
1603            a: vec![],
1604        };
1605        let json = serde_json::to_string(&icp).unwrap();
1606        // d, a, c are always serialized (spec requires all fields)
1607        assert!(json.contains("\"d\":"), "d must always be present");
1608        assert!(json.contains("\"a\":"), "a must always be present");
1609        assert!(json.contains("\"c\":"), "c must always be present");
1610        // x is still conditionally omitted (empty)
1611        assert!(!json.contains("\"x\":"), "empty x must be omitted");
1612        assert!(json.contains("\"s\":\"0\""), "s must serialize as hex");
1613    }
1614
1615    #[test]
1616    fn event_enum_deserializes_by_t_field() {
1617        let json = r#"{"v":"KERI10JSON000000_","t":"icp","d":"ETestSaid","i":"E123","s":"0","kt":"1","k":["DKey"],"nt":"1","n":["ENext"],"bt":"0","b":[]}"#;
1618        let event: Event = serde_json::from_str(json).unwrap();
1619        assert!(event.is_inception());
1620        assert_eq!(event.sequence().value(), 0);
1621    }
1622
1623    #[test]
1624    fn digest_seal_roundtrips() {
1625        let seal = Seal::digest("EDigest123");
1626        let json = serde_json::to_string(&seal).unwrap();
1627        assert_eq!(json, r#"{"d":"EDigest123"}"#);
1628        let parsed: Seal = serde_json::from_str(&json).unwrap();
1629        assert_eq!(seal, parsed);
1630    }
1631
1632    #[test]
1633    fn seal_event_location_roundtrips() {
1634        // A.8 (F-36): the KERI §7 event-location seal {i,s,p,t,d} round-trips.
1635        let seal = Seal::EventLocation {
1636            i: Prefix::new_unchecked("EPrefix".to_string()),
1637            s: KeriSequence::new(3),
1638            p: Said::new_unchecked("EPrior".to_string()),
1639            t: "ixn".to_string(),
1640            d: Said::new_unchecked("EDigest".to_string()),
1641        };
1642        let json = serde_json::to_string(&seal).unwrap();
1643        let parsed: Seal = serde_json::from_str(&json).unwrap();
1644        assert_eq!(seal, parsed);
1645        assert_eq!(parsed.digest_value().map(|d| d.as_str()), Some("EDigest"));
1646    }
1647
1648    #[test]
1649    fn seal_rejects_malformed_i_s() {
1650        // A.8 (F-37): a malformed {i,s} seal (no d) must error, not silently
1651        // collapse to a LatestEstablishment {i} that drops `s`.
1652        let r: Result<Seal, _> = serde_json::from_str(r#"{"i":"EPrefix","s":"3"}"#);
1653        assert!(r.is_err(), "malformed {{i,s}} seal must be rejected");
1654        // and an entirely unknown field set errors too.
1655        let r2: Result<Seal, _> = serde_json::from_str(r#"{"zz":"x"}"#);
1656        assert!(r2.is_err());
1657    }
1658
1659    #[test]
1660    fn key_event_seal_roundtrips() {
1661        let seal = Seal::key_event(
1662            Prefix::new_unchecked("EPrefix".to_string()),
1663            KeriSequence::new(1),
1664            Said::new_unchecked("ESaid".to_string()),
1665        );
1666        let json = serde_json::to_string(&seal).unwrap();
1667        let parsed: Seal = serde_json::from_str(&json).unwrap();
1668        assert_eq!(seal, parsed);
1669    }
1670
1671    #[test]
1672    fn indexed_signature_serde_roundtrip() {
1673        let sig = IndexedSignature {
1674            index: 2,
1675            prior_index: None,
1676            sig: vec![0xab; 64],
1677        };
1678        let json = serde_json::to_string(&sig).unwrap();
1679        assert!(json.contains("\"index\":2"));
1680        assert!(
1681            !json.contains("prior_index"),
1682            "a None prior_index must be omitted from the wire: {json}"
1683        );
1684        let parsed: IndexedSignature = serde_json::from_str(&json).unwrap();
1685        assert_eq!(parsed, sig);
1686    }
1687
1688    #[test]
1689    fn signed_event_accessors() {
1690        use crate::types::{CesrKey, Threshold, VersionString};
1691        let icp = IcpEvent {
1692            v: VersionString::placeholder(),
1693            d: Said::new_unchecked("ESAID123".to_string()),
1694            i: Prefix::new_unchecked("EPrefix".to_string()),
1695            s: KeriSequence::new(0),
1696            kt: Threshold::Simple(1),
1697            k: vec![CesrKey::new_unchecked("DKey".to_string())],
1698            nt: Threshold::Simple(1),
1699            n: vec![Said::new_unchecked("ENext".to_string())],
1700            bt: Threshold::Simple(0),
1701            b: vec![],
1702            c: vec![],
1703            a: vec![],
1704        };
1705        let signed = SignedEvent::new(
1706            Event::Icp(icp),
1707            vec![IndexedSignature {
1708                index: 0,
1709                prior_index: None,
1710                sig: vec![0u8; 64],
1711            }],
1712        );
1713        assert_eq!(signed.said().as_str(), "ESAID123");
1714        assert_eq!(signed.sequence().value(), 0);
1715        assert_eq!(signed.prefix().as_str(), "EPrefix");
1716        assert_eq!(signed.signatures.len(), 1);
1717        assert_eq!(signed.signatures[0].index, 0);
1718    }
1719
1720    #[test]
1721    fn seal_digest_value() {
1722        let seal = Seal::digest("ETest123");
1723        assert_eq!(seal.digest_value().unwrap().as_str(), "ETest123");
1724        let latest = Seal::LatestEstablishment {
1725            i: Prefix::new_unchecked("EPrefix".to_string()),
1726        };
1727        assert!(latest.digest_value().is_none());
1728    }
1729}