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!
|
||||
|
||||
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