Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
jvdsn
GitHub Repository: jvdsn/crypto-attacks
Path: blob/master/shared/polynomial.py
2587 views
1
import logging
2
3
from sage.all import ZZ
4
from sage.all import Zmod
5
6
from shared.crt import fast_crt
7
8
9
def _polynomial_hgcd(ring, a0, a1):
10
assert a1.degree() < a0.degree()
11
12
if a1.degree() <= a0.degree() / 2:
13
return 1, 0, 0, 1
14
15
m = a0.degree() // 2
16
b0 = ring(a0.list()[m:])
17
b1 = ring(a1.list()[m:])
18
R00, R01, R10, R11 = _polynomial_hgcd(ring, b0, b1)
19
d = R00 * a0 + R01 * a1
20
e = R10 * a0 + R11 * a1
21
if e.degree() < m:
22
return R00, R01, R10, R11
23
24
q, f = d.quo_rem(e)
25
g0 = ring(e.list()[m // 2:])
26
g1 = ring(f.list()[m // 2:])
27
S00, S01, S10, S11 = _polynomial_hgcd(ring, g0, g1)
28
return S01 * R00 + (S00 - q * S01) * R10, S01 * R01 + (S00 - q * S01) * R11, S11 * R00 + (S10 - q * S11) * R10, S11 * R01 + (S10 - q * S11) * R11
29
30
31
def fast_polynomial_gcd(a0, a1):
32
"""
33
Uses a divide-and-conquer algorithm (HGCD) to compute the polynomial gcd.
34
More information: Aho A. et al., "The Design and Analysis of Computer Algorithms" (Section 8.9)
35
:param a0: the first polynomial
36
:param a1: the second polynomial
37
:return: the polynomial gcd
38
"""
39
# TODO: implement extended variant of half GCD?
40
assert a0.parent() == a1.parent()
41
42
if a0.degree() == a1.degree():
43
if a1 == 0:
44
return a0
45
a0, a1 = a1, a0 % a1
46
elif a0.degree() < a1.degree():
47
a0, a1 = a1, a0
48
49
assert a0.degree() > a1.degree()
50
ring = a0.parent()
51
52
# Optimize recursive tail call.
53
while True:
54
logging.debug(f"deg(a0) = {a0.degree()}, deg(a1) = {a1.degree()}")
55
_, r = a0.quo_rem(a1)
56
if r == 0:
57
return a1.monic()
58
59
R00, R01, R10, R11 = _polynomial_hgcd(ring, a0, a1)
60
b0 = R00 * a0 + R01 * a1
61
b1 = R10 * a0 + R11 * a1
62
if b1 == 0:
63
return b0.monic()
64
65
_, r = b0.quo_rem(b1)
66
if r == 0:
67
return b1.monic()
68
69
a0 = b1
70
a1 = r
71
72
73
def polynomial_gcd_crt(a, b, factors):
74
"""
75
Uses the Chinese Remainder Theorem to compute the polynomial gcd modulo a composite number.
76
:param a: the first polynomial
77
:param b: the second polynomial
78
:param factors: the factors of m (tuples of primes and exponents)
79
:return: the polynomial gcd modulo m
80
"""
81
assert a.base_ring() == b.base_ring() == ZZ
82
83
gs = []
84
ps = []
85
for p, _ in factors:
86
zmodp = Zmod(p)
87
gs.append(fast_polynomial_gcd(a.change_ring(zmodp), b.change_ring(zmodp)).change_ring(ZZ))
88
ps.append(p)
89
90
g, _ = fast_crt(gs, ps)
91
return g
92
93
94
def polynomial_xgcd(a, b):
95
"""
96
Computes the extended GCD of two polynomials using Euclid's algorithm.
97
:param a: the first polynomial
98
:param b: the second polynomial
99
:return: a tuple containing r, s, and t
100
"""
101
assert a.base_ring() == b.base_ring()
102
103
r_prev, r = a, b
104
s_prev, s = 1, 0
105
t_prev, t = 0, 1
106
107
while r:
108
try:
109
q = r_prev // r
110
r_prev, r = r, r_prev - q * r
111
s_prev, s = s, s_prev - q * s
112
t_prev, t = t, t_prev - q * t
113
except RuntimeError:
114
raise ArithmeticError("r is not invertible", r)
115
116
return r_prev, s_prev, t_prev
117
118
119
def polynomial_inverse(p, m):
120
"""
121
Computes the inverse of a polynomial modulo a polynomial using the extended GCD.
122
:param p: the polynomial
123
:param m: the polynomial modulus
124
:return: the inverse of p modulo m
125
"""
126
g, s, t = polynomial_xgcd(p, m)
127
return s * g.lc() ** -1
128
129
130
def max_norm(p):
131
"""
132
Computes the max norm (infinity norm) of a polynomial.
133
:param p: the polynomial
134
:return: a tuple containing the monomial degrees of the largest coefficient and the coefficient
135
"""
136
max_degs = None
137
max_coeff = 0
138
for degs, coeff in p.dict().items():
139
if abs(coeff) > max_coeff:
140
max_degs = degs
141
max_coeff = abs(coeff)
142
143
return max_degs, max_coeff
144
145