From mboxrd@z Thu Jan 1 00:00:00 1970 Delivery-date: Sat, 11 Apr 2026 14:55:57 -0700 Received: from mail-oo1-f59.google.com ([209.85.161.59]) by mail.fairlystable.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.94.2) (envelope-from ) id 1wBgJ2-000119-GO for bitcoindev@gnusha.org; Sat, 11 Apr 2026 14:55:57 -0700 Received: by mail-oo1-f59.google.com with SMTP id 006d021491bc7-689b18091ebsf6307406eaf.1 for ; Sat, 11 Apr 2026 14:55:55 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlegroups.com; s=20251104; t=1775944550; x=1776549350; darn=gnusha.org; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:x-original-sender:mime-version :subject:message-id:to:from:date:sender:from:to:cc:subject:date :message-id:reply-to; bh=wlz8NTlJaFvIAKebTJd2OLHUxiOMMelhYDTxpDTgfD4=; b=vlQgvirstYV+ClZAgHe3/KDqcf9NwEl34NaRiAxMONMcunOcIvwXIGK260q+MEOuch 5M44z58tP9kpz1ZWrcSxghwyIz8Eo4RoqEtFaJ2+zoqhiz0q2G32bSQvgKVB8GPrM4Iq uogPVXWZkNP8fHUUeHFzeYkZDMfJQmwEoKuvy0qhRuUR/mOCrwUR8EfIwMkDTW9gB7AS MwjbbKa1y45+ga3YKeevNR/MShwJR8ZMCtJKs8ofDYxV4onyyfs8WOIHQiM+MDMa12LM hbBTKgjiG7bZj3jzllEGyZcG9KWoFD51kHGSucCCBEelWiSSzEuz9ns5q1sqMptE2QY4 oseA== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1775944550; x=1776549350; darn=gnusha.org; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:x-original-sender:mime-version :subject:message-id:to:from:date:from:to:cc:subject:date:message-id :reply-to; bh=wlz8NTlJaFvIAKebTJd2OLHUxiOMMelhYDTxpDTgfD4=; b=sq7pVHQ0BR3OWX+Rx09wHfcYWjxktT45q6t6NlB5Byc/kRg5YQcVL7Ah0IRMawIoR6 4jSNKUG3n5J3TLGY01wm2tvhXmFh7W2Cjt6Uvo0nNmNbXE0uUgLckqugQly9rrIUkSgF fjqJi7bdQfQ4x26GVF9vy6NX0oAUCPO5+gS3J+8A0WQY5PwhEHNYjY0UEhxJ2zh06wKI ikPWTZPX2/qWKivxuBFsFyTCzyjz5bl2s6gKwm4qAgxiBwYDhs9TTFR4lqdPQeZow3S/ 6q+39YFqVWt5/7IZgC2epornSEMte2127hw6dzydY5G5wf87DKBDqQxOWlulN0Z4ylWe Vifw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775944550; x=1776549350; h=list-unsubscribe:list-subscribe:list-archive:list-help:list-post :list-id:mailing-list:precedence:x-original-sender:mime-version :subject:message-id:to:from:date:x-beenthere:x-gm-message-state :sender:from:to:cc:subject:date:message-id:reply-to; bh=wlz8NTlJaFvIAKebTJd2OLHUxiOMMelhYDTxpDTgfD4=; b=YEaCWmivanlXD39TUrGcE0sU+BzO33p9jKJsnfycK5X8YiMDJIkQkMg+/Ac27golke eFhdNopwtM1ED2w4CX7non1XLiU6J4hLQmIQtjlab6qYvgEFxWWPXMUCP4uNb/w4zNed lm+95M057EACVhd6n2ylSuQ8xSjy51WSmGHObZaBBnawHWYOs+PXSHCtsClF50kZKwEM QMvZwGiG2OYXKJfB3fgXSWyODg+71KgLOrIEgzdwpavvIlxl0Te5I9g1x3atmlllTxCe VdFShgeAWfOoVgMUHoRCj/AfK+NiJBftDkjIxUyf0eRPoEWJ3XM6/wPOvjpWNwt/FGKx n9Fg== Sender: bitcoindev@googlegroups.com X-Forwarded-Encrypted: i=1; AJvYcCXXD8/hN/NoT9qTIR58qovA8TMki0+k2eSjbICN9SKdTncxhr1+H7tPgJl0cASDYc8geMtlUUd4jLid@gnusha.org X-Gm-Message-State: AOJu0YycVIwXIFrOddRpHqWpVWe/gHR+G+EQ+Q5CXXdCUUvCvNPn11UD S08PnSrNQSRmhjICk/fnn3Bq79lpAfCbpt/q4oAov/JUbo5FWgbMrH+n X-Received: by 2002:a05:6820:1897:b0:687:93da:80be with SMTP id 006d021491bc7-68bf8bd0226mr3402636eaf.30.1775944549962; Sat, 11 Apr 2026 14:55:49 -0700 (PDT) X-BeenThere: bitcoindev@googlegroups.com; h="AYAyTiJXEMowmchS3SdJJoldLC9DQ2BlPvjCiyQc7IwatZktGQ==" Received: by 2002:a05:6871:3591:b0:416:1b5c:16df with SMTP id 586e51a60fabf-423dd9d9554ls1060147fac.2.-pod-prod-08-us; Sat, 11 Apr 2026 14:55:45 -0700 (PDT) X-Received: by 2002:a05:6808:c3ea:b0:467:23e2:5097 with SMTP id 5614622812f47-4789f10a143mr4226835b6e.37.1775944544887; Sat, 11 Apr 2026 14:55:44 -0700 (PDT) Received: by 2002:a05:690c:4c01:b0:79a:e1a5:fe7e with SMTP id 00721157ae682-7ae240f9947ms7b3; Sat, 11 Apr 2026 14:54:28 -0700 (PDT) X-Received: by 2002:a05:690c:6e86:b0:7a2:4f74:ec8a with SMTP id 00721157ae682-7af71f485fcmr86414207b3.43.1775944467861; Sat, 11 Apr 2026 14:54:27 -0700 (PDT) Date: Sat, 11 Apr 2026 14:54:27 -0700 (PDT) From: Jan Thomasewsky To: Bitcoin Development Mailing List Message-Id: <46489794-cce9-436f-854c-33828140e218n@googlegroups.com> Subject: [bitcoindev] [BIP Draft] Worst-Case Taproot Witness Weight Estimation MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----=_Part_551884_1357510190.1775944467240" X-Original-Sender: programaciong730@gmail.com Precedence: list Mailing-list: list bitcoindev@googlegroups.com; contact bitcoindev+owners@googlegroups.com List-ID: X-Google-Group-Id: 786775582512 List-Post: , List-Help: , List-Archive: , List-Unsubscribe: , X-Spam-Score: -0.5 (/) ------=_Part_551884_1357510190.1775944467240 Content-Type: multipart/alternative; boundary="----=_Part_551885_557812516.1775944467240" ------=_Part_551885_557812516.1775944467240 Content-Type: text/plain; charset="UTF-8" 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. ------=_Part_551885_557812516.1775944467240 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable BIP: ?
Layer: Applications
Title: Worst-Case Taproot Witness Weigh= t Estimation
Author: Jan Thomasewsky <jan_btcln@proton.me>
= Status: Draft
Type: Standards Track
Created: 2026-04-11
Lice= nse: CC-BY-4.0
Requires: 341, 380, 386


Abstract
=
This document specifies how wallet implementations should estimate th= e witness
weight of inputs spending BIP 341 Taproot outputs when const= ructing
transactions. Existing implementations assume key path spendin= g for all
tr() inputs, which underestimates the weight when a script p= ath spend is
required and causes the transaction to broadcast at a low= er effective fee
rate than the user intended.


Motivat= ion

When constructing a transaction a wallet must estimate the w= itness weight of
each input to calculate the correct fee. For a tr(KEY= , TREE) output the
witness differs substantially depending on which sp= ending path is used.

A key path spend requires a single Schnorr = signature. The witness
contribution, excluding the witness element cou= nt varint, is 66 weight units
(1-byte compact-size length prefix + 64-= byte Schnorr signature + 1-byte
sighash flag for the worst-case non-de= fault sighash type).

A script path spend requires the satisfacti= on of the chosen leaf, the leaf
script itself, and a control block of:=

=C2=A0 =C2=A0 1 + 32 + 32 * depth

bytes. For a simpl= e 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
comple= x scripts the gap widens further.

Current wallet implementations= , including Bitcoin Core, return the key path
weight for any tr() inpu= t regardless of whether the wallet holds the internal
key. When the in= ternal key is unavailable and the wallet must spend via a
script path,= the transaction is undersized in the fee estimate.

In a congest= ed mempool this can push the effective fee rate below the minimum
rela= y threshold or below the fee rate required for timely inclusion in a
b= lock, leaving the transaction unconfirmed indefinitely. This also weakensthe reliability of RBF fee bumping, since the initial transaction ancho= rs
future replacement fees too low.

For example, a 1-in-2-o= ut transaction spending a tr(KEY_A, pk(KEY_B)) output
via the script p= ath at a target of 10 sat/vbyte would be estimated at 131
vbytes and b= roadcast with a fee of 1310 sats, but its actual size is 148
vbytes, g= iving an effective fee rate of 8.85 sat/vbyte.

More generally, t= his 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
wi= thout heuristics, aligning fee estimation with the exact set of possiblesatisfactions encoded in the descriptor.


Specification<= br />
Wallet software that constructs transactions spending tr() outpu= ts 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 cons= truction time. All sizes in
this section are expressed in witness weig= ht units.

The returned value excludes the witness element count = varint, which is
accounted for separately by the transaction weight ca= lculation 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): maximu= m byte size of the witness elements satisfying the
=C2=A0 leaf script,= including the compact-size varint prefix of each element but
=C2=A0 e= xcluding 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 =3D 0).

- varint(n): byte len= gth of the compact-size encoding of n.

Pseudocode:

= =C2=A0 =C2=A0 best =3D 66 =C2=A0# key path: 1 + 64 + 1

=C2=A0 = =C2=A0 for each leaf in TREE:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 if sat_size(= leaf) is unknown or script_size(leaf) is unknown:
=C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 continue

=C2=A0 =C2=A0 =C2=A0 =C2=A0 cb_si= ze =3D 1 + 32 + 32 * depth(leaf)

=C2=A0 =C2=A0 =C2=A0 =C2=A0 lea= f_weight =3D sat_size(leaf) \
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 + varint(script_size(leaf)) + script_size(l= eaf) \
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 + varint(cb_size) + cb_size

=C2=A0 =C2=A0 =C2=A0 =C2=A0 i= f leaf_weight > best:
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 bes= t =3D leaf_weight

=C2=A0 =C2=A0 return best

If all le= aves are skipped due to unknown sizes, the function returns 66.
Implem= entations encountering this case with a non-empty TREE SHOULD log a
wa= rning, as it typically indicates an incomplete descriptor.

If th= e wallet holds the internal key and can confirm it is usable for
signi= ng, it SHOULD return 66 directly without evaluating the leaves.

=
Descriptor availability

This algorithm requires the full t= r() descriptor including the script tree.
Implementations that reconst= ruct descriptors from on-chain data or from a
signing provider that do= es not retain tree information may lose the tree
structure, causing th= e estimator to fall back to a key-path-only result.

Implementati= ons MUST use the stored descriptor rather than a reconstructed
one whe= n estimating input weight.


Rationale

Worst-case= weight

Using the maximum across all available spending paths en= sures the transaction
is never broadcast below the target fee rate. Ov= erestimation results in a
slightly higher fee, which is preferable to = underestimation that can leave
transactions stuck.

This mir= rors the worst-case satisfaction model already used for other
descript= or types such as wsh() with multiple satisfaction paths.

Witness= stack count

The witness element count varint is excluded for co= nsistency 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 p= reserves
backwards-compatible behaviour for tr(KEY) descriptors. Disti= nguishing
between key-path-only and incomplete descriptors is left to = the caller.


Security Considerations

Fee underes= timation

Wallets that do not implement this specification may br= oadcast transactions
at a lower effective fee rate than intended. For = complex trees, the mismatch
can exceed 100%, making confirmation unlik= ely.

Fee overestimation

This specification always ret= urns a worst-case weight. If a lighter path is
used, the user overpays= the difference. Implementations MAY adjust the fee
after path selecti= on.

Malformed descriptors

Descriptors with very large= trees or deep nesting may cause excessive CPU
usage or inflated fee e= stimates. Implementations SHOULD enforce reasonable
limits.


Backwards Compatibility

This proposal does not affect con= sensus or network behaviour. Wallets that
adopt it produce more accura= te fee estimates. Others continue to function but
remain subject to un= derestimation.


Test Vectors

All values exclude = the witness element count varint.

Single leaf at depth 0
Descriptor: tr(KEY_A, pk(KEY_B))

- sat_size =3D 66
- sc= ript_size =3D 34
- depth =3D 0
- cb_size =3D 33
- leaf_weigh= t =3D 135

Key path weight =3D 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 =3D 65
- le= af_weight =3D 167

Leaf 2 (multi_a):
- script_size =3D 68- sat_size =3D 132
- cb_size =3D 65
- leaf_weight =3D 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<= br />Bitcoin Core:

- In src/script/descriptor.cpp, update TRDesc= riptor::MaxSatisfactionWeight
=C2=A0 and TRDescriptor::MaxSatisfaction= Elems to iterate over all tapleaves and
=C2=A0 return the maximum weig= ht.

- In src/wallet/spend.cpp, update GetDescriptor() to prefer = stored
=C2=A0 descriptors over InferDescriptor(), which loses the scri= pt tree and causes
=C2=A0 fallback to rawtr().


Refere= nces

BIP 340 - Schnorr Signatures for secp256k1
BIP 341 - T= aproot
BIP 380 - Output Script Descriptors
BIP 386 - tr() Descrip= tors

--
You received this message because you are subscribed to the Google Groups &= quot;Bitcoin Development Mailing List" group.
To unsubscribe from this group and stop receiving emails from it, send an e= mail to bitcoind= ev+unsubscribe@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/bitcoind= ev/46489794-cce9-436f-854c-33828140e218n%40googlegroups.com.
------=_Part_551885_557812516.1775944467240-- ------=_Part_551884_1357510190.1775944467240--