lean formal verification for move-stdlib - #335
Draft
ganymedio wants to merge 2 commits into
Draft
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Move stdlib formalization + VM↔Lean difftest
Summary
stdlib-focused formalization of Move in Lean (
MovementFormal): high-level specs, a bytecode-levelMoveModel, and refinement proofs that connect the two. It also adds a VM↔Lean regression harness: a Rust tool drives the real Aptos Move VM, writes a JSON oracle of concrete outcomes, and a Lean executable replays those cases and checks agreement.Workspace wiring (
move-lean-difftest), docs underaptos-move/framework/formal/, anddifftest-stdlib.sh(stdlib-only suites) are included so reviewers canlake buildthe proofs and run the difftest pipeline locally. Confidential-assets formalization, CA corpora, and CA-only difftest suites are not part of this change.Contents of this PR
aptos-move/framework/formal/lean/— Lean 4 packageMovementFormal:Std.*(move-stdlib-oriented lemmas),Refinement.Std.*(refinements againstMoveModel),MoveModel.*(bytecode, catalogs, step lemmas),AptosStd.*,SmokeTests.*,DiffTest.*(oracle runner). Toolchain:lean/lean-toolchain(leanprover/lean4:v4.24.0). Lake roots are listed inlean/lakefile.lean; default library target isMovementFormal, executabledifftest(lake exe difftest).aptos-move/framework/formal/difftest/— Rust cratemove-lean-difftest(workspace member): runs the Aptos Move VM, emits JSON oracle files.aptos-move/framework/formal/difftest-stdlib.sh— Runs stdlib-only VM suites thenlake exe diffteston the generated oracle (same commands as below).aptos-move/framework/formal/README.md,aptos-move/framework/formal/.gitignore, and related ignores underlean//difftest/— docs and ignore rules for generated artifacts (difftest_oracle*.json,.lake/, etc.).Cargo.toml/Cargo.lock— registermove-lean-difftestin the workspace.Refinements in this PR (
MovementFormal.Refinement.Std.*)What these are: Lean theorem files (Lake roots in
lean/lakefile.lean). Most rows proveeval(theMoveModelinterpreter) on a named env + native index + arguments equals the result of the matchingMovementFormal.Std.*definition (or a tiny bytecode program wired to that spec). They are not Rust tests. The real VM is exercised separately by VM↔Lean difftests on finite oracles.MovementFormal.Refinement.Std.BcsNative.bcsToBytes_*andBcsCataloghelpers agree withMovementFormal.Std.Bcs(bcs.moveprimitives): per-typeto_bytesencodings,serialized_size_*,vector<u8>, etc. —lean/MovementFormal/Refinement/Std/Bcs.lean.MovementFormal.Refinement.Std.CorePrograms.Coreprograms instdModuleEnv,evalat fixed indices equals the spec on all inputs (e.g.add_u64,is_zero_u64,bcs_to_bytes_u64,read_via_ref,inc_via_ref,vec_push_and_len) —lean/MovementFormal/Refinement/Std/Core.lean.MovementFormal.Refinement.Std.StdPrimitivesPrograms.StdPrimitivesin a single-function env (native index 8 afterstdNatives)eval-matchesStd.Error/Std.BitVectorfor the coverederror::…/bit_vector::…shapes —lean/MovementFormal/Refinement/Std/StdPrimitives.lean.MovementFormal.Refinement.Std.Vectorvector::containsas inPrograms.Vector(native index 18 instdModuleEnv) refinesMovementFormal.Std.Vector.contains(loop + return; see file header for proof structure) —lean/MovementFormal/Refinement/Std/Vector.lean.MovementFormal.Refinement.Std.ErrorerrorCatalogModuleEnvindex 0–12eval-matches the correspondingMovementFormal.Std.ErrorAPI (canonical,invalid_argument, …) —lean/MovementFormal/Refinement/Std/Error.lean.MovementFormal.Refinement.Std.HashhashCatalogModuleEnv: native 0 =Std.Hash.Sha2_256, native 1 =Std.Hash.Sha3_256on the samevector<u8>bytes —lean/MovementFormal/Refinement/Std/Hash.lean.MovementFormal.Refinement.Std.SignersignerCatalogModuleEnv:borrow_address/address_ofeval-matchMovementFormal.Std.Signer—lean/MovementFormal/Refinement/Std/Signer.lean.MovementFormal.Refinement.Std.FixedPoint32Catalogfp32Oracle*inMoveModel/Native/StdPrimitivesreturn the same values asMovementFormal.Std.FixedPoint32on the same oracle wires —lean/MovementFormal/Refinement/Std/FixedPoint32Catalog.lean.MovementFormal.Refinement.Std.OptionCatalogoptionOracleU64*vsMovementFormal.Std.OptiononOption<u64>scratch wires —lean/MovementFormal/Refinement/Std/OptionCatalog.lean.MovementFormal.Refinement.Std.BitVectorCatalogbitVector*natives vsMovementFormal.Std.BitVectoronMvBitVectorwires —lean/MovementFormal/Refinement/Std/BitVectorCatalog.lean.MovementFormal.Refinement.Std.AclCatalogaclOracle*vsMovementFormal.Std.AclonMvAclwires (e.g. empty ACL) —lean/MovementFormal/Refinement/Std/AclCatalog.lean.MovementFormal.Refinement.Std.StringCatalogevalonstringCatalogModuleEnvvsMovementFormal.Std.Stringon inputs aligned withstringOracle*—lean/MovementFormal/Refinement/Std/StringCatalog.lean.MovementFormal.Refinement.Std.CmpCatalogMovementFormal.Std.Cmp(compareU64,compareBool,compareU8,compareAddress,compareU16/32/128/256, andisLt/isEq/ …) on fixed argument pairs (rfl/native_decide). This file does not currently relateevalonCmpCatalogModuleEnv; it checks the pure comparison layer used alongside thecmpdifftest suite —lean/MovementFormal/Refinement/Std/CmpCatalog.lean.move-stdlib coverage (what this PR actually covers)
Source of truth for “stdlib” here:
third_party/move/move-stdlib/sources(12 modules:bcs,hash,vector,signer,option,error,string,bit_vector,fixed_point32,type_name,ascii,unit_test).Lean specs (
MovementFormal.Std.*, Lake roots inlean/lakefile.lean) line up with most of those modules: BCS, hash (Keccak / SHA2-256 / SHA3-256), vector operations, signer, option, error (+ canonical error math), string / UTF-8, bit vector, fixed-point32,type_name, and Cmp (comparison helpers used with thecmporacle path).MovementFormal.Std.ByteArrayAppendsupports byte-level facts used alongside BCS/string-style reasoning.Gaps / partiality relative to that 12-file tree:
ascii.move— no separateStd/Ascii.lean; ASCII behavior is intended to sit under the string work, not a 1:1 file mirror.unit_test.move— not a formal verification target; no spec layer.Refinement.Std.*rows are catalog-only or fixed-point lemmas (called out in the table above).Acl:MovementFormal.Std.Acland matching catalogs/refinement exist for difftest and verification plans, but ACL is not one of the 12third_party/move/move-stdlibsources; treat it as framework-adjacent surface area wired into the same harness.Axioms — what would be required to remove them
Axioms are uneven: some are “finish the
simpproof,” others are “whole sub-project.”grep ^axiomunderMovementFormal/)Arithmetic.lean:step_eq_u64,step_neq_u64,step_eq_bool,step_neq_boolstepon.eq/.neqfor non-reference scalars; case split onBEq/MoveValue(order of hours to a few days total, not crypto).ByteArrayLemmas.lean(address length,ByteArrayextensionality-style facts)ByteArrayis unfolded vs kept abstract (days if proofs fight kernel reduction).PCChaining,NativeCallPatterns,OraclePatterns,BorrowFieldChains,CopyLocChains,ProvenChains,CopyLocChains.chain_five_moveLoc_two_copyLoc, …run/stepproof obligation (days per lemma, weeks+ if you drain the whole list systematically—or delete unused axioms if nothing imports them).EdwardsCurve25519,RistrettoEncoding,Bulletproofs,Curve25519Field/Ristretto255primality, …Practical takeaway: eliminating only the
Arithmeticequality axioms and tightening ByteArray lemmas is realistic follow-up in this repo; eliminating every axiom including composition scaffolding and elliptic-curve / BP facts is a multi-track program (proof engineering + math), not a small cleanup.Setup
aptos-move/framework/formal/lean/lean-toolchain(Lean 4.24.x). The first time you use Lake from that directory it may clone Mathlib and other packages (network).Before
lake build: download Mathlib’s pre-built artifacts (otherwise Lean builds all of Mathlib from source — very slow). Run this exact command fromaptos-move/framework/formal/lean/(from repo root you can copy-paste the two lines as-is):One line from repo root (same thing):
Details:
aptos-move/framework/formal/lean/README.md.lake build(Lean library + refinements)Builds every Lake root in
lakefile.lean(specs,MoveModel,Refinement.Std.*, smoke tests, difftest support code). From repo root (includes Mathlib cache step):Build only the
difftestexecutable (runlake exe cache get!first on a fresh checkout, same as above):VM↔Lean difftests
Option A — script (repo root). Run
lake exe cache get!fromaptos-move/framework/formal/lean/at least once on a new machine (see Setup); the script does not run it for you.chmod +x aptos-move/framework/formal/difftest-stdlib.sh # once if needed ./aptos-move/framework/formal/difftest-stdlib.shOption B — same steps by hand (repo root for step 1):
Expected:
471passed,0failed,1skipped (test_read_std_counter/borrow_global,skip_lean).Oracle JSON under
difftest/is gitignored; regenerate with step 1 before running Lean.Optional: compile the Rust oracle tool (no Lean)
If you only want to confirm the
move-lean-difftestcrate typechecks in the workspace—withoutlake, Mathlib, orlake exe difftest—from repo root: