Riskless/static/js/modules/crypto/paillier.js

431 lines
11 KiB
JavaScript
Raw Permalink Normal View History

2023-04-20 09:13:03 +00:00
import {
cryptoRandom,
generate_prime,
generate_safe_prime,
KEY_SIZE,
} from "./random_primes.js";
2023-04-11 14:39:49 +00:00
import { gcd, mod_exp } from "./math.js";
2023-04-13 12:32:29 +00:00
const PAILLIER = 0;
const JURIK = 1;
2023-04-15 13:28:13 +00:00
function RSTransform(g, a, p) {
let plainText = p.toString(16);
if (plainText.length % 2 !== 0) {
plainText = "0" + plainText;
}
let aStr = a.toString(16);
if (aStr.length % 2 !== 0) {
aStr = "0" + aStr;
}
let hasher = new jsSHA("SHAKE256", "HEX");
hasher.update(g.toString(16));
hasher.update(plainText);
hasher.update(aStr);
return BigInt("0x" + hasher.getHash("HEX", { outputLen: 2048 }));
}
2023-04-10 10:19:11 +00:00
class Ciphertext {
2023-04-13 15:33:32 +00:00
constructor(key, plainText, r, set) {
2023-04-21 14:03:15 +00:00
while (plainText < 0n) {
plainText += key.n2;
}
2023-04-13 15:33:32 +00:00
if (set !== undefined) {
this.pubKey = key;
this.plainText = plainText;
this.readOnly = false;
return;
}
if (r === undefined) {
2023-04-13 12:32:29 +00:00
// Use the optimised form using Jacobi classes
r = cryptoRandom();
2023-02-11 14:59:24 +00:00
2023-04-13 12:32:29 +00:00
// Compute g^m by binomial theorem.
let gm = (1n + key.n * plainText) % key.n2;
2023-03-17 10:42:11 +00:00
2023-04-13 12:32:29 +00:00
// Compute g^m h^r.
2023-04-17 15:32:35 +00:00
this.cipherText = (gm * key.hn_exp(r)) % key.n2;
2023-04-06 19:42:24 +00:00
2023-04-13 12:32:29 +00:00
// Force into range.
while (this.cipherText < 0n) {
this.cipherText += key.n2;
}
this.mode = JURIK;
2023-04-17 15:32:35 +00:00
this.r = key.h_exp(r);
2023-04-13 12:32:29 +00:00
} else {
// Use the standard form
// Compute g^m by binomial theorem.
let gm = (1n + key.n * plainText) % key.n2;
// Compute g^m r^n.
this.cipherText = (gm * mod_exp(r, key.n, key.n2)) % key.n2;
// Force into range.
while (this.cipherText < 0n) {
this.cipherText += key.n2;
}
this.mode = PAILLIER;
this.r = r;
2023-04-06 19:42:24 +00:00
}
2023-03-18 15:41:37 +00:00
this.pubKey = key;
2023-03-17 10:42:11 +00:00
this.plainText = plainText;
this.readOnly = false;
}
update(c) {
2023-04-10 18:05:10 +00:00
this.cipherText = (this.cipherText * c.cipherText) % this.pubKey.n2;
this.r = (this.r * c.r) % this.pubKey.n2;
2023-04-14 15:04:24 +00:00
this.plainText = (this.plainText + c.plainText) % this.pubKey.n2;
// Force into range
2023-04-10 18:05:10 +00:00
while (this.cipherText < 0n) {
this.cipherText += this.pubKey.n2;
}
2023-04-14 15:04:24 +00:00
while (this.plainText < 0n) {
this.plainText += this.pubKey.n2;
}
2023-03-17 10:42:11 +00:00
}
2023-04-25 17:05:44 +00:00
mul(e) {
this.cipherText = mod_exp(this.cipherText, e, this.pubKey.n2);
this.r = mod_exp(this.r, e, this.pubKey.n2);
this.plainText = (this.plainText * e) % this.pubKey.n2;
// Force into range
while (this.cipherText < 0n) {
this.cipherText += this.pubKey.n2;
}
while (this.plainText < 0n) {
this.plainText += this.pubKey.n2;
}
}
2023-03-17 10:42:11 +00:00
toString() {
2023-04-10 18:05:10 +00:00
return "0x" + this.cipherText.toString(16);
2023-04-14 09:55:20 +00:00
}
toJSON() {
return "0x" + this.cipherText.toString(16);
2023-03-17 10:42:11 +00:00
}
2023-03-18 15:41:37 +00:00
prove() {
2023-04-10 18:05:10 +00:00
return new ValueProofSessionProver(this);
2023-03-18 15:41:37 +00:00
}
2023-04-10 18:05:10 +00:00
// Construct a non-interactive proof
proveNI() {
2023-04-13 12:32:29 +00:00
let rp = cryptoRandom(KEY_SIZE * 2);
2023-04-10 18:05:10 +00:00
while (rp >= this.pubKey.n) {
2023-04-13 12:32:29 +00:00
rp = cryptoRandom(KEY_SIZE * 2);
2023-04-10 18:05:10 +00:00
}
let a = mod_exp(rp, this.pubKey.n, this.pubKey.n2);
2023-04-15 13:28:13 +00:00
let challenge = RSTransform(this.pubKey.g, a, this.plainText);
2023-04-10 18:05:10 +00:00
return {
plainText: "0x" + this.plainText.toString(16),
a: "0x" + a.toString(16),
proof:
"0x" +
(
((rp % this.pubKey.n) * mod_exp(this.r, challenge, this.pubKey.n)) %
this.pubKey.n
).toString(16),
};
}
asReadOnlyCiphertext() {
return new ReadOnlyCiphertext(this.pubKey, this.cipherText);
2023-03-18 15:41:37 +00:00
}
2023-04-13 15:33:32 +00:00
clone() {
let c = new Ciphertext(this.pubKey, this.plainText, 0, true);
c.cipherText = this.cipherText;
c.r = this.r;
c.mode = this.mode;
return c;
}
2023-03-17 10:42:11 +00:00
}
2023-04-10 18:05:10 +00:00
class ValueProofSessionProver {
2023-03-18 15:41:37 +00:00
constructor(cipherText) {
this.cipherText = cipherText;
2023-04-13 12:32:29 +00:00
this.rp = cryptoRandom(KEY_SIZE * 2);
2023-03-18 15:41:37 +00:00
while (this.rp >= this.cipherText.pubKey.n) {
2023-04-13 12:32:29 +00:00
this.rp = cryptoRandom(KEY_SIZE * 2);
2023-03-18 15:41:37 +00:00
}
}
2023-03-24 16:53:02 +00:00
get a() {
return mod_exp(this.rp, this.cipherText.pubKey.n, this.cipherText.pubKey.n2);
2023-03-24 16:53:02 +00:00
}
2023-03-18 15:41:37 +00:00
noise() {
return mod_exp(this.rp, this.cipherText.pubKey.n, this.cipherText.pubKey.n2);
2023-03-18 15:41:37 +00:00
}
prove(challenge) {
return (
((this.rp % this.cipherText.pubKey.n) *
mod_exp(this.cipherText.r, challenge, this.cipherText.pubKey.n)) %
this.cipherText.pubKey.n
);
}
asVerifier() {
return this.cipherText
2023-04-10 18:05:10 +00:00
.asReadOnlyCiphertext()
.prove(this.cipherText.plainText, this.noise());
2023-03-18 15:41:37 +00:00
}
}
2023-04-10 18:05:10 +00:00
window.Ciphertext = Ciphertext;
2023-03-18 15:41:37 +00:00
2023-04-10 10:19:11 +00:00
export class ReadOnlyCiphertext {
2023-04-10 18:05:10 +00:00
constructor(key, cipherText) {
2023-04-21 14:03:15 +00:00
if (typeof cipherText !== "bigint") {
throw "ReadOnlyCiphertext must take BigInt parameter";
}
2023-04-10 18:05:10 +00:00
this.cipherText = cipherText;
2023-03-18 15:41:37 +00:00
this.pubKey = key;
2023-03-17 10:42:11 +00:00
this.readOnly = true;
}
update(c) {
2023-04-10 18:05:10 +00:00
this.cipherText = (this.cipherText * c.cipherText) % this.pubKey.n2;
// Force into range
2023-04-10 18:05:10 +00:00
while (this.cipherText < 0n) {
this.cipherText += this.pubKey.n2;
}
2023-03-17 10:42:11 +00:00
}
2023-03-18 15:41:37 +00:00
2023-04-25 17:05:44 +00:00
mul(e) {
this.cipherText = mod_exp(this.cipherText, e, this.pubKey.n2);
// Force into range
while (this.cipherText < 0n) {
this.cipherText += this.pubKey.n2;
}
}
2023-03-24 16:53:02 +00:00
prove(plainText, a) {
2023-04-10 18:05:10 +00:00
return new ValueProofSessionVerifier(this, plainText, a);
}
verifyNI(statement) {
2023-04-15 13:28:13 +00:00
let challenge = RSTransform(
this.pubKey.g,
BigInt(statement.a),
BigInt(statement.plainText)
);
2023-04-10 18:05:10 +00:00
let verifier = new ValueProofSessionVerifier(
this,
BigInt(statement.plainText),
BigInt(statement.a),
2023-04-15 13:28:13 +00:00
challenge
2023-04-10 18:05:10 +00:00
);
2023-04-14 15:04:24 +00:00
if (verifier.verify(BigInt(statement.proof))) {
return BigInt(statement.plainText);
} else {
return null;
}
2023-03-18 15:41:37 +00:00
}
clone() {
2023-04-10 18:05:10 +00:00
return new ReadOnlyCiphertext(this.pubKey, this.cipherText);
}
2023-03-17 10:42:11 +00:00
}
2023-04-10 18:05:10 +00:00
class ValueProofSessionVerifier {
constructor(cipherText, plainText, a, challenge) {
// Clone, otherwise the update below will mutate the original value
this.cipherText = cipherText.clone();
this.cipherText.update(this.cipherText.pubKey.encrypt(-1n * plainText, 1n));
2023-04-10 18:05:10 +00:00
if (challenge === undefined) {
// Shift the challenge down by 1 to ensure it is smaller than either prime factor.
2023-04-13 12:32:29 +00:00
this.challenge = cryptoRandom(KEY_SIZE) << 1n;
2023-04-10 18:05:10 +00:00
} else {
this.challenge = challenge;
}
2023-03-18 15:41:37 +00:00
this.a = a;
this.plainText = plainText;
2023-03-18 15:41:37 +00:00
}
verify(proof) {
// check coprimality
if (gcd(proof, this.cipherText.pubKey.n) !== 1n) return -1;
2023-04-10 18:05:10 +00:00
if (gcd(this.cipherText.cipherText, this.cipherText.pubKey.n) !== 1n) return -2;
if (gcd(this.a, this.cipherText.pubKey.n) !== 1n) return -3;
2023-03-18 15:41:37 +00:00
// check exp
2023-04-07 17:59:33 +00:00
return mod_exp(proof, this.cipherText.pubKey.n, this.cipherText.pubKey.n2) ===
2023-03-18 15:41:37 +00:00
(this.a *
mod_exp(
2023-04-10 18:05:10 +00:00
this.cipherText.cipherText,
2023-03-18 15:41:37 +00:00
this.challenge,
2023-04-07 17:59:33 +00:00
this.cipherText.pubKey.n2
2023-03-18 15:41:37 +00:00
)) %
2023-04-07 17:59:33 +00:00
this.cipherText.pubKey.n2
? 1
: -4;
2023-03-18 15:41:37 +00:00
}
}
2023-04-10 18:05:10 +00:00
window.ReadOnlyCiphertext = ReadOnlyCiphertext;
2023-03-18 15:41:37 +00:00
2023-03-17 10:42:11 +00:00
export class PaillierPubKey {
2023-04-11 14:39:49 +00:00
constructor(n, h) {
2023-03-17 10:42:11 +00:00
this.n = n;
2023-04-11 14:39:49 +00:00
if (h === undefined) {
2023-04-13 12:32:29 +00:00
let x = cryptoRandom(KEY_SIZE * 2);
2023-04-11 14:39:49 +00:00
while (x >= this.n) {
2023-04-13 12:32:29 +00:00
x = cryptoRandom(KEY_SIZE * 2);
2023-04-11 14:39:49 +00:00
}
this.h = ((-1n * x ** 2n) % this.n) + this.n;
} else {
this.h = h;
}
2023-03-17 10:42:11 +00:00
this.g = this.n + 1n;
2023-04-11 14:39:49 +00:00
this.n2 = this.n ** 2n;
this.hn = mod_exp(this.h, this.n, this.n2);
2023-04-17 15:32:35 +00:00
this._h_cache = [];
this._hn_cache = [];
2023-04-21 14:03:15 +00:00
// Browser dies on higher key sizes :P
if (KEY_SIZE <= 1024) {
for (let i = 0n; i < BigInt(KEY_SIZE); i++) {
this._h_cache.push(mod_exp(this.h, 2n ** i, this.n));
this._hn_cache.push(mod_exp(this.hn, 2n ** i, this.n2));
}
2023-04-17 15:32:35 +00:00
}
}
h_exp(b) {
2023-04-21 14:03:15 +00:00
if (KEY_SIZE > 1024) {
return mod_exp(this.h, b, this.n);
}
2023-04-17 15:32:35 +00:00
let ctr = 1n;
let i = 0;
while (b !== 0n) {
if (b % 2n === 1n) {
ctr *= this._h_cache[i];
ctr %= this.n;
}
i++;
b >>= 1n;
}
return ctr;
}
hn_exp(b) {
2023-04-21 14:03:15 +00:00
if (KEY_SIZE > 1024) {
return mod_exp(this.hn, b, this.n2);
}
2023-04-17 15:32:35 +00:00
let ctr = 1n;
let i = 0;
while (b !== 0n) {
if (b % 2n === 1n) {
ctr *= this._hn_cache[i];
ctr %= this.n2;
}
i++;
b >>= 1n;
}
return ctr;
2023-03-17 10:42:11 +00:00
}
encrypt(m, r) {
2023-04-10 10:19:11 +00:00
return new Ciphertext(this, m, r);
2023-02-08 17:55:45 +00:00
}
2023-03-13 14:52:14 +00:00
toJSON() {
return {
n: "0x" + this.n.toString(16),
2023-04-11 14:39:49 +00:00
h: "0x" + this.h.toString(16),
2023-03-13 14:52:14 +00:00
};
}
static fromJSON(data) {
2023-04-11 14:39:49 +00:00
return new PaillierPubKey(BigInt(data.n), BigInt(data.h));
2023-03-13 14:52:14 +00:00
}
2023-02-08 17:55:45 +00:00
}
2023-03-13 14:52:14 +00:00
class PaillierPrivKey {
constructor(p, q) {
this.n = p * q;
2023-04-07 17:59:33 +00:00
// precompute square of n
this.n2 = this.n ** 2n;
this.lambda = (p - 1n) * (q - 1n);
this.mu = mod_exp(this.lambda, this.lambda - 1n, this.n);
}
decrypt(c) {
2023-04-07 17:59:33 +00:00
return (((mod_exp(c, this.lambda, this.n2) - 1n) / this.n) * this.mu) % this.n;
2023-02-08 15:52:02 +00:00
}
2023-02-08 17:55:45 +00:00
}
2023-04-11 14:39:49 +00:00
function check_gcd(primes, new_prime) {
for (let prime of primes) {
if (gcd(prime - 1n, new_prime - 1n) === 2n) {
return prime;
}
}
return null;
}
export function generate_keypair() {
2023-03-13 14:52:14 +00:00
let p, q, pubKey, privKey;
2023-04-11 14:39:49 +00:00
if (
window.sessionStorage.getItem("p") !== null &&
window.sessionStorage.getItem("q") !== null
) {
p = BigInt(window.sessionStorage.getItem("p"));
q = BigInt(window.sessionStorage.getItem("q"));
2023-04-11 14:39:49 +00:00
} else {
2023-04-20 09:13:03 +00:00
p = generate_safe_prime();
q = generate_safe_prime();
}
2023-02-08 17:55:45 +00:00
2023-04-11 14:39:49 +00:00
window.sessionStorage.setItem("p", p);
window.sessionStorage.setItem("q", q);
2023-03-13 14:52:14 +00:00
pubKey = new PaillierPubKey(p * q);
privKey = new PaillierPrivKey(p, q);
return { pubKey, privKey };
}
// p = a.prove(); v = p.asVerifier(); v.verify(p.prove(v.challenge));