Closed26

snarkjsのtutorialをやってみる

ywzxywzx

目標

なんとなく概念だけ理解しているSnarksを実際に使えるレベルまで理解する
https://github.com/iden3/snarkjs#guide

前提

現状概念的に理解していること

  • zkSNARKS = Succint Non interactive ARgument of Knowledge
  • C(Common Reference String) : generate a proving key(pk) and a verification key(vk) by taking a secret parameters lambda .
  • P(Prover): generate proof (pr=P(pk,x,w)) with public input(x)* and private witness(w)
  • V(Verifier): V(vk,x,pr) return true if hte proof(pr) is correct
    • →C(x,w)=true

trusted setupについて

  • the secret parameter lambda を知っている人は、V(vk,x,pr)がtrueになる虚偽のproofを生成することが可能
  • ただ、fake proofを作成できるだけで、private inputはわからない
    the secret parameter lambdaはtrusted setupと呼ばれ、この問題のことをtoxic wasteという
  • trusted setupのセキュリティーを高めるために、不特定多数の人にコミットメントしてもらうのがceremony
  • trusted setupが必要ないライブラリーも存在している(zkSTARKs)

結果

  • 各要素に関する理解度は高まった。
  • circomはかなり雰囲気で書いている。ハッシュの使い方などあっているかわからない。やりたいことと実装の制約を受けそう。
  • trusted setupが必要なZKPを使う場合、アプリケーションを動かすまでに時間がかかってしまう
  • solidityではhashの裏側の知識がなくてもなんとなく使えるが、circomでは裏側と知ってないとプロダクトレベルのアプリケーションを作成するのは厳しそう。Solidityのassembly書いてるような感じ
    • 例えば、なぜnulifierを31にしているのか、Pedersen(248)は何を意味するかなどなど...
  • 検証が通った時はとても爽快!!

疑問

  • witnessとはなんなのか?(完全には理解できなかった)
  • Phase1とPhase2にceremonyがなぜ別れているのか
  • zk friendlyなhash関数はpedersonとか、poseidonとかあるみたいだが、どれがメインで使われているものか?
  • circuit_final.zkey は公開しても大丈夫か?
  • ceremonyの中でもどの情報が漏れるとまずいのか?(contributeする際のrandom text(Entropy)? )

参考

https://docs.circom.io/getting-started/installation/

https://github.com/iden3/snarkjs

https://github.com/tornadocash/tornado-cli/blob/master/cli.js

https://github.com/tornadocash/tornado-core/blob/master/circuits/withdraw.circom

https://github.com/couger-inc/cream/tree/4c5142238674557a86dd890b202d40402dc751f8/packages/circuits/circom

https://github.com/Unitychain/zkvote-node

ywzxywzx

1. power of tau ceremony の開始

snarkjs powersoftau new bn128 14 pot14_0000.ptau -v
  • bn128 : curve type
  • 14 : ceremoryの最大参加人数。14は2*14=16.384になる。最大は28。これだけ集まったらすごい

pot14_0000.ptauの中身はbinary fileだった

ywzxywzx

2. ceremony への contribute

snarkjs powersoftau contribute pot14_0000.ptau pot14_0001.ptau --name="First contribution" -v
Enter a random text. (Entropy): first
[DEBUG] snarkJS: Calculating First Challenge Hash
[DEBUG] snarkJS: Calculate Initial Hash: tauG1
[DEBUG] snarkJS: Calculate Initial Hash: tauG2
[DEBUG] snarkJS: Calculate Initial Hash: alphaTauG1
[DEBUG] snarkJS: Calculate Initial Hash: betaTauG1
[DEBUG] snarkJS: processing: tauG1: 0/32767
[DEBUG] snarkJS: processing: tauG1: 16384/32767
[DEBUG] snarkJS: processing: tauG2: 0/16384
[DEBUG] snarkJS: processing: tauG2: 8192/16384
[DEBUG] snarkJS: processing: alphaTauG1: 0/16384
[DEBUG] snarkJS: processing: betaTauG1: 0/16384
[DEBUG] snarkJS: processing: betaTauG2: 0/1
[INFO]  snarkJS: Contribution Response Hash imported:
		7d33ece0 c684cd08 37fd5965 08e87289
		3cece108 2c3099ea f7da51ad 9abb2115
		7cecb423 152ed432 4a0ea5e7 c8513df3
		9a39082a 0d59d967 661efa9e da6efe95
