Skip to content

[Question] Should java-tron restrict ECDSA signature malleability? #11

@Federico2014

Description

@Federico2014

Question

The main transaction-signature verification path in java-tron currently has two independent
normative gaps:

  1. Missing low-S enforcement: it does not reject non-canonical signatures with s > n/2,
    so signature malleability remains possible.
  2. Missing range checks: it does not enforce 1 ≤ r, s < n, which is a basic precondition
    of ECDSA / SEC 1 verification.

The range check (1 ≤ r, s < n) is already implemented correctly in the EVM ecrecover
precompile path via validateComponents, but the main transaction path does not apply it.
Low-S enforcement is also missing in the main transaction path, while the EVM path remains
permissive for Ethereum-compatibility reasons, as discussed below. Should these gaps be closed?

Context

What are you trying to achieve?

ECDSA signatures have a well-known malleability property: for any valid signature (r, s, v),
multiple transformed variants may also pass verification and recover the same address. The
following variants were analyzed against the current java-tron codebase:

Variant Condition Accepted by tx verification? Recovers same address?
(r, n − s, flip(v)) always Yes Yes
(r + n, s, v) r + n must fit into 32 bytes, i.e. r + n < q (probability about 2^-128); because the main path does not explicitly check r < n, such non-canonical encodings are theoretically possible Theoretical only Yes
(r, s + n, v) s + n must fit into 32 bytes, i.e. s < 2^256 - n (probability about 2^-128); because the main path does not explicitly check s < n, such non-canonical encodings are theoretically possible Theoretical only Yes
(r + n, s + n, v) both conditions above Theoretical only Yes

n is the curve order and q is the field prime.

The only practically relevant variant is (r, n − s, flip(v)). It always works and does
not depend on whether the verification path checks r, s < n. By contrast, the r + n and
s + n variants are additionally constrained by the x-coordinate check (x < q) and by the
32-byte encoding limit, so their probability is about 2^-128 and they are negligible in
practice. They are best understood as evidence that the main path lacks < n range enforcement
and may accept certain non-canonical encodings, not as a practically exploitable attack surface.

What have you tried so far?

Code analysis of the current implementation shows three inconsistencies between signing and
verification behavior:

1. Signing side (compliant)

ECKey.doSign (line 786) calls .toCanonicalised(), so signatures produced by java-tron
itself are always low-S. HALF_CURVE_ORDER is defined with a BIP-62 comment
(ECKey.java:79). Signatures generated by wallet-cli are also always low-S.

2. Transaction and block verification paths (missing low-S enforcement)

The transaction verification call chain is:

TransactionCapsule.checkWeightSignUtils.signatureToAddress
ECKey.signatureToKeyBytesECKey.recoverPubBytesFromSignature

There is no low-S check anywhere in this path.

Block verification (BlockCapsule.validateSignature, line 184) also calls
SignUtils.signatureToAddress, so it shares the same underlying implementation as transaction
verification. The same gap exists in both paths, and the analysis and remediation options below
apply equally to both.

3. Transaction and block verification paths (missing range checks)

recoverPubBytesFromSignature (ECKey.java:518) only checks that r, s ≥ 0; it does not
enforce an upper bound. Under ECDSA / SEC 1 verification preconditions, r, s should satisfy
1 ≤ r, s < n. This constraint is already implemented correctly in the EVM ecrecover path via
validateComponents (ECKey.java:923), but neither the transaction nor block verification path
applies the same check.

As a result, the three code paths do not apply the same acceptance rules to the same signature:
the EVM path explicitly rejects signatures with r ≥ n or s ≥ n, while the transaction and
block verification paths lack corresponding explicit range enforcement and may accept some
non-canonical encodings.

Relevant documentation or code

Location Notes
ECKey.java:79 HALF_CURVE_ORDER definition with BIP-62 reference
ECKey.java:786 doSign calls toCanonicalised(), so output is always low-S
ECKey.java:948 toCanonicalised() implementation
ECKey.java:387 signatureToKeyBytes, main verification path, no low-S check and no r, s < n check
ECKey.java:518 recoverPubBytesFromSignature, only checks r, s ≥ 0, no upper bound
ECKey.java:923 validateComponents, enforces 1 ≤ r, s < n, but only used by the EVM precompile
TransactionCapsule.java:229 checkWeight, transaction-signature verification entry point
BlockCapsule.java:184 validateSignature, block-signature verification entry point; shares the same underlying path
PrecompiledContracts.java:365,595 validateComponents call sites (EVM only)

Reference implementation comparison: Hyperledger Besu

Using Hyperledger Besu as a reference, the coverage of each check looks like this:

Check Besu main tx path Besu ecrecover java-tron main tx path java-tron ecrecover
1 ≤ r, s < n range check
low-S on signing N/A N/A
low-S on verification ✓ (EIP-2, Homestead+) ✗ (spec intentionally permissive) ✗ (spec intentionally permissive)

java-tron's ecrecover behavior is reasonable: validateComponents correctly enforces
1 ≤ r, s < n, and not enforcing low-S there is consistent with Ethereum rules
(ethereum/tests explicitly require ecrecover to accept high-S signatures). The real issue is
the main transaction path: both checks are missing there, and its behavior is asymmetric with
the already-correct EVM path.

Security impact of the current behavior

Under the current transaction model, there is no direct security impact. TRON transaction IDs are
derived from the transaction body and do not include signature bytes, so an attacker cannot
change a transaction ID by mutating the signature. Replay and double-spend protections are
therefore unaffected. This is unlike pre-SegWit Bitcoin, where signature malleability could alter
the IDs of unconfirmed transactions.

However, accepting non-canonical signatures still has indirect downsides:

  1. Standards mismatch: under ECDSA / SEC 1 verification preconditions, r, s should satisfy
    1 ≤ r, s < n; BIP-62 and EIP-2 require s ≤ n/2. The main transaction path currently
    enforces neither.
  2. Path inconsistency: the EVM ecrecover path already enforces range checks via
    validateComponents, while the transaction and block verification paths do not. Besu and other
    Ethereum clients enforce low-S on transaction validation from Homestead onward, while
    java-tron does not. The asymmetry across these paths increases cognitive load for auditors and
    integrators.
  3. Interoperability: hardware wallets and standard signing libraries typically enforce low-S
    and range constraints. Silently accepting non-canonical signatures can hide integration bugs.
  4. Bug bounty false-positive cost: the current non-canonical behavior already causes many
    security researchers to submit bug bounty reports claiming that missing low-S enforcement is a
    vulnerability. Each report requires manual review and response, creating ongoing operational
    overhead. Even when these reports are correctly closed as non-exploitable, they still create
    noise around the project's security posture. Enforcing the missing constraints would eliminate
    this source of recurring false positives.

Consensus Impact and Compatibility

If these changes are applied to the main transaction or block verification rules and affect the
validity of new transactions or blocks, they must be treated as consensus changes and activated
through a network proposal.

Transaction and block signature verification both run in consensus-critical paths. If the
acceptance rules are changed while some nodes upgrade and others do not, different nodes may
disagree on the validity of the same transaction or block, creating chain-split risk.

A safer upgrade path is to gate new rules behind a TRON network proposal (on-chain governance).
The two changes may be advanced together or independently:

  1. Add a new proposal parameter, for example ALLOW_STRICT_SIGNATURE_VALIDATION.
  2. Store the activation state in DynamicPropertiesStore.
  3. Read that flag inside TransactionCapsule.checkWeight and BlockCapsule.validateSignature
    before applying the corresponding checks.
  4. The new rules take effect only after the proposal is approved by SR vote and executed on-chain.

Historical transaction compatibility

Already-confirmed historical transactions will not be re-validated, so they are unaffected.
However, it cannot be assumed that all historical transactions carry canonical signatures. Over
the lifetime of the TRON mainnet, third-party wallets, SDKs, or custom tools may have submitted
transactions without normalizing s, and may not have guaranteed r, s < n either.

The new rules should apply only to transactions submitted after proposal activation. Operators and
integrators would need to complete any required signing-tool upgrades before the proposal takes
effect.

Questions for the Community

  1. Should java-tron enforce low-S constraints (s ≤ n/2) in the main transaction-signature
    verification path, consistent with doSign, Bitcoin BIP-62, Ethereum EIP-2, and Besu's
    MainnetTransactionValidator?
  2. Should java-tron enforce range constraints (1 ≤ r, s < n) in the main
    transaction-signature verification path, consistent with ECDSA / SEC 1 verification
    preconditions and with its own existing EVM ecrecover implementation?
  3. Should the two changes be combined into a single proposal, or advanced independently? If split,
    which one should go first?
  4. If adopted, what migration window should operators be given?

Environment

  • Network: mainnet and all networks
  • java-tron version: current develop

Additional Information (Optional)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions