Polygon PIL-STARK 源码分析

本文以Plookup约束为例对PIL-STARK源码进行分析。

1. 定义starkStruct

首先定义starkStruct 结构为:

        const starkStruct = {
            nBits: 3,   // 基域 2**3 = 8
            nBitsExt: 4,  // 扩域:2**4 = 6 
            nQueries: 8,
            verificationHashType: "GL",  // 采用GoldLicks Hash
            steps: [
                { nBits: 4 },
                { nBits: 2 }
            ]
        };

2. 编译PIL

Plookup 的PIL 代码有两个文件:

simple_plookup_main.pil为:

constant %N = 2**3;

namespace Global(%N);
    pol constant L1;  // Lagrange basis 

include "simple_plookup.pil";

simple_plookup.pil 为:

namespace SimplePlookup(%N);


    pol commit a;  // commitment polynomia

    pol constant A;   // constant polynomial
    pol constant ONE;
    pol constant ZERO;

    a in A;   // plookup

对PIL 源码进行编译:

const pil = await compile(Fr, path.join(__dirname, "sm_simple_plookup", "simple_plookup_main.pil"));

得到PIL为:

{
 "nCommitments": 1, //承诺多项式的个数
 "nQ": 0,
 "nIm": 0,
 "nConstants": 4, //常量承诺多项式的个数
 "publics": [],
 "references": {
  "Global.L1": {
   "type": "constP",  //常量承诺多项式类型
   "id": 0,        //在`constP` 的id
   "polDeg": 8,       
   "isArray": false
  },
  "SimplePlookup.a": {
   "type": "cmP",      //承诺多项式类型
   "id": 0,           //在`cmP` 承诺中的id
   "polDeg": 8,
   "isArray": false
  },
  "SimplePlookup.A": {
   "type": "constP",
   "id": 1,
   "polDeg": 8,
   "isArray": false
  },
  "SimplePlookup.ONE": {
   "type": "constP",
   "id": 2,
   "polDeg": 8,
   "isArray": false
  },
  "SimplePlookup.ZERO": {
   "type": "constP",
   "id": 3,
   "polDeg": 8,
   "isArray": false
  }
 },
 "expressions": [  // 包含两个表达式
  {
   "op": "cm",
   "deg": 1,
   "id": 0,
   "next": false
  },
  {
   "op": "const",
   "deg": 1,
   "id": 1,
   "next": false
  }
 ],
 "polIdentities": [],
 "plookupIdentities": [
  {
   "f": [
    0         // 表示式 id
   ],
   "t": [
    1          // 表达式 id
   ],
   "selF": null,
   "selT": null,
   "fileName": "simple_plookup.pil",
   "line": 10
  }
 ],
 "permutationIdentities": [],
 "connectionIdentities": []
}

3. build constPols and cmPols

其中 N=8.

module.exports.buildConstants = async function (pols) {
    const N = pols.A.length;

    let p = 0;
    for (let i = 0; i < N; i++) {
        pols.A[i] = BigInt(i);
        pols.ONE[i] = 1n;
        pols.ZERO[i] = 0n;
    }
}

module.exports.execute = async function (pols) {

    const N = pols.a.length;

    for (let i = 0; i < N; i++) {
        pols.a[i] = BigInt(i);
    }
}

4. StarkInfo 初始化

    const res = {     // 用于保存最终的StarkInfo 的结果
        varPolMap: [],
        puCtx: [],
        peCtx: [],
        ciCtx: []
    };

    res.starkStruct = starkStruct;
    res.nConstants = pil.nConstants;
    res.nPublics = pil.publics.length;

    generatePublicCalculators(res, pil);  // 处理公开输入
    res.nCm1 = pil.nCommitments;

    const ctx = {
        pil: pil,
        calculated: {
            exps: {},
            expsPrime: {}
        },
        tmpUsed: 0,
        code: []
    };

    const ctx2ns = {
        pil: pil,
        calculated: {
            exps: {},
            expsPrime: {}
        },
        tmpUsed: 0,
        code: []
    };

