# B2Pool Stratum Protocol **Version 1.13 — 2026-09-07** Canonical URL: This document describes exactly the Stratum dialect B2Pool speaks, in enough detail to write a miner against it from nothing. It is written to be read by a program as well as by a person: every field is given with its length, its byte order and its meaning, and the proof-of-work is given as a reference implementation with a test vector you can check your code against. Check the version line above to detect updates. Changes are listed at the end. --- ## 0. Read this first **B2Pool mines the BLAKE2b fork of Bitcoin (BIP-110), whose proof of work is BLAKE2b — not SHA-256.** A SHA-256 ASIC cannot mine this chain. It will connect, authorise, submit shares and have every single one of them rejected, because the pool verifies a different hash than the machine computed. **A BLAKE2b job is not a Bitcoin job with the hash swapped out.** The wire format differs in four ways that will break a miner written from the classic Stratum V1 description: | | Bitcoin Stratum | B2Pool | |---|---|---| | `mining.notify` `coinb2` | the second half of the coinbase | **always empty** | | `mining.notify` merkle branch | the merkle path | **always empty** | | `mining.notify` `ntime` | 4 bytes | **8 bytes** | | `mining.submit` `nonce` | 4 bytes | **8 bytes** | The miner never sees the coinbase transaction and never builds a merkle root. It is handed a 39-byte `coinb1` that already commits to the whole block, and it rebuilds a *work root* from that plus its extranonce. Section 5 gives the construction. --- ## 1. Chain | | | |---|---| | Chain | Bitcoin with BLAKE2b proof of work (BIP-110); ticker **BTC** | | Node software | Bitcoin Knots 29.4.1 or later | | Proof of work | BLAKE2b-256 over an 80-byte work header | | Fork height (mainnet) | 961640 | | Block header | header-v2, 164 bytes | --- ## 2. Endpoints Plain TCP. There is no TLS on the stratum ports. | Hardware | Shared (TIDES) | Solo | |---|---|---| | ASIC | 3333 | 3334 | | FPGA and GPU | 4444 | 4445 | | CPU | 5555 | 5556 | Available on `de.b2pool.io` (Frankfurt), `hel.b2pool.io` (Helsinki) and `ord.b2pool.io` (Chicago). All regions serve the same pool and the same balance; the ports mean the same thing everywhere. Every host resolves to both an A and an AAAA record and the pool listens on both address families. Per-port difficulty: | Port pair | Starting difficulty | Never goes below | Ceiling | |---|---|---|---| | 3333 / 3334 | 16384 | 1024 | 8388608 | | 4444 / 4445 | 128 | 128 | 8388608 | | 5555 / 5556 | 1 | 1 | 8388608 | The pool retargets towards **one share every 30 seconds** after 72 shares or 240 seconds, whichever comes first. Difficulty 1 is the floor of the chain, not a pool policy: one difficulty-1 share costs 2³² ≈ 4.29 billion hashes and nothing smaller exists. --- ## 3. Framing - One JSON object per line, UTF-8, terminated by `\n` (`0x0A`). - Requests carry `id`, `method`, `params`. Responses carry `id`, `result`, `error`. Notifications from the pool carry `id: null`. - `error` is `null` on success, otherwise `[code, "message", null]`. - Requests may be pipelined; the pool does not require you to wait for one reply before sending the next message, and replies may arrive in a different order than the requests. - All binary values on the wire are lowercase hex strings. --- ## 4. Handshake The minimum exchange, in the order a miner should send it: ``` -> {"id":1,"method":"mining.subscribe","params":["myminer/1.0"]} -> {"id":2,"method":"mining.extranonce.subscribe","params":[]} -> {"id":3,"method":"mining.authorize","params":["bc1q…vty4.rig01","x"]} ``` ### 4.1 mining.subscribe ``` -> {"id":1,"method":"mining.subscribe","params":["myminer/1.0"]} <- {"id":1,"result":[[["mining.notify","6a9d9fba"]],"ba9f9d6a",8],"error":null} ``` `params[0]` is an optional user agent string. An optional `params[1]` is a session id for resumption. The result is `[subscriptions, extranonce1, extranonce2_size]`: - `extranonce1` — hex, **4 bytes** (8 hex characters), unique to this connection - `extranonce2_size` — **8**, the number of bytes you choose per share **`extranonce1` and `extranonce2` must total exactly 12 bytes.** That is fixed by the work-root construction in section 5 and is not negotiable. ### 4.2 mining.extranonce.subscribe ``` -> {"id":2,"method":"mining.extranonce.subscribe","params":[]} <- {"id":2,"result":true,"error":null} ``` Supported. It is answered before the pool requires you to be subscribed or authorised, so you may send it at any point, including pipelined with `mining.subscribe`. Having subscribed, you must handle `mining.set_extranonce` (section 6.3). ### 4.3 mining.authorize ``` -> {"id":3,"method":"mining.authorize","params":["bc1q…vty4.rig01","x"]} <- {"id":3,"result":true,"error":null} ``` - `params[0]` — **your payout address**, optionally followed by `.` and a rig name. The address is the account; there is no registration and no password. On the solo ports this address receives the whole block, so check it. - `params[1]` — ignored. `d=` sets a difficulty floor of your own, which the pool honours as long as it is above the port's floor. The two port families validate differently, and the difference matters: - **Solo ports.** The address is checked against the node. An address it cannot validate is refused with `result: false`, because a solo block is paid in the coinbase and there would be nobody to pay. - **Shared ports.** Any username authorises. The block pays the pool, which distributes afterwards, so the name is only an accounting key — but **only a valid address can actually be paid out**. Mining the shared ports under a name that is not an address accrues a balance that cannot be sent anywhere. Always use your address. ### 4.4 mining.configure ``` -> {"id":0,"method":"mining.configure","params":[["version-rolling"], {"version-rolling.mask":"1fffe000"}]} <- {"id":0,"result":{"version-rolling":true,"version-rolling.mask":"1fffe000"},"error":null} ``` Accepted at any time. Version rolling is negotiated the usual way, but note that on this chain the version reaches the hash only through the commitment already baked into `coinb1` — see section 5.4. --- ## 5. Work ### 5.1 mining.notify ``` <- {"id":null,"method":"mining.notify","params":[ "6a9d9fba", // job_id "000000000000c1a3…", // prevhash, 32 B "0000001b11a8e2c1…00000000", // coinb1, 39 B "", // coinb2, always empty [], // merkle branch, always empty "20000000", // version, 4 B "17030ecd", // nbits, 4 B "00000000c0ffee00", // ntime, 8 B true // clean_jobs ]} ``` | # | Field | Length | Notes | |---|---|---|---| | 0 | `job_id` | — | echo it back in `mining.submit` | | 1 | `prevhash` | 32 B | **not** the previous block hash — a hidden form of it, see 5.2 | | 2 | `coinb1` | 39 B | contains the header commitment | | 3 | `coinb2` | — | always `""` | | 4 | `merkle_branch` | — | always `[]` | | 5 | `version` | 4 B | for reference; does not enter the work header | | 6 | `nbits` | 4 B | **network** target — the one a block must beat, not your share target | #### Two targets, and they are not the same number This is the single most common misreading of this protocol, so it is spelled out here. Every miner deals with **two** thresholds: | | Where it comes from | What it means | |---|---|---| | **Share target** | `mining.set_difficulty` (V1) · `SetTarget.maximum_target` (V2) | Beat this and the pool credits you. Happens constantly. | | **Network target** | `nbits` in the job (V1) · `SetNewPrevHash.nbits` (V2) | Beat this and you have found a **block**. Happens rarely. | The share target is many orders of magnitude easier. On 4444 it is difficulty 128 while the network sits near 285,000,000 — so about one share in two million is also a block. A miner that compares its hash against the share target and calls the result a block candidate will announce one on every accepted share. Both are sent on every port and every protocol version, and both mean the same thing everywhere: as of 7 September 2026 the DATUM ports carried the share target in `nbits` and no longer do. If you want the network difficulty for a display or an earnings estimate, `nbits` is the field; if you want to know whether to submit, that is the share target. **Nothing about submission changes with this.** You never tell the pool that a share is a block: `mining.submit` and `SubmitSharesStandard` carry the same fields either way, the pool re-derives the hash, and it decides. Your own classification is a display matter. | 7 | `ntime` | **8 B** | a starting value for nonce space, see 5.5 | | 8 | `clean_jobs` | bool | `true` means abandon current work immediately | `prevhash`, `coinb1` and `ntime` go into the hash **verbatim, as byte strings**. Do not reverse them, and do not treat them as 32-bit word arrays the way the classic Bitcoin `prevhash` is handled. ### 5.2 The hidden prevhash Field 1 is not the previous block's hash. The pool computes ``` hidden = tagged_sha256("Bitcoin prevblock header, hashed", reverse(prevblockhash)) hidden[0..6] = 0 ``` where `tagged_sha256(tag, m) = SHA256(SHA256(tag) ‖ SHA256(tag) ‖ m)` (the BIP340 construction). The top six bytes are cleared so that a hasher cannot see or reach the network target. **You receive the finished value and use it as it comes**; the derivation is documented only so you can recognise it. ### 5.3 The work root ``` work_root = BLAKE2b-256( 0x00 ‖ coinb1 ‖ extranonce ) ``` - `coinb1` — the 39 bytes from `mining.notify` - `extranonce` — `extranonce1 ‖ extranonce2`, **exactly 12 bytes** - the leaf is therefore 1 + 39 + 12 = **52 bytes** `extranonce2` is yours to choose; it is your primary nonce space. ### 5.4 The work header and the hash ``` work_header = hidden_prevhash(32) ‖ nonce(8) ‖ ntime(8) ‖ work_root(32) // 80 bytes H = BLAKE2b-256(work_header) // 32 bytes ``` That is the whole proof of work. There is no double hashing and no SHA-256 in this path. The block's version, timestamp, merkle root, height and transaction count all reach the hash through the commitment inside `coinb1`. This is why the miner does not need them and why they are not rolled. > A pool may XOR the result with a mask derived from a per-block XOR key. B2Pool > does not use one: the key is all zeroes and the mask is therefore all zeroes, > so `H` is used unchanged. A miner written for this pool can ignore it. ### 5.5 Nonce space Three fields are yours to roll, giving 2¹²⁸ of space in total: | Field | Size | Where it lands | |---|---|---| | `extranonce2` | 8 B | into `work_root` | | `nonce` | 8 B | work header, offset 32 | | `ntime` | 8 B | work header, offset 40 | **`ntime` is nonce space, not a timestamp.** It is *not* the block time. In the 164-byte block header its low four bytes become `m_time_offset` and its high four become `m_nonce3`, and the block's actual `nTime` comes from the job, not from you. B2Pool never sets the header's time-offset flag, so the pool ignores the value entirely when deriving the block time and applies no range check to it. Roll all eight bytes freely. Start from the value in `mining.notify`. Changing `extranonce2` requires recomputing `work_root`; changing `nonce` or `ntime` does not. ### 5.6 Difficulty and target The usual Stratum convention, with ``` diff1 = 0xFFFF * 2^208 ``` Interpret `H` as a **big-endian** 256-bit integer `h`. Then ``` share_difficulty = diff1 / h ``` A share is worth submitting when `share_difficulty >= D`, where `D` is the difficulty from the most recent `mining.set_difficulty`, i.e. when ``` h <= diff1 / D ``` A hash whose top 32 bits are not zero can never be a valid block on this chain and is rejected outright. --- ## 6. Messages from the pool ### 6.1 mining.set_difficulty ``` <- {"id":null,"method":"mining.set_difficulty","params":[16384]} ``` Applies from the next `mining.notify` onward. The pool accepts shares at the previous difficulty until the next job, because few miners switch precisely. ### 6.2 mining.notify Section 5.1. Sent on every new job. `clean_jobs = true` means the chain tip moved: abandon current work at once, because shares for the old job will be rejected as stale. ### 6.3 mining.set_extranonce ``` <- {"id":null,"method":"mining.set_extranonce","params":["44a29d6a",8]} ``` Sent only if you subscribed with `mining.extranonce.subscribe`. - `params[0]` — the new `extranonce1` - `params[1]` — the new `extranonce2_size` **Apply it to the next `mining.notify` you receive, even if the job id is unchanged.** It always arrives before the job it applies to. Continuing on the old extranonce produces shares above target and every one of them is rejected. ### 6.4 client.show_message ``` <- {"id":null,"method":"client.show_message","params":["Block 968455 solved by …"]} ``` Informational. Display it or ignore it. --- ## 7. Submitting ``` -> {"id":9,"method":"mining.submit","params":[ "bc1q…vty4.rig01", // worker, as authorised "6a9d9fba", // job_id from mining.notify "0000000000000000", // extranonce2, 8 bytes = 16 hex "00000000c0ffee00", // ntime, 8 bytes = 16 hex "1122334455667788" // nonce, 8 bytes = 16 hex ]} <- {"id":9,"result":true,"error":null} ``` Lengths are not negotiable on this chain: | Parameter | Bytes | Hex characters | |---|---|---| | `extranonce2` | 8 | 16 | | `ntime` | **8** | **16** | | `nonce` | **8** | **16** | A rejected share returns `result: false` with an error: | Code | Message | Meaning | |---|---|---| | 20 | `Ntime out of range`, `Params not array`, `Invalid array size`, `No job_id`, … | malformed submission, or a field the pool could not use | | 21 | `Invalid JobID`, `Stale` | the job is unknown or has been superseded; use the newest `mining.notify` | | 22 | `Duplicate` | this nonce combination was already submitted | | 23 | `Above target` | the hash does not meet the difficulty you were given | | 24 | `Worker mismatch` | the worker in `mining.submit` is not the one you authorised | Persistent `23 / Above target` on **every** share, with your best share far below the assigned difficulty, means your miner is computing a different hash than the pool verifies — not that it is unlucky. See section 0. --- ## 8. Reference implementation Complete and self-contained. This is the whole miner-side computation. ```python import hashlib DIFF1 = 0xFFFF * (1 << 208) def blake2b256(b): return hashlib.blake2b(b, digest_size=32).digest() def work_root(coinb1: bytes, extranonce: bytes) -> bytes: """coinb1 is the 39 bytes from mining.notify, extranonce is exactly 12.""" assert len(coinb1) == 39 and len(extranonce) == 12 return blake2b256(b"\x00" + coinb1 + extranonce) def pow_hash(hidden_prevhash: bytes, nonce: bytes, ntime: bytes, root: bytes) -> bytes: """All four come straight off the wire; nothing is reversed.""" assert len(hidden_prevhash) == 32 and len(nonce) == 8 assert len(ntime) == 8 and len(root) == 32 return blake2b256(hidden_prevhash + nonce + ntime + root) # 80-byte header def share_difficulty(h: bytes) -> float: return DIFF1 / int.from_bytes(h, "big") ``` A miner loop is then: ```python root = work_root(coinb1, extranonce1 + extranonce2) # once per extranonce2 h = pow_hash(hidden_prevhash, nonce, ntime, root) # once per nonce if share_difficulty(h) >= assigned_difficulty: submit(job_id, extranonce2, ntime, nonce) ``` ### 8.1 Test vector Verified against the pool's own implementation. If your code reproduces this, your hashing is correct. ``` coinb1 0000001b11a8e2c16084cc487838df37dd867fed260104a09be4298db7a1e833ff4b0900000000 extranonce 0102030405060708090a0b0c work_root b1bd2099bfadca5f276ea8fc0c3148aa6c775d61ed5d5a4ce9eb3d9fae01f178 hidden_prevhash (32 bytes, from mining.notify field 1) nonce 1122334455667788 ntime 00000000c0ffee00 ``` The `work_root` line above depends only on `coinb1` and `extranonce` and can be checked on its own — it is the single most useful check, because it isolates the one construction that has no Bitcoin equivalent: ```python coinb1 = bytes.fromhex("0000001b11a8e2c16084cc487838df37dd867fed260104a09be4298db7a1e833ff4b0900000000") enonce = bytes.fromhex("0102030405060708090a0b0c") assert work_root(coinb1, enonce).hex() == \ "b1bd2099bfadca5f276ea8fc0c3148aa6c775d61ed5d5a4ce9eb3d9fae01f178" ``` --- ## 9. Behaviour worth knowing - **Connection rate limiting.** Several connections from one address within a second may be refused. One connection per miner is expected; reconnect with a backoff rather than immediately. - **Idle connections.** The pool does not require `mining.ping`. It keeps a connection open as long as it is being used. - **Stale shares.** After `clean_jobs = true`, shares for the previous job are rejected. There is a small grace period for measured network latency. - **Payout.** On the shared ports the block pays the pool, which then distributes it in proportion to the difficulty each address contributed. On the solo ports the block pays the finder's address directly in the coinbase. Only accepted shares count towards a payout; rejected shares are recorded so you can see them, and are never paid. --- ## 9b. Stratum V2 Everything below is what a V2 miner needs beyond the V2 specification itself. The parts that are ordinary V2 are not restated; the parts this chain changes cannot be discovered from the wire, so they are fixed here. ### 9b.1 Endpoint and authentication | | | |---|---| | Mining, shared | **`13333` · `14444` · `15555`** on `de`, `hel` and `ord` | | Mining, solo | **`13334` · `14445` · `15556`**, every region | | Job Declaration | **`13337`** shared, **`13338`** solo, every region | | Encryption | Noise NX, as the V2 specification requires | | Authority public key (x-only, hex) | `cc22ab3495b26c1a5d0a5c834df4ae8926cc7dbf8ef6c292f951f174280cd323` | | Authority URL form | `stratum2+tcp://de.b2pool.io:13333/9cBUXZGrvPo7sHWCzHJrY3VMWJP21ja5RseomRoeE72JvrGya8A` | The authority key is persistent, it will not change when the pool restarts, and it is **the same in every region** — one key to pin, wherever you connect. Each host holds its own Noise static key, certified by that shared authority, so the regions are distinguishable to the protocol without being separate identities to you. The V2 ports are the V1 ports with a `1` in front, so the mapping extends to the GPU and CPU tiers the same way. **A V2 port is the V1 port with a `1` in front, and nothing else changes.** Same hardware tiers, same difficulties, same accounting: | Tier | V1 | V2 | Starts at | Never below | |---|---|---|---|---| | ASIC | 3333 | **13333** | 16,384 | 1,024 | | FPGA and GPU | 4444 | **14444** | 128 | 128 | | CPU | 5555 | **15555** | 1 | 1 | Pick the tier that matches your hardware, exactly as you would on V1 — a 5090 belongs on 14444. Shares are credited and paid like any others; this is the production pool, not a sandbox. ### 9b.2 What the pool supports | | | |---|---| | Protocol version | 2 | | Channels | Standard (`0x10`) and Extended (`0x13`) | | Job Declaration | open — see 9b.40 | | `REQUIRES_STANDARD_JOBS` | accepted | | `REQUIRES_WORK_SELECTION` | accepted when Job Declaration is enabled, refused otherwise | | `REQUIRES_VERSION_ROLLING` | accepted only when the pool advertises a version mask | | Extranonce | pool prefix **4 bytes**, miner-rolled **8 bytes**, total **12** — see below | On an **extended** channel the pool always grants the full eight bytes of miner-rolled extranonce, whatever `min_extranonce_size` asks for, because the work root takes exactly twelve. A request above eight is refused with `unsupported-min-extranonce-size`. On a **standard** channel `extranonce_size` is zero: the pool supplies all twelve bytes and the miner rolls only `nonce` and `ntime`. ### 9b.3 The field mapping V2 carries `nonce`, `ntime` and `version` as **32-bit** fields. This chain takes **eight bytes** of nonce and eight of ntime. Extended channels do not change this — the widths are in the message definitions. The mapping is therefore fixed here: | V2 field | Width on the wire | Placement in the chain's field | |---|---|---| | `nonce` | `u32` | bytes 0–3, **little-endian**; bytes 4–7 **zero** | | `ntime` | `u32` | bytes 0–3, **little-endian**; bytes 4–7 **zero** | | `version` | `u32` | used as-is | | `extranonce` | up to 32 bytes | first 8 bytes only; the pool prepends its 4-byte prefix | So V2 `nonce = 0xddccbbaa` becomes the eight bytes `aa bb cc dd 00 00 00 00`, which is exactly what a V1 miner sending `aabbccdd00000000` produces. The two endpoints therefore accept the same share from the same counter. **No search space is lost.** On an extended channel a miner has 2^32 of nonce, 2^32 of ntime and 2^64 of extranonce — 2^128 in total, against the roughly 2^46 hashes a share at difficulty 16384 costs. Even on a standard channel, nonce and ntime together give 2^64. The zeroed upper halves cost nothing; they exist so that both ends agree on one encoding rather than two. `ntime` is **not** a timestamp on this chain and is **not** range-checked. It is nonce space: roll the whole 32-bit range freely, including splitting your threads by it. The block's time comes from the job, and the header field the node would read a time offset out of is inert while the job's flags are zero — which they always are here. Three ntime values spanning the range give three different hashes and one block time. Up to and including pool version 1.2.0 a leftover window on the V2 path rejected values more than 7000 above the job's time with `invalid-ntime`. Narrow rolls worked, wide ones did not. Fixed in 1.2.1. ### 9b.35 The standard job contract This is the part that cannot be guessed from the V2 specification, because two fields carry something other than their names suggest. | V2 field | On SHA-256 Bitcoin | **On BLAKE2b Bitcoin** | |---|---|---| | `SetNewPrevHash.prev_hash` | previous block hash | the **hidden** prevhash — use verbatim | | `NewMiningJob.merkle_root` | SHA256d merkle root | the **header commitment** | **You are never sent the block height, the transaction count, the version or nBits, and you do not need them.** The commitment already binds all four. That is the answer to "how does a standard miner get the commitment data": it does not assemble it, it is handed the finished commitment. The hidden prevhash is `tagged_SHA256("Bitcoin prevblock header, hashed", reverse32(prevhash))` with its **first six bytes zeroed**. The pool computes it; you never see the real tip, which is deliberate — it is also what keeps the network target out of your hands. If the first six bytes of what you receive are not zero, you are talking to a pool that has this wrong. From the two fields, one job produces work like this: ``` coinb1(39) = 0x00 0x00 0x00 || commitment(32) || 0x00 0x00 0x00 0x00 leaf(52) = 0x00 || coinb1(39) || extranonce(12) work_root = BLAKE2b-256(leaf) work(80) = prev_hash(32) || nonce(8) || ntime(8) || work_root(32) hash = reverse32( BLAKE2b-256(work) ) ``` `extranonce` is the channel's prefix followed by the bytes you roll, twelve in total. `nonce` and `ntime` are the eight-byte fields of 9b.3 — your `u32` in the low four bytes, the upper four zero. **On an extended channel** you receive `NewExtendedMiningJob` instead, and it carries the same thing in the shape that message has: | Field | What it holds here | |---|---| | `coinbase_tx_prefix` | the 40-byte `0x00 \|\| coinb1` — the leaf, minus your extranonce | | `coinbase_tx_suffix` | **empty** | | `merkle_path` | **empty** | So `work_root = BLAKE2b-256(coinbase_tx_prefix || extranonce)`, and prefix (40) plus extranonce (12) is the 52-byte leaf exactly. There is no coinbase to assemble and no path to walk: the commitment already stands for the whole block. If you receive a non-empty suffix or path on this chain, the pool has it wrong. **Solo** works the same way, with one difference that matters: the coinbase pays *you*, so your commitment is yours alone and differs from every other miner's on the same block. You do not have to do anything about that — the job you are sent already carries your own. The hash is then XOR-masked with the job's key. For every job this pool issues the key is zero, so the mask is the identity and you can ignore it. Compare against the share target from `SetTarget`. A share must additionally have its **top four bytes zero**, which is the least a header-v2 share can satisfy; the pool rejects anything else however low the rest is. ### 9b.4 The 164-byte header The proof-of-work is BLAKE2b over the 80-byte work header of section 5, not SHA256d over an 80-byte Bitcoin header. A stock V2 miner will find nothing here until that is changed. Section 5 is the whole of the difference. The serialised block header — what `submitblock` takes, and what the fields above end up in — is 164 bytes: | Offset | Bytes | Field | |---|---|---| | 0 | 4 | `version | 0x80000000`, little-endian | | 4 | 32 | previous block hash | | 36 | 32 | merkle root (SHA256d, unchanged by the fork) | | 68 | 4 | block time, little-endian | | 72 | 4 | nBits, little-endian | | 76 | 8 | **nonce** | | 84 | 4 | **ntime bytes 4–7** | | 88 | 4 | zero | | 92 | 12 | **extranonce** | | 104 | 4 | **ntime bytes 0–3** | | 108 | 2 | transaction count, little-endian | | 110 | 1 | flags | | 111 | 1 | xor key mask clear bits | | 112 | 16 | xor key | | 128 | 4 | block height, little-endian | | 132 | 32 | merge-mining right-hand side | Note that **ntime is split**: its low four bytes sit at offset 104 and its high four at offset 84. Getting that backwards produces a header the pool will not recognise. For jobs this pool issues, `flags`, `xor key`, `xor key mask clear bits` and the merge-mining right-hand side are all **zero**. ### 9b.5 Test vector This is block **968,704** of the live chain, found by this pool on 7 September 2026. Every field is readable from any node of this chain, and the hash below is the block's own hash. If your implementation reproduces it, it agrees with Knots. ``` version 0x20000000 prevhash 44e5888a0f8c1c1a85d0ce33adbfe5b8b3be9d465a669ada0e00000000000000 height 968704 merkle_root b1f9ef0a43a48e44e480b2bed09295e8be57cffe62048ae10a04f4cda7085357 time 1788742146 nbits 0x190f0b50 txcount 74 flags 0 extranonce 74e09d6a0200000000000000 (12 bytes) nonce 53a864ec548e0190 (8 bytes) ntime 00000000020a9e6a (8 bytes) hash (LE) 7cfe56452a2a0819313373d079d4b998f2b3deedb8c80eff0300000000000000 block hash 0000000000000003ff0ec8b8eddeb3f298b9d479d073333119082a2a4556fe7c ``` The block hash is the little-endian hash reversed, as everywhere in Bitcoin. That vector uses all eight nonce bytes, because it was found over V1. A V2 miner cannot produce it: under the mapping in 9b.3 the upper four bytes are always zero. It is given to check the **hash construction**, which is identical on both protocols. For the V2 path, here is the same job with V2-expressible fields. It is not a valid share — it is a known hash for known inputs, which is what you need to check an implementation: ``` V2 nonce 0x11223344 -> 8-byte field 4433221100000000 V2 ntime 0x6a9e0a02 -> 8-byte field 020a9e6a00000000 extranonce 74e09d6a0200000000000000 (pool prefix 74e09d6a, miner rolls 0200000000000000) hash (LE) c1864f57ff74e5b8532f081497dc166804aa1d1f67fc27871adbf07804a15b70 ``` The 164-byte header for that submission places the fields exactly as 9b.4 says: `nonce` at 76 is `4433221100000000`, the **high** half of ntime at 84 is `00000000`, the extranonce at 92 is `74e09d6a0200000000000000`, and the **low** half of ntime at 104 is `020a9e6a`. Verify it against the pool's own implementation: ``` b2_tool hash 20000000 968704 1788742146 190f0b50 74 0 \ 74e09d6a0200000000000000 53a864ec548e0190 00000000020a9e6a ``` --- ### 9b.40 Job Declaration You choose the transactions; we check the block and pay you. Endpoints: | | Port | |---|---| | Shared | **13337** | | Solo | **13338** | On `de.b2pool.io`, `hel.b2pool.io` and `ord.b2pool.io`. Same Noise NX handshake and the **same authority public key** as the mining ports — a JD connection is not separately authenticated: ``` cc22ab3495b26c1a5d0a5c834df4ae8926cc7dbf8ef6c292f951f174280cd323 ``` **The full flow.** Job Declaration alone gets you a validated template; it is `SetCustomMiningJob`, on the *mining* connection, that binds it to a channel so the shares count. Both halves are implemented: ``` JD connection (13337/13338) Mining connection (13333/13334) -------------------------- ------------------------------ SetupConnection AllocateMiningJobToken -> .Success (token) DeclareMiningJob <- ProvideMissingTransactions -> .Success (the txs we lack) -> DeclareMiningJob.Success SetCustomMiningJob (same token) -> .Success -> mine, submit as usual PushSolution (on a found block) ``` `SetCustomMiningJob` requires `REQUIRES_WORK_SELECTION` on the mining connection and an **extended** channel; a standard channel is refused with `invalid-channel-id`. The token must be one this pool declared, and the coinbase outputs are checked again there — the same rule as below. **What we require of your coinbase.** This is where we are stricter than the specification, so read it before you build one: | | Rule | |---|---| | **Shared** (13337) | **100 % of spendable value** to the pool's script, and at least the block subsidy. Any other funded output gives `DeclareMiningJob.Error`. | | **Solo** (13338) | Your payout script funded with a non-zero value. Other outputs are fine. | Spec §6.4.3 asks only that the pool's output be funded at all, and offers as its remedy that the pool "pay proportionally smaller rewards for this job". A pool that accounts in shares has no such knob, so refusing the declare is the only lever we have. Solo pays you, so it carries no such rule. **Validation.** Every declared block is put to our node before we answer `Success` — `getblocktemplate` in proposal mode. If we cannot ask the node you get `validation-unavailable`, never a claim that your template is bad. If the tip moved under you, the error carries the node's own words (`inconclusive-not-best-prevblk`, `prev-blk-not-found`, `time-too-old` …) so you can rebuild rather than guess. **Where your templates come from.** We do not run a Stratum V2 Template Provider, and you do not need ours — choosing your own transactions is the whole point. Run a node of this chain and build from its `getblocktemplate`: ``` add-apt-repository ppa:luke-jr/bitcoinknots apt install bitcoind # Knots 29.4.1, the same build this pool runs bitcoin-cli getblocktemplate '{"rules":["segwit","blake2b"]}' ``` The `blake2b` rule is required; without it the node refuses the call. And note the 164-byte header of 9b.3 — a template from this chain is not a SHA-256 Bitcoin template, and the work you hash is assembled as described there. ## 10. Changes | Version | Date | Change | |---|---|---| | 1.13 | 2026-09-07 | The DATUM ports carried the per-client share target in `nbits`; they now carry the consensus target, as the ckpool ports always did. New note under the job fields: share target and network target are two different numbers, and which message carries each. | | 1.12 | 2026-09-07 | Job Declaration open on 13337 (shared) and 13338 (solo) in all three regions. New 9b.40: the full JD plus SetCustomMiningJob flow, the coinbase rules, how declared blocks are validated, and where templates come from. | | 1.11 | 2026-09-07 | Naming: the chain is Bitcoin with BLAKE2b proof of work (BIP-110), ticker BTC. Where a contrast is needed the two are named by their proof of work, SHA-256 and BLAKE2b. Nothing on the wire changed. | | 1.10 | 2026-09-07 | Solo over V2 open on 13334/14445/15556 in all three regions. Documents the extended-channel job shape: prefix carries the 40-byte leaf head, suffix and merkle path are empty. | | 1.9 | 2026-09-07 | V2 open in all three regions — Frankfurt, Helsinki and Chicago — sharing one authority key. | | 1.8 | 2026-09-07 | `ntime` is genuinely unrestricted on the V2 path now: a leftover window rejected wide rolls with `invalid-ntime` while narrow ones passed. Section 9b.3 says so explicitly. | | 1.7 | 2026-09-07 | All three V2 mining ports open: 13333, 14444, 15555, each carrying the difficulties of the V1 port it mirrors. | | 1.6 | 2026-09-07 | Section 9b.35: the standard job contract. `prev_hash` carries the hidden prevhash and `merkle_root` the header commitment, neither of which the V2 specification can express — with the full job-to-80-byte-work derivation. GPU-tier port 14444 opened; each V2 port inherits the difficulties of the V1 port at the same position. | | 1.5 | 2026-09-07 | Mining endpoint `de.b2pool.io:13333` is open. It carries the ASIC tier's difficulties and pays like any other port. | | 1.4 | 2026-09-07 | Section 9b rewritten as a full V2 specification: endpoint and persistent authority public key, supported version, channels, extensions and extranonce sizing, the 32-to-64-bit field mapping with byte order, the complete 164-byte header layout including the split `ntime`, and a test vector taken from live block 968,704. | | 1.3 | 2026-09-07 | Job Declaration now validates BLAKE2b. Documents where the block height and transaction count come from for a declared job, and that `ntime` is nonce space rather than a timestamp and is not range-checked. | | 1.2 | 2026-09-07 | Section 9b: Stratum V2. Fixes the mapping of V2's 32-bit `nonce` and `ntime` onto this chain's eight-byte fields — low four bytes little-endian, upper four zero — because the protocol cannot express it and both ends must agree. Job Declaration is reachable but does not yet validate BLAKE2b. | | 1.1 | 2026-09-07 | ASIC ports: starting difficulty 32768 -> 16384, floor 8192 -> 1024. The pool only retargets when a share arrives, so the starting value is also the floor until the first one; at 32768 a 1 TH/s machine could go minutes before its first share and look dead. No wire format changed. Note that share difficulty is quantised to powers of two, so a value like 12000 is used as 8192. | | 1.0 | 2026-09-06 | First publication. Covers `mining.subscribe`, `mining.authorize`, `mining.configure`, `mining.extranonce.subscribe`, `mining.submit`, `mining.notify`, `mining.set_difficulty`, `mining.set_extranonce`, the BLAKE2b work construction and a verified test vector. |