Skip to content

feat(crypto): harden ECKey validation - #46

Open
Federico2014 wants to merge 1 commit into
developfrom
feature/ec-key-validation-hardening
Open

feat(crypto): harden ECKey validation#46
Federico2014 wants to merge 1 commit 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.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

ECKey now uses Tron Bouncy Castle types and validates private scalars and public points. Wallet decryption wraps invalid-key errors and clears decrypted bytes. Witness keystore loading passes validated keys through the updated flow. Tests cover validation, equality, caching, error propagation, and cleanup.

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 uses Tron Bouncy Castle types. Constructors and factories reject invalid private scalars, public points, and non-canonical encodings. Tests cover invalid inputs and matching key pairs.
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 use volatile fields and defensive copies. Equality compares public points. Tests cover equality and cache isolation.
Wallet and witness key handling
crypto/src/main/java/org/tron/keystore/Wallet.java, framework/src/main/java/org/tron/core/config/args/WitnessInitializer.java, framework/src/test/java/org/tron/keystore/WalletAddressValidationTest.java, framework/src/test/java/org/tron/core/config/args/WitnessInitializer*.java
Wallet decryption wraps invalid private-key arguments in CipherException and clears decrypted bytes on success or failure. Witness tests verify key loading and error propagation.

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

Sequence Diagram(s)

sequenceDiagram
  participant Wallet
  participant SignUtils
  participant WitnessInitializer
  Wallet->>SignUtils: reconstruct decrypted private key
  SignUtils-->>Wallet: ECKey or IllegalArgumentException
  Wallet->>Wallet: wrap error and clear private-key bytes
  WitnessInitializer->>Wallet: load witness keystore
  Wallet-->>WitnessInitializer: key or CipherException
Loading

Possibly related issues

🚥 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
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.
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.
✨ 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.

1 participant