Reversible modular addition — register encoding, verified full adder, ripple cost (ECDLP Tranche 2, Pass 1) #
Category: 1-Mathlib (CSD-free; staged as a Mathlib-upstream candidate).
The addition layer of the reversible-circuit substrate
(Circuit.lean / Cost.lean, specs/ecdlp-resource-plan.md). Pass 1 delivers a sorry-free
semantic + derived-cost scaffold: the register readout regVal, the fully verified four-gate
full-adder gadget, its derived cost, and the linear Toffoli / CNOT count of an k-slice ripple
adder (composed through the Tranche-1 cost_comp_* lemmas, not re-derived).
The locked design decision is respected throughout: every cost claim is about an exhibited
Circuit and is discharged through circuitCost. The full-adder correctness is genuine
all-inputs coverage (decide over Fintype (Fin 4 → Bool)), not a single example.
What is proved here #
Pass 1 (cost + gadget):
regVallittle-endian readout,regVal_lt_two_pow, and anX-flip round-trip lemma.fullAdder(four gates) with full all-inputs correctness on the concreteState 4layout: sum bitb ← a ⊕ b ⊕ cin, carry-outcout ← majority(a, b, cin)(withcoutinitfalse),aandcinpreserved.fullAdder_cost: derivedtoffoli = 2,cnot = 2(and the fullCostrecord).rippleAdder(ank-slice concatenation offullAddergadgets) with linear cost:(circuitCost (rippleAdder ...)).toffoli = 2 * kand.cnot = 2 * k, composed through the Tranche-1 composition lemmas.
Pass 2 Stage A (general gadget): fullAdder_correct_general lifts the full-adder correctness
off the concrete State 4 to arbitrary distinct Fin n wires, via the Circuit-level frame lemma
denote_apply_of_forall_not_mem / fullAdder_apply_of_ne.
Pass 2 Stage B (carry-chain arithmetic — the modular-addition correctness): rippleCirc, the
in-place ripple adder over a disjoint-wire RippleLayout, computes (A + B) mod 2 ^ n into register
B: rippleCirc_correct. Proved by induction on the slices (rippleCirc_invariant, the 4-clause
carry invariant) lifting fullAdder_correct_general through the frame lemma and the per-slice
arithmetic fulladder_nat. Non-vacuity witnessed by rippleLayout2 (a concrete 2-bit layout on
Fin 7). This is the genuine computational correctness, not just the cost.
Register encoding (little-endian) #
The general place-value update law for Function.update s i b: replacing wire i's bit by b
(for ANY prior bit s i and ANY new bit b) removes i's old place-value contribution and adds the
new one. The reusable round-trip helper consumed by ModMul / ScalarMul.
The full-adder gadget (the verified primitive) #
The four-gate full adder on wires a b cin cout (with cout initialised false):
b ← a ⊕ b ⊕ cin (sum bit), cout ← majority(a, b, cin) (carry-out), a/cin unchanged.
The gate order realises the standard in-place adder: write the partial carry into cout from
(a,b), fold a into b, fold (cin,b) into cout, fold cin into b. Correctness on the
concrete layout is fullAdder_correct.
Equations
- Reversible.fullAdder a b cin cout = [Reversible.Gate.CCX a b cout, Reversible.Gate.CX a b, Reversible.Gate.CCX cin b cout, Reversible.Gate.CX cin b]
Instances For
Full-adder correctness — genuine all-inputs coverage. On the concrete State 4 layout
(wires 0,1,2,3 = a,b,cin,cout), with cout initialised false, the gadget computes the sum bit
on wire 1, the carry-out on wire 3, and preserves a (wire 0) and cin (wire 2). Proved by
decide over the finite State 4 = Fin 4 → Bool (16 inputs, each input fixed s 3 = false).
Frame lemma for the gadget. A wire distinct from all four of a, b, cin, cout is untouched
by fullAdder (every gate's wires lie in {a, b, cin, cout}). The payoff of the Circuit-level
denote_apply_of_forall_not_mem; lets the carry-chain (Pass 2) lift this slice over a register.
Full-adder correctness, general Fin n wires. For pairwise-distinct wires a, b, cin, cout
with cout initialised false, the gadget writes the sum bit to b, the carry-out to cout, and
preserves a and cin — over arbitrary Fin n (not just the concrete State 4 of
fullAdder_correct). This is the slice the ripple carry-chain (Pass 2) iterates.
Derived cost of the gadget #
Derived cost of the full adder (from the gate list, via circuitCost): two Toffolis, two
CNOTs, Toffoli depth two, everything else zero. Not asserted — read off [CCX, CX, CCX, CX].
Ripple adder (general n): linear cost #
An k-slice ripple adder: the concatenation of fullAdder gadgets, one per quadruple of wires
in slices. A quadruple is (a, b, cin, cout). The wire layout (which carries feed which sums) is a
Pass-2 concern; Pass 1 fixes only the gate list (hence the cost).
Equations
- Reversible.rippleAdder slices = List.flatMap (fun (q : Fin n × Fin n × Fin n × Fin n) => Reversible.fullAdder q.1 q.2.1 q.2.2.1 q.2.2.2) slices
Instances For
Ripple-adder Toffoli count is linear in the number of slices. Each slice contributes exactly
two Toffolis (fullAdder_toffoli); the count adds over the concatenation
(cost_comp_toffoli_count). Proved by induction over the slice list, composing the Tranche-1
lemma — not by re-deriving the sum.
Pass 2: ripple carry-chain arithmetic correctness #
The Pass-1 ripple cost says nothing about what rippleAdder computes. Here we exhibit a concrete
in-place ripple layout (input registers A, B and a carry chain C, as injective pairwise-disjoint
wire families) and prove the register B ends up holding (A + B) mod 2 ^ n, by induction on the
slices, lifting fullAdder_correct_general through the frame lemma fullAdder_apply_of_ne.
Place-value readout of the low k bits of a register laid out on wires f i (place value
2 ^ i at index i). Indices are ℕ to keep the layout arithmetic coercion-free.
Equations
- Reversible.regValRange f s k = ∑ i ∈ Finset.range k, (s (f i)).toNat * 2 ^ i
Instances For
The full-adder arithmetic identity on ℕ. The sum bit plus twice the carry equals the arithmetic sum of the three input bits — the per-slice fact the carry chain accumulates.
A ripple-carry adder layout on m wires for n-bit registers: input registers A, B and a
carry chain C (with C 0 the input carry and C n the output carry), as ℕ-indexed wire families.
The three images are pairwise disjoint and each is injective on its used index range — exactly the
geometry hypotheses any real layout satisfies (they are about wires, not about the computation).
Wires of register
A(the first addend).Wires of register
B(the second addend; overwritten with the sum).Wires of the carry chain (
C i= carry into biti).
Instances For
The circuit of the first k slices (bits 0 .. k-1).
Equations
Instances For
The full ripple adder: all n slices.
Equations
Instances For
The carry-chain invariant. After the first k slices: register B's low k bits plus the
carry into bit k equal the low-k sum of A and B (P1); register A is untouched (P2); the
unprocessed high bits of B (P4) and the unset high carries (P5) are preserved. By induction on k,
each step lifting fullAdder_correct_general through the frame lemma fullAdder_apply_of_ne.
Ripple-carry adder correctness (Pass 2 headline). For a disjoint-wire layout with all carries
initialised false, the ripple adder leaves register B holding (A + B) mod 2 ^ n. The carry-chain
identity, derived from the exhibited circuit rippleCirc — not postulated.
Non-vacuity witness #
A concrete 2-bit ripple layout on Fin 7 (register A on wires 0,1, B on 2,3, carry chain
C on 4,5,6), exhibiting that RippleLayout is inhabited and rippleCirc_correct applies — the
headline is not vacuously quantified over an unsatisfiable hypothesis bundle.