Reversible modular addition — the verified value primitive (a, b) ↦ (a, (a+b) mod N) (ECDLP Phase 2, Stage S6.3b) #
Category: 1-Mathlib (CSD-free; staged as a Mathlib-upstream candidate).
This module verifies the modular-addition VALUE primitive (a, b) ↦ (a, (a + b) mod N) over bit
registers, by chaining two already-verified blocks:
modAdd L = rippleCirc L.addStep ++ modReduce L.reduceStep
- Add step
rippleCirc L.addStep(Aop= the operanda, read-only addend;Bholdsb; fresh carry chainCadd): witha, b < Nand2N ≤ 2ⁿ(soa + b < 2N ≤ 2ⁿ),rippleCirc_correctgivesB ← (a + b) mod 2ⁿ = a + b(NO wrap, viaNat.mod_eq_of_lt). - Reduce step
modReduce L.reduceStep(S6.3a): nowBholdsx = a + b < 2N, somodReduce_correctapplies and leavesB ← x mod N = (a + b) mod N.
The single load-bearing range fact is a + b < 2N: it is what makes the single-step S6.3a
reduction apply (a 2w-bit product would need the iterated schedule). The add step's carry chain
Cadd is disjoint from everything the reduce step touches (A1, A2, C1, C2, anc, B), so after the
add: A1/A2 are still at their presets and C1/C2/anc are still false (frame lemmas below). B
after the add is exactly a + b.
Carve line (what this is, and is NOT) #
This is the value-correct modular-addition atom in the fresh-ancilla model. Group-law / field
semantics are NOT in play here: this is ℕ / mod N bit arithmetic.
Named residue (do NOT read this as a clean reusable in-place primitive):
- Dirty carries / flag (the genuine S6.3b-cleanup residue). The carry chains
Cadd,C1,C2and the comparison flagC1 nare left dirty aftermodAdd. Correctness holds because the layout supplies fresh wires per use (modAdd_correctrequiresC* / ancinitialisedfalse). In- place reuse across many additions needs carry-clean / ancilla-restoring adders — which the corpus'srippleCirc/cRippleCircdo NOT provide. Closing this needs either Cuccaro-style inline carry-uncompute adders, or reversing the carry generation, or the high-bit self-cleaning modular- adder variant. That is the genuine remaining S6.3b-cleanup work, NOT built here. - Uncontrolled only. This is the UNCONTROLLED modular adder. The CONTROLLED version (S6.3b-2) and
the interleaved MSB-first double-and-reduce modular MULTIPLY over
𝔽_p(S6.3c) are the subsequent tranches. This module is the arithmetic core a modular multiply iterates.
So: a verified modular-addition value atom with dirty carries (fresh-ancilla model), NOT the modular multiply and NOT a clean in-place adder.
Honest cost #
modularAdd_toffoli derives 12n Toffolis from the exhibited gate list: add step 2n
(rippleCirc, the rippleAdder-style ripple count) + reduce step 10n (modReduceCtrl_toffoli),
composed through cost_comp_toffoli_count.
A modular-addition layout on Fin m for n-bit registers. Bundles the operand register Aop
(read-only addend a), the accumulator B (holds b, overwritten with (a+b) mod N), the add
step's own carry chain Cadd, and the reduce sub-data (A1, A2, C1, C2, anc).
The geometry fields give: (i) addStep : RippleLayout well-formed (Aop, B, Cadd pairwise
disjoint + bounded-injective), (ii) reduceStep : ModReduceLayout well-formed, and (iii) Cadd and
Aop disjoint from every reduce-step wire so the add step's carries are fresh and the operand
survives the whole circuit.
Injectivity fields are bounded (< n for registers, < n + 1 for carry chains), exactly as
ModReduceLayout / MulLayout: an unbounded ℕ → Fin m injectivity field is uninhabitable and would
make the theorems vacuous.
Operand register: holds
a, read-only addend (preserved bymodAdd).Accumulator: holds
b, overwritten with(a + b) mod N.Add-step carry chain (disjoint from the reduce step).
Reduce step-1 constant register (preset to
2ⁿ − N).Reduce step-1 carry chain;
C1 nis the comparison flag.Reduce step-3 constant register (preset to
N).Reduce step-3 carry chain.
- anc : Fin m
Reduce shared clean ancilla.
Instances For
The reduce step as a ModReduceLayout (S6.3a).
Equations
- One or more equations did not get rendered due to their size.
Instances For
The modular-addition circuit. Register add (rippleCirc) followed by the S6.3a single-step
modular reduction (modReduce).
Equations
Instances For
Frame: a wire disjoint from addStep's registers survives the add step #
rippleCirc L.addStep touches only the wires {Aop k, B k, Cadd k} (the slices are
fullAdder (Aop k) (B k) (Cadd k) (Cadd (k+1))). Any wire distinct from all of those passes through
unchanged. This is the generic frame lemma; the four corollaries below specialise it to the reduce
step's A1 / A2 / C1 / C2 / anc wires.
Generic add-step frame. A wire w with w ≠ Aop k, w ≠ B k, w ≠ Cadd k for all k is
left unchanged by rippleCirc L.addStep.
After the add step, A1 is unchanged on every wire (disjoint from the add step).
After the add step, A2 is unchanged on every wire.
After the add step, C1 is unchanged on every wire.
After the add step, C2 is unchanged on every wire.
After the add step, anc is unchanged.
Frame: the operand Aop survives the reduce step #
modReduce L.reduceStep touches only {A1, A2, C1, C2, anc, B} (and the flag C1 n). Since Aop
is disjoint from all of these, the operand passes through the reduce step unchanged.
Operand frame through the reduce step. Aop j is untouched by modReduce L.reduceStep.
Value correctness #
The verified modular-addition value primitive. For a disjoint-wire ModAddLayout with all
carry chains (Cadd, C1, C2) and the ancilla anc initialised false, register A1 preset to
2ⁿ − N, register A2 preset to N, operand register Aop holding a, accumulator B holding
b, with a < N, b < N, 2N ≤ 2ⁿ (so a + b < 2N ≤ 2ⁿ): modAdd L leaves register B
holding (a + b) mod N.
Proof. The add step (rippleCirc_correct) writes (a + b) mod 2ⁿ = a + b to B (no wrap, since
a + b < 2N ≤ 2ⁿ), preserving Aop = a (P2 of the ripple invariant) and — via the frame lemmas — the
reduce step's presets A1 = 2ⁿ − N, A2 = N and clean carries C1 = C2 = false, anc = false. The
reduce step (modReduce_correct, S6.3a) then maps B = a + b < 2N to (a + b) mod N.
The range hypothesis is taken as 2 * N ≤ 2 ^ n directly (the honest load-bearing form; the gloss
N ≤ 2^(n-1) is only equivalent for n ≥ 1 and diverges at n = 0, so it is avoided):
it forces both a + b < 2ⁿ, so the add does not wrap, and
a + b < 2N, so the single-step S6.3a reduction applies.
The operand register is intact. modAdd L leaves Aop holding a (read-only addend). The
add step preserves Aop (P2 of the ripple invariant) and the reduce step is disjoint from Aop.
The modular-addition output is a genuine residue in [0, N). Corollary of modAdd_correct
and Nat.mod_lt.
Derived cost #
Derived Toffoli cost of the modular adder: 12n Toffolis, from the exhibited gate list. Add
step 2n (rippleCirc, the same ripple count as rippleAdder) + reduce step 10n
(modReduceCtrl_toffoli), composed through cost_comp_toffoli_count.
Non-vacuity witness #
A concrete 3-bit modular-addition layout on Fin 25:
- operand
Aop → {0,1,2}, accumulatorB → {3,4,5}, add-carryCadd → {6,7,8,9}, - reduce step-1 constant
A1 → {10,11,12}, step-1 carryC1 → {13,14,15,16}, - reduce step-3 constant
A2 → {17,18,19}, step-3 carryC2 → {20,21,22,23}, ancilla24.
(n = 3 is needed, not n = 2: the modular adder requires 2N ≤ 2ⁿ so the add does not wrap; for
N = 3 that forces 2ⁿ ≥ 6, i.e. n ≥ 3. At n = 2 the case a = b = 2 gives a + b = 4 = 2²,
which would wrap.) This exhibits that ModAddLayout is inhabited, so the headlines are not vacuously
quantified. The concrete runs below add modulo N = 3 at fully specified input states:
a = 2, b = 2 ↦ (2 + 2) mod 3 = 1 and a = 1, b = 1 ↦ (1 + 1) mod 3 = 2.
A concrete 3-bit modular-addition layout on Fin 25.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Interface lemmas (CONVENTIONS §9.1, F1): the wire assignments of the concrete layout, one lemma per field, so consumers cite these instead of unfolding the whole structure.
modAddLayout2's operand register wires.
modAddLayout2's accumulator wires.
modAddLayout2's step-1 constant-register wires.
modAddLayout2's step-3 constant-register wires.
modAddLayout2's shared ancilla wire.
Concrete input state for n = 3, N = 3: operand Aop = a (wires 0,1,2), accumulator B = b
(wires 3,4,5), A1 = 5 = 2³ − 3 (wires 10,12, bits 0 and 2), A2 = 3 (wires 17,18), all
carries / ancilla false. Parameterised by the data bits of a (wires 0,1,2) and b (wires
3,4,5).
Equations
- One or more equations did not get rendered due to their size.
Instances For
The hypotheses of modAdd_correct hold at modAddState2 (carries/ancilla clear, A1 = 5,
A2 = 3), for any data bits. The regValRange register-value preconditions are concrete sums,
discharged by decide.