Value Authorization
A value-write operation mutates a leaf in a value cage MPFS trie. Every leaf mutation requires two authorizations: the leaf owner's Ed25519 authorization against the identity registry key-state, and the cage oracle's co-signature (necessary-not-sufficient — the oracle provides liveness and cannot forge; see Architecture Overview).
Current-authorization path reframed to the sovereign per-AID checkpoint (#92)
The trie_key / sliding-root-window resolution described on this page is the
rejected Candidate-B shared/global MPFS registry shape. Per the #92 decision
(specs/92-checkpoint-contention/DECISION.md), each AID's current authority now lives
in its own sovereign, per-AID, quantity-one uniquely-tokenized checkpoint UTxO —
asset id (checkpoint_policy_id, aid_asset_name), current keys in the inline
CheckpointDatum. A cage resolves current authority by reading that AID's checkpoint as
a CIP-31 reference input; a delta = 0 rotation (seq + 1) advances it and makes
pending authorizations stale (universal re-authorization). Discovery is a generic
(policy_id, asset_name) multi-asset index lookup (any indexer / node / sidecar), not
a shared-registry inclusion proof against a windowed root. The mechanical redeemer/proof
re-cut is downstream #24.
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). The returned TxOut is locked
at the designated script-hash address; the datum itself does not own an address. 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.
The freeze registry below is preserved, but note honestly: it is a shared, attacker-contendable UTxO — not sovereign. The sovereign emergency path must not reintroduce a shared attacker-contendable UTxO; re-cutting R-FRZ sovereign is a downstream residual.
Under the superseded shared-registry shape the cage script resolved the authorizing
identity by trie_key; under the sovereign per-AID checkpoint (#92) it instead reads the
AID's own (checkpoint_policy_id, aid_asset_name) checkpoint UTxO (see
AID Model).
Registry reference
The cage takes the identity registry UTxO (and the freeze registry UTxO) as CIP-31 reference inputs (non-spending). The registry datum carries a sliding window of valid roots. The cage accepts an inclusion proof valid against any root in the window, tolerating registry write latency.
Signer resolution
The cage resolves cur_pubkey by:
- Verifying an MPFS inclusion proof
trie_key → leafagainst a root in the registry datum window - Checking
leaf.status == Active— closed and frozen identities are rejected - Checking no active
FreezeMarkerfortrie_key(absence proof against the freeze root; a marker is active whilemarker.seq == key_state.seq— see Identity Operations — Emergency freeze) - Reading
leaf.key_state.cur_pubkey
The CESR AID plays no role in on-chain authorization.
Option A — Detached signature
The transaction redeemer carries the raw public key and a detached Ed25519 signature over a fully-bound authorization message. The AID owner need not sign the Cardano transaction itself — the authorization travels as data.
On-chain checks:
- Signer resolution above (Active + no freeze marker)
vk == leaf.key_state.cur_pubkeyEd25519.verify(vk, auth_msg, sig)
Authorization message:
auth_msg = cbor({
domain : "cardano-keri/value-write/v1",
network_id : NetworkId,
value_cage_policy_id : PolicyId,
value_cage_thread_token : AssetName,
trie_key : ByteArray[32],
key_seq : Int,
identity_root : ByteArray,
value_input_root : ByteArray,
value_output_root : ByteArray,
op_hash : ByteArray,
counter : Int,
valid_from : POSIXTime,
valid_until : POSIXTime
})
The message binds to the specific cage, the trie_key, the key sequence,
both MPFS roots, the operation, and a counter with validity window for replay
protection.
flowchart TD
A["Redeemer: {trie_key, vk, sig, inclusion_proof, freeze_proof, auth_msg}"] --> B
B["inclusion_proof valid for trie_key at some root in window?"]
B -->|yes| C["leaf.status == Active and no active FreezeMarker?"]
B -->|no| FAIL1["Fail: bad inclusion proof"]
C -->|yes| D["vk == leaf.key_state.cur_pubkey?"]
C -->|no| FAIL2["Fail: identity closed/frozen"]
D -->|yes| E["Ed25519.verify(vk, auth_msg, sig)?"]
D -->|no| FAIL3["Fail: key mismatch"]
E -->|yes| F["Oracle co-signature + MPFS update valid?"]
E -->|no| FAIL4["Fail: bad signature"]
F -->|yes| OK["Value-write authorized"]
F -->|no| FAIL5["Fail: oracle/trie op"]
style OK fill:#1e3a2f,stroke:#4a9040,color:#e0e0e0
style FAIL1 fill:#3a1e1e,stroke:#d94a4a,color:#e0e0e0
style FAIL2 fill:#3a1e1e,stroke:#d94a4a,color:#e0e0e0
style FAIL3 fill:#3a1e1e,stroke:#d94a4a,color:#e0e0e0
style FAIL4 fill:#3a1e1e,stroke:#d94a4a,color:#e0e0e0
style FAIL5 fill:#3a1e1e,stroke:#d94a4a,color:#e0e0e0
Option B — Native signer
The AID owner signs the value-write transaction as a native Cardano
extra_signatory. The cage verifies blake2b_224(cur_pubkey) is a
transaction signatory.
On-chain checks:
- Signer resolution above (Active + no freeze marker)
blake2b_224(leaf.key_state.cur_pubkey) ∈ tx.extra_signatories- Oracle co-signature + MPFS update valid
No app-level signature required. The ledger enforces that the named key signed the transaction.
flowchart TD
A["tx.extra_signatories + inclusion_proof + freeze_proof"] --> B
B["inclusion_proof valid for trie_key at some root in window?"]
B -->|yes| C["leaf.status == Active and no active FreezeMarker?"]
B -->|no| FAIL1["Fail: bad inclusion proof"]
C -->|yes| D["blake2b_224(cur_pubkey) ∈ extra_signatories?"]
C -->|no| FAIL2["Fail: identity closed/frozen"]
D -->|yes| E["Oracle co-signature + MPFS update valid?"]
D -->|no| FAIL3["Fail: AID key not a signer"]
E -->|yes| OK["Value-write authorized"]
E -->|no| FAIL4["Fail: oracle/trie op"]
style OK fill:#1e3a2f,stroke:#4a9040,color:#e0e0e0
style FAIL1 fill:#3a1e1e,stroke:#d94a4a,color:#e0e0e0
style FAIL2 fill:#3a1e1e,stroke:#d94a4a,color:#e0e0e0
style FAIL3 fill:#3a1e1e,stroke:#d94a4a,color:#e0e0e0
style FAIL4 fill:#3a1e1e,stroke:#d94a4a,color:#e0e0e0
Replay protection: Cardano's UTxO model guarantees uniqueness — each value-write spends and recreates the cage UTxO. No counter or validity interval needed.
Choosing the mode
The two options are not a preference ranking — each is structurally required in different flows. The verifier library must expose both.
| Situation | Mode | Why |
|---|---|---|
| The AID owner signs the executing transaction (self-submitted cage writes, contract state transitions, delegation certificates) | Option B | Cheapest: no app-level signature, replay protection free from UTxO uniqueness |
| A third party signs the executing transaction (batcher-executed DEX orders, ceremony assemblers, any order/intent consumed later) | Option A — the only option | The owner's key is not among the transaction signatories; the authorization must travel inside the data, fully bound and validity-bounded |
| The AID key must stay isolated from any Cardano payment key (hardware custody constraints) | Option A | The key never signs a Cardano transaction |
Option B cannot cover the flagship use case
On batcher-model DEXes the trader never signs the executing transaction, so a required-signer check has nothing to find. Detached-signature authorization is part of the use-case-invariant core — see Business Cases — factored core and the Regulated DeFi case. Where the actor does sign the transaction, Option B remains the cheaper path.
When Option A is used, the auth_msg counter and validity bounds are
required.
Window root selection
The cage redeemer includes the specific root from the window used for the
inclusion proof. The cage script verifies that root is present in
registry_datum.roots. This makes the proof deterministic and auditable.
If the registry advances the root (new inception, rotation, close, or freeze) between proof construction and tx inclusion, the old root may drop out of the window. The proof must be recomputed against a root still in the window. Window depth 10 gives strong liveness at typical registry write rates.