From 2b4e5f41e3d111634c9aef2b413825cd8a6e3c55 Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Thu, 24 Oct 2024 16:34:30 -0700 Subject: [PATCH 1/8] ECWindowAddR complete --- qualtran/bloqs/factoring/ecc/ec_add_r.py | 151 ++++++++++++++++-- qualtran/bloqs/factoring/ecc/ec_add_r_test.py | 57 ++++++- .../factoring/ecc/ec_phase_estimate_r.py | 41 ++++- qualtran/bloqs/factoring/ecc/ec_point.py | 2 +- qualtran/bloqs/factoring/ecc/ecc.ipynb | 77 +++++++-- .../factoring/ecc/find_ecc_private_key.py | 24 ++- qualtran/serialization/resolver_dict.py | 3 + 7 files changed, 306 insertions(+), 49 deletions(-) diff --git a/qualtran/bloqs/factoring/ecc/ec_add_r.py b/qualtran/bloqs/factoring/ecc/ec_add_r.py index 8b2087314..9fce173d5 100644 --- a/qualtran/bloqs/factoring/ecc/ec_add_r.py +++ b/qualtran/bloqs/factoring/ecc/ec_add_r.py @@ -18,10 +18,26 @@ import sympy from attrs import frozen -from qualtran import Bloq, bloq_example, BloqDocSpec, QBit, QUInt, Register, Signature +from qualtran import ( + Bloq, + bloq_example, + BloqBuilder, + BloqDocSpec, + QBit, + QMontgomeryUInt, + QUInt, + Register, + Signature, + Soquet, + SoquetT, +) +from qualtran.bloqs.data_loading import QROAMClean from qualtran.drawing import Circle, Text, TextBox, WireSymbol +from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator from qualtran.simulation.classical_sim import ClassicalValT +from qualtran.symbolics import is_symbolic, Shaped +from .ec_add import ECAdd from .ec_point import ECPoint @@ -113,13 +129,14 @@ class ECWindowAddR(Bloq): Args: n: The bitsize of the two registers storing the elliptic curve point - window_size: The number of bits in the window. - R: The elliptic curve point to add. + R: The elliptic curve point to add (NOT in montgomery form). + ec_window_size: The number of bits in the ECAdd window. + mul_window_size: The number of bits in the modular multiplication window. Registers: ctrl: `window_size` control bits. - x: The x component of the input elliptic curve point of bitsize `n`. - y: The y component of the input elliptic curve point of bitsize `n`. + x: The x component of the input elliptic curve point of bitsize `n` in montgomery form. + y: The y component of the input elliptic curve point of bitsize `n` in montgomery form. References: [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). @@ -127,19 +144,118 @@ class ECWindowAddR(Bloq): """ n: int - window_size: int R: ECPoint + ec_window_size: int + mul_window_size: int = 1 @cached_property def signature(self) -> 'Signature': return Signature( [ - Register('ctrl', QBit(), shape=(self.window_size,)), + Register('ctrl', QBit(), shape=(self.ec_window_size,)), Register('x', QUInt(self.n)), Register('y', QUInt(self.n)), ] ) + @cached_property + def qrom(self) -> QROAMClean: + if is_symbolic(self.n) or is_symbolic(self.ec_window_size): + log_block_sizes = None + if is_symbolic(self.n) and not is_symbolic(self.ec_window_size): + # We assume that bitsize is much larger than window_size + log_block_sizes = (0,) + return QROAMClean( + [ + Shaped((2**self.ec_window_size,)), + Shaped((2**self.ec_window_size,)), + Shaped((2**self.ec_window_size,)), + ], + selection_bitsizes=(self.ec_window_size,), + target_bitsizes=(self.n, self.n, self.n), + log_block_sizes=log_block_sizes, + ) + + cR = self.R + data_a, data_b, data_lam = [0], [0], [0] + for _ in range(1, 2**self.ec_window_size): + data_a.append(QMontgomeryUInt(self.n).uint_to_montgomery(int(cR.x), int(self.R.mod))) + data_b.append(QMontgomeryUInt(self.n).uint_to_montgomery(int(cR.y), int(self.R.mod))) + lam_num = (3 * cR.x**2 + cR.curve_a) % cR.mod + lam_denom = (2 * cR.y) % cR.mod + if lam_denom != 0: + lam = (lam_num * pow(lam_denom, -1, mod=cR.mod)) % cR.mod + else: + lam = 0 + data_lam.append(QMontgomeryUInt(self.n).uint_to_montgomery(int(lam), int(self.R.mod))) + cR = cR + self.R + + return QROAMClean( + [data_a, data_b, data_lam], + selection_bitsizes=(self.ec_window_size,), + target_bitsizes=(self.n, self.n, self.n), + ) + + def build_composite_bloq( + self, bb: 'BloqBuilder', ctrl: 'SoquetT', x: 'Soquet', y: 'Soquet' + ) -> Dict[str, 'SoquetT']: + ctrl = bb.join(ctrl) + + ctrl, a, b, lam_r, *junk = bb.add(self.qrom, selection=ctrl) + + a, b, x, y, lam_r = bb.add( + # TODO(https://github.com/quantumlib/Qualtran/issues/1476): make ECAdd accept SymbolicInt. + ECAdd(n=self.n, mod=int(self.R.mod), window_size=self.mul_window_size), + a=a, + b=b, + x=x, + y=y, + lam_r=lam_r, + ) + + if junk: + assert len(junk) == 3 + ctrl = bb.add( + self.qrom.adjoint(), + selection=ctrl, + target0_=a, + target1_=b, + target2_=lam_r, + junk_target0_=junk[0], + junk_target1_=junk[1], + junk_target2_=junk[2], + ) + else: + ctrl = bb.add( + self.qrom.adjoint(), selection=ctrl, target0_=a, target1_=b, target2_=lam_r + ) + + return {'ctrl': bb.split(ctrl), 'x': x, 'y': y} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + return { + self.qrom: 1, + # TODO(https://github.com/quantumlib/Qualtran/issues/1476): make ECAdd accept SymbolicInt. + ECAdd(self.n, int(self.R.mod), self.mul_window_size): 1, + self.qrom.adjoint(): 1, + } + + def on_classical_vals(self, ctrl, x, y) -> Dict[str, Union['ClassicalValT', sympy.Expr]]: + # TODO(https://github.com/quantumlib/Qualtran/issues/1476): make ECAdd accept SymbolicInt. + A = ECPoint( + QMontgomeryUInt(self.n).montgomery_to_uint(int(x), int(self.R.mod)), + QMontgomeryUInt(self.n).montgomery_to_uint(int(y), int(self.R.mod)), + mod=self.R.mod, + curve_a=self.R.curve_a, + ) + ctrls = QUInt(self.n).from_bits(ctrl) + result: ECPoint = A + (ctrls * self.R) + return { + 'ctrl': ctrl, + 'x': QMontgomeryUInt(self.n).uint_to_montgomery(int(result.x), int(self.R.mod)), + 'y': QMontgomeryUInt(self.n).uint_to_montgomery(int(result.y), int(self.R.mod)), + } + def wire_symbol( self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() ) -> 'WireSymbol': @@ -153,16 +269,21 @@ def wire_symbol( return TextBox(f'$+{self.R.y}$') raise ValueError(f'Unrecognized register name {reg.name}') - def __str__(self): - return f'ECWindowAddR({self.n=})' - @bloq_example -def _ec_window_add() -> ECWindowAddR: - n, p = sympy.symbols('n p') +def _ec_window_add_r() -> ECWindowAddR: + n, p, w = sympy.symbols('n p w') Rx, Ry = sympy.symbols('Rx Ry') - ec_window_add = ECWindowAddR(n=n, window_size=3, R=ECPoint(Rx, Ry, mod=p)) - return ec_window_add + ec_window_add_r = ECWindowAddR(n=n, ec_window_size=w, R=ECPoint(Rx, Ry, mod=p)) + return ec_window_add_r + + +@bloq_example +def _ec_window_add_r_small() -> ECWindowAddR: + n = 16 + P = ECPoint(2, 2, mod=7, curve_a=3) + ec_window_add_r_small = ECWindowAddR(n=n, R=P, ec_window_size=4) + return ec_window_add_r_small -_EC_WINDOW_ADD_BLOQ_DOC = BloqDocSpec(bloq_cls=ECWindowAddR, examples=[_ec_window_add]) +_EC_WINDOW_ADD_BLOQ_DOC = BloqDocSpec(bloq_cls=ECWindowAddR, examples=[_ec_window_add_r_small]) diff --git a/qualtran/bloqs/factoring/ecc/ec_add_r_test.py b/qualtran/bloqs/factoring/ecc/ec_add_r_test.py index f36ec4e11..0255869f4 100644 --- a/qualtran/bloqs/factoring/ecc/ec_add_r_test.py +++ b/qualtran/bloqs/factoring/ecc/ec_add_r_test.py @@ -12,16 +12,59 @@ # See the License for the specific language governing permissions and # limitations under the License. -from qualtran.bloqs.factoring.ecc.ec_add_r import _ec_add_r, _ec_add_r_small, _ec_window_add +import numpy as np +import pytest +import qualtran.testing as qlt_testing +from qualtran import QMontgomeryUInt, QUInt +from qualtran.bloqs.factoring.ecc.ec_add_r import ( + _ec_add_r, + _ec_add_r_small, + _ec_window_add_r_small, + ECWindowAddR, +) +from qualtran.resource_counting.generalizers import ignore_alloc_free, ignore_split_join -def test_ec_add_r(bloq_autotester): - bloq_autotester(_ec_add_r) +from .ec_add_r import ECWindowAddR +from .ec_point import ECPoint -def test_ec_add_r_small(bloq_autotester): - bloq_autotester(_ec_add_r_small) +@pytest.mark.parametrize('bloq', [_ec_add_r, _ec_add_r_small, _ec_window_add_r_small]) +def test_ec_add_r(bloq_autotester, bloq): + bloq_autotester(bloq) -def test_ec_window_add(bloq_autotester): - bloq_autotester(_ec_window_add) +@pytest.mark.parametrize('a,b', [(15, 13), (0, 0)]) +@pytest.mark.parametrize( + ['n', 'window_size'], + [ + (n, window_size) + for n in range(5, 8) + for window_size in range(1, n + 1) + if n % window_size == 0 + ], +) +def test_ec_window_add_r_bloq_counts(n, window_size, a, b): + p = 17 + R = ECPoint(a, b, mod=p) + bloq = ECWindowAddR(n=n, R=R, ec_window_size=window_size) + qlt_testing.assert_equivalent_bloq_counts(bloq, [ignore_alloc_free, ignore_split_join]) + + +@pytest.mark.parametrize( + ['n', 'm'], [(n, m) for n in range(7, 9) for m in range(1, n + 1) if n % m == 0] +) +@pytest.mark.parametrize('a,b', [(15, 13), (0, 0)]) +@pytest.mark.parametrize('x,y', [(15, 13), (5, 8)]) +@pytest.mark.parametrize('ctrl', [0, 1, 5, 8]) +def test_ec_window_add_r_classical(n, m, ctrl, x, y, a, b): + p = 17 + R = ECPoint(a, b, mod=p) + x = QMontgomeryUInt(n).uint_to_montgomery(x, p) + y = QMontgomeryUInt(n).uint_to_montgomery(y, p) + ctrl = QUInt(m).to_bits(ctrl % (2**m)) + bloq = ECWindowAddR(n=n, R=R, ec_window_size=m, mul_window_size=m) + ret1 = bloq.call_classically(ctrl=ctrl, x=x, y=y) + ret2 = bloq.decompose_bloq().call_classically(ctrl=ctrl, x=x, y=y) + for i, ret1_i in enumerate(ret1): + np.testing.assert_array_equal(ret1_i, ret2[i]) diff --git a/qualtran/bloqs/factoring/ecc/ec_phase_estimate_r.py b/qualtran/bloqs/factoring/ecc/ec_phase_estimate_r.py index ffe03f2f9..eae395310 100644 --- a/qualtran/bloqs/factoring/ecc/ec_phase_estimate_r.py +++ b/qualtran/bloqs/factoring/ecc/ec_phase_estimate_r.py @@ -12,9 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +import functools from functools import cached_property -from typing import Dict +from typing import Dict, Union +import numpy as np import sympy from attrs import frozen @@ -34,7 +36,7 @@ from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator from .._factoring_shims import MeasureQFT -from .ec_add_r import ECAddR +from .ec_add_r import ECAddR, ECWindowAddR from .ec_point import ECPoint @@ -48,27 +50,54 @@ class ECPhaseEstimateR(Bloq): Args: n: The bitsize of the elliptic curve points' x and y registers. point: The elliptic curve point to phase estimate against. + ec_window_size: The number of bits in the ECAdd window. + mul_window_size: The number of bits in the modular multiplication window. """ n: int point: ECPoint + ec_window_size: int = 1 + mul_window_size: int = 1 @cached_property def signature(self) -> 'Signature': return Signature([Register('x', QUInt(self.n)), Register('y', QUInt(self.n))]) + @property + def ec_add(self) -> Union[functools.partial[ECAddR], functools.partial[ECWindowAddR]]: + if self.ec_window_size == 1: + return functools.partial(ECAddR, n=self.n) + return functools.partial( + ECWindowAddR, + n=self.n, + ec_window_size=self.ec_window_size, + mul_window_size=self.mul_window_size, + ) + def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[str, 'SoquetT']: if isinstance(self.n, sympy.Expr): raise DecomposeTypeError("Cannot decompose symbolic `n`.") ctrl = [bb.add(PlusState()) for _ in range(self.n)] - for i in range(self.n): - ctrl[i], x, y = bb.add(ECAddR(n=self.n, R=2**i * self.point), ctrl=ctrl[i], x=x, y=y) + + if self.ec_window_size == 1: + for i in range(self.n): + ctrl[i], x, y = bb.add(self.ec_add(R=2**i * self.point), ctrl=ctrl[i], x=x, y=y) + else: + ctrls = np.split(np.array(ctrl), self.n // self.ec_window_size) + for i in range(self.n // self.ec_window_size): + ctrls[i], x, y = bb.add( + self.ec_add(R=2 ** (self.ec_window_size * i) * self.point), + ctrl=ctrls[i], + x=x, + y=y, + ) + ctrl = np.concatenate(ctrls, axis=None) bb.add(MeasureQFT(n=self.n), x=ctrl) return {'x': x, 'y': y} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': - return {ECAddR(n=self.n, R=self.point): self.n, MeasureQFT(n=self.n): 1} + return {self.ec_add(R=self.point): self.n // self.ec_window_size, MeasureQFT(n=self.n): 1} def __str__(self) -> str: return f'PE${self.point}$' @@ -90,4 +119,4 @@ def _ec_pe_small() -> ECPhaseEstimateR: return ec_pe_small -_EC_PE_BLOQ_DOC = BloqDocSpec(bloq_cls=ECPhaseEstimateR, examples=[_ec_pe]) +_EC_PE_BLOQ_DOC = BloqDocSpec(bloq_cls=ECPhaseEstimateR, examples=[_ec_pe, _ec_pe_small]) diff --git a/qualtran/bloqs/factoring/ecc/ec_point.py b/qualtran/bloqs/factoring/ecc/ec_point.py index c17ea5957..8037ccd35 100644 --- a/qualtran/bloqs/factoring/ecc/ec_point.py +++ b/qualtran/bloqs/factoring/ecc/ec_point.py @@ -63,7 +63,7 @@ def __add__(self, other): lam_num = (other.y - self.y) % self.mod lam_denom = (other.x - self.x) % self.mod - lam = (lam_num * pow(lam_denom, -1, mod=self.mod)) % self.mod + lam = (lam_num * pow(int(lam_denom), -1, mod=int(self.mod))) % self.mod xr = (lam**2 - other.x - self.x) % self.mod yr = (lam * (self.x - xr) - self.y) % self.mod return ECPoint(xr, yr, mod=self.mod, curve_a=self.curve_a) diff --git a/qualtran/bloqs/factoring/ecc/ecc.ipynb b/qualtran/bloqs/factoring/ecc/ecc.ipynb index 9215d113d..359c10ea9 100644 --- a/qualtran/bloqs/factoring/ecc/ecc.ipynb +++ b/qualtran/bloqs/factoring/ecc/ecc.ipynb @@ -89,7 +89,9 @@ "#### Parameters\n", " - `n`: The bitsize of the elliptic curve points' x and y registers.\n", " - `base_point`: The base point $P$ with unknown order $r$ such that $P = [r] P$.\n", - " - `public_key`: The public key $Q$ such that $Q = [k] P$ for private key $k$. \n", + " - `public_key`: The public key $Q$ such that $Q = [k] P$ for private key $k$.\n", + " - `ec_window_size`: The number of bits in the ECAdd window.\n", + " - `mul_window_size`: The number of bits in the modular multiplication window. \n", "\n", "#### References\n", " - [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). Litinski. 2023. Figure 4 (a).\n" @@ -217,7 +219,9 @@ "\n", "#### Parameters\n", " - `n`: The bitsize of the elliptic curve points' x and y registers.\n", - " - `point`: The elliptic curve point to phase estimate against.\n" + " - `point`: The elliptic curve point to phase estimate against.\n", + " - `ec_window_size`: The number of bits in the ECAdd window.\n", + " - `mul_window_size`: The number of bits in the modular multiplication window.\n" ] }, { @@ -256,6 +260,20 @@ "ec_pe = ECPhaseEstimateR(n=n, point=ECPoint(Rx, Ry, mod=p))" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "4616d33a", + "metadata": { + "cq.autogen": "ECPhaseEstimateR.ec_pe_small" + }, + "outputs": [], + "source": [ + "n = 3\n", + "Rx, Ry, p = sympy.symbols('R_x R_y p')\n", + "ec_pe_small = ECPhaseEstimateR(n=n, point=ECPoint(Rx, Ry, mod=p))" + ] + }, { "cell_type": "markdown", "id": "33824ce4", @@ -276,8 +294,8 @@ "outputs": [], "source": [ "from qualtran.drawing import show_bloqs\n", - "show_bloqs([ec_pe],\n", - " ['`ec_pe`'])" + "show_bloqs([ec_pe, ec_pe_small],\n", + " ['`ec_pe`', '`ec_pe_small`'])" ] }, { @@ -463,13 +481,14 @@ "\n", "#### Parameters\n", " - `n`: The bitsize of the two registers storing the elliptic curve point\n", - " - `window_size`: The number of bits in the window.\n", - " - `R`: The elliptic curve point to add. \n", + " - `R`: The elliptic curve point to add (NOT in montgomery form).\n", + " - `ec_window_size`: The number of bits in the ECAdd window.\n", + " - `mul_window_size`: The number of bits in the modular multiplication window. \n", "\n", "#### Registers\n", " - `ctrl`: `window_size` control bits.\n", - " - `x`: The x component of the input elliptic curve point of bitsize `n`.\n", - " - `y`: The y component of the input elliptic curve point of bitsize `n`. \n", + " - `x`: The x component of the input elliptic curve point of bitsize `n` in montgomery form.\n", + " - `y`: The y component of the input elliptic curve point of bitsize `n` in montgomery form. \n", "\n", "#### References\n", " - [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). Litinski. 2013. Section 1, eq. (3) and (4).\n" @@ -506,9 +525,23 @@ }, "outputs": [], "source": [ - "n, p = sympy.symbols('n p')\n", + "n, p, w = sympy.symbols('n p w')\n", "Rx, Ry = sympy.symbols('Rx Ry')\n", - "ec_window_add = ECWindowAddR(n=n, window_size=3, R=ECPoint(Rx, Ry, mod=p))" + "ec_window_add_r = ECWindowAddR(n=n, ec_window_size=w, R=ECPoint(Rx, Ry, mod=p))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2ab0c101", + "metadata": { + "cq.autogen": "ECWindowAddR.ec_window_add_r_small" + }, + "outputs": [], + "source": [ + "n = 16\n", + "P = ECPoint(2, 2, mod=7, curve_a=3)\n", + "ec_window_add_r_small = ECWindowAddR(n=n, R=P, ec_window_size=4)" ] }, { @@ -531,8 +564,8 @@ "outputs": [], "source": [ "from qualtran.drawing import show_bloqs\n", - "show_bloqs([ec_window_add],\n", - " ['`ec_window_add`'])" + "show_bloqs([ec_window_add_r_small],\n", + " ['`ec_window_add_r_small`'])" ] }, { @@ -555,9 +588,23 @@ "outputs": [], "source": [ "from qualtran.resource_counting.generalizers import ignore_split_join\n", - "ec_window_add_g, ec_window_add_sigma = ec_window_add.call_graph(max_depth=1, generalizer=ignore_split_join)\n", - "show_call_graph(ec_window_add_g)\n", - "show_counts_sigma(ec_window_add_sigma)" + "ec_window_add_r_small_g, ec_window_add_r_small_sigma = ec_window_add_r_small.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(ec_window_add_r_small_g)\n", + "show_counts_sigma(ec_window_add_r_small_sigma)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "718403a6", + "metadata": { + "cq.autogen": "ECWindowAddR.ec_window_add_r" + }, + "outputs": [], + "source": [ + "n, p, w = sympy.symbols('n p w')\n", + "Rx, Ry = sympy.symbols('Rx Ry')\n", + "ec_window_add_r = ECWindowAddR(n=n, ec_window_size=w, R=ECPoint(Rx, Ry, mod=p))" ] } ], diff --git a/qualtran/bloqs/factoring/ecc/find_ecc_private_key.py b/qualtran/bloqs/factoring/ecc/find_ecc_private_key.py index efaa42ed3..3827b9ece 100644 --- a/qualtran/bloqs/factoring/ecc/find_ecc_private_key.py +++ b/qualtran/bloqs/factoring/ecc/find_ecc_private_key.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import functools from functools import cached_property from typing import Dict @@ -66,6 +67,8 @@ class FindECCPrivateKey(Bloq): n: The bitsize of the elliptic curve points' x and y registers. base_point: The base point $P$ with unknown order $r$ such that $P = [r] P$. public_key: The public key $Q$ such that $Q = [k] P$ for private key $k$. + ec_window_size: The number of bits in the ECAdd window. + mul_window_size: The number of bits in the modular multiplication window. References: [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). @@ -75,6 +78,8 @@ class FindECCPrivateKey(Bloq): n: int base_point: ECPoint public_key: ECPoint + ec_window_size: int = 1 + mul_window_size: int = 1 @cached_property def signature(self) -> 'Signature': @@ -92,15 +97,24 @@ def curve_a(self) -> SymbolicInt: raise ValueError("Inconsistent curve parameters in the two points.") return self.base_point.curve_a + @property + def ec_pe_r(self) -> functools.partial[ECPhaseEstimateR]: + return functools.partial( + ECPhaseEstimateR, + n=self.n, + ec_window_size=self.ec_window_size, + mul_window_size=self.mul_window_size, + ) + def build_composite_bloq(self, bb: 'BloqBuilder') -> Dict[str, 'SoquetT']: x = bb.add(IntState(bitsize=self.n, val=self.base_point.x)) y = bb.add(IntState(bitsize=self.n, val=self.base_point.y)) - x, y = bb.add(ECPhaseEstimateR(n=self.n, point=self.base_point), x=x, y=y) - x, y = bb.add(ECPhaseEstimateR(n=self.n, point=self.public_key), x=x, y=y) + x, y = bb.add(self.ec_pe_r(point=self.base_point), x=x, y=y) + x, y = bb.add(self.ec_pe_r(point=self.public_key), x=x, y=y) - bb.add(Free(QUInt(self.n)), reg=x) - bb.add(Free(QUInt(self.n)), reg=y) + bb.add(Free(QUInt(self.n), dirty=True), reg=x) + bb.add(Free(QUInt(self.n), dirty=True), reg=y) return {} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': @@ -108,7 +122,7 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': Ry = ssa.new_symbol('Ry') generic_point = ECPoint(Rx, Ry, mod=self.mod, curve_a=self.curve_a) - return {ECPhaseEstimateR(n=self.n, point=generic_point): 2} + return {self.ec_pe_r(point=generic_point): 2} def cost_attrs(self): return [('n', self.n)] diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index 347c78b61..6ca815137 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -98,6 +98,7 @@ import qualtran.bloqs.data_loading.qrom import qualtran.bloqs.data_loading.select_swap_qrom import qualtran.bloqs.factoring._factoring_shims +import qualtran.bloqs.factoring.ecc import qualtran.bloqs.factoring.ecc.ec_add import qualtran.bloqs.factoring.rsa import qualtran.bloqs.for_testing.atom @@ -332,6 +333,7 @@ "qualtran.bloqs.data_loading.qrom.QROM": qualtran.bloqs.data_loading.qrom.QROM, "qualtran.bloqs.data_loading.qroam_clean.QROAMClean": qualtran.bloqs.data_loading.qroam_clean.QROAMClean, "qualtran.bloqs.data_loading.qroam_clean.QROAMCleanAdjoint": qualtran.bloqs.data_loading.qroam_clean.QROAMCleanAdjoint, + "qualtran.bloqs.data_loading.qroam_clean.QROAMCleanAdjointWrapper": qualtran.bloqs.data_loading.qroam_clean.QROAMCleanAdjointWrapper, "qualtran.bloqs.data_loading.select_swap_qrom.SelectSwapQROM": qualtran.bloqs.data_loading.select_swap_qrom.SelectSwapQROM, "qualtran.bloqs.mod_arithmetic.CModAddK": qualtran.bloqs.mod_arithmetic.CModAddK, "qualtran.bloqs.mod_arithmetic.mod_addition.ModAdd": qualtran.bloqs.mod_arithmetic.mod_addition.ModAdd, @@ -349,6 +351,7 @@ "qualtran.bloqs.mod_arithmetic.mod_multiplication.DirtyOutOfPlaceMontgomeryModMul": qualtran.bloqs.mod_arithmetic.mod_multiplication.DirtyOutOfPlaceMontgomeryModMul, "qualtran.bloqs.mod_arithmetic.mod_multiplication.SingleWindowModMul": qualtran.bloqs.mod_arithmetic.mod_multiplication.SingleWindowModMul, "qualtran.bloqs.factoring._factoring_shims.MeasureQFT": qualtran.bloqs.factoring._factoring_shims.MeasureQFT, + "qualtran.bloqs.factoring.ecc.ec_add_r.ECWindowAddR": qualtran.bloqs.factoring.ecc.ec_add_r.ECWindowAddR, "qualtran.bloqs.factoring.ecc.ec_add._ECAddStepOne": qualtran.bloqs.factoring.ecc.ec_add._ECAddStepOne, "qualtran.bloqs.factoring.ecc.ec_add._ECAddStepTwo": qualtran.bloqs.factoring.ecc.ec_add._ECAddStepTwo, "qualtran.bloqs.factoring.ecc.ec_add._ECAddStepThree": qualtran.bloqs.factoring.ecc.ec_add._ECAddStepThree, From 30d1eca0ac71b90961c1d4bf8c4049ca785f4f4a Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Thu, 24 Oct 2024 18:15:08 -0700 Subject: [PATCH 2/8] fix test cases --- qualtran/bloqs/factoring/ecc/ec_add_r.py | 8 -------- qualtran/bloqs/factoring/ecc/ec_point.py | 4 +++- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/qualtran/bloqs/factoring/ecc/ec_add_r.py b/qualtran/bloqs/factoring/ecc/ec_add_r.py index 9fce173d5..625ad1ce7 100644 --- a/qualtran/bloqs/factoring/ecc/ec_add_r.py +++ b/qualtran/bloqs/factoring/ecc/ec_add_r.py @@ -270,14 +270,6 @@ def wire_symbol( raise ValueError(f'Unrecognized register name {reg.name}') -@bloq_example -def _ec_window_add_r() -> ECWindowAddR: - n, p, w = sympy.symbols('n p w') - Rx, Ry = sympy.symbols('Rx Ry') - ec_window_add_r = ECWindowAddR(n=n, ec_window_size=w, R=ECPoint(Rx, Ry, mod=p)) - return ec_window_add_r - - @bloq_example def _ec_window_add_r_small() -> ECWindowAddR: n = 16 diff --git a/qualtran/bloqs/factoring/ecc/ec_point.py b/qualtran/bloqs/factoring/ecc/ec_point.py index 8037ccd35..d528285bb 100644 --- a/qualtran/bloqs/factoring/ecc/ec_point.py +++ b/qualtran/bloqs/factoring/ecc/ec_point.py @@ -14,7 +14,7 @@ from attrs import frozen -from qualtran.symbolics import SymbolicInt +from qualtran.symbolics import is_symbolic, SymbolicInt @frozen @@ -49,6 +49,8 @@ def __add__(self, other): if (other.mod != self.mod) or (other.curve_a != self.curve_a): raise ValueError('Use consistent mod and curve') + if is_symbolic(self.x, self.y, other.x, other.y, self.mod, self.curve_a): + return self if self == -other: return ECPoint.inf(mod=self.mod, curve_a=self.curve_a) if self == ECPoint.inf(mod=self.mod, curve_a=self.curve_a): From cb9091b4b4586d08c13d253ec1414898da24c345 Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Fri, 25 Oct 2024 11:44:02 -0700 Subject: [PATCH 3/8] Fix mypy errors --- qualtran/bloqs/factoring/ecc/ec_add_r.py | 3 ++- qualtran/bloqs/factoring/ecc/ec_add_r_test.py | 2 +- qualtran/bloqs/factoring/ecc/ecc.ipynb | 18 ++---------------- 3 files changed, 5 insertions(+), 18 deletions(-) diff --git a/qualtran/bloqs/factoring/ecc/ec_add_r.py b/qualtran/bloqs/factoring/ecc/ec_add_r.py index 625ad1ce7..9e360c846 100644 --- a/qualtran/bloqs/factoring/ecc/ec_add_r.py +++ b/qualtran/bloqs/factoring/ecc/ec_add_r.py @@ -15,6 +15,7 @@ from functools import cached_property from typing import Dict, Optional, Tuple, Union +import numpy as np import sympy from attrs import frozen @@ -199,7 +200,7 @@ def qrom(self) -> QROAMClean: def build_composite_bloq( self, bb: 'BloqBuilder', ctrl: 'SoquetT', x: 'Soquet', y: 'Soquet' ) -> Dict[str, 'SoquetT']: - ctrl = bb.join(ctrl) + ctrl = bb.join(np.array(ctrl)) ctrl, a, b, lam_r, *junk = bb.add(self.qrom, selection=ctrl) diff --git a/qualtran/bloqs/factoring/ecc/ec_add_r_test.py b/qualtran/bloqs/factoring/ecc/ec_add_r_test.py index 0255869f4..d70171a99 100644 --- a/qualtran/bloqs/factoring/ecc/ec_add_r_test.py +++ b/qualtran/bloqs/factoring/ecc/ec_add_r_test.py @@ -62,7 +62,7 @@ def test_ec_window_add_r_classical(n, m, ctrl, x, y, a, b): R = ECPoint(a, b, mod=p) x = QMontgomeryUInt(n).uint_to_montgomery(x, p) y = QMontgomeryUInt(n).uint_to_montgomery(y, p) - ctrl = QUInt(m).to_bits(ctrl % (2**m)) + ctrl = np.array(QUInt(m).to_bits(ctrl % (2**m))) bloq = ECWindowAddR(n=n, R=R, ec_window_size=m, mul_window_size=m) ret1 = bloq.call_classically(ctrl=ctrl, x=x, y=y) ret2 = bloq.decompose_bloq().call_classically(ctrl=ctrl, x=x, y=y) diff --git a/qualtran/bloqs/factoring/ecc/ecc.ipynb b/qualtran/bloqs/factoring/ecc/ecc.ipynb index 359c10ea9..bf13fbadc 100644 --- a/qualtran/bloqs/factoring/ecc/ecc.ipynb +++ b/qualtran/bloqs/factoring/ecc/ecc.ipynb @@ -111,7 +111,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "6cb48b0a-44c8-40e5-ba1e-d0b3a0745046", "metadata": {}, "outputs": [], @@ -518,7 +518,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "id": "e1a3a397", "metadata": { "cq.autogen": "ECWindowAddR.ec_window_add" @@ -592,20 +592,6 @@ "show_call_graph(ec_window_add_r_small_g)\n", "show_counts_sigma(ec_window_add_r_small_sigma)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "718403a6", - "metadata": { - "cq.autogen": "ECWindowAddR.ec_window_add_r" - }, - "outputs": [], - "source": [ - "n, p, w = sympy.symbols('n p w')\n", - "Rx, Ry = sympy.symbols('Rx Ry')\n", - "ec_window_add_r = ECWindowAddR(n=n, ec_window_size=w, R=ECPoint(Rx, Ry, mod=p))" - ] } ], "metadata": { From 4daddba40ee63dbebb1e3d06932cda3202cc92c7 Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Wed, 30 Oct 2024 18:17:02 -0700 Subject: [PATCH 4/8] Address comments --- qualtran/bloqs/factoring/ecc/ec_add_r.py | 24 ++++++++-------- qualtran/bloqs/factoring/ecc/ec_add_r_test.py | 26 +++++++++++++++-- .../factoring/ecc/ec_phase_estimate_r.py | 28 ++++++++++++------- qualtran/bloqs/factoring/ecc/ec_point.py | 4 ++- qualtran/bloqs/factoring/ecc/ecc.ipynb | 28 ++++++------------- .../factoring/ecc/find_ecc_private_key.py | 6 ++-- 6 files changed, 68 insertions(+), 48 deletions(-) diff --git a/qualtran/bloqs/factoring/ecc/ec_add_r.py b/qualtran/bloqs/factoring/ecc/ec_add_r.py index 9e360c846..d012e3979 100644 --- a/qualtran/bloqs/factoring/ecc/ec_add_r.py +++ b/qualtran/bloqs/factoring/ecc/ec_add_r.py @@ -131,7 +131,7 @@ class ECWindowAddR(Bloq): Args: n: The bitsize of the two registers storing the elliptic curve point R: The elliptic curve point to add (NOT in montgomery form). - ec_window_size: The number of bits in the ECAdd window. + add_window_size: The number of bits in the ECAdd window. mul_window_size: The number of bits in the modular multiplication window. Registers: @@ -146,14 +146,14 @@ class ECWindowAddR(Bloq): n: int R: ECPoint - ec_window_size: int + add_window_size: int mul_window_size: int = 1 @cached_property def signature(self) -> 'Signature': return Signature( [ - Register('ctrl', QBit(), shape=(self.ec_window_size,)), + Register('ctrl', QBit(), shape=(self.add_window_size,)), Register('x', QUInt(self.n)), Register('y', QUInt(self.n)), ] @@ -161,25 +161,25 @@ def signature(self) -> 'Signature': @cached_property def qrom(self) -> QROAMClean: - if is_symbolic(self.n) or is_symbolic(self.ec_window_size): + if is_symbolic(self.n) or is_symbolic(self.add_window_size): log_block_sizes = None - if is_symbolic(self.n) and not is_symbolic(self.ec_window_size): + if is_symbolic(self.n) and not is_symbolic(self.add_window_size): # We assume that bitsize is much larger than window_size log_block_sizes = (0,) return QROAMClean( [ - Shaped((2**self.ec_window_size,)), - Shaped((2**self.ec_window_size,)), - Shaped((2**self.ec_window_size,)), + Shaped((2**self.add_window_size,)), + Shaped((2**self.add_window_size,)), + Shaped((2**self.add_window_size,)), ], - selection_bitsizes=(self.ec_window_size,), + selection_bitsizes=(self.add_window_size,), target_bitsizes=(self.n, self.n, self.n), log_block_sizes=log_block_sizes, ) cR = self.R data_a, data_b, data_lam = [0], [0], [0] - for _ in range(1, 2**self.ec_window_size): + for _ in range(1, 2**self.add_window_size): data_a.append(QMontgomeryUInt(self.n).uint_to_montgomery(int(cR.x), int(self.R.mod))) data_b.append(QMontgomeryUInt(self.n).uint_to_montgomery(int(cR.y), int(self.R.mod))) lam_num = (3 * cR.x**2 + cR.curve_a) % cR.mod @@ -193,7 +193,7 @@ def qrom(self) -> QROAMClean: return QROAMClean( [data_a, data_b, data_lam], - selection_bitsizes=(self.ec_window_size,), + selection_bitsizes=(self.add_window_size,), target_bitsizes=(self.n, self.n, self.n), ) @@ -275,7 +275,7 @@ def wire_symbol( def _ec_window_add_r_small() -> ECWindowAddR: n = 16 P = ECPoint(2, 2, mod=7, curve_a=3) - ec_window_add_r_small = ECWindowAddR(n=n, R=P, ec_window_size=4) + ec_window_add_r_small = ECWindowAddR(n=n, R=P, add_window_size=4) return ec_window_add_r_small diff --git a/qualtran/bloqs/factoring/ecc/ec_add_r_test.py b/qualtran/bloqs/factoring/ecc/ec_add_r_test.py index d70171a99..d2ea9ad93 100644 --- a/qualtran/bloqs/factoring/ecc/ec_add_r_test.py +++ b/qualtran/bloqs/factoring/ecc/ec_add_r_test.py @@ -47,23 +47,43 @@ def test_ec_add_r(bloq_autotester, bloq): def test_ec_window_add_r_bloq_counts(n, window_size, a, b): p = 17 R = ECPoint(a, b, mod=p) - bloq = ECWindowAddR(n=n, R=R, ec_window_size=window_size) + bloq = ECWindowAddR(n=n, R=R, add_window_size=window_size) qlt_testing.assert_equivalent_bloq_counts(bloq, [ignore_alloc_free, ignore_split_join]) +@pytest.mark.parametrize( + ['n', 'm'], [(n, m) for n in range(7, 8) for m in range(1, n + 1) if n % m == 0] +) +@pytest.mark.parametrize('a,b', [(15, 13), (0, 0)]) +@pytest.mark.parametrize('x,y', [(15, 13), (5, 8)]) +@pytest.mark.parametrize('ctrl', [0, 1, 5]) +def test_ec_window_add_r_classical(n, m, ctrl, x, y, a, b): + p = 17 + R = ECPoint(a, b, mod=p) + x = QMontgomeryUInt(n).uint_to_montgomery(x, p) + y = QMontgomeryUInt(n).uint_to_montgomery(y, p) + ctrl = np.array(QUInt(m).to_bits(ctrl % (2**m))) + bloq = ECWindowAddR(n=n, R=R, add_window_size=m, mul_window_size=m) + ret1 = bloq.call_classically(ctrl=ctrl, x=x, y=y) + ret2 = bloq.decompose_bloq().call_classically(ctrl=ctrl, x=x, y=y) + for i, ret1_i in enumerate(ret1): + np.testing.assert_array_equal(ret1_i, ret2[i]) + + +@pytest.mark.slow @pytest.mark.parametrize( ['n', 'm'], [(n, m) for n in range(7, 9) for m in range(1, n + 1) if n % m == 0] ) @pytest.mark.parametrize('a,b', [(15, 13), (0, 0)]) @pytest.mark.parametrize('x,y', [(15, 13), (5, 8)]) @pytest.mark.parametrize('ctrl', [0, 1, 5, 8]) -def test_ec_window_add_r_classical(n, m, ctrl, x, y, a, b): +def test_ec_window_add_r_classical_slow(n, m, ctrl, x, y, a, b): p = 17 R = ECPoint(a, b, mod=p) x = QMontgomeryUInt(n).uint_to_montgomery(x, p) y = QMontgomeryUInt(n).uint_to_montgomery(y, p) ctrl = np.array(QUInt(m).to_bits(ctrl % (2**m))) - bloq = ECWindowAddR(n=n, R=R, ec_window_size=m, mul_window_size=m) + bloq = ECWindowAddR(n=n, R=R, add_window_size=m, mul_window_size=m) ret1 = bloq.call_classically(ctrl=ctrl, x=x, y=y) ret2 = bloq.decompose_bloq().call_classically(ctrl=ctrl, x=x, y=y) for i, ret1_i in enumerate(ret1): diff --git a/qualtran/bloqs/factoring/ecc/ec_phase_estimate_r.py b/qualtran/bloqs/factoring/ecc/ec_phase_estimate_r.py index eae395310..d56400d20 100644 --- a/qualtran/bloqs/factoring/ecc/ec_phase_estimate_r.py +++ b/qualtran/bloqs/factoring/ecc/ec_phase_estimate_r.py @@ -47,16 +47,20 @@ class ECPhaseEstimateR(Bloq): This is used as a subroutine in `FindECCPrivateKey`. First, we phase-estimate the addition of the base point $P$, then of the public key $Q$. + When the ellptic curve point addition window size is 1 we use the ECAddR bloq which has it's + own bespoke circuit; when it is greater than 1 we use the windowed circuit which uses + pre-computed classical additions loaded into the circuit. + Args: n: The bitsize of the elliptic curve points' x and y registers. point: The elliptic curve point to phase estimate against. - ec_window_size: The number of bits in the ECAdd window. + add_window_size: The number of bits in the ECAdd window. mul_window_size: The number of bits in the modular multiplication window. """ n: int point: ECPoint - ec_window_size: int = 1 + add_window_size: int = 1 mul_window_size: int = 1 @cached_property @@ -65,28 +69,32 @@ def signature(self) -> 'Signature': @property def ec_add(self) -> Union[functools.partial[ECAddR], functools.partial[ECWindowAddR]]: - if self.ec_window_size == 1: + if self.add_window_size == 1: return functools.partial(ECAddR, n=self.n) return functools.partial( ECWindowAddR, n=self.n, - ec_window_size=self.ec_window_size, + add_window_size=self.add_window_size, mul_window_size=self.mul_window_size, ) + @property + def num_windows(self) -> int: + return self.n // self.add_window_size + def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[str, 'SoquetT']: if isinstance(self.n, sympy.Expr): raise DecomposeTypeError("Cannot decompose symbolic `n`.") ctrl = [bb.add(PlusState()) for _ in range(self.n)] - if self.ec_window_size == 1: + if self.add_window_size == 1: for i in range(self.n): ctrl[i], x, y = bb.add(self.ec_add(R=2**i * self.point), ctrl=ctrl[i], x=x, y=y) else: - ctrls = np.split(np.array(ctrl), self.n // self.ec_window_size) - for i in range(self.n // self.ec_window_size): + ctrls = np.split(np.array(ctrl), self.num_windows) + for i in range(self.num_windows): ctrls[i], x, y = bb.add( - self.ec_add(R=2 ** (self.ec_window_size * i) * self.point), + self.ec_add(R=2 ** (self.add_window_size * i) * self.point), ctrl=ctrls[i], x=x, y=y, @@ -97,7 +105,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[ return {'x': x, 'y': y} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': - return {self.ec_add(R=self.point): self.n // self.ec_window_size, MeasureQFT(n=self.n): 1} + return {self.ec_add(R=self.point): self.num_windows, MeasureQFT(n=self.n): 1} def __str__(self) -> str: return f'PE${self.point}$' @@ -105,7 +113,7 @@ def __str__(self) -> str: @bloq_example def _ec_pe() -> ECPhaseEstimateR: - n, p = sympy.symbols('n p ') + n, p = sympy.symbols('n p') Rx, Ry = sympy.symbols('R_x R_y') ec_pe = ECPhaseEstimateR(n=n, point=ECPoint(Rx, Ry, mod=p)) return ec_pe diff --git a/qualtran/bloqs/factoring/ecc/ec_point.py b/qualtran/bloqs/factoring/ecc/ec_point.py index d528285bb..79fea7751 100644 --- a/qualtran/bloqs/factoring/ecc/ec_point.py +++ b/qualtran/bloqs/factoring/ecc/ec_point.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sympy from attrs import frozen from qualtran.symbolics import is_symbolic, SymbolicInt @@ -50,7 +51,8 @@ def __add__(self, other): raise ValueError('Use consistent mod and curve') if is_symbolic(self.x, self.y, other.x, other.y, self.mod, self.curve_a): - return self + x, y, p = sympy.symbols('x y p') + return ECPoint(x=x, y=y, mod=p) if self == -other: return ECPoint.inf(mod=self.mod, curve_a=self.curve_a) if self == ECPoint.inf(mod=self.mod, curve_a=self.curve_a): diff --git a/qualtran/bloqs/factoring/ecc/ecc.ipynb b/qualtran/bloqs/factoring/ecc/ecc.ipynb index bf13fbadc..0818bb895 100644 --- a/qualtran/bloqs/factoring/ecc/ecc.ipynb +++ b/qualtran/bloqs/factoring/ecc/ecc.ipynb @@ -90,7 +90,7 @@ " - `n`: The bitsize of the elliptic curve points' x and y registers.\n", " - `base_point`: The base point $P$ with unknown order $r$ such that $P = [r] P$.\n", " - `public_key`: The public key $Q$ such that $Q = [k] P$ for private key $k$.\n", - " - `ec_window_size`: The number of bits in the ECAdd window.\n", + " - `add_window_size`: The number of bits in the ECAdd window.\n", " - `mul_window_size`: The number of bits in the modular multiplication window. \n", "\n", "#### References\n", @@ -217,10 +217,14 @@ "This is used as a subroutine in `FindECCPrivateKey`. First, we phase-estimate the\n", "addition of the base point $P$, then of the public key $Q$.\n", "\n", + "When the ellptic curve point addition window size is 1 we use the ECAddR bloq which has it's\n", + "own bespoke circuit; when it is greater than 1 we use the windowed circuit which uses\n", + "pre-computed classical additions loaded into the circuit.\n", + "\n", "#### Parameters\n", " - `n`: The bitsize of the elliptic curve points' x and y registers.\n", " - `point`: The elliptic curve point to phase estimate against.\n", - " - `ec_window_size`: The number of bits in the ECAdd window.\n", + " - `add_window_size`: The number of bits in the ECAdd window.\n", " - `mul_window_size`: The number of bits in the modular multiplication window.\n" ] }, @@ -255,7 +259,7 @@ }, "outputs": [], "source": [ - "n, p = sympy.symbols('n p ')\n", + "n, p = sympy.symbols('n p')\n", "Rx, Ry = sympy.symbols('R_x R_y')\n", "ec_pe = ECPhaseEstimateR(n=n, point=ECPoint(Rx, Ry, mod=p))" ] @@ -482,7 +486,7 @@ "#### Parameters\n", " - `n`: The bitsize of the two registers storing the elliptic curve point\n", " - `R`: The elliptic curve point to add (NOT in montgomery form).\n", - " - `ec_window_size`: The number of bits in the ECAdd window.\n", + " - `add_window_size`: The number of bits in the ECAdd window.\n", " - `mul_window_size`: The number of bits in the modular multiplication window. \n", "\n", "#### Registers\n", @@ -516,20 +520,6 @@ "### Example Instances" ] }, - { - "cell_type": "code", - "execution_count": 20, - "id": "e1a3a397", - "metadata": { - "cq.autogen": "ECWindowAddR.ec_window_add" - }, - "outputs": [], - "source": [ - "n, p, w = sympy.symbols('n p w')\n", - "Rx, Ry = sympy.symbols('Rx Ry')\n", - "ec_window_add_r = ECWindowAddR(n=n, ec_window_size=w, R=ECPoint(Rx, Ry, mod=p))" - ] - }, { "cell_type": "code", "execution_count": null, @@ -541,7 +531,7 @@ "source": [ "n = 16\n", "P = ECPoint(2, 2, mod=7, curve_a=3)\n", - "ec_window_add_r_small = ECWindowAddR(n=n, R=P, ec_window_size=4)" + "ec_window_add_r_small = ECWindowAddR(n=n, R=P, add_window_size=4)" ] }, { diff --git a/qualtran/bloqs/factoring/ecc/find_ecc_private_key.py b/qualtran/bloqs/factoring/ecc/find_ecc_private_key.py index 3827b9ece..05785939f 100644 --- a/qualtran/bloqs/factoring/ecc/find_ecc_private_key.py +++ b/qualtran/bloqs/factoring/ecc/find_ecc_private_key.py @@ -67,7 +67,7 @@ class FindECCPrivateKey(Bloq): n: The bitsize of the elliptic curve points' x and y registers. base_point: The base point $P$ with unknown order $r$ such that $P = [r] P$. public_key: The public key $Q$ such that $Q = [k] P$ for private key $k$. - ec_window_size: The number of bits in the ECAdd window. + add_window_size: The number of bits in the ECAdd window. mul_window_size: The number of bits in the modular multiplication window. References: @@ -78,7 +78,7 @@ class FindECCPrivateKey(Bloq): n: int base_point: ECPoint public_key: ECPoint - ec_window_size: int = 1 + add_window_size: int = 1 mul_window_size: int = 1 @cached_property @@ -102,7 +102,7 @@ def ec_pe_r(self) -> functools.partial[ECPhaseEstimateR]: return functools.partial( ECPhaseEstimateR, n=self.n, - ec_window_size=self.ec_window_size, + add_window_size=self.add_window_size, mul_window_size=self.mul_window_size, ) From 07de406adb2dec8680fc15ac8f5d500878a8a56a Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Wed, 30 Oct 2024 18:23:47 -0700 Subject: [PATCH 5/8] Clear outputs of ecc notebook --- qualtran/bloqs/factoring/ecc/ecc.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qualtran/bloqs/factoring/ecc/ecc.ipynb b/qualtran/bloqs/factoring/ecc/ecc.ipynb index 0818bb895..33e82abca 100644 --- a/qualtran/bloqs/factoring/ecc/ecc.ipynb +++ b/qualtran/bloqs/factoring/ecc/ecc.ipynb @@ -111,7 +111,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "6cb48b0a-44c8-40e5-ba1e-d0b3a0745046", "metadata": {}, "outputs": [], From d5f3147a2cffb18071bc46e07e92959853d24d85 Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Wed, 30 Oct 2024 18:25:50 -0700 Subject: [PATCH 6/8] slight speedup on test by using very small bitsize --- qualtran/bloqs/factoring/ecc/ec_add_r_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qualtran/bloqs/factoring/ecc/ec_add_r_test.py b/qualtran/bloqs/factoring/ecc/ec_add_r_test.py index d2ea9ad93..65e437811 100644 --- a/qualtran/bloqs/factoring/ecc/ec_add_r_test.py +++ b/qualtran/bloqs/factoring/ecc/ec_add_r_test.py @@ -52,7 +52,7 @@ def test_ec_window_add_r_bloq_counts(n, window_size, a, b): @pytest.mark.parametrize( - ['n', 'm'], [(n, m) for n in range(7, 8) for m in range(1, n + 1) if n % m == 0] + ['n', 'm'], [(n, m) for n in range(4, 5) for m in range(1, n + 1) if n % m == 0] ) @pytest.mark.parametrize('a,b', [(15, 13), (0, 0)]) @pytest.mark.parametrize('x,y', [(15, 13), (5, 8)]) From 4c5b5e44d17165999b24857ad44e6f5fa5516963 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Thu, 31 Oct 2024 16:33:15 -0700 Subject: [PATCH 7/8] keep sympy symbolic --- qualtran/bloqs/factoring/ecc/ec_point.py | 25 ++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/qualtran/bloqs/factoring/ecc/ec_point.py b/qualtran/bloqs/factoring/ecc/ec_point.py index 79fea7751..eb885c54c 100644 --- a/qualtran/bloqs/factoring/ecc/ec_point.py +++ b/qualtran/bloqs/factoring/ecc/ec_point.py @@ -17,6 +17,12 @@ from qualtran.symbolics import is_symbolic, SymbolicInt +ec_times_x = sympy.Function("ec_times_x") +ec_times_y = sympy.Function("ec_times_y") +"""Support purely-symbolic ECPoint operations. +https://docs.sympy.org/latest/guides/custom-functions.html#easy-cases-fully-symbolic-or-fully-evaluated +""" + @frozen class ECPoint: @@ -50,9 +56,6 @@ def __add__(self, other): if (other.mod != self.mod) or (other.curve_a != self.curve_a): raise ValueError('Use consistent mod and curve') - if is_symbolic(self.x, self.y, other.x, other.y, self.mod, self.curve_a): - x, y, p = sympy.symbols('x y p') - return ECPoint(x=x, y=y, mod=p) if self == -other: return ECPoint.inf(mod=self.mod, curve_a=self.curve_a) if self == ECPoint.inf(mod=self.mod, curve_a=self.curve_a): @@ -72,11 +75,21 @@ def __add__(self, other): yr = (lam * (self.x - xr) - self.y) % self.mod return ECPoint(xr, yr, mod=self.mod, curve_a=self.curve_a) - def __mul__(self, other): - if other == 0: + def __mul__(self, times): + if times == 0: return ECPoint.inf(mod=self.mod, curve_a=self.curve_a) + if is_symbolic(self.x, self.y): + # Symbolic case: use sympy.Function to opaquely represent the + # multiplication operation + return ECPoint( + ec_times_x(self.x, times), + ec_times_y(self.y, times), + mod=self.mod, + curve_a=self.curve_a, + ) + # Otherwise, multiplication by an integer is repeated addition x = self - for _ in range(other - 1): + for _ in range(times - 1): x = x + self return x From e93edd5b3d541693a188b910736c08cf363564f2 Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Fri, 1 Nov 2024 17:07:01 -0700 Subject: [PATCH 8/8] Fix nit --- qualtran/bloqs/factoring/ecc/ec_add_r.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qualtran/bloqs/factoring/ecc/ec_add_r.py b/qualtran/bloqs/factoring/ecc/ec_add_r.py index d012e3979..9ced3a9f1 100644 --- a/qualtran/bloqs/factoring/ecc/ec_add_r.py +++ b/qualtran/bloqs/factoring/ecc/ec_add_r.py @@ -179,16 +179,17 @@ def qrom(self) -> QROAMClean: cR = self.R data_a, data_b, data_lam = [0], [0], [0] + mon_int = QMontgomeryUInt(self.n) for _ in range(1, 2**self.add_window_size): - data_a.append(QMontgomeryUInt(self.n).uint_to_montgomery(int(cR.x), int(self.R.mod))) - data_b.append(QMontgomeryUInt(self.n).uint_to_montgomery(int(cR.y), int(self.R.mod))) + data_a.append(mon_int.uint_to_montgomery(int(cR.x), int(self.R.mod))) + data_b.append(mon_int.uint_to_montgomery(int(cR.y), int(self.R.mod))) lam_num = (3 * cR.x**2 + cR.curve_a) % cR.mod lam_denom = (2 * cR.y) % cR.mod if lam_denom != 0: lam = (lam_num * pow(lam_denom, -1, mod=cR.mod)) % cR.mod else: lam = 0 - data_lam.append(QMontgomeryUInt(self.n).uint_to_montgomery(int(lam), int(self.R.mod))) + data_lam.append(mon_int.uint_to_montgomery(int(lam), int(self.R.mod))) cR = cR + self.R return QROAMClean(