Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ const commands = [
cmd: "zkey export verificationkey [circuit_final.zkey] [verification_key.json]",
description: "Exports a verification key",
alias: ["zkev"],
options: "-bls12381-solidity",
action: zkeyExportVKey
},
{
Expand Down Expand Up @@ -588,7 +589,14 @@ async function zkeyExportVKey(params, options) {

if (options.verbose) Logger.setLogLevel("DEBUG");

const vKey = await zkey.exportVerificationKey(zKeyFileName, logger);
let vKey = await zkey.exportVerificationKey(zKeyFileName, logger);

if (options["bls12381-solidity"] &&
vKey.protocol === "groth16" &&
vKey.curve === "bls12381") {

vKey = await zkey.processVerificationKeyForSolidity(vKey);
}

await bfj.write(vKeyFilename, stringifyBigInts(vKey), {space: 1});

Expand Down Expand Up @@ -638,10 +646,12 @@ async function zkeyExportSolidityVerifier(params, options) {
templates.groth16 = await fs.promises.readFile(path.join(__dirname, "templates", "verifier_groth16.sol.ejs"), "utf8");
templates.plonk = await fs.promises.readFile(path.join(__dirname, "templates", "verifier_plonk.sol.ejs"), "utf8");
templates.fflonk = await fs.promises.readFile(path.join(__dirname, "templates", "verifier_fflonk.sol.ejs"), "utf8");
templates["groth16-bls12381"] = await fs.promises.readFile(path.join(__dirname, "templates", "verifier_groth16_bls12381.sol.ejs"), "utf8");
} else {
templates.groth16 = await fs.promises.readFile(path.join(__dirname, "..", "templates", "verifier_groth16.sol.ejs"), "utf8");
templates.plonk = await fs.promises.readFile(path.join(__dirname, "..", "templates", "verifier_plonk.sol.ejs"), "utf8");
templates.fflonk = await fs.promises.readFile(path.join(__dirname, "..", "templates", "verifier_fflonk.sol.ejs"), "utf8");
templates["groth16-bls12381"] = await fs.promises.readFile(path.join(__dirname, "..", "templates", "verifier_groth16_bls12381.sol.ejs"), "utf8");
}

const verifierCode = await zkey.exportSolidityVerifier(zkeyName, templates, logger);
Expand Down Expand Up @@ -676,7 +686,11 @@ async function zkeyExportSolidityCalldata(params, options) {

let res;
if (proof.protocol == "groth16") {
res = await groth16.exportSolidityCallData(proof, pub);
if (proof.curve === "bls12381") {
res = await groth16.exportSolidityCallData(proof, pub, "bls12381");
} else {
res = await groth16.exportSolidityCallData(proof, pub);
}
} else if (proof.protocol == "plonk") {
res = await plonk.exportSolidityCallData(proof, pub);
} else if (proof.protocol === "fflonk") {
Expand Down
64 changes: 49 additions & 15 deletions src/groth16_exportsoliditycalldata.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,55 @@ function p256(n) {
return nstr;
}

export default async function groth16ExportSolidityCallData(_proof, _pub) {
const proof = unstringifyBigInts(_proof);
const pub = unstringifyBigInts(_pub);

let inputs = "";
for (let i=0; i<pub.length; i++) {
if (inputs != "") inputs = inputs + ",";
inputs = inputs + p256(pub[i]);
}
function p256NoZeroX(n) {
let nstr = n.toString(16);
while (nstr.length < 64) nstr = "0"+nstr;
return nstr;
}

let S;
S=`[${p256(proof.pi_a[0])}, ${p256(proof.pi_a[1])}],` +
`[[${p256(proof.pi_b[0][1])}, ${p256(proof.pi_b[0][0])}],[${p256(proof.pi_b[1][1])}, ${p256(proof.pi_b[1][0])}]],` +
`[${p256(proof.pi_c[0])}, ${p256(proof.pi_c[1])}],` +
`[${inputs}]`;
function p512NoZeroX(n) {
let nstr = n.toString(16);
while (nstr.length < 128) nstr = "0"+nstr;
return nstr;
}

return S;
export default async function groth16ExportSolidityCallData(_proof, _pub, _curve="bn254") {
if (_curve === "bn254") {
const proof = unstringifyBigInts(_proof);
const pub = unstringifyBigInts(_pub);

let inputs = "";
for (let i=0; i<pub.length; i++) {
if (inputs != "") inputs = inputs + ",";
inputs = inputs + p256(pub[i]);
}

let S;
S=`[${p256(proof.pi_a[0])}, ${p256(proof.pi_a[1])}],` +
`[[${p256(proof.pi_b[0][1])}, ${p256(proof.pi_b[0][0])}],[${p256(proof.pi_b[1][1])}, ${p256(proof.pi_b[1][0])}]],` +
`[${p256(proof.pi_c[0])}, ${p256(proof.pi_c[1])}],` +
`[${inputs}]`;

return S;
} else if (_curve === "bls12381") {
const proof = unstringifyBigInts(_proof);
const pub = unstringifyBigInts(_pub);

let inputs = "";
for (let i=0; i<pub.length; i++) {
inputs = inputs + p256NoZeroX(pub[i]);
}

const calldata = p512NoZeroX(proof.pi_a[0]) +
p512NoZeroX(proof.pi_a[1]) +
p512NoZeroX(proof.pi_b[0][0]) +
p512NoZeroX(proof.pi_b[0][1]) +
p512NoZeroX(proof.pi_b[1][0]) +
p512NoZeroX(proof.pi_b[1][1]) +
p512NoZeroX(proof.pi_c[0]) +
p512NoZeroX(proof.pi_c[1]) +
inputs

return calldata;
}
}
1 change: 1 addition & 0 deletions src/zkey.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ export {default as exportJson} from "./zkey_export_json.js";
export {default as bellmanContribute} from "./zkey_bellman_contribute.js";
export {default as exportVerificationKey} from "./zkey_export_verificationkey.js";
export {default as exportSolidityVerifier} from "./zkey_export_solidityverifier.js";
export {default as processVerificationKeyForSolidity} from "./zkey_process_verificationkey_for_solidity.js";
13 changes: 10 additions & 3 deletions src/zkey_export_solidityverifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,25 @@ import ejs from "ejs";

import exportVerificationKey from "./zkey_export_verificationkey.js";
import fflonkExportSolidityVerifierCmd from "./fflonk_export_solidity_verifier.js";
import processVerificationKeyForSolidity from "./zkey_process_verificationkey_for_solidity.js";
// Not ready yet
// module.exports.generateVerifier_kimleeoh = generateVerifier_kimleeoh;

export default async function exportSolidityVerifier(zKeyName, templates, logger) {

const verificationKey = await exportVerificationKey(zKeyName, logger);
let verificationKey = await exportVerificationKey(zKeyName, logger);
let protocol = verificationKey.protocol;

if ("fflonk" === verificationKey.protocol) {
if ("fflonk" === protocol) {
return fflonkExportSolidityVerifierCmd(verificationKey, templates, logger);
}

let template = templates[verificationKey.protocol];
if (protocol === "groth16" && verificationKey.curve === "bls12381") {
protocol = "groth16-bls12381";
verificationKey = {vk: await processVerificationKeyForSolidity(verificationKey)};
}

let template = templates[protocol];

return ejs.render(template, verificationKey);
}
37 changes: 37 additions & 0 deletions src/zkey_process_verificationkey_for_solidity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
function processFp(fp) {
const hexStr = BigInt(fp).toString(16).padStart(128, '0');
const part1 = '0x' + hexStr.slice(0, 64);
const part2 = '0x' + hexStr.slice(64);
return [part1, part2];
}

export default async function processVerificationKeyForSolidity(vKey) {
const alpha = [...processFp(vKey.vk_alpha_1[0]), ...processFp(vKey.vk_alpha_1[1])];

const beta = [
[...processFp(vKey.vk_beta_2[0][0]), ...processFp(vKey.vk_beta_2[0][1])],
[...processFp(vKey.vk_beta_2[1][0]), ...processFp(vKey.vk_beta_2[1][1])]
];

const gamma = [
[...processFp(vKey.vk_gamma_2[0][0]), ...processFp(vKey.vk_gamma_2[0][1])],
[...processFp(vKey.vk_gamma_2[1][0]), ...processFp(vKey.vk_gamma_2[1][1])]
];

const delta = [
[...processFp(vKey.vk_delta_2[0][0]), ...processFp(vKey.vk_delta_2[0][1])],
[...processFp(vKey.vk_delta_2[1][0]), ...processFp(vKey.vk_delta_2[1][1])]
];

const ic = vKey.IC.map(x => [...processFp(x[0]), ...processFp(x[1])]);

const result = {
alpha,
beta,
gamma,
delta,
ic
};

return result;
}
180 changes: 180 additions & 0 deletions templates/verifier_groth16_bls12381.sol.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// SPDX-License-Identifier: GPL-3.0
// Author: Rubydusa 2025
/*
This file is generated with [snarkJS](https://github.com/iden3/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/>.
*/

pragma solidity >=0.7.0 <0.9.0;

contract Groth16Verifier {
// precompiles
uint256 constant G1_ADD_PRECOMPILE = 11;
uint256 constant G1_MSM_PRECOMPILE = 12;
uint256 constant PAIRING_PRECOMPILE = 15;
// Main subgroup order
uint256 constant q = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001;

// Verification Key data
bytes32 constant alphax_0 = <%= vk.alpha[0] %>;
bytes32 constant alphax_1 = <%= vk.alpha[1] %>;
bytes32 constant alphay_0 = <%= vk.alpha[2] %>;
bytes32 constant alphay_1 = <%= vk.alpha[3] %>;

bytes32 constant betax1_0 = <%= vk.beta[0][0] %>;
bytes32 constant betax1_1 = <%= vk.beta[0][1] %>;
bytes32 constant betay1_0 = <%= vk.beta[0][2] %>;
bytes32 constant betay1_1 = <%= vk.beta[0][3] %>;
bytes32 constant betax2_0 = <%= vk.beta[1][0] %>;
bytes32 constant betax2_1 = <%= vk.beta[1][1] %>;
bytes32 constant betay2_0 = <%= vk.beta[1][2] %>;
bytes32 constant betay2_1 = <%= vk.beta[1][3] %>;

bytes32 constant gammax1_0 = <%= vk.gamma[0][0] %>;
bytes32 constant gammax1_1 = <%= vk.gamma[0][1] %>;
bytes32 constant gammay1_0 = <%= vk.gamma[0][2] %>;
bytes32 constant gammay1_1 = <%= vk.gamma[0][3] %>;
bytes32 constant gammax2_0 = <%= vk.gamma[1][0] %>;
bytes32 constant gammax2_1 = <%= vk.gamma[1][1] %>;
bytes32 constant gammay2_0 = <%= vk.gamma[1][2] %>;
bytes32 constant gammay2_1 = <%= vk.gamma[1][3] %>;

bytes32 constant deltax1_0 = <%= vk.delta[0][0] %>;
bytes32 constant deltax1_1 = <%= vk.delta[0][1] %>;
bytes32 constant deltay1_0 = <%= vk.delta[0][2] %>;
bytes32 constant deltay1_1 = <%= vk.delta[0][3] %>;
bytes32 constant deltax2_0 = <%= vk.delta[1][0] %>;
bytes32 constant deltax2_1 = <%= vk.delta[1][1] %>;
bytes32 constant deltay2_0 = <%= vk.delta[1][2] %>;
bytes32 constant deltay2_1 = <%= vk.delta[1][3] %>;

uint256 constant nPublic = <%= vk.ic.length - 1 %>;
uint256 constant proofLength = <%= 512 + 32 * (vk.ic.length - 1) %>;
<% for (let i = 0; i < vk.ic.length; i++) { %>
bytes32 constant IC<%= i %>x_0 = <%= vk.ic[i][0] %>;
bytes32 constant IC<%= i %>x_1 = <%= vk.ic[i][1] %>;
bytes32 constant IC<%= i %>y_0 = <%= vk.ic[i][2] %>;
bytes32 constant IC<%= i %>y_1 = <%= vk.ic[i][3] %>;
<% } %>

bytes4 constant ERROR_NOT_IN_FIELD = 0x81452424; // cast sig "NotInField(uint256)"
bytes4 constant ERROR_INVALID_PROOF_LENGTH = 0x4dc5f6a4; // cast sig "InvalidProofLength()"
bytes4 constant ERROR_G1_MSM_FAILED = 0x5f776986; // cast sig "G1MSMFailed()"
bytes4 constant ERROR_G1_ADD_FAILED = 0xd6cc76eb; // cast sig "G1AddFailed()"
bytes4 constant ERROR_PAIRING_FAILED = 0x4df45e2f; // cast sig "PairingFailed()"

// proof structure:
// negA: 128 bytes
// B: 256 bytes
// C: 128 bytes
// pubSignals: 32 * nPublic bytes
function verifyProof(bytes calldata proof) public view returns (bool){
assembly {
function checkField(v) {
if iszero(lt(v, q)) {
mstore(0, ERROR_NOT_IN_FIELD)
mstore(4, v)
revert(0, 0x24)
}
}

// check proof length
if iszero(eq(proof.length, proofLength)) {
mstore(0, ERROR_INVALID_PROOF_LENGTH)
revert(0, 0x04)
}

let tmp := 0
let o := proof.offset
// msm of pub signals (skipping IC0)
<% for (let i = 1; i < vk.ic.length; i++) { %>
mstore(<%= 160 * (i - 1) %>, IC<%= i %>x_0)
mstore(<%= 160 * (i - 1) + 32 %>, IC<%= i %>x_1)
mstore(<%= 160 * (i - 1) + 64 %>, IC<%= i %>y_0)
mstore(<%= 160 * (i - 1) + 96 %>, IC<%= i %>y_1)
tmp := calldataload(add(o, <%= 512 + 32 * (i - 1) %>))
checkField(tmp)
mstore(<%= 160 * (i - 1) + 128 %>, tmp)
<% } %>
// vk
let success := staticcall(sub(gas(), 2000), G1_MSM_PRECOMPILE, 0, <%= 160 * vk.ic.length %>, 0, 128)
if iszero(success) {
mstore(0, ERROR_G1_MSM_FAILED)
revert(0, 0x04)
}

mstore(128, IC0x_0)
mstore(160, IC0x_1)
mstore(192, IC0y_0)
mstore(224, IC0y_1)
// add IC0 to the result of the MSM
success := staticcall(sub(gas(), 2000), G1_ADD_PRECOMPILE, 0, 256, 0, 128)
// in practice this error should never happen, only if the precompile is broken
if iszero(success) {
mstore(0, ERROR_G1_ADD_FAILED)
revert(0, 0x04)
}

// https://www.zeroknowledgeblog.com/index.php/groth16
// e(vk, gamma) * e(C, delta) * e(alpha, beta) * e(-A, B) == 1

// gamma
mstore(128, gammax1_0)
mstore(160, gammax1_1)
mstore(192, gammay1_0)
mstore(224, gammay1_1)
mstore(256, gammax2_0)
mstore(288, gammax2_1)
mstore(320, gammay2_0)
mstore(352, gammay2_1)
// C
calldatacopy(384, add(o, 384), 128)
// delta
mstore(512, deltax1_0)
mstore(544, deltax1_1)
mstore(576, deltay1_0)
mstore(608, deltay1_1)
mstore(640, deltax2_0)
mstore(672, deltax2_1)
mstore(704, deltay2_0)
mstore(736, deltay2_1)
// alpha
mstore(768, alphax_0)
mstore(800, alphax_1)
mstore(832, alphay_0)
mstore(864, alphay_1)
// beta
mstore(896, betax1_0)
mstore(928, betax1_1)
mstore(960, betay1_0)
mstore(992, betay1_1)
mstore(1024, betax2_0)
mstore(1056, betax2_1)
mstore(1088, betay2_0)
mstore(1120, betay2_1)
// e(-A, B)
calldatacopy(1152, o, 384)

// result of pairing check is saved to offset 0
success := staticcall(sub(gas(), 2000), PAIRING_PRECOMPILE, 0, 1536, 0, 32)
if iszero(success) {
mstore(0, ERROR_PAIRING_FAILED)
revert(0, 0x04)
}

return(0, 0x20)
}
}
}