Skip to content

feat(crypto): harden ECKey validation - #46

Open
Federico2014 wants to merge 3 commits into
developfrom
feature/ec-key-validation-hardening
Open

feat(crypto): harden ECKey validation#46
Federico2014 wants to merge 3 commits into
developfrom
feature/ec-key-validation-hardening

Conversation

@Federico2014

@Federico2014 Federico2014 commented Aug 3, 2026

Copy link
Copy Markdown
Owner

What does this PR do?

This PR hardens secp256k1 ECKey construction, state handling, provider usage, and keystore decryption.

  • Rejects null, empty, zero, out-of-range, and oversized private-key encodings before curve operations, while accepting the single leading zero sign byte used by BigInteger for valid high-bit scalars.
  • Validates public-key encoding length and prefix, curve membership, point validity, and infinity state.
  • Ensures supplied private and public keys belong to the same key pair.
  • Uses the Bouncy Castle provider consistently for EC key generation and signing.
  • Removes APIs that bypass key-pair validation or expose private keys through string output.
  • Aligns equality and hash codes with public-key identity.
  • Returns defensive copies of cached address and node ID values.
  • Converts invalid decrypted keys to CipherException and clears the raw decrypted private-key buffer on both success and failure paths.

Why are these changes required?

ECKey previously accepted malformed or inconsistent key material, including unbounded private-key byte arrays and mismatched private/public key pairs. It also exposed mutable cached arrays and advertised configurable provider support that the signing implementation did not consistently honor. In addition, malformed decrypted keys could escape the keystore boundary as IllegalArgumentException, and the raw decrypted private-key buffer remained in memory unnecessarily.

These changes make validation predictable, reject invalid input before expensive curve operations, keep key generation and signing aligned with the Bouncy Castle implementation, preserve cache encapsulation, and normalize keystore failures while reducing plaintext private-key lifetime.

This PR has been tested by:

  • ./gradlew :framework:test --tests org.tron.common.crypto.ECKeyTest
  • ./gradlew :framework:test --tests org.tron.keystore.WalletAddressValidationTest --tests org.tron.core.config.args.WitnessInitializerKeystoreTest
  • ./gradlew :framework:checkstyleMain :framework:checkstyleTest

Follow up

None.

Extra details

This change removes the public constructors that accept an arbitrary Provider, removes fromPrivateAndPrecalculatedPublic and toStringWithPrivate, and changes null or empty private-key input from returning null to throwing IllegalArgumentException. Invalid decrypted keystore keys are now reported as CipherException. No SM2, signature verification, signature recovery, configuration, or consensus behavior is changed.

Compatibility impact

As part of this hardening, consumers using the following public ECKey APIs may be affected by source and binary compatibility changes:

  • Provider-based constructors are removed. Only the Bouncy Castle provider is supported; use ECKey(SecureRandom) for key generation.
  • fromPrivateAndPrecalculatedPublic is removed. Use the validated ECKey(BigInteger, ECPoint) constructor instead.
  • toStringWithPrivate is removed without replacement to prevent accidental private-key exposure.

Downstream consumers must update their code and recompile when upgrading. This does not change node protocol, consensus, transaction, or on-chain behavior.

Summary by CodeRabbit

  • Security Enhancements

    • Added validation for private keys, public keys, and elliptic-curve points.
    • Rejects invalid or mismatched key pairs during key creation.
    • Protects cached address and node ID data from unintended modification.
    • Clears decrypted private-key material from memory after use, including failures.
    • Provides clearer errors when encrypted wallet data contains an invalid private key.
  • Compatibility

    • Signature handling now supports low-S generation and both supported verification forms.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 21f6c530-feb4-4c04-892b-dffbf7f21a35

📥 Commits

Reviewing files that changed from the base of the PR and between 8827d37 and 28d5aaf.

📒 Files selected for processing (2)
  • crypto/src/main/java/org/tron/common/crypto/ECKey.java
  • framework/src/test/java/org/tron/common/crypto/ECKeyTest.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • framework/src/test/java/org/tron/common/crypto/ECKeyTest.java
  • crypto/src/main/java/org/tron/common/crypto/ECKey.java

📝 Walkthrough

Walkthrough

ECKey now validates key material through Tron Bouncy Castle, hardens cache and equality behavior, and removes generic provider handling. Wallet.decrypt wraps invalid keys and clears decrypted bytes. Tests cover validation, cleanup, and witness error propagation.

Changes

Crypto and witness key validation

Layer / File(s) Summary
ECKey construction and validation
crypto/src/main/java/org/tron/common/crypto/ECKey.java, framework/src/test/java/org/tron/common/crypto/ECKeyTest.java
ECKey validates private scalars, public-key encodings, curve points, and private/public key consistency. Factories reject invalid inputs. Tests cover boundary and malformed cases.
ECKey cache and equality behavior
crypto/src/main/java/org/tron/common/crypto/ECKey.java, framework/src/test/java/org/tron/common/crypto/ECKeyTest.java
Address and node-ID caches are volatile and returned as defensive copies. Equality compares public points only.
Wallet and witness key handling
crypto/src/main/java/org/tron/keystore/Wallet.java, framework/src/main/java/org/tron/..., framework/src/test/java/org/tron/keystore/WalletAddressValidationTest.java
Wallet.decrypt wraps invalid private-key arguments and clears decrypted bytes on success or failure. Tests verify cleanup and witness keystore error propagation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Mergeability Score: ⚪ Minimal · up to 28d5a

