Skip to content

Commit d1dc433

Browse files
committed
added addition for polynomials + some comments
1 parent e04c06c commit d1dc433

6 files changed

Lines changed: 333 additions & 44 deletions

File tree

lattice_methods/basis_reduction_2d.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,48 @@
1+
"""
2+
Implementation of a basic two-dimensional lattice basis reduction algorithm.
3+
4+
Module author: Aleksandr Lebedev <alebede1@hs-mittweida.de>
5+
Date: 2025-05-28
6+
"""
7+
18
import numpy as np
29

310
def reduce_2d_basis(basis1, basis2, verbose=False):
11+
"""
12+
Performs Gauss-style reduction of a 2D lattice basis.
13+
14+
This function reduces a two-dimensional lattice basis using a simplified form
15+
of Gauss' lattice basis reduction algorithm. The process iteratively subtracts
16+
integer multiples of vectors to produce a shorter, more orthogonal basis.
17+
18+
:param basis1: First basis vector.
19+
:type basis1: numpy.ndarray
20+
:param basis2: Second basis vector.
21+
:type basis2: numpy.ndarray
22+
:param verbose: If True, returns a log of each reduction step.
23+
:type verbose: bool
424
25+
:return:
26+
If verbose is False, returns a list [b1, b2] representing the reduced basis.
27+
If verbose is True, returns a list of dictionaries with reduction steps, where each dictionary contains:
28+
29+
- 'step' (str or int): Iteration index or '→ shortest'
30+
- 'b1' (numpy.ndarray): Current first basis vector
31+
- 'b2' (numpy.ndarray): Current second basis vector
32+
:rtype: list
33+
34+
.. note::
35+
The algorithm terminates when the projection coefficient `t` becomes zero.
36+
This is a simplified form of Gauss’ lattice basis reduction in 2D.
37+
"""
538
data = []
639

740
steps = 0
841

42+
#TODO linear ind check
43+
# if np.linalg.matrix_rank(np.column_stack((basis1, basis2))) < 2:
44+
# raise ValueError("Input vectors are linearly dependent.")
45+
946
while True:
1047

1148
if np.linalg.norm(basis2) < np.linalg.norm(basis1):

lattice_methods/lll.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,44 @@
1+
"""
2+
Implementation of the classical LLL lattice basis reduction algorithm.
3+
4+
Module author: Aleksandr Lebedev <alebede1@hs-mittweida.de>
5+
6+
This module provides an implementation of the Lenstra–Lenstra–Lovász (LLL) algorithm
7+
for lattice basis reduction. It uses Gram-Schmidt orthogonalization to iteratively
8+
reduce a given basis to a shorter and more orthogonal form.
9+
"""
10+
111
import numpy as np
212
from lattice_methods.utils import gram_schmidt
313

414
def lll_reduce(basis, delta=0.75, verbose=False):
15+
"""
16+
Performs LLL (Lenstra–Lenstra–Lovász) lattice basis reduction.
17+
18+
This function applies the classical LLL algorithm to reduce a given lattice basis
19+
to a shorter and nearly orthogonal form.
20+
21+
:param basis: A list of NumPy vectors representing the lattice basis.
22+
:type basis: list[numpy.ndarray]
23+
:param delta: Lovász parameter, typically in the range (0.5, 1). Default is 0.75.
24+
:type delta: float
25+
:param verbose: If True, enables step-by-step debug output (currently unused).
26+
:type verbose: bool
27+
28+
:return: A list of NumPy vectors representing the LLL-reduced lattice basis.
29+
:rtype: list[numpy.ndarray]
30+
31+
.. note::
32+
This function assumes all basis vectors are linearly independent.
33+
34+
.. warning::
35+
No validation is performed on the input; ensure basis vectors are valid.
36+
37+
.. seealso::
38+
:func:`lattice_methods.utils.gram_schmidt` for orthogonalization.
39+
"""
40+
41+
##TODO verbose ..
542

643
basis = [b.copy() for b in basis]
744
n = len(basis)

lattice_methods/ntru.py

