cover_image

关于Groth16延展攻击

Kurt Pan XPTY
2022年08月17日 00:03

感谢Trapdoor Tech李星老师和安比实验室Even的帮助

1背景

Geometry是Kobi Gurkan 等人所在的一个新成立不久的研究组织,Kobi本人曾给ZK HACK 出过9道puzzles,这次又合作给出了这个关于Groth16延展攻击的新puzzle:ZK Hack x Geometry Puzzle I

该puzzle的主要内容在这个github仓库中:zkhack-groth-puzzle, 

英文题解参见这篇文章。 

背景信息和中文题解,强烈推荐参考李星老师的文章

SNARK领域中的延展攻击,是指给定敌手一个合法证明,敌手在不知道见证的条件下自己生成新的(对相同或不同公开输入的)合法证明。

对于相同公开输入的伪造证明的方法请参见这篇文章How to Generate a Groth16 Proof for Forgery,但是其中描述的两个攻击对于需要更改公开输入到特定值(如本题为攻击者的以太坊地址)时并不适用。

2理论

论文BKSV20 中的如下定理给出了Groth16抵抗延展攻击的一个条件:

定理:如果 是线性独立的,且 Span 。则Groth16在代数群模型中,离散对数假设下,可达到弱白盒模拟可抽取性质

如果条件“ 是线性独立的,且 Span ”不满足,则可以构造更改公开输入的延展攻击。本文下文即描述在不满足该条件时如何利用该点进行延展攻击伪造证明。

固定某个 (分别是见证和公开输入的下标),使得多项式 分别和 线性相关。记:

于是也线性相关,即存在某个线性因子使得:


Groth16的验证方程如下:

项分离出来后如下:

如果只考虑配对操作后群中的指数,验证方程如下:

注意这种形式下未知的分母已经被消去了。


如果更改公开输入值,结果变为:

和原始差了一项。通过修改方程右式中的如下,则可以在方程右边平衡左边原始结果多出来的

注意上式使用了线性相关关系:

其中

在公开参数中已知可用。而公开参数里存储的是

不能被直接使用。

于是有:

依然只考虑配对操作后群中的指数,上式等于:

注意到最后一行正是原始验证方程的右式,这意味着敌手改变了公共输入,并对新的语句伪造出了合法的证明,即成功进行了延展攻击。

3实操

具体到本题电路中,公开输入a和见证b的相关约束为: , 公开输入只有

,

其中 = new_a , = a,

const buildMalleabeC = async (stringified_c, matching_base_index, matching_pub_input, new_public_input, lf) => {
    const c = unstringifyBigInts(stringified_c)

    const {fd: fdZKey, sections: sectionsZKey} = await binFileUtils.readBinFile(finalZkeyPath, "zkey"21<<251<<23)
    const buffBasesC = await binFileUtils.readSection(fdZKey, sectionsZKey, 8)
    fdZKey.close()

    const curve = await buildBn128();
    const Fr = curve.Fr;
    const G1 = curve.G1;

    const new_pi = new Uint8Array(Fr.n8);
    Scalar.toRprLE(new_pi, 0, new_public_input, Fr.n8);

    const matching_pub = new Uint8Array(Fr.n8);
    Scalar.toRprLE(matching_pub, 0, matching_pub_input, Fr.n8);

    const sGIn = curve.G1.F.n8*2
    const matching_base = buffBasesC.slice(matching_base_index*sGIn, matching_base_index*sGIn + sGIn)
    
    const linear_factor = Fr.e(lf.toString(10))

    const delta_lf = Fr.mul(linear_factor, Fr.sub(matching_pub, new_pi));

    const p = await curve.G1.timesScalar(matching_base, delta_lf);
    const affine_c = G1.fromObject(c);

    const malleable_c = G1.toAffine(G1.add(affine_c, p))
    return stringifyBigInts(G1.toObject(malleable_c))
}

const linearDep = BigInt(-1)
const matchingBase = 0;
const malleable_c = await buildMalleabeC(proof.pi_c, matchingBase, BigInt(a), new_a, linearDep)
proof.pi_c = malleable_c;

4防御

  • 引入“伪平方约束”:对于一个不涉及任何约束的公开输入,添加一个简单的约束作为防伪造“护身符”,比如
  • 引入“魔法约束” 并非全部是零多项式,从而非零,从而变会使验证方程不通过。本质是确保公开输入线性无关从而满足论文中非延展的条件。

[1] Geometry: https://geometryresearch.xyz/about
[2] Kobi Gurkan: https://twitter.com/kobigurk
[3] ZK HACK: https://zkhack.dev/events/
[4] ZK Hack x Geometry Puzzle I: https://geometryresearch.xyz/notebook/zkhack-groth-challenge
[5] zkhack-groth-puzzle: https://github.com/geometryresearch/zkhack-groth-puzzle
[6] 这篇: https://geometry.xyz/notebook/groth16-malleability
[7] How to Generate a Groth16 Proof for Forgery: https://medium.com/ppio/how-to-generate-a-groth16-proof-for-forgery-9f857b0dcafd
[8] BKSV20: https://eprint.iacr.org/2020/811