Prime-field modular inversion

Modular inversion over a 256-bit prime, inside a complete elliptic-curve point addition. The discovered circuit uses a Euclid-like algorithm whose steps all have the same shape, and it recovers most of its own history from its registers instead of storing it.

In brief

Prior circuits either keep a compact log of every step, which costs hundreds of qubits, or pack their values so tightly that they need no log but many Toffolis. There is no single baseline.

The discovered circuit uses divsteps: each step adds, subtracts, or halves, chosen by one parity bit and the sign of a small counter. To undo a step it must know the choice. About two thirds of the choices are visible in a few register bits. A small decoder reads most of the rest from the low bits of a coefficient. Only the deepest choices go into a short stack. The circuit also stops before the worst case, and accepts failure on the rare inputs that need more steps.

Decoding deeper choices removes stored history but needs larger decoders, so one design yields a family of circuits.

Inversion strategyConstructionToffolisQubitsTQ-cost
Binary gcd and dialog trickSchrottenloher, 2026, gate-optimized1.9 × 10⁶1,4462.7 × 10⁹
Babbush et al., 2026, low-gate≤ 2.1 × 10⁶≤ 1,425≤ 3.0 × 10⁹
Schrottenloher, 2026, space-optimized2.4 × 10⁶1,1922.9 × 10⁹
Babbush et al., 2026, low-qubit≤ 2.7 × 10⁶≤ 1,175≤ 3.2 × 10⁹
Divsteps with decodingDivstep, wide registers3.3 × 10⁶1,1913.9 × 10⁹
Divstep, extra adder workspace3.6 × 10⁶1,0313.8 × 10⁹
Divstep circuit4.0 × 10⁶9353.7 × 10⁹
Divstep, decoding cutoff 54.3 × 10⁶9274.0 × 10⁹
Divstep, decoding cutoff 64.4 × 10⁶9134.0 × 10⁹
Divstep, decoding cutoff 74.6 × 10⁶9044.2 × 10⁹
Divstep, decoding cutoff 85.3 × 10⁶8974.8 × 10⁹
Divstep, decoding cutoff 97.9 × 10⁶8937.0 × 10⁹
Packed Euclidean walkLuo et al., 20267.0 × 10⁷8385.8 × 10¹⁰
Specialized packed walkSpecialized Euclidean circuit2.1 × 10⁷8431.7 × 10¹⁰
Babbush et al. report upper bounds.
1

Registers and setup

init

The point P arrives in two 256-qubit registers, x1 and y1. The fixed point Q arrives as classical bits, and point_cbspecs turns them into the constants that the later modular additions use. That work is classical and costs no Toffolis.

The circuit allocates one more 256-qubit word, A, and a multiplier machine whose pool of workspace qubits is resized phase by phase. HmrDivstepInv binds the inversion to x1: during each walk, the integer g and the coefficient fv live on the x1 word.

x1, y1, ox, oy = LC.declare_point_io(b, n)
# per-shot cbspecs (free classical networks of the addend bits)
S_x2, S_y2, S_3x2 = LC.point_cbspecs(b, ox, oy, p, n)
A = b.qubits(n)
mm = E.MulMachine(b)
singles, ma = mm.singles, mm.ma
b.claim("io/x1", x1)
b.claim("io/y1", y1)
b.claim("A", A)
mm.claim()
inv = E.HmrDivstepInv(b, x1)
2

Subtract Q

s1-const-subs

Both coordinates move relative to Q: x1 becomes Px − Qx and y1 becomes Py − Qy, modulo p. Each subtraction is a comparison, two controlled constant additions, and a second comparison that clears the flag qubit.

The slope λ = (Py − Qy)(Px − Qx)−1 is now the quotient of y1 by x1.

Subtract Q 16,320 Toffolis · peak 775 qubits
with b.phase("s1-const-subs"):
LC.const_modp(b, x1, S_x2, singles["flag"], y1, singles["clean3"], p, n, inv=True)
LC.const_modp(b, y1, S_y2, singles["flag"], x1, singles["clean3"], p, n, inv=True)
3

Forward walk

s2-walkA

The walk starts from (f, g) = (p, x1) with coefficients fv = 0 and gv = 1, and then runs 533 divsteps. g overwrites x1 in place, and f and gv fill the empty word A. As f and g shrink, fv and gv grow into the positions they vacate.

The multiplier pool parks at width zero (mm.off()), because the walk allocates its own scratch qubits. Every step has the same gates; only the control values differ between inputs.

2560x1gfv2560Afgv0100200300400500zero test from step 499divstep t (of 533) →bits
Widths of the four walk values over the 533 steps. As g and f shrink, fv and gv take over the bits they free. Only the few dozen bits above the dashed line are new wires.
Walk 1, forward 615,953 Toffolis · peak 935 qubits
with b.phase("s2-walkA"):
mm.off()
inv.start(A, "s2-walkA", "s2-walkB")
inv.walk.emit_fwd()
4

One forward step: update, then store or measure

HmrWalk.step_fwd

The swap bit s is (z ≥ 0) AND (g odd). Under s, the step swaps f with g and fv with gv, and replaces z by −z − 1. The branch bit b copies the low bit of g. Under b, controlled ripple adders add f into g and fv into gv, with a subtraction instead when the step also swaps. Then z grows by one, g halves, and fv doubles. After the first 498 steps, a zero test on g sets the frozen flag and writes the step offset into tst.

The step then disposes of b. The class flag c3 marks live nonswap steps with z ≤ 0 after the update. These are the steps whose b the updated registers do not show directly. The level flags read the trailing zero bits of fv: j2, j3 and j4 mark depths two to four, and j5p marks depth five or more.

Only a j5p step pushes b onto the record R. At every other step, the reverse walk can rebuild b, so the step measures b in the X basis. The measurement frees the qubit and leaves a phase, which the reverse step corrects using the stored outcome. s needs no measurement: it clears against bit 1 of fv, since a swap leaves fv with exactly one trailing zero bit.

def emit_fwd(self):
for t in range(self.S):
self.step_fwd(t)
⋯ 438 lines
def step_fwd(self, t):
b = self.b
g = G(b)
with b.phase(self.phase_fwd):
s, bw = self._fwd_add(g, t)
# F5 z += 1 on live lanes (pre-freeze: every lane is live)
early = t < self.Tmin
self._z_step(g, early)
self._fwd_shift_exit(t, early)
# rider stage: armed on every step of the data window
# [Tmin, S). A lane's alive steps are [0, tstop), so
# armed AND alive covers exactly the tstop - Tmin
# data-bearing stages — the walk itself unary-decodes the
# count; a lane still live at the schedule cut fails
# through the cut's own priced channel. Mirror slot:
# between R13 and R14 on the rev leg.
if self.rider is not None and self.rider["legs"].get("fwd") and self.Tmin <= t:
self._rider_stage(self.rider["legs"]["fwd"])
# F7 freeze detect + flips (blocked zero test: the zf
# witness lives through F16, coexisting with the decode
# transients — a plain chain's trail there pushes the
# armed-window census past the walk peak). tst stores the
# freeze index OFFSET t+1-Tmin (values 1..S-Tmin; 0 =
# never fired).
if not early:
# block=16: R13's small-block argument, bounded below
# by the keeper count — unlike R13's witness, zf's
# keeper ladder lives through the decode (F16 releases
# it), so extra keepers land on the decode apex, the
# arena's binding moment. 16 halves the chain apex at
# the widest armed rows (WG <= ~24) without adding a
# keeper; the ztest Toffoli count is block-invariant.
zf, zfk = g.ztest_blocked(self.g.ids + [self.g.sign], block=16)
fire = g.andp(zf, self.frz, pol=(True, False))
b.cx(fire, self.frz)
for i in range(self.Wt):
if ((t + 1 - self.Tmin) >> i) & 1:
b.cx(fire, self.tst[i])
# F8 class flags; frz already flipped, so c3 reads the NEW
# frozen state: a lane that freezes at this step pushes no
# record. Pre-freeze, frz is a constant 0 and drops from the
# chain. Armed steps keeperize the z ztest (one block >=
# Wz): the chain trail would otherwise sit through the
# decode apex — the arena's binding moment — at Wz-1 live
# wires vs the keeper's 1; the F14 recompute is the
# uncompute price (Wz-2 Toffoli per test).
if early:
znz, znzt = g.zero_test(list(self.z))
else:
znz, znzk = g.ztest_blocked(list(self.z), block=self.Wz)
zpos = g.andp(self.z[self.Wz - 1], znz, pol=(False, False))
if early:
c3, c3t = g.and_chain([s, zpos], [False, False])
else:
c3, c3t = g.and_chain([self.frz, s, zpos], [False, False, False])
# F9 j-flags
jf = self._jflags(g, c3)
# (F11 absent: the forward leg never READS the decoded
# bit — bw carries the branch and the record port stores
# it. The j flags remain: F13's store port reads the
# ladder's last rung.)
# F13 R push: brigade rotation up, then port. The span
# clips at t: fill before step t's push is at most the
# number of prior class-3 steps <= t (a theorem).
self._r_fit(t)
sp = min(self.spans[t], t)
for i in range(sp - 1, -1, -1):
b.cswap(jf["j5p"], self.R[i], self.R[i + 1])
b.cswap(jf["j5p"], bw, self.R[0])
# F14 flag/test deaths (no forward decode to kill)
self._jflags_release(g, jf, c3)
g.release_chain(c3t)
g.andm(zpos, self.z[self.Wz - 1], znz, pol=(False, False))
if early:
g.release_chain(znzt)
else:
g.ztest_free(znzk)
# F15 s death; armed steps then run F16
self._s_death(g, s, None if early else fire)
if not early:
# F16 fire death: witness [tst == t+1-Tmin] & (g == 0)
# & frz
eqt, eqtt = g.eq_const(self.tst, t + 1 - self.Tmin)
w1 = g.andp(eqt, zf)
# fire holds w1 & frz — free retirement
g.andm(fire, w1, self.frz)
g.andm(w1, eqt, zf)
g.release_chain(eqtt)
g.ztest_free(zfk)
# F17 b death: HMR; the outcome-conditioned fixup lands on
# the reverse leg's b' at the mirrored step
self.o_fwd[t] = b.hmr(bw)
b.recycle(bw)
⋯
def _fwd_add(self, g, t):
"""Run the forward step entry through F4: arm at t = Tmin, pad
the pairs, mint the sigma root, swap the rows, mint the branch
bit and run the controlled row adds. Returns (s, bw).
"""
b = self.b
sc = self.sched
if t == self.Tmin:
self._arm_alloc()
# width entry: pair pads for the sigma swap
Wm = sc.fg_pair(t)
Vm = sc.v_pair(t)
self.f.grow_to(Wm)
self.g.grow_to(Wm)
self.fv.grow_to(Vm)
self.gv.grow_to(Vm)
# F1 sigma root (frozen lanes have g[0] = 0, so s = 0)
s = g.andp(self.z[self.Wz - 1], self.g.pos(0), pol=(False, True))
# F2 conditional row swap + z negate-conjugation
for fw_, gw_ in zip(self.f.wires(), self.g.wires()):
b.cswap(s, fw_, gw_)
for fw_, gw_ in zip(self.fv.wires(), self.gv.wires()):
b.cswap(s, fw_, gw_)
for i in range(self.Wz):
b.cx(s, self.z[i])
# F3 branch-bit mint
bw = b.qubit()
b.cx(self.g.pos(0), bw)
# F4 branch arithmetic: add headroom, negate-conjugated
# controlled adds ([CDKM] cells, shared gadget)
self.g.grow_to(Wm + 1)
self.f.grow_to(Wm + 1)
self.gv.grow_to(Vm + 1)
self.fv.grow_to(Vm + 1)
u = g.andp(s, bw)
for w_ in self.g.wires():
b.cx(u, w_)
for w_ in self.gv.wires():
b.cx(u, w_)
# the add carry is a step transient (|0> outside the two
# calls), so it is not carried in the arena census
car = b.qubit()
g.ctrl_add_rows(bw, self.f.wires(), self.g.wires(), car, cin=u, qcap=self.qcap)
g.ctrl_add_rows(bw, self.fv.wires(), self.gv.wires(), car, cin=u, qcap=self.qcap)
b.recycle(car)
g.andm(u, s, bw)
return s, bw
⋯ 213 lines
def _jflags(self, g, c3):
"""Compute the j anchor flags. a1 = c3 AND ~fv[2] is the XOR c3 ^ j2
(j2 and a1 partition c3), and a2 = a1 ^ j3 likewise — both
mint by CX pairs (Clifford), not ANDs.
"""
b = self.b
j2 = g.andp(c3, self.fv.pos(2))
a1 = b.qubit()
b.cx(c3, a1)
b.cx(j2, a1)
j3 = g.andp(a1, self.fv.pos(3))
a2 = b.qubit()
b.cx(a1, a2)
b.cx(j3, a2)
j4 = g.andp(a2, self.fv.pos(4))
j5p = b.qubit()
b.cx(a2, j5p)
b.cx(j4, j5p)
return {"j2": j2, "a1": a1, "j3": j3, "a2": a2, "j4": j4, "j5p": j5p}
5

