diff --git a/static/js/modules/crypto/paillier.js b/static/js/modules/crypto/paillier.js index f6b9b54..fead085 100644 --- a/static/js/modules/crypto/paillier.js +++ b/static/js/modules/crypto/paillier.js @@ -52,18 +52,25 @@ class Ciphertext { this.pubKey = key; this.plainText = plainText; + while (this.plainText < 0n) { + this.plainText += key.n2; + } + this.readOnly = false; } update(c) { this.cipherText = (this.cipherText * c.cipherText) % this.pubKey.n2; this.r = (this.r * c.r) % this.pubKey.n2; - this.plainText += c.plainText; + this.plainText = (this.plainText + c.plainText) % this.pubKey.n2; // Force into range while (this.cipherText < 0n) { this.cipherText += this.pubKey.n2; } + while (this.plainText < 0n) { + this.plainText += this.pubKey.n2; + } } toString() { @@ -195,7 +202,11 @@ export class ReadOnlyCiphertext { BigInt(statement.challenge) ); - return verifier.verify(BigInt(statement.proof)); + if (verifier.verify(BigInt(statement.proof))) { + return BigInt(statement.plainText); + } else { + return null; + } } clone() { diff --git a/static/js/modules/crypto/random_primes.js b/static/js/modules/crypto/random_primes.js index 9f4c839..93628b5 100644 --- a/static/js/modules/crypto/random_primes.js +++ b/static/js/modules/crypto/random_primes.js @@ -20,9 +20,9 @@ export function cryptoRandom(bits) { } /** - * Generate random integer of length 2048 bits. + * Generate random integer of length N bits. * - * We generate between 2^2047 and 2^2048 - 1 by adding differences. + * We generate between 2^(N - 1) and 2^N - 1 by adding differences. */ function generate_bigint() { let intRepr = cryptoRandom(); diff --git a/static/js/modules/interface/proofs.js b/static/js/modules/interface/proofs.js index 6a616de..196810d 100644 --- a/static/js/modules/interface/proofs.js +++ b/static/js/modules/interface/proofs.js @@ -21,31 +21,12 @@ function cryptoShuffle(l) { return out; } -window.cryptoShuffle = cryptoShuffle; - const ROUNDS = 24; -function proveRegions(regions) { - // Construct prover coins - let coins = []; - - let regionNames = Object.keys(regions); - for (let x = 0; x < ROUNDS; x++) { - let psi = cryptoShuffle(structuredClone(regionNames)).join(""); - let newRegions = structuredClone(regions); - // rearrange keys - for (let index = 0; index < regionNames.length; index++) { - let c = regions[psi[index]].clone(); - // re-blind - c.update(c.pubKey.encrypt(0n)); - newRegions[regionNames[index]] = c; - } - coins.push(newRegions); - } - +function getCoins(text) { // Construct verifier coins let hasher = new jsSHA("SHA3-256", "TEXT"); - hasher.update(JSON.stringify(coins)); + hasher.update(text); let hash = hasher.getHash("UINT8ARRAY"); let verifierCoins = []; @@ -57,14 +38,129 @@ function proveRegions(regions) { } } + return verifierCoins; +} + +window.cryptoShuffle = cryptoShuffle; + +function proveRegions(regions) { + // Construct prover coins + let proofs = []; + let privateInputs = []; + + let regionNames = Object.keys(regions).sort(); + for (let x = 0; x < ROUNDS; x++) { + let psi = cryptoShuffle(structuredClone(regionNames)).join(""); + let newRegions = structuredClone(regions); + + // Rearrange keys + for (let index = 0; index < regionNames.length; index++) { + newRegions[regionNames[index]] = regions[psi[index]].pubKey.encrypt( + regions[psi[index]].plainText * -1n + ); + } + + proofs.push(newRegions); + privateInputs.push(psi); + } + + let verifierCoins = getCoins(JSON.stringify(proofs)); + + let verifications = []; + // Construct prover proofs - for (let coin of verifierCoins) { + for (let i = 0; i < ROUNDS; i++) { + let coin = verifierCoins[i]; + let proof = proofs[i]; + let privateInput = privateInputs[i]; + if (coin === 1) { // Reveal bijection and proof for zero + let zeroProofs = {}; + + for (let i = 0; i < regionNames.length; i++) { + let name = regionNames[i]; + let psiName = privateInput[i]; + + let c = proof[name].clone(); + c.update(regions[psiName]); + zeroProofs[name] = c.proveNI(); + } + + let ver = { + psi: privateInput, + zeroProofs: zeroProofs, + }; + verifications.push(ver); } else { // Reveal proof for plaintext + let valueProofs = {}; + + for (let name of regionNames) { + valueProofs[name] = proof[name].proveNI(); + } + + verifications.push({ valueProofs: valueProofs }); } } + + return { + regions: regions, + proofs: proofs, + verifications: verifications, + }; } window.proveRegions = proveRegions; + +export function verifyRegions(obj, key) { + let verifierCoins = getCoins(JSON.stringify(obj.proofs)); + let regions = obj.regions; + let regionNames = Object.keys(regions).sort(); + + for (let i = 0; i < ROUNDS; i++) { + let proof = obj.proofs[i]; + let verification = obj.verifications[i]; + + if (verifierCoins[i] === 1) { + for (let regionName of regionNames) { + // Undo psi + let originalRegion = proof[regionName]; + + // Compute product + let c = new ReadOnlyCiphertext(key, BigInt(regions[regionName])); + c.update(new ReadOnlyCiphertext(key, BigInt(originalRegion))); + + // Check ciphertext is zero + let plaintext = c.verifyNI(verification.zeroProofs[regionName]); + + if (plaintext !== 0n) { + console.log(plaintext); + return false; + } + } + } else { + let foundOne = false; + + for (let name of Object.keys(verification.valueProofs)) { + let ciphertext = new ReadOnlyCiphertext(key, BigInt(proof[name])); + let plaintext = ciphertext.verifyNI(verification.valueProofs[name]); + if (plaintext === null) { + return false; + } else if (plaintext === 1n) { + if (foundOne) { + return false; + } else { + foundOne = true; + } + } + } + } + } + + return true; +} + +window.verifyRegions = verifyRegions; + +// verifyRegions(proveRegions({A:paillier.pubKey.encrypt(0n),B:paillier.pubKey.encrypt(1n),C:paillier.pubKey.encrypt(0n),D:paillier.pubKey.encrypt(0n),E:paillier.pubKey.encrypt(0n)}), paillier.pubKey) diff --git a/whitepaper/Dissertation.pdf b/whitepaper/Dissertation.pdf index 941cf31..9de8332 100644 Binary files a/whitepaper/Dissertation.pdf and b/whitepaper/Dissertation.pdf differ diff --git a/whitepaper/Dissertation.tex b/whitepaper/Dissertation.tex index 458d847..a2b04f7 100644 --- a/whitepaper/Dissertation.tex +++ b/whitepaper/Dissertation.tex @@ -566,7 +566,7 @@ Timing results versus RSA are backed experimentally by my implementation. The fo console.log(performance.measure("duration", "start", "end").duration) \end{minted} -Performing 250 Paillier encrypts required 48,800ms. On the other hand, performing 250 RSA encrypts required just 60ms. +Performing 250 Paillier encrypts required 47,000ms. On the other hand, performing 250 RSA encrypts required just 40ms. The speed of decryption is considerably less important in this circumstance, as Paillier ciphertexts are not decrypted during the execution of the program. @@ -580,7 +580,9 @@ Taking this idea further, one may simply cache $r^n$ for a number of randomly ge \textbf{Alternative Paillier scheme.} \cite{Jurik2003ExtensionsTT} presents an optimised encryption scheme based on the subgroup of elements with Jacobi symbol $+1$. This forms a group as the Jacobi symbol is multiplicative, being a generalisation of the Legendre symbol. -I used this scheme to reduce the time to encrypt to half. Greater optimisations are possible through pre-computation of fixed-base exponentials, but this takes a considerable amount of time, and I found it infeasible within my implementation, since keypairs are only used for a single session. +Using this scheme alone reduced the time to encrypt by a half. Greater optimisations are possible through pre-computation of fixed-base exponentials, but this takes a considerable amount of time, and I found it infeasible within my implementation, since keypairs are only used for a single session. + +Furthermore, in practice gains were closer to a reduction by a third, since in the modified scheme additional computation must be performed to attain the $r$ that would work with normal Paillier, in order to perform the zero-knowledge proofs from before. \textbf{Smaller key size.} The complexity of Paillier encryption increases with key size. Using a smaller key could considerably reduce the time taken \cite{paillier1999public}. @@ -599,12 +601,12 @@ The other proofs do not translate so trivially to this structure however. In fac All measurements taken on Brave 1.50.114 (Chromium 112.0.5615.49) 64-bit, using a Ryzen 5 3600 CPU. \begin{center} - \begin{tabular}{|c|c|c|} + \begin{tabular}{|c|c|c|c|} \hline - Modulus size & Na\"ive encrypt & Jacobi encrypt \\\hline - $n = 1024$ & cell5 & 4ms \\ - $n = 2048$ & cell8 & 22ms \\ - $n = 4096$ & cell8 & 128ms \\ + Modulus size & Na\"ive encrypt & Jacobi encrypt & RSA encrypt \\\hline + $|n| = 1024$ & 6ms & 4ms & <1ms \\ + $|n| = 2048$ & 34ms & 22ms & <1ms \\ + $|n| = 4096$ & 189ms & 128ms & <1ms \\ \hline \end{tabular} \end{center}