snarkjs
Version:
zkSNARKs implementation in JavaScript
1,118 lines (892 loc) • 56.5 kB
JavaScript
/*
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 * as binFileUtils from "@iden3/binfileutils";
import * as zkeyUtils from "./zkey_utils.js";
import * as wtnsUtils from "./wtns_utils.js";
import { BigBuffer, Scalar, utils } from "ffjavascript";
import { FFLONK_PROTOCOL_ID } from "./zkey_constants.js";
import {
ZKEY_FF_A_MAP_SECTION,
ZKEY_FF_ADDITIONS_SECTION,
ZKEY_FF_B_MAP_SECTION,
ZKEY_FF_C0_SECTION,
ZKEY_FF_C_MAP_SECTION,
ZKEY_FF_LAGRANGE_SECTION,
ZKEY_FF_PTAU_SECTION,
ZKEY_FF_QC_SECTION,
ZKEY_FF_QL_SECTION,
ZKEY_FF_QM_SECTION,
ZKEY_FF_QO_SECTION,
ZKEY_FF_QR_SECTION,
ZKEY_FF_SIGMA1_SECTION,
ZKEY_FF_SIGMA2_SECTION,
ZKEY_FF_SIGMA3_SECTION,
} from "./fflonk_constants.js";
import { Keccak256Transcript } from "./Keccak256Transcript.js";
import { Proof } from "./proof.js";
import { Polynomial } from "./polynomial/polynomial.js";
import { Evaluations } from "./polynomial/evaluations.js";
import { CPolynomial } from "./polynomial/cpolynomial.js";
const { stringifyBigInts } = utils;
export default async function fflonkProve(zkeyFileName, witnessFileName, logger, options) {
if (logger) logger.info("FFLONK PROVER STARTED");
// Read witness file
if (logger) logger.info("> Reading witness file");
const {
fd: fdWtns,
sections: wtnsSections
} = await binFileUtils.readBinFile(witnessFileName, "wtns", 2, 1 << 25, 1 << 23);
const wtns = await wtnsUtils.readHeader(fdWtns, wtnsSections);
//Read zkey file
if (logger) logger.info("> 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.protocolId !== FFLONK_PROTOCOL_ID) {
throw new Error("zkey file is not fflonk");
}
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 sFr = curve.Fr.n8;
const sG1 = curve.G1.F.n8 * 2;
const sDomain = zkey.domainSize * sFr;
if (logger) {
logger.info("----------------------------");
logger.info(" FFLONK PROVE SETTINGS");
logger.info(` Curve: ${curve.name}`);
logger.info(` Circuit power: ${zkey.power}`);
logger.info(` Domain size: ${zkey.domainSize}`);
logger.info(` Vars: ${zkey.nVars}`);
logger.info(` Public vars: ${zkey.nPublic}`);
logger.info(` Constraints: ${zkey.nConstraints}`);
logger.info(` Additions: ${zkey.nAdditions}`);
logger.info("----------------------------");
}
//Read witness data
if (logger) logger.info("> Reading witness file data");
const buffWitness = await binFileUtils.readSection(fdWtns, wtnsSections, 2);
await fdWtns.close();
// 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(zkey.nAdditions * sFr);
let buffers = {};
let polynomials = {};
let evaluations = {};
// To divide prime fields the Extended Euclidean Algorithm for computing modular inverses is needed.
// NOTE: This is the equivalent of compute 1/denominator and then multiply it by the numerator.
// The Extended Euclidean Algorithm is expensive in terms of computation.
// For the special case where we need to do many modular inverses, there's a simple mathematical trick
// that allows us to compute many inverses, called Montgomery batch inversion.
// More info: https://vitalik.ca/general/2018/07/21/starks_part_3.html
// Montgomery batch inversion reduces the n inverse computations to a single one
// To save this (single) inverse computation on-chain, will compute it in proving time and send it to the verifier.
// The verifier will have to check:
// 1) the denominator is correct multiplying by himself non-inverted -> a * 1/a == 1
// 2) compute the rest of the denominators using the Montgomery batch inversion
// The inversions are:
// · denominator needed in step 8 and 9 of the verifier to multiply by 1/Z_H(xi)
// · denominator needed in step 10 and 11 of the verifier
// · denominator needed in the verifier when computing L_i^{S1}(X) and L_i^{S2}(X)
// · L_i i=1 to num public inputs, needed in step 6 and 7 of the verifier to compute L_1(xi) and PI(xi)
let toInverse = {};
let challenges = {};
let roots = {};
let proof = new Proof(curve, logger);
if (logger) logger.info(`> Reading Section ${ZKEY_FF_ADDITIONS_SECTION}. Additions`);
await calculateAdditions();
if (logger) logger.info(`> Reading Sections ${ZKEY_FF_SIGMA1_SECTION},${ZKEY_FF_SIGMA2_SECTION},${ZKEY_FF_SIGMA3_SECTION}. Sigma1, Sigma2 & Sigma 3`);
if (logger) logger.info("··· 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_FF_SIGMA1_SECTION][0].p);
await fdZKey.readToBuffer(polynomials.Sigma2.coef, 0, sDomain, zkeySections[ZKEY_FF_SIGMA2_SECTION][0].p);
await fdZKey.readToBuffer(polynomials.Sigma3.coef, 0, sDomain, zkeySections[ZKEY_FF_SIGMA3_SECTION][0].p);
if (logger) logger.info("··· 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_FF_SIGMA1_SECTION][0].p + sDomain);
await fdZKey.readToBuffer(evaluations.Sigma2.eval, 0, sDomain * 4, zkeySections[ZKEY_FF_SIGMA2_SECTION][0].p + sDomain);
await fdZKey.readToBuffer(evaluations.Sigma3.eval, 0, sDomain * 4, zkeySections[ZKEY_FF_SIGMA3_SECTION][0].p + sDomain);
if (logger) logger.info(`> Reading Section ${ZKEY_FF_PTAU_SECTION}. Powers of Tau`);
const PTau = new BigBuffer(zkey.domainSize * 16 * sG1);
// domainSize * 9 + 18 = SRS length in the zkey saved in setup process.
// it corresponds to the maximum SRS length needed, specifically to commit C2
// notice that the reserved buffers size is zkey.domainSize * 16 * sG1 because a power of two buffer size is needed
// the remaining buffer not filled from SRS are set to 0
await fdZKey.readToBuffer(PTau, 0, (zkey.domainSize * 9 + 18) * sG1, zkeySections[ZKEY_FF_PTAU_SECTION][0].p);
// START FFLONK PROVER PROTOCOL
if (globalThis.gc) globalThis.gc();
// ROUND 1. Compute C1(X) polynomial
if (logger) logger.info("");
if (logger) logger.info("> ROUND 1");
await round1();
delete polynomials.T0;
delete evaluations.QL;
delete evaluations.QR;
delete evaluations.QM;
delete evaluations.QO;
delete evaluations.QC;
if (globalThis.gc) globalThis.gc();
// ROUND 2. Compute C2(X) polynomial
if (logger) logger.info("> ROUND 2");
await round2();
delete buffers.A;
delete buffers.B;
delete buffers.C;
delete evaluations.A;
delete evaluations.B;
delete evaluations.C;
delete evaluations.Sigma1;
delete evaluations.Sigma2;
delete evaluations.Sigma3;
delete evaluations.lagrange1;
delete evaluations.Z;
if (globalThis.gc) globalThis.gc();
// ROUND 3. Compute opening evaluations
if (logger) logger.info("> ROUND 3");
await round3();
delete polynomials.A;
delete polynomials.B;
delete polynomials.C;
delete polynomials.Z;
delete polynomials.T1;
delete polynomials.T2;
delete polynomials.Sigma1;
delete polynomials.Sigma2;
delete polynomials.Sigma3;
delete polynomials.QL;
delete polynomials.QR;
delete polynomials.QM;
delete polynomials.QC;
delete polynomials.QO;
if (globalThis.gc) globalThis.gc();
// ROUND 4. Compute W(X) polynomial
if (logger) logger.info("> ROUND 4");
await round4();
if (globalThis.gc) globalThis.gc();
// ROUND 5. Compute W'(X) polynomial
if (logger) logger.info("> ROUND 5");
await round5();
delete polynomials.C0;
delete polynomials.C1;
delete polynomials.C2;
delete polynomials.R1;
delete polynomials.R2;
delete polynomials.F;
delete polynomials.L;
delete polynomials.ZT;
delete polynomials.ZTS2;
await fdZKey.close();
if (globalThis.gc) globalThis.gc();
proof.addEvaluation("inv", getMontgomeryBatchedInverse());
// Prepare proof
let _proof = proof.toObjectProof();
_proof.protocol = "fflonk";
_proof.curve = curve.name;
// Prepare public inputs
let publicSignals = [];
for (let i = 1; i <= zkey.nPublic; i++) {
const i_sFr = i * sFr;
const pub = buffWitness.slice(i_sFr, i_sFr + sFr);
publicSignals.push(Scalar.fromRprLE(pub));
}
if (logger) logger.info("FFLONK PROVER FINISHED");
return {
proof: stringifyBigInts(_proof),
publicSignals: stringifyBigInts(publicSignals)
};
async function calculateAdditions() {
if (logger) logger.info("··· Computing additions");
const additionsBuff = await binFileUtils.readSection(fdZKey, zkeySections, ZKEY_FF_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 + sFr * 2;
for (let i = 0; i < zkey.nAdditions; i++) {
if (logger && (0 !== i) && (i % 100000 === 0)) logger.info(` 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 + sFr);
offset += sFr;
const factor2 = additionsBuff.slice(offset, offset + sFr);
// 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, sFr * 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) {
let diff = zkey.nVars - zkey.nAdditions;
if (idx < diff) {
return buffWitness.slice(idx * sFr, idx * sFr + sFr);
} else if (idx < zkey.nVars) {
const offset = (idx - diff) * sFr;
return buffInternalWitness.slice(offset, offset + sFr);
}
return Fr.zero;
}
async function round1() {
// STEP 1.1 - Generate random blinding scalars (b_1, ..., b9) ∈ F
challenges.b = [];
for (let i = 1; i <= 9; i++) {
challenges.b[i] = Fr.random();
}
// STEP 1.2 - Compute wire polynomials a(X), b(X) and c(X)
if (logger) logger.info("> Computing A, B, C wire polynomials");
await computeWirePolynomials();
// STEP 1.3 - Compute the quotient polynomial T0(X)
if (logger) logger.info("> Computing T0 polynomial");
await computeT0();
// STEP 1.4 - Compute the FFT-style combination polynomial C1(X)
if (logger) logger.info("> Computing C1 polynomial");
await computeC1();
// The first output of the prover is ([C1]_1)
if (logger) logger.info("> Computing C1 multi exponentiation");
let commitC1 = await polynomials.C1.multiExponentiation(PTau, "C1");
proof.addPolynomial("C1", commitC1);
return 0;
async function computeWirePolynomials() {
if (logger) logger.info("··· 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 sections and fill the buffers
const aMapBuff = await binFileUtils.readSection(fdZKey, zkeySections, ZKEY_FF_A_MAP_SECTION);
const bMapBuff = await binFileUtils.readSection(fdZKey, zkeySections, ZKEY_FF_B_MAP_SECTION);
const cMapBuff = await binFileUtils.readSection(fdZKey, zkeySections, ZKEY_FF_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 * sFr;
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);
}
// Blind a(X), b(X) and c(X) polynomials coefficients with blinding scalars b
buffers.A.set(challenges.b[1], sDomain - 64);
buffers.A.set(challenges.b[2], sDomain - 32);
buffers.B.set(challenges.b[3], sDomain - 64);
buffers.B.set(challenges.b[4], sDomain - 32);
buffers.C.set(challenges.b[5], sDomain - 64);
buffers.C.set(challenges.b[6], sDomain - 32);
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.info("··· Computing A ifft");
polynomials.A = await Polynomial.fromEvaluations(buffers.A, curve, logger);
if (logger) logger.info("··· Computing B ifft");
polynomials.B = await Polynomial.fromEvaluations(buffers.B, curve, logger);
if (logger) logger.info("··· 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.info("··· Computing A fft");
evaluations.A = await Evaluations.fromPolynomial(polynomials.A, 4, curve, logger);
if (logger) logger.info("··· Computing B fft");
evaluations.B = await Evaluations.fromPolynomial(polynomials.B, 4, curve, logger);
if (logger) logger.info("··· Computing C fft");
evaluations.C = await Evaluations.fromPolynomial(polynomials.C, 4, curve, logger);
// Check degrees
if (polynomials.A.degree() >= zkey.domainSize) {
throw new Error("A Polynomial is not well calculated");
}
if (polynomials.B.degree() >= zkey.domainSize) {
throw new Error("B Polynomial is not well calculated");
}
if (polynomials.C.degree() >= zkey.domainSize) {
throw new Error("C Polynomial is not well calculated");
}
}
async function computeT0() {
if (logger) logger.info(`··· Reading sections ${ZKEY_FF_QL_SECTION}, ${ZKEY_FF_QR_SECTION}` +
`, ${ZKEY_FF_QM_SECTION}, ${ZKEY_FF_QO_SECTION}, ${ZKEY_FF_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_FF_QL_SECTION][0].p + sDomain);
await fdZKey.readToBuffer(evaluations.QR.eval, 0, sDomain * 4, zkeySections[ZKEY_FF_QR_SECTION][0].p + sDomain);
await fdZKey.readToBuffer(evaluations.QM.eval, 0, sDomain * 4, zkeySections[ZKEY_FF_QM_SECTION][0].p + sDomain);
await fdZKey.readToBuffer(evaluations.QO.eval, 0, sDomain * 4, zkeySections[ZKEY_FF_QO_SECTION][0].p + sDomain);
await fdZKey.readToBuffer(evaluations.QC.eval, 0, sDomain * 4, zkeySections[ZKEY_FF_QC_SECTION][0].p + sDomain);
// Read Lagrange polynomials & evaluations from zkey file
const lagrangePolynomials = await binFileUtils.readSection(fdZKey, zkeySections, ZKEY_FF_LAGRANGE_SECTION);
evaluations.lagrange1 = new Evaluations(lagrangePolynomials, curve, logger);
// Reserve memory for buffers T0
buffers.T0 = new BigBuffer(sDomain * 4);
if (logger) logger.info("··· Computing T0 evaluations");
for (let i = 0; i < zkey.domainSize * 4; i++) {
if (logger && (0 !== i) && (i % 100000 === 0)) logger.info(` T0 evaluation ${i}/${zkey.domainSize * 4}`);
// Get related evaluations to compute current T0 evaluation
const a = evaluations.A.getEvaluation(i);
const b = evaluations.B.getEvaluation(i);
const c = evaluations.C.getEvaluation(i);
const ql = evaluations.QL.getEvaluation(i);
const qr = evaluations.QR.getEvaluation(i);
const qm = evaluations.QM.getEvaluation(i);
const qo = evaluations.QO.getEvaluation(i);
const qc = evaluations.QC.getEvaluation(i);
// Compute current public input
let pi = Fr.zero;
for (let j = 0; j < zkey.nPublic; j++) {
const offset = (j * 5 * zkey.domainSize) + zkey.domainSize + i;
const lPol = evaluations.lagrange1.getEvaluation(offset);
const aVal = buffers.A.slice(j * sFr, (j + 1) * sFr);
pi = Fr.sub(pi, Fr.mul(lPol, aVal));
}
//T0(X) = [q_L(X)·a(X) + q_R(X)·b(X) + q_M(X)·a(X)·b(X) + q_O(X)·c(X) + q_C(X) + PI(X)] · 1/Z_H(X)
// Compute first T0(X)·Z_H(X), so divide later the resulting polynomial by Z_H(X)
// expression 1 -> q_L(X)·a(X)
const e1 = Fr.mul(a, ql);
// expression 2 -> q_R(X)·b(X)
const e2 = Fr.mul(b, qr);
// expression 3 -> q_M(X)·a(X)·b(X)
const e3 = Fr.mul(Fr.mul(a, b), qm);
// expression 4 -> q_O(X)·c(X)
const e4 = Fr.mul(c, qo);
// t0 = expressions 1 + expression 2 + expression 3 + expression 4 + qc + pi
const t0 = Fr.add(e1, Fr.add(e2, Fr.add(e3, Fr.add(e4, Fr.add(qc, pi)))));
buffers.T0.set(t0, i * sFr);
}
if (logger) logger.info("buffer T0: " + buffers.T0.byteLength / sFr);
// Compute the coefficients of the polynomial T0(X) from buffers.T0
if (logger) logger.info("··· Computing T0 ifft");
polynomials.T0 = await Polynomial.fromEvaluations(buffers.T0, curve, logger);
if (logger) logger.info("T0 length: " + polynomials.T0.length());
if (logger) logger.info("T0 degree: " + polynomials.T0.degree());
// Divide the polynomial T0 by Z_H(X)
if (logger) logger.info("··· Computing T0 / ZH");
polynomials.T0.divByZerofier(zkey.domainSize, Fr.one);
// Check degree
if (polynomials.T0.degree() >= 2 * zkey.domainSize - 2) {
throw new Error(`T0 Polynomial is not well calculated (degree is ${polynomials.T0.degree()} and must be less than ${2 * zkey.domainSize + 2}`);
}
delete buffers.T0;
}
async function computeC1() {
let C1 = new CPolynomial(4, curve, logger);
C1.addPolynomial(0, polynomials.A);
C1.addPolynomial(1, polynomials.B);
C1.addPolynomial(2, polynomials.C);
C1.addPolynomial(3, polynomials.T0);
polynomials.C1 = C1.getPolynomial();
// Check degree
if (polynomials.C1.degree() >= 8 * zkey.domainSize - 8) {
throw new Error("C1 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.info("> Computing challenges beta and gamma");
const transcript = new Keccak256Transcript(curve);
// Add C0 to the transcript
transcript.addPolCommitment(zkey.C0);
// Add A to the transcript
for (let i = 0; i < zkey.nPublic; i++) {
transcript.addScalar(buffers.A.slice(i * sFr, i * sFr + sFr));
}
// Add C1 to the transcript
transcript.addPolCommitment(proof.getPolynomial("C1"));
challenges.beta = transcript.getChallenge();
if (logger) logger.info("··· challenges.beta: " + Fr.toString(challenges.beta));
// Compute permutation challenge gamma
transcript.reset();
transcript.addScalar(challenges.beta);
challenges.gamma = transcript.getChallenge();
if (logger) logger.info("··· challenges.gamma: " + Fr.toString(challenges.gamma));
// STEP 2.2 - Compute permutation polynomial z(X)
if (logger) logger.info("> Computing Z polynomial");
await computeZ();
// STEP 2.3 - Compute quotient polynomial T1(X) and T2(X)
if (logger) logger.info("> Computing T1 polynomial");
await computeT1();
if (logger) logger.info("> Computing T2 polynomial");
await computeT2();
// STEP 2.4 - Compute the FFT-style combination polynomial C2(X)
if (logger) logger.info("> Computing C2 polynomial");
await computeC2();
// The second output of the prover is ([C2]_1)
if (logger) logger.info("> Computing C2 multi exponentiation");
let commitC2 = await polynomials.C2.multiExponentiation(PTau, "C2");
proof.addPolynomial("C2", commitC2);
return 0;
async function computeZ() {
if (logger) logger.info("··· 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++) {
if (logger && (0 !== i) && (i % 100000 === 0)) logger.info(` Z evaluation ${i}/${zkey.domainSize}`);
const i_sFr = i * sFr;
// Z(X) := numArr / denArr
// numArr := (a + beta·ω + gamma)(b + beta·ω·k1 + gamma)(c + beta·ω·k2 + gamma)
const betaw = Fr.mul(challenges.beta, w);
let num1 = buffers.A.slice(i_sFr, i_sFr + sFr);
num1 = Fr.add(num1, betaw);
num1 = Fr.add(num1, challenges.gamma);
let num2 = buffers.B.slice(i_sFr, i_sFr + sFr);
num2 = Fr.add(num2, Fr.mul(zkey.k1, betaw));
num2 = Fr.add(num2, challenges.gamma);
let num3 = buffers.C.slice(i_sFr, i_sFr + sFr);
num3 = Fr.add(num3, Fr.mul(zkey.k2, betaw));
num3 = Fr.add(num3, challenges.gamma);
let num = Fr.mul(num1, Fr.mul(num2, num3));
// denArr := (a + beta·sigma1 + gamma)(b + beta·sigma2 + gamma)(c + beta·sigma3 + gamma)
let den1 = buffers.A.slice(i_sFr, i_sFr + sFr);
den1 = Fr.add(den1, Fr.mul(challenges.beta, evaluations.Sigma1.getEvaluation(i * 4)));
den1 = Fr.add(den1, challenges.gamma);
let den2 = buffers.B.slice(i_sFr, i_sFr + sFr);
den2 = Fr.add(den2, Fr.mul(challenges.beta, evaluations.Sigma2.getEvaluation(i * 4)));
den2 = Fr.add(den2, challenges.gamma);
let den3 = buffers.C.slice(i_sFr, i_sFr + sFr);
den3 = Fr.add(den3, Fr.mul(challenges.beta, evaluations.Sigma3.getEvaluation(i * 4)));
den3 = Fr.add(den3, challenges.gamma);
let den = Fr.mul(den1, Fr.mul(den2, den3));
// Multiply current num value with the previous one saved in numArr
num = Fr.mul(numArr.slice(i_sFr, i_sFr + sFr), num);
numArr.set(num, ((i + 1) % zkey.domainSize) * sFr);
// Multiply current den value with the previous one saved in denArr
den = Fr.mul(denArr.slice(i_sFr, i_sFr + sFr), den);
denArr.set(den, ((i + 1) % zkey.domainSize) * sFr);
// Next omega
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 * sFr;
const z = Fr.mul(numArr.slice(i_sFr, i_sFr + sFr), denArr.slice(i_sFr, i_sFr + sFr));
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, sFr), Fr.one)) {
throw new Error("Copy constraints does not match");
}
// Compute polynomial coefficients z(X) from buffers.Z
if (logger) logger.info("··· Computing Z ifft");
polynomials.Z = await Polynomial.fromEvaluations(buffers.Z, curve, logger);
// Compute extended evaluations of z(X) polynomial
if (logger) logger.info("··· 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 computeT1() {
if (logger) logger.info("··· Computing T1 evaluations");
buffers.T1 = new BigBuffer(sDomain * 2);
buffers.T1z = new BigBuffer(sDomain * 2);
// Set initial omega
let omega = Fr.one;
for (let i = 0; i < zkey.domainSize * 2; i++) {
if (logger && (0 !== i) && (i % 100000 === 0)) logger.info(` T1 evaluation ${i}/${zkey.domainSize * 4}`);
const omega2 = Fr.square(omega);
const z = evaluations.Z.getEvaluation(i * 2);
const zp = Fr.add(Fr.add(Fr.mul(challenges.b[7], omega2), Fr.mul(challenges.b[8], omega)), challenges.b[9]);
// T1(X) := (z(X) - 1) · L_1(X)
// Compute first T1(X)·Z_H(X), so divide later the resulting polynomial by Z_H(X)
const lagrange1 = evaluations.lagrange1.getEvaluation(zkey.domainSize + i * 2);
let t1 = Fr.mul(Fr.sub(z, Fr.one), lagrange1);
let t1z = Fr.mul(zp, lagrange1);
buffers.T1.set(t1, i * sFr);
buffers.T1z.set(t1z, i * sFr);
// Compute next omega
omega = Fr.mul(omega, Fr.w[zkey.power + 1]);
}
// Compute the coefficients of the polynomial T1(X) from buffers.T1
if (logger) logger.info("··· Computing T1 ifft");
polynomials.T1 = await Polynomial.fromEvaluations(buffers.T1, curve, logger);
// Divide the polynomial T1 by Z_H(X)
polynomials.T1.divByZerofier(zkey.domainSize, Fr.one);
// Compute the coefficients of the polynomial T1z(X) from buffers.T1z
if (logger) logger.info("··· Computing T1z ifft");
polynomials.T1z = await Polynomial.fromEvaluations(buffers.T1z, curve, logger);
// Add the polynomial T1z to T1 to get the final polynomial T1
polynomials.T1.add(polynomials.T1z);
// Check degree
if (polynomials.T1.degree() >= zkey.domainSize + 2) {
throw new Error("T1 Polynomial is not well calculated");
}
delete buffers.T1;
delete buffers.T1z;
delete polynomials.T1z;
}
async function computeT2() {
if (logger) logger.info("··· Computing T2 evaluations");
buffers.T2 = new BigBuffer(sDomain * 4);
buffers.T2z = new BigBuffer(sDomain * 4);
// Set initial omega
let omega = Fr.one;
for (let i = 0; i < zkey.domainSize * 4; i++) {
if (logger && (0 !== i) && (i % 100000 === 0)) logger.info(` T2 evaluation ${i}/${zkey.domainSize * 4}`);
const omega2 = Fr.square(omega);
const omegaW = Fr.mul(omega, Fr.w[zkey.power]);
const omegaW2 = Fr.square(omegaW);
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 zp = Fr.add(Fr.add(Fr.mul(challenges.b[7], omega2), Fr.mul(challenges.b[8], omega)), challenges.b[9]);
const zWp = Fr.add(Fr.add(Fr.mul(challenges.b[7], omegaW2), Fr.mul(challenges.b[8], omegaW)), challenges.b[9]);
const sigma1 = evaluations.Sigma1.getEvaluation(i);
const sigma2 = evaluations.Sigma2.getEvaluation(i);
const sigma3 = evaluations.Sigma3.getEvaluation(i);
// T2(X) := [ (a(X) + beta·X + gamma)(b(X) + beta·k1·X + gamma)(c(X) + beta·k2·X + gamma)z(X)
// -(a(X) + beta·sigma1(X) + gamma)(b(X) + beta·sigma2(X) + gamma)(c(X) + beta·sigma3(X) + gamma)z(Xω)] · 1/Z_H(X)
// Compute first T2(X)·Z_H(X), so divide later the resulting polynomial by Z_H(X)
// expression 1 -> (a(X) + beta·X + gamma)(b(X) + beta·k1·X + gamma)(c(X) + beta·k2·X + gamma)z(X)
const betaX = Fr.mul(challenges.beta, omega);
let e11 = Fr.add(a, betaX);
e11 = Fr.add(e11, challenges.gamma);
let e12 = Fr.add(b, Fr.mul(betaX, zkey.k1));
e12 = Fr.add(e12, challenges.gamma);
let e13 = Fr.add(c, Fr.mul(betaX, zkey.k2));
e13 = Fr.add(e13, challenges.gamma);
let e1 = Fr.mul(Fr.mul(Fr.mul(e11, e12), e13), z);
let e1z = Fr.mul(Fr.mul(Fr.mul(e11, e12), e13), zp);
// const [e1, e1z] = MulZ.mul4(e11, e12, e13, z, ap, bp, cp, zp, i % 4, Fr);
// expression 2 -> (a(X) + beta·sigma1(X) + gamma)(b(X) + beta·sigma2(X) + gamma)(c(X) + beta·sigma3(X) + gamma)z(Xω)
let e21 = Fr.add(a, Fr.mul(challenges.beta, sigma1));
e21 = Fr.add(e21, challenges.gamma);
let e22 = Fr.add(b, Fr.mul(challenges.beta, sigma2));
e22 = Fr.add(e22, challenges.gamma);
let e23 = Fr.add(c, Fr.mul(challenges.beta, sigma3));
e23 = Fr.add(e23, challenges.gamma);
let e2 = Fr.mul(Fr.mul(Fr.mul(e21, e22), e23), zW);
let e2z = Fr.mul(Fr.mul(Fr.mul(e21, e22), e23), zWp);
// const [e2, e2z] = MulZ.mul4(e21, e22, e23, zW, ap, bp, cp, zWp, i % 4, Fr);
let t2 = Fr.sub(e1, e2);
let t2z = Fr.sub(e1z, e2z);
buffers.T2.set(t2, i * sFr);
buffers.T2z.set(t2z, i * sFr);
// Compute next omega
omega = Fr.mul(omega, Fr.w[zkey.power + 2]);
}
// Compute the coefficients of the polynomial T2(X) from buffers.T2
if (logger) logger.info("··· Computing T2 ifft");
polynomials.T2 = await Polynomial.fromEvaluations(buffers.T2, curve, logger);
// Divide the polynomial T2 by Z_H(X)
if (logger) logger.info("··· Computing T2 / ZH");
polynomials.T2.divByZerofier(zkey.domainSize, Fr.one);
// Compute the coefficients of the polynomial T2z(X) from buffers.T2z
if (logger) logger.info("··· Computing T2z ifft");
polynomials.T2z = await Polynomial.fromEvaluations(buffers.T2z, curve, logger);
// Add the polynomial T2z to T2 to get the final polynomial T2
polynomials.T2.add(polynomials.T2z);
// Check degree
if (polynomials.T2.degree() >= 3 * zkey.domainSize) {
throw new Error("T2 Polynomial is not well calculated");
}
delete buffers.T2;
delete buffers.T2z;
delete polynomials.T2z;
}
async function computeC2() {
let C2 = new CPolynomial(3, curve, logger);
C2.addPolynomial(0, polynomials.Z);
C2.addPolynomial(1, polynomials.T1);
C2.addPolynomial(2, polynomials.T2);
polynomials.C2 = C2.getPolynomial();
// Check degree
if (polynomials.C2.degree() >= 9 * zkey.domainSize) {
throw new Error("C2 Polynomial is not well calculated");
}
}
}
async function round3() {
if (logger) logger.info("> Computing challenge xi");
// STEP 3.1 - Compute evaluation challenge xi ∈ S
const transcript = new Keccak256Transcript(curve);
transcript.addScalar(challenges.gamma);
transcript.addPolCommitment(proof.getPolynomial("C2"));
// Obtain a xi_seeder from the transcript
// To force h1^4 = xi, h2^3 = xi and h_3^2 = xiω
// we compute xi = xi_seeder^12, h1 = xi_seeder^3, h2 = xi_seeder^4 and h3 = xi_seeder^6
challenges.xiSeed = transcript.getChallenge();
const xiSeed2 = Fr.square(challenges.xiSeed);
// Compute omega8, omega4 and omega3
roots.w8 = [];
roots.w8[0] = Fr.one;
for (let i = 1; i < 8; i++) {
roots.w8[i] = Fr.mul(roots.w8[i - 1], zkey.w8);
}
roots.w4 = [];
roots.w4[0] = Fr.one;
for (let i = 1; i < 4; i++) {
roots.w4[i] = Fr.mul(roots.w4[i - 1], zkey.w4);
}
roots.w3 = [];
roots.w3[0] = Fr.one;
roots.w3[1] = zkey.w3;
roots.w3[2] = Fr.square(zkey.w3);
// Compute h0 = xiSeeder^3
roots.S0 = {};
roots.S0.h0w8 = [];
roots.S0.h0w8[0] = Fr.mul(xiSeed2, challenges.xiSeed);
for (let i = 1; i < 8; i++) {
roots.S0.h0w8[i] = Fr.mul(roots.S0.h0w8[0], roots.w8[i]);
}
// Compute h1 = xi_seeder^6
roots.S1 = {};
roots.S1.h1w4 = [];
roots.S1.h1w4[0] = Fr.square(roots.S0.h0w8[0]);
for (let i = 1; i < 4; i++) {
roots.S1.h1w4[i] = Fr.mul(roots.S1.h1w4[0], roots.w4[i]);
}
// Compute h2 = xi_seeder^8
roots.S2 = {};
roots.S2.h2w3 = [];
roots.S2.h2w3[0] = Fr.mul(roots.S1.h1w4[0], xiSeed2);
roots.S2.h2w3[1] = Fr.mul(roots.S2.h2w3[0], roots.w3[1]);
roots.S2.h2w3[2] = Fr.mul(roots.S2.h2w3[0], roots.w3[2]);
roots.S2.h3w3 = [];
// Multiply h3 by third-root-omega to obtain h_3^3 = xiω
// So, h3 = xi_seeder^8 ω^{1/3}
roots.S2.h3w3[0] = Fr.mul(roots.S2.h2w3[0], zkey.wr);
roots.S2.h3w3[1] = Fr.mul(roots.S2.h3w3[0], roots.w3[1]);
roots.S2.h3w3[2] = Fr.mul(roots.S2.h3w3[0], roots.w3[2]);
// Compute xi = xi_seeder^24
challenges.xi = Fr.mul(Fr.square(roots.S2.h2w3[0]), roots.S2.h2w3[0]);
if (logger) logger.info("··· challenges.xi: " + Fr.toString(challenges.xi));
// 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_FF_QL_SECTION][0].p);
await fdZKey.readToBuffer(polynomials.QR.coef, 0, sDomain, zkeySections[ZKEY_FF_QR_SECTION][0].p);
await fdZKey.readToBuffer(polynomials.QM.coef, 0, sDomain, zkeySections[ZKEY_FF_QM_SECTION][0].p);
await fdZKey.readToBuffer(polynomials.QO.coef, 0, sDomain, zkeySections[ZKEY_FF_QO_SECTION][0].p);
await fdZKey.readToBuffer(polynomials.QC.coef, 0, sDomain, zkeySections[ZKEY_FF_QC_SECTION][0].p);
// STEP 3.2 - Compute opening evaluations and add them to the proof (third output of the prover)
if (logger) logger.info("··· Computing evaluations");
proof.addEvaluation("ql", polynomials.QL.evaluate(challenges.xi));
proof.addEvaluation("qr", polynomials.QR.evaluate(challenges.xi));
proof.addEvaluation("qm", polynomials.QM.evaluate(challenges.xi));
proof.addEvaluation("qo", polynomials.QO.evaluate(challenges.xi));
proof.addEvaluation("qc", polynomials.QC.evaluate(challenges.xi));
proof.addEvaluation("s1", polynomials.Sigma1.evaluate(challenges.xi));
proof.addEvaluation("s2", polynomials.Sigma2.evaluate(challenges.xi));
proof.addEvaluation("s3", polynomials.Sigma3.evaluate(challenges.xi));
proof.addEvaluation("a", polynomials.A.evaluate(challenges.xi));
proof.addEvaluation("b", polynomials.B.evaluate(challenges.xi));
proof.addEvaluation("c", polynomials.C.evaluate(challenges.xi));
proof.addEvaluation("z", polynomials.Z.evaluate(challenges.xi));
challenges.xiw = Fr.mul(challenges.xi, Fr.w[zkey.power]);
proof.addEvaluation("zw", polynomials.Z.evaluate(challenges.xiw));
proof.addEvaluation("t1w", polynomials.T1.evaluate(challenges.xiw));
proof.addEvaluation("t2w", polynomials.T2.evaluate(challenges.xiw));
}
async function round4() {
if (logger) logger.info("> Computing challenge alpha");
// STEP 4.1 - Compute challenge alpha ∈ F
const transcript = new Keccak256Transcript(curve);
transcript.addScalar(challenges.xiSeed);
transcript.addScalar(proof.getEvaluation("ql"));
transcript.addScalar(proof.getEvaluation("qr"));
transcript.addScalar(proof.getEvaluation("qm"));
transcript.addScalar(proof.getEvaluation("qo"));
transcript.addScalar(proof.getEvaluation("qc"));
transcript.addScalar(proof.getEvaluation("s1"));
transcript.addScalar(proof.getEvaluation("s2"));
transcript.addScalar(proof.getEvaluation("s3"));
transcript.addScalar(proof.getEvaluation("a"));
transcript.addScalar(proof.getEvaluation("b"));
transcript.addScalar(proof.getEvaluation("c"));
transcript.addScalar(proof.getEvaluation("z"));
transcript.addScalar(proof.getEvaluation("zw"));
transcript.addScalar(proof.getEvaluation("t1w"));
transcript.addScalar(proof.getEvaluation("t2w"));
challenges.alpha = transcript.getChallenge();
if (logger) logger.info("··· challenges.alpha: " + Fr.toString(challenges.alpha));
// STEP 4.2 - Compute F(X)
if (logger) logger.info("> Reading C0 polynomial");
polynomials.C0 = new Polynomial(new BigBuffer(sDomain * 8), curve, logger);
await fdZKey.readToBuffer(polynomials.C0.coef, 0, sDomain * 8, zkeySections[ZKEY_FF_C0_SECTION][0].p);
if (logger) logger.info("> Computing R0 polynomial");
computeR0();
if (logger) logger.info("> Computing R1 polynomial");
computeR1();
if (logger) logger.info("> Computing R2 polynomial");
computeR2();
if (logger) logger.info("> Computing F polynomial");
await computeF();
// The fourth output of the prover is ([W1]_1), where W1:=(f/Z_t)(x)
if (logger) logger.info("> Computing W1 multi exponentiation");
let commitW1 = await polynomials.F.multiExponentiation(PTau, "W1");
proof.addPolynomial("W1", commitW1);
return 0;
function computeR0() {
// COMPUTE R0
// Compute the coefficients of R0(X) from 8 evaluations using lagrange interpolation. R0(X) ∈ F_{<8}[X]
// We decide to use Lagrange interpolations because the R0 degree is very small (deg(R0)===7),
// and we were not able to compute it using current ifft implementation because the omega are different
polynomials.R0 = Polynomial.lagrangePolynomialInterpolation(
[roots.S0.h0w8[0], roots.S0.h0w8[1], roots.S0.h0w8[2], roots.S0.h0w8[3],
roots.S0.h0w8[4], roots.S0.h0w8[5], roots.S0.h0w8[6], roots.S0.h0w8[7]],
[polynomials.C0.evaluate(roots.S0.h0w8[0]), polynomials.C0.evaluate(roots.S0.h0w8[1]),
polynomials.C0.evaluate(roots.S0.h0w8[2]), polynomials.C0.evaluate(roots.S0.h0w8[3]),
polynomials.C0.evaluate(roots.S0.h0w8[4]), polynomials.C0.evaluate(roots.S0.h0w8[5]),
polynomials.C0.evaluate(roots.S0.h0w8[6]), polynomials.C0.evaluate(roots.S0.h0w8[7])], curve);
// Check the degree of r0(X) < 8
if (polynomials.R0.degree() > 7) {
throw new Error("R0 Polynomial is not well calculated");
}
}
function computeR1() {
// COMPUTE R1
// Compute the coefficients of R1(X) from 4 evaluations using lagrange interpolation. R1(X) ∈ F_{<4}[X]
// We decide to use Lagrange interpolations because the R1 degree is very small (deg(R1)===3),
// and we were not able to compute it using current ifft implementation because the omega are different
polynomials.R1 = Polynomial.lagrangePolynomialInterpolation(
[roots.S1.h1w4[0], roots.S1.h1w4[1], roots.S1.h1w4[2], roots.S1.h1w4[3]],
[polynomials.C1.evaluate(roots.S1.h1w4[0]), polynomials.C1.evaluate(roots.S1.h1w4[1]),
polynomials.C1.evaluate(roots.S1.h1w4[2]), polynomials.C1.evaluate(roots.S1.h1w4[3])], curve);
// Check the degree of r1(X) < 4
if (polynomials.R1.degree() > 3) {
throw new Error("R1 Polynomial is not well calculated");
}
}
function computeR2() {
// COMPUTE R2
// Compute the coefficients of r2(X) from 6 evaluations using lagrange interpolation. r2(X) ∈ F_{<6}[X]
// We decide to use Lagrange interpolations because the R2.degree is very small (deg(R2)===5),
// and we were not able to compute it using current ifft implementation because the omega are different
polynomials.R2 = Polynomial.lagrangePolynomialInterpolation(
[roots.S2.h2w3[0], roots.S2.h2w3[1], roots.S2.h2w3[2],
roots.S2.h3w3[0], roots.S2.h3w3[1], roots.S2.h3w3[2]],
[polynomials.C2.evaluate(roots.S2.h2w3[0]), polynomials.C2.evaluate(roots.S2.h2w3[1]),
polynomials.C2.evaluate(roots.S2.h2w3[2]), polynomials.C2.evaluate(roots.S2.h3w3[0]),
polynomials.C2.evaluate(roots.S2.h3w3[1]), polynomials.C2.evaluate(roots.S2.h3w3[2])], curve);
// Check the degree of r2(X) < 6
if (polynomials.R2.degree() > 5) {
throw new Error("R2 Polynomial is not well calculated");
}
}
async function computeF() {
if (logger) logger.info("··· Computing F polynomial");
// COMPUTE F(X)
polynomials.F = Polynomial.fromPolynomial(polynomials.C0, curve, logger);
polynomials.F.sub(polynomials.R0);
polynomials.F.divByZerofier(8, challenges.xi);
let f2 = Polynomial.fromPolynomial(polynomials.C1, curve, logger);
f2.sub(polynomials.R1);
f2.mulScalar(challenges.alpha);
f2.divByZerofier(4, challenges.xi);
let f3 = Polynomial.fromPolynomial(polynomials.C2, curve, logger);
f3.sub(polynomials.R2);
f3.mulScalar(Fr.square(challenges.alpha));
f3.divByZerofier(3, challenges.xi);
f3.divByZerofier(3, challenges.xiw);
polynomials.F.add(f2);
polynomials.F.add(f3);
if (polynomials.F.degree() >= 9 * zkey.domainSize - 6) {
throw new Error("F Polynomial is not well calculated");
}
}
}
async function round5() {
if (logger) logger.info("> Computing challenge y");
// STEP 5.1 - Compute random evaluation point y ∈ F
const transcript = new Keccak256Transcript(curve);
transcript.addScalar(challenges.alpha);
transcript.addPolCommitment(proof.getPolynomial("W1"));
challenges.y = transcript.getChallenge();
if (logger) logger.info("··· challenges.y: " + Fr.toString(challenges.y));
// STEP 5.2 - Compute L(X)
if (logger) logger.info("> Computing L polynomial");
await computeL();
if (logger) logger.info("> Computing ZTS2 polynomial");
await computeZTS2();
let ZTS2Y = polynomials.ZTS2.evaluate(challenges.y);
ZTS2Y = Fr.inv(ZTS2Y);
polynomials.L.mulScalar(ZTS2Y);
const polDividend = Polynomial.fromCoefficientsArray([Fr.neg(challenges.y), Fr.one], curve);
if (logger) logger.info("> Computing W' = L / ZTS2 polynomial");
const polRemainder = polynomials.L.divBy(polDividend);
//Check polReminder degree is equal to zero
if (polRemainder.degree() > 0) {
throw new Error(`Degree of L(X)/(ZTS2(y)(X-y)) remainder is ${polRemainder.degree()} and should be 0`);
}
if (polynomials.L.degree() >= 9 * zkey.domainSize - 1) {
throw new Error("Degree of L(X)/(ZTS2(y)(X-y)) is not correct");
}
// The fifth output of the prover is ([W2]_1), where W2:=(f/Z_t)(x)
if (logger) logger.info("> Computing W' multi exponentiation");
let commitW2 = await polynomials.L.multiExponentiation(PTau, "W2");
proof.addPolynomial("W2", commitW2);
return 0;
async function computeL() {
if (logger) logger.info("··· Computing L polynomial");
const evalR0Y = polynomials.R0.evaluate(challenges.y);
const evalR1Y = polynomials.R1.evaluate(challenges.y);
const evalR2Y = polynomials.R2.evaluate(challenges.y);
let mulL0 = Fr.sub(challenges.y, roots.S0.h0w8[0]);
for (let i = 1; i < 8; i++) {
mulL0 = Fr.mul(mulL0, Fr.sub(challenges.y, roots.S0.h0w8[i]));
}
let mulL1 = Fr.sub(challenges.y, roots.S1.h1w4[0]);
for (let i = 1; i < 4; i++) {
mulL1 = Fr.mul(mulL1, Fr.sub(challenges.y, roots.S1.h1w4[i]));
}
let mulL2 = Fr.sub(challenges.y,