Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Commitments

A commitment is the atomic unit of confidential state:

Commitment
  ├── commitment_id   deterministic digest over (owner, token, value, nonce)
  ├── owner           the account that holds it
  ├── token           the asset it is denominated in
  ├── value           **private simulation state** (never observable output)
  ├── nonce           deterministic blinding nonce
  └── created_at      ledger sequence of creation

Commitment IDs are a pure function of their content — no randomness beyond the seeded nonce — which is exactly what makes “same seed + same operation sequence ⇒ same commitments” hold across runs and machines.

Lifecycle

create (Active) ──consume──▶ consumed + nullifier registered
  • Deposit creates an active commitment for the depositor.
  • Transfer consumes the sender’s active commitments (each registers a nullifier) and creates fresh commitments for the recipient and change.
  • Merge consumes several owned commitments and creates one carrying their summed value.
  • Withdraw consumes commitments and moves value back to the public world (with a change commitment when the withdrawal is partial).

Every mutation goes through the store’s commitment lifecycle, which guards the invariants:

  • Duplicate nonces are rejected — an identical live commitment cannot be created twice.
  • Consumed commitments are rejected — replaying a spent commitment fails with consumed_commitment (double-spend protection).
  • Wrong owners are rejected — an account can only consume its own commitments.

Nullifiers

When a commitment is consumed, its nullifier — a deterministic digest of the commitment ID — is registered in the store’s append-only registry. The lifecycle is strictly unused → consumed and never consumed → reused: any attempt to spend the same commitment again would re-derive the same nullifier and be rejected. The registry is a first-class part of the state root, so replaying a store reproduces it exactly, and the fuzz suite asserts the registry always matches the consumed set.

The privacy boundary

The commitment’s value is private simulation state. It lives in the state store and is visible to the test harness — that is the point of a simulator — but events, transactions, proofs, and published output never carry it. Observable records reference commitments by ID only (inputs, outputs on a transaction), never by value.

Merkle state

For flows that depend on accumulator-style structures, the state crate provides a sparse Merkle tree over commitment-style leaves with deterministic roots and membership paths (see docs/state-model.md and the crucible_state::merkle module) — the shape crucible-prover will consume for membership proofs.