| Document #: | VOCABULARY |
| Date: | 2026-08-10 |
| Project: | Programming Language C++ |
| Audience: |
Library Evolution Working Group SG14 (Low-Latency / Financial) |
| Reply-to: |
Daniel Pfeifer <daniel@pfeifer-mail.de> |
[ Drafting note: For illustrative purposes only. This document is written in the style of a WG21 standardization paper. It has not been submitted to the ISO C++ committee and is not under active consideration for standardization. ]
This paper proposes adding a set of vocabulary types for the Bitcoin
protocol to the C++ Standard Library under the header
<bitcoin>.
These types live in
namespace bitcoin.
The types provide common, strongly typed representations for fundamental
Bitcoin protocol entities, allowing independently developed code to
exchange these entities without conversions between library-specific
types. Wire-format parsing and serialization are not proposed by this
paper.
Code implementing the Bitcoin protocol needs common representations for fundamental protocol entities such as transaction identifiers, monetary amounts, scripts, transactions, and blocks. When these entities are represented by library-specific types, code that interfaces between libraries must translate between those types even when they represent the same protocol entity.
This paper proposes vocabulary types for these entities so that independently developed components can exchange them directly.
Wire-format parsing and serialization are not part of this proposal.
This is a pure library addition. It requires no core language changes. It depends on the quantities and units library proposed in [P3045R8] and on:
<array>,
<concepts>,
<cstddef>,
<cstdint><compare>,
<format>,
<ranges>,
<span>,
<stdexcept>No existing names are modified or deprecated.
txid,
wtxid,
block_hash, and
hash256 are all 32-byte digest
values, but they must not be implicitly interconvertible. A function
accepting a txid must not silently
accept a block_hash or a
wtxid. This rules out aliasing them
to the same underlying type (for example,
using txid = std::array<std::byte, 32>).
The proposal models these as distinct specializations of an
exposition-only template
basic-hash-id<Tag>,
producing separate, non-interconvertible types with identical storage
representation.
Bitcoin’s double-SHA256 digests are stored on the wire in
little-endian (“natural hash”) byte order. Block explorers, wallet
software, and similar tools commonly display them with the bytes
reversed. The proposed types store wire order internally.
std::format
produces the customary display representation.
Bitcoin monetary values should not be confused with unrelated
integers.
bitcoin::amount
is a
std::quantity
specialization [P3045R8] with
std::int64_t
representation and
bitcoin::units::satoshi
as its reference unit. The type represents Bitcoin-denominated
quantities; protocol-level constraints such as the valid money range are
not imposed by the type itself.
Pattern-matching for P2PKH,
P2TR, opcode enumeration, and script
execution belong to a higher-level facility (not proposed here). At
vocabulary level the paper provides two opaque script types: owning
script and non-owning
script_ref. Their public observation
surface is limited to
empty() and
as_bytes(),
which yields a
std::span<const std::byte>
over serialized bytes.
script_ref is intentionally not
named script_view: it does not model
std::ranges::view
(or
std::ranges::range)
because script objects do not provide script-level iteration operations
directly.
Providing both types permits accessors such as
tx_input::script()
and
tx_output::script()
to return a script by value without requiring the implementation to
store a script subobject internally
or to allocate and copy on every access. An implementation may therefore
store a script directly, store offsets into transaction backing storage,
or use any other representation consistent with the specified
observers.
size()
observer for script typesA script has at least two natural notions of size: serialized byte
count and, in a future decoding facility, instruction count. An
unqualified
size()
observer would therefore obscure the unit being measured. The byte count
is obtainable as
as_bytes(s).size().
Any future instruction-level facility can expose its own size in terms
appropriate to that facility.
script and
script_ref are carriers for
serialized script bytes, including oversized scripts. They do not
enforce a fixed maximum serialized length as a construction-time class
invariant.
Protocol and policy limits on script size are modeled by semantic operations that need them (for example, script predicates), not by the fundamental storage types themselves.
Each tx_input exposes its witness
data through a
witness()
observer returning an implementation-defined range of byte strings. An
empty range indicates a non-SegWit input. The segregated-witness
wire-format defined in [BIP-141]
places witness data at the end of the transaction, but that is an
encoding artifact and imposes no constraint on the vocabulary-level
design. Implementations may store witness data with each input or in a
separate parallel structure.
block_headerblock_header is specified as an
aggregate struct with public data members to allow aggregate
initialization. Hash computation is handled by the
block_hash constructor rather than a
member function, preserving the separation between data and
computation.
With the exception of
block_header (which is an aggregate;
see above), all other types are
class with
private data members and accessor-only public interfaces. Collection
accessors return an unspecified type satisfying the
value-range<T>
named requirement (see Exposition-only range
helper) rather than
const std::vector<T>&,
permitting implementations to choose the backing representation.
The sub-namespace
bitcoin::units
contains the monetary unit constants
satoshi and
btc. Exposing them as ordinary unit
objects permits construction, conversion, and formatting through the
quantity interface.
Bitcoin Core [Bitcoin Core] uses
the legacy terms scriptSig and
scriptPubKey for the input
authorization script and output locking script, respectively. The
clearer alternatives are
input_script and
output_script; however, in the
proposed interface, the owning classes
tx_input and
tx_output already supply that
input/output context. The accessors are therefore simply named
script().
This paper does not mandate any particular ABI versioning scheme.
Whether to use per-type
inline namespace
versioning
(e.g. inline namespace transaction_v1),
a single top-level
inline namespace bitcoin_v1,
or no versioning at all is an implementer’s choice. The paper requires
only that all types and constants are accessible as
bitcoin::hash256,
bitcoin::amount,
etc. — i.e. as direct members of
namespace bitcoin.
One exposition-only helper constrains return types of collection accessors in this paper. It is not part of the public API.
value-range<T>
is an exposition-only requirement for the unspecified return types of
collection accessors. A type R
satisfies
value-range<T>
if it models
std::ranges::view,
std::ranges::sized_range,
and
std::ranges::random_access_range,
andstd::ranges::range_value_t<R>
is T. The reference type is
implementation-defined: implementations may return
const T&
into stored elements or T by value
for handle-based types. Each collection class exposes its return type as
a named member typedef
(e.g. transaction::input_view)
whose concrete type is implementation-defined but must satisfy
value-range<T>.
The wording in this section is relative to the C++ Working Draft.
[ Editor's note: Add
<bitcoin> to the table
of standard library headers in [headers] and insert a new Clause [bitcoin]
after [time.h.syn]. ]
#define __cpp_lib_bitcoin 214XXXL // also in <bitcoin>This Clause describes the header
<bitcoin>,
which provides vocabulary types for the Bitcoin wire protocol.
<bitcoin>
synopsisnamespace bitcoin {
namespace units {
inline constexpr /* see [bitcoin.amount] */ satoshi;
inline constexpr /* see [bitcoin.amount] */ btc;
}
using amount = std::quantity<units::satoshi, std::int64_t>;
template<class Tag> class basic-hash-id; // exposition only
using hash256 = basic-hash-id</* unspecified */>;
using txid = basic-hash-id</* unspecified */>;
using wtxid = basic-hash-id</* unspecified */>;
using block_hash = basic-hash-id</* unspecified */>;
class script;
class script_ref;
class outpoint;
class tx_input;
class tx_output;
class transaction;
struct block_header;
class block;
} // namespace bitcoin
template<class Tag> struct std::formatter<bitcoin::basic-hash-id<Tag>>;
template<class Tag> struct std::hash<bitcoin::basic-hash-id<Tag>>;
template<> struct std::hash<bitcoin::outpoint>;amountamount is an alias for
std::quantity<units::satoshi, std::int64_t>.
It represents a Bitcoin-denominated quantity measured in satoshis. The
type does not impose protocol-level constraints such as the valid money
range.
namespace bitcoin {
namespace units {
inline constexpr /* unit */ satoshi;
inline constexpr /* unit */ btc;
}
using amount = std::quantity<units::satoshi, std::int64_t>;
} // namespace bitcoinunits::satoshi
denotes the reference unit of
amount.
units::btc
denotes
100'000'000 * units::satoshi.
basic-hash-idbasic-hash-id is an
exposition-only class template.
hash256 ([bitcoin.hash256]),
txid ([bitcoin.txid]),
wtxid ([bitcoin.wtxid]), and
block_hash ([bitcoin.block_hash])
are distinct specializations with unspecified tag types. Specializations
with different tag types are unrelated types; comparisons between them
are ill-formed.
A default-constructed
basic-hash-id holds
all-zero bytes.
An exposition-only concept
is-hash-source<Tag, T>
is satisfied when T is a permitted
source type for constructing the hash specialization with tag
Tag. The permitted pairs are:
Tag
|
T
|
|---|---|
block_hash tag |
block_header |
block_hash tag |
block |
txid tag |
transaction |
wtxid tag |
transaction |
No other pairs satisfy the concept. In particular,
hash256 has no hash-source
types.
namespace bitcoin {
template<class Tag, class T>
concept is-hash-source = /* see [bitcoin.hashid.overview] */; // exposition only
template<class Tag>
class basic-hash-id { // exposition only
public:
constexpr basic-hash-id() noexcept;
constexpr explicit basic-hash-id(
std::span<const std::byte, 32> bytes) noexcept;
template<class T>
requires is-hash-source<Tag, T>
explicit basic-hash-id(const T& src);
[[nodiscard]] constexpr explicit operator bool() const noexcept;
friend constexpr std::span<const std::byte, 32>
as_bytes(const basic-hash-id&) noexcept;
friend constexpr bool operator==(
const basic-hash-id&,
const basic-hash-id&) noexcept = default;
friend constexpr std::strong_ordering operator<=>(
const basic-hash-id&,
const basic-hash-id&) noexcept = default;
private:
std::array<std::byte, 32> value; // exposition only
};
} // namespace bitcoin
template<class Tag> struct std::formatter<bitcoin::basic-hash-id<Tag>>;
template<class Tag> struct std::hash<bitcoin::basic-hash-id<Tag>>;constexpr basic-hash-id() noexcept;Postconditions:
!*this
is true.
constexpr explicit basic-hash-id(
std::span<const std::byte, 32> bytes) noexcept;Effects: Initializes the stored bytes by copying from
bytes.
template<class T>
requires is-hash-source<Tag, T>
explicit basic-hash-id(const T& src);Effects: Computes the SHA256d digest of the serialization of
src and initializes the object with
the resulting digest as follows:
T is
block_header, the SHA256d of the
serialized block header fields.T is
block, equivalent to
basic-hash-id<Tag>(src.header()).T is
transaction and
Tag is the
txid tag, the SHA256d of the
witness-stripped serialization of
src.T is
transaction and
Tag is the
wtxid tag, the SHA256d of the full
serialization of src including
witness data.[[nodiscard]] constexpr explicit operator bool() const noexcept;Returns:
false if all
stored bytes are
std::byte{0},
and true
otherwise.
friend constexpr std::span<const std::byte, 32>
as_bytes(const basic-hash-id& h) noexcept;Returns: A read-only view of the 32 wire-order bytes of
h.
std::formatter<bitcoin::basic-hash-id<Tag>>
formats a value as 64 lowercase hexadecimal digits in display byte order
(bytes reversed relative to wire order), as used by block explorers.
std::hash<bitcoin::basic-hash-id<Tag>>
is provided. The hash value is computed over the 32 wire-order bytes of
the value.
hash256hash256 is a general-purpose
32-byte hash value, used where no stronger domain type applies — for
example, the merkle root in a block header ([bitcoin.block_header]).
namespace bitcoin {
using hash256 = basic-hash-id</* unspecified */>;
} // namespace bitcointxidtxid identifies a transaction by
the SHA256d of its witness-stripped serialization.
namespace bitcoin {
using txid = basic-hash-id</* unspecified */>;
} // namespace bitcoinwtxidwtxid is the SHA256d of the full
transaction serialization including witness data, as defined by [BIP-141].
A wtxid and the
txid of the same transaction are
equal only for transactions that carry no witness data.
namespace bitcoin {
using wtxid = basic-hash-id</* unspecified */>;
} // namespace bitcoinblock_hashblock_hash is the SHA256d of the
serialized block header fields.
namespace bitcoin {
using block_hash = basic-hash-id</* unspecified */>;
} // namespace bitcoinscriptnamespace bitcoin {
class script_ref;
class script {
public:
script() noexcept;
explicit script(std::span<const std::byte> b);
explicit script(script_ref s);
[[nodiscard]] bool empty() const noexcept;
friend std::span<const std::byte> as_bytes(const script& s) noexcept;
friend bool operator==(const script& lhs, const script& rhs) noexcept;
};
} // namespace bitcoinscript() noexcept;Postconditions:
empty() is
true.
explicit script(std::span<const std::byte> b);Effects: Initializes the stored byte sequence by copying
from b.
explicit script(script_ref s);Effects: Initializes the stored byte sequence by copying
from
as_bytes(s).
[[nodiscard]] bool empty() const noexcept;Returns:
as_bytes(*this).empty().
[[nodiscard]] friend std::span<const std::byte>
as_bytes(const script& s) noexcept;Returns: A span over the stored byte sequence of
s.
friend bool operator==(const script& lhs, const script& rhs) noexcept;Returns:
true if
as_bytes(lhs)
and
as_bytes(rhs)
compare equal element-wise; otherwise
false.
script_refnamespace bitcoin {
class script;
class script_ref {
public:
script_ref() noexcept;
explicit script_ref(std::span<const std::byte> b) noexcept;
script_ref(const script& s) noexcept;
script_ref(script&&) = delete;
script_ref(const script&&) = delete;
[[nodiscard]] bool empty() const noexcept;
friend std::span<const std::byte> as_bytes(script_ref s) noexcept;
friend bool operator==(script_ref lhs, script_ref rhs) noexcept;
};
} // namespace bitcoinscript_ref() noexcept;Postconditions:
empty() is
true.
explicit script_ref(std::span<const std::byte> b) noexcept;Effects: Initializes the referenced byte sequence to
b.
script_ref(const script& s) noexcept;Effects: Initializes the referenced byte sequence to
as_bytes(s).
Remarks: The deleted rvalue constructor overloads prevent
script_ref from binding to a
temporary script.
[[nodiscard]] bool empty() const noexcept;Returns:
as_bytes(*this).empty().
[[nodiscard]] friend std::span<const std::byte>
as_bytes(script_ref s) noexcept;Returns: A span over the referenced byte sequence of
s.
friend bool operator==(script_ref lhs, script_ref rhs) noexcept;Returns:
true if
as_bytes(lhs)
and
as_bytes(rhs)
compare equal element-wise; otherwise
false.
outpointoutpoint identifies a transaction
output by its transaction identifier and output index.
namespace bitcoin {
class outpoint {
public:
constexpr outpoint(bitcoin::txid txid, std::size_t index) noexcept;
[[nodiscard]] bitcoin::txid txid() const noexcept;
[[nodiscard]] std::size_t index() const noexcept;
friend bool
operator==(const outpoint& lhs, const outpoint& rhs) noexcept;
friend std::strong_ordering
operator<=>(const outpoint& lhs, const outpoint& rhs) noexcept;
};
} // namespace bitcoin
template<> struct std::hash<bitcoin::outpoint>;std::hash<bitcoin::outpoint>
is provided. The hash value is computed by combining
std::hash<bitcoin::txid>{}(txid())
with index()
in an implementation-defined manner consistent with
operator==.
[[nodiscard]] bitcoin::txid txid() const noexcept;
[[nodiscard]] std::size_t index() const noexcept;Returns: The txid of the
transaction containing the referenced output, and the output index
within that transaction, respectively.
tx_inputwitness_view satisfies the
value-range<std::span<const std::byte>>
named requirement. Construction is implementation-defined.
namespace bitcoin {
class tx_input {
public:
using witness_view = /* see [bitcoin.tx_input.overview] */;
[[nodiscard]] bitcoin::outpoint prevout() const noexcept;
[[nodiscard]] bitcoin::script_ref script() const noexcept;
[[nodiscard]] std::uint32_t sequence() const noexcept;
[[nodiscard]] witness_view witness() const;
friend bool operator==(const tx_input& lhs, const tx_input& rhs) noexcept;
};
} // namespace bitcoin[[nodiscard]] bitcoin::outpoint prevout() const noexcept;
[[nodiscard]] bitcoin::script_ref script() const noexcept;
[[nodiscard]] std::uint32_t sequence() const noexcept;Returns: The previous output, a non-owning
script_ref referring to the input
script, and the sequence number, respectively.
[[nodiscard]] witness_view witness() const;Returns: A view of the witness items for this input. Each
element is a
std::span<const std::byte>
over one witness item. An empty view indicates a non-SegWit input.
tx_outputnamespace bitcoin {
class tx_output {
public:
[[nodiscard]] bitcoin::amount value() const noexcept;
[[nodiscard]] bitcoin::script_ref script() const noexcept;
friend bool operator==(const tx_output& lhs, const tx_output& rhs)
noexcept;
};
} // namespace bitcoin[[nodiscard]] bitcoin::amount value() const noexcept;
[[nodiscard]] bitcoin::script_ref script() const noexcept;Returns: The stored output value and a non-owning
script_ref referring to the output
script, respectively.
transactioninput_view and
output_view each satisfy the
value-range<T>
named requirement for their respective element types. Objects of type
transaction may be
default-constructed. All other construction is implementation-defined.
Wire-format parsing and serialization are specified separately.
namespace bitcoin {
class transaction {
public:
using input_view = /* see [bitcoin.transaction.overview] */;
using output_view = /* see [bitcoin.transaction.overview] */;
transaction();
[[nodiscard]] std::int32_t version() const noexcept;
[[nodiscard]] std::uint32_t locktime() const noexcept;
[[nodiscard]] input_view inputs() const;
[[nodiscard]] output_view outputs() const;
friend bool operator==(const transaction& lhs, const transaction& rhs)
noexcept;
};
} // namespace bitcointransaction();Postconditions:
inputs().empty()
and
outputs().empty().
[[nodiscard]] std::int32_t version() const noexcept;
[[nodiscard]] std::uint32_t locktime() const noexcept;Returns: The transaction version and locktime fields, respectively.
[[nodiscard]] input_view inputs() const;
[[nodiscard]] output_view outputs() const;Returns: Views of the inputs and outputs, respectively.
block_headernamespace bitcoin {
struct block_header {
std::int32_t version;
bitcoin::block_hash prev_block_hash;
bitcoin::hash256 merkle_root;
std::chrono::sys_seconds time;
std::uint32_t bits;
std::uint32_t nonce;
friend bool operator==(const block_header& lhs, const block_header& rhs)
noexcept = default;
friend std::strong_ordering operator<=>(const block_header& lhs,
const block_header& rhs) noexcept = default;
};
} // namespace bitcoinblock_header is an aggregate with
the following public data members:
Member
|
Type
|
Description
|
|---|---|---|
version |
std::int32_t |
Block version number |
prev_block_hash |
bitcoin::block_hash |
Hash of the preceding block |
merkle_root |
bitcoin::hash256 |
Merkle root of transactions |
time |
std::chrono::sys_seconds |
Block timestamp |
bits |
std::uint32_t |
Compact target encoding |
nonce |
std::uint32_t |
Nonce for proof-of-work |
The block hash is obtained by constructing a
bitcoin::block_hash
from the header:
bitcoin::block_hash{hdr}.
See [bitcoin.hashid.cons].
blocktransaction_view satisfies the
value-range<bitcoin::transaction>
named requirement. Objects of type
block may be default-constructed.
All other construction is implementation-defined. Wire-format parsing
and serialization are specified separately.
namespace bitcoin {
class block {
public:
using transaction_view = /* see [bitcoin.block.overview] */;
block();
[[nodiscard]] const bitcoin::block_header& header() const noexcept;
[[nodiscard]] transaction_view transactions() const;
friend bool operator==(const block& lhs, const block& rhs) noexcept;
};
} // namespace bitcoinblock();Postconditions:
transactions().empty().
[[nodiscard]] const bitcoin::block_header& header() const noexcept;Returns: The block header.
[[nodiscard]] transaction_view transactions() const;Returns: A view of the transactions. By convention,
transactions().front()
is the coinbase transaction when the block is non-empty.