The seam: divide and measure y1

s2-seam

At the end of the walk, g = 0, f = ±1, and f·fv ≡ 2t x1−1 (mod p), where t counts the active steps. The closer clears f down to its sign qubit and cancels gv, which equals ±p and so is fixed by that sign.

row_mul adds |fv|·y1 into the empty word A and applies the 498 halvings that every input shares. sign_fix negates A when the signs of f and fv differ. A now holds λ·2t − 498.

y1 is no longer needed here. b.hmr measures each of its qubits in the X basis. This returns the word to |0⟩ and leaves a phase that depends on y1 and the outcomes mbits. The freed word becomes the new home of f and gv (rebind_band, closer_inv). Finally, arm_rider attaches a modular halving of A to the reverse walk.

Multiply by inverse 272,471 Toffolis · peak 925 qubits
with b.phase("s2-seam"):
mm.on(E.HMR_POOL_PLATEAU, seam=True)
inv.walk.closer()
inv.row_mul(y1, A, ma)
inv.sign_fix(A, ma)
mbits = [b.hmr(q) for q in y1]
inv.row_unpark(y1, ma)
inv.walk.pool.rebind_band("fw", list(y1))
inv.walk.closer_inv()
inv.arm_rider(A, {"rev": "halve"})
mm.off()
6

Reverse walk

s2-walkB

The reverse walk undoes the 533 forward steps in the opposite order. It restores g to x1 and returns f, fv, gv and the record to zero; finish then frees the walk registers.

From step 499 on, each active reverse step also halves A modulo p. These t − 498 halvings remove the remaining power of two, and no register ever holds t. A now holds λ exactly.

Walk 1, reverse 637,032 Toffolis · peak 935 qubits
with b.phase("s2-walkB"):
inv.walk.emit_rev()
inv.walk.rider = None
inv.finish()
7

One reverse step: recover b, then undo

HmrWalk.step_rev

To undo a step, the reverse step needs s and b, and the forward step kept neither. First, a comparison of tst with the step offset clears the frozen flag at the step where the walk froze. The swap bit is bit 1 of fv: a swap leaves fv with exactly one trailing zero, and every other step leaves at least two. The same class and level flags as in the forward step classify the step.

A step at depth five or more pops b from the record. At depths two to four, _decode evaluates the rule b = bitj−1(gv′·q−1 mod 2j), where fv′ = 2jq with q odd. For the prime p, the circuit writes this rule as an XOR of products of a few bits of f, fv and gv. At depth two it is gv′[1] ⊕ fv′[3]. At every other step b = s, with one exception: a nonswap at which the walk just froze had g odd, which the assembly of b adds.