[INFO]  snarkJS: Next Challenge Hash:
		75fffa7c c5ead890 2e6b5fec eda1c74d
		bc894378 b26fbe67 74ac00ee d7f7a89c
		2f395cf4 9b5eb64b e62dd184 e02e91d9
		1f8219a0 491cb1a2 eea51651 f69c277c
  • entropyをcommitする
  • entropyはコマンド実行後に記入できる
  • pot14_0000.ptauがinputされるファイルで、pot14_0001.ptauがcommit後のファイル
  • --name: リファレンスとして利用
ywzxywzx

3. 2回目のcontribution

snarkjs powersoftau contribute pot14_0001.ptau pot14_0002.ptau --name="Second contribution" -v -e="some random text"
  • -e : でentropyを指定できるみたい

これをみんなで繰り返す感じ
trusted setupの必要なzkのサービス作るのは、すごいハードがあるな

ywzxywzx

4. third party softwareを使って3回目のcontribution

snarkjs powersoftau export challenge pot14_0002.ptau challenge_0003
snarkjs powersoftau challenge contribute bn128 challenge_0003 response_0003 -e="some random text"
snarkjs powersoftau import response pot14_0002.ptau response_0003 pot14_0003.ptau -n="Third contribution name"
  • entropyを強固にするためにcontributionに多様性を持たせる
ywzxywzx

5. これまでのcontributionの検証

snarkjs powersoftau verify pot14_0003.ptau  
[INFO]  snarkJS: Powers Of tau file OK!
[INFO]  snarkJS: Next challenge hash:
		dde539d6 8125b3f5 94206538 c3b6d048
		923e868b b8520440 a705b519 cee10a1a
		4e4bd72c 16be5713 aa6003c1 b95015e0
		a39a504e 4de10e3c 392a5fad d9113d57
[INFO]  snarkJS: -----------------------------------------------------
[INFO]  snarkJS: Contribution #3: Third contribution name
[INFO]  snarkJS: Next Challenge:
		dde539d6 8125b3f5 94206538 c3b6d048
		923e868b b8520440 a705b519 cee10a1a
		4e4bd72c 16be5713 aa6003c1 b95015e0
		a39a504e 4de10e3c 392a5fad d9113d57
[INFO]  snarkJS: Response Hash:
		270ef72d cbfdb9d4 91de1f3d ad8c35cf
		5ba5d508 e288b582 2d58f0ae 2be9e754
		89b6cb84 c6953773 7016a2ee d86fb00b
		13276901 5b1ab513 14a889a2 5c06e9d3
[INFO]  snarkJS: Response Hash:
		223bb4d6 385cd562 ba04d472 a6177907
		b40fed6a 78e86e0f ff826a79 7574ccb5
		688bec2e 7380b249 3b303baf c6ad67a8
		57a4f5cc 32838cdb 8f0ce657 2c5936a3
[INFO]  snarkJS: Powers Of tau file OK!
[INFO]  snarkJS: -----------------------------------------------------
[INFO]  snarkJS: Contribution #2: Second contribution
[INFO]  snarkJS: Next Challenge:
		223bb4d6 385cd562 ba04d472 a6177907
		b40fed6a 78e86e0f ff826a79 7574ccb5
		688bec2e 7380b249 3b303baf c6ad67a8
		57a4f5cc 32838cdb 8f0ce657 2c5936a3
[INFO]  snarkJS: Response Hash:
		152c6828 e1a93580 68741d59 32188439
		d6d6b9be fb4427b4 5e414062 c3474965
		c21c5bc8 6b043e6d 14f7abfb 80e04719
		40737b9c 97c07ebd 3a6ced7c 6b80e209
[INFO]  snarkJS: Response Hash:
		75fffa7c c5ead890 2e6b5fec eda1c74d
		bc894378 b26fbe67 74ac00ee d7f7a89c
		2f395cf4 9b5eb64b e62dd184 e02e91d9
		1f8219a0 491cb1a2 eea51651 f69c277c
