Skip to content

Commit bd23c23

Browse files
authored
perf: pedersen hash lookup tables (xJonathanLEI#245)
1 parent 9e4cbfd commit bd23c23

File tree

12 files changed

+229
-35
lines changed

12 files changed

+229
-35
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ members = [
2525
"starknet-ff",
2626
"starknet-macros",
2727
"starknet-curve",
28+
"starknet-crypto-codegen",
2829
"examples/starknet-wasm",
2930
]
3031

starknet-core/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ For instructions on running the benchmarks yourself, check out [this page](../BE
1111
### Native
1212

1313
```log
14-
class_hash time: [117.32 ms 117.55 ms 117.78 ms]
14+
class_hash time: [18.931 ms 18.943 ms 18.958 ms]
1515
```
1616

1717
### WebAssembly
@@ -32,17 +32,17 @@ wasmer-js 0.4.1
3232
`wasmer` results:
3333

3434
```log
35-
class_hash time: [1.0280 s 1.0283 s 1.0287 s]
35+
class_hash time: [126.30 ms 126.47 ms 126.65 ms]
3636
```
3737

3838
`wasmtime` results:
3939

4040
```log
41-
class_hash time: [836.69 ms 839.33 ms 841.92 ms]
41+
class_hash time: [108.80 ms 109.02 ms 109.24 ms]
4242
```
4343

4444
Node.js results:
4545

4646
```log
47-
class_hash time: [900.73 ms 901.09 ms 901.47 ms]
47+
class_hash time: [113.77 ms 114.36 ms 115.07 ms]
4848
```

starknet-crypto-codegen/Cargo.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "starknet-crypto-codegen"
3+
version = "0.1.0"
4+
authors = ["Jonathan LEI <[email protected]>"]
5+
license = "MIT OR Apache-2.0"
6+
edition = "2021"
7+
readme = "README.md"
8+
repository = "https://github.com/xJonathanLEI/starknet-rs"
9+
homepage = "https://starknet.rs/"
10+
description = """
11+
Codegen macros for `starknet-crypto`
12+
"""
13+
keywords = ["ethereum", "starknet", "web3"]
14+
15+
[lib]
16+
proc-macro = true
17+
18+
[dependencies]
19+
starknet-curve = { version = "0.1.0", path = "../starknet-curve" }
20+
starknet-ff = { version = "0.1.0", path = "../starknet-ff" }
21+
syn = "1.0.96"

starknet-crypto-codegen/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Codegen macros for `starknet-crypto`

starknet-crypto-codegen/src/lib.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Code ported from the build.rs script here:
2+
// https://github.com/eqlabs/pathfinder/blob/7f9a6bb0264943f93a633f61fc4e0bc9237f68a0/crates/stark_hash/build.rs
3+
4+
use std::fmt::Write;
5+
6+
use proc_macro::TokenStream;
7+
use starknet_curve::{
8+
curve_params::{PEDERSEN_P0, PEDERSEN_P1, PEDERSEN_P2, PEDERSEN_P3},
9+
AffinePoint,
10+
};
11+
use syn::{parse_macro_input, LitInt};
12+
13+
#[proc_macro]
14+
pub fn lookup_table(input: TokenStream) -> TokenStream {
15+
let input = parse_macro_input!(input as LitInt);
16+
let bits: u32 = input.base10_parse().expect("invalid bits");
17+
18+
let mut output = String::new();
19+
writeln!(output, "pub const CURVE_CONSTS_BITS: usize = {};", bits).unwrap();
20+
21+
push_points(&mut output, "P0", PEDERSEN_P0, 248, bits).expect("push_points failed");
22+
push_points(&mut output, "P1", PEDERSEN_P1, 4, bits).expect("push_points failed");
23+
push_points(&mut output, "P2", PEDERSEN_P2, 248, bits).expect("push_points failed");
24+
push_points(&mut output, "P3", PEDERSEN_P3, 4, bits).expect("push_points failed");
25+
26+
output.parse().unwrap()
27+
}
28+
29+
fn push_points(
30+
buf: &mut String,
31+
name: &str,
32+
base: AffinePoint,
33+
max_bits: u32,
34+
bits: u32,
35+
) -> std::fmt::Result {
36+
let full_chunks = max_bits / bits;
37+
let leftover_bits = max_bits % bits;
38+
let table_size_full = (1 << bits) - 1;
39+
let table_size_leftover = (1 << leftover_bits) - 1;
40+
let len = full_chunks * table_size_full + table_size_leftover;
41+
42+
writeln!(
43+
buf,
44+
"pub const CURVE_CONSTS_{}: [::starknet_curve::AffinePoint; {}] = [",
45+
name, len
46+
)?;
47+
48+
let mut bits_left = max_bits;
49+
let mut outer_point = base;
50+
while bits_left > 0 {
51+
let eat_bits = std::cmp::min(bits_left, bits);
52+
let table_size = (1 << eat_bits) - 1;
53+
54+
// Loop through each possible bit combination except zero
55+
let mut inner_point = outer_point;
56+
for _ in 1..(table_size + 1) {
57+
push_point(buf, &inner_point)?;
58+
inner_point.add_assign(&outer_point);
59+
}
60+
61+
// Shift outer point #bits times
62+
bits_left -= eat_bits;
63+
for _i in 0..bits {
64+
outer_point.double_assign();
65+
}
66+
}
67+
68+
writeln!(buf, "];")?;
69+
Ok(())
70+
}
71+
72+
fn push_point(buf: &mut String, p: &AffinePoint) -> std::fmt::Result {
73+
let x = p.x.into_mont();
74+
let y = p.y.into_mont();
75+
writeln!(buf, "::starknet_curve::AffinePoint {{")?;
76+
writeln!(buf, "x: ::starknet_ff::FieldElement::from_mont([")?;
77+
writeln!(buf, "{},", x[0])?;
78+
writeln!(buf, "{},", x[1])?;
79+
writeln!(buf, "{},", x[2])?;
80+
writeln!(buf, "{},", x[3])?;
81+
writeln!(buf, "]),")?;
82+
writeln!(buf, "y: ::starknet_ff::FieldElement::from_mont([")?;
83+
writeln!(buf, "{},", y[0])?;
84+
writeln!(buf, "{},", y[1])?;
85+
writeln!(buf, "{},", y[2])?;
86+
writeln!(buf, "{},", y[3])?;
87+
writeln!(buf, "]),")?;
88+
writeln!(buf, "infinity: false,")?;
89+
writeln!(buf, "}},")?;
90+
Ok(())
91+
}

starknet-crypto/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Low-level cryptography utilities for StarkNet
1313
keywords = ["ethereum", "starknet", "web3"]
1414

1515
[dependencies]
16+
starknet-crypto-codegen = { version = "0.1.0", path = "../starknet-crypto-codegen" }
1617
starknet-curve = { version = "0.1.0", path = "../starknet-curve" }
1718
starknet-ff = { version = "0.1.0", path = "../starknet-ff" }
1819
crypto-bigint = "0.3.2"

starknet-crypto/README.md

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ For instructions on running the benchmarks yourself, check out [this page](../BE
2121
### Native
2222

2323
```log
24-
ecdsa_get_public_key time: [1.4756 ms 1.4761 ms 1.4765 ms]
25-
ecdsa_sign time: [1.4728 ms 1.4735 ms 1.4743 ms]
26-
ecdsa_verify time: [3.1314 ms 3.1331 ms 3.1348 ms]
27-
pedersen_hash time: [223.93 µs 224.41 µs 225.04 µs]
28-
rfc6979_generate_k time: [2.2909 µs 2.2922 µs 2.2935 µs]
24+
ecdsa_get_public_key time: [1.5350 ms 1.5461 ms 1.5629 ms]
25+
ecdsa_sign time: [1.5379 ms 1.5420 ms 1.5474 ms]
26+
ecdsa_verify time: [3.2405 ms 3.2443 ms 3.2487 ms]
27+
pedersen_hash time: [31.775 µs 31.988 µs 32.273 µs]
28+
rfc6979_generate_k time: [2.3819 µs 2.3904 µs 2.4020 µs]
2929
```
3030

3131
### WebAssembly
@@ -46,31 +46,31 @@ wasmer-js 0.4.1
4646
`wasmer` results:
4747

4848
```log
49-
ecdsa_get_public_key time: [3.0703 ms 3.0806 ms 3.0914 ms]
50-
ecdsa_sign time: [3.1632 ms 3.1665 ms 3.1717 ms]
51-
ecdsa_verify time: [6.9936 ms 7.0168 ms 7.0426 ms]
52-
pedersen_hash time: [2.0007 ms 2.0076 ms 2.0145 ms]
53-
rfc6979_generate_k time: [12.197 µs 12.203 µs 12.210 µs]
49+
ecdsa_get_public_key time: [3.1780 ms 3.2041 ms 3.2374 ms]
50+
ecdsa_sign time: [3.2371 ms 3.2554 ms 3.2785 ms]
51+
ecdsa_verify time: [7.5052 ms 7.5168 ms 7.5297 ms]
52+
pedersen_hash time: [267.19 µs 268.74 µs 270.89 µs]
53+
rfc6979_generate_k time: [12.501 µs 12.512 µs 12.525 µs]
5454
```
5555

5656
`wasmtime` results:
5757

5858
```log
59-
ecdsa_get_public_key time: [2.8495 ms 2.8541 ms 2.8618 ms]
60-
ecdsa_sign time: [2.8797 ms 2.8814 ms 2.8832 ms]
61-
ecdsa_verify time: [6.7528 ms 6.7570 ms 6.7613 ms]
62-
pedersen_hash time: [1.6589 ms 1.6618 ms 1.6651 ms]
63-
rfc6979_generate_k time: [10.833 µs 10.839 µs 10.845 µs]
59+
ecdsa_get_public_key time: [2.9626 ms 2.9677 ms 2.9734 ms]
60+
ecdsa_sign time: [2.9489 ms 2.9603 ms 2.9730 ms]
61+
ecdsa_verify time: [6.8464 ms 6.8792 ms 6.9221 ms]
62+
pedersen_hash time: [220.62 µs 221.77 µs 223.72 µs]
63+
rfc6979_generate_k time: [11.263 µs 11.281 µs 11.304 µs]
6464
```
6565

6666
Node.js results:
6767

6868
```log
69-
ecdsa_get_public_key time: [2.6020 ms 2.6138 ms 2.6304 ms]
70-
ecdsa_sign time: [2.5616 ms 2.5654 ms 2.5697 ms]
71-
ecdsa_verify time: [6.2385 ms 6.2399 ms 6.2412 ms]
72-
pedersen_hash time: [1.7788 ms 1.7899 ms 1.8013 ms]
73-
rfc6979_generate_k time: [9.6454 µs 9.6483 µs 9.6514 µs]
69+
ecdsa_get_public_key time: [2.7033 ms 2.7220 ms 2.7461 ms]
70+
ecdsa_sign time: [2.7405 ms 2.7431 ms 2.7461 ms]
71+
ecdsa_verify time: [6.5923 ms 6.6322 ms 6.6816 ms]
72+
pedersen_hash time: [230.24 µs 230.84 µs 231.74 µs]
73+
rfc6979_generate_k time: [9.9566 µs 9.9891 µs 10.032 µs]
7474
```
7575

7676
## Credits

starknet-crypto/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod ecdsa;
44
mod error;
55
mod fe_utils;
66
mod pedersen_hash;
7+
mod pedersen_points;
78
mod rfc6979;
89

910
#[cfg(test)]

starknet-crypto/src/pedersen_hash.rs

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
use starknet_curve::{curve_params, AffinePoint, ProjectivePoint};
22
use starknet_ff::FieldElement;
33

4+
use crate::pedersen_points::*;
5+
46
const SHIFT_POINT: ProjectivePoint = ProjectivePoint::from_affine_point(&curve_params::SHIFT_POINT);
5-
const PEDERSEN_P0: ProjectivePoint = ProjectivePoint::from_affine_point(&curve_params::PEDERSEN_P0);
6-
const PEDERSEN_P1: ProjectivePoint = ProjectivePoint::from_affine_point(&curve_params::PEDERSEN_P1);
7-
const PEDERSEN_P2: ProjectivePoint = ProjectivePoint::from_affine_point(&curve_params::PEDERSEN_P2);
8-
const PEDERSEN_P3: ProjectivePoint = ProjectivePoint::from_affine_point(&curve_params::PEDERSEN_P3);
97

108
/// Computes the Starkware version of the Pedersen hash of x and y. All inputs are little-endian.
119
///
@@ -17,20 +15,45 @@ pub fn pedersen_hash(x: &FieldElement, y: &FieldElement) -> FieldElement {
1715
let x = x.to_bits_le();
1816
let y = y.to_bits_le();
1917

18+
// Preprocessed material is lookup-tables for each chunk of bits
19+
let table_size = (1 << CURVE_CONSTS_BITS) - 1;
20+
let add_points = |acc: &mut ProjectivePoint, bits: &[bool], prep: &[AffinePoint]| {
21+
bits.chunks(CURVE_CONSTS_BITS)
22+
.enumerate()
23+
.for_each(|(i, v)| {
24+
let offset = bools_to_usize_le(v);
25+
if offset > 0 {
26+
// Table lookup at 'offset-1' in table for chunk 'i'
27+
acc.add_affine_assign(&prep[i * table_size + offset - 1]);
28+
}
29+
});
30+
};
31+
2032
// Compute hash
21-
let mut accumulator = SHIFT_POINT;
22-
accumulator.add_assign(&PEDERSEN_P0.multiply(&x[..248])); // Add a_low * P1
23-
accumulator.add_assign(&PEDERSEN_P1.multiply(&x[248..252])); // Add a_high * P2
24-
accumulator.add_assign(&PEDERSEN_P2.multiply(&y[..248])); // Add b_low * P3
25-
accumulator.add_assign(&PEDERSEN_P3.multiply(&y[248..252])); // Add b_high * P4
33+
let mut acc = SHIFT_POINT;
34+
add_points(&mut acc, &x[..248], &CURVE_CONSTS_P0); // Add a_low * P1
35+
add_points(&mut acc, &x[248..252], &CURVE_CONSTS_P1); // Add a_high * P2
36+
add_points(&mut acc, &y[..248], &CURVE_CONSTS_P2); // Add b_low * P3
37+
add_points(&mut acc, &y[248..252], &CURVE_CONSTS_P3); // Add b_high * P4
2638

2739
// Convert to affine
28-
let result = AffinePoint::from(&accumulator);
40+
let result = AffinePoint::from(&acc);
2941

3042
// Return x-coordinate
3143
result.x
3244
}
3345

46+
#[inline]
47+
fn bools_to_usize_le(bools: &[bool]) -> usize {
48+
let mut result: usize = 0;
49+
for (ind, bit) in bools.iter().enumerate() {
50+
if *bit {
51+
result += 1 << ind;
52+
}
53+
}
54+
result
55+
}
56+
3457
#[cfg(test)]
3558
mod tests {
3659
use super::*;

0 commit comments

Comments
 (0)