/ docs / architecture / identity-model.md
identity-model.md
  1  # Identity Model
  2  
  3  KERI-inspired identity management: inception events, the Key Event Log, DID derivation, and key rotation.
  4  
  5  ## Core Concepts
  6  
  7  Auths uses three KERI (Key Event Receipt Infrastructure) concepts adapted for Git-native storage:
  8  
  9  1. **Self-certifying identifiers** -- the identity DID is derived from the inception event's content hash, making it cryptographically bound to its creation parameters.
 10  
 11  2. **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.
 12  
 13  3. **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.
 14  
 15  ## Identity vs. Device
 16  
 17  !!! info "The one thing to remember"
 18      **Your identity is not your key.** Keys live on devices and can be rotated. Your identity (`did:keri:E...`) is permanent and survives key changes.
 19  
 20  ```mermaid
 21  flowchart TD
 22      A["<strong>IDENTITY</strong><br/>did:keri:E...<br/><small>Permanent, derived from KEL</small>"]
 23      B["<strong>DEVICE A</strong><br/>did:key:z6Mk...<br/><small>laptop</small>"]
 24      C["<strong>DEVICE B</strong><br/>did:key:z6Mk...<br/><small>phone</small>"]
 25  
 26      A -->|"signed attestation"| B
 27      A -->|"signed attestation"| C
 28  ```
 29  
 30  - **Identity** (`did:keri:E...`): Your stable cryptographic identifier. Derived from the inception event's SAID. Survives key rotation.
 31  - **Device** (`did:key:z6Mk...`): A per-machine Ed25519 keypair. Devices are instruments that act on behalf of an identity, authorized via signed attestations.
 32  
 33  ## DID Derivation
 34  
 35  ### did:keri (identity)
 36  
 37  The `did:keri` identifier is derived from the inception event:
 38  
 39  1. Build the inception event JSON with `d` and `i` fields set to empty defaults
 40  2. Compute the Blake3 hash of that JSON
 41  3. Encode as Base64url (no padding) with an `E` prefix (KERI derivation code for Blake3-256)
 42  4. Set both `d` (SAID) and `i` (prefix) to this value -- for inception, they are identical
 43  
 44  ```
 45  Inception JSON (d="", i="") --> Blake3 --> Base64url --> "E" + encoded
 46                                                           |
 47                                                           v
 48                                                 did:keri:EXq5Yqa...
 49  ```
 50  
 51  The SAID is 44 characters: `E` prefix + 43 characters of Base64url-encoded Blake3 hash.
 52  
 53  From source (`auths-core/src/crypto/said.rs`):
 54  
 55  ```rust
 56  pub fn compute_said(event_json: &[u8]) -> Said {
 57      let hash = blake3::hash(event_json);
 58      let encoded = URL_SAFE_NO_PAD.encode(hash.as_bytes());
 59      Said::new_unchecked(format!("E{}", encoded))
 60  }
 61  ```
 62  
 63  ### did:key (device)
 64  
 65  Device identifiers use the `did:key` method, which encodes the public key directly in the DID string:
 66  
 67  1. Prepend the Ed25519 multicodec prefix `[0xED, 0x01]` to the 32-byte public key
 68  2. Encode as Base58btc
 69  3. Prepend `did:key:z`
 70  
 71  ```
 72  32-byte Ed25519 pubkey --> [0xED, 0x01] ++ pubkey --> Base58btc --> "did:key:z" + encoded
 73  ```
 74  
 75  From source (`auths-crypto/src/did_key.rs`):
 76  
 77  ```rust
 78  pub fn ed25519_pubkey_to_did_key(public_key: &[u8; 32]) -> String {
 79      let mut prefixed = vec![0xED, 0x01];
 80      prefixed.extend_from_slice(public_key);
 81      let encoded = bs58::encode(prefixed).into_string();
 82      format!("did:key:z{encoded}")
 83  }
 84  ```
 85  
 86  Decoding reverses this process: strip `did:key:z`, Base58-decode, validate the `[0xED, 0x01]` multicodec prefix, extract the 32-byte key.
 87  
 88  ## KERI Event Types
 89  
 90  The KEL contains three event types, discriminated by a `t` field:
 91  
 92  ### Inception Event (`icp`)
 93  
 94  Creates a new identity. The inception event establishes the identifier prefix and commits to the first rotation key.
 95  
 96  | Field | Type | Description |
 97  |-------|------|-------------|
 98  | `v` | string | Version: `"KERI10JSON"` |
 99  | `t` | string | Type: `"icp"` |