5. generateStep2

主要的代码如下:

module.exports = function generateStep2(res, pil, ctx) {

    const E = new ExpressionOps(); //用于表达式的运算

    for (let i=0; i<pil.plookupIdentities.length; i++) {  //循环结构, 对PIL的每个plookup, 分别处理
        const puCtx = {};
        const pi = pil.plookupIdentities[i];
        
        // 计算 t 表达式  
        let tExp = null;
        const u = E.challenge("u");
        const defVal = E.challenge("defVal");
        for (let j=0; j<pi.t.length; j++) {
            const e = E.exp(pi.t[j]);
            if (tExp) {
                tExp = E.add(E.mul(u, tExp), e);
            } else {
                tExp = e;
            }
        }
        if (pi.selT !== null) {
            tExp = E.sub(tExp, defVal);
            tExp = E.mul(tExp, E.exp(pi.selT));
            tExp = E.add(tExp, defVal);

            tExp.idQ = pil.nQ;  //Q 可能表示 Quadraic, 二次的
            pil.nQ++;    
        }

        puCtx.tExpId = pil.expressions.length;
        tExp.keep = true;
        pil.expressions.push(tExp);

        
        // 计算 f  表达式
        fExp = null;
        for (let j=0; j<pi.f.length; j++) {
            const e = E.exp(pi.f[j]);
            if (fExp) {
                fExp = E.add(E.mul(fExp, u), e);
            } else {
                fExp = e;
            }
        }
        if (pi.selF !== null) {
            fExp = E.sub(fExp, E.exp(puCtx.tExpId));
            fExp = E.mul(fExp, E.exp(pi.selF));
            fExp = E.add(fExp, E.exp(puCtx.tExpId));

            fExp.idQ = pil.nQ;   
            pil.nQ++;
        }

        puCtx.fExpId = pil.expressions.length;
        fExp.keep = true;
        pil.expressions.push(fExp);

        pilCodeGen(ctx, puCtx.fExpId, false);
        pilCodeGen(ctx, puCtx.tExpId, false);

        puCtx.h1Id = pil.nCommitments++;   // 每个plookup 新添加h1和h2 两个承诺:
        puCtx.h2Id = pil.nCommitments++;

        res.puCtx.push(puCtx);
    }

    res.step2prev = buildCode(ctx);
}

pil.expressions 数组目前变为4个表达式:

0:{op: 'cm', deg: 1, id: 0, next: false}
1:{op: 'const', deg: 1, id: 1, next: false}
2:{op: 'exp', id: 1, next: false, keep: true} // tExp: id 1 指向第 1 个表达式
3:{op: 'exp', id: 0, next: false, keep: true}  // fExp: id 0 指向第 0 个表达式

fExp 执行 pilCodeGen 操作后,

 pilCodeGen(ctx, puCtx.fExpId, false);

ctx 添加2两个code:

// 第0个code, 执行复制操作 cm0 -> exp 0 
0:{expId: 0, prime: false, code: Array(1), idQ: undefined}
code:(1) [{…}]
0:{op: 'copy', dest: {…}, src: Array(1)}
dest:{type: 'exp', prime: false, id: 0}
op:'copy'
src:(1) [{…}]
0:{type: 'cm', id: 0, prime: false}

// 第 1 个 code, 执行复制操作 exp 0 -> exp3
1:{expId: 3, prime: false, code: Array(1), idQ: undefined}
code:(1) [{…}]
0:{op: 'copy', dest: {…}, src: Array(1)}
dest:{type: 'exp', prime: false, id: 3}
id:3
prime:false
type:'exp'
op:'copy'
src:(1) [{…}]
0:{type: 'exp', id: 0, prime: false}

对tExp 执行 pilCodeGen 运算:

pilCodeGen(ctx, puCtx.tExpId, false);

ctx 再次添加两个code, 分别:

