diff --git a/crates/precompile/bench/eip2537.rs b/crates/precompile/bench/eip2537.rs index b619c5f1a6..9f6302e6ca 100644 --- a/crates/precompile/bench/eip2537.rs +++ b/crates/precompile/bench/eip2537.rs @@ -1,104 +1,153 @@ -//! Benchmarks for the BLS12-381 precompiles +//! Benchmarks for the BLS12-381 precompiles based on benchmarkoor compute cases. + use ark_bls12_381::{Fq, Fr, G1Affine, G2Affine}; -use ark_ec::AffineRepr; +use ark_ec::{AffineRepr, CurveGroup}; +use ark_ff::UniformRand; use ark_std::rand::{rngs::StdRng, SeedableRng}; -use arkworks_general::{encode_base_field, encode_field_32_bytes, random_field, random_points}; -use criterion::{measurement::Measurement, BenchmarkGroup}; -use primitives::Bytes; -use revm_precompile::bls12_381_const::{PADDED_FP_LENGTH, PADDED_G1_LENGTH, PADDED_G2_LENGTH}; - +use criterion::{measurement::WallTime, BenchmarkGroup, Throughput}; +use primitives::{hex, Bytes}; +use revm_precompile::{ + bls12_381_const::{ + FP_LENGTH, FP_PAD_BY, G1_MSM_INPUT_LENGTH, G2_MSM_INPUT_LENGTH, PADDED_FP_LENGTH, + PADDED_G1_LENGTH, PADDED_G2_LENGTH, PAIRING_INPUT_LENGTH, SCALAR_LENGTH, + }, + Precompile, +}; +use std::hint::black_box; + +const GAS_LIMIT: u64 = u64::MAX; +const RESERVOIR: u64 = 0; const RNG_SEED: u64 = 42; -const MAX_MSM_SIZE: usize = 256; -const MAX_PAIRING_PAIRS: usize = 16; +const UNCACHABLE_INPUTS: usize = 32; + +const SPEC_Q: [u8; SCALAR_LENGTH] = + hex!("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"); type PrecompileInput = Vec; mod arkworks_general { - use super::StdRng; - use ark_bls12_381::Fq; - use ark_ec::AffineRepr; - use ark_ff::Field; - + use super::{Fq, FP_LENGTH, FP_PAD_BY, PADDED_FP_LENGTH}; use ark_serialize::CanonicalSerialize; - use revm_precompile::bls12_381_const::{FP_LENGTH, FP_PAD_BY, PADDED_FP_LENGTH}; - - pub(super) fn random_points(num_points: usize, rng: &mut StdRng) -> Vec

{ - let mut points = Vec::new(); - for _ in 0..num_points { - points.push(P::rand(rng)); - } - points - } - - pub(super) fn random_field(num_scalars: usize, rng: &mut StdRng) -> Vec { - let mut points = Vec::new(); - for _ in 0..num_scalars { - points.push(F::rand(rng)); - } - points - } - - // Note: This is kept separate from encode_base_field since it's for Fr scalars (32 bytes) - // while encode_base_field is for Fq field elements (padded to 64 bytes) - pub(super) fn encode_field_32_bytes(field: &F) -> Vec { - let mut bytes_be = vec![0u8; 32]; - field - .serialize_uncompressed(&mut bytes_be[..]) - .expect("Failed to serialize field element"); - bytes_be.reverse(); - - bytes_be - } - // Add padding to Fq field element and convert it to big endian (BE) format - pub(super) fn encode_base_field(fp: &Fq) -> Vec { + pub(super) fn encode_base_field(fp: &Fq) -> [u8; PADDED_FP_LENGTH] { let mut bytes = [0u8; FP_LENGTH]; fp.serialize_uncompressed(&mut bytes[..]) - .expect("Failed to serialize field element"); - bytes.reverse(); // Convert to big endian + .expect("failed to serialize field element"); + bytes.reverse(); - // Add padding - let mut padded_bytes = vec![0; PADDED_FP_LENGTH]; + let mut padded_bytes = [0u8; PADDED_FP_LENGTH]; padded_bytes[FP_PAD_BY..PADDED_FP_LENGTH].copy_from_slice(&bytes); - padded_bytes } } -/// Encode a BLS12-381 G1 point -// Note: This has been copied in from precompile/src/bls12_381 since -// those are not public +fn push_padded_fp(input: &mut PrecompileInput, fp: [u8; FP_LENGTH]) { + input.extend_from_slice(&[0u8; FP_PAD_BY]); + input.extend_from_slice(&fp); +} + +fn fp_from_unpadded(fp: [u8; FP_LENGTH]) -> PrecompileInput { + let mut input = Vec::with_capacity(PADDED_FP_LENGTH); + push_padded_fp(&mut input, fp); + input +} + +fn spec_p_minus_1() -> PrecompileInput { + fp_from_unpadded(hex!( + "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaa" + )) +} + +fn fp2_from_unpadded(c0: [u8; FP_LENGTH], c1: [u8; FP_LENGTH]) -> PrecompileInput { + let mut input = Vec::with_capacity(2 * PADDED_FP_LENGTH); + push_padded_fp(&mut input, c0); + push_padded_fp(&mut input, c1); + input +} + +fn g1_from_unpadded(x: [u8; FP_LENGTH], y: [u8; FP_LENGTH]) -> [u8; PADDED_G1_LENGTH] { + let mut input = [0u8; PADDED_G1_LENGTH]; + input[FP_PAD_BY..PADDED_FP_LENGTH].copy_from_slice(&x); + input[PADDED_FP_LENGTH + FP_PAD_BY..2 * PADDED_FP_LENGTH].copy_from_slice(&y); + input +} + +fn g2_from_unpadded( + x_c0: [u8; FP_LENGTH], + x_c1: [u8; FP_LENGTH], + y_c0: [u8; FP_LENGTH], + y_c1: [u8; FP_LENGTH], +) -> [u8; PADDED_G2_LENGTH] { + let mut input = [0u8; PADDED_G2_LENGTH]; + input[FP_PAD_BY..PADDED_FP_LENGTH].copy_from_slice(&x_c0); + input[PADDED_FP_LENGTH + FP_PAD_BY..2 * PADDED_FP_LENGTH].copy_from_slice(&x_c1); + input[2 * PADDED_FP_LENGTH + FP_PAD_BY..3 * PADDED_FP_LENGTH].copy_from_slice(&y_c0); + input[3 * PADDED_FP_LENGTH + FP_PAD_BY..4 * PADDED_FP_LENGTH].copy_from_slice(&y_c1); + input +} + +fn spec_p1() -> [u8; PADDED_G1_LENGTH] { + g1_from_unpadded( + hex!("112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca9426"), + hex!("186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a21"), + ) +} + +fn spec_g1() -> [u8; PADDED_G1_LENGTH] { + g1_from_unpadded( + hex!("17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb"), + hex!("08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1"), + ) +} + +fn spec_p2() -> [u8; PADDED_G2_LENGTH] { + g2_from_unpadded( + hex!("103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f27"), + hex!("086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68"), + hex!("0f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e"), + hex!("0d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d878451"), + ) +} + +fn spec_g2() -> [u8; PADDED_G2_LENGTH] { + g2_from_unpadded( + hex!("024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8"), + hex!("13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e"), + hex!("0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801"), + hex!("0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be"), + ) +} + +/// Encode a BLS12-381 G1 point. pub fn encode_bls12381_g1_point(input: &G1Affine) -> [u8; PADDED_G1_LENGTH] { let mut output = [0u8; PADDED_G1_LENGTH]; let Some((x, y)) = input.xy() else { - return output; // Point at infinity, return all zeros + return output; }; - let x_encoded = encode_base_field(&x); - let y_encoded = encode_base_field(&y); + let x_encoded = arkworks_general::encode_base_field(&x); + let y_encoded = arkworks_general::encode_base_field(&y); - // Copy the encoded values to the output output[..PADDED_FP_LENGTH].copy_from_slice(&x_encoded); output[PADDED_FP_LENGTH..].copy_from_slice(&y_encoded); output } -/// Encode a BLS12-381 G2 point +/// Encode a BLS12-381 G2 point. pub fn encode_bls12381_g2_point(input: &G2Affine) -> [u8; PADDED_G2_LENGTH] { let mut output = [0u8; PADDED_G2_LENGTH]; let Some((x, y)) = input.xy() else { - return output; // Point at infinity, return all zeros + return output; }; - let x_c0_encoded = encode_base_field(&x.c0); - let x_c1_encoded = encode_base_field(&x.c1); - let y_c0_encoded = encode_base_field(&y.c0); - let y_c1_encoded = encode_base_field(&y.c1); + let x_c0_encoded = arkworks_general::encode_base_field(&x.c0); + let x_c1_encoded = arkworks_general::encode_base_field(&x.c1); + let y_c0_encoded = arkworks_general::encode_base_field(&y.c0); + let y_c1_encoded = arkworks_general::encode_base_field(&y.c1); - // Copy encoded values to output output[..PADDED_FP_LENGTH].copy_from_slice(&x_c0_encoded); output[PADDED_FP_LENGTH..2 * PADDED_FP_LENGTH].copy_from_slice(&x_c1_encoded); output[2 * PADDED_FP_LENGTH..3 * PADDED_FP_LENGTH].copy_from_slice(&y_c0_encoded); @@ -107,196 +156,319 @@ pub fn encode_bls12381_g2_point(input: &G2Affine) -> [u8; PADDED_G2_LENGTH] { output } -fn g1_add_test_vectors(num_test_vectors: usize, rng: &mut StdRng) -> Vec { - let num_g1_points = num_test_vectors * 2; - let points: Vec = random_points(num_g1_points, rng); - - points - .chunks_exact(2) - .map(|chunk| { - let lhs = chunk[0]; - let rhs = chunk[1]; - let mut g1_add_input = Vec::new(); - g1_add_input.extend(encode_bls12381_g1_point(&lhs)); - g1_add_input.extend(encode_bls12381_g1_point(&rhs)); - g1_add_input - }) - .collect() -} - -fn g2_add_test_vectors(num_test_vectors: usize, rng: &mut StdRng) -> Vec { - let num_g2_points = num_test_vectors * 2; - let points: Vec = random_points(num_g2_points, rng); - - points - .chunks_exact(2) - .map(|chunk| { - let lhs = chunk[0]; - let rhs = chunk[1]; - let mut g2_add_input = Vec::new(); - g2_add_input.extend(encode_bls12381_g2_point(&lhs)); - g2_add_input.extend(encode_bls12381_g2_point(&rhs)); - g2_add_input - }) - .collect() -} - -/// Add benches for the BLS12-381 G1 add precompile -pub fn add_g1_add_benches(group: &mut BenchmarkGroup<'_, M>) { - use revm_precompile::bls12_381::g1_add::PRECOMPILE; - - let mut rng = StdRng::seed_from_u64(RNG_SEED); - let test_vectors = g1_add_test_vectors(1, &mut rng); - let input = Bytes::from(test_vectors[0].clone()); - - group.bench_function("g1_add", |b| { - b.iter(|| PRECOMPILE.execute(&input, u64::MAX, 0)); - }); +fn splitmix32(seed: u64) -> u32 { + let mut z = seed.wrapping_add(0x9e3779b97f4a7c15); + z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9); + z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb); + ((z ^ (z >> 31)) as u32).max(1) } -/// Add benches for the BLS12-381 G2 add precompile -pub fn add_g2_add_benches(group: &mut BenchmarkGroup<'_, M>) { - use revm_precompile::bls12_381::g2_add::PRECOMPILE; - - let mut rng = StdRng::seed_from_u64(RNG_SEED); - let test_vectors = g2_add_test_vectors(1, &mut rng); - let input = Bytes::from(test_vectors[0].clone()); +fn seeded_g1_point(seed: u64) -> [u8; PADDED_G1_LENGTH] { + let scalar = Fr::from(splitmix32(seed) as u64); + let point = (G1Affine::generator() * scalar).into_affine(); + encode_bls12381_g1_point(&point) +} - group.bench_function("g2_add", |b| { - b.iter(|| PRECOMPILE.execute(&input, u64::MAX, 0)); - }); +fn seeded_g2_point(seed: u64) -> [u8; PADDED_G2_LENGTH] { + let scalar = Fr::from(splitmix32(seed) as u64); + let point = (G2Affine::generator() * scalar).into_affine(); + encode_bls12381_g2_point(&point) } -/// Add benches for the BLS12-381 G1 msm precompile -pub fn add_g1_msm_benches(group: &mut BenchmarkGroup<'_, M>) { - use revm_precompile::bls12_381::g1_msm::PRECOMPILE; +fn seeded_fp(seed: u64) -> PrecompileInput { + let mut rng = StdRng::seed_from_u64(seed); + let fp = Fq::rand(&mut rng); + arkworks_general::encode_base_field(&fp).to_vec() +} - let sizes_to_bench = [MAX_MSM_SIZE, MAX_MSM_SIZE / 2, 2, 1]; +fn seeded_fp2(seed: u64) -> PrecompileInput { + let mut rng = StdRng::seed_from_u64(seed); + let c0 = Fq::rand(&mut rng); + let c1 = Fq::rand(&mut rng); - for size in sizes_to_bench { - let mut rng = StdRng::seed_from_u64(RNG_SEED); - let test_vector = g1_msm_test_vectors(size, &mut rng); - let input = Bytes::from(test_vector); + let mut input = Vec::with_capacity(2 * PADDED_FP_LENGTH); + input.extend_from_slice(&arkworks_general::encode_base_field(&c0)); + input.extend_from_slice(&arkworks_general::encode_base_field(&c1)); + input +} - group.bench_function(format!("g1_msm (size {size})"), |b| { - b.iter(|| PRECOMPILE.execute(&input, u64::MAX, 0)); - }); - } +fn g1_add_input(lhs: [u8; PADDED_G1_LENGTH]) -> PrecompileInput { + let mut input = Vec::with_capacity(2 * PADDED_G1_LENGTH); + input.extend_from_slice(&lhs); + input.extend_from_slice(&spec_p1()); + input } -fn g1_msm_test_vectors(msm_size: usize, rng: &mut StdRng) -> PrecompileInput { - let points: Vec = random_points(msm_size, rng); - let scalars: Vec = random_field(msm_size, rng); +fn g2_add_input(lhs: [u8; PADDED_G2_LENGTH]) -> PrecompileInput { + let mut input = Vec::with_capacity(2 * PADDED_G2_LENGTH); + input.extend_from_slice(&lhs); + input.extend_from_slice(&spec_p2()); + input +} - let mut input = Vec::new(); - for (point, scalar) in points.iter().zip(scalars.iter()) { - input.extend(encode_bls12381_g1_point(point)); - input.extend(encode_field_32_bytes(scalar)); +fn g1_msm_input(point: [u8; PADDED_G1_LENGTH], k: usize) -> PrecompileInput { + let mut input = Vec::with_capacity(k * G1_MSM_INPUT_LENGTH); + for _ in 0..k { + input.extend_from_slice(&point); + input.extend_from_slice(&SPEC_Q); } - input } -fn g2_msm_test_vectors(msm_size: usize, rng: &mut StdRng) -> PrecompileInput { - let points: Vec = random_points(msm_size, rng); - let scalars: Vec = random_field(msm_size, rng); - - let mut input = Vec::new(); - for (point, scalar) in points.iter().zip(scalars.iter()) { - input.extend(encode_bls12381_g2_point(point)); - input.extend(encode_field_32_bytes(scalar)); +fn g2_msm_input(point: [u8; PADDED_G2_LENGTH], k: usize) -> PrecompileInput { + let mut input = Vec::with_capacity(k * G2_MSM_INPUT_LENGTH); + for _ in 0..k { + input.extend_from_slice(&point); + input.extend_from_slice(&SPEC_Q); } - input } -/// Add benches for the BLS12-381 G2 msm precompile -pub fn add_g2_msm_benches(group: &mut BenchmarkGroup<'_, M>) { - use revm_precompile::bls12_381::g2_msm::PRECOMPILE; - - let sizes_to_bench = [MAX_MSM_SIZE, MAX_MSM_SIZE / 2, 2, 1]; - - for size in sizes_to_bench { - let mut rng = StdRng::seed_from_u64(RNG_SEED); - let test_vector = g2_msm_test_vectors(size, &mut rng); - let input = Bytes::from(test_vector); - - group.bench_function(format!("g2_msm (size {size})"), |b| { - b.iter(|| PRECOMPILE.execute(&input, u64::MAX, 0)); - }); +fn pairing_input(num_pairs: usize) -> PrecompileInput { + let mut input = Vec::with_capacity(num_pairs * PAIRING_INPUT_LENGTH); + for _ in 0..num_pairs { + input.extend_from_slice(&spec_g1()); + input.extend_from_slice(&spec_g2()); } + input } -fn pairing_test_vectors(num_pairs: usize, rng: &mut StdRng) -> PrecompileInput { - // Generate random G1 and G2 points for pairing - let g1_points: Vec = random_points(num_pairs, rng); - let g2_points: Vec = random_points(num_pairs, rng); - - let mut input = Vec::new(); - for (g1, g2) in g1_points.iter().zip(g2_points.iter()) { - input.extend(encode_bls12381_g1_point(g1)); - input.extend(encode_bls12381_g2_point(g2)); +fn seeded_pairing_input(num_pairs: usize, seed: u64) -> PrecompileInput { + let mut input = Vec::with_capacity(num_pairs * PAIRING_INPUT_LENGTH); + for i in 0..num_pairs as u64 { + input.extend_from_slice(&seeded_g1_point(seed + 2 * i)); + input.extend_from_slice(&seeded_g2_point(seed + 2 * i + 1)); } - input } -/// Add benches for the BLS12-381 pairing precompile -pub fn add_pairing_benches(group: &mut BenchmarkGroup<'_, M>) { - use revm_precompile::bls12_381::pairing::PRECOMPILE; - - let sizes_to_bench = [MAX_PAIRING_PAIRS, MAX_PAIRING_PAIRS / 2, 2, 1]; - - for pairs in sizes_to_bench { - let mut rng = StdRng::seed_from_u64(RNG_SEED); - let test_vector = pairing_test_vectors(pairs, &mut rng); - let input = Bytes::from(test_vector); - - group.bench_function(format!("pairing ({pairs} pairs)"), |b| { - b.iter(|| PRECOMPILE.execute(&input, u64::MAX, 0)); +fn bench_precompile( + group: &mut BenchmarkGroup<'_, WallTime>, + name: &'static str, + precompile: &Precompile, + input: PrecompileInput, +) { + let input = Bytes::from(input); + group.throughput(Throughput::Bytes(input.len() as u64)); + group.bench_function(name, |b| { + b.iter(|| { + let output = precompile + .execute(black_box(&input), GAS_LIMIT, RESERVOIR) + .expect("BLS12-381 benchmark input succeeds"); + black_box(output); }); - } + }); } -fn map_fp_to_g1_test_vectors(rng: &mut StdRng) -> PrecompileInput { - let fp: Fq = random_field(1, rng)[0]; - encode_base_field(&fp) +fn bench_feedback_precompile( + group: &mut BenchmarkGroup<'_, WallTime>, + name: &'static str, + precompile: &Precompile, + input: PrecompileInput, + ret_size: usize, +) { + group.throughput(Throughput::Bytes(input.len() as u64)); + group.bench_function(name, move |b| { + let mut input = input.clone(); + b.iter(|| { + // Benchmarkoor's uncachable BLS loop uses args_offset=0, ret_offset=0, and + // args_size=CALLDATASIZE, so each call writes its output over the next input prefix. + let output = precompile + .execute(black_box(input.as_slice()), GAS_LIMIT, RESERVOIR) + .expect("BLS12-381 benchmark input succeeds"); + let copy_len = ret_size.min(output.bytes.len()).min(input.len()); + input[..copy_len].copy_from_slice(&output.bytes[..copy_len]); + black_box(&input); + black_box(output); + }); + }); } -/// Add benches for the BLS12-381 map fp to g1 precompiles -pub fn add_map_fp_to_g1_benches(group: &mut BenchmarkGroup<'_, M>) { - use revm_precompile::bls12_381::map_fp_to_g1::PRECOMPILE; - - let mut rng = StdRng::seed_from_u64(RNG_SEED); - let test_vector = map_fp_to_g1_test_vectors(&mut rng); - let input = Bytes::from(test_vector); - - group.bench_function("map_fp_to_g1", |b| { - b.iter(|| PRECOMPILE.execute(&input, u64::MAX, 0)); +fn bench_cycling_precompile( + group: &mut BenchmarkGroup<'_, WallTime>, + name: &'static str, + precompile: &Precompile, + inputs: Vec, +) { + let inputs: Vec = inputs.into_iter().map(Bytes::from).collect(); + group.throughput(Throughput::Bytes(inputs[0].len() as u64)); + group.bench_function(name, move |b| { + let mut index = 0; + b.iter(|| { + let input = &inputs[index % inputs.len()]; + index = index.wrapping_add(1); + + let output = precompile + .execute(black_box(input), GAS_LIMIT, RESERVOIR) + .expect("BLS12-381 benchmark input succeeds"); + black_box(output); + }); }); } -fn map_fp2_to_g2_test_vectors(rng: &mut StdRng) -> PrecompileInput { - let fp_c0: Fq = random_field(1, rng)[0]; - let fp_c1: Fq = random_field(1, rng)[0]; - - let mut input = Vec::new(); - - input.extend(encode_base_field(&fp_c0)); - input.extend(encode_base_field(&fp_c1)); +/// Add benchmarkoor-named benches for the BLS12-381 precompiles. +pub fn add_benches(group: &mut BenchmarkGroup<'_, WallTime>) { + add_cached_benches(group); + add_msm_size_benches(group); + add_pairing_size_benches(group); + add_uncachable_benches(group); +} - input +fn add_cached_benches(group: &mut BenchmarkGroup<'_, WallTime>) { + bench_precompile( + group, + "test_bls12_381[bls12_g1add]", + &revm_precompile::bls12_381::g1_add::PRECOMPILE, + g1_add_input(spec_g1()), + ); + bench_precompile( + group, + "test_bls12_381[bls12_g1msm]", + &revm_precompile::bls12_381::g1_msm::PRECOMPILE, + g1_msm_input(spec_p1(), 128), + ); + bench_precompile( + group, + "test_bls12_381[bls12_g2add]", + &revm_precompile::bls12_381::g2_add::PRECOMPILE, + g2_add_input(spec_g2()), + ); + bench_precompile( + group, + "test_bls12_381[bls12_g2msm]", + &revm_precompile::bls12_381::g2_msm::PRECOMPILE, + g2_msm_input(spec_p2(), 64), + ); + bench_precompile( + group, + "test_bls12_381[bls12_pairing_check]", + &revm_precompile::bls12_381::pairing::PRECOMPILE, + pairing_input(1), + ); + bench_precompile( + group, + "test_bls12_381[bls12_fp_to_g1]", + &revm_precompile::bls12_381::map_fp_to_g1::PRECOMPILE, + spec_p_minus_1(), + ); + bench_precompile( + group, + "test_bls12_381[bls12_fp_to_g2]", + &revm_precompile::bls12_381::map_fp2_to_g2::PRECOMPILE, + fp2_from_unpadded( + hex!("1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaa"), + hex!("1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaa"), + ), + ); } -/// Add benches for the BLS12-381 map fp2 to g2 precompiles -pub fn add_map_fp2_to_g2_benches(group: &mut BenchmarkGroup<'_, M>) { - use revm_precompile::bls12_381::map_fp2_to_g2::PRECOMPILE; +fn add_msm_size_benches(group: &mut BenchmarkGroup<'_, WallTime>) { + for k in [1, 16, 64, 128] { + bench_precompile( + group, + match k { + 1 => "test_bls12_g1_msm[k=1]", + 16 => "test_bls12_g1_msm[k=16]", + 64 => "test_bls12_g1_msm[k=64]", + 128 => "test_bls12_g1_msm[k=128]", + _ => unreachable!(), + }, + &revm_precompile::bls12_381::g1_msm::PRECOMPILE, + g1_msm_input(spec_p1(), k), + ); + + bench_precompile( + group, + match k { + 1 => "test_bls12_g2_msm[k=1]", + 16 => "test_bls12_g2_msm[k=16]", + 64 => "test_bls12_g2_msm[k=64]", + 128 => "test_bls12_g2_msm[k=128]", + _ => unreachable!(), + }, + &revm_precompile::bls12_381::g2_msm::PRECOMPILE, + g2_msm_input(spec_p2(), k), + ); + } +} - let mut rng = StdRng::seed_from_u64(RNG_SEED); - let test_vector = map_fp2_to_g2_test_vectors(&mut rng); - let input = Bytes::from(test_vector); +fn add_pairing_size_benches(group: &mut BenchmarkGroup<'_, WallTime>) { + for num_pairs in [1, 3, 6, 12, 24] { + bench_precompile( + group, + match num_pairs { + 1 => "test_bls12_pairing[num_pairs=1]", + 3 => "test_bls12_pairing[num_pairs=3]", + 6 => "test_bls12_pairing[num_pairs=6]", + 12 => "test_bls12_pairing[num_pairs=12]", + 24 => "test_bls12_pairing[num_pairs=24]", + _ => unreachable!(), + }, + &revm_precompile::bls12_381::pairing::PRECOMPILE, + pairing_input(num_pairs), + ); + } +} - group.bench_function("map_fp2_to_g2", |b| { - b.iter(|| PRECOMPILE.execute(&input, u64::MAX, 0)); - }); +fn add_uncachable_benches(group: &mut BenchmarkGroup<'_, WallTime>) { + bench_feedback_precompile( + group, + "test_bls12_381_uncachable[bls12_g1add]", + &revm_precompile::bls12_381::g1_add::PRECOMPILE, + g1_add_input(seeded_g1_point(0)), + PADDED_G1_LENGTH, + ); + bench_feedback_precompile( + group, + "test_bls12_381_uncachable[bls12_g2add]", + &revm_precompile::bls12_381::g2_add::PRECOMPILE, + g2_add_input(seeded_g2_point(0)), + PADDED_G2_LENGTH, + ); + bench_feedback_precompile( + group, + "test_bls12_381_uncachable[bls12_g1msm]", + &revm_precompile::bls12_381::g1_msm::PRECOMPILE, + g1_msm_input(seeded_g1_point(0), 1), + PADDED_G1_LENGTH, + ); + bench_feedback_precompile( + group, + "test_bls12_381_uncachable[bls12_g2msm]", + &revm_precompile::bls12_381::g2_msm::PRECOMPILE, + g2_msm_input(seeded_g2_point(0), 1), + PADDED_G2_LENGTH, + ); + bench_feedback_precompile( + group, + "test_bls12_381_uncachable[bls12_fp_to_g1]", + &revm_precompile::bls12_381::map_fp_to_g1::PRECOMPILE, + seeded_fp(0), + PADDED_FP_LENGTH, + ); + bench_feedback_precompile( + group, + "test_bls12_381_uncachable[bls12_fp_to_g2]", + &revm_precompile::bls12_381::map_fp2_to_g2::PRECOMPILE, + seeded_fp2(0), + 2 * PADDED_FP_LENGTH, + ); + + for num_pairs in [1, 3, 6, 12, 24] { + bench_cycling_precompile( + group, + match num_pairs { + 1 => "test_bls12_pairing_uncachable[num_pairs=1]", + 3 => "test_bls12_pairing_uncachable[num_pairs=3]", + 6 => "test_bls12_pairing_uncachable[num_pairs=6]", + 12 => "test_bls12_pairing_uncachable[num_pairs=12]", + 24 => "test_bls12_pairing_uncachable[num_pairs=24]", + _ => unreachable!(), + }, + &revm_precompile::bls12_381::pairing::PRECOMPILE, + (0..UNCACHABLE_INPUTS) + .map(|seed| seeded_pairing_input(num_pairs, RNG_SEED + seed as u64)) + .collect(), + ); + } } diff --git a/crates/precompile/bench/main.rs b/crates/precompile/bench/main.rs index 4ac9929cfb..ca06a6e4ff 100644 --- a/crates/precompile/bench/main.rs +++ b/crates/precompile/bench/main.rs @@ -16,13 +16,7 @@ pub fn benchmark_crypto_precompiles(c: &mut Criterion) { let mut group = c.benchmark_group("Crypto Precompile benchmarks"); // Run BLS12-381 benchmarks (EIP-2537) - eip2537::add_g1_add_benches(&mut group); - eip2537::add_g2_add_benches(&mut group); - eip2537::add_g1_msm_benches(&mut group); - eip2537::add_g2_msm_benches(&mut group); - eip2537::add_pairing_benches(&mut group); - eip2537::add_map_fp_to_g1_benches(&mut group); - eip2537::add_map_fp2_to_g2_benches(&mut group); + eip2537::add_benches(&mut group); // Run BN254 benchmarks eip1962::add_bn254_add_benches(&mut group); diff --git a/crates/precompile/src/bls12_381/arkworks.rs b/crates/precompile/src/bls12_381/arkworks.rs index f4acc94c74..d57fe699fc 100644 --- a/crates/precompile/src/bls12_381/arkworks.rs +++ b/crates/precompile/src/bls12_381/arkworks.rs @@ -434,12 +434,13 @@ pub(crate) fn p1_msm_bytes( // NB: MSM requires subgroup check let point = read_g1(&x, &y)?; - // Skip zero scalars after validating the point - if scalar_bytes.iter().all(|&b| b == 0) { + let scalar = read_scalar(&scalar_bytes)?; + + // Skip scalars that reduce to zero after validating the point. + if scalar.is_zero() { continue; } - let scalar = read_scalar(&scalar_bytes)?; g1_points.push(point); scalars.push(scalar); } @@ -472,12 +473,13 @@ pub(crate) fn p2_msm_bytes( // NB: MSM requires subgroup check let point = read_g2(&x_0, &x_1, &y_0, &y_1)?; - // Skip zero scalars after validating the point - if scalar_bytes.iter().all(|&b| b == 0) { + let scalar = read_scalar(&scalar_bytes)?; + + // Skip scalars that reduce to zero after validating the point. + if scalar.is_zero() { continue; } - let scalar = read_scalar(&scalar_bytes)?; g2_points.push(point); scalars.push(scalar); } diff --git a/crates/precompile/src/bls12_381/blst.rs b/crates/precompile/src/bls12_381/blst.rs index 0ae841058a..a698eddd74 100644 --- a/crates/precompile/src/bls12_381/blst.rs +++ b/crates/precompile/src/bls12_381/blst.rs @@ -9,10 +9,11 @@ use crate::{ use blst::{ blst_bendian_from_fp, blst_final_exp, blst_fp, blst_fp12, blst_fp12_is_one, blst_fp12_mul, blst_fp2, blst_fp_from_bendian, blst_map_to_g1, blst_map_to_g2, blst_miller_loop, blst_p1, - blst_p1_add_or_double_affine, blst_p1_affine, blst_p1_affine_in_g1, blst_p1_affine_on_curve, - blst_p1_from_affine, blst_p1_mult, blst_p1_to_affine, blst_p2, blst_p2_add_or_double_affine, - blst_p2_affine, blst_p2_affine_in_g2, blst_p2_affine_on_curve, blst_p2_from_affine, - blst_p2_mult, blst_p2_to_affine, blst_scalar, blst_scalar_from_bendian, MultiPoint, + blst_p1_add_or_double_affine, blst_p1_affine, blst_p1_affine_in_g1, blst_p1_affine_is_inf, + blst_p1_affine_on_curve, blst_p1_from_affine, blst_p1_mult, blst_p1_to_affine, blst_p2, + blst_p2_add_or_double_affine, blst_p2_affine, blst_p2_affine_in_g2, blst_p2_affine_is_inf, + blst_p2_affine_on_curve, blst_p2_from_affine, blst_p2_mult, blst_p2_to_affine, blst_scalar, + blst_scalar_from_be_bytes, MultiPoint, }; use std::vec::Vec; @@ -398,6 +399,29 @@ fn decode_g1_on_curve( fn read_g1(x: &[u8; FP_LENGTH], y: &[u8; FP_LENGTH]) -> Result { _extract_g1_input(x, y, true) } + +fn read_g1_msm( + x: &[u8; FP_LENGTH], + y: &[u8; FP_LENGTH], +) -> Result, PrecompileHalt> { + let out = decode_g1_on_curve(x, y)?; + + // SAFETY: Out is a blst value. + if unsafe { blst_p1_affine_is_inf(&out) } { + return Ok(None); + } + + // NB: MSM requires a subgroup check for non-infinity points. + // + // Infinity is the identity, lies in every subgroup, and contributes nothing to + // the MSM result, so it can be skipped after the on-curve validation above. + // SAFETY: Out is a blst value. + if unsafe { !blst_p1_affine_in_g1(&out) } { + return Err(PrecompileHalt::Bls12381G1NotInSubgroup); + } + + Ok(Some(out)) +} /// Extracts a G1 point in Affine format from the x and y coordinates /// without performing a subgroup check. /// @@ -515,6 +539,31 @@ fn read_g2( ) -> Result { _extract_g2_input(a_x_0, a_x_1, a_y_0, a_y_1, true) } + +fn read_g2_msm( + a_x_0: &[u8; FP_LENGTH], + a_x_1: &[u8; FP_LENGTH], + a_y_0: &[u8; FP_LENGTH], + a_y_1: &[u8; FP_LENGTH], +) -> Result, PrecompileHalt> { + let out = decode_g2_on_curve(a_x_0, a_x_1, a_y_0, a_y_1)?; + + // SAFETY: Out is a blst value. + if unsafe { blst_p2_affine_is_inf(&out) } { + return Ok(None); + } + + // NB: MSM requires a subgroup check for non-infinity points. + // + // Infinity is the identity, lies in every subgroup, and contributes nothing to + // the MSM result, so it can be skipped after the on-curve validation above. + // SAFETY: Out is a blst value. + if unsafe { !blst_p2_affine_in_g2(&out) } { + return Err(PrecompileHalt::Bls12381G2NotInSubgroup); + } + + Ok(Some(out)) +} /// Extracts a G2 point in Affine format from the x and y coordinates /// without performing a subgroup check. /// @@ -583,7 +632,8 @@ fn read_fp(input: &[u8; FP_LENGTH]) -> Result { } /// Extracts a scalar from a 32 byte slice representation, decoding the input as a Big Endian -/// unsigned integer. If the input is not exactly 32 bytes long, an error is returned. +/// unsigned integer and reducing it modulo the subgroup order. If the input is not exactly 32 +/// bytes long, an error is returned. /// /// From [EIP-2537](https://eips.ethereum.org/EIPS/eip-2537): /// * A scalar for the multiplication operation is encoded as 32 bytes by performing BigEndian @@ -600,16 +650,17 @@ fn read_scalar(input: &[u8]) -> Result { let mut out = blst_scalar::default(); // SAFETY: `input` length is checked previously, out is a blst value. unsafe { - // Note: We do not use `blst_scalar_fr_check` here because, from EIP-2537: - // - // * The corresponding integer is not required to be less than or equal than main subgroup - // order `q`. - blst_scalar_from_bendian(&mut out, input.as_ptr()) - }; + blst_scalar_from_be_bytes(&mut out, input.as_ptr(), input.len()); + } Ok(out) } +#[inline] +fn scalar_is_zero(scalar: &blst_scalar) -> bool { + scalar.b.iter().all(|&b| b == 0) +} + /// Checks if the input is a valid big-endian representation of a field element. fn is_valid_be(input: &[u8; 48]) -> bool { *input < MODULUS_REPR @@ -693,15 +744,17 @@ pub(crate) fn p1_msm_bytes( for pair_result in point_scalar_pairs { let ((x, y), scalar_bytes) = pair_result?; - // NB: MSM requires subgroup check - let point = read_g1(&x, &y)?; + let Some(point) = read_g1_msm(&x, &y)? else { + continue; + }; + + let scalar = read_scalar(&scalar_bytes)?; - // Skip zero scalars after validating the point - if scalar_bytes.iter().all(|&b| b == 0) { + // Skip scalars that reduce to zero after validating the point. + if scalar_is_zero(&scalar) { continue; } - let scalar = read_scalar(&scalar_bytes)?; g1_points.push(point); scalars.push(scalar); } @@ -731,15 +784,17 @@ pub(crate) fn p2_msm_bytes( for pair_result in point_scalar_pairs { let ((x_0, x_1, y_0, y_1), scalar_bytes) = pair_result?; - // NB: MSM requires subgroup check - let point = read_g2(&x_0, &x_1, &y_0, &y_1)?; + let Some(point) = read_g2_msm(&x_0, &x_1, &y_0, &y_1)? else { + continue; + }; + + let scalar = read_scalar(&scalar_bytes)?; - // Skip zero scalars after validating the point - if scalar_bytes.iter().all(|&b| b == 0) { + // Skip scalars that reduce to zero after validating the point. + if scalar_is_zero(&scalar) { continue; } - let scalar = read_scalar(&scalar_bytes)?; g2_points.push(point); scalars.push(scalar); } @@ -761,3 +816,20 @@ pub(crate) fn p2_msm_bytes( pub(crate) fn pairing_check_bytes(pairs: &[PairingPair]) -> Result { super::pairing_common::pairing_check_bytes_generic(pairs, read_g1, read_g2, pairing_check) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn read_g1_msm_skips_infinity() { + let zero = [0u8; FP_LENGTH]; + assert!(read_g1_msm(&zero, &zero).unwrap().is_none()); + } + + #[test] + fn read_g2_msm_skips_infinity() { + let zero = [0u8; FP_LENGTH]; + assert!(read_g2_msm(&zero, &zero, &zero, &zero).unwrap().is_none()); + } +} diff --git a/crates/precompile/src/bls12_381/g1_msm.rs b/crates/precompile/src/bls12_381/g1_msm.rs index 334a0d4f10..cf7f627778 100644 --- a/crates/precompile/src/bls12_381/g1_msm.rs +++ b/crates/precompile/src/bls12_381/g1_msm.rs @@ -64,6 +64,24 @@ pub fn g1_msm(input: &[u8], gas_limit: u64) -> EthPrecompileResult { mod test { use super::*; use primitives::{hex, Bytes}; + use std::vec::Vec; + + const SCALAR_MODULUS: [u8; SCALAR_LENGTH] = + hex!("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"); + + fn g1_generator_with_scalar_modulus() -> Bytes { + let mut input = Vec::with_capacity(G1_MSM_INPUT_LENGTH); + input.extend_from_slice(&[0u8; 16]); + input.extend_from_slice(&hex!( + "17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb" + )); + input.extend_from_slice(&[0u8; 16]); + input.extend_from_slice(&hex!( + "08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1" + )); + input.extend_from_slice(&SCALAR_MODULUS); + input.into() + } #[test] fn bls_g1multiexp_g1_not_on_curve_but_in_subgroup() { @@ -71,4 +89,11 @@ mod test { let fail = g1_msm(&input, G1_MSM_BASE_GAS_FEE); assert_eq!(fail, Err(PrecompileHalt::Bls12381G1NotOnCurve)); } + + #[test] + fn bls_g1msm_scalar_modulus_returns_infinity() { + let output = g1_msm(&g1_generator_with_scalar_modulus(), G1_MSM_BASE_GAS_FEE).unwrap(); + assert_eq!(output.gas_used, G1_MSM_BASE_GAS_FEE); + assert_eq!(output.bytes, Bytes::from(vec![0; PADDED_G1_LENGTH])); + } } diff --git a/crates/precompile/src/bls12_381/g2_msm.rs b/crates/precompile/src/bls12_381/g2_msm.rs index 10b993610a..b8b688f217 100644 --- a/crates/precompile/src/bls12_381/g2_msm.rs +++ b/crates/precompile/src/bls12_381/g2_msm.rs @@ -55,3 +55,42 @@ pub fn g2_msm(input: &[u8], gas_limit: u64) -> EthPrecompileResult { Ok(EthPrecompileOutput::new(required_gas, padded_result.into())) } + +#[cfg(test)] +mod test { + use super::*; + use primitives::{hex, Bytes}; + use std::vec::Vec; + + const SCALAR_MODULUS: [u8; SCALAR_LENGTH] = + hex!("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"); + + fn g2_generator_with_scalar_modulus() -> Bytes { + let mut input = Vec::with_capacity(G2_MSM_INPUT_LENGTH); + input.extend_from_slice(&[0u8; 16]); + input.extend_from_slice(&hex!( + "024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8" + )); + input.extend_from_slice(&[0u8; 16]); + input.extend_from_slice(&hex!( + "13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e" + )); + input.extend_from_slice(&[0u8; 16]); + input.extend_from_slice(&hex!( + "0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801" + )); + input.extend_from_slice(&[0u8; 16]); + input.extend_from_slice(&hex!( + "0606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be" + )); + input.extend_from_slice(&SCALAR_MODULUS); + input.into() + } + + #[test] + fn bls_g2msm_scalar_modulus_returns_infinity() { + let output = g2_msm(&g2_generator_with_scalar_modulus(), G2_MSM_BASE_GAS_FEE).unwrap(); + assert_eq!(output.gas_used, G2_MSM_BASE_GAS_FEE); + assert_eq!(output.bytes, Bytes::from(vec![0; PADDED_G2_LENGTH])); + } +}