本文介绍了在以太坊等区块链中,为了实现零知识证明(ZKP)并提高效率,对哈希函数的需求。传统哈希函数如SHA-256在区块链环境中计算成本高昂,因此提出了Pedersen哈希(基于椭圆曲线数学)和Poseidon哈希(基于有限域计算)作为替代方案,并提供了代码示例。
以太坊正在快速发展,我们现在可以创建零知识证明 (ZKP) 来证明交易,而无需泄露私人信息。这些通常是 SNARK、STARK 和 Bulletproof,这些方法可以有效地将交易添加到区块链上,并且不需要大量的检查。但是,我们现有的哈希方法并非设计为在区块链基础设施中良好运行,因此我们必须找到替代方案。
使用零知识证明电路,我们通常必须证明某个加密哈希函数原像的知识。对于:
H( a)= b
a 是 b 的原像。此原像可能是密钥、密码或某人的私人信息。因此,我们可以通过哈希其数字形式来证明几乎任何事情。
因此,此证明的核心是哈希方法的使用及其与区块链的集成。为此,我们现有的哈希方法在计算时间和创建 ZKP 电路方面可能成本高昂。这可能会增加 gas 成本。总的来说,我们需要将哈希方法约束为有限域运算,因为这些运算符合我们的区块链智能合约方法,并允许我们有效地构建 ZKP 电路。这些方法是 Pedersen 哈希 (它使用椭圆曲线数学)和 Poseidon 哈希 (它使用有限域计算)。
我很幸运能与 Merkle-Damgård (MD2/MD4/MD5) 哈希方法的两位共同发明人 Ralph Merkle 和 Ivan Damgård 交谈。因此,哈希方法的发展一直是网络安全的真正奇迹,其中任何具有 SHA-256 安全级别的都可以被视为安全的。如果我们看一下区块链,ECDSA 签名存在一个威胁,但每个区块的安全性都通过 SHA-256 和 Merkle 树来保护。因此,所有已创建的区块(以及未来的区块)都是安全的,即使交易签名存在被破解的风险。
我们现在使用的三种核心哈希方法是 SHA-256、SHA-3(又名 Keccak)和 Blake2。最流行的是 SHA-256,它使用 MD 类型的结构:
参考 [ 这里]
参考 [ 这里]
我们可以看到,存在大量的位移 (>>>),并使用了 NOT(带有向下线的线)、XOR(带有圆圈的“+”)和 AND(向上的箭头)。红色部分对 SHA-256 进行以 2³² 为模的加法。
Pedersen 哈希涉及将消息拆分为 k 个块( m 1, m 2 … mk),然后使用这些块基于多个生成器点( G 1, G 2 … Gk)生成一系列椭圆曲线点。然后,哈希是所创建点的加法: H = m 1. G 1+ m 2. G 2+ m 3. G 3。
如果我们使用三个块,则将消息分为三个:
m = m 1|| m 2|| m 3
然后,我们生成三个间隔良好的生成器点:
G 1, G 2, G 3
免费加入 Medium 即可获得这位作家的更新。
Pedersen 哈希然后是:
Hash =( m 1). G 1+( m 2). G 2+( m 3). G 3
其中 ( m 1). G 1, ( m 2). G 2 和 ( m 3). G 3 是曲线上的点。
让我们采用 Tiny Jubjub 曲线上的三个点 [ 这里]:
gx1,_ := new(big.Int).SetString("18930368022820495955728484915491405972470733850014661777449844430438130630919",10 )
gy1,_ := new(big.Int).SetString("0",10)
gx2,_ := new(big.Int).SetString("12576558175125128246374296641007938322302911659474853157208587689687229831284",10 )
gy2,_ := new(big.Int).SetString("3",10)
gx3,_ := new(big.Int).SetString("18869612826929474874643025262839512853875949931275176402223244870011103237908",10 )
gy3,_ := new(big.Int).SetString("5",10)
然后我们可以使用以下代码计算哈希值 [ 这里]:
package main
import (
"fmt"
"github.com/iden3/go-iden3-crypto/v2/babyjub"
"math/big"
"encoding/hex"
"os"
)
func splitMessage(inStr string, size int) []string { // splitMessage 函数,将输入字符串 inStr 分割成指定大小 size 的切片
var elements []string
elementSize := len(inStr) / size // 计算每个元素的大小
for i := 0; i < size; i++ {
start := i * elementSize // 计算起始位置
end := start + elementSize // 计算结束位置
if i == size-1 { // 如果是最后一个元素, 则结束位置为字符串末尾
end = len(inStr)
}
elements = append(elements, inStr[start:end]) // 将分割后的字符串添加到 elements 切片中
}
return elements // 返回分割后的字符串切片
}
func main() { // main 函数
// Baby Jubjub 曲线上的三个生成器值:
//19763400273586758549882294695966112539108188722254631372974345487113325868230, 13
//12576558175125128246374296641007938322302911659474853157208587689687229831284, 3
//18869612826929474874643025262839512853875949931275176402223244870011103237908, 5
str:="Testing 123" // 设置默认字符串 "Testing 123"
argCount := len(os.Args[1:]) // 获取命令行参数的数量
if (argCount>0) {str= string(os.Args[1])} // 如果有命令行参数, 则使用第一个参数作为字符串
fmt.Printf("Input message: %s\n\n",str) // 打印输入的消息
value := hex.EncodeToString([]byte(str)) // 将字符串转换为十六进制编码的字符串
res:= splitMessage(value,3) // 将十六进制编码的字符串分割成三个部分
gx1,_ := new(big.Int).SetString("19763400273586758549882294695966112539108188722254631372974345487113325868230",10 ) // 初始化大整数 gx1
gy1,_ := new(big.Int).SetString("13",10) // 初始化大整数 gy1
gx2,_ := new(big.Int).SetString("12576558175125128246374296641007938322302911659474853157208587689687229831284",10 ) // 初始化大整数 gx2
gy2,_ := new(big.Int).SetString("3",10) // 初始化大整数 gy2
gx3,_ := new(big.Int).SetString("18869612826929474874643025262839512853875949931275176402223244870011103237908",10 ) // 初始化大整数 gx3
gy3,_ := new(big.Int).SetString("5",10) // 初始化大整数 gy3
G1 := &babyjub.Point{X: gx1, Y: gy1} // 创建 Baby Jubjub 曲线上的点 G1
G2 := &babyjub.Point{X: gx2, Y: gy2} // 创建 Baby Jubjub 曲线上的点 G2
G3 := &babyjub.Point{X: gx3, Y: gy3} // 创建 Baby Jubjub 曲线上的点 G3
fmt.Printf("G1 (%s, %s)\n",G1.X.String(),G1.Y.String()) // 打印 G1 的坐标
fmt.Printf("G2 (%s, %s)\n",G2.X.String(),G2.Y.String()) // 打印 G2 的坐标
fmt.Printf("G3 (%s, %s)\n\n",G3.X.String(),G3.Y.String()) // 打印 G3 的坐标
m_1,_:=hex.DecodeString(res[0]) // 将分割后的第一个字符串从十六进制解码为字节数组
m_2,_:=hex.DecodeString(res[1]) // 将分割后的第二个字符串从十六进制解码为字节数组
m_3,_:=hex.DecodeString(res[2]) // 将分割后的第三个字符串从十六进制解码为字节数组
M1:=new(big.Int).SetBytes(m_1) // 将字节数组转换为大整数 M1
M2:=new(big.Int).SetBytes(m_2) // 将字节数组转换为大整数 M2
M3:=new(big.Int).SetBytes(m_3) // 将字节数组转换为大整数 M3
M1_G1 := babyjub.NewPoint().Mul(M1,G1) // 计算 M1 * G1
M2_G2 := babyjub.NewPoint().Mul(M2,G2) // 计算 M2 * G2
M3_G3 := babyjub.NewPoint().Mul(M3,G3) // 计算 M3 * G3
M1_G1_M2_G2 := babyjub.NewPoint().Projective().Add(M1_G1.Projective(),M2_G2.Projective()) // 计算 M1 * G1 + M2 * G2
M1_G1_M2_G2_M3_G3 := babyjub.NewPoint().Projective().Add(M1_G1_M2_G2,M3_G3.Projective()) // 计算 M1 * G1 + M2 * G2 + M3 * G3
fmt.Printf("(%d).G1 Point (%s, %s)\n",M1,M1_G1.X.String(),M1_G1.Y.String()) // 打印 M1 * G1 的坐标
fmt.Printf("(%d).G2 Point (%s, %s)\n",M2,M2_G2.X.String(),M2_G2.Y.String()) // 打印 M2 * G2 的坐标
fmt.Printf("(%d).G3 Point (%s, %s)\n\n",M3,M3_G3.X.String(),M3_G3.Y.String()) // 打印 M3 * G3 的坐标
fmt.Printf("Result: (%s, %s)\n\n",M1_G1_M2_G2_M3_G3.X.String(),M1_G1_M2_G2_M3_G3.Y.String()) // 打印最终结果的坐标
fmt.Printf("Pedersen Hash: %s\n",M1_G1_M2_G2_M3_G3.X.String()) // 打印 Pedersen 哈希值
}
结果是 [ 这里]:
Input message (in hex): 010203040506070809
G1 (19763400273586758549882294695966112539108188722254631372974345487113325868230, 13)
G2 (12576558175125128246374296641007938322302911659474853157208587689687229831284, 3)
G3 (18869612826929474874643025262839512853875949931275176402223244870011103237908, 5)
(66051).G1 Point (16037773783835146438558403421260836675868272902364849599426875451057520106906, 14728403640743494614641112385959960394789183729111213630842711014573474773378)
(263430).G2 Point (11852499477985754981114582499827808786287610741986412935345795152171632055691, 2674722056485308817737914684425248014914348605929003485370677590616611371783)
(460809).G3 Point (7621534404334108830225350985637201742729050263412910533478640032004698408257, 3768640800306573992890050298007966758509235918251221830887953108815318470075)
Result: (8246050116631628695869530651297899884448852706174930565536327904583775502268, 21660911736906458468630355032769751803544552633662219825808465470014769680600)
Pedersen Hash: 8246050116631628695869530651297899884448852706174930565536327904583775502268
当然,在现实生活中,我们会将生成器点分隔开。
Poseidon [1] 和 Poseidon 2 [2] 哈希函数专注于可验证的计算协议,并在素数域上产生小的电路尺寸。它们通常用于零知识证明应用程序中,例如以太坊,并且在 STARK 基准测试中具有出色的性能。总的来说,Poseidon 哈希使用 sponge 函数(与 SHA-3 (Keccak) 一起使用)进行哈希,因此包含一个状态。字段本身约为 255 位,并提供约 116 位的安全性。
在本例中,我们将采用一个三值向量并计算 Poseidon 哈希 [ 这里]:
package main
import (
"fmt"
"github.com/iden3/go-iden3-crypto/v2/babyjub"
"math/big"
"strconv"
"os"
)
func main() { // main 函数
start:=0 // 定义起始值为 0
points:=10 // 定义点的数量为 10
valid_points:=0 // 定义有效点的数量为 0
argCount := len(os.Args[1:]) // 获取命令行参数的数量
if argCount > 0 { // 如果存在命令行参数
start,_ = strconv.Atoi(os.Args[1]) // 将第一个参数转换为整数,并赋值给 start
}
if argCount > 1 { // 如果存在第二个命令行参数
points,_ = strconv.Atoi(os.Args[2]) // 将第二个参数转换为整数,并赋值给 points
}
if (points>50) { // 如果点的数量大于 50
fmt.Printf("Too many points") // 打印 “Too many points”
return // 退出程序
}
end:=start+points // 计算结束值
y:=big.NewInt(int64(start)) // 创建一个新的大整数,初始值为 start
for i:=start;i<end;i++ { // 循环遍历从 start 到 end 的值
P,err:= babyjub.PointFromSignAndY(true,y) // 从签名和 Y 坐标创建一个 babyjub 点
if (err==nil) { // 如果没有错误发生
fmt.Printf("P Point (%s, %s)\n",P.X.String(),P.Y.String()) // 打印点的 X 和 Y 坐标
valid_points=valid_points+1 // 有效点数量加 1
} else { // 如果发生错误
fmt.Printf(" No point at y=%d. Error: %v\n", y,err) // 打印错误信息
}
y = y.Add(y,big.NewInt(1)) // y 的值加 1
}
fmt.Printf("\nNumber of valid points: %d\n", valid_points) // 打印有效点的数量
}
[1]、[1, 4] 和 [1, 4, 3] 向量的示例如下 [ 这里]:
Inputs 1. Poseidon Hash: 18586133768512220936620570745912940619677854269274689475585506675881198879027
Inputs 1, 4. Poseidon Hash: 20093115681644140910448217843618788628911204837480265095337820971629649645527
Inputs 1, 4, 3. Poseidon Hash: 11581812134676561997526789540623636669301006969489560800875483048762369668960
[1] Grassi, L., Khovratovich, D., Rechberger, C., Roy, A., & Schofnegger, M. (2021). Poseidon: A new hash function for {Zero-Knowledge} proof systems. In 30th USENIX Security Symposium (USENIX Security 21) (pp. 519–535).
[2] Grassi, L., Khovratovich, D., & Schofnegger, M. (2023, July). Poseidon2: A faster version of the poseidon hash function. In International Conference on Cryptology in Africa (pp. 177–203). Cham: Springer Nature Switzerland.
- 原文链接: billatnapier.medium.com/...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!