tx

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

Internal.hs (4810B)


      1 {-# OPTIONS_HADDOCK hide #-}
      2 {-# LANGUAGE BangPatterns #-}
      3 {-# LANGUAGE DeriveGeneric #-}
      4 {-# LANGUAGE RecordWildCards #-}
      5 
      6 -- |
      7 -- Module: Bitcoin.Prim.Tx.Internal
      8 -- Copyright: (c) 2025 Jared Tobin
      9 -- License: MIT
     10 -- Maintainer: Jared Tobin <jared@ppad.tech>
     11 --
     12 -- Transaction types and serialisation builders, shared by the public
     13 -- modules.
     14 
     15 module Bitcoin.Prim.Tx.Internal (
     16     -- * Transaction types
     17     TxId(..)
     18   , OutPoint(..)
     19   , TxIn(..)
     20   , TxOut(..)
     21   , Witness(..)
     22   , Tx(..)
     23 
     24     -- * Builders
     25   , to_strict
     26   , put_word32_le
     27   , put_word64_le
     28   , put_compact
     29   , put_bytes
     30   , put_outpoint
     31   , put_txin
     32   , put_txout
     33   , put_witness
     34   ) where
     35 
     36 import Control.DeepSeq (NFData(..))
     37 import qualified Data.ByteString as BS
     38 import qualified Data.ByteString.Builder as BSB
     39 import qualified Data.ByteString.Lazy as BL
     40 import Data.List.NonEmpty (NonEmpty(..))
     41 import Data.Word (Word32, Word64)
     42 import GHC.Generics (Generic)
     43 
     44 -- types ----------------------------------------------------------------------
     45 
     46 -- | Transaction ID: 32 bytes, the double-SHA256 of the transaction's
     47 --   legacy serialisation, in internal byte order (the reverse of the
     48 --   usual hex display).
     49 newtype TxId = TxId BS.ByteString
     50   deriving (Eq, Ord, Show, Generic)
     51 
     52 instance NFData TxId
     53 
     54 -- | Transaction outpoint (txid + output index).
     55 data OutPoint = OutPoint
     56   { op_txid  :: {-# UNPACK #-} !TxId
     57   , op_vout  :: {-# UNPACK #-} !Word32
     58   } deriving (Eq, Ord, Show, Generic)
     59 
     60 instance NFData OutPoint
     61 
     62 -- | Witness stack for a single input (empty for a non-witness input).
     63 newtype Witness = Witness [BS.ByteString]
     64   deriving (Eq, Show, Generic)
     65 
     66 instance NFData Witness
     67 
     68 -- | Transaction input.
     69 data TxIn = TxIn
     70   { txin_prevout    :: {-# UNPACK #-} !OutPoint
     71   , txin_script_sig :: !BS.ByteString
     72   , txin_sequence   :: {-# UNPACK #-} !Word32
     73   , txin_witness    :: !Witness
     74   } deriving (Eq, Show, Generic)
     75 
     76 instance NFData TxIn
     77 
     78 -- | Transaction output.
     79 data TxOut = TxOut
     80   { txout_value         :: {-# UNPACK #-} !Word64  -- ^ satoshis
     81   , txout_script_pubkey :: !BS.ByteString
     82   } deriving (Eq, Show, Generic)
     83 
     84 instance NFData TxOut
     85 
     86 -- | Complete transaction.
     87 --
     88 --   Bitcoin requires at least one input and one output, enforced here
     89 --   via 'NonEmpty' lists. Each input carries its own witness, so a
     90 --   transaction is serialised in the segwit format exactly when some
     91 --   input's witness is non-empty.
     92 data Tx = Tx
     93   { tx_version   :: {-# UNPACK #-} !Word32
     94   , tx_inputs    :: !(NonEmpty TxIn)
     95   , tx_outputs   :: !(NonEmpty TxOut)
     96   , tx_locktime  :: {-# UNPACK #-} !Word32
     97   } deriving (Eq, Show, Generic)
     98 
     99 instance NFData Tx
    100 
    101 -- builders -------------------------------------------------------------------
    102 
    103 -- | Convert a Builder to a strict ByteString.
    104 to_strict :: BSB.Builder -> BS.ByteString
    105 to_strict = BL.toStrict . BSB.toLazyByteString
    106 {-# INLINE to_strict #-}
    107 
    108 -- | Encode a Word32 as little-endian bytes.
    109 put_word32_le :: Word32 -> BSB.Builder
    110 put_word32_le = BSB.word32LE
    111 {-# INLINE put_word32_le #-}
    112 
    113 -- | Encode a Word64 as little-endian bytes.
    114 put_word64_le :: Word64 -> BSB.Builder
    115 put_word64_le = BSB.word64LE
    116 {-# INLINE put_word64_le #-}
    117 
    118 -- | Encode a Word64 as Bitcoin compactSize (varint).
    119 --
    120 --   Encoding:
    121 --   - 0x00-0xfc: 1 byte (value itself)
    122 --   - 0xfd-0xffff: 0xfd ++ 2 bytes LE
    123 --   - 0x10000-0xffffffff: 0xfe ++ 4 bytes LE
    124 --   - larger: 0xff ++ 8 bytes LE
    125 put_compact :: Word64 -> BSB.Builder
    126 put_compact !n
    127     | n <= 0xfc       = BSB.word8 (fromIntegral n)
    128     | n <= 0xffff     = BSB.word8 0xfd <> BSB.word16LE (fromIntegral n)
    129     | n <= 0xffffffff = BSB.word8 0xfe <> BSB.word32LE (fromIntegral n)
    130     | otherwise       = BSB.word8 0xff <> BSB.word64LE n
    131 {-# INLINE put_compact #-}
    132 
    133 -- | Encode compactSize-length-prefixed bytes (Bitcoin @ser_string@).
    134 put_bytes :: BS.ByteString -> BSB.Builder
    135 put_bytes !bs =
    136      put_compact (fromIntegral (BS.length bs))
    137   <> BSB.byteString bs
    138 {-# INLINE put_bytes #-}
    139 
    140 -- | Encode an OutPoint (txid + vout).
    141 put_outpoint :: OutPoint -> BSB.Builder
    142 put_outpoint OutPoint {..} =
    143     let !(TxId !txid_bs) = op_txid
    144     in  BSB.byteString txid_bs <> put_word32_le op_vout
    145 {-# INLINE put_outpoint #-}
    146 
    147 -- | Encode a TxIn (without its witness, which is serialised
    148 --   separately).
    149 put_txin :: TxIn -> BSB.Builder
    150 put_txin TxIn {..} =
    151        put_outpoint txin_prevout
    152     <> put_bytes txin_script_sig
    153     <> put_word32_le txin_sequence
    154 {-# INLINE put_txin #-}
    155 
    156 -- | Encode a TxOut.
    157 put_txout :: TxOut -> BSB.Builder
    158 put_txout TxOut {..} =
    159        put_word64_le txout_value
    160     <> put_bytes txout_script_pubkey
    161 {-# INLINE put_txout #-}
    162 
    163 -- | Encode a Witness stack.
    164 put_witness :: Witness -> BSB.Builder
    165 put_witness (Witness items) =
    166        put_compact (fromIntegral (length items))
    167     <> foldMap put_bytes items
    168 {-# INLINE put_witness #-}