@@ -2,6 +2,8 @@ package psbt
22
33import (
44 "bytes"
5+ "math"
6+ "math/bits"
57
68 "github.com/btcsuite/btcd/btcec/v2/schnorr"
79 "github.com/btcsuite/btcd/txscript"
@@ -93,12 +95,61 @@ func (s *TaprootBip32Derivation) SortBefore(other *TaprootBip32Derivation) bool
9395 return bytes .Compare (s .XOnlyPubKey , other .XOnlyPubKey ) < 0
9496}
9597
98+ // minTaprootBip32DerivationByteSize returns the minimum number of bytes
99+ // required to encode a Taproot BIP32 derivation field, given the number of
100+ // leaf hashes.
101+ //
102+ // NOTE: This function does not account for the size of the BIP32 child indexes,
103+ // as we are only computing the minimum size (which occurs when the path is
104+ // empty). The bits package is used to safely detect and handle overflows.
105+ func minTaprootBip32DerivationByteSize (numHashes uint64 ) (uint64 , error ) {
106+ // The Taproot BIP32 derivation field is encoded as:
107+ // [compact size uint: number of leaf hashes]
108+ // [N × 32 bytes: leaf hashes]
109+ // [4 bytes: master key fingerprint]
110+ // [M × 4 bytes: BIP32 child indexes]
111+ //
112+ // To compute the minimum size given the number of hashes only, we assume:
113+ // - N = numHashes (provided)
114+ // - M = 0 (no child indexes)
115+ //
116+ // So the base byte size is:
117+ // 1 (leaf hash count) + (N × 32) + 4 (fingerprint)
118+ //
119+ // First, we calculate the total number of bytes for the leaf hashes.
120+ mulCarry , totalHashesBytes := bits .Mul64 (numHashes , 32 )
121+ if mulCarry != 0 {
122+ return 0 , ErrInvalidPsbtFormat
123+ }
124+
125+ // Since we're computing the minimum possible size, we add a constant that
126+ // accounts for the fixed size fields:
127+ // * 1 byte for the compact size leaf hash count (assumes numHashes < 0xfd)
128+ // * 4 bytes for the master key fingerprint
129+ // Total: 5 bytes.
130+ // All other fields (e.g., BIP32 path) are assumed absent for minimum size
131+ // calculation.
132+ result , addCarry := bits .Add64 (5 , totalHashesBytes , 0 )
133+ if addCarry != 0 {
134+ return 0 , ErrInvalidPsbtFormat
135+ }
136+
137+ return result , nil
138+ }
139+
96140// ReadTaprootBip32Derivation deserializes a byte slice containing the Taproot
97141// BIP32 derivation info that consists of a list of leaf hashes as well as the
98142// normal BIP32 derivation info.
99143func ReadTaprootBip32Derivation (xOnlyPubKey ,
100144 value []byte ) (* TaprootBip32Derivation , error ) {
101145
146+ // This function allocates additional memory while parsing the serialized
147+ // data. To prevent potential out-of-memory (OOM) issues, we must validate
148+ // the length of the value slice before proceeding.
149+ if len (value ) > MaxPsbtValueLength {
150+ return nil , ErrInvalidPsbtFormat
151+ }
152+
102153 // The taproot key BIP 32 derivation path is defined as:
103154 // <hashes len> <leaf hash>* <4 byte fingerprint> <32-bit uint>*
104155 // So we get at least 5 bytes for the length and the 4 byte fingerprint.
@@ -113,9 +164,22 @@ func ReadTaprootBip32Derivation(xOnlyPubKey,
113164 return nil , ErrInvalidPsbtFormat
114165 }
115166
116- // A hash is 32 bytes in size, so we need at least numHashes*32 + 5
117- // bytes to be present.
118- if len (value ) < (int (numHashes )* 32 )+ 5 {
167+ // As a safety/sanity check, verify that the hash count fits in a `uint32`.
168+ // This isn’t mandated by BIP‑371, but it prevents overflow and limits
169+ // derivations to about 137 GiB of data.
170+ if numHashes > math .MaxUint32 {
171+ return nil , ErrInvalidPsbtFormat
172+ }
173+
174+ // Given the number of hashes, we can calculate the minimum byte size
175+ // of the taproot BIP32 derivation.
176+ minByteSize , err := minTaprootBip32DerivationByteSize (numHashes )
177+ if err != nil {
178+ return nil , err
179+ }
180+
181+ // Ensure that value is at least the minimum size.
182+ if uint64 (len (value )) < minByteSize {
119183 return nil , ErrInvalidPsbtFormat
120184 }
121185
0 commit comments