Skip to content

Optimizing cryptographic algorithm implementations #6374

Open

Description

@Federico2014

Background

Currently, the BN128Addition, BN128Multiplication, and BN128Pairing precompiled contracts suffer from performance bottlenecks, leading to transaction timeouts and limiting the application of zero-knowledge proofs on the TRON network, as shown in #4311 and #5492.

The zkBob team has proposed two optimization approaches:

  • The first one, described in #5507, wraps the arkworks cryptographic library via JNI. It offers significantly higher performance, achieving a 3x to 30x speedup for BN128 precompiled contracts.

  • The second one, described in #5611, uses Montgomery optimizations. This results in a 17% performance degradation for BN128Addition, but offers around 30% improvement for BN128Multiplication and BN128Pairing.

To optimize BN128 precompiled contract performance, Besu supports two implementations:

We conducted a comprehensive benchmark of these implementations:

Test results on macOS (2.6 GHz 6-Core Intel Core i7, 16GB RAM), in microseconds (μs):

  BN128Addition BN128Multiplication BN128Pairing (2 pairs)
java-tron 32.718 2584.739 62751.503
arkworks 12.971 97.659 1792.001
Montgomery 41.512 1731.617 41900.001
besu-java 6.5 14163.3 1295543.0
besu-gnark 4.1 55.5 654.2

On Ubuntu 22.04.5 LTS, AMD x86_64, 16 cores, 32GB RAM, the benchmark results (μs):

  BN128Addition BN128Multiplication BN128Pairing (2 pairs)
java-tron 23.654 1896.336 46889.459
arkworks 5.154 83.243 1646.457
Montgomery 29.903 1543.336 35772.447
besu-java 2.5 10413.6 796687.1
besu-gnark 2.4 53.5 655.9

Taking the Ubuntu results as an example, we can see that integrating arkworks significantly improves the performance of BN128 precompiled contracts on java-tron:

  • BN128Addition: ~2.5× speedup

  • BN128Multiplication: ~26× speedup

  • BN128Pairing: ~35× speedup

Moreover, java-tron’s performance is far behind the besu-gnark implementation:

  • BN128Addition: ~10× slower

  • BN128Multiplication: ~35× slower

  • BN128Pairing: ~71× slower

Taking this Groth16 contract example, which involves 6 BN128Addition, 5 BN128Multiplication, and 4 BN128Pairing operations:

  • java-tron execution time:

    6×23.654+5×1896.336+2×46889.459≈103402.44 μs

  • arkworks execution time:

    6×5.154+5×83.243+2×1646.457≈3740.05 μs( 27.6×faster)

  • besu-gnark execution time:

    6×2.4+5×53.5+2×655.9≈1593.70 μs( 65×faster)

In addition to BN128 precompiled contracts, Besu has also optimized EcRecover and Secp256k1 signature verification. The besu-java implementation uses the BouncyCastle library, while besu-native wraps bitcoin-core/secp256k1 via JNA. We benchmarked and compared their performance as well:

Test results on macOS (2.6 GHz 6-Core Intel Core i7, 16GB RAM), in μs:

  EcRecover Secp256k1 ModExp
java-tron 1956.975 1218.343 58.418
besu-java 959.4 891.7 21.222
besu-native 50.6 45.3 34.585

Test results on Ubuntu 22.04.5 LTS, AMD x86_64, 16 cores, 32GB RAM:

  EcRecover Secp256k1 ModExp
java-tron 916.184 875.647 20.168
besu-java 679.2 670.2 16.428
besu-native 50.4 47.3 34.165

From the Ubuntu benchmark results, we can see that besu-native achieves ~18× speedup over java-tron in both EcRecover and Secp256k1 signature verification. For ModExp, the performance difference is smaller, possibly due to limited test coverage.

From the above benchmarks, it is clear that optimizing the performance of the BN128 precompiled contracts and other cryptographic algorithms is necessary. The besu-native implementation serves as a strong reference, as it also includes optimized support for Blake2bf and BLS12-381, which can help improve the scalability of the TRON network.

Rationale

