What is mathematically proven, what it rests on, and how that's enforced.
PSL's value proposition is verifiable trust. This file is the honest,
single-source map of the formal layer: every load-bearing theorem, the exact
property it establishes, and the complete set of axioms it depends on. It is
machine-checked — lean/PSL/Audit.lean re-derives the axiom footprint of
each theorem below at build time and fails the build if it drifts, and the
formal-verification CI job runs that build on every PR. So this table cannot
silently go stale: if it disagrees with the code, CI is red.
Scope note. This ledger covers the Lean 4 formal layer (
lean/PSL/), which models the ledger semantics, the Sparse Merkle Tree, and the mempool compliance policy. Properties verified empirically (bit-exact primitive re-execution, cross-platform determinism, ed25519 signature correctness) are tracked indocs/STATUS.mdgates, not here. The honest boundary between "proved" and "tested" is the point.
Every load-bearing theorem rests on a subset of exactly these five axioms —
nothing else (no sorry, no native_decide):
| Axiom | Kind | Why it's trusted |
|---|---|---|
propext |
Lean foundation | Propositional extensionality — one of Lean's three standard axioms. |
Quot.sound |
Lean foundation | Quotient soundness — standard Lean axiom. |
Classical.choice |
Lean foundation | Classical choice — standard Lean axiom (used by by_cases/mathlib). |
PSL.MPT.hash_collision_resistant |
Crypto assumption | BLAKE3 is collision-resistant. Explicit, declared in MPT.lean. |
PSL.MPT.hash_length |
Crypto assumption | BLAKE3-256 emits a fixed 32-byte digest. Explicit, declared in MPT.lean. |
The first three are the standard trusted base of essentially all Lean/mathlib
developments. The last two are the only domain assumptions, and they are the
standard, expected form for a hash-based proof. There is no sorryAx and no
Lean.ofReduceBool (native_decide) anywhere in the load-bearing set.
| Property (what a regulator / integrator can rely on) | Theorem | File | Axioms |
|---|---|---|---|
| A transfer never changes the total supply of any asset (given a well-keyed state, a duplicate-free working set, and distinct endpoints both in that set). | transfer_conserves |
PSL/Conservation.lean |
propext, Quot.sound |
| A freeze never changes any balance (given a well-keyed state). | freeze_conserves |
PSL/Conservation.lean |
Quot.sound |
| The total supply of an asset can change only via mint or burn — a transfer or freeze that changes supply is impossible. | supply_changes_only_via_authority |
PSL/Conservation.lean |
propext, Quot.sound |
| A mint increases the asset's total supply by exactly the minted amount. | mint_increases_supply |
PSL/LedgerInvariants.lean |
propext, Quot.sound |
| A burn decreases the asset's total supply by exactly the burned amount. | burn_decreases_supply |
PSL/LedgerInvariants.lean |
propext, Quot.sound |
| A frozen sender cannot move funds — its transfer is a no-op (state and success flag unchanged). Freeze-authority enforcement. | frozen_sender_transfer_noop |
PSL/LedgerInvariants.lean |
none |
| A successful transfer strictly advances the sender's nonce by one (replay/ordering monotonicity). | transfer_success_increments_nonce |
PSL/LedgerInvariants.lean |
none |
Block-level supply accounting: over ANY block, for every asset, supply_before + total_minted = supply_after + total_burned, where the totals sum exactly the successful mint/burn amounts. Supply moves only by authorized amounts no matter how transactions are interleaved. (Relies on wellKeyed_applyTx/wellKeyed_applyBlock: the WellKeyed invariant is preserved by every operation — proven with no axioms — so per-tx theorems legitimately chain.) |
block_supply_accounting |
PSL/BlockAccounting.lean |
propext, Quot.sound |
| A block containing no mint or burn transactions cannot change the supply of any asset. | block_without_authority_conserves |
PSL/BlockAccounting.lean |
propext, Quot.sound |
Value binding: for a committed (root, key), no two proofs can verify with different values. A forged alternative value that still verifies would break collision-resistance. (Phone-side balance-proof soundness.) |
inclusion_proof_sound |
PSL/MPT.lean |
propext, Classical.choice, Quot.sound, hash_collision_resistant, hash_length |
| Completeness: the honestly-generated proof for any key verifies against the model root. Purely structural — needs no collision-resistance. | inclusion_proof_complete |
PSL/SMTModel.lean |
propext, Quot.sound, hash_length |
Correctness (capstone): any proof that verifies against a model root carries exactly the stored value m key. Soundness + completeness combined: the committed root pins down precisely the map's value at every key. With an absent key (m key = []) this is non-inclusion soundness. |
inclusion_proof_correct |
PSL/SMTModel.lean |
propext, Classical.choice, Quot.sound, hash_collision_resistant, hash_length |
The state commitment depends only on the final key→value map — writing two distinct keys in either order yields the same root (spec-level form of the Rust put_order_independent_for_independent_keys test). |
smt_root_order_independent |
PSL/SMTModel.lean |
propext, Quot.sound |
Compliance admission policy (PSL/Compliance.lean, modeling sequencer/src/mempool.rs::validate; signature verification abstracted as an opaque proposition). Nine theorems, all axiom-free: a high-value transfer without travel-rule metadata is rejected; a freeze needs both issuer authority and a court order; mint/burn need the (capability-enabled) issuer authority; a frozen sender's transfer/burn is rejected; a wrong-nonce transfer/burn is rejected; an invalid signature is rejected; and a fully-compliant transfer is admitted. |
travel_rule_high_value_rejected, freeze_non_authority_rejected, freeze_without_court_order_rejected, mint_non_authority_rejected, burn_non_authority_rejected, frozen_sender_rejected, nonce_mismatch_rejected, invalid_signature_rejected, compliant_transfer_admitted |
PSL/Compliance.lean |
none |
Together these give a complete supply-accounting picture, per transaction
and per block: total supply is invariant under transfer and freeze,
moves by precisely the authorized amount under mint and burn, and over an
entire block the books balance exactly (before + minted = after + burned).
And the Merkle layer is closed in both directions: honest proofs verify
(completeness), and anything that verifies is the truth
(soundness/correctness).
The formal layer states what is actually true of the model, with the hypotheses that are genuinely required — and proves each hypothesis is necessary with a counterexample, rather than quietly assuming it away:
WellKeyed(∀ pk, (s.accounts pk).pubkey = pk) — the model writes an account at indexa.pubkey, so a mis-keyed state letsfreezeclobber a different slot. Counterexample:freeze_not_conserves_without_wellkeyed(no axioms — puredecide).live.Nodup— a duplicated key double-counts the moved delta. Counterexample:transfer_not_conserves_without_nodup(no axioms).- The previous
supply_changes_only_via_authoritywas vacuous (its conclusion held for the constant"mint", ignoring its hypothesis);original_authority_conclusion_is_vacuousdemonstrates that defect, and the current statement is the honest one. - The previous MPT
inclusion_proof_soundwas ill-posed (its conclusion aboutvalue.lengthis enforced by no verifier); it is replaced by value binding, the property the SMT actually guarantees and that thetampered_value_fails_proofcrypto test exercises.
hashisopaquein Lean — the proofs treat BLAKE3 as a collision- resistant fixed-length function (the two axioms above), not by modeling the compression function. This is standard; closing it would require a verified BLAKE3, which mathlib does not provide.- Lean ↔ Rust/C correspondence is a hand-translation contract, not a
proof:
lean/PSL/Ledger.leanmirrors the executable semantics insequencer/src/trace.rs(composed from theprimitives/*.cmicro-ops);lean/PSL/MPT.lean::verifyProofandlean/PSL/SMTModel.leanmirrorcrypto/src/smt.rs. Divergence is guarded empirically (gate 1 bit-exact vectors) and bytools/check_lean_drift.py, which hashes every watched implementation source against a pinned manifest and runs in CI (theformal-verificationjob fails if a watched source changes without the manifest being re-reviewed). - Correspondence-audit findings (2026-06, recorded honestly rather than
papered over):
- The previous drift checker watched
ledger_*.cfiles that never existed in this tree — it had never run successfully, so the hand-translation contract was unenforced until now. The watch list is fixed and pinned. Account.assetIdis model-only: the Rust 64-byte account record has no asset_id field (asset_id exists only on transactions). The Lean assetId guards model the intended per-asset partitioning; the implementation enforces it at a different layer.- Rust's transfer credit path uses u128
wrapping_addwhile Lean balances are ℕ — the conservation theorems do not cover a u128 wraparound of a recipient balance. Mint useschecked_add(fails safe). Whether wrap is reachable depends on issuer mint policy; treating this as out of scope of the formal claim is a documented decision, not an oversight.
- The previous drift checker watched
- The SMT model is functional, not imperative.
PSL/SMTModel.leanmodels the tree as a pure function of the key→value map (rootHash), faithful tocrypto/src/smt.rs's hashing scheme (leaf/internal/default-subtree rules, MSB-first key bits). Completeness, correctness, and order-independence are proven against this functional spec. The imperative node-storeputin Rust agreeing with the functional spec remains an empirically-tested property (the 100k randomized-put determinism test), per the hand-translation contract. - Compliance: policy proven, signature abstracted.
PSL/Compliance.leanmodels themempool.rs::validateadmission policy and proves all nine regulator-facing gate properties (axiom-free). Signature verification itself is abstracted as an opaque propositionSigValid(the same treatmenthashgets) — ed25519 correctness is a tested, not formalized, property. - Essentially the whole load-bearing surface is now formalized. Supply
conservation/accounting (per-tx and per-block), mint/burn exactness,
freeze-authority, nonce/replay monotonicity, Merkle
soundness/completeness/correctness, root order-independence, and the
compliance admission policy all have machine-checked theorems above. The
remaining honest gaps are the correspondence items: the hand-translation
contract (guarded by the CI drift check), the opaque-
hash/SigValidabstractions, the functional-vs-imperative SMTput, and the documented ℕ-vs-u128 wraparound scoping.
cd lean
elan toolchain install $(cat lean-toolchain) # one-time
lake exe cache get # prebuilt mathlib (~1-2 min)
lake build # builds proofs + runs the audit gateA passing build prints ✓ formal audit passed: 22 load-bearing theorems rest only on the 5 allowed axioms. To see the footprint yourself:
echo 'import PSL
open PSL PSL.MPT PSL.Compliance
#print axioms transfer_conserves
#print axioms supply_changes_only_via_authority
#print axioms mint_increases_supply
#print axioms block_supply_accounting
#print axioms inclusion_proof_sound
#print axioms inclusion_proof_correct
#print axioms smt_root_order_independent
#print axioms travel_rule_high_value_rejected
#print axioms freeze_without_court_order_rejected
#print axioms compliant_transfer_admitted' > /tmp/Ax.lean
lake env lean /tmp/Ax.lean