Compact Size Encoding
Compact size encoding is Bitcoin's variable-length integer format used to represent data lengths in transaction serialization.
Key Takeaways
- Compact size encoding is Bitcoin's variable-length integer format for representing counts and lengths in raw transactions: it uses 1 byte for values 0 to 252, 3 bytes for values up to 65,535, 5 bytes for values up to roughly 4.3 billion, and 9 bytes for larger values.
- Every Bitcoin transaction uses compact size integers to specify the number of inputs and outputs, the length of each script, and the count and size of witness items.
- Compact size encoding is distinct from Bitcoin Core's internal VarInt format: both are variable-length integer encodings, but they use different schemes and appear in different contexts.
What Is Compact Size Encoding?
Compact size encoding (sometimes called CompactSize or "varint" in older documentation) is the variable-length unsigned integer format used throughout Bitcoin's transaction serialization and peer-to-peer protocol messages. Rather than using a fixed-width integer for every count or length field, compact size encoding adapts its byte width to the value being represented: small numbers take just 1 byte, while larger numbers use a prefix byte followed by 2, 4, or 8 additional bytes.
This encoding dates back to Satoshi Nakamoto's original Bitcoin implementation. Because it is embedded in the raw transaction format, the compact size encoding rules are part of Bitcoin's consensus: every node must parse and validate these integers identically, or risk chain splits. The format is defined not by a BIP but by the reference implementation in Bitcoin Core's serialize.h, specifically the ReadCompactSize and WriteCompactSize functions.
How It Works
Compact size encoding uses the first byte of the integer to signal how many bytes follow. There are four encoding ranges:
Encoding Rules
| Value Range | First Byte | Following Bytes | Total Size |
|---|---|---|---|
| 0 to 252 | Value itself (0x00 to 0xFC) | None | 1 byte |
| 253 to 65,535 | 0xFD | 2 bytes (little-endian uint16) | 3 bytes |
| 65,536 to 4,294,967,295 | 0xFE | 4 bytes (little-endian uint32) | 5 bytes |
| 4,294,967,296 to 2^64 − 1 | 0xFF | 8 bytes (little-endian uint64) | 9 bytes |
The key insight is that byte values 0xFD (253), 0xFE (254), and 0xFF (255) are reserved as prefix markers. They never represent a value directly: if the first byte is 0xFD, the parser knows to read the next 2 bytes as a little-endian unsigned integer. This is why single-byte encoding stops at 252, not 255.
Encoding Examples
Here is how different values are serialized in compact size format (all multi-byte values use little-endian byte order):
Value: 1
Hex: 01
Bytes: [0x01]
Size: 1 byte
Value: 252
Hex: fc
Bytes: [0xFC]
Size: 1 byte
Value: 253
Hex: fdfd00
Bytes: [0xFD, 0xFD, 0x00]
Size: 3 bytes
Value: 550
Hex: fd2602
Bytes: [0xFD, 0x26, 0x02]
Size: 3 bytes
Value: 65535
Hex: fdffff
Bytes: [0xFD, 0xFF, 0xFF]
Size: 3 bytes
Value: 65536
Hex: fe00000100
Bytes: [0xFE, 0x00, 0x00, 0x01, 0x00]
Size: 5 bytesCanonical Encoding
Because the format allows redundant representations (for example, the value 100 could technically be encoded as 3 bytes using the 0xFD prefix), Bitcoin Core enforces canonical encoding during deserialization. The ReadCompactSize function rejects non-canonical forms where a shorter encoding would suffice. This prevents malleability issues and ensures deterministic serialization.
For example, encoding the value 100 as [0xFD, 0x64, 0x00] instead of the canonical [0x64] would be rejected. Any transaction containing a non-canonical compact size integer is treated as invalid.
Where Compact Size Appears in Transactions
Compact size integers appear throughout the raw Bitcoin transaction format. Every transaction includes multiple compact size fields:
Transaction Structure
A serialized Bitcoin transaction (post-SegWit) contains these compact size fields:
- Input count: the number of transaction inputs, encoded as a compact size integer before the input list
- Output count: the number of transaction outputs, encoded similarly before the output list
- scriptSig length: the byte length of each input's unlocking script, preceding the script data
- scriptPubKey length: the byte length of each output's locking script, preceding the script data
For SegWit transactions, additional compact size fields appear in the witness structure:
- Witness item count: the number of witness stack items for each input
- Witness item length: the byte length of each individual witness element (signatures, public keys, scripts)
Beyond transactions, compact size encoding also appears in block serialization (the transaction count at the start of a block's transaction list) and in various peer-to-peer protocol messages such as inv, getdata, and addr.
Practical Size Impact
For typical transactions, compact size fields add minimal overhead. A transaction with fewer than 253 inputs or outputs uses just 1 byte for each count field. Script lengths under 253 bytes (which covers virtually all standard scripts) also use 1 byte. Since Bitcoin Script imposes a 10,000-byte limit on script sizes, a script at the maximum length would need a 3-byte compact size prefix: still very compact.
These fields contribute to the overall transaction weight and therefore affect the fee a transaction must pay. Witness compact size fields benefit from the witness discount, counting as only 1 weight unit per byte rather than 4.
CompactSize vs. Bitcoin Core VarInt
A common source of confusion: Bitcoin Core uses two different variable-length integer encodings, and both are sometimes called "varint." They are not interchangeable.
| Feature | CompactSize | Core VarInt (CVarInt) |
|---|---|---|
| Origin | Satoshi's original code | Introduced in Bitcoin Core 0.8 |
| Used in | Transaction serialization, P2P protocol | UTXO database (chainstate), on-disk storage |
| Consensus-critical | Yes | No (internal storage only) |
| Scheme | Prefix-byte markers (0xFD, 0xFE, 0xFF) | 7-bit encoding with continuation bit |
| 1-byte range | 0 to 252 | 0 to 127 |
| Redundancy | Possible (rejected by canonical check) | None by design |
Bitcoin Core's VarInt uses a 7-bit encoding scheme where the most significant bit of each byte indicates whether more bytes follow. This is more space-efficient for certain value ranges but was designed for internal storage, not the consensus-critical wire format. When reading Bitcoin documentation or source code, pay attention to which encoding is being referenced: transaction data always uses CompactSize, while the chainstate database uses VarInt.
Use Cases
Transaction Parsing and Construction
Any software that creates or reads raw Bitcoin transactions must implement compact size encoding correctly. Wallet software, block explorers, and node implementations all rely on it to determine where one field ends and the next begins in the byte stream. Incorrect parsing would misinterpret the entire transaction structure.
Script and Witness Serialization
When constructing witness data for SegWit or Taproot transactions, each witness element is length-prefixed with a compact size integer. For a standard P2WPKH input, the witness contains two items (a signature and a public key), each preceded by its compact size length:
# P2WPKH witness stack
02 # compact size: 2 witness items
47 # compact size: 71 bytes (signature length)
3044...01 # 71-byte DER signature
21 # compact size: 33 bytes (pubkey length)
02ae...7f # 33-byte compressed public keyFee Estimation and Size Calculation
Accurate fee estimation requires knowing the exact serialized size of a transaction, including all compact size fields. Tools like the Bitcoin Optech transaction size calculator account for compact size overhead when computing virtual bytes and transaction weight. For most standard transactions, compact size fields are 1 byte each, but transactions with more than 252 inputs or outputs would use 3-byte count fields.
Protocol Message Framing
The Bitcoin peer-to-peer protocol uses compact size integers extensively in messages that carry lists: inventory vectors in inv messages, address lists in addr messages, and header lists in headers messages all begin with a compact size count.
Risks and Considerations
Implementation Correctness
Failing to implement canonical encoding checks can create subtle bugs. A parser that accepts non-canonical compact size integers may produce valid-seeming results but disagree with Bitcoin Core about transaction validity. This could lead to accepting invalid transactions or rejecting valid ones, potentially causing chain forks in alternative node implementations.
Denial-of-Service Vectors
Because compact size integers can theoretically represent values up to 2^64 − 1, a malicious peer could send a message claiming to contain billions of items. Bitcoin Core guards against this by capping ReadCompactSize at a maximum value (0x02000000, or about 33 million) and by imposing message-level size limits. Any implementation parsing compact size integers must include similar bounds checks to avoid memory exhaustion.
Byte-Order Sensitivity
Multi-byte compact size values use little-endian byte order, matching Bitcoin's general serialization convention. Developers working on big-endian systems or porting code between languages must handle byte-order conversion carefully. A swapped byte order would produce wildly incorrect values and corrupt the entire transaction parse.
Confusion with Other Formats
The overlap in naming between CompactSize and Bitcoin Core's internal VarInt encoding has caused bugs in third-party software. Additionally, other systems (Protocol Buffers, LEB128) use the term "varint" for entirely different encodings. When working with Bitcoin serialization, always verify which specific format is expected for a given context.
This glossary entry is for informational purposes only and does not constitute financial or investment advice. Always do your own research before using any protocol or technology.