zk-Snark开发入门篇之circom开发

由于已有些许基于circom circuit的zkp开发经验,所以本文就围绕circom开发来做一个入门级report。首先,对circom的特点、开发流程做了一个简要介绍;而后,分享了一些常用的circom库及用例;最后,以Tornado为例做了一个应用案例分析。

Part 1. Circom开发概述

Circom是一个底层用rust实现的编译器,它可以编译用circom语言实现的circuit。它将circuit编译的结果以contraints的形式输出,这些constraints能被用于计算相应生成逻辑的proof。

Single Circuit

上图的circuit在最后一行 s3 <= 1 - s1 * s2 设置了constraint,并将 s3 输出为计算结果。需要注意的是,circom的input默认为private,output默认为public,同时,这些信号的可视性都可以通过显式定义进行修改。

Composable Circuit

如上图,通过对单个电路的组合,可以实现功能各异的复杂电路,并且在工程上也能通过调用circom库避免重复造轮子。

Circuit开发流程

  1. 配置开发环境

  2. 开发和编译circuit

    (1) --r1cs生成二进制的constraint文件; --wasm生成用于产生witness的文件.

    circom xxx.circom --r1cs --wasm --sym --c
    

    (2) 生成描述circuit描述

    snarkjs r1cs info xxx.r1cs
    
  3. js生成witness (以groth16为例), 用于链下测试circuit运算正确性

    const circuit = await wasm_tester("xx.circom");
    
    const INPUT = {
        "xx": xx,
        "xx": xx
    }
    
    const witness = await circuit.calculateWitness(INPUT, true);
    
  4. 生成proof

    (1) 下载trusted setup phase1 参数

    wget https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_10.ptau
    

    (2) line1: 导入trusted setup phase 1参数

    (3) line2: 进行phase2 trusted setup

    snarkjs groth16 setup xxx.r1cs powersOfTau28_hez_final_10.ptau circuit_0000.zkey
    snarkjs zkey contribute circuit_0000.zkey circuit_final.zkey --name="1st Contributor Name" -v -e="random text"
    snarkjs zkey export verificationkey circuit_final.zkey verification_key.json
    

    (4) 编译输出solidity verifier脚本

    snarkjs zkey export solidityverifier circuit_final.zkey Verifier.sol
    

    (5) js脚本生成proof,public signal

    const { proof, publicSignals } = await groth16.fullProve(Input{xxx}, "xxx.wasm","circuit_final.zkey");
    
  5. 链下验证proof

    snarkjs groth16 prove xxx.zkey witness.wtns proof.json public.json
    
  6. 链上验证proof

    (1) 生成solidity calldata

    const calldata = await groth16.exportSolidityCallData(proof, publicSignals);
    

Part 2. Circom开发常用库

Circomlib

包括加密原语的Circom实现、条件语法实现:

  • 条件语法实现: mux1.circom; mux2.circom; mux3.circom
  • hash function: poseiden.circom
  • digital signature algorithm: eddsa.circom

Circomlib-matrix

矩阵计算实现:

  • 矩阵加乘运算: matAdd.circom; matElemMul.circom; matElemPow.circom; matElemSum
  • 转置:transpose.circom
  • trace: trace.circom

snarkjs

zkSnark的js和Web Assembly实现:

  • 实现trusted setup multi-party ceremonies, 包括universal powers of tau ceremony以及phase2 ceremony
  • 可直接应用在node.js和浏览器中
  • 底层加密原语通过wasm实现,使用并行计算提升效率

Part 3. Circom应用案例 —— Tornado cash

Tornado cash的core版本只支持少数可选面值的匿名转账;在升级到nova版本后,由于通过UTXO的账本数据结构进行记账,在功能上实现突破,目前可以实现自定义任意面值的匿名转账。此外,有很重要的一个技术点:不论是core版本还是nova版本都使用了Merkle Tree Root数组链上存证校验。

Tornado-core

Tornado-core的账本数据结构较为简单,使用包含nullifier和secret信息(这两者都在链下随机生成,并由用户保管)的commitment作为转账凭证,通过验证nullifier状态避免double spending。链上链下的数据一致性则通过MerkleTree Root数组链上存证的方式进行校验。

  • 生成commitment
function generateDeposit() {
  let deposit = {
    secret: rbigint(31),
    nullifier: rbigint(31),
  }
  const preimage = Buffer.concat([deposit.nullifier.leInt2Buff(31), deposit.secret.leInt2Buff(31)])
  deposit.commitment = pedersenHash(preimage)
  return deposit
}
  • 取款校验circom核心逻辑:校验nullifier和MerkleTree Root
// verify nullifier 
    component hasher = CommitmentHasher();
    hasher.nullifier <== nullifier;
    hasher.secret <== secret;
    hasher.nullifierHash === nullifierHash;

// verify commitment based on hash(secret, nullifier), so which commitment is used for withdrawal is not revealed
    component tree = MerkleTreeChecker(levels);
    tree.leaf <== hasher.commitment;
    tree.root <== root;
    for (var i = 0; i < levels; i++) {
        tree.pathElements[i] <== pathElements[i];
        tree.pathIndices[i] <== pathIndices[i];
    }
  • 可选面值的局限性

由于core版本的记账数据结构不能对金额进行拆分,如果不使用固定面值,则会由于面值信息将存款人和提款人的信息联系上,从而使匿名转账在实作中失效。

Tornado-nova

相较于core版本,nova版本在设计理念上的创新,主要在于借鉴了Bitcoin的UTXO账本数据结构。它将每一笔转账以UTXO链式转移的方式实现,这样就使得转账金额可拆分,即时存款人和提款人将任意的存款金额信息公开,也可以通过拆分金额交易的方式实现匿名转账。(由于nova版本代码较长,在此就不贴出)

  • UTXO

UTXO的数据结构包含:金额、随机数salt、index;生成上链存证MerkleTree Root的叶子节点为UTXO的commitment(即金额、随机数salt以及公钥的poseidon哈希);nullifier为commitment、index以及私钥的poseidon哈希。

  • keypair解锁UTXO

TX中未解锁的outputs(即在使用时作为下次交易的inputs)需要使用仅由用户生成并持有的keypair进行解锁。

  • 交易circom核心逻辑

校验commitment、nullifier以及与MerkleTree Root的一致性。

Reference

  1. https://docs.circom.io/
  2. https://github.com/enricobottazzi/ZKverse
  3. https://github.com/iden3/circomlib
  4. https://libraries.io/npm/circomlib-matrix
  5. https://github.com/iden3/snarkjs
  6. https://github.com/tornadocash/tornado-core
  7. https://github.com/tornadocash/tornado-nova
Subscribe to Mobius
Receive the latest updates directly to your inbox.
Verification
This entry has been permanently stored onchain and signed by its creator.