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

89197 bc high missing zk leaf recording permanently freezes miner fee balances credited to keyless wormhole accounts

Submitted on Aug 17th 2026 at 19:04:32 UTC by @Pig46940 for Audit Comp | Quantus

  • Report ID: #89197

  • Report Type: Blockchain/DLT

  • Report severity: High

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

  • Impacts:

    • Permanent freezing of funds

Description

Brief/Intro

When Quantus settles a Wormhole private- or public-batch proof, it credits the block author's share of the volume fee directly to the author's keyless Wormhole account. The credit increases the miner's on-chain free balance and Quantus emits MinerVolumeFeePaid, but the settlement path does not insert the corresponding transfer leaf into the ZK tree. Because a Wormhole account has no ordinary signing key and can spend only by proving a recorded leaf, the credited miner-fee balance cannot be transferred or withdrawn. This happens during normal settlement and requires no malicious or privileged actor.

Vulnerability Details

Every balance credit to a keyless Wormhole account must have a corresponding ZK-tree leaf committing the recipient, transfer count, asset ID, and amount. Without that leaf, the account may have a positive on-chain balance but has no authorization path for spending it.

Quantus explicitly documents the required credit-and-record sequence

The normal user-exit path credits an exit account with the event-free Unbalanced::increase_balance primitive and then explicitly calls record_transfer. The developers' comment explains why both operations are required:

  • pallets/wormhole/src/lib.rs

  • pallets/wormhole/src/lib.rs

This is not an inferred requirement. Quantus deliberately uses an event-free credit and pairs it with an explicit recorder call. Using mint_into would cause the runtime extension to record the leaf from Balances::Minted; using increase_balance requires the pallet to record the leaf itself.

The miner-fee path performs only half of the required sequence

The miner-fee path uses the same event-free balance operation, but its success branch emits only MinerVolumeFeePaid:

  • pallets/wormhole/src/lib.rs

There is no record_transfer or record_transfer_proof call after the successful credit. Consequently:

  1. The account's free balance increases.

  2. Its TransferCount entry does not increase.

  3. No leaf representing the miner fee is inserted into pallet-zk-tree.

  4. Quantus nevertheless emits an event stating that the miner fee was paid.

The transaction extension cannot recover the omitted leaf

WormholeProofRecorderExtension scans events produced by an extrinsic and records only four balance-event variants:

  • runtime/src/transaction_extensions.rs

The miner fee cannot match any of these branches:

  • increase_balance is intentionally event-free, so it produces no Balances::Minted event.

  • The only success event is pallet_wormhole::Event::MinerVolumeFeePaid.

  • MinerVolumeFeePaid is not matched by the extension and therefore reaches _ => None.

  • The miner-fee code contains no explicit fallback recorder call.

The credited destination is necessarily a keyless Wormhole account

process_exit_bundle obtains the author through qp_wormhole::extract_author_from_digest, which derives the payout address by re-hashing the miner-supplied digest, the same keyless construction used throughout Wormhole. No signing key corresponds to it; the only spending mechanism is a Wormhole proof built from the secret and a recorded ZK-tree leaf.

A later, unrelated deposit cannot repair this credit: each leaf commits its own amount and transfer count, so a proof can only withdraw the value a leaf actually represents. The positive balance alone is not spendable.

Fee quantization must also be considered by the fix

The production configuration charges a four-basis-point volume fee and burns 50%, leaving 50% for the miner. ZK leaves encode amounts in quanta of 10_000_000_000 planck. Although the total fee is rounded to a complete quantum, splitting it can create a sub-quantum miner payment:

Therefore, merely adding record_transfer(miner_fee) is insufficient for every payment: a 5,000,000,000-planck leaf encodes an amount of zero. The remediation must both record miner-fee credits and preserve sub-quantum remainders, for example by accumulating them until a full leaf quantum is payable or by rounding before crediting while retaining the remainder for a later payment.

Impact Details

Selected impact: Critical — Permanent freezing of funds.

