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 precondition of ECDSA / SEC 1 verification.The range check (
1 ≤ r, s < n) is already implemented correctly in the EVMecrecoverprecompile path viavalidateComponents, 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 does not depend on whether the verification path checksr, s < n. By contrast, ther + nands + nvariants are additionally constrained by the x-coordinate check (x < q) and by the 32-byte encoding limit, so their probability is about2^-128and they are negligible in practice. They are best understood as evidence that the main path lacks< nrange 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_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 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 thatr, s ≥ 0; it does not enforce 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 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 ≥ nors ≥ n, while the transaction and block 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 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:
r, sshould satisfy1 ≤ r, s < n; BIP-62 and EIP-2 requires ≤ n/2. The main transaction path currently enforces neither.ecrecoverpath already enforces range checks viavalidateComponents, 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.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-signature verification path, consistent withdoSign, BitcoinBIP-62, EthereumEIP-2, and Besu'sMainnetTransactionValidator?1 ≤ r, s < n) in the main transaction-signature verification path, consistent with ECDSA / SEC 1 verification preconditions and with its own existing EVMecrecoverimplementation?Environment
developAdditional Information (Optional)