Skip to content

Commit

Permalink
perf+feat: generalize and constify Pedersen hash (#851)
Browse files Browse the repository at this point in the history
Slightly more generic: instead of being implemented only for the STARK
curve, make it generic over curves on the STARK252 prime field. Future
work may make it work for more fields.

Initialization is much faster now: all parameters are computed at
compile time.
  • Loading branch information
Oppen authored Mar 26, 2024
1 parent 0cd3ef5 commit 6e33e20
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 70 deletions.
4 changes: 2 additions & 2 deletions crypto/benches/criterion_pedersen.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use lambdaworks_crypto::hash::pedersen::Pedersen;
use lambdaworks_crypto::hash::pedersen::PedersenStarkCurve;
use lambdaworks_math::field::element::FieldElement;
use lambdaworks_math::field::fields::fft_friendly::stark_252_prime_field::Stark252PrimeField;
use lambdaworks_math::traits::ByteConversion;
Expand All @@ -16,11 +17,10 @@ fn pedersen_benchmarks(c: &mut Criterion) {
let x = FieldElement::<Stark252PrimeField>::from_bytes_be(&felt1).unwrap();
let y = FieldElement::<Stark252PrimeField>::from_bytes_be(&felt2).unwrap();
let mut group = c.benchmark_group("Pedersen Benchmark");
let pedersen = black_box(Pedersen::default());

// Benchmark with black_box is 0.41% faster
group.bench_function("Hashing with black_box", |bench| {
bench.iter(|| black_box(pedersen.hash(&x, &y)))
bench.iter(|| black_box(PedersenStarkCurve::hash(&x, &y)))
});
}
criterion_group!(pedersen, pedersen_benchmarks);
Expand Down
85 changes: 38 additions & 47 deletions crypto/src/hash/pedersen/mod.rs
Original file line number Diff line number Diff line change
@@ -1,74 +1,66 @@
use lambdaworks_math::{
elliptic_curve::short_weierstrass::{
curves::stark_curve::StarkCurve, point::ShortWeierstrassProjectivePoint,
},
elliptic_curve::short_weierstrass::point::ShortWeierstrassProjectivePoint as Point,
field::{
element::FieldElement, fields::fft_friendly::stark_252_prime_field::Stark252PrimeField,
element::FieldElement as FE,
fields::fft_friendly::stark_252_prime_field::Stark252PrimeField,
},
};

mod constants;
mod parameters;
use self::parameters::PedersenParameters;
use parameters::PedersenParameters;
pub use parameters::PedersenStarkCurve;

pub struct Pedersen {
params: PedersenParameters,
}
mod private {
use super::*;

impl Default for Pedersen {
fn default() -> Self {
let pedersen_stark_default_params = PedersenParameters::default();
Self::new_with_params(pedersen_stark_default_params)
}
}
pub trait Sealed {}

impl Pedersen {
pub fn new_with_params(params: PedersenParameters) -> Self {
Self { params }
}

// Taken from Jonathan Lei's starknet-rs
// https://github.com/xJonathanLEI/starknet-rs/blob/4ab2f36872435ce57b1d8f55856702a6a30f270a/starknet-crypto/src/pedersen_hash.rs
impl<P: PedersenParameters> Sealed for P {}
}

pub trait Pedersen: PedersenParameters + self::private::Sealed {
/// Implements Starkware version of Pedersen hash of x and y.
/// Divides each of x and y into 4-bit chunks, and uses lookup tables to accumulate pre-calculated
/// points corresponding to a given chunk.
/// Accumulation starts from a "shift_point" whose points are derived from digits of pi.
/// Pre-calculated points are multiples by powers of 2 of the "shift_point".
///
/// Find specification at https://docs.starkware.co/starkex/crypto/pedersen-hash-function.html
pub fn hash(
&self,
x: &FieldElement<Stark252PrimeField>,
y: &FieldElement<Stark252PrimeField>,
) -> FieldElement<Stark252PrimeField> {
fn hash(x: &FE<Self::F>, y: &FE<Self::F>) -> FE<Self::F>;

/// Performs lookup to find the constant point corresponding to 4-bit chunks of given input.
/// Keeps adding up those points to the given accumulation point.
fn lookup_and_accumulate(acc: &mut Point<Self::EC>, bits: &[bool], prep: &[Point<Self::EC>]);
}

// FIXME: currently we make some assumptions that apply to `Stark252PrimeField`, so only mark the
// implementation when that's the field.
impl<P: PedersenParameters<F = Stark252PrimeField>> Pedersen for P {
// Taken from Jonathan Lei's starknet-rs
// https://github.com/xJonathanLEI/starknet-rs/blob/4ab2f36872435ce57b1d8f55856702a6a30f270a/starknet-crypto/src/pedersen_hash.rs

fn hash(x: &FE<Self::F>, y: &FE<Self::F>) -> FE<Self::F> {
let x = x.to_bits_le();
let y = y.to_bits_le();
let mut acc = self.params.shift_point.clone();
let mut acc = P::SHIFT_POINT.clone();

self.lookup_and_accumulate(&mut acc, &x[..248], &self.params.points_p1); // Add a_low * P1
self.lookup_and_accumulate(&mut acc, &x[248..252], &self.params.points_p2); // Add a_high * P2
self.lookup_and_accumulate(&mut acc, &y[..248], &self.params.points_p3); // Add b_low * P3
self.lookup_and_accumulate(&mut acc, &y[248..252], &self.params.points_p4); // Add b_high * P4
Self::lookup_and_accumulate(&mut acc, &x[..248], &P::POINTS_P1); // Add a_low * P1
Self::lookup_and_accumulate(&mut acc, &x[248..252], &P::POINTS_P2); // Add a_high * P2
Self::lookup_and_accumulate(&mut acc, &y[..248], &P::POINTS_P3); // Add b_low * P3
Self::lookup_and_accumulate(&mut acc, &y[248..252], &P::POINTS_P4); // Add b_high * P4

*acc.to_affine().x()
}

/// Performs lookup to find the constant point corresponding to 4-bit chunks of given input.
/// Keeps adding up those points to the given accumulation point.
fn lookup_and_accumulate(
&self,
acc: &mut ShortWeierstrassProjectivePoint<StarkCurve>,
bits: &[bool],
prep: &[ShortWeierstrassProjectivePoint<StarkCurve>],
) {
bits.chunks(self.params.curve_const_bits)
fn lookup_and_accumulate(acc: &mut Point<Self::EC>, bits: &[bool], prep: &[Point<Self::EC>]) {
bits.chunks(P::CURVE_CONST_BITS)
.enumerate()
.for_each(|(i, v)| {
let offset = bools_to_usize_le(v);
if offset > 0 {
// Table lookup at 'offset-1' in table for chunk 'i'
*acc = acc.operate_with_affine(&prep[i * self.params.table_size + offset - 1]);
*acc = acc.operate_with_affine(&prep[i * P::TABLE_SIZE + offset - 1]);
}
})
}
Expand All @@ -88,24 +80,23 @@ fn bools_to_usize_le(bools: &[bool]) -> usize {
#[cfg(test)]
mod tests {
use super::*;
use crate::hash::pedersen::parameters::PedersenStarkCurve;

// Test case ported from:
// https://github.com/starkware-libs/crypto-cpp/blob/95864fbe11d5287e345432dbe1e80dea3c35fc58/src/starkware/crypto/ffi/crypto_lib_test.go

#[test]
fn test_stark_curve() {
let pedersen = Pedersen::default();

let x = FieldElement::<Stark252PrimeField>::from_hex_unchecked(
let x = FE::from_hex_unchecked(
"03d937c035c878245caf64531a5756109c53068da139362728feb561405371cb",
);
let y = FieldElement::<Stark252PrimeField>::from_hex_unchecked(
let y = FE::from_hex_unchecked(
"0208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a",
);
let hash = pedersen.hash(&x, &y);
let hash = PedersenStarkCurve::hash(&x, &y);
assert_eq!(
hash,
FieldElement::<Stark252PrimeField>::from_hex_unchecked(
FE::from_hex_unchecked(
"030e480bed5fe53fa909cc0f8c4d99b8f9f2c016be4c41e13a4848797979c662"
)
);
Expand Down
55 changes: 34 additions & 21 deletions crypto/src/hash/pedersen/parameters.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,49 @@
use lambdaworks_math::elliptic_curve::short_weierstrass::traits::IsShortWeierstrass;
use lambdaworks_math::elliptic_curve::short_weierstrass::{
curves::stark_curve::StarkCurve, point::ShortWeierstrassProjectivePoint,
curves::stark_curve::StarkCurve, point::ShortWeierstrassProjectivePoint as Point,
};
use lambdaworks_math::elliptic_curve::traits::IsEllipticCurve;
use lambdaworks_math::field::fields::fft_friendly::stark_252_prime_field::Stark252PrimeField;
use lambdaworks_math::field::traits::IsPrimeField;

use crate::hash::pedersen::constants::*;

pub struct PedersenParameters {
pub curve_const_bits: usize,
pub table_size: usize,
pub shift_point: ShortWeierstrassProjectivePoint<StarkCurve>,
pub points_p1: [ShortWeierstrassProjectivePoint<StarkCurve>; 930],
pub points_p2: [ShortWeierstrassProjectivePoint<StarkCurve>; 15],
pub points_p3: [ShortWeierstrassProjectivePoint<StarkCurve>; 930],
pub points_p4: [ShortWeierstrassProjectivePoint<StarkCurve>; 15],
pub trait PedersenParameters {
type F: IsPrimeField + Clone;
type EC: IsEllipticCurve<BaseField = Self::F> + IsShortWeierstrass + Clone;

const CURVE_CONST_BITS: usize;
const TABLE_SIZE: usize;
const SHIFT_POINT: Point<Self::EC>;
const POINTS_P1: [Point<Self::EC>; 930];
const POINTS_P2: [Point<Self::EC>; 15];
const POINTS_P3: [Point<Self::EC>; 930];
const POINTS_P4: [Point<Self::EC>; 15];
}

impl Default for PedersenParameters {
pub struct PedersenStarkCurve;

impl Default for PedersenStarkCurve {
fn default() -> Self {
Self::new()
}
}

impl PedersenParameters {
impl PedersenStarkCurve {
pub fn new() -> Self {
let curve_const_bits = 4;
Self {
curve_const_bits,
table_size: (1 << curve_const_bits) - 1,
shift_point: shift_point(),
points_p1: points_p1(),
points_p2: points_p2(),
points_p3: points_p3(),
points_p4: points_p4(),
}
Self {}
}
}

impl PedersenParameters for PedersenStarkCurve {
type F = Stark252PrimeField;
type EC = StarkCurve;

const CURVE_CONST_BITS: usize = 4;
const TABLE_SIZE: usize = (1 << Self::CURVE_CONST_BITS) - 1;
const SHIFT_POINT: Point<Self::EC> = shift_point();
const POINTS_P1: [Point<Self::EC>; 930] = points_p1();
const POINTS_P2: [Point<Self::EC>; 15] = points_p2();
const POINTS_P3: [Point<Self::EC>; 930] = points_p3();
const POINTS_P4: [Point<Self::EC>; 15] = points_p4();
}

0 comments on commit 6e33e20

Please sign in to comment.