Skip to content

Formally prove the correctness of Sqrt.sol and Cbrt.sol#511

Open
duncancmt wants to merge 94 commits intodcmt/newton-raphson-optimizationfrom
dcmt/codex-prove-sqrt-cbrt
Open

Formally prove the correctness of Sqrt.sol and Cbrt.sol#511
duncancmt wants to merge 94 commits intodcmt/newton-raphson-optimizationfrom
dcmt/codex-prove-sqrt-cbrt

Conversation

@duncancmt
Copy link
Collaborator

No description provided.

duncancmt and others added 28 commits February 26, 2026 18:02
Machine-checked proof that _sqrt converges to within 1 ULP of isqrt(x) for
all uint256 inputs, and that the floor-correction step yields exactly isqrt(x).

Proof structure (zero sorry, no Mathlib):

- FloorBound.lean: Each truncated Babylonian step >= isqrt(x) (AM-GM +
  integrality). Absorbing set {isqrt, isqrt+1} is preserved.

- StepMono.lean: Step is non-decreasing in z for overestimates (z^2 > x),
  justifying the max-propagation upper-bound strategy.

- SqrtCorrect.lean: Definitions matching EVM semantics, native_decide
  verification of all 256 bit-width octaves, lower bound chain through 6
  steps, and floor correction proof.

Also includes verify_sqrt.py, the Python prototype that guided the Lean proof.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Proves the cubic AM-GM inequality (3m-2z)*z^2 <= m^3 and the resulting
floor bound: a single truncated Newton-Raphson step for cube root never
undershoots icbrt(x). This is the core mathematical lemma needed for the
full cbrt convergence proof.

Key technique: the witness identity m^3 - (3m-2z)*z^2 = (m-z)^2*(m+2z)
is proved by expanding both sides via simp [add_mul, mul_add, mul_assoc,
mul_comm, mul_left_comm] then closing with omega -- a 4-line ring-substitute
that works without Mathlib.

Also includes verify_cbrt.py (Python convergence prototype) and updates the
formal/ README to track both sqrt (complete) and cbrt (in progress).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds CbrtCorrect.lean with full convergence and correction proofs:
- native_decide verification of all 256 bit-width octaves
- Lower bound chain through 6 NR iterations (icbrt(x) <= _cbrt(x))
- Floor correction proof (cbrt returns exactly icbrt(x))
- Seed and step positivity invariants

Combined with the cubic AM-GM floor bound from the previous commit,
this gives a complete machine-checked proof that Cbrt.sol is correct
for all uint256 inputs (0 sorry, no Mathlib).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prove `innerCbrt x ≤ icbrt x + 1` unconditionally for all x < 2^256,
completing the end-to-end formal verification of Cbrt.sol.

The proof uses a per-octave finite certificate scheme (cribbed from the
sqrt proof):

- FiniteCert.lean: 248-entry lookup tables with native_decide proofs
  of error bounds d1..d6 ≤ 1 for octaves 8..255
- CertifiedChain.lean: chains 6 NR steps through the error recurrence
  d_{k+1} = d_k^2/lo + 1, using an analytic d1 bound derived from the
  cubic identity m^3+2s^3-3ms^2 = (m-s)^2(m+2s)
- Wiring.lean: maps x to its certificate octave and produces the
  unconditional theorems floorCbrt_correct_u256 and
  floorCbrt_correct_u256_all

Zero sorry. Full project builds with `lake build`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add .github/workflows/cbrt-formal.yml patterned after sqrt-formal.yml:
  generates FiniteCert.lean from generate_cbrt_cert.py then runs lake build
- Remove FiniteCert.lean from git tracking (auto-generated, like sqrt's
  GeneratedSqrtModel.lean) and add to .gitignore
- Add --output flag to generate_cbrt_cert.py for CI usage
- Rewrite README.md with full proof architecture, end-to-end verify
  instructions, and file inventory

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add the final layer that formally links the Solidity implementation to
the proven-correct mathematical spec:

- generate_cbrt_model.py: parses Cbrt.sol assembly and emits EVM-faithful
  and normalized Nat Lean models of _cbrt, cbrt, cbrtUp (forked from
  the sqrt analog)
- GeneratedCbrtSpec.lean (1015 lines, 0 sorry): proves
  - model_cbrt_evm = model_cbrt on uint256 (no overflow)
  - model_cbrt = innerCbrt (Nat model matches hand-written spec)
  - model_cbrt_floor_evm_correct: EVM floor model = icbrt
  - model_cbrt_up_evm_upper_bound: EVM ceiling model rounds up correctly
