哈希

哈希本质上是一个将任意长度的输入数据(通常称为消息)转换为固定大小值的过程,该值通常称为“哈希”。这种转换是确定性的,这意味着相同的输入将始终生成相同的哈希值。哈希函数是各种领域的基石,包括数据存储、密码学和数据完整性验证 - 并且在开发智能合约时经常使用,尤其是在使用 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允许它们使用您选择的哈希函数进行哈希 - 只要结构体的所有字段本身都是可哈希的。即使 T1 本身可哈希,您也无法在包含不可哈希值的结构体上派生 Hashtrait,例如 Array<T>Felt252Dict<T>

Hashtrait伴随着 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() -> HashStatePedersenTrait::new(base: felt252) -> HashState 初始化哈希状态。然后,可以使用 update(self: HashState, value: felt252) -> HashStateupdate_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_spanArray<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();
}


Last change: 2023-12-01, commit: f916800