[INFO]  snarkJS: Powers Of tau file OK!
[INFO]  snarkJS: -----------------------------------------------------
[INFO]  snarkJS: Contribution #1: First contribution
[INFO]  snarkJS: Next Challenge:
		75fffa7c c5ead890 2e6b5fec eda1c74d
		bc894378 b26fbe67 74ac00ee d7f7a89c
		2f395cf4 9b5eb64b e62dd184 e02e91d9
		1f8219a0 491cb1a2 eea51651 f69c277c
[INFO]  snarkJS: Response Hash:
		7d33ece0 c684cd08 37fd5965 08e87289
		3cece108 2c3099ea f7da51ad 9abb2115
		7cecb423 152ed432 4a0ea5e7 c8513df3
		9a39082a 0d59d967 661efa9e da6efe95
[INFO]  snarkJS: Response Hash:
		bc0bde79 80381fa6 42b20975 91dd83f1
		ed15b003 e15c3552 0af32c95 eb519149
		2a6f3175 215635cf c10e6098 e2c612d0
		ca84f1a9 f90b5333 560c8af5 9b9209f4
[INFO]  snarkJS: -----------------------------------------------------
[WARN]  snarkJS: this file does not contain phase2 precalculated values. Please run:
   snarkjs "powersoftau preparephase2" to prepare this file to be used in the phase2 ceremony.
[INFO]  snarkJS: Powers of Tau Ok!

ちゃんとできてる!

ywzxywzx

6. random beacon の適応

snarkjs powersoftau beacon pot14_0003.ptau pot14_beacon.ptau 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 10 -n="Final Beacon"
  • a random beacon = a source of public randomness that is not available before a fixed time.
    • 遅延ハッシュ関数?
  • 10 : 2^10 回反復して実行
  • ここまででフェーズ1完了
ywzxywzx

7. phase 2 の準備

snarkjs powersoftau prepare phase2 pot14_beacon.ptau pot14_final.ptau -v
  • ここまでで一番処理が重い
  • 今までのcontributionを用いて、特定の計算を暗号化している?数学的に使えるようにしている?
  • 最終的なアウトプットはthe circuit proving and verification keys

実際にプロダクトローンチするときはこのプロセスはワクワクするだろうなー

ywzxywzx

8. final ptau の検証

snarkjs powersoftau verify pot14_final.ptau

これにてtrusted setupは終了!

ywzxywzx

9. circuit の作成

cat <<EOT > circuit.circom
pragma circom 2.0.0;

template Multiplier(n) {
    signal input a;
    signal input b;
    signal output c;

    signal int[n];

    int[0] <== a*a + b;
    for (var i=1; i<n; i++) {
    int[i] <== int[i-1]*int[i-1] + b;
    }

    c <== int[n-1];
}

component main = Multiplier(1000);
EOT
ywzxywzx

10. Compile the circuit

circom circuit.circom --r1cs --wasm --sym
  • r1cs : 回路の制約システムのbinary file
  • wasm : witnessを生成するためのwasmファイルの生成
  • sym : symbolファイル. r1csを読みやすくするファイル
ywzxywzx

11. circuitの確認

=> % snarkjs r1cs info circuit.r1cs                                                                                                                                                                22:56:44
[INFO]  snarkJS: Curve: bn-128
[INFO]  snarkJS: # of Wires: 1003
[INFO]  snarkJS: # of Constraints: 1000
[INFO]  snarkJS: # of Private Inputs: 2
[INFO]  snarkJS: # of Public Inputs: 0
[INFO]  snarkJS: # of Labels: 1004
[INFO]  snarkJS: # of Outputs: 1
  • 今回、'a,b' はpublic inputなので、publicが2、privateが0になっている
ywzxywzx

12. constraintsの確認

snarkjs r1cs print circuit.r1cs circuit.sym
  • constraintsを理解できてないので要復習
ywzxywzx

13. r1csをjsonに変換

snarkjs r1cs export json circuit.r1cs circuit.r1cs.json
cat circuit.r1cs.json
ywzxywzx

14. witnessの計算

  • inputの作成