The change hardens EC key validation and keystore error handling without any supplied evidence of a current correctness, security, or availability issue; no actionable merge-blocking risk remains beyond normal checks and review.

Possibly related issues

  • Federico2014/java-tron issue 47: The PR implements the issue’s ECKey validation and keystore-hardening objectives.
  • Federico2014/java-tron issue 17: The PR overlaps with the issue’s ECKey validation and private-key zeroing objectives.

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: stronger ECKey validation in the crypto module.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/ec-key-validation-hardening

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
framework/src/test/java/org/tron/common/crypto/ECKeyTest.java (1)

78-96: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Strengthen the oversized private-key case so it isolates the length rule.

new byte[33] is rejected by two independent rules: the 32-byte length limit and the zero-scalar check. The assertion therefore does not prove that the length limit works. Add a 33-byte array that holds a valid nonzero scalar. This case also documents the sign-padded BigInteger.toByteArray() encoding that isValidPrivateKey now rejects.

♻️ Proposed additional assertion
     assertFalse(ECKey.isValidPrivateKey(new byte[33]));
+    // 33-byte sign-padded encoding of a valid scalar is rejected by the length rule.
+    byte[] signPadded = new byte[33];
+    System.arraycopy(Hex.decode(privString), 0, signPadded, 1, 32);
+    assertFalse(ECKey.isValidPrivateKey(signPadded));
     assertFalse(ECKey.isValidPrivateKey(BigInteger.ZERO));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/src/test/java/org/tron/common/crypto/ECKeyTest.java` around lines
78 - 96, Update shouldValidatePrivateKeyRange so the oversized byte-array
assertion uses a 33-byte value containing a valid nonzero scalar, such as the
sign-padded encoding of the existing valid private key, isolating rejection by
length rather than the zero-scalar check. Preserve the current null, empty,
boundary, and fromPrivate assertions.
framework/src/test/java/org/tron/core/config/args/WitnessInitializerTest.java (1)

124-125: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Use a canonical 32-byte encoding for the curve-order boundary.

ECKey.isValidPrivateKey(...bytes...) checks privateKey.length <= 32 before converting bytes with new BigInteger(1, privateKey) and checking < N. ECKey.CURVE.getN().toByteArray() is 33 bytes because the secp256k1 order starts with ff, so this test covers oversized input rather than the scalar-boundary check.

Use ByteArray.fromHexString(ECKey.CURVE.getN().toString(16)) so the invalid scalar has the canonical 32-byte magnitude.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@framework/src/test/java/org/tron/core/config/args/WitnessInitializerTest.java`
around lines 124 - 125, Update the invalidKey initialization in
WitnessInitializerTest to use
ByteArray.fromHexString(ECKey.CURVE.getN().toString(16)), ensuring the
curve-order boundary is represented as a canonical 32-byte value and exercises
the scalar-boundary validation rather than the oversized-input check.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@framework/src/test/java/org/tron/common/crypto/ECKeyTest.java`:
- Around line 78-96: Update shouldValidatePrivateKeyRange so the oversized
byte-array assertion uses a 33-byte value containing a valid nonzero scalar,
such as the sign-padded encoding of the existing valid private key, isolating
rejection by length rather than the zero-scalar check. Preserve the current
null, empty, boundary, and fromPrivate assertions.

In
`@framework/src/test/java/org/tron/core/config/args/WitnessInitializerTest.java`:
- Around line 124-125: Update the invalidKey initialization in
WitnessInitializerTest to use
ByteArray.fromHexString(ECKey.CURVE.getN().toString(16)), ensuring the
curve-order boundary is represented as a canonical 32-byte value and exercises
the scalar-boundary validation rather than the oversized-input check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a2fe83d0-29a8-4bd2-b2e2-2a5f81219a67

📥 Commits

Reviewing files that changed from the base of the PR and between e89c0d6 and 0a22ffd.

📒 Files selected for processing (4)
  • crypto/src/main/java/org/tron/common/crypto/ECKey.java
  • framework/src/main/java/org/tron/core/config/args/WitnessInitializer.java
  • framework/src/test/java/org/tron/common/crypto/ECKeyTest.java
  • framework/src/test/java/org/tron/core/config/args/WitnessInitializerTest.java

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 4 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread crypto/src/main/java/org/tron/common/crypto/ECKey.java Outdated
Comment thread framework/src/test/java/org/tron/common/crypto/ECKeyTest.java
Comment thread framework/src/main/java/org/tron/core/config/args/WitnessInitializer.java Outdated
Comment thread framework/src/test/java/org/tron/common/crypto/ECKeyTest.java Outdated
Comment thread framework/src/main/java/org/tron/core/config/args/WitnessInitializer.java Outdated
@Federico2014 Federico2014 changed the title fix(crypto): harden ECKey validation feat(crypto): harden ECKey validation Aug 3, 2026
@Federico2014
Federico2014 force-pushed the feature/ec-key-validation-hardening branch 3 times, most recently from bf45f6b to 07283db Compare August 4, 2026 07:48
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.

[Feature] Enhance ECKey validation and key handling

1 participant