commit 500140d29936670be7064731f40961a0915aebce
parent 46b3d68a099ce6d0616218a11c20ceb670833544
Author: Jared Tobin <jared@jtobin.io>
Date: Sun, 25 Jan 2026 18:16:25 +0400
Polish: add Haddock examples and fix line lengths
- Add documentation examples to Tx.hs public API:
to_bytes, from_bytes, to_bytes_legacy, to_base16, from_base16, txid
- Improve sighash_legacy and sighash_segwit documentation
- Fix 4 lines exceeding 80 characters in Sighash.hs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat:
2 files changed, 63 insertions(+), 17 deletions(-)
diff --git a/lib/Bitcoin/Prim/Tx.hs b/lib/Bitcoin/Prim/Tx.hs
@@ -91,6 +91,11 @@ data Tx = Tx
-- | 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@Tx {..}
| null tx_witnesses = to_bytes_legacy tx
@@ -107,7 +112,15 @@ to_bytes tx@Tx {..}
-- | Serialise a transaction to legacy format (no witness data).
--
--- Used for txid computation.
+-- 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 {..} = to_strict $
put_word32_le tx_version
@@ -117,11 +130,20 @@ to_bytes_legacy Tx {..} = to_strict $
<> foldMap put_txout tx_outputs
<> put_word32_le tx_locktime
--- | Serialise a transaction to base16.
+-- | Serialise a transaction to base16 (hex).
+--
+-- @
+-- to_base16 tx = B16.encode (to_bytes tx)
+-- @
to_base16 :: Tx -> BS.ByteString
to_base16 tx = B16.encode (to_bytes tx)
--- | Parse a transaction from base16.
+-- | Parse a transaction from base16 (hex).
+--
+-- @
+-- -- round-trip
+-- from_base16 (to_base16 tx) == Just tx
+-- @
from_base16 :: BS.ByteString -> Maybe Tx
from_base16 b16 = do
bs <- B16.decode b16
@@ -201,6 +223,13 @@ put_witness (Witness items) =
--
-- 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 !bs = do
-- need at least 4 bytes for version
@@ -421,5 +450,14 @@ get_many getter !bs = go []
-- 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 (SHA256.hash (SHA256.hash (to_bytes_legacy tx)))
diff --git a/lib/Bitcoin/Prim/Tx/Sighash.hs b/lib/Bitcoin/Prim/Tx/Sighash.hs
@@ -93,17 +93,21 @@ hash256 :: BS.ByteString -> BS.ByteString
hash256 = SHA256.hash . SHA256.hash
{-# INLINE hash256 #-}
--- legacy sighash ---------------------------------------------------------------
+-- legacy sighash -------------------------------------------------------------
--- | Compute legacy sighash.
+-- | Compute legacy sighash for P2PKH/P2SH inputs.
--
-- Modifies a copy of the transaction based on sighash flags, appends
-- the sighash type as 4-byte little-endian, and double SHA256s.
--
--- >>> let tx = ... -- some transaction
--- >>> let spk = ... -- scriptPubKey being spent
--- >>> sighash_legacy tx 0 spk SIGHASH_ALL
--- <32-byte hash>
+-- @
+-- -- sign input 0 with SIGHASH_ALL
+-- let hash = sighash_legacy tx 0 scriptPubKey SIGHASH_ALL
+-- -- use hash with ECDSA signing
+-- @
+--
+-- For SIGHASH_SINGLE with input index >= output count, returns the
+-- special \"sighash single bug\" value (0x01 followed by 31 zero bytes).
sighash_legacy
:: Tx
-> Int -- ^ input index
@@ -146,7 +150,8 @@ modify_tx_legacy Tx{..} !idx !script_pubkey !sighash_type =
zero_other_sequences !_ [] = []
zero_other_sequences !i (inp : rest)
| i == idx = inp : zero_other_sequences (i + 1) rest
- | otherwise = inp { txin_sequence = 0 } : zero_other_sequences (i + 1) rest
+ | otherwise =
+ inp { txin_sequence = 0 } : zero_other_sequences (i + 1) rest
-- Process inputs based on sighash type
!inputs_cleared = clear_scripts 0 tx_inputs
@@ -216,17 +221,20 @@ put_txin_legacy TxIn{..} =
<> put_word32_le txin_sequence
{-# INLINE put_txin_legacy #-}
--- BIP143 segwit sighash --------------------------------------------------------
+-- BIP143 segwit sighash -------------------------------------------------------
-- | Compute BIP143 segwit sighash.
--
--- Required for signing segwit inputs (P2WPKH, P2WSH).
+-- Required for signing segwit inputs (P2WPKH, P2WSH). Unlike legacy
+-- sighash, this commits to the value being spent, preventing fee
+-- manipulation attacks.
--
--- >>> let tx = ... -- some transaction
--- >>> let sc = ... -- scriptCode
--- >>> let val = 50000 -- value in satoshis
--- >>> sighash_segwit tx 0 sc val SIGHASH_ALL
--- <32-byte hash>
+-- @
+-- -- sign P2WPKH input 0
+-- let scriptCode = ... -- P2WPKH scriptCode
+-- let hash = sighash_segwit tx 0 scriptCode inputValue SIGHASH_ALL
+-- -- use hash with ECDSA signing
+-- @
sighash_segwit
:: Tx
-> Int -- ^ input index