The affected value is not merely a prospective reward or an entry in an unclaimed-yield accumulator. Quantus has already:

  • incorporated the fee into the miner's ordinary free_balance through increase_balance;

  • emitted MinerVolumeFeePaid, representing the payment as completed; and

  • removed that amount from the fee settlement flow rather than retaining it for a future claim.

Nevertheless, the miner cannot use either available spending mechanism:

  • An ordinary signed transfer is impossible because the destination is a keyless Wormhole address, not the miner's signing account.

  • A Wormhole withdrawal is impossible because no leaf exists for the credited fee.

Thus, Quantus records the amount as an existing account balance while omitting the only authorization object capable of spending it. The entire successfully credited miner fee is frozen, not only the sub-quantum remainder. The loss accumulates whenever private- or public-batch settlement pays a nonzero fee to a block author.

There is no recovery action available to the affected miner. Restoring existing balances would require protocol-level intervention, such as a runtime migration that reconstructs valid leaves or moves the affected balances. The report does not claim that a hard fork is necessarily required; the permanent-freeze characterization refers to the absence of any holder-accessible recovery or spending path.

The finding does not depend on unmodified Substrate behavior, leaked credentials, governance participation, a malicious genesis block, or access to the trusted miner-to-node connection.

References

  • Developer comment documenting the event-free credit and mandatory explicit recorder pairing: https://github.com/immunefi-team/audit-comp-quantus-chain/blob/3b243b870a5442a6e5f443056f67bbba87d472f5/pallets/wormhole/src/lib.rs#L816-L859

  • Miner-fee credit without a recorder call: https://github.com/immunefi-team/audit-comp-quantus-chain/blob/3b243b870a5442a6e5f443056f67bbba87d472f5/pallets/wormhole/src/lib.rs#L937-L974

  • Exhaustive event match in WormholeProofRecorderExtension: https://github.com/immunefi-team/audit-comp-quantus-chain/blob/3b243b870a5442a6e5f443056f67bbba87d472f5/runtime/src/transaction_extensions.rs#L331-L395

  • record_transfer increments TransferCount and inserts the ZK-tree leaf: https://github.com/immunefi-team/audit-comp-quantus-chain/blob/3b243b870a5442a6e5f443056f67bbba87d472f5/pallets/wormhole/src/lib.rs#L1257-L1329

  • Wormhole author derivation from the QPoW digest: https://github.com/immunefi-team/audit-comp-quantus-chain/blob/3b243b870a5442a6e5f443056f67bbba87d472f5/primitives/wormhole/src/lib.rs#L97-L145

  • Production fee configuration: https://github.com/immunefi-team/audit-comp-quantus-chain/blob/3b243b870a5442a6e5f443056f67bbba87d472f5/runtime/src/configs/mod.rs#L728-L753

  • ZK-leaf amount quantization: https://github.com/immunefi-team/audit-comp-quantus-chain/blob/3b243b870a5442a6e5f443056f67bbba87d472f5/pallets/zk-tree/src/tree.rs#L99-L151

Proof of Concept

1

Insert the test

In pallets/wormhole/src/tests.rs, locate the end of test_verify_private_batch_cannot_replay. Insert the following function after that test's closing brace and immediately before the comment /// Regenerate the test fixture when circuit parameters change. This position is inside private_batch_proof_tests but outside every existing function.

2

Run the PoC

Run only this PoC from the repository root:

3

Observe the results

The independent test passes and establishes all parts of the broken transition:

  • Balances::free_balance(miner) increases by expected_miner_fee.

  • Quantus emits MinerVolumeFeePaid for the same account and amount.

  • TransferCount[miner] remains unchanged.

  • The ZK-tree leaf count increases only for ordinary user exits.

  • No additional leaf represents the miner-fee credit.

  • No Balances::Transfer or Balances::Minted event exists for the extension to scan.

This reproduces the complete broken state transition in the production settlement path: Quantus finalizes payment into a keyless Wormhole account while omitting the only proof material that can authorize spending that balance.

Was this helpful?