mirror of
https://github.com/saymrwulf/fips205-slhdsa-verified.git
synced 2026-09-04 20:03:44 +00:00
Addresses the round-2 reviewer punch-list. No theorem statement, proof term,
or fold definition changed; the eleven cones are unchanged (independent
collectAxioms dump in verification/RECORDED-RUN.md).
AUDIT GATE (both reviewers, the critical one)
- Retire the bash #print-axioms text parser (fail-open on empty/truncated
reports, and only a SUBSET check). Replace with verification/Proofs/Audit.lean:
reads each certificate's cone from the kernel via collectAxioms and asserts
EXACT set equality against its expected boundary. Extra axiom, dropped
oracle, renamed/deleted cert, or an axiom/opaque sham each throw -> non-zero
Lean exit. No text to misparse; nothing fails open. check.sh Phase 3 now just
compiles it (and still requires the explicit PASSED line).
- check-selftest.sh rewritten to attack the new gate: dead-file, smuggled extra
axiom (named), dropped-oracle (subset would pass, exact must not), and a
vanished certificate (the collectAxioms-returns-[] trap). All four rejected.
REPRODUCIBILITY (GPT B1.4 / B1.5)
- extract.sh refuses a wrong-commit or dirty source tree (fail-closed), takes
an optional source-path arg, and pins the source commit.
- verification/PROVENANCE.json: single machine-readable pin set (source +
charon + aeneas commits/channel + lean + ocaml) with generated-file sha256.
- Re-running extract.sh reproduces gen/SlhVerify/{Types,Funs}.lean
byte-identically (companion fips205-source commit adds Cargo.lock +
rust-toolchain.toml; verified not to perturb the model).
DOC HONESTY (both reviewers)
- README: fix the self-contradiction (apex "not yet proven" trailer vs the
proven apex), the false "oracles kept OUTSIDE every cone" (they are INSIDE,
by design), "deployed monomorphic path" and "semantics-identical for every
parameter set" overclaims, "only two lines changed", stale snapshot head;
retitle the stale future-tense "what will be claimed" section.
- TRUSTED-BASE: drop "nothing proven yet"; add base_2b-inner and deployment-
bridge non-claims explicitly; current pin.
- ChainSpec header: "deployed monomorphic path" -> private verify_mono facade
(comment only).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
127 lines
6.6 KiB
Bash
Executable file
127 lines
6.6 KiB
Bash
Executable file
#!/usr/bin/env bash
|
||
# Adversarial self-test of the check.sh gates (the R3-5 tradition: an audit that
|
||
# cannot fail is theater). The axiom audit now runs INSIDE Lean
|
||
# (Proofs/Audit.lean, exact cone per certificate via collectAxioms), so these
|
||
# attacks target that gate's actual guarantees — not the retired text parser.
|
||
# Every attack MUST make check.sh fail, via the intended gate:
|
||
#
|
||
# 1. DEAD FILE — a stray Proofs/*.lean not in the manifest.
|
||
# 2. SMUGGLED AXIOM — a certificate whose real cone contains a disallowed
|
||
# axiom, declared clean. Exact-equality must report it
|
||
# as `extra=[...]` (the classic extra-axiom detection).
|
||
# 3. DROPPED ORACLE — a certificate whose expected cone claims an oracle
|
||
# its real proof does NOT use. A subset checker would
|
||
# pass this; exact-equality must report `missing=[...]`.
|
||
# This is the property the round-2 review demanded and
|
||
# the retired subset parser could never enforce.
|
||
# 4. VANISHED CERT — a certificate name that no longer resolves. Since
|
||
# `collectAxioms` returns [] for a missing name (a
|
||
# fail-open trap), the audit must report NOT FOUND.
|
||
#
|
||
# Green here means: the gate genuinely rejects all four. Self-cleaning: the real
|
||
# check.sh / Proofs/Audit.lean are backed up and restored around every attack.
|
||
set -euo pipefail
|
||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||
cd "$HERE"
|
||
|
||
restore() {
|
||
[ -f check.sh.selftest.bak ] && mv -f check.sh.selftest.bak check.sh
|
||
[ -f Proofs/Audit.lean.selftest.bak ] && mv -f Proofs/Audit.lean.selftest.bak Proofs/Audit.lean
|
||
return 0
|
||
}
|
||
cleanup() {
|
||
restore
|
||
rm -f Proofs/Stray.lean Proofs/Stray.olean \
|
||
Proofs/EvilSpec.lean Proofs/EvilSpec.olean Proofs/Audit.olean
|
||
}
|
||
trap cleanup EXIT
|
||
backup() { cp -f check.sh check.sh.selftest.bak; cp -f Proofs/Audit.lean Proofs/Audit.lean.selftest.bak; }
|
||
|
||
echo "check-selftest: attacking the gates"
|
||
echo "===================================="
|
||
|
||
# ── Attack 1: dead file ─────────────────────────────────────────────────────
|
||
echo "-- stray" > Proofs/Stray.lean
|
||
if ./check.sh > /tmp/selftest-dead.out 2>&1; then
|
||
echo "✗ ATTACK 1 SUCCEEDED: check.sh stayed green with a dead file"; exit 1
|
||
fi
|
||
grep -q "DEAD FILE" /tmp/selftest-dead.out \
|
||
|| { echo "✗ ATTACK 1: failed, but not via the dead-file gate"; cat /tmp/selftest-dead.out; exit 1; }
|
||
rm -f Proofs/Stray.lean Proofs/Stray.olean
|
||
echo "✓ attack 1 rejected (dead-file gate works)"
|
||
|
||
# ── Attack 2: smuggled disallowed axiom in a real cone ──────────────────────
|
||
# A new certificate whose cone genuinely contains `evil_ax`, declared as clean
|
||
# (kernel-3) in the expected table. Exact-equality must flag extra=[evil_ax].
|
||
backup
|
||
cat > Proofs/EvilSpec.lean <<'EOF'
|
||
import Proofs.ChainSpec
|
||
axiom evil_ax : True
|
||
theorem evil_thm : True := evil_ax
|
||
EOF
|
||
python3 - <<'PY'
|
||
import re
|
||
# check.sh: add EvilSpec to PROOFS so Phase 2 builds it and the dead-file gate
|
||
# passes (inject right after the array's opening paren — no hard-coded contents).
|
||
s = open("check.sh").read()
|
||
assert 'PROOFS=(\n' in s, "check.sh PROOFS array shape changed"
|
||
s = s.replace('PROOFS=(\n', 'PROOFS=(\n "EvilSpec"\n', 1)
|
||
open("check.sh","w").write(s)
|
||
# Audit.lean: import EvilSpec and claim evil_thm is kernel-3 clean.
|
||
a = open("Proofs/Audit.lean").read()
|
||
assert 'import Proofs.ApexSpec' in a, "Audit.lean import shape changed"
|
||
a = a.replace('import Proofs.ApexSpec', 'import Proofs.ApexSpec\nimport Proofs.EvilSpec', 1)
|
||
assert 'def expectedCones : List (Name × List Name) :=' in a and ' [ (' in a, "Audit.lean table shape changed"
|
||
a = a.replace(' [ (', ' [ (`evil_thm, kernel3),\n (', 1)
|
||
open("Proofs/Audit.lean","w").write(a)
|
||
PY
|
||
if ./check.sh > /tmp/selftest-evil.out 2>&1; then
|
||
echo "✗ ATTACK 2 SUCCEEDED: audit passed a smuggled disallowed axiom"; exit 1
|
||
fi
|
||
grep -q "AUDIT FAILED" /tmp/selftest-evil.out \
|
||
|| { echo "✗ ATTACK 2: failed, but not via the axiom audit"; cat /tmp/selftest-evil.out; exit 1; }
|
||
grep -q "evil_ax" /tmp/selftest-evil.out \
|
||
|| { echo "✗ ATTACK 2: rejected, but the audit did not name the smuggled axiom"; cat /tmp/selftest-evil.out; exit 1; }
|
||
restore; rm -f Proofs/EvilSpec.lean Proofs/EvilSpec.olean Proofs/Audit.olean
|
||
echo "✓ attack 2 rejected (extra-axiom detection works — evil_ax named)"
|
||
|
||
# ── Attack 3: dropped-oracle (subset would pass; exact must not) ─────────────
|
||
# Claim to_int_loop_eq depends on oracle.f. Its real cone is kernel-3 only, so
|
||
# the audit must report missing=[verify_mono.oracle.f].
|
||
backup
|
||
python3 - <<'PY'
|
||
import re
|
||
a = open("Proofs/Audit.lean").read()
|
||
new, n = re.subn(r'(`fips205\.to_int_loop_eq,\s*)kernel3\)', r'\1kernel3 ++ [oracleF])', a)
|
||
assert n == 1, f"expected exactly one to_int table entry, patched {n}"
|
||
open("Proofs/Audit.lean","w").write(new)
|
||
PY
|
||
if ./check.sh > /tmp/selftest-drop.out 2>&1; then
|
||
echo "✗ ATTACK 3 SUCCEEDED: audit passed a certificate missing a claimed oracle (subset hole!)"; exit 1
|
||
fi
|
||
grep -q "AUDIT FAILED" /tmp/selftest-drop.out \
|
||
|| { echo "✗ ATTACK 3: failed, but not via the axiom audit"; cat /tmp/selftest-drop.out; exit 1; }
|
||
grep -q "missing=\[verify_mono.oracle.f\]" /tmp/selftest-drop.out \
|
||
|| { echo "✗ ATTACK 3: rejected, but not by naming the missing oracle (exact-cone not enforced?)"; cat /tmp/selftest-drop.out; exit 1; }
|
||
restore; rm -f Proofs/Audit.olean
|
||
echo "✓ attack 3 rejected (missing-oracle detection works — exact cone enforced, not subset)"
|
||
|
||
# ── Attack 4: vanished certificate (collectAxioms-returns-[] trap) ──────────
|
||
backup
|
||
python3 - <<'PY'
|
||
a = open("Proofs/Audit.lean").read()
|
||
assert a.count('`fips205.chain_free_loop_eq') >= 1
|
||
a = a.replace('`fips205.chain_free_loop_eq,', '`fips205.chain_free_loop_eq_VANISHED,', 1)
|
||
open("Proofs/Audit.lean","w").write(a)
|
||
PY
|
||
if ./check.sh > /tmp/selftest-vanish.out 2>&1; then
|
||
echo "✗ ATTACK 4 SUCCEEDED: audit stayed green for a non-existent certificate (fail-open!)"; exit 1
|
||
fi
|
||
grep -q "NOT FOUND" /tmp/selftest-vanish.out \
|
||
|| { echo "✗ ATTACK 4: failed, but not via the existence check"; cat /tmp/selftest-vanish.out; exit 1; }
|
||
restore; rm -f Proofs/Audit.olean
|
||
echo "✓ attack 4 rejected (existence check works — a vanished cert cannot pass as 0-axiom)"
|
||
|
||
echo
|
||
echo "SELFTEST GREEN: the audit genuinely rejects extra axioms, dropped oracles,"
|
||
echo "vanished certificates, and dead proof files."
|