With b rebuilt, the step corrects the phase of the forward measurement. It erases the decoder output by another X-basis measurement with its own phase fixups (_pred_death). Then it undoes the shift, the counter increment and the addition. The restored g holds b as its low bit, so one CNOT clears b. The step then undoes the swap. Decoders therefore appear only in reverse steps.

def emit_rev(self):
self._replay[0] = True
for t in range(self.S - 1, -1, -1):
self.step_rev(t)
⋯ 528 lines
def step_rev(self, t):
b = self.b
g = G(b)
with b.phase(self.phase_rev):
# entry widths = fwd step t's exit windows
self._r_fit(t)
# R1 unfreeze comparator; its AND trail releases BEFORE the
# controlled tst fan changes the trail's literals. Pre-freeze
# steps carry no unfreeze machinery (tst < Tmin+1 is
# unreachable).
early = t < self.Tmin
if not early:
eqt, eqtt = g.eq_const(self.tst, t + 1 - self.Tmin)
fireR = g.andp(eqt, self.frz)
g.release_chain(eqtt)
b.cx(fireR, self.frz)
for i in range(self.Wt):
if ((t + 1 - self.Tmin) >> i) & 1:
b.cx(fireR, self.tst[i])
# R2 sigma witness from the post-state carrier (pre-freeze
# the ~frz literal is constant-1: a plain copy)
if early:
sp_ = b.qubit()
b.cx(self.fv.pos(1), sp_)
else:
sp_ = g.andp(self.frz, self.fv.pos(1), pol=(False, True))
# R3 class flags; the extra ~fireR literal mirrors F8: the
# step at which a lane froze pops no record. Armed steps
# keeperize the z ztest exactly as F8 (the trail would sit
# through the R6 decode apex); R12 recomputes at release.
if early:
znz, znzt = g.zero_test(list(self.z))
else:
znz, znzk = g.ztest_blocked(list(self.z), block=self.Wz)
zpos = g.andp(self.z[self.Wz - 1], znz, pol=(False, False))
if early:
c3, c3t = g.and_chain([sp_, zpos], [False, False])
else:
c3, c3t = g.and_chain([self.frz, sp_, zpos, fireR], [False, False, False, False])
# R4 j-flags
jf = self._jflags(g, c3)
# R5 R pop: port out first, then brigade rotation down
bpw = b.qubit()
b.cswap(jf["j5p"], self.R[0], bpw)
sp = min(self.spans[t], t)
for i in range(sp):
b.cswap(jf["j5p"], self.R[i], self.R[i + 1])
# R6 decode
pred = b.qubit()
anchors, undo = self._decode(g, jf, pred)
# R7 b' assembly: pred ^ s' ^ (fireR & ~s'); the unfreeze
# term is gated by the sigma witness. Pre-freeze there
# is no unfreeze term.
b.cx(pred, bpw)
b.cx(sp_, bpw)
if not early:
fnos = g.andp(fireR, sp_, pol=(True, False))
b.cx(fnos, bpw)
g.andm(fnos, fireR, sp_, pol=(True, False))
# (R8-R10 mismatch-safety machinery absent — see F11.)
# R11 deferred forward-b fixup: demolished value = b' & ~j5p
b.x(jf["j5p"])
b.cz(bpw, jf["j5p"], cond=self.o_fwd[t])
b.x(jf["j5p"])
# R12 decode deaths BEFORE un-run changes the anchors the
# decode read
self._pred_death(pred, anchors)
self._decode_release(g, undo)
self._jflags_release(g, jf, c3)
g.release_chain(c3t)
g.andm(zpos, self.z[self.Wz - 1], znz, pol=(False, False))
if early:
g.release_chain(znzt)
else:
g.ztest_free(znzk)
# R13 fireR death (g still post-state; g == 0 is absorbing,
# so (g == 0) & ~frz witnesses exactly a just-fired lane)
if not early:
# block=8: the armed-window row is narrow (WG <= ~26),
# so small blocks cut the ladder apex (~25 -> ~12 live
# transients) at the arena's binding moment; the ztest
# Toffoli count is block-invariant.
zfr, zfrk = g.ztest_blocked(self.g.ids + [self.g.sign], block=8)
# fireR holds zfr & ~frz — free retirement (before
# ztest_free so zfr is live)
g.andm(fireR, zfr, self.frz, pol=(True, False))
g.ztest_free(zfrk)
# rider stage (mirror slot of the fwd leg's; same
# [Tmin, S) arm; runs before the repack inverse, so it
# sees the same post-repack row state the fwd rider saw)
if self.rider is not None and self.rider["legs"].get("rev") and self.Tmin <= t:
self._rider_stage(self.rider["legs"]["rev"])
self._repack_inv()
# R14 un-run: un-shifts, z decrement, un-adds, at the fwd
# step's grown widths (the mirror law: _rev_unshift)
self._rev_unshift(t, early)
self._z_step(g, early, dec=True)
self._rev_unadd(g, t, sp_, bpw, early)
⋯
def _decode(self, g, jf, pred):
"""Decode payload. Identical cubes across strata mint their
AND chain ONCE (keyed by base wire + cube); every payload
hangs off the shared leaf. Anchors stay per-payload.
"""
b = self.b
anchors = []
undo = []
cache = {}
b.ccx(jf["j2"], self.gv.pos(1), pred)
b.ccx(jf["j2"], self.fv.pos(3), pred)
anchors += [(jf["j2"], self.gv.pos(1)), (jf["j2"], self.fv.pos(3))]
for j in (3, 4):
base = jf["j3"] if j == 3 else jf["j4"]
for cubes, term in self.grouped[j]:
for cube in cubes:
key = (base, tuple(sorted(cube.items())))
if key in cache:
leaf = cache[key]
else:
ks = sorted(cube)
wires = [base] + [self.f.pos(1 + v) for v in ks]
pols = [True] + [bool(cube[v]) for v in ks]
if len(wires) == 1:
leaf, trail = base, None
else:
leaf, trail = g.and_chain(wires, pols)
cache[key] = leaf
undo.append(trail)
if term[0] == "const":
b.cx(leaf, pred)
anchors.append((leaf,))
else:
kind, i = term
w = self.gv.pos(j - 1 + i) if kind == "ag" else self.fv.pos(j + i)
b.ccx(leaf, w, pred)
anchors.append((leaf, w))
return anchors, undo
⋯ 6 lines
def _pred_death(self, pred, anchors):
b = self.b
m = b.hmr(pred)
for a in anchors:
if len(a) == 1:
b.z(a[0], cond=m)
else:
b.cz(a[0], a[1], cond=m)
b.recycle(pred)
8

