Skip to content

Follow one identity

Alice wants a treasury to recognize her identity after she replaces her keys. Her KERI AID (Autonomic Identifier) stays the same; her signed key event log records which keys may act for it. Cardano holds a checkpoint of that key state. The treasury reads the checkpoint when deciding whether to accept Alice's authorization.

This chapter follows that one identity through four runnable examples. Each starts with a guarantee, states its conditions, then shows the successful and refused outcomes. The implementation responsibilities and evidence limits sit beside the claim they qualify.

What you are playing

These are simulations of the accepted M1 design, not commands against the deployed V1 contracts. The checkpoint model represents evidence as facts; it does not check real signatures, CESR encodings or witness receipts. The registry model abstracts requests, folds and uniqueness; it does not execute an MPFS proof or a Cardano transaction. See the architecture overview for the explicit V1/M1 distinction and the deployment guide for deployed behavior.

The parts Alice depends on

---
config:
  htmlLabels: false
---
flowchart TD
    Alice["Alice and her KERI keys"] --> Events["Signed key events<br/>and witness receipts"]
    Events --> Relayer["Relayer<br/>builds the transaction"]
    Relayer --> Checkpoint["Checkpoint<br/>stable identity<br/>changing key state"]
    Registry["Registry<br/>one entry per AID"] --> Checkpoint
    Checkpoint --> Treasury["Treasury validator<br/>checks identity state<br/>and authorization"]

KERI supplies the evidence; a relayer carries it to Cardano. The checkpoint validator and reference observers enforce the transaction rules. An off-chain follower helps locate the current output. The treasury's validator checks that output and its own authorization rule; finding an output through an indexer does not make it authoritative.

The two simulators give separate views of this architecture. The checkpoint simulator focuses on one identity's lifecycle and money. The registry simulator focuses on the shared registration boundary. Loading one does not transfer state to the other.

What registration guarantees

In the registry model, a successful first registration creates one registry entry and its matching checkpoint together. A second registration of the same AID is refused. This guarantee depends on valid inception evidence, an absent registry key and a successful fold. Merely submitting a request establishes none of those outcomes.

Moment What is established What is still absent
Alice fixes her KERI inception The inception determines the AID. Witness receipts concern this event. A registry entry and checkpoint NFT need not exist.
Someone contributes a registration request The inbox holds a pending request and its funding. Submission alone creates no checkpoint NFT or active leaf.
A fold successfully processes that request The model admits the inception, requires no existing leaf, inserts AID → active token, creates the matching checkpoint and locks the bond. There is no separately successful insertion if the coupled registration fails.

The duplicate-registration example exposes both sides: Alice's fold creates the checkpoint; Mallory can submit a request but cannot process a second registration. The checkpoint example below abstracts that entire registration boundary into one register step.

What must enforce the guarantee on Cardano?

The intended implementation must couple the MPFS Add/Insert and checkpoint NFT mint in the same fold transaction. Validators check that transaction; the plugin does not mint an NFT through a later side effect.

Responsibility Required check
MPFS insertion The AID key is absent and the insertion produces the correct registry root. Generic MPFS does not interpret KERI inception evidence.
KERI admission and checkpoint validation The inception evidence authorizes the projected key state, and the inserted leaf identifies the matching checkpoint output and its required value.
Mint authorization The quantity-one checkpoint mint is bound to that same admitted registration and inserted leaf; minting cannot bypass that registration boundary.

These are required implementation obligations. The registry's processBody models their combined result; its Env.inception predicate assumes the cryptographic answer. It does not specify the exact division of checks between the KERI plugin, checkpoint minting policy and registration observers. The concrete script interfaces and asset-name encoding must be verified separately before this becomes an implementation guarantee.

The existing V1 registration path has an earlier BLAKE3 proof token that certifies an inception-to-AID hash relationship. That token is distinct from the checkpoint NFT and grants no identity authority. KERI witnesses receipt the inception event, not the Cardano NFT. See Identity Operations for the V1 transaction path.

Proposed: attest inception before requesting registration

The attestation says: “This hash identifies a valid KERI inception.” It does not say: “This AID has never been registered.” Multiple attestations of the same inception are allowed. The registry supplies uniqueness when it admits registration and creates the checkpoint.

This is the proposed evidence boundary for M1; it is not yet implemented or represented as a separate action in the simulators. The architecture diagram shows the attestation, request, atomic fold and consumer boundaries together.

