diff --git a/data/research.js b/data/research.js index 07d639e..dad4da2 100644 --- a/data/research.js +++ b/data/research.js @@ -1,4 +1,14 @@ export const research = [ + { + slug: 'how-a-formal-audit-works-in-detail', + title: 'How a Formal Audit Works in Detail', + subtitle: + 'The technical path from repo access to machine-checked security guarantees.', + description: + 'The workflow behind an LFG Labs formal audit: specification, threat modeling, Verity modeling, Lean proofs, reports, and proof maintenance.', + date: '2026-04-23', + tag: 'Explainer' + }, { slug: 'midas-feed-growth-safety', title: 'Midas Growth-Aware Feed Safety Guarantees', diff --git a/pages/research/how-a-formal-audit-works-in-detail.jsx b/pages/research/how-a-formal-audit-works-in-detail.jsx new file mode 100644 index 0000000..c876b7d --- /dev/null +++ b/pages/research/how-a-formal-audit-works-in-detail.jsx @@ -0,0 +1,394 @@ +import Head from 'next/head' +import Link from 'next/link' +import PageLayout from '../../components/PageLayout' +import ResearchCard from '../../components/ResearchCard' +import ExternalLink from '../../components/research/ExternalLink' +import { getSortedResearch } from '../../lib/getSortedResearch' + +const AUDIT_PIPELINE = [ + 'Freeze a baseline commit', + 'Map the architecture and attack surface', + 'Write human-reviewable security specs', + 'Model the contract logic in Verity', + 'Prove the model satisfies the specs', + 'Report bugs and ambiguous behavior as they appear', + 'Re-run proofs after fixes', + 'Publish the final audit report and proof artifacts' +] + +const SPEC_EXAMPLE = `-- Functional guarantee, written before proof work starts. +def no_unbacked_note_created + (before after : ProtocolState) : Prop := + after.totalNotes <= after.totalBackedAssets ∧ + stateTransitionIsAuthorized before after ∧ + nullifiersAreNotReused before after` + +const MODEL_EXAMPLE = `verity_contract ShieldedPool where + storage + roots : Uint256 → Uint256 := slot 0 + nullifiers : Uint256 → Bool := slot 1 + commitments : Uint256 → Bool := slot 2 + + function withdraw + (root nullifier commitment amount : Uint256) : Unit := do + require (rootIsKnown roots root) "unknown root" + requireNot (getMapping nullifiers nullifier) "spent note" + require (verifyWithdrawProof root nullifier commitment amount) + "invalid proof" + setMapping nullifiers nullifier true + transferOut amount` + +const PROOF_TASK = `theorem withdraw_preserves_accounting + (s : ContractState) + (root nullifier commitment amount : Uint256) : + let result := (withdraw root nullifier commitment amount).run s + result.isOk → + no_unbacked_note_created s result.state := by + -- Unfold the Verity model. + -- Use the signed-off spec. + -- Discharge every state-transition case. + -- Lean accepts only if all cases are covered.` + +const CIRCUIT_AXIOM = `-- The circuit verifier is outside the EVM proof. +-- We state exactly what it is assumed to guarantee. +axiom withdraw_circuit_sound + (root nullifier commitment amount : Uint256) : + verifyWithdrawProof root nullifier commitment amount = true → + validWithdrawWitness root nullifier commitment amount` + +const BENCHMARK_INPUTS = [ + 'Fixed contract model', + 'Fixed formal specification', + 'One editable proof file', + 'One theorem name' +] + +const BENCHMARK_EVALUATION = [ + 'Reject placeholders such as sorry, admit, or new axiom', + 'Run Lean against the submitted proof', + 'Accept only if the required theorem compiles' +] + +function Snippet({ children }) { + return ( +
+ {children}
+
+ )
+}
+
+function NumberedList({ items }) {
+ return (
+ + The technical path from repo access to machine-checked security + guarantees. +
++ A formal audit is not just a normal audit with proofs added at the + end. The proof work changes the audit process itself. +
+
+ We first decide what the protocol must guarantee, then model the
+ relevant contract logic in{' '}
+
+ The engagement starts from a fixed commit hash, but the work is + iterative. Every important claim needs a spec, a model, and a + proof. +
++ Before writing proofs, we audit the protocol with the team and + decide what actually matters: funds cannot be created out of + thin air, withdrawals cannot be replayed, state transitions are + authorized, accounting stays solvent, and edge cases behave + intentionally. +
++ The output is not a vague checklist. It is a set of formal + properties that engineers can review and sign off on before the + proof phase starts. +
++ This is where most audit judgment lives. If the wrong invariant is + specified, a perfect proof still proves the wrong thing. +
++ Verity lets us encode EVM-style contract logic in Lean: storage + slots, mappings, require checks, uint256 arithmetic, transfers, + and state updates. The model is the executable object that the + proof talks about. +
++ The point is not to rewrite the whole repository mechanically. + The point is to model the behavior needed to prove the agreed + guarantees, while documenting every boundary that is assumed + rather than proved. +
++ A theorem ties the implementation model to the signed-off spec. + Lean checks every case. If one branch is missing, if arithmetic + wraps unexpectedly, or if a state update breaks the invariant, the + proof does not compile. +
++ This is the difference between “we reviewed this” and + “this exact property was checked by a proof kernel.” +
++ Modeling forces every implicit assumption into the open: which + state can change, which caller is authorized, which arithmetic + can overflow, which roots are valid, which nullifiers are spent, + and which off-chain component is trusted. +
++ When the model cannot satisfy the spec, we do not hide that + until the final report. We sync with the engineering team, + explain the failing case, let them patch, and continue from the + new behavior. +
++ The pre-audit report records the bugs, ambiguities, required + fixes, and the on-chain/off-chain attack surface before the + final proof is presented. +
++ If the protocol uses circuits, we audit and formalize the + circuit specification in Lean. The circuit compiler and + underlying cryptography are outside the EVM proof unless they + are separately verified. +
++ That means the EVM proof can use a circuit guarantee as an + axiom, but the axiom must say exactly what is being trusted. +
++ This is stronger than leaving the circuit as a black box. The + trusted boundary is named, scoped, and reviewable. +
+
+ The{' '}
+
+ This keeps proof work honest. A proof is not accepted because it + looks plausible. It is accepted only if Lean checks it without + placeholders. +
++ After the audit, small implementation changes should not require + starting over. If the core invariants stay the same, we update + the Verity model and re-prove that the new implementation still + satisfies the same specs. +
++ If a change breaks or changes the core invariant, that is not + maintenance. It is new scope, because the security claim itself + changed. +
+