cat <<EOT > input.json
{"a": 3, "b": 11}
EOT
  • witnessの作成
cd circuit_js
node generate_witness.js circuit.wasm ../input.json ../witness.wtns
  • 生成されたwitnessをr1csで確認
cd ..
snarkjs wtns check circuit.r1cs witness.wtns

疑問

  • witnessはproofを生成するごとに必要?
ywzxywzx

15. Setup

proofを生成するためのsetup。Gwoth16は各回路毎にceremoryが必要

// Plonk
snarkjs plonk setup circuit.r1cs pot14_final.ptau circuit_final.zkey

// Fflonk
snarkjs fflonk setup circuit.r1cs pot14_final.ptau circuit.zkey

// Groth 16
snarkjs groth16 setup circuit.r1cs pot14_final.ptau circuit_0000.zkey
  • zkey : proving keyとverification keyがある

今回はGroth16を選択します

ywzxywzx

16 ~ phase 2 ceremony

// first contribute
snarkjs zkey contribute circuit_0000.zkey circuit_0001.zkey --name="1st Contributor Name" -v

// second contribute
snarkjs zkey contribute circuit_0001.zkey circuit_0002.zkey --name="Second contribution Name" -v -e="Another random entropy"

// third contribute
snarkjs zkey export bellman circuit_0002.zkey  challenge_phase2_0003
snarkjs zkey bellman contribute bn128 challenge_phase2_0003 response_phase2_0003 -e="some random text"
snarkjs zkey import bellman circuit_0002.zkey response_phase2_0003 circuit_0003.zkey -n="Third contribution name"

// verity zkey
snarkjs zkey verify circuit.r1cs pot14_final.ptau circuit_0003.zkey

// apply random beacon
snarkjs zkey beacon circuit_0003.zkey circuit_final.zkey 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 10 -n="Final Beacon phase2"

// verify final zkey
snarkjs zkey verify circuit.r1cs pot14_final.ptau circuit_final.zkey

// export verification key
snarkjs zkey export verificationkey circuit_final.zkey verification_key.json

疑問

  • phase1とphase2のceremonyの違い
ywzxywzx

23. proofの作成

// plonk
snarkjs plonk prove circuit_final.zkey witness.wtns proof.json public.json

// fflonk
snarkjs fflonk prove circuit.zkey witness.wtns proof.json public.json

// groth16
snarkjs groth16 prove circuit_final.zkey witness.wtns proof.json public.json
  • proof.json : proofが含まれている
  • public.json : public inputとoutputが含まれている
ywzxywzx

24. proofの証明

snarkjs plonk verify verification_key.json public.json proof.json
or
snarkjs fflonk verify verification_key.json public.json proof.json
or
snarkjs groth16 verify verification_key.json public.json proof.json

実際の決行結果。OKって出てる!

% snarkjs groth16 verify verification_key.json public.json proof.json                                                                                                                           10:22:54

[INFO]  snarkJS: OK!
ywzxywzx

25. Solidityのverifierを生成

snarkjs zkey export solidityverifier circuit_final.zkey verifier.sol

生成されたSolidityファイル

// SPDX-License-Identifier: GPL-3.0
/*
    Copyright 2021 0KIMS association.

    This file is generated with [snarkJS](https://github.com/iden3/snarkjs).

    snarkJS is a free software: you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    snarkJS is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
    License for more details.

    You should have received a copy of the GNU General Public License
    along with snarkJS. If not, see <https://www.gnu.org/licenses/>.
*/

pragma solidity >=0.7.0 <0.9.0;