Stage Guarantee What it does not establish
Earlier attestation transaction Under the designated attestation policy, this AID hashes a valid inception with authenticated initial key state. Issuing another attestation for the same inception is allowed. Registry absence, a checkpoint, or current authority after subsequent rotations.
Registration request Carries the AID, compact authenticated evidence and funding needed for later processing. Successful admission or a reservation of the AID.
Successful registration fold Verifies the attestation and its binding to the initial checkpoint state, proves registry absence, and atomically inserts the leaf and mints the checkpoint. Permission to register again using another copy of the attestation.

To remove the full inception from the request and fold, the earlier validation must establish the required inception admission checks, including signatures and witness receipts. The attestation must authenticate both the AID and the initial key-state projection, under a pinned validation policy. Otherwise a request could pair a valid inception hash with unrelated checkpoint keys. An unauthenticated datum placed beside an attestation token is not enough. Registration-time fields such as the refund address, birth slot and bonds are still checked at the fold.

The existing V1 BLAKE3 proof token supplies only the hash relationship; it does not by itself certify inception admission. Extending that boundary requires a concrete attestation format and validation rules. Whether a fold references or consumes the attestation is still to be specified; single-use attestation is not the source of checkpoint uniqueness. Even with two valid attestations and two pending requests, only the first successful registration can satisfy the registry absence check.

The duplicate scenario already demonstrates that registration boundary using an assumed inception fact. It does not execute the proposed attestation validation. Moving this work earlier can keep the raw inception out of the shared fold, but transaction size and execution-budget feasibility still need measurement.

What does active token promise?

The registry stores an AID's lifecycle and indirection:

Registry-model leaf Meaning
active token Identifies the checkpoint incarnation. Consumers resolve and validate the checkpoint output; the leaf does not certify consumability.
dormant k Preserves the key state from which a witnessed revival must rotate. The checkpoint model represents its parked state with a hash.
convicted Permanent terminal marker. The identity cannot register or revive.

A never-registered AID has no leaf. Live, poisoned and frozen are checkpoint conditions, not additional registry leaf variants. In the registry model's reap/fold handoff, an active leaf can temporarily have a pending go-request instead of a checkpoint; the leaf alone never authorizes consumer use.

The registry simulator's token and k are abstract integers. It allocates nextToken during registration, and a fresh one during permitted revival; those integers do not define serialized Cardano asset names. A concrete asset is identified by policy ID plus asset name. Knowing an AID-derived name before Cardano registration does not establish that its mint is unique.

The bounded guarantee is no second first-registration and at most one checkpoint per AID in the registry model, with revival as a separate guarded operation. It is not a proof that concrete token bytes are minted only once across every future revival. See the registry model reference.

How to play an example

  1. Expand Show … .dsl — copy and play, then copy the whole code block using its copy button. You can also open its .dsl link.
  2. Open the simulator linked immediately above that block.
  3. Paste into the text area next to Load DSL, then select Load DSL.
  4. Use to step forward, or to play. Playback pauses at the main result; press it again for the remaining steps. Select a branch chip to try an alternative; compare the results with What to observe below the block.

Every block includes its own setup. Loading it starts a fresh story: no previous example, manual evidence or edited parameter is needed. The download and displayed block come from the same file. expect fields describe results checked by the documentation build; they do not grant permission to an action.

The checkpoint examples use model units: D = 1000 is the conviction bond (stake against duplicity), B = 5 the freeze bond, P = 2 the relayer premium and W = 10 the juvenility window in slots. Addresses 1 and 2 stand for Alice and Hal. These small integers are simulator values, not real addresses or deployment settings.

1. Alice registers: existence comes before trust

The treasury cannot use an absent identity. Registration creates its checkpoint with both bonds and a pool for relayer payments. A new checkpoint then waits out the juvenility window before a consumer can accept it.

Guarantee and conditions: in this model, the consumer refuses a fresh registration until its age reaches W, even when both bonds are full and the checkpoint is neither frozen nor poisoned. Registration itself remains allowed.

Open the checkpoint simulator · Open register.dsl

Show register.dsl — copy and play
grammar: 1
family: checkpoint

story: 3881
title: "Alice registers and waits"
goal: "Make one identity readable after the registration window."
params:
  D: 1000
  B: 5
  P: 2
  W: 10
atoms: []
step:
  slot: 0
  who: treasury
  say: "Nothing on chain for this AID: the treasury fails closed, and only a registration can happen first."
  expect:
    verdict: not-present
step:
  slot: 0
  who: alice
  say: "Alice registers her public inception with refund address 1 and pool 10."
  action:
    register:
      refund: 1
      pool0: 10
  expect:
    ok: true
    live:
      sn: 0
      epoch: 0
      poisoned: false
      bornAt: 0
      refundTo: 1
      pool: 10
      frozen: false
    flow:
      dregIn: 1000
      bIn: 5
      poolIn: 10
    verdict: juvenile
step:
  slot: 9
  who: treasury
  say: "Nine slots later the checkpoint is still juvenile: born at slot 0, the window W is 10."
  expect:
    verdict: juvenile
step:
  slot: 10
  who: treasury
  say: "At slot 10 the treasury accepts it: both bonds full, not poisoned, past juvenility."
  expect:
    verdict: consumable
fork:
  id: twice
  at: 3
  title: "Registering the same AID again"
  step:
    slot: 10
    who: alice
    say: "Registering the same AID again is refused: the token is minted once, ever."
    action:
      register:
        refund: 1
        pool0: 10
    expect:
      ok: false
      reason: already-present
      verdict: consumable
      live:
        sn: 0
        epoch: 0
        refundTo: 1
        pool: 10

What to observe: before registration the verdict is not-present. Registration at slot 0 locks the two bonds and pool 10, with Alice at refund address 1. The verdict remains juvenile at slot 9 and becomes consumable at slot 10. The alternative Registering the same AID again refuses the action with already-present and preserves the existing checkpoint.

Existence and consumability answer different questions. A juvenile checkpoint exists and can participate in lifecycle operations; the waiting window gates consumer use. For the transaction structure behind registration, continue to Identity Operations.

2. Alice rotates: evidence must reach Cardano

Alice replaces her keys without changing her AID. Witnessed rotation evidence alone does not advance the Cardano checkpoint: Hal must submit the corresponding transaction. His payment comes from the separate pool.

Guarantee and conditions: with matching witnessed rotation evidence and enough pool to pay P, an accepted keep rotation advances the checkpoint and pays the named relayer. Replaying the old evidence against the new epoch is refused. Real witness and signature verification is assumed by this simulation.

Open the checkpoint simulator · Open rotate.dsl

Show rotate.dsl — copy and play
grammar: 1
family: checkpoint

story: 3882
title: "Alice rotates; Hal is paid"
goal: "Separate witnessed evidence from the transaction that advances the checkpoint."
params:
  D: 1000
  B: 5
  P: 2
  W: 10
atoms: []
step:
  slot: 0
  who: alice
  say: "Alice's checkpoint is registered with her own refund address and a pool of 10."
  action:
    register:
      refund: 1
      pool0: 10
  expect:
    ok: true
    verdict: juvenile
step:
  slot: 12
  who: alice
  say: "The model is given witnessed rotation evidence from epoch 0, sequence 0 to sequence 1. No transaction has landed yet."
  evidence:
    add: [{"rotationTo":[0,0,1]}]
  expect:
    verdict: consumable
    live:
      sn: 0
      epoch: 0
      pool: 10
step:
  slot: 12
  who: hal
  say: "Hal submits the rotation as an advance with keep, naming himself payee. The chain pays P from the pool to Hal."
  action:
    rotate:
      sn': 1
      op: keep
      payee: 2
      refund': null
  expect:
    ok: true
    live:
      sn: 1
      epoch: 1
      poisoned: false
      bornAt: 0
      refundTo: 1
      pool: 8
      frozen: false
    flow:
      hunter:
        addr: 2
        dreg: 0
        b: 0
        pool: 2
    verdict: consumable
fork:
  id: twice
  at: 2
  title: "Hal tries to land the same rotation again"
  step:
    slot: 12
    who: hal
    say: "Landing it twice does nothing: the evidence names epoch 0 at sequence 0; the checkpoint is at epoch 1."
    action:
      rotate:
        sn': 1
        op: keep
        payee: 2
        refund': null
    expect:
      ok: false
      reason: no-witnessed-rotation
      verdict: consumable
      live:
        sn: 1
        epoch: 1
        pool: 8

What to observe: the evidence step at slot 12 leaves the checkpoint at sequence 0. Hal's rotate advances sequence and epoch to 1, keeps Alice's refund address, reduces the pool from 10 to 8 and pays 2 to Hal at address 2. The checkpoint remains consumable: this rotation does not restart juvenility. The alternative Hal tries to land the same rotation again refuses with no-witnessed-rotation. The old evidence names epoch 0 and sequence 0; it cannot be reused against epoch 1.

