Skip to content

Conversation

@xqft
Copy link
Contributor

@xqft xqft commented Oct 8, 2025

Motivation

Elliptic Curve pairing is one of the most expensive operations in a zkVM .We are using lambdaworks for implementing the ecpairing precompile, which is not optimized for zkVMs. We must use each zkVM pairing patch/precompile instead.

This is true for other operations as well, like ecmul or ecadd, which can be optimized the same way in future PRs

Flamegraphs for block 23385900 Mainnet
ecpairing cycles are reduced from 135k to 6k, which is a 10% improvement in the total cycles.
before:
image
after:
image

@github-actions github-actions bot added the L2 Rollup client label Oct 8, 2025
@github-actions
Copy link

github-actions bot commented Oct 8, 2025

Lines of code report

Total lines added: 24
Total lines removed: 0
Total lines changed: 24

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

@github-actions
Copy link

github-actions bot commented Oct 8, 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 4.731 ± 0.024 4.709 4.778 1.01 ± 0.01
main_levm_BubbleSort 4.689 ± 0.063 4.633 4.834 1.00 ± 0.01
pr_revm_BubbleSort 4.729 ± 0.023 4.705 4.770 1.01 ± 0.01
pr_levm_BubbleSort 4.674 ± 0.026 4.643 4.708 1.00

Benchmark Results: ERC20Approval

Command Mean [s] Min [s] Max [s] Relative
main_revm_ERC20Approval 1.539 ± 0.005 1.533 1.550 1.00
main_levm_ERC20Approval 1.650 ± 0.005 1.645 1.660 1.07 ± 0.01
pr_revm_ERC20Approval 1.545 ± 0.011 1.534 1.572 1.00 ± 0.01
pr_levm_ERC20Approval 1.675 ± 0.017 1.656 1.709 1.09 ± 0.01

Benchmark Results: ERC20Mint

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ERC20Mint 184.4 ± 0.3 184.0 185.1 1.00
main_levm_ERC20Mint 198.9 ± 1.8 197.0 203.0 1.08 ± 0.01
pr_revm_ERC20Mint 185.5 ± 1.9 184.1 189.1 1.01 ± 0.01
pr_levm_ERC20Mint 200.0 ± 1.0 199.0 201.8 1.08 ± 0.01

Benchmark Results: ERC20Transfer

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ERC20Transfer 350.3 ± 1.3 349.5 353.5 1.00
main_levm_ERC20Transfer 386.0 ± 1.7 384.2 389.0 1.10 ± 0.01
pr_revm_ERC20Transfer 351.9 ± 2.0 349.8 356.1 1.00 ± 0.01
pr_levm_ERC20Transfer 392.5 ± 3.5 388.2 398.3 1.12 ± 0.01

Benchmark Results: Factorial

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Factorial 233.6 ± 1.1 231.6 235.3 1.00
main_levm_Factorial 278.9 ± 2.1 276.6 281.5 1.19 ± 0.01
pr_revm_Factorial 234.1 ± 3.9 232.1 244.9 1.00 ± 0.02
pr_levm_Factorial 274.5 ± 0.3 274.1 275.1 1.18 ± 0.01

Benchmark Results: FactorialRecursive

Command Mean [s] Min [s] Max [s] Relative
main_revm_FactorialRecursive 1.642 ± 0.033 1.601 1.699 1.00
main_levm_FactorialRecursive 8.839 ± 0.074 8.742 8.989 5.38 ± 0.12
pr_revm_FactorialRecursive 1.669 ± 0.026 1.621 1.707 1.02 ± 0.03
pr_levm_FactorialRecursive 8.943 ± 0.105 8.801 9.129 5.45 ± 0.13

Benchmark Results: Fibonacci

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Fibonacci 210.7 ± 1.2 207.6 211.8 1.00 ± 0.01
main_levm_Fibonacci 255.9 ± 4.0 253.8 267.0 1.22 ± 0.02
pr_revm_Fibonacci 210.4 ± 0.3 210.0 210.9 1.00
pr_levm_Fibonacci 255.4 ± 3.7 251.6 264.2 1.21 ± 0.02

Benchmark Results: FibonacciRecursive

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_FibonacciRecursive 860.3 ± 9.9 847.7 874.4 1.01 ± 0.02
main_levm_FibonacciRecursive 1045.2 ± 11.3 1033.2 1066.6 1.22 ± 0.02
pr_revm_FibonacciRecursive 855.0 ± 11.6 839.4 876.2 1.00
pr_levm_FibonacciRecursive 1046.8 ± 25.5 1030.0 1116.3 1.22 ± 0.03

Benchmark Results: ManyHashes

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ManyHashes 12.4 ± 0.1 12.3 12.5 1.00 ± 0.01
main_levm_ManyHashes 13.7 ± 0.1 13.7 13.8 1.12 ± 0.01
pr_revm_ManyHashes 12.3 ± 0.0 12.3 12.4 1.00
pr_levm_ManyHashes 13.8 ± 0.1 13.7 13.9 1.12 ± 0.01

Benchmark Results: MstoreBench

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_MstoreBench 291.2 ± 85.7 262.3 534.9 1.10 ± 0.32
main_levm_MstoreBench 757.3 ± 1.0 755.7 759.0 2.87 ± 0.03
pr_revm_MstoreBench 264.2 ± 2.5 261.8 269.3 1.00
pr_levm_MstoreBench 758.9 ± 2.5 755.1 762.6 2.87 ± 0.03

