Riskless/static/js/modules/interface/proofs.js
2023-04-16 21:28:29 +01:00

166 lines
4.5 KiB
JavaScript

/**
* CSPRNG Fisher-Yates shuffle.
*
* Only works on lists up to 255 elements.
*/
function cryptoShuffle(l) {
let out = [];
for (let i = l.length - 1; i > 0; i--) {
let value = new Uint8Array([0]);
crypto.getRandomValues(value);
while (value[0] > i) {
crypto.getRandomValues(value);
}
let v = l.splice(value[0], 1);
out.push(v[0]);
}
out.push(l[0]);
return out;
}
const ROUNDS = 48;
function getCoins(text) {
// Construct verifier coins
let hasher = new jsSHA("SHA3-256", "TEXT");
hasher.update(text);
let hash = hasher.getHash("UINT8ARRAY");
let verifierCoins = [];
for (let i = 0; i < ROUNDS / 8; i++) {
let v = hash[i];
for (let j = 0; j < 8; j++) {
verifierCoins.push(v & 1);
v >>= 1;
}
}
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 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) {
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)