import hashlib
import secrets
from typing import Optional, Tuple
def H(x: int, y: int, s: int, p: int) -> int:
data = f"{x}|{y}|{s}".encode()
digest = hashlib.sha256(data).digest()
return int.from_bytes(digest, "big") % p
def sqrt_mod(a: int, p: int) -> Optional[int]:
a = a % p
if a == 0:
return 0
ls = pow(a, (p - 1) // 2, p)
if ls == p - 1:
return None
if p % 4 == 3:
return pow(a, (p + 1) // 4, p)
q = p - 1
s = 0
while q % 2 == 0:
q //= 2
s += 1
z = 2
while pow(z, (p - 1) // 2, p) != p - 1:
z += 1
m = s
c = pow(z, q, p)
t = pow(a, q, p)
r = pow(a, (q + 1) // 2, p)
while True:
if t % p == 1:
return r
t2i = t
i = 0
for i2 in range(1, m):
t2i = pow(t2i, 2, p)
if t2i == 1:
i = i2
break
b = pow(c, 1 << (m - i - 1), p)
m = i
c = pow(b, 2, p)
t = (t * c) % p
r = (r * b) % p
def T(point: Tuple[int, int], s: int, a: int, p: int) -> Tuple[int, int]:
x, y = point
inv2 = pow(2, p - 2, p)
trials = 0
s_current = s
while trials < 10:
h = H(x, y, s_current, p)
x_candidate = ((x + a + h) * inv2) % p
y_sq = (x * y + h) % p
y_candidate = sqrt_mod(y_sq, p)
if y_candidate is not None:
return x_candidate, y_candidate
s_current += 1
trials += 1
raise ValueError(
f"T: Gagal menemukan sqrt untuk y^2={y_sq} mod {p} setelah {trials} percobaan."
)
def _pow_T_range(P: Tuple[int, int], start_s: int, exp: int, a: int, p: int) -> Tuple[int, int]:
result = P
curr_s = start_s
for _ in range(exp):
result = T(result, curr_s, a, p)
curr_s += 1
return result
def keygen(p: int, a: int, P0: Tuple[int, int]) -> Tuple[int, Tuple[int, int]]:
while True:
k = secrets.randbelow(p - 1) + 1
try:
Q = _pow_T_range(P0, start_s=1, exp=k, a=a, p=p)
return k, Q
except ValueError:
continue
def encrypt(
m: int,
public_Q: Tuple[int, int],
k: int,
p: int,
a: int,
P0: Tuple[int, int],
) -> Tuple[Tuple[int, int], Tuple[int, int], int]:
while True:
r = secrets.randbelow(p - 1) + 1
try:
C1 = _pow_T_range(P0, start_s=1, exp=r, a=a, p=p)
except ValueError:
continue
try:
Sr = _pow_T_range(public_Q, start_s=k + 1, exp=r, a=a, p=p)
except ValueError:
continue
M = (m % p, 0)
C2 = ((M[0] + Sr[0]) % p, (M[1] + Sr[1]) % p)
return C1, C2, r
def decrypt(
C1: Tuple[int, int],
C2: Tuple[int, int],
k: int,
r: int,
a: int,
p: int,
) -> int:
S = _pow_T_range(C1, start_s=r + 1, exp=k, a=a, p=p)
M0 = (C2[0] - S[0]) % p
return M0