Skip to content

Conversation

@xqft
Copy link
Contributor

@xqft xqft commented Dec 5, 2025

To use the patched substrate-bn of both zkVMs.

Benchmarks for ZisK:

main (7722c65):

| Block    | Gas Used     | Steps         | Duration (s) | TP (Msteps/s) | Freq (MHz) | Clocks/step |
|----------|--------------|---------------|--------------|----------------|------------|--------------|
| 23919400 |   41,075,722 |   668,153,769 |      16.3140 |        40.9557 |  1999.0000 |      48.8088 |
| 23919500 |   40,237,085 |   777,636,222 |      18.2261 |        42.6661 |  1999.0000 |      46.8522 |
| 23919600 |   24,064,259 |   512,450,533 |      12.0544 |        42.5113 |  1999.0000 |      47.0228 |
| 23919700 |   20,862,238 |   445,609,968 |      10.6352 |        41.8996 |  1999.0000 |      47.7093 |
| 23919800 |   31,813,109 |   596,881,989 |      14.2796 |        41.7996 |  1999.0000 |      47.8234 |
| 23919900 |   22,917,739 |   404,876,652 |       9.9049 |        40.8764 |  1999.0000 |      48.9035 |
| 23920000 |   37,256,487 |   649,722,314 |      15.3030 |        42.4573 |  1999.0000 |      47.0826 |
| 23920100 |   33,542,307 |   598,963,230 |      14.1022 |        42.4729 |  1999.0000 |      47.0653 |
| 23920200 |   22,994,047 |   417,072,430 |      10.2212 |        40.8046 |  1999.0000 |      48.9895 |
| 23920300 |   53,950,967 |   966,469,904 |      23.0963 |        41.8452 |  1999.0000 |      47.7713 |

zisk_ecadd:

| Block    | Gas Used     | Steps         | Duration (s) | TP (Msteps/s) | Freq (MHz) | Clocks/step |
|----------|--------------|---------------|--------------|----------------|------------|--------------|
| 23919400 |   41,075,722 |   664,968,601 |      16.3159 |        40.7560 |  1999.0000 |      49.0480 |
| 23919500 |   40,237,085 |   775,906,640 |      18.2147 |        42.5978 |  1999.0000 |      46.9273 |
| 23919600 |   24,064,259 |   511,611,990 |      12.0984 |        42.2877 |  1999.0000 |      47.2714 |
| 23919700 |   20,862,238 |   444,896,241 |      10.4587 |        42.5382 |  1999.0000 |      46.9930 |
| 23919800 |   31,813,109 |   596,005,571 |      14.2294 |        41.8856 |  1999.0000 |      47.7252 |
| 23919900 |   22,917,739 |   404,355,231 |       9.7270 |        41.5704 |  1999.0000 |      48.0871 |
| 23920000 |   37,256,487 |   648,362,675 |      15.0888 |        42.9699 |  1999.0000 |      46.5209 |
| 23920100 |   33,542,307 |   598,148,173 |      14.2343 |        42.0217 |  1999.0000 |      47.5707 |
| 23920200 |   22,994,047 |   416,341,598 |       9.8153 |        42.4178 |  1999.0000 |      47.1265 |
| 23920300 |   53,950,967 |   964,216,522 |      22.9837 |        41.9521 |  1999.0000 |      47.6495 |

diff in steps:

23919400: -0.48%
23919500: -0.22%
23919600: -0.16%
23919700: -0.16%
23919800: -0.15% 
3919900: -0.13%
23920000: -0.21%
23920100: -0.14%
23920200: -0.18%
23920300: -0.23%

@github-actions github-actions bot added the L2 Rollup client label Dec 5, 2025
@github-actions
Copy link

github-actions bot commented Dec 5, 2025

Lines of code report

Total lines added: 66
Total lines removed: 0
Total lines changed: 66

Detailed view
+------------------------------------------+-------+------+
| File                                     | Lines | Diff |
+------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/precompiles.rs | 1732  | +66  |
+------------------------------------------+-------+------+

@github-actions
Copy link

github-actions bot commented Dec 5, 2025

Benchmark Results Comparison

No significant difference was registered for any benchmark run.

Detailed Results

Benchmark Results: BubbleSort

Command Mean [s] Min [s] Max [s] Relative
main_revm_BubbleSort 3.009 ± 0.050 2.972 3.105 1.01 ± 0.02
main_levm_BubbleSort 3.078 ± 0.028 3.048 3.135 1.04 ± 0.01
pr_revm_BubbleSort 2.973 ± 0.026 2.949 3.024 1.00
pr_levm_BubbleSort 3.112 ± 0.027 3.088 3.187 1.05 ± 0.01

