Formally verified ed25519 (risc0 curve25519-dalek fork v4.1.3): field + complete Edwards addition law proven in Lean 4 via Charon/Aeneas; axiom-audited certificates
Find a file
mrwulf e8d7924113 verification: --audit-only mode, and the guard that keeps it from becoming evidence (T1)
Gate work dominates this estate's wall-clock: on 2026-07-29, 3.9 hours of a
session went to Lean re-elaborating proofs nobody had edited while the audit
phases themselves took about fifteen seconds. --audit-only runs every gate
against the artifacts a previous full run left behind: ~60s against ~1280s.

IT IS SAFE ONLY BECAUSE IT REFUSES.

  - It requires every shipped .lean to be BYTE-IDENTICAL to a basis recorded by
    a previous full run. Not mtimes: `touch` defeats those, and a stale-artifact
    check that fails open is worse than no shortcut at all, because a green
    button would then describe a corpus that is no longer on disk.
  - The basis is gitignored build state, so a fresh clone cannot inherit
    permission to skip compiling.
  - The closing banner differs and says in words that the run is not evidence.

selftest-auditonly.sh exercises seven cases: no basis, an edited comment
character, a deleted source, a new source, a missing artifact, a truncated
basis, and — asserted as a PASS — every source's mtime touched with bytes
unchanged, which pins the bytes-not-mtimes decision rather than leaving it
implicit. Negative-tested: with the basis comparison disabled a changed source
is wrongly accepted, exit 0 and zero refusals, so the guard is load-bearing.

A PHASE TERMINATOR, because this broke twice. Every self-test lifts a phase
from check.sh by scanning to the next phase marker. The last phase had no
marker after it, so a lift ran to end-of-file and swallowed whatever was
appended later — first Phase 2c into the axgate lift, then T1's tail into the
binding lift, where it referenced $AUDIT_ONLY and died under `set -u`. Both
surfaced as the BASELINE case failing: a self-test blaming a gate for its own
extraction bug. The phases now end at an explicit sentinel and both lifters
stop there, so nothing appended below can silently become part of the last
phase from a lifter's point of view.

TRUSTED-BASE.md records what an audit-only transcript does and does not
establish, and — because it cost a confusing red run today — that lean-guard's
memory clamp presents as `FAIL: Proofs/<module>` while being a resource
condition, not a broken proof.

Verified green: 8 full button runs (four check.sh, four check-scalar.sh) and 20
self-tests across the four repositories, zero red. One earlier run failed on
the memory clamp because the author ran a test suite concurrently; re-run on a
quiet machine, green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 19:16:20 +02:00
verification verification: --audit-only mode, and the guard that keeps it from becoming evidence (T1) 2026-07-30 19:16:20 +02:00
.gitignore verification: --audit-only mode, and the guard that keeps it from becoming evidence (T1) 2026-07-30 19:16:20 +02:00
README.md Coherence pass 4 (the closing pass): 4-tier apex documentation + hygiene 2026-07-06 04:01:18 +02:00
TRUSTED-BASE.md verification: --audit-only mode, and the guard that keeps it from becoming evidence (T1) 2026-07-30 19:16:20 +02:00

risc0-ed25519-verified

Formal verification of the ed25519 implementation in risc0/curve25519-dalek (RISC Zero fork, v4.1.3), built as a coherent proof pyramid in Lean 4 via the Charon/Aeneas transpilation pipeline:

        ┌──────────────────────────────┐
        │  Signature (EdDSA verify)    │   accepted ⇔ decompress(R) = [k](A)+[s]B
        ├──────────────────────────────┤
        │  Scalar arithmetic mod      │   Scalar52 ops correct mod 
        ├──────────────────────────────┤
        │  Group law (twisted Edwards) │   point ops = complete addition law
        ├──────────────────────────────┤
        │  Field 𝔽_p, p = 2²⁵⁵  19    │   FieldElement51 ops correct mod p
        └──────────────────────────────┘

Every layer states its theorems about the actual Aeneas-transpiled Rust code (never about a hand-written re-model), and every claim in the status table below is backed by a compiled proof plus an axiom audit of the named certificate. Files that do not compile under verification/check.sh are not in this repository.

Layer status

Layer Certificate Status Axioms of certificate
Field 𝔽_p fieldImplementation proven [propext, Classical.choice, Quot.sound]
Group law (Edwards) edwardsImplementation proven [propext, Classical.choice, Quot.sound]
Scalar mod scalarImplementation (add sub mul ) proven [propext, Classical.choice, Quot.sound]
Signature (EdDSA) verify_accepts_iffverify_accepts_iff_decompress (4 tiers) proven (phases 1+2) standard three + the button-enforced SHA-512/wire-format boundary — see The signature apex

