/** * 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 = 24; 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; export 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) function proveRange() {}