Confidential flows
The lifecycle:
Register → Deposit → Merge → ConfidentialTransfer → Withdraw
The validation pipeline
Every operation passes four ordered checks before state changes:
Operation
→ structural validation (well-formed, amounts positive, ...)
→ authorization validation (signature valid, signer == actor, permission)
→ state validation (accounts/tokens exist, registered, active, owned)
→ proof requirement (present + valid where required)
→ apply transition
Authorization (“is the actor allowed to do this?”) is deliberately separate
from cryptographic validity (“is the proof/signature well-formed?”). An
operation can be cryptographically valid yet unauthorized — the exact case
safeguard-hooks and crucible-scenarios will exercise later.
Operation recording
Every operation produces exactly one structured transaction, whatever its outcome:
Success -> applied and committed (version bumped, transition recorded)
Rejected -> failed validation/authorization/state/balance/proof before any
mutation (nothing changed, operation_rejected event)
Failed -> started inside the transaction scope and rolled back
(nothing changed, operation_rejected event)
Rejected and failed operations never bump the version and never move the
state root — atomicity is absolute — but they leave a deterministic-ID
record with the structured error embedded, so conformance suites can
assert on the rejection record, not only on the returned error. The
operation_rejected event carries the stable error code in its metadata
(never amounts).
Flow semantics
Register
Per (account, token). Duplicates are duplicate_registration. On tokens
that require registration, this is the gate for everything else.
Deposit
Public asset → confidential commitment. A fresh blinding nonce is drawn
from the seeded RNG, so equal-value deposits stay distinguishable but
reproducible. The account’s confidential head points at the new commitment.
Merge
Consolidate several active commitments owned by the merger into one whose value is their sum (overflow-checked). Each input is consumed (nullified). Merging is how an account consolidates before a large withdrawal.
Confidential transfer
The most important flow:
Sender → select all active commitments → validate coverage
→ prove through ProofProvider
→ consume inputs (nullify each)
→ create recipient commitment (amount) + change commitment (if any)
→ update heads → record
The simulator never proves itself: it builds a ProofRequest with
public inputs (sender, recipient, token, amount, input IDs) separate from
private inputs (values), hands it to the configured provider, and fails
with InvalidProof if no provider is present or the provider rejects. The
ProofReference recorded on the transaction is opaque to the simulator.
Withdraw
Confidential → public. Spends the account’s active commitments; when
withdrawing less than the total, a change commitment keeps the remainder
confidential. Requires no proof — this is the public side of the boundary.
Events (observable output)
| Operation | Success event | Rejection event |
|---|---|---|
| Register | account_registered | operation_rejected |
| Deposit | deposit_completed | operation_rejected |
| Merge | merge_completed | operation_rejected |
| Transfer | confidential_transfer_completed | operation_rejected |
| Withdraw | withdrawal_completed | operation_rejected |
Events never carry amounts or commitment values. They report the actor, token, ledger, and a stable result — enough to describe what happened without leaking how much. A rejection event adds only the stable error code to its metadata. The privacy boundary is enforced by design and asserted by regression tests.