Repair the phase and move λ

s2-recompute

The measurement of y1 left a phase, and λ sits in A instead of y1. With x1 and A = λ in hand, one multiplication recomputes y1 = x1·λ, which is the measured value Py − Qy.

A Z gate on each qubit whose outcome is 1 cancels the phase. The inverse multiplication clears y1 again, and a swap moves λ into y1. The early measurement freed the y1 word for the reverse walk, at the cost of these two multiplications, 383,522 Toffolis.

Phase repair 383,522 Toffolis · peak 935 qubits
with b.phase("s2-recompute"):
mm.on(E._hmr_pool_off())
E.mul_modp(b, x1, A, y1, p, n, ma) # y1 = x1*A = y exact
for q, m in zip(y1, mbits):
b.z(q, cond=m) # Z^b phase fixup
E.mul_modp(b, x1, A, y1, p, n, ma, inv=True)
for yq, aq in zip(y1, A):
b.swap(yq, aq)
9

Subtract λ2 without storing it

s4-sub-squ

Now x1 = Px − Qx and y1 = λ. The new x-coordinate needs λ2, but the circuit never holds it in a register. The word A is idle, so the circuit releases it to make room for the carries of the square.

_subtract_square then removes λ2 from x1 one term at a time. A constant addition adds 3Qx, which leaves x1 = Px + 2Qx − λ2. Finally the circuit allocates A again.

x update 64,540 Toffolis · peak 935 qubits
with b.phase("s4-sub-squ"):
mm.pool_width(0)
b.release_recycle("A", A)
_subtract_square(b, y1, x1)
b.set_phase("s4-sub-squ")
LC.const_modp(b, x1, S_3x2, singles["flag"], y1, singles["clean3"], p, n)
A[:] = b.qubits(n)
b.claim("A", A)
mm.pool_width(E._hmr_pool_off())
10

The fused square

FusedSquare

Write λ = λ0 + Bλ1 with B = 2128 (the two halves). Since B2 = 2256 ≡ F = 232 + 977 (mod p), λ2 ≡ (1 − B)λ02 + (F − B)λ12 + B(λ0 + λ1)2. These are three squares of half-width values. cross_term forms λ0 + λ1 in place on λ1 plus one spill qubit and subtracts λ0 again afterwards.

For each term, _term builds the square in a fresh product register, one row addition per bit. It adds the product into x1 at the shifts and signs of its factor, and runs the rows backwards to clear the product before the next term starts. _fold sends bits at or above bit 256 back in at the signed digits of F.

Each addition lets its carry run only a margin of bits past its slice (_short). A carry that would travel further is dropped, which is a rare failure. _square_margin picks the smallest margin that keeps the summed failure probability within the share of the failure budget assigned to the square. A slice that ends at bit 255 needs a reduction: _modular adds F when the carry out is set. It then erases that carry by measurement and repairs the phase with a comparison of the top bits.

