Zcash Stratum V2 Protocol Reference

Complete message format documentation for Zcash Stratum V2.

Message Frame

All messages use this frame format:

+----------------+----------------+------------------+
|  Message Type  |  Payload Len   |     Payload      |
|    (1 byte)    |   (2 bytes)    |   (variable)     |
+----------------+----------------+------------------+
  • All integers are little-endian
  • Strings are length-prefixed (1-byte length + UTF-8 bytes)
  • Arrays are count-prefixed (varies by type)

Noise Encryption

When Noise is enabled, the frame above is encrypted after the handshake completes.

Pattern: Noise_NK_25519_ChaChaPoly_BLAKE2s

Handshake:

Client                              Server
  |                                    |
  |------- e, es (48 bytes) --------->|
  |<------ e, ee (48 bytes) ----------|
  |                                    |
  |  [Encrypted frames from here]      |

Message Types

Connection Setup

TypeNameDirection
0x00SetupConnectionC -> S
0x01SetupConnection.SuccessS -> C
0x02SetupConnection.ErrorS -> C

Mining Channel

TypeNameDirection
0x10OpenStandardMiningChannelC -> S
0x11OpenStandardMiningChannel.SuccessS -> C
0x12OpenStandardMiningChannel.ErrorS -> C
0x14UpdateChannelC -> S
0x15UpdateChannel.ErrorS -> C

Mining Operations

TypeNameDirection
0x1CSubmitSharesStandardC -> S
0x1DSubmitShares.SuccessS -> C
0x1ESubmitShares.ErrorS -> C
0x1FNewMiningJobS -> C
0x20NewPrevHashS -> C
0x21SetTargetS -> C

Job Declaration (Coinbase-Only)

TypeNameDirection
0x50AllocateMiningJobTokenC -> S
0x51AllocateMiningJobToken.SuccessS -> C
0x52SetCustomMiningJobC -> S
0x53SetCustomMiningJob.SuccessS -> C
0x54SetCustomMiningJob.ErrorS -> C
0x55PushSolutionC -> S

Job Declaration (Full-Template)

TypeNameDirection
0x56SetFullTemplateJobC -> S
0x57SetFullTemplateJob.SuccessS -> C
0x58SetFullTemplateJob.ErrorS -> C
0x59GetMissingTransactionsS -> C
0x5AProvideMissingTransactionsC -> S

Message Definitions

SetupConnection (0x00)

+----------+----------+----------+----------+
| protocol | min_ver  | max_ver  |  flags   |
|  2 bytes |  2 bytes |  2 bytes | 4 bytes  |
+----------+----------+----------+----------+
| host_len |    endpoint_host (UTF-8)       |
|  1 byte  |    variable                    |
+----------+--------------------------------+
|   port   | vendor_len |  vendor (UTF-8)  |
|  2 bytes |   1 byte   |   variable       |
+----------+------------+------------------+
| hw_len   | hardware_version (UTF-8)       |
| 1 byte   |    variable                    |
+----------+--------------------------------+
| fw_len   |   firmware (UTF-8)             |
| 1 byte   |    variable                    |
+----------+--------------------------------+
| dev_len  |   device_id (UTF-8)            |
| 1 byte   |    variable                    |
+----------+--------------------------------+

Fields:

  • protocol: 0 = Mining Protocol
  • min_version / max_version: Protocol version range (currently 2)
  • flags: Feature flags (0 for base features)

SetupConnection.Success (0x01)

+----------+----------+
| used_ver |  flags   |
|  2 bytes | 4 bytes  |
+----------+----------+

OpenStandardMiningChannel (0x10)

+------------+------------+--------------+-------------------+
| request_id |  user_len  |  user_ident  | nominal_hashrate  |
|  4 bytes   |   1 byte   |   variable   |     4 bytes       |
+------------+------------+--------------+-------------------+
| max_extranonce |
|     2 bytes    |
+----------------+

Fields:

  • request_id: Client-generated request identifier
  • user_identity: Worker identifier (e.g., “wallet.worker”)
  • nominal_hashrate: Expected hashrate as float32
  • max_extranonce: Maximum extranonce size client supports

OpenStandardMiningChannel.Success (0x11)

+------------+------------+---------------+------------------+
| request_id | channel_id | extranonce_len| extranonce       |
|  4 bytes   |  4 bytes   |    1 byte     | variable (<=32)  |
+------------+------------+---------------+------------------+
| group_channel_id |
|      4 bytes     |
+------------------+

NewMiningJob (0x1F)

+------------+----------+------------+
| channel_id |  job_id  | future_job |
|  4 bytes   | 4 bytes  |   1 byte   |
+------------+----------+------------+
|  version   |     prev_hash (32 bytes)     |
|  4 bytes   |                              |
+------------+------------------------------+
|        merkle_root (32 bytes)             |
+-------------------------------------------+
|     block_commitments (32 bytes)          |
+-------------------------------------------+
|   nbits    |   ntime    |
|  4 bytes   |  4 bytes   |
+------------+------------+

