cardano-keri
Self-certifying identities on Cardano, bridged to the Veridian / KERI ecosystem.
New here? Start with the KERI primer — it explains what KERI is, how pre-rotation works, what Veridian is, and what Cardano adds. Then the ACDC primer covers the credential layer — how vLEI credentials chain, how revocation works, and how Cardano verifies a chain on-chain. For the financial and institutional concepts behind the use cases (securities, KYC/AML, custody, escrow, batchers…), see the Finance primer.
Identity on Cardano
Start with the presentation for the user journey, then read Self-Certifying Identities on Cardano for the design decisions, measurements, threat model, and milestone boundary.
Implementation status
- Shipped substrate: MPFS plugin support. This is the concrete extension point for domain-specific value-cage authorization.
- cardano-keri status: research/design and prototypes for an MPFS identity plugin. The identity registry, freeze registry, super watcher, and Veridian/KERI bridge are not shipped runtime infrastructure in this repo.
- Security gates: issue #99 hardened the value-cage validator's token and AID-ownership invariants — a unique thread token confined to its state output, a pinned migration predecessor, and an oracle that is necessary but not sufficient for AID authority. This is one completed security gate among the verification, hardening, bridge, and runtime work still required; it does not make the cage production- or mainnet-ready, and closing it does not lift the prototype label.
- Design target: on-chain-verifiable key-state operations using
blake2b_256, Ed25519, MPF proofs, and MPFS cage/plugin composition. - Delivery plan: see the Roadmap — five milestones building the use-case-invariant core first (identity, verification, signing bridge), each closed by a vertical E2E demo, with the business-case adapters last.
The one idea
In the proposed identity plugin, inception commits to two things: the key you use now, and the hash of the key you will use next. That commitment lives on-chain. When you rotate, you reveal the pre-committed next key. A thief who steals your current key cannot rotate your identity — they do not know the pre-committed next key.
Real-world use case: vLEI
cardano-keri explores an MPFS plugin bridge for GLEIF vLEI —
the cryptographic extension of the Legal Entity Identifier, the entity ID
that MiFID II, Basel III, and eIDAS 2.0 already rely on for identification.
(Identity evidence, not compliance: nothing here satisfies AML, sanctions
screening, or reporting obligations by itself.) In the design, a legal
entity's KERI AID
(the root of its vLEI credential chain) maps to a stable Cardano trie_key,
enabling compliance-gated contracts, non-censorable key history, governance
eligibility, and on-chain ACDC notarization. See vLEI Bridge
for the full use-case analysis.
Node-level attribution: the Amaru question
Where does cardano-keri sit relative to the proposed Veridian × Amaru node-level attribution work, what is still missing for full ACDC support (schema + revocation/TEL anchoring), and does anything actually need to live inside the node? See Amaru Integration Analysis.
That analysis also records the MPFS-side contention pattern: snapshot the cage UTxO datum/value root for a value-write, then rebuild from a newer snapshot if another write advances the cage before submission.
Key derivation: the AID keys the checkpoint
One identifier keys the on-chain identity: the CESR AID. The former separate
trie_key = blake2b_256(cbor({cur_pubkey, next_digest})) derivation is superseded — the
identity leaf is keyed by cesr_aid and holds a KERI-shaped checkpoint, advanced only by
witness-receipted anchoring seals (specs/68-keystate-shape/identity-model.md, PR #87).
Its physical current-authority store is the sovereign per-AID checkpoint UTxO —
each AID's own (checkpoint_policy_id, aid_asset_name) UTxO (inline CheckpointDatum,
delta = 0 rotation), discovered by a generic (policy_id, asset_name) multi-asset
lookup, not a shared identity_root registry with a sliding-root window (the
rejected Candidate B); see specs/92-checkpoint-contention/DECISION.md. The
trie_key → KeyState / identity_root framing in the System components mermaid below
is that superseded shared-registry shape; the mechanical re-cut is downstream #24, and the
freeze registry stays a shared, attacker-contendable UTxO (not sovereign).
Indexer / discovery trust boundary. The generic (policy_id, asset_name) index lookup
supplies only a candidate outref / location for liveness — never identity or
current-authority truth. The consuming transaction validates the returned UTxO against
the ledger: the exact quantity-one policy + asset; an accepted checkpoint script /
version / lineage; a well-formed inline datum with the expected AID / sequence binding
and the current weighted key state; and the applicable active / freeze rules
(validation rules, not datum fields). A stale or false outref fails ledger validation
(it no longer exists, or no longer matches) → refresh / retry; it can never yield forged
authority. An indexer outage only blocks transaction construction (liveness) — it never
grants false authority.
flowchart LR
ICP["cesr_inception_event"] --> H["blake3<br/>(E-native, the production KERI default)"]
H --> AID["cesr_aid<br/>(KERI identifier, 32 bytes)"]
style AID fill:#3a2f1e,stroke:#d9a04a,color:#e0e0e0
AID -->|"asset name"| CK["Checkpoint<br/>raw keys+weights · kt · next_keys/nt (blake3)<br/>witnesses · toad · seq"]
style CK fill:#1e3a5f,stroke:#4a90d9,color:#e0e0e0
SEAL["witnessed anchoring seal<br/>(canonical payload commitments)"] -->|"advance tx:<br/>seal + threshold receipts"| CK
The CESR AID is the KERI-native identifier
used by Veridian and KERI witnesses. Native Blake3 AIDs are served as-is — the checkpoint stores the standard
KEL n digests byte-for-byte, so no digest-agility patch and no
Cardano-specific AID flavor exist. The genesis binding
cesr_aid == blake3(icp_bytes) is verified trustlessly by the hash-proof
minter for inception events up to one blake3 chunk (1024 B — every observed
production shape below GLEIF-Root scale); every later advance is cryptographic
via the dual-threshold reveal. The historical F-prefix option is retired — see
Blake2b-256 AID Requirement for the
archived rationale.
System components
flowchart TD
subgraph Chain
IR["Identity Registry UTxO<br/>thread_token + identity_root<br/>MPF trie: trie_key → KeyState"]
FR["Freeze Registry UTxO<br/>freeze_token + freeze_root<br/>emergency revocation"]
VC0["Value Cage UTxO<br/>tx_in A + value_root A<br/>(pre-state)"]
VC1["Value Cage UTxO<br/>tx_in B + value_root B<br/>(current after contention)"]
end
subgraph "MPFS Plugin / Sidecar Snapshot Cache"
SnapA["Snapshot A<br/>tx_in A + value_root A"]
SnapB["Snapshot B<br/>tx_in B + value_root B"]
Build["Build value-write<br/>proof + unsigned tx"]
Retry["Stale snapshot<br/>discard + rebuild"]
end
VC0 -->|"snapshot live cage pre-state"| SnapA
SnapA --> Build
Owner["AID Owner<br/>(cur_pubkey)"] -->|"authorizes"| ITX["Identity / freeze tx"]
ITX -->|"rotation/inception<br/>(spends)"| IR
ITX -->|"freeze<br/>(next_key authorized)"| FR
Owner -->|"signs built tx"| VTX["Value-write tx"]
Build -->|"built against snapshot"| VTX
VTX -->|"tries to spend tx_in A"| VC0
VC0 -->|"another write wins first<br/>spends A, recreates B"| VC1
VTX -->|"tx_in A already spent"| Retry
Retry -->|"read newer live cage"| SnapB
VC1 -->|"snapshot current pre-state"| SnapB
SnapB -->|"rebuild against B"| Build
IR -->|"CIP-31 reference input"| VTX
FR -->|"CIP-31 reference input"| VTX
style IR fill:#1e3a5f,stroke:#4a90d9,color:#e0e0e0
style FR fill:#3a1e1e,stroke:#d94a4a,color:#e0e0e0
style VC0 fill:#1e3a2f,stroke:#4a9040,color:#e0e0e0
style VC1 fill:#1e3a2f,stroke:#4a9040,color:#e0e0e0
style SnapA fill:#3a2f1e,stroke:#d9a04a,color:#e0e0e0
style SnapB fill:#3a2f1e,stroke:#d9a04a,color:#e0e0e0
style Retry fill:#3a1e1e,stroke:#d94a4a,color:#e0e0e0