def _subtract_square(b, lam, out):
"""out -= lam^2 mod p with the engine's FusedSquare: the low, high and
cross squares each fold into out and clear before the next starts.
"""
sq = E.FusedSquare(b, lam, out, QCAP)
b.set_phase("squ-a-lo")
sq.low_term()
b.set_phase("squ-b-hi")
sq.high_term()
b.set_phase("squ-c-sum")
sq.cross_term()
sq.close()
⋯
class FusedSquare:
"""out -= lam^2 mod p in three square terms (low, high, cross).
Call low_term(), high_term() and cross_term() in any order, then
close(). `cap` is the arena ceiling the component must not exceed.
"""
def __init__(self, b, lam, out, cap):
assert len(lam) == N and len(out) == N
self.b, self.out, self.cap = b, list(out), int(cap)
self.l0, self.l1 = list(lam[:SQ_HALF]), list(lam[SQ_HALF:])
self.margin = _square_margin()
self.one = b.qubit()
b.x(self.one)
def room(self):
return self.cap - self.b.active_qubits
# -- square of v into a fresh 2n-wire register ---------------------------
def _rows(self, v, prod, clear):
"""Build prod = v^2 - 1 from |0> (clear=False), or return it to |0>
(clear=True). Row i adds 2^m + (2 v_i - 1) R + v_i at offset 2i+1,
with R = v >> (i+1) and m = len(R): the R wires are complemented
when v_i = 0, the row's addend ends with v_i itself, and the carry-in
is 1. Row i's carries borrow the zero wires above its window.
"""
b, n = self.b, len(v)
top = prod[2 * n - 1]
order = range(n - 1)
if clear:
b.cx(v[n - 1], top)
b.x(top)
order = reversed(order)
else:
for j in range(n):
b.cx(v[j], prod[j])
b.x(prod[j])
for i in order:
ctl, rest = v[i], v[i + 1 :]
window = prod[2 * i + 1 : n + i + 2]
b.x(ctl)
for q in rest:
b.cx(ctl, q)
b.x(ctl)
if clear:
for q in window:
b.x(q)
_ripple_add(
b, window, rest + [ctl], cin=self.one, room=self.room(), spares=prod[n + i + 2 :]
)
if clear:
for q in window:
b.x(q)
b.x(ctl)
for q in rest:
b.cx(ctl, q)
b.x(ctl)
if clear:
for j in range(n):
b.x(prod[j])
b.cx(v[j], prod[j])
else:
b.cx(v[n - 1], top)
b.x(top)
# -- folding a shifted slice into out ------------------------------------
def _short(self, val, shift, sign, cin):
"""out += sign * 2^shift * (val + cin), no reduction: the slice ends
`margin` bits below bit 256 and the carry stops there.
"""
b = self.b
window = self.out[shift : shift + len(val) + self.margin]
if sign < 0:
for q in window:
b.x(q)
_ripple_add(b, window, val, cin=cin, room=self.room())
if sign < 0:
for q in window:
b.x(q)
def _modular(self, val, shift, sign, cin):
"""out += sign * 2^shift * (val + cin) mod p for a slice that ends at
bit 255: add with carry-out c, add F when c is set, then erase c
by measurement and recompute it from the top `margin` bits as a
phase ([out < slice] exactly when the add wrapped).
"""
b, out, t = self.b, self.out, self.margin
assert shift + len(val) == N
if sign < 0:
for q in out:
b.x(q)
carry = b.qubit()
_ripple_add(b, out[shift:], val, cin=cin, cout=carry, room=self.room())
fold_window = out[: SQ_FOLD.bit_length() + t]
assert self.room() >= len(fold_window) - 1, "fused square: ceiling too low"
_add_scaled_bit(b, fold_window, carry, SQ_FOLD)
m = b.hmr(carry)
b.recycle(carry)
assert self.room() >= t, "fused square: ceiling too low"
_phase_if_below(b, out[N - t :], val[len(val) - t :], m)
if sign < 0:
for q in out:
b.x(q)
def _fold(self, val, shift, sign, cin):
"""out += sign * 2^shift * (val + cin) mod p for any slice: the part
at or above bit 256 re-enters at the signed digits of F.
"""
low, high = val[: N - shift], val[N - shift :]
if _is_short(shift, len(low), self.margin):
self._short(low, shift, sign, cin)
else:
self._modular(low, shift, sign, cin)
for j, d in SQ_FOLD_DIGITS if high else ():
self._fold(high, j, sign * d, None)
# -- the three terms ------------------------------------------------------
def _term(self, v, folds):
b = self.b
prod = b.qubits(2 * len(v))
b.claim("square/product", prod)
self._rows(v, prod, clear=False)
for shift, sign in folds:
self._fold(prod, shift, sign, self.one)
self._rows(v, prod, clear=True)
b.release("square/product", prod)
for q in reversed(prod):
b.recycle(q)
def low_term(self):
"""out -= (1 - B) * l0^2."""
self._term(self.l0, _folds_low())
def high_term(self):
"""out -= (F - B) * l1^2."""
self._term(self.l1, _folds_high())
def cross_term(self):
"""out -= B * (l0 + l1)^2; the sum forms in place on l1 plus one
fresh top wire and is undone afterwards.
"""
b = self.b
spill = b.qubit()
s = self.l1 + [spill]
_ripple_add(b, s, self.l0, room=self.room())
self._term(s, _folds_cross())
for q in s:
b.x(q)
_ripple_add(b, s, self.l0, room=self.room())
for q in s:
b.x(q)
b.recycle(spill)
def close(self):
self.b.x(self.one)
self.b.recycle(self.one)
⋯ 28 lines
def _square_margin():
"""Return the truncation margin t in bits: each accumulation fails with
probability at most 2 * 2^-t (short: carry escape, wrap past p;
modular: F-window escape, top-bit tie), and the sum over all
accumulations of the three terms stays within SQ_FAIL_SHARE.
"""
t = 1
while True:
ops = 0
for width, folds in (
(2 * SQ_HALF, _folds_low()),
(2 * SQ_HALF, _folds_high()),
(2 * SQ_HALF + 2, _folds_cross()),
):
ops += sum(_fold_events(width, shift) for shift, _ in folds)
if 2 * ops * 2.0**-t <= SQ_FAIL_SHARE:
return t
t += 1
11

Second inversion: multiply λ into place

s6-mul1, s6-walkC, s6-seam, s6-walkD

The circuit now needs y1 ← x1·λ. The product forms in A (multiply); erasing λ afterwards requires λ = A/x1, so the circuit inverts x1 a second time. An X-basis measurement erases λ at once and frees the word to host f and gv.

