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