Identity Model¶
KERI-inspired identity management: inception events, the Key Event Log, DID derivation, and key rotation.
Core Concepts¶
Auths uses three KERI (Key Event Receipt Infrastructure) concepts adapted for Git-native storage:
-
Self-certifying identifiers -- the identity DID is derived from the inception event's content hash, making it cryptographically bound to its creation parameters.
-
Pre-rotation -- each event commits to the next rotation key before it is needed. A compromised current key cannot forge a rotation because the next key was committed in a previous event.
-
Key Event Log (KEL) -- an append-only, hash-chained sequence of events that records every key lifecycle operation. Replaying the KEL produces the current key state.
Identity vs. Device¶
The one thing to remember
Your identity is not your key. Your root identity (did:keri:E...) is permanent and survives key changes. Each device is a KERI delegated identifier with its own did:keri: AID that the root anchored — so the device signs day to day while the root key can stay cold.
flowchart TD
A["<strong>ROOT IDENTITY</strong><br/>did:keri:E...<br/><small>Permanent, derived from its icp KEL</small>"]
B["<strong>DEVICE #0</strong><br/>did:keri:E...<br/><small>laptop — delegated (dip), its own KEL</small>"]
C["<strong>DEVICE / AGENT</strong><br/>did:keri:E...<br/><small>phone — delegated (dip), its own KEL</small>"]
A -->|"delegation: dip anchored by an ixn on the root KEL"| B
A -->|"delegation: dip anchored by an ixn on the root KEL"| C
- Root identity (
did:keri:E...): Your stable cryptographic identifier, derived from the inception (icp) event's SAID. It is the trust anchor a verifier pins (.auths/roots) and it anchors delegations; its key is touched rarely — to add or revoke a device — so it can stay cold. - Device (
did:keri:E...): A KERI delegated identifier (dip) with its own key and its own KEL, anchored by the root.auths init(developer profile) delegates a first device — device #0 — automatically, so a fresh identity already hasidentity_did != device_did. Devices rotate independently (drt) and are independently revocable (a single root-authoredixn— no device key required). Commit trailers carry the device'sdid:keri:form (Auths-Device); the authoritative device identity is this delegateddid:keri:AID, not the rawdid:key:(below). Agents are modeled the same way — a role-marked delegated identifier — so devices and agents are onedip/drtconcept. See device-model.md.
DID Derivation¶
did:keri (identity)¶
The did:keri identifier is derived from the inception event:
- Build the inception event JSON with
dandifields set to empty defaults - Compute the Blake3 hash of that JSON
- Encode as Base64url (no padding) with an
Eprefix (KERI derivation code for Blake3-256) - Set both
d(SAID) andi(prefix) to this value -- for inception, they are identical
The SAID is 44 characters: E prefix + 43 characters of Base64url-encoded Blake3 hash.
From source (auths-core/src/crypto/said.rs):
pub fn compute_said(event_json: &[u8]) -> Said {
let hash = blake3::hash(event_json);
let encoded = URL_SAFE_NO_PAD.encode(hash.as_bytes());
Said::new_unchecked(format!("E{}", encoded))
}
did:key (raw key material)¶
A raw signing key has a did:key encoding that packs the public key directly into the string. Under delegation this is the underlying key material — a device's authoritative identity is its delegated did:keri: AID (above), not this did:key. The did:key form is still used for ephemeral / one-time CI signers and wherever a bare public key is referenced.
- Prepend the Ed25519 multicodec prefix
[0xED, 0x01]to the 32-byte public key - Encode as Base58btc
- Prepend
did:key:z
From source (auths-crypto/src/did_key.rs):
pub fn ed25519_pubkey_to_did_key(public_key: &[u8; 32]) -> String {
let mut prefixed = vec![0xED, 0x01];
prefixed.extend_from_slice(public_key);
let encoded = bs58::encode(prefixed).into_string();
format!("did:key:z{encoded}")
}
Decoding reverses this process: strip did:key:z, Base58-decode, validate the [0xED, 0x01] multicodec prefix, extract the 32-byte key.
KERI Event Types¶
The KEL contains three event types, discriminated by a t field:
Inception Event (icp)¶
Creates a new identity. The inception event establishes the identifier prefix and commits to the first rotation key.
| Field | Type | Description |
|---|---|---|
v |
string | Version: "KERI10JSON" |
t |
string | Type: "icp" |
d |
string | SAID (Blake3 hash of the event; d/i placeholder-filled) |
i |
string | Identifier prefix (same as d for inception) |
s |
string | Sequence number: "0" |
kt |
string | Key threshold: "1" for single-sig |
k |
string[] | Current public key(s), KERI CESR encoded (D + Base64url) |
nt |
string | Next key threshold: "1" |
n |
string[] | Next key commitment(s) (Blake3 hash of next public key) |
bt |
string | Witness threshold: "0" when no witnesses |
b |
string[] | Witness/backer list (empty when none) |
c |
string[] | Configuration traits (EO, DND, DID, RB, NRB) |
a |
Seal[] | Anchored seals |
Rotation Event (rot)¶
Rotates to a pre-committed key. The new key must match the previous event's next-key commitment.
| Field | Type | Description |
|---|---|---|
v |
string | Version: "KERI10JSON" |
t |
string | Type: "rot" |
d |
string | SAID of this event |
i |
string | Identifier prefix |
s |
string | Sequence number (increments with each event) |
p |
string | Previous event SAID (creates the hash chain) |
kt |
string | Key threshold |
k |
string[] | New current key(s) |
nt |
string | Next key threshold |
n |
string[] | New next key commitment(s) |
bt |
string | Backer threshold |
br |
string[] | Backer cuts (witnesses/backers to remove) |
ba |
string[] | Backer adds (witnesses/backers to add) |
c |
string[] | Configuration traits (only RB/NRB may change at rotation) |
a |
Seal[] | Anchored seals |
Setting nt to "0" and n to [] abandons the identity -- no further rotations are possible.
Interaction Event (ixn)¶
Anchors data in the KEL without changing keys. Used to link attestations, delegations, or other artifacts to the identity's event history.
| Field | Type | Description |
|---|---|---|
v |
string | Version: "KERI10JSON" |
t |
string | Type: "ixn" |
d |
string | SAID of this event |
i |
string | Identifier prefix |
s |
string | Sequence number |
p |
string | Previous event SAID |
a |
Seal[] | Anchored seals (the primary purpose of IXN events) |
Delegated Inception (dip) / Delegated Rotation (drt)¶
Devices and agents are delegated identifiers: each runs its own KEL that begins with a dip (delegated inception) rather than an icp, and rotates with drt (delegated rotation) rather than rot. A dip mirrors an icp plus a di field naming its delegator (the root's prefix); a drt mirrors a rot with that delegation binding preserved. For a dip, i == d (the delegated AID is self-addressing over its own inception).
A delegated event is only valid once the delegator has anchored it: the root appends an ixn to its KEL carrying a seal that references the delegated event (validate_delegation checks both sides of this binding). So a device's authority is provable from two linked KELs — the device's own dip/drt chain, and the root ixn that anchored the dip. Revoking a delegated identifier is a single root-authored ixn anchoring a Seal::Digest of the delegated prefix — no device key required.
KEL events carry no in-body signature and no in-body timestamp. Signatures attach out-of-band as CESR indexed-signature groups; see [SPEC.md] §1 for the normative per-type field sets.
Key Event Log (KEL)¶
The KEL is an append-only, hash-chained sequence of events stored as Git commits:
icp (s=0) --> rot (s=1) --> ixn (s=2) --> rot (s=3) --> ...
d=E_abc d=E_def d=E_ghi d=E_jkl
p=E_abc p=E_def p=E_ghi
Chain Integrity¶
Each event references the previous event's SAID via the p field, forming a verifiable hash chain. Breaking any link invalidates all subsequent events.
SAID Computation¶
The SAID is computed by:
- Filling
d(and, for inception,i) with a placeholder of equal length - Serializing to canonical JSON
- Computing the Blake3-256 hash
- Encoding as
E+ Base64url (no padding), then writing it back intod(andi)
Signatures are computed over the finalized event bytes — the event after its v (byte count) and d (SAID) have been written back — not over a cleared form. This closes the forge path where a signature over a d: "" skeleton could be replayed against a finalized event.
Pre-Rotation¶
The n field in each event contains a commitment to the next rotation key:
When a rotation event appears, the verifier:
- Extracts the new key from
k[0] - Computes
Blake3(new_key_bytes) - Compares with the previous event's
n[0] - Only accepts the rotation if they match
This prevents a compromised current key from forging a rotation -- the attacker would need to know the pre-committed next key.
Key State¶
Replaying the KEL produces a KeyState struct:
pub struct KeyState {
pub prefix: Prefix, // KERI identifier (used in did:keri:<prefix>)
pub current_keys: Vec<String>, // Current signing key(s), CESR encoded
pub next_commitment: Vec<String>, // Next key commitment(s) for pre-rotation
pub sequence: u64, // Current sequence number
pub last_event_said: Said, // SAID of the last processed event
pub is_abandoned: bool, // Empty next commitment = abandoned
}
Key state is derived, not stored. It is computed by replaying events from inception. The validate_kel function is a pure function with no I/O:
For performance, a three-tier caching strategy avoids full replay on every access:
- Cache hit: Cached state matches current tip -- return immediately (O(1))
- Incremental: Cache is behind by k events -- validate only new events (O(k))
- Full replay: Cache missing or invalid -- replay entire KEL (O(n))
Seals¶
A seal anchors external data or another event in the a[] field. Seals are structural KERI seals, discriminated by their field shape (not by a type string):
| Variant | Shape | Purpose |
|---|---|---|
| Digest | {"d"} |
Anchor an artifact by its SAID (e.g. an attestation blob). |
| Source event | {"s","d"} |
Reference an event by sequence + SAID. |
| Key event | {"i","s","d"} |
Reference another identifier's key event. |
| Event location | {"i","s","p","t","d"} |
KERI v1.1 §7 location seal; anchors a delegated event. |
| Latest establishment | {"i"} |
Reference an identifier's latest establishment event. |
Extended shapes (MerkleRoot, RegistrarBacker) are gated behind the seal-extensions feature and are not part of the default wire surface. The canonical field sets are normative in [SPEC.md] §6.
KERI Key Encoding¶
Public keys in KERI events use CESR (Composable Event Streaming Representation) encoding:
- Ed25519:
D(transferable) orB(non-transferable) + Base64url(32-byte key) - P-256 (secp256r1):
1AAJ(transferable) or1AAI(non-transferable) + Base64url(33-byte compressed SEC1 key)
"D" + Base64url(ed25519_public_key_bytes) // Ed25519, transferable
"1AAJ" + Base64url(p256_compressed_bytes) // P-256, transferable
The derivation code carries both the curve and the transferability in-band; verifiers never dispatch on key length. Parsing is KeriPublicKey::parse (auths-keri/src/keys.rs):
- Match the derivation code (
D/B/1AAJ/1AAI) - Base64url-decode the remaining characters
- Validate the decoded length for the curve (32 bytes Ed25519, 33 bytes P-256)