Skip to main content
How to fetch work from a KRNX node, hand it to kHeavyHash miners, take a share back and submit a network solution. Self-contained. This guide covers only the public mining interface of the KRNX main chain. A pool obtains the node-selected kHeavyHash pre-image through krnx_getWork and returns a nonce through krnx_submitWork. The pool does not need to identify how the template was produced: treat its fields as opaque and use them unchanged. The main-chain proof of work is kHeavyHash: two cSHAKE256 passes, a 64×64 matrix, and a little-endian result comparison. A miner that supports this algorithm can mine KRNX without changing its hashing implementation. Two rules break an integration if you do not account for them:
  1. getWork()[10] always contains milliseconds: parse the hex as uint64 and pass it without any further unit conversion. Section 4.
  2. Share difficulty and consensus difficulty are different scales, exactly 2^32 apart. Section 5.

1. What the node must provide

Your own KRNX node with HTTP-RPC and template production enabled:
The reward goes to the node’s etherbase, not to the stratum login. The login is only an accounting label; payouts are the pool’s own business. Three RPC methods, nothing else: All three are mirrored in the eth_ namespace (eth_getWork, eth_submitWork) — the names match stock krnxd, so existing ethash pool code only needs its hashing swapped out.

2. krnx_getWork

Eleven strings. A pool needs four of them: Work is new when [0] changes. Everything else in the header is already folded into that hash. If the node is not mining or is still syncing, the method returns an error. That is normal: wait, and do not close the stratum port.

Push instead of polling

With --miner.notify http://pool:1234/work, the node POSTs the selected JSON work array whenever the template changes. Polling krnx_getWork every 200–500 ms is a practical alternative. --miner.notify.full carries the KRNX header and does not implement this work-array contract, so use the ordinary array or polling.

3. The kHeavyHash pre-image

KRNX uses this fixed pre-image:
Then:
  1. first = cSHAKE256(data, customization="ProofOfWorkHash") → 32 bytes.
  2. matrix = generate(PRE_POW_HASH) — a 64×64 matrix of 4-bit values from xoshiro256++ seeded with the seal hash, regenerated until its rank (float64 Gaussian elimination, eps = 1e-9) reaches 64.
  3. mixed[i] = first[i] XOR (product[2i]<<4 | product[2i+1]), where product[i] = (Σ matrix[i][j] * vector[j]) >> 10 and vector holds the nibbles of first.
  4. final = cSHAKE256(mixed, customization="HeavyHash") → 32 bytes.
  5. The PoW value is final read little-endian. A block is solved when that value is <= the target.
The matrix depends on the seal hash alone: compute it once per work package, then roll only the nonce.

Golden vector

Every implementation must reproduce exactly this:
The node pins the same vector in TestKHeavyHashPowValueGolden (consensus/kheavyhash/kheavyhash_test.go). Every example in section 10 is checked against it.

4. The timestamp: always milliseconds

work[10] always contains the exact uint64 TIME placed in the kHeavyHash pre-image, in milliseconds. KRNX Header.Time itself remains in seconds for the EVM, DAA, reward lockups, and every other chain rule, but a pool must not derive PoW time from the header or convert units itself. A miner parses [10] as a 64-bit integer and places it in the pre-image exactly as received. Multiplication, division, or storage in uint32 changes or truncates the pre-image, so the node rejects the resulting solutions. The rule is: send exactly the numeric value from work[10] without unit conversion.

Where to get it

Use only work[10], the hex-encoded numeric millisecond value:
work[9] contains a second-based KRNX header.Time, but it is a different field with a different purpose. Do not compare it with [10] or use it to identify a work type. The standard three-parameter krnx_submitWork path requires no such classification.

Do not roll TIME

A pool must not roll TIME along with the nonce. work[0] and work[10] form one immutable job. Fetch a new template to obtain a new time.

5. Difficulty: two scales

