Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: alternative (slow) pedersen hash impl optimized for size #675

Merged
merged 1 commit into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
Loading