100  | `d` | string | SAID (Blake3 hash of event with `d`, `i`, `x` cleared) |
101  | `i` | string | Identifier prefix (same as `d` for inception) |
102  | `s` | string | Sequence number: `"0"` |
103  | `kt` | string | Key threshold: `"1"` for single-sig |
104  | `k` | string[] | Current public key(s), KERI CESR encoded (`D` + Base64url) |
105  | `nt` | string | Next key threshold: `"1"` |
106  | `n` | string[] | Next key commitment(s) (Blake3 hash of next public key) |
107  | `bt` | string | Witness threshold: `"0"` when no witnesses |
108  | `b` | string[] | Witness list (empty when no witnesses) |
109  | `a` | Seal[] | Anchored seals (optional) |
110  | `x` | string | Ed25519 signature over canonical event (Base64url) |
111  
112  ### Rotation Event (`rot`)
113  
114  Rotates to a pre-committed key. The new key must match the previous event's next-key commitment.
115  
116  | Field | Type | Description |
117  |-------|------|-------------|
118  | `v` | string | Version: `"KERI10JSON"` |
119  | `t` | string | Type: `"rot"` |
120  | `d` | string | SAID of this event |
121  | `i` | string | Identifier prefix |
122  | `s` | string | Sequence number (increments with each event) |
123  | `p` | string | Previous event SAID (creates the hash chain) |
124  | `kt` | string | Key threshold |
125  | `k` | string[] | New current key(s) |
126  | `nt` | string | Next key threshold |
127  | `n` | string[] | New next key commitment(s) |
128  | `bt` | string | Witness threshold |
129  | `b` | string[] | Witness list |
130  | `a` | Seal[] | Anchored seals (optional) |
131  | `x` | string | Signature by the **new** key (the key that satisfied the commitment) |
132  
133  Setting `nt` to `"0"` and `n` to `[]` abandons the identity -- no further rotations are possible.
134  
135  ### Interaction Event (`ixn`)
136  
137  Anchors data in the KEL without changing keys. Used to link attestations, delegations, or other artifacts to the identity's event history.
138  
139  | Field | Type | Description |
140  |-------|------|-------------|
141  | `v` | string | Version: `"KERI10JSON"` |
142  | `t` | string | Type: `"ixn"` |
143  | `d` | string | SAID of this event |
144  | `i` | string | Identifier prefix |
145  | `s` | string | Sequence number |
146  | `p` | string | Previous event SAID |
147  | `a` | Seal[] | Anchored seals (the primary purpose of IXN events) |
148  | `x` | string | Signature by the current key |
149  
150  ## Key Event Log (KEL)
151  
152  The KEL is an append-only, hash-chained sequence of events stored as Git commits:
153  
154  ```
155  icp (s=0) --> rot (s=1) --> ixn (s=2) --> rot (s=3) --> ...
156    d=E_abc      d=E_def      d=E_ghi      d=E_jkl
157                 p=E_abc      p=E_def      p=E_ghi
158  ```
159  
160  ### Chain Integrity
161  
162  Each event references the previous event's SAID via the `p` field, forming a verifiable hash chain. Breaking any link invalidates all subsequent events.
163  
164  ### SAID Computation
165  
166  The SAID is computed by:
167  
168  1. Clearing the `d`, `x`, and (for inception) `i` fields
169  2. Serializing to JSON
170  3. Computing Blake3-256 hash
171  4. Encoding as `E` + Base64url (no padding)
172  
173  This same canonical form (with `d`, `i`, `x` cleared) is used for signature computation, avoiding circular dependencies between SAID and signature.
174  
175  ### Pre-Rotation
176  
177  The `n` field in each event contains a **commitment** to the next rotation key:
178  
179  ```
180  commitment = "E" + Base64url(Blake3(next_public_key_bytes))
181  ```
182  
183  When a rotation event appears, the verifier:
184  
185  1. Extracts the new key from `k[0]`
186  2. Computes `Blake3(new_key_bytes)`
187  3. Compares with the previous event's `n[0]`
188  4. Only accepts the rotation if they match
189  
190  This prevents a compromised current key from forging a rotation -- the attacker would need to know the pre-committed next key.
191  
192  ## Key State
193  
194  Replaying the KEL produces a `KeyState` struct:
195  
196  ```rust
197  pub struct KeyState {
198      pub prefix: Prefix,          // KERI identifier (used in did:keri:<prefix>)
199      pub current_keys: Vec<String>, // Current signing key(s), CESR encoded
200      pub next_commitment: Vec<String>, // Next key commitment(s) for pre-rotation
201      pub sequence: u64,           // Current sequence number
202      pub last_event_said: Said,   // SAID of the last processed event
203      pub is_abandoned: bool,      // Empty next commitment = abandoned
204  }
205  ```
206  
207  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:
208  
209  ```rust
210  pub fn validate_kel(events: &[Event]) -> Result<KeyState, ValidationError>
211  ```
212  
213  For performance, a three-tier caching strategy avoids full replay on every access:
214  
215  1. **Cache hit**: Cached state matches current tip -- return immediately (O(1))
216  2. **Incremental**: Cache is behind by k events -- validate only new events (O(k))
217  3. **Full replay**: Cache missing or invalid -- replay entire KEL (O(n))
218  
219  ## Seals
220  
221  Seals anchor external data in KERI events. They contain a digest of the anchored artifact and a type indicator:
222  
223  ```json
224  { "d": "EAttestDigest...", "type": "device-attestation" }
225  ```
226  
227  Seal types include:
228  
229  | Type | Purpose |
230  |------|---------|
231  | `device-attestation` | Links a device attestation to the KEL |
232  | `revocation` | Records a revocation in the KEL |
233  | `delegation` | Records a capability delegation |
234  
235  Seals appear in the `a` field of any event type, binding the external artifact's integrity to the identity's event history.
236  
237  ## KERI Key Encoding
238  
239  Public keys in KERI events use CESR (Composable Event Streaming Representation) encoding:
240  
241  - **Prefix**: `D` -- derivation code for Ed25519
242  - **Payload**: Base64url (no padding) encoded 32-byte public key
243  
244  ```
245  "D" + Base64url(ed25519_public_key_bytes)
246  ```
247  
248  Parsing a KERI-encoded key (`auths-crypto/src/keri.rs`):
249  
250  1. Validate the `D` prefix
251  2. Base64url-decode the remaining characters
252  3. Validate the result is exactly 32 bytes