Implements the CRT, BSGS, and Pohlig-Hellman algorithms
This commit is contained in:
parent
0ede13a12d
commit
51f19d17ab
2 changed files with 127 additions and 0 deletions
3
README
3
README
|
|
@ -1 +1,4 @@
|
||||||
The "imbaud python library" (imp lib), or just imp for short!
|
The "imbaud python library" (imp lib), or just imp for short!
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
- define a getPrime function like PyCryptodome's
|
||||||
|
|
|
||||||
124
imp/math/crt.py
Normal file
124
imp/math/crt.py
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
def crt(eqs: list[tuple[int, int]]) -> tuple[int, int]:
|
||||||
|
'''
|
||||||
|
Solves simultaneous linear diophantine equations
|
||||||
|
using the Chinese-Remainder Theorem.
|
||||||
|
Example:
|
||||||
|
>>> crt([2, 3), (3, 5), (2, 7)])
|
||||||
|
(23, 105)
|
||||||
|
# aka x is congruent to 23 modulo 105
|
||||||
|
'''
|
||||||
|
# calculate the quotient and remainder form of the unknown
|
||||||
|
q = 1
|
||||||
|
r = 0
|
||||||
|
# store remainders and moduli for each linear equation
|
||||||
|
R = [eq[0] for eq in eqs]
|
||||||
|
M = [eq[1] for eq in eqs]
|
||||||
|
N = len(eqs)
|
||||||
|
for i in range(N):
|
||||||
|
(R_i, M_i) = (R[i], M[i])
|
||||||
|
# adjust quotient and remainder
|
||||||
|
r += R_i * q
|
||||||
|
q *= M_i
|
||||||
|
# apply current eq to future eqs
|
||||||
|
for j in range(i+1, N):
|
||||||
|
R[j] -= R_i
|
||||||
|
R[j] *= pow(M_i, -1, M[j])
|
||||||
|
R[j] %= M[j]
|
||||||
|
return r # NOTE: forall integers k: qk+r is also a solution
|
||||||
|
|
||||||
|
|
||||||
|
from math import isqrt, prod
|
||||||
|
|
||||||
|
def bsgs(g: int, h: int, n: int) -> int:
|
||||||
|
'''
|
||||||
|
The Baby-Step Giant-Step algorithm computes the
|
||||||
|
discrete logarithm (or order) of an element in a
|
||||||
|
finite abelian group.
|
||||||
|
|
||||||
|
Specifically, for a generator g of a finite abelian
|
||||||
|
group G order n, and an element h in G, the BSGS
|
||||||
|
algorithm returns the integer x such that g^x = h.
|
||||||
|
For example, if G = Z_n the cyclic group order n then:
|
||||||
|
bsgs solves g^x = h (mod n) for x.
|
||||||
|
'''
|
||||||
|
# ensure g and h are reduced modulo n
|
||||||
|
g %= n
|
||||||
|
h %= n
|
||||||
|
# ignore trivial case
|
||||||
|
# NOTE: for the Pohlig-Hellman algorithm to work properly
|
||||||
|
# NOTE: BSGS must return 1 NOT 0 for bsgs(1, 1, n)
|
||||||
|
if g == h: return 1
|
||||||
|
m = isqrt(n) + 1 # ceil(sqrt(n))
|
||||||
|
# store g^j values in a hash table
|
||||||
|
H = {j: pow(g, j, n) for j in range(m)}
|
||||||
|
I = pow(g, -m, n)
|
||||||
|
y = h
|
||||||
|
for i in range(m):
|
||||||
|
for j in range(m):
|
||||||
|
if H[j] == y:
|
||||||
|
return i*m + j
|
||||||
|
y = (y * I) % n
|
||||||
|
return None # No Solutions
|
||||||
|
|
||||||
|
def factors_prod(pf: list[tuple[int, int]]) -> int:
|
||||||
|
return prod(p**e for (p, e) in pf)
|
||||||
|
|
||||||
|
def sph(g: int, h: int, pf: list[tuple[int, int]]) -> int:
|
||||||
|
'''
|
||||||
|
The Silver-Pohlig-Hellman algorithm for computing
|
||||||
|
discrete logarithms in finite abelian groups whose
|
||||||
|
order is a smooth integer.
|
||||||
|
|
||||||
|
NOTE: this works in the special case,
|
||||||
|
NOTE: but I can't get the general case to work :(
|
||||||
|
'''
|
||||||
|
# ignore the trivial case
|
||||||
|
if g == h:
|
||||||
|
return 1
|
||||||
|
R = len(pf) # number of prime factors
|
||||||
|
N = factors_prod(pf) # pf is the prime factorisation of N
|
||||||
|
print('N', N)
|
||||||
|
# Special Case: groups of prime-power order:
|
||||||
|
if R == 1:
|
||||||
|
(p, e) = pf[0]
|
||||||
|
x = 0
|
||||||
|
y = pow(g, p**(e-1), N)
|
||||||
|
for k in range(e):
|
||||||
|
# temporary variables for defining h_k
|
||||||
|
w = pow(g, -x, N)
|
||||||
|
# NOTE: by construction the order of h_k must divide p
|
||||||
|
h_k = pow(w*h, p**(e-1-k), N)
|
||||||
|
# apply BSGS to find d such that y^d = h_k
|
||||||
|
d = bsgs(y, h_k, N)
|
||||||
|
if d is None:
|
||||||
|
return None # No Solutions
|
||||||
|
x = x + (p**k)*d
|
||||||
|
return x
|
||||||
|
|
||||||
|
# General Case:
|
||||||
|
eqs = []
|
||||||
|
for i in range(R):
|
||||||
|
(p_i, e_i) = pf[i]
|
||||||
|
# phi = (p_i - 1)*(p_i**(e_i - 1)) # Euler totient
|
||||||
|
# pe = p_i**e_i
|
||||||
|
# P = (N//(p_i**e_i)) % phi # reduce mod phi by Euler's theorem
|
||||||
|
g_i = pow(g, N//(p_i**e_i), N)
|
||||||
|
h_i = pow(h, N//(p_i**e_i), N)
|
||||||
|
print("g and h", g_i, h_i)
|
||||||
|
print("p^e", p_i, e_i)
|
||||||
|
x_i = sph(g_i, h_i, [(p_i,e_i)])
|
||||||
|
print("x_i", x_i)
|
||||||
|
if x_i is None:
|
||||||
|
return None # No Solutions
|
||||||
|
eqs.append((x_i, p_i**e_i))
|
||||||
|
print()
|
||||||
|
# ignore the quotient, solve CRT for 0 <= x <= n-1
|
||||||
|
x = crt(eqs) # NOTE: forall integers k: Nk+x is also a solution
|
||||||
|
print('solution:', x)
|
||||||
|
print(eqs)
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
result = sph(3, 11, [(2, 1),(7,1)])
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue