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