- Updated CI to generate model from Cbrt.sol before building
- Updated README with full end-to-end verification instructions

The proof is now end-to-end: from Cbrt.sol Solidity assembly through
auto-generated Lean model to machine-checked correctness on uint256.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
duncancmt and others added 30 commits February 28, 2026 21:13
Prove that 6 Babylonian steps from the fixed seed (floor(sqrt(2^255)))
give a 1-ULP bracket for natSqrt(x) when x ∈ [2^254, 2^256), and that
floor correction gives exactly natSqrt(x). This is the mathematical
foundation for bridging model_sqrt512_evm to natSqrt.

The main EVM bridge theorem is stated but sorry'd pending mechanical
EVM bridge sub-lemmas (normalization, Newton, Karatsuba, correction).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Factor the main theorem into model_sqrt512_evm_eq_sqrt512 (sorry'd EVM
bridge showing the model matches the algebraic sqrt512 spec) composed
with sqrt512_correct (already fully proved). This isolates the remaining
work to showing each EVM phase (normalization, Newton, Karatsuba,
correction, denormalization) matches the algebraic spec.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split model_sqrt512_evm_eq_sqrt512 into three sorry'd pieces:
- evm_normalization_bridge: EVM bit-shifts = multiply by 4^shift
- evm_compute_bridge: EVM Newton+Karatsuba+correction = karatsubaFloor
- composition: threading the bridge results through the model

