snarkjs
Version:
zkSNARKs implementation in JavaScript
890 lines (692 loc) • 38.1 kB
JavaScript
/*
Copyright 2021 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 <https://www.gnu.org/licenses/>.
*/
/* Implementation of this paper: https://eprint.iacr.org/2019/953.pdf section 8.4 */
import * as binFileUtils from "@iden3/binfileutils";
import * as zkeyUtils from "./zkey_utils.js";
import * as wtnsUtils from "./wtns_utils.js";
import { Scalar, utils, BigBuffer } from "ffjavascript";
const {stringifyBigInts} = utils;
import { Proof } from "./proof.js";
import { Keccak256Transcript } from "./Keccak256Transcript.js";
import { MulZ } from "./mul_z.js";
import { ZKEY_PL_HEADER_SECTION,
ZKEY_PL_ADDITIONS_SECTION,
ZKEY_PL_A_MAP_SECTION,
ZKEY_PL_B_MAP_SECTION,
ZKEY_PL_C_MAP_SECTION,
ZKEY_PL_QM_SECTION,
ZKEY_PL_QL_SECTION,
ZKEY_PL_QR_SECTION,
ZKEY_PL_QO_SECTION,
ZKEY_PL_QC_SECTION,
ZKEY_PL_SIGMA_SECTION,
ZKEY_PL_LAGRANGE_SECTION,
ZKEY_PL_PTAU_SECTION,
} from "./plonk_constants.js";
import { Polynomial } from "./polynomial/polynomial.js";
import { Evaluations } from "./polynomial/evaluations.js";
export default async function plonk16Prove(zkeyFileName, witnessFileName, logger, options) {
const {fd: fdWtns, sections: sectionsWtns} = await binFileUtils.readBinFile(witnessFileName, "wtns", 2, 1<<25, 1<<23);
// Read witness file
if (logger) logger.debug("> Reading witness file");
const wtns = await wtnsUtils.readHeader(fdWtns, sectionsWtns);
// Read zkey file
if (logger) logger.debug("> Reading zkey file");
const {fd: fdZKey, sections: zkeySections} = await binFileUtils.readBinFile(zkeyFileName, "zkey", 2, 1<<25, 1<<23);
const zkey = await zkeyUtils.readHeader(fdZKey, zkeySections, undefined, options);
if (zkey.protocol != "plonk") {
throw new Error("zkey file is not plonk");
}
if (!Scalar.eq(zkey.r, wtns.q)) {
throw new Error("Curve of the witness does not match the curve of the proving key");
}
if (wtns.nWitness != zkey.nVars -zkey.nAdditions) {
throw new Error(`Invalid witness length. Circuit: ${zkey.nVars}, witness: ${wtns.nWitness}, ${zkey.nAdditions}`);
}
const curve = zkey.curve;
const Fr = curve.Fr;
const n8r = curve.Fr.n8;
const sDomain = zkey.domainSize * n8r;
if (logger) {
logger.debug("----------------------------");
logger.debug(" PLONK PROVE SETTINGS");
logger.debug(` Curve: ${curve.name}`);
logger.debug(` Circuit power: ${zkey.power}`);
logger.debug(` Domain size: ${zkey.domainSize}`);
logger.debug(` Vars: ${zkey.nVars}`);
logger.debug(` Public vars: ${zkey.nPublic}`);
logger.debug(` Constraints: ${zkey.nConstraints}`);
logger.debug(` Additions: ${zkey.nAdditions}`);
logger.debug("----------------------------");
}
//Read witness data
if (logger) logger.debug("> Reading witness file data");
const buffWitness = await binFileUtils.readSection(fdWtns, sectionsWtns, 2);
// First element in plonk is not used and can be any value. (But always the same).
// We set it to zero to go faster in the exponentiations.
buffWitness.set(Fr.zero, 0);
const buffInternalWitness = new BigBuffer(n8r*zkey.nAdditions);
let buffers = {};
let polynomials = {};
let evaluations = {};
let challenges = {};
let proof = new Proof(curve, logger);
const transcript = new Keccak256Transcript(curve);
if (logger) logger.debug(`> Reading Section ${ZKEY_PL_ADDITIONS_SECTION}. Additions`);
await calculateAdditions();
if (logger) logger.debug(`> Reading Section ${ZKEY_PL_SIGMA_SECTION}. Sigma1, Sigma2 & Sigma 3`);
if (logger) logger.debug("··· Reading Sigma polynomials ");
polynomials.Sigma1 = new Polynomial(new BigBuffer(sDomain), curve, logger);
polynomials.Sigma2 = new Polynomial(new BigBuffer(sDomain), curve, logger);
polynomials.Sigma3 = new Polynomial(new BigBuffer(sDomain), curve, logger);
await fdZKey.readToBuffer(polynomials.Sigma1.coef, 0, sDomain, zkeySections[ZKEY_PL_SIGMA_SECTION][0].p);
await fdZKey.readToBuffer(polynomials.Sigma2.coef, 0, sDomain, zkeySections[ZKEY_PL_SIGMA_SECTION][0].p + 5 * sDomain);
await fdZKey.readToBuffer(polynomials.Sigma3.coef, 0, sDomain, zkeySections[ZKEY_PL_SIGMA_SECTION][0].p + 10 * sDomain);
if (logger) logger.debug("··· Reading Sigma evaluations");
evaluations.Sigma1 = new Evaluations(new BigBuffer(sDomain * 4), curve, logger);
evaluations.Sigma2 = new Evaluations(new BigBuffer(sDomain * 4), curve, logger);
evaluations.Sigma3 = new Evaluations(new BigBuffer(sDomain * 4), curve, logger);
await fdZKey.readToBuffer(evaluations.Sigma1.eval, 0, sDomain * 4, zkeySections[ZKEY_PL_SIGMA_SECTION][0].p + sDomain);
await fdZKey.readToBuffer(evaluations.Sigma2.eval, 0, sDomain * 4, zkeySections[ZKEY_PL_SIGMA_SECTION][0].p + 6 * sDomain);
await fdZKey.readToBuffer(evaluations.Sigma3.eval, 0, sDomain * 4, zkeySections[ZKEY_PL_SIGMA_SECTION][0].p + 11 * sDomain);
if (logger) logger.debug(`> Reading Section ${ZKEY_PL_PTAU_SECTION}. Powers of Tau`);
const PTau = await binFileUtils.readSection(fdZKey, zkeySections, ZKEY_PL_PTAU_SECTION);
let publicSignals = [];
for (let i=1; i<= zkey.nPublic; i++) {
const pub = buffWitness.slice(i*Fr.n8, i*Fr.n8+Fr.n8);
publicSignals.push(Scalar.fromRprLE(pub));
}
if (logger) logger.debug("");
if (logger) logger.debug("> ROUND 1");
await round1();
if (logger) logger.debug("> ROUND 2");
await round2();
if (logger) logger.debug("> ROUND 3");
await round3();
if (logger) logger.debug("> ROUND 4");
await round4();
if (logger) logger.debug("> ROUND 5");
await round5();
///////////////////////
// Final adjustments //
///////////////////////
await fdZKey.close();
await fdWtns.close();
// Prepare proof
let _proof = proof.toObjectProof(false);
_proof.protocol = "plonk";
_proof.curve = curve.name;
if (logger) logger.debug("PLONK PROVER FINISHED");
return {
proof: stringifyBigInts(_proof),
publicSignals: stringifyBigInts(publicSignals)
};
async function calculateAdditions() {
if (logger) logger.debug("··· Computing additions");
const additionsBuff = await binFileUtils.readSection(fdZKey, zkeySections, ZKEY_PL_ADDITIONS_SECTION);
// sizes: wireId_x = 4 bytes (32 bits), factor_x = field size bits
// Addition form: wireId_a wireId_b factor_a factor_b (size is 4 + 4 + sFr + sFr)
const sSum = 8 + n8r * 2;
for (let i = 0; i < zkey.nAdditions; i++) {
if (logger && (0 !== i) && (i % 100000 === 0)) logger.debug(` addition ${i}/${zkey.nAdditions}`);
// Read addition values
let offset = i * sSum;
const signalId1 = readUInt32(additionsBuff, offset);
offset += 4;
const signalId2 = readUInt32(additionsBuff, offset);
offset += 4;
const factor1 = additionsBuff.slice(offset, offset + n8r);
offset += n8r;
const factor2 = additionsBuff.slice(offset, offset + n8r);
// Get witness value
const witness1 = getWitness(signalId1);
const witness2 = getWitness(signalId2);
//Calculate final result
const result = Fr.add(Fr.mul(factor1, witness1), Fr.mul(factor2, witness2));
buffInternalWitness.set(result, n8r * i);
}
}
function readUInt32(b, o) {
const buff = b.slice(o, o+4);
const buffV = new DataView(buff.buffer, buff.byteOffset, buff.byteLength);
return buffV.getUint32(0, true);
}
function getWitness(idx) {
if (idx < zkey.nVars-zkey.nAdditions) {
return buffWitness.slice(idx*n8r, idx*n8r+n8r);
} else if (idx < zkey.nVars) {
return buffInternalWitness.slice((idx - (zkey.nVars-zkey.nAdditions))*n8r, (idx-(zkey.nVars-zkey.nAdditions))*n8r + n8r);
} else {
return curve.Fr.zero;
}
}
async function round1() {
// STEP 1.1 - Generate random blinding scalars (b1, ..., b11) ∈ F
challenges.b = [];
for (let i=1; i<=11; i++) {
challenges.b[i] = curve.Fr.random();
}
// STEP 1.2 - Compute wire polynomials a(X), b(X) and c(X)
if (logger) logger.debug("> Computing A, B, C wire polynomials");
await computeWirePolynomials();
// STEP 1.3 - Compute [a]_1, [b]_1, [c]_1
if (logger) logger.debug("> Computing A, B, C MSM");
let commitA = await polynomials.A.multiExponentiation(PTau, "A");
let commitB = await polynomials.B.multiExponentiation(PTau, "B");
let commitC = await polynomials.C.multiExponentiation(PTau, "C");
// First output of the prover is ([A]_1, [B]_1, [C]_1)
proof.addPolynomial("A", commitA);
proof.addPolynomial("B", commitB);
proof.addPolynomial("C", commitC);
return 0;
}
async function computeWirePolynomials() {
if (logger) logger.debug("··· Reading data from zkey file");
// Build A, B and C evaluations buffer from zkey and witness files
buffers.A = new BigBuffer(sDomain);
buffers.B = new BigBuffer(sDomain);
buffers.C = new BigBuffer(sDomain);
// Read zkey file to the buffers
const aMapBuff = await binFileUtils.readSection(fdZKey, zkeySections, ZKEY_PL_A_MAP_SECTION);
const bMapBuff = await binFileUtils.readSection(fdZKey, zkeySections, ZKEY_PL_B_MAP_SECTION);
const cMapBuff = await binFileUtils.readSection(fdZKey, zkeySections, ZKEY_PL_C_MAP_SECTION);
// Compute all witness from signal ids and set them to A,B & C buffers
for (let i = 0; i < zkey.nConstraints; i++) {
const i_sFr = i * n8r;
const offset = i * 4;
// Compute A value from a signal id
const signalIdA = readUInt32(aMapBuff, offset);
buffers.A.set(getWitness(signalIdA), i_sFr);
// Compute B value from a signal id
const signalIdB = readUInt32(bMapBuff, offset);
buffers.B.set(getWitness(signalIdB), i_sFr);
// Compute C value from a signal id
const signalIdC = readUInt32(cMapBuff, offset);
buffers.C.set(getWitness(signalIdC), i_sFr);
}
buffers.A = await Fr.batchToMontgomery(buffers.A);
buffers.B = await Fr.batchToMontgomery(buffers.B);
buffers.C = await Fr.batchToMontgomery(buffers.C);
// Compute the coefficients of the wire polynomials a(X), b(X) and c(X) from A,B & C buffers
if (logger) logger.debug("··· Computing A ifft");
polynomials.A = await Polynomial.fromEvaluations(buffers.A, curve, logger);
if (logger) logger.debug("··· Computing B ifft");
polynomials.B = await Polynomial.fromEvaluations(buffers.B, curve, logger);
if (logger) logger.debug("··· Computing C ifft");
polynomials.C = await Polynomial.fromEvaluations(buffers.C, curve, logger);
// Compute extended evaluations of a(X), b(X) and c(X) polynomials
if (logger) logger.debug("··· Computing A fft");
evaluations.A = await Evaluations.fromPolynomial(polynomials.A, 4, curve, logger);
if (logger) logger.debug("··· Computing B fft");
evaluations.B = await Evaluations.fromPolynomial(polynomials.B, 4, curve, logger);
if (logger) logger.debug("··· Computing C fft");
evaluations.C = await Evaluations.fromPolynomial(polynomials.C, 4, curve, logger);
// Blind a(X), b(X) and c(X) polynomials coefficients with blinding scalars b
polynomials.A.blindCoefficients([challenges.b[2], challenges.b[1]]);
polynomials.B.blindCoefficients([challenges.b[4], challenges.b[3]]);
polynomials.C.blindCoefficients([challenges.b[6], challenges.b[5]]);
// Check degrees
if (polynomials.A.degree() >= zkey.domainSize + 2) {
throw new Error("A Polynomial is not well calculated");
}
if (polynomials.B.degree() >= zkey.domainSize + 2) {
throw new Error("B Polynomial is not well calculated");
}
if (polynomials.C.degree() >= zkey.domainSize + 2) {
throw new Error("C Polynomial is not well calculated");
}
}
async function round2() {
// STEP 2.1 - Compute permutation challenge beta and gamma ∈ F
// Compute permutation challenge beta
if (logger) logger.debug("> Computing challenges beta and gamma");
transcript.reset();
transcript.addPolCommitment(zkey.Qm);
transcript.addPolCommitment(zkey.Ql);
transcript.addPolCommitment(zkey.Qr);
transcript.addPolCommitment(zkey.Qo);
transcript.addPolCommitment(zkey.Qc);
transcript.addPolCommitment(zkey.S1);
transcript.addPolCommitment(zkey.S2);
transcript.addPolCommitment(zkey.S3);
// Add A to the transcript
for (let i = 0; i < zkey.nPublic; i++) {
transcript.addScalar(buffers.A.slice(i * n8r, i * n8r + n8r));
}
// Add A, B, C to the transcript
transcript.addPolCommitment(proof.getPolynomial("A"));
transcript.addPolCommitment(proof.getPolynomial("B"));
transcript.addPolCommitment(proof.getPolynomial("C"));
challenges.beta = transcript.getChallenge();
if (logger) logger.debug("··· challenges.beta: " + Fr.toString(challenges.beta, 16));
// Compute permutation challenge gamma
transcript.reset();
transcript.addScalar(challenges.beta);
challenges.gamma = transcript.getChallenge();
if (logger) logger.debug("··· challenges.gamma: " + Fr.toString(challenges.gamma, 16));
// STEP 2.2 - Compute permutation polynomial z(X)
if (logger) logger.debug("> Computing Z polynomial");
await computeZ();
// STEP 2.3 - Compute permutation [z]_1
if (logger) logger.debug("> Computing Z MSM");
let commitZ = await polynomials.Z.multiExponentiation(PTau, "Z");
// Second output of the prover is ([Z]_1)
proof.addPolynomial("Z", commitZ);
}
async function computeZ() {
if (logger) logger.debug("··· Computing Z evaluations");
let numArr = new BigBuffer(sDomain);
let denArr = new BigBuffer(sDomain);
// Set the first values to 1
numArr.set(Fr.one, 0);
denArr.set(Fr.one, 0);
// Set initial omega
let w = Fr.one;
for (let i = 0; i < zkey.domainSize; i++) {
const i_n8r = i * n8r;
const a = buffers.A.slice(i_n8r, i_n8r + n8r);
const b = buffers.B.slice(i_n8r, i_n8r + n8r);
const c = buffers.C.slice(i_n8r, i_n8r + n8r);
// Z(X) := numArr / denArr
// numArr := (a + beta·ω + gamma)(b + beta·ω·k1 + gamma)(c + beta·ω·k2 + gamma)
const betaw = Fr.mul(challenges.beta, w);
let n1 = Fr.add(a, betaw);
n1 = Fr.add(n1, challenges.gamma);
let n2 = Fr.add(b, Fr.mul(zkey.k1, betaw));
n2 = Fr.add(n2, challenges.gamma);
let n3 = Fr.add(c, Fr.mul(zkey.k2, betaw));
n3 = Fr.add(n3, challenges.gamma);
let num = Fr.mul(n1, Fr.mul(n2, n3));
// denArr := (a + beta·sigma1 + gamma)(b + beta·sigma2 + gamma)(c + beta·sigma3 + gamma)
let d1 = Fr.add(a, Fr.mul(evaluations.Sigma1.getEvaluation(i * 4), challenges.beta));
d1 = Fr.add(d1, challenges.gamma);
let d2 = Fr.add(b, Fr.mul(evaluations.Sigma2.getEvaluation(i * 4), challenges.beta));
d2 = Fr.add(d2, challenges.gamma);
let d3 = Fr.add(c, Fr.mul(evaluations.Sigma3.getEvaluation(i * 4), challenges.beta));
d3 = Fr.add(d3, challenges.gamma);
let den = Fr.mul(d1, Fr.mul(d2, d3));
// Multiply current num value with the previous one saved in numArr
num = Fr.mul(numArr.slice(i_n8r, i_n8r + n8r), num);
numArr.set(num, ((i + 1) % zkey.domainSize) * n8r);
// Multiply current den value with the previous one saved in denArr
den = Fr.mul(denArr.slice(i_n8r, i_n8r + n8r), den);
denArr.set(den, ((i + 1) % zkey.domainSize) * n8r);
w = Fr.mul(w, Fr.w[zkey.power]);
}
// Compute the inverse of denArr to compute in the next command the
// division numArr/denArr by multiplying num · 1/denArr
denArr = await Fr.batchInverse(denArr);
// TODO: Do it in assembly and in parallel
// Multiply numArr · denArr where denArr was inverted in the previous command
for (let i = 0; i < zkey.domainSize; i++) {
const i_sFr = i * n8r;
const z = Fr.mul(numArr.slice(i_sFr, i_sFr + n8r), denArr.slice(i_sFr, i_sFr + n8r));
numArr.set(z, i_sFr);
}
// From now on the values saved on numArr will be Z(X) buffer
buffers.Z = numArr;
if (!Fr.eq(numArr.slice(0, n8r), Fr.one)) {
throw new Error("Copy constraints does not match");
}
// Compute polynomial coefficients z(X) from buffers.Z
if (logger) logger.debug("··· Computing Z ifft");
polynomials.Z = await Polynomial.fromEvaluations(buffers.Z, curve, logger);
// Compute extended evaluations of z(X) polynomial
if (logger) logger.debug("··· Computing Z fft");
evaluations.Z = await Evaluations.fromPolynomial(polynomials.Z, 4, curve, logger);
// Blind z(X) polynomial coefficients with blinding scalars b
polynomials.Z.blindCoefficients([challenges.b[9], challenges.b[8], challenges.b[7]]);
// Check degree
if (polynomials.Z.degree() >= zkey.domainSize + 3) {
throw new Error("Z Polynomial is not well calculated");
}
delete buffers.Z;
}
async function round3() {
if (logger) logger.debug("> Computing challenge alpha");
// STEP 3.1 - Compute evaluation challenge alpha ∈ F
transcript.reset();
transcript.addScalar(challenges.beta);
transcript.addScalar(challenges.gamma);
transcript.addPolCommitment(proof.getPolynomial("Z"));
challenges.alpha = transcript.getChallenge();
challenges.alpha2 = Fr.square(challenges.alpha);
if (logger) logger.debug("··· challenges.alpha: " + Fr.toString(challenges.alpha, 16));
// Compute quotient polynomial T(X)
if (logger) logger.debug("> Computing T polynomial");
await computeT();
// Compute [T1]_1, [T2]_1, [T3]_1
if (logger) logger.debug("> Computing T MSM");
let commitT1 = await polynomials.T1.multiExponentiation(PTau, "T1");
let commitT2 = await polynomials.T2.multiExponentiation(PTau, "T2");
let commitT3 = await polynomials.T3.multiExponentiation(PTau, "T3");
// Third output of the prover is ([T1]_1, [T2]_1, [T3]_1)
proof.addPolynomial("T1", commitT1);
proof.addPolynomial("T2", commitT2);
proof.addPolynomial("T3", commitT3);
}
async function computeT() {
if (logger)
logger.debug(`··· Reading sections ${ZKEY_PL_QL_SECTION}, ${ZKEY_PL_QR_SECTION}` +
`, ${ZKEY_PL_QM_SECTION}, ${ZKEY_PL_QO_SECTION}, ${ZKEY_PL_QC_SECTION}. Q selectors`);
// Reserve memory for Q's evaluations
evaluations.QL = new Evaluations(new BigBuffer(sDomain * 4), curve, logger);
evaluations.QR = new Evaluations(new BigBuffer(sDomain * 4), curve, logger);
evaluations.QM = new Evaluations(new BigBuffer(sDomain * 4), curve, logger);
evaluations.QO = new Evaluations(new BigBuffer(sDomain * 4), curve, logger);
evaluations.QC = new Evaluations(new BigBuffer(sDomain * 4), curve, logger);
// Read Q's evaluations from zkey file
await fdZKey.readToBuffer(evaluations.QL.eval, 0, sDomain * 4, zkeySections[ZKEY_PL_QL_SECTION][0].p + sDomain);
await fdZKey.readToBuffer(evaluations.QR.eval, 0, sDomain * 4, zkeySections[ZKEY_PL_QR_SECTION][0].p + sDomain);
await fdZKey.readToBuffer(evaluations.QM.eval, 0, sDomain * 4, zkeySections[ZKEY_PL_QM_SECTION][0].p + sDomain);
await fdZKey.readToBuffer(evaluations.QO.eval, 0, sDomain * 4, zkeySections[ZKEY_PL_QO_SECTION][0].p + sDomain);
await fdZKey.readToBuffer(evaluations.QC.eval, 0, sDomain * 4, zkeySections[ZKEY_PL_QC_SECTION][0].p + sDomain);
// Read Lagrange polynomials & evaluations from zkey file
evaluations.Lagrange = new Evaluations(new BigBuffer(sDomain * 4 * zkey.nPublic), curve, logger);
for (let i = 0; i < zkey.nPublic; i++) {
await fdZKey.readToBuffer(evaluations.Lagrange.eval, i * sDomain * 4, sDomain * 4, zkeySections[ZKEY_PL_LAGRANGE_SECTION][0].p + i * 5 * sDomain + sDomain);
}
buffers.T = new BigBuffer(sDomain * 4);
buffers.Tz = new BigBuffer(sDomain * 4);
if (logger) logger.debug("··· Computing T evaluations");
let w = Fr.one;
for (let i = 0; i < zkey.domainSize * 4; i++) {
if (logger && (0 !== i) && (i % 100000 === 0))
logger.debug(` T evaluation ${i}/${zkey.domainSize * 4}`);
const a = evaluations.A.getEvaluation(i);
const b = evaluations.B.getEvaluation(i);
const c = evaluations.C.getEvaluation(i);
const z = evaluations.Z.getEvaluation(i);
const zw = evaluations.Z.getEvaluation((zkey.domainSize * 4 + 4 + i) % (zkey.domainSize * 4));
const qm = evaluations.QM.getEvaluation(i);
const ql = evaluations.QL.getEvaluation(i);
const qr = evaluations.QR.getEvaluation(i);
const qo = evaluations.QO.getEvaluation(i);
const qc = evaluations.QC.getEvaluation(i);
const s1 = evaluations.Sigma1.getEvaluation(i);
const s2 = evaluations.Sigma2.getEvaluation(i);
const s3 = evaluations.Sigma3.getEvaluation(i);
const ap = Fr.add(challenges.b[2], Fr.mul(challenges.b[1], w));
const bp = Fr.add(challenges.b[4], Fr.mul(challenges.b[3], w));
const cp = Fr.add(challenges.b[6], Fr.mul(challenges.b[5], w));
const w2 = Fr.square(w);
const zp = Fr.add(Fr.add(Fr.mul(challenges.b[7], w2), Fr.mul(challenges.b[8], w)), challenges.b[9]);
const wW = Fr.mul(w, Fr.w[zkey.power]);
const wW2 = Fr.square(wW);
const zWp = Fr.add(Fr.add(Fr.mul(challenges.b[7], wW2), Fr.mul(challenges.b[8], wW)), challenges.b[9]);
let pi = Fr.zero;
for (let j = 0; j < zkey.nPublic; j++) {
const offset = (j * 4 * zkey.domainSize) + i;
const lPol = evaluations.Lagrange.getEvaluation(offset);
const aVal = buffers.A.slice(j * n8r, (j + 1) * n8r);
pi = Fr.sub(pi, Fr.mul(lPol, aVal));
}
// e1 := a(X)b(X)qM(X) + a(X)qL(X) + b(X)qR(X) + c(X)qO(X) + PI(X) + qC(X)
let [e1, e1z] = MulZ.mul2(a, b, ap, bp, i % 4, Fr);
e1 = Fr.mul(e1, qm);
e1z = Fr.mul(e1z, qm);
e1 = Fr.add(e1, Fr.mul(a, ql));
e1z = Fr.add(e1z, Fr.mul(ap, ql));
e1 = Fr.add(e1, Fr.mul(b, qr));
e1z = Fr.add(e1z, Fr.mul(bp, qr));
e1 = Fr.add(e1, Fr.mul(c, qo));
e1z = Fr.add(e1z, Fr.mul(cp, qo));
e1 = Fr.add(e1, pi);
e1 = Fr.add(e1, qc);
// e2 := α[(a(X) + βX + γ)(b(X) + βk1X + γ)(c(X) + βk2X + γ)z(X)]
const betaw = Fr.mul(challenges.beta, w);
let e2a = a;
e2a = Fr.add(e2a, betaw);
e2a = Fr.add(e2a, challenges.gamma);
let e2b = b;
e2b = Fr.add(e2b, Fr.mul(betaw, zkey.k1));
e2b = Fr.add(e2b, challenges.gamma);
let e2c = c;
e2c = Fr.add(e2c, Fr.mul(betaw, zkey.k2));
e2c = Fr.add(e2c, challenges.gamma);
let e2d = z;
let [e2, e2z] = MulZ.mul4(e2a, e2b, e2c, e2d, ap, bp, cp, zp, i % 4, Fr);
e2 = Fr.mul(e2, challenges.alpha);
e2z = Fr.mul(e2z, challenges.alpha);
// e3 := α[(a(X) + βSσ1(X) + γ)(b(X) + βSσ2(X) + γ)(c(X) + βSσ3(X) + γ)z(Xω)]
let e3a = a;
e3a = Fr.add(e3a, Fr.mul(challenges.beta, s1));
e3a = Fr.add(e3a, challenges.gamma);
let e3b = b;
e3b = Fr.add(e3b, Fr.mul(challenges.beta, s2));
e3b = Fr.add(e3b, challenges.gamma);
let e3c = c;
e3c = Fr.add(e3c, Fr.mul(challenges.beta, s3));
e3c = Fr.add(e3c, challenges.gamma);
let e3d = zw;
let [e3, e3z] = MulZ.mul4(e3a, e3b, e3c, e3d, ap, bp, cp, zWp, i % 4, Fr);
e3 = Fr.mul(e3, challenges.alpha);
e3z = Fr.mul(e3z, challenges.alpha);
// e4 := α^2(z(X)−1)L1(X)
let e4 = Fr.sub(z, Fr.one);
e4 = Fr.mul(e4, evaluations.Lagrange.getEvaluation(i));
e4 = Fr.mul(e4, challenges.alpha2);
let e4z = Fr.mul(zp, evaluations.Lagrange.getEvaluation(i));
e4z = Fr.mul(e4z, challenges.alpha2);
let t = Fr.add(Fr.sub(Fr.add(e1, e2), e3), e4);
let tz = Fr.add(Fr.sub(Fr.add(e1z, e2z), e3z), e4z);
buffers.T.set(t, i * n8r);
buffers.Tz.set(tz, i * n8r);
w = Fr.mul(w, Fr.w[zkey.power + 2]);
}
// Compute the coefficients of the polynomial T0(X) from buffers.T0
if (logger)
logger.debug("··· Computing T ifft");
polynomials.T = await Polynomial.fromEvaluations(buffers.T, curve, logger);
// Divide the polynomial T0 by Z_H(X)
if (logger)
logger.debug("··· Computing T / ZH");
polynomials.T.divZh(zkey.domainSize, 4);
// Compute the coefficients of the polynomial Tz(X) from buffers.Tz
if (logger)
logger.debug("··· Computing Tz ifft");
polynomials.Tz = await Polynomial.fromEvaluations(buffers.Tz, curve, logger);
// Add the polynomial T1z to T1 to get the final polynomial T1
polynomials.T.add(polynomials.Tz);
// Check degree
if (polynomials.T.degree() >= zkey.domainSize * 3 + 6) {
throw new Error("T Polynomial is not well calculated");
}
// t(x) has degree 3n + 5, we are going to split t(x) into three smaller polynomials:
// T1' and T2' with a degree < n and T3' with a degree n+5
// such that t(x) = T1'(X) + X^n T2'(X) + X^{2n} T3'(X)
// To randomize the parts we use blinding scalars b_10 and b_11 in a way that doesn't change t(X):
// T1(X) = T1'(X) + b_10 X^n
// T2(X) = T2'(X) - b_10 + b_11 X^n
// T3(X) = T3'(X) - b_11
// such that
// t(X) = T1(X) + X^n T2(X) + X^2n T3(X)
if (logger) logger.debug("··· Computing T1, T2, T3 polynomials");
polynomials.T1 = new Polynomial(new BigBuffer((zkey.domainSize + 1) * n8r), curve, logger);
polynomials.T2 = new Polynomial(new BigBuffer((zkey.domainSize + 1) * n8r), curve, logger);
polynomials.T3 = new Polynomial(new BigBuffer((zkey.domainSize + 6) * n8r), curve, logger);
polynomials.T1.coef.set(polynomials.T.coef.slice(0, sDomain), 0);
polynomials.T2.coef.set(polynomials.T.coef.slice(sDomain, sDomain * 2), 0);
polynomials.T3.coef.set(polynomials.T.coef.slice(sDomain * 2, sDomain * 3 + 6 * n8r), 0);
// Add blinding scalar b_10 as a new coefficient n
polynomials.T1.setCoef(zkey.domainSize, challenges.b[10]);
// compute t_mid(X)
// Subtract blinding scalar b_10 to the lowest coefficient of t_mid
const lowestMid = Fr.sub(polynomials.T2.getCoef(0), challenges.b[10]);
polynomials.T2.setCoef(0, lowestMid);
polynomials.T2.setCoef(zkey.domainSize, challenges.b[11]);
// compute t_high(X)
//Subtract blinding scalar b_11 to the lowest coefficient of t_high
const lowestHigh = Fr.sub(polynomials.T3.getCoef(0), challenges.b[11]);
polynomials.T3.setCoef(0, lowestHigh);
}
async function round4() {
if (logger) logger.debug("> Computing challenge xi");
// STEP 4.1 - Compute evaluation challenge xi ∈ F
transcript.reset();
transcript.addScalar(challenges.alpha);
transcript.addPolCommitment(proof.getPolynomial("T1"));
transcript.addPolCommitment(proof.getPolynomial("T2"));
transcript.addPolCommitment(proof.getPolynomial("T3"));
challenges.xi = transcript.getChallenge();
challenges.xiw = Fr.mul(challenges.xi, Fr.w[zkey.power]);
if (logger) logger.debug("··· challenges.xi: " + Fr.toString(challenges.xi, 16));
// Fourth output of the prover is ( a(xi), b(xi), c(xi), s1(xi), s2(xi), z(xiw) )
proof.addEvaluation("eval_a", polynomials.A.evaluate(challenges.xi));
proof.addEvaluation("eval_b", polynomials.B.evaluate(challenges.xi));
proof.addEvaluation("eval_c", polynomials.C.evaluate(challenges.xi));
proof.addEvaluation("eval_s1", polynomials.Sigma1.evaluate(challenges.xi));
proof.addEvaluation("eval_s2", polynomials.Sigma2.evaluate(challenges.xi));
proof.addEvaluation("eval_zw", polynomials.Z.evaluate(challenges.xiw));
}
async function round5() {
if (logger) logger.debug("> Computing challenge v");
// STEP 5.1 - Compute evaluation challenge v ∈ F
transcript.reset();
transcript.addScalar(challenges.xi);
transcript.addScalar(proof.getEvaluation("eval_a"));
transcript.addScalar(proof.getEvaluation("eval_b"));
transcript.addScalar(proof.getEvaluation("eval_c"));
transcript.addScalar(proof.getEvaluation("eval_s1"));
transcript.addScalar(proof.getEvaluation("eval_s2"));
transcript.addScalar(proof.getEvaluation("eval_zw"));
challenges.v = [];
challenges.v[1] = transcript.getChallenge();
if (logger) logger.debug("··· challenges.v: " + Fr.toString(challenges.v[1], 16));
for (let i = 2; i < 6; i++) {
challenges.v[i] = Fr.mul(challenges.v[i - 1], challenges.v[1]);
}
// STEP 5.2 Compute linearisation polynomial r(X)
if (logger) logger.debug("> Computing linearisation polynomial R(X)");
await computeR();
//STEP 5.3 Compute opening proof polynomial Wxi(X)
if (logger) logger.debug("> Computing opening proof polynomial Wxi(X) polynomial");
computeWxi();
//STEP 5.4 Compute opening proof polynomial Wxiw(X)
if (logger) logger.debug("> Computing opening proof polynomial Wxiw(X) polynomial");
computeWxiw();
if (logger) logger.debug("> Computing Wxi, Wxiw MSM");
let commitWxi = await polynomials.Wxi.multiExponentiation(PTau, "Wxi");
let commitWxiw = await polynomials.Wxiw.multiExponentiation(PTau, "Wxiw");
// Fifth output of the prover is ([Wxi]_1, [Wxiw]_1)
proof.addPolynomial("Wxi", commitWxi);
proof.addPolynomial("Wxiw", commitWxiw);
}
async function computeR() {
const Fr = curve.Fr;
// Reserve memory for Q's polynomials
polynomials.QL = new Polynomial(new BigBuffer(sDomain), curve, logger);
polynomials.QR = new Polynomial(new BigBuffer(sDomain), curve, logger);
polynomials.QM = new Polynomial(new BigBuffer(sDomain), curve, logger);
polynomials.QO = new Polynomial(new BigBuffer(sDomain), curve, logger);
polynomials.QC = new Polynomial(new BigBuffer(sDomain), curve, logger);
// Read Q's evaluations from zkey file
await fdZKey.readToBuffer(polynomials.QL.coef, 0, sDomain, zkeySections[ZKEY_PL_QL_SECTION][0].p);
await fdZKey.readToBuffer(polynomials.QR.coef, 0, sDomain, zkeySections[ZKEY_PL_QR_SECTION][0].p);
await fdZKey.readToBuffer(polynomials.QM.coef, 0, sDomain, zkeySections[ZKEY_PL_QM_SECTION][0].p);
await fdZKey.readToBuffer(polynomials.QO.coef, 0, sDomain, zkeySections[ZKEY_PL_QO_SECTION][0].p);
await fdZKey.readToBuffer(polynomials.QC.coef, 0, sDomain, zkeySections[ZKEY_PL_QC_SECTION][0].p);
challenges.xin = challenges.xi;
for (let i = 0; i < zkey.power; i++) {
challenges.xin = Fr.square(challenges.xin);
}
challenges.zh = Fr.sub(challenges.xin, Fr.one);
const L = [];
const n = Fr.e(zkey.domainSize);
let w = Fr.one;
for (let i = 1; i <= Math.max(1, zkey.nPublic); i++) {
L[i] = Fr.div(Fr.mul(w, challenges.zh), Fr.mul(n, Fr.sub(challenges.xi, w)));
w = Fr.mul(w, Fr.w[zkey.power]);
}
const eval_l1 = Fr.div(
Fr.sub(challenges.xin, Fr.one),
Fr.mul(n, Fr.sub(challenges.xi, Fr.one))
);
if (logger) {
logger.debug("Lagrange Evaluations: ");
for (let i=1; i<L.length; i++) {
logger.debug(`L${i}(xi)=` + Fr.toString(L[i], 16));
}
}
let eval_pi = Fr.zero;
for (let i=0; i<publicSignals.length; i++) {
const w = Fr.e(publicSignals[i]);
eval_pi = Fr.sub(eval_pi, Fr.mul(w, L[i+1]));
}
if (logger) logger.debug("PI: " + Fr.toString(eval_pi, 16));
// Compute constant parts of R(X)
const coef_ab = Fr.mul(proof.evaluations.eval_a, proof.evaluations.eval_b);
let e2a = proof.evaluations.eval_a;
const betaxi = Fr.mul(challenges.beta, challenges.xi);
e2a = Fr.add(e2a, betaxi);
e2a = Fr.add(e2a, challenges.gamma);
let e2b = proof.evaluations.eval_b;
e2b = Fr.add(e2b, Fr.mul(betaxi, zkey.k1));
e2b = Fr.add(e2b, challenges.gamma);
let e2c = proof.evaluations.eval_c;
e2c = Fr.add(e2c, Fr.mul(betaxi, zkey.k2));
e2c = Fr.add(e2c, challenges.gamma);
const e2 = Fr.mul(Fr.mul(Fr.mul(e2a, e2b), e2c), challenges.alpha);
let e3a = proof.evaluations.eval_a;
e3a = Fr.add(e3a, Fr.mul(challenges.beta, proof.evaluations.eval_s1));
e3a = Fr.add(e3a, challenges.gamma);
let e3b = proof.evaluations.eval_b;
e3b = Fr.add(e3b, Fr.mul(challenges.beta, proof.evaluations.eval_s2));
e3b = Fr.add(e3b, challenges.gamma);
let e3 = Fr.mul(e3a, e3b);
e3 = Fr.mul(e3, proof.evaluations.eval_zw);
e3 = Fr.mul(e3, challenges.alpha);
const e4 = Fr.mul(eval_l1, challenges.alpha2);
polynomials.R = new Polynomial(new BigBuffer((zkey.domainSize + 6) * n8r), curve, logger);
polynomials.R.add(polynomials.QM, coef_ab);
polynomials.R.add(polynomials.QL, proof.evaluations.eval_a);
polynomials.R.add(polynomials.QR, proof.evaluations.eval_b);
polynomials.R.add(polynomials.QO, proof.evaluations.eval_c);
polynomials.R.add(polynomials.QC);
polynomials.R.add(polynomials.Z, e2);
polynomials.R.sub(polynomials.Sigma3, Fr.mul(e3, challenges.beta));
polynomials.R.add(polynomials.Z, e4);
let tmp = Polynomial.fromPolynomial(polynomials.T3, curve, logger);
tmp.mulScalar(Fr.square(challenges.xin));
tmp.add(polynomials.T2, challenges.xin);
tmp.add(polynomials.T1);
tmp.mulScalar(challenges.zh);
polynomials.R.sub(tmp);
let r0 = Fr.sub(eval_pi, Fr.mul(e3, Fr.add(proof.evaluations.eval_c, challenges.gamma)));
r0 = Fr.sub(r0, e4);
if (logger) logger.debug("r0: " + Fr.toString(r0, 16));
polynomials.R.addScalar(r0);
}
function computeWxi() {
polynomials.Wxi = new Polynomial(new BigBuffer(sDomain + 6 * n8r), curve, logger);
polynomials.Wxi.add(polynomials.R);
polynomials.Wxi.add(polynomials.A, challenges.v[1]);
polynomials.Wxi.add(polynomials.B, challenges.v[2]);
polynomials.Wxi.add(polynomials.C, challenges.v[3]);
polynomials.Wxi.add(polynomials.Sigma1, challenges.v[4]);
polynomials.Wxi.add(polynomials.Sigma2, challenges.v[5]);
polynomials.Wxi.subScalar(Fr.mul(challenges.v[1], proof.evaluations.eval_a));
polynomials.Wxi.subScalar(Fr.mul(challenges.v[2], proof.evaluations.eval_b));
polynomials.Wxi.subScalar(Fr.mul(challenges.v[3], proof.evaluations.eval_c));
polynomials.Wxi.subScalar(Fr.mul(challenges.v[4], proof.evaluations.eval_s1));
polynomials.Wxi.subScalar(Fr.mul(challenges.v[5], proof.evaluations.eval_s2));
polynomials.Wxi.divByZerofier(1, challenges.xi);
}
async function computeWxiw() {
polynomials.Wxiw = Polynomial.fromPolynomial(polynomials.Z, curve, logger);
polynomials.Wxiw.subScalar(proof.evaluations.eval_zw);
polynomials.Wxiw.divByZerofier(1, challenges.xiw);
}
}