Keryx (KRX) Stratum Protocol
v1.2Pool: krx.suprnova.cc Β· Algorithm: keryx-hash (kHeavyHash + KERYX_MATRIX_SALT_V4 + WaveMix ARX, KeryxHash matmul) Β· Published: 2026-06-08 Β· Updated: 2026-07-06 (H3)
This document specifies the wire protocol the suprnova Keryx pool speaks on its stratum ports so third-party miner authors can implement compatible mining clients alongside the reference keryx-miner.
Keryx pools speak the kaspad-dialect stratum β JSON-RPC 2.0 with positional-array params (the convention shared by kaspad / nautilus / Karlsenkoin pools), plus a mandatory Optimistic Proof of Inference (OPoI) tag on every submitted share. The OPoI tag is a deterministic 16-hex fingerprint derived from the share nonce via a fixed-point MLP; it is what makes the chain's per-share Proof-of-Useful-Work property work without slowing share verification.
1. Endpoints#
| Region | Host | Ports |
|---|---|---|
| EU | krx.suprnova.cc | 4401 β 4404 |
| Port | Difficulty mode | Starting share-difficulty | Range |
|---|---|---|---|
4401 | VarDiff Low | 8 | 1 β 10,000 (CPU / laptop / small GPU) |
4402 | Fixed | 4 | β 1.7 GH/s sweet spot |
4403 | Fixed | 128 | β 55 GH/s sweet spot |
4404 | VarDiff High | 1024 | 64 β 10,000,000 (recommended for GPU rigs) |
Recommended for new miners: port4404. The VarDiff retargets every 60 s toward a 10-second-per-share cadence and handles anything from a single GPU up to multi-rig farms. For very small workers (sub-GH/s) use4401instead β it has a much lower starting share-difficulty.
No TLS: all four stratum ports are plain TCP. The pool does not currently offer a TLS endpoint. The HAProxy edge in front of the pool injects a PROXY v1 line so the pool sees the real client IP; that wrapping is invisible to miners.
2. Transport#
- TCP, single long-lived connection per worker. No connection pooling, no multiplexing.
- Framing: newline-delimited JSON. One JSON value per line, terminated with
\n(0x0A). No length prefix. - Encoding: UTF-8.
- Maximum line length: 16 KiB. Stratum lines are small (a few hundred bytes); this limit is just to bound a misbehaving client.
- Idle / keepalive: the pool will close any connection that goes 15 minutes without a valid share (the standard kaspad-pool watchdog). Miners that legitimately produce slower than one share per 15 min should drop to
4401where the share-difficulty floor is 1.
3. JSON-RPC dialect (kaspad-style)#
The protocol is a kaspad-dialect of JSON-RPC 2.0 β the same dialect used by Kaspa / Karlsenkoin / Nautilus / Pyrin pools and inherited unchanged by the Keryx ecosystem:
- The
"jsonrpc": "2.0"field is optional and typically omitted by both client and server. - Request IDs are numeric integers. They do not need to be monotonic.
- Server-pushed notifications use
"id": null. paramsis always a positional ARRAY, never an object. (This is the key difference from XMRig-dialect pools β get this wrong and the miner fails to parse the very firstmining.notify.)- Method names are dotted lowercase:
mining.subscribe,mining.authorize,mining.notify,mining.set_difficulty,mining.submit. - Numeric fields that exceed JavaScript's safe-integer range (the
headerSeedu64 words, thedaaScore) are emitted as raw JSON numbers, not strings. Parse them with a BigInt-aware JSON parser.
Response shapes
Success:
{"id": <req_id>, "result": <value>, "error": null}
Error:
{"id": <req_id>, "result": null, "error": [<code>, "<message>", null]}
The kaspad error format is a 3-element array ([code, message, data]), not the JSON-RPC-2 object form. The third element is reserved for additional data and is currently always null.
Pool error codes
| Code | Message | Cause |
|---|---|---|
| 20 | Other / unknown | Unspecified pool-side error |
| 21 | Job not found | job_id no longer matches any cached template (stale) |
| 22 | Duplicate share | Identical (jobId, nonceHex, wallet) already submitted |
| 23 | Low difficulty share | Hash does not meet the worker's current share-target |
| 24 | Unauthorized worker | mining.submit arrived before successful authorize, or OPoI tag missing/wrong length |
| 25 | Not subscribed | mining.submit arrived before mining.subscribe |
| 26 | Invalid OPoI tag | The 16-hex opoiTag does not match the pool-recomputed tag_fixed(nonce) |
| 27 | Invalid / missing PoM proof | Post-fork only (DAA β₯ 37,780,000): the 6th mining.submit param (pomProofHex) was absent, malformed, or failed verification. See Β§12. |
4. Client β Server messages#
4.1 mining.subscribe
Sent first on every fresh connection, before mining.authorize. Carries the miner's user-agent string (and optionally a session-id hint that the pool ignores).
{"id": 1, "method": "mining.subscribe", "params": ["my-keryx-miner/0.3.2"]}
The pool responds with a kaspad-style ack:
{
"id": 1,
"result": [
[ ["mining.set_difficulty", "<subId>"],
["mining.notify", "<subId>"] ],
"<extranonce_hex>",
<ext2_size>
],
"error": null
}
Fields in the result tuple (in this exact order):
- Subscription channels array (informational; miners ignore it but it must be present).
extranonce_hexβ currently always"00"(1 byte of extranonce-1 reserved for the pool, 0 used). A miner that wants to be future-proof should still concatenate this with its own extranonce-2 before building the nonce.ext2_sizeβ number of bytes of extranonce the miner controls inside its 64-bit nonce. Withextranonce_hex = "00"this is7.
4.2 mining.authorize
Sent second, immediately after the subscribe ack. Carries the wallet (CashAddr) and password.
{
"id": 2,
"method": "mining.authorize",
"params": [
"keryx:qp0vrxc0k5w0pcyem6vau2pjgztje880tsm239rywtm7l7uv2pcxzq55n8khs.rtx4090",
"x"
]
}
Params (positional)
- username (string) β a Keryx CashAddr address optionally followed by
.<worker>. See Β§5 for the address format. - password (string) β either
"x"(no preference, let VarDiff drive) or a directive such as"d=4096"to pin the starting share-difficulty on the VarDiff High port. See Β§7.
The pool responds with {"id":2,"result":true,"error":null}. From this point the pool will start pushing mining.set_difficulty and mining.notify.
4.3 mining.submit
{
"id": 10,
"method": "mining.submit",
"params": [
"keryx:qp0vr...n8khs.rtx4090",
"1a7c",
"00000000deadbeef",
"8f6cf7a4732dbe13",
"",
"0100ad50ad0bd461d8ab44efc0214989eb3..."
]
}
Params (positional)
- worker (string) β the same username that was authorized (with the same optional
.<worker>suffix). - jobId (string) β must match the latest
mining.notify[0]the client received. Stale submissions return error 21. - nonceHex (string) β 16 hex characters, little-endian u64 nonce that satisfies the share target.
- opoiTag (string) β mandatory. 16 hex characters = the 8-byte
tag_fixed(nonce)output from the Keryx OPoI fixed-point MLP. The pool recomputes it; a mismatch returns error 26. - ipfsCID (string, optional) β empty string or a CID of an inference result the miner has uploaded. Logged for audit; not required, not validated. Send
""if you have nothing to publish. - pomProofHex (string, optional pre-fork; required post-fork) β lowercase hex of a Borsh-encoded
PomProofblob proving possession of the declared tier's model weights. Required at DAA β₯ 37,780,000 (Fri 2026-06-26 18:00 UTC). Empty string or omitted pre-fork is accepted. See Β§12 for the full PoM extension spec.
Computing the OPoI tag
The OPoI tag is a deterministic 3-layer fixed-point MLP (32 β 256 β 128 β 32) over the share nonce. It is the cheapest part of the Proof-of-Useful-Work scheme β completing it takes microseconds β but a missing or wrong tag invalidates the share. Reference implementations:
- Rust:
keryx-node/inference/src/model_fixed.rs::tag_fixed(nonce: u64) -> [u8; 8] - Go (bridge):
keryx-stratum-bridge/src/keryxstratum/opoi.go - The official keryx-miner bakes it in via the
keryx-inferencecrate.
4.4 No other client β server methods
The pool does not implement mining.suggest_difficulty, mining.ping, keepalived, XMRig-style login, or any object-params variant. All return error 20.
5. Address format#
Keryx uses the CashAddr encoding (the same family Kaspa uses), not Segwit / bech32m. The prefix is colon-delimited from the payload:
| Network | Prefix | Example |
|---|---|---|
| Mainnet | keryx: | keryx:qp0vrxc0k5w0pcyem6vau2pjgztje880tsm239rywtm7l7uv2pcxzq55n8khs |
| Testnet-10 | keryxtest: | keryxtest:qpkzggeyp0v7l7mx9nnems2l474v62la6dwr5kqefuu4s7j3q4cqs5f9f9ta9 |
| Simnet | keryxsim: | keryxsim:... |
| Devnet | keryxdev: | keryxdev:... |
The pool refuses non-mainnet prefixes on the production stratum (4401-4404) and returns error 24 (Unauthorized worker) at authorize-time.
Gluedaddress.workerform: kaspad-style miners (including keryx-miner) concatenate the worker name onto the wallet with a.β e.g.keryx:qpβ¦n8khs.rtx4090. The pool splits at the first.(CashAddr never contains one) and treats the suffix as the worker name.
6. Server β Client messages#
6.1 mining.set_difficulty
{"id": null, "method": "mining.set_difficulty", "params": [1024]}
Pushed whenever the pool's VarDiff (or the operator-pinned fixed difficulty) changes. The single parameter is the new share-difficulty as a positive integer. The pool always sends one mining.set_difficulty before the first mining.notify on a new connection.
6.2 mining.notify
{
"id": null,
"method": "mining.notify",
"params": [
"1a7c",
[
11181278953413291283,
4793928374829834912,
9123847123847128371,
6234782634872364871
],
1717862400,
17275842
]
}
Params (positional, all required)
- jobId (string) β short opaque identifier. Echo it back unchanged in
mining.submit. - headerSeed (array of 4 raw-JSON u64 numbers) β the canonical 32-byte
pre_pow_hashsplit into four little-endian u64 words. Re-emitted by the pool as raw JSON numbers (not strings) and not quoted; use a BigInt-aware parser. - timestamp (integer) β Unix seconds at which the template was rolled. Miners MAY roll this within Β±2 hours.
- daaScore (integer or raw-JSON u64) β the DAA score of the next block, used by the share-target / network-target math. Carries the salt-fork activation signal: at
daaScore β₯ 21,932,751the chain is on SALT v4.
Cadence
- One
mining.notifyis pushed immediately aftermining.authorizesucceeds. - The pool subscribes to
keryxd'snewBlockTemplatewRPC notification β a fresh template arrives within milliseconds of any tip change. - A 250 ms polling backstop guarantees coverage if a notification is missed.
- There is no "clean" / "force-restart" boolean as the 4th param β that's a Stratum-V1 convention from the Bitcoin pool world and is not present here. The miner should always restart its hashing loop on a new
mining.notify.
6.3 Acks
mining.submit is acknowledged with one of:
{"id":10,"result":true,"error":null} β accepted share
{"id":10,"result":null,"error":[21,"Job not found",null]} β stale
{"id":10,"result":null,"error":[23,"Low difficulty share",null]} β below share target
{"id":10,"result":null,"error":[26,"Invalid OPoI tag",null]} β tag mismatch
If the share also clears the chain's network target, the pool internally calls submitBlock on keryxd and emits a Slack-style "Block found!" event to the public block feed (visible on the Blocks page). The miner still just sees {"result":true,"error":null}.
7. Custom difficulty via the password field#
On the VarDiff High port (4404), miners may pin their starting share difficulty by passing a d=N directive in the password parameter of mining.authorize:
"password": "d=4096" β pin start at 4096
Range: 64 β€ N β€ 10,000,000. Values outside this range are silently ignored and VarDiff takes over from its default starting point.
Effect
- The pool uses
Nas the starting share difficulty for this worker. - VarDiff retargeting continues to operate; if the miner's actual hashrate doesn't match
N, the share-rate will drift toward the 10-second-per-share target. - To suppress VarDiff entirely, use one of the fixed ports (
4402d=4 or4403d=128). - On the fixed ports the
d=Ndirective is parsed but ignored.
keryx-miner example
./keryx-miner --mining-address \
--pool krx.suprnova.cc:4404 \
--wallet keryx:qp0vr...n8khs \
--worker rtx4090 \
--password "d=4096"
8. Connection lifecycle#
1. Client TCP-connects to krx.suprnova.cc:<port>
2. Client β mining.subscribe (params=[user_agent])
3. Server β subscribe ack ([[channelsβ¦], extranonce, ext2_size])
4. Client β mining.authorize (params=[wallet[.worker], password])
5. Server β authorize ack (result: true)
6. Server β mining.set_difficulty (params=[diff])
7. Server β mining.notify (params=[jobId, headerSeed, ts, daaScore])
--- now mining ---
8. Server β mining.notify (on every new template / VarDiff retarget)
9. Server β mining.set_difficulty (only when the difficulty changes; usually
bundled just before a notify)
10. Client β mining.submit (when miner finds a share that meets target)
11. Server β submit ack (true or error code 21/22/23/24/26)
loop 8β11
12. Either side closes TCP. Pool watchdog closes idle conns after
15 min of no valid share.
Reconnection policy for well-behaved miners
- On clean connection close: reconnect after 1 s.
- On TCP error: reconnect with exponential backoff (1, 2, 4, 8, 16, 30, 30, β¦).
- On error code 24 (
Unauthorized worker) at authorize-time: do not auto-reconnect β the wallet is wrong; surface the error. - On any other server-side error: continue with the standard backoff.
Grace period: for 60 s after a VarDiff retarget, the pool accepts shares at the previous difficulty as well as the new one. This avoids losing in-flight work when a retarget races with an outbound share.
9. Worked example#
A complete subscribe β authorize β notify β submit β ack transcript (whitespace added for readability β the wire is one line per JSON value):
[client β server]
{"id":1,"method":"mining.subscribe","params":["my-keryx-miner/0.3.2"]}
[server β client]
{"id":1,"result":[[["mining.set_difficulty","s1"],["mining.notify","s1"]],"00",7],"error":null}
[client β server]
{"id":2,"method":"mining.authorize","params":["keryx:qp0vr...n8khs.rtx4090","x"]}
[server β client]
{"id":2,"result":true,"error":null}
[server β client]
{"id":null,"method":"mining.set_difficulty","params":[1024]}
[server β client]
{"id":null,"method":"mining.notify","params":[
"1a7c",
[11181278953413291283,4793928374829834912,9123847123847128371,6234782634872364871],
1717862400,
17275842
]}
... miner crunches keryx-hash + tag_fixed(nonce) ...
[client β server]
{"id":10,"method":"mining.submit","params":["keryx:qp0vr...n8khs.rtx4090","1a7c","00000000deadbeef","8f6cf7a4732dbe13"]}
[server β client]
{"id":10,"result":true,"error":null}
10. The OPoI tag in detail#
Keryx's per-share tag_fixed(nonce) is the cheap half of Optimistic Proof of Inference: a deterministic 3-layer fixed-point MLP (32 β 256 β 128 β 32) over the salted share nonce. Output is the lower 8 bytes of the final hidden state, serialised as 16 lowercase hex characters.
- The MLP weights are baked into the protocol (file
keryx-node/inference/src/model_fixed.rs). They do not change between blocks. - Computing the tag for one nonce takes microseconds on CPU β it is not the work, just a fingerprint. The actual PoW work is the keryx-hash hashloop.
- Every accepted share must include the correct
opoiTag. The pool recomputes it and rejects mismatches with error 26. - This is what gives Keryx its Proof-of-Useful-Work property β the tag forces the miner to evaluate the inference model on the same nonce it claimed to hash, so it can't sidestep the inference step.
Larger AI inference tasks (the optional AiRequest path) are separate from the per-share tag. They are negotiated out-of-band over the same connection but are not described in this spec; they are not required for normal mining.
11. Reference miners#
Two published miners speak this protocol:
- keryx-miner (upstream Keryx Labs) β CUDA + OpenCL, open source. Implements the base v1.0 protocol (no PoM extension): github.com/Keryx-Labs/keryx-miner.
- keryx-miner-supr (Suprnova fork) β optimized CUDA build, ships the PoM extension since v0.6.1. Required for post-fork mining (Fri 2026-06-26 18:00 UTC, DAA 37,780,000), and v0.6.7+ is required post-H3 (DAA 43,450,000, 2026-07-05 β see Β§12.3): github.com/ocminer/keryx-miner-supr.
If you ship a new miner, we'd love to add it here. Open a PR against this document or email mining@suprnova.cc (subject: "Stratum spec β new miner").
12. Proof-of-Model (PoM) hardfork extension#
At DAA 37,780,000 (Friday 2026-06-26 18:00 UTC), Keryx hard-forks to Proof-of-Model (PoM): every accepted block must carry a valid PomProof demonstrating the producer held a declared model's weights in VRAM while mining. After the +24 h grace period (Sat 2026-06-27 ~18:00 UTC), the pool refuses any mining.submit without a proof. This section is the wire spec for the proof envelope. Algorithmic details of the proof itself are in keryx-node/consensus/core/src/pom.rs (the canonical verifier β bit-exact with chain).
12.1 Wire envelope
The PoM extension is a 6th positional param on mining.submit β pomProofHex. Per the v1.x stability guarantees in Β§13, this is an additive extension: pre-v0.6 miners that send only the first 4 params keep working pre-fork. Post-fork (or whenever the operator flips pom.requireOnSubmit=true), submits without a 6th param are rejected with error code 24 or 27.
// pre-fork (still works)
{"id": 10, "method": "mining.submit", "params":
["keryx:qp0vr...n8khs.rtx4090", "1a7c", "00000000deadbeef", "8f6cf7a4732dbe13"]}
// pre-fork with 5th + 6th param attached (forward-compat path; pool stores the proof
// in the block body but does not validate it consensus-side yet)
{"id": 10, "method": "mining.submit", "params":
["keryx:qp0vr...n8khs.rtx4090", "1a7c", "00000000deadbeef", "8f6cf7a4732dbe13",
"", "0100ad50ad0bd461d8ab44efc0214989eb3...<~96KB hex>"]}
// post-fork (mandatory β same shape; pool now consensus-validates the proof)
{"id": 10, "method": "mining.submit", "params":
["keryx:qp0vr...n8khs.rtx4090", "1a7c", "00000000deadbeef", "8f6cf7a4732dbe13",
"", "0100ad50ad0bd461d8ab44efc0214989eb3...<~96KB hex>"]}
12.2 PomProof encoding
The proof is Borsh-serialized, then lowercase-hex-encoded. The canonical struct (from keryx-node/consensus/core/src/pom.rs):
pub struct PomProof {
pub tier: u8, // index into POM_TIERS
pub trace_root: [u8; 32], // Merkle root of state[0..=K]
pub pow_value: [u8; 32], // claimed PoW value
pub final_state: u64, // state[K]
pub initial_trace_path: Vec<[u8; 32]>, // binds seed β trace
pub final_trace_path: Vec<[u8; 32]>, // binds final_state β trace
pub openings: Vec<PomOpening>, // POM_OPENINGS Fiat-Shamir openings
}
pub struct PomOpening {
pub state_before: u64,
pub chunk: [u8; 32], // 32-byte weight chunk
pub weight_path: Vec<[u8; 32]>, // Merkle path under R_T
pub trace_path_before: Vec<[u8; 32]>,
pub trace_path_after: Vec<[u8; 32]>,
}
Typical size for the smallest tier (tier-0, EXAONE-4.0-1.2B-abliterated): ~48 KB raw / ~96 KB hex. Larger tiers (Mistral-7B-v0.3, GLM-4-9B, Qwen3.6-27B, Kimi-Linear-48B-abliterated) produce proofs of similar order β Merkle paths are logβ(N) in the model's chunk count.
Pool-side size cap: pom.maxProofHexBytes = 2 MB. Larger blobs are rejected with error 25 before they reach the verifier.
12.3 Tiers + activation
| Tier | Model | R_T chunks | Approx. proof size (hex) |
|---|---|---|---|
| 0 | EXAONE-4.0-1.2B-abliterated | 28,943,588 | ~96 KB |
| 1 | Mistral-7B-Instruct-v0.3-abliterated | 185,827,840 | ~102 KB |
| 2 | GLM-4-9B-0414-abliterated | 258,040,832 | ~103 KB |
| 3 | Qwen3.6-27B-abliterated | 516,762,688 | ~106 KB |
| 4 | Kimi-Linear-48B-A3B-abliterated | 927,994,064 | ~108 KB |
Canonical tier roots (32-byte R_T) and chunk counts (N) are pinned in keryx-node/consensus/core/src/config/params.rs (POM_TIERS). Constants:
POM_WALK_STEPS = 256β data-dependent VRAM reads per attemptPOM_OPENINGS = 32β Fiat-Shamir openings revealed per proofPOM_CHUNK_WORDS = 4(= 32 bytes per chunk, 4 LE u64 words)POM_SEED_SALT = 0x4B65727978500("KeryxP")POM_ACTIVATION_DAA = 37,780,000(mainnet β original PoM fork, 2026-06-26)POM_H3_ACTIVATION_DAA = 43,450,000(mainnet β H3 hardfork, 2026-07-05 ~18:00 UTC)
H3 hardfork update (DAA 43,450,000, 2026-07-05)
At POM_H3_ACTIVATION_DAA = 43,450,000 the chain hard-forks again ("H3"). Two things change in the PoM path:
- The PoM proof-of-PoW-history (PPH) fold gains a forced-update salt, so the fold β and therefore the resulting
pow_valueand trace β differ from the pre-H3 computation. - The block header now commits the proof's
final_state(au64) β the miner sets it on submit exactly like the nonce, and it is bound into the block hash post-gate.
The wire envelope is unchanged. It is still the 6th positional pomProofHex param carrying the same Borsh PomProof struct (which already contains final_state at byte offset 65). No new params, no format break β H3 changes the computation of a valid proof, not its shape. The canonical, bit-exact algorithm (including the H3 salt constant) lives in keryx-node/consensus/core/src/pom.rs; it is not reproduced here.
Miner requirement: to produce valid proofs at DAA β₯ 43,450,000 you must run keryx-miner-supr v0.6.7+. Older builds compute the pre-H3 fold; their shares fail verification (error 27 β typically BadFinalTracePath / a PoW-value mismatch) and, post-gate, the pool also rejects sub-v0.6.7 clients at mining.submit (error 24) with an upgrade notice.
12.4 Pool-side modes
The pool runs the share-verify subprocess (pool-verifier) in one of three modes, selected by combining pom.enabled and pom.requireOnSubmit in the pool config:
pom.enabled | pom.requireOnSubmit | Verifier mode | Behavior |
|---|---|---|---|
| false | any | disabled | 6th param parsed only if sent; never forwarded to daemon. Pre-PoM behavior. |
| true | false | passthrough | Share validation = kHeavyHash (pre-fork). If 6th param present, pool attaches it to RpcRawBlock.body.pom_proof on submitBlock. Daemon stores it (ignored pre-fork, validated post-fork). Current production state. |
| true | true | verify | Share validation REQUIRES a valid PomProof. Missing β error 24/27. Borsh-decode failure β error 27. Proof-verify failure β error 27. Use this on testnet today; flips on production at fork +24 h. |
12.5 How to test your miner against this pool now
Pre-fork, the production pool runs in passthrough. That lets you ship a PoM-enabled miner today and verify the wire envelope without waiting for Friday:
- Point your miner at
stratum+tcp://krx.suprnova.cc:4401(or any of 4402-4404). - Send the 6-param
mining.submitfor every share. The pool accepts pre-v0.6 4-param submits too β don't be afraid to ship the 6-param shape from day one. - On a block-difficulty share, the pool will log
[KeryxValidator] forwarding submitBlock with pomProofBytes=<N> worker=<your-worker>on its side. You won't see this directly; instead, check the public Blocks page for your worker name as the finder. - To test the full PoM-PoW + verify path (not just envelope), build keryxd with the
pom_activation_daa_scoreparam patched to0on testnet-10 and point a PoM-PoW miner at it. The verifier source needed for that test is published separately β emailmining@suprnova.ccfor the snapshot.
12.6 PoM-related rejection codes
- Code 24 β
Unauthorized worker: after the +24 h grace flip, sent with message starting"β UPGRADE TO keryx-miner-supr/0.6.1+ β¦"if the miner's UA isn't on a PoM-capable build. Miner'sErrorCode::Unauthorizedhandler returnsErrβ connection drops. - Code 25 β
Not subscribed: 6th param exceeded thepom.maxProofHexBytescap (2 MB), or hex-decode failed (odd length, non-hex chars). - Code 27 β
Invalid / missing PoM proof(post-fork only):pom_proof_hex required in verify modeβ 6th param empty / absent underverifymodePomProof borsh decode: <error>β bytes weren't a valid BorshPomProofpom_proof.tier N out of rangeβ tier index beyondPOM_TIERS.len()invalid pom proof: BadInitialState | BadFinalTracePath | BadStateBeforePath | BadWeightPath | BadStateAfterPathβ the consensus verifier (keryx-consensus-core::pom::verify_pom_proof) rejected the proof structure or paths
TargetNotMetis not a hard fail; the pool falls through to the share-target check, so a proof that meets share-target but not network-target still earns share credit.
12.7 Minimum miner-side checklist
- Build the PoM walk + opening generation on the GPU (CUDA / OpenCL / Metal). Bit-identical to
keryx-consensus-core::pom. - Generate a
PomProofon every block-worthy nonce (or on every share, if you're using the pre-fork test loop). - Serialize via Borsh, hex-encode lowercase, attach as the 6th
mining.submitparam. - Until the operator-side flip, you may also attach an empty string for the 6th param and the pool will accept the submit β but you need real proofs working before Saturday +18:00 UTC.
- Watch for
warn!("Share rejected by pool ... : β UPGRADE TO keryx-miner-supr/0.6.1+ ...")in your miner log β that's the pool's one-shot upgrade notice before the grace-window flip.
13. Upstream source references#
Canonical implementation details live in the open-source Keryx tree:
- keryx-hash PoW algorithm (kHeavyHash + KERYX_MATRIX_SALT_V4 + WaveMix ARX):
keryx-node/consensus/pow/ - Pre-PoW hash (the 32-byte
pre_pow_hashthat becomesheaderSeed):keryx-node/consensus/core/src/hashing/header.rs::hash_override_nonce_time - Salt-fork activation DAA scores:
keryx-node/consensus/core/src/config/params.rsβ v2 at17,275,000, v4 at21,932,751. - OPoI tag (mandatory per share):
keryx-node/inference/src/model_fixed.rs::tag_fixed(nonce: u64) -> [u8; 8] - Reference stratum bridge (Go): github.com/Keryx-Labs/keryx-stratum-bridge
- Node + wRPC daemon: github.com/Keryx-Labs/keryx-node
- PoM verifier + types (canonical, bit-exact with chain):
keryx-node/consensus/core/src/pom.rsβ exposesPomProof,PomOpening,pom_block_seed(),pom_pow_value(),verify_pom_proof(). - PoM tier table + activation DAA:
keryx-node/consensus/core/src/config/params.rsβPOM_TIERS,POM_WALK_STEPS,POM_OPENINGS,POM_ACTIVATION_DAA. - Daemon-side PoM validation entry-point (mirrors what the pool's
verifymode does):keryx-node/consensus/src/pipeline/body_processor/body_validation_in_isolation.rs::check_pom_proof.
14. Compatibility & versioning#
This document describes stratum protocol version 1.0, the kaspad-dialect adopted by Keryx pools and the dialect spoken by keryx-miner v0.3.x. A miner implementing this spec will work against any compatible Keryx pool.
Stability guarantees
- The positional argument lists for
mining.subscribe,mining.authorize,mining.notify,mining.set_difficulty, andmining.submitwill not change within v1.x. - Additional fields may be appended after the last positional param in either direction; clients MUST ignore extra trailing fields they don't recognise.
- The OPoI
tag_fixedweights are part of the chain consensus rules β they can only change via a Keryx hard-fork coordinated through upstream Keryx Labs. - Error codes 20β29 are reserved for the protocol. Pool-specific codes start at 100.
Suggested minimum miner behaviours for spec conformance
- Send
mining.subscribefirst, thenmining.authorize, before the pool will push any work. - Compute and attach the OPoI tag on every
mining.submit. Missing or wrong tags are rejected with error 26 β your share is unpaid. - Restart the hashing loop on every new
mining.notify; there is no "clean" bool β every notify is fresh. - Re-target whenever a new
mining.set_difficultyarrives; do not drop in-flight shares β the 60 s grace period covers one retarget overlap. - Reconnect with exponential backoff on any error other than authorize-time error 24.
- Parse the
headerSeedarray elements as u64 BigInts (they exceed JS safe-int range).
15. Changelog#
| Version | Date | Notes |
|---|---|---|
| 1.2 | 2026-07-06 | Documented the H3 hardfork (DAA 43,450,000, 2026-07-05): Β§12.3 gains the H3 update note β the PoM proof-of-PoW-history fold is salted and the block header commits the proof's final_state; the wire envelope is unchanged (same 6th pomProofHex param / Borsh PomProof). Added POM_H3_ACTIVATION_DAA; keryx-miner-supr v0.6.7+ required post-H3 (older builds fail verification with error 27 and are refused at submit post-gate). Algorithmic details (incl. the H3 salt) remain canonical in pom.rs, not reproduced here. |
| 1.1 | 2026-06-25 | Added Β§12 Proof-of-Model (PoM) hardfork extension. Documents the 6th mining.submit param (pomProofHex), the 5th optional ipfsCID, error code 27 (PoM-related rejects), the three pool-verifier modes (disabled / passthrough / verify), POM_TIERS, the v0.6.1+ reference miner, and how to test against the production pool before the Friday 2026-06-26 18:00 UTC fork (DAA 37,780,000). |
| 1.0 | 2026-06-08 | Initial public specification (kaspad-dialect, OPoI tag, SALT v4). |
Copyright Β© suprnova.cc. This document is published openly under CC-BY-4.0 to encourage development of additional Keryx mining clients.