Skip to content

Commit

Permalink
feat: alternative (slow) pedersen hash impl optimized for size (#675)
Browse files Browse the repository at this point in the history
  • Loading branch information
xJonathanLEI authored Nov 5, 2024
1 parent 38e4360 commit 2291904
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 13 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ jobs:
profile: minimal
override: true

- name: Test starknet-crypto with pedersen_no_lookup
run: |
cargo test -p starknet-crypto --features pedersen_no_lookup
- name: Run cargo tests
uses: nick-fields/retry@v2
with:
Expand Down Expand Up @@ -53,6 +57,10 @@ jobs:
profile: minimal
override: true

- name: Test starknet-crypto with pedersen_no_lookup
run: |
cargo test -p starknet-crypto --features pedersen_no_lookup
- name: Run cargo tests
uses: nick-fields/retry@v2
with:
Expand Down
1 change: 1 addition & 0 deletions starknet-crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ default = ["std", "signature-display"]
std = ["starknet-types-core/std"]
alloc = ["hex?/alloc", "starknet-types-core/alloc"]
signature-display = ["dep:hex", "alloc"]
pedersen_no_lookup = []

[dev-dependencies]
criterion = { version = "0.4.0", default-features = false }
Expand Down
8 changes: 8 additions & 0 deletions starknet-crypto/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ poseidon_hash_many time: [41.878 µs 41.911 µs 41.945 µs]
rfc6979_generate_k time: [11.564 µs 11.566 µs 11.569 µs]
```

## Binary size optimization

By default, `starknet-crypto` ships with a Pedersen hash implementation utilizing a lookup table for better performance. To optimize for binary size over performance, the crate offers a `pedersen_no_lookup` feature, which uses a vanilla unoptimized implementation instead.

> [!WARNING]
>
> Enabling the `pedersen_no_lookup` feature significantly slows down hashing performance by approximately a factor of `10`. Make sure you understand the impact on your use case before turning it on.
## Credits

Most of the code in this crate for the Pedersen hash implementation was inspired and modified from the awesome [`pathfinder` from Equilibrium](https://github.com/eqlabs/pathfinder/blob/b091cb889e624897dbb0cbec3c1df9a9e411eb1e/crates/pedersen/src/lib.rs).
Expand Down
14 changes: 14 additions & 0 deletions starknet-crypto/src/pedersen_hash/default.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use starknet_types_core::{
felt::Felt,
hash::{Pedersen, StarkHash},
};

/// Computes the Starkware version of the Pedersen hash of x and y. All inputs are little-endian.
///
/// ### Parameters
///
/// - `x`: The x coordinate.
/// - `y`: The y coordinate.
pub fn pedersen_hash(x: &Felt, y: &Felt) -> Felt {
Pedersen::hash(x, y)
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
use starknet_types_core::{
felt::Felt,
hash::{Pedersen, StarkHash},
};
#[cfg(not(feature = "pedersen_no_lookup"))]
mod default;
#[cfg(not(feature = "pedersen_no_lookup"))]
pub use default::pedersen_hash;

/// Computes the Starkware version of the Pedersen hash of x and y. All inputs are little-endian.
///
/// ### Parameters
///
/// - `x`: The x coordinate.
/// - `y`: The y coordinate.
pub fn pedersen_hash(x: &Felt, y: &Felt) -> Felt {
Pedersen::hash(x, y)
}
#[cfg(feature = "pedersen_no_lookup")]
mod no_lookup;
#[cfg(feature = "pedersen_no_lookup")]
pub use no_lookup::pedersen_hash;

#[cfg(test)]
mod tests {
Expand Down
97 changes: 97 additions & 0 deletions starknet-crypto/src/pedersen_hash/no_lookup.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Size-optimized implementation ported from:
// https://github.com/andrewmilson/sandstorm/blob/9e256c4933aa2d89f794b3ed7c293b32984fe1ce/builtins/src/pedersen/mod.rs#L24-L50

use starknet_curve::curve_params::SHIFT_POINT;
use starknet_types_core::{curve::ProjectivePoint, felt::Felt};

/// Computes the Starkware version of the Pedersen hash of x and y. All inputs are little-endian.
///
/// ### Parameters
///
/// - `x`: The x coordinate.
/// - `y`: The y coordinate.
pub fn pedersen_hash(x: &Felt, y: &Felt) -> Felt {
// Temporarily defining the projective points inline, as `ProjectivePoint::new()` is incorrectly
// not `const`.
// TODO: turn these into consts once upstream is fixed.
let p0_projective: ProjectivePoint = ProjectivePoint::new(
Felt::from_raw([
241691544791834578,
518715844721862878,
13758484295849329960,
3602345268353203007,
]),
Felt::from_raw([
368891789801938570,
433857700841878496,
13001553326386915570,
13441546676070136227,
]),
Felt::ONE,
);
let p1_projective: ProjectivePoint = ProjectivePoint::new(
Felt::from_raw([
253000153565733272,
10043949394709899044,
12382025591154462459,
16491878934996302286,
]),
Felt::from_raw([
285630633187035523,
5191292837124484988,
2545498000137298346,
13950428914333633429,
]),
Felt::ONE,
);
let p2_projective: ProjectivePoint = ProjectivePoint::new(
Felt::from_raw([
338510149841406402,
12916675983929588442,
18195981508842736832,
1203723169299412240,
]),
Felt::from_raw([
161068411212710156,
11088962269971685343,
11743524503750604092,
12352616181161700245,
]),
Felt::ONE,
);
let p3_projective: ProjectivePoint = ProjectivePoint::new(
Felt::from_raw([
425493972656615276,
299781701614706065,
10664803185694787051,
1145636535101238356,
]),
Felt::from_raw([
345457391846365716,
6033691581221864148,
4428713245976508844,
8187986478389849302,
]),
Felt::ONE,
);

let processed_x = process_element(x, &p0_projective, &p1_projective);
let processed_y = process_element(y, &p2_projective, &p3_projective);

// Unwrapping is safe as this never fails
(processed_x + processed_y + SHIFT_POINT)
.to_affine()
.unwrap()
.x()
}

#[inline(always)]
fn process_element(x: &Felt, p1: &ProjectivePoint, p2: &ProjectivePoint) -> ProjectivePoint {
let x = x.to_biguint();
let shift = 252 - 4;
let high_part = &x >> shift;
let low_part = x - (&high_part << shift);
let x_high = Felt::from(high_part);
let x_low = Felt::from(low_part);
p1 * x_low + p2 * x_high
}

0 comments on commit 2291904

Please sign in to comment.