哈希
哈希本质上是一个将任意长度的输入数据(通常称为消息)转换为固定大小值的过程,该值通常称为“哈希”。这种转换是确定性的,这意味着相同的输入将始终生成相同的哈希值。哈希函数是各种领域的基石,包括数据存储、密码学和数据完整性验证 - 并且在开发智能合约时经常使用,尤其是在使用 Merkle 树时。
本章,我们将介绍 Cairo 库中原生实现的两种哈希函数:Poseidon 和 Pedersen。我们将讨论何时以及如何使用它们,并通过 Cairo 程序展示示例。
Cairo中的哈希函数
Cairo 核心库提供了两种哈希函数:Pedersen 和 Poseidon。
Pedersen 哈希函数是一种基于椭圆曲线密码学的加密算法。这些函数对椭圆曲线上的点进行操作——本质上是使用这些点的位置进行数学运算——这在一个方向上很容易做到,但却很难反过来操作。这种单向性是基于椭圆曲线离散对数问题 (ECDLP),它是一个很难的问题,可以确保哈希函数的安全性。无法逆转这些操作的困难性使 Pedersen 哈希函数在加密用途上安全可靠。
Poseidon 是一系列哈希函数,作为代数电路非常高效。它的设计特别适用于零知识证明系统,包括 STARKs (所以也适用于 Cairo)。Poseidon 使用一种称为“sponge construction”的方法,该方法吸收数据并使用称为 Hades 置换的过程安全地转换数据。Cairo 版本的 Poseidon 基于具有三元素状态置换和 特定参数的设计。
何时使用它们?
Pedersen 是 Starknet 上最初使用的哈希函数,现在仍用于计算存储中变量的地址(例如,LegacyMap
使用 Pedersen 对 Starknet 上存储映射的键进行哈希)。然而,由于 Poseidon 在处理 STARK 证明系统时比 Pedersen 更便宜,更快,因此现在它已成为 Cairo 程序中推荐使用的哈希函数。
使用哈希函数
核心库使哈希操作变得简单。Hash
trait适用于所有可以转换为 felt252
的类型,包括 felt252
本身。对于像结构体这样更复杂的类型,派生 Hash
允许它们使用您选择的哈希函数进行哈希 - 只要结构体的所有字段本身都是可哈希的。即使 T
1 本身可哈希,您也无法在包含不可哈希值的结构体上派生 Hash
trait,例如 Array<T>
或 Felt252Dict<T>
。
Hash
trait伴随着 HashStateTrait
,它定义了用于处理哈希的基本方法。它们允许您初始化一个哈希状态,其中将包含哈希函数每次应用后哈希的临时值;更新哈希状态,并在计算完成后将其最终确定。HashStateTrait
定义如下:
#![allow(unused)] fn main() { /// A trait for hash state accumulators. trait HashStateTrait<S> { fn update(self: S, value: felt252) -> S; fn finalize(self: S) -> felt252; } /// A trait for values that can be hashed. trait Hash<T, S, +HashStateTrait<S>> { /// Updates the hash state with the given value. fn update_state(state: S, value: T) -> S; } }
要在代码中使用哈希,您必须首先导入相关的trait和函数。在以下示例中,我们将演示如何使用 Pedersen 和 Poseidon 哈希函数对结构体进行哈希处理。
首先,需要根据我们想要使用的哈希函数,使用 PoseidonTrait::new() -> HashState
或 PedersenTrait::new(base: felt252) -> HashState
初始化哈希状态。然后,可以使用 update(self: HashState, value: felt252) -> HashState
或 update_with(self: S, value: T) -> S
函数多次更新哈希状态,具体更新次数取决于需要。最后,使用 finalize(self: HashState) -> felt252
函数完成哈希计算并返回哈希值。
#![allow(unused)] fn main() { use pedersen::PedersenTrait; use poseidon::PoseidonTrait; use hash::{HashStateTrait, HashStateExTrait}; }
#![allow(unused)] fn main() { #[derive(Drop, Hash)] struct StructForHash { first: felt252, second: felt252, third: (u32, u32), last: bool, } }
由于我们的结构体派生了 HashTrait 这个trait,我们可以使用以下方式调用 Poseidon 哈希函数:
use pedersen::PedersenTrait; use poseidon::PoseidonTrait; use hash::{HashStateTrait, HashStateExTrait}; #[derive(Drop, Hash)] struct StructForHash { first: felt252, second: felt252, third: (u32, u32), last: bool, } fn main() -> felt252 { let struct_to_hash = StructForHash { first: 0, second: 1, third: (1, 2), last: false }; let hash = PoseidonTrait::new().update_with(struct_to_hash).finalize(); hash }
同样地,我们也可以使用以下方式调用 Pedersen 哈希函数:
use pedersen::PedersenTrait; use poseidon::PoseidonTrait; use hash::{HashStateTrait, HashStateExTrait}; #[derive(Drop, Hash)] struct StructForHash { first: felt252, second: felt252, third: (u32, u32), last: bool, } fn main() -> felt252 { let struct_to_hash = StructForHash { first: 0, second: 1, third: (1, 2), last: false }; let hash = PedersenTrait::new(0).update_with(struct_to_hash).finalize(); hash }
高级哈希:使用 Poseidon 哈希数组
让我们来看一个哈希包含 Span<felt252>
.的函数的例子。
要哈希 Span<felt252>
或包含 Span<felt252>
的结构体,您可以使用 poseidon 中的内置函数 poseidon_hash_span(mut span: Span<felt252>) -> felt252
。同样,您可以通过对其 span 调用 poseidon_hash_span
来哈希 Array<felt252>
。
首先,让我们引入以下trait和函数:
#![allow(unused)] fn main() { use poseidon::PoseidonTrait; use poseidon::poseidon_hash_span; use hash::{HashStateTrait, HashStateExTrait}; }
现在,我们来定义结构体。正如您可能注意到的,我们没有派生 Hash trait。如果您尝试在这个结构体上派生 Hash trait,它会引发错误,因为结构体包含一个不可哈希的字段。
#[derive(Drop)]
struct StructForHashArray {
first: felt252,
second: felt252,
third: Array<felt252>,
}
在这个例子中,我们初始化了一个 HashState (hash
) 并更新了它,然后在 HashState 上调用了函数 finalize()
以获得计算出的哈希 hash_felt252
。我们使用 poseidon_hash_span
对 Array<felt252>
的 Span
进行哈希计算,以计算其哈希值。
use poseidon::PoseidonTrait; use poseidon::poseidon_hash_span; use hash::{HashStateTrait, HashStateExTrait}; #[derive(Drop)] struct StructForHashArray { first: felt252, second: felt252, third: Array<felt252>, } fn main() { let struct_to_hash = StructForHashArray { first: 0, second: 1, third: array![1, 2, 3, 4, 5] }; let mut hash = PoseidonTrait::new().update(struct_to_hash.first).update(struct_to_hash.second); let hash_felt252 = hash.update(poseidon_hash_span(struct_to_hash.third.span())).finalize(); }