The main theorem model_sqrt512_evm_correct is fully proved assuming
these bridges, via sqrt512_correct.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove failed decomposition attempts that hit kernel deep recursion
(the model's shared let bindings prevent naive term decomposition).
The single remaining sorry (model_sqrt512_evm_eq_sqrt512) captures
the full EVM-to-algebraic bridge. The main theorem composition
model_sqrt512_evm_correct is fully proved via sqrt512_correct.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restructure the 512-bit sqrt EVM bridge proof into well-defined layers:

- Section 5: Norm model helpers — prove normStep_eq_bstep (Babylonian step
  in the unbounded norm model = bstep), normFloor_correction, and chain
  them to show norm_inner_sqrt_eq_natSqrt on normalized [2^254, 2^256) inputs.

- Section 6: Norm→sqrt512 bridge — decomposes into normalization, inner sqrt
  (proved via floorSqrt_fixed_eq_natSqrt), Karatsuba quotient with carry,
  correction, and un-normalization. Single sorry remaining.

- Section 7: EVM→norm bridge — proves individual op equivalences:
  evmSub_eq_of_le, evmAdd_eq_of_bounded, evmShl_eq_normShl (with v*2^s
  bound), evmShr_eq_of_small, plus the critical overflow cancellation
  lemma evmSub_evmAdd_eq_of_overflow showing EVM overflow+underflow at
  the combine step (r=2^256) produces the same result as unbounded Nat.
  Single sorry remaining for threading these through the full let-chain.

Reduces total sorry count from 5 to 2 (model_sqrt512_norm_eq_sqrt512 and
model_sqrt512_evm_eq_norm).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The norm model (model_sqrt512) uses unbounded normShl/normMul which
don't match EVM uint256 semantics, making the old factorization
(EVM → norm → sqrt512) unprovable. Restructure to prove model_sqrt512_evm
= sqrt512 directly via 3 sub-lemmas:
  A. EVM normalization produces correct normalized words
  B. EVM inner sqrt gives natSqrt (reuses convergence certificate)
  C. EVM Karatsuba + correction + un-normalization = karatsubaFloor / 2^k

Reduces from 2 false sorry's to 4 true sorry's (3 sub-lemmas + assembly).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add evm_bstep_eq showing each EVM Babylonian step equals bstep when
z ∈ [2^127, 2^129) and x < 2^256 (sum doesn't overflow). Chain 6 steps
to show EVM inner sqrt matches norm inner sqrt. Two sub-sorry's remain:
- bstep lower bound (AM-GM: bstep ≥ sqrt(x) ≥ 2^127)
- floor correction matching (evmSub/evmLt = normSub/normLt on bounded inputs)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Close the EVM inner sqrt proof by:
- Using babylon_step_floor_bound for the lower bound (2^127 ≤ bstep)
- Showing evmSub/evmLt/evmDiv = their Nat counterparts on bounded inputs
- Chaining through correction_correct for the floor correction step

Remaining 3 sorry's: normalization (A), Karatsuba+correction (C+D), assembly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix 5 build errors:
- evm_bstep_eq: rewrite evmDiv before evmAdd (goal has evmDiv not x/z)
- evm_bstep_eq: fix sum bound proof (avoid Nat.pow_succ, use omega)
- evm_bstep_eq: fix upper bound (use omega instead of Nat.div_lt_div_right)
- evm_inner_sqrt_eq_natSqrt: use set/rw instead of simp only to avoid
  massive term expansion; name intermediates with set, rewrite step by step
- evmShl_eq': remove unused Nat.shiftLeft_eq from simp

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Build succeeds with 4 sorry's decomposing the EVM bridge:
  A. evm_normalization_correct — EVM normalization gives x*4^k/2^256
  B. evm_inner_sqrt_eq_natSqrt — EVM Babylonian steps = natSqrt
  C+D. evm_karatsuba_correction_unnorm — Karatsuba + correction + unnorm
  main. model_sqrt512_evm_eq_sqrt512 — assembly of A+B+C+D

Helper evm_bstep_eq is fully proved (no sorry).
Reduced from 2 false sorry's to 4 true, well-defined sorry's.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract _innerSqrt, _karatsubaQuotient, _sqrtCorrection from _sqrt in
512Math.sol. Extend yul_to_lean.py to support multi-return functions
(tuple types, __component_N projections). The pipeline now generates 4
separate Lean models instead of one monolithic ~30-binding term, making
each sorry sub-lemma target ~2-10 let-bindings.

All private sub-functions are inlined by solc → identical bytecode.
Fuzz test (1000 runs) confirms the regenerated Lean model matches.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prove model_innerSqrt_evm_correct by factoring through norm model:
- model_innerSqrt_snd_def: residue = x - fst^2 (by rfl)
- model_innerSqrt_snd_eq_residue: norm residue = x - natSqrt(x)^2
- model_innerSqrt_evm_correct: both components correct (uses evm_eq_norm)

Add helper theorems natSqrt_lt_2_128 and natSqrt_ge_2_127 for bounds.

Remaining sorry: model_innerSqrt_evm_eq_norm (EVM ops = norm ops on
bounded inputs, ~10 let-bindings via evm_bstep_eq chain).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract `_bstep` as a separate Solidity function so the Lean model
generator produces `model_bstep_evm` calls instead of inlining 6
copies of `evmShr(1, evmAdd(r, evmDiv(x, r)))`. This makes each
Babylonian step independently provable via `model_bstep_evm_eq_bstep`.

Gas unchanged (solc inlines private pure functions): μ 3549→3549.

Prove `model_innerSqrt_evm_eq_norm` by:
- Chaining 6 `model_bstep_evm_eq_bstep` applications (each gives
  equality + [2^127, 2^129) bounds for the next step)
- Showing the correction `evmSub z6 (evmLt (evmDiv x z6) z6)` matches
  `normSub z6 (normLt (normDiv x z6) z6)` via `correction_correct`
- Showing the residue `evmSub x (evmMul r r)` matches `normSub x
  (normMul r r)` since r = natSqrt(x) < 2^128 so r^2 < 2^256

Remaining sorry's: 3 (karatsubaQuotient, sqrtCorrection, composition)
plus 1 pre-existing normalization proof broken by Lean v4.28 API changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewrite the normalization proof to handle Lean 4.28 breaking changes:
- Nat.add_mul_mod_self → Nat.add_mul_mod_self_right
- Nat.mul_div_mul_left pattern matching changed
- Nat.mul_lt_mul → Nat.mul_lt_mul_of_le_of_lt
- ring tactic unavailable (Mathlib-only)
- set tactic unavailable (Mathlib-only)

Key structural fix: avoid `intro` for let-bindings (which creates
opaque defs that rw/simp can't penetrate in Lean 4.28). Instead use
`show` to inline all let-bindings upfront, then `intro` only for
variables that need case-splitting.

Case-split on dbl_k = 0 (where evmShr 256 returns 0 since 256 ≥ 256)
vs dbl_k > 0 (where evmShr (256 - dbl_k) works normally).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… 2 sorrys

Close the Karatsuba quotient EVM bridge (sorry 2/4 → proved):
- Handle no-carry case (res < 2^128): n_evm = n_full directly
- Handle carry case (res >= 2^128): correction via WORD_MOD = d*qw + rw + 1
- Change hypothesis from res <= 2*natSqrt(...) to res <= 2*r_hi

Revise model_sqrtCorrection_evm_correct spec to raw EVM bridge form:
- Result = r_hi*2^128 + r_lo - cmp (where cmp is 257-bit comparison)
- Add hypotheses: r_lo <= 2^128, rem < 2*r_hi, hedge condition
- Scaffold EVM simplification (constant folding, all ops reduced to Nat)

Add helper lemmas:
- mul_mod_sq: (a*n) % (n*n) = (a%n)*n
- mul_pow128_mod_word: (a*2^128) % 2^256 = (a%2^128)*2^128
- div_of_mul_add / mod_of_mul_add: Euclidean division after recomposition

Remaining: 2 sorrys (sqrtCorrection comparison logic, composition proof)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4-way case split on rem/2^128 ∈ {0,1} × r_lo/2^128 ∈ {0,1}:
- (0,0): comparisons match directly, no EVM overflow
- (0,1): r_lo=2^128, cmp=1, handle evmAdd overflow via evmSub_evmAdd_eq_of_overflow
- (1,0): cmp=0, rem*2^128 ≥ 2^256 > r_lo^2
- (1,1): contradiction via hedge

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Close the last sorry by proving the EVM model of 512-bit sqrt equals
the algebraic sqrt512 function. The proof decomposes into:

- evm_composition_eq_karatsubaFloor: composition of the three EVM
  sub-models (innerSqrt, karatsubaQuotient, sqrtCorrection) equals
  karatsubaFloor on normalized inputs. Uses the Karatsuba algebraic
  identity x + q² = r² + rem·H + x_lo_lo via correction_equiv.

- karatsubaFloor_lt_word: result fits in 256 bits, via
  karatsubaFloor_eq_natSqrt and natSqrt upper bound.

- Main theorem assembly: unfold model_sqrt512_evm, rewrite the
  composition to karatsubaFloor, then convert evmShr to division.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace all 21 native_decide calls with decide (using maxRecDepth for
deep convergence certificates and Fin 256 enumeration). Fix 6 unused
variable/simp warnings. Axiom set now minimal: propext, Classical.choice,
Quot.sound.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Solidity helper functions in 512Math.sol were renamed:
  _bstep → _sqrt_babylonianStep
  _innerSqrt → _sqrt_baseCase
  _karatsubaQuotient → _sqrt_karatsubaQuotient
  _sqrtCorrection → _sqrt_correction

Update generate_sqrt512_model.py to reference the new Solidity names
while preserving the Lean model names (model_bstep, model_innerSqrt,
etc.) so downstream proofs remain stable.

Fix two proofs in GeneratedSqrt512Spec.lean to accommodate the slightly
different generated model (double-AND shift wrapping and reversed
operand order in addition from the new compiler output).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tructure

- Add formal/sqrt/generate_sqrt_cert.py that generates FiniteCert.lean
  with SqrtCert (256 octaves) and Sqrt512Cert (fixed-seed certificates
  for octaves 254/255), replacing the hand-written FiniteCert.lean and
  inline certificate definitions in GeneratedSqrt512Spec.lean.

- Refactor model_innerSqrt_evm_eq_norm: extract shared bstep chain and
  correction logic into evm_innerSqrt_pair, eliminating ~100 lines of
  duplicated proof code across the .1/.2 components.

- Update CI: sqrt-formal.yml and sqrt512-formal.yml now generate the
  certificate before building, and sqrt512-formal.yml builds proofs
  (not just the model evaluator), matching sqrt/cbrt patterns.

- Consolidate formal READMEs into a single formal/README.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Solc 0.8.33 emits `and(and(1,255),255)` type-cleanup wrappers around
shift amounts and may reorder commutative operands in the Yul IR for
`_sqrt_babylonianStep`. Update the `model_bstep_eq_bstep` and
`model_bstep_evm_eq_bstep` proofs to constant-fold the nested AND back
to 1 and handle the `add(div(x,z),z)` vs `add(z,div(x,z))` reordering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Lake warns about a missing manifest when the SqrtProof dependency is
resolved during a fresh CI checkout. Track the file in git so it is
present at clone time.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant