Skip to content

feat(crypto): add MLDSA / FN-DSA / SLH-DSA / Ephemeral secp256k1 post-quantum schemes#22

Open
Federico2014 wants to merge 18 commits intorelease_pqc_basefrom
feat/add-additional-pq-schemes
Open

feat(crypto): add MLDSA / FN-DSA / SLH-DSA / Ephemeral secp256k1 post-quantum schemes#22
Federico2014 wants to merge 18 commits intorelease_pqc_basefrom
feat/add-additional-pq-schemes

Conversation

@Federico2014
Copy link
Copy Markdown
Owner

@Federico2014 Federico2014 commented Apr 27, 2026

Summary

Builds on the ML-DSA base in release_pqc_base and adds:

  • Per-scheme governance: split ALLOW_ML_DSA into ALLOW_ML_DSA_44 / ALLOW_ML_DSA_65, plus new ALLOW_SLH_DSA, ALLOW_FN_DSA, ALLOW_EPHEMERAL_SECP256K1 proposal flags. Witness-side allowlist tightened so only {ML_DSA_44, ML_DSA_65, FN_DSA, SLH_DSA} are valid localwitness_seed_pq_scheme values.
  • SLH-DSA-SHA2-128s (FIPS 205) and FN-DSA / Falcon-512 signer/verifier wired into PqSignatureRegistry with the same fromSeed / verify API as ML-DSA.
  • Ephemeral secp256k1 scheme: 32-byte PQ Merkle root in Permission.Key.public_key, EphemeralWitness carried in AuthWitness.signature, per-account replay protection via ephemeral_used_bitmap (≤ 8 KiB) + last_ephemeral_nonce + Transaction.raw.nonce, with domain-separated digest TRON_EPHEMERAL_TX_AUTH_V1. Includes MerkleTree (SHA-256, depth ≤ 20) and EphemeralSecp256k1 (proof depth ≤ 16).
  • TVM precompiles: 0x12 VerifyMlDsa44 (4500 energy) and 0x14 VerifyMlDsa65 (7000 energy), independently gated by their per-scheme flags.
  • Witness PQ seed config validation: schemes outside the allowlist now fail fast at startup with a clear error.

Test plan

  • :framework:checkstyleMain clean
  • :actuator:compileJava :common:compileJava :framework:compileTestJava clean
  • New tests: MlDsaPrecompileTest, SignatureSchemeBenchmarkTest, EphemeralSecp256k1Test, ephemeral integration cases in TransactionCapsuleTest, replay-protection scenarios in chainbase
  • ArgsTest / WitnessInitializerTest still pass after the seed-scheme allowlist tightening
  • Full :framework:test run before merge

Summary by cubic

Adds new post‑quantum signature options (SLH‑DSA, FN‑DSA) and an Ephemeral secp256k1 auth path, plus ML‑DSA verify precompiles, all gated by per‑scheme governance flags. Introduces PQ auth witnesses for transactions/blocks and per‑account replay protection for the ephemeral scheme.

  • New Features

    • Per‑scheme governance: ALLOW_ML_DSA_44, ALLOW_ML_DSA_65, ALLOW_SLH_DSA, ALLOW_FN_DSA, ALLOW_EPHEMERAL_SECP256K1 with strict proposal validation and runtime gating.
    • PQ schemes wired via PqSignatureRegistry with fromSeed/verify for ML‑DSA‑44/65, SLH‑DSA‑SHA2‑128s, and FN‑DSA/Falcon‑512.
    • Ephemeral secp256k1: 32‑byte Merkle root in Permission.Key.public_key, AuthWitness carries EphemeralWitness, replay protection via ephemeral_used_bitmap/last_ephemeral_nonce and Transaction.raw.nonce (domain TRON_EPHEMERAL_TX_AUTH_V1).
    • TVM precompiles: 0x12 VerifyMlDsa44, 0x14 VerifyMlDsa65, each gated by its flag.
    • Witness PQ seed config: localwitness_seed_pq_scheme and localwitness_seed_pq with allowlist enforcement; unsupported schemes fail fast at startup.
  • Migration

    • Governance: activate the needed ALLOW_* flags before using each scheme.
    • Node config: set committee.allowMlDsa44/committee.allowMlDsa65/committee.allowSlhDsa/committee.allowFnDsa/committee.allowEphemeralSecp256k1; set localwitness_seed_pq_scheme and localwitness_seed_pq (use the correct seed length per scheme). PQ‑only witnesses must set localWitnessAccountAddress.
    • Accounts: update permissions to the intended SignatureScheme and expected public_key format; ephemeral accounts must store the Merkle root and include nonces when signing. ML‑DSA verify precompiles expect input [msg32 | signature | publicKey].

Written for commit d00d184. Summary will update on new commits. Review in cubic

Micro-benchmark comparing keygen/sign/verify latency for secp256k1
ECKey, ML-DSA-44 and ML-DSA-65, for tracking PQ signature integration
performance characteristics across releases.
Introduce two TVM precompiles for post-quantum signature verification,
gated on the existing ALLOW_ML_DSA proposal:
- 0x12 VerifyMlDsa44: ML-DSA-44 (FIPS-204, SHAKE256), 4500 energy.
  Compatible with EIP-8051 0x12 at the algorithm level; uses raw
  1312-byte public keys (BouncyCastle-native form) rather than the
  20512-byte expanded form, so wire bytes differ.
- 0x14 VerifyMlDsa65: ML-DSA-65 (TRON extension), 7000 energy.

Input layout for both: [msg 32B | signature | publicKey], output is a
32-byte word (1 on success, 0 otherwise). Wires VMConfig.allowMlDsa()
through ConfigLoader so the flag is loaded from the store on startup.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 27, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 987192af-6170-489f-be55-60d27c61fb78

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/add-additional-pq-schemes

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.

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.

8 issues found across 54 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="chainbase/src/main/java/org/tron/common/utils/LocalWitnesses.java">

<violation number="1" location="chainbase/src/main/java/org/tron/common/utils/LocalWitnesses.java:136">
P1: Seed length validation is hardcoded to `MLDSA65.SEED_LENGTH` (32 bytes) but FN-DSA and SLH-DSA require 48-byte seeds. A witness configured with `pqScheme = FN_DSA` or `SLH_DSA` will fail to start because its valid 96-hex-char seed gets rejected here. The validation should derive the expected length from the configured `pqScheme` (or accept a scheme parameter).</violation>
</file>

<file name="chainbase/src/main/java/org/tron/core/capsule/TransactionCapsule.java">

<violation number="1" location="chainbase/src/main/java/org/tron/core/capsule/TransactionCapsule.java:737">
P2: Redundant `computeRawHash(transaction)` call: the txid is already computed in `validateStructuredSignature` (line 737) and should be passed as a parameter to `ephemeralPreVerifyChecks` rather than recomputed. This adds an unnecessary SHA-256 hash per ephemeral witness on the transaction validation hot path.</violation>
</file>

<file name="common/src/main/java/org/tron/core/Constant.java">

<violation number="1" location="common/src/main/java/org/tron/core/Constant.java:64">
P2: The new PQ constants are currently unused and duplicate existing authoritative constants in crypto/capsule classes, creating a second source of truth.</violation>
</file>

<file name="actuator/src/main/java/org/tron/core/utils/ProposalUtil.java">

<violation number="1" location="actuator/src/main/java/org/tron/core/utils/ProposalUtil.java:889">
P1: Add fork-version gating for the new PQ proposal IDs; they currently bypass activation checks used by other recently added governance params.</violation>
</file>

<file name="framework/src/main/resources/config.conf">

<violation number="1" location="framework/src/main/resources/config.conf:778">
P2: The added committee config hint uses deprecated key `allowMlDsa`; update it to the current per-scheme keys to avoid operator misconfiguration.</violation>
</file>

<file name="framework/src/test/java/org/tron/core/BandwidthProcessorTest.java">

<violation number="1" location="framework/src/test/java/org/tron/core/BandwidthProcessorTest.java:934">
P2: Don't swallow unrelated exceptions here; the test can pass without exercising the create-account cap check.</violation>
</file>

<file name="actuator/src/main/java/org/tron/core/actuator/AccountPermissionUpdateActuator.java">

<violation number="1" location="actuator/src/main/java/org/tron/core/actuator/AccountPermissionUpdateActuator.java:312">
P2: `expectedPublicKeyLength` duplicates `PqSignatureRegistry.getPublicKeyLength`; use the registry as the single source of truth. If a new scheme is added to the registry but not to this switch, valid keys will be rejected. Replace the manual switch with a `PqSignatureRegistry.contains` + `getPublicKeyLength` call.

