Skip to content

feat(vm): add P256VERIFY precompile (TIP-7951)#6720

Merged
CodeNinjaEvan merged 3 commits intotronprotocol:developfrom
yanghang8612:feat/tip-7951-p256verify
May 8, 2026
Merged

feat(vm): add P256VERIFY precompile (TIP-7951)#6720
CodeNinjaEvan merged 3 commits intotronprotocol:developfrom
yanghang8612:feat/tip-7951-p256verify

Conversation

@yanghang8612
Copy link
Copy Markdown
Collaborator

@yanghang8612 yanghang8612 commented Apr 28, 2026

Summary

  • Adds the P256VERIFY precompile (TIP-7951 / EIP-7951) at address 0x100, gated by the ALLOW_TVM_OSAKA proposal (no separate P256VERIFY config-file knob — activates via on-chain proposal vote, mirroring ALLOW_TVM_SELFDESTRUCT_RESTRICTION).
  • Pure-Java BouncyCastle port — same path as ECRecover, no native dependency.
  • 782 conformance vectors (P256VerifyTest): 778 from the Wycheproof ecdsa_secp256r1_sha256_p1363_test suite plus 4 reference / invalid-input cases adopted from go-ethereum's EIP-7951 fixtures.
  • Optional opt-in manual microbenchmarks (PrecompileBenchmark, guarded by -DrunPrecompileBenchmark=true) compare against ECRecover. TEST 4 (coldNoWarmup) measures no-execute-warmup latency to reflect the low-frequency mainnet case where the JVM has not JIT-compiled the precompile path.

Spec

The 6900-energy cost is the value mandated by EIP-7951 (not derived from these benchmarks); the benchmarks below confirm that this spec value is conservative for TRON's JVM.

Test

  • ./gradlew :framework:test --tests org.tron.common.runtime.vm.P256VerifyTest
  • ./gradlew :framework:test --tests org.tron.common.runtime.vm.AllowTvmOsakaTest
  • ./gradlew :framework:test --no-daemon -DrunPrecompileBenchmark=true --tests 'org.tron.common.runtime.vm.PrecompileBenchmark.coldNoWarmup' -i (manual)
  • ./gradlew :framework:test --no-daemon -DrunPrecompileBenchmark=true --tests 'org.tron.common.runtime.vm.PrecompileBenchmark' -i (manual)

Benchmark

Server: Linux 8 cores / 32 GB. Two separate opt-in --no-daemon runs so the cold measurement gets a fresh JVM:

  1. cold-only — -DrunPrecompileBenchmark=true --tests 'PrecompileBenchmark.coldNoWarmup'
  2. full class — -DrunPrecompileBenchmark=true --tests 'PrecompileBenchmark' (provides warm steady-state and fail-path numbers)

Warm steady-state — TEST 1 / TEST 3 (5000-iter warmup, 5000 × 5 measure)

precompile (energy) TEST 1: single fixed input TEST 3: 100 rotating keys
ECRecover (3000) 1,166 ns/op (858 K ops/s) 1,182 ns/op (846 K ops/s)
P256Verify (6900) 2,034 ns/op (492 K ops/s) 2,040 ns/op (490 K ops/s)
P256 / EC time ratio 1.75× 1.73×

At C2 steady state P256 is ~1.75× slower than ECRecover, well under the 2.30× energy ratio (6900/3000) — i.e. the EIP-7951-mandated 6900 leaves ~24% headroom against TRON's measured cost.

Cold no-execute-warmup — TEST 4 (100 distinct inputs, per-call timing)

precompile (energy) call #1 calls #2..10 (avg) calls #11..100 (avg)
ECRecover (3000) 21.47 ms 1.44 ms 1.39 ms
P256Verify (6900) 12.21 ms 2.68 ms 2.11 ms

The first measured execute() call sees an unprimed precompile path, but inputs are generated before timing, so this is not a full cryptographic helper classloading measurement. Both precompiles still land in the 10–20 ms range for the first measured call. After 100 invocations the JVM has not yet triggered C1/C2 (-XX:CompileThreshold=10000 default), so per-call cost stays around 2 ms — three orders of magnitude above the C2 steady state of ~2 µs. This is the realistic upper bound for a low-frequency precompile that rarely reaches JIT steady state.

Fail-path early-exit — TEST 2 (warm, 5000-iter warmup, 5000 × 5 measure)

stage that fails ns/op ops/s
1. len != 160 103 9.7 M
2. r ≥ N (modulus bound) 440 2.3 M
3. qx ≥ P (field bound) 382 2.6 M
4. point at infinity 325 3.1 M
5. point off-curve 2,141 467 K
6. ECDSA verify fails 1,982,966 504
0. VALID full pass 1,999,964 500

Confirms the cheap guards short-circuit before scalar multiplication: length / bound / infinity checks finish in ~100–500 ns, three orders of magnitude faster than a full ECDSA pass. Off-curve detection costs one curve-equation evaluation (~2 µs) but is still ~1000× cheaper than a full verify. A failing ECDSA verify costs the same as a passing one — both run the full scalar multiplication; only the final equality check differs.

@github-actions github-actions Bot requested a review from CodeNinjaEvan April 28, 2026 11:05
@halibobo1205 halibobo1205 added the topic:vm VM, smart contract label Apr 28, 2026
@halibobo1205 halibobo1205 added this to the GreatVoyage-v4.8.2 milestone Apr 28, 2026
@yanghang8612 yanghang8612 force-pushed the feat/tip-7951-p256verify branch from 29c4ccb to 4837049 Compare May 6, 2026 03:28
Pure-Java BC port of EIP-7951; gated by ALLOW_TVM_OSAKA.
Manual @test, not part of regular suite. Run via --tests filter.
@yanghang8612 yanghang8612 force-pushed the feat/tip-7951-p256verify branch from 4837049 to 9d4e1d1 Compare May 7, 2026 01:22
* Single-threaded, pure-Java BouncyCastle path. The first three tests use a
* 5000-iteration JIT warmup; coldNoWarmup deliberately skips it.
*/
public class PrecompileBenchmark {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Really impressive benchmark methodology here — the cold-no-warmup test in particular is a thoughtful touch for low-frequency precompiles, and the headroom analysis in the PR description is genuinely convincing. 🎯

One small concern: the class Javadoc says "Not part of the regular test suite — invoke explicitly", but because the methods use @Test without @Ignore they will still be picked up by a plain ./gradlew :framework:test run. The 4 benchmarks bring ~100 keypair generations plus 5×5000-iter measurement loops per benchmark, which adds noticeable time and a flood of System.out.printf lines to CI logs.

The repo already has a precedent for this exact case at framework/src/test/java/org/tron/common/runtime/vm/TimeBenchmarkTest.java, which is annotated @Ignore at the class level and still runs fine via --tests selection. Would it be reasonable to add @Ignore here as well so the benchmark stays opt-in? The two --tests … invocations in the PR description would still work unchanged.

@yanghang8612 yanghang8612 requested review from aiden3885 and alan-eth May 8, 2026 07:53
Copy link
Copy Markdown
Collaborator

@CodeNinjaEvan CodeNinjaEvan left a comment

Choose a reason for hiding this comment

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

LGTM

@CodeNinjaEvan CodeNinjaEvan merged commit 736e0f1 into tronprotocol:develop May 8, 2026
14 of 15 checks passed
@github-project-automation github-project-automation Bot moved this to Done in java-tron May 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

topic:vm VM, smart contract

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants