🦧

collaborative-circomを触ってみた

2024/09/20に公開

概要

この記事ではcollaborative-circom(co-circomと呼ばれたりする)のサンプルコードを実行し、一連の流れを紹介します。
ローカルでMPCを構成し、secret sharing->proof->verifyまでやっていきましょう。

collaborative-circomとは?

co-circomはcollaborative SNARKをcircomで実行可能にしたツールです。
オーストリアのTaceoLabsが提供しています。
https://docs.taceo.io/poseidon.html

https://github.com/TaceoLabs/collaborative-circom

以下のような機能を提供しています。

  • zkとMPCを組み合わせた秘密計算ができる
  • Circomのシンタックスを変えたりしなくて良い
  • Snark.js、Solidity Veriferなどの環境も使える
  • Ciromのシンタックスはそのままで良い。groth16とPlonkも実行可能
  • 複数の参加者が共同でinputを提供できる

zkもMPCじゃんという話は一旦置いておきます

collaborative SNARKとは?

MPCはREP3 MPCというShamierの秘密分散法に近い線形秘密分散法をベースとしており、複数の当事者がそれぞれの入力を非公開にしたまま、共同でProofを生成することができます。Circuitへのinputを秘密分散法におけるshareに分割し、他の当事者へ共有します。それぞれの当事者はこのshareを元にProofを作成することができます。

詳細な原理はこちらの論文を参照ください。
https://eprint.iacr.org/2021/1530
カジュアルに原理を知りたい方にはこの動画の方が良いと思います。
https://youtu.be/A6gjVn_Gkf4?feature=shared
また、このcollaborative SNARK自体の実装はこちら
https://github.com/alex-ozdemir/collaborative-zksnark
そもそも証明者に負担がかかる作業をMPCで代替してるワケなのでこのアプローチでは計算と通信の複雑性が増すので高いリソースを持ったサーバーが必要です。
最近ではその点を改善した手法も提案されています。HyperPlonkを使っているようですね。
このアプローチだと大規模なCircuitも組めそう。
https://eprint.iacr.org/2024/143

Installation

先述の通り、CircuitやProofの生成は普段通りのCircom+SnarkJSで実装可能です。まずは公式の通りに進めて下さい。

最後に $sudo mv co-circom /usr/local/bin/を実行して任意のプロジェクトから呼び出せるようにしておくと便利です。
以下のようにバージョンが確認できれば完了です。

$co-circom -V
co-circom 0.5.0

Example

今回はPoseidon Hash(groth16)のサンプルを実行してみようと思います。

Poseidon Hash:https://scrapbox.io/bitpickers/Poseidon_Hash

まずはローカルにクローンして下さい。

$ git clone https://github.com/TaceoLabs/collaborative-circom.git
$ cd co-circom/co-circom/examples

さて、一連の処理を定義したshell scriptが用意されているので各実行ステップを見ていきましょう
https://github.com/TaceoLabs/collaborative-circom/blob/main/co-circom/co-circom/examples/groth16/run_full_poseidon.sh

ちなみにexamplesのメンテが間に合っていないようで、pathを更新しなければなりませんでした。
PRを投げているのでご参照ください。以降のサンプルコードはこのブランチとします。

Circuit

今回のcirocom実装はこんな感じで至ってシンプルです。

pragma circom 2.0.0;

include "poseidon.circom";

component main = Poseidon(2);

exampleではすでにコンパイルされ、r1csやzkeyファイル、verification_key.jsonが用意されています。
もし自前で用意したcircuitを使いたい場合は以下のように実行して下さい。circomに触れたことがある方にとっては不便ないと思います。
$circom poseidon.circom --r1cs

Network

MPCのNetwork Configはこのようにtomlファイルで定義されます。
この辺から普通のCircomとの変化を楽しむことができます。

[compiler]
allow_leaky_loops = false
link_library = ["./lib"]

[vm]
allow_leaky_logs = false

[network]
my_id = 0
bind_addr = "0.0.0.0:10000"
key_path = "./data/key0.der"
[[network.parties]]
id = 0
# normally we would use DNS name here such as localhost, but localhost under windows is resolved to ::1, which causes problems since we bind to ipv4 above
dns_name = "127.0.0.1:10000"
cert_path = "./data/cert0.der"
[[network.parties]]
id = 1
dns_name = "127.0.0.1:10001"
cert_path = "./data/cert1.der"
[[network.parties]]
id = 2
dns_name = "127.0.0.1:10002"
cert_path = "./data/cert2.der"

詳細なフィールドについてはこちらを参照ください。

MPC Partyのセットアップに際し、configをmy_idをユニークにしてpartyの数だけ用意します。
このサンプルでは3PCとなっています。
https://github.com/TaceoLabs/collaborative-circom/tree/main/co-circom/co-circom/examples/configs

split input into shares

MPCにおけるPreprocessing phaseです。
今回はシンプルな3PCなので一つのinputファイルを元に3つのshareに分割していきます。
input.jsonを用意して

{
    "inputs": [
        "-0x1515",
        "0x8392ef9d"
    ]
}

以下のコマンドを実行します。

$cargo run --release --bin co-circom -- split-input --circuit groth16/test_vectors/poseidon/circuit.circom --input groth16/test_vectors/poseidon/input.json --protocol REP3 --curve BN254 --out-dir groth16/test_vectors/poseidon --config groth16/test_vectors/poseidon/config.toml 

