The carry-clean (ancilla-restoring) MODULAR adder (ECDLP Phase 2, Stage 2) #
Category: 1-Mathlib (CSD-free; staged as a Mathlib-upstream candidate).
Stage 1 (CuccaroAdd.lean) delivered the in-place, ancilla-restoring ripple-carry adder
cuccaroAdd : A ← (A + B) mod 2ⁿ (B preserved, the single ancilla Z returned false). This
module builds, on top of it, the carry-clean MODULAR adder cuccaroModAdd:
Acc ← (a + b) mod N (a, b < N, 2N ≤ 2ⁿ)
that leaves every flag / scratch / carry wire restored to its input value, so it is reusable in
place inside a multiply loop with Θ(n) qubits (versus the Θ(n²) fresh-ancilla penalty the dirty
ModularAdd.modAdd carries). This is the qubit collapse that turns the per-multiply Toffoli budget
from the fresh-ancilla ~30n² toward ~12n² (an n-fold reuse of a 12n+10-Toffoli clean add).
The construction (clean Beauregard, controlled-add-free) #
All arithmetic runs on n+1-bit registers (the extra high wire is the sign / carry-out, which a
plain Cuccaro adder does not expose, so it is materialised as register bit n). With 2N ≤ 2ⁿ, all
operands have bit n clear and a + b < 2N ≤ 2ⁿ < 2ⁿ⁺¹, so the adds never overflow n+1 bits.
Acc += B—cuccaroAdd;Acc = a + b.Acc -= Nreg—cuccaroSub(theinverseadder);Acc = a + b − N (mod 2ⁿ⁺¹).flag ^= Acc[n]— copy the sign bit:flag = [a + b < N].Mask ^= flag·Nreg—nToffolis:Mask = flag ? N : 0(the masked constant — this replaces a controlled adder, which the CCX-only gate set cannot build without C³X).Acc += Mask—cuccaroAdd;Acc = (a + b) mod N =: r.Mask ^= flag·Nreg— uncompute the mask:Mask = 0.Acc -= B—cuccaroSub; sign bitAcc[n] = [r < b] = ¬flag.flag ^= Acc[n]; X flag— uncompute the flag:flag = ¬(flag ^^ ¬flag) = 0. THE clean step.Acc += B—cuccaroAdd; restoreAcc = r.
The flag uncompute (step 8) is the Beauregard trick: for a, b < N,
(a + b mod N) < b ⟺ a + b ≥ N ⟺ ¬flag, so re-deriving the comparison against the preserved
addend b recomputes the flag and a CNOT+X clears it. This is what most formalisations stop short
of; it is what makes the adder ancilla-restoring.
What is proved (all sorry-free, foundational-triple-only) #
cuccaroSub_correct/_preserves_B/_preserves_Z/_preserves_external— the clean subtractor as theinverseCuccaro adder, characterised via the bijection (reversible_inverse_correct') and the unconditionalcuccaroAdd_preserves_Z/_external.cuccaroModAdd_correct—regValRange Acc (denote (cuccaroModAdd L) s) n = (a + b) % N.cuccaroModAdd_clean— the point: the flag wire, the carry-out wireAcc[n], everyMaskscratch wire, and the ancillaZare all restored tofalse. The adder is reusable.cuccaroModAdd_preserves_operand— the addendBis intact (andNreg = N).cuccaroModAdd_toffoli— derived12n + 10Toffolis (5 carry-out passes at2(n+1)+ two masks atn).
Scope (honest) #
This is the carry-clean modular adder (Stage 2): it makes the modular ADD reusable in Θ(n) qubits. The carry-clean modular MULTIPLY (folding this over the multiplicand bits) and the secp256k1 figure re-cost are Stage 2b / Stage 3, not built here.
Small gate-action helpers (single-wire reads of CX / X / CCX) #
A wire distinct from the target of a CX is unchanged.
The target of a non-degenerate CX reads s c ^^ s t.
A wire distinct from the target of an X is unchanged.
The target of an X is flipped.
A wire distinct from the target of a CCX is unchanged.
The top (sign) bit of a register as a comparison #
Register bit k is the ≥ 2ᵏ comparison of the low k+1 bits. Reads off
regValRange_succ (= low + bit·2ᵏ, low < 2ᵏ): the bit is set iff the k+1-bit value is ≥ 2ᵏ.
This materialises the sign / carry-out bit that the carry-clean Cuccaro adder does not expose.
The clean subtractor: cuccaroSub = inverse cuccaroAdd #
cuccaroAdd is a bijection, so its gate-reverse inverse is the exact inverse permutation
Acc ← Acc − B (mod 2ⁿ). All four facts are characterised through the bijection identity
denote (cuccaroAdd L) (denote (cuccaroSub L) s) = s (reversible_inverse_correct') plus the
unconditional forward lemmas cuccaroAdd_preserves_Z / cuccaroAdd_preserves_external.
The clean ripple-carry subtractor: the gate-reverse of the Cuccaro adder.
Equations
Instances For
The subtractor restores the ancilla Z to its input value, unconditionally.
The clean subtractor is value-correct. For s Z = false, register A ends holding
(A − B) mod 2ⁿ (in ℕ-truncation-safe form (A + 2ⁿ − B) mod 2ⁿ), in place, with Z restored.
Cost of the subtractor: 2n Toffolis (the gate-reverse of cuccaroAdd).
The modular-adder layout #
A carry-clean modular-adder layout on m wires for n-bit operands.
Accumulator / result register (
n+1wires; bitnis the sign / carry-out, kept clean).Addend register (holds
b, preserved).Constant register (preset to
N, preserved).Work register for the masked constant (init/returned
0).- flag : Fin m
Comparison flag (init/returned
false). - Z : Fin m
Cuccaro ancilla (init/returned
false).
Instances For
The first k masked-copy gates: CCX flag Nreg[i] Mask[i] for i < k.
Equations
- Reversible.maskCopyPrefix L k = List.map (fun (i : ℕ) => Reversible.Gate.CCX L.flag (L.Nreg i) (L.Mask i)) (List.range k)
Instances For
The modular adder #
The carry-clean modular adder. Nine stages (5 Cuccaro passes + 2 masks + 3 single gates); see the module header for the schematic.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The full nine-stage specification of the carry-clean modular adder: the value
Acc = (a + b) mod N, the flag/mask/ancilla/top-bit restorations, and the operand
preservations, in one conjunction.
Factoring note (§9.4 review, 2026-08-06). At 313 lines this is the corpus's longest
proof, and it was reviewed for stage-lemma extraction under the library-grade standard.
Verdict: already maximally factored. Every step is a one-to-three-line citation of a
named block lemma (cuccaroAdd_correct, cuccaroSub_correct, maskCopy_*, the
*_preserves_* frames); the length is the irreducible bookkeeping of nine stages × five
tracked invariants (Acc value, B/Nreg/Mask preservation, flag/ancilla state),
organised under the ===== STAGE k ===== banners. Re-packaging into per-stage invariant
records was assessed and rejected: the record statements alone would add ~150 lines of
boilerplate while every proof step already cites its named lemma — indirection without
content.
Correctness. The carry-clean modular adder leaves Acc = (a + b) mod N.
The carry-clean property — THE point. Every scratch wire is restored to false: the flag,
the carry-out / sign wire Acc[n], every Mask wire, and the Cuccaro ancilla Z. So cuccaroModAdd
is ancilla-restoring and reusable in place inside a multiply loop with Θ(n) qubits.
The addend operand is intact — low value AND top padding wires. B = b and Nreg = N
survive the whole circuit (needed for the flag uncompute), and the width-(n+1) top padding wires
B[n], Nreg[n] are restored to their input values. The top-wire conclusions are what let a
Stage-2b multiply-loop caller re-establish the hBtop : B n = false / hNtop : Nreg n = false
preconditions of the next iteration through the public API (F1 repair).
Derived cost: 12n + 10 Toffolis #
Derived Toffoli cost: 12n + 10. Five carry-out Cuccaro passes at 2(n+1) each (three adds
- two subtractors) plus the two mask gadgets at
neach; the three single gates[CX],[CX, X]are free. So5·2(n+1) + 2n = 12n + 10. The leading12nmatches the dirtyModularAdd.modAdd; the win is the qubit count (Θ(n) reusable, vs Θ(n²) fresh per add), not the Toffoli constant.
Non-vacuity witness + #eval / runArr cross-check (both branches + flag-clean) #
A concrete n = 3 layout on Fin 18: Acc → {0,1,2,3} (bit 3 = carry-out), B → {4,5,6,7},
Nreg → {8,9,10,11} (preset N = 3 on wires 8,9), Mask → {12,13,14,15}, flag → 16, Z → 17.
n = 3 is forced by 2N ≤ 2ⁿ: for N = 3 that needs 2ⁿ ≥ 6. The strict Array evaluator
runArr (with the proven bridge regValRangeArr_eq back to denote) witnesses both branches
(1+1 mod 3 = 2, the a+b<N add-back branch; 2+2 mod 3 = 1, the a+b≥N no-add-back branch) and
that the flag, the carry-out wire, and the Mask scratch all read clean afterward.
A concrete n = 3 carry-clean modular-adder layout on Fin 18.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Structural hypotheses of cuccaroModAdd_correct hold at cuccaroModState3 (clean scratch,
Nreg = 3), for any data bits.