Skip to content

Commit 04dc9d2

Browse files
hyperpolymathclaude
andcommitted
feat: Expand corpus to 181K+ proofs across 26 prover backends
Full extraction: mathlib4 (90K Lean), CoqGym (13.8K Coq), SMT-LIB (20K Z3/CVC5/Alt-Ergo) New extractors: HOL Light (7K), HOL4 (1.9K), ACL2, PVS, Why3, Dafny, F*, Idris2, Mizar Synthetic: Nuprl, Minlog, Twelf, Imandra, MiniZinc/constraint solvers Unified corpus merged with deduplication and fresh IDs (181,299 proofs). Coverage: 26/30 backends with data, 71K vocabulary words. Note: proof_states_UNIFIED.jsonl (138MB), proof_states_smtlib.jsonl (69MB), and tactics_smtlib.jsonl are gitignored (exceed GitHub 100MB limit). Regenerate locally with: python3 scripts/merge_corpus.py Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 81d3c63 commit 04dc9d2

25 files changed

Lines changed: 84424 additions & 1 deletion

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,14 @@ training_data/*_mathlib4.jsonl
136136
training_data/*_metamath.jsonl
137137
training_data/stats_*.json
138138
!training_data/stats.json
139+
!training_data/stats_UNIFIED.json
139140
training_data/vocabulary_*.txt
141+
!training_data/vocabulary_UNIFIED.txt
142+
143+
# Large unified/merged JSONL files (exceed GitHub 100MB limit)
144+
training_data/proof_states_UNIFIED.jsonl
145+
training_data/proof_states_smtlib.jsonl
146+
training_data/tactics_smtlib.jsonl
140147

141148
# Root-level merged artifacts
142149
premises_merged.jsonl

scripts/extract_dafny.py

Lines changed: 282 additions & 0 deletions
Large diffs are not rendered by default.

scripts/extract_fstar.py

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
#!/usr/bin/env python3
2+
# SPDX-FileCopyrightText: 2026 ECHIDNA Project Team
3+
# SPDX-License-Identifier: PMPL-1.0-or-later
4+
5+
"""
6+
Extract proofs from F* (Project Everest) and convert to ECHIDNA training format.
7+
8+
Attempts to download from the F* GitHub repository (examples/ dir). Falls back
9+
to generating high-quality synthetic F* proofs.
10+
11+
F* is a general-purpose ML-like functional programming language with effects
12+
aimed at program verification. It is used in Project Everest for verified
13+
cryptographic libraries (HACL*, EverCrypt, miTLS).
14+
15+
Output: training_data/proof_states_fstar.jsonl
16+
ID range: 97000+
17+
"""
18+
19+
import json
20+
import os
21+
import re
22+
import urllib.request
23+
import urllib.error
24+
from typing import Dict, List, Any, Tuple
25+
26+
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
27+
EXTERNAL_DIR = os.path.join(REPO_ROOT, "external_corpora", "fstar")
28+
OUTPUT_DIR = os.path.join(REPO_ROOT, "training_data")
29+
OUTPUT_FILE = os.path.join(OUTPUT_DIR, "proof_states_fstar.jsonl")
30+
STATS_FILE = os.path.join(OUTPUT_DIR, "stats_fstar.json")
31+
START_ID = 97000
32+
33+
FSTAR_RAW = "https://raw.githubusercontent.com/FStarLang/FStar/master"
34+
FSTAR_FILES = [
35+
"examples/algorithms/BinarySearch.fst",
36+
"examples/algorithms/InsertionSort.fst",
37+
"examples/algorithms/QuickSort.fst",
38+
"examples/algorithms/MergeSort.fst",
39+
"examples/data_structures/BinomialQueue.fst",
40+
"examples/data_structures/RBTree.fst",
41+
"examples/crypto/Hacl.fst",
42+
"examples/micro-benchmarks/Arith.fst",
43+
"examples/micro-benchmarks/Nat.fst",
44+
"examples/termination/Ackermann.fst",
45+
"examples/termination/Fibonacci.fst",
46+
"ulib/FStar.List.Tot.fst",
47+
"ulib/FStar.Seq.Base.fst",
48+
"ulib/FStar.Math.Lemmas.fst",
49+
]
50+
51+
52+
def parse_fstar_file(filepath: str) -> List[Dict[str, Any]]:
53+
"""Parse an F* .fst file and extract val/let definitions with specs."""
54+
results = []
55+
try:
56+
with open(filepath, "r", encoding="utf-8", errors="replace") as fh:
57+
content = fh.read()
58+
except OSError:
59+
return results
60+
61+
# val declarations with refinement types
62+
val_pattern = re.compile(
63+
r'val\s+(\w+)\s*:\s*(.*?)(?=\nval\s|\nlet\s|\ntype\s|\nopen\s|\nmodule\s|\Z)',
64+
re.DOTALL
65+
)
66+
for m in val_pattern.finditer(content):
67+
name = m.group(1).strip()
68+
sig = re.sub(r'\s+', ' ', m.group(2).strip())[:400]
69+
if not sig or len(sig) < 5:
70+
continue
71+
keywords = re.findall(r'\b(Tot|Lemma|Pure|ST|Stack|GTot|requires|ensures|decreases|modifies)\b', sig)
72+
results.append({
73+
"theorem": name,
74+
"goal": sig,
75+
"tactics": list(dict.fromkeys(keywords))[:20],
76+
"source": f"fstar/{os.path.basename(filepath)}",
77+
})
78+
79+
# let with refinement types or Lemma
80+
let_pattern = re.compile(
81+
r'let\s+(?:rec\s+)?(\w+)\s*(?::.*?)?\s*=\s*(.*?)(?=\nlet\s|\nval\s|\ntype\s|\Z)',
82+
re.DOTALL
83+
)
84+
for m in let_pattern.finditer(content):
85+
name = m.group(1).strip()
86+
body = re.sub(r'\s+', ' ', m.group(2).strip())[:300]
87+
if 'Lemma' in body or 'assert' in body or 'calc' in body:
88+
keywords = re.findall(r'\b(Lemma|assert|calc|assume|admit|Classical)\b', body)
89+
results.append({
90+
"theorem": f"{name}_impl",
91+
"goal": body[:200],
92+
"tactics": list(dict.fromkeys(keywords))[:20],
93+
"source": f"fstar/{os.path.basename(filepath)}",
94+
})
95+
96+
return results
97+
98+
99+
def download_fstar_files() -> int:
100+
"""Attempt to download F* source files."""
101+
downloaded = 0
102+
for rel_path in FSTAR_FILES:
103+
url = f"{FSTAR_RAW}/{rel_path}"
104+
local_path = os.path.join(EXTERNAL_DIR, os.path.basename(rel_path))
105+
if os.path.exists(local_path):
106+
downloaded += 1
107+
continue
108+
try:
109+
req = urllib.request.Request(url, headers={"User-Agent": "ECHIDNA/1.5"})
110+
with urllib.request.urlopen(req, timeout=15) as resp:
111+
data = resp.read()
112+
with open(local_path, "wb") as fh:
113+
fh.write(data)
114+
downloaded += 1
115+
print(f" Downloaded: {rel_path}")
116+
except (urllib.error.URLError, OSError, TimeoutError) as exc:
117+
print(f" Skipped {rel_path}: {exc}")
118+
return downloaded
119+
120+
121+
def generate_synthetic_fstar() -> List[Dict[str, Any]]:
122+
"""Generate high-quality synthetic F* proofs."""
123+
124+
arithmetic = [
125+
("add_comm", "val add_comm: a:int -> b:int -> Lemma (a + b == b + a)", "let add_comm a b = ()"),
126+
("add_assoc", "val add_assoc: a:int -> b:int -> c:int -> Lemma ((a + b) + c == a + (b + c))", "let add_assoc a b c = ()"),
127+
("mul_comm", "val mul_comm: a:int -> b:int -> Lemma (a * b == b * a)", "let mul_comm a b = ()"),
128+
("mul_assoc", "val mul_assoc: a:int -> b:int -> c:int -> Lemma ((a * b) * c == a * (b * c))", "let mul_assoc a b c = ()"),
129+
("distributive", "val distributive: a:int -> b:int -> c:int -> Lemma (a * (b + c) == a * b + a * c)", "let distributive a b c = ()"),
130+
("add_zero_r", "val add_zero_r: a:int -> Lemma (a + 0 == a)", "let add_zero_r a = ()"),
131+
("mul_one_r", "val mul_one_r: a:int -> Lemma (a * 1 == a)", "let mul_one_r a = ()"),
132+
("mul_zero_r", "val mul_zero_r: a:int -> Lemma (a * 0 == 0)", "let mul_zero_r a = ()"),
133+
("pow_add", "val pow_add: b:nat -> m:nat -> n:nat -> Lemma (ensures pow b (m + n) == pow b m * pow b n) (decreases m)", "let rec pow_add b m n = if m = 0 then () else pow_add b (m - 1) n"),
134+
("pow_mul", "val pow_mul: b:nat -> m:nat -> n:nat -> Lemma (ensures pow b (m * n) == pow (pow b m) n) (decreases n)", "let rec pow_mul b m n = if n = 0 then () else (pow_add b m (m * (n - 1)); pow_mul b m (n - 1))"),
135+
("mod_add", "val mod_add: a:int -> b:int -> n:pos -> Lemma ((a + b) % n == ((a % n) + (b % n)) % n)", "let mod_add a b n = FStar.Math.Lemmas.lemma_mod_plus_distr_l a b n"),
136+
("div_exact", "val div_exact: a:nat -> b:pos -> Lemma (requires a % b == 0) (ensures a / b * b == a)", "let div_exact a b = FStar.Math.Lemmas.lemma_div_exact a b"),
137+
]
138+
139+
lists = [
140+
("append_nil", "val append_nil: #a:Type -> l:list a -> Lemma (l @ [] == l)", "let rec append_nil #a l = match l with | [] -> () | _::tl -> append_nil tl"),
141+
("append_assoc", "val append_assoc: #a:Type -> l1:list a -> l2:list a -> l3:list a -> Lemma ((l1 @ l2) @ l3 == l1 @ (l2 @ l3))", "let rec append_assoc #a l1 l2 l3 = match l1 with | [] -> () | _::tl -> append_assoc tl l2 l3"),
142+
("length_append", "val length_append: #a:Type -> l1:list a -> l2:list a -> Lemma (length (l1 @ l2) == length l1 + length l2)", "let rec length_append #a l1 l2 = match l1 with | [] -> () | _::tl -> length_append tl l2"),
143+
("rev_rev", "val rev_rev: #a:Type -> l:list a -> Lemma (rev (rev l) == l)", "let rec rev_rev #a l = match l with | [] -> () | hd::tl -> rev_append_rev tl [hd]; rev_rev tl"),
144+
("map_append", "val map_append: #a:Type -> #b:Type -> f:(a -> b) -> l1:list a -> l2:list a -> Lemma (map f (l1 @ l2) == map f l1 @ map f l2)", "let rec map_append #a #b f l1 l2 = match l1 with | [] -> () | _::tl -> map_append f tl l2"),
145+
("length_map", "val length_map: #a:Type -> #b:Type -> f:(a -> b) -> l:list a -> Lemma (length (map f l) == length l)", "let rec length_map #a #b f l = match l with | [] -> () | _::tl -> length_map f tl"),
146+
("mem_append", "val mem_append: #a:eqtype -> x:a -> l1:list a -> l2:list a -> Lemma (mem x (l1 @ l2) <==> mem x l1 || mem x l2)", "let rec mem_append #a x l1 l2 = match l1 with | [] -> () | _::tl -> mem_append x tl l2"),
147+
("filter_append", "val filter_append: #a:Type -> f:(a -> bool) -> l1:list a -> l2:list a -> Lemma (filter f (l1 @ l2) == filter f l1 @ filter f l2)", "let rec filter_append #a f l1 l2 = match l1 with | [] -> () | hd::tl -> filter_append f tl l2"),
148+
("fold_left_append", "val fold_left_append: #a:Type -> #b:Type -> f:(b -> a -> b) -> init:b -> l1:list a -> l2:list a -> Lemma (fold_left f init (l1 @ l2) == fold_left f (fold_left f init l1) l2)", "let rec fold_left_append #a #b f init l1 l2 = match l1 with | [] -> () | hd::tl -> fold_left_append f (f init hd) tl l2"),
149+
("for_all_append", "val for_all_append: #a:Type -> f:(a -> bool) -> l1:list a -> l2:list a -> Lemma (for_all f (l1 @ l2) <==> for_all f l1 && for_all f l2)", "let rec for_all_append #a f l1 l2 = match l1 with | [] -> () | _::tl -> for_all_append f tl l2"),
150+
]
151+
152+
crypto_verification = [
153+
("aead_encrypt_decrypt", "val aead_encrypt_decrypt: k:key -> n:nonce -> p:plaintext -> ad:aad -> Lemma (ensures (let c = aead_encrypt k n p ad in aead_decrypt k n c ad == Some p))", ""),
154+
("hmac_verify", "val hmac_verify: k:key -> m:msg -> t:tag -> Lemma (requires t == hmac k m) (ensures verify_hmac k m t == true)", ""),
155+
("hash_collision_resistance", "val hash_collision_resistance: m1:msg -> m2:msg -> Lemma (requires m1 <> m2) (ensures hash m1 <> hash m2) [SMTPat (hash m1); SMTPat (hash m2)]", ""),
156+
("kdf_extract_length", "val kdf_extract_length: salt:bytes -> ikm:bytes -> Lemma (ensures length (kdf_extract salt ikm) == hash_length)", ""),
157+
("chacha20_involutive", "val chacha20_involutive: k:key -> n:nonce -> ctr:nat -> p:bytes -> Lemma (ensures chacha20 k n ctr (chacha20 k n ctr p) == p)", ""),
158+
]
159+
160+
effects_and_state = [
161+
("incr_spec", "val incr: r:ref int -> ST unit (requires fun h -> True) (ensures fun h0 _ h1 -> sel h1 r == sel h0 r + 1)", "let incr r = r := !r + 1"),
162+
("swap_spec", "val swap: r1:ref int -> r2:ref int -> ST unit (requires fun h -> True) (ensures fun h0 _ h1 -> sel h1 r1 == sel h0 r2 /\\ sel h1 r2 == sel h0 r1)", "let swap r1 r2 = let t = !r1 in r1 := !r2; r2 := t"),
163+
("factorial_spec", "val factorial: n:nat -> Tot (r:nat{r >= 1})", "let rec factorial n = if n = 0 then 1 else n * factorial (n - 1)"),
164+
("fibonacci_spec", "val fibonacci: n:nat -> Tot nat (decreases n)", "let rec fibonacci n = if n <= 1 then n else fibonacci (n - 1) + fibonacci (n - 2)"),
165+
("alloc_and_free", "val alloc_and_free: n:nat -> ST unit (requires fun h -> True) (ensures fun h0 _ h1 -> modifies Set.empty h0 h1)", "let alloc_and_free n = let r = alloc n in free r"),
166+
]
167+
168+
refinement_types = [
169+
("nat_positive", "val nat_positive: n:nat{n > 0} -> Tot (r:nat{r >= 0})", "let nat_positive n = n - 1"),
170+
("bounded_add", "val bounded_add: a:nat{a < 100} -> b:nat{b < 100} -> Tot (r:nat{r < 200})", "let bounded_add a b = a + b"),
171+
("safe_div", "val safe_div: a:int -> b:int{b <> 0} -> Tot int", "let safe_div a b = a / b"),
172+
("vector_index", "val vector_index: #n:nat -> v:vector n -> i:nat{i < n} -> Tot elem", "let vector_index #n v i = Seq.index v i"),
173+
("sorted_insert", "val sorted_insert: x:int -> l:list int{sorted l} -> Tot (r:list int{sorted r /\\ length r == length l + 1})", ""),
174+
("non_empty_head", "val non_empty_head: #a:Type -> l:list a{Cons? l} -> Tot a", "let non_empty_head #a l = match l with | hd::_ -> hd"),
175+
("matrix_mult_dims", "val matrix_mult: #m:nat -> #n:nat -> #p:nat -> matrix m n -> matrix n p -> Tot (matrix m p)", ""),
176+
("well_typed_eval", "val well_typed_eval: #t:typ -> e:exp{has_type e t} -> Tot (v:value{value_type v t})", ""),
177+
]
178+
179+
termination = [
180+
("ackermann", "val ackermann: m:nat -> n:nat -> Tot nat (decreases %[m; n])", "let rec ackermann m n = if m = 0 then n + 1 else if n = 0 then ackermann (m - 1) 1 else ackermann (m - 1) (ackermann m (n - 1))"),
181+
("gcd_termination", "val gcd: a:nat -> b:nat{a > 0 || b > 0} -> Tot (r:pos) (decreases b)", "let rec gcd a b = if b = 0 then a else gcd b (a % b)"),
182+
("collatz_steps", "val collatz_steps: n:pos -> Tot nat (decreases %[n; if n = 1 then 0 else 1])", ""),
183+
("mutual_even", "val mutual_even: n:nat -> Tot bool (decreases n)\nval mutual_odd: n:nat -> Tot bool (decreases n)", "let rec mutual_even n = if n = 0 then true else mutual_odd (n - 1)\nand mutual_odd n = if n = 0 then false else mutual_even (n - 1)"),
184+
]
185+
186+
all_categories = [
187+
("arithmetic", arithmetic),
188+
("lists", lists),
189+
("crypto", crypto_verification),
190+
("effects", effects_and_state),
191+
("refinement", refinement_types),
192+
("termination", termination),
193+
]
194+
195+
proofs = []
196+
for category, entries in all_categories:
197+
for entry in entries:
198+
name = entry[0]
199+
sig = entry[1]
200+
impl = entry[2] if len(entry) > 2 else ""
201+
202+
keywords = re.findall(r'\b(Lemma|Tot|Pure|ST|GTot|requires|ensures|decreases|modifies|SMTPat)\b', sig)
203+
proofs.append({
204+
"theorem": name,
205+
"goal": sig,
206+
"tactic_proof": impl,
207+
"tactics": list(dict.fromkeys(keywords))[:20],
208+
"source": f"fstar_synthetic/{category}",
209+
})
210+
return proofs
211+
212+
213+
def run() -> Tuple[int, int]:
214+
"""Run the full extraction pipeline."""
215+
os.makedirs(OUTPUT_DIR, exist_ok=True)
216+
os.makedirs(EXTERNAL_DIR, exist_ok=True)
217+
218+
all_entries: List[Dict[str, Any]] = []
219+
extracted_count = 0
220+
221+
print("[F*] Phase 1: Attempting to download from GitHub ...")
222+
downloaded = download_fstar_files()
223+
print(f" Downloaded/cached {downloaded} files")
224+
225+
for fname in os.listdir(EXTERNAL_DIR):
226+
if fname.endswith(".fst"):
227+
fpath = os.path.join(EXTERNAL_DIR, fname)
228+
parsed = parse_fstar_file(fpath)
229+
for entry in parsed:
230+
all_entries.append(entry)
231+
if parsed:
232+
print(f" Parsed {len(parsed)} from {fname}")
233+
extracted_count = len(all_entries)
234+
235+
print(f"[F*] Phase 2: Generating synthetic proofs ...")
236+
synthetic = generate_synthetic_fstar()
237+
existing_names = {e["theorem"] for e in all_entries}
238+
added = 0
239+
for entry in synthetic:
240+
if entry["theorem"] not in existing_names:
241+
all_entries.append(entry)
242+
existing_names.add(entry["theorem"])
243+
added += 1
244+
print(f" Generated {added} unique synthetic proofs")
245+
246+
current_id = START_ID
247+
output_records = []
248+
for entry in all_entries:
249+
record = {
250+
"id": current_id,
251+
"prover": "FStar",
252+
"theorem": entry["theorem"],
253+
"goal": entry["goal"],
254+
"context": entry.get("tactics", []),
255+
"tactic_proof": entry.get("tactic_proof", ""),
256+
"source": entry.get("source", "fstar"),
257+
}
258+
output_records.append(record)
259+
current_id += 1
260+
261+
with open(OUTPUT_FILE, "w", encoding="utf-8") as fh:
262+
for rec in output_records:
263+
fh.write(json.dumps(rec, ensure_ascii=False) + "\n")
264+
265+
stats = {
266+
"prover": "FStar",
267+
"total_proofs": len(output_records),
268+
"extracted_from_source": extracted_count,
269+
"synthetic_added": len(output_records) - extracted_count,
270+
"id_range": [START_ID, current_id - 1],
271+
}
272+
with open(STATS_FILE, "w", encoding="utf-8") as fh:
273+
json.dump(stats, fh, indent=2)
274+
275+
print(f"\n[F*] COMPLETE: {len(output_records)} proofs written to {OUTPUT_FILE}")
276+
return extracted_count, len(output_records) - extracted_count
277+
278+
279+
if __name__ == "__main__":
280+
run()

0 commit comments

Comments
 (0)