rotationTo: [0, 0, 1] is an assumed verified fact in this model. In the implementation, parsing, signatures, next-key commitments and witness thresholds need their real checks. This is the boundary explained by reference observers. The simulation demonstrates the state transition and payment, not cryptographic verification.

3. The treasury reads: current keys can become unusable

The treasury must check the checkpoint at each use. A key that was acceptable yesterday may belong to a poisoned epoch today. Alice can use the current key quorum to declare that epoch untrustworthy; once that declaration lands, waiting does not repair it.

Guarantee and conditions: once a current-quorum poison declaration lands, the model's consumer predicate refuses that epoch. Advancing time does not clear poison. This guarantee covers checkpoint eligibility, not the treasury's own signature verification or payment authorization.

Open the checkpoint simulator · Open consume.dsl

Show consume.dsl — copy and play
grammar: 1
family: checkpoint

story: 3883
title: "A treasury reads, then refuses poisoned keys"
goal: "Observe why a consumer must check current checkpoint status for every use."
params:
  D: 1000
  B: 5
  P: 2
  W: 10
atoms: []
step:
  slot: 0
  who: alice
  say: "Alice's checkpoint is registered with her own refund address and a pool of 10."
  action:
    register:
      refund: 1
      pool0: 10
  expect:
    ok: true
    verdict: juvenile
step:
  slot: 12
  who: alice
  say: "The model is given witnessed rotation evidence from epoch 0, sequence 0 to sequence 1. No transaction has landed yet."
  evidence:
    add: [{"rotationTo":[0,0,1]}]
  expect:
    verdict: consumable
step:
  slot: 12
  who: hal
  say: "Hal submits the rotation as an advance with keep, naming himself payee. The chain pays P from the pool to Hal."
  action:
    rotate:
      sn': 1
      op: keep
      payee: 2
      refund': null
  expect:
    ok: true
    live:
      sn: 1
      epoch: 1
      poisoned: false
      bornAt: 0
      refundTo: 1
      pool: 8
      frozen: false
    flow:
      hunter:
        addr: 2
        dreg: 0
        b: 0
        pool: 2
    verdict: consumable
step:
  slot: 13
  who: treasury
  say: "The checkpoint is eligible. The application must separately check its own authorization and signature."
  expect:
    verdict: consumable
    live:
      sn: 1
      pool: 8
step:
  slot: 14
  who: alice
  say: "A current epoch quorum authorizes poison. Once landed, consumers stop accepting this epoch."
  evidence:
    add: [{"quorum":[1]}]
  action: poison
  expect:
    ok: true
    live:
      sn: 1
      epoch: 1
      poisoned: true
      pool: 8
    flow: {}
    verdict: poisoned
step:
  slot: 24
  who: treasury
  say: "Waiting does not clear poison. The treasury still refuses the checkpoint."
  expect:
    verdict: poisoned
    live:
      poisoned: true
      pool: 8

What to observe: this example registers and rotates Alice from scratch. At slot 13 the treasury sees consumable. At slot 14 a current-epoch quorum fact allows poison to land; the verdict becomes poisoned. At slot 24 it is still poisoned, even though the original waiting window has long elapsed. The pool stays at 8. Neither time nor the consumer's previous decision clears poison.

consumable means eligible under the model's checkpoint rules. It does not mean the treasury has verified a spending signature or authorized a payment. The application must also validate the checkpoint's identity, token, script and datum, then apply its own authorization rule. The consumer checklist explains those responsibilities; Value Authorization expands the model's refusal conditions.

4. The registry refuses a duplicate: follow the money

Now look at Alice through the registry simulator. Here AID 11 is Alice's abstract identifier. A contributed request is pending work; only a successful fold updates the registry. Mallory can pay to submit a duplicate request, but cannot turn it into a second registered identity.

Guarantee and conditions: processing a registration requires an absent AID leaf. With Alice already registered, a duplicate request cannot create another checkpoint. Refunding that pending request is a separate operation with its own rejection-window condition.

Open the registry simulator · Open duplicate.dsl

Show duplicate.dsl — copy and play
grammar: 1
family: registry

id: 3884
slug: architecture-duplicate
story: "Alice keeps one registry entry"
narrative: "Register Alice, attempt a duplicate, then return its bond after the rejection window."
params:
  D: 1000
  tip: 2
  Mc: 4
  Mr: 1
  process: 10
  retract: 10
  W: 5
  far: 1000000000