contract Groth16Verifier {
    // Scalar field size
    uint256 constant r    = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
    // Base field size
    uint256 constant q   = 21888242871839275222246405745257275088696311157297823662689037894645226208583;

    // Verification Key data
    uint256 constant alphax  = 18712674356861626896757009025687602725210820216304635264620479592450406329388;
    uint256 constant alphay  = 21201160479174563788085618814045375892998967271910465805550111138485812562384;
    uint256 constant betax1  = 6397767952456345111109890018805147657449482188663200202099441941382684922249;
    uint256 constant betax2  = 1031914164219788346728201756331336046254081657552143577869826750276546580376;
    uint256 constant betay1  = 10314878877228187607647061624746478475559001084584511583082950439560380411240;
    uint256 constant betay2  = 18647239902447966464243128842451132848313496915470151596628105008731376078483;
    uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;
    uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;
    uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531;
    uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930;
    uint256 constant deltax1 = 14133835417711969824957902269297611337435395223355120168018193341228421792743;
    uint256 constant deltax2 = 14489197459206398478831836706999721375348172822070685587229927545739695521998;
    uint256 constant deltay1 = 14876284108737680636580574787030636686006864903929862157929504818767194594337;
    uint256 constant deltay2 = 18773531984080155592167771121180341848472115244847951389567955172215104945974;

    
    uint256 constant IC0x = 9537301685309826448508211352626541730374335540610193167484208189129108696235;
    uint256 constant IC0y = 1952344178142922204566422550369216688006328914188700713187179877639647350870;
    
    uint256 constant IC1x = 7179809393906979726414314139910248955072516835648097610001921229168923677096;
    uint256 constant IC1y = 9139291606196857545346718115880521495052143453800812335542582109766699660651;
    
 
    // Memory data
    uint16 constant pVk = 0;
    uint16 constant pPairing = 128;

    uint16 constant pLastMem = 896;

    function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[1] calldata _pubSignals) public view returns (bool) {
        assembly {
            function checkField(v) {
                if iszero(lt(v, q)) {
                    mstore(0, 0)
                    return(0, 0x20)
                }
            }
            
            // G1 function to multiply a G1 value(x,y) to value in an address
            function g1_mulAccC(pR, x, y, s) {
                let success
                let mIn := mload(0x40)
                mstore(mIn, x)
                mstore(add(mIn, 32), y)
                mstore(add(mIn, 64), s)

                success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64)

                if iszero(success) {
                    mstore(0, 0)
                    return(0, 0x20)
                }

                mstore(add(mIn, 64), mload(pR))
                mstore(add(mIn, 96), mload(add(pR, 32)))

                success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)

                if iszero(success) {
                    mstore(0, 0)
                    return(0, 0x20)
                }
            }

            function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk {
                let _pPairing := add(pMem, pPairing)
                let _pVk := add(pMem, pVk)

                mstore(_pVk, IC0x)
                mstore(add(_pVk, 32), IC0y)

                // Compute the linear combination vk_x
                
                g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0)))
                

                // -A
                mstore(_pPairing, calldataload(pA))
                mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q))

                // B
                mstore(add(_pPairing, 64), calldataload(pB))
                mstore(add(_pPairing, 96), calldataload(add(pB, 32)))
                mstore(add(_pPairing, 128), calldataload(add(pB, 64)))
                mstore(add(_pPairing, 160), calldataload(add(pB, 96)))

                // alpha1
                mstore(add(_pPairing, 192), alphax)
                mstore(add(_pPairing, 224), alphay)

                // beta2
                mstore(add(_pPairing, 256), betax1)
                mstore(add(_pPairing, 288), betax2)
                mstore(add(_pPairing, 320), betay1)
                mstore(add(_pPairing, 352), betay2)

                // vk_x
                mstore(add(_pPairing, 384), mload(add(pMem, pVk)))
                mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32))))


                // gamma2
                mstore(add(_pPairing, 448), gammax1)
                mstore(add(_pPairing, 480), gammax2)
                mstore(add(_pPairing, 512), gammay1)
                mstore(add(_pPairing, 544), gammay2)

                // C
                mstore(add(_pPairing, 576), calldataload(pC))
                mstore(add(_pPairing, 608), calldataload(add(pC, 32)))

                // delta2
                mstore(add(_pPairing, 640), deltax1)
                mstore(add(_pPairing, 672), deltax2)
                mstore(add(_pPairing, 704), deltay1)
                mstore(add(_pPairing, 736), deltay2)


                let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20)

                isOk := and(success, mload(_pPairing))
            }

            let pMem := mload(0x40)
            mstore(0x40, add(pMem, pLastMem))

            // Validate that all evaluations ∈ F
            
            checkField(calldataload(add(_pubSignals, 0)))
            
            checkField(calldataload(add(_pubSignals, 32)))
            

            // Validate all evaluations
            let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem)

            mstore(0, isValid)
             return(0, 0x20)
         }
     }
 }

