Reversible modular addition — register encoding, verified full adder, ripple cost #
Category: 1-Mathlib (CSD-free; staged as a Mathlib-upstream candidate).
The addition layer of the reversible-circuit substrate
(Circuit.lean / Cost.lean; the application that motivated this substrate lives in the Ecdsafail repository, specs/ecdsa/ecdlp-resource-plan.md there). 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 a 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 the bit-update lawregVal_update_eq.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(ak-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 with all carry wires initially false,
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.
The addition modulus is 2 ^ n. The carry wires are part of the supplied layout and are not
uncomputed by this circuit. circuitCost fixes ancilla = 0 and counts Toffoli as a primitive
with tCount = 0; these conventions do not assert zero carry workspace or a zero-T implementation
over a decomposed gate set. Its toffoliDepth is the sequential gate count.
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 pairwise-distinct 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 states, of which eight satisfy
the initialisation hypothesis 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): qubits = n,
two Toffolis, two CNOTs and sequential Toffoli depth two. The remaining fields are zero by
the abstract cost model; in particular, it does not separately count the supplied carry
wire as ancilla or decompose Toffoli gates into T gates.
Ripple adder (general n): linear cost #
A k-slice concatenation of fullAdder gadgets, one per quadruple (a, b, cin, cout)
in slices. This definition imposes no wire-layout or initialisation conditions; the cost
lemmas hold for any slice list. Arithmetic correctness is proved below for rippleCirc
with a RippleLayout and zero-initialised carries.
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 families have disjoint images over all natural indices, including unused indices.
Injectivity is required only on the used ranges: 0 .. n-1 for A, B and 0 .. n for C.
These are wire-layout hypotheses; carry initialisation is assumed separately by correctness.
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. With all carry wires initially false, after the first k
slices: the value of register B's low k bits plus 2 ^ k times the carry into bit k
equals the sum of the original low-k values 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.