Skip to content

fix(crypto): strengthen signature length validation#6776

Open
Federico2014 wants to merge 5 commits into
tronprotocol:release_v4.8.2from
Federico2014:fix/sig_length_limit
Open

fix(crypto): strengthen signature length validation#6776
Federico2014 wants to merge 5 commits into
tronprotocol:release_v4.8.2from
Federico2014:fix/sig_length_limit

Conversation

@Federico2014
Copy link
Copy Markdown
Collaborator

@Federico2014 Federico2014 commented May 18, 2026

Summary

  • Tighten signature-length validation across transaction, block, relay, and PBFT verification paths.
  • Centralize the bounds in ChainConstant.MIN_SIGNATURE_SIZE (65) and ChainConstant.MAX_SIGNATURE_SIZE (68), and centralize the predicate in SignUtils.isValidLength(int size, boolean checkMaxSignatureSize).
  • Gate the upper bound with DynamicPropertiesStore.signatureMaxSizeChecked(), which reflects whether ALLOW_TVM_OSAKA is enabled.
  • Preserve compatibility for read-only transaction introspection by keeping Wallet.getTransactionApprovedList on a lower-bound-only check, so historical transactions with padded signatures remain inspectable.
  • Harden PBFT quorum accounting by deduplicating accepted votes by recovered SR address rather than raw signature bytes.

Design Notes

Why use [65, 68] instead of strict 65?

After TVM Osaka is enabled, the accepted signature-length window is [65, 68] bytes rather than strict == 65.

This is a deliberate compatibility trade-off:

  • A canonical ECDSA signature is exactly 65 bytes (r || s || v).
  • Historical on-chain data contains signatures with a small amount of trailing padding that ECDSA recovery silently ignores.
  • Using MAX_SIGNATURE_SIZE = 68 closes the previously unbounded encoding window while remaining compatible with historical data already on chain.
  • The long-term goal is to tighten the rule to strict == 65 once the compatibility window is no longer needed.

Because the bounds are centralized in ChainConstant and the predicate is centralized in SignUtils.isValidLength, tightening the rule later will be straightforward.

Why gate the upper bound with signatureMaxSizeChecked()?

The upper bound is enforced through DynamicPropertiesStore.signatureMaxSizeChecked(), which currently maps to getAllowTvmOsaka() == 1L.

This keeps the policy decision in chain state while keeping the signature-length predicate itself small and reusable:

  • SignUtils.isValidLength(size, false) enforces only the lower bound.
  • SignUtils.isValidLength(size, true) enforces both the lower and upper bounds.
  • Validation and submission paths pass the Osaka-aware boolean from DynamicPropertiesStore.
  • The read-only lookup path in Wallet.getTransactionApprovedList intentionally passes false so historical padded signatures remain readable even after Osaka is enabled.

PBFT quorum accounting

validPbftSign now deduplicates accepted votes by recovered SR address rather than by raw signature bytes.

This keeps quorum accounting robust even when the same signer submits signatures with different byte-level encodings. A regression test, PbftDataSyncHandlerTest.testValidPbftSignDedupesByAddress, covers this behavior.

Compatibility

This PR tightens validation behavior, but it does not apply the new upper bound uniformly to every path:

  • Before TVM Osaka, only the lower bound (>= 65) is enforced.
  • After TVM Osaka, transaction, block, relay, and PBFT validation paths enforce both bounds ([65, 68]).
  • Wallet.getTransactionApprovedList remains intentionally more permissive and continues to skip the upper bound so historical transactions can still be inspected.

Breaking Changes

The following public method signatures changed:

Method Before After
TransactionCapsule.checkWeight (Permission, List<ByteString>, byte[], List<ByteString>) (Permission, List<ByteString>, byte[], List<ByteString>, boolean)
TransactionCapsule.addSign (byte[], AccountStore) (byte[], AccountStore, DynamicPropertiesStore)

All in-tree callers were updated.

External JVM consumers of the chainbase artifact, such as SDKs, signing services, and third-party tools, must update these call sites. Existing callers will fail at compile time rather than silently misbehave at runtime.

Main Code Changes

