Skip to content

feat(vm): implement TIP-854 canonicalize sign-precompile calldata#6715

Open
yanghang8612 wants to merge 2 commits intotronprotocol:developfrom
yanghang8612:feat/precompile-canonical-input
Open

feat(vm): implement TIP-854 canonicalize sign-precompile calldata#6715
yanghang8612 wants to merge 2 commits intotronprotocol:developfrom
yanghang8612:feat/precompile-canonical-input

Conversation

@yanghang8612
Copy link
Copy Markdown
Collaborator

What does this PR do?

Implements TIP-854 (Canonicalize calldata for signature-verification precompiles) for java-tron, gated behind the existing ALLOW_TVM_OSAKA hardfork.

  • Adds a calldata-length guard at the top of ValidateMultiSign.execute and BatchValidateSign.doExecute. The guard rejects when, with W = 32, any of the following holds: data == null, data.length % W != 0, data.length < H*W, or (data.length - H*W) % (I*W) != 0. (H, I) are the same constants the per-call energy formula (words - H) / I already bakes in: (5, 5) for validateMultiSign, (5, 6) for batchValidateSign.
  • On reject: execute returns (false, empty) without invoking the decoder and without performing any ecrecover. The invoking call frame — reachable through any of CALL / CALLTOKEN / STATICCALL / DELEGATECALL / CALLCODE — consumes its pre-allocated energy, the stack receives 0, memory receives no return data, and the outer transaction continues with its remaining budget intact.
  • For canonically-shaped calldata (length == H*W + I*W*N for some N >= 0), the new rule is a no-op. Pre-activation behaviour, including the per-call energy cost, is byte-for-byte unchanged.
  • No new proposal / committee.* key / CommonParameter / Args plumbing — reuses the already-wired Osaka gate.

Why are these changes required?

The two precompiles charge energy under a fixed shape assumption — the pricing formula treats calldata as a static head followed by exactly N equally-sized tail items — but the existing execute path does not enforce the same shape before decoding. The decoder follows whatever offsets calldata supplies and silently zero-pads any missing bytes through Arrays.copyOfRange. As a result, the set of byte strings accepted is a strict superset of the shapes pricing has ever been evaluated for: non-word-aligned trailing bytes are dropped, inputs shorter than the static head are zero-padded out, and tails that don't decompose into an integer number of items still flow through. This makes the precompiles harder to reason about for wallets, SDKs, indexers, audits, and formal specifications. This PR closes the gap by collapsing the accepted set to exactly the shapes pricing already assumes.

This PR has been tested by:

  • Unit Tests
    • ValidateMultiSignContractTest (3 new cases): rejects malformed calldata across three buckets (non-32-aligned tail, fewer than H words, aligned-but-bad-tail) plus null; canonical-input behaviour identical pre- vs post-activation; pre-activation does not take the new reject path.
    • BatchValidateSignContractTest (3 new cases): same three shapes, parameterised for (H, I) = (5, 6); the canonical-input case uses real 65-byte signatures so each bytes element encodes in exactly four words.
    • OperationsTest.testTip854OuterFrameContainment: drives both precompiles through Program.callToPrecompiledAddress with malformed calldata under Op.CALL and asserts (a) no exception propagates to the outer frame, (b) the inner CALL pushes 0, (c) the outer frame keeps executing afterwards.
  • Manual Testing
    • ./gradlew :actuator:compileJava :framework:compileTestJava — OK.
    • ./gradlew :framework:test --tests "*ValidateMultiSignContractTest" --tests "*BatchValidateSignContractTest" --tests "*OperationsTest.testTip854*" — all pass.

Follow up

  • Validation of inner dynamic offsets and any further decoder hardening (abi.encode conformance) is intentionally out of scope per the TIP and can be addressed in a follow-up if desired.

@github-actions github-actions Bot requested a review from CodeNinjaEvan April 28, 2026 08:54
@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/precompile-canonical-input branch from 3d45f40 to b59683f Compare May 6, 2026 02:27
…alidateSign under Osaka

Reject calldata that doesn't fit the (words - H) / I shape (H=5,
I=5/6) inside execute(); rejected inputs return Pair.of(false,
EMPTY_BYTE_ARRAY). getEnergyForData unchanged.
… precompiles

Add Osaka-gated rejection cases (mis-aligned, short head, bad
tail, null) and a Program#callToPrecompiledAddress integration
test pinning outer-frame containment.
@yanghang8612 yanghang8612 force-pushed the feat/precompile-canonical-input branch from b59683f to 8ad2227 Compare May 6, 2026 03:28
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: No status

Development

Successfully merging this pull request may close these issues.

3 participants