Version 2 Zcash P2P Network Protocol

Draft

Terminology

The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", "MAY", and "RECOMMENDED" in this document are to be interpreted as described in BCP 14 1 when, and only when, they appear in all capitals.

The terms "Mainnet" and "Testnet" are to be interpreted as described in section 3.12 of the Zcash Protocol Specification. 2

The term "network upgrade" is to be interpreted as described in ZIP 200. 3

The term "block chain" in this document is to be interpreted as described in section 3.3 of the Zcash Protocol Specification. 4

The term "legacy protocol" in this document refers to the Zcash P2P network protocol specified in ZIP 204 5.

peer
A network node that participates in the Zcash P2P protocol by maintaining connections to other peers over one or more supported transports and exchanging protocol data over streams.
transport
A concrete mechanism, such as QUIC 6 or Tor onion services 7, that carries connections between peers and provides the stream layer of this protocol (see Stream Layer and Transports).
connection
A session between two peers over a supported transport, over which P2P streams are exchanged.
initiator
The peer that initiated a connection (the transport-level client).
responder
The peer that accepted a connection (the transport-level server). In the context of a single request stream, responder instead denotes the peer serving the request, irrespective of which peer accepted the connection (see Request Streams); which sense is meant is indicated by context.
requester
The peer that opened a request stream and sends the request on it. Either the initiator or the responder of a connection may be the requester on a given request stream.
stream
An ordered, reliable byte stream within a connection, provided by the transport. Streams are typed by their first byte (see Stream Layer).
request stream
A bidirectional stream carrying a single request and its response.
announcement stream
A long-lived unidirectional stream carrying unsolicited announcements (blocks, transactions, or peer addresses).
record
A length-delimited unit of data on an announcement or handshake stream.
protocol version
A 32-bit integer identifying the set of protocol features supported by a node. Higher values indicate support for more recent features.

Abstract

This ZIP specifies version 2 of the Zcash peer-to-peer network protocol, a successor to the protocol specified in ZIP 204 5. It specifies the protocol's transport-neutral stream layer, the QUIC and Tor transports that realize it, the connection handshake, stream and record types, and relay behavior, and is intended to be sufficient for an implementor to build a conformant Zcash network peer.

Motivation

ZIP 204 5 specifies the current Zcash P2P network protocol, whose message-passing architecture is inherited from Bitcoin: a single unencrypted TCP bytestream carrying framed messages with implicit correlation between requests and responses. This ZIP proposes a successor protocol that replaces that architecture with multiplexed streams carried over an arbitrary transport that provides a secure channel between peers and a means of identifying the network and addressing peers. The protocol is specified against that abstract transport interface; this ZIP defines two concrete transports, one based on QUIC 6 and one carried over Tor onion services 7. The motivations for this change are:

  • Transport security. The legacy transport was unencrypted and unauthenticated, exposing all P2P traffic to passive network observers. Every transport of this protocol provides an encrypted channel: the QUIC transport integrates TLS 1.3 8 9, and the Tor transport inherits the encryption of onion service connections. (Bitcoin addressed the same gap with a bespoke encrypted transport, BIP 324 10; this protocol provides equivalent protection using standardized protocols.)

  • Multiplexing without head-of-line blocking. On the legacy transport, a single 2 MiB block message stalled all other traffic on the connection. Streams allow concurrent block downloads, transaction relay, and control traffic to proceed independently — on the QUIC transport, at both the application and packet-loss level.

  • Explicit request correlation. Legacy responses (block, tx, headers, notfound) carried no request identifiers, forcing implementations to correlate them heuristically. In this protocol, each request occupies its own stream, so correlation, cancellation, and per-request timeouts are structural.

  • Simplification. Transport-level mechanisms that the legacy protocol carried in the application layer — payload checksums, network magic, keep-alives and latency measurement, flow control — are provided by the transport and are deleted from the application protocol.

No backwards compatibility with the legacy protocol is provided; this ZIP replaces it entirely. See Deployment.

This ZIP does not specify the encoding of transactions and block headers; for those, refer to the Zcash Protocol Specification 11 12. The encoding of a whole block in terms of its header and transactions is part of the peer-to-peer protocol rather than the consensus protocol 12, and is specified in Serialized Blocks.

Changes from the Legacy Protocol

This section is non-normative. Relative to the legacy protocol specified in ZIP 204 5, this ZIP:

  • Replaces network magic bytes with transport-level network identification (for the QUIC transport, ALPN protocol identifiers; for the Tor transport, an in-band network identifier; see QUIC Transport and Tor Transport).
  • Removes per-message headers entirely: the 12-byte command string is replaced by a 1-byte stream type, and the length and SHA-256d checksum fields are subsumed by QUIC framing and authenticated encryption.
  • Replaces the version/verack handshake with a single init record exchange on a dedicated handshake stream (see Connection Handshake). The timestamp, addr_recv, and addr_from fields are removed.
  • Removes ping and pong; QUIC provides keep-alives and RTT measurement.
  • Removes reject; QUIC application error codes are used instead (see Application Error Codes).
  • Removes inv, getdata, and notfound; announcements are carried on dedicated unidirectional streams, and object requests carry per-item results.
  • Removes the inventory-walk getblocks message; headers-first synchronization is the baseline synchronization method, and new request streams support checkpoint-based bulk synchronization: get-hashes (known hashes and sync-cost metadata), get-block-range (anchored block streaming), get-tree-roots (note commitment tree roots, verified against header commitments), and get-object (content-addressed synchronization artifacts). See Synchronization and 13.
  • Removes the deprecated alert message.
  • Removes BIP 37 Bloom filtering (filterload, filteradd, filterclear) and the NODE_BLOOM service flag.
  • Removes the legacy addr message and CAddress/CService encodings; the addrv2 address encoding of ZIP 155 14 is the only address format.
  • Retains divided block relay — header-based block announcement and compact block relay — recast onto announcement streams and request streams, with a block's coinbase transaction and transaction IDs additionally available in get-headers responses (see Block Relay).

Specification

This specification is organized bottom-up. Transports defines how connections are established and secured (QUIC and Tor); Stream Layer defines the typed streams that carry all application data, and Data Types and Encoding the shared encodings. Connection Handshake and Protocol Versioning cover connection setup. Request Stream Types and Announcement Stream Types specify the individual request and announcement formats, and Block Relay, Synchronization, Transaction Relay, and Address Relay the behaviors built on them.

Network Parameters

Networks

There are three Zcash networks: Mainnet, Testnet, and Regtest. Every connection is bound to exactly one network, determined during connection establishment as each transport defines (see Transports); this replaces the network magic bytes of the legacy protocol.

DNS Seeds

The DNS seed hostnames used for initial peer discovery on each network are those listed in ZIP 204 15. Regtest does not use DNS seeds or hardcoded seed nodes.

Peer Discovery

A node discovers peers through the following mechanisms:

  1. Persisted addresses. A node SHOULD persist the peer addresses it learns (see Address Relay) across restarts, and on startup SHOULD attempt connections to previously learned addresses before consulting seeds.

  2. DNS seeding. When the node has no usable persisted addresses — on first start, or when too few persisted addresses yield successful connections — it queries the DNS seed hostnames listed above for A and AAAA records, and attempts connections to the resulting addresses on the default port of the QUIC transport.

  3. Hardcoded seed nodes. If DNS seeding fails or is insufficient, the node MAY fall back to a compiled-in list of seed node addresses. DNS seeding is preferred over hardcoded seed nodes.

  4. Address relay. Connected peers exchange address information using get-addr request streams and address announcement streams (see get-addr and Address Announcements). This mechanism operates independently of the above.

DNS seeding yields IP addresses only, and so bootstraps only IP-based transports such as QUIC. Addresses of peers reachable over overlay transports — such as the TORV3 addresses of the Tor transport (see Tor Transport) — are learned through address relay or provided by local configuration.

Transports

The protocol semantics in this document are defined against the abstract stream layer of Stream Layer. A transport is a concrete mechanism that carries connections between peers and realizes that stream layer. This ZIP defines two transports: one based on QUIC (see QUIC Transport), and one carried over Tor onion services (see Tor Transport). Future revisions may define additional transports — a transport carrying the stream layer over the Nym mixnet is anticipated (see Deployment) — and a node MAY support several transports simultaneously. The protocol semantics on a connection are identical regardless of its transport.

Every transport MUST provide:

  • a secure channel between the two peers: confidentiality and integrity for all application data;
  • identification of the network (see Networks) during connection establishment, such that a connection between peers on different networks fails before any application data is exchanged;
  • a form of address by which peers reachable over the transport are identified, encodable as a network address record (see Network Address Record);
  • the stream operations and guarantees of Transport Requirements.

Connection Management

The transport provides keep-alives and round-trip time measurement; the legacy ping/pong messages have no equivalent in this protocol.

  • A node SHOULD use an idle timeout of at least 120 seconds, and SHOULD use the transport's keep-alive mechanism to keep connections it wishes to retain from idling out. The specific values are implementation-defined.
  • A node MUST close a connection on which the application handshake (see Connection Handshake) has not completed within a bounded time, measured from the establishment of the transport connection so that it covers everything a peer sends beforehand — including the Tor transport's connection preamble (see Tor Transport). The specific timeout is implementation-defined; values between 3 and 60 seconds were used by legacy implementations. Without such a timeout, a peer that connects and then sends nothing holds a connection slot indefinitely, and the transport's keep-alive mechanism — answered by the transport stack rather than by the application — will report the connection healthy.
  • A node SHOULD bound the number of concurrent inbound connections it accepts, and SHOULD bound the share of them originating from any one transport, so that inbound slots on a transport whose addresses are free to generate cannot displace the rest.
  • Per-request timeouts are implementation-defined and are applied per stream (see Request Streams).

The specific connection limits are implementation-defined; the legacy reference values in ZIP 204 5 remain reasonable defaults.

A node SHOULD maintain at most one connection to a given remote address. Self-connection detection via the handshake nonce is specified in Init Record.

QUIC Transport

The QUIC transport uses QUIC version 1 6 over UDP, secured with TLS 1.3 8 as specified by RFC 9001 9.

  • A node MUST NOT use 0-RTT (early data). Note: a peer that attempts to use 0-RTT MUST have its connection closed — a responder MUST NOT enable the acceptance of early data, and a node whose peer nevertheless attempts to use 0-RTT MUST close the connection with the PROTOCOL_ERROR error code. (0-RTT data is replayable by an attacker.)
  • QUIC datagrams 16 are not used by this protocol. A node MUST ignore the peer's max_datagram_frame_size transport parameter and MUST NOT send DATAGRAM frames.
  • A node MAY support connection migration as specified in RFC 9000. Migration links the node's traffic across the old and new network paths; a node for which that linkage is a privacy concern SHOULD establish a fresh connection instead of migrating.
  • The address validation and amplification limits of RFC 9000 (including Retry packets) apply; a responder under load SHOULD use Retry to validate client addresses before committing state.
  • QUIC's loss recovery provides round-trip time measurement, and QUIC PING frames provide keep-alives. A node using the QUIC transport SHOULD advertise a max_idle_timeout transport parameter consistent with the idle timeout of Connection Management.

Network Identification

The network of a QUIC connection is identified by an Application-Layer Protocol Negotiation (ALPN) 17 protocol identifier, negotiated in the QUIC-TLS handshake:

NetworkALPN Identifier
Mainnetzcash/main
Testnetzcash/test
Regtestzcash/regtest

A node MUST offer exactly the ALPN identifier of the network it operates on, and MUST NOT complete a connection on which ALPN negotiation did not select that identifier.

Incompatible future revisions of this transport will be assigned new ALPN identifiers.

Default Ports

The QUIC transport uses the default port numbers of the legacy protocol, as specified in ZIP 204 18, as UDP port numbers.

Certificates

Connections are encrypted but endpoints are not authenticated: the goal is protection against passive network observers, not endpoint identity.

  • A node presents a self-signed X.509 certificate, or a raw public key 19, for an Ed25519 or ECDSA P-256 key. The key MAY be ephemeral or persistent, at the node's option; a node concerned about cross-connection linkability SHOULD use per-connection ephemeral keys.
  • A node MUST be prepared to accept both certificate encodings (X.509 and raw public key) and both key types from its peer.
  • A node MUST NOT require the peer's certificate to chain to any certification authority, and MUST NOT reject a connection on the basis of certificate contents (subject names, expiry, or extensions).
  • Client certificates are not requested: the responder MUST NOT require TLS client authentication.

