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 #-}