For the complete documentation index, see llms.txt. This page is also available as Markdown.

88529 bc medium incorrect base extrinsic weight for ml dsa 87 undercharges transaction verification by 70

Submitted on Aug 13th 2026 at 16:31:32 UTC by @Pig46940 for Audit Comp | Quantus

  • Report ID: #88529

  • Report Type: Blockchain/DLT

  • Report severity: Medium

  • Target: https://github.com/immunefi-team/audit-comp-quantus-chain

  • Impacts:

    • Inaccuracies to the weight (similar to gas) accounting system that under or overcharge by more than 50% of the correct amount (and are not intentional as indicated by the program's terms)

Description

Brief/Intro

Quantus uses ML-DSA-87 signatures for ordinary signed extrinsics, but its runtime still uses an ExtrinsicBaseWeight generated by the generic Substrate Development runtime. As a result, Quantus accounts only 108,157 ns of base work per extrinsic even though ML-DSA-87 signature verification alone took an average of 394,756 ns in a release-mode test. This is a ~70% under-accounting of the measured verification cost and allows substantially more CPU work into a block than the configured weight limit represents.

Vulnerability Details

Quantus configures its block weights with BlockWeights::with_sensible_defaults:

  • runtime/src/configs/mod.rs

pub RuntimeBlockWeights: BlockWeights = BlockWeights::with_sensible_defaults(
	Weight::from_parts(6u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX),
	NORMAL_DISPATCH_RATIO,
);

with_sensible_defaults does not benchmark or derive a Quantus-specific base-extrinsic weight. It calls BlockWeights::builder(), whose default for every non-mandatory dispatch class is the framework constant constants::ExtrinsicBaseWeight:

  • pallets/frame-system/src/limits.rs

The metadata in the active ExtrinsicBaseWeight file shows that the value was generated by substrate-node benchmark overhead for the Substrate Development runtime:

  • frame/support/src/weights/extrinsic_weights.rs

That benchmark does not represent the signature path used by Quantus. The Quantus runtime defines its extrinsics with the runtime Signature type, and FRAME Executive verifies the signature before dispatch:

  • runtime/src/lib.rs

  • runtime/src/lib.rs

For an ML-DSA-87 signature, this reaches Quantus's verify_ml_dsa_87 implementation:

  • primitives/dilithium-crypto/src/traits.rs

There is no separate weight charged for this verification. The inherited base-extrinsic value is added to the extrinsic's recorded weight, and it is also used to calculate the base transaction fee.

The repository already contains the machinery needed to generate the correct value. Its benchmark overhead command uses Quantus's RemarkBuilder, which constructs and signs a real Signature::Dilithium87 extrinsic. However, the committed runtime configuration does not use output generated by that builder; it continues to inherit the generic Substrate value.

The root cause is therefore not a defect in upstream Substrate. It is a Quantus runtime configuration error: Quantus changed the transaction signature scheme to ML-DSA-87 without replacing the base-extrinsic benchmark inherited from the generic Substrate runtime.

Impact Details

Selected the following in-scope impact:

Inaccuracies to the weight (similar to gas) accounting system that under or overcharge by more than 50% of the correct amount.

Release-mode PoC in my environment produced the following result:

The percentage under-accounting, measured against the actual verification cost, is:

This is a conservative comparison. The measured 394,756 ns is only the ML-DSA-87 verification operation, whereas the configured 108,157 ns base weight is expected to cover the complete baseline cost of processing an extrinsic, including decoding, signed-extension processing, and signature verification.

Because the base weight is charged to every signed extrinsic, a user can trigger the discrepancy with ordinary valid transactions. No miner privileges, node credentials, leaked keys, malicious genesis state, governance action, or access to the trusted miner-to-node network is required. A sustained stream of valid signed transactions therefore makes nodes perform materially more computation than the block-weight accounting records. It also makes that computation cheaper than intended because the same base weight feeds into the base transaction fee.

References

Proof of Concept

The following test reads the base-extrinsic weight from the actual Quantus runtime configuration and compares it with the average time required to verify an ML-DSA-87 signature. It deliberately measures signature verification alone, making the comparison conservative.

1

Add the test module

Add this line to runtime/tests/transactions/mod.rs:

2

Add the PoC test

Create runtime/tests/transactions/ml_dsa_weight_poc.rs with the following content:

3

Run the PoC

From the repository root at commit 3b243b870a5442a6e5f443056f67bbba87d472f5, run:

4

Observe the result

On my test system, the test passed with:

The assertion requires the measured ML-DSA-87 verification time to exceed twice the configured value. This directly tests the program's greater-than-50% threshold because, for actual cost A and charged cost C:

Here, 394,756 > 2 * 108,157, so the assertion succeeds.

Was this helpful?