require "digest"
require_relative "laicrypto/version"
module Laicrypto
class Error < StandardError; end
def self.H(x, y, s, p)
data = "#{x}|#{y}|#{s}"
digest = Digest::SHA256.digest(data)
digest_bn = digest.unpack1("H*").to_i(16)
digest_bn % p
end
def self.mod_pow(base, exp, mod)
result = 1
b = base % mod
e = exp
while e > 0
result = (result * b) % mod if (e & 1) != 0
b = (b * b) % mod
e >>= 1
end
result
end
def self.sqrt_mod(a, p)
a = a % p
return 0 if a == 0
ls = mod_pow(a, (p - 1) / 2, p)
return nil if ls == p - 1
if p % 4 == 3
return mod_pow(a, (p + 1) / 4, p)
end
q = p - 1
s = 0
while (q & 1) == 0
q >>= 1
s += 1
end
z = 2
while mod_pow(z, (p - 1) / 2, p) != p - 1
z += 1
end
m = s
c = mod_pow(z, q, p)
t = mod_pow(a, q, p)
r = mod_pow(a, (q + 1) / 2, p)
loop do
return r if t == 1
t2i = t
i = 0
(1...m).each do |j|
t2i = (t2i * t2i) % p
if t2i == 1
i = j
break
end
end
b = mod_pow(c, 1 << (m - i - 1), p)
m = i
c = (b * b) % p
t = (t * c) % p
r = (r * b) % p
end
end
def self.T(point, s, a, p)
x, y = point
inv2 = mod_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)
return [x_candidate, y_candidate] unless y_candidate.nil?
s_current += 1
trials += 1
end
raise Error, "T: Gagal menemukan sqrt untuk y^2=#{y_sq} mod #{p} setelah #{trials} percobaan."
end
def self._pow_T_range(P, start_s, exp, a, p)
result = P.dup
curr_s = start_s
exp.times do
result = T(result, curr_s, a, p)
curr_s += 1
end
result
end
def self.keygen(p, a, P0)
loop do
k_candidate = rand(1...p)
begin
Q = _pow_T_range(P0, 1, k_candidate, a, p)
return { k: k_candidate, Q: Q }
rescue Error
next
end
end
end
def self.encrypt(m, public_Q, k, p, a, P0)
loop do
r = rand(1...p)
begin
C1 = _pow_T_range(P0, 1, r, a, p)
rescue Error
next
end
begin
Sr = _pow_T_range(public_Q, k + 1, r, a, p)
rescue Error
next
end
M0 = m % p
M = [ M0 < 0 ? M0 + p : M0, 0 ]
C2 = [ (M[0] + Sr[0]) % p, (M[1] + Sr[1]) % p ]
return { C1: C1, C2: C2, r: r }
end
end
def self.decrypt(C1, C2, k, r, a, p)
S = _pow_T_range(C1, r + 1, k, a, p)
M0 = (C2[0] - S[0]) % p
M0 < 0 ? M0 + p : M0
end
end