prove and verify rounds

This commit is contained in:
jude 2023-04-14 16:04:24 +01:00
parent 0f8ad2a0a8
commit 35dbf321e9
5 changed files with 142 additions and 33 deletions

View File

@ -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() {

View File

@ -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();

View File

@ -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;
}
}
}
}
}
window.proveRegions = proveRegions;
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)

Binary file not shown.

View File

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