Benchmark Results: ERC20Approval

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ERC20Approval 980.3 ± 6.9 975.0 996.8 1.00
main_levm_ERC20Approval 1088.6 ± 19.0 1074.6 1141.2 1.11 ± 0.02
pr_revm_ERC20Approval 989.0 ± 11.1 976.5 1010.4 1.01 ± 0.01
pr_levm_ERC20Approval 1089.6 ± 7.2 1081.8 1107.4 1.11 ± 0.01

Benchmark Results: ERC20Mint

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ERC20Mint 132.8 ± 0.8 131.6 134.1 1.00
main_levm_ERC20Mint 162.0 ± 1.0 160.7 163.9 1.22 ± 0.01
pr_revm_ERC20Mint 134.3 ± 0.8 133.1 135.8 1.01 ± 0.01
pr_levm_ERC20Mint 165.0 ± 5.4 162.1 179.9 1.24 ± 0.04

Benchmark Results: ERC20Transfer

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ERC20Transfer 235.6 ± 4.6 231.2 244.8 1.01 ± 0.02
main_levm_ERC20Transfer 273.9 ± 2.1 269.5 276.6 1.18 ± 0.02
pr_revm_ERC20Transfer 232.6 ± 2.4 230.8 238.3 1.00
pr_levm_ERC20Transfer 276.6 ± 2.9 273.8 282.8 1.19 ± 0.02

Benchmark Results: Factorial

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Factorial 225.2 ± 1.2 223.9 227.8 1.00 ± 0.01
main_levm_Factorial 265.7 ± 1.7 263.6 269.0 1.18 ± 0.01
pr_revm_Factorial 224.8 ± 1.4 223.0 227.5 1.00
pr_levm_Factorial 264.7 ± 1.5 262.5 266.8 1.18 ± 0.01

Benchmark Results: FactorialRecursive

Command Mean [s] Min [s] Max [s] Relative
main_revm_FactorialRecursive 1.655 ± 0.044 1.591 1.730 1.01 ± 0.03
main_levm_FactorialRecursive 8.318 ± 0.050 8.247 8.410 5.08 ± 0.06
pr_revm_FactorialRecursive 1.636 ± 0.016 1.602 1.655 1.00
pr_levm_FactorialRecursive 8.283 ± 0.044 8.174 8.325 5.06 ± 0.06

Benchmark Results: Fibonacci

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Fibonacci 199.3 ± 1.1 198.5 201.7 1.00
main_levm_Fibonacci 253.9 ± 4.4 248.6 259.2 1.27 ± 0.02
pr_revm_Fibonacci 199.6 ± 1.4 198.2 202.6 1.00 ± 0.01
pr_levm_Fibonacci 262.9 ± 18.7 247.8 312.8 1.32 ± 0.09

Benchmark Results: FibonacciRecursive

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_FibonacciRecursive 877.1 ± 12.9 866.8 911.9 1.17 ± 0.02
main_levm_FibonacciRecursive 753.5 ± 7.5 744.8 766.1 1.01 ± 0.01
pr_revm_FibonacciRecursive 859.9 ± 13.0 847.9 881.5 1.15 ± 0.02
pr_levm_FibonacciRecursive 748.7 ± 5.5 737.7 755.3 1.00

Benchmark Results: ManyHashes

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ManyHashes 8.3 ± 0.1 8.2 8.4 1.00
main_levm_ManyHashes 9.0 ± 0.1 8.9 9.1 1.08 ± 0.01
pr_revm_ManyHashes 8.5 ± 0.1 8.4 8.6 1.02 ± 0.01
pr_levm_ManyHashes 9.0 ± 0.2 8.9 9.6 1.09 ± 0.03

Benchmark Results: MstoreBench

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_MstoreBench 258.3 ± 1.7 255.9 260.7 1.08 ± 0.01
main_levm_MstoreBench 240.7 ± 2.6 238.2 245.3 1.01 ± 0.01
pr_revm_MstoreBench 261.7 ± 4.6 257.6 272.2 1.10 ± 0.02
pr_levm_MstoreBench 238.8 ± 1.2 237.2 241.0 1.00

Benchmark Results: Push

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Push 287.3 ± 1.1 285.9 289.6 1.00
main_levm_Push 308.5 ± 1.7 306.9 312.8 1.07 ± 0.01
pr_revm_Push 292.9 ± 3.5 289.8 302.4 1.02 ± 0.01
pr_levm_Push 308.1 ± 2.9 303.6 313.4 1.07 ± 0.01