上記のコマンドではここまでみてきたcircuit,input,network configのファイルを受け取り、REP3 MPCプロトコルを使って秘密分散しています。

出力先に3つのファイル(input.json.0.shared、input.json.1.shared、input.json.2.shared)が生成されます。

このような実行結果が出れば成功です。

INFO run_split_input: co_circom: 333: Split input into shares successfully

run witness extension in MPC

ここからはMPCにおけるOnline phaseです。
先ほど作成したのsharesを使ってMPCでsecret-shared witnessファイルを生成します。

# run witness extension in MPC
$cargo run --release --bin co-circom -- generate-witness --input groth16/test_vectors/poseidon/input.json.0.shared --circuit groth16/test_vectors/poseidon/circuit.circom --protocol REP3 --curve BN254 --config configs/party1.toml --out groth16/test_vectors/poseidon/witness.wtns.0.shared &
cargo run --release --bin co-circom -- generate-witness --input groth16/test_vectors/poseidon/input.json.1.shared --circuit groth16/test_vectors/poseidon/circuit.circom --protocol REP3 --curve BN254 --config configs/party2.toml --out groth16/test_vectors/poseidon/witness.wtns.1.shared &
cargo run --release --bin co-circom -- generate-witness --input groth16/test_vectors/poseidon/input.json.2.shared --circuit groth16/test_vectors/poseidon/circuit.circom --protocol REP3 --curve BN254 --config configs/party3.toml --out groth16/test_vectors/poseidon/witness.wtns.2.shared

以下のようなログが出力され各partyがそれぞれ異なるwitnessファイルを得ていることがわかります。

INFO run_generate_witness: co_circom: 536: Party 0: Witness extension took 22.247 ms
INFO run_generate_witness: co_circom: 536: Party 2: Witness extension took 22.607 ms
INFO run_generate_witness: co_circom: 536: Party 1: Witness extension took 22.776 ms
INFO run_generate_witness: co_circom: 401: Witness successfully written to groth16/test_vectors/poseidon/witness.wtns.0.shared
INFO run_generate_witness: co_circom: 401: Witness successfully written to groth16/test_vectors/poseidon/witness.wtns.2.shared
INFO run_generate_witness: co_circom: 401: Witness successfully written to groth16/test_vectors/poseidon/witness.wtns.1.shared

run proving in MPC

いよいよProof生成です。

$cargo run --release --bin co-circom -- generate-proof groth16 --witness groth16/test_vectors/poseidon/witness.wtns.0.shared --zkey groth16/test_vectors/poseidon/poseidon.zkey --protocol REP3 --curve BN254 --config configs/party1.toml --out proof.0.json --public-input public_input.json &
cargo run --release --bin co-circom -- generate-proof groth16 --witness groth16/test_vectors/poseidon/witness.wtns.1.shared --zkey groth16/test_vectors/poseidon/poseidon.zkey --protocol REP3 --curve BN254 --config configs/party2.toml --out proof.1.json &
cargo run --release --bin co-circom -- generate-proof groth16 --witness groth16/test_vectors/poseidon/witness.wtns.2.shared --zkey groth16/test_vectors/poseidon/poseidon.zkey --protocol REP3 --curve BN254 --config configs/party3.toml --out proof.2.json

ここでも各partyがそれぞれproofを生成していることがわかります。

INFO run_generate_proof: co_circom: 502: Party 2: starting proof generation..
INFO run_generate_proof: co_circom: 502: Party 1: starting proof generation..
INFO run_generate_proof: co_circom: 502: Party 0: starting proof generation..
INFO run_generate_proof: co_circom: 506: Party 1: Proof generation took 63.335 ms
INFO run_generate_proof: co_circom: 506: Party 2: Proof generation took 63.895 ms
INFO run_generate_proof: co_circom: 506: Party 0: Proof generation took 63.842 ms
INFO run_generate_proof: co_circom: 542: Wrote proof to file proof.1.json
INFO run_generate_proof: co_circom: 635: Proof generation finished successfully
INFO run_generate_proof: co_circom: 542: Wrote proof to file proof.0.json
INFO run_generate_proof: co_circom: 542: Wrote proof to file proof.2.json
INFO run_generate_proof: co_circom: 635: Proof generation finished successfully
INFO run_generate_proof: co_circom: 630: Wrote public inputs to file public_input.json
INFO run_generate_proof: co_circom: 635: Proof generation finished successfully
    Finished `release` profile [optimized] target(s) in 0.28s

verify proof

最後にProofを検証しましょう。

# verify proof
$cargo run --release --bin co-circom -- verify groth16 --proof proof.0.json --vk groth16/test_vectors/poseidon/verification_key.json --public-input public_input.json --curve BN254
INFO run_verify: co_circom: 693: Proof verification took 5.084 ms
INFO run_verify: co_circom: 714: Proof verified successfully

まとめ

複数のprivate inputを元にProofを生成したり、secure computingの実行結果からsmart contractを発火させるなどzkpを用いたユースケースが広がりそうです。
Circomに馴染みがある方であれば割と簡単に使いこなせるのではないでしょうか。
他にもshareを統合する機能異なるMPCプロトコルに変換する機能(REP3からShamier's Secret Sharingへの変換など)が提供されています。
今後は単一のProverで実行した際の比較やベンチマークをとっていきたいです。

Discussion