// 第2个code, 执行复制操作 const 1 -> exp 1 
2:{expId: 1, prime: false, code: Array(1), idQ: undefined}
code:(1) [{…}]
0:{op: 'copy', dest: {…}, src: Array(1)}
length:1
expId:1
dest:{type: 'exp', prime: false, id: 1}
op:'copy'
src:(1) [{…}]
0:{type: 'const', id: 1, prime: false}

// 第3个code, 执行复制操作 exp 1 -> exp 2
3:{expId: 2, prime: false, code: Array(1), idQ: undefined}
code:(1) [{…}]
0:{op: 'copy', dest: {…}, src: Array(1)}
dest:{type: 'exp', prime: false, id: 2}
op:'copy'
src:(1) [{…}]
0:{type: 'exp', id: 1, prime: false}

生成puCtx的 结构为:

{tExpId: 2, fExpId: 3, h1Id: 1, h2Id: 2}
fExpId:3 // f 表达式 id
h1Id:1   // h1 承诺(cm) id 
h2Id:2     // h2 承诺 id 
tExpId:2   // t 表达式 id 

buildCode 整合所有的code, 共4个,分别放于first, i, last 中:

res.step2prev = buildCode(ctx);

和上述的code 顺序对应:

first:(4) [{…}, {…}, {…}, {…}]
0:{op: 'copy', dest: {…}, src: Array(1)}
dest:{type: 'exp', prime: false, id: 0}
op:'copy'
src:(1) [{…}]
0:{type: 'cm', id: 0, prime: false}

1:{op: 'copy', dest: {…}, src: Array(1)}
dest:{type: 'exp', prime: false, id: 3}
op:'copy'
src:(1) [{…}]
0:{type: 'exp', id: 0, prime: false}


2:{op: 'copy', dest: {…}, src: Array(1)}
dest:{type: 'exp', prime: false, id: 1}
op:'copy'
src:(1) [{…}]
0:{type: 'const', id: 1, prime: false}


3:{op: 'copy', dest: {…}, src: Array(1)}
dest:{type: 'exp', prime: false, id: 2}
op:'copy'
src:(1) [{…}]
0:{type: 'exp', id: 1, prime: false}

nCm2 表示为第2步的承诺个数。

res.nCm2 = pil.nCommitments - res.nCm1;

6. generateStep3

generatePermutationsLC

对置换进行处理,本例不需要,略过;

generatePlookupZ

对查找表进行处理。

下面的计算对应着Plooup 算法:

