零知识证明:零知识证明(Zero—Knowledge Proof),是由S.Goldwasser、S.Micali及C.Rackoff在20世纪80年代初提出的。它指的是证明者能够在不向验证者提供任何有用的信息的情况下,使验证者相信某个论断是正确的。零知识证明实质上是一种涉及两方或更多方的协议,即两方或更多方完成一项任务所需采取的一系列步骤。证明者向验证者证明并使其相信自己知道或拥有某一消息,但证明过程不能向验证者泄漏任何关于被证明消息的信息。大量事实证明,零知识证明在密码学中非常有用。如果能够将零知识证明用于验证,将可以有效解决许多问题。
零知识证明 zero-knowledge proofs,简称ZKPs,我们都知道区块链本身的一个关键优势就是透明性,但是在很多情况下,智能合约应用却出于各种商业或法律原因需要保障数据隐私,比如传入真实有效的数据来触发智能合约执行,但这涉及到信息会有泄露的风险。如何避免这种情况的发生呢?零知识证明的出现很好地解决了这个问题。
零知识证明(Zero Knowledge Proof)的研究最早始于1985年,由MIT教授Shafi Goldwasser, Silvio Micali 和 密码学大师Charles在《The Knowledge Complexity of Interactive Proof-Systems》论文中提出。正是这篇文章提出了 零知识证明 这个伟大概念,并逐步成为了现代密码学理论的根基之一,而Shafi Goldwasser和Silvio Micali也于2012 年获得了有“计算机界诺贝尔奖”之称的图灵奖。零知识证明系统所要完成的任务是「证明某一个事实并且不泄露知识」。这个过程就是零知识证明。
首先介绍低版本的代码:
首先看清楚版本号,在go/mod里面:
module awesomeProject16
go 1.18
require (
github.com/consensys/gnark v0.5.2
github.com/consensys/gnark-crypto v0.5.3
)
在终端输入:(切换版本包的版本号)
go get github.com/consensys/gnark@v0.5.2
go get github.com/consensys/gnark-crypto@v0.5.3
下面是gnark的源码:
package main
import (
"bytes"
"encoding/hex"
"fmt"
"github.com/consensys/gnark-crypto/ecc"
bn254 "github.com/consensys/gnark-crypto/ecc/bn254/fr/mimc"
"github.com/consensys/gnark/backend"
"github.com/consensys/gnark/backend/groth16"
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/std/hash/mimc"
"math/big"
)
type Circuit struct {
PreImage frontend.Variable //这是需要隐藏的原文
Hash frontend.Variable `gnark:",public"` //这是公开的Hash值(加密后的,他人无法得知真实内容)
}
func (circuit *Circuit) Define(curveID ecc.ID, api frontend.API) error {
mimc, _ := mimc.NewMiMC("seed", curveID, api)
mimc.Write(circuit.PreImage)
api.AssertIsEqual(circuit.Hash, mimc.Sum())
return nil
}
//Hash函数,根据原文得到Hash值
func mimcHash(data []byte) string {
f := bn254.NewMiMC("seed")
f.Write(data)
hash := f.Sum(nil)
hashInt := big.NewInt(0).SetBytes(hash)
return hashInt.String()
}
func main() {
//preImage := []byte{0x01, 0x02, 0x03}
preImage := []byte("我有家产三千万")
hash := mimcHash(preImage)
fmt.Printf("hash值是: %s\n", hash)
var circuit Circuit
r1cs, err := frontend.Compile(ecc.BN254, backend.GROTH16, &circuit)
if err != nil {
fmt.Printf("Compile failed : %v\n", err)
return
}
pk, vk, err := groth16.Setup(r1cs)
if err != nil {
fmt.Printf("Setup failed\n")
return
}
witness := &Circuit{
PreImage: frontend.Value(preImage),
Hash: frontend.Value(hash),
}
proof, err := groth16.Prove(r1cs, pk, witness)
if err != nil {
fmt.Printf("Prove failed: %v\n", err)
return
}
var proofBuffer bytes.Buffer
proofBuffer.Reset()
proof.WriteRawTo(&proofBuffer)
proofBuffer.Bytes()
var vkBuffer bytes.Buffer
vkBuffer.Reset()
vk.WriteRawTo(&vkBuffer)
vkBuffer.Bytes()
//fmt.Println("proof:", proof)
//fmt.Println("vk:", vk)
fmt.Printf("proof: %s\n", hex.EncodeToString(proofBuffer.Bytes()))
fmt.Printf("verifykey: %s\n", hex.EncodeToString(vkBuffer.Bytes()))
fmt.Println("proofBuffer.Bytes():", proofBuffer.Bytes())
fmt.Println("vkBuffer.Bytes()",vkBuffer.Bytes())
//fmt.Println(hex.DecodeString(hex.EncodeToString(proofBuffer.Bytes())))//将string类型转成[]byte
//VerifyProof(hash, vkBuffer.Bytes(), proofBuffer.Bytes())
//记录hash, vkbytes, proofbytes的值就可以勇于进行零知识证明验证是否是本人,本人无需出示原文件,只需出示hash, vkbytes, proofbytes即可。
proofbytes, _ := hex.DecodeString("27ade0a7bff8814b47de7ab820d57532353145b3c0aa897a567939edee285ce1254cf5a54adfe56d860e0700481da652237137f08cd4174cefd9c23d5c60b51504873ffc3f2cefca1c4eccfcd3ddf84aa78d32a87d72768972bd1502c82284470ffa61707ee11628e7d0cf0111fd0b034f72554dc75b621bfdc2168ebc2985a221bd018617bd553849a282c9b3a30fbdb09ed6ec2cd02da18e7fe53db423d3dc0db60054a9777315fb4f618c741dc96ed7d9261e4c20a94190802cdd563bbc661b141c3fc7bfab7e753bfd39a0c9956abfa7fdab964d71de3c80ff3dfa26451112e4d650c737be6b1b8a2ffc96b32061894619f299e6db5ad30427cafc76433e")
vkbytes, _ := hex.DecodeString("06a16d1f49bbe0640132ae85e86b10aa774d0f2ed1b89da4ea5199648679fa270af8f1b1a0a3d0bebe9a2ab0e9f9053c54fd8b609f38c9f851aba0f96d7620702bc330523ca579d1220c870ef25409525989ba9209536cdc9a113d9cb4fe403f2d75022f0e837699e4a0a060a0433536206d7804b0df5372e2bfd2ce43b130972bd6680f6abfbafec1b4551cb2990d51403d79a0199775ade4adf5bf8ce83e882685a7134549e3d5497c68de8015508268c9c7539941e6cfba86de1a1d28b4570657b89357a4e69b1c558b0d89a9c8e2752585de6d2e59f519c52f0b328bee4d182229f307b208e32fb8bb29e238f28543744d1aa065511769c728d06f9524e118b7c6aaa0e6fd70b480997b8c522168b6f98f1de0737d5f4f1d2ea27d3a751d0917b17234b0344a44b5af524267db7c82d9bebf319462bc2de49fe30d77645325fbdb28fddd7f6eb3d97ff2b284e66dcebbc3cbc1cc00881e37112cba41786b2bcc29687d4e807f7ed07f6bcd02159b9540f3ef55eb3463aeb1a25c6cc98bb8273bf06ff874eadc7d80418e894876eb14c69d152867130593f60f13bfba5f7f2c30d0ee29d461f03a448fface2bbdb4f5e28cc52d4ef7ccf78f0848d2cc37b40b709b1590fcd243d314acd2efe08e978f2c61662a1ee17d8f06ec3d69e476c22aedaf1d8f4841f4bfb7b43191d5390f1640f4c9bd6635a6c85799366b00e6f12810e7ffed58937d642bafd614174ca3a41e78340a438976d067e91368f682a42ff42c2d000f79c72c8b1a76739db73498c85f9bd958659645ad21626ce8e7320000000229b22d6ea68903dfda226c235df272a1cc6403c78318ffbc84f7b4623a5ad6de28ba81762e8739d9957d285d20cca76e82672236640e65e99395141e7613eefb1a4f24786381aff9c6ae29b165913e75144983f261d4c31558c7b08440f108a12780267560766d9b627b895d1aeae7841810ed19799bd32f04f395e9bdb1866c")
VerifyProof(hash, vkbytes, proofbytes)
//fmt.Println("proof:", proofBuffer.String())
//fmt.Println("vkBuffer.:", vkBuffer.String())
//VerifyProof(hash, proof, vk)
/*
publicWitness := &Circuit{
Hash: frontend.Value(hash),
}
//进行零知识证明校验
err = groth16.Verify(proof, vk, publicWitness) //这里用到了proof和vk需要记录下来
if err != nil {
fmt.Printf("零知识证明认证失败! %v\n", err)
return
} else {
fmt.Printf("零知识证明认证成功!\n")
}
*/
}
//零知识证明判断函数
func VerifyProof(hash string, verifyKey []byte, proofbytes []byte) (bool, error) {
publicWitness := &Circuit{
Hash: frontend.Value(hash),
}
proof := groth16.NewProof(ecc.BN254)
proof.ReadFrom(bytes.NewBuffer(proofbytes))
vk := groth16.NewVerifyingKey(ecc.BN254)
vk.ReadFrom(bytes.NewBuffer(verifyKey))
//进行零知识证明校验
err := groth16.Verify(proof, vk, publicWitness) //这里用到了proof和vk需要记录下来
if err != nil {
fmt.Printf("零知识证明认证失败! %v\n", err)
return false, err
} else {
fmt.Printf("零知识证明认证成功!\n")
return true, nil
}
}
然后介绍最新版本的代码:
注意:go语言的版本要在1.17以上!以下是go mod中各个包的版本:
//最新版本的gnark和gnark-crypto
github.com/consensys/gnark v0.7.1
github.com/consensys/gnark-crypto v0.7.0
module go_code/awesomeProject19
go 1.18
require (
//最新版本的gnark和gnark-crypto
github.com/consensys/gnark v0.7.1
github.com/consensys/gnark-crypto v0.7.0
)
require (
github.com/fxamacker/cbor/v2 v2.2.0 // indirect
github.com/mmcloughlin/addchain v0.4.0 // indirect
github.com/rs/zerolog v1.26.1 // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 // indirect
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect
)
代码:
package main
import (
"bytes"
"encoding/hex"
"fmt"
"github.com/consensys/gnark-crypto/ecc"
"github.com/consensys/gnark-crypto/hash"
"github.com/consensys/gnark/backend/groth16"
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/frontend/cs/r1cs"
"github.com/consensys/gnark/std/hash/mimc"
"math/big"
"os"
)
type Circuit struct {
// struct tag on a variable is optional
// default uses variable name and secret visibility.
PreImage frontend.Variable
Hash frontend.Variable `gnark:",public"`
}
// Define declares the circuit's constraints
// Hash = mimc(PreImage)
func (circuit *Circuit) Define(api frontend.API) error {
// hash function
mimc, _ := mimc.NewMiMC(api)
// specify constraints
// mimc(preImage) == hash
mimc.Write(circuit.PreImage)
api.AssertIsEqual(circuit.Hash, mimc.Sum())
return nil
}
//hash函数
func hashCalc(preImage string) string {
hi, _ := big.NewInt(0).SetString(preImage, 10)
h := hash.MIMC_BN254.New()
h.Write(hi.Bytes())
rd := h.Sum(nil)
r1 := big.NewInt(0).SetBytes(rd).String()
return r1
}
func main() {
// 外部系统生成Hash零知识证明电路
var circuit Circuit
ccs, err := frontend.Compile(ecc.BN254, r1cs.NewBuilder, &circuit)
if err != nil {
panic(err)
}
// groth16 zkSNARK: Setup
pk, vk, err := groth16.Setup(ccs)
if err != nil {
panic(err)
}
//根据原文计算hash值
//preImage := "16130099170765464552823636852555369511329944820189892919423002775646948828469"
preImage := "1"
hash := hashCalc(preImage)
fmt.Println("hash:", hash)
// witness definition
assignment := Circuit{
PreImage: preImage,
Hash: hash,
}
witness, err := frontend.NewWitness(&assignment, ecc.BN254)
if err != nil {
panic(err)
}
proof, err := groth16.Prove(ccs, pk, witness)
if err != nil {
panic(err)
}
var proofBuffer bytes.Buffer
proofBuffer.Reset()
proof.WriteRawTo(&proofBuffer)
proofBuffer.Bytes()
var vkBuffer bytes.Buffer
vkBuffer.Reset()
vk.WriteRawTo(&vkBuffer)
vkBuffer.Bytes()
fmt.Printf("proof: %s\n", hex.EncodeToString(proofBuffer.Bytes()))
fmt.Printf("verifykey: %s\n", hex.EncodeToString(vkBuffer.Bytes()))
//将上述产生的proof和verifykey字符串输出到文本中,方便复制粘贴!
fileName := "gnark.txt"
dstFile, err := os.Create(fileName)
if err != nil {
fmt.Println(err.Error())
return
}
defer dstFile.Close()
str := "原文:" + preImage + "\n" + "Hash:" + hash + "\nproof:" + hex.EncodeToString(proofBuffer.Bytes()) + "\n" + "verifykey:" + hex.EncodeToString(vkBuffer.Bytes())
dstFile.WriteString(str + "\n")
// VerifyProof 函数放到智能合约执行
r, err := VerifyProof(hash, vkBuffer.Bytes(), proofBuffer.Bytes())
if err != nil {
panic(err)
}
if r {
fmt.Println("验证通过!")
} else {
fmt.Println("验证失败!")
}
}
func VerifyProof(hash string, verifyKey []byte, publicWitness []byte) (bool, error) {
assignment1 := Circuit{
Hash: hash,
}
publicWitness1, err := frontend.NewWitness(&assignment1, ecc.BN254, frontend.PublicOnly())
if err != nil {
return false, err
}
proof := groth16.NewProof(ecc.BN254)
proof.ReadFrom(bytes.NewBuffer(publicWitness))
vk := groth16.NewVerifyingKey(ecc.BN254)
vk.ReadFrom(bytes.NewBuffer(verifyKey))
err = groth16.Verify(proof, vk, publicWitness1)
if err != nil {
return false, err
}
return true, nil
}
顾名思义,零知识证明就是既能充分证明自己是某种权益的合法拥有者,又不把有关的信息泄露出去——即给外界的“知识”为“零”。其实,零知识证明并不是什么新东西,早在16世纪的文艺复兴时期,意大利有两位数学家为竞争一元三次方程求根公式发现者的桂冠,就采用了零知识证明的方法。当时,数学家塔尔塔里雅和菲奥都宣称自己掌握了这个求根公式,为了证明自己没有说谎,又不把公式的具体内容公布出来(可能在当时数学公式也是一种技术秘密),他们摆开了擂台:双方各出30个一元三次方程给对方解,谁能全部解出,就说明谁掌握了这个公式。比赛结果显示,塔尔塔里雅解出了菲奥出的全部30个方程,而菲奥一个也解不出。于是人们相信塔尔塔里雅是一元三次方程求根公式的真正发现者,虽然当时除了塔尔塔里雅外,谁也不知道这个公式到底是个什么样子。从这个故事,我们可以初步了解零知识证明的概念。