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

89438 bc critical miner controlled future timestamps bypass reversible transfer cancellation windows and cause direct loss of funds

Submitted on Aug 19th 2026 at 11:01:31 UTC by @Pig46940 for Audit Comp | Quantus

  • Report ID: #89438

  • Report Type: Blockchain/DLT

  • Report severity: Critical

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

  • Impacts:

    • Direct loss of funds

Description

Brief/Intro

Quantus allows a reversible transfer to use a timestamp delay as short as 12 seconds, but its timestamp inherent accepts a block timestamp almost 30 seconds ahead of the importing node's clock. The timestamp-aware Scheduler processes pending transfers during on_initialize, before the current block's inherent and signed extrinsics. A miner can therefore publish an accepted future timestamp that makes a pending transfer eligible in the following block. Even if the sender's cancellation is included well before the promised deadline, Scheduler executes first, pays the recipient, removes the pending transfer, and causes the cancellation to fail with PendingTxNotFound.

Vulnerability Details

For the entire delay selected by a sender, a reversible transfer must remain pending and cancellable. A block producer acting within the timestamp range accepted by consensus must not be able to shorten that window.

This is also the behavior described in the Quantus documentation: funds remain in escrow during the delay window, the sender can cancel during that window, and the transfer executes only after the delay expires(https://docs.quantus.com/deep-dives/safety/).

Quantus permits a 12-second timestamp delay

The target block time is 12,000 milliseconds:

The runtime uses that value as the minimum timestamp-based reversible-transfer delay. It also configures a 24-second scheduler bucket and a timestamp minimum period of only 100 milliseconds:

schedule_transfer_with_delay accepts either a block-number delay or a millisecond delay. Timestamp delays are accepted when they are at least MinDelayPeriodMoment:

The timestamp target is calculated from the current on-chain timestamp:

Consensus accepts timestamps up to 30 seconds in the future

The Timestamp pallet accepts a claimed timestamp as long as it is not more than 30 seconds ahead of the importing node's inherent timestamp:

This check is part of the production QPoW import path. import_block calls check_inherents, which is gated by a block-height threshold before it validates anything:

The node sets check_inherents_after to 0 (node/src/service.rs) and supplies the node's real system clock as the inherent data via from_system_time(), so the gate above is never taken and the comparison is always against a genuine wall-clock reading.

Therefore, the PoC is exercising the same timestamp acceptance rule used when a node imports a production block.

Scheduler executes before cancellation extrinsics

Timestamp agendas are processed in Scheduler's on_initialize using T::TimeProvider::now(). At this point, the current block's timestamp inherent has not executed, so Scheduler sees the timestamp inherited from the preceding block:

Because pallet hooks execute before block extrinsics, a cancellation included in that block cannot race Scheduler. Once Scheduler has executed the transfer, PendingTransfers has been removed and the cancellation fails with PendingTxNotFound.

Concrete attack sequence

The PoC uses an initial timestamp of 35,000 ms because it makes the bucket transition explicit:

1

In block 1, schedule the transfer

The sender schedules a transfer with the production minimum delay of 12,000 ms. The timestamp target is therefore 47,000 ms.

2

Quantus places the call in a timestamp agenda

Quantus places the call in timestamp agenda 72,000. Due to bucket normalization, a preceding timestamp of 65,000 normalizes to 72,000 and causes that agenda to be serviced.

3

In block 2, claim a consensus-accepted future timestamp

An attacker who wins a QPoW block claims timestamp 65,000 while the importing node's simulated wall clock is 35,100. The timestamp is only 29,900 ms ahead and passes check_inherent.

4

In block 3, Scheduler executes the pending transfer

Only another 100 ms of simulated wall time has passed. Scheduler's on_initialize sees the preceding timestamp of 65,000 and executes the pending transfer before any block-3 extrinsic.

5

The current block timestamp passes validation

The current block timestamp also passes inherent validation because the runtime permits 100 ms timestamp increments.

6

The sender's cancellation fails

The sender's cancellation is dispatched in block 3, only 200 ms after scheduling and well inside the selected 12-second window. It fails with PendingTxNotFound because the recipient has already been paid.

The control branch uses the same accounts, amount, delay, blocks, and cancellation. Its only difference is that timestamps advance with the simulated wall clock. In that branch, cancellation succeeds and the recipient receives nothing.

The attack is timing- and bucket-alignment-dependent, but it is directly reachable. A malicious recipient or cooperating miner can target pending transfers whose timestamp phase is covered by the permitted drift. The attack requires winning a QPoW block, not majority hash power, a privileged address, leaked credentials, or a malicious genesis block. The following block may be produced by any miner; Scheduler deterministically executes before its signed extrinsics.

Root cause and scope

The root cause is not the existence of a conventional timestamp-drift allowance by itself. The vulnerability is created by Quantus's original integration of:

  • timestamp-based reversible transfers;

  • a timestamp-aware Scheduler;

  • a minimum security delay of 12 seconds;

  • a 24-second timestamp bucket; and

  • a consensus-permitted future timestamp drift of 30 seconds.

Quantus treats a miner-influenced timestamp as the clock for a security-critical cancellation guarantee, while allowing the promised minimum window to be shorter than the accepted manipulation range. The vulnerable feature and the unsafe runtime coupling are Quantus-specific.

Impact Details

The selected impact is Critical: Direct loss of funds.

This is not merely an earlier delivery of funds that would necessarily have occurred anyway. The PoC includes the sender's cancellation inside the selected delay and establishes two different final ownership outcomes:

The sender's authorization is conditional and explicitly revocable during the configured window. In the attack branch, the sender exercises that right before the deadline, but the timestamp manipulation changes the final owner of the funds. The recipient receives value that the control execution returns to the sender.

A practical scenario does not require compromised keys. A user may schedule a reversible payment to a counterparty and then detect fraud, an incorrect amount, or another condition requiring cancellation. If that counterparty operates or cooperates with a miner, the counterparty can exploit the timestamp window so that a following block executes the payment before the timely cancellation extrinsic.

This report does not claim permanent freezing, an unauthorized replacement of the destination, or a network shutdown. The concrete impact is loss of the sender's funds through bypass of the protocol-enforced cancellation right.

The safest remediation is to use block-number delays exclusively for reversible-transfer and high-security cancellation windows.

If timestamp delays remain supported, the execution target must account for the maximum future drift. For example, a requested cancellation duration delay should not become eligible before:

The drift should be a shared configurable value rather than an unrelated constant hidden inside timestamp inherent validation. Add an integration test in which the timestamp is advanced to the maximum consensus-accepted value and assert that a minimum-delay transfer remains cancellable until the complete promised wall-clock interval has elapsed.

References

  • Quantus documentation, User Safety Features: https://docs.quantus.com/deep-dives/safety/

  • Runtime target time, timestamp bucket, and minimum period: https://github.com/immunefi-team/audit-comp-quantus-chain/blob/3b243b870a5442a6e5f443056f67bbba87d472f5/runtime/src/configs/mod.rs#L150-L183

  • Runtime reversible-transfer configuration: https://github.com/immunefi-team/audit-comp-quantus-chain/blob/3b243b870a5442a6e5f443056f67bbba87d472f5/runtime/src/configs/mod.rs#L524-L551

  • Timestamp future-drift validation: https://github.com/immunefi-team/audit-comp-quantus-chain/blob/3b243b870a5442a6e5f443056f67bbba87d472f5/pallets/timestamp/src/lib.rs#L285-L327

  • Custom-delay entry point and delay validation: https://github.com/immunefi-team/audit-comp-quantus-chain/blob/3b243b870a5442a6e5f443056f67bbba87d472f5/pallets/reversible-transfers/src/lib.rs#L459-L485

  • Timestamp scheduling target: https://github.com/immunefi-team/audit-comp-quantus-chain/blob/3b243b870a5442a6e5f443056f67bbba87d472f5/pallets/reversible-transfers/src/lib.rs#L768-L805

  • Timestamp Scheduler hook and bucket processing: https://github.com/immunefi-team/audit-comp-quantus-chain/blob/3b243b870a5442a6e5f443056f67bbba87d472f5/pallets/scheduler/src/lib.rs#L377-L422

  • Scheduler timestamp resolution: https://github.com/immunefi-team/audit-comp-quantus-chain/blob/3b243b870a5442a6e5f443056f67bbba87d472f5/pallets/scheduler/src/lib.rs#L668-L715

  • QPoW inherent validation during block import: https://github.com/immunefi-team/audit-comp-quantus-chain/blob/3b243b870a5442a6e5f443056f67bbba87d472f5/client/consensus/qpow/src/lib.rs#L168-L198

  • Import path invocation: https://github.com/immunefi-team/audit-comp-quantus-chain/blob/3b243b870a5442a6e5f443056f67bbba87d472f5/client/consensus/qpow/src/lib.rs#L247-L264

  • Node timestamp provider and block-zero configuration: https://github.com/immunefi-team/audit-comp-quantus-chain/blob/3b243b870a5442a6e5f443056f67bbba87d472f5/node/src/service.rs#L639-L656

  • Runtime inherent API: https://github.com/immunefi-team/audit-comp-quantus-chain/blob/3b243b870a5442a6e5f443056f67bbba87d472f5/runtime/src/apis.rs#L91-L100

Proof of Concept

The PoC is a repository-native runtime integration test. It executes a control branch and an attack branch against the production Quantus runtime configuration.

1

Add the test-only timestamp dependency

Add this line under [dev-dependencies] in runtime/Cargo.toml:

2

Add the integration test

Add the following imports to runtime/tests/transactions/reversible_integration.rs:

Append the following code at module scope at the end of that file:

3

Run the test

Actual result

The passing assertions establish that the control recipient gains zero while the attack recipient gains the full transfer amount, and that both future timestamp values pass the production Timestamp pallet's inherent validation.

Was this helpful?