{-# OPTIONS_HADDOCK prune #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

-- |
-- Module: Bitcoin.Prim.Tx
-- Copyright: (c) 2025 Jared Tobin
-- License: MIT
-- Maintainer: Jared Tobin <jared@ppad.tech>
--
-- Minimal Bitcoin transaction primitives, including raw transaction
-- types, serialisation to/from bytes, and txid computation.

module Bitcoin.Prim.Tx (
    -- * Transaction Types
    Tx(..)
  , TxIn(..)
  , TxOut(..)
  , OutPoint(..)
  , Witness(..)
  , TxId(..)
  , mkTxId

    -- * Serialisation
  , to_bytes
  , from_bytes
  , to_bytes_legacy
  , to_base16
  , from_base16

    -- * TxId
  , txid

    -- * Internal (for Sighash)
  , put_word32_le
  , put_word64_le
  , put_compact
  , put_outpoint
  , put_txout
  , to_strict
  ) where

import qualified Crypto.Hash.SHA256 as SHA256
import Data.Bits ((.|.), shiftL)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Base16 as B16
import qualified Data.ByteString.Builder as BSB
import qualified Data.ByteString.Lazy as BL
import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.List.NonEmpty as NE
import Data.Word (Word32, Word64)
import GHC.Generics (Generic)

-- | Transaction ID (32 bytes, little-endian double-SHA256).
newtype TxId = TxId BS.ByteString
  deriving (TxId -> TxId -> Bool
(TxId -> TxId -> Bool) -> (TxId -> TxId -> Bool) -> Eq TxId
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: TxId -> TxId -> Bool
== :: TxId -> TxId -> Bool
$c/= :: TxId -> TxId -> Bool
/= :: TxId -> TxId -> Bool
Eq, Int -> TxId -> ShowS
[TxId] -> ShowS
TxId -> String
(Int -> TxId -> ShowS)
-> (TxId -> String) -> ([TxId] -> ShowS) -> Show TxId
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> TxId -> ShowS
showsPrec :: Int -> TxId -> ShowS
$cshow :: TxId -> String
show :: TxId -> String
$cshowList :: [TxId] -> ShowS
showList :: [TxId] -> ShowS
Show, (forall x. TxId -> Rep TxId x)
-> (forall x. Rep TxId x -> TxId) -> Generic TxId
forall x. Rep TxId x -> TxId
forall x. TxId -> Rep TxId x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. TxId -> Rep TxId x
from :: forall x. TxId -> Rep TxId x
$cto :: forall x. Rep TxId x -> TxId
to :: forall x. Rep TxId x -> TxId
Generic)

-- | Construct a TxId from a 32-byte ByteString.
--
--   Returns 'Nothing' if the input is not exactly 32 bytes.
--
--   @
--   mkTxId (BS.replicate 32 0x00) == Just (TxId ...)
--   mkTxId (BS.replicate 31 0x00) == Nothing
--   @
mkTxId :: BS.ByteString -> Maybe TxId
mkTxId :: ByteString -> Maybe TxId
mkTxId ByteString
bs
  | ByteString -> Int
BS.length ByteString
bs Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
32 = TxId -> Maybe TxId
forall a. a -> Maybe a
Just (ByteString -> TxId
TxId ByteString
bs)
  | Bool
otherwise          = Maybe TxId
forall a. Maybe a
Nothing

-- | Transaction outpoint (txid + output index).
data OutPoint = OutPoint
  { OutPoint -> TxId
op_txid  :: {-# UNPACK #-} !TxId
  , OutPoint -> Word32
op_vout  :: {-# UNPACK #-} !Word32
  } deriving (OutPoint -> OutPoint -> Bool
(OutPoint -> OutPoint -> Bool)
-> (OutPoint -> OutPoint -> Bool) -> Eq OutPoint
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: OutPoint -> OutPoint -> Bool
== :: OutPoint -> OutPoint -> Bool
$c/= :: OutPoint -> OutPoint -> Bool
/= :: OutPoint -> OutPoint -> Bool
Eq, Int -> OutPoint -> ShowS
[OutPoint] -> ShowS
OutPoint -> String
(Int -> OutPoint -> ShowS)
-> (OutPoint -> String) -> ([OutPoint] -> ShowS) -> Show OutPoint
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> OutPoint -> ShowS
showsPrec :: Int -> OutPoint -> ShowS
$cshow :: OutPoint -> String
show :: OutPoint -> String
$cshowList :: [OutPoint] -> ShowS
showList :: [OutPoint] -> ShowS
Show, (forall x. OutPoint -> Rep OutPoint x)
-> (forall x. Rep OutPoint x -> OutPoint) -> Generic OutPoint
forall x. Rep OutPoint x -> OutPoint
forall x. OutPoint -> Rep OutPoint x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. OutPoint -> Rep OutPoint x
from :: forall x. OutPoint -> Rep OutPoint x
$cto :: forall x. Rep OutPoint x -> OutPoint
to :: forall x. Rep OutPoint x -> OutPoint
Generic)

-- | Transaction input.
data TxIn = TxIn
  { TxIn -> OutPoint
txin_prevout    :: {-# UNPACK #-} !OutPoint
  , TxIn -> ByteString
txin_script_sig :: !BS.ByteString
  , TxIn -> Word32
txin_sequence   :: {-# UNPACK #-} !Word32
  } deriving (TxIn -> TxIn -> Bool
(TxIn -> TxIn -> Bool) -> (TxIn -> TxIn -> Bool) -> Eq TxIn
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: TxIn -> TxIn -> Bool
== :: TxIn -> TxIn -> Bool
$c/= :: TxIn -> TxIn -> Bool
/= :: TxIn -> TxIn -> Bool
Eq, Int -> TxIn -> ShowS
[TxIn] -> ShowS
TxIn -> String
(Int -> TxIn -> ShowS)
-> (TxIn -> String) -> ([TxIn] -> ShowS) -> Show TxIn
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> TxIn -> ShowS
showsPrec :: Int -> TxIn -> ShowS
$cshow :: TxIn -> String
show :: TxIn -> String
$cshowList :: [TxIn] -> ShowS
showList :: [TxIn] -> ShowS
Show, (forall x. TxIn -> Rep TxIn x)
-> (forall x. Rep TxIn x -> TxIn) -> Generic TxIn
forall x. Rep TxIn x -> TxIn
forall x. TxIn -> Rep TxIn x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. TxIn -> Rep TxIn x
from :: forall x. TxIn -> Rep TxIn x
$cto :: forall x. Rep TxIn x -> TxIn
to :: forall x. Rep TxIn x -> TxIn
Generic)

-- | Transaction output.
data TxOut = TxOut
  { TxOut -> Word64
txout_value         :: {-# UNPACK #-} !Word64  -- ^ satoshis
  , TxOut -> ByteString
txout_script_pubkey :: !BS.ByteString
  } deriving (TxOut -> TxOut -> Bool
(TxOut -> TxOut -> Bool) -> (TxOut -> TxOut -> Bool) -> Eq TxOut
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: TxOut -> TxOut -> Bool
== :: TxOut -> TxOut -> Bool
$c/= :: TxOut -> TxOut -> Bool
/= :: TxOut -> TxOut -> Bool
Eq, Int -> TxOut -> ShowS
[TxOut] -> ShowS
TxOut -> String
(Int -> TxOut -> ShowS)
-> (TxOut -> String) -> ([TxOut] -> ShowS) -> Show TxOut
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> TxOut -> ShowS
showsPrec :: Int -> TxOut -> ShowS
$cshow :: TxOut -> String
show :: TxOut -> String
$cshowList :: [TxOut] -> ShowS
showList :: [TxOut] -> ShowS
Show, (forall x. TxOut -> Rep TxOut x)
-> (forall x. Rep TxOut x -> TxOut) -> Generic TxOut
forall x. Rep TxOut x -> TxOut
forall x. TxOut -> Rep TxOut x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. TxOut -> Rep TxOut x
from :: forall x. TxOut -> Rep TxOut x
$cto :: forall x. Rep TxOut x -> TxOut
to :: forall x. Rep TxOut x -> TxOut
Generic)

-- | Witness stack for a single input.
newtype Witness = Witness [BS.ByteString]
  deriving (Witness -> Witness -> Bool
(Witness -> Witness -> Bool)
-> (Witness -> Witness -> Bool) -> Eq Witness
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Witness -> Witness -> Bool
== :: Witness -> Witness -> Bool
$c/= :: Witness -> Witness -> Bool
/= :: Witness -> Witness -> Bool
Eq, Int -> Witness -> ShowS
[Witness] -> ShowS
Witness -> String
(Int -> Witness -> ShowS)
-> (Witness -> String) -> ([Witness] -> ShowS) -> Show Witness
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Witness -> ShowS
showsPrec :: Int -> Witness -> ShowS
$cshow :: Witness -> String
show :: Witness -> String
$cshowList :: [Witness] -> ShowS
showList :: [Witness] -> ShowS
Show, (forall x. Witness -> Rep Witness x)
-> (forall x. Rep Witness x -> Witness) -> Generic Witness
forall x. Rep Witness x -> Witness
forall x. Witness -> Rep Witness x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. Witness -> Rep Witness x
from :: forall x. Witness -> Rep Witness x
$cto :: forall x. Rep Witness x -> Witness
to :: forall x. Rep Witness x -> Witness
Generic)

-- | Complete transaction.
--
--   Bitcoin requires at least one input and one output, enforced here
--   via 'NonEmpty' lists.
data Tx = Tx
  { Tx -> Word32
tx_version   :: {-# UNPACK #-} !Word32
  , Tx -> NonEmpty TxIn
tx_inputs    :: !(NonEmpty TxIn)
  , Tx -> NonEmpty TxOut
tx_outputs   :: !(NonEmpty TxOut)
  , Tx -> [Witness]
tx_witnesses :: ![Witness]  -- ^ empty list for legacy tx
  , Tx -> Word32
tx_locktime  :: {-# UNPACK #-} !Word32
  } deriving (Tx -> Tx -> Bool
(Tx -> Tx -> Bool) -> (Tx -> Tx -> Bool) -> Eq Tx
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Tx -> Tx -> Bool
== :: Tx -> Tx -> Bool
$c/= :: Tx -> Tx -> Bool
/= :: Tx -> Tx -> Bool
Eq, Int -> Tx -> ShowS
[Tx] -> ShowS
Tx -> String
(Int -> Tx -> ShowS)
-> (Tx -> String) -> ([Tx] -> ShowS) -> Show Tx
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Tx -> ShowS
showsPrec :: Int -> Tx -> ShowS
$cshow :: Tx -> String
show :: Tx -> String
$cshowList :: [Tx] -> ShowS
showList :: [Tx] -> ShowS
Show, (forall x. Tx -> Rep Tx x)
-> (forall x. Rep Tx x -> Tx) -> Generic Tx
forall x. Rep Tx x -> Tx
forall x. Tx -> Rep Tx x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. Tx -> Rep Tx x
from :: forall x. Tx -> Rep Tx x
$cto :: forall x. Rep Tx x -> Tx
to :: forall x. Rep Tx x -> Tx
Generic)

-- serialisation ---------------------------------------------------------------

-- | Serialise a transaction to bytes.
--
--   Uses segwit format if witnesses are present, legacy otherwise.
--
--   @
--   -- round-trip
--   from_bytes (to_bytes tx) == Just tx
--   @
to_bytes :: Tx -> BS.ByteString
to_bytes :: Tx -> ByteString
to_bytes tx :: Tx
tx@Tx {[Witness]
Word32
NonEmpty TxOut
NonEmpty TxIn
tx_version :: Tx -> Word32
tx_inputs :: Tx -> NonEmpty TxIn
tx_outputs :: Tx -> NonEmpty TxOut
tx_witnesses :: Tx -> [Witness]
tx_locktime :: Tx -> Word32
tx_version :: Word32
tx_inputs :: NonEmpty TxIn
tx_outputs :: NonEmpty TxOut
tx_witnesses :: [Witness]
tx_locktime :: Word32
..}
    | [Witness] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Witness]
tx_witnesses = Tx -> ByteString
to_bytes_legacy Tx
tx
    | Bool
otherwise         = Builder -> ByteString
to_strict (Builder -> ByteString) -> Builder -> ByteString
forall a b. (a -> b) -> a -> b
$
           Word32 -> Builder
put_word32_le Word32
tx_version
        Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Word8 -> Builder
BSB.word8 Word8
0x00  -- marker
        Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Word8 -> Builder
BSB.word8 Word8
0x01  -- flag
        Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Word64 -> Builder
put_compact (Int -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (NonEmpty TxIn -> Int
forall a. NonEmpty a -> Int
NE.length NonEmpty TxIn
tx_inputs))
        Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> (TxIn -> Builder) -> NonEmpty TxIn -> Builder
forall m a. Monoid m => (a -> m) -> NonEmpty a -> m
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap TxIn -> Builder
put_txin NonEmpty TxIn
tx_inputs
        Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Word64 -> Builder
put_compact (Int -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (NonEmpty TxOut -> Int
forall a. NonEmpty a -> Int
NE.length NonEmpty TxOut
tx_outputs))
        Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> (TxOut -> Builder) -> NonEmpty TxOut -> Builder
forall m a. Monoid m => (a -> m) -> NonEmpty a -> m
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap TxOut -> Builder
put_txout NonEmpty TxOut
tx_outputs
        Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> (Witness -> Builder) -> [Witness] -> Builder
forall m a. Monoid m => (a -> m) -> [a] -> m
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap Witness -> Builder
put_witness [Witness]
tx_witnesses
        Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Word32 -> Builder
put_word32_le Word32
tx_locktime

-- | Serialise a transaction to legacy format (no witness data).
--
--   Used for txid computation. Excludes witness data even if present.
--
--   @
--   -- for legacy tx (no witnesses), same as to_bytes
--   to_bytes_legacy legacyTx == to_bytes legacyTx
--
--   -- for segwit tx, strips witnesses
--   BS.length (to_bytes_legacy segwitTx) < BS.length (to_bytes segwitTx)
--   @
to_bytes_legacy :: Tx -> BS.ByteString
to_bytes_legacy :: Tx -> ByteString
to_bytes_legacy Tx {[Witness]
Word32
NonEmpty TxOut
NonEmpty TxIn
tx_version :: Tx -> Word32
tx_inputs :: Tx -> NonEmpty TxIn
tx_outputs :: Tx -> NonEmpty TxOut
tx_witnesses :: Tx -> [Witness]
tx_locktime :: Tx -> Word32
tx_version :: Word32
tx_inputs :: NonEmpty TxIn
tx_outputs :: NonEmpty TxOut
tx_witnesses :: [Witness]
tx_locktime :: Word32
..} = Builder -> ByteString
to_strict (Builder -> ByteString) -> Builder -> ByteString
forall a b. (a -> b) -> a -> b
$
       Word32 -> Builder
put_word32_le Word32
tx_version
    Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Word64 -> Builder
put_compact (Int -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (NonEmpty TxIn -> Int
forall a. NonEmpty a -> Int
NE.length NonEmpty TxIn
tx_inputs))
    Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> (TxIn -> Builder) -> NonEmpty TxIn -> Builder
forall m a. Monoid m => (a -> m) -> NonEmpty a -> m
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap TxIn -> Builder
put_txin NonEmpty TxIn
tx_inputs
    Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Word64 -> Builder
put_compact (Int -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (NonEmpty TxOut -> Int
forall a. NonEmpty a -> Int
NE.length NonEmpty TxOut
tx_outputs))
    Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> (TxOut -> Builder) -> NonEmpty TxOut -> Builder
forall m a. Monoid m => (a -> m) -> NonEmpty a -> m
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap TxOut -> Builder
put_txout NonEmpty TxOut
tx_outputs
    Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Word32 -> Builder
put_word32_le Word32
tx_locktime

-- | Serialise a transaction to base16 (hex).
--
--   @
--   to_base16 tx = B16.encode (to_bytes tx)
--   @
to_base16 :: Tx -> BS.ByteString
to_base16 :: Tx -> ByteString
to_base16 Tx
tx = ByteString -> ByteString
B16.encode (Tx -> ByteString
to_bytes Tx
tx)

-- | Parse a transaction from base16 (hex).
--
--   @
--   -- round-trip
--   from_base16 (to_base16 tx) == Just tx
--   @
from_base16 :: BS.ByteString -> Maybe Tx
from_base16 :: ByteString -> Maybe Tx
from_base16 ByteString
b16 = do
  bs <- ByteString -> Maybe ByteString
B16.decode ByteString
b16
  from_bytes bs

-- internal: builders ----------------------------------------------------------

-- | Convert a Builder to a strict ByteString.
to_strict :: BSB.Builder -> BS.ByteString
to_strict :: Builder -> ByteString
to_strict = LazyByteString -> ByteString
BL.toStrict (LazyByteString -> ByteString)
-> (Builder -> LazyByteString) -> Builder -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Builder -> LazyByteString
BSB.toLazyByteString
{-# INLINE to_strict #-}

-- | Encode a Word32 as little-endian bytes.
put_word32_le :: Word32 -> BSB.Builder
put_word32_le :: Word32 -> Builder
put_word32_le = Word32 -> Builder
BSB.word32LE
{-# INLINE put_word32_le #-}

-- | Encode a Word64 as little-endian bytes.
put_word64_le :: Word64 -> BSB.Builder
put_word64_le :: Word64 -> Builder
put_word64_le = Word64 -> Builder
BSB.word64LE
{-# INLINE put_word64_le #-}

-- | Encode a Word64 as Bitcoin compactSize (varint).
--
--   Encoding:
--   - 0x00-0xfc: 1 byte (value itself)
--   - 0xfd-0xffff: 0xfd ++ 2 bytes LE
--   - 0x10000-0xffffffff: 0xfe ++ 4 bytes LE
--   - larger: 0xff ++ 8 bytes LE
put_compact :: Word64 -> BSB.Builder
put_compact :: Word64 -> Builder
put_compact !Word64
n
    | Word64
n Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word64
0xfc       = Word8 -> Builder
BSB.word8 (Word64 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
n)
    | Word64
n Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word64
0xffff     = Word8 -> Builder
BSB.word8 Word8
0xfd Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Word16 -> Builder
BSB.word16LE (Word64 -> Word16
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
n)
    | Word64
n Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word64
0xffffffff = Word8 -> Builder
BSB.word8 Word8
0xfe Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Word32 -> Builder
BSB.word32LE (Word64 -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
n)
    | Bool
otherwise       = Word8 -> Builder
BSB.word8 Word8
0xff Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Word64 -> Builder
BSB.word64LE Word64
n
{-# INLINE put_compact #-}

-- | Encode an OutPoint (txid + vout).
put_outpoint :: OutPoint -> BSB.Builder
put_outpoint :: OutPoint -> Builder
put_outpoint OutPoint {Word32
TxId
op_txid :: OutPoint -> TxId
op_vout :: OutPoint -> Word32
op_txid :: TxId
op_vout :: Word32
..} =
    let !(TxId !ByteString
txid_bs) = TxId
op_txid
    in  ByteString -> Builder
BSB.byteString ByteString
txid_bs Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Word32 -> Builder
put_word32_le Word32
op_vout
{-# INLINE put_outpoint #-}

-- | Encode a TxIn.
put_txin :: TxIn -> BSB.Builder
put_txin :: TxIn -> Builder
put_txin TxIn {Word32
ByteString
OutPoint
txin_prevout :: TxIn -> OutPoint
txin_script_sig :: TxIn -> ByteString
txin_sequence :: TxIn -> Word32
txin_prevout :: OutPoint
txin_script_sig :: ByteString
txin_sequence :: Word32
..} =
       OutPoint -> Builder
put_outpoint OutPoint
txin_prevout
    Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Word64 -> Builder
put_compact (Int -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int
BS.length ByteString
txin_script_sig))
    Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> ByteString -> Builder
BSB.byteString ByteString
txin_script_sig
    Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Word32 -> Builder
put_word32_le Word32
txin_sequence
{-# INLINE put_txin #-}

-- | Encode a TxOut.
put_txout :: TxOut -> BSB.Builder
put_txout :: TxOut -> Builder
put_txout TxOut {Word64
ByteString
txout_value :: TxOut -> Word64
txout_script_pubkey :: TxOut -> ByteString
txout_value :: Word64
txout_script_pubkey :: ByteString
..} =
       Word64 -> Builder
put_word64_le Word64
txout_value
    Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> Word64 -> Builder
put_compact (Int -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int
BS.length ByteString
txout_script_pubkey))
    Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> ByteString -> Builder
BSB.byteString ByteString
txout_script_pubkey
{-# INLINE put_txout #-}

-- | Encode a Witness stack.
put_witness :: Witness -> BSB.Builder
put_witness :: Witness -> Builder
put_witness (Witness [ByteString]
items) =
       Word64 -> Builder
put_compact (Int -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral ([ByteString] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [ByteString]
items))
    Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> (ByteString -> Builder) -> [ByteString] -> Builder
forall m a. Monoid m => (a -> m) -> [a] -> m
forall (t :: * -> *) m a.
(Foldable t, Monoid m) =>
(a -> m) -> t a -> m
foldMap ByteString -> Builder
put_witness_item [ByteString]
items
  where
    put_witness_item :: BS.ByteString -> BSB.Builder
    put_witness_item :: ByteString -> Builder
put_witness_item !ByteString
item =
           Word64 -> Builder
put_compact (Int -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int
BS.length ByteString
item))
        Builder -> Builder -> Builder
forall a. Semigroup a => a -> a -> a
<> ByteString -> Builder
BSB.byteString ByteString
item
{-# INLINE put_witness #-}

-- decoding --------------------------------------------------------------------

-- | Parse a transaction from bytes.
--
--   Automatically detects segwit vs legacy format by checking for
--   marker byte 0x00 followed by flag 0x01 after the version field.
--
--   Returns 'Nothing' on invalid or truncated input.
--
--   @
--   -- round-trip
--   from_bytes (to_bytes tx) == Just tx
--   @
from_bytes :: BS.ByteString -> Maybe Tx
from_bytes :: ByteString -> Maybe Tx
from_bytes !ByteString
bs = do
  -- need at least 4 bytes for version
  Bool -> Maybe ()
guard (ByteString -> Int
BS.length ByteString
bs Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
4)
  let !version :: Word32
version = ByteString -> Int -> Word32
get_word32_le ByteString
bs Int
0
      !off0 :: Int
off0 = Int
4
  -- check for segwit marker (0x00) and flag (0x01)
  if   ByteString -> Int
BS.length ByteString
bs Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
off0 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1
    Bool -> Bool -> Bool
&& HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
off0 Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0x00
    Bool -> Bool -> Bool
&& HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs (Int
off0 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1) Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0x01
  then ByteString -> Word32 -> Int -> Maybe Tx
parse_segwit ByteString
bs Word32
version (Int
off0 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
2)
  else ByteString -> Word32 -> Int -> Maybe Tx
parse_legacy ByteString
bs Word32
version Int
off0

-- Parse legacy transaction (no witness data)
parse_legacy :: BS.ByteString -> Word32 -> Int -> Maybe Tx
parse_legacy :: ByteString -> Word32 -> Int -> Maybe Tx
parse_legacy !ByteString
bs !Word32
version !Int
off0 = do
  -- input count
  (input_count, off1) <- ByteString -> Int -> Maybe (Word64, Int)
get_compact ByteString
bs Int
off0
  -- inputs (must have at least one)
  (inputs_list, off2) <- get_many get_txin bs off1 (fromIntegral input_count)
  inputs <- NE.nonEmpty inputs_list
  -- output count
  (output_count, off3) <- get_compact bs off2
  -- outputs (must have at least one)
  (outputs_list, off4) <- get_many get_txout bs off3 (fromIntegral output_count)
  outputs <- NE.nonEmpty outputs_list
  -- locktime (4 bytes)
  guard (BS.length bs >= off4 + 4)
  let !locktime = ByteString -> Int -> Word32
get_word32_le ByteString
bs Int
off4
      !off5 = Int
off4 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
4
  -- should have consumed all bytes
  guard (off5 == BS.length bs)
  pure $! Tx version inputs outputs [] locktime

-- Parse segwit transaction (with witness data)
parse_segwit :: BS.ByteString -> Word32 -> Int -> Maybe Tx
parse_segwit :: ByteString -> Word32 -> Int -> Maybe Tx
parse_segwit !ByteString
bs !Word32
version !Int
off0 = do
  -- input count
  (input_count, off1) <- ByteString -> Int -> Maybe (Word64, Int)
get_compact ByteString
bs Int
off0
  -- inputs (must have at least one)
  (inputs_list, off2) <- get_many get_txin bs off1 (fromIntegral input_count)
  inputs <- NE.nonEmpty inputs_list
  -- output count
  (output_count, off3) <- get_compact bs off2
  -- outputs (must have at least one)
  (outputs_list, off4) <- get_many get_txout bs off3 (fromIntegral output_count)
  outputs <- NE.nonEmpty outputs_list
  -- witnesses (one per input)
  (witnesses, off5) <- get_many get_witness bs off4 (fromIntegral input_count)
  -- locktime (4 bytes)
  guard (BS.length bs >= off5 + 4)
  let !locktime = ByteString -> Int -> Word32
get_word32_le ByteString
bs Int
off5
      !off6 = Int
off5 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
4
  -- should have consumed all bytes
  guard (off6 == BS.length bs)
  pure $! Tx version inputs outputs witnesses locktime

-- internal helpers ------------------------------------------------------------

-- | Guard for Maybe monad.
guard :: Bool -> Maybe ()
guard :: Bool -> Maybe ()
guard Bool
True  = () -> Maybe ()
forall a. a -> Maybe a
Just ()
guard Bool
False = Maybe ()
forall a. Maybe a
Nothing
{-# INLINE guard #-}

-- | Decode a 32-bit little-endian word at the given offset.
--   Does not bounds-check; caller must ensure sufficient bytes.
get_word32_le :: BS.ByteString -> Int -> Word32
get_word32_le :: ByteString -> Int -> Word32
get_word32_le !ByteString
bs !Int
off =
  let !b0 :: Word32
b0 = Word8 -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
off) :: Word32
      !b1 :: Word32
b1 = Word8 -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs (Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1)) :: Word32
      !b2 :: Word32
b2 = Word8 -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs (Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
2)) :: Word32
      !b3 :: Word32
b3 = Word8 -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs (Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
3)) :: Word32
  in  Word32
b0 Word32 -> Word32 -> Word32
forall a. Bits a => a -> a -> a
.|. (Word32
b1 Word32 -> Int -> Word32
forall a. Bits a => a -> Int -> a
`shiftL` Int
8) Word32 -> Word32 -> Word32
forall a. Bits a => a -> a -> a
.|. (Word32
b2 Word32 -> Int -> Word32
forall a. Bits a => a -> Int -> a
`shiftL` Int
16) Word32 -> Word32 -> Word32
forall a. Bits a => a -> a -> a
.|. (Word32
b3 Word32 -> Int -> Word32
forall a. Bits a => a -> Int -> a
`shiftL` Int
24)
{-# INLINE get_word32_le #-}

-- | Decode a 64-bit little-endian word at the given offset.
--   Does not bounds-check; caller must ensure sufficient bytes.
get_word64_le :: BS.ByteString -> Int -> Word64
get_word64_le :: ByteString -> Int -> Word64
get_word64_le !ByteString
bs !Int
off =
  let !b0 :: Word64
b0 = Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
off) :: Word64
      !b1 :: Word64
b1 = Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs (Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1)) :: Word64
      !b2 :: Word64
b2 = Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs (Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
2)) :: Word64
      !b3 :: Word64
b3 = Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs (Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
3)) :: Word64
      !b4 :: Word64
b4 = Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs (Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
4)) :: Word64
      !b5 :: Word64
b5 = Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs (Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
5)) :: Word64
      !b6 :: Word64
b6 = Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs (Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
6)) :: Word64
      !b7 :: Word64
b7 = Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs (Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
7)) :: Word64
  in  Word64
b0 Word64 -> Word64 -> Word64
forall a. Bits a => a -> a -> a
.|. (Word64
b1 Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`shiftL` Int
8) Word64 -> Word64 -> Word64
forall a. Bits a => a -> a -> a
.|. (Word64
b2 Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`shiftL` Int
16) Word64 -> Word64 -> Word64
forall a. Bits a => a -> a -> a
.|. (Word64
b3 Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`shiftL` Int
24)
          Word64 -> Word64 -> Word64
forall a. Bits a => a -> a -> a
.|. (Word64
b4 Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`shiftL` Int
32) Word64 -> Word64 -> Word64
forall a. Bits a => a -> a -> a
.|. (Word64
b5 Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`shiftL` Int
40)
          Word64 -> Word64 -> Word64
forall a. Bits a => a -> a -> a
.|. (Word64
b6 Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`shiftL` Int
48) Word64 -> Word64 -> Word64
forall a. Bits a => a -> a -> a
.|. (Word64
b7 Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`shiftL` Int
56)
{-# INLINE get_word64_le #-}

-- | Decode a 16-bit little-endian word at the given offset.
--   Does not bounds-check; caller must ensure sufficient bytes.
get_word16_le :: BS.ByteString -> Int -> Word64
get_word16_le :: ByteString -> Int -> Word64
get_word16_le !ByteString
bs !Int
off =
  let !b0 :: Word64
b0 = Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
off) :: Word64
      !b1 :: Word64
b1 = Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs (Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1)) :: Word64
  in  Word64
b0 Word64 -> Word64 -> Word64
forall a. Bits a => a -> a -> a
.|. (Word64
b1 Word64 -> Int -> Word64
forall a. Bits a => a -> Int -> a
`shiftL` Int
8)
{-# INLINE get_word16_le #-}

-- | Decode compactSize (Bitcoin's variable-length integer).
--   Returns (value, new_offset).
--   Enforces minimal encoding: rejects non-minimal representations.
get_compact :: BS.ByteString -> Int -> Maybe (Word64, Int)
get_compact :: ByteString -> Int -> Maybe (Word64, Int)
get_compact !ByteString
bs !Int
off
  | Int
off Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= ByteString -> Int
BS.length ByteString
bs = Maybe (Word64, Int)
forall a. Maybe a
Nothing
  | Bool
otherwise = case HasCallStack => ByteString -> Int -> Word8
ByteString -> Int -> Word8
BS.index ByteString
bs Int
off of
      Word8
tag | Word8
tag Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word8
0xfc ->
        -- Single byte: value is the tag itself
        (Word64, Int) -> Maybe (Word64, Int)
forall a. a -> Maybe a
Just (Word8 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word8
tag, Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1)

      Word8
0xfd ->
        -- 2-byte value follows
        if ByteString -> Int
BS.length ByteString
bs Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
3
        then Maybe (Word64, Int)
forall a. Maybe a
Nothing
        else
          let !val :: Word64
val = ByteString -> Int -> Word64
get_word16_le ByteString
bs (Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1)
          in  if Word64
val Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
< Word64
0xfd
              then Maybe (Word64, Int)
forall a. Maybe a
Nothing  -- non-minimal encoding
              else (Word64, Int) -> Maybe (Word64, Int)
forall a. a -> Maybe a
Just (Word64
val, Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
3)

      Word8
0xfe ->
        -- 4-byte value follows
        if ByteString -> Int
BS.length ByteString
bs Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
5
        then Maybe (Word64, Int)
forall a. Maybe a
Nothing
        else
          let !val :: Word64
val = Word32 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ByteString -> Int -> Word32
get_word32_le ByteString
bs (Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1)) :: Word64
          in  if Word64
val Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word64
0xffff
              then Maybe (Word64, Int)
forall a. Maybe a
Nothing  -- non-minimal encoding
              else (Word64, Int) -> Maybe (Word64, Int)
forall a. a -> Maybe a
Just (Word64
val, Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
5)

      Word8
_ -> -- 0xff
        -- 8-byte value follows
        if ByteString -> Int
BS.length ByteString
bs Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
9
        then Maybe (Word64, Int)
forall a. Maybe a
Nothing
        else
          let !val :: Word64
val = ByteString -> Int -> Word64
get_word64_le ByteString
bs (Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1)
          in  if Word64
val Word64 -> Word64 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word64
0xffffffff
              then Maybe (Word64, Int)
forall a. Maybe a
Nothing  -- non-minimal encoding
              else (Word64, Int) -> Maybe (Word64, Int)
forall a. a -> Maybe a
Just (Word64
val, Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
9)
{-# INLINE get_compact #-}

-- | Decode an outpoint (txid + vout).
--   Returns (OutPoint, new_offset).
get_outpoint :: BS.ByteString -> Int -> Maybe (OutPoint, Int)
get_outpoint :: ByteString -> Int -> Maybe (OutPoint, Int)
get_outpoint !ByteString
bs !Int
off
  | ByteString -> Int
BS.length ByteString
bs Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
36 = Maybe (OutPoint, Int)
forall a. Maybe a
Nothing
  | Bool
otherwise =
      let !txid_bytes :: ByteString
txid_bytes = Int -> ByteString -> ByteString
BS.take Int
32 (Int -> ByteString -> ByteString
BS.drop Int
off ByteString
bs)
          !vout :: Word32
vout = ByteString -> Int -> Word32
get_word32_le ByteString
bs (Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
32)
      in  (OutPoint, Int) -> Maybe (OutPoint, Int)
forall a. a -> Maybe a
Just (TxId -> Word32 -> OutPoint
OutPoint (ByteString -> TxId
TxId ByteString
txid_bytes) Word32
vout, Int
off Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
36)
{-# INLINE get_outpoint #-}

-- | Decode a transaction input.
--   Returns (TxIn, new_offset).
get_txin :: BS.ByteString -> Int -> Maybe (TxIn, Int)
get_txin :: ByteString -> Int -> Maybe (TxIn, Int)
get_txin !ByteString
bs !Int
off0 = do
  -- outpoint: 36 bytes
  (outpoint, off1) <- ByteString -> Int -> Maybe (OutPoint, Int)
get_outpoint ByteString
bs Int
off0
  -- scriptSig length + bytes
  (script_len, off2) <- get_compact bs off1
  let !slen = Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
script_len
  guard (BS.length bs >= off2 + slen)
  let !script_sig = Int -> ByteString -> ByteString
BS.take Int
slen (Int -> ByteString -> ByteString
BS.drop Int
off2 ByteString
bs)
      !off3 = Int
off2 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
slen
  -- sequence: 4 bytes
  guard (BS.length bs >= off3 + 4)
  let !seqn = ByteString -> Int -> Word32
get_word32_le ByteString
bs Int
off3
      !off4 = Int
off3 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
4
  pure (TxIn outpoint script_sig seqn, off4)

-- | Decode a transaction output.
--   Returns (TxOut, new_offset).
get_txout :: BS.ByteString -> Int -> Maybe (TxOut, Int)
get_txout :: ByteString -> Int -> Maybe (TxOut, Int)
get_txout !ByteString
bs !Int
off0 = do
  -- value: 8 bytes
  Bool -> Maybe ()
guard (ByteString -> Int
BS.length ByteString
bs Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
off0 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
8)
  let !value :: Word64
value = ByteString -> Int -> Word64
get_word64_le ByteString
bs Int
off0
      !off1 :: Int
off1 = Int
off0 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
8
  -- scriptPubKey length + bytes
  (script_len, off2) <- ByteString -> Int -> Maybe (Word64, Int)
get_compact ByteString
bs Int
off1
  let !slen = Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
script_len
  guard (BS.length bs >= off2 + slen)
  let !script_pk = Int -> ByteString -> ByteString
BS.take Int
slen (Int -> ByteString -> ByteString
BS.drop Int
off2 ByteString
bs)
      !off3 = Int
off2 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
slen
  pure (TxOut value script_pk, off3)

-- | Decode a witness stack for one input.
--   Returns (Witness, new_offset).
get_witness :: BS.ByteString -> Int -> Maybe (Witness, Int)
get_witness :: ByteString -> Int -> Maybe (Witness, Int)
get_witness !ByteString
bs !Int
off0 = do
  -- stack item count
  (item_count, off1) <- ByteString -> Int -> Maybe (Word64, Int)
get_compact ByteString
bs Int
off0
  -- each item: length + bytes
  (items, off2) <- get_many get_witness_item bs off1 (fromIntegral item_count)
  pure (Witness items, off2)

-- | Decode a single witness stack item (length-prefixed bytes).
get_witness_item :: BS.ByteString -> Int -> Maybe (BS.ByteString, Int)
get_witness_item :: ByteString -> Int -> Maybe (ByteString, Int)
get_witness_item !ByteString
bs !Int
off0 = do
  (item_len, off1) <- ByteString -> Int -> Maybe (Word64, Int)
get_compact ByteString
bs Int
off0
  let !ilen = Word64 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
item_len
  guard (BS.length bs >= off1 + ilen)
  let !item = Int -> ByteString -> ByteString
BS.take Int
ilen (Int -> ByteString -> ByteString
BS.drop Int
off1 ByteString
bs)
  pure (item, off1 + ilen)

-- | Decode multiple items using a decoder function.
--   Returns (list of items, new_offset).
get_many :: (BS.ByteString -> Int -> Maybe (a, Int))
         -> BS.ByteString -> Int -> Int -> Maybe ([a], Int)
get_many :: forall a.
(ByteString -> Int -> Maybe (a, Int))
-> ByteString -> Int -> Int -> Maybe ([a], Int)
get_many ByteString -> Int -> Maybe (a, Int)
getter !ByteString
bs = [a] -> Int -> Int -> Maybe ([a], Int)
forall {t}. (Ord t, Num t) => [a] -> Int -> t -> Maybe ([a], Int)
go []
  where
    go :: [a] -> Int -> t -> Maybe ([a], Int)
go ![a]
acc !Int
off !t
n
      | t
n t -> t -> Bool
forall a. Ord a => a -> a -> Bool
<= t
0    = ([a], Int) -> Maybe ([a], Int)
forall a. a -> Maybe a
Just ([a] -> [a]
forall a. [a] -> [a]
reverse [a]
acc, Int
off)
      | Bool
otherwise = do
          (item, off') <- ByteString -> Int -> Maybe (a, Int)
getter ByteString
bs Int
off
          go (item : acc) off' (n - 1)
{-# INLINE get_many #-}

-- txid ------------------------------------------------------------------------

-- | Compute the transaction ID (double SHA256 of legacy serialisation).
--
--   The txid is computed from the legacy serialisation, so segwit
--   transactions have the same txid regardless of witness data.
--
--   @
--   -- Satoshi->Hal tx (block 170)
--   txid satoshiHalTx ==
--     TxId "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"
--   @
txid :: Tx -> TxId
txid :: Tx -> TxId
txid Tx
tx = ByteString -> TxId
TxId (ByteString -> ByteString
SHA256.hash (ByteString -> ByteString
SHA256.hash (Tx -> ByteString
to_bytes_legacy Tx
tx)))