Stratum difficulty 1 is worth about 2^32 hashes. Hence:
Confusing the two is a factor of 2^32 in the books: either miner hashrate reads four billion times too low, or shares arrive in a flood the pool never sized for. For example, a network difficulty of 4.3·10^12 is 1000 on the miner scale, and a difficulty-125 share costs roughly 5.4·10^11 hashes. Rules that hold wherever the chain’s difficulty happens to sit:
  • Do not hard-code the starting difficulty — derive the band from the current network difficulty: start at netdiff/8, floor at netdiff/4096, ceiling at netdiff.
  • The ceiling is the netdiff derived from [2]: above it every share is already a network solution, and the pool goes blind to a miner that produces nothing.
  • A share that solves a block is always accepted, even when it misses the current vardiff bound.
Miner hashrate is estimated as usual: Σ (share_difficulty * 2^32) / elapsed.

6. KRNX Stratum interface

Example full exchange:

Two job formats

Ordinary: the seal hash read as four little-endian uint64s:
The miner reassembles the same 32 bytes from them. The third parameter is the timestamp (section 4). Big job (BzMiner, IceRiver, Goldshell and several ASIC stacks): instead of the array and the time, a single 80-character hex string — the first 40 bytes of the pre-image:
Which format to use is decided by a regex on the mining.subscribe user agent.

Extra nonce

The prefix occupies the high bytes of the 64-bit nonce; the miner rolls the rest. Two prefix bytes give 65536 connections and 48 bits of search space each. Never hand out more than three: an ASIC exhausts what is left in a fraction of a second. On submit a miner sends either all eight bytes (0x0001000000000527) or only its own part (000000000527). When fewer than 16 - len(prefix) hex characters arrive, the pool prepends the prefix itself.

7. Submitting a block

The answer is true when the node accepts the solution for processing, or false. A successful response alone does not mean that the KRNX height has already changed. false means one of:
  • the node no longer holds work with that powHash — a stale;
  • the PoW misses the target — the pool and the node disagree on the pre-image, most often the timestamp;
  • the block is too old relative to the current height.
The method has a legacy fourth-parameter variant that changes the header’s Extra. It is outside the universal public contract: some node-selected templates are immutable, so an external pool should always use the ordinary three-parameter call. Do not tie the submission to the miner’s socket: a miner may drop the connection right after solving, and the block still has to go out. Ask for fresh work immediately after a submit — the template is spent either way.

8. Splitting work across connections

The universal method is to partition the 64-bit nonce space. The pool assigns a unique high-byte prefix through mining.set_extranonce, and the miner scans the remaining low bits. Connections may share one PRE_POW_HASH while searching disjoint nonce sets. A two-byte prefix provides 65,536 independent 48-bit spaces. Keep the assigned prefix with the connection, reconstruct the full 64-bit nonce on mining.submit, and send it through the ordinary three-parameter krnx_submitWork call. This works for every job selected by the node. The public RPC does not currently guarantee a distinct PRE_POW_HASH per connection; that requires node-side issuance of derived jobs.

9. What you may and may not change

Free: Not allowed:

10. Code examples

Complete working clients (fetch work → decode the timestamp → compute difficulty → scan nonces → submit a block) live in docs/examples/kheavyhash-pool/. All of them are checked against the golden vector from section 3 and run against a live node. Python and Node.js are fast enough to validate an integration, not to hash for real. Below is the core of each implementation: building the pre-image and both cSHAKE256 passes.

10.1 Go

Targets and scales:
Inside the node the algorithm is already exported, so a Go pool living in this repository does not reimplement it:

10.2 Node.js

10.3 Rust

Own implementation on the sha3 crate:

10.4 Python

Checking a whole work package:

11. Direct integration checklist

  1. krnx_getWork answers, and [0] changes when the template does.
  2. Your hasher reproduces the golden vector from section 3.
  3. The timestamp comes from [10] as uint64 milliseconds without conversion.
  4. mining.notify ships the seal hash such that a miner rebuilds exactly [0] from the four words.
  5. Difficulty is set on the (2^224−1)/diff scale and below the block difficulty.
  6. A share that solves a block is accepted even if it misses the vardiff bound.
  7. krnx_submitWork returns true; the pool does not infer a new height from this response and tracks the next powHash separately.
  8. The node’s rejection rate is near zero. If it is not, see section 12.

12. Diagnostics