Walk C runs forward on x1 with a rider that halves A at each active step from 499 on. At the seam, row_mul and sign_fix rebuild λ in y1 exactly. The Z fixup cancels the phase of the measurement, and the inverse calls clear y1 again.

Walk D undoes walk C, and its rider doubles A back to x1·λ. A final swap leaves y1 = x1·λ.

Multiply 191,761 Toffolis · peak 935 qubits
with b.phase("s6-mul1"):
E.mul_modp(b, x1, y1, A, p, n, ma)
Walk 2, forward 628,378 Toffolis · peak 935 qubits
with b.phase("s6-walkC"):
mbits2 = [b.hmr(q) for q in y1]
mm.off()
inv.start(y1, "s6-walkC", "s6-walkD")
inv.arm_rider(A, {"fwd": "halve", "rev": "double"})
inv.walk.emit_fwd()
Recover y 542,580 Toffolis · peak 925 qubits
with b.phase("s6-seam"):
mm.on(E.HMR_POOL_PLATEAU, seam=True)
inv.walk.closer()
inv.row_mul(A, y1, ma) # y1 = |fv|*A*2^-Tmin
inv.sign_fix(y1, ma) # y1 = y exact
for q, m in zip(y1, mbits2):
b.z(q, cond=m)
inv.sign_fix(y1, ma, inv=True)
# erase pair: the facing lifts cancel (the row parks in
# magnitude form through the sign fix + fixup, which never
# touch the row ids; the inv call carries the un-reduce +
# un-lift)
inv.row_mul(A, y1, ma, inv=True)
inv.walk.closer_inv()
mm.off()
Walk 2, reverse 637,032 Toffolis · peak 935 qubits
with b.phase("s6-walkD"):
inv.walk.emit_rev()
inv.walk.rider = None
inv.finish()
mm.on(E._hmr_pool_off())
for yq, aq in zip(y1, A):
b.swap(yq, aq)
12

Final update and teardown

s7-final, close

x1 becomes p − x1 and then gains Qx, which gives Rx = λ2 − Px − Qx. Before the negation, x1 held Qx − Rx, so y1 = λ(Qx − Rx). Subtracting Qy gives Ry.

The teardown releases A and the multiplier machine.

