Skip to main content

auths_keri/
types.rs

1use std::borrow::Borrow;
2use std::fmt;
3use std::str::FromStr;
4
5use serde::{Deserialize, Deserializer, Serialize};
6
7use crate::keys::{KeriDecodeError, KeriPublicKey};
8
9// ── KERI Identifier Newtypes ────────────────────────────────────────────────
10
11/// Error when constructing KERI newtypes with invalid values.
12#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
13#[error("Invalid KERI {type_name}: {reason}")]
14pub struct KeriTypeError {
15    /// Which KERI type failed validation.
16    pub type_name: &'static str,
17    /// Why validation failed.
18    pub reason: String,
19}
20
21/// Validate a CESR derivation code for AIDs (Prefix).
22///
23/// Accepts any valid CESR primitive prefix: uppercase letter or digit.
24/// `D` = Ed25519, `E` = Blake3-256, `1` = secp256k1, etc.
25fn validate_prefix_derivation_code(s: &str) -> Result<(), KeriTypeError> {
26    if s.is_empty() {
27        return Err(KeriTypeError {
28            type_name: "Prefix",
29            reason: "must not be empty".into(),
30        });
31    }
32    let first = s.as_bytes()[0];
33    if !first.is_ascii_uppercase() && !first.is_ascii_digit() {
34        return Err(KeriTypeError {
35            type_name: "Prefix",
36            reason: format!(
37                "must start with a CESR derivation code (uppercase letter or digit), got '{}'",
38                &s[..s.len().min(10)]
39            ),
40        });
41    }
42    Ok(())
43}
44
45/// Validate a CESR derivation code for SAIDs (digest only).
46///
47/// SAIDs are always digests — currently only Blake3-256 (`E`).
48fn validate_said_derivation_code(s: &str) -> Result<(), KeriTypeError> {
49    if s.is_empty() {
50        return Err(KeriTypeError {
51            type_name: "Said",
52            reason: "must not be empty".into(),
53        });
54    }
55    if !s.starts_with('E') {
56        return Err(KeriTypeError {
57            type_name: "Said",
58            reason: format!(
59                "must start with 'E' (Blake3 derivation code), got '{}'",
60                &s[..s.len().min(10)]
61            ),
62        });
63    }
64    Ok(())
65}
66
67/// Strongly-typed KERI identifier prefix (e.g., `"ETest123..."`, `"DKey456..."`).
68///
69/// A prefix is the autonomous identifier (AID) for a KERI identity. For
70/// self-addressing AIDs it starts with `E` (Blake3-256 digest of the inception
71/// event); for key-based AIDs it starts with `D` (Ed25519 public key) or
72/// another CESR derivation code.
73///
74/// Args:
75/// * Inner `String` must start with a valid CESR derivation code (uppercase
76///   letter or digit). Enforced by `new()`, not by serde.
77///
78/// Usage:
79/// ```ignore
80/// let prefix = Prefix::new("ETest123abc".to_string())?;
81/// assert_eq!(prefix.as_str(), "ETest123abc");
82/// ```
83#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize)]
84#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
85#[repr(transparent)]
86pub struct Prefix(String);
87
88impl Prefix {
89    /// Validates and wraps a KERI prefix string.
90    ///
91    /// Accepts any valid CESR derivation code (`D` for Ed25519, `E` for Blake3,
92    /// `1` for secp256k1, etc.). See [`validate_prefix_derivation_code`] for details.
93    pub fn new(s: String) -> Result<Self, KeriTypeError> {
94        validate_prefix_derivation_code(&s)?;
95        Ok(Self(s))
96    }
97
98    /// Wraps a prefix string without validation (for trusted internal paths).
99    pub fn new_unchecked(s: String) -> Self {
100        Self(s)
101    }
102
103    /// Returns the inner string slice.
104    pub fn as_str(&self) -> &str {
105        &self.0
106    }
107
108    /// Consumes self and returns the inner String.
109    pub fn into_inner(self) -> String {
110        self.0
111    }
112
113    /// Returns true if the inner string is empty (placeholder during event construction).
114    pub fn is_empty(&self) -> bool {
115        self.0.is_empty()
116    }
117}
118
119// Wire deserialization rejects an empty prefix: a present-but-empty identifier is
120// never valid on the wire, and `#[serde(default)]` removal only catches a *missing*
121// field. Internal placeholders go through `Default`/`new_unchecked`, not this path.
122impl<'de> Deserialize<'de> for Prefix {
123    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
124        let s = String::deserialize(deserializer)?;
125        if s.is_empty() {
126            return Err(serde::de::Error::custom("Prefix must not be empty"));
127        }
128        Ok(Self(s))
129    }
130}
131
132impl fmt::Display for Prefix {
133    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134        f.write_str(&self.0)
135    }
136}
137
138impl AsRef<str> for Prefix {
139    fn as_ref(&self) -> &str {
140        &self.0
141    }
142}
143
144impl Borrow<str> for Prefix {
145    fn borrow(&self) -> &str {
146        &self.0
147    }
148}
149
150impl From<Prefix> for String {
151    fn from(p: Prefix) -> String {
152        p.0
153    }
154}
155
156impl PartialEq<str> for Prefix {
157    fn eq(&self, other: &str) -> bool {
158        self.0 == other
159    }
160}
161
162impl PartialEq<&str> for Prefix {
163    fn eq(&self, other: &&str) -> bool {
164        self.0 == *other
165    }
166}
167
168impl PartialEq<Prefix> for str {
169    fn eq(&self, other: &Prefix) -> bool {
170        self == other.0
171    }
172}
173
174impl PartialEq<Prefix> for &str {
175    fn eq(&self, other: &Prefix) -> bool {
176        *self == other.0
177    }
178}
179
180/// KERI Self-Addressing Identifier (SAID).
181///
182/// A Blake3 hash that uniquely identifies a KERI event. Creates the
183/// hash chain: each event's `p` (previous) field is the prior event's SAID.
184///
185/// Structurally identical to `Prefix` (both start with 'E') but semantically
186/// distinct — a prefix identifies an *identity*, a SAID identifies an *event*.
187///
188/// Args:
189/// * Inner `String` must be non-empty (rejected on deserialize) and should start
190///   with `'E'` (the Blake3 digest derivation code, enforced by `new()`).
191///
192/// Usage:
193/// ```ignore
194/// let said = Said::new("ESAID123".to_string())?;
195/// assert_eq!(said.as_str(), "ESAID123");
196/// ```
197#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize)]
198#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
199#[repr(transparent)]
200pub struct Said(String);
201
202impl Said {
203    /// Validates and wraps a KERI SAID string.
204    ///
205    /// Only accepts `E` prefix (digest derivation codes).
206    pub fn new(s: String) -> Result<Self, KeriTypeError> {
207        validate_said_derivation_code(&s)?;
208        Ok(Self(s))
209    }
210
211    /// Wraps a SAID string without validation (for `compute_said()` output and storage loads).
212    pub fn new_unchecked(s: String) -> Self {
213        Self(s)
214    }
215
216    /// Returns the inner string slice.
217    pub fn as_str(&self) -> &str {
218        &self.0
219    }
220
221    /// Consumes self and returns the inner String.
222    pub fn into_inner(self) -> String {
223        self.0
224    }
225
226    /// Returns true if the inner string is empty (placeholder during event construction).
227    pub fn is_empty(&self) -> bool {
228        self.0.is_empty()
229    }
230}
231
232// Wire deserialization rejects an empty SAID: an event must carry a real `d`/`n`
233// on the wire (removing `#[serde(default)]` on `d` only catches a *missing* field).
234// `compute_said`'s placeholder and other internal uses go through `new_unchecked`.
235impl<'de> Deserialize<'de> for Said {
236    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
237        let s = String::deserialize(deserializer)?;
238        if s.is_empty() {
239            return Err(serde::de::Error::custom("Said must not be empty"));
240        }
241        Ok(Self(s))
242    }
243}
244
245impl fmt::Display for Said {
246    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247        f.write_str(&self.0)
248    }
249}
250
251impl AsRef<str> for Said {
252    fn as_ref(&self) -> &str {
253        &self.0
254    }
255}
256
257impl Borrow<str> for Said {
258    fn borrow(&self) -> &str {
259        &self.0
260    }
261}
262
263impl From<Said> for String {
264    fn from(s: Said) -> String {
265        s.0
266    }
267}
268
269impl PartialEq<str> for Said {
270    fn eq(&self, other: &str) -> bool {
271        self.0 == other
272    }
273}
274
275impl PartialEq<&str> for Said {
276    fn eq(&self, other: &&str) -> bool {
277        self.0 == *other
278    }
279}
280
281impl PartialEq<Said> for str {
282    fn eq(&self, other: &Said) -> bool {
283        self == other.0
284    }
285}
286
287impl PartialEq<Said> for &str {
288    fn eq(&self, other: &Said) -> bool {
289        *self == other.0
290    }
291}
292
293// ── Fraction ────────────────────────────────────────────────────────────────
294
295/// Exact rational number for weighted threshold arithmetic.
296///
297/// Uses integer cross-multiplication for comparison — NOT floating point.
298/// This ensures `1/3 + 1/3 + 1/3` equals exactly `1`.
299///
300/// Usage:
301/// ```
302/// use auths_keri::Fraction;
303/// let f: Fraction = "1/3".parse().unwrap();
304/// assert_eq!(f.numerator, 1);
305/// assert_eq!(f.denominator, 3);
306/// ```
307#[derive(Debug, Clone, PartialEq, Eq, Hash)]
308pub struct Fraction {
309    /// Numerator of the fraction.
310    pub numerator: u64,
311    /// Denominator of the fraction (must be > 0).
312    pub denominator: u64,
313}
314
315/// Error when parsing a `Fraction` from a string.
316#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
317pub enum FractionError {
318    /// Missing the `/` separator.
319    #[error("missing '/' separator in fraction: {0:?}")]
320    MissingSeparator(String),
321    /// Numerator or denominator is not a valid integer.
322    #[error("invalid integer in fraction: {0}")]
323    InvalidInt(String),
324    /// Denominator is zero.
325    #[error("fraction denominator must not be zero")]
326    ZeroDenominator,
327}
328
329impl Fraction {
330    /// Check if the sum of the given fractions is >= 1, using exact integer arithmetic.
331    ///
332    /// Uses cross-multiplication to avoid floating-point imprecision.
333    /// `1/3 + 1/3 + 1/3` returns `true` (exactly 1).
334    ///
335    /// Usage:
336    /// ```
337    /// use auths_keri::Fraction;
338    /// let thirds: Vec<Fraction> = vec!["1/3".parse().unwrap(); 3];
339    /// let refs: Vec<&Fraction> = thirds.iter().collect();
340    /// assert!(Fraction::sum_meets_one(&refs));
341    /// ```
342    pub fn sum_meets_one(fractions: &[&Fraction]) -> bool {
343        if fractions.is_empty() {
344            return false;
345        }
346        // Accumulate as num/den using: a/b + c/d = (a*d + c*b) / (b*d)
347        // Use u128 to avoid overflow with u64 numerators/denominators.
348        let mut num: u128 = 0;
349        let mut den: u128 = 1;
350        for f in fractions {
351            // num/den + f.numerator/f.denominator
352            // = (num * f.denominator + f.numerator * den) / (den * f.denominator)
353            num = num * (f.denominator as u128) + (f.numerator as u128) * den;
354            den *= f.denominator as u128;
355        }
356        // Check num/den >= 1, i.e., num >= den
357        num >= den
358    }
359}
360
361impl FromStr for Fraction {
362    type Err = FractionError;
363
364    fn from_str(s: &str) -> Result<Self, Self::Err> {
365        let (num_str, den_str) = s
366            .split_once('/')
367            .ok_or_else(|| FractionError::MissingSeparator(s.to_string()))?;
368        let numerator: u64 = num_str
369            .parse()
370            .map_err(|_| FractionError::InvalidInt(num_str.to_string()))?;
371        let denominator: u64 = den_str
372            .parse()
373            .map_err(|_| FractionError::InvalidInt(den_str.to_string()))?;
374        if denominator == 0 {
375            return Err(FractionError::ZeroDenominator);
376        }
377        Ok(Self {
378            numerator,
379            denominator,
380        })
381    }
382}
383
384impl fmt::Display for Fraction {
385    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386        write!(f, "{}/{}", self.numerator, self.denominator)
387    }
388}
389
390impl Serialize for Fraction {
391    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
392        serializer.serialize_str(&self.to_string())
393    }
394}
395
396impl<'de> Deserialize<'de> for Fraction {
397    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
398        let s = String::deserialize(deserializer)?;
399        s.parse().map_err(serde::de::Error::custom)
400    }
401}
402
403// ── Threshold ───────────────────────────────────────────────────────────────
404
405/// KERI signing/backer threshold.
406///
407/// Simple thresholds are hex-encoded integers (`"1"`, `"2"`, `"a"`).
408/// Weighted thresholds are clause lists of fractions (`[["1/2","1/2"]]`).
409/// Clauses are ANDed; each is satisfied when the sum of verified weights >= 1.
410///
411/// Usage:
412/// ```
413/// use auths_keri::Threshold;
414/// let t: Threshold = serde_json::from_str("\"2\"").unwrap();
415/// assert_eq!(t.simple_value(), Some(2));
416/// ```
417#[derive(Debug, Clone, PartialEq, Eq)]
418pub enum Threshold {
419    /// M-of-N threshold (hex-encoded integer in JSON).
420    Simple(u64),
421    /// Fractionally weighted threshold (list of clause lists in JSON).
422    /// Clauses are ANDed; each is satisfied when sum of verified weights >= 1.
423    Weighted(Vec<Vec<Fraction>>),
424}
425
426impl Threshold {
427    /// Get the simple threshold value, if this is a simple threshold.
428    pub fn simple_value(&self) -> Option<u64> {
429        match self {
430            Threshold::Simple(v) => Some(*v),
431            Threshold::Weighted(_) => None,
432        }
433    }
434
435    /// Check that this threshold is structurally satisfiable against a key
436    /// (or commitment / backer) list of length `count`.
437    ///
438    /// This is a structural guard run at validation entry — it rejects events
439    /// whose threshold can never be met regardless of which signatures arrive:
440    /// a `Simple(n)` with `n > count`, a non-zero `Simple` over an empty list,
441    /// or a `Weighted` clause whose length doesn't match `count`.
442    ///
443    /// Args:
444    /// * `count` - Length of the list the threshold governs (`k`, `n`, or `b`).
445    ///
446    /// Usage:
447    /// ```
448    /// use auths_keri::Threshold;
449    /// assert!(Threshold::Simple(2).validate_satisfiable(3).is_ok());
450    /// assert!(Threshold::Simple(5).validate_satisfiable(1).is_err());
451    /// ```
452    pub fn validate_satisfiable(&self, count: usize) -> Result<(), KeriTypeError> {
453        match self {
454            Threshold::Simple(0) => Ok(()),
455            Threshold::Simple(n) if *n as usize > count => Err(KeriTypeError {
456                type_name: "Threshold",
457                reason: format!("simple threshold {n} exceeds list length {count}"),
458            }),
459            Threshold::Simple(_) => Ok(()),
460            Threshold::Weighted(clauses) => {
461                for clause in clauses {
462                    if clause.len() != count {
463                        return Err(KeriTypeError {
464                            type_name: "Threshold",
465                            reason: format!(
466                                "weighted clause length {} != list length {count}",
467                                clause.len()
468                            ),
469                        });
470                    }
471                }
472                Ok(())
473            }
474        }
475    }
476
477    /// Check if the threshold is satisfied by the given set of verified key indices.
478    ///
479    /// For `Simple(n)`: at least `n` unique indices must be verified.
480    /// For `Weighted(clauses)`: ALL clauses must be independently satisfied.
481    /// A clause is satisfied when the sum of weights at verified indices >= 1.
482    ///
483    /// Args:
484    /// * `verified_indices` - Indices of keys whose signatures have been verified.
485    /// * `key_count` - Total number of keys in the key list (for bounds checking).
486    ///
487    /// Usage:
488    /// ```
489    /// use auths_keri::Threshold;
490    /// let t = Threshold::Simple(2);
491    /// assert!(t.is_satisfied(&[0, 1], 3));
492    /// assert!(!t.is_satisfied(&[0], 3));
493    /// ```
494    pub fn is_satisfied(&self, verified_indices: &[u32], key_count: usize) -> bool {
495        // Deduplicate indices and filter out-of-range
496        let mut unique: std::collections::HashSet<u32> = std::collections::HashSet::new();
497        for &idx in verified_indices {
498            if (idx as usize) < key_count {
499                unique.insert(idx);
500            }
501        }
502
503        match self {
504            Threshold::Simple(required) => unique.len() as u64 >= *required,
505            Threshold::Weighted(clauses) => {
506                // ALL clauses must be satisfied (ANDed)
507                for clause in clauses {
508                    let verified_fractions: Vec<&Fraction> = clause
509                        .iter()
510                        .enumerate()
511                        .filter(|(i, _)| unique.contains(&(*i as u32)))
512                        .map(|(_, f)| f)
513                        .collect();
514                    if !Fraction::sum_meets_one(&verified_fractions) {
515                        return false;
516                    }
517                }
518                true
519            }
520        }
521    }
522}
523
524impl Serialize for Threshold {
525    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
526        match self {
527            Threshold::Simple(v) => serializer.serialize_str(&format!("{v:x}")),
528            Threshold::Weighted(clauses) => clauses.serialize(serializer),
529        }
530    }
531}
532
533impl<'de> Deserialize<'de> for Threshold {
534    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
535        let value = serde_json::Value::deserialize(deserializer)?;
536        match value {
537            serde_json::Value::String(s) => {
538                let v = u64::from_str_radix(&s, 16).map_err(|_| {
539                    serde::de::Error::custom(format!("invalid hex threshold: {s:?}"))
540                })?;
541                Ok(Threshold::Simple(v))
542            }
543            serde_json::Value::Array(arr) => {
544                let clauses: Vec<Vec<Fraction>> = arr
545                    .into_iter()
546                    .map(|clause| match clause {
547                        serde_json::Value::Array(weights) => weights
548                            .into_iter()
549                            .map(|w| match w {
550                                serde_json::Value::String(s) => {
551                                    s.parse().map_err(serde::de::Error::custom)
552                                }
553                                _ => Err(serde::de::Error::custom("weight must be a string")),
554                            })
555                            .collect::<Result<Vec<_>, _>>(),
556                        _ => Err(serde::de::Error::custom("clause must be an array")),
557                    })
558                    .collect::<Result<Vec<_>, _>>()?;
559                Ok(Threshold::Weighted(clauses))
560            }
561            _ => Err(serde::de::Error::custom(
562                "threshold must be a hex string or array of clause arrays",
563            )),
564        }
565    }
566}
567
568impl Default for Threshold {
569    fn default() -> Self {
570        Threshold::Simple(0)
571    }
572}
573
574// ── CesrKey ─────────────────────────────────────────────────────────────────
575
576/// A CESR-encoded public key (e.g., `D` + base64url Ed25519, `1AAI` + base64url P-256).
577///
578/// Wraps the qualified string form. Use `parse()` to extract
579/// the curve-tagged `KeriPublicKey` for cryptographic operations.
580///
581/// Usage:
582/// ```
583/// use auths_keri::CesrKey;
584/// let key = CesrKey::new_unchecked("DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".into());
585/// assert!(key.parse().is_ok());
586/// ```
587#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
588#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
589#[repr(transparent)]
590pub struct CesrKey(String);
591
592impl CesrKey {
593    /// Wrap a qualified key string without validation.
594    pub fn new_unchecked(s: String) -> Self {
595        Self(s)
596    }
597
598    /// Parse the inner CESR string, dispatching on the derivation code prefix.
599    ///
600    /// Supports Ed25519 (`D` prefix) and P-256 (`1AAJ`/`1AAI` prefix).
601    /// Returns `Err(UnsupportedKeyType)` for unknown prefixes.
602    pub fn parse(&self) -> Result<KeriPublicKey, KeriDecodeError> {
603        KeriPublicKey::parse(&self.0)
604    }
605
606    /// Get the raw CESR-qualified string.
607    pub fn as_str(&self) -> &str {
608        &self.0
609    }
610
611    /// Consumes self and returns the inner String.
612    pub fn into_inner(self) -> String {
613        self.0
614    }
615}
616
617impl fmt::Display for CesrKey {
618    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
619        f.write_str(&self.0)
620    }
621}
622
623impl AsRef<str> for CesrKey {
624    fn as_ref(&self) -> &str {
625        &self.0
626    }
627}
628
629// ── ConfigTrait ─────────────────────────────────────────────────────────────
630
631/// KERI configuration trait codes.
632///
633/// These control identity behavior at inception and may be updated at rotation
634/// (for `RB`/`NRB` only). If two conflicting traits appear, the latter supersedes.
635///
636/// Usage:
637/// ```
638/// use auths_keri::ConfigTrait;
639/// let traits: Vec<ConfigTrait> = serde_json::from_str(r#"["EO","DND"]"#).unwrap();
640/// assert!(traits.contains(&ConfigTrait::EstablishmentOnly));
641/// ```
642#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
643#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
644pub enum ConfigTrait {
645    /// Establishment-Only: only establishment events in KEL.
646    #[serde(rename = "EO")]
647    EstablishmentOnly,
648    /// Do-Not-Delegate: cannot act as delegator.
649    #[serde(rename = "DND")]
650    DoNotDelegate,
651    /// Registrar Backers: backer list provides registrar backer AIDs.
652    #[serde(rename = "RB")]
653    RegistrarBackers,
654    /// No Registrar Backers: switch back to witnesses.
655    #[serde(rename = "NRB")]
656    NoRegistrarBackers,
657}
658
659// ── VersionString ───────────────────────────────────────────────────────────
660
661/// KERI v1.x version string: `KERI10JSON{hhhhhh}_` (17 chars).
662///
663/// The size field is the total serialized byte count of the event.
664///
665/// Usage:
666/// ```
667/// use auths_keri::VersionString;
668/// let vs = VersionString::json(256);
669/// assert_eq!(vs.to_string(), "KERI10JSON000100_");
670/// ```
671#[derive(Debug, Clone, PartialEq, Eq)]
672pub struct VersionString {
673    /// Serialization kind (e.g., "JSON", "CBOR").
674    pub kind: String,
675    /// Serialized byte count.
676    pub size: u32,
677}
678
679impl VersionString {
680    /// Create a version string for JSON serialization with the given byte count.
681    pub fn json(size: u32) -> Self {
682        Self {
683            kind: "JSON".to_string(),
684            size,
685        }
686    }
687
688    /// Create a placeholder version string (size = 0, to be updated after serialization).
689    pub fn placeholder() -> Self {
690        Self {
691            kind: "JSON".to_string(),
692            size: 0,
693        }
694    }
695}
696
697impl Default for VersionString {
698    fn default() -> Self {
699        Self::placeholder()
700    }
701}
702
703impl fmt::Display for VersionString {
704    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
705        write!(f, "KERI10{}{:06x}_", self.kind, self.size)
706    }
707}
708
709impl Serialize for VersionString {
710    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
711        serializer.serialize_str(&self.to_string())
712    }
713}
714
715impl<'de> Deserialize<'de> for VersionString {
716    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
717        let s = String::deserialize(deserializer)?;
718        // Full 17-char format: "KERI10JSON000100_"
719        if s.len() >= 17 && s.ends_with('_') {
720            let size_hex = &s[10..16];
721            let size = u32::from_str_radix(size_hex, 16).map_err(|_| {
722                serde::de::Error::custom(format!("invalid version string size: {size_hex:?}"))
723            })?;
724            let kind = s[6..10].to_string();
725            Ok(Self { kind, size })
726        } else {
727            // KERI v1.1 mandates the full 17-char form (`KERI10JSON{size:06x}_`).
728            // Short/legacy strings are rejected — they are wire-incompatible
729            // with spec verifiers, which parse the declared size from `v`.
730            Err(serde::de::Error::custom(format!(
731                "invalid KERI version string: {s:?}"
732            )))
733        }
734    }
735}
736
737// ── JsonSchema impls for custom serde types ─────────────────────────────────
738
739#[cfg(feature = "schema")]
740mod schema_impls {
741    use super::*;
742
743    impl schemars::JsonSchema for Fraction {
744        fn schema_name() -> String {
745            "Fraction".to_string()
746        }
747        fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
748            schemars::schema::SchemaObject {
749                instance_type: Some(schemars::schema::InstanceType::String.into()),
750                ..Default::default()
751            }
752            .into()
753        }
754    }
755
756    impl schemars::JsonSchema for Threshold {
757        fn schema_name() -> String {
758            "Threshold".to_string()
759        }
760        fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
761            // Union: string (hex integer) or array of arrays of strings (weighted)
762            schemars::schema::Schema::Bool(true)
763        }
764    }
765
766    impl schemars::JsonSchema for crate::events::Seal {
767        fn schema_name() -> String {
768            "Seal".to_string()
769        }
770        fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
771            // Untagged union of seal variants — accept any object
772            schemars::schema::Schema::Bool(true)
773        }
774    }
775
776    impl schemars::JsonSchema for VersionString {
777        fn schema_name() -> String {
778            "VersionString".to_string()
779        }
780        fn json_schema(_gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
781            schemars::schema::SchemaObject {
782                instance_type: Some(schemars::schema::InstanceType::String.into()),
783                ..Default::default()
784            }
785            .into()
786        }
787    }
788}
789
790#[cfg(test)]
791#[allow(clippy::unwrap_used)]
792mod tests {
793    use super::*;
794
795    // ── Fraction ────────────────────────────────────────────────────────
796
797    #[test]
798    fn fraction_parse_valid() {
799        let f: Fraction = "1/3".parse().unwrap();
800        assert_eq!(f.numerator, 1);
801        assert_eq!(f.denominator, 3);
802    }
803
804    #[test]
805    fn fraction_parse_rejects_zero_denominator() {
806        let err = "1/0".parse::<Fraction>().unwrap_err();
807        assert_eq!(err, FractionError::ZeroDenominator);
808    }
809
810    #[test]
811    fn fraction_parse_rejects_missing_separator() {
812        assert!("42".parse::<Fraction>().is_err());
813    }
814
815    #[test]
816    fn fraction_serde_roundtrip() {
817        let f = Fraction {
818            numerator: 1,
819            denominator: 2,
820        };
821        let json = serde_json::to_string(&f).unwrap();
822        assert_eq!(json, "\"1/2\"");
823        let parsed: Fraction = serde_json::from_str(&json).unwrap();
824        assert_eq!(parsed, f);
825    }
826
827    #[test]
828    fn fraction_display() {
829        let f = Fraction {
830            numerator: 3,
831            denominator: 4,
832        };
833        assert_eq!(f.to_string(), "3/4");
834    }
835
836    // ── Threshold ───────────────────────────────────────────────────────
837
838    #[test]
839    fn threshold_simple_from_hex() {
840        let t: Threshold = serde_json::from_str("\"a\"").unwrap();
841        assert_eq!(t, Threshold::Simple(10));
842        assert_eq!(t.simple_value(), Some(10));
843    }
844
845    #[test]
846    fn threshold_simple_serialize_as_hex() {
847        let t = Threshold::Simple(16);
848        let json = serde_json::to_string(&t).unwrap();
849        assert_eq!(json, "\"10\""); // 16 decimal = 10 hex
850    }
851
852    #[test]
853    fn threshold_simple_roundtrip() {
854        let t = Threshold::Simple(2);
855        let json = serde_json::to_string(&t).unwrap();
856        let parsed: Threshold = serde_json::from_str(&json).unwrap();
857        assert_eq!(parsed, t);
858    }
859
860    #[test]
861    fn threshold_weighted_roundtrip() {
862        let json = r#"[["1/2","1/2"],["1/3","1/3","1/3"]]"#;
863        let t: Threshold = serde_json::from_str(json).unwrap();
864        assert!(t.simple_value().is_none());
865        if let Threshold::Weighted(clauses) = &t {
866            assert_eq!(clauses.len(), 2);
867            assert_eq!(clauses[0].len(), 2);
868            assert_eq!(clauses[1].len(), 3);
869            assert_eq!(clauses[0][0].numerator, 1);
870            assert_eq!(clauses[0][0].denominator, 2);
871        } else {
872            panic!("expected Weighted");
873        }
874        let reserialized = serde_json::to_string(&t).unwrap();
875        let reparsed: Threshold = serde_json::from_str(&reserialized).unwrap();
876        assert_eq!(reparsed, t);
877    }
878
879    #[test]
880    fn threshold_rejects_invalid_hex() {
881        let result = serde_json::from_str::<Threshold>("\"xyz\"");
882        assert!(result.is_err());
883    }
884
885    // ── Fraction arithmetic ─────────────────────────────────────────────
886
887    #[test]
888    fn fraction_sum_one_third_times_three() {
889        let f: Fraction = "1/3".parse().unwrap();
890        assert!(Fraction::sum_meets_one(&[&f, &f, &f]));
891    }
892
893    #[test]
894    fn fraction_sum_two_thirds_not_enough() {
895        let f: Fraction = "1/3".parse().unwrap();
896        assert!(!Fraction::sum_meets_one(&[&f, &f]));
897    }
898
899    #[test]
900    fn fraction_sum_halves() {
901        let f: Fraction = "1/2".parse().unwrap();
902        assert!(Fraction::sum_meets_one(&[&f, &f]));
903        assert!(!Fraction::sum_meets_one(&[&f]));
904    }
905
906    #[test]
907    fn fraction_sum_empty_is_false() {
908        assert!(!Fraction::sum_meets_one(&[]));
909    }
910
911    // ── Threshold::is_satisfied ─────────────────────────────────────────
912
913    #[test]
914    fn threshold_simple_satisfied() {
915        let t = Threshold::Simple(2);
916        assert!(t.is_satisfied(&[0, 1], 3));
917        assert!(t.is_satisfied(&[0, 1, 2], 3));
918        assert!(!t.is_satisfied(&[0], 3));
919    }
920
921    #[test]
922    fn threshold_simple_zero_always_satisfied() {
923        let t = Threshold::Simple(0);
924        assert!(t.is_satisfied(&[], 3));
925    }
926
927    #[test]
928    fn threshold_simple_deduplicates_indices() {
929        let t = Threshold::Simple(2);
930        // Same index twice doesn't count as 2
931        assert!(!t.is_satisfied(&[0, 0], 3));
932    }
933
934    #[test]
935    fn threshold_simple_rejects_out_of_range() {
936        let t = Threshold::Simple(1);
937        // Index 5 is out of range for 3 keys
938        assert!(!t.is_satisfied(&[5], 3));
939    }
940
941    #[test]
942    fn threshold_weighted_two_of_three() {
943        // [["1/2","1/2","1/2"]] — any 2 of 3
944        let t: Threshold = serde_json::from_str(r#"[["1/2","1/2","1/2"]]"#).unwrap();
945        assert!(t.is_satisfied(&[0, 1], 3));
946        assert!(t.is_satisfied(&[1, 2], 3));
947        assert!(!t.is_satisfied(&[0], 3));
948    }
949
950    #[test]
951    fn threshold_weighted_with_reserves() {
952        // [["1/2","1/2","1/2","1/4","1/4"]] — 2 main OR 1 main + 2 reserves
953        let t: Threshold = serde_json::from_str(r#"[["1/2","1/2","1/2","1/4","1/4"]]"#).unwrap();
954        assert!(t.is_satisfied(&[0, 1], 5)); // 2 main
955        assert!(t.is_satisfied(&[0, 3, 4], 5)); // 1 main + 2 reserves
956        assert!(!t.is_satisfied(&[3, 4], 5)); // reserves only: 1/4+1/4=1/2 < 1
957    }
958
959    #[test]
960    fn threshold_weighted_multi_clause_and() {
961        // [["1/2","1/2"],["1/3","1/3","1/3"]] — both clauses must be satisfied
962        let t: Threshold = serde_json::from_str(r#"[["1/2","1/2"],["1/3","1/3","1/3"]]"#).unwrap();
963        // Indices 0,1 satisfy clause 1 (1/2+1/2=1), but clause 2 needs 2 of {0,1,2}
964        // Index 0 in clause 2 = 1/3, index 1 in clause 2 = 1/3 → 2/3 < 1
965        assert!(!t.is_satisfied(&[0, 1], 3));
966        // Indices 0,1,2 satisfy both clauses
967        assert!(t.is_satisfied(&[0, 1, 2], 3));
968    }
969
970    // ── CesrKey ─────────────────────────────────────────────────────────
971
972    #[test]
973    fn cesr_key_roundtrip() {
974        let key_str = "DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
975        let key = CesrKey::new_unchecked(key_str.to_string());
976        let json = serde_json::to_string(&key).unwrap();
977        let parsed: CesrKey = serde_json::from_str(&json).unwrap();
978        assert_eq!(parsed.as_str(), key_str);
979    }
980
981    #[test]
982    fn cesr_key_parse_valid() {
983        let key =
984            CesrKey::new_unchecked("DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_string());
985        assert!(key.parse().is_ok());
986    }
987
988    #[test]
989    fn cesr_key_parse_invalid() {
990        let key = CesrKey::new_unchecked("not-a-valid-key".to_string());
991        assert!(key.parse().is_err());
992    }
993
994    // ── ConfigTrait ─────────────────────────────────────────────────────
995
996    #[test]
997    fn config_trait_serde_roundtrip() {
998        let traits = vec![ConfigTrait::EstablishmentOnly, ConfigTrait::DoNotDelegate];
999        let json = serde_json::to_string(&traits).unwrap();
1000        assert_eq!(json, r#"["EO","DND"]"#);
1001        let parsed: Vec<ConfigTrait> = serde_json::from_str(&json).unwrap();
1002        assert_eq!(parsed, traits);
1003    }
1004
1005    #[test]
1006    fn config_trait_all_variants_roundtrip() {
1007        let all = vec![
1008            ConfigTrait::EstablishmentOnly,
1009            ConfigTrait::DoNotDelegate,
1010            ConfigTrait::RegistrarBackers,
1011            ConfigTrait::NoRegistrarBackers,
1012        ];
1013        let json = serde_json::to_string(&all).unwrap();
1014        assert_eq!(json, r#"["EO","DND","RB","NRB"]"#);
1015        let parsed: Vec<ConfigTrait> = serde_json::from_str(&json).unwrap();
1016        assert_eq!(parsed, all);
1017    }
1018
1019    // ── VersionString ───────────────────────────────────────────────────
1020
1021    #[test]
1022    fn version_string_display() {
1023        let vs = VersionString::json(256);
1024        assert_eq!(vs.to_string(), "KERI10JSON000100_");
1025    }
1026
1027    #[test]
1028    fn version_string_placeholder() {
1029        let vs = VersionString::placeholder();
1030        assert_eq!(vs.to_string(), "KERI10JSON000000_");
1031        assert_eq!(vs.size, 0);
1032    }
1033
1034    #[test]
1035    fn version_string_parse_full() {
1036        let vs: VersionString = serde_json::from_str("\"KERI10JSON000100_\"").unwrap();
1037        assert_eq!(vs.kind, "JSON");
1038        assert_eq!(vs.size, 256);
1039    }
1040
1041    #[test]
1042    fn version_string_rejects_legacy_short() {
1043        // KERI v1.1 mandates the 17-char form; the legacy short form
1044        // (`KERI10JSON` with no size/terminator) must be rejected, not
1045        // silently accepted with size 0.
1046        assert!(serde_json::from_str::<VersionString>("\"KERI10JSON\"").is_err());
1047        assert!(serde_json::from_str::<VersionString>("\"KERI10JSON0001\"").is_err());
1048    }
1049
1050    #[test]
1051    fn version_string_roundtrip() {
1052        let vs = VersionString::json(1024);
1053        let json = serde_json::to_string(&vs).unwrap();
1054        let parsed: VersionString = serde_json::from_str(&json).unwrap();
1055        assert_eq!(parsed, vs);
1056    }
1057
1058    #[test]
1059    fn version_string_rejects_invalid() {
1060        assert!(serde_json::from_str::<VersionString>("\"INVALID\"").is_err());
1061    }
1062}