Benchmark Results: SstoreBench_no_opt

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_SstoreBench_no_opt 170.3 ± 3.6 166.6 177.7 1.90 ± 0.04
main_levm_SstoreBench_no_opt 89.6 ± 0.9 88.3 91.4 1.00
pr_revm_SstoreBench_no_opt 172.5 ± 4.3 166.7 182.0 1.92 ± 0.05
pr_levm_SstoreBench_no_opt 91.8 ± 2.9 89.1 97.4 1.02 ± 0.03

@xqft xqft marked this pull request as ready for review December 9, 2025 15:47
@xqft xqft requested a review from a team as a code owner December 9, 2025 15:47
Copilot AI review requested due to automatic review settings December 9, 2025 15:47
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements the ecadd precompile using substrate-bn for ZisK and SP1 zkVMs to improve performance. The benchmarks show a 0.13-0.48% reduction in steps across tested blocks.

  • Refactors the existing ecadd function to extract point addition logic into a new bn254_g1_add helper function
  • Provides two feature-gated implementations of bn254_g1_add: one using arkworks (default) and one using substrate-bn (for sp1/zisk features)
  • Updates coordinate validation to use helper functions (parse_bn254_g1 and validate_bn254_g1_coords)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

#[cfg(any(feature = "sp1", feature = "zisk"))]
#[inline]
pub fn bn254_g1_add(first_point: G1, second_point: G1) -> Result<Bytes, VMError> {
use substrate_bn::{AffineG1, Fq, G1, Group};
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The import of substrate_bn::G1 shadows the custom G1 struct defined at line 946. While this works because the import is scoped to this function, it could be confusing for readers. Consider using a qualified import or type alias for clarity:

use substrate_bn::{AffineG1, Fq, Group};
use substrate_bn::G1 as SubstrateG1;

Then use SubstrateG1 instead of G1 in the function body (lines 833, 837).

Copilot uses AI. Check for mistakes.
Comment on lines +785 to +817
if first_point.is_zero() {
let (g1_x, g1_y) = (
Fq::from_slice(&second_point.0.to_big_endian())
.map_err(|_| PrecompileError::ParsingInputError)?,
Fq::from_slice(&second_point.1.to_big_endian())
.map_err(|_| PrecompileError::ParsingInputError)?,
);
// Validate that the point is on the curve
AffineG1::new(g1_x, g1_y).map_err(|_| PrecompileError::InvalidPoint)?;

let mut x_bytes = [0u8; 32];
let mut y_bytes = [0u8; 32];
g1_x.to_big_endian(&mut x_bytes);
g1_y.to_big_endian(&mut y_bytes);
return Ok(Bytes::from([x_bytes, y_bytes].concat()));
}

if second_point.is_zero() {
let (g1_x, g1_y) = (
Fq::from_slice(&first_point.0.to_big_endian())
.map_err(|_| PrecompileError::ParsingInputError)?,
Fq::from_slice(&first_point.1.to_big_endian())
.map_err(|_| PrecompileError::ParsingInputError)?,
);
// Validate that the point is on the curve
AffineG1::new(g1_x, g1_y).map_err(|_| PrecompileError::InvalidPoint)?;

let mut x_bytes = [0u8; 32];
let mut y_bytes = [0u8; 32];
g1_x.to_big_endian(&mut x_bytes);
g1_y.to_big_endian(&mut y_bytes);
return Ok(Bytes::from([x_bytes, y_bytes].concat()));
}
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for handling first_point.is_zero() (lines 785-800) and second_point.is_zero() (lines 802-817) is duplicated. This could be extracted into a helper function that converts a G1 point to bytes while validating it's on the curve. This would reduce code duplication and improve maintainability.

Example:

fn g1_point_to_bytes(point: &G1) -> Result<Bytes, VMError> {
    let (g1_x, g1_y) = (
        Fq::from_slice(&point.0.to_big_endian())
            .map_err(|_| PrecompileError::ParsingInputError)?,
        Fq::from_slice(&point.1.to_big_endian())
            .map_err(|_| PrecompileError::ParsingInputError)?,
    );
    // Validate that the point is on the curve
    AffineG1::new(g1_x, g1_y).map_err(|_| PrecompileError::InvalidPoint)?;
    
    let mut x_bytes = [0u8; 32];
    let mut y_bytes = [0u8; 32];
    g1_x.to_big_endian(&mut x_bytes);
    g1_y.to_big_endian(&mut y_bytes);
    Ok(Bytes::from([x_bytes, y_bytes].concat()))
}

Then use:

if first_point.is_zero() {
    return g1_point_to_bytes(&second_point);
}
if second_point.is_zero() {
    return g1_point_to_bytes(&first_point);
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L2 Rollup client

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants