bitcoin_wire/transaction
The transaction domain deserializes, inspects, validates, and serializes Bitcoin transactions while preserving Bitcoin’s wire representation.
Features
- Safe deserialization: Configurable resource limits constrain work and allocation when deserializing untrusted transaction bytes.
- Rich decode diagnostics: Decode errors include byte offsets and stable structural paths.
- Format detection: Legacy and SegWit transaction encodings remain distinct.
- Transaction inspection: Access versions, lock times, inputs, outputs, outpoints, script bytes, output values, and SegWit witness stacks.
- Script classification: Structurally identify P2PKH, P2SH, P2WPKH, P2WSH, P2TR, P2A, and other output script templates.
- Context-free consensus validation: Check transaction-local rules such as input/output presence, Bitcoin Core’s transaction base-size check (at most 1,000,000 stripped bytes, excluding witness data), output value ranges, coinbase structure, and duplicate inputs. Witness data excluded by that base-size check still contributes to the separate 4,000,000-WU block-weight limit.
- Validation-aware API: Phantom types distinguish parsed transactions from transactions that passed context-free consensus validation.
- Serialization, measurements, and identifiers: Produce stripped or full wire bytes, compute BIP 141 base size, total size, weight, and upward-rounded virtual size in vbytes, and compute txids and wtxids.
Quick Start
import bitcoin_wire/hash256
import bitcoin_wire/transaction
import gleam/result
pub fn display_txid_from_bytes(
bytes: BitArray,
) -> Result(String, transaction.DecodeError) {
bytes
|> transaction.deserialize
|> result.map(transaction.compute_txid)
|> result.map(hash256.to_display_hex)
}
pub fn txid_bytes_from_hex(
hex: String,
) -> Result(BitArray, transaction.DeserializeHexError) {
hex
|> transaction.deserialize_hex
|> result.map(transaction.compute_txid)
|> result.map(hash256.to_bytes_le)
}
Outpoint txids and computed txids and wtxids are exposed as Hash256 values in
the same little-endian order used on the Bitcoin wire. Use
hash256.to_display_hex for conventional explorer notation or
hash256.to_bytes_le for the exact 32 wire-order bytes.
Output Values
Bitcoin’s wire format encodes output values as signed 64-bit integers. Consensus
permits only non-negative amounts within Bitcoin’s money range. To model the
wire format faithfully, OutputValue preserves the full signed range exactly on
Erlang and JavaScript; validate_context_free_consensus checks the monetary
constraints separately.
get_output_value returns an opaque OutputValue. Use the conversion helpers
to display it, inspect its wire bytes, or attempt native integer conversion:
import bitcoin_wire/transaction
import gleam/int
import gleam/io
let value = transaction.get_output_value(output)
let wire_bytes = transaction.output_value_to_bytes_le(value)
case transaction.output_value_to_int(value) {
Ok(satoshis) ->
io.println("Native integer amount: " <> int.to_string(satoshis))
Error(Nil) -> {
let decimal_satoshis = transaction.output_value_to_string(value)
io.println("Amount outside the native integer range: " <> decimal_satoshis)
}
}
Decode Policy
deserialize and deserialize_hex apply these defaults:
| Limit | Default |
|---|---|
| Serialized transaction size | 4,000,000 bytes |
| Input count | 100,000 |
| Output count | 125,000 |
| Total witness item count across all input stacks | 4,000,000 |
These defaults are permissive: their size and count ceilings accommodate canonical encodings of consensus-valid transactions under current Bitcoin limits. A transaction must fit within the 4,000,000-weight-unit block limit, and serialized byte size cannot exceed weight. Each witness item requires at least one byte. Inputs and outputs require at least 41 and 9 non-witness bytes, respectively, charged at four weight units per byte; their count ceilings exceed what can fit. See BIP 141 for the weight rules.
Serialized size does not directly bound heap usage: decoded collections can occupy much more memory than their wire bytes. Callers with tighter resource budgets can configure lower limits.
Use default_decode_policy and the decode_policy_with_* builders to customize
them:
let policy =
transaction.default_decode_policy()
|> transaction.decode_policy_with_max_tx_size(1_000_000)
|> transaction.decode_policy_with_max_input_count(5_000)
|> transaction.decode_policy_with_max_witness_item_count(50_000)
let result = transaction.deserialize_with_policy(transaction_bytes, policy)
The size limit applies to the complete input buffer before decoding. Input and
output count limits are checked before their collections are decoded, after
checking whether the counts can fit in the remaining bytes. Script lengths,
witness item counts, and witness item lengths must also fit the remaining input.
The witness item limit applies to the cumulative count across all input stacks,
including zero-length items; empty stacks contribute zero. Each stack’s count is
checked against the remaining bytes, then against the cumulative limit before
any of that stack’s items are decoded. A violation reports MaxWitnessItemCount
with the cumulative count and points to the current stack’s item-count field.
Hex entry points enforce the same byte-size limit before allocating decoded bytes. Invalid hex takes precedence over size-policy errors, so oversized strings may still need to be scanned.
When transactions are decoded inside a block, the block’s serialized-size limit
provides the byte envelope and max_tx_size is ignored. Each contained
transaction still uses its configured input, output, and total witness item count
limits.
These limits constrain decoding resources, not consensus validity. A parsed transaction has not passed script execution or context-free consensus checks, and a strict custom policy can reject consensus-valid transactions.
Context-Free Consensus Validation
Deserialization produces a Transaction(Parsed). Pass that transaction to
validate_context_free_consensus to obtain a
Transaction(ContextFreeValidated):
let assert Ok(validated_transaction) =
transaction.validate_context_free_consensus(parsed_transaction)
On failure, Error(violations) contains a list of ConsensusViolation values.
Independent violations are collected in deterministic validation order.
Scope
The module performs whole-value deserialization, structural inspection, serialization, output script classification, and documented context-free consensus checks. It does not perform full transaction validation requiring UTXO lookup, script execution, signature verification, block context, mempool policy, or network/RPC access.