Benchmark Results: Push

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Push 294.8 ± 1.0 293.7 297.2 1.00
main_levm_Push 825.8 ± 7.3 819.9 842.8 2.80 ± 0.03
pr_revm_Push 295.0 ± 1.6 293.3 298.1 1.00 ± 0.01
pr_levm_Push 825.1 ± 2.1 822.1 828.2 2.80 ± 0.01

Benchmark Results: SstoreBench_no_opt

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_SstoreBench_no_opt 217.8 ± 1.3 216.4 221.4 2.39 ± 0.04
main_levm_SstoreBench_no_opt 91.2 ± 1.5 89.8 93.6 1.00
pr_revm_SstoreBench_no_opt 219.5 ± 5.6 216.7 235.2 2.41 ± 0.07
pr_levm_SstoreBench_no_opt 92.2 ± 1.6 89.9 94.6 1.01 ± 0.02

@xqft xqft marked this pull request as ready for review October 13, 2025 18:06
@xqft xqft requested a review from a team as a code owner October 13, 2025 18:06
Copilot AI review requested due to automatic review settings October 13, 2025 18:06
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 integrates SP1 patches for the ecpairing precompile by adding substrate-bn as an optional dependency and refactoring the BN254 pairing implementation to support both lambdaworks and substrate-bn backends.

Key changes:

  • Adds substrate-bn dependency with SP1 patches for BN254 pairing operations
  • Refactors ecpairing precompile to use a feature-flag based backend selection
  • Simplifies coordinate parsing and validation logic

Reviewed Changes

Copilot reviewed 4 out of 6 changed files in this pull request and generated 4 comments.

File Description
crates/vm/levm/src/precompiles.rs Refactors ecpairing precompile with simplified parsing and dual backend support
crates/vm/levm/Cargo.toml Adds substrate-bn dependency with SP1 patch and sp1 feature flag
crates/vm/Cargo.toml Adds sp1 feature flag propagation
crates/l2/prover/src/guest_program/src/sp1/Cargo.toml Enables sp1 feature for ethrex-vm dependency

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.


let (second_point_x, second_point_y) = parse_second_point_coordinates(&input)?;
#[allow(unreachable_code)]
let pairing_check = 'inner: {
Copy link
Contributor

Choose a reason for hiding this comment

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

Using a label is not a recommended practice.
I suggest to use a construction that is less error prone. For example, something that refuses to compile when using incompatible features.

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested approach: let pairing_check be a function with the appropriate implementation for each config, e.g.:

#[cfg(feature = "sp1")]
fn pairing_check(batch: &[(G1, G2)]) -> Result<bool, VMError> {
    if batch.is_empty() {
         return Ok(true);
    }
    //  Code currently in `pairing_substrate`
}

#[cfg(not(feature = "sp1")]
fn pairing_check(batch: &[(G1, G2)]) -> Result<bool, VMError> {
    if batch.is_empty() {
         return Ok(true);
    }
    //  Code currently in `pairing_lambdaworks`
}

The calling code would then not need to add special cases, it would just be:

let pairing_check = pairing_check(&batch)?;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

addressed! thank you :)

Copy link
Contributor

@pablodeymo pablodeymo left a comment

Choose a reason for hiding this comment

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

I would like my comment to be addressed

@github-project-automation github-project-automation bot moved this to Requires Changes in ethrex_l2 Oct 16, 2025
Comment on lines 840 to 841
let result = if batch.is_empty() {
substrate_bn::Gt::one()
Copy link
Contributor

Choose a reason for hiding this comment

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

I would prefer to do an early return here to avoid the huge else.

Copy link
Contributor Author

@xqft xqft Oct 16, 2025

Choose a reason for hiding this comment

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

done!

Comment on lines 907 to 920
let g1 = if g1.0.is_zero() && g1.1.is_zero() {
ProjectiveG1::neutral_element()
} else {
let (g1_x, g1_y) = (
Fq::from_bytes_be(&g1.0.to_big_endian())
.map_err(|_| InternalError::msg("failed to parse g1 x"))?,
Fq::from_bytes_be(&g1.1.to_big_endian())
.map_err(|_| InternalError::msg("failed to parse g1 y"))?,
);
LambdaworksG1::create_point_from_affine(g1_x, g1_y)
.map_err(|_| PrecompileError::InvalidPoint)?
};
let g2 = if g2.0.is_zero() && g2.1.is_zero() && g2.2.is_zero() && g2.3.is_zero() {
ProjectiveG2::neutral_element()
Copy link
Contributor

Choose a reason for hiding this comment

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

this for loop body it's better to check the condition of equality with neutral element (is_neutral_element) within the top of the loop

Copy link
Contributor Author

@xqft xqft Oct 16, 2025

Choose a reason for hiding this comment

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

an early continue; there is tricky because we would be skipping the creation of one of the points (create_point_from_affinity), which contains implicit validation checks.

e.g. if G1 is zero and G2 is an invalid point, the precompile should revert but instead it would continue.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we talked internally and decided to leave it as it is, but communicate to the Lambdaworks team the wish to implement the ecpairing and other operations in Lambdaworks to avoid having too much cryptography code in LEVM

@ilitteri ilitteri added this pull request to the merge queue Oct 17, 2025
Merged via the queue into main with commit 742d55a Oct 17, 2025
52 checks passed
@ilitteri ilitteri deleted the substrate-bn branch October 17, 2025 23:31
@github-project-automation github-project-automation bot moved this from Requires Changes to Done in ethrex_l2 Oct 17, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L2 Rollup client

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants