zkruのGo実装を試してみる
ここでは、擬似的に同じスコープ内でproofを証明&検証可能にすることで、L2→L1のデータ送受信を表現している。
テスト用の数式とその解からproofを作成し、L2→L1に送る(mvコマンドで同じディレクトリに配置する)。そのあとL1で送られてきたproofを検証するという流れ
https://scrapbox.io/files/60ffa19d5557f6001c4c6d94.png
これらを実行し、気になるコードをチェックしていき!
circuit gen
code:go
go run circuit.go
code:circuit.go
~
func New() *frontend.R1CS {
// create root constraint system
circuit := frontend.New()
nbAccounts := 16 // 16 accounts so we know that the proof length is 5
depth := 5 // size fo the inclusion proofs
batchSize := 1 // nbTranfers to batch in a proof
rollupCircuit(&circuit, batchSize, depth, nbAccounts)
r1cs := circuit.ToR1CS()
return r1cs
}
~
関数から得られる回路をrollupさせた後に、R1CS(回路を演算配列に変形させたもの)が生成される。
実際には、バイナリファイルが生成された.
あらかじめrollupしておくってのは知らなかった
r1cs setup
code:go
go run setup.go
code:setup.go
~
func main() {
circuitPath := filepath.Clean("../circuit.r1cs")
circuitName := filepath.Base(circuitPath)
vkPath := filepath.Join("../.", circuitName+".vk")
pkPath := filepath.Join("../.", circuitName+".pk")
var bigIntR1cs frontend.R1CS
if err := gob.Read(circuitPath, &bigIntR1cs, gurvy.BN256); err != nil {
fmt.Println("error:", err)
os.Exit(-1)
}
r1cs := backend_bn256.Cast(&bigIntR1cs)
fmt.Printf("%-30s %-30s %-d constraints\n", "loaded circuit", circuitPath, r1cs.NbConstraints)
// run setup
var pk groth16_bn256.ProvingKey
var vk groth16_bn256.VerifyingKey
start := time.Now()
groth16_bn256.Setup(&r1cs, &pk, &vk)
duration := time.Since(start)
fmt.Printf("%-30s %-30s %-30s\n", "setup completed", "", duration)
if err := gob.Write(vkPath, &vk, gurvy.BN256); err != nil {
fmt.Println("error:", err)
os.Exit(-1)
}
fmt.Printf("%-30s %s\n", "generated verifying key", vkPath)
if err := gob.Write(pkPath, &pk, gurvy.BN256); err != nil {
fmt.Println("error:", err)
os.Exit(-1)
}
fmt.Printf("%-30s %s\n", "generated proving key", pkPath)
}
Trusted Setupのお時間
ここでは、証明者と検証者に渡す鍵を作成している。通常、この作業は信頼できる第三者が必要で、MPCを使って行われる機密性の高い作業なんだけど、そんな雰囲気がない
groth16ってのがその役割なのかな?
L2 transfer_on_prove
code:go
go run .
code:output
% go run .
go: downloading golang.org/x/sys v0.0.0-20200805065543-0cf7623e9dbd
loaded circuit ../circuit.r1cs 45809 constraints
loaded proving key ../circuit.r1cs.pk
generated proof circuit.proof 1.2859406s
proof作成には時間がかかる。ここではなんかダウンロードしてるけど、2回目以降でも1sはかかった。
ここでは、circuit.proofとinput.publicが生成される
L1 transfer_on_verify
code:output
% go run .
loaded verifying key ../circuit.r1cs.vk
loaded input ../input.public 2 inputs
proof is valid ../circuit.proof 2.9694ms
proof作成に比べると、検証はすぐに終わる。
code:verify.go
// verifyCmd represents the verify command
func main() {
fInputPath = filepath.Clean("../input.public")
fVkPath := filepath.Clean("../circuit.r1cs.vk")
proofPath := filepath.Clean("../circuit.proof")
var vk groth16_bn256.VerifyingKey
if err := gob.Read(fVkPath, &vk, gurvy.BN256); err != nil {
fmt.Println("can't load verifying key")
fmt.Println(err)
os.Exit(-1)
}
fmt.Printf("%-30s %-30s\n", "loaded verifying key", fVkPath)
// parse input file
r1csInput := backend.NewAssignment()
err := r1csInput.ReadFile(fInputPath)
if err != nil {
fmt.Println("can't parse input", err)
os.Exit(-1)
}
fmt.Printf("%-30s %-30s %-d inputs\n", "loaded input", fInputPath, len(r1csInput))
// load proof
var proof groth16_bn256.Proof
if err := gob.Read(proofPath, &proof, gurvy.BN256); err != nil {
fmt.Println("can't parse proof", err)
os.Exit(-1)
}
// verify proof
start := time.Now()
result, err := groth16_bn256.Verify(&proof, &vk, r1csInput)
if err != nil || !result {
fmt.Printf("%-30s %-30s %-30s\n", "proof is invalid", proofPath, time.Since(start))
if err != nil {
fmt.Println(err)
}
os.Exit(-1)
}
fmt.Printf("%-30s %-30s %-30s\n", "proof is valid", proofPath, time.Since(start))
}