Why should this feature exist?

  • Optimize the performance of BN128 precompiled contracts to avoid transaction timeouts and promote ZK-based applications on the TRON network;
  • Accelerate EcRecover and Secp256k1 signature verification to improve TRON’s scalability;
  • Optimize additional cryptographic primitives such as ModExp, Blake2bf, and BLS12-381.

What are the use-cases?

  • ZK application developers;
  • Reducing signature verification time for all TRON transactions;

Specification

  • Replace the current java-tron BN128 precompiled contract implementation with the optimized version;
  • Replace the current java-tron implementation of EcRecover and Secp256k1 with optimized versions;
  • Apply similar performance improvements to other cryptographic primitives.

Test Specification

  • Benchmark execution speed of the optimized BN128 precompiled contract and compare it to the current implementation;
  • Benchmark EcRecover and Secp256k1 verification performance before and after optimization;
  • Measure execution time of ZK-related transactions before and after optimization;
  • Measure execution time of regular transactions before and after optimization;

Scope Of Impact

  • BN128 and EcRecover precompiled contracts;
  • Reduced transaction processing time by accelerating signature verification;
  • Potential gas cost adjustments for affected precompiled contracts;
  • Performance improvements must preserve consensus correctness to avoid network forks, requiring proposal-level changes;
  • As signature verification is a major performance bottleneck in block production, improvements should be evaluated in the context of block time scheduling.

Implementation

Do you have ideas regarding the implementation of this feature?

