UNPKG

snarkjs

Version:

zkSNARKs implementation in JavaScript

561 lines (449 loc) 21.4 kB
/* Copyright 2022 iden3 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 <https://www.gnu.org/licenses/>. */ import {readR1csFd} from "r1csfile"; import * as utils from "./powersoftau_utils.js"; import {createBinFile, endWriteSection, readBinFile, startWriteSection, writeBigInt,} from "@iden3/binfileutils"; import {log2} from "./misc.js"; import {BigBuffer, Scalar} from "ffjavascript"; import BigArray from "./bigarray.js"; import { ZKEY_FF_HEADER_SECTION, ZKEY_FF_ADDITIONS_SECTION, ZKEY_FF_A_MAP_SECTION, ZKEY_FF_B_MAP_SECTION, ZKEY_FF_C_MAP_SECTION, ZKEY_FF_QL_SECTION, ZKEY_FF_QR_SECTION, ZKEY_FF_QM_SECTION, ZKEY_FF_QO_SECTION, ZKEY_FF_QC_SECTION, ZKEY_FF_SIGMA1_SECTION, ZKEY_FF_SIGMA2_SECTION, ZKEY_FF_SIGMA3_SECTION, ZKEY_FF_LAGRANGE_SECTION, ZKEY_FF_PTAU_SECTION, FF_T_POL_DEG_MIN, ZKEY_FF_NSECTIONS, ZKEY_FF_C0_SECTION, } from "./fflonk_constants.js"; import {FFLONK_PROTOCOL_ID, HEADER_ZKEY_SECTION} from "./zkey_constants.js"; import { getFFlonkAdditionConstraint, getFFlonkConstantConstraint, getFFlonkMultiplicationConstraint } from "./plonk_equation.js"; import {r1csConstraintProcessor} from "./r1cs_constraint_processor.js"; import {Polynomial} from "./polynomial/polynomial.js"; import * as binFileUtils from "@iden3/binfileutils"; import {Evaluations} from "./polynomial/evaluations.js"; import {CPolynomial} from "./polynomial/cpolynomial.js"; export default async function fflonkSetup(r1csFilename, ptauFilename, zkeyFilename, logger) { if (logger) logger.info("FFLONK SETUP STARTED"); if (globalThis.gc) globalThis.gc(); // Read PTau file if (logger) logger.info("> Reading PTau file"); const {fd: fdPTau, sections: pTauSections} = await readBinFile(ptauFilename, "ptau", 1, 1 << 22, 1 << 24); if (!pTauSections[12]) { throw new Error("Powers of Tau is not well prepared. Section 12 missing."); } // Get curve defined in PTau if (logger) logger.info("> Getting curve from PTau settings"); const {curve} = await utils.readPTauHeader(fdPTau, pTauSections); // Read r1cs file if (logger) logger.info("> Reading r1cs file"); const {fd: fdR1cs, sections: sectionsR1cs} = await readBinFile(r1csFilename, "r1cs", 1, 1 << 22, 1 << 24); const r1cs = await readR1csFd(fdR1cs, sectionsR1cs, {loadConstraints: false, loadCustomGates: true}); // Potential error checks if (r1cs.prime !== curve.r) { throw new Error("r1cs curve does not match powers of tau ceremony curve"); } // Initializations const Fr = curve.Fr; const sFr = curve.Fr.n8; const sG1 = curve.G1.F.n8 * 2; const sG2 = curve.G2.F.n8 * 2; let polynomials = {}; let evaluations = {}; let PTau; let settings = { nVars: r1cs.nVars, nPublic: r1cs.nOutputs + r1cs.nPubInputs }; const plonkConstraints = new BigArray(); let plonkAdditions = new BigArray(); // Process constraints inside r1cs if (logger) logger.info("> Processing FFlonk constraints"); await computeFFConstraints(curve.Fr, r1cs, logger); if (globalThis.gc) globalThis.gc(); // As the t polynomial is n+5 we need at least a power of 4 //TODO check!!!! // NOTE : plonkConstraints + 2 = #constraints + blinding coefficients for each wire polynomial settings.cirPower = Math.max(FF_T_POL_DEG_MIN, log2((plonkConstraints.length + 2) - 1) + 1); settings.domainSize = 2 ** settings.cirPower; if (pTauSections[2][0].size < (settings.domainSize * 9 + 18) * sG1) { throw new Error("Powers of Tau is not big enough for this circuit size. Section 2 too small."); } if (pTauSections[3][0].size < sG2) { throw new Error("Powers of Tau is not well prepared. Section 3 too small."); } if (logger) { logger.info("----------------------------"); logger.info(" FFLONK SETUP SETTINGS"); logger.info(` Curve: ${curve.name}`); logger.info(` Circuit power: ${settings.cirPower}`); logger.info(` Domain size: ${settings.domainSize}`); logger.info(` Vars: ${settings.nVars}`); logger.info(` Public vars: ${settings.nPublic}`); logger.info(` Constraints: ${plonkConstraints.length}`); logger.info(` Additions: ${plonkAdditions.length}`); logger.info("----------------------------"); } // Compute k1 and k2 to be used in the permutation checks if (logger) logger.info("> computing k1 and k2"); const [k1, k2] = computeK1K2(); // Compute omega 3 (w3) and omega 4 (w4) to be used in the prover and the verifier // w3^3 = 1 and w4^4 = 1 if (logger) logger.info("> computing w3"); const w3 = computeW3(); if (logger) logger.info("> computing w4"); const w4 = computeW4(); if (logger) logger.info("> computing w8"); const w8 = computeW8(); if (logger) logger.info("> computing wr"); const wr = getOmegaCubicRoot(settings.cirPower, curve.Fr); // Write output zkey file await writeZkeyFile(); await fdR1cs.close(); await fdPTau.close(); if (logger) logger.info("FFLONK SETUP FINISHED"); return 0; async function computeFFConstraints(Fr, r1cs, logger) { // Add public inputs and outputs for (let i = 0; i < settings.nPublic; i++) { plonkConstraints.push(getFFlonkConstantConstraint(i + 1, Fr)); } // Add all constraints from r1cs file const r1csProcessor = new r1csConstraintProcessor(Fr, getFFlonkConstantConstraint, getFFlonkAdditionConstraint, getFFlonkMultiplicationConstraint, logger); const bR1cs = await binFileUtils.readSection(fdR1cs, sectionsR1cs, 2); let bR1csPos = 0; for (let i = 0; i < r1cs.nConstraints; i++) { if ((logger) && (i !== 0) && (i % 500000 === 0)) { logger.info(` processing r1cs constraints ${i}/${r1cs.nConstraints}`); } const [constraints, additions] = r1csProcessor.processR1csConstraint(settings, ...readConstraint()); plonkConstraints.push(...constraints); plonkAdditions.push(...additions); } function readConstraint() { const c = []; c[0] = readLC(); c[1] = readLC(); c[2] = readLC(); return c; } function readLC() { const lc = {}; const buffUL32 = bR1cs.slice(bR1csPos, bR1csPos + 4); bR1csPos += 4; const buffUL32V = new DataView(buffUL32.buffer); const nIdx = buffUL32V.getUint32(0, true); const buff = bR1cs.slice(bR1csPos, bR1csPos + (4 + r1cs.n8) * nIdx); bR1csPos += (4 + r1cs.n8) * nIdx; const buffV = new DataView(buff.buffer); for (let i = 0; i < nIdx; i++) { const idx = buffV.getUint32(i * (4 + r1cs.n8), true); const val = r1cs.F.fromRprLE(buff, i * (4 + r1cs.n8) + 4); lc[idx] = val; } return lc; } return 0; } async function writeZkeyFile() { if (logger) logger.info("> Writing the zkey file"); const fdZKey = await createBinFile(zkeyFilename, "zkey", 1, ZKEY_FF_NSECTIONS, 1 << 22, 1 << 24); if (logger) logger.info(`··· Writing Section ${HEADER_ZKEY_SECTION}. Zkey Header`); await writeZkeyHeader(fdZKey); if (logger) logger.info(`··· Writing Section ${ZKEY_FF_ADDITIONS_SECTION}. Additions`); await writeAdditions(fdZKey); if (globalThis.gc) globalThis.gc(); if (logger) logger.info(`··· Writing Section ${ZKEY_FF_A_MAP_SECTION}. A Map`); await writeWitnessMap(fdZKey, ZKEY_FF_A_MAP_SECTION, 0, "A map"); if (globalThis.gc) globalThis.gc(); if (logger) logger.info(`··· Writing Section ${ZKEY_FF_B_MAP_SECTION}. B Map`); await writeWitnessMap(fdZKey, ZKEY_FF_B_MAP_SECTION, 1, "B map"); if (globalThis.gc) globalThis.gc(); if (logger) logger.info(`··· Writing Section ${ZKEY_FF_C_MAP_SECTION}. C Map`); await writeWitnessMap(fdZKey, ZKEY_FF_C_MAP_SECTION, 2, "C map"); if (globalThis.gc) globalThis.gc(); if (logger) logger.info(`··· Writing Section ${ZKEY_FF_QL_SECTION}. QL`); await writeQMap(fdZKey, ZKEY_FF_QL_SECTION, 3, "QL"); if (globalThis.gc) globalThis.gc(); if (logger) logger.info(`··· Writing Section ${ZKEY_FF_QR_SECTION}. QR`); await writeQMap(fdZKey, ZKEY_FF_QR_SECTION, 4, "QR"); if (globalThis.gc) globalThis.gc(); if (logger) logger.info(`··· Writing Section ${ZKEY_FF_QM_SECTION}. QM`); await writeQMap(fdZKey, ZKEY_FF_QM_SECTION, 5, "QM"); if (globalThis.gc) globalThis.gc(); if (logger) logger.info(`··· Writing Section ${ZKEY_FF_QO_SECTION}. QO`); await writeQMap(fdZKey, ZKEY_FF_QO_SECTION, 6, "QO"); if (globalThis.gc) globalThis.gc(); if (logger) logger.info(`··· Writing Section ${ZKEY_FF_QC_SECTION}. QC`); await writeQMap(fdZKey, ZKEY_FF_QC_SECTION, 7, "QC"); if (globalThis.gc) globalThis.gc(); if (logger) logger.info(`··· Writing Sections ${ZKEY_FF_SIGMA1_SECTION},${ZKEY_FF_SIGMA2_SECTION},${ZKEY_FF_SIGMA3_SECTION}. Sigma1, Sigma2 & Sigma 3`); await writeSigma(fdZKey); if (globalThis.gc) globalThis.gc(); if (logger) logger.info(`··· Writing Section ${ZKEY_FF_LAGRANGE_SECTION}. Lagrange Polynomials`); await writeLagrangePolynomials(fdZKey); if (globalThis.gc) globalThis.gc(); if (logger) logger.info(`··· Writing Section ${ZKEY_FF_PTAU_SECTION}. Powers of Tau`); await writePtau(fdZKey); if (globalThis.gc) globalThis.gc(); if (logger) logger.info(`··· Writing Section ${ZKEY_FF_C0_SECTION}. C0`); await writeC0(fdZKey); if (globalThis.gc) globalThis.gc(); if (logger) logger.info(`··· Writing Section ${ZKEY_FF_HEADER_SECTION}. FFlonk Header`); await writeFFlonkHeader(fdZKey); if (globalThis.gc) globalThis.gc(); if (logger) logger.info("> Writing the zkey file finished"); await fdZKey.close(); } async function writeZkeyHeader(fdZKey) { await startWriteSection(fdZKey, HEADER_ZKEY_SECTION); await fdZKey.writeULE32(FFLONK_PROTOCOL_ID); await endWriteSection(fdZKey); } async function writeAdditions(fdZKey) { await startWriteSection(fdZKey, ZKEY_FF_ADDITIONS_SECTION); // Written values are 2 * 32 bit integers (2 * 4 bytes) + 2 field size values ( 2 * sFr bytes) const buffOut = new Uint8Array(8 + 2 * sFr); const buffOutV = new DataView(buffOut.buffer); for (let i = 0; i < plonkAdditions.length; i++) { if ((logger) && (i !== 0) && (i % 500000 === 0)) logger.info(` writing Additions: ${i}/${plonkAdditions.length}`); const addition = plonkAdditions[i]; buffOutV.setUint32(0, addition[0], true); buffOutV.setUint32(4, addition[1], true); buffOut.set(addition[2], 8); buffOut.set(addition[3], 8 + sFr); await fdZKey.write(buffOut); } await endWriteSection(fdZKey); } async function writeWitnessMap(fdZKey, sectionNum, posConstraint, name) { await startWriteSection(fdZKey, sectionNum); for (let i = 0; i < plonkConstraints.length; i++) { if (logger && (i !== 0) && (i % 500000 === 0)) { logger.info(` writing witness ${name}: ${i}/${plonkConstraints.length}`); } await fdZKey.writeULE32(plonkConstraints[i][posConstraint]); } await endWriteSection(fdZKey); } async function writeQMap(fdZKey, sectionNum, posConstraint, name) { // Compute Q from q evaluations let Q = new BigBuffer(settings.domainSize * sFr); for (let i = 0; i < plonkConstraints.length; i++) { Q.set(plonkConstraints[i][posConstraint], i * sFr); if ((logger) && (i !== 0) && (i % 500000 === 0)) { logger.info(` writing ${name}: ${i}/${plonkConstraints.length}`); } } polynomials[name] = await Polynomial.fromEvaluations(Q, curve, logger); evaluations[name] = await Evaluations.fromPolynomial(polynomials[name], 4, curve, logger); // Write Q coefficients and evaluations await startWriteSection(fdZKey, sectionNum); await fdZKey.write(polynomials[name].coef); await fdZKey.write(evaluations[name].eval); await endWriteSection(fdZKey); } async function writeSigma(fdZKey) { // Compute sigma const sigma = new BigBuffer(sFr * settings.domainSize * 3); const lastSeen = new BigArray(settings.nVars); const firstPos = new BigArray(settings.nVars); let w = Fr.one; for (let i = 0; i < settings.domainSize; i++) { if (i < plonkConstraints.length) { buildSigma(plonkConstraints[i][0], i); buildSigma(plonkConstraints[i][1], settings.domainSize + i); buildSigma(plonkConstraints[i][2], settings.domainSize * 2 + i); } else if (i < settings.domainSize - 2) { buildSigma(0, i); buildSigma(0, settings.domainSize + i); buildSigma(0, settings.domainSize * 2 + i); } else { sigma.set(w, i * sFr); sigma.set(Fr.mul(w, k1), (settings.domainSize + i) * sFr); sigma.set(Fr.mul(w, k2), (settings.domainSize * 2 + i) * sFr); } w = Fr.mul(w, Fr.w[settings.cirPower]); if ((logger) && (i !== 0) && (i % 500000 === 0)) { logger.info(` writing sigma phase1: ${i}/${plonkConstraints.length}`); } } for (let i = 0; i < settings.nVars; i++) { if (typeof firstPos[i] !== "undefined") { sigma.set(lastSeen[i], firstPos[i] * sFr); } else { // throw new Error("Variable not used"); console.log("Variable not used"); } if ((logger) && (i !== 0) && (i % 500000 === 0)) logger.info(` writing sigma phase2: ${i}/${settings.nVars}`); } if (globalThis.gc) globalThis.gc(); // Write sigma coefficients and evaluations for (let i = 0; i < 3; i++) { const sectionId = 0 === i ? ZKEY_FF_SIGMA1_SECTION : 1 === i ? ZKEY_FF_SIGMA2_SECTION : ZKEY_FF_SIGMA3_SECTION; let name = "S" + (i + 1); polynomials[name] = await Polynomial.fromEvaluations(sigma.slice(settings.domainSize * sFr * i, settings.domainSize * sFr * (i + 1)), curve, logger); evaluations[name] = await Evaluations.fromPolynomial(polynomials[name], 4, curve, logger); await startWriteSection(fdZKey, sectionId); await fdZKey.write(polynomials[name].coef); await fdZKey.write(evaluations[name].eval); await endWriteSection(fdZKey); if (globalThis.gc) globalThis.gc(); } return 0; function buildSigma(signalId, idx) { if (typeof lastSeen[signalId] === "undefined") { firstPos[signalId] = idx; } else { sigma.set(lastSeen[signalId], idx * sFr); } let v; if (idx < settings.domainSize) { v = w; } else if (idx < 2 * settings.domainSize) { v = Fr.mul(w, k1); } else { v = Fr.mul(w, k2); } lastSeen[signalId] = v; } } async function writeLagrangePolynomials(fdZKey) { await startWriteSection(fdZKey, ZKEY_FF_LAGRANGE_SECTION); const l = Math.max(settings.nPublic, 1); for (let i = 0; i < l; i++) { let buff = new BigBuffer(settings.domainSize * sFr); buff.set(Fr.one, i * sFr); await writeP4(fdZKey, buff); } await endWriteSection(fdZKey); } async function writePtau(fdZKey) { await startWriteSection(fdZKey, ZKEY_FF_PTAU_SECTION); // domainSize * 9 + 18 = maximum SRS length needed, specifically to commit C2 PTau = new BigBuffer((settings.domainSize * 9 + 18) * sG1); await fdPTau.readToBuffer(PTau, 0, (settings.domainSize * 9 + 18) * sG1, pTauSections[2][0].p); await fdZKey.write(PTau); await endWriteSection(fdZKey); } async function writeC0(fdZKey) { // C0(X) := QL(X^8) + X · QR(X^8) + X^2 · QO(X^8) + X^3 · QM(X^8) + X^4 · QC(X^8) // + X^5 · SIGMA1(X^8) + X^6 · SIGMA2(X^8) + X^7 · SIGMA3(X^8) let C0 = new CPolynomial(8, curve, logger); C0.addPolynomial(0, polynomials.QL); C0.addPolynomial(1, polynomials.QR); C0.addPolynomial(2, polynomials.QO); C0.addPolynomial(3, polynomials.QM); C0.addPolynomial(4, polynomials.QC); C0.addPolynomial(5, polynomials.S1); C0.addPolynomial(6, polynomials.S2); C0.addPolynomial(7, polynomials.S3); polynomials.C0 = C0.getPolynomial(); // Check degree if (polynomials.C0.degree() >= 8 * settings.domainSize) { throw new Error("C0 Polynomial is not well calculated"); } await startWriteSection(fdZKey, ZKEY_FF_C0_SECTION); await fdZKey.write(polynomials.C0.coef); await endWriteSection(fdZKey); } async function writeFFlonkHeader(fdZKey) { await startWriteSection(fdZKey, ZKEY_FF_HEADER_SECTION); const primeQ = curve.q; const n8q = (Math.floor((Scalar.bitLength(primeQ) - 1) / 64) + 1) * 8; await fdZKey.writeULE32(n8q); await writeBigInt(fdZKey, primeQ, n8q); const primeR = curve.r; const n8r = (Math.floor((Scalar.bitLength(primeR) - 1) / 64) + 1) * 8; await fdZKey.writeULE32(n8r); await writeBigInt(fdZKey, primeR, n8r); // Total number of r1cs vars await fdZKey.writeULE32(settings.nVars); // Total number of r1cs public vars = outputs + public inputs await fdZKey.writeULE32(settings.nPublic); await fdZKey.writeULE32(settings.domainSize); await fdZKey.writeULE32(plonkAdditions.length); await fdZKey.writeULE32(plonkConstraints.length); await fdZKey.write(k1); await fdZKey.write(k2); await fdZKey.write(w3); await fdZKey.write(w4); await fdZKey.write(w8); await fdZKey.write(wr); let bX_2; bX_2 = await fdPTau.read(sG2, pTauSections[3][0].p + sG2); await fdZKey.write(bX_2); let commitC0 = await polynomials.C0.multiExponentiation(PTau, "C0"); await fdZKey.write(commitC0); await endWriteSection(fdZKey); } async function writeP4(fdZKey, buff) { const [coefficients, evaluations4] = await Polynomial.to4T(buff, settings.domainSize, [], Fr); await fdZKey.write(coefficients); await fdZKey.write(evaluations4); return [coefficients, evaluations4]; } function computeK1K2() { let k1 = Fr.two; while (isIncluded(k1, [], settings.cirPower)) Fr.add(k1, Fr.one); let k2 = Fr.add(k1, Fr.one); while (isIncluded(k2, [k1], settings.cirPower)) Fr.add(k2, Fr.one); return [k1, k2]; function isIncluded(k, kArr, pow) { const domainSize = 2 ** pow; let w = Fr.one; for (let i = 0; i < domainSize; i++) { if (Fr.eq(k, w)) return true; for (let j = 0; j < kArr.length; j++) { if (Fr.eq(k, Fr.mul(kArr[j], w))) return true; } w = Fr.mul(w, Fr.w[pow]); } return false; } } function computeW3() { let generator = Fr.e(31624); // Exponent is order(r - 1) / 3 let orderRsub1 = 3648040478639879203707734290876212514758060733402672390616367364429301415936n; let exponent = Scalar.div(orderRsub1, Scalar.e(3)); return Fr.exp(generator, exponent); } function computeW4() { return Fr.w[2]; } function computeW8() { return Fr.w[3]; } function getOmegaCubicRoot(power, Fr) { // Hardcorded 3th-root of Fr.w[28] const firstRoot = Fr.e(467799165886069610036046866799264026481344299079011762026774533774345988080n); return Fr.exp(firstRoot, 2 ** (28 - power)); } }