From ec0601ee13b3d972e0b5767b4b8e6a303922266b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Est=C3=A9fano=20Bargas?= Date: Thu, 30 Oct 2025 16:15:28 -0300 Subject: [PATCH 01/11] implement ecmul with substrate for sp1 --- crates/vm/levm/src/precompiles.rs | 95 ++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 21 deletions(-) diff --git a/crates/vm/levm/src/precompiles.rs b/crates/vm/levm/src/precompiles.rs index 73ce2f91185..b457e66535d 100644 --- a/crates/vm/levm/src/precompiles.rs +++ b/crates/vm/levm/src/precompiles.rs @@ -706,21 +706,19 @@ pub fn ecadd(calldata: &Bytes, gas_remaining: &mut u64, _fork: Fork) -> Result Result { // If calldata does not reach the required length, we should fill the rest with zeros let calldata = fill_with_zeros(calldata, 96); - increase_precompile_consumed_gas(ECMUL_COST, gas_remaining)?; - let point_x = calldata.get(0..32).ok_or(InternalError::Slicing)?; - let point_y = calldata.get(32..64).ok_or(InternalError::Slicing)?; - let scalar = calldata.get(64..96).ok_or(InternalError::Slicing)?; + let point = parse_bn254_g1(&calldata); + validate_bn254_g1_coords(&point)?; + let scalar = parse_bn254_scalar(&calldata); - if u256_from_big_endian(point_x) >= ALT_BN128_PRIME - || u256_from_big_endian(point_y) >= ALT_BN128_PRIME - { - return Err(PrecompileError::InvalidPoint.into()); - } + bn254_g1_mul(point, scalar) +} - let x = ark_bn254::Fq::from_be_bytes_mod_order(point_x); - let y = ark_bn254::Fq::from_be_bytes_mod_order(point_y); +#[cfg(not(feature = "sp1"))] +pub fn bn254_g1_mul(point: G1, scalar: U256) -> Result { + let x = ark_bn254::Fq::from_be_bytes_mod_order(&point.0.to_big_endian()); + let y = ark_bn254::Fq::from_be_bytes_mod_order(&point.1.to_big_endian()); if x.is_zero() && y.is_zero() { return Ok(Bytes::from([0u8; 64].to_vec())); @@ -731,7 +729,7 @@ pub fn ecmul(calldata: &Bytes, gas_remaining: &mut u64, _fork: Fork) -> Result Result Result { + use substrate_bn::{AffineG1, Fq, Fr, G1 as SubstrateG1, Group}; + + let g1: SubstrateG1 = if g1.is_zero() { + SubstrateG1::zero() + } else { + let (g1_x, g1_y) = ( + Fq::from_slice(&g1.0.to_big_endian()) + .map_err(|_| PrecompileError::ParsingInputError)?, + Fq::from_slice(&g1.1.to_big_endian()) + .map_err(|_| PrecompileError::ParsingInputError)?, + ); + AffineG1::new(g1_x, g1_y) + .map_err(|_| PrecompileError::InvalidPoint)? + .into() + }; + + let scalar = + Fr::from_slice(&scalar.to_big_endian()).map_err(|_| PrecompileError::ParsingInputError)?; + + let result = if g1.is_zero() || scalar == Fr::one() { + g1 + } else { + g1 * scalar + }; + + let mut x_bytes = [0u8; 32]; + let mut y_bytes = [0u8; 32]; + result.x().to_big_endian(&mut x_bytes); + result.y().to_big_endian(&mut y_bytes); + let out = [x_bytes, y_bytes].concat(); + + Ok(Bytes::from(out)) +} + const ALT_BN128_PRIME: U256 = U256([ 0x3c208c16d87cfd47, 0x97816a916871ca8d, @@ -769,12 +804,26 @@ impl G2 { } } +/// Parses first 32 bytes as BN254 scalar #[inline] -fn parse_bn254_coords(buf: &[u8; 192]) -> (G1, G2) { +fn parse_bn254_scalar(buf: &[u8]) -> U256 { + u256_from_big_endian(&buf[64..96]) +} + +/// Parses first 64 bytes as a BN254 G1 point +#[inline] +fn parse_bn254_g1(buf: &[u8]) -> G1 { let (g1_x, g1_y) = ( u256_from_big_endian(&buf[..32]), u256_from_big_endian(&buf[32..64]), ); + + G1(g1_x, g1_y) +} + +/// Parses 128 bytes as a BN254 G2 point, skipping the first 64 bytes +#[inline] +fn parse_bn254_g2(buf: &[u8]) -> G2 { let (g2_xy, g2_xx, g2_yy, g2_yx) = ( u256_from_big_endian(&buf[64..96]), u256_from_big_endian(&buf[96..128]), @@ -782,16 +831,21 @@ fn parse_bn254_coords(buf: &[u8; 192]) -> (G1, G2) { u256_from_big_endian(&buf[160..]), ); - (G1(g1_x, g1_y), G2(g2_xx, g2_xy, g2_yx, g2_yy)) + G2(g2_xx, g2_xy, g2_yx, g2_yy) } #[inline] -fn validate_bn254_coords(g1: &G1, g2: &G2) -> Result { +fn validate_bn254_g1_coords(g1: &G1) -> Result<(), VMError> { // check each element is in field if g1.0 >= ALT_BN128_PRIME || g1.1 >= ALT_BN128_PRIME { return Err(PrecompileError::CoordinateExceedsFieldModulus.into()); } + Ok(()) +} +#[inline] +fn validate_bn254_g2_coords(g2: &G2) -> Result<(), VMError> { + // check each element is in field if g2.0 >= ALT_BN128_PRIME || g2.1 >= ALT_BN128_PRIME || g2.2 >= ALT_BN128_PRIME @@ -799,8 +853,7 @@ fn validate_bn254_coords(g1: &G1, g2: &G2) -> Result { { return Err(PrecompileError::CoordinateExceedsFieldModulus.into()); } - - Ok(true) + Ok(()) } /// Performs a bilinear pairing on points on the elliptic curve 'alt_bn128', returns 1 on success and 0 on failure @@ -818,10 +871,10 @@ pub fn ecpairing(calldata: &Bytes, gas_remaining: &mut u64, _fork: Fork) -> Resu for input in calldata.chunks_exact(192) { #[expect(unsafe_code, reason = "chunks_exact ensures the conversion is valid")] let input: [u8; 192] = unsafe { input.try_into().unwrap_unchecked() }; - let (g1, g2) = parse_bn254_coords(&input); - if validate_bn254_coords(&g1, &g2)? { - batch.push((g1, g2)); - } + let (g1, g2) = (parse_bn254_g1(&input), parse_bn254_g2(&input)); + validate_bn254_g1_coords(&g1)?; + validate_bn254_g2_coords(&g2)?; + batch.push((g1, g2)); } let pairing_check = if batch.is_empty() { From be9dc69b1f1257a0c8d40ca955d1c2921a72b282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Est=C3=A9fano=20Bargas?= Date: Fri, 31 Oct 2025 10:35:30 -0300 Subject: [PATCH 02/11] fixes --- crates/vm/levm/src/precompiles.rs | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/crates/vm/levm/src/precompiles.rs b/crates/vm/levm/src/precompiles.rs index b457e66535d..bc6f5ba0aff 100644 --- a/crates/vm/levm/src/precompiles.rs +++ b/crates/vm/levm/src/precompiles.rs @@ -748,30 +748,23 @@ pub fn bn254_g1_mul(point: G1, scalar: U256) -> Result { #[cfg(feature = "sp1")] #[inline] pub fn bn254_g1_mul(g1: G1, scalar: U256) -> Result { - use substrate_bn::{AffineG1, Fq, Fr, G1 as SubstrateG1, Group}; + use substrate_bn::{AffineG1, Fq, Fr, Group}; - let g1: SubstrateG1 = if g1.is_zero() { - SubstrateG1::zero() - } else { - let (g1_x, g1_y) = ( - Fq::from_slice(&g1.0.to_big_endian()) - .map_err(|_| PrecompileError::ParsingInputError)?, - Fq::from_slice(&g1.1.to_big_endian()) - .map_err(|_| PrecompileError::ParsingInputError)?, - ); - AffineG1::new(g1_x, g1_y) - .map_err(|_| PrecompileError::InvalidPoint)? - .into() - }; + if g1.is_zero() || scalar.is_zero() { + return Ok(Bytes::from([0u8; 64].to_vec())); + } + + let (g1_x, g1_y) = ( + Fq::from_slice(&g1.0.to_big_endian()).map_err(|_| PrecompileError::ParsingInputError)?, + Fq::from_slice(&g1.1.to_big_endian()).map_err(|_| PrecompileError::ParsingInputError)?, + ); + + let g1 = AffineG1::new(g1_x, g1_y).map_err(|_| PrecompileError::InvalidPoint)?; let scalar = Fr::from_slice(&scalar.to_big_endian()).map_err(|_| PrecompileError::ParsingInputError)?; - let result = if g1.is_zero() || scalar == Fr::one() { - g1 - } else { - g1 * scalar - }; + let result = g1 * scalar; let mut x_bytes = [0u8; 32]; let mut y_bytes = [0u8; 32]; From 2e394b3a89114a3113a28b6cc6c499cd2c9913eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Est=C3=A9fano=20Bargas?= Date: Fri, 31 Oct 2025 11:18:51 -0300 Subject: [PATCH 03/11] use slicing --- crates/vm/levm/src/precompiles.rs | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/crates/vm/levm/src/precompiles.rs b/crates/vm/levm/src/precompiles.rs index bc6f5ba0aff..3b23592ae85 100644 --- a/crates/vm/levm/src/precompiles.rs +++ b/crates/vm/levm/src/precompiles.rs @@ -708,7 +708,9 @@ pub fn ecmul(calldata: &Bytes, gas_remaining: &mut u64, _fork: Fork) -> Result U256 { /// Parses first 64 bytes as a BN254 G1 point #[inline] -fn parse_bn254_g1(buf: &[u8]) -> G1 { +fn parse_bn254_g1(buf: &[u8]) -> Option { let (g1_x, g1_y) = ( - u256_from_big_endian(&buf[..32]), - u256_from_big_endian(&buf[32..64]), + u256_from_big_endian(buf.get(..32)?), + u256_from_big_endian(buf.get(32..64)?), ); - G1(g1_x, g1_y) + Some(G1(g1_x, g1_y)) } /// Parses 128 bytes as a BN254 G2 point, skipping the first 64 bytes #[inline] -fn parse_bn254_g2(buf: &[u8]) -> G2 { +fn parse_bn254_g2(buf: &[u8]) -> Option { let (g2_xy, g2_xx, g2_yy, g2_yx) = ( - u256_from_big_endian(&buf[64..96]), - u256_from_big_endian(&buf[96..128]), - u256_from_big_endian(&buf[128..160]), - u256_from_big_endian(&buf[160..]), + u256_from_big_endian(buf.get(64..96)?), + u256_from_big_endian(buf.get(96..128)?), + u256_from_big_endian(buf.get(128..160)?), + u256_from_big_endian(buf.get(160..)?), ); - G2(g2_xx, g2_xy, g2_yx, g2_yy) + Some(G2(g2_xx, g2_xy, g2_yx, g2_yy)) } #[inline] @@ -862,9 +864,9 @@ pub fn ecpairing(calldata: &Bytes, gas_remaining: &mut u64, _fork: Fork) -> Resu let mut batch = Vec::new(); for input in calldata.chunks_exact(192) { - #[expect(unsafe_code, reason = "chunks_exact ensures the conversion is valid")] - let input: [u8; 192] = unsafe { input.try_into().unwrap_unchecked() }; - let (g1, g2) = (parse_bn254_g1(&input), parse_bn254_g2(&input)); + let (Some(g1), Some(g2)) = (parse_bn254_g1(&input), parse_bn254_g2(&input)) else { + return Err(InternalError::Slicing.into()); + }; validate_bn254_g1_coords(&g1)?; validate_bn254_g2_coords(&g2)?; batch.push((g1, g2)); From effb630b11bee6dd132e95c6f0d579e12a6fd7b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Est=C3=A9fano=20Bargas?= Date: Fri, 31 Oct 2025 11:21:51 -0300 Subject: [PATCH 04/11] use offset --- crates/vm/levm/src/precompiles.rs | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/vm/levm/src/precompiles.rs b/crates/vm/levm/src/precompiles.rs index 3b23592ae85..acbe4eee4ea 100644 --- a/crates/vm/levm/src/precompiles.rs +++ b/crates/vm/levm/src/precompiles.rs @@ -708,11 +708,11 @@ pub fn ecmul(calldata: &Bytes, gas_remaining: &mut u64, _fork: Fork) -> Result U256 { - u256_from_big_endian(&buf[64..96]) +fn parse_bn254_scalar(buf: &[u8], offset: usize) -> U256 { + u256_from_big_endian(&buf[offset..offset + 64]) } -/// Parses first 64 bytes as a BN254 G1 point +/// Parses 64 bytes as a BN254 G1 point #[inline] -fn parse_bn254_g1(buf: &[u8]) -> Option { +fn parse_bn254_g1(buf: &[u8], offset: usize) -> Option { let (g1_x, g1_y) = ( - u256_from_big_endian(buf.get(..32)?), - u256_from_big_endian(buf.get(32..64)?), + u256_from_big_endian(buf.get(offset..offset + 32)?), + u256_from_big_endian(buf.get(offset + 32..offset + 64)?), ); Some(G1(g1_x, g1_y)) } -/// Parses 128 bytes as a BN254 G2 point, skipping the first 64 bytes +/// Parses 128 bytes as a BN254 G2 point #[inline] -fn parse_bn254_g2(buf: &[u8]) -> Option { +fn parse_bn254_g2(buf: &[u8], offset: usize) -> Option { let (g2_xy, g2_xx, g2_yy, g2_yx) = ( - u256_from_big_endian(buf.get(64..96)?), - u256_from_big_endian(buf.get(96..128)?), - u256_from_big_endian(buf.get(128..160)?), - u256_from_big_endian(buf.get(160..)?), + u256_from_big_endian(buf.get(offset..offset + 32)?), + u256_from_big_endian(buf.get(offset + 32..offset + 64)?), + u256_from_big_endian(buf.get(offset + 64..offset + 96)?), + u256_from_big_endian(buf.get(offset + 96..offset + 128)?), ); Some(G2(g2_xx, g2_xy, g2_yx, g2_yy)) @@ -864,7 +864,7 @@ pub fn ecpairing(calldata: &Bytes, gas_remaining: &mut u64, _fork: Fork) -> Resu let mut batch = Vec::new(); for input in calldata.chunks_exact(192) { - let (Some(g1), Some(g2)) = (parse_bn254_g1(&input), parse_bn254_g2(&input)) else { + let (Some(g1), Some(g2)) = (parse_bn254_g1(&input, 0), parse_bn254_g2(&input, 64)) else { return Err(InternalError::Slicing.into()); }; validate_bn254_g1_coords(&g1)?; From 718e183164e49b9ca707d6bd55d6a1067298164a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Est=C3=A9fano=20Bargas?= Date: Fri, 31 Oct 2025 12:02:13 -0300 Subject: [PATCH 05/11] Update crates/vm/levm/src/precompiles.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- crates/vm/levm/src/precompiles.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vm/levm/src/precompiles.rs b/crates/vm/levm/src/precompiles.rs index acbe4eee4ea..8d12d078b72 100644 --- a/crates/vm/levm/src/precompiles.rs +++ b/crates/vm/levm/src/precompiles.rs @@ -802,7 +802,7 @@ impl G2 { /// Parses 32 bytes as BN254 scalar #[inline] fn parse_bn254_scalar(buf: &[u8], offset: usize) -> U256 { - u256_from_big_endian(&buf[offset..offset + 64]) + u256_from_big_endian(&buf[offset..offset + 32]) } /// Parses 64 bytes as a BN254 G1 point From e406e46ac7a22981afb80d25be274c8db51f5562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Est=C3=A9fano=20Bargas?= Date: Fri, 31 Oct 2025 13:14:25 -0300 Subject: [PATCH 06/11] use safer add and slicing --- crates/vm/levm/src/precompiles.rs | 43 +++++++++++++++++-------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/crates/vm/levm/src/precompiles.rs b/crates/vm/levm/src/precompiles.rs index 8d12d078b72..7331dd609e3 100644 --- a/crates/vm/levm/src/precompiles.rs +++ b/crates/vm/levm/src/precompiles.rs @@ -708,12 +708,13 @@ pub fn ecmul(calldata: &Bytes, gas_remaining: &mut u64, _fork: Fork) -> Result U256 { - u256_from_big_endian(&buf[offset..offset + 32]) +fn parse_bn254_scalar(buf: &[u8], offset: usize) -> Option { + buf.get(offset..offset.checked_add(32)?) + .map(u256_from_big_endian) } /// Parses 64 bytes as a BN254 G1 point #[inline] fn parse_bn254_g1(buf: &[u8], offset: usize) -> Option { - let (g1_x, g1_y) = ( - u256_from_big_endian(buf.get(offset..offset + 32)?), - u256_from_big_endian(buf.get(offset + 32..offset + 64)?), - ); - - Some(G1(g1_x, g1_y)) + let chunk = buf.get(offset..offset.checked_add(64)?)?; + let (x_bytes, y_bytes) = chunk.split_at_checked(32)?; + Some(G1( + u256_from_big_endian(x_bytes), + u256_from_big_endian(y_bytes), + )) } /// Parses 128 bytes as a BN254 G2 point -#[inline] fn parse_bn254_g2(buf: &[u8], offset: usize) -> Option { - let (g2_xy, g2_xx, g2_yy, g2_yx) = ( - u256_from_big_endian(buf.get(offset..offset + 32)?), - u256_from_big_endian(buf.get(offset + 32..offset + 64)?), - u256_from_big_endian(buf.get(offset + 64..offset + 96)?), - u256_from_big_endian(buf.get(offset + 96..offset + 128)?), - ); - - Some(G2(g2_xx, g2_xy, g2_yx, g2_yy)) + let chunk = buf.get(offset..offset.checked_add(128)?)?; + let (g2_xy, rest) = chunk.split_at_checked(32)?; + let (g2_xx, rest) = rest.split_at_checked(32)?; + let (g2_yy, g2_yx) = rest.split_at_checked(32)?; + Some(G2( + u256_from_big_endian(g2_xx), + u256_from_big_endian(g2_xy), + u256_from_big_endian(g2_yx), + u256_from_big_endian(g2_yy), + )) } #[inline] From a9aba91295f2815305aa44a689aabaf9f0553dda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Est=C3=A9fano=20Bargas?= Date: Fri, 31 Oct 2025 13:15:01 -0300 Subject: [PATCH 07/11] change name --- crates/vm/levm/src/precompiles.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/vm/levm/src/precompiles.rs b/crates/vm/levm/src/precompiles.rs index 7331dd609e3..df14929c1f3 100644 --- a/crates/vm/levm/src/precompiles.rs +++ b/crates/vm/levm/src/precompiles.rs @@ -708,14 +708,14 @@ pub fn ecmul(calldata: &Bytes, gas_remaining: &mut u64, _fork: Fork) -> Result Date: Fri, 31 Oct 2025 13:20:19 -0300 Subject: [PATCH 08/11] clippy --- crates/vm/levm/src/precompiles.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vm/levm/src/precompiles.rs b/crates/vm/levm/src/precompiles.rs index df14929c1f3..dceb208d6d5 100644 --- a/crates/vm/levm/src/precompiles.rs +++ b/crates/vm/levm/src/precompiles.rs @@ -867,7 +867,7 @@ pub fn ecpairing(calldata: &Bytes, gas_remaining: &mut u64, _fork: Fork) -> Resu let mut batch = Vec::new(); for input in calldata.chunks_exact(192) { - let (Some(g1), Some(g2)) = (parse_bn254_g1(&input, 0), parse_bn254_g2(&input, 64)) else { + let (Some(g1), Some(g2)) = (parse_bn254_g1(input, 0), parse_bn254_g2(input, 64)) else { return Err(InternalError::Slicing.into()); }; validate_bn254_g1_coords(&g1)?; From a6df6f95a40d938b1419f1eb65c843f4bb2574a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Est=C3=A9fano=20Bargas?= Date: Fri, 31 Oct 2025 13:51:40 -0300 Subject: [PATCH 09/11] fix slicing --- crates/vm/levm/src/precompiles.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/vm/levm/src/precompiles.rs b/crates/vm/levm/src/precompiles.rs index dceb208d6d5..7e6abde57c3 100644 --- a/crates/vm/levm/src/precompiles.rs +++ b/crates/vm/levm/src/precompiles.rs @@ -708,9 +708,9 @@ pub fn ecmul(calldata: &Bytes, gas_remaining: &mut u64, _fork: Fork) -> Result Date: Fri, 31 Oct 2025 16:08:02 -0300 Subject: [PATCH 10/11] Update cfg attributes for bn254_g1_mul function --- crates/vm/levm/src/precompiles.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/vm/levm/src/precompiles.rs b/crates/vm/levm/src/precompiles.rs index 7e6abde57c3..85405a70dc7 100644 --- a/crates/vm/levm/src/precompiles.rs +++ b/crates/vm/levm/src/precompiles.rs @@ -718,7 +718,8 @@ pub fn ecmul(calldata: &Bytes, gas_remaining: &mut u64, _fork: Fork) -> Result Result { let x = ark_bn254::Fq::from_be_bytes_mod_order(&point.0.to_big_endian()); let y = ark_bn254::Fq::from_be_bytes_mod_order(&point.1.to_big_endian()); @@ -748,7 +749,7 @@ pub fn bn254_g1_mul(point: G1, scalar: U256) -> Result { Ok(Bytes::from(out)) } -#[cfg(feature = "sp1")] +#[cfg(any(feature = "sp1", feature = "risc0"))] #[inline] pub fn bn254_g1_mul(g1: G1, scalar: U256) -> Result { use substrate_bn::{AffineG1, Fq, Fr, Group}; From 38f3aafc9d0f2b0e47b86439c318f5d772e5808d Mon Sep 17 00:00:00 2001 From: ilitteri Date: Fri, 31 Oct 2025 20:52:31 -0300 Subject: [PATCH 11/11] Fix RISC0's compilation --- crates/vm/levm/src/precompiles.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/vm/levm/src/precompiles.rs b/crates/vm/levm/src/precompiles.rs index abc58192bd8..50a5fdfb83f 100644 --- a/crates/vm/levm/src/precompiles.rs +++ b/crates/vm/levm/src/precompiles.rs @@ -752,7 +752,7 @@ pub fn bn254_g1_mul(point: G1, scalar: U256) -> Result { #[cfg(any(feature = "sp1", feature = "risc0"))] #[inline] pub fn bn254_g1_mul(g1: G1, scalar: U256) -> Result { - use substrate_bn::{AffineG1, Fq, Fr, Group}; + use substrate_bn::{AffineG1, Fq, Fr, G1, Group}; if g1.is_zero() || scalar.is_zero() { return Ok(Bytes::from([0u8; 64].to_vec())); @@ -768,6 +768,11 @@ pub fn bn254_g1_mul(g1: G1, scalar: U256) -> Result { let scalar = Fr::from_slice(&scalar.to_big_endian()).map_err(|_| PrecompileError::ParsingInputError)?; + #[cfg(feature = "risc0")] + // RISC0's substrate-bn patch does not implement Mul for AffineG1, but + // it does implement Mul for G1. So we convert AffineG1 to G1 first. + let result = G1::from(g1) * scalar; + #[cfg(feature = "sp1")] let result = g1 * scalar; let mut x_bytes = [0u8; 32];