Bulk Transfer Performance

Bulk synchronization moves hundreds of gigabytes over the request streams of this protocol, and its throughput is governed by transport configuration rather than by the protocol itself. The following applies to nodes that serve or perform bulk synchronization (see 13):

  • A node SHOULD size its flow control windows to the bandwidth-delay product it intends to sustain: at least 16 MiB of per-stream receive window and 64 MiB of connection receive window are RECOMMENDED. Default windows tuned for interactive traffic cap bulk throughput at a small fraction of link capacity once round-trip time is non-negligible.
  • A node serving bulk streams SHOULD use a paced, model-based congestion controller suited to high bandwidth-delay paths; loss-triggered controllers collapse under modest jitter at these transfer sizes.
  • Implementations SHOULD use platform UDP batching and segmentation offloads where available; per-packet processing, not cryptography, is the dominant CPU cost of QUIC bulk transfer.
  • One QUIC connection is typically processed by one core, bounding per-connection throughput. Synchronizing nodes obtain aggregate throughput by downloading from several peers concurrently rather than by tuning a single connection (see 13).

Stream Layer Mapping

The abstract stream layer of Transport Requirements maps onto QUIC as follows:

Stream layer conceptQUIC realization
Bidirectional / unidirectional streamQUIC bidirectional / unidirectional streams.
Finishing a stream directionA STREAM frame with the FIN bit.
Resetting a stream with an error codeRESET_STREAM carrying the application error code.
Cancelling a peer's sending with an error codeSTOP_SENDING carrying the application error code.
Closing the connection with an error codeCONNECTION_CLOSE (application variant) carrying the application error code.
Per-stream backpressureQUIC stream and connection flow control.
Stream concurrency limitsinitial_max_streams_bidi, initial_max_streams_uni, and MAX_STREAMS frames.
Keep-aliveQUIC PING frames.

Tor Transport

The Tor transport carries connections over the Tor network 20 as connections to version 3 onion services 7. It serves traffic for which the metadata protection of onion routing outweighs its latency cost (see Deployment and Security and Privacy Considerations), and allows a node to participate in the network without exposing an IP address.

  • A node that accepts inbound connections over this transport runs a version 3 onion service and advertises its address as a TORV3 network address record (see Network Address Record); the record's port field is the onion service virtual port, which SHOULD be the default port of the network (see Default Ports).
  • The initiator connects to the responder's onion service through the Tor network. The initiator needs no onion service of its own and is anonymous to the responder.
  • The secure channel is provided by Tor itself: onion service connections are end-to-end encrypted, and the connection is authenticated to the responder's onion address (the Ed25519 key that the TORV3 address encodes). TLS is not used inside the connection, and the initiator is deliberately not authenticated (cf. Certificates).
  • The transport provides a single ordered bytestream per connection; the stream layer is realized on top of it by the framing layer of Stream Framing.

Note: The underlying Tor connection is a single TCP-like bytestream, so loss or congestion on it stalls all streams of the connection; the framing layer removes head-of-line blocking between streams at the application level only (see Deployment for transport selection).

Connection Preamble

Immediately after the connection is established, each peer sends a preamble, before any frames:

SizeFieldDescription
variesnetworkNetwork identifier string (CompactSize-prefixed; see below).
variesinitial_max_dataInitial connection-level flow control credit in bytes (CompactSize).
variesinitial_max_stream_dataInitial per-stream flow control credit in bytes (CompactSize).
variesinitial_max_streams_bidiInitial limit on the peer's concurrent bidirectional streams (CompactSize).
variesinitial_max_streams_uniInitial limit on the peer's concurrent unidirectional streams (CompactSize).

The network string is the identifier of the node's network, using the same values as the QUIC transport's ALPN identifiers (see Network Identification): zcash/main, zcash/test, or zcash/regtest. A node MUST close the connection if the peer's network string differs from its own; this replaces ALPN-based network identification, which is unavailable without TLS.

The flow control fields play the role of the corresponding QUIC transport parameters: they are the initial credit available to the peer for the corresponding limit (see Stream Framing). A node MUST allow an initial_max_stream_data of at least 2,228,224 bytes (the maximum record payload length plus framing headroom), and SHOULD allow the stream concurrency minimums of Transport Requirements.

The preamble precedes every frame, and therefore precedes the record payload limit of Records and the malformed-frame rule of Stream Framing; neither bounds it. Its fields are bounded here instead, and a node MUST enforce these bounds while parsing the preamble, before allocating storage for a field:

  • The network string MUST NOT exceed 16 bytes.
  • Each of the four flow control fields MUST NOT exceed 2^62 − 1, the corresponding limit in QUIC 6.
  • A node MUST close the connection immediately, without sending frames, if a preamble field exceeds its bound, if the preamble is not complete within the handshake timeout below, or if a CompactSize in the preamble is not canonically encoded (see Data Types and Encoding).

The handshake timeout of Connection Management runs from the establishment of the transport connection, so it covers the preamble as well as the init record; an initiator that opens a connection and stalls mid-preamble is closed by it. This matters most for this transport: onion service addresses are free to generate and effectively unbannable (see Misbehavior and Banning), so a connection slot held open at no cost is the resource an attacker is actually consuming.

Incompatible future revisions of this transport will be assigned new network identifier strings.

Stream Framing

The stream layer is realized over the connection's ordered bytestream by a framing layer whose stream semantics — stream states, flow control, and error signalling — are modeled on those of QUIC 6.

Stream IDs are integers. The two least significant bits of a stream ID encode its kind, as in QUIC: 0x0 initiator-opened bidirectional, 0x1 responder-opened bidirectional, 0x2 initiator-opened unidirectional, 0x3 responder-opened unidirectional. Successive streams of each kind are opened with sequentially increasing stream IDs; a stream is opened implicitly by the first frame that its opener sends referencing its ID.

Each frame is a 1-byte frame type followed by type-dependent fields. Integer fields, including stream IDs, are CompactSize-encoded.

TypeFrameFieldsSemantics
0x00PING8-byte opaque valueKeep-alive and RTT measurement; the receiver MUST respond with PONG.
0x01PONG8-byte echoed valueResponse to PING.
0x02STREAMstream ID, flags (1 byte), length, dataAppends data to the stream. Flag bit 0 (FIN) finishes the sender's direction of the stream.
0x03RESET_STREAMstream ID, error codeResets the sender's direction of the stream (see Transport Requirements).
0x04STOP_SENDINGstream ID, error codeRequests that the peer stop sending on the stream.
0x05MAX_DATAmaximumRaises the connection-level flow control limit (cumulative bytes of stream data).
0x06MAX_STREAM_DATAstream ID, maximumRaises the flow control limit of one stream (cumulative bytes).
0x07MAX_STREAMS_BIDIcountRaises the limit on the peer's cumulative count of opened bidirectional streams.
0x08MAX_STREAMS_UNIcountRaises the limit on the peer's cumulative count of opened unidirectional streams.
0x09CLOSEerror codeCloses the connection with an application error code; the sender then closes the underlying connection.

Flow control limits are cumulative, as in QUIC: a limit counts total bytes sent on a stream (respectively, on all streams; respectively, total streams opened) since the beginning of the connection, and each MAX_* frame communicates a new absolute limit. Limits never decrease; a MAX_* frame with a lower value than a previously communicated limit is ignored. A node MUST NOT exceed a limit communicated by its peer; a peer that does so is a connection error of type PROTOCOL_ERROR (equally, a frame that is malformed or has an unrecognized frame type).

The length of a STREAM frame MUST NOT exceed 65,536 bytes; larger application writes are split across multiple STREAM frames, allowing frames of other streams to be interleaved between them.

The abstract stream layer maps onto this framing as follows:

Stream layer conceptFraming realization
Bidirectional / unidirectional streamStream ID kinds 0x0/0x1 and 0x2/0x3.
Finishing a stream directionA STREAM frame with the FIN flag.
Resetting a stream with an error codeRESET_STREAM carrying the application error code.
Cancelling a peer's sending with an error codeSTOP_SENDING carrying the application error code.
Closing the connection with an error codeCLOSE carrying the application error code.
Per-stream backpressureMAX_DATA and MAX_STREAM_DATA credit.
Stream concurrency limitsPreamble initial limits and MAX_STREAMS_* frames.
Keep-alivePING frames.

Stream Layer

All application data is carried on streams provided by the transport. The first byte of every stream is a stream type that determines the format and semantics of the remaining stream data.

Transport Requirements

The stream layer that every transport provides consists of bidirectional streams (both peers can send) and unidirectional streams (only the opener can send), with the following operations and guarantees:

  • Either peer can open streams of either kind at any time. The receiver of a stream can tell which peer opened it and of which kind it is.
  • Data written to a stream is delivered reliably and in order within that stream. Streams are mutually independent: delay or loss on one stream MUST NOT prevent delivery of data on other streams.
  • A sender can finish its sending direction of a stream, signalling that no more data will be sent on it.
  • A peer can reset a stream it is sending on, abandoning it with an application error code (see Application Error Codes), and can cancel the peer's sending direction of a stream, requesting with an application error code that the peer stop sending.
  • Either peer can close the connection with an application error code.
  • The receiver of a stream can apply per-stream backpressure to bound how much data it must buffer.
  • Each peer can bound the number of streams of each kind that the other peer may have concurrently open. A node SHOULD allow at least 32 concurrent bidirectional streams, and SHOULD allow at least 8 concurrent unidirectional streams (sufficient for the announcement streams plus headroom for future types).

How each transport realizes these operations is specified in the corresponding transport section (for QUIC, see Stream Layer Mapping).

Stream Types

CodeNameKindDescription
0x00HandshakeBidirectional, initiator onlyConnection handshake and control (see Connection Handshake).
0x01get-headersBidirectionalRequest block headers, optionally with transaction IDs.
0x02get-blocksBidirectionalRequest full blocks.
0x03get-txBidirectionalRequest transactions.
0x04get-addrBidirectionalRequest peer addresses.
0x05get-mempoolBidirectionalSubscribe to mempool contents.
0x06get-hashesBidirectionalRequest best-chain block hashes and per-block sync-cost metadata.
0x07get-block-rangeBidirectionalStream a contiguous range of blocks, verified against an anchor hash.
0x08get-tree-rootsBidirectionalRequest per-block note commitment tree roots for a height range.
0x09get-objectBidirectionalRequest a range of a content-addressed synchronization artifact.
0x10Block announcementsUnidirectionalAnnounce new blocks (see Block Announcements).
0x11Transaction announcementsUnidirectionalAnnounce new transactions (see Transaction Announcements).
0x12Address announcementsUnidirectionalGossip peer addresses (see Address Announcements).

Stream type 0x00 and the ranges 0x01–0x0F and 0x10–0x1F are reserved for the handshake, request stream types, and announcement stream types respectively; future revisions are expected to assign new stream types from the matching range.

To refuse a stream opened by its peer, a node cancels the peer's sending direction of that stream with the applicable error code and, if the stream is bidirectional, also resets its own sending direction with the same code; refusing a unidirectional stream involves only the cancellation.

A node receiving a stream whose type byte it does not recognize MUST refuse that stream with the UNSUPPORTED_STREAM_TYPE error code (see Application Error Codes) and MUST NOT treat it as a connection error or assign a misbehavior penalty. This allows future stream types to be deployed without version gating.

A stream that is finished by its opener before a complete type byte has been received is a connection error of type PROTOCOL_ERROR.

Request Streams

A request stream is a bidirectional stream carrying exactly one request and its response:

  1. The requester opens a bidirectional stream, writes the stream type byte followed by the request in the format defined for that type, and finishes its sending direction.
  2. The responder writes the response in the format defined for that type and finishes its sending direction. (For a stream type that defines an open-ended response, the responder instead keeps its sending direction open; see get-mempool.)