function generatePlookupZ(res, pil, ctx) {
    const E = new ExpressionOps();

    for (let i=0; i<pil.plookupIdentities.length; i++) { // 循环,对每个plookup操作分别处理
        const puCtx = res.puCtx[i];   
        puCtx.zId = pil.nCommitments++;  // 添加一个承诺


        const h1 = E.cm(puCtx.h1Id);     // 生成承诺表达式
        const h2 =  E.cm(puCtx.h2Id);        // 生成承诺表达式
        const h1p = E.cm(puCtx.h1Id, true);   //h1的下一步
        const f = E.exp(puCtx.fExpId);       // f 表达式
        const t = E.exp(puCtx.tExpId);       // t 表达式
        const tp = E.exp(puCtx.tExpId, true);   // t 表达式下一步
        const z = E.cm(puCtx.zId);             // z 表达式
        const zp = E.cm(puCtx.zId, true);     // z 表达式下一步

        if ( typeof pil.references["Global.L1"] === "undefined") throw new Error("Global.L1 must be defined");

        const l1 = E.const(pil.references["Global.L1"].id);

        const c1 = E.mul(l1,  E.sub(z, E.number(1)));  // 对应 z(g) = 1 约束条件
        c1.deg=2;
        puCtx.c1Id = pil.expressions.length; // 表达式的 id
        pil.expressions.push(c1);  
        pil.polIdentities.push({e: puCtx.c1Id}); //  最终表达式放于polIdentities 中
 
        const gamma = E.challenge("gamma");
        const beta = E.challenge("beta");

        const numExp = E.mul(
            E.mul(
                E.add(f, gamma),
                E.add(
                    E.add(
                        t,
                        E.mul(
                            tp,
                            beta
                        )
                    ),
                    E.mul(gamma,E.add(E.number(1), beta))
                )
            ),
            E.add(E.number(1), beta)
        );
        numExp.idQ = pil.nQ++;
        puCtx.numId = pil.expressions.length;  // 分子表达式id 
        numExp.keep = true;
        pil.expressions.push(numExp);       

        const denExp = E.mul(
            E.add(
                E.add(
                    h1,
                    E.mul(
                        h2,
                        beta
                    )
                ),
                E.mul(gamma,E.add(E.number(1), beta))
            ),
            E.add(
                E.add(
                    h2,
                    E.mul(
                        h1p,
                        beta
                    )
                ),
                E.mul(gamma,E.add(E.number(1), beta))
            )
        );
        denExp.idQ = pil.nQ++;
        puCtx.denId = pil.expressions.length; // 分母表达式 id 
        denExp.keep = true;
        pil.expressions.push(denExp);

        const num = E.exp(puCtx.numId);
        const den = E.exp(puCtx.denId);

        const c2 = E.sub(  E.mul(zp, den), E.mul(z, num)  ); // 对就上文公式 4(b)表达
        c2.deg=2;
        puCtx.c2Id = pil.expressions.length;   // c2 表达式 id 
        pil.expressions.push(c2);
        pil.polIdentities.push({e: puCtx.c2Id});    // 表达式约束放入poldIdentities中。

        pilCodeGen(ctx, puCtx.numId, false); // 生成分子表达式的code
        pilCodeGen(ctx, puCtx.denId, false);  // 生成分母表达式的code
    }
}

得到的puCtx 结构为:

  {
   "tExpId": 2,   // t 表达式id
   "fExpId": 3,   // f 表达式id 
   "h1Id": 1,     // h1 承诺id 
   "h2Id": 2,     // h2 承诺id 
   "zId": 3,      // z 承诺id 
   "c1Id": 4,     // c1 表达式id
   "numId": 5,     // num 表达式id
   "denId": 6,    // den 表达式id
   "c2Id": 7      // c2 表达式 id 
  }

generatePermutationZ

生成置换多项式Z, 本例不需要,略过

generateConnectionZ

生成ConnectinsZ, 本例不需要,略过

最后生成第code, 如下所示:

    res.step3prev = buildCode(ctx);

计算第三步的承诺个数:

    res.nCm3 = pil.nCommitments - res.nCm1 - res.nCm2;

7. generateConstraintPolynomial

module.exports = function generateConstraintPolynomial(res, pil, ctx, ctx2ns) {

    const E = new ExpressionOps();

    const vc = E.challenge("vc");
    let cExp = null;
    for (let i=0; i<pil.polIdentities.length; i++) {  // 将所有的约束表达式线性组合在一起
        const e = E.exp(pil.polIdentities[i].e);
        if (cExp) {
            cExp = E.add(E.mul(vc, cExp), e);
        } else {
            cExp = e;
        }
    }
    cExp.idQ = pil.nQ++;
    res.cExp = pil.expressions.length;  // 组合表达式的 id
    pil.expressions.push(cExp);

    for (let i=0; i<pil.expressions.length; i++) {
        if (typeof pil.expressions[i].idQ != "undefined") {
            pilCodeGen(ctx, i);                            
            pilCodeGen(ctx2ns, i, false, "evalQ");      //
        }
    }

    res.step4 = buildCode(ctx);
    res.step42ns = buildCode(ctx2ns);
}

参考

https://github.com/0xPolygonHermez/pil-stark/blob/main/test/stark_simple_plookup.test.js

https://eprint.iacr.org/2020/315.pdf

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容