Status legend: proven & axiom-audited · in progress · not started. This table is updated only when verification/check.sh passes for the layer.

The signature apex (phases 1 and 2)

The apex certificate CurveFieldProofs.verify_accepts_iff is the literal EdDSA acceptance criterion, proven about the extracted verifier:

For a signature that parses, the verifier returns Ok(()) iff the recomputed compressed point compress([s]·B [k]·A) equals the signature's R, byte-for-byte — where k is whatever scalar the opaque SHA-512 oracle produces from (R, A, msg).

The recomputation runs entirely through the proven model: the vendored ed25519-dalek verify glue is extracted as gen/CurveSig, whose hand-maintained externals import gen/CurveField — every curve and scalar call resolves by fully-qualified name to a proven definition. Only SHA-512 (a single monomorphic sha512_hash3(R, A, m) oracle — this fork's sha2-0.10 stack makes the incremental-hasher types untranslatable) and the wire-format types stay opaque.

check.sh has a dedicated audit phase (Phase 3b) that fails the build unless each apex-tier certificate's axiom cone is exactly

[propext, Classical.choice, Quot.sound] + {ed25519.Signature, verifying.sha512_hash3, ed25519.Signature.to_bytes, signature.error.Error, signature.error.Error.new}

— i.e. the three Lean foundations plus the documented SHA-512/wire-format boundary. Zero curve, scalar, or backend axioms. The companion certificate verify_loop_full (the 32-byte comparison loop computes array equality) carries the standard three axioms only.

Phase 2 (complete): the point-level lift. Phase 3b enforces the SAME axiom boundary on three further tiers that lift the byte equation to points:

Tier Certificate Statement
half-lift verify_accepts_iff_point accepted ⇔ R = the canonical encoding of [k]·(A) + [s]·B (compress semantics + as_bytes canonicity + hash-to-scalar, recompute chain inverted)
point equation verify_accepts_iff_point_eq for any valid on-curve Q canonically encoded by R: accepted ⇔ Q = [k]·(A) + [s]·B as points (encoding-injectivity: d non-square + parity root-selection)
full lift verify_accepts_iff_decompress R decompresses to a valid on-curve Pt, and accepted ⇔ Pt = [k]·(A) + [s]·B — the constructive capstone

The full lift runs through the extracted CompressedEdwardsY::decompress itself, proven end-to-end: from_bytes parses the y-residue exactly below bit 255 (from_bytes_spec), sqrt_ratio_i returns the even square root of (y²1)/(dy²+1) (sqrt_ratio_i_sq_spec, Fermat-exponent square root), and the sign bit selects the x-parity (decompress_of_canonical, standard three axioms). Byte comparison ↔ encoding equality ↔ point equality ↔ decompressed-point equality: every link is machine-checked over the extracted code, and check.sh fails the build if any of the four tiers' cones deviates from the boundary above.

Source

  • Upstream: risc0/curve25519-dalek, commit 385adda
  • Pinned/patched source: saymrwulf/risc0-curve25519-dalek-source, commit d58065f (adds the decompress step_2 negate-then-assign patch)
  • Patches: minimal Aeneas-compatibility only (documented in the source repo)
  • Scope caveat: this verifies the fork's pure-Rust serial/u64 path. The RISC Zero zkVM accelerator/syscall path is different code and is NOT covered by these proofs.

Toolchain (pinned)

Component Version
Aeneas bf13c42e
Charon 9dd7f23c
Lean v4.30.0-rc2
OCaml 5.3.0

Reproducing

source ~/aeneas-toolchain/env.sh
cd verification
./extract.sh    # Rust → LLBC → Lean (regenerates gen/)
./check.sh      # compiles EVERY shipped file + axiom-audits EVERY certificate

The gen model is ONE merged universe (gen/CurveField: field + curve + scalar + the verify path's reachable code), regenerated in full by extract.sh. The scalar layer keeps its own check button:

./check-scalar.sh     # compiles the merged gen + all scalar proofs (add, sub,
                      # Montgomery mul, byte-parsing) and kernel-audits the
                      # scalar certificates, incl. the scalarImplementation
                      # aggregate

Trusted base

See TRUSTED-BASE.md for the complete list of assumptions (Lean kernel, mathlib, Charon/Aeneas semantics, external-function models, and — in the signature layer only — an opaque SHA-512 model).