Riskless/static/js/modules/crypto/paillier.js
2023-03-18 15:41:37 +00:00

180 lines
4.2 KiB
JavaScript

import { random2048, generate_prime } from "./random_primes.js";
import { mod_exp } from "./math.js";
class Cyphertext {
constructor(key, plainText) {
// Compute g^m r^n mod n^2
let r = random2048();
// Resample to avoid modulo bias.
while (r >= key.n) {
r = random2048();
}
// Compute g^m by binomial theorem.
let gm = (1n + key.n * plainText) % key.n ** 2n;
// Compute g^m r^n from crt
this.cyphertext = (gm * mod_exp(r, key.n, key.n ** 2n)) % key.n ** 2n;
this.r = r;
this.pubKey = key;
this.plainText = plainText;
this.readOnly = false;
this.rp = null;
}
update(c) {
this.cyphertext *= c.cyphertext;
this.r *= c.r;
this.plainText += c.plainText;
}
toString() {
return "0x" + this.cyphertext.toString(16);
}
prove() {
return new ProofSessionProver(this);
}
asReadOnlyCyphertext() {
return new ReadOnlyCyphertext(this.pubKey, this.cyphertext);
}
}
class ProofSessionProver {
constructor(cipherText) {
this.cipherText = cipherText;
this.rp = random2048();
while (this.rp >= this.cipherText.pubKey.n) {
this.rp = random2048();
}
}
noise() {
return mod_exp(this.rp, this.cipherText.pubKey.n, this.cipherText.pubKey.n ** 2n);
}
prove(challenge) {
return {
proof:
((this.rp % this.cipherText.pubKey.n) *
mod_exp(this.cipherText.r, challenge, this.cipherText.pubKey.n)) %
this.cipherText.pubKey.n,
};
}
}
window.Cyphertext = Cyphertext;
export class ReadOnlyCyphertext {
constructor(key, cyphertext) {
this.cyphertext = cyphertext;
this.pubKey = key;
this.readOnly = true;
this.proofPromise = null;
}
update(c) {
this.cyphertext *= c.cyphertext;
}
async prove() {
// request a proof
let promise = new Promise((res) => (this.proofPromise = res));
}
}
class ProofSessionVerifier {
constructor(cipherText, a) {
this.cipherText = cipherText;
this.challenge = random2048();
this.a = a;
}
verify(proof) {
// check coprimality
if (gcd(proof, this.cipherText.pubKey.n) !== 1n) return false;
if (gcd(this.cipherText.cyphertext, this.cipherText.pubKey.n) !== 1n)
return false;
if (gcd(this.a, this.cipherText.pubKey.n) !== 1n) return false;
// check exp
return (
mod_exp(proof, this.cipherText.pubKey.n, this.cipherText.pubKey.n ** 2n) ===
(this.a *
mod_exp(
this.cipherText.cyphertext,
this.challenge,
this.cipherText.pubKey.n ** 2n
)) %
this.cipherText.pubKey.n ** 2n
);
}
}
window.ReadOnlyCyphertext = ReadOnlyCyphertext;
export class PaillierPubKey {
constructor(n) {
this.n = n;
this.g = this.n + 1n;
}
encrypt(m) {
return new Cyphertext(this, m);
}
toJSON() {
return {
n: "0x" + this.n.toString(16),
};
}
static fromJSON(data) {
return new PaillierPubKey(BigInt(data.n));
}
}
class PaillierPrivKey {
constructor(p, q) {
this.n = p * q;
this.lambda = (p - 1n) * (q - 1n);
this.mu = mod_exp(this.lambda, this.lambda - 1n, this.n);
}
decrypt(c) {
return (
(((mod_exp(c, this.lambda, this.n ** 2n) - 1n) / this.n) * this.mu) % this.n
);
}
}
export function generate_keypair() {
let p, q, pubKey, privKey;
if (window.sessionStorage.getItem("p") === null) {
p = generate_prime();
window.sessionStorage.setItem("p", p);
} else {
p = BigInt(window.sessionStorage.getItem("p"));
}
if (window.sessionStorage.getItem("q") === null) {
q = generate_prime();
window.sessionStorage.setItem("q", q);
} else {
q = BigInt(window.sessionStorage.getItem("q"));
}
pubKey = new PaillierPubKey(p * q);
privKey = new PaillierPrivKey(p, q);
return { pubKey, privKey };
}