Lines changed: 160 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,40 @@
1+
"""
2+
Implementation of the NTRU public-key cryptosystem.
3+
4+
Module author: Aleksandr Lebedev <alebede1@hs-mittweida.de>
5+
6+
This module provides functions for NTRU key generation, encryption, and decryption.
7+
It offers efficient lattice-based cryptographic primitives aimed at post-quantum security,
8+
supporting flexible parameter choices and secure message handling.
9+
"""
10+
111
from sympy import Poly, symbols, invert, ZZ, gcd, GF, isprime
212
import numpy as np
313

414
x = symbols('x')
515

16+
def poly_inv_mod_ring(polynomial_f, N, q):
17+
"""
18+
Computes the inverse of a polynomial modulo (pow(x,N) - 1) and q.
19+
20+
This function attempts to find the inverse of a given polynomial `polynomial_f` in the ring
21+
of polynomials modulo (pow(x,N) - 1) with coefficients reduced modulo `q`. It works both when `q`
22+
is prime and composite by setting the appropriate polynomial domain.
623
7-
def invert_poly(polynomial_f, N, q):
24+
:param polynomial_f: The polynomial to invert (as a SymPy Poly object).
25+
:type polynomial_f: sympy.Poly
26+
:param N: The degree defining the modulus polynomial pow(x,N) - 1.
27+
:type N: int
28+
:param q: The modulus for coefficient arithmetic.
29+
:type q: int
30+
31+
:return: List of coefficients of the inverse polynomial if it exists; otherwise, None.
32+
:rtype: list[int] or None
33+
34+
.. note::
35+
The function returns None if `polynomial_f` is not invertible modulo (pow(x,N) - 1, q), i.e., if
36+
the gcd of `polynomial_f` and the modulus polynomial is not constant.
37+
"""
838

939
#f = Poly(f_coeffs, x, domain=GF(q))
1040
if(isprime(q)):
@@ -27,8 +57,44 @@ def pad_left(poly, N):
2757
return [0] * (N - len(poly)) + poly
2858

2959

60+
def poly_add_mod_ring(p1, p2, q):
61+
62+
length = max(len(p1), len(p2))
63+
result = [0] * length
64+
65+
p1 = pad_left(p1, length)
66+
p2 = pad_left(p2, length)
67+
68+
for i in range(length):
69+
result[i] += (p1[i] + p2[i])
70+
71+
return [c % q for c in result]
72+
73+
def poly_mult_mod_ring(p1, p2, N, q):
74+
"""
75+
Performs multiplication of two polynomials modulo (pow(x,N) - 1) and coefficient modulus q.
76+
77+
This function computes the product of two polynomials represented by coefficient lists `p1` and `p2`.
78+
The multiplication is done modulo the polynomial (pow(x,N) - 1), which means the coefficients
79+
are reduced with wrap-around at degree N, and all coefficients are taken modulo `q`.
80+
81+
:param p1: Coefficients of the first polynomial (highest degree first).
82+
:type p1: list[int]
83+
:param p2: Coefficients of the second polynomial (highest degree first).
84+
:type p2: list[int]
85+
:param N: Degree of the modulus polynomial (pow(x,N) - 1).
86+
:type N: int
87+
:param q: Modulus for coefficient arithmetic.
88+
:type q: int
3089
31-
def poly_mult_mod_strict(p1, p2, N, q):
90+
:return: Coefficients of the resulting polynomial after modular multiplication,
91+
in highest degree first order.
92+
:rtype: list[int]
93+
94+
.. note::
95+
The input polynomials are assumed to be represented as lists of coefficients with
96+
the highest degree coefficient first. The output is normalized to remove trailing zeros.
97+
"""
3298
p1 = p1[::-1]
3399
p2 = p2[::-1]
34100

@@ -53,23 +119,69 @@ def poly_mult_mod_strict(p1, p2, N, q):
53119

54120
return [c % q for c in result[::-1]]
55121

