From 6452fbc7d2f1e9f7038c01c8b882eb205ff3350d Mon Sep 17 00:00:00 2001 From: Gerald Teschl Date: Wed, 26 Jun 2024 23:24:42 +0200 Subject: [PATCH 1/5] Some fixes/extensions --- doc/kryptools.ipynb | 99 ++++++++++++++++++++++++++++++++++++--------- kryptools/poly.py | 41 ++++++++++++++++--- 2 files changed, 115 insertions(+), 25 deletions(-) diff --git a/doc/kryptools.ipynb b/doc/kryptools.ipynb index f677c36..ca6d32b 100644 --- a/doc/kryptools.ipynb +++ b/doc/kryptools.ipynb @@ -359,7 +359,6 @@ ], "source": [ "from fractions import Fraction\n", - "\n", "from kryptools import cf, convergents\n", "\n", "cf(Fraction(18, 23))" @@ -411,7 +410,7 @@ { "data": { "text/plain": [ - "-1" + "1" ] }, "execution_count": 13, @@ -422,7 +421,7 @@ "source": [ "from kryptools import jacobi_symbol\n", "\n", - "jacobi_symbol(3, 7)" + "jacobi_symbol(4, 7)" ] }, { @@ -493,7 +492,7 @@ "id": "76fb4f61-d902-4432-92c9-7383c4e068cd", "metadata": {}, "source": [ - "To compute the order of a element of the [multiplicative group of integers modulo $n$](https://en.wikipedia.org/wiki/Multiplicative_group_of_integers_modulo_n), $\\mathbb{Z}_n^*$, use" + "To compute the order of an element of the [multiplicative group of integers modulo $n$](https://en.wikipedia.org/wiki/Multiplicative_group_of_integers_modulo_n), $\\mathbb{Z}_n^*$, use" ] }, { @@ -1558,6 +1557,14 @@ " return Poly(c, ring = gf, modulus = kyber(n))" ] }, + { + "cell_type": "markdown", + "id": "49847633-a975-4537-9ddd-9fc49b898dce", + "metadata": {}, + "source": [ + "The matrix" + ] + }, { "cell_type": "code", "execution_count": 47, @@ -1577,12 +1584,21 @@ } ], "source": [ - "Matrix(\n", + "A = Matrix(\n", " [\n", " [PolyKyber([randint(0, p - 1) for _ in range(n)]) for i in range(k)]\n", " for j in range(k)\n", " ]\n", - ")" + ")\n", + "A" + ] + }, + { + "cell_type": "markdown", + "id": "2fdfe1e9-2cd4-4725-afa7-08b195d3b1c6", + "metadata": {}, + "source": [ + "The small solution" ] }, { @@ -1594,7 +1610,8 @@ { "data": { "text/plain": [ - "[ x^3 + x + 96, 96 x^2 + x + 1 ]" + "[ x^3 + x + 96 ]\n", + "[ 96 x^2 + x + 1 ]" ] }, "execution_count": 48, @@ -1603,7 +1620,16 @@ } ], "source": [ - "Matrix([[PolyKyber([randint(-1, 1) for _ in range(n)]) for i in range(k)]])" + "s = Matrix([ PolyKyber([randint(-1,1) for _ in range(n)]) for i in range(k)])\n", + "s" + ] + }, + { + "cell_type": "markdown", + "id": "ce0eed66-9270-4a04-871b-13578cbde26f", + "metadata": {}, + "source": [ + "The error" ] }, { @@ -1615,7 +1641,8 @@ { "data": { "text/plain": [ - "[ 96 x^3 + x + 95, 2 x ]" + "[ 96 x^3 + x + 95 ]\n", + "[ 2 x ]" ] }, "execution_count": 49, @@ -1625,7 +1652,39 @@ ], "source": [ "q = p // (4 * k * n)\n", - "Matrix([[PolyKyber([binomialvariate(2 * q) - q for _ in range(n)]) for i in range(k)]])" + "e = Matrix([PolyKyber([binomialvariate(2 * q) - q for _ in range(n)]) for i in range(k)])\n", + "e" + ] + }, + { + "cell_type": "markdown", + "id": "684f101f-e79f-4a30-90a5-f5dd3a0557eb", + "metadata": {}, + "source": [ + "The inhomogenous vector" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "fabcbecf-cf96-4211-a1e3-ad0f5a337a09", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[ 47 x^3 + 63 x^2 + 60 x + 38 ]\n", + "[ 47 x^3 + 30 x^2 + 42 x + 47 ]" + ] + }, + "execution_count": 50, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "b = A * s + e\n", + "b" ] }, { @@ -1646,7 +1705,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 51, "id": "9b566550-f23c-4fbc-8b7c-63bbee8e0c72", "metadata": {}, "outputs": [ @@ -1685,7 +1744,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 52, "id": "55c29e0c-e0c2-409a-af23-6590fd9cc545", "metadata": {}, "outputs": [ @@ -1695,7 +1754,7 @@ "(113, 91)" ] }, - "execution_count": 51, + "execution_count": 52, "metadata": {}, "output_type": "execute_result" } @@ -1717,7 +1776,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 53, "id": "2a3d8e7d-71b3-4a93-b6cc-803f00432c56", "metadata": {}, "outputs": [ @@ -1727,7 +1786,7 @@ "129" ] }, - "execution_count": 52, + "execution_count": 53, "metadata": {}, "output_type": "execute_result" } @@ -1747,7 +1806,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 54, "id": "bb93b5fc-99c9-4977-8299-9342b84e143d", "metadata": {}, "outputs": [], @@ -1767,7 +1826,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 55, "id": "41e18357-da42-4cd7-bd4e-1c3355f1c69d", "metadata": {}, "outputs": [], @@ -1790,7 +1849,7 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 56, "id": "af81d5f2-a524-4555-8e74-06a0c9a7ea08", "metadata": {}, "outputs": [ @@ -1801,7 +1860,7 @@ " 0x02449ef2ed30a8deb96e584a08c329adbf1be87ce40f1a0e7b4e86178682c41a9c)" ] }, - "execution_count": 55, + "execution_count": 56, "metadata": {}, "output_type": "execute_result" } @@ -1837,7 +1896,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.2" + "version": "3.12.1" } }, "nbformat": 4, diff --git a/kryptools/poly.py b/kryptools/poly.py index 1366f3b..d49ae40 100644 --- a/kryptools/poly.py +++ b/kryptools/poly.py @@ -83,7 +83,12 @@ def map(self, func): def __add__(self, other: "Poly") -> "Poly": if not isinstance(other, self.__class__): - raise NotImplementedError(f"Cannot add {self} and {other}.") + try: + tmp = self.coeff[:] + tmp[0] += other + return self.__class__(tmp, modulus=self.modulus) + except: + return NotImplemented ls, lo = len(self.coeff), len(other.coeff) if ls < lo: scoeff = self.coeff + (lo - ls) * [0] @@ -98,12 +103,27 @@ def __add__(self, other: "Poly") -> "Poly": modulus = other.modulus return self.__class__([s + o for s, o in zip(scoeff, ocoeff)], modulus=modulus) + def __radd__(self, other: "Poly") -> "Poly": + if not isinstance(other, self.__class__): + try: + tmp = self.coeff[:] + tmp[0] += other + return self.__class__(tmp, modulus=self.modulus) + except: + pass + return NotImplemented + def __neg__(self) -> "Poly": return Poly([-s for s in self.coeff], modulus=self.modulus) def __sub__(self, other: "Poly") -> "Poly": if not isinstance(other, self.__class__): - raise NotImplementedError(f"Cannot subtract {self} and {other}.") + try: + tmp = self.coeff[:] + tmp[0] -= other + return self.__class__(tmp, modulus=self.modulus) + except: + return NotImplemented ls, lo = len(self.coeff), len(other.coeff) if ls < lo: scoeff = self.coeff + (lo - ls) * [0] @@ -118,11 +138,22 @@ def __sub__(self, other: "Poly") -> "Poly": modulus = other.modulus return self.__class__([s - o for s, o in zip(scoeff, ocoeff)], modulus=modulus) + def __rsub__(self, other: "Poly") -> "Poly": + if not isinstance(other, self.__class__): + try: + tmp = self.coeff[:] + tmp[0] -= other + return self.__class__(tmp, modulus=self.modulus) + except: + pass + return NotImplemented + def __mul__(self, other: "Poly") -> "Poly": - if isinstance(other, int): - return Poly([other * s for s in self.coeff]) if not isinstance(other, self.__class__): - raise NotImplementedError(f"Cannot multiply {self} and {other}.") + try: + return Poly([other * s for s in self.coeff]) + except: + return NotImplemented ls, lo = len(self.coeff), len(other.coeff) coeff = [0] * (ls + lo - 1) for k in range(ls + lo - 1): From 4c3eabdfff800f1c5d2bdd0215d483f308585ad5 Mon Sep 17 00:00:00 2001 From: Gerald Teschl Date: Thu, 27 Jun 2024 10:05:16 +0200 Subject: [PATCH 2/5] Small improvements/extensions --- doc/kryptools.ipynb | 73 ++++++++++++++++++++++++++----------- kryptools/__init__.py | 4 +-- kryptools/la.py | 83 +++++++++++++++++++++++++++++++++---------- kryptools/lat.py | 17 +++++---- 4 files changed, 128 insertions(+), 49 deletions(-) diff --git a/doc/kryptools.ipynb b/doc/kryptools.ipynb index ca6d32b..fb0b8d9 100644 --- a/doc/kryptools.ipynb +++ b/doc/kryptools.ipynb @@ -1476,6 +1476,39 @@ "U" ] }, + { + "cell_type": "markdown", + "id": "e88ff7af-c3aa-4fdc-a83f-2f535df0b72c", + "metadata": {}, + "source": [ + "To get a random unimodular matrix use" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "id": "694c25e5-d01f-4acf-ab57-69bffb0fb9a8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[ 1, -8, -9 ]\n", + "[ 0, 3, 4 ]\n", + "[ 4, 2, 9 ]" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from kryptools import random_unimodular_matrix\n", + "\n", + "random_unimodular_matrix(3, max_val = 9)" + ] + }, { "cell_type": "markdown", "id": "c8427b8b-db49-4fa1-ba07-63382746bcca", @@ -1494,7 +1527,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": 46, "id": "d3c69787-da17-4cd3-b282-a68ee4d177e6", "metadata": {}, "outputs": [ @@ -1504,7 +1537,7 @@ "3 x^2 + 2 x + 1" ] }, - "execution_count": 45, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } @@ -1533,7 +1566,7 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": 47, "id": "8d4c4b43-e593-407a-92f8-86b653a09c8a", "metadata": {}, "outputs": [], @@ -1567,7 +1600,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 48, "id": "bf50f802-93f5-4c79-b982-a208486f9a72", "metadata": {}, "outputs": [ @@ -1578,7 +1611,7 @@ "[ 27 x^3 + 74 x^2 + 45 x + 61, 17 x^3 + 36 x^2 + 17 x + 64 ]" ] }, - "execution_count": 47, + "execution_count": 48, "metadata": {}, "output_type": "execute_result" } @@ -1603,7 +1636,7 @@ }, { "cell_type": "code", - "execution_count": 48, + "execution_count": 49, "id": "9e0ebbd3-0d4b-4e1b-b4dd-323b5a86dc23", "metadata": {}, "outputs": [ @@ -1614,7 +1647,7 @@ "[ 96 x^2 + x + 1 ]" ] }, - "execution_count": 48, + "execution_count": 49, "metadata": {}, "output_type": "execute_result" } @@ -1634,7 +1667,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 50, "id": "563280a9-27b9-4707-ae14-9aeef9bbe5a6", "metadata": {}, "outputs": [ @@ -1645,7 +1678,7 @@ "[ 2 x ]" ] }, - "execution_count": 49, + "execution_count": 50, "metadata": {}, "output_type": "execute_result" } @@ -1666,7 +1699,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 51, "id": "fabcbecf-cf96-4211-a1e3-ad0f5a337a09", "metadata": {}, "outputs": [ @@ -1677,7 +1710,7 @@ "[ 47 x^3 + 30 x^2 + 42 x + 47 ]" ] }, - "execution_count": 50, + "execution_count": 51, "metadata": {}, "output_type": "execute_result" } @@ -1705,7 +1738,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 52, "id": "9b566550-f23c-4fbc-8b7c-63bbee8e0c72", "metadata": {}, "outputs": [ @@ -1744,7 +1777,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 53, "id": "55c29e0c-e0c2-409a-af23-6590fd9cc545", "metadata": {}, "outputs": [ @@ -1754,7 +1787,7 @@ "(113, 91)" ] }, - "execution_count": 52, + "execution_count": 53, "metadata": {}, "output_type": "execute_result" } @@ -1776,7 +1809,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 54, "id": "2a3d8e7d-71b3-4a93-b6cc-803f00432c56", "metadata": {}, "outputs": [ @@ -1786,7 +1819,7 @@ "129" ] }, - "execution_count": 53, + "execution_count": 54, "metadata": {}, "output_type": "execute_result" } @@ -1806,7 +1839,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 55, "id": "bb93b5fc-99c9-4977-8299-9342b84e143d", "metadata": {}, "outputs": [], @@ -1826,7 +1859,7 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 56, "id": "41e18357-da42-4cd7-bd4e-1c3355f1c69d", "metadata": {}, "outputs": [], @@ -1849,7 +1882,7 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 57, "id": "af81d5f2-a524-4555-8e74-06a0c9a7ea08", "metadata": {}, "outputs": [ @@ -1860,7 +1893,7 @@ " 0x02449ef2ed30a8deb96e584a08c329adbf1be87ce40f1a0e7b4e86178682c41a9c)" ] }, - "execution_count": 56, + "execution_count": 57, "metadata": {}, "output_type": "execute_result" } diff --git a/kryptools/__init__.py b/kryptools/__init__.py index 55b0a18..9816e06 100644 --- a/kryptools/__init__.py +++ b/kryptools/__init__.py @@ -5,8 +5,8 @@ from .dlp import dlog from .ec import EC_Weierstrass from .factor import factorint -from .la import Matrix -from .lat import gram_det, hadamard_ratio, hermite_nf, gram_schmidt, babai_round_cvp, babai_plane_cvp, lagrange_lr, lll +from .la import Matrix, zeros, eye +from .lat import gram_det, hadamard_ratio, hermite_nf, gram_schmidt, babai_round_cvp, babai_plane_cvp, lagrange_lr, lll, random_unimodular_matrix from .nt import cf, convergents, jacobi_symbol, sqrt_mod, euler_phi, order, carmichael_lambda from .poly import Poly from .primes import sieve_eratosthenes, isprime diff --git a/kryptools/la.py b/kryptools/la.py index f283f97..0c49b5d 100644 --- a/kryptools/la.py +++ b/kryptools/la.py @@ -2,7 +2,7 @@ Linear algebra """ -from math import sqrt, prod +from math import inf, sqrt, prod class Matrix: """ @@ -48,12 +48,18 @@ def __getitem__(self, item): i, j = item if isinstance(i, int) and isinstance(j, int): return self.matrix[i][j] - rows = range(self.rows)[i] if isinstance(i, int): - rows = [ rows ] - cols = range(self.cols)[j] + rows = [ i ] + elif isinstance(i, list): + rows = i + else: + rows = range(self.rows)[i] if isinstance(j, int): - cols = [ cols ] + cols = [ j ] + elif isinstance(j, list): + cols = i + else: + cols = range(self.cols)[j] return Matrix([[self.matrix[i][j] for j in cols] for i in rows]) i, j = divmod(item, self.cols) return self.matrix[i][j] @@ -64,12 +70,18 @@ def __setitem__(self, item, value): if isinstance(i, int) and isinstance(j, int): self.matrix[i][j] = value return - rows = range(self.rows)[i] if isinstance(i, int): - rows = [ rows ] - cols = range(self.cols)[j] + rows = [ i ] + elif isinstance(i, list): + rows = i + else: + rows = range(self.rows)[i] if isinstance(j, int): - cols = [ cols ] + cols = [ j ] + elif isinstance(j, list): + cols = i + else: + cols = range(self.cols)[j] for i, ii in zip(cols,range(len(cols))): for j, jj in zip(rows,range(len(rows))): self.matrix[j][i] = value[jj,ii] @@ -97,9 +109,17 @@ def norm2(self) -> float: "Squared Frobenius/Euclidean norm." return sum( sum(x*x for x in row) for row in self.matrix ) - def norm(self) -> float: - "Frobenius/Euclidean norm." - return sqrt(self.norm2()) + def norm(self, p: int = 2) -> float: + "p-norm of a matrix regarded as a vector." + if p == 2: + return sqrt(self.norm2()) + if p == 1: + return sum( sum(abs(x) for x in row) for row in self.matrix ) + if p == inf: + return max( max(abs(x) for x in row) for row in self.matrix ) + tmp = sum( sum(abs(x)**p for x in row) for row in self.matrix ) + return(tmp**(1/p)) + def dot(self, other) -> int: if self.rows == 1 and other.rows == 1 and self.cols == other.cols: @@ -112,14 +132,16 @@ def transpose(self) -> "Matrix": return Matrix([list(i) for i in zip(*self.matrix)]) def multiply(self, other) -> "Matrix": - if not isinstance(other, Matrix) or self.cols != other.rows: + if not isinstance(other, Matrix): return NotImplemented - result = [[0 for j in range(other.cols)] for i in range(self.rows)] + if self.cols != other.rows: + raise NotImplementedError("Matrix dimensions do not match!") + result = self.zeros(self.rows, other.cols) for i in range(self.rows): for j in range(other.cols): for k in range(other.rows): - result[i][j] += self.matrix[i][k] * other.matrix[k][j] - return Matrix(result) + result.matrix[i][j] += self.matrix[i][k] * other.matrix[k][j] + return result def __add__(self, other) -> "Matrix": if isinstance(other, Matrix) and other.cols == self.cols and other.rows == self.rows: @@ -211,13 +233,38 @@ def inv(self) -> "Matrix": raise ValueError("Matrix is not invertible!") return MM[:,n:] + def zeros(self, m: int = None, n: int = None): + if not m and not n: + n, m = self.cols, self.rows + elif not n: + n = m + zero = 0 * self[0] + return Matrix([[ zero for j in range(n)] for i in range(m) ]) + + def eye(self, m: int = None, n: int = None): + def delta(i, j): + if i == j: + return 1 + return 0 + if not m and not n: + n, m = self.cols, self.rows + elif not n: + n = m + zero = 0 * self[0] + one = 1 + zero + return Matrix([[ delta(i, j) for j in range(n) ] for i in range(m) ]) + -def zeros(m:int, n: int) -> "Matrix": +def zeros(m: int, n: int = None) -> "Matrix": + if not n: + n = m return Matrix([[ 0 for j in range(n)] for i in range(m) ]) -def eye(m:int, n: int) -> "Matrix": +def eye(m:int, n: int = None) -> "Matrix": def delta(i, j): if i == j: return 1 return 0 + if not n: + n = m return Matrix([[ delta(i, j) for j in range(n) ] for i in range(m) ]) diff --git a/kryptools/lat.py b/kryptools/lat.py index a45df97..83cb281 100644 --- a/kryptools/lat.py +++ b/kryptools/lat.py @@ -4,7 +4,8 @@ from math import prod from fractions import Fraction -from .la import Matrix, zeros, eye +from random import choice, sample +from .la import Matrix, zeros def hermite_nf(M: Matrix) -> Matrix: "Compute the Hermite normal form of a matrix M." @@ -51,7 +52,7 @@ def norm2(v: Matrix) -> float: def gram_schmidt(U: Matrix) -> (Matrix, Matrix): "Compute the Gram-Schmidt orthogonalization of the column vectors of a matrix M." - M = eye(U.cols, U.rows) + M = U.eye() Us = U[:, :] for j in range(1, U.rows): tmp = U[:, j] @@ -105,7 +106,7 @@ def lll(V: Matrix, delta: float = 0.75, sort: bool = True) -> Matrix: U = V[:, :] Us = U[:, :] Us.map(Fraction) - M = zeros(U.cols, U.rows) + M = U.zeros() M.map(Fraction) M[0, 0] = norm2(Us[:, 0]) # we store the squared norms on the diagonal for l in range(1, U.rows): # Gram-Schmidt decomposition @@ -161,11 +162,9 @@ def lll(V: Matrix, delta: float = 0.75, sort: bool = True) -> Matrix: U[:, j] = tmp[j] return U -from random import choice, sample - -def random_unimodular_matrix(n: int, iterations: int = 50, max_val: int = 9) -> Matrix: +def random_unimodular_matrix(n: int, iterations: int = 50, max_val: int = None) -> Matrix: "Create a random unimodular matrix of dimension n." - W = Matrix.zeros(n, n) + W = zeros(n) for i in range(n): for j in range(i, n): W[i, j] = choice([-1, 1]) @@ -173,10 +172,10 @@ def random_unimodular_matrix(n: int, iterations: int = 50, max_val: int = 9) -> for _ in range(iterations): i, j = sample(range(n), 2) tmp = W[i, :] + choice([-1, 1]) * W[j, :] - if max([abs(x) for x in tmp]) <= max_val: + if not max_val or max([abs(x) for x in tmp]) <= max_val: W[i, :] = tmp i, j = sample(range(n), 2) tmp = W[:, i] + choice([-1, 1]) * W[:, j] - if max([abs(x) for x in tmp]) <= max_val: + if not max_val or max([abs(x) for x in tmp]) <= max_val: W[:, i] = tmp return W From b6c53fab3d79392ce63e9c7f2f2d9ac43a7f9f6d Mon Sep 17 00:00:00 2001 From: Gerald Teschl Date: Thu, 27 Jun 2024 11:14:09 +0200 Subject: [PATCH 3/5] Fixed some pylint recommendations --- doc/kryptools.ipynb | 40 ++++++++++++++++++++-------------------- kryptools/__init__.py | 6 +++--- kryptools/dlp.py | 5 ++--- kryptools/dlp_ic.py | 2 +- kryptools/dlp_qs.py | 25 ++++++++++++------------- kryptools/ec.py | 34 ++++++++++++++++++++++------------ kryptools/factor.py | 6 +++--- kryptools/factor_dix.py | 27 ++++++++++++++++++++++----- kryptools/factor_ecm.py | 3 +++ kryptools/la.py | 15 +++++++++++---- kryptools/nt.py | 18 ++++++++---------- kryptools/poly.py | 2 ++ kryptools/primes.py | 5 ++--- 13 files changed, 111 insertions(+), 77 deletions(-) diff --git a/doc/kryptools.ipynb b/doc/kryptools.ipynb index fb0b8d9..e72b22a 100644 --- a/doc/kryptools.ipynb +++ b/doc/kryptools.ipynb @@ -618,11 +618,22 @@ "execution_count": 19, "id": "39f76a97-1b88-4b95-aac7-bf403043d7b7", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "487" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "from kryptools.factor_pm1 import factor_pm1\n", "\n", - "factor_pm1(90003)" + "factor_pm1(832283)" ] }, { @@ -638,22 +649,11 @@ "execution_count": 20, "id": "e68d0db0-7d1d-4a00-ad6e-10656c829708", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "3" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "from kryptools.factor_ecm import factor_ecm\n", "\n", - "factor_ecm(90003)" + "factor_ecm(832283)" ] }, { @@ -673,7 +673,7 @@ { "data": { "text/plain": [ - "3" + "1709" ] }, "execution_count": 21, @@ -684,7 +684,7 @@ "source": [ "from kryptools.factor_dix import factor_dixon\n", "\n", - "factor_dixon(90003)" + "factor_dixon(832283)" ] }, { @@ -704,7 +704,7 @@ { "data": { "text/plain": [ - "3" + "1709" ] }, "execution_count": 22, @@ -715,7 +715,7 @@ "source": [ "from kryptools.factor_qs import factor_qs\n", "\n", - "factor_qs(90003)" + "factor_qs(832283)" ] }, { @@ -1890,7 +1890,7 @@ "data": { "text/plain": [ "(23529166972251975810298992610444093284962271729068871731505428628592805361023,\n", - " 0x02449ef2ed30a8deb96e584a08c329adbf1be87ce40f1a0e7b4e86178682c41a9c)" + " 0x03449ef2ed30a8deb96e584a08c329adbf1be87ce40f1a0e7b4e86178682c41a9c)" ] }, "execution_count": 57, diff --git a/kryptools/__init__.py b/kryptools/__init__.py index 9816e06..a76e8cb 100644 --- a/kryptools/__init__.py +++ b/kryptools/__init__.py @@ -2,12 +2,12 @@ Implemenation of same basic algorithms used in cryptography. """ +from .nt import cf, convergents, jacobi_symbol, sqrt_mod, euler_phi, order, carmichael_lambda +from .primes import sieve_eratosthenes, isprime +from .factor import factorint from .dlp import dlog from .ec import EC_Weierstrass -from .factor import factorint from .la import Matrix, zeros, eye from .lat import gram_det, hadamard_ratio, hermite_nf, gram_schmidt, babai_round_cvp, babai_plane_cvp, lagrange_lr, lll, random_unimodular_matrix -from .nt import cf, convergents, jacobi_symbol, sqrt_mod, euler_phi, order, carmichael_lambda from .poly import Poly -from .primes import sieve_eratosthenes, isprime from .Zmod import Zmod diff --git a/kryptools/dlp.py b/kryptools/dlp.py index 44f5c2c..4a73d99 100644 --- a/kryptools/dlp.py +++ b/kryptools/dlp.py @@ -28,10 +28,9 @@ def _dlog_switch(a: int, b: int, n: int, m: int) -> int: """Compute the discrete log_a(b) in Z_n of an element a of order m choosing an appropriate method.""" if m < 1000: return dlog_naive(a, b, n, m) - elif log(m) - 6 < 2 * sqrt(log(n) * log(log(n))): # compare the theoreticaly expected running times ob bsgs and ic; the constant 6 is determined experimentally + if log(m) - 6 < 2 * sqrt(log(n) * log(log(n))): # compare the theoreticaly expected running times ob bsgs and ic; the constant 6 is determined experimentally return dlog_bsgs(a, b, n, m) - else: - return dlog_qs(a, b, n, m) + return dlog_qs(a, b, n, m) def _dlog_ph(a: int, b: int, n: int, q: int, k: int) -> int: """Compute the discrete log_a(b) in Z_n of an element a of order q^k using Pohlig-Hellman reduction.""" diff --git a/kryptools/dlp_ic.py b/kryptools/dlp_ic.py index 59e0de5..15e379e 100644 --- a/kryptools/dlp_ic.py +++ b/kryptools/dlp_ic.py @@ -69,7 +69,7 @@ def process_relation(relation: list) -> None or int: rinv = pow(ri, -1, m) relation[i] = 1 for j in range(i+1, len_relations + 1): - relation[j] = rinv * relation[j] % m + relation[j] = rinv * relation[j] % m relations[i] = relation if verbose > 2: print(n_relations, f"rel found (index={i}) :", relation) diff --git a/kryptools/dlp_qs.py b/kryptools/dlp_qs.py index 86339a0..45204cd 100644 --- a/kryptools/dlp_qs.py +++ b/kryptools/dlp_qs.py @@ -50,18 +50,18 @@ def is_smooth(n: int, factorbase: list, factorbase_len: int, smallprimes_len: in while n % p == 0: # divide by p as many times as possible factors[i] += 1 n = n // p - if pollard_k: + if pollard_k: if gcd(pow(2, pollard_k, n)-1, n) == 1: # Pollard p-1 test return None # most likely not smooth, give up for i in range(smallprimes_len, factorbase_len): p = factorbase[i] while n % p == 0: # divide by p as many times as possible factors[i] += 1 - n = n // p + n = n // p if n != 1: return None # the number factors if at the end nothing is left return factors - + def dlog_qs(a: int, b: int, n: int, m: int, pollard: bool = True, sieve_factor: float = None, verbose: int = 0) -> int: """ Compute the discrete log_a(b) in Z_p of an element a of prime order m using Index Calculus with a quadratic sieve. @@ -104,7 +104,7 @@ def find_relation(include_b: bool) -> None or list: relation += [ 0, x ] relation = [0] * sieve_bound + relation # sieve values + primes + b + x return relation - + # this functions does the linear algebra def process_relation(relation: list) -> None or int: """Add a new relation to the linear system and keep the system in echelon form.""" @@ -122,10 +122,10 @@ def process_relation(relation: list) -> None or int: relation[j] = (relation[j] - ri * relations[i][j]) % m continue # normalize the first nonzero entry - rinv = pow(ri, -1, m) + rinv = pow(ri, -1, m) relation[i] = 1 for j in range(i+1, len_relations + 1): - relation[j] = rinv * relation[j] % m + relation[j] = rinv * relation[j] % m relations[i] = relation if verbose > 2: print(n_relations, f"rel found (index={i}) :", relation) @@ -143,8 +143,8 @@ def process_relation(relation: list) -> None or int: # if ri > 0: # relations[i][index] = 0 # for j in range(index+1, len_relations + 1): - # relations[i][j] = (relations[i][j] - ri * relation[j]) % m - #print(n_relations, f"i={i}={index} ({len_relations})", relations) + # relations[i][j] = (relations[i][j] - ri * relation[j]) % m + #print(n_relations, f"i={i}={index} ({len_relations})", relations) if index == len_relations - 1: # we found the solution if verbose: print(f"Success after {n_relations} relations out of {len_relations}.") @@ -160,7 +160,7 @@ def process_relation(relation: list) -> None or int: # Determine the parameters # - B, expected_trys, expected_trys2 = determine_factorbound(n) + B, expected_trys, expected_trys2 = determine_factorbound(n) max_trys = 10 * expected_trys factorbase = [] factorbase = tuple(p for p in sieve_eratosthenes(B) if gcd(p,n) == 1) # compute the factorbse @@ -172,7 +172,7 @@ def process_relation(relation: list) -> None or int: if pollard: # should we speed up trial division with Pollard p-1 smallprimes_len, pollard_k = determine_trialdivison_bounds(B // 150, factorbase) no_sieve_bound = 1 # We do not sieve for primes smaller than this bound (not worth the effort) - no_sieve_primes = [ ] + no_sieve_primes = [ ] for i in range(factorbase_len): if factorbase[i] > no_sieve_bound: break @@ -195,7 +195,7 @@ def process_relation(relation: list) -> None or int: # # Do the sieving # - + sn = isqrt(n - 1) + 1 # ceil(sqrt(n)) d = sn**2 - n sn2 = 2 * sn @@ -203,7 +203,6 @@ def process_relation(relation: list) -> None or int: for j in range(sieve_bound): # we sieve with respect to the quadratic polynomial f_j(x) = (x+sn)*(x+j+sn) - n = x^2 + (2*sn+j)*x + (d+j*sn) snj = sn2 + j - snj2 = snj**2 dj = d + j * sn max_j = sieve_bound - j for i in range(factorbase_len): @@ -294,5 +293,5 @@ def process_relation(relation: list) -> None or int: res = process_relation(relation) if res: return(res) - + raise Exception(f"Sorry, Quadratic sieve could not find enough relations! ({n_relations} - {n_relations_redundant} = {n_relations - n_relations_redundant} out of {len_relations}). Try to increase sieve_factor={sieve_factor}") diff --git a/kryptools/ec.py b/kryptools/ec.py index c8aa9e8..1c0cd5e 100644 --- a/kryptools/ec.py +++ b/kryptools/ec.py @@ -26,12 +26,13 @@ class EC_Weierstrass(): >>> P + Q (195, 41) """ + # pylint: disable=too-many-instance-attributes def __init__(self, p: int, a: int, b: int, order: int = None): if p < 3: raise ValueError(f"{p} must be a prime larger than 2.") if (4 * pow(a, 3, p) + 27 * pow(b, 2, p) ) % p == 0: - raise ValueError(f"Curve is singular!") + raise ValueError("Curve is singular!") self.p = p self.gf = Zmod(p, short = True) self.a = self.gf(a % p) @@ -44,9 +45,9 @@ def __init__(self, p: int, a: int, b: int, order: int = None): def __call__(self, x: int | str | None = None, y: int | None = None, short: bool = False): if isinstance(x, str): x = x.replace(" ", "") - type = x[:2] + coordinate_type = x[:2] x = x[2:] - if type == '02' or type == '03': + if coordinate_type in ('02', '03'): short = 1 x = int(x, 16) if type == '02': @@ -70,15 +71,17 @@ def __contains__(self, P: "ECPoint") -> bool: return P.y**2 == P.x**3 + self.a * P.x + self.b def info(self): - format = "Weierstrass curve y^2 = x^3" + "Display some basic info on the curve" + out = "Weierstrass curve y^2 = x^3" if int(self.a): - format += f" + {self.a} x" + out += f" + {self.a} x" if int(self.b): - format += f" + {self.b}" - format += f" over Z_{self.p}." - print(format) + out += f" + {self.b}" + out += f" over Z_{self.p}." + print(out) def add(self, x1, y1, x2, y2): + "Point addition" if x1 is None: return x2, y2 if x2 is None: @@ -93,6 +96,7 @@ def add(self, x1, y1, x2, y2): return x3, y3 def dbl(self, x, y): + "Point doubling" if x is None or not y: return None, None s = (3 * x**2 + self.a) / (2 * y) @@ -101,6 +105,7 @@ def dbl(self, x, y): return x3, y3 def mult(self, j: int, x, y): # Addition-subtraction ladder + "Point multiplication" if j == 0: return None, None if j < 0: @@ -126,6 +131,7 @@ def mult(self, j: int, x, y): # Addition-subtraction ladder return xx, yy def random(self): + "Return a random point." j = -1 while j == -1: x = self.gf(randint(0, self.p - 1)) @@ -134,6 +140,7 @@ def random(self): return ECPoint(x, randint(0, 1), self, short = True) def order(self, order: int = None) -> int: + "Return the group order." if order: self.group_order = order elif not self.group_order: @@ -144,6 +151,7 @@ def order(self, order: int = None) -> int: return self.group_order def factor_order(self) -> dict: + "Factor the group order." if self.group_order_factors: return self.group_order_factors if not self.group_order: @@ -152,6 +160,7 @@ def factor_order(self) -> dict: return self.group_order_factors def order_naive(self) -> int: + "Return the order of the group by adding the Legendre symbols." a1 = (int(self.a) + 1) % self.p y = int(self.b) order = self.p + 1 + legendre_symbol(y, self.p) @@ -161,6 +170,7 @@ def order_naive(self) -> int: return order def order_shanks_mestre(self) -> int: + "Return the order of the group using the Shanks-Mestre algorithm." if self.p < 230: return self.order_naive() j = 1 @@ -215,7 +225,7 @@ def order_shanks_mestre(self) -> int: class ECPoint: "Point on an elliptic curve" - def __init__(self, x: int, y: int, curve: EC_Weierstrass, short:bool = False): + def __init__(self, x: int|None, y: int|None, curve: EC_Weierstrass, short:bool = False): self.curve = curve if x is None: self.x = None @@ -343,7 +353,7 @@ def dlog_switch(Q, P: "ECPoint", m: int) -> int: def dlog_naive(Q, P: "ECPoint", m: int) -> int: """Compute the discrete log_P(Q) in EC using an exhaustive search.""" if not Q.curve == P.curve and not isinstance(Q, P.__class__): - raise ValueError(f"Points must be on the same curve!") + raise ValueError("Points must be on the same curve!") j = 0 xx, yy = None, None while xx != Q.x: @@ -358,7 +368,7 @@ def dlog_naive(Q, P: "ECPoint", m: int) -> int: def dlog_bsgs(Q, P: "ECPoint", m: int) -> int: """Compute the discrete log_P(Q) in EC if P has order m using Shanks' baby-step-giant-step algorithm.""" if not Q.curve == P.curve and not isinstance(P, Q.__class__): - raise ValueError(f"Points must be on the same curve!") + raise ValueError("Points must be on the same curve!") mm = 1 + isqrt(m - 1) m2 = mm//2 + mm % 1 # we use the group symmetry to halve the number of steps # initialize baby_steps table @@ -372,7 +382,7 @@ def dlog_bsgs(Q, P: "ECPoint", m: int) -> int: giant_stride = -mm * P giant_step = Q for l in range(mm+1): - if giant_step.x == None: + if giant_step.x is None: return l * mm if int(giant_step.x) in baby_steps: j = baby_steps[int(giant_step.x)][0] diff --git a/kryptools/factor.py b/kryptools/factor.py index 5d510e8..08b2376 100644 --- a/kryptools/factor.py +++ b/kryptools/factor.py @@ -5,6 +5,9 @@ from math import isqrt, gcd from .primes import sieve_eratosthenes, isprime +from .factor_pm1 import _pm1_parameters, factor_pm1 +from .factor_ecm import _ecm_parameters, factor_ecm +#from .factor_qs import factor_qs # Factoring @@ -27,9 +30,6 @@ def _factor_fermat(n: int, steps: int = 10) -> list: return a - b a += step -from .factor_pm1 import _pm1_parameters, factor_pm1 -from .factor_ecm import _ecm_parameters, factor_ecm -#from .factor_qs import factor_qs def factorint(n: int, verbose: int = 0) -> list: "Factor a number." diff --git a/kryptools/factor_dix.py b/kryptools/factor_dix.py index 53a23e6..ac6f3a1 100644 --- a/kryptools/factor_dix.py +++ b/kryptools/factor_dix.py @@ -4,9 +4,27 @@ from math import isqrt, gcd, sqrt, log, exp, ceil from .primes import sieve_eratosthenes -from .nt import legendre_symbol, sqrt_mod +from .nt import legendre_symbol from .factor_qs import bytexor, byteset, bytetest +def is_smooth(n: int, factorbase: list, lfb: int) -> bytes or None: + """Try to factor n with respect to a given factorbase. + Upon success a bytestring, whose bits are the exponents with repect to the factorbase mod 2, is returned. + Otherwise None.""" + factors = bytearray(b"\x00") * lfb # we store the exponents mod 2 as bits + if n < 0: + byteset(factors, 0) + n *= -1 + for i, p in enumerate(factorbase): + k = 0 + while n % p == 0: # divide by p as many times as possible + k = (k + 1) % 2 # we only need the exponents mod 2 + n = n // p + if k: + byteset(factors, i + 1) + if n != 1: + return None # the number factors if at the end nothing is left + return factors def factor_dixon(n: int) -> list: """Find factors of n using the method of Dixon.""" @@ -19,7 +37,7 @@ def factor_dixon(n: int) -> list: 2 + 3 * u + 2 * u * log(u) ) # Newton iteration B = int(exp(log(n) / u)) - + # B = int(exp(0.5 * sqrt( log(n) * log(log(n)) )*( 1 + 1/log(log(n)) ))) factorbase = [] for p in sieve_eratosthenes(B): # compute the factorbase @@ -44,7 +62,7 @@ def process_relation(j: int, relation: bytes): index = lf # this will be the index of the first nonzero entry # print(f'{j:3}', ' '.join(f'{b:08b}' for b in reversed(relation))) for i in range(lf): - if bytetest(relation, i) and relations[i] != None: # make this entry zero if we can (Gauss elimination) + if bytetest(relation, i) and relations[i] is not None: # make this entry zero if we can (Gauss elimination) bytexor(relation, relations[i]) if bytetest(relation, i) and index == lf: # is this the index of the first nonzero entry? index = i @@ -85,6 +103,5 @@ def process_relation(j: int, relation: bytes): continue res = process_relation(j, relation) if res: - return(res) + return res return n - diff --git a/kryptools/factor_ecm.py b/kryptools/factor_ecm.py index 1845a6c..d187541 100644 --- a/kryptools/factor_ecm.py +++ b/kryptools/factor_ecm.py @@ -11,6 +11,7 @@ def dbl(P, c2, p): + "EC Montgommery point doubling" # c2 = c - 2 t1 = (P[0] + P[1]) % p t2 = (P[0] - P[1]) % p @@ -19,12 +20,14 @@ def dbl(P, c2, p): def add(P1, P2, P3, p): + "EC Montgommery point addition" t1 = pow(P1[0] * P2[0] - P1[1] * P2[1], 2, p) t2 = pow(P1[0] * P2[1] - P2[0] * P1[1], 2, p) return P3[1] * t1 % p, P3[0] * t2 % p def mult(k, P, c2, p): + "EC Montgommery point multiplication" if k == 1: return P if k == 2: diff --git a/kryptools/la.py b/kryptools/la.py index 0c49b5d..c2e2adb 100644 --- a/kryptools/la.py +++ b/kryptools/la.py @@ -118,10 +118,11 @@ def norm(self, p: int = 2) -> float: if p == inf: return max( max(abs(x) for x in row) for row in self.matrix ) tmp = sum( sum(abs(x)**p for x in row) for row in self.matrix ) - return(tmp**(1/p)) + return tmp**(1/p) def dot(self, other) -> int: + "Dot product of two vectors." if self.rows == 1 and other.rows == 1 and self.cols == other.cols: return sum(x * y for x, y in zip(self.matrix[0], other.matrix[0])) if self.cols == 1 and other.cols == 1 and self.rows == other.rows: @@ -129,9 +130,11 @@ def dot(self, other) -> int: return NotImplemented def transpose(self) -> "Matrix": + "Transpose of a matrix." return Matrix([list(i) for i in zip(*self.matrix)]) def multiply(self, other) -> "Matrix": + "Matrix multiplication." if not isinstance(other, Matrix): return NotImplemented if self.cols != other.rows: @@ -172,7 +175,7 @@ def rref(self) -> "Matrix": R = self[:, :] i = 0 for j in range(n): - if not R[i, j]: # search for am nonzero entry in the present column + if not R[i, j]: # search for a nonzero entry in the present column for ii in range(i+1,m): if R[ii, j]: R[i, :], R[ii, :] = R[ii, :], R[i, :] # swap rows @@ -195,12 +198,12 @@ def det(self) -> int: "Compute the determinant of a matrix M." if self.rows != self.rows: raise ValueError("Matrix must be square!") - n = self.cols + n, m = self.cols, self.rows R = self[:, :] D = 1 i = 0 for j in range(n): - if not R[i, j]: # search for am nonzero entry in the present column + if not R[i, j]: # search for a nonzero entry in the present column for ii in range(i+1,m): if R[ii, j]: D *= -1 @@ -234,6 +237,7 @@ def inv(self) -> "Matrix": return MM[:,n:] def zeros(self, m: int = None, n: int = None): + "Returns a zero matrix of the same dimension" if not m and not n: n, m = self.cols, self.rows elif not n: @@ -242,6 +246,7 @@ def zeros(self, m: int = None, n: int = None): return Matrix([[ zero for j in range(n)] for i in range(m) ]) def eye(self, m: int = None, n: int = None): + "Returns an identity matrix of the same dimension" def delta(i, j): if i == j: return 1 @@ -256,11 +261,13 @@ def delta(i, j): def zeros(m: int, n: int = None) -> "Matrix": + "Returns a zero matrix of the given dimension" if not n: n = m return Matrix([[ 0 for j in range(n)] for i in range(m) ]) def eye(m:int, n: int = None) -> "Matrix": + "Returns an identity matrix of the given dimension" def delta(i, j): if i == j: return 1 diff --git a/kryptools/nt.py b/kryptools/nt.py index 5cf358d..8637ae8 100644 --- a/kryptools/nt.py +++ b/kryptools/nt.py @@ -10,7 +10,6 @@ """ from math import gcd, prod from fractions import Fraction -from .factor import factorint # Euclid and friends @@ -115,8 +114,7 @@ def crt(a: list, m: list) -> int: def fraction_repr(self): if self.denominator == 1: return str(self.numerator) - else: - return str(self.numerator) + "/" + str(self.denominator) + return str(self.numerator) + "/" + str(self.denominator) Fraction.__repr__ = fraction_repr @@ -209,7 +207,7 @@ def sqrt_mod(a: int, p: int) -> list: # Euler phi and Carmichael function -from math import prod +from .factor import factorint def euler_phi(n: int) -> int: @@ -250,22 +248,22 @@ def order(a: int, n: int, factor=False) -> int: factors[p] += k - 1 else: factors[p] = k - 1 - order = 1 # compute the group order euler_phi(n) as our current guess + order_a = 1 # compute the group order euler_phi(n) as our current guess for p, k in factors.items(): - order *= p**k + order_a *= p**k if factor: # we compute the factorization of the order along the way factors_order = {} # factorization of the order for p, k in factors.items(): i = 0 for _ in range(k): - order_try = order // p + order_try = order_a // p if pow(a, order_try, n) == 1: - order = order_try + order_a = order_try i += 1 else: break if factor and i < k: factors_order[p] = k - i if factor: - return order, factors_order - return order + return order_a, factors_order + return order_a diff --git a/kryptools/poly.py b/kryptools/poly.py index d49ae40..eed4f9c 100644 --- a/kryptools/poly.py +++ b/kryptools/poly.py @@ -184,6 +184,7 @@ def __pow__(self, i: int) -> "Poly": return res def divmod(self, other: "Poly") -> ("Poly", "Poly"): + "Polynom division with remainder" if isinstance(other, list): other = self.__class__(other) elif not isinstance(other, self.__class__): @@ -214,6 +215,7 @@ def divmod(self, other: "Poly") -> ("Poly", "Poly"): ) def mod(self, other: "Poly") -> None: + "Remainder of polynom division" if isinstance(other, list): other = self.__class__(other) elif not isinstance(other, self.__class__): diff --git a/kryptools/primes.py b/kryptools/primes.py index 38980b5..ee606a5 100644 --- a/kryptools/primes.py +++ b/kryptools/primes.py @@ -4,7 +4,8 @@ isprime(n) test if n is probably prime miller_rabin_test(n, b) Miller-Rabin primality test with base b """ -from math import isqrt +from math import isqrt, gcd +from .nt import jacobi_symbol # Erathostenes @@ -86,8 +87,6 @@ def _lucas_sequence(n, D, k): def _is_strong_lucas_prp(n: int) -> bool: """Strong Lucas primality test.""" - from math import gcd - from .nt import jacobi_symbol # remove powers of 2 from n+1 (= k * 2**s) k = (n + 1) // 2 From 39400624b8b05dcd86c491f24011d536d59732c3 Mon Sep 17 00:00:00 2001 From: Gerald Teschl Date: Thu, 27 Jun 2024 11:18:16 +0200 Subject: [PATCH 4/5] Fix --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3a96616..8c90547 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kryptools" -version = "0.2" +version = "0.3" authors = [ { name="Gerald Teschl", email="gerald.teschl@univie.ac.at" }, ] From acbd89db125f70fbd0f1033c224c1b590b37873d Mon Sep 17 00:00:00 2001 From: Gerald Teschl Date: Thu, 27 Jun 2024 11:37:28 +0200 Subject: [PATCH 5/5] Fixed omission --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7504929..7709dce 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ The tools contained are: * number theory: sqrt modulo primes, crt, continued fractions, etc. * primes: Sieve of Erathostenes, primality tests * solvers for discrete logarithms (naive, Pollard rho, Shanks baby step/giant step, index calculus, quadratic sieve) -* integer factorization (Pollard p-1, Lentra's ECM, Dixon, basic quadratic sieve) +* integer factorization (Fermat, Pollard p-1, Lentra's ECM, Dixon, basic quadratic sieve) * linear algebra: Hermite normal form, Gram-Schmidt * lattices: Babai rounding/nearest plane, lattice reduction