BIP: ? Layer: Applications Title: Worst-Case Taproot Witness Weight Estimation Author: Jan Thomasewsky Status: Draft Type: Standards Track Created: 2026-04-11 License: CC-BY-4.0 Requires: 341, 380, 386 Abstract This document specifies how wallet implementations should estimate the witness weight of inputs spending BIP 341 Taproot outputs when constructing transactions. Existing implementations assume key path spending for all tr() inputs, which underestimates the weight when a script path spend is required and causes the transaction to broadcast at a lower effective fee rate than the user intended. Motivation When constructing a transaction a wallet must estimate the witness weight of each input to calculate the correct fee. For a tr(KEY, TREE) output the witness differs substantially depending on which spending path is used. A key path spend requires a single Schnorr signature. The witness contribution, excluding the witness element count varint, is 66 weight units (1-byte compact-size length prefix + 64-byte Schnorr signature + 1-byte sighash flag for the worst-case non-default sighash type). A script path spend requires the satisfaction of the chosen leaf, the leaf script itself, and a control block of: 1 + 32 + 32 * depth bytes. For a simple pk(X) leaf at depth 0 the witness contribution is 135 weight units, more than twice the key path size, and for deeper trees or more complex scripts the gap widens further. Current wallet implementations, including Bitcoin Core, return the key path weight for any tr() input regardless of whether the wallet holds the internal key. When the internal key is unavailable and the wallet must spend via a script path, the transaction is undersized in the fee estimate. In a congested mempool this can push the effective fee rate below the minimum relay threshold or below the fee rate required for timely inclusion in a block, leaving the transaction unconfirmed indefinitely. This also weakens the reliability of RBF fee bumping, since the initial transaction anchors future replacement fees too low. For example, a 1-in-2-out transaction spending a tr(KEY_A, pk(KEY_B)) output via the script path at a target of 10 sat/vbyte would be estimated at 131 vbytes and broadcast with a fee of 1310 sats, but its actual size is 148 vbytes, giving an effective fee rate of 8.85 sat/vbyte. More generally, this specification formalises the principle that fee estimation should be descriptor-aware rather than script-type-aware. A wallet that has a full tr() descriptor can compute a deterministic worst-case weight without heuristics, aligning fee estimation with the exact set of possible satisfactions encoded in the descriptor. Specification Wallet software that constructs transactions spending tr() outputs MUST compute the maximum witness weight for each input as specified below. This estimator is conservative by construction and does not assume knowledge of the eventual spending path at transaction construction time. All sizes in this section are expressed in witness weight units. The returned value excludes the witness element count varint, which is accounted for separately by the transaction weight calculation layer. Weight calculation Let TREE be the set of tapleaves in the descriptor, each with an associated depth in the Merkle tree. Define: - sat_size(leaf): maximum byte size of the witness elements satisfying the leaf script, including the compact-size varint prefix of each element but excluding the witness element count varint. - script_size(leaf): byte size of the serialized leaf script. - depth(leaf): depth of the leaf in the Merkle tree (root = 0). - varint(n): byte length of the compact-size encoding of n. Pseudocode: best = 66 # key path: 1 + 64 + 1 for each leaf in TREE: if sat_size(leaf) is unknown or script_size(leaf) is unknown: continue cb_size = 1 + 32 + 32 * depth(leaf) leaf_weight = sat_size(leaf) \ + varint(script_size(leaf)) + script_size(leaf) \ + varint(cb_size) + cb_size if leaf_weight > best: best = leaf_weight return best If all leaves are skipped due to unknown sizes, the function returns 66. Implementations encountering this case with a non-empty TREE SHOULD log a warning, as it typically indicates an incomplete descriptor. If the wallet holds the internal key and can confirm it is usable for signing, it SHOULD return 66 directly without evaluating the leaves. Descriptor availability This algorithm requires the full tr() descriptor including the script tree. Implementations that reconstruct descriptors from on-chain data or from a signing provider that does not retain tree information may lose the tree structure, causing the estimator to fall back to a key-path-only result. Implementations MUST use the stored descriptor rather than a reconstructed one when estimating input weight. Rationale Worst-case weight Using the maximum across all available spending paths ensures the transaction is never broadcast below the target fee rate. Overestimation results in a slightly higher fee, which is preferable to underestimation that can leave transactions stuck. This mirrors the worst-case satisfaction model already used for other descriptor types such as wsh() with multiple satisfaction paths. Witness stack count The witness element count varint is excluded for consistency with existing descriptor interfaces, where it is handled by the transaction assembly layer. Fallback when tree is unknown Returning the key path weight when no leaf sizes can be computed preserves backwards-compatible behaviour for tr(KEY) descriptors. Distinguishing between key-path-only and incomplete descriptors is left to the caller. Security Considerations Fee underestimation Wallets that do not implement this specification may broadcast transactions at a lower effective fee rate than intended. For complex trees, the mismatch can exceed 100%, making confirmation unlikely. Fee overestimation This specification always returns a worst-case weight. If a lighter path is used, the user overpays the difference. Implementations MAY adjust the fee after path selection. Malformed descriptors Descriptors with very large trees or deep nesting may cause excessive CPU usage or inflated fee estimates. Implementations SHOULD enforce reasonable limits. Backwards Compatibility This proposal does not affect consensus or network behaviour. Wallets that adopt it produce more accurate fee estimates. Others continue to function but remain subject to underestimation. Test Vectors All values exclude the witness element count varint. Single leaf at depth 0 Descriptor: tr(KEY_A, pk(KEY_B)) - sat_size = 66 - script_size = 34 - depth = 0 - cb_size = 33 - leaf_weight = 135 Key path weight = 66 Result: 135 Two leaves at depth 1 Descriptor: tr(KEY_A, {pk(KEY_B), multi_a(2,KEY_C,KEY_D)}) Leaf 1 (pk): - cb_size = 65 - leaf_weight = 167 Leaf 2 (multi_a): - script_size = 68 - sat_size = 132 - cb_size = 65 - leaf_weight = 267 Result: 267 Key path only Descriptor: tr(KEY_A) Result: 66 Reference Implementation A reference implementation can be achieved with the following changes to Bitcoin Core: - In src/script/descriptor.cpp, update TRDescriptor::MaxSatisfactionWeight and TRDescriptor::MaxSatisfactionElems to iterate over all tapleaves and return the maximum weight. - In src/wallet/spend.cpp, update GetDescriptor() to prefer stored descriptors over InferDescriptor(), which loses the script tree and causes fallback to rawtr(). References BIP 340 - Schnorr Signatures for secp256k1 BIP 341 - Taproot BIP 380 - Output Script Descriptors BIP 386 - tr() Descriptors -- You received this message because you are subscribed to the Google Groups "Bitcoin Development Mailing List" group. To unsubscribe from this group and stop receiving emails from it, send an email to bitcoindev+unsubscribe@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/bitcoindev/46489794-cce9-436f-854c-33828140e218n%40googlegroups.com.