Polygon zkEVM 递归证明过程
2022年12月21日,Polygon zkEVM 发布了第二个测试网,也是主网上线前最后一个测试网。此次升级主要在zkEVM 实现递归证明,实现Batch 聚合过程,并将每个Batch证明时间降低从原先的10min 降到 4min 以下,每个Batch的Gas limit 从 4 millon 增加到 10 milion, 进一步降低交易费用,增加吞吐量,并且还引入了EIP-155 (引入chain_id, 防止重入攻击) 的支持。
recursive1
生成recursive1证明需要两轮 stark->stark
证明,即 , 皆定义在Goldilocks
域上。
对于STARK 转换为STARK, 首先通过PIL2Circom
过程,生成circom
验证电路,用于验证STARK证明; 再将circom
电路编译成R1CS 之后,转换变Plonk 形式的电路,再生成PIL
的描述,通过这个过程,可以用于验证STARK 证明的PIL约束; 然后根据PIL, 可以生成新的STARK证明.
因此recursive1
证明的实现过程如下:
关于具体的解析过程,详见 STARK->SNARK
解析文档。
生成 证时的Stark struct 结构为:
{
"nBits": 23, // 基域
"nBitsExt": 24, // 扩域
"nQueries": 128, // FRI 查询点的个数
"verificationHashType": "GL", // Goldilocks 域
"steps": [
{"nBits": 24}, // FRI各层域的位数
{"nBits": 19},
{"nBits": 14},
{"nBits": 10},
{"nBits": 6}
]
}
生成 证明时的Stark struct 结构为:
{
"nBits": 22,
"nBitsExt": 24,
"nQueries": 64,
"verificationHashType": "GL",
"steps": [
{"nBits": 24},
{"nBits": 20},
{"nBits": 15},
{"nBits": 10},
{"nBits": 5}
]
}
recursive1
验证 的验证电路recursive1.circom
为:
pragma circom 2.1.0;
pragma custom_templates;
include "c12a.verifier.circom";
template Main() {
//publics 43个元素分别为:
// 0-7: oldStateRoot; 8-15: OldAccInputHash; 16: oldBatchNum; 17: chainId,
// 18-25:newStateRoot; 26-33: newAccInputHash; 34-41: localExitRoot, 42: newBatchNum
signal input publics[43];
signal input rootC[4]; // 常量多项式对应的Merkle 根
signal input root1[4]; // commit多项式对应的Merkle 根
signal input root2[4]; // plookup 多项式对应的Merkle 根
signal input root3[4]; // plookup/permutation/connection Z 多项式对应的根
signal input root4[4]; // q多项式 约束对应的Merkle 根
signal input evals[94][3]; // 对应starkinfo 中 evMap中各个承诺多项式的值。
signal input s0_vals1[64][12]; //FRI proof[0]: 64 表示查询点的个数,12表示commit多项式的个数(cm1_2ns)
signal input s0_vals3[64][41];
//FRI proof[0]: 64 表示查询点的个数,41表示plookup/permutation/connection Z多项式的个数
signal input s0_vals4[64][12]; //FRI proof[0]: 64 表示查询点的个数,12表示 q多项式的个数(q_2ns)
signal input s0_valsC[64][34]; //FRI proof[0]: 64 表示查询点的个数,34表示常量多项式的个数
signal input s0_siblings1[64][24][4]; //s0_vals1对应的Merkle 路径,有24层,每个节点由4个Goldilocks 元素组成
signal input s0_siblings3[64][24][4];
signal input s0_siblings4[64][24][4];
signal input s0_siblingsC[64][24][4];
signal input s1_root[4]; // fri step 1 root
signal input s2_root[4]; // fri step 2 root
signal input s3_root[4]; // fri step 3 root
signal input s4_root[4]; // fri step 4 root
signal input s1_vals[64][48]; // fri 1 tree对应查询点的叶子节点,48为对应叶子width
signal input s1_siblings[64][20][4]; // fri 1 tree 查询点对应的Merkle路径
signal input s2_vals[64][96];
signal input s2_siblings[64][15][4];
signal input s3_vals[64][96];
signal input s3_siblings[64][10][4];
signal input s4_vals[64][96];
signal input s4_siblings[64][5][4];
signal input finalPol[32][3]; // 最后一轮,每个都为宽度为3的值
component vA = StarkVerifier();
vA.publics <== publics;
vA.root1 <== root1;
vA.root2 <== root2;
vA.root3 <== root3;
vA.root4 <== root4;
vA.evals <== evals;
vA.s0_vals1 <== s0_vals1;
vA.s0_vals3 <== s0_vals3;
vA.s0_vals4 <== s0_vals4;
vA.s0_valsC <== s0_valsC;
vA.s0_siblings1 <== s0_siblings1;
vA.s0_siblings3 <== s0_siblings3;
vA.s0_siblings4 <== s0_siblings4;
vA.s0_siblingsC <== s0_siblingsC;
vA.s1_root <== s1_root;
vA.s2_root <== s2_root;
vA.s3_root <== s3_root;
vA.s4_root <== s4_root;
vA.s1_vals <== s1_vals;
vA.s1_siblings <== s1_siblings;
vA.s2_vals <== s2_vals;
vA.s2_siblings <== s2_siblings;
vA.s3_vals <== s3_vals;
vA.s3_siblings <== s3_siblings;
vA.s4_vals <== s4_vals;
vA.s4_siblings <== s4_siblings;
vA.finalPol <== finalPol;
}
component main {public [publics, rootC]}= Main(); // 公开输入为:publics 和 常量多项式根
生成recursive1
证明时的的 Stark struct 为:
{
"nBits": 20,
"nBitsExt": 24,
"nQueries": 32,
"verificationHashType": "GL",
"steps": [
{"nBits": 24},
{"nBits": 19},
{"nBits": 14},
{"nBits": 9},
{"nBits": 5}
]
}
recursive2
通过合并两个recursive1
证明的验证过程,生成recursive2
证明。
recrsive2
的验证电路recursive2.circom.ejs
为, 其中包含了两个recursive1
证明的验证。
注: 在进行ejs注入时,需要提供recursive1
常量根。
pragma circom 2.1.0;
pragma custom_templates;
include "recursive1.verifier.circom";
include "mux1.circom";
include "iszero.circom";
template Main() {
var rootCSingle[4];
rootCSingle[0] = <%- constRoot[0] %>;
rootCSingle[1] = <%- constRoot[1] %>;
rootCSingle[2] = <%- constRoot[2] %>;
rootCSingle[3] = <%- constRoot[3] %>;
signal input publics[43];
signal input rootC[4];
signal input a_publics[43];
signal input a_root1[4];
signal input a_root2[4];
signal input a_root3[4];
signal input a_root4[4];
signal input a_evals[72][3];
signal input a_s0_vals1[32][12];
signal input a_s0_vals3[32][3];
signal input a_s0_vals4[32][36];
signal input a_s0_valsC[32][34];
signal input a_s0_siblings1[32][24][4];
signal input a_s0_siblings3[32][24][4];
signal input a_s0_siblings4[32][24][4];
signal input a_s0_siblingsC[32][24][4];
signal input a_s1_root[4];
signal input a_s2_root[4];
signal input a_s3_root[4];
signal input a_s4_root[4];
signal input a_s1_vals[32][96];
signal input a_s1_siblings[32][19][4];
signal input a_s2_vals[32][96];
signal input a_s2_siblings[32][14][4];
signal input a_s3_vals[32][96];
signal input a_s3_siblings[32][9][4];
signal input a_s4_vals[32][48];
signal input a_s4_siblings[32][5][4];
signal input a_finalPol[32][3];
signal input b_publics[43];
signal input b_root1[4];
signal input b_root2[4];
signal input b_root3[4];
signal input b_root4[4];
signal input b_evals[72][3];
signal input b_s0_vals1[32][12];
signal input b_s0_vals3[32][3];
signal input b_s0_vals4[32][36];
signal input b_s0_valsC[32][34];
signal input b_s0_siblings1[32][24][4];
signal input b_s0_siblings3[32][24][4];
signal input b_s0_siblings4[32][24][4];
signal input b_s0_siblingsC[32][24][4];
signal input b_s1_root[4];
signal input b_s2_root[4];
signal input b_s3_root[4];
signal input b_s4_root[4];
signal input b_s1_vals[32][96];
signal input b_s1_siblings[32][19][4];
signal input b_s2_vals[32][96];
signal input b_s2_siblings[32][14][4];
signal input b_s3_vals[32][96];
signal input b_s3_siblings[32][9][4];
signal input b_s4_vals[32][48];
signal input b_s4_siblings[32][5][4];
signal input b_finalPol[32][3];
component vA = StarkVerifier();
for (var i=0; i<43; i++) {
vA.publics[i] <== a_publics[i];
}
vA.root1 <== a_root1;
vA.root2 <== a_root2;
vA.root3 <== a_root3;
vA.root4 <== a_root4;
vA.evals <== a_evals;
vA.s0_vals1 <== a_s0_vals1;
vA.s0_vals3 <== a_s0_vals3;
vA.s0_vals4 <== a_s0_vals4;
vA.s0_valsC <== a_s0_valsC;
vA.s0_siblings1 <== a_s0_siblings1;
vA.s0_siblings3 <== a_s0_siblings3;
vA.s0_siblings4 <== a_s0_siblings4;
vA.s0_siblingsC <== a_s0_siblingsC;
vA.s1_root <== a_s1_root;
vA.s2_root <== a_s2_root;
vA.s3_root <== a_s3_root;
vA.s4_root <== a_s4_root;
vA.s1_vals <== a_s1_vals;
vA.s1_siblings <== a_s1_siblings;
vA.s2_vals <== a_s2_vals;
vA.s2_siblings <== a_s2_siblings;
vA.s3_vals <== a_s3_vals;
vA.s3_siblings <== a_s3_siblings;
vA.s4_vals <== a_s4_vals;
vA.s4_siblings <== a_s4_siblings;
vA.finalPol <== a_finalPol;
component isOneBatchA = IsZero();
isOneBatchA.in <== a_publics[42] - a_publics[16] - 1;
component a_muxRootC = MultiMux1(4);
a_muxRootC.c[0] <== rootC;
a_muxRootC.c[1] <== rootCSingle;
a_muxRootC.s <== isOneBatchA.out;
for (var i=0; i<4; i++) {
vA.publics[43+i] <== rootC[i];
}
vA.rootC <== a_muxRootC.out;
component vB = StarkVerifier();
for (var i=0; i<43; i++) {
vB.publics[i] <== b_publics[i];
}
vB.root1 <== b_root1;
vB.root2 <== b_root2;
vB.root3 <== b_root3;
vB.root4 <== b_root4;
vB.evals <== b_evals;
vB.s0_vals1 <== b_s0_vals1;
vB.s0_vals3 <== b_s0_vals3;
vB.s0_vals4 <== b_s0_vals4;
vB.s0_valsC <== b_s0_valsC;
vB.s0_siblings1 <== b_s0_siblings1;
vB.s0_siblings3 <== b_s0_siblings3;
vB.s0_siblings4 <== b_s0_siblings4;
vB.s0_siblingsC <== b_s0_siblingsC;
vB.s1_root <== b_s1_root;
vB.s2_root <== b_s2_root;
vB.s3_root <== b_s3_root;
vB.s4_root <== b_s4_root;
vB.s1_vals <== b_s1_vals;
vB.s1_siblings <== b_s1_siblings;
vB.s2_vals <== b_s2_vals;
vB.s2_siblings <== b_s2_siblings;
vB.s3_vals <== b_s3_vals;
vB.s3_siblings <== b_s3_siblings;
vB.s4_vals <== b_s4_vals;
vB.s4_siblings <== b_s4_siblings;
vB.finalPol <== b_finalPol;
component isOneBatchB = IsZero();
isOneBatchB.in <== b_publics[42] - b_publics[16] - 1;
component b_muxRootC = MultiMux1(4);
b_muxRootC.c[0] <== rootC;
b_muxRootC.c[1] <== rootCSingle;
b_muxRootC.s <== isOneBatchB.out;
for (var i=0; i<4; i++) {
vB.publics[43+i] <== rootC[i];
}
vB.rootC <== b_muxRootC.out;
// oldStateRoot
for (var i=0; i<8; i++) {
a_publics[i] === publics[i];
}
// oldAccInputHash
for (var i=8; i<16; i++) {
a_publics[i] === publics[i];
}
// oldBatchNum
a_publics[16] === publics[16];
// chainId
a_publics[17] === publics[17];
// midStateRoot
for (var i=0; i<8; i++) {
b_publics[i] === a_publics[18+i];
}
// midAccInputHash
for (var i=8; i<16; i++) {
b_publics[i] === a_publics[18+i];
}
// midBatchNum
b_publics[16] === a_publics[18+24];
// chainId
b_publics[17] === publics[17];
// newStateRoot
for (var i=0; i<8; i++) {
publics[18+i] === b_publics[18+i];
}
// newAccInputHash
for (var i=8; i<16; i++) {
publics[18+i] === b_publics[18+i];
}
// localExitRoot
for (var i=16; i<24; i++) {
publics[18+i] === b_publics[18+i];
}
// newBatchNum
publics[18+24] === b_publics[18+24];
}
component main {public [publics, rootC]}= Main();
注:publics 43个元素分别为: 0-7: oldStateRoot; 8-15: OldAccInputHash; 16: oldBatchNum; 17: chainId, 18-25:newStateRoot; 26-33: newAccInputHash; 34-41: localExitRoot, 42: newBatchNum.
生成recursive2
证明时的的 Stark struct 和 recursive1
的一致,皆为:
{
"nBits": 20,
"nBitsExt": 24,
"nQueries": 32,
"verificationHashType": "GL",
"steps": [
{"nBits": 24},
{"nBits": 19},
{"nBits": 14},
{"nBits": 9},
{"nBits": 5}
]
}
由于recursive1.pil(生成recursive1 证明)和
recursive2.pil(生成recursive2证明)完全一致,并且
recursive1.verifier.circom(验证recursive1证明) 和
recursive2.verifier.circom` (验证recursive2证明) 一致,
"recursive_pil_check": ". ./pre.sh && F1=$BDIR/recursive1.pil && F2=$BDIR/recursive2.pil && diff $F1 $F2 || (echo \"ERROR: $F1 $F2 are different\"; exit 1)",
"recursive_verifier_check": ". ./pre.sh && F1=$BDIR/recursive1.verifier.circom && F2=$BDIR/recursive2.verifier.circom && diff $F1 $F2 || (echo \"ERROR: $F1 $F2 are different\"; exit 1)",
因此可以将一个recursive2
证明当作一个recursive1
证明, 可以再次将一个recursive2
证明和一个recursive1
证明,合并成一个新的recursive2
证明。
recursivef
在对recursivef.circom.ejs
进行 ejs 注入时,需要提供recursive1
和recursive2
的常量根:
const template = await fs.promises.readFile(path.join(__dirname, "..", "recursive", "recursivef.circom.ejs"), "utf8");
const obj = {
constRoot1: constRoot1,
constRoot2: constRoot2,
};
console.log(obj)
const verifier = ejs.render(template , obj);
recursivef.circom.ejs
的实现过程为如下,主要是对一个STARK 证明的验证:
pragma circom 2.1.0;
pragma custom_templates;
include "recursive2.verifier.circom";
include "mux1.circom";
include "iszero.circom";
template Main() {
signal input publics[43];
signal input root1[4];
signal input root2[4];
signal input root3[4];
signal input root4[4];
signal input evals[72][3];
signal input s0_vals1[32][12];
signal input s0_vals3[32][3];
signal input s0_vals4[32][36];
signal input s0_valsC[32][34];
signal input s0_siblings1[32][24][4];
signal input s0_siblings3[32][24][4];
signal input s0_siblings4[32][24][4];
signal input s0_siblingsC[32][24][4];
signal input s1_root[4];
signal input s2_root[4];
signal input s3_root[4];
signal input s4_root[4];
signal input s1_vals[32][96];
signal input s1_siblings[32][19][4];
signal input s2_vals[32][96];
signal input s2_siblings[32][14][4];
signal input s3_vals[32][96];
signal input s3_siblings[32][9][4];
signal input s4_vals[32][48];
signal input s4_siblings[32][5][4];
signal input finalPol[32][3];
component sv = StarkVerifier();
for (var i=0; i<43; i++) {
sv.publics[i] <== publics[i];
}
sv.root1 <== root1;
sv.root2 <== root2;
sv.root3 <== root3;
sv.root4 <== root4;
sv.evals <== evals;
sv.s0_vals1 <== s0_vals1;
sv.s0_vals3 <== s0_vals3;
sv.s0_vals4 <== s0_vals4;
sv.s0_valsC <== s0_valsC;
sv.s0_siblings1 <== s0_siblings1;
sv.s0_siblings3 <== s0_siblings3;
sv.s0_siblings4 <== s0_siblings4;
sv.s0_siblingsC <== s0_siblingsC;
sv.s1_root <== s1_root;
sv.s2_root <== s2_root;
sv.s3_root <== s3_root;
sv.s4_root <== s4_root;
sv.s1_vals <== s1_vals;
sv.s1_siblings <== s1_siblings;
sv.s2_vals <== s2_vals;
sv.s2_siblings <== s2_siblings;
sv.s3_vals <== s3_vals;
sv.s3_siblings <== s3_siblings;
sv.s4_vals <== s4_vals;
sv.s4_siblings <== s4_siblings;
sv.finalPol <== finalPol;
component isOne = IsZero();
isOne.in <== publics[42] -publics[16] -1;
component muxKey = MultiMux1(4);
muxKey.s <== isOne.out;
muxKey.c[0][0] <== <%- constRoot2[0] %>;
muxKey.c[0][1] <== <%- constRoot2[1] %>;
muxKey.c[0][2] <== <%- constRoot2[2] %>;
muxKey.c[0][3] <== <%- constRoot2[3] %>;
muxKey.c[1][0] <== <%- constRoot1[0] %>;
muxKey.c[1][1] <== <%- constRoot1[1] %>;
muxKey.c[1][2] <== <%- constRoot1[2] %>;
muxKey.c[1][3] <== <%- constRoot1[3] %>;
sv.publics[43] <== <%- constRoot2[0] %>;
sv.publics[44] <== <%- constRoot2[1] %>;
sv.publics[45] <== <%- constRoot2[2] %>;
sv.publics[46] <== <%- constRoot2[3] %>;
sv.rootC[0] <== muxKey.out[0];
sv.rootC[1] <== muxKey.out[1];
sv.rootC[2] <== muxKey.out[2];
sv.rootC[3] <== muxKey.out[3];
}
component main {public [publics]}= Main();
生成 recursivef
证明的Stark struck 结构为:
{
"nBits": 19,
"nBitsExt": 23,
"nQueries": 32,
"verificationHashType": "BN128", // 定义在 BN128 标量域上
"steps": [
{"nBits": 23},
{"nBits": 20},
{"nBits": 16},
{"nBits": 12},
{"nBits": 8},
{"nBits": 4}
]
}
final
final.circom
电路主要用于验证recursivef
证明,生成最后的SNARK 证明。
隐私输入为:aggregatorAddr, oldStateRoot, oldAcInputHash, oldBatchNum, chainId, newStateRoot, newAccInputHash, newLocalExitRoot, newBatchNum.
公开输入:上述所有隐私输入变量拼接后计算得到的Hash值。
pragma circom 2.1.0;
/*
aggregatorAddr -> 160 -> 160
oldStateRoot -> 256 -> 416
oldAccInputHash -> 256 -> 672
oldBathcNum -> 64 -> 736
chainId -> 64 -> 800
newStateRoot -> 256 -> 1056
newAccInputHash -> 256 -> 1312
newLocalExitRoot -> 256 -> 1568
newBatchNum -> 64 -> 1632
Total: 1632
*/
include "sha256/sha256.circom";
include "bitify.circom";
include "recursivef.verifier.circom";
template Main() {
signal output publicsHash;
signal input aggregatorAddr;
signal input publics[43];
signal input root1;
signal input root2;
signal input root3;
signal input root4;
signal input evals[72][3];
signal input s0_vals1[32][12];
signal input s0_vals3[32][3];
signal input s0_vals4[32][36];
signal input s0_valsC[32][34];
signal input s0_siblings1[32][6][16];
signal input s0_siblings3[32][6][16];
signal input s0_siblings4[32][6][16];
signal input s0_siblingsC[32][6][16];
signal input s1_root;
signal input s2_root;
signal input s3_root;
signal input s4_root;
signal input s5_root;
signal input s1_vals[32][24];
signal input s1_siblings[32][5][16];
signal input s2_vals[32][48];
signal input s2_siblings[32][4][16];
signal input s3_vals[32][48];
signal input s3_siblings[32][3][16];
signal input s4_vals[32][48];
signal input s4_siblings[32][2][16];
signal input s5_vals[32][48];
signal input s5_siblings[32][1][16];
signal input finalPol[16][3];
component sv = StarkVerifier();
sv.publics <== publics;
sv.root1 <== root1;
sv.root2 <== root2;
sv.root3 <== root3;
sv.root4 <== root4;
sv.evals <== evals;
sv.s0_vals1 <== s0_vals1;
sv.s0_vals3 <== s0_vals3;
sv.s0_vals4 <== s0_vals4;
sv.s0_valsC <== s0_valsC;
sv.s0_siblings1 <== s0_siblings1;
sv.s0_siblings3 <== s0_siblings3;
sv.s0_siblings4 <== s0_siblings4;
sv.s0_siblingsC <== s0_siblingsC;
sv.s1_root <== s1_root;
sv.s2_root <== s2_root;
sv.s3_root <== s3_root;
sv.s4_root <== s4_root;
sv.s5_root <== s5_root;
sv.s1_vals <== s1_vals;
sv.s1_siblings <== s1_siblings;
sv.s2_vals <== s2_vals;
sv.s2_siblings <== s2_siblings;
sv.s3_vals <== s3_vals;
sv.s3_siblings <== s3_siblings;
sv.s4_vals <== s4_vals;
sv.s4_siblings <== s4_siblings;
sv.s5_vals <== s5_vals;
sv.s5_siblings <== s5_siblings;
sv.finalPol <== finalPol;
component publicsHasher = Sha256(1632);
component n2bAggregatorAddr = Num2Bits(160);
n2bAggregatorAddr.in <== aggregatorAddr;
for (var i=0; i<160; i++) {
publicsHasher.in[0 + 160 - 1 -i] <== n2bAggregatorAddr.out[i];
}
component n2bOldStateRoot[8];
for (var i=0; i<8; i++) {
n2bOldStateRoot[i] = Num2Bits(32);
n2bOldStateRoot[i].in <== publics[0 + i];
for (var j=0; j<32; j++) {
publicsHasher.in[160 + 32*(8-i) - 1 -j] <== n2bOldStateRoot[i].out[j];
}
}
component n2bOldAccInputHash[8];
for (var i=0; i<8; i++) {
n2bOldAccInputHash[i] = Num2Bits(32);
n2bOldAccInputHash[i].in <== publics[8 + i];
for (var j=0; j<32; j++) {
publicsHasher.in[416 + 32*(8-i) - 1 -j] <== n2bOldAccInputHash[i].out[j];
}
}
// Do 63 bits to avoid aliasing
component n2bOldBatchNum = Num2Bits(63);
n2bOldBatchNum.in <== publics[16];
for (var i=0; i<63; i++) {
publicsHasher.in[672 + 64 - 1 -i] <== n2bOldBatchNum.out[i];
}
publicsHasher.in[672] <== 0;
component n2bChainId = Num2Bits(63);
n2bChainId.in <== publics[17];
for (var i=0; i<63; i++) {
publicsHasher.in[736 + 64 - 1 -i] <== n2bChainId.out[i];
}
publicsHasher.in[736] <== 0;
component n2bNewStateRoot[8];
for (var i=0; i<8; i++) {
n2bNewStateRoot[i] = Num2Bits(32);
n2bNewStateRoot[i].in <== publics[18+i];
for (var j=0; j<32; j++) {
publicsHasher.in[800 + 32*(8-i) - 1 -j] <== n2bNewStateRoot[i].out[j];
}
}
component n2bNewAccInputHash[8];
for (var i=0; i<8; i++) {
n2bNewAccInputHash[i] = Num2Bits(32);
n2bNewAccInputHash[i].in <== publics[26+i];
for (var j=0; j<32; j++) {
publicsHasher.in[1056 + 32*(8-i) - 1 -j] <== n2bNewAccInputHash[i].out[j];
}
}
component n2bNewLocalExitRoot[8];
for (var i=0; i<8; i++) {
n2bNewLocalExitRoot[i] = Num2Bits(32);
n2bNewLocalExitRoot[i].in <== publics[34+i];
for (var j=0; j<32; j++) {
publicsHasher.in[1312 + 32*(8-i) - 1 -j] <== n2bNewLocalExitRoot[i].out[j];
}
}
component n2bNewBatchNum = Num2Bits(63);
n2bNewBatchNum.in <== publics[42];
for (var i=0; i<63; i++) {
publicsHasher.in[1568 + 64 - 1 -i] <== n2bNewBatchNum.out[i];
}
publicsHasher.in[1568] <== 0;
component b2nPublicsHash = Bits2Num(256);
for (var i = 0; i < 256; i++) {
b2nPublicsHash.in[i] <== publicsHasher.out[255-i];
}
publicsHash <== b2nPublicsHash.out;
}
component main = Main();
将final.circom
编译为R1CS后,即可生成最后SNARK 证明,完成整个递归证明过程。在目前的c++/js 版本的zkevm-prover中,最终生成的还是Groth16证明。
参考
https://polygon.technology/blog/final-approach-last-testnet-for-an-upgraded-polygon-zkevm
https://eips.ethereum.org/EIPS/eip-155