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

88830 bc low missing constraint private batch allows real outputs with canonical zero exit account

Submitted on Aug 15th 2026 at 05:18:28 UTC by @bugtester for Audit Comp | Quantus

  • Report ID: #88830

  • Report Type: Blockchain/DLT

  • Report severity: Low

  • Target: https://github.com/immunefi-team/audit-comp-quantus-qp-zk-circuits

  • Impacts:

    • Modification of transaction fees outside of design parameters

Description

Brief/Intro

The private-batch aggregation circuit does not enforce that a real output with a non-zero amount must have a non-zero exit account.

The leaf-level circuit exposes the exit accounts as public inputs without constraining them to be non-zero. The private-batch circuit distinguishes dummy proofs using the block-hash sentinel and only masks exits to zero for those dummy proofs. For a real proof, the exit account is forwarded unchanged.

As a result, the circuit accepts the following state:

real proof
output_amount > 0
exit_account = [0, 0, 0, 0]

The private-batch aggregation logic then treats the zero exit account as a normal grouping key, aggregates its associated amount, and places the resulting (amount, zero-exit) pair into the aggregated public inputs.

This breaks the invariant that real positive-value exits must have a valid destination account.

Vulnerability Details

https://github.com/immunefi-team/audit-comp-quantus-qp-zk-circuits/blob/2a6914ed0184fbc0aaf621801ac8b8b7e3b01f6e/wormhole/aggregator/src/private_batch/circuit/circuit_logic.rs#L329-L376

The private-batch circuit first determines whether a child proof is a dummy based on the block-hash sentinel.

For each output slot, dummy proofs are masked to:

For a real proof, is_dummy_i is false and the original exit_raw value is returned unchanged.

The subsequent aggregation logic compares exit accounts, groups matching exits, sums their amounts, and emits the resulting exit and amount.

There is no constraint enforcing:

Therefore, the aggregation circuit does not prevent a real positive-value output from using the same canonical zero value that is otherwise used for dummy/unused exits.

Impact Details

The missing constraint allows a real, positive-value exit to be represented with the canonical zero exit account.

The private-batch circuit uses the zero exit account as the canonical representation for dummy/unused outputs, but does not enforce that the same value cannot be used by a real output carrying a non-zero amount.

A real leaf can contain:

The private-batch logic does not reject this combination. Because real slots are forwarded without validating the exit account, the positive amount enters the aggregation logic under the zero exit key and is exposed in the aggregated public inputs as:

This breaks the expected invariant that every real positive-value exit has a valid destination account.

The ZK circuit therefore accepts and proves an invalid settlement state: a positive-value output is associated with the canonical zero destination.

If the downstream settlement layer processes the aggregated output without independently rejecting the zero exit account, the associated value can be sent to the zero/ownerless destination, resulting in permanent loss or misdirection of funds.

Even where downstream validation rejects the zero destination, the circuit still permits an invalid positive-value zero-exit state to become part of a valid aggregated proof, allowing malformed settlement batches to reach the settlement layer and potentially cause settlement failure or batch-level disruption.

Proof of Concept

The issue can be reproduced by constructing a real leaf proof with a non-zero output amount and a zero exit account, then passing that proof through the private-batch aggregation circuit.

The relevant code paths are:

Add a circuit constraint requiring every real positive-value output to have a non-zero exit account:

For example:

This ensures that:

cannot satisfy the circuit.

Was this helpful?