Final update 17,084 Toffolis · peak 935 qubits
with b.phase("s7-final"):
LC.neg_modp(b, x1, y1, singles["clean3"], p, n)
LC.const_modp(b, x1, S_x2, singles["flag"], y1, singles["clean3"], p, n)
LC.const_modp(b, y1, S_y2, singles["flag"], x1, singles["clean3"], p, n, inv=True)
# ================= teardown ========================================
Release 0 Toffolis · peak 935 qubits
b.set_phase("close")
b.release("A", A)
mm.off()
for w in reversed(list(A)):
b.recycle(w)
return x1, y1, ox, oy
Start (init): 0 Toffolis, peak 775 qubitsSubtract Q (s1-const-subs): 16,320 Toffolis, peak 775 qubitsWalk 1, forward (s2-walkA): 615,953 Toffolis, peak 935 qubitsMultiply by inverse (s2-seam): 272,471 Toffolis, peak 925 qubitsWalk 1, reverse (s2-walkB): 637,032 Toffolis, peak 935 qubitsPhase repair (s2-recompute): 383,522 Toffolis, peak 935 qubitsx update (s4-sub-squ): 64,540 Toffolis, peak 935 qubitsMultiply (s6-mul1): 191,761 Toffolis, peak 935 qubitsWalk 2, forward (s6-walkC): 628,378 Toffolis, peak 935 qubitsRecover y (s6-seam): 542,580 Toffolis, peak 925 qubitsWalk 2, reverse (s6-walkD): 637,032 Toffolis, peak 935 qubitsFinal update (s7-final): 17,084 Toffolis, peak 935 qubitsRelease (close): 0 Toffolis, peak 935 qubits935512peak live qubits per phase, above the 512 I/O qubits4.0 × 10⁶ Toffolis →
def build_circuit(b, p=P_MOD, n=N):
"""Emit the point addition |P> -> |P+Q>; Q per shot on the
Bit-kind inputs. Returns (x1, y1, ox, oy).
"""
x1, y1, ox, oy = LC.declare_point_io(b, n)
# per-shot cbspecs (free classical networks of the addend bits)
S_x2, S_y2, S_3x2 = LC.point_cbspecs(b, ox, oy, p, n)
A = b.qubits(n)
mm = E.MulMachine(b)
singles, ma = mm.singles, mm.ma
b.claim("io/x1", x1)
b.claim("io/y1", y1)
b.claim("A", A)
mm.claim()
inv = E.HmrDivstepInv(b, x1)
Subtract Q 16,320 Toffolis · peak 775 qubits
with b.phase("s1-const-subs"):
LC.const_modp(b, x1, S_x2, singles["flag"], y1, singles["clean3"], p, n, inv=True)
LC.const_modp(b, y1, S_y2, singles["flag"], x1, singles["clean3"], p, n, inv=True)
# ================= s2-idiv: y1 <- y1 / x1 ==========================
# walk-A fwd (g rides x1; f/gv host = the |0> A word) -> closer
# (fv parked signed; A word back |0>) -> plateau row-mul A =
# |fv|*y1*2^{-Tmin} + sign fix (A = y1*x1^{-1}*2^{tstop-Tmin}) ->
# HMR(y1) -> closer_inv (f/gv rehomed to the freed y1 word) ->
# walk-B rev with rider halves (A *= 2^{Tmin-tstop}) -> recompute/
# phase-fix/erase y1 -> swap(y1, A).
# The mul pool and its plan exist only at mul sites: the walk legs
# allocate their own scratch (decode chains, zero-test keepers,
# rider fold anc), so the pool parks at width 0 around them and
# the walk census does not carry idle pool wires.
Walk 1, forward 615,953 Toffolis · peak 935 qubits
with b.phase("s2-walkA"):
mm.off()
inv.start(A, "s2-walkA", "s2-walkB")
inv.walk.emit_fwd()
Multiply by inverse 272,471 Toffolis · peak 925 qubits
with b.phase("s2-seam"):
mm.on(E.HMR_POOL_PLATEAU, seam=True)
inv.walk.closer()
inv.row_mul(y1, A, ma)
inv.sign_fix(A, ma)
mbits = [b.hmr(q) for q in y1]
inv.row_unpark(y1, ma)
inv.walk.pool.rebind_band("fw", list(y1))
inv.walk.closer_inv()
inv.arm_rider(A, {"rev": "halve"})
mm.off()
Walk 1, reverse 637,032 Toffolis · peak 935 qubits
with b.phase("s2-walkB"):
inv.walk.emit_rev()
inv.walk.rider = None
inv.finish()
Phase repair 383,522 Toffolis · peak 935 qubits
with b.phase("s2-recompute"):
mm.on(E._hmr_pool_off())
E.mul_modp(b, x1, A, y1, p, n, ma) # y1 = x1*A = y exact
for q, m in zip(y1, mbits):
b.z(q, cond=m) # Z^b phase fixup
E.mul_modp(b, x1, A, y1, p, n, ma, inv=True)
for yq, aq in zip(y1, A):
b.swap(yq, aq)
# ============ s4: x1 <- x1 + 3*Q.x - lambda^2 = P.x + 2*Q.x - lambda^2
# The fused square subtracts lambda^2 from x1 term by term, so no
# square register survives the phase. The A word is |0> and idle
# here; it is freed so the square's carries can use its width, and
# allocated again afterwards.
x update 64,540 Toffolis · peak 935 qubits
with b.phase("s4-sub-squ"):
mm.pool_width(0)
b.release_recycle("A", A)
_subtract_square(b, y1, x1)
b.set_phase("s4-sub-squ")
LC.const_modp(b, x1, S_3x2, singles["flag"], y1, singles["clean3"], p, n)
A[:] = b.qubits(n)
b.claim("A", A)
mm.pool_width(E._hmr_pool_off())
# ================= s6-imul: y1 <- x1 * y1 ==========================
# A = x1*y1 (exact, tape empty) -> HMR(y1) -> walk-C fwd on x1
# (f/gv host = the freed y1 word; rider halves: A *= 2^{Tmin-tstop})
# -> closer -> plateau row-mul y1 = |fv|*A*2^{-Tmin} + sign fix (= y exact)
# -> Z^b -> erase pair -> closer_inv -> walk-D rev with rider
# doubles (A restored to x1*y1) -> swap(y1, A).
Multiply 191,761 Toffolis · peak 935 qubits
with b.phase("s6-mul1"):
E.mul_modp(b, x1, y1, A, p, n, ma)
Walk 2, forward 628,378 Toffolis · peak 935 qubits
with b.phase("s6-walkC"):
mbits2 = [b.hmr(q) for q in y1]
mm.off()
inv.start(y1, "s6-walkC", "s6-walkD")
inv.arm_rider(A, {"fwd": "halve", "rev": "double"})
inv.walk.emit_fwd()
Recover y 542,580 Toffolis · peak 925 qubits
with b.phase("s6-seam"):
mm.on(E.HMR_POOL_PLATEAU, seam=True)
inv.walk.closer()
inv.row_mul(A, y1, ma) # y1 = |fv|*A*2^-Tmin
inv.sign_fix(y1, ma) # y1 = y exact
for q, m in zip(y1, mbits2):
b.z(q, cond=m)
inv.sign_fix(y1, ma, inv=True)
# erase pair: the facing lifts cancel (the row parks in
# magnitude form through the sign fix + fixup, which never
# touch the row ids; the inv call carries the un-reduce +
# un-lift)
inv.row_mul(A, y1, ma, inv=True)
inv.walk.closer_inv()
mm.off()
Walk 2, reverse 637,032 Toffolis · peak 935 qubits
with b.phase("s6-walkD"):
inv.walk.emit_rev()
inv.walk.rider = None
inv.finish()
mm.on(E._hmr_pool_off())
for yq, aq in zip(y1, A):
b.swap(yq, aq)
# ================= s7-final ========================================
Final update 17,084 Toffolis · peak 935 qubits
with b.phase("s7-final"):
LC.neg_modp(b, x1, y1, singles["clean3"], p, n)
LC.const_modp(b, x1, S_x2, singles["flag"], y1, singles["clean3"], p, n)
LC.const_modp(b, y1, S_y2, singles["flag"], x1, singles["clean3"], p, n, inv=True)
# ================= teardown ========================================
Release 0 Toffolis · peak 935 qubits
b.set_phase("close")
b.release("A", A)
mm.off()
for w in reversed(list(A)):
b.recycle(w)
return x1, y1, ox, oy