-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathideals.py
More file actions
156 lines (122 loc) · 4.02 KB
/
ideals.py
File metadata and controls
156 lines (122 loc) · 4.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
"""
Helper functions for various computations associated to
the quaternion algebra, and ideals and orders of the
quaternion algebra.
Some of these functions could be ported to SageMath. It's a TODO
for when SQISign is being less actively worked on.
"""
# Sage Imports
from sage.all import (
randint,
ZZ,
ceil,
log,
gcd,
Matrix,
prod
)
# Local imports
from setup import p, O0, ω
# ================================================ #
# Helpers for elements of the quaternion algebra #
# ================================================ #
def quadratic_norm(x, y):
"""
Given two integers x,y, which correspond
to the element x + ωy ∈ R[ω], returns
Nrd(x + ωy)
Note: This function implements the norm
function f(x,y) in the SQISign papers.
For SQISign, we have ω = i and i^2 = -1
so f(x,y) = x**2 + y**2 which is more
efficient
"""
if ω**2 != -1:
raise ValueError(f"quadratic_norm() requires Z[ω] with ω^2 = -1. {ω = }")
return ZZ(x) ** 2 + ZZ(y) ** 2
def is_integral(I):
"""
Checks whether the input ideal is integral.
"""
return all([b in I.left_order() for b in I.basis()])
def ideal_basis_gcd(I):
"""
Computes the gcd of the coefficients of
the ideal written as a linear combination
of the basis of its left order.
"""
I_basis = I.basis_matrix()
O_basis = I.left_order().unit_ideal().basis_matrix()
# Write I in the basis of its left order
M = I_basis * O_basis.inverse()
return gcd((gcd(M_row) for M_row in M))
def is_cyclic(I):
"""
Computes whether the input ideal is cyclic,
all the work is done by the helper function
`ideal_basis_gcd()`.
"""
return ideal_basis_gcd(I) == 1
def reduced_basis(I, check=False):
"""
Computes the Minkowski reduced basis of the
input ideal. Note: this produces the same
result for all ideals in the equivalence class
so corresponds to the reduced basis of the
smallest equivalent ideal to I
Input: an ideal
Output: A Minkowski-reduced basis
Optional: when check is True, the basis is
checked against the Minkowski bounds
"""
def _matrix_to_gens(M, B):
"""
Converts from a matrix to generators in the quat.
algebra
"""
return [sum(c * g for c, g in zip(row, B)) for row in M]
B = I.basis()
G = I.gram_matrix()
U = G.LLL_gram().transpose()
reduced_basis_elements = _matrix_to_gens(U, B)
if check:
norm_product = 16 * prod([x.reduced_norm() for x in reduced_basis_elements])
tmp = p**2 * I.norm() ** 4
assert norm_product <= 4 * tmp, "Minkowski reduced basis is too large"
assert norm_product >= tmp, "Minkowski reduced basis is too small"
return reduced_basis_elements
def left_isomorphism(I, J):
"""
Given two isomorphic left ideals I, J computes
α such that J = I*α
"""
B = I.quaternion_algebra()
if B != J.quaternion_algebra():
raise ValueError("Arguments must be ideals in the same algebra.")
if I.left_order() != J.left_order():
raise ValueError("Arguments must have the same left order.")
IJ = I.conjugate() * J
L = reduced_basis(IJ)
for t in L:
α = t / I.norm()
if J == I * α:
return α
raise ValueError("Could not find a left isomorphism...")
def ideal_generator(I, coprime_factor=1):
"""
Given an ideal I of norm D, finds a generator
α such that I = O(α,D) = Oα + OD
Optional: Enure the norm of the generator is coprime
to the integer coprime_factor
"""
OI = I.left_order()
D = ZZ(I.norm())
bound = ceil(4 * log(p))
gcd_norm = coprime_factor * D**2
# Stop infinite loops.
for _ in range(1000):
α = sum([b * randint(-bound, bound) for b in I.basis()])
if gcd(ZZ(α.reduced_norm()), gcd_norm) == D:
assert I == OI * α + OI * D
return α
raise ValueError(f"Cannot find a good α for D = {D}, I = {I}, n(I) = {D}")