前序博客有:
以https://github.com/0xPolygonHermez/pil-stark为例,Polygon zkEVM中实现了2种Merkle tree(二者均采用Poseidon 哈希函数):
if (starkStruct.verificationHashType == "GL") {
// 借鉴了https://github.com/filecoin-project/neptune 中的优化策略
const poseidon = await buildPoseidonGL();
MH = await buildMerklehashGL();
transcript = new Transcript(poseidon);
} else if (starkStruct.verificationHashType == "BN128") {
const poseidonBN128 = await buildPoseidonBN128();
MH = await buildMerklehashBN128();
transcript = new TranscriptBN128(poseidonBN128);
} else {
throw new Error("Invalid Hash Type: "+ starkStruct.verificationHashType);
}
Polygon zkEVM中包含的Merkle tree主要有:【详细可看Polygon zkEVM公式梳理】
Q
的多项式(如中间多项式约束表达(Goldilocks基域)和合并的polIdentities约束约束表达(Goldilocks extension 3 域))扩域evaluation值——ctx.q_2ns。
FRI 折叠Merkle化证明配置信息starkStruct为:
{
"nBits": 10, //待证明FRI多项式的degree为2^nBits
"nBitsExt": 11,
"nQueries": 8,
"verificationHashType": "GL",
"steps": [
{"nBits": 11}, //第一个step的nBits值必须等于上面的nBitsExt
{"nBits": 7},
{"nBits": 3}
]
}
FRI 折叠Merkle化证明核心代码为:
async prove(transcript, pol, queryPol) {
const self = this;
const proof = [];
const F = this.F;
let polBits = log2(pol.length);
assert(1<<polBits == pol.length, "Invalid poluynomial size"); // Check the input polynomial is a power of 2
assert(polBits == this.inNBits, "Invalid polynomial size");
let shiftInv = F.shiftInv;
let shift = F.shift;
let tree = [];
for (let si = 0; si<this.steps.length; si++) proof[si] = {};
for (let si = 0; si<this.steps.length; si++) {
const reductionBits = polBits - this.steps[si].nBits;
const pol2N = 1 << (polBits - reductionBits);
const nX = pol.length / pol2N;
const pol2_e = new Array(pol2N);
let special_x = transcript.getField();
let sinv = shiftInv;
const wi = F.inv(F.w[polBits]);
for (let g = 0; g<pol.length/nX; g++) {
if (si==0) {
pol2_e[g] = pol[g];
} else {
const ppar = new Array(nX);
for (let i=0; i<nX; i++) {
ppar[i] = pol[(i*pol2N)+g];
}
const ppar_c = F.ifft(ppar);
const zyd_e = evalPol(F, ppar_c, F.mul(sinv, special_x));
polMulAxi(F, ppar_c, F.one, sinv); // Multiplies coefs by 1, shiftInv, shiftInv^2, shiftInv^3, ......
pol2_e[g] = evalPol(F, ppar_c, special_x);
assert(F.eq(zyd_e, pol2_e[g]));
sinv = F.mul(sinv, wi);
}
}
if (si < this.steps.length-1) {
const nGroups = 1<< this.steps[si+1].nBits;
let groupSize = (1 << this.steps[si].nBits) / nGroups;
const pol2_etb = getTransposedBuffer(pol2_e, this.steps[si+1].nBits);
tree[si] = await this.MH.merkelize(pol2_etb, 3* groupSize, nGroups);
proof[si+1].root= this.MH.root(tree[si]);
transcript.put(this.MH.root(tree[si]));
} else {
for (let i=0; i<pol2_e.length; i++) {
transcript.put(pol2_e[i]);
}
}
pol = pol2_e;
polBits = polBits-reductionBits;
for (let j=0; j<reductionBits; j++) {
shiftInv = F.mul(shiftInv, shiftInv);
shift = F.mul(shift, shift);
}
}
// ...........
}
参考V神博客 STARKs, Part 3: Into the Weeds:
pol
为待证明FRI多项式 pol ( X ) \text{pol}(X) pol(X)的点值表示:【其中引入 g g g实现Coset-FRI。】
{ ( g w e 0 , pol ( g w e 0 ) ) , ( g w e 1 , pol ( g w e 1 ) ) , ( g w e 2 , pol ( g w e 2 ) ) , ⋯ ( g w e 2 nBitsExt − 1 , pol ( g w e 2 nBitsExt − 1 ) ) } \{(gw_e^0, \text{pol}(gw_e^0)),(gw_e^1, \text{pol}(gw_e^1)),(gw_e^2, \text{pol}(gw_e^2)),\cdots (gw_e^{2^{\text{nBitsExt}}-1}, \text{pol}(gw_e^{2^{\text{nBitsExt}}-1}))\} {(gwe0,pol(gwe0)),(gwe1,pol(gwe1)),(gwe2,pol(gwe2)),⋯(gwe2nBitsExt−1,pol(gwe2nBitsExt−1))}
待证明FRI多项式 pol ( X ) \text{pol}(X) pol(X)的degree为 N = 2 nBits N=2^{\text{nBits}} N=2nBits, X X X取自于由 w = g w e w=gw_e w=gwe生成元派生的域,其中 w e 2 nBitsExt = 1 w_e^{2^{\text{nBitsExt}}}=1 we2nBitsExt=1。
【以上代码中const wi = F.inv(F.w[polBits]);
中wi满足 wi 2 steps[i-1].nBits = 1 \text{wi}^{2^{\text{steps[i-1].nBits}}}=1 wi2steps[i-1].nBits=1】
前序博客有:
基于Goldilocks域的Merkle tree,其Poseidon hash实现借鉴了https://github.com/filecoin-project/neptune(Poseidon hashing over BLS12-381)中的优化策略,其基于的是Goldilocks extension 3 field。
详细代码实现见pil-Stark项目中的poseidon.js。
pil-stark项目中的 linearhash.js中,LinearHash类中,主要实现了hash
函数:【将输入vals铺平展开为flatVals:若展开后的flatVals长度小于等于4,则补零为长度为4的结果返回;否则,将flatVals补齐为最近的8的整数倍,以8个元素为一组inHash,递归调用poseidon,返回最后一次调用poseidon函数的结果。】
module.exports = class LinearHash {
constructor(poseidon) {
this.H = poseidon; //基于Goldilocks域的Poseidon hash
}
hash(vals) { //输入为vals数组,每个元素也可能是数组
const flatVals = [];//将输入vals展开铺平存入flatVals中。
for (let i=0; i<vals.length; i++) {
if (Array.isArray(vals[i])) {
for (let j=0; j<vals[i].length; j++) {
flatVals.push(vals[i][j]);
}
} else {
flatVals.push(vals[i]);
}
}
let st = [0n, 0n, 0n, 0n];
//若flatVals长度小于4,则在其后补零到长度为4.
if (flatVals.length <= 4) {
for (let i=0; i<flatVals.length;i++) {
st[i] = flatVals[i];
}
return st;
}
let inHash = [];
for (let i=0; i<flatVals.length;i++) {
inHash.push(flatVals[i]);
//将铺平后的flatVals,每8个元素为一组inHash,递归调用poseidon函数
if (inHash.length == 8) {
st = this.H(inHash, st);//递归调用,st为输入和输入
inHash.length = 0;
}
}
//若铺平后的flatVals长度不是8的整数倍,则后续补零到为最近的8的整数倍。
if (inHash.length>0) {
while (inHash.length<8) inHash.push(0n);
//对补零后的inHash块,递归调用poseidon
st = this.H(inHash, st);
}
//返回最后一次调用poseidon的结果
return st;
}
}
linearHash
函数功能为:
if (width <=4) {
for (let i=0; i<heigth; i++) {
for (let j=0; j<width; j++) {
buffOut[i*4+j] = buffIn[width*i + j];
}
}
return buffOut;
}
buildWasm
,会实现wasm代码,公开基于Goldilocks域的add/mul/square/poseidon/multiLinearHash/merkelizeLevel函数。glwasm.multiLinearHash(pIn, width, heigth, pOut);
,其中pIn以矩阵表示,具有width列,height行。(每列元素为Goldilocks基域元素)。multiLinearHash是指将每8个元素为一组调用poseidon
函数进行运算。pOut的大小为:height*4。【为所有的叶子节点】for (let i=0; i<res.length; i++) { //设置所有叶子节点
tree.nodes.set(res[i], i*nPerThreadF*4);
}
let pIn = 0;
let n64 = height*4;
let nextN64 = (Math.floor((n64-1)/8)+1)*4;
let pOut = pIn + nextN64*2*8;
while (n64>4) {
// FIll with zeros if n nodes in the leve is not even
await _merkelizeLevel(tree.nodes, pIn, nextN64/4, pOut);
n64 = nextN64;
nextN64 = (Math.floor((n64-1)/8)+1)*4;
pIn = pOut;
pOut = pIn + nextN64*2*8;
}
基于Goldilocks域的Merkle tree的基本结构为:
const tree = {
//为待Merkle化的所有元素,以矩阵表示,具有w列h行。
//buff每个元素基于Goldilocks extension 3 域,
//所以,buff每个元素由3个Goldilocks基域元素组成。
elements: buff,
//若h=2^n,则nodes总数为: (2^{n+1}-1)*4,即每个节点可存放4个Goldilocks基域元素。
nodes: new BigUint64Array(this._getNNodes(height*4)),
width: width, //为待Merkle化元素矩阵表示的列数w * 3(因基于Goldilocks extension 3 域)。
height: height //为待Merkle化元素矩阵表示的行数h,假设h=2^n
};
tree[si] = await this.MH.merkelize(pol2_etb, 3* groupSize, nGroups);
,其中:
在将某输入Merkle化(merkelize
)的过程中:
linearHash
函数: for (let i=0; i< height; i+=nPerThreadF) {
const curN = Math.min(nPerThreadF, height-i);
console.log("slicing buff "+i);
const bb = tree.elements.slice(i*width, (i+curN)*width);
// const bb = new BigUint64Array(tree.elements.buffer, tree.elements.byteOffset + i*width*8, curN*width);
if (self.useThreads) {
console.log("creating thread "+i);
promisesLH.push(pool.exec("linearHash", [bb, width, i, height]));
} else { //详细的linearHash实现见上一节分析。
res.push(await linearHash(bb, width, i, curN));
}
}
for (let i=0; i<res.length; i++) {
tree.nodes.set(res[i], i*nPerThreadF*4);
}
let pIn = 0;
let n64 = height*4;
let nextN64 = (Math.floor((n64-1)/8)+1)*4;
let pOut = pIn + nextN64*2*8;
while (n64>4) {
// FIll with zeros if n nodes in the leve is not even
await _merkelizeLevel(tree.nodes, pIn, nextN64/4, pOut);
n64 = nextN64;
nextN64 = (Math.floor((n64-1)/8)+1)*4;
pIn = pOut;
pOut = pIn + nextN64*2*8;
}
https://github.com/iden3/wasmbuilder——wasmbuilder:为手写构造wasm代码的Javascript库。