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+
111from sympy import Poly , symbols , invert , ZZ , gcd , GF , isprime
212import numpy as np
313
414x = 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-
57122def 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-
67144def 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
72161def 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-
101212def 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-
117240def 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