The carry-clean (Cuccaro) ripple-carry adder — in-place, ancilla-restoring (ECDLP Phase 2, Stage 1) #
Category: 1-Mathlib (CSD-free; staged as a Mathlib-upstream candidate).
The single highest-leverage reversible gadget the corpus lacked: an in-place, ancilla-restoring
ripple-carry adder (Cuccaro, Draper, Kutin, Moulton 2004). Unlike rippleCirc / cRippleCirc
(which thread an explicit Θ(n)-wire carry chain C and leave its top carry dirty), the Cuccaro adder
stores the carries inside the preserved addend register during the computation and restores them,
needing only one clean ancilla Z (the low carry, init false, returned false). This collapses
the fresh-ancilla penalty that every prior ECDLP adder (ModularAdd, ModularAddCtrl,
DoublingAssembly) carried as a named residue, and brings the per-multiply Toffoli cost from ~30n²
toward ~2n².
The construction (standard Cuccaro, mod 2ⁿ, no output carry, one ancilla) #
Two three-gate blocks on (c, b, a) (carry-in, sum register, addend register):
maj c b a := [CX a b, CX a c, CCX c b a]— folds the carry-out intoa:a ← majority(a, b, c),b ← b ⊕ a,c ← c ⊕ a(the(b,c)side effects are undone byuma).uma c b a := [CCX c b a, CX a c, CX c b]— writes the sum bit and restores: applied to a post-majstate it givesb ← a ⊕ b ⊕ c(the sum),a ← a(restored),c ← c(restored).
The circuit is a forward maj chain then a backward uma chain, with the carry threading through the
addend register's wires. Equivalently — and this is the formulation proved here — it is the recursive
maj c b₀ a₀ ++ (adder on the high bits with carry-in a₀) ++ uma c b₀ a₀, which makes the
compute / uncompute correctness a clean induction on the bit count (cuccaroRec_correct).
What is proved here #
maj/umawith per-block all-inputsdecidecorrectness (maj_correct/uma_correcton the concreteState 3) and the generalFin mlifts (maj_correct_general/uma_correct_general).cuccaroAdd_correct— generaln, in-place sum: registerAends holding(A + B) mod 2ⁿ.cuccaroAdd_preserves_B— registerB(the addend) is returned untouched.cuccaroAdd_ancilla_clean— the carry-clean property: the ancillaZreturns tofalse, so the adder is reusable in place with no fresh ancilla. This is what distinguishes it fromrippleCirc/cRippleCirc.cuccaroAdd_toffoli— derived2nToffolis (nmaj+numa, each oneCCX).
Scope (honest): this is the value-correct, in-place, ancilla-restoring adder modulo 2ⁿ. It does
no modular (mod N) reduction — a carry-clean modular adder / multiply is Stage 2. The n=3
witness (cuccaroLayout3, 5 + 6 mod 8 = 3) is #eval/decide-cross-checked through the strict
Array evaluator runArr.
Arithmetic helpers #
The carry-propagation modular identity: a low bit plus twice a value, reduced mod 2P, is the low
bit plus twice the value reduced mod P. The arithmetic core of the per-slice carry step.
The MAJ / UMA blocks #
The maj (majority) block on wires c b a (carry-in, sum register, addend register):
[CX a b, CX a c, CCX c b a]. Folds the carry-out into a.
Equations
- Reversible.maj c b a = [Reversible.Gate.CX a b, Reversible.Gate.CX a c, Reversible.Gate.CCX c b a]
Instances For
The uma (un-majority-and-add) block on wires c b a: [CCX c b a, CX a c, CX c b]. Applied to a
post-maj state, writes the sum into b and restores a, c.
Equations
- Reversible.uma c b a = [Reversible.Gate.CCX c b a, Reversible.Gate.CX a c, Reversible.Gate.CX c b]
Instances For
uma correctness — all-inputs decide. On State 3 (wires 0,1,2 = c,b,a), the raw gate
action: a ← a ⊕ (c ∧ b), then c ← c ⊕ a', then b ← b ⊕ c'.
uma correctness, general Fin m wires. For pairwise-distinct c, b, a, the raw gate action
(in terms of the input bits). Composed with the post-maj relation it yields the sum / restoration.
Derived block costs #
The Cuccaro layout and recursive circuit #
A Cuccaro-adder layout on m wires for n-bit registers: the sum register A (overwritten with
the sum), the addend register B (preserved; also holds the carry chain internally during the run),
and a single clean ancilla Z (the low carry, init/returned false). The two register images are
pairwise disjoint, disjoint from Z, and each injective on its used index range.
Wires of the sum register
A(overwritten with(A + B) mod 2ⁿ).Wires of the addend register
B(preserved; carries threaded through and restored).- Z : Fin m
The single clean ancilla (low carry; init/returned
false).
Instances For
The recursive Cuccaro adder on the len bits start .. start + len - 1 with carry-in wire
carry: maj carry A_start B_start ++ (recurse on the high bits, carry-in B_start) ++ uma carry A_start B_start. The carry-out of the low slice (majority, stored in B start) is the
carry-in of the recursive remainder; the outer uma writes the low sum bit and restores.
Equations
- Reversible.cuccaroRec L x✝¹ x✝ 0 = []
- Reversible.cuccaroRec L x✝¹ x✝ len.succ = Reversible.maj x✝¹ (L.A x✝) (L.B x✝) ++ Reversible.cuccaroRec L (L.B x✝) (x✝ + 1) len ++ Reversible.uma x✝¹ (L.A x✝) (L.B x✝)
Instances For
The full Cuccaro adder: all n bits, carry-in the ancilla Z.
Equations
- Reversible.cuccaroAdd L = Reversible.cuccaroRec L L.Z 0 n
Instances For
Recursive correctness invariant #
The Cuccaro carry-chain invariant. For the len-bit recursive adder with carry-in carry
(disjoint from the register range): register A ends holding (carry + A + B) mod 2^len (P1);
register B is restored (P2); the carry-in wire is restored (P3); any external wire is preserved (P4).
By induction on len, peeling one maj/uma pair per step.
Headline theorems #
Cuccaro adder correctness (in-place sum). For a disjoint-wire layout with the ancilla Z
initialised false, the Cuccaro adder leaves register A holding (A + B) mod 2ⁿ — in place, with no
carry chain, one ancilla.
Register B is preserved. The addend register is returned untouched (carries threaded through
it during the run are restored). The ancilla hypothesis hZ is taken for API uniformity with the other
headlines; B-preservation does not actually consume it.
The carry-clean property. The ancilla Z returns to false: the Cuccaro adder borrows the
low-carry wire clean and returns it clean, so it is reusable in place with no fresh ancilla. This is
what distinguishes it from rippleCirc / cRippleCirc (which leave the top carry dirty).
The carry-in wire is restored unconditionally. The ancilla Z is returned to its input
value for every input state (not only false): clause P3 of cuccaroRec_correct carries no
hypothesis on s Z. This is the lemma the clean subtractor (cuccaroSub) needs to pin the ancilla of
an inverse-image state. cuccaroAdd_ancilla_clean is the s Z = false specialisation.
External wires are preserved unconditionally. A wire distinct from the ancilla Z and from
every used wire of registers A, B (indices < n) is left unchanged, for every input state
(clause P4 of cuccaroRec_correct carries no s Z hypothesis). The frame lemma that lets a clean
modular adder thread several Cuccaro passes past its constant / work / flag registers.
Derived cost: 2n Toffolis #
Derived Toffoli cost: 2n. n maj blocks + n uma blocks, each exactly one CCX; the
CXs are CNOTs (zero Toffoli). The honest in-place carry-clean overhead — half the ~30n²-per-multiply
fresh-ancilla cost the corpus adders carried.
Non-vacuity witness + #eval / decide cross-check #
A concrete 3-bit layout on Fin 7 (A → {0,1,2}, B → {3,4,5}, Z → 6). The headline applies, and
the strict Array evaluator runArr (with the proven bridge regValRangeArr_eq back to denote)
witnesses 5 + 6 mod 8 = 3, the ancilla returning false, and B intact.
A concrete 3-bit Cuccaro layout on Fin 7.
Equations
- One or more equations did not get rendered due to their size.