Skip to content

Commit 2291904

Browse files
authored
feat: alternative (slow) pedersen hash impl optimized for size (#675)
1 parent 38e4360 commit 2291904

File tree

6 files changed

+136
-13
lines changed

6 files changed

+136
-13
lines changed

.github/workflows/test.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ jobs:
2525
profile: minimal
2626
override: true
2727

28+
- name: Test starknet-crypto with pedersen_no_lookup
29+
run: |
30+
cargo test -p starknet-crypto --features pedersen_no_lookup
31+
2832
- name: Run cargo tests
2933
uses: nick-fields/retry@v2
3034
with:
@@ -53,6 +57,10 @@ jobs:
5357
profile: minimal
5458
override: true
5559

60+
- name: Test starknet-crypto with pedersen_no_lookup
61+
run: |
62+
cargo test -p starknet-crypto --features pedersen_no_lookup
63+
5664
- name: Run cargo tests
5765
uses: nick-fields/retry@v2
5866
with:

starknet-crypto/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ default = ["std", "signature-display"]
3131
std = ["starknet-types-core/std"]
3232
alloc = ["hex?/alloc", "starknet-types-core/alloc"]
3333
signature-display = ["dep:hex", "alloc"]
34+
pedersen_no_lookup = []
3435

3536
[dev-dependencies]
3637
criterion = { version = "0.4.0", default-features = false }

starknet-crypto/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ poseidon_hash_many time: [41.878 µs 41.911 µs 41.945 µs]
6565
rfc6979_generate_k time: [11.564 µs 11.566 µs 11.569 µs]
6666
```
6767

68+
## Binary size optimization
69+
70+
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.
71+
72+
> [!WARNING]
73+
>
74+
> 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.
75+
6876
## Credits
6977

7078
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).
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use starknet_types_core::{
2+
felt::Felt,
3+
hash::{Pedersen, StarkHash},
4+
};
5+
6+
/// Computes the Starkware version of the Pedersen hash of x and y. All inputs are little-endian.
7+
///
8+
/// ### Parameters
9+
///
10+
/// - `x`: The x coordinate.
11+
/// - `y`: The y coordinate.
12+
pub fn pedersen_hash(x: &Felt, y: &Felt) -> Felt {
13+
Pedersen::hash(x, y)
14+
}
Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
1-
use starknet_types_core::{
2-
felt::Felt,
3-
hash::{Pedersen, StarkHash},
4-
};
1+
#[cfg(not(feature = "pedersen_no_lookup"))]
2+
mod default;
3+
#[cfg(not(feature = "pedersen_no_lookup"))]
4+
pub use default::pedersen_hash;
55

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

1611
#[cfg(test)]
1712
mod tests {
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Size-optimized implementation ported from:
2+
// https://github.com/andrewmilson/sandstorm/blob/9e256c4933aa2d89f794b3ed7c293b32984fe1ce/builtins/src/pedersen/mod.rs#L24-L50
3+
4+
use starknet_curve::curve_params::SHIFT_POINT;
5+
use starknet_types_core::{curve::ProjectivePoint, felt::Felt};
6+
7+
/// Computes the Starkware version of the Pedersen hash of x and y. All inputs are little-endian.
8+
///
9+
/// ### Parameters
10+
///
11+
/// - `x`: The x coordinate.
12+
/// - `y`: The y coordinate.
13+
pub fn pedersen_hash(x: &Felt, y: &Felt) -> Felt {
14+
// Temporarily defining the projective points inline, as `ProjectivePoint::new()` is incorrectly
15+
// not `const`.
16+
// TODO: turn these into consts once upstream is fixed.
17+
let p0_projective: ProjectivePoint = ProjectivePoint::new(
18+
Felt::from_raw([
19+
241691544791834578,
20+
518715844721862878,
21+
13758484295849329960,
22+
3602345268353203007,
23+
]),
24+
Felt::from_raw([
25+
368891789801938570,
26+
433857700841878496,
27+
13001553326386915570,
28+
13441546676070136227,
29+
]),
30+
Felt::ONE,
31+
);
32+
let p1_projective: ProjectivePoint = ProjectivePoint::new(
33+
Felt::from_raw([
34+
253000153565733272,
35+
10043949394709899044,
36+
12382025591154462459,
37+
16491878934996302286,
38+
]),
39+
Felt::from_raw([
40+
285630633187035523,
41+
5191292837124484988,
42+
2545498000137298346,
43+
13950428914333633429,
44+
]),
45+
Felt::ONE,
46+
);
47+
let p2_projective: ProjectivePoint = ProjectivePoint::new(
48+
Felt::from_raw([
49+
338510149841406402,
50+
12916675983929588442,
51+
18195981508842736832,
52+
1203723169299412240,
53+
]),
54+
Felt::from_raw([
55+
161068411212710156,
56+
11088962269971685343,
57+
11743524503750604092,
58+
12352616181161700245,
59+
]),
60+
Felt::ONE,
61+
);
62+
let p3_projective: ProjectivePoint = ProjectivePoint::new(
63+
Felt::from_raw([
64+
425493972656615276,
65+
299781701614706065,
66+
10664803185694787051,
67+
1145636535101238356,
68+
]),
69+
Felt::from_raw([
70+
345457391846365716,
71+
6033691581221864148,
72+
4428713245976508844,
73+
8187986478389849302,
74+
]),
75+
Felt::ONE,
76+
);
77+
78+
let processed_x = process_element(x, &p0_projective, &p1_projective);
79+
let processed_y = process_element(y, &p2_projective, &p3_projective);
80+
81+
// Unwrapping is safe as this never fails
82+
(processed_x + processed_y + SHIFT_POINT)
83+
.to_affine()
84+
.unwrap()
85+
.x()
86+
}
87+
88+
#[inline(always)]
89+
fn process_element(x: &Felt, p1: &ProjectivePoint, p2: &ProjectivePoint) -> ProjectivePoint {
90+
let x = x.to_biguint();
91+
let shift = 252 - 4;
92+
let high_part = &x >> shift;
93+
let low_part = x - (&high_part << shift);
94+
let x_high = Felt::from(high_part);
95+
let x_low = Felt::from(low_part);
96+
p1 * x_low + p2 * x_high
97+
}

0 commit comments

Comments
 (0)