Skip to main content

auths_cli/commands/
keri_emit.rs

1//! `auths keri-emit` — emit a raw KERI event (JSON) from deterministic inputs.
2//!
3//! A hidden interop surface, like `did-webs` / `key-state`: it takes fixed,
4//! caller-supplied inputs (keys, delegator, seals) and prints the canonical event
5//! JSON the auths KERI builders produce, so the conformance suite can diff it
6//! byte-for-byte against the keripy reference (`eventing.incept(delpre=...)` /
7//! `eventing.interact(data=[seal])`). It builds nothing new — it calls the same
8//! `auths_keri` finalizers the real delegation path uses.
9//!
10//! It never touches a KEL or the keychain; it is a pure event serializer.
11
12use anyhow::{Result, anyhow};
13use auths_keri::{
14    CesrKey, DipEvent, DipEventInit, Event, IxnEvent, KeriSequence, Prefix, Said, Seal, Threshold,
15    VersionString, finalize_dip_event, finalize_ixn_event,
16};
17use clap::Parser;
18
19use crate::config::CliConfig;
20
21/// Emit a raw KERI event as canonical JSON (interop / conformance surface).
22#[derive(Parser, Debug, Clone)]
23#[command(
24    about = "Emit a raw KERI event (dip/ixn) as JSON from deterministic inputs (interop surface)",
25    after_help = "Examples:
26  auths keri-emit dip --key DA... --delegator EAbc... [--next EN...]
27  auths keri-emit ixn --pre EAbc... --sn 1 --prev EPrev... --seal-digest EDev..."
28)]
29pub struct KeriEmitCommand {
30    #[command(subcommand)]
31    pub kind: KeriEmitKind,
32}
33
34/// Which event to emit.
35#[derive(clap::Subcommand, Debug, Clone)]
36pub enum KeriEmitKind {
37    /// Delegated inception (`dip`): the delegate self-signs; `di` names the delegator.
38    Dip(DipArgs),
39    /// Interaction (`ixn`): anchors one seal in the KEL (e.g. a delegator-side revocation).
40    Ixn(IxnArgs),
41}
42
43/// Inputs for a delegated inception.
44#[derive(Parser, Debug, Clone)]
45pub struct DipArgs {
46    /// The delegate's current signing key, CESR-encoded (qb64) — the same form keripy's `verfer.qb64`.
47    #[clap(long, value_name = "CESR")]
48    pub key: String,
49    /// The delegator's AID prefix (becomes the dip's `di`).
50    #[clap(long, value_name = "PREFIX")]
51    pub delegator: String,
52    /// Optional next-key commitment (pre-rotation digest). Absent → `nt=0`, `n=[]`.
53    #[clap(long, value_name = "SAID")]
54    pub next: Option<String>,
55}
56
57/// Inputs for an interaction event.
58#[derive(Parser, Debug, Clone)]
59pub struct IxnArgs {
60    /// The AID prefix authoring the interaction (the delegator, for a revocation).
61    #[clap(long, value_name = "PREFIX")]
62    pub pre: String,
63    /// Sequence number of this interaction.
64    #[clap(long)]
65    pub sn: u64,
66    /// Prior event SAID (`p`).
67    #[clap(long, value_name = "SAID")]
68    pub prev: String,
69    /// A digest seal `{d}` to anchor (auths's delegator-side revocation marker: the device prefix).
70    #[clap(long, value_name = "SAID")]
71    pub seal_digest: String,
72}
73
74impl KeriEmitCommand {
75    /// Build the requested event, finalize its SAID, and print the canonical JSON.
76    pub fn execute(&self, _ctx: &CliConfig) -> Result<()> {
77        let json = match &self.kind {
78            KeriEmitKind::Dip(args) => emit_dip(args)?,
79            KeriEmitKind::Ixn(args) => emit_ixn(args)?,
80        };
81        println!("{json}");
82        Ok(())
83    }
84}
85
86/// Build + finalize a delegated inception and return its canonical JSON.
87fn emit_dip(args: &DipArgs) -> Result<String> {
88    let (nt, n) = match &args.next {
89        Some(next) => (
90            Threshold::Simple(1),
91            vec![Said::new_unchecked(next.clone())],
92        ),
93        None => (Threshold::Simple(0), vec![]),
94    };
95    let dip = finalize_dip_event(DipEvent::new(DipEventInit {
96        v: VersionString::placeholder(),
97        d: Said::default(),
98        i: Prefix::default(),
99        s: KeriSequence::new(0),
100        kt: Threshold::Simple(1),
101        k: vec![CesrKey::new_unchecked(args.key.clone())],
102        nt,
103        n,
104        bt: Threshold::Simple(0),
105        b: vec![],
106        c: vec![],
107        a: vec![],
108        di: Prefix::new_unchecked(args.delegator.clone()),
109    }))
110    .map_err(|e| anyhow!("finalize dip: {e}"))?;
111    serde_json::to_string(&Event::Dip(dip)).map_err(|e| anyhow!("serialize dip: {e}"))
112}
113
114/// Build + finalize an interaction anchoring a single digest seal; return its canonical JSON.
115fn emit_ixn(args: &IxnArgs) -> Result<String> {
116    let ixn = finalize_ixn_event(IxnEvent {
117        v: VersionString::placeholder(),
118        d: Said::default(),
119        i: Prefix::new_unchecked(args.pre.clone()),
120        s: KeriSequence::new(args.sn as u128),
121        p: Said::new_unchecked(args.prev.clone()),
122        a: vec![Seal::Digest {
123            d: Said::new_unchecked(args.seal_digest.clone()),
124        }],
125    })
126    .map_err(|e| anyhow!("finalize ixn: {e}"))?;
127    serde_json::to_string(&Event::Ixn(ixn)).map_err(|e| anyhow!("serialize ixn: {e}"))
128}