prove and verify rounds
This commit is contained in:
parent
0f8ad2a0a8
commit
35dbf321e9
@ -52,18 +52,25 @@ class Ciphertext {
|
|||||||
this.pubKey = key;
|
this.pubKey = key;
|
||||||
this.plainText = plainText;
|
this.plainText = plainText;
|
||||||
|
|
||||||
|
while (this.plainText < 0n) {
|
||||||
|
this.plainText += key.n2;
|
||||||
|
}
|
||||||
|
|
||||||
this.readOnly = false;
|
this.readOnly = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(c) {
|
update(c) {
|
||||||
this.cipherText = (this.cipherText * c.cipherText) % this.pubKey.n2;
|
this.cipherText = (this.cipherText * c.cipherText) % this.pubKey.n2;
|
||||||
this.r = (this.r * c.r) % 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
|
// Force into range
|
||||||
while (this.cipherText < 0n) {
|
while (this.cipherText < 0n) {
|
||||||
this.cipherText += this.pubKey.n2;
|
this.cipherText += this.pubKey.n2;
|
||||||
}
|
}
|
||||||
|
while (this.plainText < 0n) {
|
||||||
|
this.plainText += this.pubKey.n2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
@ -195,7 +202,11 @@ export class ReadOnlyCiphertext {
|
|||||||
BigInt(statement.challenge)
|
BigInt(statement.challenge)
|
||||||
);
|
);
|
||||||
|
|
||||||
return verifier.verify(BigInt(statement.proof));
|
if (verifier.verify(BigInt(statement.proof))) {
|
||||||
|
return BigInt(statement.plainText);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clone() {
|
clone() {
|
||||||
|
@ -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() {
|
function generate_bigint() {
|
||||||
let intRepr = cryptoRandom();
|
let intRepr = cryptoRandom();
|
||||||
|
@ -21,31 +21,12 @@ function cryptoShuffle(l) {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.cryptoShuffle = cryptoShuffle;
|
|
||||||
|
|
||||||
const ROUNDS = 24;
|
const ROUNDS = 24;
|
||||||
|
|
||||||
function proveRegions(regions) {
|
function getCoins(text) {
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct verifier coins
|
// Construct verifier coins
|
||||||
let hasher = new jsSHA("SHA3-256", "TEXT");
|
let hasher = new jsSHA("SHA3-256", "TEXT");
|
||||||
hasher.update(JSON.stringify(coins));
|
hasher.update(text);
|
||||||
let hash = hasher.getHash("UINT8ARRAY");
|
let hash = hasher.getHash("UINT8ARRAY");
|
||||||
|
|
||||||
let verifierCoins = [];
|
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
|
// 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) {
|
if (coin === 1) {
|
||||||
// Reveal bijection and proof for zero
|
// 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 {
|
} else {
|
||||||
// Reveal proof for plaintext
|
// 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;
|
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)
|
||||||
|
Binary file not shown.
@ -566,7 +566,7 @@ Timing results versus RSA are backed experimentally by my implementation. The fo
|
|||||||
console.log(performance.measure("duration", "start", "end").duration)
|
console.log(performance.measure("duration", "start", "end").duration)
|
||||||
\end{minted}
|
\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.
|
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.
|
\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}.
|
\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.
|
All measurements taken on Brave 1.50.114 (Chromium 112.0.5615.49) 64-bit, using a Ryzen 5 3600 CPU.
|
||||||
|
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\begin{tabular}{|c|c|c|}
|
\begin{tabular}{|c|c|c|c|}
|
||||||
\hline
|
\hline
|
||||||
Modulus size & Na\"ive encrypt & Jacobi encrypt \\\hline
|
Modulus size & Na\"ive encrypt & Jacobi encrypt & RSA encrypt \\\hline
|
||||||
$n = 1024$ & cell5 & 4ms \\
|
$|n| = 1024$ & 6ms & 4ms & <1ms \\
|
||||||
$n = 2048$ & cell8 & 22ms \\
|
$|n| = 2048$ & 34ms & 22ms & <1ms \\
|
||||||
$n = 4096$ & cell8 & 128ms \\
|
$|n| = 4096$ & 189ms & 128ms & <1ms \\
|
||||||
\hline
|
\hline
|
||||||
\end{tabular}
|
\end{tabular}
|
||||||
\end{center}
|
\end{center}
|
||||||
|
Loading…
Reference in New Issue
Block a user