M-05: Account Authentication Does Not Bound the Deducted Transaction Fee
- Dominant language
- Rust
- Stars
- 132
- Forks
- 167
- Avg merge
- 1d 23h
- Merged PRs (30d)
- 110
Description
`SingleSig` authentication signs the commitment of a transaction summary built by `create_tx_summary`, covering `ACCOUNT_DELTA_COMMITMENT`, `INPUT_NOTES_COMMITMENT`, `OUTPUT_NOTES_COMMITMENT`, and a `SALT` of `[0, 0, ref_block_num, final_nonce]`. The reference block number is deliberately included so that the signer commits to the fee parameters of its intended reference block, which the documentation in `authenticate_transaction` describes as determining "the fee amount that is deducted."
However, the deducted fee is not fully determined by those parameters. The kernel computes it as `verification_base_fee` multiplied by `ilog2(num_tx_cycles) + 1` in `compute_fee`, where the cycle count is an input that the signed summary does not constrain. The transaction script that drives the cycle count is supplied by the prover from the advice stack in `process_tx_script_data` and is absent from the summary. The fee is computed from `clk` only after the account delta commitment has been finalized, and `compute_and_remove_fee` removes the fee asset from the vault without affecting the delta, since the code treats modifications at that point as "essentially ignored." As a result, a party that re-proves the transaction in a delegated-proving or relaying setting can reuse the existing signature while substituting a script that preserves the delta and note commitments but executes additional cycles, increasing the fee charged to the signer's account.
This belongs to a broader class of issues in which the native account is charged a transaction fee that its owner never authorized or bounded, because the fee is applied unconditionally once the authentication procedure completes and is never part of the signed message. The same exposure appears more directly in components that expose a permissionless completion path, such as `auth_tx_acl`, where a transaction that consumes a note or makes a non-trigger state change can complete with no signature as long as note usage stays within the `allow_unauthorized` configuration. In that case an attacker needs neither a reused signature nor cycle padding to make the account pay a fee. Across these cases the practical effect is the same: slow balance erosion through repeated unauthorized fee payments, rather than direct theft, since the fee accrues to the block producer and not to the attacker.
The amplification available to a re-prover is constrained by the fee's logarithmic dependence on the cycle count. The prover's compute grows linearly with the number of padded cycles, while the resulting fee grows only as `ilog2` of that count, so each additional unit of fee charged to the victim requires roughly doubling the attacker's proving work. This holds even when the attacker is the block producer that collects the fee, since the proving cost of the padded transaction is borne by the attacker, making amplification beyond the baseline fee economically self-defeating.
Consider binding the fee to what the account authorizes, for example by including an explicit maximum fee amount (or fee faucet and amount) in the transaction summary and enforcing it during fee computation, or by committing to the transaction script root. For permissionless completion paths, consider drawing the fee against a value the account can constrain rather than allowing any completing transaction to deduct it unconditionally.
Current state of the issue:
- Fee deduction mechanism: completely removed from the kernel #3108 , the unconditional fee-deduction behavior the finding relies on no longer exists.
- SingleSig cycle-padding: no longer possible, because the kernel deducts no fee at all.
- `auth_tx_acl` allow_unauthorized: fixed since switched to exempt-list (default-deny) semantics #3065.
So, the above are fixed. The below issue should be considered while designing the fee mechanism.
- the signature does not bound the fee: It's an open issue, before fees are reintroduced, `max_fee` must be bound into the tx summary and the tx script root must be committed to.
Contributor guide
Assessment
This issue has not been assessed yet.