tx

Minimal transaction primitives (docs.ppad.tech/tx).
git clone git://git.ppad.tech/tx.git
Log | Files | Refs | README | LICENSE

Sighash.hs (20862B)


      1 {-# OPTIONS_HADDOCK prune #-}
      2 {-# LANGUAGE BangPatterns #-}
      3 {-# LANGUAGE DeriveGeneric #-}
      4 {-# LANGUAGE OverloadedStrings #-}
      5 {-# LANGUAGE RecordWildCards #-}
      6 
      7 -- |
      8 -- Module: Bitcoin.Prim.Tx.Sighash
      9 -- Copyright: (c) 2025 Jared Tobin
     10 -- License: MIT
     11 -- Maintainer: Jared Tobin <jared@ppad.tech>
     12 --
     13 -- Sighash computation for legacy, BIP143 segwit, and BIP341 taproot
     14 -- transactions.
     15 
     16 module Bitcoin.Prim.Tx.Sighash (
     17     -- * Sighash Types
     18     SighashType(..)
     19   , encode_sighash
     20 
     21     -- * Legacy Sighash
     22   , sighash_legacy
     23 
     24     -- * BIP143 Segwit Sighash
     25   , sighash_segwit
     26 
     27     -- * BIP341 Taproot Sighash
     28   , sighash_taproot_keypath
     29   , sighash_taproot_scriptpath
     30 
     31     -- * Script code
     32   , strip_codeseparators
     33   ) where
     34 
     35 import Bitcoin.Prim.Tx.Internal
     36     ( Tx(..)
     37     , TxIn(..)
     38     , TxOut(..)
     39     , put_word32_le
     40     , put_word64_le
     41     , put_compact
     42     , put_bytes
     43     , put_outpoint
     44     , put_txin
     45     , put_txout
     46     , to_strict
     47     )
     48 import Control.DeepSeq (NFData)
     49 import Control.Monad (guard)
     50 import qualified Crypto.Hash.SHA256 as SHA256
     51 import Data.Bits ((.&.))
     52 import qualified Data.ByteString as BS
     53 import qualified Data.ByteString.Builder as BSB
     54 import qualified Data.List.NonEmpty as NE
     55 import Data.Word (Word8, Word32, Word64)
     56 import GHC.Generics (Generic)
     57 
     58 -- | Canonical sighash type flags.
     59 --
     60 --   The Bitcoin consensus rules commit the full 32-bit @hashType@ to
     61 --   the signature preimage and only use its low byte for behavioral
     62 --   dispatch (low 5 bits select base type; bit 0x80 selects
     63 --   ANYONECANPAY). 'SighashType' enumerates the six canonical
     64 --   single-byte hashTypes; pass arbitrary 32-bit values directly when
     65 --   reproducing non-canonical hashes.
     66 data SighashType
     67   = SIGHASH_ALL
     68   | SIGHASH_NONE
     69   | SIGHASH_SINGLE
     70   | SIGHASH_ALL_ANYONECANPAY
     71   | SIGHASH_NONE_ANYONECANPAY
     72   | SIGHASH_SINGLE_ANYONECANPAY
     73   deriving (Eq, Show, Generic)
     74 
     75 instance NFData SighashType
     76 
     77 -- | Encode a canonical 'SighashType' to its 32-bit hashType value.
     78 --
     79 --   @
     80 --   encode_sighash SIGHASH_ALL                 == 0x01
     81 --   encode_sighash SIGHASH_SINGLE_ANYONECANPAY == 0x83
     82 --   @
     83 encode_sighash :: SighashType -> Word32
     84 encode_sighash !st = case st of
     85   SIGHASH_ALL                 -> 0x01
     86   SIGHASH_NONE                -> 0x02
     87   SIGHASH_SINGLE              -> 0x03
     88   SIGHASH_ALL_ANYONECANPAY    -> 0x81
     89   SIGHASH_NONE_ANYONECANPAY   -> 0x82
     90   SIGHASH_SINGLE_ANYONECANPAY -> 0x83
     91 {-# INLINE encode_sighash #-}
     92 
     93 -- | Internal base sighash classification derived from a 32-bit hashType.
     94 data BaseType = BaseAll | BaseNone | BaseSingle
     95   deriving Eq
     96 
     97 -- | Behavioral base type: @hashType & 0x1f@. 2 → NONE, 3 → SINGLE,
     98 --   anything else → ALL.
     99 base_type :: Word32 -> BaseType
    100 base_type !ht = case ht .&. 0x1f of
    101   2 -> BaseNone
    102   3 -> BaseSingle
    103   _ -> BaseAll
    104 {-# INLINE base_type #-}
    105 
    106 -- | Check ANYONECANPAY flag: @hashType & 0x80@.
    107 is_anyonecanpay :: Word32 -> Bool
    108 is_anyonecanpay !ht = (ht .&. 0x80) /= 0
    109 {-# INLINE is_anyonecanpay #-}
    110 
    111 -- | 32 zero bytes.
    112 zero32 :: BS.ByteString
    113 zero32 = BS.replicate 32 0x00
    114 {-# NOINLINE zero32 #-}
    115 
    116 -- | Hash of 0x01 followed by 31 zero bytes (SIGHASH_SINGLE edge case).
    117 sighash_single_bug :: BS.ByteString
    118 sighash_single_bug = BS.cons 0x01 (BS.replicate 31 0x00)
    119 {-# NOINLINE sighash_single_bug #-}
    120 
    121 -- | Double SHA256.
    122 hash256 :: BS.ByteString -> BS.ByteString
    123 hash256 = SHA256.hash . SHA256.hash
    124 {-# INLINE hash256 #-}
    125 
    126 -- | Strip @OP_CODESEPARATOR@ (0xab) opcodes from a script, skipping
    127 --   push-data sections so that data bytes equal to 0xab are preserved.
    128 --
    129 --   This is consensus-required preprocessing for the legacy sighash
    130 --   scriptCode (see Bitcoin Core's @CTransactionSignatureSerializer@).
    131 --   BIP143 segwit sighash does /not/ perform this stripping; for
    132 --   segwit, the caller is responsible for trimming the scriptCode to
    133 --   the portion after the last executed @OP_CODESEPARATOR@.
    134 --
    135 --   On a malformed script (truncated push data), the malformed tail is
    136 --   copied verbatim without further codeseparator processing.
    137 strip_codeseparators :: BS.ByteString -> BS.ByteString
    138 strip_codeseparators !script
    139   | not (0xab `BS.elem` script) = script  -- fast path: nothing to strip
    140   | otherwise = BS.pack (go (BS.unpack script))
    141   where
    142     go :: [Word8] -> [Word8]
    143     go [] = []
    144     go (b : rest)
    145       | b == 0xab              = go rest
    146       | b >= 0x01 && b <= 0x4b = push (fromIntegral b) [b] rest
    147       | b == 0x4c              = case rest of
    148           (n : rest') -> push (fromIntegral n) [b, n] rest'
    149           []          -> [b]
    150       | b == 0x4d              = case rest of
    151           (n0 : n1 : rest') ->
    152             let !len = fromIntegral n0
    153                      + fromIntegral n1 * 0x100
    154             in  push len [b, n0, n1] rest'
    155           _ -> b : rest
    156       | b == 0x4e              = case rest of
    157           (n0 : n1 : n2 : n3 : rest') ->
    158             let !len = fromIntegral n0
    159                      + fromIntegral n1 * 0x100
    160                      + fromIntegral n2 * 0x10000
    161                      + fromIntegral n3 * 0x1000000
    162             in  push len [b, n0, n1, n2, n3] rest'
    163           _ -> b : rest
    164       | otherwise              = b : go rest
    165 
    166     -- | Copy a push header and N data bytes verbatim. On truncation,
    167     --   @splitAt@ yields @(available, [])@ so @go []@ closes the
    168     --   recursion naturally; the malformed tail is preserved.
    169     push :: Int -> [Word8] -> [Word8] -> [Word8]
    170     push !len !header !rest =
    171       let (chunk, rest') = splitAt len rest
    172       in  header ++ chunk ++ go rest'
    173 {-# INLINABLE strip_codeseparators #-}
    174 
    175 -- legacy sighash -------------------------------------------------------------
    176 
    177 -- | Compute legacy sighash for P2PKH/P2SH inputs.
    178 --
    179 --   Modifies a copy of the transaction based on hashType flags, appends
    180 --   the 4-byte little-endian hashType, and double SHA256s. The
    181 --   @hashType@ is committed to the preimage verbatim; only its low byte
    182 --   determines behavior (see 'base_type', 'is_anyonecanpay').
    183 --
    184 --   @
    185 --   -- sign input 0 with SIGHASH_ALL
    186 --   let hash = sighash_legacy tx 0 scriptPubKey (encode_sighash SIGHASH_ALL)
    187 --   -- non-canonical hashType (consensus-valid, committed raw)
    188 --   let hash = sighash_legacy tx 0 scriptPubKey 0x6f29291f
    189 --   @
    190 --
    191 --   For base SIGHASH_SINGLE with input index >= output count, returns
    192 --   the special \"sighash single bug\" value (0x01 followed by 31 zero
    193 --   bytes).
    194 --
    195 --   The input index is /not/ validated against the input count; an
    196 --   out-of-range @idx@ produces a deterministic but
    197 --   consensus-undefined hash. Matches Bitcoin Core, which @assert@s on
    198 --   the same precondition. Contrast 'sighash_segwit', which validates
    199 --   and returns 'Nothing'.
    200 sighash_legacy
    201   :: Tx
    202   -> Int              -- ^ input index
    203   -> BS.ByteString    -- ^ scriptPubKey being spent
    204   -> Word32           -- ^ hashType
    205   -> BS.ByteString    -- ^ 32-byte hash
    206 sighash_legacy !tx !idx !script_pubkey !ht
    207   -- SIGHASH_SINGLE edge case: index >= number of outputs
    208   | base == BaseSingle && idx >= NE.length (tx_outputs tx) =
    209       sighash_single_bug
    210   | otherwise =
    211       let !serialized = serialize_legacy_sighash tx idx script_pubkey ht
    212       in  hash256 serialized
    213   where
    214     !base = base_type ht
    215 
    216 -- | Serialize transaction for legacy sighash computation.
    217 --   Handles all sighash flags directly without constructing intermediate Tx.
    218 serialize_legacy_sighash
    219   :: Tx
    220   -> Int
    221   -> BS.ByteString
    222   -> Word32
    223   -> BS.ByteString
    224 serialize_legacy_sighash Tx{..} !idx !script_pubkey !ht =
    225   let !script' = strip_codeseparators script_pubkey
    226       !base = base_type ht
    227       !anyonecanpay = is_anyonecanpay ht
    228       !inputs_list = NE.toList tx_inputs
    229       !outputs_list = NE.toList tx_outputs
    230 
    231       -- Clear all scriptSigs, set signing input's script to scriptPubKey
    232       clear_scripts :: Int -> [TxIn] -> [TxIn]
    233       clear_scripts !_ [] = []
    234       clear_scripts !i (inp : rest)
    235         | i == idx  = inp { txin_script_sig = script' } : clear_rest
    236         | otherwise = inp { txin_script_sig = BS.empty } : clear_rest
    237         where
    238           !clear_rest = clear_scripts (i + 1) rest
    239 
    240       -- For NONE/SINGLE: zero out sequence numbers for other inputs
    241       zero_other_sequences :: Int -> [TxIn] -> [TxIn]
    242       zero_other_sequences !_ [] = []
    243       zero_other_sequences !i (inp : rest)
    244         | i == idx  = inp : zero_other_sequences (i + 1) rest
    245         | otherwise =
    246             inp { txin_sequence = 0 } : zero_other_sequences (i + 1) rest
    247 
    248       -- Process inputs based on sighash type
    249       !inputs_cleared = clear_scripts 0 inputs_list
    250 
    251       !inputs_processed = case base of
    252         BaseNone   -> zero_other_sequences 0 inputs_cleared
    253         BaseSingle -> zero_other_sequences 0 inputs_cleared
    254         _          -> inputs_cleared
    255 
    256       -- ANYONECANPAY: keep only signing input
    257       !final_inputs
    258         | anyonecanpay = case safe_index inputs_processed idx of
    259             Just inp -> [inp]
    260             Nothing  -> []  -- shouldn't happen if idx is valid
    261         | otherwise = inputs_processed
    262 
    263       -- Process outputs based on sighash type
    264       !final_outputs = case base of
    265         BaseNone   -> []
    266         BaseSingle -> build_single_outputs outputs_list idx
    267         _          -> outputs_list
    268 
    269   in  to_strict $
    270          put_word32_le tx_version
    271       <> put_compact (fromIntegral (length final_inputs))
    272       <> foldMap put_txin final_inputs
    273       <> put_compact (fromIntegral (length final_outputs))
    274       <> foldMap put_txout final_outputs
    275       <> put_word32_le tx_locktime
    276       <> put_word32_le ht
    277 
    278 -- | Build outputs for SIGHASH_SINGLE: keep only output at idx,
    279 --   replace earlier outputs with empty/zero outputs.
    280 build_single_outputs :: [TxOut] -> Int -> [TxOut]
    281 build_single_outputs !outs !target_idx = go 0 outs
    282   where
    283     go :: Int -> [TxOut] -> [TxOut]
    284     go !_ [] = []
    285     go !i (o : rest)
    286       | i == target_idx = [o]  -- keep this one and stop
    287       | i < target_idx  = empty_output : go (i + 1) rest
    288       | otherwise       = []   -- shouldn't reach here
    289 
    290     -- Empty output: -1 (0xffffffffffffffff) value, empty script
    291     empty_output :: TxOut
    292     empty_output = TxOut 0xffffffffffffffff BS.empty
    293 
    294 -- | Safe list indexing.
    295 safe_index :: [a] -> Int -> Maybe a
    296 safe_index [] _ = Nothing
    297 safe_index (x : xs) !n
    298   | n < 0     = Nothing
    299   | n == 0    = Just x
    300   | otherwise = safe_index xs (n - 1)
    301 {-# INLINE safe_index #-}
    302 
    303 -- BIP143 segwit sighash ------------------------------------------------------
    304 
    305 -- | Compute BIP143 segwit sighash.
    306 --
    307 --   Required for signing segwit inputs (P2WPKH, P2WSH). Unlike legacy
    308 --   sighash, this commits to the value being spent, preventing fee
    309 --   manipulation attacks. The @hashType@ is committed to the preimage
    310 --   verbatim; only its low byte determines behavior.
    311 --
    312 --   Returns 'Nothing' if the input index is out of range.
    313 --
    314 --   @
    315 --   -- sign P2WPKH input 0
    316 --   let scriptCode = ...  -- P2WPKH scriptCode
    317 --   let hash = sighash_segwit tx 0 scriptCode inputValue
    318 --                  (encode_sighash SIGHASH_ALL)
    319 --   -- use hash with ECDSA signing (after checking Just)
    320 --   @
    321 sighash_segwit
    322   :: Tx
    323   -> Int              -- ^ input index
    324   -> BS.ByteString    -- ^ scriptCode
    325   -> Word64           -- ^ value being spent (satoshis)
    326   -> Word32           -- ^ hashType
    327   -> Maybe BS.ByteString    -- ^ 32-byte hash, or Nothing if index invalid
    328 sighash_segwit !tx !idx !script_code !value !ht = do
    329   preimage <- build_bip143_preimage tx idx script_code value ht
    330   pure $! hash256 preimage
    331 
    332 -- | Build BIP143 preimage for signing.
    333 --   Returns Nothing if the input index is out of range.
    334 build_bip143_preimage
    335   :: Tx
    336   -> Int
    337   -> BS.ByteString
    338   -> Word64
    339   -> Word32
    340   -> Maybe BS.ByteString
    341 build_bip143_preimage Tx{..} !idx !script_code !value !ht = do
    342   -- Get the input being signed; fail if index out of range
    343   let !inputs_list = NE.toList tx_inputs
    344       !outputs_list = NE.toList tx_outputs
    345   signing_input <- safe_index inputs_list idx
    346 
    347   let !base = base_type ht
    348       !anyonecanpay = is_anyonecanpay ht
    349 
    350       -- hashPrevouts: double SHA256 of all outpoints, or zero if ANYONECANPAY
    351       !hash_prevouts
    352         | anyonecanpay = zero32
    353         | otherwise    = hash256 $ to_strict $
    354             foldMap (put_outpoint . txin_prevout) tx_inputs
    355 
    356       -- hashSequence: double SHA256 of all sequences, or zero if
    357       -- ANYONECANPAY or NONE or SINGLE
    358       !hash_sequence
    359         | anyonecanpay        = zero32
    360         | base == BaseSingle  = zero32
    361         | base == BaseNone    = zero32
    362         | otherwise = hash256 $ to_strict $
    363             foldMap (put_word32_le . txin_sequence) tx_inputs
    364 
    365       -- hashOutputs: depends on sighash type
    366       !hash_outputs = case base of
    367         BaseNone   -> zero32
    368         BaseSingle ->
    369           case safe_index outputs_list idx of
    370             Nothing  -> zero32  -- index out of range
    371             Just out -> hash256 $ to_strict $ put_txout out
    372         _ -> hash256 $ to_strict $ foldMap put_txout tx_outputs
    373 
    374       !outpoint = txin_prevout signing_input
    375       !sequence_n = txin_sequence signing_input
    376 
    377   pure $! to_strict $
    378        put_word32_le tx_version
    379     <> BSB.byteString hash_prevouts
    380     <> BSB.byteString hash_sequence
    381     <> put_outpoint outpoint
    382     <> put_compact (fromIntegral (BS.length script_code))
    383     <> BSB.byteString script_code
    384     <> put_word64_le value
    385     <> put_word32_le sequence_n
    386     <> BSB.byteString hash_outputs
    387     <> put_word32_le tx_locktime
    388     <> put_word32_le ht
    389 
    390 -- BIP341 taproot sighash ----------------------------------------------------
    391 
    392 -- | Precomputed BIP340 tagged-hash key for @\"TapSighash\"@.
    393 tap_sighash_tag :: BS.ByteString
    394 tap_sighash_tag = SHA256.hash "TapSighash"
    395 {-# NOINLINE tap_sighash_tag #-}
    396 
    397 -- | BIP340 tagged hash with the @\"TapSighash\"@ tag:
    398 --   @SHA256(tag_hash || tag_hash || msg)@.
    399 tap_sighash :: BS.ByteString -> BS.ByteString
    400 tap_sighash !msg =
    401   SHA256.hash (tap_sighash_tag <> tap_sighash_tag <> msg)
    402 {-# INLINE tap_sighash #-}
    403 
    404 -- | Single SHA256 of a Builder's output.
    405 sha :: BSB.Builder -> BS.ByteString
    406 sha = SHA256.hash . to_strict
    407 {-# INLINE sha #-}
    408 
    409 -- | Valid taproot hash types per BIP341: 0x00 (DEFAULT), 0x01..0x03,
    410 --   0x81..0x83. Non-canonical values are signalled as invalid in
    411 --   contrast with legacy\/segwit, which commit arbitrary 32-bit values.
    412 is_valid_taproot_ht :: Word8 -> Bool
    413 is_valid_taproot_ht !ht =
    414      ht == 0x00 || ht == 0x01 || ht == 0x02 || ht == 0x03
    415   || ht == 0x81 || ht == 0x82 || ht == 0x83
    416 {-# INLINE is_valid_taproot_ht #-}
    417 
    418 -- | Compute BIP341 taproot sighash for a /key-path/ spend.
    419 --
    420 --   The caller must supply, in input order, the amount and
    421 --   scriptPubKey of every previous output being spent (the entire
    422 --   set is committed to the preimage when not using
    423 --   @SIGHASH_ANYONECANPAY@).
    424 --
    425 --   The annex, if present, must include the mandatory 0x50 prefix
    426 --   byte (as it appears in the witness).
    427 --
    428 --   Returns 'Nothing' if any of the following holds:
    429 --
    430 --     * @hash_type@ is not a canonical taproot value
    431 --     * the input index is out of range
    432 --     * @amounts@ or @scriptPubKeys@ does not match the input count
    433 --     * an annex is supplied without the 0x50 prefix or is empty
    434 --     * @hash_type@ is @SIGHASH_SINGLE@ (or its ACP variant) and the
    435 --       input index has no corresponding output (such a signature
    436 --       would be consensus-invalid per BIP341)
    437 --
    438 --   @
    439 --   sighash_taproot_keypath tx 0 amounts scriptPubKeys Nothing 0x00
    440 --   @
    441 sighash_taproot_keypath
    442   :: Tx
    443   -> Int                  -- ^ input index
    444   -> [Word64]             -- ^ amounts for all inputs (in order)
    445   -> [BS.ByteString]      -- ^ scriptPubKeys for all inputs (in order)
    446   -> Maybe BS.ByteString  -- ^ optional annex (including 0x50 prefix)
    447   -> Word8                -- ^ hash type
    448   -> Maybe BS.ByteString  -- ^ 32-byte hash, or Nothing on invalid input
    449 sighash_taproot_keypath !tx !idx !amts !spks !annex !ht =
    450   taproot_sighash tx idx amts spks annex Nothing ht
    451 
    452 -- | Compute BIP341 taproot sighash for a /script-path/ (tapscript)
    453 --   spend.
    454 --
    455 --   In addition to the key-path inputs, takes:
    456 --
    457 --     * the 32-byte tap leaf hash (BIP342: tagged hash of @leaf_ver ||
    458 --       ser_string(script)@), computed by the caller
    459 --     * the codeseparator position (0xffffffff if none was executed)
    460 --
    461 --   Returns 'Nothing' under the same conditions as
    462 --   'sighash_taproot_keypath', plus when @tap_leaf_hash@ is not
    463 --   exactly 32 bytes.
    464 sighash_taproot_scriptpath
    465   :: Tx
    466   -> Int                  -- ^ input index
    467   -> [Word64]             -- ^ amounts for all inputs (in order)
    468   -> [BS.ByteString]      -- ^ scriptPubKeys for all inputs (in order)
    469   -> Maybe BS.ByteString  -- ^ optional annex (including 0x50 prefix)
    470   -> BS.ByteString        -- ^ tap leaf hash (32 bytes)
    471   -> Word32               -- ^ codeseparator position
    472   -> Word8                -- ^ hash type
    473   -> Maybe BS.ByteString
    474 sighash_taproot_scriptpath !tx !idx !amts !spks !annex !leaf !csep !ht =
    475   taproot_sighash tx idx amts spks annex (Just (leaf, csep)) ht
    476 
    477 -- | Internal worker shared by 'sighash_taproot_keypath' and
    478 --   'sighash_taproot_scriptpath'. @Nothing@ for the extension argument
    479 --   selects the key-path; @Just (leaf_hash, codesep_pos)@ selects the
    480 --   script-path.
    481 taproot_sighash
    482   :: Tx
    483   -> Int
    484   -> [Word64]
    485   -> [BS.ByteString]
    486   -> Maybe BS.ByteString
    487   -> Maybe (BS.ByteString, Word32)
    488   -> Word8
    489   -> Maybe BS.ByteString
    490 taproot_sighash Tx{..} !idx !amts !spks !annex !sp_ext !ht = do
    491   guard (is_valid_taproot_ht ht)
    492   case annex of
    493     Just a  -> guard (not (BS.null a) && BS.index a 0 == 0x50)
    494     Nothing -> pure ()
    495   case sp_ext of
    496     Just (lh, _) -> guard (BS.length lh == 32)
    497     Nothing      -> pure ()
    498 
    499   let !inputs_list  = NE.toList tx_inputs
    500       !outputs_list = NE.toList tx_outputs
    501       !n_inputs     = length inputs_list
    502       !n_outputs    = length outputs_list
    503 
    504   guard (idx >= 0 && idx < n_inputs)
    505   guard (length amts == n_inputs)
    506   guard (length spks == n_inputs)
    507   -- BIP341: SIGHASH_SINGLE without a corresponding output is invalid;
    508   -- reject rather than return a digest no consensus-valid signature
    509   -- could match.
    510   guard (ht .&. 0x03 /= 0x03 || idx < n_outputs)
    511 
    512   signing_input  <- safe_index inputs_list idx
    513   signing_amount <- safe_index amts         idx
    514   signing_spk    <- safe_index spks         idx
    515 
    516   let -- BIP341 maps DEFAULT (0x00) to ALL for output handling.
    517       out_type | ht == 0x00 = 0x01 :: Word8
    518                | otherwise  = ht .&. 0x03
    519       acp           = (ht .&. 0x80) /= 0
    520       annex_present = case annex  of Just _ -> True; Nothing -> False
    521       ext_flag      = case sp_ext of Just _ -> 1;    Nothing -> 0 :: Word8
    522       spend_type    = ext_flag * 2 + (if annex_present then 1 else 0)
    523 
    524       -- Lazily bound: ACP omits these four; NONE/SINGLE omit sha_outputs.
    525       sha_prevouts =
    526         sha (foldMap (put_outpoint . txin_prevout) inputs_list)
    527       sha_amounts       = sha (foldMap put_word64_le amts)
    528       sha_scriptpubkeys = sha (foldMap put_bytes spks)
    529       sha_sequences     =
    530         sha (foldMap (put_word32_le . txin_sequence) inputs_list)
    531       sha_outputs_all   = sha (foldMap put_txout outputs_list)
    532 
    533       sha_annex_bs = case annex of
    534         Just a  -> sha (put_bytes a)
    535         Nothing -> BS.empty
    536 
    537       -- safe_index always succeeds for SINGLE post-guard above; the
    538       -- fallback is defensive and unreachable in practice.
    539       sha_single_output_bs = case safe_index outputs_list idx of
    540         Just o  -> sha (put_txout o)
    541         Nothing -> BS.empty
    542 
    543       msg = to_strict $
    544            BSB.word8 0x00              -- epoch
    545         <> BSB.word8 ht                -- hash_type
    546         <> put_word32_le tx_version
    547         <> put_word32_le tx_locktime
    548         <> (if acp
    549               then mempty
    550               else BSB.byteString sha_prevouts
    551                 <> BSB.byteString sha_amounts
    552                 <> BSB.byteString sha_scriptpubkeys
    553                 <> BSB.byteString sha_sequences)
    554         <> (if out_type == 0x01
    555               then BSB.byteString sha_outputs_all
    556               else mempty)
    557         <> BSB.word8 spend_type
    558         <> (if acp
    559               then put_outpoint   (txin_prevout signing_input)
    560                 <> put_word64_le  signing_amount
    561                 <> put_bytes      signing_spk
    562                 <> put_word32_le  (txin_sequence signing_input)
    563               else put_word32_le (fromIntegral idx))
    564         <> (if annex_present
    565               then BSB.byteString sha_annex_bs
    566               else mempty)
    567         <> (if out_type == 0x03
    568               then BSB.byteString sha_single_output_bs
    569               else mempty)
    570         <> (case sp_ext of
    571               Just (leaf, csep) ->
    572                    BSB.byteString leaf
    573                 <> BSB.word8 0x00       -- key_version
    574                 <> put_word32_le csep
    575               Nothing -> mempty)
    576 
    577   pure $! tap_sighash msg