diff --git a/cli.js b/cli.js index 2b6a50f..226c5d8 100755 --- a/cli.js +++ b/cli.js @@ -46,8 +46,9 @@ const Scalar = require("ffjavascript").Scalar; const assert = require("assert"); -const groth16Prover = require("./src/groth16_prover"); const zkey = require("./src/zkey"); +const zksnark = require("./src/zksnark"); +const curves = require("./src/curves"); const commands = [ { @@ -123,7 +124,7 @@ const commands = [ action: powersOfTawExportChallange }, { - cmd: "powersoftau challange contribute [response]", + cmd: "powersoftau challange contribute [response]", description: "Contribute to a challange", alias: ["ptcc"], options: "-verbose|v -entropy|e", @@ -207,6 +208,20 @@ const commands = [ options: "-verbose|v -entropy|e -name|n", action: zkeyContribute }, + { + cmd: "zkey beacon ", + description: "adds a beacon", + alias: ["zkb"], + options: "-verbose|v -name|n", + action: zkeyBeacon + }, + { + cmd: "zkey challange contribute [response]", + description: "contributes to a llallange file in bellman format", + alias: ["zkcc"], + options: "-verbose|v -entropy|e", + action: zkeyChallangeContribute + }, { cmd: "zkey export vkey [circuit.zkey] [verification_key.json]", description: "Exports a verification key", @@ -454,7 +469,7 @@ async function zksnarkProve(params, options) { const publicName = params[3] || "public.json"; - const {proof, publicSignals} = await groth16Prover(zkeyName, witnessName, options.verbose); + const {proof, publicSignals} = await zksnark.groth16.prover(zkeyName, witnessName, options.verbose); await fs.promises.writeFile(proofName, JSON.stringify(stringifyBigInts(proof), null, 1), "utf-8"); await fs.promises.writeFile(publicName, JSON.stringify(stringifyBigInts(publicSignals), null, 1), "utf-8"); @@ -473,10 +488,14 @@ async function zksnarkVerify(params, options) { const pub = unstringifyBigInts(JSON.parse(fs.readFileSync(publicName, "utf8"))); const proof = unstringifyBigInts(JSON.parse(fs.readFileSync(proofName, "utf8"))); +/* const protocol = verificationKey.protocol; if (!zkSnark[protocol]) throw new Error("Invalid protocol"); const isValid = zkSnark[protocol].isValid(verificationKey, proof, pub); +*/ + + const isValid = await zksnark.groth16.verifier(verificationKey, proof, pub); if (isValid) { console.log("OK"); @@ -512,7 +531,7 @@ async function zkeyExportVKey(params) { vk_gamma_2: zKey.vk_gamma_2, vk_delta_2: zKey.vk_delta_2, - vk_alphabeta_12: curve.pairing( zKey.vk_alpha_1 , zKey.vk_beta_2 ) + vk_alphabeta_12: await curve.pairing( zKey.vk_alpha_1 , zKey.vk_beta_2 ) }; await fs.promises.writeFile(verificationKeyName, JSON.stringify(stringifyBigInts(vKey), null, 1), "utf-8"); @@ -648,20 +667,22 @@ async function powersOfTawExportChallange(params, options) { return await powersOfTaw.exportChallange(ptauName, challangeName, options.verbose); } - +// powersoftau challange contribute [response] async function powersOfTawChallangeContribute(params, options) { let challangeName; let responseName; - challangeName = params[0]; + const curve = curves.getCurveFromName(params[0]); - if (params.length < 2) { + challangeName = params[1]; + + if (params.length < 3) { responseName = changeExt(challangeName, "response"); } else { - responseName = params[1]; + responseName = params[2]; } - return await powersOfTaw.challangeContribute(bn128, challangeName, responseName, options.entropy, options.verbose); + return await powersOfTaw.challangeContribute(curve, challangeName, responseName, options.entropy, options.verbose); } @@ -766,7 +787,7 @@ async function zkeyNew(params, options) { ptauName = params[1]; } - if (params.length < 2) { + if (params.length < 3) { zkeyName = "circuit.zkey"; } else { zkeyName = params[2]; @@ -792,7 +813,7 @@ async function zkeyExportBellman(params, options) { mpcparamsName = params[1]; } - return zkey.exportMPCParams(zkeyName, mpcparamsName, options.verbose); + return zkey.exportBellman(zkeyName, mpcparamsName, options.verbose); } @@ -857,3 +878,38 @@ async function zkeyContribute(params, options) { return zkey.contribute(zkeyOldName, zkeyNewName, options.name, options.entropy, options.verbose); } + +// zkey beacon +async function zkeyBeacon(params, options) { + let zkeyOldName; + let zkeyNewName; + let beaconHashStr; + let numIterationsExp; + + zkeyOldName = params[0]; + zkeyNewName = params[1]; + beaconHashStr = params[2]; + numIterationsExp = params[3]; + + return await zkey.beacon(zkeyOldName, zkeyNewName, options.name ,numIterationsExp, beaconHashStr, options.verbose); +} + + +// zkey challange contribute [response]", +async function zkeyChallangeContribute(params, options) { + let challangeName; + let responseName; + + const curve = curves.getCurveFromName(params[0]); + + challangeName = params[1]; + + if (params.length < 3) { + responseName = changeExt(challangeName, "response"); + } else { + responseName = params[2]; + } + + return zkey.challangeContribute(curve, challangeName, responseName, options.entropy, options.verbose); +} + diff --git a/src/curves.js b/src/curves.js index b43785d..d03c936 100644 --- a/src/curves.js +++ b/src/curves.js @@ -10,3 +10,20 @@ module.exports.getCurveFromQ = function getCurveFromQ(q) { } return curve; }; + +module.exports.getCurveFromName = function getCurveFromName(name) { + let curve; + const normName = normalizeName(name); + if (["BN128", "BN254", "ALTBN128"].indexOf(normName) >= 0) { + curve = bn128; + } else { + throw new Error(`Curve not supported: ${name}`); + } + return curve; + + function normalizeName(n) { + return n.toUpperCase().match(/[A-Za-z0-9]+/g).join(""); + } + +}; + diff --git a/src/misc.js b/src/misc.js index f36d4ae..f865ef9 100644 --- a/src/misc.js +++ b/src/misc.js @@ -109,6 +109,47 @@ async function getRandomRng(entropy) { return rng; } +function rngFromBeaconParams(beaconHash, numIterationsExp) { + let nIterationsInner; + let nIterationsOuter; + if (numIterationsExp<32) { + nIterationsInner = (1 << numIterationsExp) >>> 0; + nIterationsOuter = 1; + } else { + nIterationsInner = 0x100000000; + nIterationsOuter = (1 << (numIterationsExp-32)) >>> 0; + } + + let curHash = beaconHash; + for (let i=0; i { - rl.question("Enter a random text. (Entropy): ", (input) => resolve(input) ); - }); -} - - async function challangeContribute(curve, challangeFilename, responesFileName, entropy, verbose) { await Blake2b.ready(); @@ -63,10 +47,6 @@ async function challangeContribute(curve, challangeFilename, responesFileName, e const fdTo = await fastFile.createOverride(responesFileName); - while (!entropy) { - entropy = await askEntropy(); - } - // Calculate the hash console.log("Hashing challange"); const challangeHasher = Blake2b(64); @@ -84,21 +64,7 @@ async function challangeContribute(curve, challangeFilename, responesFileName, e console.log("Current Challange Hash: "); console.log(misc.formatHash(challangeHash)); - const hasher = Blake2b(64); - - hasher.update(crypto.randomBytes(64)); - - const enc = new TextEncoder(); // always utf-8 - hasher.update(enc.encode(entropy)); - - const hash = Buffer.from(hasher.digest()); - - const seed = []; - for (let i=0;i<8;i++) { - seed[i] = hash.readUInt32BE(i*4); - } - - const rng = new ChaCha(seed); + const rng = await misc.getRandomRng(entropy); const key = keyPair.createPTauKey(curve, challangeHash, rng); @@ -125,12 +91,11 @@ async function challangeContribute(curve, challangeFilename, responesFileName, e await fdTo.write(challangeHash); responseHasher.update(challangeHash); - - await contributeSection("G1", (1< { - rl.question("Enter a random text. (Entropy): ", (input) => resolve(input) ); - }); -} - async function contribute(oldPtauFilename, newPTauFilename, name, entropy, verbose) { await Blake2b.ready(); @@ -51,20 +37,9 @@ async function contribute(oldPtauFilename, newPTauFilename, name, entropy, verbo } // Generate a random key - while (!entropy) { - entropy = await askEntropy(); - } - const hasher = Blake2b(64); - hasher.update(crypto.randomBytes(64)); - const enc = new TextEncoder(); // always utf-8 - hasher.update(enc.encode(entropy)); - const hash = Buffer.from(hasher.digest()); - const seed = []; - for (let i=0;i<8;i++) { - seed[i] = hash.readUInt32BE(i*4); - } - const rng = new ChaCha(seed); + const rng = await misc.getRandomRng(entropy); + curContribution.key = keyPair.createPTauKey(curve, lastChallangeHash, rng); diff --git a/src/powersoftau_utils.js b/src/powersoftau_utils.js index f2d09e5..7c3faeb 100644 --- a/src/powersoftau_utils.js +++ b/src/powersoftau_utils.js @@ -1,11 +1,9 @@ -const fastFile = require("fastfile"); const assert = require("assert"); const Scalar = require("ffjavascript").Scalar; const bn128 = require("ffjavascript").bn128; const Blake2b = require("blake2b-wasm"); -const ChaCha = require("ffjavascript").ChaCha; const keyPair = require("./keypair"); -const crypto = require("crypto"); +const misc = require("./misc"); async function writePTauHeader(fd, curve, power, ceremonyPower) { // Write the header @@ -345,30 +343,8 @@ function calculateFirstChallangeHash(curve, power, verbose) { function keyFromBeacon(curve, challangeHash, beaconHash, numIterationsExp) { - let nIterationsInner; - let nIterationsOuter; - if (numIterationsExp<32) { - nIterationsInner = (1 << numIterationsExp) >>> 0; - nIterationsOuter = 1; - } else { - nIterationsInner = 0x100000000; - nIterationsOuter = (1 << (numIterationsExp-32)) >>> 0; - } - let curHash = beaconHash; - for (let i=0; i=256) { + console.log("Maximum lenght of beacon hash is 255 bytes"); + return false; + } + + numIterationsExp = parseInt(numIterationsExp); + if ((numIterationsExp<10)||(numIterationsExp>63)) { + console.log("Invalid numIterationsExp. (Must be between 10 and 63)"); + return false; + } + + + const {fd: fdOld, sections: sections} = await binFileUtils.readBinFile(zkeyNameOld, "zkey", 2); + const zkey = await zkeyUtils.readHeader(fdOld, sections, "groth16"); + + const curve = getCurve(zkey.q); + await curve.loadEngine(); + + const mpcParams = await zkeyUtils.readMPCParams(fdOld, curve, sections); + + const fdNew = await binFileUtils.createBinFile(zkeyNameNew, "zkey", 1, 10); + + const rng = await misc.rngFromBeaconParams(beaconHash, numIterationsExp); + + const transcriptHasher = Blake2b(64); + transcriptHasher.update(mpcParams.csHash); + for (let i=0; i=0; i--) { + const c = mpcParams.contributions[i]; + console.log("-------------------------"); + console.log(`contribution #${i+1}${c.name ? c.name : ""}:`); + console.log(misc.formatHash(c.contributionHash)); + if (c.type == 1) { + console.log(`Beacon generator: ${misc.byteArray2hex(c.beaconHash)}`); + console.log(`Beacon iterations Exp: ${c.numIterationsExp}`); + } + } + console.log("-------------------------"); + return true; diff --git a/src/zksnark.js b/src/zksnark.js new file mode 100644 index 0000000..4ff988f --- /dev/null +++ b/src/zksnark.js @@ -0,0 +1,8 @@ + + +module.exports = { + groth16: { + prover: module.require("./zksnark_groth16_prover"), + verifier: module.require("./zksnark_groth16_verifier") + } +}; diff --git a/src/groth16_prover.js b/src/zksnark_groth16_prover.js similarity index 99% rename from src/groth16_prover.js rename to src/zksnark_groth16_prover.js index 76579b9..e8fe579 100644 --- a/src/groth16_prover.js +++ b/src/zksnark_groth16_prover.js @@ -95,6 +95,9 @@ async function groth16Prover(zkeyFileName, witnessFileName, verbose) { proof.protocol = "groth"; + await fdZKey.close(); + await fdWtns.close(); + return {proof, publicSignals}; } diff --git a/src/zksnark_groth16_verifier.js b/src/zksnark_groth16_verifier.js new file mode 100644 index 0000000..aa2435e --- /dev/null +++ b/src/zksnark_groth16_verifier.js @@ -0,0 +1,48 @@ +/* + Copyright 2018 0kims association. + + This file is part of snarkjs. + + snarkjs is a free software: you can redistribute it and/or + modify it under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your option) + any later version. + + snarkjs is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along with + snarkjs. If not, see . +*/ + +/* Implementation of this paper: https://eprint.iacr.org/2016/260.pdf */ + + +const bn128 = require("ffjavascript").bn128; + +const G1 = bn128.G1; + +module.exports = async function isValid(vk_verifier, proof, publicSignals) { +/* + let cpub = vk_verifier.IC[0]; + for (let s= 0; s< vk_verifier.nPublic; s++) { + cpub = G1.add( cpub, G1.mulScalar( vk_verifier.IC[s+1], publicSignals[s])); + } +*/ + + let cpub = await G1.multiExp(vk_verifier.IC.slice(1), publicSignals); + cpub = G1.add(cpub, vk_verifier.IC[0]); + + const res = await bn128.pairingEq( + bn128.G1.neg(proof.pi_a) , proof.pi_b, + cpub , vk_verifier.vk_gamma_2, + proof.pi_c , vk_verifier.vk_delta_2, + + vk_verifier.vk_alpha_1, vk_verifier.vk_beta_2 + ); + + if (! res) return false; + return true; +};