56-
57122
def check_coeff_range(poly_coeffs, bounds):
123+
"""
124+
Checks whether all polynomial coefficients lie within specified bounds.
125+
126+
This function verifies that each coefficient in `poly_coeffs` is within the inclusive range
127+
defined by `bounds` (left_bound and right_bound).
128+
129+
:param poly_coeffs: List of polynomial coefficients.
130+
:type poly_coeffs: list[int]
131+
:param bounds: Tuple specifying inclusive lower and upper bounds (left_bound, right_bound).
132+
:type bounds: tuple[int, int]
133+
134+
:return: True if all coefficients are within the bounds, False otherwise.
135+
:rtype: bool
136+
"""
58137
left_bound, right_bound = bounds
59138
for i in range(len(poly_coeffs)):
60139
if(poly_coeffs[i] < left_bound or poly_coeffs[i] > right_bound):
61140
return False
62141

63142
return True
64143

65-
66-
67144
def center_poly_coeffs(poly, q):
68-
return [((c + q//2) % q) - q//2 for c in poly]
145+
"""
146+
Centers polynomial coefficients modulo q around zero.
147+
148+
This function maps each coefficient `c` of the polynomial `poly` into the range
149+
[-q//2, q//2), effectively centering the coefficients modulo `q`.
69150
151+
:param poly: List of polynomial coefficients.
152+
:type poly: list[int]
153+
:param q: Modulus for coefficient arithmetic.
154+
:type q: int
70155
156+
:return: List of centered coefficients in the range [-q//2, q//2).
157+
:rtype: list[int]
158+
"""
159+
return [((c + q//2) % q) - q//2 for c in poly]
71160

72161
def ntru_generate_keys(N : int, p: int, q : int, polynomial_g : Poly, polynomial_f : Poly):
162+
"""
163+
Generates public and private keys for the NTRU cryptosystem.
164+
165+
This function computes the NTRU key pair based on parameters N, p, q and the
166+
private polynomials `polynomial_f` and `polynomial_g`. It returns the public key
167+
and the inverse of `polynomial_f` modulo q, which serves as part of the private key.
168+
169+
:param N: Degree of the polynomials and ring dimension.
170+
:type N: int
171+
:param p: Small modulus parameter for message space.
172+
:type p: int
173+
:param q: Large modulus parameter for polynomial arithmetic.
174+
:type q: int
175+
:param polynomial_g: Polynomial used in key generation (SymPy Poly).
176+
:type polynomial_g: sympy.Poly
177+
:param polynomial_f: Private polynomial used for key generation (SymPy Poly).
178+
:type polynomial_f: sympy.Poly
179+
180+
:return: Tuple `(pub_key, prv_key)` where
181+
- `pub_key` is a list `[N, p, q, h]` representing the public key parameters and polynomial,
182+
- `prv_key` is a list `[polynomial_f, Fp]` representing the private key polynomial and its inverse modulo p.
183+
:rtype: tuple[list, list]
184+
"""
73185

74186
if(gcd(p, q) != 1 or p >= q):
75187
print("ERROR SMTH WRONG WITH p, q ")
@@ -82,79 +194,83 @@ def ntru_generate_keys(N : int, p: int, q : int, polynomial_g : Poly, polynomial
82194

83195
poly_f_over_p = Poly(polynomial_f, x, domain=GF(p))
84196

85-
Fp = invert_poly(poly_f_over_p, N, p)
86-
Fq = invert_poly(poly_f_over_q, N, q)
197+
Fp = poly_inv_mod_ring(poly_f_over_p, N, p)
198+
Fq = poly_inv_mod_ring(poly_f_over_q, N, q)
87199

88200
if Fp is None or Fq is None:
89201
print("ERROR SMTH WRONG WITH POLYNOMIALS Fq Fp")
90202
return
91203

92204
Fqp = [p * x for x in Fq]
93-
h = poly_mult_mod_strict(Fqp, polynomial_g.all_coeffs(), N, q)
205+
h = poly_mult_mod_ring(Fqp, polynomial_g.all_coeffs(), N, q)
94206

95207
pub_key = [N, p, q, h]
96208
prv_key = [polynomial_f, Fp]
97209

98210
return pub_key, prv_key
99211

100-
101212
def ntru_encryption(pubkey, polynomial_phi: Poly, polynomial_m: Poly):
102-
213+
"""
214+
Encrypts a message polynomial using the NTRU public key.
215+
216+
This function performs NTRU encryption by computing the ciphertext polynomial as
217+
the sum of the product of the random polynomial `polynomial_phi` with the public key polynomial `h`,
218+
plus the message polynomial `polynomial_m`, all modulo `q`.
219+
220+
:param pubkey: Public key represented as a list `[N, p, q, h]`.
221+
:type pubkey: list
222+
:param polynomial_phi: Random polynomial used for encryption (SymPy Poly).
223+
:type polynomial_phi: sympy.Poly
224+
:param polynomial_m: Message polynomial to encrypt (SymPy Poly).
225+
:type polynomial_m: sympy.Poly
226+
227+
:return: Ciphertext polynomial coefficients modulo q.
228+
:rtype: list[int]
229+
"""
103230
N, p, q, h = pubkey
104231

105232
phi_coeffs = polynomial_phi.all_coeffs()
106233
m_coeffs = polynomial_m.all_coeffs()
107234

108-
##TODO ADDITION FOR POLYNOMIALS
109-
c = poly_mult_mod_strict(phi_coeffs, h, N, q)
110-
m_coeffs = pad_left(m_coeffs, N)
111-
112-
ciphertext = [(c[i] + m_coeffs[i]) % q for i in range(N)]
235+
c = poly_mult_mod_ring(phi_coeffs, h, N, q)
236+
ciphertext = poly_add_mod_ring(c, m_coeffs, q)
113237

114238
return ciphertext
115239

116-
117240
def ntru_decryption(pubkey, prvkey, ciphertext):
241+
"""
242+
Decrypts a ciphertext polynomial using the NTRU private key.
243+
244+
This function performs NTRU decryption by multiplying the ciphertext with the private
245+
polynomial `polynomial_f` modulo (pow(x,N) - 1, q), centering the coefficients if needed,
246+
and then multiplying by the inverse of `polynomial_f` modulo p to recover the original message polynomial.
247+
248+
:param pubkey: Public key represented as a list `[N, p, q, h]`.
249+
:type pubkey: list
250+
:param prvkey: Private key represented as a list `[polynomial_f, Fp]`,
251+
where `polynomial_f` is the private polynomial and `Fp` its inverse modulo p.
252+
:type prvkey: list
253+
:param ciphertext: Ciphertext polynomial coefficients.
254+
:type ciphertext: list[int]
255+
256+
:return: Decrypted message polynomial over GF(p).
257+
:rtype: sympy.Poly
258+
"""
118259
[polynomial_f, Fp] = prvkey
119260
N, p, q, h = pubkey
120261

121262
f_coeffs = polynomial_f.all_coeffs()
122-
a = poly_mult_mod_strict(f_coeffs, ciphertext, N, q)
263+
a = poly_mult_mod_ring(f_coeffs, ciphertext, N, q)
123264

124265
cond = check_coeff_range(a, (-q/2, q/2))
125266
if not cond:
126267
a = center_poly_coeffs(a, q)
127268

128-
Fpa = poly_mult_mod_strict(Fp, a, N, p)
269+
Fpa = poly_mult_mod_ring(Fp, a, N, p)
129270
#print(Poly(Fpa, x, domain=GF(p)))
130271

131272
return Poly(Fpa, x, domain=GF(p))
132273

133-
#
134-
# N = 7
135-
# p = 3
136-
# q = 41
137-
#
138-
#
139-
# phi = [1, -1, 0, 0, 0, 1, -1]
140-
# m = [0, -1, 0, 1, 1, -1, 1]
141-
# g = [1, 0, 1, 0, -1, -1, 0]
142-
# f = [1, 0, -1, 1, 1, 0, -1]
143-
#
144-
#
145-
#
146-
#
147-
# poly_f = Poly(f, x)
148-
# poly_g = Poly(g, x)
149-
# poly_m = Poly(m, x, domain=GF(p))
150-
# poly_phi = Poly(phi, x)
151-
#
152-
# pub_key, prv_key = ntru_generate_keys(N, p, q, poly_g, poly_f)
153-
# ciphertext = ntru_encryption(pub_key, poly_phi, poly_m)
154-
# poly_d = ntru_decryption(pub_key, prv_key, ciphertext)
155-
#
156-
# print(poly_m)
157-
# print(poly_d)
158274

159275

160276

0 commit comments

Comments
 (0)