Transfer is a Lean 4 + Mathlib library for reasoning up to a registered relation. A user declares once how an abstract value relates to a concrete representation of it. The library then synthesizes witnesses of that relation, transfers statements and terms across it, and decides it structurally by congruence. Every step produces a kernel-checked proof term.
The design follows Trocq's modular parametricity: its lattice of relation
strengths and its combinators. It also draws on relational congruence closure, on
functorial type casts and description-based deriving from AdapTT, on
heterogeneous congruence (the set-level form of cubical hcongr), and on data
refinement from CoqEAL. It builds on Lean's relational automation: grind,
gcongr, simp/norm_cast, type classes, conv, aesop and mvcgen. The
library adds the witness synthesis, the transfer tactics, and one
coordinating tactic, param_auto, that dispatches to all of these surfaces.
The library is used in compiler verification, to relate emitted low-level code to
its algebraic specification, and in program verification, to transfer mvcgen /
Std.Do triples and to reason about code extracted from Rust by hax up to its
representation.
Used by: the CatCrypt verified compiler, where transfer is one of three correctness techniques beside the compiler's descent and ascent (CORRECTNESS.md).
Single entry point: import Transfer. The example suites are the separate library
TransferExamples (import Transfer.Examples). Released under LGPL-3.0 (see
LICENSE).
- Manual — concepts, tactics, and worked examples
- API reference — per-declaration signatures and docstrings, no proof bodies
CI publishes both from main to GitHub Pages. To build them locally, see the
manual and API-reference subsections under Building.
The Lean code blocks in this README are verbatim excerpts of
Transfer/Examples/Readme.lean, which compiles
them against the library.
See BUILDING.md for the full instructions: the library, use as a dependency, the manual, and the API reference. The short form:
lake exe cache get # fetch prebuilt Mathlib oleans — run this FIRST
LEAN_NUM_THREADS=6 lake build TransferThe library operates on a single object: a relation R between a value and a
representation of it. The relation takes one of two forms: Related enc a b
(enc a = b, the encoding view) or, graded, Param (m n) A B (relation + maps).
It is registered once through attributes (@[param], @[transfer],
RelatedBinOp instances, deriving Param). Three operations then act on it.
1 — Build R a b (synthesis). Type-class resolution finds or composes a
witness. HasParam/Related instance search composes registered witnesses
through the structure of a term, as Trocq's Elpi search does in Coq.
example (a b c : F) : Related (id : F → F) (a * b + c) (bbFieldAdd (bbFieldMul a b) c) :=
inferInstance -- composed from the registered RelatedBinOp squares2 — Transfer a goal along R (parametricity). param_transfer moves a
∀-statement to the other representation, and #transfer emits the ⟦·⟧
translation of a term.
-- ℤ ↠ ZMod p: an integer fact transfers to its modular image.
example (p : ℕ) [NeZero p] :
(∀ i : ℤ, (Int.cast i : ZMod p) + 0 = (Int.cast i : ZMod p)) →
(∀ x : ZMod p, x + 0 = x) := by
param_transfer
intro i x (hix : (Int.cast i : ZMod p) = x) h
rw [← hix]; exact h
#transfer (fun x : Nat => Nat.succ x * x) -- ⟦·⟧ : the term + its relatedness proof3 — Decide R a b structurally (congruence). Relational congruence closure
relates two structurally similar terms.
example (a b c : F) : a * b + c = bbFieldAdd (bbFieldMul a b) c := by param_solveIn these examples F is ZMod p for the prime p = 2^31 - 2^27 + 1, and
bbFieldMul/bbFieldAdd are two operations (Transfer/Examples/ExampleField.lean)
registered as realizations of * and + (Transfer/Base/Related.lean).
All three uses run on one inference rule:
related arguments give related applications — from
R aᵢ bᵢinferR (f a…) (g b…)(R_arrow/R_forall).
The same rule appears in four places:
- as the application/λ rule of the parametricity translation (use 2),
- as the congruence step of the closure tactics (use 3),
- as AdapTT's functoriality of type formers, which lets
Rcompose through→,×, and inductive types, - and as heterogeneous congruence: the general
R_forallrelates outputs of different fiber types across a representation change (hcongr_hetero,HCongrConnection).
Each use bottoms out in existing Lean machinery. Type classes discharge synthesis,
grind discharges the equational congruence-closure core, and
simp/norm_cast/gcongr/conv/aesop/mvcgen are native surfaces the
library connects to.
The library has two axes. The vertical axis is transfer across representations (parametricity). The horizontal axis is congruence within a representation. The functoriality rule above connects them.
param_auto dispatches to every surface above, so one call site closes goals in
different relations. It routes each goal to the tactic that closes it
(param_solve, rcongr, norm_cast, gcongr, grind):
example (a : Nat) : a + 0 = a := by param_auto -- Eq
example (a b : Nat) (h : a ≤ b) : a + 1 ≤ b + 1 := by param_auto -- ≤, via gcongr
example (a b : Nat) : -- cast graph, via norm_cast
((a + b : Nat) : Int) = (a : Int) + b := by param_auto
example (a b c : F) : -- registered rep. change, via rcongr
a * b + c = bbFieldAdd (bbFieldMul a b) c := by param_autoThe dispatch strategy can be reordered or made goal-directed without changing any
proof that calls param_auto. The specialized tactics below remain available for
a proof that names one strategy.
A relation carries more or less structure, and a transfer uses only as much as
the goal requires. Param (m n) A B grades this:
What R carries |
Use it for |
|---|---|
| a bare relation | relate values that share no maps |
a forward map (a cast A → B) |
push a value/computation to its representation |
a backward decoder (B → A) |
a retraction — pull a representation back (e.g. ℤ ↠ ZMod p) |
| both, coherent | an equivalence (e.g. ℕ ↔ binary) |
A relation is registered at the strength that can be proved. The level solver
(ParamInfer) then picks the minimum strength a given goal needs and weakens
down the lattice (ParamWeaken, auto_weaken). Worked examples: ParamRetraction
(ℤ↠ZMod p / ℕ↠ZMod p), ParamCryptoExamples (ℕ↔binary, Num↦ℕ), and
ExampleField, a registered-operation demo with the identity encoding. The
cross-type field retraction BitVec 64 ↠ ZMod p is in MachineLimbField.
Transfer composes and extends through three mechanisms:
- The relation
Rcomposes.Param_transgluesA↔BandB↔CintoA↔C. - Casts compose and preserve identity.
cast_transis AdapTT'sAdaptComp(cast (R∘S) = cast S ∘ cast R) andcast_idisAdaptId; the combinators distribute over composition (ParamCoherence). These theorems certify the functor laws. In Lean the composite's forward map is function composition, so the laws hold byrfl, and composition through nested type formers needs no further proof. AdapTT obtains the same laws from a definitional cast calculus. The laws are cited explicitly where a proof builds a chain of casts by hand. The dependent formers extend the non-dependent→/×/Listlaws:paramForall(Π) andparamSigma(Σ,sigma_cast_eq=Adapt Σ = Σ Adapt) carry the same functoriality to dependent types. - Inductive
Ris derived.deriving Paramreads an inductive's constructor signature (AdapTT'sIndDesc) and generates its constructor-wise relation and a fullParaminstance. It rejects mixed-variance inductives (AdapTT's Non-example). It covers records, enums, and uniform-recursive (List/Tree-shape) types.
inductive Pair (A B : Type) | mk : A → B → Pair A B
deriving ParamFour tactics act on one relation with two strategies and their union (full
reference: TACTICS.md):
| Tactic | Strategy | Closes |
|---|---|---|
rcongr / hgcongr |
top-down descent | a registered cross-head op-tree, leaving per-argument relatedness subgoals |
param_cc |
bottom-up closure (via grind) |
transitivity / context-hypothesis chains |
param_solve |
descent + closure-leaf | the union of the above — the default |
param_guard |
descent through guarded squares | op-trees whose squares hold only under a side condition; open guards are returned as goals |
A square that holds only on a range is registered with its guard, a proposition
on the abstract operands (Base/RelatedOn.lean): the class
RelatedBinOpOn enc P op bop has the single field
comm : ∀ x y, P x y → enc (op x y) = bop (enc x) (enc y), with P an outParam.
RelatedUnOpOn enc P op uop and RelatedRelOn enc P r br
(P x y → (r x y ↔ br (enc x) (enc y))) are the unary and relation forms. Every
total RelatedBinOp/RelatedUnOp is a guarded square with guard True
(RelatedBinOp.toOn), and BinOpHomOn.toRelatedBinOpOn converts a
ReprTransfer.BinOpHomOn. The guard of a node is stated on that node's
operands, so the guards of a composite are ordinary side conditions:
-- truncated `ℕ` subtraction and shift against wrapping 64-bit words
example (a b s : ℕ) (ha : a < 2 ^ 64) (hb : b ≤ a) (hs : s < 64) :
enc64 ((a - b) >>> s) = (enc64 a - enc64 b) >>> enc64 s := by
param_guard -- guards `b ≤ a` and `a - b < 2 ^ 64 ∧ s < 64`, closed by omegaparam_guard discharges each guard with transfer_guard_disch
(first | trivial | assumption | omega | bv_omega | decide, extensible by
macro_rules) or with param_guard (disch := tac), and returns the guards it
cannot close as goals. When a subterm has no square, the error names that subterm
and its head pair. param_solve also descends through guarded squares, with the
default discharger. Examples: Examples/GuardedSquares.lean.
hgcongr is the heterogeneous generalization of gcongr. It is
attribute-driven (@[hgcongr]) and keyed on a pair of heads, so it relates
different head functions f ≠ g, a case gcongr rejects by construction.
HGCongr.lean carries the upstream Mathlib.Tactic.GCongr.Core patch that would
add this to gcongr.
The library reuses native Lean machinery. Transfer is only as sound as the
registered witnesses, so a missing witness remains a visible residual goal.
For the same reason the aesop rule set is opt-in: it never searches for a square
on its own.
| Mechanism | Role |
|---|---|
| type classes | the synthesis engine (HasParam/Related resolution) |
grind |
the equational congruence-closure backend for param_cc (foundation squares dual-tagged @[grind =]) |
gcongr |
homogeneous congruence; hgcongr is its cross-head generalization |
simp / norm_cast |
the closed-equation fast path; a @[norm_cast] move-lemma is a transfer witness over the cast graph (ParamNormCast) |
conv |
focused sub-term transfer (transferConv) |
aesop |
an opt-in Transfer rule set bundling the transfer rules |
Std.Do / mvcgen |
Hoare-triple transfer via the Kleisli relation RComp (ParamTripleTransfer); rcomp (folded into param_transfer) assembles the witness |
| coercions | a Coe/CoeTC derived from a forward map (ParamCoe) |
RComp Rα c c' is the Kleisli lift of a value relation Rα to monadic
computations: for every pair of postconditions related pointwise by Rα, the
weakest precondition of c entails that of c'. RCompOk is its
success-restricted variant: each value that c returns without an exception has
an Rα-related partner among the results of c' (ParamRCompOk, tactic
rcomp_ok). Both are instances of Param, and both compose (ParamRCompParam).
A file that uses the library starts with
import Transfer
open Transfer Transfer.Param Transfer.ExampleFieldand can then register witnesses, transfer statements, derive relations, and close goals by congruence:
-- register a witness
def double (n : ℕ) : ℕ := 2 * n
@[param] theorem doubleR : RArrow Eq Eq double double := fun _ _ h => congrArg double h
-- transfer a ∀-statement across ℤ ↠ ZMod p (domain relation auto-resolved)
example (p : ℕ) [NeZero p] :
(∀ i : ℤ, (Int.cast i : ZMod p) + 0 = (Int.cast i : ZMod p)) →
(∀ x : ZMod p, x + 0 = x) := by
param_transfer
intro i x (hix : (Int.cast i : ZMod p) = x) h
rw [← hix]; exact h
-- derive the relation for a data type
inductive Pair (A B : Type) | mk : A → B → Pair A B deriving Param
-- close a representation-change equation by congruence
example (a b c : F) : a * b + c = bbFieldAdd (bbFieldMul a b) c := by param_solveFull per-tactic / per-attribute reference: TACTICS.md.
The source under Transfer/ is organized into concept subdirectories. The root
Transfer.lean is the single entry point.
Base/ — the Related/RelatedBinOp substrate and the class-hierarchy,
level and tactic substrate the engine reuses.
| Module | Content |
|---|---|
Core |
the repr registry and the repr_transfer tactic |
FieldRegistry |
the Baby Bear field kernels as registered operations |
Hierarchy |
the relation hierarchy as classes |
HigherOrderTransfer |
binder / higher-order / ∀ transfer as lemmas |
LevelRefusal |
the univalence-free refusal guard |
Levels |
relatedness levels: the three-point view of the Trocq lattice |
Related |
Related: transfer by instance resolution |
RelatedAt |
level-annotated relatedness |
TransferInduction |
transported induction, the equiv-level combinator |
TransferLevel |
the level-aware transfer! elaborator |
TransferTactic |
the transfer tactic (automatic binder translation) |
UnivalenceStatus |
univalence in Lean 4 and the relation between universes |
Hierarchy/
| Module | Content |
|---|---|
ParamEquiv |
the equivalence bridge and Param weakening |
ParamHierarchy |
the Param relation hierarchy |
ParamLevel |
the level lattice and the minimal-(m,n) solver core |
ParamWeaken |
the ⊑-driven Param weakening (level navigation) |
Combinators/
| Module | Content |
|---|---|
ParamArray |
the Array container rule and relational foldl transfer |
ParamArrow |
the arrow (app / function) parametricity rule |
ParamCoherence |
the coherence-law suite (functor laws) |
ParamData |
the data-type (× / Option / List) parametricity rules |
ParamForall |
the dependent-Π (∀) parametricity rule; mapHas_forall and paramForallAt give it at every output level, with the inputs the tables forallReq and forallFiberReq compute, none of which asks for map4 |
ParamSigma |
the dependent-pair (Σ) parametricity rule |
ParamTrans |
relation composition (Param_trans) |
Synthesis/
| Module | Content |
|---|---|
ParamInfer |
variable-level minimal-class inference |
ParamInferMeta |
level inference over Expr |
ParamResolve |
the upward level search (param_resolve) |
ParamSynth |
the Param-witness synthesizer |
ParamSynthExt |
the multi-level Param-witness synthesizer |
Translate/
| Module | Content |
|---|---|
ParamDB |
the persistent @[param] constant database |
ParamTranslate |
the term-level ⟦·⟧ synthesizer (the parametricity translation) |
ParamTranslateFull |
the integrated term-level translator ⟦·⟧ |
ParamTranslateOp |
the operator/arity rule for the term-level translator |
ParamTranslateTy |
the binder type-translation rule (change of representation) |
Statements/
| Module | Content |
|---|---|
ParamForallNested |
dependent-Π transfer for nested ∀-statements |
ParamTransfer |
∀-transfer through the Param engine (Map1_forall) |
ParamTransferTac |
param_transfer, end-to-end ∀-transfer |
Congruence/
| Module | Content |
|---|---|
GCongrProbe |
whether @[gcongr] accepts cross-head realization lemmas |
HCongrConnection |
a set-level analogue of cubical heterogeneous congruence (hcongr) |
HGCongr |
hgcongr: heterogeneous (cross-head) generalized congruence |
HGCongrInit |
the hgcongr engine: head-pair-keyed database, attribute, and tactic |
ParamAuto |
param_auto: one coordinator over every congruence surface |
ParamCompose |
param_compose: descend-and-dispatch congruence |
ParamCongrClosure |
param_cc: relational congruence closure over Related |
ParamSolve |
param_solve: the common generalization of rcongr and param_cc |
ParamGuard |
param_guard: descent through guarded squares, open guards returned as goals |
RCongr |
rcongr: relational (cross-head) congruence descent |
Deriving/
| Module | Content |
|---|---|
ParamCongr |
derive_param_congr: a constructor-congruence Related deriver |
ParamDerive |
hand-ported Param lifts and the specification of the @[derive Param] handler |
ParamDeriveHandler |
the description-based @[derive Param] handler |
Integrations/
| Module | Content |
|---|---|
AesopIntegration |
the Transfer aesop rule set |
AesopRuleSet |
the declaration module of the Transfer aesop rule set |
GrindIntegration |
grind as a transfer leaf discharger |
ParamAutoWeaken |
auto-weakening of witnesses and a unified transfer entry |
ParamCoe |
the coercion integration (Param witness → Coe) |
ParamConv |
conv-mode transfer |
ParamForIn |
forIn / do-loop transfer: foldlR meets RComp |
ParamNormCast |
norm_cast move-lemmas as Param relatedness witnesses |
ParamRComp |
rcomp: structural assembly of an RComp witness |
ParamRCompOk |
RCompOk: the success-restricted Kleisli relation |
ParamRCompParam |
RComp/RCompOk as instances of Param, and their composition |
ParamRelatedBridge |
one witness, both engines: RelatedBinOp ⇒ RArrow |
ParamTripleTransfer |
transferring a Hoare/wp triple through a Param relation; defines the Kleisli relation RComp |
Examples/
| Module | Content |
|---|---|
EffectfulTransfer |
an effectful transfer across a value-type change, proved by mvcgen |
ExampleField |
a self-contained example field for the engine demos |
IsogenyBoundedProbe |
BLS12-381 hash-to-curve isogeny identity: bounded per-coefficient probe |
MachineLimbField |
machine limbs ↔ prime field: a non-diagonal heterogeneous dependent example |
ParamCryptoDomains |
the Baby Bear field as a first-class transfer domain |
ParamCryptoExamples |
cryptographic representation examples: coherence laws and deriving Param |
ParamRetraction |
the ℤ ↠ ZMod p retraction transfer domain (non-diagonal) |
PeanoBinNat |
the peano_bin_nat example, first-order slice |
RCompOkExamples |
RCompOk over a two-outcome test monad |
Readme |
the examples of this README |
StrongExamples |
congruence that the native tactics cannot do |
Trocq |
the Trocq example suite (index) |
Trocq/Summable |
Trocq summable: summability transfers across an equivalence |
Hex suite: worked refinements on the carriers of leanprover/hex.
| Module | Content |
|---|---|
HexArrayCompute |
hex's Array-backed storage: carrier-agnostic refinement and verified compute |
HexDecide |
discharging a hex side condition by computation on the concrete representation |
HexEffectful |
transferring a hex elimination step across dense storage, effectfully |
HexMatrixCorrespondence |
hex-style dense-storage ↔ Mathlib correspondence, generated by the engine |
HexSeqPoly |
hex's dense polynomial storage: a non-injective refinement at map2a |
ZMod suite: deciding modular identities by bounded computation.
| Module | Content |
|---|---|
ZModDecide |
decide_zmod: ground ring identities over ZMod m by bounded residue computation |
ZModPolyDecide |
decide_zmod_poly: univariate polynomial identities over ZMod m |
ZModPowMod |
powMod: binary modular exponentiation for kernel-decide modular arithmetic |
CoqEAL suite: the library TransferCoqEAL, rooted at Transfer.Examples.CoqEAL,
which depends on CompPoly.
| Module | Content |
|---|---|
CoqEAL |
the CoqEAL / Trocq example suite (index) |
CoqEAL/BareissDet |
CoqEAL bareiss: a fraction-free determinant certified against Matrix.det |
CoqEAL/BinInt |
CoqEAL binint: sign + magnitude refines ℤ |
CoqEAL/BinNat |
CoqEAL binnat: List Bool refines the natural numbers |
CoqEAL/BinRat |
CoqEAL rational/binrat: num/den pairs refine ℚ |
CoqEAL/ComputePolynomial |
computing with Mathlib's Polynomial through a CompPoly refinement |
CoqEAL/GaussPivotStep |
Gaussian elimination: single forward-elimination pivot step |
CoqEAL/GaussSweep |
Gaussian elimination: multi-row forward sweep |
CoqEAL/Karatsuba |
CoqEAL Karatsuba polynomial multiplication |
CoqEAL/KaratsubaRec |
recursive Karatsuba on CPolynomial R |
CoqEAL/Multipoly |
CoqEAL multipoly: multivariate polynomials as finite maps |
CoqEAL/Rank |
CoqEAL rank: matrix rank certified against Matrix.rank |
CoqEAL/SeqMatrix |
CoqEAL seqmatrix: List (List R) refines the entry function |
CoqEAL/SeqMatrixMul |
CoqEAL seqmatrix multiplication: List (List R) row-column product |
CoqEAL/SeqPoly |
CoqEAL seqpoly: List R refines the coefficient function |
CoqEAL/Strassen |
Strassen one-level 2×2 block multiplication identity |
CoqEAL/StrassenRec |
recursive (fuel-driven) Strassen block multiplication |
CoqEAL/ToomCook |
CoqEAL toomcook: the evaluate–multiply–interpolate core |
Top-level modules and libraries
| Module | Content |
|---|---|
Transfer |
the single entry point |
Transfer/Audit |
axiom ledger: a build-time hygiene check (lake build Transfer.Audit) |
ReprTransfer (library) |
representation transfer for emit-realization bridges |
ReprTransferExpr (library) |
compositional transfer via the abstraction theorem |
The library builds on the work credited in ATTRIBUTION.md:
Trocq (Cohen–Crance–Mahboubi), whose parametricity lattice and combinators the
engine's core adapts; AdapTT (functorial casts and description-based
deriving); CoqEAL (data refinement); the cubical congruence of
Gjørup–Spitters; and Lean 4 / Mathlib.