snarkjs
Version:
zkSNARKs implementation in JavaScript
385 lines (302 loc) • 12.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 */
import * as curves from "./curves.js";
import { utils } from "ffjavascript";
const {unstringifyBigInts} = utils;
import { Keccak256Transcript } from "./Keccak256Transcript.js";
export default async function plonkVerify(_vk_verifier, _publicSignals, _proof, logger) {
let vk_verifier = unstringifyBigInts(_vk_verifier);
_proof = unstringifyBigInts(_proof);
let publicSignals = unstringifyBigInts(_publicSignals);
const curve = await curves.getCurveFromName(vk_verifier.curve);
const Fr = curve.Fr;
const G1 = curve.G1;
if (logger) logger.info("PLONK VERIFIER STARTED");
let proof = fromObjectProof(curve,_proof);
vk_verifier = fromObjectVk(curve, vk_verifier);
if (!isWellConstructed(curve, proof)) {
logger.error("Proof is not well constructed");
return false;
}
if (publicSignals.length != vk_verifier.nPublic) {
logger.error("Invalid number of public inputs");
return false;
}
const challenges = calculatechallenges(curve, proof, publicSignals, vk_verifier);
if (logger) {
logger.debug("beta: " + Fr.toString(challenges.beta, 16));
logger.debug("gamma: " + Fr.toString(challenges.gamma, 16));
logger.debug("alpha: " + Fr.toString(challenges.alpha, 16));
logger.debug("xi: " + Fr.toString(challenges.xi, 16));
for(let i=1;i<6;i++) {
if (logger) logger.debug("v: " + Fr.toString(challenges.v[i], 16));
}
logger.debug("u: " + Fr.toString(challenges.u, 16));
}
const L = calculateLagrangeEvaluations(curve, challenges, vk_verifier);
if (logger) {
for (let i=1; i<L.length; i++) {
logger.debug(`L${i}(xi)=` + Fr.toString(L[i], 16));
}
}
if (publicSignals.length != vk_verifier.nPublic) {
logger.error("Number of public signals does not match with vk");
return false;
}
const pi = calculatePI(curve, publicSignals, L);
if (logger) {
logger.debug("PI(xi): " + Fr.toString(pi, 16));
}
const r0 = calculateR0(curve, proof, challenges, pi, L[1]);
if (logger) {
logger.debug("r0: " + Fr.toString(r0, 16));
}
const D = calculateD(curve, proof, challenges, vk_verifier, L[1]);
if (logger) {
logger.debug("D: " + G1.toString(G1.toAffine(D), 16));
}
const F = calculateF(curve, proof, challenges, vk_verifier, D);
if (logger) {
logger.debug("F: " + G1.toString(G1.toAffine(F), 16));
}
const E = calculateE(curve, proof, challenges, r0);
if (logger) {
logger.debug("E: " + G1.toString(G1.toAffine(E), 16));
}
const res = await isValidPairing(curve, proof, challenges, vk_verifier, E, F);
if (logger) {
if (res) {
logger.info("OK!");
} else {
logger.warn("Invalid Proof");
}
}
return res;
}
function fromObjectProof(curve, proof) {
const G1 = curve.G1;
const Fr = curve.Fr;
const res = {};
res.A = G1.fromObject(proof.A);
res.B = G1.fromObject(proof.B);
res.C = G1.fromObject(proof.C);
res.Z = G1.fromObject(proof.Z);
res.T1 = G1.fromObject(proof.T1);
res.T2 = G1.fromObject(proof.T2);
res.T3 = G1.fromObject(proof.T3);
res.eval_a = Fr.fromObject(proof.eval_a);
res.eval_b = Fr.fromObject(proof.eval_b);
res.eval_c = Fr.fromObject(proof.eval_c);
res.eval_zw = Fr.fromObject(proof.eval_zw);
res.eval_s1 = Fr.fromObject(proof.eval_s1);
res.eval_s2 = Fr.fromObject(proof.eval_s2);
res.Wxi = G1.fromObject(proof.Wxi);
res.Wxiw = G1.fromObject(proof.Wxiw);
return res;
}
function fromObjectVk(curve, vk) {
const G1 = curve.G1;
const G2 = curve.G2;
const Fr = curve.Fr;
const res = vk;
res.Qm = G1.fromObject(vk.Qm);
res.Ql = G1.fromObject(vk.Ql);
res.Qr = G1.fromObject(vk.Qr);
res.Qo = G1.fromObject(vk.Qo);
res.Qc = G1.fromObject(vk.Qc);
res.S1 = G1.fromObject(vk.S1);
res.S2 = G1.fromObject(vk.S2);
res.S3 = G1.fromObject(vk.S3);
res.k1 = Fr.fromObject(vk.k1);
res.k2 = Fr.fromObject(vk.k2);
res.X_2 = G2.fromObject(vk.X_2);
return res;
}
function isWellConstructed(curve, proof) {
const G1 = curve.G1;
if (!G1.isValid(proof.A)) return false;
if (!G1.isValid(proof.B)) return false;
if (!G1.isValid(proof.C)) return false;
if (!G1.isValid(proof.Z)) return false;
if (!G1.isValid(proof.T1)) return false;
if (!G1.isValid(proof.T2)) return false;
if (!G1.isValid(proof.T3)) return false;
if (!G1.isValid(proof.Wxi)) return false;
if (!G1.isValid(proof.Wxiw)) return false;
return true;
}
function calculatechallenges(curve, proof, publicSignals, vk) {
const Fr = curve.Fr;
const res = {};
const transcript = new Keccak256Transcript(curve);
// Challenge round 2: beta and gamma
transcript.addPolCommitment(vk.Qm);
transcript.addPolCommitment(vk.Ql);
transcript.addPolCommitment(vk.Qr);
transcript.addPolCommitment(vk.Qo);
transcript.addPolCommitment(vk.Qc);
transcript.addPolCommitment(vk.S1);
transcript.addPolCommitment(vk.S2);
transcript.addPolCommitment(vk.S3);
for (let i = 0; i < publicSignals.length; i++) {
transcript.addScalar(Fr.e(publicSignals[i]));
}
transcript.addPolCommitment(proof.A);
transcript.addPolCommitment(proof.B);
transcript.addPolCommitment(proof.C);
res.beta = transcript.getChallenge();
transcript.reset();
transcript.addScalar(res.beta);
res.gamma = transcript.getChallenge();
// Challenge round 3: alpha
transcript.reset();
transcript.addScalar(res.beta);
transcript.addScalar(res.gamma);
transcript.addPolCommitment(proof.Z);
res.alpha = transcript.getChallenge();
// Challenge round 4: xi
transcript.reset();
transcript.addScalar(res.alpha);
transcript.addPolCommitment(proof.T1);
transcript.addPolCommitment(proof.T2);
transcript.addPolCommitment(proof.T3);
res.xi = transcript.getChallenge();
// Challenge round 5: v
transcript.reset();
transcript.addScalar(res.xi);
transcript.addScalar(proof.eval_a);
transcript.addScalar(proof.eval_b);
transcript.addScalar(proof.eval_c);
transcript.addScalar(proof.eval_s1);
transcript.addScalar(proof.eval_s2);
transcript.addScalar(proof.eval_zw);
res.v = [];
res.v[1] = transcript.getChallenge();
for (let i=2; i<6; i++ ) res.v[i] = Fr.mul(res.v[i-1], res.v[1]);
// Challenge: u
transcript.reset();
transcript.addPolCommitment(proof.Wxi);
transcript.addPolCommitment(proof.Wxiw);
res.u = transcript.getChallenge();
return res;
}
function calculateLagrangeEvaluations(curve, challenges, vk) {
const Fr = curve.Fr;
let xin = challenges.xi;
let domainSize = 1;
for (let i=0; i<vk.power; i++) {
xin = Fr.square(xin);
domainSize *= 2;
}
challenges.xin = xin;
challenges.zh = Fr.sub(xin, Fr.one);
const L = [];
const n = Fr.e(domainSize);
let w = Fr.one;
for (let i=1; i<=Math.max(1, vk.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[vk.power]);
}
return L;
}
function calculatePI(curve, publicSignals, L) {
const Fr = curve.Fr;
let pi = Fr.zero;
for (let i=0; i<publicSignals.length; i++) {
const w = Fr.e(publicSignals[i]);
pi = Fr.sub(pi, Fr.mul(w, L[i+1]));
}
return pi;
}
function calculateR0(curve, proof, challenges, pi, l1) {
const Fr = curve.Fr;
const e1 = pi;
const e2 = Fr.mul(l1, Fr.square(challenges.alpha));
let e3a = Fr.add(proof.eval_a, Fr.mul(challenges.beta, proof.eval_s1));
e3a = Fr.add(e3a, challenges.gamma);
let e3b = Fr.add(proof.eval_b, Fr.mul(challenges.beta, proof.eval_s2));
e3b = Fr.add(e3b, challenges.gamma);
let e3c = Fr.add(proof.eval_c, challenges.gamma);
let e3 = Fr.mul(Fr.mul(e3a, e3b), e3c);
e3 = Fr.mul(e3, proof.eval_zw);
e3 = Fr.mul(e3, challenges.alpha);
const r0 = Fr.sub(Fr.sub(e1, e2), e3);
return r0;
}
function calculateD(curve, proof, challenges, vk, l1) {
const G1 = curve.G1;
const Fr = curve.Fr;
let d1 = G1.timesFr(vk.Qm, Fr.mul(proof.eval_a, proof.eval_b));
d1 = G1.add(d1, G1.timesFr(vk.Ql, proof.eval_a));
d1 = G1.add(d1, G1.timesFr(vk.Qr, proof.eval_b));
d1 = G1.add(d1, G1.timesFr(vk.Qo, proof.eval_c));
d1 = G1.add(d1, vk.Qc);
const betaxi = Fr.mul(challenges.beta, challenges.xi);
const d2a1 = Fr.add(Fr.add(proof.eval_a, betaxi), challenges.gamma);
const d2a2 = Fr.add(Fr.add(proof.eval_b, Fr.mul(betaxi, vk.k1)), challenges.gamma);
const d2a3 = Fr.add(Fr.add(proof.eval_c, Fr.mul(betaxi, vk.k2)), challenges.gamma);
const d2a = Fr.mul(Fr.mul(Fr.mul(d2a1, d2a2), d2a3), challenges.alpha);
const d2b = Fr.mul(l1, Fr.square(challenges.alpha));
const d2 = G1.timesFr(proof.Z, Fr.add(Fr.add(d2a, d2b), challenges.u));
const d3a = Fr.add(Fr.add(proof.eval_a, Fr.mul(challenges.beta, proof.eval_s1)), challenges.gamma);
const d3b = Fr.add(Fr.add(proof.eval_b, Fr.mul(challenges.beta, proof.eval_s2)), challenges.gamma);
const d3c = Fr.mul(Fr.mul(challenges.alpha, challenges.beta), proof.eval_zw);
const d3 = G1.timesFr(vk.S3, Fr.mul(Fr.mul(d3a, d3b), d3c));
const d4low = proof.T1;
const d4mid = G1.timesFr(proof.T2, challenges.xin);
const d4high = G1.timesFr(proof.T3, Fr.square(challenges.xin));
let d4 = G1.add(d4low, G1.add(d4mid, d4high));
d4 = G1.timesFr(d4, challenges.zh);
const d = G1.sub(G1.sub(G1.add(d1, d2), d3), d4);
return d;
}
function calculateF(curve, proof, challenges, vk, D) {
const G1 = curve.G1;
let res = G1.add(D, G1.timesFr(proof.A, challenges.v[1]));
res = G1.add(res, G1.timesFr(proof.B, challenges.v[2]));
res = G1.add(res, G1.timesFr(proof.C, challenges.v[3]));
res = G1.add(res, G1.timesFr(vk.S1, challenges.v[4]));
res = G1.add(res, G1.timesFr(vk.S2, challenges.v[5]));
return res;
}
function calculateE(curve, proof, challenges, r0) {
const G1 = curve.G1;
const Fr = curve.Fr;
let e = Fr.add(Fr.neg(r0), Fr.mul(challenges.v[1], proof.eval_a));
e = Fr.add(e, Fr.mul(challenges.v[2], proof.eval_b));
e = Fr.add(e, Fr.mul(challenges.v[3], proof.eval_c));
e = Fr.add(e, Fr.mul(challenges.v[4], proof.eval_s1));
e = Fr.add(e, Fr.mul(challenges.v[5], proof.eval_s2));
e = Fr.add(e, Fr.mul(challenges.u, proof.eval_zw));
const res = G1.timesFr(G1.one, e);
return res;
}
async function isValidPairing(curve, proof, challenges, vk, E, F) {
const G1 = curve.G1;
const Fr = curve.Fr;
let A1 = proof.Wxi;
A1 = G1.add(A1, G1.timesFr(proof.Wxiw, challenges.u));
let B1 = G1.timesFr(proof.Wxi, challenges.xi);
const s = Fr.mul(Fr.mul(challenges.u, challenges.xi), Fr.w[vk.power]);
B1 = G1.add(B1, G1.timesFr(proof.Wxiw, s));
B1 = G1.add(B1, F);
B1 = G1.sub(B1, E);
const res = await curve.pairingEq(
G1.neg(A1) , vk.X_2,
B1 , curve.G2.one
);
return res;
}