plugin: 7
actors:
  1: Alice
  2: Bob
  3: "Hal (folder)"
  4: Mallory
  5: "Cora (convictor)"
  6: "Sam (reaper)"
env:
  inception: [11]
step:
  now: 0
  actor: anyone
  as: Alice
  action:
    contribute:
      aid: 11
      owner: 1
      submittedAt: 0
      op: register
  expect:
    ok: true
    flow:
      deposited: 1002
      locked: []
      refunds: []
      tips: null
      premium: null
      intoRequest: 0
step:
  now: 1
  actor: anyone
  as: Hal
  action:
    fold:
      folder: 3
      gen: 0
      plugin: 7
      batch: [{"id":0,"do":"process"}]
  expect:
    ok: true
    flow:
      deposited: 0
      locked: [{"aid":11,"value":1000}]
      refunds: []
      tips:
        addr: 3
        value: 2
      premium: null
      intoRequest: 0
step:
  now: 5
  actor: anyone
  as: Mallory
  action:
    contribute:
      aid: 11
      owner: 4
      submittedAt: 5
      op: register
  expect:
    ok: true
    flow:
      deposited: 1002
      locked: []
      refunds: []
      tips: null
      premium: null
      intoRequest: 0
step:
  now: 6
  actor: anyone
  as: Hal
  action:
    fold:
      folder: 3
      gen: 1
      plugin: 7
      batch: [{"id":1,"do":"process"}]
  expect:
    ok: false
    reason: already-registered
step:
  now: 25
  actor: anyone
  as: Sam
  action:
    fold:
      folder: 6
      gen: 1
      plugin: 7
      batch: [{"id":1,"do":"reject"}]
  expect:
    ok: true
    flow:
      refunds: [{"addr":4,"value":1000}]
      tips:
        addr: 6
        value: 2
fork:
  id: sam-too-early
  at: 3
  title: "Sam rejects Mallory's request in phase 1"
  expectFinal:
    gen: 1
    plugin: 7
    leaves: [{"aid":11,"status":{"active":0}}]
    ckpts: [{"aid":11,"ckpt":{"token":0,"k":0,"st":"live"}}]
    requests: [{"id":1,"aid":11,"owner":4,"submittedAt":5,"op":"register"}]
    nextReq: 2
    nextToken: 1
  step:
    now: 6
    actor: anyone
    as: "Sam — rejecting in phase 1"
    action:
      fold:
        folder: 6
        gen: 1
        plugin: 7
        batch: [{"id":1,"do":"reject"}]
    expect:
      ok: false
      reason: not-rejectable
expectFinal:
  gen: 2
  plugin: 7
  leaves: [{"aid":11,"status":{"active":0}}]
  ckpts: [{"aid":11,"ckpt":{"token":0,"k":0,"st":"live"}}]
  requests: []
  nextReq: 2
  nextToken: 1

What to observe: Alice contributes 1002: bond 1000 plus tip 2. Hal processes her request, locking 1000 and receiving the tip. Mallory then contributes the same amount for AID 11. Processing her request refuses with already-registered. At slot 25 Sam rejects that pending request: 1000 returns to Mallory at address 4, and tip 2 goes to Sam at address 6. The final state has one leaf, one checkpoint and no pending requests.

Try Sam rejects Mallory's request in phase 1. At slot 6 it refuses with not-rejectable; the pending request remains. Refusing a duplicate registration does not make early rejection legal. This example supplies its own registry parameters and actor map: Hal is address 3 here, and the registry's W = 5 serves its own model rather than importing the checkpoint example's settings.

The uniqueness boundary belongs to the registry. The checkpoint can then evolve without storing all identities in its datum. See The Registry as an MPFS Instance for the request/fold architecture and proof boundary.

From the model to a running system

The examples establish modeled lifecycle outcomes. They do not test transaction submission, rollback recovery, witness availability or deployment configuration. Use these references when crossing those boundaries:

Question Next page
Which behavior is deployed V1, and which belongs to M1? Architecture overview
Which validator or observer owns each transaction check? Identity Operations and Observer Architecture
How does a service find and follow checkpoint outputs? The follower
What must the consuming application verify? Consumer checklist
How do requests become unique registry entries? The Registry as an MPFS Instance

To check the examples locally, run node scripts/check-architecture-scenarios.mjs --selftest, build the docs, then run node scripts/check-architecture-scenarios.mjs --site site. CI replays every source and checks the actual rendered code blocks and published downloads against it.