2023-03-19 15:31:29 +00:00
|
|
|
import { cryptoRandom, generate_prime } from "./random_primes.js";
|
2023-03-03 17:34:15 +00:00
|
|
|
import { mod_exp } from "./math.js";
|
|
|
|
|
2023-04-10 10:19:11 +00:00
|
|
|
class Ciphertext {
|
2023-03-19 15:31:29 +00:00
|
|
|
constructor(key, plainText, r) {
|
|
|
|
if (r === undefined) {
|
|
|
|
r = cryptoRandom(4096);
|
|
|
|
|
|
|
|
// Resample to avoid modulo bias.
|
|
|
|
while (r >= key.n) {
|
|
|
|
r = cryptoRandom(4096);
|
|
|
|
}
|
2023-02-11 14:59:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Compute g^m by binomial theorem.
|
2023-04-07 17:59:33 +00:00
|
|
|
let gm = (1n + key.n * plainText) % key.n2;
|
2023-03-17 10:42:11 +00:00
|
|
|
|
2023-04-06 19:42:24 +00:00
|
|
|
// Compute g^m r^n from crt.
|
2023-04-07 17:59:33 +00:00
|
|
|
this.cyphertext = (gm * mod_exp(r, key.n, key.n2)) % key.n2;
|
2023-04-06 19:42:24 +00:00
|
|
|
|
|
|
|
// Force into range.
|
|
|
|
while (this.cyphertext < 0n) {
|
2023-04-07 17:59:33 +00:00
|
|
|
this.cyphertext += key.n2;
|
2023-04-06 19:42:24 +00:00
|
|
|
}
|
|
|
|
|
2023-03-17 10:42:11 +00:00
|
|
|
this.r = r;
|
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-09 17:36:58 +00:00
|
|
|
this.cyphertext = (this.cyphertext * c.cyphertext) % this.pubKey.n2;
|
|
|
|
this.r = (this.r * c.r) % this.pubKey.n2;
|
2023-03-17 10:42:11 +00:00
|
|
|
this.plainText += c.plainText;
|
2023-04-06 11:15:19 +00:00
|
|
|
|
|
|
|
// Force into range
|
|
|
|
while (this.cyphertext < 0n) {
|
2023-04-09 17:36:58 +00:00
|
|
|
this.cyphertext += this.pubKey.n2;
|
2023-04-06 11:15:19 +00:00
|
|
|
}
|
2023-03-17 10:42:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
toString() {
|
|
|
|
return "0x" + this.cyphertext.toString(16);
|
|
|
|
}
|
2023-03-18 15:41:37 +00:00
|
|
|
|
|
|
|
prove() {
|
2023-04-10 10:19:11 +00:00
|
|
|
return new ZeroProofSessionProver(this);
|
2023-03-18 15:41:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
asReadOnlyCyphertext() {
|
2023-04-10 10:19:11 +00:00
|
|
|
return new ReadOnlyCiphertext(this.pubKey, this.cyphertext);
|
2023-03-18 15:41:37 +00:00
|
|
|
}
|
2023-03-17 10:42:11 +00:00
|
|
|
}
|
|
|
|
|
2023-04-10 10:19:11 +00:00
|
|
|
class ZeroProofSessionProver {
|
2023-03-18 15:41:37 +00:00
|
|
|
constructor(cipherText) {
|
|
|
|
this.cipherText = cipherText;
|
|
|
|
|
2023-03-19 15:31:29 +00:00
|
|
|
this.rp = cryptoRandom(4096);
|
2023-03-18 15:41:37 +00:00
|
|
|
while (this.rp >= this.cipherText.pubKey.n) {
|
2023-03-19 15:31:29 +00:00
|
|
|
this.rp = cryptoRandom(4096);
|
2023-03-18 15:41:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-24 16:53:02 +00:00
|
|
|
get a() {
|
2023-04-09 17:36:58 +00:00
|
|
|
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() {
|
2023-04-09 17:36:58 +00:00
|
|
|
return mod_exp(this.rp, this.cipherText.pubKey.n, this.cipherText.pubKey.n2);
|
2023-03-18 15:41:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
prove(challenge) {
|
2023-03-19 15:31:29 +00:00
|
|
|
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
|
|
|
|
.asReadOnlyCyphertext()
|
|
|
|
.prove(this.cipherText.plainText, this.noise());
|
2023-03-18 15:41:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-10 10:19:11 +00:00
|
|
|
window.Cyphertext = Ciphertext;
|
2023-03-18 15:41:37 +00:00
|
|
|
|
2023-04-10 10:19:11 +00:00
|
|
|
export class ReadOnlyCiphertext {
|
2023-03-17 10:42:11 +00:00
|
|
|
constructor(key, cyphertext) {
|
|
|
|
this.cyphertext = cyphertext;
|
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-09 17:36:58 +00:00
|
|
|
this.cyphertext = (this.cyphertext * c.cyphertext) % this.pubKey.n2;
|
2023-04-06 11:15:19 +00:00
|
|
|
|
|
|
|
// Force into range
|
|
|
|
while (this.cyphertext < 0n) {
|
2023-04-09 17:36:58 +00:00
|
|
|
this.cyphertext += this.pubKey.n2;
|
2023-04-06 11:15:19 +00:00
|
|
|
}
|
2023-03-17 10:42:11 +00:00
|
|
|
}
|
2023-03-18 15:41:37 +00:00
|
|
|
|
2023-03-24 16:53:02 +00:00
|
|
|
prove(plainText, a) {
|
2023-04-10 10:19:11 +00:00
|
|
|
return new ZeroProofSessionVerifier(this, plainText, a);
|
2023-03-18 15:41:37 +00:00
|
|
|
}
|
2023-04-09 17:36:58 +00:00
|
|
|
|
|
|
|
clone() {
|
2023-04-10 10:19:11 +00:00
|
|
|
return new ReadOnlyCiphertext(this.pubKey, this.cyphertext);
|
2023-04-09 17:36:58 +00:00
|
|
|
}
|
2023-03-17 10:42:11 +00:00
|
|
|
}
|
|
|
|
|
2023-04-10 10:19:11 +00:00
|
|
|
class ZeroProofSessionVerifier {
|
2023-03-19 15:31:29 +00:00
|
|
|
constructor(cipherText, plainText, a) {
|
2023-04-09 17:36:58 +00:00
|
|
|
// Clone, otherwise the update below will mutate the original value
|
|
|
|
this.cipherText = cipherText.clone();
|
2023-03-19 15:31:29 +00:00
|
|
|
this.cipherText.update(this.cipherText.pubKey.encrypt(-1n * plainText, 1n));
|
|
|
|
// Shift the challenge down by 1 to ensure it is smaller than either prime factor.
|
|
|
|
this.challenge = cryptoRandom(2048) << 1n;
|
2023-03-18 15:41:37 +00:00
|
|
|
this.a = a;
|
2023-04-09 17:36:58 +00:00
|
|
|
|
|
|
|
this.plainText = plainText;
|
2023-03-18 15:41:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
verify(proof) {
|
|
|
|
// check coprimality
|
2023-04-06 11:15:19 +00:00
|
|
|
if (gcd(proof, this.cipherText.pubKey.n) !== 1n) return -1;
|
|
|
|
if (gcd(this.cipherText.cyphertext, 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(
|
|
|
|
this.cipherText.cyphertext,
|
|
|
|
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
|
2023-04-06 11:15:19 +00:00
|
|
|
? 1
|
|
|
|
: -4;
|
2023-03-18 15:41:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-10 10:19:11 +00:00
|
|
|
window.ReadOnlyCyphertext = ReadOnlyCiphertext;
|
2023-03-18 15:41:37 +00:00
|
|
|
|
2023-03-17 10:42:11 +00:00
|
|
|
export class PaillierPubKey {
|
|
|
|
constructor(n) {
|
|
|
|
this.n = n;
|
2023-04-07 17:59:33 +00:00
|
|
|
this.n2 = this.n ** 2n;
|
2023-03-17 10:42:11 +00:00
|
|
|
this.g = this.n + 1n;
|
|
|
|
}
|
|
|
|
|
2023-03-19 15:31:29 +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),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
static fromJSON(data) {
|
|
|
|
return new PaillierPubKey(BigInt(data.n));
|
|
|
|
}
|
2023-02-08 17:55:45 +00:00
|
|
|
}
|
|
|
|
|
2023-03-13 14:52:14 +00:00
|
|
|
class PaillierPrivKey {
|
2023-02-12 16:51:28 +00:00
|
|
|
constructor(p, q) {
|
|
|
|
this.n = p * q;
|
2023-04-07 17:59:33 +00:00
|
|
|
// precompute square of n
|
|
|
|
this.n2 = this.n ** 2n;
|
2023-02-12 16:51:28 +00:00
|
|
|
this.lambda = (p - 1n) * (q - 1n);
|
2023-03-03 17:34:15 +00:00
|
|
|
this.mu = mod_exp(this.lambda, this.lambda - 1n, this.n);
|
2023-02-12 16:51:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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-03-03 17:34:15 +00:00
|
|
|
export function generate_keypair() {
|
2023-03-13 14:52:14 +00:00
|
|
|
let p, q, pubKey, privKey;
|
|
|
|
|
2023-02-13 14:55:25 +00:00
|
|
|
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"));
|
|
|
|
}
|
2023-02-08 17:55:45 +00:00
|
|
|
|
2023-03-13 14:52:14 +00:00
|
|
|
pubKey = new PaillierPubKey(p * q);
|
|
|
|
privKey = new PaillierPrivKey(p, q);
|
2023-03-03 17:34:15 +00:00
|
|
|
|
|
|
|
return { pubKey, privKey };
|
|
|
|
}
|
2023-04-09 17:36:58 +00:00
|
|
|
|
|
|
|
// p = a.prove(); v = p.asVerifier(); v.verify(p.prove(v.challenge));
|