Fields:

  • channel_id: Mining channel this job belongs to
  • job_id: Server-assigned job identifier
  • future_job: If true, don’t start mining until NewPrevHash
  • version: Block version (5 for NU5+)
  • prev_hash: Previous block hash (32 bytes)
  • merkle_root: Merkle root of transactions (32 bytes)
  • block_commitments: hashBlockCommitments (32 bytes, NU5+)
  • nbits: Compact difficulty target
  • ntime: Block timestamp

SetTarget (0x21)

+------------+----------------------------------+
| channel_id |        max_target (32 bytes)     |
|  4 bytes   |                                  |
+------------+----------------------------------+

Fields:

  • max_target: Maximum hash value for valid share (big-endian)

NewPrevHash (0x20)

+------------+----------------------------------+
| channel_id |        prev_hash (32 bytes)      |
|  4 bytes   |                                  |
+------------+----------------------------------+
| min_ntime  |   nbits    |
|  4 bytes   |  4 bytes   |
+------------+------------+

Semantics: Invalidates all jobs with different prev_hash.

SubmitSharesStandard (0x1C)

+------------+----------+----------+
| channel_id | seq_num  |  job_id  |
|  4 bytes   | 4 bytes  | 4 bytes  |
+------------+----------+----------+
|           nonce (32 bytes)       |
+----------------------------------+
|   ntime    | solution_len |
|  4 bytes   |   2 bytes    |
+------------+--------------+------+
|     solution (1344 bytes)        |
+----------------------------------+

Fields:

  • channel_id: Mining channel
  • sequence_number: Incrementing submission counter
  • job_id: Job this solution is for
  • nonce: Full 32-byte nonce (NONCE_1 || NONCE_2)
  • ntime: Block timestamp (may be rolled)
  • solution: Equihash solution (1344 bytes for 200,9)

SubmitShares.Success (0x1D)

+------------+--------------+------------------+-----------------+
| channel_id | last_seq_num | new_submits_ok   | new_shares_sum  |
|  4 bytes   |   4 bytes    |     4 bytes      |     8 bytes     |
+------------+--------------+------------------+-----------------+

SubmitShares.Error (0x1E)

+------------+----------+------------+
| channel_id | seq_num  | error_code |
|  4 bytes   | 4 bytes  |  4 bytes   |
+------------+----------+------------+

Error Codes: | Code | Meaning | |------|---------| | 0x01 | Invalid channel | | 0x02 | Stale share | | 0x03 | Difficulty too low | | 0x04 | Invalid solution | | 0x05 | Duplicate share |

Job Declaration Messages

AllocateMiningJobToken (0x50)

+------------+----------+---------------------+---------------+
| request_id | user_len |  user_identifier    | requested_mode|
|  4 bytes   |  1 byte  |     variable        |    1 byte     |
+------------+----------+---------------------+---------------+

Fields:

  • requested_mode: 0 = CoinbaseOnly, 1 = FullTemplate

AllocateMiningJobToken.Success (0x51)

+------------+-----------+---------------------------+
| request_id | token_len |    mining_job_token       |
|  4 bytes   |  2 bytes  |       variable            |
+------------+-----------+---------------------------+
| coinbase_out_len | coinbase_output |
|     2 bytes      |    variable     |
+------------------+-----------------+
| coinbase_max_add | async_allowed | granted_mode |
|     4 bytes      |    1 byte     |    1 byte    |
+------------------+---------------+--------------+

SetCustomMiningJob (0x52)

+------------+------------+-----------+---------------------------+
| channel_id | request_id | token_len |    mining_job_token       |
|  4 bytes   |  4 bytes   |  2 bytes  |       variable            |
+------------+------------+-----------+---------------------------+
|  version   |     prev_hash (32 bytes)     |
|  4 bytes   |                              |
+------------+------------------------------+
|        merkle_root (32 bytes)             |
+-------------------------------------------+
|     block_commitments (32 bytes)          |
+-------------------------------------------+
| coinbase_tx_len |    coinbase_tx          |
|     2 bytes     |      variable           |
+-----------------+-------------------------+
|   ntime    |   nbits    |
|  4 bytes   |  4 bytes   |
+------------+------------+

SetFullTemplateJob (0x56)