Either peer may open request streams; there is no client/server asymmetry beyond the rules stated for individual stream types.

  • A responder MAY begin serving a request as soon as the request is syntactically complete, without waiting for the requester to finish its sending direction.
  • A responder that cannot or will not serve a request MAY reset its sending direction of the stream with the REFUSED error code instead of sending a response.
  • A requester that no longer needs a response SHOULD cancel the responder's sending direction with the CANCELLED error code; the responder SHOULD then reset the stream and abandon work on the request.
  • A reset or refused request stream is never resumed or reused. A requester that retries a request after a reset does so by opening a new stream, and the next byte the requesting side sends — the first byte of that new stream — MUST be the stream type byte, as for any other stream.
  • A node SHOULD apply an implementation-defined timeout to each outstanding request, cancelling the stream if the response does not complete in time.
  • A node MUST NOT send more than one request on a stream. Unless the stream type defines an open-ended response, any data following a complete request or response on a stream is a connection error of type PROTOCOL_ERROR.
  • A request or response that does not conform to the format defined for its stream type — including an unrecognized result value in a response — is a connection error of type PROTOCOL_ERROR, unless other handling is specified for the stream type.

A node bounds the number of concurrent requests a peer may have outstanding using the transport's stream concurrency limits (see Transport Requirements).

Announcement Streams

An announcement stream is a long-lived unidirectional stream carrying a sequence of records for one announcement topic.

  • After the connection handshake completes, each peer SHOULD open one announcement stream of each type it intends to send announcements on, and keep it open for the life of the connection.
  • A node MUST NOT open more than one announcement stream of a given type in the same direction at a time. A second concurrent stream of the same type is a connection error of type PROTOCOL_ERROR. If an announcement stream is reset or finished, the sender MAY open a replacement.
  • Announcements are best-effort: if transport backpressure prevents a record from being written promptly, the sender MAY drop the announcement rather than queueing it indefinitely.
  • A record on an announcement stream that does not conform to the format defined for its stream type — a nonconforming record — is a connection error of type PROTOCOL_ERROR, unless other handling is specified for the stream type. (New record formats are introduced under new protocol versions, so an unrecognized format is a violation, not a newer peer.)

Records

Each record on an announcement or handshake stream is encoded as a fixed-size 4-byte length prefix followed by that many bytes of payload:

SizeFieldDescription
4lengthPayload length in bytes (uint32, little-endian).
variespayloadRecord payload; format depends on the stream type.

The maximum length of a record payload — and of any individually length-prefixed element in a request or response, such as a serialized block — is 2,097,152 bytes (2 MiB). A node MUST treat a length prefix exceeding this limit as a connection error of type FLOOD.

A record MUST NOT be processed until it is complete. A stream that is finished in the middle of a record is a connection error of type PROTOCOL_ERROR.

Application Error Codes

The following application error codes are used when closing a connection, resetting a stream, or cancelling a peer's sending direction (see Transport Requirements):

CodeConstantUsage
0x00NO_ERRORGraceful connection close.
0x01PROTOCOL_ERRORThe peer violated this specification.
0x02UNSUPPORTED_STREAM_TYPEStream refusal: unrecognized stream type.
0x03OBSOLETEThe peer's protocol version is below the minimum for the current network epoch (see Network Upgrade Epoch Enforcement).
0x04SELF_CONNECTIONThe connection is to the node itself (see Init Record).
0x05FLOODThe peer exceeded a size or rate limit.
0x06MISBEHAVIORThe peer's misbehavior score reached the ban threshold (see Misbehavior and Banning).
0x07CANCELLEDStop-sending/reset: the request is no longer wanted.
0x08REFUSEDStream reset: the responder declines to serve this request.
0x09INTERNAL_ERRORThe sender encountered an internal error.

Where this document states that an event is a connection error of a given type, a node detecting that event MUST close the connection with the corresponding error code.

A node receiving an error code it does not recognize MUST treat it as INTERNAL_ERROR.

Data Types and Encoding

The integer types and byte order, the CompactSize variable-length integer encoding (including its canonicity requirement), and the string encoding are as specified in ZIP 204 21. As there, a node MUST reject non-canonical CompactSize encodings.

Every CompactSize count and length in this protocol is peer-supplied and may be as large as 2^64 − 1 on the wire, whatever bound this document places on its legitimate values. A node MUST check a count or length against its specified bound before using it, and MUST NOT allocate storage in proportion to a declared count before the corresponding data has been received. Where this document requires a bound on a sum of two counts, the node MUST also bound each addend, since a sum of two unbounded values can wrap and satisfy the bound on the sum. Likewise, a node computing an offset or a remaining length from peer-supplied values MUST perform the arithmetic in a way that cannot overflow or underflow.

Serialized Blocks

The Zcash Protocol Specification defines the encodings of block headers and transactions, but leaves the encoding of a whole block to the peer-to-peer protocol 12. A serialized block is encoded as follows, unchanged from the payload of the legacy block message:

SizeFieldDescription
variesheaderThe serialized block header 12 (including the Equihash solution).
variestx_countNumber of transactions in the block (CompactSize).
variestxnstx_count serialized transactions 11, in block order.

Network Address Record

Peer addresses use the addrv2 address record specified in ZIP 155 14; it is the only address encoding in this protocol. The record fields (time, services, networkID, sizeAddr, addr, port), the network IDs (IPV4, IPV6, TORV3, I2P, CJDNS), the 512-byte addr limit, the per-network-ID length and encoding rules, and the prohibition on gossiping addresses with unrecognized network IDs are all as specified in ZIP 155.

The port field is interpreted per transport: for QUIC endpoints it is the UDP port, and for TORV3 addresses it is the onion service virtual port (see Tor Transport). It MUST be 0 if not relevant for the network.

The time field of a relayed record is attacker-suppliable and unauthenticated: it is the relaying peer's claim about when the address was last seen, and nothing binds it to reality. Because it is the freshness signal an address manager ranks on, an unvalidated time set far in the future makes an attacker's addresses permanently the freshest entries of whatever buckets they occupy, and so a preferred choice at every outbound peer selection — an eclipse vector 22 that operates from inside the buckets, where the bucketing of Address Book Management affords no protection. A node therefore:

  • MUST clamp the time of a relayed record before storing it: a time more than 10 minutes in the future, or equal to 0, is replaced by the current time less 5 days.
  • SHOULD NOT relay, and SHOULD eventually evict, a record whose clamped time is more than 30 days in the past.
  • SHOULD NOT make the relayed time the primary key of outbound peer selection, and SHOULD prefer its own record of successful connections to an address over any peer's claim about it.

These are the protections legacy implementations applied in code; they are stated here because the field is load-bearing for peer selection.

Note: Each network ID implies the transport by which the address is reachable: IPV4, IPV6, and CJDNS addresses identify QUIC endpoints, and TORV3 addresses identify onion services of the Tor transport. I2P addresses are not reachable via either transport defined in this ZIP; they remain defined for gossip so that nodes can learn them ahead of a future revision that defines an I2P-capable transport.

Transaction References

Transactions are identified in announcements and requests by transaction references. Per ZIP 239 23, transactions with version ≥ 5 are relayed by wtxid (the txid followed by the authorizing data commitment auth_digest), and transactions with version ≤ 4 by txid. A transaction reference is encoded as a 1-byte type followed by a type-dependent identifier:

TypeNameTotal SizeDescription
0x01TXID33 bytesTransaction with version ≤ 4, identified by its 32-byte txid.
0x02WTXID65 bytesTransaction with version ≥ 5, identified by its 64-byte wtxid (txid followed by auth_digest). See ZIP 239 23.
0x03SHORTID39 bytesTransaction within a specific block, identified by a 32-byte block hash followed by a 6-byte short transaction ID (see Short Transaction IDs). Only valid in get-tx requests.

A transaction reference with an unrecognized type is a connection error of type PROTOCOL_ERROR. Using the wrong reference type for a transaction's version is subject to a misbehavior penalty only where the sender of the reference necessarily holds the transaction and so knows its version: in announcement stream records and get-mempool response records (see Misbehavior and Banning). A get-tx requester, by contrast, may not hold the transaction it is requesting, and can have derived the reference type from data supplied by another peer (see Requesting Missing Transactions); a responder receiving a get-tx reference whose type does not match the referenced transaction's version MUST answer that reference with a not-found result, and MUST NOT assign a misbehavior penalty for it.

Service Flags

Service flags are advertised in the services field of init records and network address records. Bit numbering is as in ZIP 204

NameBitDescription
NODE_NETWORK0The node is capable of serving the complete block chain.
NODE_TREE_ROOTS3The node maintains a per-block note commitment tree root index and serves get-tree-roots requests.
NODE_SYNC_ARTIFACTS4The node serves content-addressed synchronization artifacts via get-object requests.
NODE_NETWORK_LIMITED10The node is capable of serving at least the most recent 2,304 blocks (approximately two days).

A node that does not hold the complete block chain — because it synchronized from a state snapshot and has not finished backfilling, or because it prunes historical blocks — MUST NOT advertise NODE_NETWORK; it SHOULD advertise NODE_NETWORK_LIMITED if it can serve at least the most recent 2,304 blocks of its best chain. A node selecting peers to request historical blocks from uses these flags (see 13).

Bits 24–31 remain reserved for temporary experiments, as in ZIP 204 24.

A node MUST ignore service bits that it does not recognize, and SHOULD preserve them when relaying address records (see Address Relay).

The legacy NODE_BLOOM flag (bit 2) is retired along with BIP 37 Bloom filtering, and MUST NOT be advertised.

Connection Handshake

Peers perform an application handshake immediately after the transport connection is established.

Handshake Sequence

  1. The initiator opens a bidirectional stream with stream type 0x00 (the handshake stream) and sends an init record.
  2. The responder sends its own init record on the same stream.
  3. The handshake is complete for a peer once it has both sent and received an init record. The negotiated protocol version is min(local_version, remote_version).

The two init records are independent: the responder MAY send its init record as soon as it observes the handshake stream's type byte, without waiting for the initiator's init record to arrive.

Only the initiator may open the handshake stream, and a connection has exactly one: a handshake stream opened by the responder, or a second handshake stream, is a connection error of type PROTOCOL_ERROR.

A node MUST NOT open any other stream before it has sent its init record, and MUST NOT send announcement records or requests before the handshake is complete. A node MAY buffer streams received from a peer before the handshake completes, or MAY refuse them with REFUSED (see Stream Types); it MUST NOT process them before the handshake completes.

The handshake stream remains open for the life of the connection. Future record kinds on the handshake stream may be defined by later revisions; a node MUST ignore handshake-stream records whose kind it does not recognize. Finishing or resetting the handshake stream signals intent to disconnect; a node observing this SHOULD close the connection with NO_ERROR.

Records on the handshake stream use the encoding of Records. The record payload begins with a 1-byte record kind; kind 0x00 is the init record.

Init Record

The init record payload (following the kind byte) has the following format:

SizeFieldDescription
4versionThe sender's advertised protocol version (uint32).
variesservicesService flags (CompactSize-encoded uint64).
8nonceRandom nonce for self-connection detection (uint64).
variesuser_agentUser agent string (CompactSize-prefixed, max 256 bytes).
4start_heightBest block height known to the sender (uint32).
1relayWhether the sender wants transaction relay (uint8; 0 or 1).
1announceWhether the sender requests high-bandwidth compact block announcements (uint8; 0 or 1). See Compact Block Relay.
1full_idsWhether the sender requests full transaction IDs in compact block announcements (uint8; 0 or 1). See Full Transaction IDs.

All fields are mandatory. The legacy timestamp, addr_recv, and addr_from fields are removed.

The nonce field is used for self-connection detection. If a node receives an init record containing a nonce it recently sent in its own init records, it MUST close the connection with the SELF_CONNECTION error code.

If relay is 0, the peer MUST NOT open a transaction announcement stream to the sender, and SHOULD NOT announce transactions to it by any other means.

There is no mechanism for changing the value of the relay, announce, or full_ids fields during the life of a connection; a node that wants to change them disconnects and performs a new handshake.

Handshake Validation

A receiving node MUST validate the init record as follows:

  • The version field MUST be at least the minimum protocol version of this ZIP (see Protocol Versioning), and at least the protocol version associated with the current network epoch (see Network Upgrade Epoch Enforcement). On failure, the node MUST close the connection with the OBSOLETE error code.
  • The nonce is checked for self-connection as specified in Init Record.
  • The user_agent string MUST NOT exceed 256 bytes.
  • The relay, announce, and full_ids fields MUST each be 0 or 1.
  • A peer MUST NOT send more than one init record per connection.

Except where another error code is specified above, a node MUST close the connection with the PROTOCOL_ERROR error code if any of these checks fail.

Handshake Example (non-normative)

Connection establishment over the QUIC transport:

Initiator                                     Responder
   ═══ QUIC handshake (ALPN "zcash/main") ═══
   ── open stream 0x00: type byte, init ────▶
   ◀──────── same stream: init ──────────────
        (handshake is complete for each peer once it
         has both sent and received an init record)
   ── open announcement streams 0x10..0x12 ─▶
   ◀─ open announcement streams 0x10..0x12 ──
   ...requests and announcements flow in both directions...

Protocol Versioning

Protocol versions are 32-bit integers, advertised in the version field of the init record. The negotiated protocol version of a connection is the minimum of the two peers' advertised versions. The negotiated protocol version determines whether the peer meets the minimum version requirements for the current network epoch (see Network Upgrade Epoch Enforcement), and gates any features that later revisions of this protocol introduce under new protocol versions.

Protocol versions inhabit a single numbering space shared with the legacy protocol: a protocol version is assigned to a (network, network upgrade) pair, not to a P2P protocol revision, and denotes the same version whether it is advertised in a legacy version message or in an init record. The association of protocol versions with network upgrades, and the procedure by which future network upgrades are assigned protocol versions, are specified in ZIP 204 25 and are not duplicated here.

At the time of writing, the current protocol version — advertised by nodes implementing the legacy protocol — is 170160 (PROTOCOL_VERSION). The protocol version from which the protocol specified by this ZIP is deployed has not yet been assigned; it will be the protocol version that ZIP 204's assignment procedure 25 assigns to the network upgrade that deploys this protocol (see Deployment). That version is the minimum peer protocol version of this protocol: every node implementing this ZIP necessarily advertises at least that version, and version-gated features of the legacy protocol (such as MSG_WTX relay and addrv2 support) are unconditionally in effect.

Network Upgrade Epoch Enforcement

Each network upgrade defines a minimum protocol version. When a network upgrade activates (as defined in ZIP 200 3), a node MUST disconnect any peer whose negotiated protocol version is less than the protocol version associated with the current epoch, using the OBSOLETE error code.

The protocol versions associated with network upgrades on each network are tabulated in ZIP 204 26.

In the preference window before an upgrade's activation, a node SHOULD preferentially connect to peers advertising the upcoming epoch's protocol version, as specified in ZIP 201 27.

Request Stream Types

This section defines each request stream type. For each type, the request and response formats and their semantics are specified. All requests and responses follow the lifecycle of Request Streams.

get-headers

Stream type: 0x01

Request:

SizeFieldDescription
varieslocator_countNumber of block locator hashes (CompactSize).
varieslocator_hashesBlock locator hashes (each 32 bytes), from highest to lowest height.
32hash_stopHash of the last desired header, or all zeros to request as many as possible.
1tx_idsWhether each returned header should be accompanied by the block's coinbase transaction and transaction IDs (uint8; 0 or 1).

Response:

SizeFieldDescription
variescountNumber of entries (CompactSize).
variesentriescount entries, each encoded as follows.

Each entry:

SizeFieldDescription
variesheaderThe block header, encoded as a CompactSize length prefix followed by the serialized header 12 (including the Equihash solution).
1has_txsPresent only if the request had tx_ids = 1. 0x01: the coinbase transaction and transaction IDs follow. 0x00: nothing further follows for this entry.
variescoinbasePresent only if has_txs is 0x01: the block's coinbase transaction, encoded as a CompactSize length prefix followed by the serialized transaction 11.
variesids_countPresent only if has_txs is 0x01: the number of transaction IDs (CompactSize).
variesidsPresent only if has_txs is 0x01: the full transaction IDs (64 bytes each; see Full Transaction IDs) of the block's transactions other than the coinbase transaction, in block order.

Requests block headers starting after the first block locator hash found in the responder's best chain, up to and including hash_stop or 160 headers (MAX_HEADERS_RESULTS), whichever comes first. The locator_count MUST NOT exceed 101, and tx_ids MUST be 0 or 1.

If none of the locator hashes are found in the responder's best chain, headers start at height 1 (the block after the genesis block). If locator_count is 0, the response consists solely of the entry for the header whose hash is hash_stop, if the responder has it. A response with count = 0 is valid and indicates that the responder has no headers to return.

If tx_ids is 1, each entry additionally identifies the block's transactions: the coinbase transaction in full (it is never otherwise relayed), and every other transaction by its full transaction ID, letting the requester fetch only the transactions it is missing via get-tx (see Relay Protocol). A responder that cannot supply a returned header's transactions sets has_txs to 0x00 for that entry, and the requester falls back to get-blocks.

A node performing bulk synchronization SHOULD set tx_ids to 0: the transaction-ID form pays off only near the chain tip, where the requester already holds most of a block's transactions. Because the ids array is not individually length-prefixed, the record payload limit of Records does not bound it, and a tx_ids = 1 request over large historical blocks would otherwise compel a response of hundreds of megabytes from a request of a few dozen bytes. Both directions are therefore bounded:

  • An entry's ids_count MUST NOT exceed 65,536, the greatest number of transactions a block can contain (see Compact Block Encoding). A larger value is a connection error of type FLOOD.
  • A responder SHOULD bound the total serialized size of its response, and MAY set has_txs to 0x00 for any entry, or return fewer entries, to stay within that bound; the requester falls back to get-blocks for those entries.

The response count MUST NOT exceed 160, and MUST NOT exceed the number of headers the request could have selected. The headers MUST form a contiguous chain (each header's hashPrevBlock must match the hash of the preceding header). A node receiving a response with more than 160 headers, or with non-contiguous headers, SHOULD discard the response and assign a misbehavior penalty (see Misbehavior and Banning); this is the handling of such responses, in place of the connection error that Request Streams would otherwise prescribe.

The legacy always-zero transaction count that followed each header in headers messages is removed.

get-blocks

Stream type: 0x02

Request:

SizeFieldDescription
variescountNumber of block hashes (CompactSize).
varieshashescount block hashes (each 32 bytes).

Response: For each requested hash, in request order:

SizeFieldDescription
1result0x00: a full block follows. 0x02: not found (nothing follows for this entry).
variesobjectIf result is 0x00: a CompactSize length prefix followed by the serialized block (see Serialized Blocks).

Requests full blocks by hash; the hashes to request are learned from headers, announcements, or get-hashes responses. The count MUST NOT exceed 128. Compact blocks cannot be requested (see Compact Block Relay).

Each delivered block MUST be the block that was requested: the header of the block delivered for an entry MUST hash to that entry's requested hash. A delivered block that does not is a connection error of type PROTOCOL_ERROR — the check is by hashing alone, so a violation is never attributable to a different chain view. Without this rule a peer could answer every entry with some other valid block, choosing which of two competing blocks the requester sees while remaining syntactically conformant.

A count of 128 bounds the number of blocks but not their size, and blocks differ in size by three orders of magnitude across the chain's history. A responder SHOULD therefore also apply a byte budget, and MAY finish its sending direction after any complete entry, having answered fewer entries than were requested; the requester re-requests the remainder from any peer. For bulk synchronization, get-block-range — which is byte-bounded by construction — is the appropriate primitive (see 13).

get-tx

Stream type: 0x03

Request:

SizeFieldDescription
variescountNumber of transaction references (CompactSize).
variestxrefscount transaction references (see Transaction References). All three reference types are permitted.

Response: For each requested reference, in request order:

SizeFieldDescription
1result0x00: a transaction follows. 0x02: not found (nothing follows for this entry).
variestxIf result is 0x00: a CompactSize length prefix followed by the serialized transaction 11.

Requests transactions by reference. The count MUST NOT exceed 50,000.

Each delivered transaction MUST be the transaction that was requested: it MUST match the corresponding entry's reference, under the identifier that reference names (txid, wtxid, or the short transaction ID relative to the named block). A delivered transaction that does not is a connection error of type PROTOCOL_ERROR.

As with get-blocks, the count bound does not bound the response in bytes: 50,000 references occupy a request of a few megabytes but could name transactions totalling far more, an amplification the count limit alone does not address. A responder SHOULD apply a byte budget to the response, and MAY finish its sending direction after any complete entry; the requester re-requests what it still needs. A requester SHOULD size a request to the bytes it is prepared to receive rather than to the count limit.

SHORTID references identify a transaction within a block relayed by a compact block; their handling is specified in Requesting Missing Transactions.

get-addr

Stream type: 0x04

Request: Empty (the stream is finished immediately after the stream type byte).

Response:

SizeFieldDescription
variescountNumber of address records (CompactSize).
variesaddressescount network address records (see Network Address Record).

Requests peer addresses from the remote node. The response count MUST NOT exceed 1000; a node receiving a larger response SHOULD discard the response and assign a misbehavior penalty (see Misbehavior and Banning); this is the handling of such responses, in place of the connection error that Request Streams would otherwise prescribe.

Each returned record carries the address's last-known service flags in its services field (see Service Flags and Network Address Record). A responder SHOULD set that field to the services it last observed the address advertise in an init record, or the services the record was relayed to it with if it has never connected to the address. The requester MAY use the field to select candidate peers offering the services it needs (for example, NODE_SYNC_ARTIFACTS or NODE_TREE_ROOTS), but relayed service flags are an unauthenticated claim: the peer's actual services are established only by the services field of its own init record upon connecting, and a node MUST NOT treat an address book entry's flags as more than a selection hint.

To impede address-based fingerprinting attacks, a node SHOULD send get-addr only on outbound connections, at most once per connection, and SHOULD only answer get-addr requests on inbound connections (resetting its sending direction of the stream with REFUSED otherwise).

get-mempool

Stream type: 0x05

Request: Empty (the stream is finished immediately after the stream type byte).

Response: Open-ended. The response is a sequence of records (see Records), each a single transaction reference (see Transaction References) of type TXID or WTXID; each field of a reference is fixed in size for its type. SHORTID references MUST NOT be used.

Subscribes to the contents of the peer's transaction memory pool. The responder first sends a record for each transaction currently in its mempool (the snapshot; no snapshot records indicate an empty mempool). It then keeps its sending direction open, and sends further records referencing transactions as they are accepted into its mempool, until the stream or connection ends. The requester fetches transactions of interest with get-tx.

This is the one stream type whose response is open-ended (see Request Streams): the responder finishing its sending direction ends the subscription (for example, on shutdown), and a requester that no longer wants the subscription cancels the responder's sending direction with CANCELLED. A node MUST NOT open more than one concurrent get-mempool stream to the same peer; a second concurrent subscription is a connection error of type PROTOCOL_ERROR.

Records after the snapshot SHOULD be subject to the same trickling delay as transaction announcements (see Trickling). The responder MAY omit references it has already sent to the same peer — in the snapshot, in an earlier record, or on a transaction announcement stream — and the requester MUST tolerate duplicate references.

Serving the snapshot costs the responder a full serialization of its mempool for a request of one byte, so a node SHOULD rate-limit get-mempool per peer — a peer that repeatedly cancels and re-opens the stream is requesting that work again each time — and SHOULD decline the request from a peer for which it has set relay = 0 in its init record, since such a peer has declined transaction relay in the other direction. A node concerned about topology inference SHOULD apply the trickling delay of Trickling to the post-snapshot records, as above; without it the subscription reports each transaction's arrival at the responder without delay, which is exactly what trickling exists to obscure.

get-hashes

Stream type: 0x06

Request:

SizeFieldDescription
4start_heightHeight of the first requested block hash (uint32, little-endian).
4countMaximum number of block hashes requested (uint32, little-endian).

Response:

SizeFieldDescription
variescountNumber of entries (CompactSize).
variesentriescount entries, each encoded as follows.

Entry k, for k from 0 to count − 1, describes the block at height start_height + k of the responder's best chain:

SizeFieldDescription
32hashHash of the block at height start_height + k.
variessizeThe block's serialized size in bytes (see Serialized Blocks) (CompactSize).
variestxsNumber of transactions in the block, including the coinbase transaction (CompactSize).
variestxoutsNumber of transparent outputs created by the block's transactions, including coinbase outputs (CompactSize).
variesnotesNumber of note commitments added by the block: two per JoinSplit description, plus one per Sapling Output description, plus one per Orchard-protocol Action description (in the Orchard or, from NU6.3, the Ironwood pool) (CompactSize).

Requests the hashes of count consecutive blocks of the responder's best chain starting at start_height, together with per-block sync-cost metadata. A requester that needs coarser granularity (for example, the spacing of its checkpoints; see 13) aggregates the per-block values itself.

The size, txs, txouts, and notes fields are scheduling hints: they let a synchronizing node estimate, before downloading, the download volume and the validation and note-commitment-tree work in each range of the chain, and so divide download work across peers and interleave transfer with computation to reach the tip fastest (see 13). Cumulative txouts values additionally locate each block's bits within the spentness-hint bitmap of 13. They are deterministic functions of the block — for a given hash, every honest responder returns identical values. A node MUST NOT rely on them for any consensus or validity purpose.

The requested count MUST NOT exceed 50,000, and the greatest requested height (start_height + count − 1) MUST NOT exceed 0xFFFFFFFF.

The response count MUST NOT exceed the requested count, MAY be less, and MAY be 0: a node omits requested heights above its chain tip, and SHOULD NOT include entries for blocks within 100 blocks of its chain tip (which could still be affected by a chain reorganization; the same margin as the reorganization rule of Checkpointed Synchronization). Truncation MUST be a prefix: if fewer entries are returned than were requested, the returned entries MUST correspond to the lowest requested heights, so that the requester can re-request the missing tail.

Responses are not self-authenticating: a node MUST NOT rely on the returned hashes for any security-relevant purpose until they are verified, per the untrusted-inputs rule of Checkpointed Synchronization. The metadata, by contrast, is checkable after the fact: once a node has downloaded an entry's block, it SHOULD verify any hints it relied upon, and SHOULD assign a misbehavior penalty (see Misbehavior and Banning) if txs or notes do not match the downloaded block. Those two fields are determined by the block hash alone, so a mismatch is not attributable to a different chain view.

size is not. A block's serialized size depends on its authorizing data — notably scriptSig lengths — which the txid, and hence the block hash, does not commit to (see get-block-range and ZIP 244 28). A node that obtained the hints from one peer and the blocks from another therefore MUST NOT penalize a size mismatch unless it has verified the delivered blocks' authorizing data commitment; otherwise a peer that pads the blocks it serves would cause the penalty to fall on the honest peer that served the hints, which is an eviction primitive rather than a defense. A size mismatch on unverified blocks is instead grounds to re-fetch the blocks.

get-block-range

Stream type: 0x07

Request:

SizeFieldDescription
32final_hashHash of the highest block in the requested range.
4countMaximum number of blocks requested (uint32, little-endian). MUST NOT exceed 65,536.
4max_bytesMaximum total serialized size of the delivered blocks, in bytes (uint32, little-endian). MUST NOT exceed 67,108,864 (64 MiB).

Response:

SizeFieldDescription
1result0x00: blocks follow. 0x02: not found (the responder does not have the block with hash final_hash; nothing follows).
variesblocksA sequence of serialized blocks, each encoded as a CompactSize length prefix followed by the serialized block (see Serialized Blocks), in descending height order: the first block is the block whose hash is final_hash, and each subsequent block is the parent of the previous one.

get-block-range is the bulk block download primitive: where get-blocks requests individual blocks by hash, get-block-range streams a contiguous chain of blocks identified by a single anchor — typically a hash the requester has verified against a trusted commitment, a validated header, or an already-verified block (see 13).

The responder streams blocks until it has delivered count blocks, or until delivering the next block would bring the total serialized size of the delivered blocks above max_bytes, whichever comes first — except that it MUST be willing to deliver the first block regardless of max_bytes, so that a maximum-size block is always retrievable. A response that exceeds either bound — more than count blocks, or any block after the first whose delivery brings the total serialized size above max_bytes — is a connection error of type FLOOD; both bounds are exact, so an honest responder never exceeds them. Because block sizes vary by three orders of magnitude across the chain's history, max_bytes — not count — is what bounds the work a stream commits one peer to; scheduling recommendations are given in 13.

Delivery is in descending height order so that every block is verifiable on arrival: the first delivered block's header MUST hash to final_hash, each subsequent delivered block's header MUST hash to the hashPrevBlock of the previously delivered block, and each delivered block's transactions MUST match its header's transaction merkle root, and each delivered block's serialized size MUST NOT exceed the maximum block size permitted by the consensus rules. A delivered block that violates these rules is a connection error of type PROTOCOL_ERROR — the rules are checkable by hashing alone, so a violation is never attributable to a different chain view. A requester with a trusted anchor therefore needs no download handles for the range's interior blocks and can assign blame exactly.

These checks bind each delivered block to the committed chain, but they do not bind all of its bytes. The transaction merkle root commits to txids, and from NU5 a v5 transaction's txid excludes its authorizing data — scriptSigs, proofs, and signatures — which is committed separately by hashAuthDataRoot within the header's hashBlockCommitments (see ZIP 244 28). Recovering the authorizing data commitment from the header requires the chain history root at the parent block, which arrival-time checking does not have, so this check is necessarily deferred to validation time. A responder can consequently deliver blocks that satisfy every rule above while carrying arbitrary authorizing data, padded up to the element size limit.

A requester therefore MUST NOT store a delivered block, serve it to another peer, or use it to regenerate a synchronization artifact until its authorizing data commitment has been checked against its header. A block whose authorizing data commitment fails that check SHOULD incur a misbehavior penalty of 100 points against the peer that delivered it: the delivered block matches the committed chain by txid, so the discrepancy is attributable to that peer and not to a different chain view. A requester SHOULD also bound the bytes it accepts on a stream by max_bytes, independently of the block count, since a block may be padded well beyond the size its hints predicted.

The responder serves the ancestor chain of the block with hash final_hash, whether or not that block is in its best chain. It MAY also finish its sending direction early, before either request bound is reached (for example, to bound the resources committed to one stream). Truncation for any reason is resumable, since the requester knows the next expected hash (the hashPrevBlock of the last delivered block) and MAY re-request the remainder — from the same or a different peer — using it as final_hash; a stream truncated by max_bytes thereby self-chunks a long span into byte-bounded, individually verified units even when the requester has no per-block size information. A requester that no longer wants the remainder cancels the responder's sending direction with CANCELLED; delivered blocks remain verified and usable. Transport flow control bounds the buffering on both sides.

get-tree-roots

Stream type: 0x08

Request:

SizeFieldDescription
4start_heightHeight of the first requested entry (uint32, little-endian).
32final_hashHash of the block at the highest requested height, start_height + count − 1 (see below).
4countMaximum number of entries requested (uint32, little-endian). MUST NOT exceed 4,000.

Response:

SizeFieldDescription
variescountNumber of entries (CompactSize).
variesentriescount entries, each encoded as follows.

Entry k, for k from 0 to count − 1, describes the block at height start_height + k of the chain identified by final_hash — the block with hash final_hash and its ancestors:

SizeFieldDescription
32sapling_rootRoot of the Sapling note commitment tree after this block.
32orchard_rootRoot of the Orchard note commitment tree after this block.
32ironwood_rootRoot of the Ironwood note commitment tree after this block.
variessapling_txsNumber of transactions in this block with Sapling components, as counted by the block's chain history tree leaf (CompactSize; see ZIP 221 29).
variesorchard_txsNumber of transactions in this block with Orchard-pool components (CompactSize).
variesironwood_txsNumber of transactions in this block with Ironwood-pool components (CompactSize).
32auth_data_rootThe block's authorizing data commitment hashAuthDataRoot (see ZIP 244 28).

For a pool that is not active at the entry's height, the corresponding root field is 32 zero bytes and the corresponding count is 0.

Requests the per-block data from which a synchronizing node can reproduce each block's note commitment tree roots and chain history tree without recomputing the trees from the blocks' note commitments — the dominant CPU cost of validating historical blocks under checkpoint-based synchronization.

The final_hash field anchors the request to a specific chain: the requester sets it to the hash — taken from headers it has already authenticated (see 13) — of the block at the highest requested height, and the requested entries describe that block and its ancestors, which final_hash identifies uniquely. A responder whose best chain does not contain the block with hash final_hash at height start_height + count − 1 MUST refuse the stream with REFUSED rather than serve entries for different blocks. An honest peer with a divergent chain view therefore refuses rather than serving mismatching entries, so an entry that fails verification is never attributable to a different chain view (cf. the tolerance rule of Checkpointed Synchronization, which this anchoring makes unnecessary here).

The entries are not self-authenticating: a node MUST NOT rely on any part of an entry for any purpose until it has verified that part against the chain's header commitments, as specified in 13. Which commitment applies depends on the height, and changes at Heartwood rather than at NU5: between Sapling and Heartwood activation the header field hashFinalSaplingRoot is the block's Sapling root and is compared directly; from Heartwood activation ZIP 221 29 repurposes that field to carry the chain history root (hashChainHistoryRoot, and from NU5 hashBlockCommitments, which binds it together with the authorizing data commitment of ZIP 244 28), and entries are verified by reconstructing the chain history tree.

Not every field of an entry is authenticated at every height. The chain history tree leaf defines no Ironwood field, so no header commits to an ironwood_root; and reconstruction verifies an entry only once the following block's header has been checked against it, so the highest entry of a response is not yet verified when the response completes. A node MUST NOT rely on an unverified root, and recomputes the corresponding tree instead.

An entry that fails verification against an authenticated header SHOULD incur a misbehavior penalty (see Misbehavior and Banning). A node MUST NOT assign that penalty on the basis of a check that does not apply at the entry's height: in particular, comparing a supplied Sapling root against hashFinalSaplingRoot above Heartwood activation compares it against an unrelated commitment and would penalize every honest responder.

Serving get-tree-roots requires a per-block root index that not every node maintains (in particular, a node that itself synchronized without computing historical trees may not have one). A node that maintains the index advertises the NODE_TREE_ROOTS service flag (see Service Flags). The response MAY contain fewer entries than requested, and MAY be empty; truncation MUST be a prefix (the returned entries MUST correspond to the lowest requested heights). A node without the index refuses the stream with REFUSED. The response count MUST NOT exceed the requested count.

get-object

Stream type: 0x09

Request:

SizeFieldDescription
32hashSHA-256 hash of the requested object.
8offsetByte offset into the object at which to start (uint64, little-endian). MUST NOT exceed 2^48 − 1.
4lengthMaximum number of bytes requested (uint32, little-endian). MUST NOT exceed 33,554,432 (32 MiB).

Response:

SizeFieldDescription
1result0x00: the object is available; its size and data follow. 0x02: not found (nothing follows).
variessizeTotal size of the object in bytes (CompactSize).
variesdataThe object's bytes from offset, up to length bytes or the end of the object, whichever comes first.

Requests a byte range of a synchronization artifact: an immutable, content-addressed object identified by the SHA-256 hash of its contents. The protocol does not interpret artifact contents; artifacts are named by trusted commitments and synchronization metadata (for example, known-hash chunk files and state snapshot pieces; see 13), and a node serves whichever artifacts it holds or can reproduce deterministically from its chain state. A node that serves artifacts advertises the NODE_SYNC_ARTIFACTS service flag (see Service Flags).

The responder MAY finish its sending direction early, having delivered fewer than the requested bytes; the requester detects the shortfall from size and MAY re-request the remainder from any peer, since object bytes are position-addressed and identical everywhere. An object is verified by hashing its complete contents; piece sizing conventions that keep each piece independently fetchable and verifiable are specified in 13.

The number of bytes returned is determined as follows, and a responder MUST evaluate the two cases in this order:

  1. If offset is greater than or equal to the object's size, the response is result = 0x00, the object's size, and no data.
  2. Otherwise the responder returns at most min(length, size − offset) bytes, starting at offset.

Evaluating the bound as min(offset + length, size) is not equivalent: the sum can wrap, yielding an end position below offset and a byte count that underflows to an enormous value. offset is bounded above for the same reason, and a responder MUST reject a request whose offset exceeds that bound as a connection error of type PROTOCOL_ERROR.

A response carrying more than length bytes of data, or data extending beyond the end of the object as given by its own size field, is a connection error of type FLOOD.

The same discipline applies to the requester. A response's size is peer-supplied and unauthenticated until the assembled object is hashed: a requester MUST NOT compute a remaining length as size − received without first checking that size is at least received, and a peer reporting a size inconsistent with a previous response for the same hash, or below the bytes it has already delivered, is a connection error of type PROTOCOL_ERROR.

Object data is verified only by hashing the complete object, so misdelivery is attributable only when a single peer delivered every byte: a node that has fetched all of an object's bytes from one peer, and finds that they do not hash to the requested hash, SHOULD assign that peer a misbehavior penalty (see Misbehavior and Banning), so that a peer answering with well-formed garbage is deselected rather than reassigned the same work indefinitely. A node that assembled an object from ranges delivered by more than one peer MUST NOT assign a misbehavior penalty when the assembled object fails verification — any of the contributing peers could be at fault — and SHOULD instead discard the data and re-fetch the object with each candidate peer serving the whole object, which isolates a misbehaving peer. Disagreement between peers about an object's size is resolved by the hash check, not penalized.

Announcement Stream Types

Block Announcements

Stream type: 0x10

Each record payload on a block announcement stream has the following format:

SizeFieldDescription
1kind0x00: header announcement. 0x01: compact block announcement.
variespayloadFor kind = 0x00: a serialized block header 12. For kind = 0x01: a compact block (see Compact Block Encoding).

Blocks are announced immediately; they are not subject to the trickling delay applied to transactions.

A node announces a new block to a given peer in one of two ways:

  1. If the peer requested high-bandwidth compact block announcements (the announce field of its init record was 1), by sending a compact block announcement (kind = 0x01).
  2. Otherwise, by sending header announcements (kind = 0x00) for the new block and for any intermediate blocks the peer has not yet been sent, up to a small implementation-defined limit. This follows the design of BIP 130 30.

A node MUST NOT send compact block announcements to a peer whose init record had announce = 0. Header announcements MAY be sent to any peer, regardless of its announce preference; in particular, if a block's compact block encoding would exceed the maximum record payload length (see Records), the sender announces that block with a header announcement instead.

A node SHOULD NOT send a header announcement for a block it has not fully validated, except as the oversize substitute above for a compact block announcement to a peer that requested high-bandwidth announcements. Correspondingly, the pre-validation penalty exemption of Relay Protocol applies to announcements of either kind received on a connection on which the receiving node requested high-bandwidth announcements (its init record had announce = 1); a header announcement received where the receiver's announce was 0 carries no such exemption, and an announced block that fails full validation is subject to the rules of Misbehavior and Banning.

An announcing node SHOULD only announce a block if it expects the peer to be able to connect the announced header to the peer's known chain (for example, because the parent block was previously announced to or by that peer). A node receiving an announcement whose header does not connect to its known chain SHOULD recover by sending a get-headers request, and MUST NOT assign a misbehavior penalty solely because a header does not connect.

Transaction Announcements

Stream type: 0x11

Each record payload on a transaction announcement stream is a single transaction reference (see Transaction References) of type TXID or WTXID. SHORTID references MUST NOT be used in announcements.

A node MUST NOT open a transaction announcement stream to a peer whose init record had relay = 0.

Announcements are subject to the trickling delay of Trickling. Interested peers fetch announced transactions with get-tx requests.

Address Announcements

Stream type: 0x12

Each record payload on an address announcement stream is a single network address record (see Network Address Record).

Address announcements are subject to the rate limits and address book management practices of Address Relay.

Block Relay

Divided Block Relay

This protocol divides block headers and block transactions, rather than transferring monolithic block messages as the legacy protocol did:

  • Block headers are gossiped directly on block announcement streams, as in BIP 130 30.
  • Block transactions are relayed separately from headers using compact block relay (adapted from BIP 152 31): a block is transferred as its header plus identifiers of its transactions, and only the transactions the receiver is missing are requested and transferred, using get-tx requests.

Both mechanisms are unconditional; the per-connection preferences are carried in the announce and full_ids fields of the init record (see Init Record).

Compact Block Relay

Compact block relay is adapted from BIP 152 31. This section specifies the protocol flows and the Zcash-specific differences from BIP 152; BIP 152 provides the design rationale.

The differences from BIP 152 are:

  • There is no sendcmpct message. Compact block relay is unconditional, and the high-bandwidth announcement preference is carried in the announce field of the init record (see Init Record). (A future change to the compact block encoding would be deployed under a new protocol version.)
  • A compact block cannot be requested: BIP 152's getdata with MSG_CMPCT_BLOCK has no equivalent, and compact blocks occur only as high-bandwidth announcements. A peer announced to in low-bandwidth mode instead requests the block's coinbase transaction and transaction IDs via get-headers with tx_ids = 1 (see Relay Protocol).
  • A compact block can identify the block's transactions either by short transaction IDs or by full transaction IDs, at the receiver's option (the full_ids field of the init record; see Full Transaction IDs). BIP 152 supports only short transaction IDs.
  • There are no getblocktxn and blocktxn messages. Missing transactions are requested with get-tx requests (using SHORTID, TXID, or WTXID references) and delivered in the corresponding responses; see Requesting Missing Transactions.
  • The header field of a compact block uses the Zcash block header encoding 12, including the Equihash solution, rather than the 80-byte Bitcoin header.
  • The input to the short transaction ID computation is based on the wtxid as defined in ZIP 239 23 for version 5 transactions, rather than on either the txid (BIP 152 version 1) or the BIP 141 wtxid (BIP 152 version 2); see Short Transaction IDs.

Compact Block Encoding

A compact block relays a block as its header plus a compact representation of its transactions:

SizeFieldDescription
variesheaderThe block header, encoded as a CompactSize length prefix followed by the serialized header 12 (including the Equihash solution).
8nonceNonce for short transaction ID computation (uint64, little-endian). If ids_kind is 1, this field SHOULD be 0 and MUST be ignored.
1ids_kind0 if ids contains short transaction IDs; 1 if it contains full transaction IDs (uint8).
variesids_countNumber of transaction IDs (CompactSize).
variesidsIf ids_kind is 0: short transaction IDs, 6 bytes each, little-endian (see Short Transaction IDs). If ids_kind is 1: full transaction IDs, 64 bytes each (see Full Transaction IDs).
variesprefilled_countNumber of prefilled transactions (CompactSize).
variesprefilled_txnsPrefilled transactions (see below).

Each transaction in the block, in block order, is represented either by a transaction ID in ids (short or full, according to ids_kind) or by a full serialized transaction in prefilled_txns; the total number of transactions in the block is ids_count + prefilled_count. Each of ids_count and prefilled_count MUST NOT exceed 65,536, and their sum MUST NOT exceed 65,536; a compact block violating any of these is a connection error of type FLOOD. Bounding the two counts individually is required as well as bounding the sum: each is an independent CompactSize that may be as large as 2^64 − 1 on the wire, and a sum of two such values can wrap to a small number that satisfies a bound the addends do not.

A node MUST set ids_kind to 1 in compact blocks sent to a peer that set full_ids = 1 in its init record, and SHOULD set ids_kind to 0 otherwise. A compact block with an ids_kind value other than 0 or 1 is a nonconforming record (see Announcement Streams).

Each entry of prefilled_txns has the following format:

SizeFieldDescription
variesindexDifferentially encoded index of this transaction within the block (CompactSize; see below).
variestxA full serialized transaction, encoded as a CompactSize length prefix followed by the serialized transaction 11.

Prefilled transaction indexes are differentially encoded as in BIP 152 31. A compact block in which any absolute index is greater than or equal to ids_count + prefilled_count, or in which indexes overflow or are not strictly increasing, is a nonconforming record (see Announcement Streams). The bound is the block's own transaction count, not the protocol maximum: an index below 65,536 can still lie outside a small block, and a reconstructor sized to the block's actual transaction count would then write out of bounds.

The coinbase transaction MUST be prefilled. A sender SHOULD additionally prefill any transaction that it predicts the receiver does not have.

Short Transaction IDs

The short transaction ID of a transaction, relative to a given compact block, is computed as specified in BIP 152 31, with two substitutions:

  1. The input to SipHash-2-4 is the transaction identifier used for relay of the transaction: for a transaction with version ≥ 5, its 64-byte wtxid as defined in ZIP 239 23 (the txid followed by the auth_digest); for a transaction with version ≤ 4, its 32-byte txid.
  2. The header hashed together with the nonce field to derive the SipHash keys is the serialized Zcash block header 12 (as it appears in the header field, without the length prefix).

The nonce SHOULD be chosen uniformly at random by the sender of a compact block, so that short ID collisions between blocks and senders are independent.

Full Transaction IDs

The full transaction ID of a transaction is the 64-byte value consisting of the transaction's txid followed by its auth_digest. For a transaction with version ≥ 5, this is the wtxid used for relay, as defined in ZIP 239 23. For a transaction with version ≤ 4, the auth_digest is the placeholder value consisting of 32 bytes of 0xFF defined in ZIP 244 28.

Matching against full transaction IDs is exact — they are not subject to collisions and involve no nonce. A node unable to do short-ID matching (for example, a pruned node) requests full transaction IDs by setting full_ids = 1 in its init record, at the cost of larger compact blocks.

Requesting Missing Transactions

Transactions that could not be matched while reconstructing a block from a compact block are requested with get-tx requests, rather than with dedicated messages as in BIP 152 (getblocktxn and blocktxn, which identify transactions by index within the block):

  • If the compact block had ids_kind = 0, each unmatched short transaction ID is requested as a SHORTID transaction reference containing the block hash and the short transaction ID copied from the compact block. Because short transaction IDs depend on the nonce of the compact block they appeared in, the responding node interprets them using the nonce of the compact block it most recently sent to the requesting peer for the identified block. A node receiving a SHORTID reference for a block for which it has not recently sent that peer a compact block, or whose short transaction ID matches no transaction — or more than one transaction — in the identified block, SHOULD answer that reference with a not-found result.
  • If the compact block had ids_kind = 1 — and likewise for each unmatched full transaction ID from a get-headers response with tx_ids = 1 (see get-headers) — each unmatched full transaction ID is requested as an ordinary transaction reference: a TXID reference containing the txid if the auth_digest half of the full transaction ID is the 32-byte 0xFF placeholder (indicating a pre-v5 transaction), or a WTXID reference containing the full transaction ID otherwise.

A node that has sent a peer a compact block, or a get-headers entry carrying transaction IDs, MUST be able, for a reasonable implementation-defined time thereafter, to serve that block's transactions to the same peer via get-tx — including by SHORTID references in the compact block case, and irrespective of whether those transactions remain in its mempool.

Relay Protocol

A node follows the BIP 152 31 protocol flows:

  • High-bandwidth mode (the receiver's init record had announce = 1): the sender announces a new block by sending a compact block announcement directly. To minimize propagation latency, the sender MAY do so as soon as it has validated the block header (checking proof of work and difficulty), before fully validating the block. Because a high-bandwidth announcement may legitimately precede full validation by the announcing peer, a node MUST NOT assign a misbehavior penalty for a high-bandwidth announcement — a compact block announcement, or a header announcement substituted for one (see Block Announcements) — of a block that fails full validation, provided the block's header is valid (including its proof of work); the same exemption covers the block's transactions served under Requesting Missing Transactions. A node SHOULD request high-bandwidth announcements from at most 3 peers, preferring the peers that most recently announced blocks to it first; changing the selection requires a new connection (see Init Record).
  • Low-bandwidth mode (the receiver's init record had announce = 0): blocks are announced to the receiver with header announcements (see Block Announcements). The receiver MAY then request the block's coinbase transaction and transaction IDs — a get-headers request with tx_ids = 1 whose hash_stop is the announced block hash — and reconstruct the block, fetching the transactions it is missing with get-tx; or it MAY request the full block via get-blocks. The receiver SHOULD use the transaction-ID form only for blocks close to its chain tip (BIP 152 31 recommends within 5 blocks), and request full blocks otherwise.

Upon receiving a compact block — or the equivalent header, coinbase transaction, and transaction IDs from a get-headers response with tx_ids = 1 — a node attempts to reconstruct the block by matching each transaction ID against the transactions it already holds (in its mempool or otherwise) — computing short transaction IDs for the held transactions if the IDs are short (ids_kind = 0), or comparing full transaction IDs directly otherwise:

  • If all transactions are available, the node reconstructs and validates the block as if it had been received as a full block.
  • If one or more transactions are missing, or (for short transaction IDs) if two or more held transactions collide on the same short ID, the node requests the unresolved transactions with a get-tx request (see Requesting Missing Transactions), and completes reconstruction as the response arrives.
  • If any requested transaction is answered with a not-found result, or if reconstruction fails after the responses arrive (for example, because a short ID collision caused a wrong match and the reconstructed block is invalid), the node SHOULD fall back to requesting the full block via get-blocks, and MUST NOT assign a misbehavior penalty solely because reconstruction failed.

An incorrectly reconstructed block fails validation of the block's merkle root and authorizing data commitment, so short ID matching does not weaken consensus enforcement.

Compact Block Relay Example (non-normative)

A new block reaches peer B (high-bandwidth mode) and peer C (low-bandwidth mode) from peer A:

A ── block announcements ────▶ B : compact block (header, short
                                   IDs, prefilled coinbase)
     B matches the short IDs against transactions it
     holds; two are unknown
B ── get-tx ─────────────────▶ A : [SHORTID, SHORTID]
A ── response ───────────────▶ B : the two transactions
     B reconstructs the block and validates it

A ── block announcements ────▶ C : header announcement
C ── get-headers ────────────▶ A : locator, hash_stop = block
                                   hash, tx_ids = 1
A ── response ───────────────▶ C : header, coinbase, full
                                   transaction IDs
     C matches the IDs, fetches missing transactions
     via get-tx, reconstructs, and validates

Synchronization

Headers-First Synchronization

Nodes synchronize the block chain using a headers-first approach; it is the baseline synchronization method of this protocol, which every node supports. It is the full-validation method: it assumes no trusted data beyond the consensus rules and the genesis block. (An alternative method for nodes with trusted checkpoint data is specified in Checkpointed Synchronization; recommended concrete synchronization strategies are specified in 13.)

  1. The synchronizing node sends a get-headers request with a block locator (typically with tx_ids = 0; see get-headers).
  2. The remote peer responds with up to 160 headers.
  3. The synchronizing node validates the headers and requests full blocks via get-blocks requests.
  4. Steps 1–3 repeat until the node is synchronized with the network.

The following rules apply to headers-first synchronization:

  • A node MUST validate each received header — including its proof of work and its difficulty adjustment — before requesting the corresponding block and before extending its header chain with it.
  • Received headers, even with valid proof of work, establish only that work was expended: a node MUST NOT treat a block as part of its best chain until the block itself has been validated under the consensus rules. Until then, headers serve to select and schedule block downloads.
  • A node MUST NOT assign a misbehavior penalty solely because a peer's headers do not connect to its known chain or reflect a different best chain (see Block Announcements); penalties apply to provably invalid headers and blocks, per Misbehavior and Banning.

Checkpointed Synchronization

A node MAY synchronize spans of the historical chain against trusted commitments: checkpoint data — bindings of block heights to block hashes — obtained through a channel the node already trusts, such as its own binary or local configuration. The bulk primitives of this protocol — get-hashes, get-block-range (whose descending delivery authenticates every block against a committed anchor on arrival), get-tree-roots, and get-object — exist to serve such procedures; a recommended concrete strategy is specified in 13. Any checkpoint-based synchronization procedure MUST obey the following rules.

  • Validated advancement. A node MUST NOT advance its validated chain except through blocks that it has either validated under the consensus rules, or authenticated by an unbroken hash chain terminating in a checkpoint bound by a trusted commitment. The extent to which consensus checks may be abbreviated for checkpoint-authenticated blocks is a matter of local policy.
  • Untrusted inputs. Peer-supplied data — hashes, headers, blocks — MUST NOT be relied upon for any security-relevant purpose until verified under the previous rule; until then it MAY be used only to select and schedule downloads. Data received from peers MUST NOT be incorporated into a node's trusted commitments.
  • Tolerance of divergent views. A node MUST NOT assign a misbehavior penalty to a peer solely because the peer's get-hashes responses fail verification against the node's commitments, or reflect a different best chain; such responses are discarded and MAY be retried with other peers.
  • Reorganization margin. Commitment-based authentication MUST NOT be applied within 100 blocks of the node's view of the network chain tip; the chain near the tip is synchronized headers-first. The responder-side margin of get-hashes is the same depth, measured the same way, so that a responder's hints and a requester's authentication agree about which blocks are near enough to the tip to be unsettled.
  • Headers-first fallback. A node MUST be capable of completing synchronization using headers-first synchronization alone, and MUST fall back to it where its commitments end or where too few peers serve get-hashes.

The commitment scheme itself — checkpoint spacing, any chunking, and the hash or signature scheme binding it — is local to the node and out of scope for this protocol.

Block Download Parameters

For headers-first synchronization and near-tip block fetching via get-blocks, the block download parameters of ZIP 204 32 — the download window, the per-peer in-transit limit, and the stalling timeout — apply unchanged. On a stall, the node MAY re-request the block from an alternative peer, cancelling the original request stream with CANCELLED.

Bulk transfer via get-block-range and get-object is not governed by those parameters: in-flight volume is bounded in bytes by transport flow control and by the synchronizing node's scheduling policy, for which 13 gives recommendations (work unit sizing, per-peer budgets, and stall detection).

Transaction Relay

Announcement-Based Relay

Transaction relay follows an announcement-based protocol:

  1. A node with a new transaction sends a transaction reference on its transaction announcement stream to each peer (subject to trickling).
  2. Peers that want the transaction request it with a get-tx request.
  3. The originating node serves the transaction in the get-tx response.

Announcements use TXID and WTXID references according to transaction version, as specified in Transaction References.

Trickling

To impede network topology inference, transaction announcements SHOULD NOT be sent immediately but SHOULD instead be "trickled" at random intervals. The specific parameters are implementation-defined; for the reference values used by legacy implementations, see ZIP 204 33.

Transaction Expiry

The avoidance of relaying transactions that are about to expire (TX_EXPIRING_SOON_THRESHOLD) is unchanged from the legacy protocol; see ZIP 204 33.

Mempool Policy

Orphan transaction handling and the minimum relay fee are local mempool policy, unchanged from the legacy protocol; see ZIP 204 33.

Address Relay

Address relay allows nodes to discover peers by propagating network address records through the network, via address announcement streams and get-addr requests.

Rate Limiting

Address records SHOULD be subject to rate limiting to prevent address flooding. The specific mechanism is implementation-defined; for the token-bucket reference values used by zcashd, see ZIP 204 34.

A node MAY additionally apply backpressure on the peer's address announcement stream to bound the rate at which it receives address records.

Address Book Management

Relayed addresses are unauthenticated, attacker-suppliable data; an address book that one source can fill leads to eclipse 22. A node SHOULD segment its address book by IP range, in the manner of the Bitcoin Core address manager design:

  • Define an address's group as an IP-range prefix of its address — for reference, Bitcoin Core and zcashd use the /16 prefix for IPv4 and the /32 prefix for IPv6 — and, for a relayed address, its source group as the group of the peer that relayed it.
  • Partition the address book into a bounded number of bounded-size buckets, and assign each address to a bucket determined by its group and its source group (keyed with a node-local secret), so that addresses from any one source group can occupy only a bounded fraction of the buckets. When a bucket is full, evict within the bucket; do not let insertions grow the book or displace addresses in other buckets.
  • Keep addresses learned from independently trusted mechanisms (DNS seeding, local configuration) distinguishable from relayed addresses, and do not let relayed addresses evict them.

A node SHOULD also diversify its outbound connections across groups: for reference, Bitcoin Core and zcashd make at most one outbound connection per group. Outbound peer selection SHOULD NOT be biased toward the addresses most recently received, since those are the easiest for an attacker to have planted.

Addresses of overlay networks (TORV3, I2P) have no IP-range structure and are free to generate, so bucketing cannot bound an attacker's share of them. A node SHOULD treat each overlay network as a separate bounded segment of its address book, and a node supporting both IP-based and overlay transports SHOULD retain a minimum number of outbound connections on IP-based transports.

Address Broadcasting

The specific broadcast intervals are implementation-defined; for the reference values used by zcashd, see ZIP 204 34.

How a node determines its own externally reachable addresses is out of scope for this ZIP; the legacy addr_recv-based self-discovery is removed (see Init Record).

Misbehavior and Banning

Peer misbehavior is handled by two mechanisms, split by severity. Unambiguous violations of "MUST"-level requirements — malformed records, invalid stream usage, exceeded hard limits — are connection errors: the node closes the connection immediately with the indicated error code (PROTOCOL_ERROR or FLOOD unless otherwise specified), with no score kept. Violations for which immediate disconnection would be disproportionate, or which an honest-but-buggy peer or a peer on a different chain could commit, instead accumulate a per-peer misbehavior score that leads to disconnection and banning only when it crosses a threshold.

Every misbehavior penalty — including any implementation-defined penalty assigned under the final row of the table below — is constrained by the following principle: a node MUST NOT assign a misbehavior penalty unless the violation is provable from the received data together with data the node has independently authenticated, does not depend on the node's own chain view, mempool contents, clock, or local policy, and could not be committed by a conformant peer acting on honest but divergent state. Behavior that fails this test — however suspicious — is instead handled by discarding the data, refusing or cancelling streams, rate limits and backpressure, or disconnection without a ban. Penalties assigned on weaker evidence are worse than none: they let an attacker who can arrange the appearance of misbehavior induce honest nodes to ban one another, narrowing each victim's peer set toward the attacker's own nodes.

The misbehavior score works as follows:

  • A node SHOULD maintain a misbehavior score for each peer. Scores SHOULD be keyed by network address and persist across connections for a period on the order of the ban duration: several penalties in this protocol are detected only after a delay (sync-cost metadata is checked once the corresponding block has been downloaded; tree-root entries verify one block behind), so a purely per-connection score would let a peer shed its score — or escape a deferred penalty entirely — by reconnecting. A penalty detected after the offending connection has closed SHOULD be applied to the address's score.
  • When the node detects one of the violations listed below, it adds the listed number of points to the peer's score:
PointsViolation
20get-headers response with more than 160 headers.
20Non-contiguous headers in a get-headers response.
20get-addr response with more than 1000 address records.
20get-hashes metadata (txs or notes) that does not match the downloaded block (see get-hashes).
100A get-tree-roots entry that fails verification against authenticated header commitments (see get-tree-roots).
100A block delivered by get-block-range whose authorizing data commitment fails verification against its header (see get-block-range).
100A get-object object, delivered entirely by the peer, that fails verification against the requested content hash (see get-object).
100Announcing a v5 transaction with a TXID reference, or a v4-or-earlier transaction with a WTXID reference, in an announcement stream record or a get-mempool response record (see Transaction References and ZIP 239 23). Wrong-typed references in get-tx requests are exempt.
variesTransaction, block, or header validation failure that is provable under the principle above: an invalid signature or proof, malformed structure, invalid proof of work, or a commitment mismatch, established with full context. The penalty is determined by the severity of the validation error, subject to the exemptions below.

Validation-failure penalties apply only where the invalidity proves that this peer misbehaved. In particular, a node MUST NOT assign a misbehavior penalty for:

  • Expiry. A transaction that is expired, or expires too soon, relative to the receiver's chain tip: relay and trickling delays make expiry races possible between honest peers (see Transaction Expiry).
  • Local policy. Rejection under local mempool policy — fee requirements, orphan handling, conflicts with mempool contents, or resource limits (see Mempool Policy).
  • Missing or moved state. A transaction whose inputs are unknown to the receiver or already spent in its current view, or any object the receiver lacks the context to validate.
  • Content of requested objects. Consensus invalidity of a block, transaction, or artifact that the node itself requested by hash or reference (get-blocks, get-block-range, get-tx, get-object): the responder served exactly the bytes the identifier names, and blame for the object's existence lies with the peer that announced it, under the rules above. (Delivered data that does not match the requested identifier is covered by each stream type's own rules.)
  • Block reconstruction service. Transactions served in fulfilment of the serving obligation of Requesting Missing Transactions (see the exemption in Relay Protocol).

Score thresholds and banning work as follows:

  • When a peer's score reaches or exceeds the node's ban threshold, the node SHOULD close the connection with the MISBEHAVIOR error code and ban the peer's network address.
  • While an address is banned, the node SHOULD NOT initiate connections to it, SHOULD refuse inbound connections from it, and SHOULD NOT include it in get-addr responses or address announcements.
  • The ban threshold and ban duration are implementation-defined; for the reference values used by legacy implementations, see ZIP 204 35.
  • Whitelisted peers accumulate misbehavior scores but are exempt from banning and from the threshold-triggered disconnection.

Bans are keyed by network address, and are only as strong as the cost of acquiring a new address. Banning is a meaningful deterrent for IP-based transports; for overlay networks whose addresses are free to generate (such as Tor onion services), it excludes only the banned address, and inbound connection limits — not ban lists — bound the node's exposure (see Address Book Management).

Address granularity cuts both ways even on IP transports. A single IPv6 operator typically controls at least a /64, so per-address IPv6 bans are nearly free to evade; a node MAY key IPv6 bans and scores by prefix (a /64 is a reasonable default). Conversely, a single IPv4 address behind carrier-grade NAT may be shared by many independent users, so a ban can exclude bystanders — a reason to keep ban durations bounded rather than to widen their scope.

Security and Privacy Considerations

Scope of transport encryption. Transport encryption protects the confidentiality and integrity of each connection against passive network observers. Peers are deliberately not authenticated (see Certificates), so an active attacker in a position to intercept a connection can terminate the encryption with its own certificate and read or modify that connection's traffic. The structural defense against an active attacker seeking to control a node's view of the network is redundancy: connections to multiple, independently discovered peers.

Denial of service. QUIC's address validation and amplification limits, including Retry packets, bound the cost that spoofed-source floods can impose (see QUIC Transport). At the application layer, resource consumption is bounded by the record payload limit (see Records), the stream concurrency limits (see Transport Requirements), the per-request count limits of each request stream type, and address rate limiting (see Address Relay); exceeding a hard size or rate limit is a connection error of type FLOOD.

A count limit is not by itself a bound on work, because the per-item size varies by orders of magnitude: get-block-range and get-object are therefore bounded in bytes as well (max_bytes and length), and for get-blocks, get-tx, and get-headers the responder applies its own byte budget and may finish early, with the requester re-requesting the remainder. The general shape of the defense is that a responder is never obliged to spend unbounded bandwidth, disk reads, or memory on one cheap request, and that every peer-supplied count or length is checked against its bound before it is used to size an allocation (see Data Types and Encoding).

Fingerprinting and linkability. A persistent TLS key would allow a node to be recognized across connections and network locations; a node concerned about this uses per-connection ephemeral keys (see Certificates). Connection migration links a node's traffic across network paths (see QUIC Transport). The user_agent field of the init record is a fingerprinting vector; a node MAY advertise a generic user agent string. The get-addr policy (see get-addr) impedes fingerprinting of a node's address book, and trickling (see Trickling) impedes inference of the network topology and of the origin of a transaction from announcement timing.

Traffic analysis. QUIC encrypts payloads but conceals neither packet timing and volume nor the IP addresses of the communicating peers. In particular, an adversary observing a node's traffic may still be able to identify it as the originator of a transaction. The Tor transport (see Tor Transport) defends against this adversary when it is local — an observer of the node's network, or the remote peer itself, learns neither the node's IP address nor which peers it communicates with — but not against a global adversary correlating traffic timing across the Tor network. The anticipated Nym mixnet transport (see Deployment) is directed at that stronger adversary.

Length leakage. Encryption conceals the content of application data but not its length: neither TLS over TCP nor QUIC pads application data by default, so encrypted packet sizes leak plaintext lengths. (QUIC defines PADDING frames 6, but an implementation must use them explicitly.) The Tor transport is coarser — Tor pads relay cells to a 509-byte body — so leakage there is granular rather than byte-exact. To remove the sharpest instance of this channel, every request format in this protocol whose fields are position-fixed (get-hashes, get-block-range, get-tree-roots, get-object, and the empty get-addr and get-mempool requests) uses fixed-length integer encodings: every request of a given type has a single wire length, smaller than a Tor cell body, so its encrypted size reveals at most the stream type and never the field values, and there is no way to misimplement the encoding so that it leaks lengths but still interoperates. Requests that carry variable-length lists (get-headers, get-blocks, get-tx) and responses necessarily reveal their gross size; a node MAY additionally use QUIC PADDING frames to obscure those lengths, at a bandwidth cost this specification does not mandate.

Tor. On a Tor transport connection the initiator is anonymous — it reveals no network address to the responder or to network observers — so a node SHOULD prefer the Tor transport (where available) for the traffic most sensitive to linkage, above all announcing transactions it originated. The responder's onion address is a stable, linkable identifier, and onion addresses are free to generate, which weakens address-based banning and address book protections for TORV3 addresses (see Misbehavior and Banning and Address Book Management).

Mixnets. The two kinds of traffic most sensitive to linkage are mempool transaction propagation — announcing a transaction links the announcing node to it, and a get-mempool subscription reveals interest in the mempool's contents — and address book queries, whose get-addr request/response pattern is a fingerprinting and mapping primitive. A node implementer SHOULD carry both over a mixnet transport once one is available (the anticipated Nym transport; see Deployment), and in the interim SHOULD carry them over the Tor transport where available, per the preference above. Mixnet delay and cover traffic address the timing correlation that onion routing alone does not.

Synchronization. Headers-first synchronization trusts proof of work: forging a history requires outspending the honest chain's accumulated work over the forged span, which an eclipsing attacker with sufficient hash power can in principle do. Checkpointed synchronization trusts the node's commitment, which shares a trust base with its binary: below its checkpoints, an attacker who cannot break the block hash function cannot cause acceptance of a false history. Under either method, an attacker controlling a node's connections can withhold data and stall synchronization; the mitigations are redundancy and re-requesting from alternative peers (see Block Download Parameters).

Eclipse attacks. An attacker that controls all of a node's connections controls its view of the block chain and mempool. The structural defenses are segmentation of the address book by IP range and diversity in outbound peer selection (see Address Book Management and 22), persisted addresses (see Peer Discovery), and rate-limited, validated address relay (see Address Relay). Overlay addresses such as onion services deserve particular caution here, since an attacker can generate them without limit (see Address Book Management).

Deployment

This ZIP replaces the legacy protocol without a compatibility bridge: nodes implementing this protocol do not interoperate with nodes implementing the legacy protocol. Deployment is coordinated through the network upgrade mechanism 3: the protocol version from which this protocol is in effect will be assigned to a network upgrade, and the epoch enforcement of ZIP 201 27 retires legacy-protocol peers at activation, as with any other network upgrade.

Because there is no compatibility bridge, an implementation deploying this protocol is expected to also implement the legacy protocol during the transition: before activation, a node participates in the network over the legacy protocol while also listening on the QUIC transport, so that DNS seeders and upgraded peers can discover and exercise QUIC endpoints ahead of activation. The QUIC transport uses UDP where the legacy transport uses TCP, so both can be served concurrently on the same port number and address. At activation, epoch enforcement retires legacy-protocol connections, and the node continues on this protocol alone.

DNS seeders are expected to probe and serve the QUIC endpoints of nodes implementing this ZIP.

The protocol semantics are specified against the abstract stream layer of Stream Layer; QUIC and Tor are the transports defined by this ZIP. Future revisions are expected to define additional transports to be used simultaneously with them; in particular, a transport carrying the stream layer over the Nym mixnet 36 is anticipated, providing network-level metadata protection (resistance to traffic analysis of which peers are communicating) stronger than onion routing provides.

Note: The transports are complementary rather than alternatives: a node is expected to use QUIC for latency-critical bulk relay and synchronization, and Tor (eventually Nym) where anonymity matters more than latency, selected per connection according to the traffic it will carry.

Formal Model

A TLA+ formal specification of the legacy protocol's connection lifecycle is available at 37; it has not yet been updated for the protocol specified here.

References

Footnotes

  1. Information on BCP 14 — "RFC 2119: Key words for use in RFCs to Indicate Requirement Levels" and "RFC 8174: Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words" ↩

  2. Zcash Protocol Specification, Version 2026.8.0 [NU6.3]. Section 3.12: Mainnet and Testnet ↩

  3. ZIP 200: Network Upgrade Mechanism ↩ ↩2 ↩3

  4. Zcash Protocol Specification, Version 2026.8.0 [NU6.3]. Section 3.3: The Block Chain ↩

  5. ZIP 204: Zcash P2P Network Protocol ↩ ↩2 ↩3 ↩4 ↩5

  6. RFC 9000: QUIC: A UDP-Based Multiplexed and Secure Transport ↩ ↩2 ↩3 ↩4 ↩5 ↩6

  7. Tor Rendezvous Specification — Version 3 Onion Services ↩ ↩2 ↩3

  8. RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3 ↩ ↩2

  9. RFC 9001: Using TLS to Secure QUIC ↩ ↩2

  10. BIP 324: Version 2 P2P Encrypted Transport Protocol ↩

  11. Zcash Protocol Specification, Version 2026.8.0 [NU6.3]. Section 7.1: Transaction Encoding and Consensus ↩ ↩2 ↩3 ↩4 ↩5

  12. Zcash Protocol Specification, Version 2026.8.0 [NU6.3]. Section 7.6: Block Header Encoding and Consensus ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9

  13. Draft ZIP: Block Chain Synchronization ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17

  14. ZIP 155: addrv2 message ↩ ↩2

  15. ZIP 204: Zcash P2P Network Protocol — DNS Seeds ↩

  16. RFC 9221: An Unreliable Datagram Extension to QUIC ↩

  17. RFC 7301: Transport Layer Security (TLS) Application-Layer Protocol Negotiation Extension ↩

  18. ZIP 204: Zcash P2P Network Protocol — Default Ports ↩

  19. RFC 7250: Using Raw Public Keys in Transport Layer Security (TLS) and Datagram Transport Layer Security (DTLS) ↩

  20. The Tor Project ↩

  21. ZIP 204: Zcash P2P Network Protocol — Data Types and Encoding ↩

  22. Ethan Heilman, Alison Kendler, Aviv Zohar, Sharon Goldberg. Eclipse Attacks on Bitcoin's Peer-to-Peer Network. 24th USENIX Security Symposium, 2015. ↩ ↩2 ↩3

  23. ZIP 239: Relay of Version 5 Transactions ↩ ↩2 ↩3 ↩4 ↩5 ↩6

  24. bit k is the bit with numeric weight 2k. ↩

  25. ZIP 204: Zcash P2P Network Protocol — Assigning Protocol Versions to Network Upgrades ↩ ↩2

  26. ZIP 204: Zcash P2P Network Protocol — Network Upgrade Epoch Enforcement ↩

  27. ZIP 201: Network Peer Management for Overwinter ↩ ↩2

  28. ZIP 244: Transaction Identifier Non-Malleability ↩ ↩2 ↩3 ↩4 ↩5

  29. ZIP 221: FlyClient - Consensus-Layer Changes ↩ ↩2

  30. BIP 130: sendheaders message ↩ ↩2

  31. BIP 152: Compact Block Relay ↩ ↩2 ↩3 ↩4 ↩5 ↩6

  32. ZIP 204: Zcash P2P Network Protocol — Block Download Parameters ↩

  33. ZIP 204: Zcash P2P Network Protocol — Transaction Relay ↩ ↩2 ↩3

  34. ZIP 204: Zcash P2P Network Protocol — Address Relay ↩ ↩2

  35. ZIP 204: Zcash P2P Network Protocol — Misbehavior and Banning ↩

  36. The Nym Mixnet ↩

  37. Zcash P2P Protocol TLA+ Specification ↩