skills/verification/c-cpp-to-lean4-translator/SKILL.md
Translates C/C++ into Lean 4 for interactive theorem proving — deep verification where automated tools fail. Use when Dafny's automation isn't enough, when proving mathematical properties of an algorithm, or when building a machine-checked proof for publication or certification.
npx skillsauth add santosomar/general-secure-coding-agent-skills c-cpp-to-lean4-translatorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Lean is for when you need a proof, not a check. Dafny asks an SMT solver "is this true?"; Lean asks you to construct the proof term. More work, more control, no timeouts.
| Situation | Why Lean |
| ----------------------------------------------------- | ----------------------------------------------------------- |
| SMT solver times out or returns unknown | You write the proof steps; no solver to time out |
| Proving mathematical properties (complexity bounds, algebraic laws) | Lean has Mathlib; Dafny has arithmetic |
| The proof itself is the artifact (paper, certification) | Lean proofs are inspectable; Dafny's are solver-internal |
| You need custom proof tactics | Lean is a tactic language; Dafny's hints are fixed |
Lean is purely functional. The translation path is: imperative C → functional model → proof about the model.
| C construct | Lean model |
| ----------------------------- | ---------------------------------------------------------------- |
| int32_t | Int32 (modular) or Int (unbounded) — same choice as Dafny |
| Loop with accumulator | Fold: xs.foldl f init — or structural recursion |
| Loop with early exit | Recursion with a sum type result: Option α or Except ε α |
| Mutable local var | Let-bind a new name each "mutation": let x₁ := ...; let x₂ := f x₁ |
| Array a[i] | a.get i with (h : i < a.size) proof, or a.get? i : Option α|
| Pointer into array | (a : Array α) × (i : Fin a.size) — a dependent pair |
| struct | structure — direct |
| malloc/pointer to object | Just the object. No heap in the pure fragment. |
If the imperative structure is too tangled to lift functionally, use StateM:
def impStep : StateM (Array Int) Unit := do
let a ← get
if h : 0 < a.size then
set (a.set ⟨0, h⟩ 42)
-- Reason about the result:
theorem impStep_sets_zero (a : Array Int) (h : 0 < a.size) :
(impStep.run a).2.get ⟨0, h⟩ = 42 := by simp [impStep, StateT.run]
But prefer the functional lift — proofs over folds are easier than proofs over monadic runs.
C:
// Count elements equal to target
size_t count(const int* a, size_t n, int target) {
size_t c = 0;
for (size_t i = 0; i < n; ++i)
if (a[i] == target) ++c;
return c;
}
Lean — functional lift:
def count (a : List Int) (target : Int) : Nat :=
a.foldl (fun c x => if x = target then c + 1 else c) 0
-- Spec via List.countP from Mathlib
theorem count_eq_countP (a : List Int) (target : Int) :
count a target = a.countP (· = target) := by
induction a with
| nil => rfl
| cons x xs ih =>
simp only [count, List.foldl_cons, List.countP_cons]
split <;> simp [ih, count]
Notes:
List instead of Array — easier induction. If you need Array for the performance story, prove on List and transport via Array.toList.count to Mathlib's countP — once connected, all of Mathlib's lemmas about countP apply to us for free.size_t → Nat (unbounded). If we cared about SIZE_MAX, we'd add a separate bound theorem.| Goal shape | Tactic |
| ----------------------------------- | --------------------------------------------------------- |
| Property of a fold | induction xs + unfold the fold one step |
| Property of recursion on Nat | induction n or Nat.rec |
| Array index in bounds | omega / simp_arith — or carry Fin n so it's by-type |
| Decidable equality case split | split after if / by_cases h : P |
| "Obvious" arithmetic | omega, ring, simp_arith |
| Reuse a library lemma | exact? / apply? — let Lean search Mathlib |
partial def. partial means "trust me" — you lose proofs-by-induction. Find the structural argument.List.countP, List.Sorted, Nat.gcd — they exist, with lemma libraries. Connect to them.## Functional model
<Lean def — the translated algorithm>
## Theorem
<what property this proves — connected to Mathlib where possible>
## Proof
<tactic script — or `sorry` placeholders with notes on what's left>
## Model fidelity
<where the Lean model diverges from the C: unbounded ints, List vs Array, etc.>
development
Extracts human-readable pseudocode from a verified formal artifact (Dafny, Lean, TLA+) while preserving the verified properties as annotations, so the proof-carrying logic can be reimplemented in a production language. Use when porting verified code to an unverified target, when documenting what a formal spec actually does, or when handing a verified algorithm to an implementer.
development
Translates natural-language or pseudocode descriptions of concurrent and distributed systems into TLA+ specifications ready for the TLC model checker. Identifies state variables, actions, type invariants, safety properties, and liveness properties from the description. Use when formalizing a protocol, when the user describes a distributed algorithm to verify, when designing a consensus or locking scheme, or when starting formal verification of a concurrent system.
testing
Reduces a TLA+ model so TLC can actually check it — shrinks constants, adds state constraints, abstracts data, or applies symmetry — when the state space is too large to enumerate. Use when TLC runs out of memory, when checking takes hours, or when a spec works at N=2 and you need confidence at larger scale.
development
TLA+-specific instance of model-guided repair — reads a TLC error trace, identifies the enabling condition that should have been false, strengthens the corresponding action, and maps the fix to source code. Use when TLC reports an invariant violation or deadlock and you have the code-to-TLA+ mapping from extraction.