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 }; }