Question
The main transaction-signature verification path in java-tron currently has two independent
normative gaps:
- Missing low-S enforcement: it does not reject non-canonical signatures with
s > n/2,
so signature malleability remains possible.
- 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.checkWeight → SignUtils.signatureToAddress →
ECKey.signatureToKeyBytes → ECKey.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:
- 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.
- 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.
- Interoperability: hardware wallets and standard signing libraries typically enforce low-S
and range constraints. Silently accepting non-canonical signatures can hide integration bugs.
- 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:
- Add a new proposal parameter, for example
ALLOW_STRICT_SIGNATURE_VALIDATION.
- Store the activation state in
DynamicPropertiesStore.
- Read that flag inside
TransactionCapsule.checkWeight and BlockCapsule.validateSignature
before applying the corresponding checks.
- 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
- 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?
- 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?
- Should the two changes be combined into a single proposal, or advanced independently? If split,
which one should go first?
- If adopted, what migration window should operators be given?
Environment
- Network: mainnet and all networks
- java-tron version: current
develop
Additional Information (Optional)
Question
The main transaction-signature verification path in java-tron currently has two independent
normative gaps:
s > n/2,so signature malleability remains possible.
1 ≤ r, s < n, which is a basic preconditionof ECDSA / SEC 1 verification.
The range check (
1 ≤ r, s < n) is already implemented correctly in the EVMecrecoverprecompile 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:
(r, n − s, flip(v))(r + n, s, v)r + nmust fit into 32 bytes, i.e.r + n < q(probability about2^-128); because the main path does not explicitly checkr < n, such non-canonical encodings are theoretically possible(r, s + n, v)s + nmust fit into 32 bytes, i.e.s < 2^256 - n(probability about2^-128); because the main path does not explicitly checks < n, such non-canonical encodings are theoretically possible(r + n, s + n, v)nis the curve order andqis the field prime.The only practically relevant variant is
(r, n − s, flip(v)). It always works and doesnot depend on whether the verification path checks
r, s < n. By contrast, ther + nands + nvariants are additionally constrained by the x-coordinate check (x < q) and by the32-byte encoding limit, so their probability is about
2^-128and they are negligible inpractice. They are best understood as evidence that the main path lacks
< nrange enforcementand 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-tronitself are always low-S.
HALF_CURVE_ORDERis 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.checkWeight→SignUtils.signatureToAddress→ECKey.signatureToKeyBytes→ECKey.recoverPubBytesFromSignatureThere is no low-S check anywhere in this path.
Block verification (
BlockCapsule.validateSignature, line 184) also callsSignUtils.signatureToAddress, so it shares the same underlying implementation as transactionverification. 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 thatr, s ≥ 0; it does notenforce an upper bound. Under ECDSA / SEC 1 verification preconditions,
r, sshould satisfy1 ≤ r, s < n. This constraint is already implemented correctly in the EVMecrecoverpath viavalidateComponents(ECKey.java:923), but neither the transaction nor block verification pathapplies 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 ≥ nors ≥ n, while the transaction andblock verification paths lack corresponding explicit range enforcement and may accept some
non-canonical encodings.
Relevant documentation or code
ECKey.java:79HALF_CURVE_ORDERdefinition with BIP-62 referenceECKey.java:786doSigncallstoCanonicalised(), so output is always low-SECKey.java:948toCanonicalised()implementationECKey.java:387signatureToKeyBytes, main verification path, no low-S check and nor, s < ncheckECKey.java:518recoverPubBytesFromSignature, only checksr, s ≥ 0, no upper boundECKey.java:923validateComponents, enforces1 ≤ r, s < n, but only used by the EVM precompileTransactionCapsule.java:229checkWeight, transaction-signature verification entry pointBlockCapsule.java:184validateSignature, block-signature verification entry point; shares the same underlying pathPrecompiledContracts.java:365,595validateComponentscall sites (EVM only)Reference implementation comparison: Hyperledger Besu
Using Hyperledger Besu as a reference, the coverage of each check looks like this:
ecrecoverecrecover1 ≤ r, s < nrange checkEIP-2, Homestead+)java-tron's
ecrecoverbehavior is reasonable:validateComponentscorrectly enforces1 ≤ r, s < n, and not enforcing low-S there is consistent with Ethereum rules(
ethereum/testsexplicitly requireecrecoverto accept high-S signatures). The real issue isthe 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:
r, sshould satisfy1 ≤ r, s < n; BIP-62 and EIP-2 requires ≤ n/2. The main transaction path currentlyenforces neither.
ecrecoverpath already enforces range checks viavalidateComponents, while the transaction and block verification paths do not. Besu and otherEthereum 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.
and range constraints. Silently accepting non-canonical signatures can hide integration bugs.
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:
ALLOW_STRICT_SIGNATURE_VALIDATION.DynamicPropertiesStore.TransactionCapsule.checkWeightandBlockCapsule.validateSignaturebefore applying the corresponding checks.
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 guaranteedr, s < neither.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
s ≤ n/2) in the main transaction-signatureverification path, consistent with
doSign, BitcoinBIP-62, EthereumEIP-2, and Besu'sMainnetTransactionValidator?1 ≤ r, s < n) in the maintransaction-signature verification path, consistent with ECDSA / SEC 1 verification
preconditions and with its own existing EVM
ecrecoverimplementation?which one should go first?
Environment
developAdditional Information (Optional)