File Change
common/.../config/Parameter.java Add MIN_SIGNATURE_SIZE and MAX_SIGNATURE_SIZE to ChainConstant
crypto/.../SignUtils.java Add isValidLength(int, boolean)
chainbase/.../DynamicPropertiesStore.java Add signatureMaxSizeChecked()
chainbase/.../TransactionCapsule.java Thread the Osaka-aware size check through checkWeight and addSign
chainbase/.../BlockCapsule.java Apply signature-size validation to witness signatures in validateSignature
framework/.../Wallet.java Reuse SignUtils.isValidLength; keep the read-only approved-list path lower-bound-only
framework/.../RelayService.java Apply signature-size validation in checkHelloMessage
framework/.../PbftDataSyncHandler.java Apply signature-size validation in ValidPbftSignTask; deduplicate quorum by recovered SR address
framework/.../PbftMsgHandler.java Pass DynamicPropertiesStore into PBFT signature analysis
consensus/.../PbftBaseMessage.java Apply signature-size validation in analyzeSignature
consensus/.../PbftMessageHandle.java Pass DynamicPropertiesStore into PBFT signature analysis
actuator/.../TransactionUtil.java Update the checkWeight call site

Test Coverage Added or Updated

  • TransactionCapsuleTest
    • Reject short signatures.
    • Reject padded signatures after TVM Osaka.
    • Preserve padded-signature acceptance before TVM Osaka.
  • BlockCapsuleTest
    • Reject short witness signatures.
    • Reject padded witness signatures after TVM Osaka.
    • Preserve padded witness-signature acceptance before TVM Osaka.
  • TransactionExpireTest
    • Keep padded signatures readable through getTransactionApprovedList after TVM Osaka.
    • Reject short signatures on the read-only path.
    • Preserve padded-signature readability before TVM Osaka.
  • TransactionUtilTest
    • Reject short signatures in getTransactionSignWeight.
    • Accept padded signatures on the read-only weight path after TVM Osaka.
  • PbftDataSyncHandlerTest
    • Reject padded PBFT signatures after TVM Osaka.
    • Deduplicate quorum by recovered SR address.
  • PbftMsgHandlerTest
    • Reject padded PBFT message signatures after TVM Osaka.
  • RelayServiceTest
    • Reject padded hello-message signatures after TVM Osaka.
  • ShieldedTransferActuatorTest, ShieldedReceiveTest, and framework/.../TransactionUtils.java
    • Update helper call sites for the new TransactionCapsule.addSign(..., DynamicPropertiesStore) signature.

@github-actions github-actions Bot requested a review from 3for May 18, 2026 03:05
Comment thread chainbase/src/main/java/org/tron/core/capsule/BlockCapsule.java
Comment thread framework/src/main/java/org/tron/core/Wallet.java Outdated
@Federico2014 Federico2014 force-pushed the fix/sig_length_limit branch from 6185c26 to 3fa9b53 Compare May 18, 2026 07:01
@Federico2014 Federico2014 requested review from 3for and Sunny6889 May 18, 2026 07:03
Comment thread chainbase/src/main/java/org/tron/core/capsule/TransactionCapsule.java Outdated
Comment thread chainbase/src/main/java/org/tron/core/capsule/TransactionCapsule.java Outdated
Comment thread crypto/src/main/java/org/tron/common/crypto/SignUtils.java Outdated
Comment thread framework/src/main/java/org/tron/core/net/service/relay/RelayService.java Outdated
private boolean validPbftSign(Raw raw, List<ByteString> srSignList,
List<ByteString> currentSrList) {
//valid sr list
if (srSignList.size() < Param.getInstance().getAgreeNodeCount()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Put this line above if (srSignList.size() != 0) {, when srSignList.size() == 0 old logic will return true, while new logic will return false.

Copy link
Copy Markdown
Collaborator Author

@Federico2014 Federico2014 May 18, 2026

Choose a reason for hiding this comment

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

This is intentional. An empty srSignList cannot meet the 2/3 + 1 quorum, so returning true in that path was a latent bug: the old code accepted a PBFT commit message with zero attached signatures as if it had passed consensus. The new guard rejects it consistently with the size-based quorum check.

Comment thread framework/src/main/java/org/tron/core/Wallet.java Outdated
Comment thread framework/src/test/java/org/tron/core/capsule/BlockCapsuleTest.java Outdated
Comment thread framework/src/test/java/org/tron/core/net/messagehandler/PbftMsgHandlerTest.java Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants