const crypto = require('crypto');
function H(x, y, s, p) {
const data = Buffer.from(`${x.toString()}|${y.toString()}|${s.toString()}`, 'utf8');
const hash = crypto.createHash('sha256').update(data).digest();
const digestBigInt = BigInt('0x' + hash.toString('hex'));
return digestBigInt % p;
}
function sqrt_mod(a, p) {
a = ((a % p) + p) % p;
if (a === 0n) return 0n;
const ls = modPow(a, (p - 1n) / 2n, p);
if (ls === p - 1n) {
return null;
}
if (p % 4n === 3n) {
return modPow(a, (p + 1n) / 4n, p);
}
let q = p - 1n;
let s = 0n;
while (q % 2n === 0n) {
q /= 2n;
s += 1n;
}
let z = 2n;
while (modPow(z, (p - 1n) / 2n, p) !== p - 1n) {
z += 1n;
}
let m = s;
let c = modPow(z, q, p);
let t = modPow(a, q, p);
let r = modPow(a, (q + 1n) / 2n, p);
while (true) {
if (t === 1n) {
return r;
}
let t2i = t;
let i = 0n;
for (let j = 1n; j < m; j++) {
t2i = (t2i * t2i) % p;
if (t2i === 1n) {
i = j;
break;
}
}
const b = modPow(c, 1n << (m - i - 1n), p);
m = i;
c = (b * b) % p;
t = (t * c) % p;
r = (r * b) % p;
}
}
function modPow(base, exp, mod) {
if (exp < 0n) throw new Error('Eksponen negatif tidak didukung');
let result = 1n;
let b = base % mod;
let e = exp;
while (e > 0n) {
if (e & 1n) {
result = (result * b) % mod;
}
b = (b * b) % mod;
e >>= 1n;
}
return result;
}
function T(point, s_seed, a, p) {
let [x, y] = point;
const inv2 = modPow(2n, p - 2n, p); let trials = 0;
let s_current = s_seed;
while (trials < 10) {
const h = H(x, y, s_current, p);
const x_candidate = ((x + a + h) * inv2) % p;
const y_sq = (x * y + h) % p;
const y_candidate = sqrt_mod(y_sq, p);
if (y_candidate !== null) {
return [x_candidate, y_candidate];
}
s_current += 1n;
trials += 1;
}
throw new Error(`T: Gagal menemukan sqrt untuk y^2 mod p setelah ${trials} percobaan.`);
}
function _pow_T_range(P, start_s, exp, a, p) {
let result = P;
let curr_s = start_s;
for (let i = 0n; i < exp; i++) {
result = T(result, curr_s, a, p);
curr_s += 1n;
}
return result;
}
function keygen(p, a, P0) {
while (true) {
const randomBytes = crypto.randomBytes(p.toString(16).length / 2 + 1);
let kCandidate = BigInt('0x' + randomBytes.toString('hex')) % (p - 1n);
kCandidate = kCandidate + 1n;
try {
const Q = _pow_T_range(P0, 1n, kCandidate, a, p);
return { k: kCandidate, Q };
} catch (e) {
continue;
}
}
}
function encrypt(m, public_Q, k, p, a, P0) {
while (true) {
const randomBytesR = crypto.randomBytes(p.toString(16).length / 2 + 1);
let r = BigInt('0x' + randomBytesR.toString('hex')) % (p - 1n);
r = r + 1n;
let C1;
try {
C1 = _pow_T_range(P0, 1n, r, a, p);
} catch (e) {
continue;
}
let Sr;
try {
Sr = _pow_T_range(public_Q, k + 1n, r, a, p);
} catch (e) {
continue;
}
const M0 = m % p;
const M = [M0 < 0n ? M0 + p : M0, 0n];
const C2x = (M[0] + Sr[0]) % p;
const C2y = (M[1] + Sr[1]) % p;
return { C1, C2: [C2x, C2y], r };
}
}
function decrypt(C1, C2, k, r, a, p) {
const S = _pow_T_range(C1, r + 1n, k, a, p);
const M0 = (C2[0] - S[0]) % p;
return M0 < 0n ? M0 + p : M0;
}
module.exports = {
H,
sqrt_mod,
T,
_pow_T_range,
keygen,
encrypt,
decrypt,
};