ProofTermAudit — machine-checked shell / vacuity detection over proof terms #
This module ports the deterministic (zero-LLM-token) core of
facebookresearch/autoform-bot's autoform/eval/ subsystem to CatCrypt.
autoform-bot grades autoformalizations by introspecting the compiled
proof term rather than the surface syntax; we reuse the same idea to
machine-check the project's shell-vs-genuine-reduction distinction.
CatCrypt's existing audit (scripts/audit_security.lean) detects "shell"
theorems via a docstring-tag convention ((composition plumbing),
(concurrent UC via ITreeN), …) and via text scanning of theorem
signatures. Those are convention-dependent: a theorem mislabelled in its
docstring, or whose body silently became trivial, slips through. This
module inspects the elaborated Expr of a declaration's proof term and
reports structural tags that no comment can fake.
Tags #
Each tag is computed purely from the proof term e := value? of a
declaration, after stripping leading fun-binders (stripLambdas). Let
nl be the number of stripped binders (the declaration's parameters and
hypotheses) and body the head-beta-reduced lambda body.
| Tag | Detection (implemented) |
|---|---|
vacuous_body | body's head constant is True.intro / trivial / And.intro of True.intros, or body is a rfl-proof (Eq.refl / rfl) at a propositionally trivial head — i.e. the proof carries no hypothesis-dependent content. |
returns_assumption | body is exactly a bvar referring to one of the nl binders — the proof fun … hᵢ … => hᵢ directly returns a hypothesis. |
trivial_constructor | body's head is a structure/inductive constructor (ConstructorVal) and the constructor has no explicit (non-instance, non-Prop-erased) data arguments derived from the binders — a content-free constructor application (e.g. ⟨⟩ : True, PUnit.unit, Unit.unit). |
field_projection_body | body is a .proj, or its head is a projection function (Environment.getProjectionFnInfo?) — the proof is "read a field", not a derivation. |
proof_by_subsingleton | body's head constant is Subsingleton.elim / Subsingleton.allEq / proof_irrel. |
proof_by_exfalso | body's head constant is False.elim / absurd / False.rec / Empty.elim — goal derived from a contradiction on an assumption. |
ignores_params | At least one of the nl stripped binders does not occur in body. (Reported with the used/total count.) This is the structural analogue of autoform-bot's "the proof does not use its premises" check. Note: a binder may legitimately appear only in the type of a later binder; this tag is advisory, surfaced together with the stronger returns_assumption / vacuous_body signals rather than failing on its own. |
Stubbed / partial #
| Tag | Status |
|---|---|
custom_hypothesis_in_type | Partial. Full autoform-bot semantics ("the statement carries a suspicious custom hypothesis that trivializes the goal") requires modelling which hypotheses are load-bearing for the conclusion — undecidable in general. We implement a conservative proxy: flag declarations whose type contains a hypothesis of the form (h : P) → P (an identity implication a returns_assumption body would discharge), or whose conclusion is defeq to True. Anything subtler is deferred. |
Shell classification #
shellTags is the subset that indicates a content-free proof:
vacuous_body, returns_assumption, trivial_constructor. The audit
driver treats a declaration as a potential undocumented shell when it
carries a shell tag but its docstring lacks an honest
(composition plumbing) / sdist_self marker, and as a
docstring/body mismatch when the docstring claims a genuine result yet
the body is vacuous_body / returns_assumption.
A genuine reduction (e.g. one that applys a composition lemma and
then discharges its premise from the hypotheses, like
UCEmulates_of_sdist followed by exact hkdf_sdist …) elaborates to a
term whose head is the composition lemma applied to the binders, so it is
not tagged — exactly the discrimination the
feedback_no_genuine_reduction memory and the UC docstring convention
are guarding.
Usage #
- Interactive:
#proof_term_tags <decl-name>logs the tags for one decl. - Programmatic:
ProofTermAudit.tagsFor (n : Name) : MetaM (Array Tag). - Batch driver: see
CatCrypt/Tactic/ProofTermAuditDriver.lean(thelake exe proofauditexecutable), which scans everyUCEmulates/*_uc_secure/ reduction theorem in the environment and prints a report consumed byscripts/audit_security.lean.
Future work (NOT implemented this increment) #
- Increment 2 — dependency-graph cone alerts. Walk the transitive
getUsedConstantscone of each security theorem and alert when a "genuine"-tagged theorem's cone bottoms out in anaxiomorsorryAxthat is not on the sanctioned list. (autoform-bot's reachability check.) - Increment 3 — LLM faithfulness jury. Grade whether the Lean statement faithfully renders the informal/RFC claim. This is the LLM-token path and is explicitly out of scope here.
A structural tag computed from a declaration's proof term.
- vacuousBody : Tag
- returnsAssumption : Tag
- trivialConstructor : Tag
- fieldProjectionBody : Tag
- proofBySubsingleton : Tag
- proofByExfalso : Tag
- ignoresParams
(used total : Nat)
: Tag
carries
(used, total)binder counts - customHypothesisInType : Tag
partial detector (see module doc)
Instances For
Equations
Equations
- One or more equations did not get rendered due to their size.
Instances For
Equations
- CatCrypt.Tactic.ProofTermAudit.instBEqTag.beq CatCrypt.Tactic.ProofTermAudit.Tag.vacuousBody CatCrypt.Tactic.ProofTermAudit.Tag.vacuousBody = true
- CatCrypt.Tactic.ProofTermAudit.instBEqTag.beq CatCrypt.Tactic.ProofTermAudit.Tag.returnsAssumption CatCrypt.Tactic.ProofTermAudit.Tag.returnsAssumption = true
- CatCrypt.Tactic.ProofTermAudit.instBEqTag.beq CatCrypt.Tactic.ProofTermAudit.Tag.trivialConstructor CatCrypt.Tactic.ProofTermAudit.Tag.trivialConstructor = true
- CatCrypt.Tactic.ProofTermAudit.instBEqTag.beq CatCrypt.Tactic.ProofTermAudit.Tag.fieldProjectionBody CatCrypt.Tactic.ProofTermAudit.Tag.fieldProjectionBody = true
- CatCrypt.Tactic.ProofTermAudit.instBEqTag.beq CatCrypt.Tactic.ProofTermAudit.Tag.proofBySubsingleton CatCrypt.Tactic.ProofTermAudit.Tag.proofBySubsingleton = true
- CatCrypt.Tactic.ProofTermAudit.instBEqTag.beq CatCrypt.Tactic.ProofTermAudit.Tag.proofByExfalso CatCrypt.Tactic.ProofTermAudit.Tag.proofByExfalso = true
- CatCrypt.Tactic.ProofTermAudit.instBEqTag.beq (CatCrypt.Tactic.ProofTermAudit.Tag.ignoresParams a a_1) (CatCrypt.Tactic.ProofTermAudit.Tag.ignoresParams b b_1) = (a == b && a_1 == b_1)
- CatCrypt.Tactic.ProofTermAudit.instBEqTag.beq CatCrypt.Tactic.ProofTermAudit.Tag.customHypothesisInType CatCrypt.Tactic.ProofTermAudit.Tag.customHypothesisInType = true
- CatCrypt.Tactic.ProofTermAudit.instBEqTag.beq x✝¹ x✝ = false
Instances For
Human-readable, stable string form (matches the module-doc table).
Equations
- CatCrypt.Tactic.ProofTermAudit.Tag.vacuousBody.toString = "vacuous_body"
- CatCrypt.Tactic.ProofTermAudit.Tag.returnsAssumption.toString = "returns_assumption"
- CatCrypt.Tactic.ProofTermAudit.Tag.trivialConstructor.toString = "trivial_constructor"
- CatCrypt.Tactic.ProofTermAudit.Tag.fieldProjectionBody.toString = "field_projection_body"
- CatCrypt.Tactic.ProofTermAudit.Tag.proofBySubsingleton.toString = "proof_by_subsingleton"
- CatCrypt.Tactic.ProofTermAudit.Tag.proofByExfalso.toString = "proof_by_exfalso"
- (CatCrypt.Tactic.ProofTermAudit.Tag.ignoresParams a a_1).toString = toString "ignores_params(" ++ toString a ++ toString "/" ++ toString a_1 ++ toString ")"
- CatCrypt.Tactic.ProofTermAudit.Tag.customHypothesisInType.toString = "custom_hypothesis_in_type"
Instances For
Equations
Tags indicating a content-free proof body (the "shell" set).
Equations
Instances For
Tags that mark the proof body as content-free.
Equations
Instances For
Term-inspection helpers #
Collect the de-Bruijn indices (relative to the body, i.e. counting
the stripped lambdas as the outermost binders) of every free bvar
occurrence. extra tracks binders introduced inside body.
Count how many of the nl outermost binders occur in body.
Equations
- CatCrypt.Tactic.ProofTermAudit.countUsedBinders nl body = (List.filter (fun (k : Nat) => (CatCrypt.Tactic.ProofTermAudit.usedBinders body 0 ∅).contains k) (List.range nl)).length
Instances For
Does body's head resolve to a structure/inductive constructor with
no explicit data argument that comes from a binder? Such an
application (e.g. PUnit.unit, ⟨⟩ : True) is a trivial constructor.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Is body (or its head) a structure-field projection?
Equations
- One or more equations did not get rendered due to their size.
Instances For
Type-side (partial) detector #
Walk a forall-telescope syntactically (no whnf/isDefEq), returning
the list of binder domains and the final conclusion. Cheap and total —
deliberately avoids the reduction cost that makes a MetaM telescope
blow heartbeats on heavy crypto types.
Conservative, syntactic custom_hypothesis_in_type proxy: scan the
type's pi-telescope for a hypothesis binder whose domain is structurally
identical to the conclusion (an (h : P) → … → P identity implication a
returns_assumption body would discharge), or a conclusion that is the
constant True. Purely syntactic so it never reduces (and never blows
heartbeats); subtler defeq cases are deferred (see module doc).
Equations
- One or more equations did not get rendered due to their size.
Instances For
The tagger #
Compute the structural tags for declaration n. Returns #[] for
declarations with no inspectable value (axioms, opaques) — they are
not the subject of this audit.
Equations
- One or more equations did not get rendered due to their size.
Pretty one-line summary of a decl's tags ("" if clean).
Equations
Instances For
Interactive command #
#proof_term_tags foo logs the structural shell/vacuity tags of foo.
Equations
- One or more equations did not get rendered due to their size.