Skip to content

fix(wallet): reject txs exceeding MAX_STANDARD_TX_WEIGHT - #544

Open
cestercian wants to merge 1 commit into
bitcoindevkit:masterfrom
cestercian:cursor/fix-max-standard-tx-weight-check-93bc
Open

cestercian wants to merge 1 commit into
bitcoindevkit:masterfrom
cestercian:cursor/fix-max-standard-tx-weight-check-93bc

Conversation

@cestercian

Copy link
Copy Markdown

Description

Neither create_tx nor create_psbt checked the assembled transaction against Bitcoin's standardness weight limit (bitcoin::policy::MAX_STANDARD_TX_WEIGHT, 400_000 WU). Dust was already rejected (OutputBelowDustLimit); weight was not.

A drain of many small UTXOs could therefore produce a fully signed PSBT over 400k WU. Every standardness-enforcing mempool rejects that transaction, but sign still returned Ok(true) — a misleading success.

Root cause: After coin selection the unsigned transaction is assembled with empty witnesses. Transaction::weight() on that value undercounts the final signed size by each input's satisfaction (witness / scriptSig) weight. No later check compared the estimated signed weight to the standardness limit.

Fix: After the unsigned tx is assembled, estimate the signed weight (tx.weight() plus each input's satisfaction weight, plus the 2-WU segwit marker when witnesses will be present) and reject when it exceeds MAX_STANDARD_TX_WEIGHT.

  • New CreateTxError::TxWeightLimitExceeded { weight, limit } (stable create_tx path)
  • New CreatePsbtError::TxWeightLimitExceeded { weight, limit } (unstable create_psbt / create_psbt_from_selector path, so RBF is covered too)

Tests:

  • Drain of 1,500 small P2WPKH UTXOs (the reported case) via a single in-memory funding transaction — no Electrum/Esplora/RPC
  • Unit-level foreign UTXO whose satisfaction weight alone exceeds the limit
  • Matching create_psbt drain regression

Fixes #543

Notes to the reviewers

  • This is a breaking change: CreateTxError is exhaustive, so downstream matches must handle the new variant. CreatePsbtError is already #[non_exhaustive].
  • The check uses estimated signed weight, not raw unsigned tx.weight(). Checking only the unsigned weight would miss the 1,500-P2WPKH-input case (~246k WU unsigned vs ~408k WU signed).
  • Satisfaction weights for create_tx come from the WeightedUtxos used in coin selection (including foreign UTXOs). The create_psbt path looks up local descriptors via max_weight_to_satisfy.

Changelog notice

  • Fixed create_tx / create_psbt producing unrelayable transactions over MAX_STANDARD_TX_WEIGHT by returning TxWeightLimitExceeded.

Before submitting

  • I followed the contribution guidelines
  • This PR breaks the existing API

Assistance: implementation drafted with Cursor (cloud agent); author is Cestercian. Commits are SSH-signed; GitHub may show Unverified if the cloud-agent SSH signing key is not registered on the account as a signing key.

create_tx and create_psbt assembled transactions without checking
bitcoin::policy::MAX_STANDARD_TX_WEIGHT (400_000 WU). A drain of many
small UTXOs could therefore produce a fully signed PSBT that every
standard mempool rejects, while sign still returned Ok(true).

After the unsigned tx is assembled, estimate the signed weight
(unsigned weight plus each input's satisfaction weight) and return
CreateTxError::TxWeightLimitExceeded / CreatePsbtError::TxWeightLimitExceeded
when it exceeds the limit.

Fixes bitcoindevkit#543
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Missing MAX_STANDARD_TX_WEIGHT check produces unrelayable txs

1 participant