zk-Snark开发入门篇之circom开发
July 14th, 2022

由于已有些许基于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.
More from Mobius

Skeleton

Skeleton

Skeleton