+------------+------------+-----------+---------------------------+
| channel_id | request_id | token_len |    mining_job_token       |
|  4 bytes   |  4 bytes   |  2 bytes  |       variable            |
+------------+------------+-----------+---------------------------+
|  version   |     prev_hash (32 bytes)     |
|  4 bytes   |                              |
+------------+------------------------------+
|        merkle_root (32 bytes)             |
+-------------------------------------------+
|     block_commitments (32 bytes)          |
+-------------------------------------------+
| coinbase_tx_len |    coinbase_tx          |
|     2 bytes     |      variable           |
+-----------------+-------------------------+
|   ntime    |   nbits    |
|  4 bytes   |  4 bytes   |
+------------+------------+
| tx_count   |
|  2 bytes   |
+------------+-----------------------------+
|     tx_short_ids (32 bytes each x N)     |
+------------------------------------------+
| tx_data_count |
|    2 bytes    |
+---------------+
| For each tx_data:                        |
| tx_len (2 bytes) | tx_data (variable)    |
+------------------------------------------+

GetMissingTransactions (0x59)

+------------+------------+----------+
| channel_id | request_id | tx_count |
|  4 bytes   |  4 bytes   | 2 bytes  |
+------------+------------+----------+
|   missing_tx_ids (32 bytes each x N)     |
+------------------------------------------+

ProvideMissingTransactions (0x5A)

+------------+------------+----------+
| channel_id | request_id | tx_count |
|  4 bytes   |  4 bytes   | 2 bytes  |
+------------+------------+----------+
| For each transaction:                    |
| tx_len (2 bytes) | tx_data (variable)    |
+------------------------------------------+

Zcash-Specific Details

Block Header Structure

Zcash block headers are 140 bytes:

+-------------------------------------------+
|           version (4 bytes)               |
+-------------------------------------------+
|          prev_hash (32 bytes)             |
+-------------------------------------------+
|         merkle_root (32 bytes)            |
+-------------------------------------------+
|      block_commitments (32 bytes)         |
+-------------------------------------------+
|           ntime (4 bytes)                 |
+-------------------------------------------+
|           nbits (4 bytes)                 |
+-------------------------------------------+
|           nonce (32 bytes)                |
+-------------------------------------------+

Equihash Parameters

Zcash uses Equihash with:

  • n = 200 (bit width)
  • k = 9 (number of collisions)
  • Solution size = 1344 bytes

Nonce Structure

Nonce (32 bytes):
+------------------+--------------------------------+
|     NONCE_1      |            NONCE_2             |
|  (pool assigns)  |      (miner iterates)          |
|    4 bytes       |           28 bytes             |
+------------------+--------------------------------+

Difficulty Calculation

Convert compact nbits to target:

fn nbits_to_target(nbits: u32) -> [u8; 32] {
    let exponent = (nbits >> 24) as usize;
    let mantissa = nbits & 0x007fffff;

    let mut target = [0u8; 32];
    if exponent <= 3 {
        let shift = 8 * (3 - exponent);
        let value = mantissa >> shift;
        target[31] = (value & 0xff) as u8;
        target[30] = ((value >> 8) & 0xff) as u8;
        target[29] = ((value >> 16) & 0xff) as u8;
    } else {
        let byte_idx = 32 - exponent;
        target[byte_idx] = (mantissa & 0xff) as u8;
        target[byte_idx + 1] = ((mantissa >> 8) & 0xff) as u8;
        target[byte_idx + 2] = ((mantissa >> 16) & 0xff) as u8;
    }
    target
}

Block Commitments (NU5+)

The block_commitments field contains hashBlockCommitments:

hashBlockCommitments = BLAKE2b-256("ZcashBlockCommit" || ...)

This includes:

  • History tree root
  • authDataRoot (for shielded transactions)
  • Reserved future commitments

Error Codes Reference

Setup Errors

CodeMeaning
0x00Unknown error
0x01Unsupported feature flags
0x02Unsupported protocol version

Channel Errors

CodeMeaning
0x01Invalid user identity
0x02Max channels exceeded

Share Errors

CodeMeaning
0x01Invalid channel
0x02Stale share
0x03Difficulty too low
0x04Invalid solution
0x05Duplicate share

Job Declaration Errors

CodeMeaning
0x01Invalid token
0x02Token expired
0x03Invalid coinbase
0x04Coinbase constraint violation
0x05Stale prev_hash
0x06Invalid merkle root
0x07Invalid version
0x08Invalid bits
0x09Server overloaded
0x0AMode mismatch
0x0BInvalid transactions
0x0CToo many transactions
0xFFOther error

Implementation Notes

Endianness

  • All protocol integers: little-endian
  • Block header fields: little-endian (except hash comparisons)
  • Hash comparison: big-endian (for difficulty checks)

String Encoding

All strings are length-prefixed UTF-8:

| length (1 byte) | UTF-8 bytes |

Optional Noise

Noise encryption is negotiated out-of-band (client knows server’s public key beforehand). The protocol itself doesn’t negotiate encryption.

Connection Lifecycle

1. TCP connect
2. [Optional] Noise handshake
3. SetupConnection
4. OpenStandardMiningChannel
5. [Receive jobs, submit shares]
6. [Optional] Job Declaration flow
7. Connection close