Skip to content

test(crypto): add regression baseline for crypto#35

Open
Federico2014 wants to merge 3 commits into
developfrom
feature/crypto-regression-test
Open

test(crypto): add regression baseline for crypto#35
Federico2014 wants to merge 3 commits into
developfrom
feature/crypto-regression-test

Conversation

@Federico2014
Copy link
Copy Markdown
Owner

@Federico2014 Federico2014 commented Jun 2, 2026

What does this PR do?

Adds a focused regression baseline for the cryptography-related precompiled contracts that previously lacked asserting unit coverage. No production code is changed — test files and JSON vector resources only.

Two layers:

  1. Direct execute() tests for point validation, signature recovery, and boundary handling:
Test class Address Cases Coverage
BN128Test 0x06–08 10 Point validation, G1 order-r / subgroup membership, the G1-vs-G2 subgroup-check asymmetry, and pairing correctness against the EIP-197 generators
ECRecoverTest 0x01 5 Recovered address matches the signer on the success path; bad recovery id, polluted v padding, zero r/s, and short input return an empty result without throwing
Blake2FTest 0x09 6 Canonical EIP-152 vector 4 input/output, plus length / finalization-flag validation and round-count gas pricing
  1. Consolidated, data-driven vectors under framework/src/test/resources/precompiles/, reusing the existing {Input, Expected, Gas, Name} JSON schema already used by p256verify_test_vectors.json:
Vector file Precompile Entries Source
bn128add_test_vectors.json ecAdd (0x06) 3 Computed against the alt_bn128 curve (G+G, G+O, G+(-G))
bn128mul_test_vectors.json ecMul (0x07) 3 Computed (2·G, 0·G, 1·G)
bn128pairing_test_vectors.json ecPairing (0x08) 3 Empty=1, e(G1,G2)·e(-G1,G2)=1, e(G1,G2)≠1; EIP-197 G2 generator
blake2f_test_vectors.json BLAKE2F (0x09) 4 EIP-152 reference vectors 4–7

PrecompileVectorTest loads these and asserts both output and gas. Adding more vectors needs only a JSON append — no test-code change.

Why are these changes required?

The crypto precompiles had coverage gaps: BN128* / PairingCheck had no direct unit test (only gas accounting was exercised through Solidity in IstanbulTest); ECRECOVER had a single non-asserting invocation; BLAKE2F was only tested indirectly via a Solidity contract. The BN128Test order-r and pairing-negation checks also lock in the G1 cofactor=1 property relevant to HackerOne #3769516.

This PR has been tested by:

  • Unit Tests — ./gradlew :framework:test: 25/25 pass
  • Checkstyle — ./gradlew :framework:checkstyleTest: pass

Vector correctness is not hand-typed: BN128 vectors are computed against the curve math in a separate script, BLAKE2F vectors are captured from actual Blake2F.execute() output, and all are re-asserted by PrecompileVectorTest against the live precompile (output + gas).

Compatibility note

TRON's ECRECOVER (0x01) returns a 21-byte TRON address (sha3omit12, keccak[11:32] with the leading byte replaced by the TRON prefix 0x41), not Ethereum's 20-byte address. Standard go-ethereum ecrecover vectors therefore cannot be reused directly — which is why ECRECOVER keeps a programmatic self-verifying test (deterministic signature → assert recovered address equals the signer) rather than a JSON vector file.

Follow up

Optional: extend the vector set with more go-ethereum bn256/blake2f boundary cases and official modExp/sha256 vectors under the same resources/precompiles/ directory.

Add direct unit coverage for the alt_bn128 (BN254) primitives behind the
ecAdd/ecMul/ecPairing precompiles: BN128Fp/BN128G1/BN128G2 point validation,
G1 order-r / subgroup behaviour, the G1-vs-G2 subgroup-check asymmetry, and
PairingCheck correctness against the EIP-197 generators.

Previously these classes had no direct tests; only gas accounting was
exercised via Solidity in IstanbulTest. Closes the high-priority gap noted
in the Q2 crypto test baseline and related to HackerOne #3769516.
Add direct execute() coverage for two crypto precompiles that previously had
no asserting unit test:

- ECRecoverTest (0x01): recovered address matches the signer on the success
  path; bad recovery id, non-zero v padding, zero r/s, and short input all
  return an empty result without throwing.
- Blake2FTest (EIP-152, 0x09): pins the canonical vector 4 input/output pair,
  plus length / finalization-flag validation and round-count gas pricing.

Extends the Q2 crypto precompile test baseline (signature, verification).
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds three independent JUnit regression test suites for cryptographic precompiles: BN128 validates alt_bn128 curve primitives (G1/G2 point acceptance, subgroup membership, pairing correctness); Blake2FTest exercises the BLAKE2F compression function with canonical vectors and edge cases; and ECRecoverTest verifies ECRECOVER signature recovery and graceful error handling on invalid inputs.

Changes

Cryptographic precompile regression tests

Layer / File(s) Summary
BN128 curve validation and pairing tests
framework/src/test/java/org/tron/common/crypto/zksnark/BN128Test.java
Defines BN128 G1 and G2 test vectors with on-curve and off-curve coordinates; validates point acceptance/rejection rules, G1 generator subgroup order via scalar multiplication by R, and pairing product correctness on empty input, negation cancellation, and single-pair cases.
BLAKE2F precompile tests
framework/src/test/java/org/tron/common/runtime/vm/Blake2FTest.java
Tests BLAKE2F F-compression against EIP-152 canonical vector, verifies gas/energy matches round count, rejects malformed inputs (incorrect length, invalid final flag), and validates acceptable inputs including zero rounds and final flag 0x00.
ECRECOVER precompile tests
framework/src/test/java/org/tron/common/runtime/vm/ECRecoverTest.java
Constructs deterministic ECDSA inputs from fixed private key and message hash; verifies successful signer address recovery and exercises error-handling paths (invalid recovery id, non-zero v-word padding, zeroed signature components, short input) that must return empty output without throwing.

🎯 2 (Simple) | ⏱️ ~12 minutes


🐰 Elliptic hops through BN128's gentle curves,
BLAKE2's rounds compress the truth,
ECRECOVER signs the proof—
Cryptographic safety served! ✨🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.83% 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.
Title check ✅ Passed The PR title 'test(crypto): add regression baseline for crypto' is partially related to the changeset. It accurately identifies that regression tests for crypto are being added, but it is overly broad and generic. It does not specify which crypto precompiles are being tested (BN128, ECRECOVER, BLAKE2F), making it less informative than the actual PR objectives.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/crypto-regression-test

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 and usage tips.

@Federico2014 Federico2014 changed the title test(crypto): add precompile regression baseline for BN128, ECRECOVER and BLAKE2F test(crypto): add regression baseline for crypto Jun 2, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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.

Inline comments:
In `@framework/src/test/java/org/tron/common/runtime/vm/Blake2FTest.java`:
- Around line 74-89: For both rejectsWrongLength and rejectsInvalidFinalFlag,
extend the assertions to ensure that when BLAKE2F.execute(input) returns false
the output bytes are empty/stale-free: after calling BLAKE2F.execute(input)
assert result.getLeft() is false and assert that result.getRight() is either
null or contains only zero bytes (no non-zero/stale data); keep the existing
energy assertion in rejectsInvalidFinalFlag (BLAKE2F.getEnergyForData(input) ==
0L). This uses the existing BLAKE2F.execute and result.getRight() to validate no
output is produced on rejection.

In `@framework/src/test/java/org/tron/common/runtime/vm/ECRecoverTest.java`:
- Around line 96-97: The test currently only asserts that the output is empty
for the polluted-`v` case but omits verifying the execution status; update the
ECRecoverTest polluted-`v` case to assert the boolean execution result
(result.getLeft()) is false like the other malformed-input tests so the contract
is enforced when calling EC_RECOVER.execute(input); locate the Pair<Boolean,
byte[]> result variable and add the same result.getLeft() assertion used
elsewhere.
- Around line 69-72: The test currently only compares the trailing bytes by
extracting tail from result.getRight(), which misses verifying the left padding;
replace that check by constructing a full 32-byte expected word (left-pad
key.getAddress() with zeros to length 32) and assertArrayEquals that full
expected 32-byte array against result.getRight(); update/removing references to
the temporary tail variable and keep using result.getRight() as the actual
value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1b1ff9dc-f403-4146-b0f7-df314881f18c