Currently, the optimization of BN128 precompiled contracts mainly considers two implementations: arkworks and besu-gnark. The arkworks-based version has already been developed by the zkBob team (see #5507), but recent benchmarks show that besu-gnark performs slightly better. Since besu-native relies on JDK 21, we also need to evaluate how to maintain compatibility with JDK 8.

A thorough discussion is still required to evaluate the advantages and potential risks of each solution before choosing the final optimization path. For EcRecover and Secp256k1, it should also be discussed within the community whether to adopt the optimization strategy used in besu-native.

Are you willing to implement this feature?
Y

Activity

  1. moved this to In Progress in java-tronon Jul 1, 2025
  2. jwrct commented on Jul 2, 2025

    @jwrct
    Contributor

    @Federico2014 From the benchmark results you provided, it seems that the performance of java-tron's signature verification is really bad, and this optimization seems to be necessary. besu-gnark is undoubtedly the best, but java-tron currently only supports JDK 8, but I see that there are already issues discussing support for higher versions of JDK, for example: #5976.

  3. Federico2014 commented on Jul 2, 2025

    @Federico2014
    CollaboratorAuthor

    @jwrct besu-native is built with JDK 21, while java-tron is based on JDK 8, so it cannot be used directly. Currently, we haven't decided how to handle the JDK compatibility issue. Alternatively, we could consider modifying besu-native to downgrade it to JDK 8.

  4. jwrct commented on Jul 2, 2025

    @jwrct
    Contributor

    @Federico2014 OK, I understand. If there is any progress, please update in time. I am looking forward to the implementation of this optimization.

  5. Federico2014 commented on Jul 2, 2025

    @Federico2014
    CollaboratorAuthor

    I also tested the execution time for verifying a single Groth16 proof transaction under three scenarios: java-tron, arkworks, and Montgomery. The test environment was a Mac with a 2.6 GHz 6-core Intel Core i7 and 16GB of RAM. The transaction timeout in java-tron was set to 200 ms.

    All test cases executed the same transaction for verifying a Groth16 proof, and the execution time was measured separately in the broadcast, pack, and apply stages. The test results are as follows (unit: ms):

    java-tron

      broadcast pack apply Result
    Test case 1 881 909 2 timeout
    Test case 2 787 622 1 timeout
    Test case 3 536 437 0 timeout
    Test case 4 426 206 0 timeout
    Test case 5 122 156 107 success
    Test case 6 133 139 125 success
    Test case 7 116 113 106 success

    arkworks

      broadcast pack apply Result
    Test case 1 33 23 18 success
    Test case 2 16 17 12 success
    Test case 3 9 13 14 success
    Test case 4 8 11 11 success
    Test case 5 12 8 9 success

    Montgomery

      broadcast pack apply Result
    Test case 1 244 199 167 success
    Test case 2 261 232 2 timeout
    Test case 3 130 462 90 success
    Test case 4 253 308 1 timeout
    Test case 5 140 178 113 Success

    From the above test results, it can be seen that with the arkworks optimization, the execution time for verifying a single Groth16 proof transaction stays within 50 ms. However, with the current java-tron implementation and Montgomery optimization, the execution time is less stable — even with the transaction timeout set to 200 ms, sometimes the transaction executes successfully, but other times it fails due to a timeout.

  6. SeriousCoding789 commented on Jul 2, 2025

    @SeriousCoding789
    Contributor

    @jwrct besu-native is built with JDK 21, while java-tron is based on JDK 8, so it cannot be used directly. Currently, we haven't decided how to handle the JDK compatibility issue. Alternatively, we could consider modifying besu-native to downgrade it to JDK 8.

    Will downgrade it to JDK 8 decrease the algorithm performance?

  7. Federico2014 commented on Jul 2, 2025

    @Federico2014
    CollaboratorAuthor

    @jwrct besu-native is built with JDK 21, while java-tron is based on JDK 8, so it cannot be used directly. Currently, we haven't decided how to handle the JDK compatibility issue. Alternatively, we could consider modifying besu-native to downgrade it to JDK 8.

    Will downgrade it to JDK 8 decrease the algorithm performance?

    I think the impact should be relatively small.

  8. Federico2014 commented on Jul 10, 2025

    @Federico2014
    CollaboratorAuthor

    I have modified besu-native to make it compatible with JDK 8 and integrated it into java-tron. After that, I conducted benchmarks for the BN128 precompiled contract.

    Test results on macOS (2.6 GHz 6-Core Intel Core i7, 16GB RAM), in μs:

      BN128Addition BN128Multiplication BN128Pairing(2 pairs)
    java-tron 58.279 2407.683 61829.418
    java-tron-arkworks 10.711 172.058 2036.048
    java-tron-gnark 74.698 50.744 758.094

    Based on the test results above, after integrating besu-gnark into java-tron, the performance of both BN128Mul and BN128Pairing has significantly improved. However, BN128Add shows no noticeable performance gain. I noticed that in besu-native, benchmarking is performed after a warm-up phase, where the precompiled contracts are executed multiple times beforehand. This allows the JVM to complete class loading, method compilation, and other preparations, preventing first-time execution overhead from affecting the benchmark results. After introducing a similar warm-up phase, my test results are as follows:

      BN128Addition BN128Multiplication BN128Pairing(2 pairs)
    java-tron 23.514 2370.796 62461.821
    java-tron-arkworks 6.246 101.516 1990.414
    java-tron-gnark 3.945 49.713 640.455

    The benchmark results of java-tron-gnark are very close to those of besu-gnark, indicating that integrating besu-gnark into java-tron can effectively improve the performance of BN128 precompiled contracts. The performance of BN128Addition, BN128Multiplication, and BN128Pairing has improved by 6x, 47x, and 97x, respectively. Moreover, it proves to be more efficient than the arkworks-based implementation.

  9. Federico2014 commented on Jul 10, 2025

    @Federico2014
    CollaboratorAuthor

    Similarly, I also tested the execution time of Groth16 proof verification transactions using java-tron-gnark. The results are as follows (unit: ms):

      broadcast pack apply Result
    Test case 1 29 14 19 success
    Test case 2 16 18 18 success
    Test case 3 13 10 11 success
    Test case 4 9 10 10 Success
    Test cast 5 27 18 36 success

    From the results, we can see that for Groth16 proof verification transactions, the execution time using java-tron-gnark is comparable to the arkworks-optimized implementation — both stay within 40 ms. This ensures smooth execution of zero-knowledge related transactions.

  10. lxcmyf commented on Jul 10, 2025

    @lxcmyf
    Collaborator

    Similarly, I also tested the execution time of Groth16 proof verification transactions using java-tron-gnark. The results are as follows (unit: ms):

      broadcast pack apply Result
    Test case 1 29 14 19 success
    Test case 2 16 18 18 success
    Test case 3 13 10 11 success
    Test case 4 9 10 10 Success
    Test cast 5 27 18 36 success
    From the results, we can see that for Groth16 proof verification transactions, the execution time using java-tron-gnark is comparable to the arkworks-optimized implementation — both stay within 40 ms. This ensures smooth execution of zero-knowledge related transactions.

    Glad to see running the benchmarks and sharing the results — the sub-40 ms latency is indeed promising.
    Just to clarify:

    1. Were these measurements taken in a controlled local environment (e.g., localhost, no network latency)?
    2. Also, do the numbers reflect only the on-chain contract execution time, or do they include pre-processing like proof decoding or deserialization?
      

    This distinction would help assess whether the performance can scale in more complex or production settings.

  11. 15 remaining items

  12. Federico2014 commented on Sep 29, 2025

    @Federico2014
    CollaboratorAuthor

    Status Update: Given the critical importance of cryptographic algorithms to overall security, the ongoing optimizations are currently undergoing comprehensive and rigorous testing to ensure robustness, stability, and performance. This feature is scheduled for the next release after 4.8.1. Please stay tuned.

  13. nkoreli commented on Mar 6, 2026

    @nkoreli

    Hi, @Federico2014 is there an estimated timeline for when this optimization will be merged? We're building a privacy protocol (Hinkal) that relies on zero-knowledge proof verification on TRON. The current BN128 pairing performance causes transaction timeouts during Groth16 proof verification, which is core to our shielded transaction flow. This is a blocker for us on TRON.
    Thanks!

  14. Federico2014 commented on Mar 6, 2026

    @Federico2014
    CollaboratorAuthor

    @nkoreli According to the current plan, the optimized BN128 pairing implementation is expected to be included in v4.8.2, which is scheduled for release in about 3 months. However, to ensure security and stability, whether it will be released will still depend on the actual testing results. Thank you very much for your attention.

  15. simbadMarino commented on Mar 6, 2026

    @simbadMarino

    Hi @Federico2014 thanks a lot for updating on the ETA!, is this issue the best place to look for testing results and updates? If there is any other place we should monitor let us know, as we have several community inquiries regarding this same topic.

  16. Federico2014 commented on Mar 9, 2026

    @Federico2014
    CollaboratorAuthor

    Hi @Federico2014 thanks a lot for updating on the ETA!, is this issue the best place to look for testing results and updates? If there is any other place we should monitor let us know, as we have several community inquiries regarding this same topic.

    Yes, all currently published progress will be posted in this issue. Just follow this issue and #6376.
    .

  17. bladehan1 commented on Mar 12, 2026

    @bladehan1
    Collaborator

    Concern: Native crypto introduces a different failure model in the consensus execution path

    The optimizations discussed in this issue (arkworks, native secp256k1, etc.) can significantly improve performance, especially for BN128 pairing and signature verification. This is important for supporting ZK-related workloads.

    However, introducing these implementations will likely require native libraries accessed through JNI/JNA, which changes the runtime failure model of the node.

    In the JVM:

    • Java exceptions are recoverable and confined to the execution flow.
    • Native crashes (e.g. SIGSEGV, SIGBUS, memory corruption) terminate the entire JVM process.

    This difference becomes particularly important because these cryptographic operations are executed inside the block execution / transaction validation path, which is consensus-critical.

    In the worst case, the failure mode could look like this:

    block execution
        ↓
    transaction triggers native bug
        ↓
    native library crash
        ↓
    JVM process exit
        ↓
    node crash
    

    In distributed systems this is already problematic, but in blockchain systems the impact can be amplified:

    If a specific transaction input triggers a native crash, every validator executing the same block may crash at the same point, since they all process identical inputs.

    This creates a potential network-wide correlated failure, which is much more severe than an isolated node crash.


    Possible mitigation strategies

    1. Native acceleration behind a feature flag

    Provide a runtime configuration allowing operators to choose between:

    pure Java crypto implementation
    or
    native accelerated implementation
    

    Pros:

    • Simple to implement
    • Allows operational rollback if instability is discovered
    • Minimal architectural change

    Cons:

    • Native crashes still terminate the JVM
    • Only mitigates rollout risk, not runtime crash isolation
    • Validators may still coordinate to ensure consistent behavior

    This approach is useful operationally but does not fundamentally change the crash model.


    2. Native crypto worker via process isolation (IPC)

    Another architecture worth evaluating is isolating native crypto implementations in a separate worker process.

    Example architecture:

    java-tron JVM
        │
        │ IPC (Unix socket / pipe / gRPC)
        ▼
    native crypto worker
        │
        └─ arkworks / secp256k1 / other native libraries
    

    In this model:

    • The JVM communicates with the native component via IPC.

    • If the native worker crashes, only the worker process exits.

    • The JVM can detect the failure and:

      • restart the worker
      • fall back to the pure Java implementation
      • safely reject the triggering transaction

    Pros:

    • Strong fault isolation
    • Native crashes no longer terminate the node
    • More robust for consensus-critical execution

    Cons:

    • Requires additional process lifecycle management
    • IPC introduces some overhead
    • Requires defining a stable request/response interface

    For batchable cryptographic workloads (e.g. pairing checks or signature verification), IPC overhead may be amortized.


    Suggestion

    Since these optimizations target consensus-critical code paths, it may be worth evaluating the feasibility of an IPC-isolated native worker.

    Even if IPC isolation is not adopted immediately, two steps may still be valuable:

    1. Provide a configuration switch to enable/disable native acceleration
    2. Document the failure model difference introduced by native libraries

    It would be helpful if maintainers could comment on:

    • Are these cryptographic calls frequent enough that IPC overhead becomes prohibitive?
    • Can operations be batched to amortize IPC costs?
    • How complex would it be to extract these operations behind a well-defined interface?
    • Would the reliability benefits justify the added architectural complexity?

    The performance benefits of native crypto are clear, but ensuring fault isolation in the consensus path may be equally important for long-term network stability.

  18. liuyifei001 commented on Mar 12, 2026

    @liuyifei001

    @bladehan1
    This seems like a novel idea — even if the JNI crashes, it would not cause the node itself to crash. However, there may be several issues:

    • If the JNI crashes, the node will no longer be able to verify signatures, which effectively means the functionality is unavailable and the node must be restarted anyway. In practice, this is not very different from the node crashing.
    • The overhead of inter-process communication is at least an order of magnitude higher than that of inter-thread communication, and it may even exceed the cost of the computation itself.
    • JNI cannot run independently. It would still need to be wrapped as something similar to a JDK-like process. This could also be relatively slow and might not be much faster than before the split.

    In fact, our JNI only involves ordinary ECDSA computations. If the testing time and functional coverage are sufficiently complete, the risk should be manageable.

  19. bladehan1 commented on Mar 13, 2026

    @bladehan1
    Collaborator

    @liuyifei001

    1. After separating the process, if JNI crashes, the node can choose to try restarting the JNI process or roll back to the Java version for execution.

    2. IPC communication overhead is a core consideration. A solution is only feasible if it maintains high performance even with added IPC communication overhead.
      For IPC communication overhead, consider batching requests and using concurrent requests to reduce average overhead.

    Performance evaluation relies on @Federico2014 verification.

  20. Federico2014 commented on Mar 17, 2026

    @Federico2014
    CollaboratorAuthor

    @bladehan1
    I compared three implementations: pure Java, JNA, and JNA + IPC. The benchmark results are as follows (unit: ms):

      BN128Addition BN128Multiplication BN128Pairing BN128Pairing (100 pairs)
    Pure Java 0.029 2.333 55.991 2227.027
    JNA 0.006 0.057 0.690 19.26
    JNA + IPC 0.131 0.128 0.720 19.42

    The results show that the additional latency introduced by IPC is minimal, around ~0.2 ms.

    In case of IPC invocation failure or timeout (currently configured as 30 ms), the system will automatically fall back to the pure Java implementation to ensure reliability.

    Note: All benchmarks were conducted on a local Mac machine with an Intel CPU. Absolute performance numbers may vary depending on hardware and deployment environments.

  21. waynercheung commented on May 15, 2026

    @waynercheung
    Collaborator

    @Federico2014 Is there any update for this issue?

  22. Federico2014 commented on May 15, 2026

    @Federico2014
    CollaboratorAuthor

    @waynercheung Thanks for the follow-up. The fix is ready and currently undergoing regression testing. I'll post an update here if there is any new progress.

  23. Federico2014 commented on Sep 3, 2026

    @Federico2014
    CollaboratorAuthor

    Status update: Federico2014/java-tron#52 contains the latest implementation work for the secp256k1 optimization tracked by this issue.

    The PR introduces an optional JNA-backed NativeSecp256k1 implementation for signing, public-key recovery, and address recovery. It also adds explicit configuration and compatibility handling: the native implementation is disabled by default, and java-tron falls back to the existing ECKey implementation when native acceleration is unavailable or cannot process a legacy-compatible input.

    This PR is currently being considered as an optimization candidate rather than the confirmed final implementation. Since cryptographic operations are used in consensus-critical execution paths, the implementation still requires careful review and validation of compatibility, security, stability, fallback behavior, and platform support before it can be adopted.

    As a result, whether this implementation will be merged and included in v4.8.3 has not yet been decided. v4.8.3 should not be considered a confirmed release target at this stage. This issue will remain open, and further updates will be posted once the evaluation and release plan are confirmed.

  24. yanghang8612 commented on Sep 11, 2026

    @yanghang8612
    Collaborator

    Implemented a pure-Java BN128 optimization, without JNI/JNA.

    Repository/branch: yanghang8612/java-tron — perf/optimize-bn128. Both platforms benchmarked 1b7ab7983f against the original bd2450fe06.

    Optimizations

    • Fixed-limb Montgomery arithmetic, specialized extension-field operations, and mixed Jacobian/affine point addition.
    • Fast G2 subgroup validation using Frobenius endomorphisms.
    • Simultaneous multi-Miller loops with shared squaring, signed NAF, and fused sparse line multiplication.

    Correctness and compatibility

    • 49 Geth Add/Mul/Pairing known-answer vectors, checked against both implementations.
    • 10,525 Besu-native/gnark differential cases during development—4,894 precompile cases and 5,631 field-arithmetic cases—with zero mismatches.
    • Original-vs-optimized comparisons include full Fp12 outputs, not just pairing’s boolean result. BigInteger-reference and randomized arithmetic tests passed over 2.43 million assertions.
    • Dedicated tests compare fast subgroup validation against full scalar multiplication, and sparse/Miller optimizations against dense/binary references.

    Boundary coverage includes empty/truncated/padded inputs, non-canonical coordinates, off-curve and non-subgroup points, infinity, cancellation, full-width scalars, and carry/borrow patterns. In particular, G1 infinity must not bypass G2 validation.

    Tested return bytes and rejection behavior matched. ABI and Energy pricing remain unchanged. These checks provide regression evidence, not a formal proof.

    Performance

    Both platforms were rerun on 2026-09-11 against the commits above. Complete-call P50 latency; speedups compare original and optimized implementations within each platform.

    Operation Linux original → optimized Speedup Mac original → optimized Speedup
    Pairing, 1 pair (ms) 40.600 → 4.196 9.68× 27.199 → 2.812 9.67×
    Pairing, 2 pairs (ms) 66.025 → 5.734 11.52× 44.428 → 3.842 11.56×
    Pairing, 8 pairs (ms) 217.247 → 14.944 14.54× 148.192 → 10.030 14.77×
    Pairing, 32 pairs (ms) 828.231 → 52.736 15.71× 562.872 → 34.751 16.20×
    Add (μs) 30.660 → 21.152 1.45× 13.625 → 8.333 1.64×
    Mul (μs) 2779.319 → 495.860 5.61× 1853.500 → 354.792 5.22×

    Setup: Linux: Xeon Platinum 8175M, CentOS 7.9, Oracle JDK 8u291. Mac: M1 Max, macOS 26.6.2, Corretto 17.0.19 ARM64. Both used the same harness and input fixtures, 1 GiB heap, ParallelGC, and no coverage instrumentation.

    Each JVM warmed up for ≥60 seconds AND ≥1,000 pairing calls / ≥10,000 Add/Mul calls. Both platforms used 2 independent JVM forks: 202 samples per pairing configuration and 10,002 per Add/Mul configuration. Implementation order alternated; all samples were retained. Source/harness hashes, actual warmup durations, raw samples and pooled statistics were independently checked.

    Both hosts were shared and used different JDKs, so these are within-platform optimization gains, not a CPU-only comparison.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

  • Status
    In Progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions