Skip to content

Commit e3aeee0

Browse files
fpapa250NoureldinYosrimpharrigan
authored
Add ECAdd() Bloq (#1425)
* Initial commit of ec add waiting on equals to be merged. * Working on tests for ECAdd * ECAdd implementation and tests * remove modmul typo * Fix mypy errors * Better bugfix for ModAdd * Change mod inv classical impl to use monttgomery inv * Fix pytest error * Fix some comments * ECAdd lots of testing * Add comments about bugs to be fixed * Reduce complexity by keeping intermediate values mod p * Stash qmontgomery tests * Address comments * Fix montgomery prod/inv calculations + pylint/mypy --------- Co-authored-by: Noureldin <noureldinyosri@google.com> Co-authored-by: Matthew Harrigan <mpharrigan@google.com>
1 parent 7344830 commit e3aeee0

File tree

11 files changed

+1639
-46
lines changed

11 files changed

+1639
-46
lines changed

qualtran/_infra/data_types.py

+43-1
Original file line numberDiff line numberDiff line change
@@ -772,9 +772,14 @@ class QMontgomeryUInt(QDType):
772772
bitsize: The number of qubits used to represent the integer.
773773
774774
References:
775-
[Montgomery modular multiplication](https://en.wikipedia.org/wiki/Montgomery_modular_multiplication)
775+
[Montgomery modular multiplication](https://en.wikipedia.org/wiki/Montgomery_modular_multiplication).
776+
777+
[Performance Analysis of a Repetition Cat Code Architecture: Computing 256-bit Elliptic Curve Logarithm in 9 Hours with 126133 Cat Qubits](https://arxiv.org/abs/2302.06639).
778+
Gouzien et al. 2023.
779+
We follow Montgomery form as described in the above paper; namely, r = 2^bitsize.
776780
"""
777781

782+
# TODO(https://github.com/quantumlib/Qualtran/issues/1471): Add modulus p as a class member.
778783
bitsize: SymbolicInt
779784

780785
@property
@@ -810,6 +815,43 @@ def assert_valid_classical_val_array(
810815
if np.any(val_array >= 2**self.bitsize):
811816
raise ValueError(f"Too-large classical values encountered in {debug_str}")
812817

818+
def montgomery_inverse(self, xm: int, p: int) -> int:
819+
"""Returns the modular inverse of an integer in montgomery form.
820+
821+
Args:
822+
xm: An integer in montgomery form.
823+
p: The modulus of the finite field.
824+
"""
825+
return ((pow(xm, -1, p)) * pow(2, 2 * self.bitsize, p)) % p
826+
827+
def montgomery_product(self, xm: int, ym: int, p: int) -> int:
828+
"""Returns the modular product of two integers in montgomery form.
829+
830+
Args:
831+
xm: The first montgomery form integer for the product.
832+
ym: The second montgomery form integer for the product.
833+
p: The modulus of the finite field.
834+
"""
835+
return (xm * ym * pow(2, -self.bitsize, p)) % p
836+
837+
def montgomery_to_uint(self, xm: int, p: int) -> int:
838+
"""Converts an integer in montgomery form to a normal form integer.
839+
840+
Args:
841+
xm: An integer in montgomery form.
842+
p: The modulus of the finite field.
843+
"""
844+
return (xm * pow(2, -self.bitsize, p)) % p
845+
846+
def uint_to_montgomery(self, x: int, p: int) -> int:
847+
"""Converts an integer into montgomery form.
848+
849+
Args:
850+
x: An integer.
851+
p: The modulus of the finite field.
852+
"""
853+
return (x * pow(2, int(self.bitsize), p)) % p
854+
813855

814856
@attrs.frozen
815857
class QGF(QDType):

qualtran/_infra/data_types_test.py

+26
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,32 @@ def test_qmontgomeryuint():
135135
assert is_symbolic(QMontgomeryUInt(sympy.Symbol('x')))
136136

137137

138+
@pytest.mark.parametrize('p', [13, 17, 29])
139+
@pytest.mark.parametrize('val', [1, 5, 7, 9])
140+
def test_qmontgomeryuint_operations(val, p):
141+
qmontgomeryuint_8 = QMontgomeryUInt(8)
142+
# Convert value to montgomery form and get the modular inverse.
143+
val_m = qmontgomeryuint_8.uint_to_montgomery(val, p)
144+
mod_inv = qmontgomeryuint_8.montgomery_inverse(val_m, p)
145+
146+
# Calculate the product in montgomery form and convert back to normal form for assertion.
147+
assert (
148+
qmontgomeryuint_8.montgomery_to_uint(
149+
qmontgomeryuint_8.montgomery_product(val_m, mod_inv, p), p
150+
)
151+
== 1
152+
)
153+
154+
155+
@pytest.mark.parametrize('p', [13, 17, 29])
156+
@pytest.mark.parametrize('val', [1, 5, 7, 9])
157+
def test_qmontgomeryuint_conversions(val, p):
158+
qmontgomeryuint_8 = QMontgomeryUInt(8)
159+
assert val == qmontgomeryuint_8.montgomery_to_uint(
160+
qmontgomeryuint_8.uint_to_montgomery(val, p), p
161+
)
162+
163+
138164
def test_qgf():
139165
qgf_256 = QGF(characteristic=2, degree=8)
140166
assert str(qgf_256) == 'QGF(2**8)'

qualtran/bloqs/arithmetic/_shims.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@
2222

2323
from attrs import frozen
2424

25-
from qualtran import Bloq, QBit, QUInt, Register, Signature
25+
from qualtran import Bloq, QBit, QMontgomeryUInt, QUInt, Register, Signature
26+
from qualtran.bloqs.arithmetic.bitwise import BitwiseNot
27+
from qualtran.bloqs.arithmetic.controlled_addition import CAdd
2628
from qualtran.bloqs.basic_gates import Toffoli
29+
from qualtran.bloqs.basic_gates.swap import TwoBitCSwap
2730
from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator
2831

2932

@@ -39,6 +42,20 @@ def build_call_graph(self, ssa: SympySymbolAllocator) -> BloqCountDictT:
3942
return {Toffoli(): self.n - 2}
4043

4144

45+
@frozen
46+
class CSub(Bloq):
47+
n: int
48+
49+
@cached_property
50+
def signature(self) -> 'Signature':
51+
return Signature(
52+
[Register('ctrl', QBit()), Register('x', QUInt(self.n)), Register('y', QUInt(self.n))]
53+
)
54+
55+
def build_call_graph(self, ssa: SympySymbolAllocator) -> BloqCountDictT:
56+
return {CAdd(QMontgomeryUInt(self.n)): 1, BitwiseNot(QMontgomeryUInt(self.n)): 3}
57+
58+
4259
@frozen
4360
class Lt(Bloq):
4461
n: int
@@ -62,3 +79,6 @@ class CHalf(Bloq):
6279
@cached_property
6380
def signature(self) -> 'Signature':
6481
return Signature([Register('ctrl', QBit()), Register('x', QUInt(self.n))])
82+
83+
def build_call_graph(self, ssa: SympySymbolAllocator) -> BloqCountDictT:
84+
return {TwoBitCSwap(): self.n}

qualtran/bloqs/factoring/ecc/ec_add.ipynb

+27-9
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,22 @@
4141
"This takes elliptic curve points given by (a, b) and (x, y)\n",
4242
"and outputs the sum (x_r, y_r) in the second pair of registers.\n",
4343
"\n",
44+
"Because the decomposition of this Bloq is complex, we split it into six separate parts\n",
45+
"corresponding to the parts described in figure 10 of the Litinski paper cited below. We follow\n",
46+
"the signature from figure 5 and break down the further decompositions based on the steps in\n",
47+
"figure 10.\n",
48+
"\n",
4449
"#### Parameters\n",
4550
" - `n`: The bitsize of the two registers storing the elliptic curve point\n",
46-
" - `mod`: The modulus of the field in which we do the addition. \n",
51+
" - `mod`: The modulus of the field in which we do the addition.\n",
52+
" - `window_size`: The number of bits in the ModMult window. \n",
4753
"\n",
4854
"#### Registers\n",
49-
" - `a`: The x component of the first input elliptic curve point of bitsize `n`.\n",
50-
" - `b`: The y component of the first input elliptic curve point of bitsize `n`.\n",
51-
" - `x`: The x component of the second input elliptic curve point of bitsize `n`, which will contain the x component of the resultant curve point.\n",
52-
" - `y`: The y component of the second input elliptic curve point of bitsize `n`, which will contain the y component of the resultant curve point.\n",
53-
" - `lam`: The precomputed lambda slope used in the addition operation. \n",
55+
" - `a`: The x component of the first input elliptic curve point of bitsize `n` in montgomery form.\n",
56+
" - `b`: The y component of the first input elliptic curve point of bitsize `n` in montgomery form.\n",
57+
" - `x`: The x component of the second input elliptic curve point of bitsize `n` in montgomery form, which will contain the x component of the resultant curve point.\n",
58+
" - `y`: The y component of the second input elliptic curve point of bitsize `n` in montgomery form, which will contain the y component of the resultant curve point.\n",
59+
" - `lam_r`: The precomputed lambda slope used in the addition operation if (a, b) = (x, y) in montgomery form. \n",
5460
"\n",
5561
"#### References\n",
5662
" - [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). Litinski. 2023. Fig 5.\n"
@@ -91,6 +97,18 @@
9197
"ec_add = ECAdd(n, mod=p)"
9298
]
9399
},
100+
{
101+
"cell_type": "code",
102+
"execution_count": null,
103+
"id": "170da165",
104+
"metadata": {
105+
"cq.autogen": "ECAdd.ec_add_small"
106+
},
107+
"outputs": [],
108+
"source": [
109+
"ec_add_small = ECAdd(5, mod=7)"
110+
]
111+
},
94112
{
95113
"cell_type": "markdown",
96114
"id": "39210af4",
@@ -111,8 +129,8 @@
111129
"outputs": [],
112130
"source": [
113131
"from qualtran.drawing import show_bloqs\n",
114-
"show_bloqs([ec_add],\n",
115-
" ['`ec_add`'])"
132+
"show_bloqs([ec_add, ec_add_small],\n",
133+
" ['`ec_add`', '`ec_add_small`'])"
116134
]
117135
},
118136
{
@@ -157,7 +175,7 @@
157175
"name": "python",
158176
"nbconvert_exporter": "python",
159177
"pygments_lexer": "ipython3",
160-
"version": "3.11.7"
178+
"version": "3.10.13"
161179
}
162180
},
163181
"nbformat": 4,

0 commit comments

Comments
 (0)