📥 Commits

Reviewing files that changed from the base of the PR and between 381d369 and 2f8bdb3.

📒 Files selected for processing (3)
  • framework/src/test/java/org/tron/common/crypto/zksnark/BN128Test.java
  • framework/src/test/java/org/tron/common/runtime/vm/Blake2FTest.java
  • framework/src/test/java/org/tron/common/runtime/vm/ECRecoverTest.java

Comment on lines +74 to +89
public void rejectsWrongLength() {
// 212 bytes — one short of the required 213.
byte[] input = new byte[212];
Pair<Boolean, byte[]> result = BLAKE2F.execute(input);
assertFalse("incorrect length must fail", result.getLeft());
}

@Test
public void rejectsInvalidFinalFlag() {
byte[] input = VECTOR_4_INPUT.clone();
input[212] = 0x02; // flag must be 0x00 or 0x01

Pair<Boolean, byte[]> result = BLAKE2F.execute(input);
assertFalse("invalid finalization flag must fail", result.getLeft());
assertEquals("invalid flag must price to zero", 0L, BLAKE2F.getEnergyForData(input));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Assert empty output on rejected inputs.

These failure-path tests only check the status bit. If execute() ever returns false but still emits stale/non-empty bytes, this suite would miss it.

Suggested test tightening
   `@Test`
   public void rejectsWrongLength() {
     // 212 bytes — one short of the required 213.
     byte[] input = new byte[212];
     Pair<Boolean, byte[]> result = BLAKE2F.execute(input);
     assertFalse("incorrect length must fail", result.getLeft());
+    assertEquals("failed precompile must not return output", 0, result.getRight().length);
   }
@@
   public void rejectsInvalidFinalFlag() {
     byte[] input = VECTOR_4_INPUT.clone();
     input[212] = 0x02; // flag must be 0x00 or 0x01

     Pair<Boolean, byte[]> result = BLAKE2F.execute(input);
     assertFalse("invalid finalization flag must fail", result.getLeft());
+    assertEquals("failed precompile must not return output", 0, result.getRight().length);
     assertEquals("invalid flag must price to zero", 0L, BLAKE2F.getEnergyForData(input));
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public void rejectsWrongLength() {
// 212 bytes — one short of the required 213.
byte[] input = new byte[212];
Pair<Boolean, byte[]> result = BLAKE2F.execute(input);
assertFalse("incorrect length must fail", result.getLeft());
}
@Test
public void rejectsInvalidFinalFlag() {
byte[] input = VECTOR_4_INPUT.clone();
input[212] = 0x02; // flag must be 0x00 or 0x01
Pair<Boolean, byte[]> result = BLAKE2F.execute(input);
assertFalse("invalid finalization flag must fail", result.getLeft());
assertEquals("invalid flag must price to zero", 0L, BLAKE2F.getEnergyForData(input));
}
public void rejectsWrongLength() {
// 212 bytes — one short of the required 213.
byte[] input = new byte[212];
Pair<Boolean, byte[]> result = BLAKE2F.execute(input);
assertFalse("incorrect length must fail", result.getLeft());
assertEquals("failed precompile must not return output", 0, result.getRight().length);
}
`@Test`
public void rejectsInvalidFinalFlag() {
byte[] input = VECTOR_4_INPUT.clone();
input[212] = 0x02; // flag must be 0x00 or 0x01
Pair<Boolean, byte[]> result = BLAKE2F.execute(input);
assertFalse("invalid finalization flag must fail", result.getLeft());
assertEquals("failed precompile must not return output", 0, result.getRight().length);
assertEquals("invalid flag must price to zero", 0L, BLAKE2F.getEnergyForData(input));
}
🤖 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/runtime/vm/Blake2FTest.java` around
lines 74 - 89, For both rejectsWrongLength and rejectsInvalidFinalFlag, extend
the assertions to ensure that when BLAKE2F.execute(input) returns false the
output bytes are empty/stale-free: after calling BLAKE2F.execute(input) assert
result.getLeft() is false and assert that result.getRight() is either null or
contains only zero bytes (no non-zero/stale data); keep the existing energy
assertion in rejectsInvalidFinalFlag (BLAKE2F.getEnergyForData(input) == 0L).
This uses the existing BLAKE2F.execute and result.getRight() to validate no
output is produced on rejection.

Comment on lines +69 to +72
// The precompile left-pads the 21-byte TRON address into a 32-byte word.
byte[] expected = key.getAddress();
byte[] tail = Arrays.copyOfRange(result.getRight(), 32 - expected.length, 32);
assertArrayEquals("recovered address must match the signer", expected, tail);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Assert the full 32-byte return word.

This only checks the tail bytes, so a regression that returns the right address with non-zero left padding would still pass. Compare against the fully padded 32-byte expected word instead.

Proposed change
-    // The precompile left-pads the 21-byte TRON address into a 32-byte word.
-    byte[] expected = key.getAddress();
-    byte[] tail = Arrays.copyOfRange(result.getRight(), 32 - expected.length, 32);
-    assertArrayEquals("recovered address must match the signer", expected, tail);
+    // The precompile left-pads the 21-byte TRON address into a 32-byte word.
+    byte[] expected = new byte[32];
+    byte[] address = key.getAddress();
+    System.arraycopy(address, 0, expected, 32 - address.length, address.length);
+    assertArrayEquals("recovered address must match the signer", expected, result.getRight());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// The precompile left-pads the 21-byte TRON address into a 32-byte word.
byte[] expected = key.getAddress();
byte[] tail = Arrays.copyOfRange(result.getRight(), 32 - expected.length, 32);
assertArrayEquals("recovered address must match the signer", expected, tail);
// The precompile left-pads the 21-byte TRON address into a 32-byte word.
byte[] expected = new byte[32];
byte[] address = key.getAddress();
System.arraycopy(address, 0, expected, 32 - address.length, address.length);
assertArrayEquals("recovered address must match the signer", expected, result.getRight());
🤖 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/runtime/vm/ECRecoverTest.java` around
lines 69 - 72, The test currently only compares the trailing bytes by extracting
tail from result.getRight(), which misses verifying the left padding; replace
that check by constructing a full 32-byte expected word (left-pad
key.getAddress() with zeros to length 32) and assertArrayEquals that full
expected 32-byte array against result.getRight(); update/removing references to
the temporary tail variable and keep using result.getRight() as the actual
value.

Comment on lines +96 to +97
Pair<Boolean, byte[]> result = EC_RECOVER.execute(input);
assertEquals("non-zero v padding must yield empty output", 0, result.getRight().length);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Check the execution status in the polluted-v case.

This case currently passes on any empty output, even if execute() starts reporting failure. Add the same result.getLeft() assertion used by the other malformed-input tests so the regression baseline matches the intended contract.

Proposed change
     Pair<Boolean, byte[]> result = EC_RECOVER.execute(input);
+    assertTrue(result.getLeft());
     assertEquals("non-zero v padding must yield empty output", 0, result.getRight().length);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Pair<Boolean, byte[]> result = EC_RECOVER.execute(input);
assertEquals("non-zero v padding must yield empty output", 0, result.getRight().length);
Pair<Boolean, byte[]> result = EC_RECOVER.execute(input);
assertTrue(result.getLeft());
assertEquals("non-zero v padding must yield empty output", 0, result.getRight().length);
🤖 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/runtime/vm/ECRecoverTest.java` around
lines 96 - 97, The test currently only asserts that the output is empty for the
polluted-`v` case but omits verifying the execution status; update the
ECRecoverTest polluted-`v` case to assert the boolean execution result
(result.getLeft()) is false like the other malformed-input tests so the contract
is enforced when calling EC_RECOVER.execute(input); locate the Pair<Boolean,
byte[]> result variable and add the same result.getLeft() assertion used
elsewhere.

Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

No issues found across 3 files

Re-trigger cubic

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