ywzxywzx

26. verification callのsimulate

snarkjs zkey export soliditycalldata public.json proof.json
["0x0ced66b67be26ba389655ae6cd691bce15f7d74d97386298c6dce1d08c5c4159", "0x03b54cc7d51b23a098610508ca7f8aebbeede7957e4cbdca2d4ed949bb5e8da6"],[["0x297ddc4b1632f13102e94fb43badf46cdcbce73e2be532127c0eb3a25157e630", "0x07d7b75f11585ea1c13c686376237bafb6b0e1310b0bfaa0869973c4ee7527aa"],["0x2d206c8f003d6b9ba75d2b6c5af4f780843cb52780ade3ae4f5ad0f978ba23a2", "0x10ee655c7e7791a11656898ccc3bff9b1117853384c40b9ae455c3933811dd6a"]],["0x295b4dd1b274370a738efdc3a316edb10073752ef06104df7612645af7ba24e4", "0x01e2e6bef279af98d203cf1834bf2b42a8a5ce6ff211eb2a5f1de7e3b53f2819"],["0x110d778eaf8b8ef7ac10f8ac239a14df0eb292a8d1b71340d527b26301a9ab08"]

実さにRemixで試してみたらOKでた!いえい!

ywzxywzx

extra. 他のinputでも試してみる

tutorialは26までだったが、他のinputでも試してみる。

input.jsonを下記に変更する。

{"a": 2, "b": 5}

proofの生成コマンドが下記なので、おそらくwitnessを作成してproofを作成する必要がありそう。

snarkjs groth16 prove circuit_final.zkey witness.wtns proof.json public.json

下記でwitnessとproofの生成

cd circuit_js
node generate_witness.js circuit.wasm ../input.json ../witness.wtns
cd ..
snarkjs groth16 prove circuit_final.zkey witness.wtns proof.json public.json

コマンドで検証もOK

% snarkjs groth16 verify verification_key.json public.json proof.json                                                                                                                           12:01:04
[INFO]  snarkJS: OK!

solidityでも検証OK

snarkjs zkey export soliditycalldata public.json proof.json                                                                                                                                   12:01:12
["0x2b2513f0f33bfc25e7c81ad9ccb097a9829d7ca5c507d0187448910d5df3d306", "0x1fd8fa7544bf488786d9439cbac505ee8801e76b4f9f6f2b9f3e2afab28ad9f8"],[["0x02bc53faaca394d0a5590d284bdc6adf48a12861e205a536aad510970a402800", "0x270b7481ffd318531e996a1acf19277d8efbdcf482545471664b8fe0198d1cb3"],["0x26e22fdb64748352270c9767cf89700f627b97b7acf0a6037cfec77fd200c8ed", "0x1c2addc89a9ff4a52baa1ff3b32e077d0308f738ae24c1c12a4e9c8e91075264"]],["0x1dfb4ee55e879d7630e0284919f032fb6bafe3c92b662fa67192b93349d9f659", "0x0baf9937752a47a1feb5319a64438884598388775131b6cfee721047912b4740"],["0x1b616cda5326dd65179e81b6c84754a4e71ffd776951b5949fb1d4aebb1a5830"]

素晴らしい!!

ywzxywzx

extra 2. public input private inputで試してみる

サンプルの回路は膨大な計算の結果が正しいことを証明するものであった。つまり、計算量の圧縮 がメイン。

tornado cashのように、情報の秘匿の文脈で、private inputpublic inputの回路を作成してみる。

回路

pragma circom 2.0.0;

include "./node_modules/circomlib/circuits/bitify.circom";
include "./node_modules/circomlib/circuits/pedersen.circom";

template SecretHasher() {
    signal input secret;
    signal output secretHash;

    component secretHasher = Pedersen(248);
    component secretBits = Num2Bits(248);

    secretBits.in <== secret;

    for (var i = 0; i < 248; i++) {
        secretHasher.in[i] <== secretBits.out[i];
    }

    secretHash <== secretHasher.out[0];
}