(Based on your team's feedback about treating the registry as the single source of truth for allowed schemes.) [FEEDBACK_USED]</violation>
</file>

<file name="framework/src/test/java/org/tron/core/capsule/BlockCapsulePqTest.java">

<violation number="1" location="framework/src/test/java/org/tron/core/capsule/BlockCapsulePqTest.java:26">
P2: This stateful test class should not extend `BaseTest`; use `BaseMethodTest` so each test gets an isolated context.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

if (StringUtils.startsWith(hex, "0x")) {
hex = hex.substring(2);
}
int expectedHexLen = MLDSA65.SEED_LENGTH * 2;
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 27, 2026

Choose a reason for hiding this comment

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

P1: Seed length validation is hardcoded to MLDSA65.SEED_LENGTH (32 bytes) but FN-DSA and SLH-DSA require 48-byte seeds. A witness configured with pqScheme = FN_DSA or SLH_DSA will fail to start because its valid 96-hex-char seed gets rejected here. The validation should derive the expected length from the configured pqScheme (or accept a scheme parameter).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At chainbase/src/main/java/org/tron/common/utils/LocalWitnesses.java, line 136:

<comment>Seed length validation is hardcoded to `MLDSA65.SEED_LENGTH` (32 bytes) but FN-DSA and SLH-DSA require 48-byte seeds. A witness configured with `pqScheme = FN_DSA` or `SLH_DSA` will fail to start because its valid 96-hex-char seed gets rejected here. The validation should derive the expected length from the configured `pqScheme` (or accept a scheme parameter).</comment>

<file context>
@@ -95,6 +116,35 @@ public void addPrivateKeys(String privateKey) {
+    if (StringUtils.startsWith(hex, "0x")) {
+      hex = hex.substring(2);
+    }
+    int expectedHexLen = MLDSA65.SEED_LENGTH * 2;
+    if (StringUtils.isBlank(hex) || hex.length() != expectedHexLen) {
+      throw new TronError(String.format("ML-DSA seed must be %d hex chars, actual: %d",
</file context>
Fix with Cubic

}
break;
}
case ALLOW_ML_DSA_44: {
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 27, 2026

Choose a reason for hiding this comment

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

P1: Add fork-version gating for the new PQ proposal IDs; they currently bypass activation checks used by other recently added governance params.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At actuator/src/main/java/org/tron/core/utils/ProposalUtil.java, line 889:

<comment>Add fork-version gating for the new PQ proposal IDs; they currently bypass activation checks used by other recently added governance params.</comment>

<file context>
@@ -886,6 +886,41 @@ public static void validator(DynamicPropertiesStore dynamicPropertiesStore,
         }
         break;
       }
+      case ALLOW_ML_DSA_44: {
+        if (value != 0 && value != 1) {
+          throw new ContractValidateException(
</file context>
Fix with Cubic

"permission uses legacy scheme, auth_witness is not allowed");
}

byte[] txid = computeRawHash(transaction).getBytes();
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 27, 2026

Choose a reason for hiding this comment

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

P2: Redundant computeRawHash(transaction) call: the txid is already computed in validateStructuredSignature (line 737) and should be passed as a parameter to ephemeralPreVerifyChecks rather than recomputed. This adds an unnecessary SHA-256 hash per ephemeral witness on the transaction validation hot path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At chainbase/src/main/java/org/tron/core/capsule/TransactionCapsule.java, line 737:

<comment>Redundant `computeRawHash(transaction)` call: the txid is already computed in `validateStructuredSignature` (line 737) and should be passed as a parameter to `ephemeralPreVerifyChecks` rather than recomputed. This adds an unnecessary SHA-256 hash per ephemeral witness on the transaction validation hot path.</comment>

<file context>
@@ -662,6 +704,183 @@ public boolean validatePubSignature(AccountStore accountStore,
+          "permission uses legacy scheme, auth_witness is not allowed");
+    }
+
+    byte[] txid = computeRawHash(transaction).getBytes();
+    List<AuthWitness> witnesses = transaction.getAuthWitnessList();
+    java.util.Set<ByteString> seen = new java.util.HashSet<>();
</file context>
Fix with Cubic

public static final String ECKey_ENGINE = "ECKey";

// Post-quantum (ML-DSA / FIPS 204) signature constants
public static final int ML_DSA_44_PUBLIC_KEY_LENGTH = 1312;
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 27, 2026

Choose a reason for hiding this comment

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

P2: The new PQ constants are currently unused and duplicate existing authoritative constants in crypto/capsule classes, creating a second source of truth.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At common/src/main/java/org/tron/core/Constant.java, line 64:

<comment>The new PQ constants are currently unused and duplicate existing authoritative constants in crypto/capsule classes, creating a second source of truth.</comment>

<file context>
@@ -60,6 +60,25 @@ public class Constant {
   public static final String ECKey_ENGINE = "ECKey";
 
+  // Post-quantum (ML-DSA / FIPS 204) signature constants
+  public static final int ML_DSA_44_PUBLIC_KEY_LENGTH = 1312;
+  public static final int ML_DSA_44_SIGNATURE_LENGTH = 2420;
+  public static final int ML_DSA_65_PUBLIC_KEY_LENGTH = 1952;
</file context>
Fix with Cubic

# consensusLogicOptimization = 0
# allowOptimizedReturnValueOfChainId = 0
# allowTvmOsaka = 0
# allowMlDsa = 0
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 27, 2026

Choose a reason for hiding this comment

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

P2: The added committee config hint uses deprecated key allowMlDsa; update it to the current per-scheme keys to avoid operator misconfiguration.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At framework/src/main/resources/config.conf, line 778:

<comment>The added committee config hint uses deprecated key `allowMlDsa`; update it to the current per-scheme keys to avoid operator misconfiguration.</comment>

<file context>
@@ -760,6 +775,7 @@ committee = {
   # consensusLogicOptimization = 0
   # allowOptimizedReturnValueOfChainId = 0
   # allowTvmOsaka = 0
+  # allowMlDsa = 0
 }
 
</file context>
Fix with Cubic

processor.consume(trx, trace);
} catch (TooBigTransactionException e) {
Assert.fail("PQ auth_witness bytes should be deducted from create-account cap check");
} catch (AccountResourceInsufficientException
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 27, 2026

Choose a reason for hiding this comment

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

P2: Don't swallow unrelated exceptions here; the test can pass without exercising the create-account cap check.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At framework/src/test/java/org/tron/core/BandwidthProcessorTest.java, line 934:

<comment>Don't swallow unrelated exceptions here; the test can pass without exercising the create-account cap check.</comment>

<file context>
@@ -881,4 +881,122 @@ public void testCalculateGlobalNetLimit() {
+      processor.consume(trx, trace);
+    } catch (TooBigTransactionException e) {
+      Assert.fail("PQ auth_witness bytes should be deducted from create-account cap check");
+    } catch (AccountResourceInsufficientException
+        | ContractValidateException
+        | TooBigTransactionResultException e) {
</file context>
Fix with Cubic

}
}

private static int expectedPublicKeyLength(SignatureScheme scheme) {
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 27, 2026

Choose a reason for hiding this comment

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

P2: expectedPublicKeyLength duplicates PqSignatureRegistry.getPublicKeyLength; use the registry as the single source of truth. If a new scheme is added to the registry but not to this switch, valid keys will be rejected. Replace the manual switch with a PqSignatureRegistry.contains + getPublicKeyLength call.

(Based on your team's feedback about treating the registry as the single source of truth for allowed schemes.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At actuator/src/main/java/org/tron/core/actuator/AccountPermissionUpdateActuator.java, line 312:

<comment>`expectedPublicKeyLength` duplicates `PqSignatureRegistry.getPublicKeyLength`; use the registry as the single source of truth. If a new scheme is added to the registry but not to this switch, valid keys will be rejected. Replace the manual switch with a `PqSignatureRegistry.contains` + `getPublicKeyLength` call.

(Based on your team's feedback about treating the registry as the single source of truth for allowed schemes.) </comment>

<file context>
@@ -237,4 +261,68 @@ public ByteString getOwnerAddress() throws InvalidProtocolBufferException {
+    }
+  }
+
+  private static int expectedPublicKeyLength(SignatureScheme scheme) {
+    switch (scheme) {
+      case ML_DSA_44:
</file context>
Fix with Cubic

@@ -0,0 +1,290 @@
package org.tron.core.capsule;
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 27, 2026

Choose a reason for hiding this comment

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

P2: This stateful test class should not extend BaseTest; use BaseMethodTest so each test gets an isolated context.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At framework/src/test/java/org/tron/core/capsule/BlockCapsulePqTest.java, line 26:

<comment>This stateful test class should not extend `BaseTest`; use `BaseMethodTest` so each test gets an isolated context.</comment>

<file context>
@@ -0,0 +1,290 @@
+import org.tron.protos.Protocol.Permission.PermissionType;
+import org.tron.protos.Protocol.SignatureScheme;
+
+public class BlockCapsulePqTest extends BaseTest {
+
+  private ECKey witnessKey;
</file context>
Fix with Cubic

@Federico2014 Federico2014 changed the title feat(crypto): add SLH-DSA / FN-DSA / Ephemeral secp256k1 schemes + ML-DSA precompiles feat(crypto): add MLDSA / FN-DSA / SLH-DSA / Ephemeral secp256k1 post-quantum schemes Apr 27, 2026
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