Skip to content

Commit 2f3a86f

Browse files
xqftCopilotilitteri
authored
chore(l2): use SP1 patched crate for ecmul precompile (#5133)
**Motivation** The ecmul precompile represents 10% of the total proving cycles, and we are not using the corresponding SP1 patched crate for this operation that uses a zkVM precompile. This PR fixes that and reduces 81k cycles (86% of ecmul, 10% of total) **Flamegraphs** before: <img width="2936" height="1134" alt="image" src="https://github.com/user-attachments/assets/42212408-8f2e-4dd4-80ea-c9b87a8383ef" /> after: <img width="2716" height="1220" alt="image" src="https://github.com/user-attachments/assets/f82e25f0-a7cb-43e9-ae4d-307a39bd9d0a" /> **Proving times** Mainnet blocks proven in a RTX 4090 | Block number | main | l2/ecmul_sp1 | |---------------|---------|----------------| | 23426993 | 13m 19s | 13m 20s | | 23426994 | 08m 42s | 08m 05s | | 23426995 | 06m 39s | 06m 35s | | 23426996 | 11m 24s | 10m 50s | --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: Ivan Litteri <[email protected]> Co-authored-by: ilitteri <[email protected]>
1 parent 6e40a72 commit 2f3a86f

File tree

1 file changed

+91
-34
lines changed

1 file changed

+91
-34
lines changed

crates/vm/levm/src/precompiles.rs

Lines changed: 91 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -770,21 +770,23 @@ pub fn ecadd(calldata: &Bytes, gas_remaining: &mut u64, _fork: Fork) -> Result<B
770770
pub fn ecmul(calldata: &Bytes, gas_remaining: &mut u64, _fork: Fork) -> Result<Bytes, VMError> {
771771
// If calldata does not reach the required length, we should fill the rest with zeros
772772
let calldata = fill_with_zeros(calldata, 96);
773-
774773
increase_precompile_consumed_gas(ECMUL_COST, gas_remaining)?;
775774

776-
let point_x = calldata.get(0..32).ok_or(InternalError::Slicing)?;
777-
let point_y = calldata.get(32..64).ok_or(InternalError::Slicing)?;
778-
let scalar = calldata.get(64..96).ok_or(InternalError::Slicing)?;
779-
780-
if u256_from_big_endian(point_x) >= ALT_BN128_PRIME
781-
|| u256_from_big_endian(point_y) >= ALT_BN128_PRIME
782-
{
783-
return Err(PrecompileError::InvalidPoint.into());
784-
}
775+
let (Some(g1), Some(scalar)) = (
776+
parse_bn254_g1(&calldata, 0),
777+
parse_bn254_scalar(&calldata, 64),
778+
) else {
779+
return Err(InternalError::Slicing.into());
780+
};
781+
validate_bn254_g1_coords(&g1)?;
782+
bn254_g1_mul(g1, scalar)
783+
}
785784

786-
let x = ark_bn254::Fq::from_be_bytes_mod_order(point_x);
787-
let y = ark_bn254::Fq::from_be_bytes_mod_order(point_y);
785+
#[cfg(all(not(feature = "sp1"), not(feature = "risc0")))]
786+
#[inline]
787+
pub fn bn254_g1_mul(point: G1, scalar: U256) -> Result<Bytes, VMError> {
788+
let x = ark_bn254::Fq::from_be_bytes_mod_order(&point.0.to_big_endian());
789+
let y = ark_bn254::Fq::from_be_bytes_mod_order(&point.1.to_big_endian());
788790

789791
if x.is_zero() && y.is_zero() {
790792
return Ok(Bytes::from([0u8; 64].to_vec()));
@@ -795,7 +797,7 @@ pub fn ecmul(calldata: &Bytes, gas_remaining: &mut u64, _fork: Fork) -> Result<B
795797
return Err(PrecompileError::InvalidPoint.into());
796798
}
797799

798-
let scalar = FrArk::from_be_bytes_mod_order(scalar);
800+
let scalar = FrArk::from_be_bytes_mod_order(&scalar.to_big_endian());
799801
if scalar.is_zero() {
800802
return Ok(Bytes::from([0u8; 64].to_vec()));
801803
}
@@ -811,6 +813,41 @@ pub fn ecmul(calldata: &Bytes, gas_remaining: &mut u64, _fork: Fork) -> Result<B
811813
Ok(Bytes::from(out))
812814
}
813815

816+
#[cfg(any(feature = "sp1", feature = "risc0"))]
817+
#[inline]
818+
pub fn bn254_g1_mul(g1: G1, scalar: U256) -> Result<Bytes, VMError> {
819+
use substrate_bn::{AffineG1, Fq, Fr, G1, Group};
820+
821+
if g1.is_zero() || scalar.is_zero() {
822+
return Ok(Bytes::from([0u8; 64].to_vec()));
823+
}
824+
825+
let (g1_x, g1_y) = (
826+
Fq::from_slice(&g1.0.to_big_endian()).map_err(|_| PrecompileError::ParsingInputError)?,
827+
Fq::from_slice(&g1.1.to_big_endian()).map_err(|_| PrecompileError::ParsingInputError)?,
828+
);
829+
830+
let g1 = AffineG1::new(g1_x, g1_y).map_err(|_| PrecompileError::InvalidPoint)?;
831+
832+
let scalar =
833+
Fr::from_slice(&scalar.to_big_endian()).map_err(|_| PrecompileError::ParsingInputError)?;
834+
835+
#[cfg(feature = "risc0")]
836+
// RISC0's substrate-bn patch does not implement Mul<Fr> for AffineG1, but
837+
// it does implement Mul<Fr> for G1. So we convert AffineG1 to G1 first.
838+
let result = G1::from(g1) * scalar;
839+
#[cfg(feature = "sp1")]
840+
let result = g1 * scalar;
841+
842+
let mut x_bytes = [0u8; 32];
843+
let mut y_bytes = [0u8; 32];
844+
result.x().to_big_endian(&mut x_bytes);
845+
result.y().to_big_endian(&mut y_bytes);
846+
let out = [x_bytes, y_bytes].concat();
847+
848+
Ok(Bytes::from(out))
849+
}
850+
814851
const ALT_BN128_PRIME: U256 = U256([
815852
0x3c208c16d87cfd47,
816853
0x97816a916871ca8d,
@@ -833,38 +870,58 @@ impl G2 {
833870
}
834871
}
835872

873+
/// Parses 32 bytes as BN254 scalar
836874
#[inline]
837-
fn parse_bn254_coords(buf: &[u8; 192]) -> (G1, G2) {
838-
let (g1_x, g1_y) = (
839-
u256_from_big_endian(&buf[..32]),
840-
u256_from_big_endian(&buf[32..64]),
841-
);
842-
let (g2_xy, g2_xx, g2_yy, g2_yx) = (
843-
u256_from_big_endian(&buf[64..96]),
844-
u256_from_big_endian(&buf[96..128]),
845-
u256_from_big_endian(&buf[128..160]),
846-
u256_from_big_endian(&buf[160..]),
847-
);
875+
fn parse_bn254_scalar(buf: &[u8], offset: usize) -> Option<U256> {
876+
buf.get(offset..offset.checked_add(32)?)
877+
.map(u256_from_big_endian)
878+
}
879+
880+
/// Parses 64 bytes as a BN254 G1 point
881+
#[inline]
882+
fn parse_bn254_g1(buf: &[u8], offset: usize) -> Option<G1> {
883+
let chunk = buf.get(offset..offset.checked_add(64)?)?;
884+
let (x_bytes, y_bytes) = chunk.split_at_checked(32)?;
885+
Some(G1(
886+
u256_from_big_endian(x_bytes),
887+
u256_from_big_endian(y_bytes),
888+
))
889+
}
848890

849-
(G1(g1_x, g1_y), G2(g2_xx, g2_xy, g2_yx, g2_yy))
891+
/// Parses 128 bytes as a BN254 G2 point
892+
fn parse_bn254_g2(buf: &[u8], offset: usize) -> Option<G2> {
893+
let chunk = buf.get(offset..offset.checked_add(128)?)?;
894+
let (g2_xy, rest) = chunk.split_at_checked(32)?;
895+
let (g2_xx, rest) = rest.split_at_checked(32)?;
896+
let (g2_yy, g2_yx) = rest.split_at_checked(32)?;
897+
Some(G2(
898+
u256_from_big_endian(g2_xx),
899+
u256_from_big_endian(g2_xy),
900+
u256_from_big_endian(g2_yx),
901+
u256_from_big_endian(g2_yy),
902+
))
850903
}
851904

852905
#[inline]
853-
fn validate_bn254_coords(g1: &G1, g2: &G2) -> Result<bool, VMError> {
906+
fn validate_bn254_g1_coords(g1: &G1) -> Result<(), VMError> {
854907
// check each element is in field
855908
if g1.0 >= ALT_BN128_PRIME || g1.1 >= ALT_BN128_PRIME {
856909
return Err(PrecompileError::CoordinateExceedsFieldModulus.into());
857910
}
911+
Ok(())
912+
}
858913

914+
#[inline]
915+
fn validate_bn254_g2_coords(g2: &G2) -> Result<(), VMError> {
916+
// check each element is in field
859917
if g2.0 >= ALT_BN128_PRIME
860918
|| g2.1 >= ALT_BN128_PRIME
861919
|| g2.2 >= ALT_BN128_PRIME
862920
|| g2.3 >= ALT_BN128_PRIME
863921
{
864922
return Err(PrecompileError::CoordinateExceedsFieldModulus.into());
865923
}
866-
867-
Ok(true)
924+
Ok(())
868925
}
869926

870927
/// Performs a bilinear pairing on points on the elliptic curve 'alt_bn128', returns 1 on success and 0 on failure
@@ -880,12 +937,12 @@ pub fn ecpairing(calldata: &Bytes, gas_remaining: &mut u64, _fork: Fork) -> Resu
880937

881938
let mut batch = Vec::new();
882939
for input in calldata.chunks_exact(192) {
883-
#[expect(unsafe_code, reason = "chunks_exact ensures the conversion is valid")]
884-
let input: [u8; 192] = unsafe { input.try_into().unwrap_unchecked() };
885-
let (g1, g2) = parse_bn254_coords(&input);
886-
if validate_bn254_coords(&g1, &g2)? {
887-
batch.push((g1, g2));
888-
}
940+
let (Some(g1), Some(g2)) = (parse_bn254_g1(input, 0), parse_bn254_g2(input, 64)) else {
941+
return Err(InternalError::Slicing.into());
942+
};
943+
validate_bn254_g1_coords(&g1)?;
944+
validate_bn254_g2_coords(&g2)?;
945+
batch.push((g1, g2));
889946
}
890947

891948
let pairing_check = if batch.is_empty() {

0 commit comments

Comments
 (0)