Machine limbs ↔ prime field: a strong non-diagonal heterogeneous dependent example #
This module is a worked end-to-end example of the Param engine on a real crypto
representation change: the machine-limb view of a prime field element and its
abstract ZMod p view. It exercises the engine at full strength — the domain is
- non-diagonal (the two sides are different types:
BitVec 64/Fin n → BitVec 64on the left,ZMod pon the right), - heterogeneous (the value leaf relates terms living in different types via a
decoder, closed by
hcongr_hetero, not nativecongr), and - dependent (the multi-limb section is the
R_foralldependent-Π relation over theFin n-indexed limb family).
It builds, in dependency order, two retractions and their store-refinement leaves:
- the single-limb split surjection
BitVec 64 ↠ ZMod p(limbFieldParam,instTransferDomLimbField,dec_add/dec_mul,LimbStoreRefines,limbLeaf_hcongr) — one 64-bit word per field element (BabyBear, Goldilocks, Mersenne-31); - the multi-limb split surjection
(Fin n → BitVec 64) ↠ ZMod p(limbVal/toLimbs/limbVal_toLimbs,multiLimbFieldParam,limbVal_congr/limbVal_hcongr/limbVal_hcongr_field,MultiLimbStoreRefines,multiLimbLeaf_hcongr) — a base-2^64limb array per field element (Curve25519n = 4, P-384n = 6, every multi-limb prime).
Both are asymmetric retractions Param .map0 .map2a … (ZMod p), mirroring
Transfer.Param.natZModDom / the ℤ ↠ ZMod p example in ParamRetraction, but
with the machine-limb type as the concrete source — the value-abstraction layer of
a verified compiler's store refinement.
Why a retraction, not an equivalence #
The decoder dec w := (w.toNat : ZMod p) (single-limb) resp.
dec limbs := (limbVal n limbs : ZMod p) (multi-limb) is surjective (every field
element is some limb('s array)'s reduction) but not injective when
p < 2^64 resp. p < 2^(64·n) — distinct limbs reduce equally. So the round-trip
holds on the ZMod p side only:
- Forward is trivial (
map0): the relation is the graph of the decoder, but at the∀-rule level the forward structure is forgotten to⟨⟩. - Backward is
map2a: the section picks the canonical limb (single-limb) resp. splits into base-2^64digits (multi-limb), andmap_in_Ris the retraction law. It is notmap2b, because that would force every reducing limb to be the canonical one, which is false.
This is the "Way 2" construction: the no-overflow condition is absorbed into the
section, which always lands a canonical representative in range, so the section law
holds unconditionally on the ZMod p side.
The limb→field split surjection BitVec 64 ↠ ZMod p as a
Param .map0 .map2a retraction (for a single-64-bit-limb prime,
hp : p ≤ 2 ^ 64).
Relation R w x := ((w.toNat : ZMod p) = x) (graph of the limb decoder).
Forward is the trivial map0. Backward is map2a: section
sec x := BitVec.ofNat 64 x.val with map_in_R the retraction
((BitVec.ofNat 64 x.val).toNat : ZMod p) = x. The section law reduces
(via BitVec.toNat_ofNat then Nat.mod_eq_of_lt from x.val < p ≤ 2^64) to
ZMod.natCast_rightInverse. It is not map2b: the decoder is not
injective, so the class is asymmetric.
Equations
Instances For
TransferDom accepts the limb→field split surjection: the BitVec 64 ↠ ZMod p
change of representation resolves automatically for param_transfer. This is a
non-diagonal TransferDom (machine limb type ≠ abstract field), the
Phase-2 value-abstraction analogue of instTransferDomNatZMod. The
single-limb bound p ≤ 2^64 is carried as a Fact so instance resolution can
discharge it.
Equations
Operation squares: feAdd/feMul transfer through the decoder #
Under a no-overflow hypothesis on the 64-bit limb sum/product, the machine
word operation commutes with the abstract field operation across the decoder
dec w := (w.toNat : ZMod p). These are the value-level commuting squares that
justify transferring feAdd/feMul from the field to the limb store: the
decoder is a (partial, overflow-guarded) ring homomorphism.
The overflow guard is essential — without it, BitVec addition wraps mod
2^64 before the field reduction, which need not agree with reducing the true
integer sum mod p.
Decoder-addition square: on limbs whose true integer sum stays below 2^64,
the decoder of the (wrapping) word sum equals the field sum of the decoders.
The no-overflow hypothesis collapses the % 2^64 wrap so BitVec addition
lines up with ℕ addition before reduction mod p.
Store refinement as heterogeneous congruence over limbFieldParam #
A machine store s : V → BitVec 64 (limb per variable) and its abstract
field-store view s' : V → ZMod p are related exactly when they are
Transfer.Param.R_forall-related over the single-limb decoder fiber
limbFieldParam — the dependent-Π relation whose (constant) fiber is the
limb→field split surjection. Reading any one variable is then an instance of
hcongr_hetero: the machine value s v : BitVec 64 and the field value
s' v' : ZMod p live in different types and are related by the decoder, i.e.
the value leaf ((s v).toNat : ZMod p) = s' v' is UA-free heterogeneous
congruence across the limb→field change of representation, not native congr.
The transport form of heterogeneous congruence (hcongr_hetero_transport)
does not apply here: it requires a map2b fiber (a forward map carrying its own
graph), whereas limbFieldParam's forward structure is map0 by design — the
decoder is recorded as the relation graph and forgotten to ⟨⟩ (the asymmetric
"Way 2" retraction). Only the relation form hcongr_hetero is available, which
is the correct reading: the leaf is a relation between representations, not a
transportable forward map.
The single-limb store-refinement relation: a machine store s : V → BitVec 64
refines an abstract field store s' : V → ZMod p when they are
R_forall-related over the (constant) limbFieldParam fiber — every
variable's machine limb decodes to its abstract field value. Unfolds to
∀ v v' (_ : v = v'), ((s v).toNat : ZMod p) = s' v'.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Single-limb value leaf as heterogeneous congruence. From a store
refinement LimbStoreRefines and equal variables v = v', the machine value
s v : BitVec 64 and the field value s' v' : ZMod p — living in different
types — are related by the decoder: ((s v).toNat : ZMod p) = s' v'. This is
hcongr_hetero at the limbFieldParam fiber, exhibiting the
machine-value↔field-value relation as UA-free heterogeneous congruence across
the limb→field change of representation.
The base-2^64 limb-array value and its digit section #
limbFieldParam above builds the single-limb split surjection
BitVec 64 ↠ ZMod p (one 64-bit word per field element — BabyBear, Goldilocks,
Mersenne-31). The remainder of this module lifts it to multi-word field
elements: a limb array Fin n → BitVec 64, read as its base-2^64 (radix-64,
little-endian) value, projects onto ZMod p. This is the representation of
Curve25519 field elements (n = 4), P-384 (n = 6), and every multi-limb prime
field.
The witness is again a retraction Param .map0 .map2a (Fin n → BitVec 64) (ZMod p):
- Forward (
map0): the relationR limbs x := ((limbVal n limbs : ZMod p) = x)is the graph of the limb-array decoderlimbs ↦ (limbVal n limbs : ZMod p), forgotten to⟨⟩at the∀-rule level. - Backward (
map2a): the sectionsec x := toLimbs n x.valsplitsx.valinto its base-2^64digits, andmap_in_Ris the retraction law((limbVal n (toLimbs n x.val) : ZMod p) = x). This is notmap2b— the decoder is not injective (distinct limb arrays reduce equally whenp < 2^(64·n)), so the class stays asymmetric, exactly as in the single-limb case.
The crux is the base-2^64 recomposition lemma limbVal_toLimbs: for
v < 2^(64·n), splitting v into digits and Horner-recomposing recovers v.
Little-endian base-2^64 value of a limb array, ∑ᵢ (limbs i).toNat · 2^(64·i),
written as the structural Horner fold
(limbs 0).toNat + 2^64 · limbVal n (Fin.tail limbs). This is the Fin-indexed
analogue of Cios4LimbProven.valLimbs / FormosaSpecBridge.limbsNat.
Equations
- Transfer.Param.limbVal 0 x_2 = 0
- Transfer.Param.limbVal n.succ f = (f 0).toNat + 2 ^ 64 * Transfer.Param.limbVal n (Fin.tail f)
Instances For
The canonical section: split v into its base-2^64 little-endian digits,
limb i being (v / 2^(64·i)) % 2^64.
Equations
- Transfer.Param.toLimbs n v i = BitVec.ofNat 64 (v / 2 ^ (64 * ↑i) % 2 ^ 64)
Instances For
The recomposition (retraction) lemma #
Base-2^64 recomposition. For v < 2^(64·n), splitting v into its
base-2^64 digits and Horner-recomposing recovers v exactly. This is the
crux retraction law behind the multi-limb section. Proved by induction on n
with the little-endian peel v = v % 2^64 + 2^64 · (v / 2^64).
The multi-limb limb-array→field split surjection as a Param .map0 .map2a
retraction. p ≤ 2^(64·n) is the multi-limb no-overflow bound (n 64-bit
limbs hold every residue).
Relation R limbs x := ((limbVal n limbs : ZMod p) = x) (graph of the
base-2^64 limb-array decoder). Forward is the trivial map0. Backward is
map2a: section sec x := toLimbs n x.val with map_in_R the retraction
((limbVal n (toLimbs n x.val) : ZMod p) = x). The section law reduces (via
limbVal_toLimbs, applicable since x.val < p ≤ 2^(64·n)) to
ZMod.natCast_rightInverse. It is not map2b: the decoder is not
injective, so the class is asymmetric. Generalizes limbFieldParam (the
n = 1 case).
Equations
- One or more equations did not get rendered due to their size.
Instances For
Multi-limb store congruence (the R_forall-over-Fin n view) #
A multi-limb store is a dependent function Fin n → BitVec 64. Two stores
that agree limb-by-limb — packaged as a Transfer.Param.R_forall over the
diagonal on Fin n with the equality fiber on BitVec 64 — decode to the same
base-2^64 value. This reads limbVal congruence off the store relation: it is
the R_forall-over-Fin n view of the limb store, the multi-limb analogue of
the single-limb LimbStoreRefines/limbLeaf_hcongr leaf.
Note this is a decoder congruence (equal inputs, equal output), a fundamentally
different fact from limbVal_toLimbs, which is the base-2^64 recomposition
identity limbVal ∘ toLimbs = id on the in-range interval — not a congruence and
not R_forall-shaped.
Multi-limb store congruence, the R_forall-over-Fin n view. Two limb
stores related by R_forall over the diagonal on Fin n with the equality
fiber on BitVec 64 (i.e. agreeing limb-by-limb, expressed relationally)
decode to the same base-2^64 value. The store relation is precisely the
dependent-Π relation R_forall at the Fin n-indexed limb family.
Field-level form of limbVal_hcongr: limb-by-limb agreeing stores decode to
the same field element ZMod p.
Multi-limb store↔field leaf as heterogeneous congruence #
The multi-limb analogue of the single-limb limbLeaf_hcongr: an environment-view
of stores S : E → (Fin n → BitVec 64) (a whole limb array per environment) and
its abstract field view X : E → ZMod p, related over the multiLimbFieldParam
fiber, exhibit the machine-store↔field-value relation
((limbVal n (S e) : ZMod p) = X e) as hcongr_hetero across the
multi-limb-array→field change of representation (a different source
type Fin n → BitVec 64 on the left).
The multi-limb store-refinement relation: an environment-indexed limb store
S : E → (Fin n → BitVec 64) refines a field store X : E → ZMod p when they
are R_forall-related over the multiLimbFieldParam fiber. Unfolds to
∀ e e' (_ : e = e'), (limbVal n (S e) : ZMod p) = X e'.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Multi-limb value leaf as heterogeneous congruence. From a multi-limb store
refinement and equal environments e = e', the machine limb store S e and
the field value X e' are related by the base-2^64 decoder:
(limbVal n (S e) : ZMod p) = X e'. This is hcongr_hetero at the
multiLimbFieldParam fiber.