template Main() {
    signal input secretHash;
    signal input secret;

    component hasher = SecretHasher();
    hasher.secret <== secret;

    secretHash === hasher.secretHash;
}

component main {public [secretHash]} = Main();
  • secretをprivate, secretHashをpublicにして、secretを持っているかの証明をする回路

pedersen hashの生成

secret生成スクリプトはこちら

const snarkjs = require('snarkjs');
const crypto = require('crypto');
const circomlib = require('circomlib');

/** Generate random number of specified byte length */
const rbigint = (nbytes) => snarkjs.bigInt.leBuff2int(crypto.randomBytes(nbytes));

/** Compute pedersen hash */
const pedersenHash = (data) => circomlib.babyJub.unpackPoint(circomlib.pedersenHash.hash(data))[0];

const main = () => {
  const secret = rbigint(31);
  const hash = pedersenHash(secret.leInt2Buff(31));

  console.log({secret, hash})
}

main();

注意input.json

input.jsonの値は""で囲む必要あり。囲んでなくて半日潰しました...

input.json
{
  "secretHash": "19453594387736930619599466479227236853954336336379209455419802476697169222580",
  "secret": "307007605995433306163765064475084281166882259195083713142912403572210678838"
}

これでも成功しました!!!
検証が通った時の爽快感半端ない!!!!

ywzxywzx

最終的に必要なファイルの整理

ファイルの整理

  • ceremonyで利用したファイルはcircuit_final.zkeyとverification_key.jsonに集約されてそう
  • witnessの生成に必要 : circuit.wasm, *input.json → witness.wtns の生成
  • proofの生成に必要 : circuit_final.zkey, witness.wtns → proof.json, public.json の生成
  • proofの証明に必要 : verification_key.json, public.json, proof.json

必要なファイルは下記だと思う

  • circuit.wasm
  • circuit_final.zkey
  • verification_key.json
ywzxywzx

extra.3 nodeでも検証

スクリプト

const snarkjs = require("snarkjs");
const fs = require("fs");

async function run() {
    const { proof, publicSignals } = await snarkjs.groth16.fullProve(
      {
        "secretHash": "19453594387736930619599466479227236853954336336379209455419802476697169222580",
        "secret": "307007605995433306163765064475084281166882259195083713142912403572210678838"
      },
      "circuit_js/circuit.wasm",
      "circuit_final.zkey"
    );

    console.log("Proof: ");
    console.log(JSON.stringify(proof, null, 1));

    const vKey = JSON.parse(fs.readFileSync("verification_key.json"));

    const res = await snarkjs.groth16.verify(vKey, publicSignals, proof);

    if (res === true) {
        console.log("Verification OK");
    } else {
        console.log("Invalid proof");
    }
}

run().then(() => {
    process.exit(0);
});

実行結果

node prove.js                                                                          15:16:52
Proof:
{
 "pi_a": [
  "86048593150950050416241473609597328465285085619181514193205893362069857571",
  "1788633298711708113870817439486285699612181339402462547222797953331671124868",
  "1"
 ],
 "pi_b": [
  [
   "16877126577791499277007026491207892846082136364207622921313219034881942764527",
   "11627221366399766622862897735658733668527736339150086600857836881335501243706"
  ],
  [
   "8151184030359756013363479050756600295631267721992385801030064926734001663143",
   "10579943309315321115337047601815312554968185820256372855285038387730557873138"
  ],
  [
   "1",
   "0"
  ]
 ],
 "pi_c": [
  "18967710012431226672710835636120361308820851234332074607104613596313474743256",
  "8352153606170882598292782926671891849774642674591250568454485445181181313544",
  "1"
 ],
 "protocol": "groth16",
 "curve": "bn128"
}
Verification OK

できた!!!

ywzxywzx

proofの生成からsolidity verificationまで

cd circuit_js && 
node generate_witness.js circuit.wasm ../input.json ../witness.wtns &&
cd .. && 
snarkjs groth16 prove circuit_final.zkey witness.wtns proof.json public.json && 
snarkjs groth16 verify verification_key.json public.json proof.json &&
snarkjs zkey export soliditycalldata public.json proof.json 
このスクラップは2023/11/10にクローズされました