Reversible modular subtraction — the verified value primitive (a, b) ↦ (a − b mod N, b) (ECDLP Phase 2, Stage S6.3e-1) #
Category: 1-Mathlib (CSD-free; staged as a Mathlib-upstream candidate).
This module verifies the modular-subtraction VALUE primitive a − b mod N over bit registers,
the one genuinely-missing field-operation primitive the elliptic-curve point formulas need (both the
a = 0 doubling 9X⁴ − 8XY² and the addition use field subtraction). It mirrors the verified
ModAdd development (fullAdder → rippleCirc → rippleCirc_correct) step-for-step, with a
borrow chain in place of the carry chain, and reuses the S6.3a conditional-add-back-on-a-flag
structure (cRippleCirc controlled on the borrow flag) for the modular fix.
modSub L = rippleSub L.subStep ++ cRippleCirc L.fixStep
- Subtract step
rippleSub L.subStep(minuend registerBholdsa, subtrahendSubholdsbread-only, fresh borrow chainBor):rippleSub_correctwritesB ← (a − b) mod 2ⁿand the borrow-out wireBor nbecomes the comparison flagdecide (a < b)(rippleSub_borrowout), preservingSub = b. - Modular fix
cRippleCirc L.fixStep(Nregpreset toN, sameB, fresh carry chainC, control = the borrow flagBor n, fresh ancillaanc): conditional add-back ofN, gated on the borrowa < b— exactly the S6.3a controlled-add structure. The borrow flag is already thea < bpredicate (noX-flip needed, unlikemodReducewhere the carry-out isN ≤ xand must be flipped).
The two branches (verified in modSub_correct):
a ≥ b(borrow clear): subtract step value(a − b) mod 2ⁿ = a − b, no add-back;a − b < Nsincea < N. SoB = a − b = (a + N − b) mod N.a < b(borrow set): subtract step value(a − b) mod 2ⁿ = a + 2ⁿ − b(no underflow wrap into range,a − bis computed mod2ⁿ); then+ N mod 2ⁿ = (a + 2ⁿ − b + N) mod 2ⁿ. Sincea − b + N < N ≤ 2ⁿ(asa < b ⇒ a + N − b < N),a + 2ⁿ − b + N = 2ⁿ + (a + N − b)wraps toa + N − b = (a + N − b) mod N.
The headline is stated as (a + N − b) % N to stay in ℕ-truncated-subtraction-safe form: for
a, b < N this is exactly the integer (a − b) mod N (both branches above land on it).
The subtractor (route (i): fullSub borrow chain, mirroring fullAdder) #
fullSub mw sw bin bout := [X mw] ++ fullAdder sw mw bin bout ++ [X mw]: borrow-subtract is
"invert minuend, add, invert result". The full adder on (sw, mw, bin, bout) after flipping mw
computes into mw the bit sw ⊕ ¬mw ⊕ bin = ¬(mw ⊕ sw ⊕ bin) and into bout the carry
majority(sw, ¬mw, bin) = majority(¬mw, sw, bin) = bout (the borrow-out), so a final X mw yields
diff = mw ⊕ sw ⊕ bin. Verified by decide over all inputs (fullSub_correct), exactly like
fullAdder_correct.
Carve line (what this is, and is NOT) #
This is the value-correct modular-subtraction atom in the fresh-ancilla model. ℕ / mod N bit
arithmetic; NO field / group semantics are in play here. It is a field-operation PRIMITIVE: the
full SLP → circuit assembly of the EC point operation (routing all field ops — add / sub / mul /
double / const-mult — and deriving M as an exhibited-circuit count) is the later S6.3e stages;
nsmul (const-mult) / neg compose from {modAdd, modDouble, modSub}. This module does NOT claim
the point operation.
Named residue (same fresh-ancilla model as modAdd): the borrow chain Bor, the comparison
flag Bor n, and the fix-step carry chain C are left dirty after modSub; correctness holds
because the layout supplies fresh wires per use (modSub_correct requires Bor / C / anc
initialised false). In-place reuse across many subtractions needs carry/borrow-clean adders —
which rippleCirc / rippleSub / cRippleCirc do NOT provide. That is the genuine remaining
cleanup work, NOT built here.
Honest cost #
modSub_toffoli derives 10n Toffolis from the exhibited gate list: subtract step 2n (rippleSub,
two Toffolis per fullSub slice — the two X mw framing gates are free) + fix step 8n
(cRippleCirc_toffoli, the controlled add-back), composed through cost_comp_toffoli_count. Same
10n as the single-step modReduce (S6.3a): a verified compare-and-conditional-add.
The full-subtractor gadget (the verified primitive) #
The six-gate full subtractor on wires mw sw bin bout (with bout initialised false):
mw ← mw ⊕ sw ⊕ bin (difference bit, in place into the minuend wire mw),
bout ← majority(¬mw, sw, bin) (borrow-out), sw/bin unchanged.
Borrow-subtract is "invert the minuend, add, invert the result": fullSub flips mw, runs
fullAdder sw mw bin bout (which writes the sum sw ⊕ ¬mw ⊕ bin into mw and the carry
majority(sw, ¬mw, bin) into bout), then flips mw back. Correctness on the concrete layout is
fullSub_correct.
Equations
- Reversible.fullSub mw sw bin bout = [Reversible.Gate.X mw] ++ Reversible.fullAdder sw mw bin bout ++ [Reversible.Gate.X mw]
Instances For
Full-subtractor correctness — genuine all-inputs coverage. On the concrete State 4 layout
(wires 0,1,2,3 = mw, sw, bin, bout), with bout initialised false, the gadget computes the
difference bit on mw (wire 0), the borrow-out on bout (wire 3), and preserves sw (wire 1)
and bin (wire 2). Proved by decide over the finite State 4 (16 inputs, each with
s 3 = false).
Frame lemma for the gadget. A wire distinct from all four of mw, sw, bin, bout is untouched
by fullSub (every gate's wires lie in {mw, sw, bin, bout}). Lets the borrow chain lift the slice
over a register.
Full-subtractor correctness, general Fin n wires. For pairwise-distinct wires
mw, sw, bin, bout with bout initialised false, the gadget writes the difference bit to mw,
the borrow-out to bout, and preserves sw and bin — over arbitrary Fin n (not just the concrete
State 4 of fullSub_correct). This is the slice the borrow chain iterates.
Derived cost of the gadget #
Derived cost of the full subtractor (from the gate list, via circuitCost): two Toffolis,
two CNOTs — the same as fullAdder (the two X framing gates are free). Read off
[X, CCX, CX, CCX, CX, X].
The full-subtractor arithmetic identity on ℕ #
The full-subtractor arithmetic identity on ℕ. The minuend bit plus twice the borrow-out
equals the difference bit plus the subtrahend bit plus the borrow-in — the per-slice borrow fact the
chain accumulates (the subtraction analogue of fulladder_nat).
The borrow chain (general n): B ← (a − b) mod 2ⁿ, borrow flag = a < b #
A SubLayout lays out the minuend register B (overwritten with (a − b) mod 2ⁿ), the subtrahend
register Sub (read-only b), and a borrow chain Bor (Bor 0 the input borrow, Bor n the
output borrow = the comparison flag). Pairwise disjoint, bounded-injective — exactly the
RippleLayout discipline.
A borrow-chain subtractor layout on m wires for n-bit registers: minuend register B
(overwritten with the difference), subtrahend Sub (read-only), and a borrow chain Bor. The three
images are pairwise disjoint and each is injective on its used index range. The injectivity fields are
bounded (< n for registers, < n + 1 for the borrow chain) — an unbounded ℕ → Fin m
injectivity field is uninhabitable and would make the theorem vacuous.
Minuend register (holds
a, overwritten with(a − b) mod 2ⁿ).Subtrahend register (holds
b, read-only).
Instances For
The circuit of the first k borrow slices (bits 0 .. k-1).
Equations
- Reversible.subPrefix L k = List.flatMap (Reversible.subSlice L) (List.range k)
Instances For
The full borrow-chain subtractor: all n slices.
Equations
Instances For
The borrow-chain invariant. After the first k slices: register B's low k bits plus the
borrow into bit k scaled by 2^k equal the low-k minuend value plus the low-k subtrahend value
(P1, the borrow recurrence B + 2^k·borrow = a + Sub over the low k bits); the subtrahend Sub is
untouched (P2); the unprocessed high bits of B (P4) and the unset high borrows (P5) are preserved.
By induction on k, lifting fullSub_correct_general through the frame lemma fullSub_apply_of_ne.
Borrow-chain subtractor correctness. For a disjoint-wire layout with all borrows initialised
false, the ripple subtractor leaves the minuend register B holding (a − b) mod 2ⁿ, where
a = B, b = Sub. The borrow recurrence, derived from the exhibited circuit rippleSub.
The borrow-out is the comparison flag. For a disjoint-wire layout with all borrows
initialised false, the ripple subtractor's output borrow wire Bor n holds decide (a < b) — it
is set exactly when the subtraction underflows, i.e. when the minuend a is below the subtrahend
b. Read off rippleSub_invariant (clause P1) together with regValRange_lt. This is the
comparison primitive for the modular fix: add N back iff the borrow is set.
Modular subtraction: the borrow chain + conditional add-back of N #
A ModSubLayout bundles the subtract step's SubLayout (minuend B, subtrahend Sub, borrow chain
Bor) and the fix step's controlled-add sub-data (constant register Nreg preset to N, fix carry
chain C, shared ancilla anc), with the control being the borrow flag Bor n (= a < b). The fix
step is the S6.3a conditional-add-back structure (cRippleCirc), gated directly on the borrow (no
X-flip, since the borrow is already the a < b predicate that triggers the add-back).
A single-step modular-subtraction layout on Fin m for n-bit registers. Bundles:
B— the minuend (holdsa, overwritten witha − b mod N);Sub— the subtrahend (holdsb, read-only, preserved);Bor— the borrow chain;Bor nis the comparison flag (=a < b), the control of the fix;Nreg— the fix constant register (preset toN), with a fresh carry chainC;anc— the shared clean ancilla for the controlled add-back.
The fields are pure wire geometry (pairwise disjointness + per-range bounded injectivity), mirroring
the SubLayout / ModReduceLayout discipline. The injectivity fields are bounded (< n for
registers, < n + 1 for chains) — an unbounded ℕ → Fin m injectivity field is uninhabitable and
would make the theorem vacuous.
Minuend register: holds
a, overwritten witha − b mod N.Subtrahend register: holds
b, read-only (preserved).Borrow chain;
Bor nis the comparison flag (=a < b).Fix-step constant register (preset to
N).Fix-step carry chain (distinct from the borrow chain).
- anc : Fin m
Shared clean ancilla for the controlled add-back.
Instances For
The modular-subtraction circuit. Borrow-chain subtractor (rippleSub, producing the a < b
borrow flag) followed by the S6.3a controlled add-back of N gated on the flag (cRippleCirc).
Equations
Instances For
Frame: fix-step inputs survive the subtract step #
rippleSub L.subStep touches only the wires {B k, Sub k, Bor k} (the slices are
fullSub (B k) (Sub k) (Bor k) (Bor (k+1))). The fix step's Nreg, C, and anc are disjoint from
those, so their values pass through the subtract step unchanged.
Generic subtract-step frame. A wire w with w ≠ B k, w ≠ Sub k, w ≠ Bor k for all k
is left unchanged by rippleSub L.subStep.
Frame: the subtrahend Sub survives the fix step #
cRippleCirc L.fixStep touches only {Nreg, B, C, anc, Bor n}. Since Sub is disjoint from all of
these, the subtrahend passes through the fix step unchanged.
Subtrahend frame through the fix step. Sub j is untouched by cRippleCirc L.fixStep.
Value correctness, both branches #
The complete single-step modular subtraction — both branches, verified from the exhibited
circuit. For a disjoint-wire ModSubLayout with the borrow chain Bor, the fix carry chain C,
and the ancilla anc all initialised false, the constant register Nreg preset to N, minuend
B holding a, subtrahend Sub holding b, with a < N, b < N, N ≤ 2ⁿ: the circuit
modSub L leaves B holding (a + N − b) mod N (= a − b mod N).
Proof. The subtract step (rippleSub_correct) writes (a + 2ⁿ − b) mod 2ⁿ to B and sets the
borrow flag Bor n = decide (a < b) (rippleSub_borrowout), preserving Sub = b and — via the
frame lemmas — Nreg = N, C = false, anc = false. The fix step (cRippleCirc_correct) adds N
back iff the flag is set. The two branches:
a ≥ b(flag clear): subtract value(a + 2ⁿ − b) mod 2ⁿ = a − b(b ≤ a < 2ⁿ); no add-back, anda − b < N(a < N), soB = a − b = (a + N − b) mod N.a < b(flag set): subtract value(a + 2ⁿ − b) mod 2ⁿ = a + 2ⁿ − b(no wrap,a + 2ⁿ − b < 2ⁿ), then+ N mod 2ⁿ = (a + 2ⁿ + N − b) mod 2ⁿ = a + N − b(a + N − b < N ≤ 2ⁿ), which is(a + N − b) mod N.
The subtrahend register is intact. modSub L leaves Sub holding b (read-only). The
subtract step preserves Sub (P2 of the borrow invariant) and the fix step is disjoint from Sub.
The modular-subtraction output is a genuine residue in [0, N). Corollary of
modSub_correct and Nat.mod_lt.
Derived cost #
Derived Toffoli cost of the modular subtractor: 10n Toffolis, from the exhibited gate list.
Subtract step 2n (rippleSub, two Toffolis per fullSub slice — the framing Xs are free) + fix
step 8n (cRippleCirc_toffoli, the controlled add-back), composed through cost_comp_toffoli_count.
Same 10n as the single-step modReduce (S6.3a): a verified compare-and-conditional-add.
Non-vacuity witness #
A concrete 3-bit modular-subtraction layout on Fin 25:
- minuend
B → {0,1,2}, subtrahendSub → {3,4,5}, borrow chainBor → {6,7,8,9}, - fix constant
Nreg → {10,11,12}, fix carry chainC → {13,14,15,16}, ancilla17.
(n = 3 is taken to share the modAdd witness scale; N = 5 needs N ≤ 2³ = 8.) This exhibits
that ModSubLayout is inhabited (the bounded-injectivity bundle is satisfiable), so the headlines
are not vacuously quantified. The concrete runs below subtract modulo N = 5 at fully-specified
input states, covering both branches: a = 3, b = 1 ↦ (3 − 1) mod 5 = 2 (the a ≥ b
no-wrap branch), a = 1, b = 3 ↦ (1 − 3) mod 5 = 3 (the a < b WRAP branch, the load-bearing
case), and a = b = 2 ↦ 0.
A concrete 3-bit modular-subtraction 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.
modSubLayout2's minuend wires.
modSubLayout2's subtrahend wires.
modSubLayout2's fix-step constant-register wires.
Concrete input state for n = 3, N = 5: minuend B = a (wires 0,1,2), subtrahend Sub = b
(wires 3,4,5), Nreg = 5 (wires 10,12, bits 0 and 2), all borrows / fix carries / ancilla
false. Parameterised by the data bits of a and b.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The concrete input state at a wire, as its defining case chain (interface lemma, §9.1).
The hypotheses of modSub_correct hold at modSubState2 (borrows / carries / ancilla clear,
Nreg = 5), for any data bits. The regValRange register-value preconditions are concrete sums,
discharged by decide.
Harness cross-check (runArr / regValRangeArr, audited SOUND) #
Fast Array Bool-backed runs of the full modSub circuit (the Fin 25 denote blows up under
#eval via lazy Function.update re-reads; runArr is O(gates)). Each printed number is certified
equal to the regValRange (denote …) of modSub_correct by regValRangeArr_eq. Both branches are
covered: a ≥ b (no wrap) and a < b (the load-bearing WRAP, where the borrow flag must fire the
add-back).