§ 08

§ 08 · Mining

Rank penalty

More noise buys a stricter bar, not an easier one

Pearl’s noise has a setting called its rank, written r: roughly, how many independent random ingredients the noise is mixed from. The miner chooses it and declares it in the certificate.

For Pearl’s first 96,250 blocks, a higher rank quietly made mining cheaper. So at block 96,251, on 6 August 2026, a soft fork added the rank penalty: rank must be at least 128, and any rank above that makes the winning bar stricter in exact proportion.

The rule is often described backwards, so this page is precise about which way it goes.

Try it

Build a noise matrix from a thin random matrix and a ±1 selector. The number of columns in the thin matrix is the rank.

Low-rank noise

A thin random matrix times a ±1 selector

Rank: columns of E_L

E_L · 8 × 4 · random

× selector S · 4 × 16

= E · 8 × 16 · hover a column

E column 0 = E_L column 3 − E_L column 1

[−3, −1, −4, −3, 3, −1, 3, −1] − [3, −3, −1, 3, 1, −1, −3, 0] = [−6, 2, −3, −6, 2, 0, 6, −1]

The selector column 0 has +1 in row 3 and −1 in row 1, nothing else. No multiplications at all: one subtraction per entry.

Rank of E

3 of 8 possible

r − 1 = 3: every selector column sums to zero, which costs one dimension.

Numbers to write it down

low rank64
full128

Low rank: 8 × 4 random numbers plus 2 small indices per column (32 + 32). Full: 8 × 16.

Real Pearl r = 128 (the minimum since block 96,251; a higher rank makes the winning bar stricter in proportion). The thin factor’s entries run from −32 to 31, so noise runs from −63 to 63. For a 4,096 × 8,192 matrix, low rank needs 540,672 numbers instead of 33,554,432, about 62× fewer, and peeling the noise off the product costs about 2r/k ≈ 3.1% extra.

Toy sizes 8 × 16 here, with factors from −4 to 3. Real jobs are thousands of rows wide.

Runs in your browser · Pearl’s own noise generator src/lib/miner.ts · noiseMatrices

What does the rank control?

Two things, and the second is the one that matters here.

  1. How the noise is built. The noise for A is a random m × r matrix times an r × k selector, so it has rank at most r. See rank and noise.
  2. How often each tile’s diary is updated. A tile folds its running sums into the diary once every r steps along k. So a ticket records k ÷ r checkpoints: 64 at rank 128 with k = 8,192, but only 32 at rank 256.

In the certificate, the rank is a 2-byte little-endian number at offsets 4–5 of the public data. On mainnet it reads 80 00: rank 128.

What changed at block 96,251?

Two rules, both enforced by every node from that height:

The rank-penalty rule
Field Size Description
Minimum rank — r ≥ 128. Before the fork, ranks down to 32 were allowed.
Penalized bar — ticket ≤ target × h·w·⌊L/r⌋·128, where L = k − (k mod r). This applies on top of the original bar, target × h·w·L.

Divide the new bar by the old one and the rule becomes clear:

T⋅hw⋅⌊L/r⌋⋅128T⋅hw⋅L=128r\frac{T \cdot h w \cdot \lfloor L/r \rfloor \cdot 128}{T \cdot h w \cdot L} = \frac{128}{r}
Exact, because L = k − (k mod r) is always a multiple of r. Neutral at r = 128; stricter above it.
1 × the original bar (every rank, before the fork)rejectedrejected1½¼⅛r = 32641282565121,024noise rank declared in the certificate
How high the winning bar is, relative to the original rule, for each rank, since block 96,251. Ranks below 128 are invalid; above 128 the bar shrinks as 128/r. Before the fork, every rank from 32 up got the full bar.

Not every rank fits every k: consensus also requires 16r ≤ k ≤ 4r². At k = 8,192 that leaves 128, 256 and 512 (64 would fit too, but is now below the minimum).

Why was it needed?

Pearl Research Labs gave the reason in the pull request that added the rule:

“A proof’s jackpot bound is scaled by the work one attempt costs, and the noise rank divides that work, so mining at a larger rank buys a proportionally easier bound for the same effort. From RankPenaltyForkHeight on, a certificate’s noise rank must be at least 128 and its jackpot must meet a bound scaled by 128/rank, which cancels the advantage.”

In other words, the old bar priced a ticket at h·w·L multiply-accumulates, but at a larger rank a ticket was really cheaper than that. The penalty prices a ticket by its number of checkpoints instead: h·w·(k/r)·128, the same as h·w·k at rank 128.

Why a larger rank makes a ticket cheaper isn’t spelled out. Two explanations both fit, and the research for this site couldn’t tell which one prompted the fork:

  • In theory. The cuPOW paper’s lower bound on producing a diary shrinks as 1/r, and with fast rectangular matrix multiplication the cost per checkpoint need not grow with r. Fewer checkpoints per ticket means less provable work per ticket.
  • In practice. On a GPU, the XOR-and-rotate folding runs on ordinary cores once per r multiply-accumulates, beside the tensor cores. A larger r means fewer folds per multiply-accumulate, so more tickets per second for a miner chasing tickets rather than useful output. A public issue in Pearl’s repository shows miners trying rank 256 before the fork.

A worked example

Which way does it go?

This is where explainers slip. One widely shared description says the fork “imposed a stricter threshold on proofs that use less noise”. The code says the opposite:

Noise rank The backwards claim What the code does
Below 128 a stricter bar rejected outright
Exactly 128 the reference the original bar, unchanged
Above 128 not penalized a stricter bar, × 128/r

More noise is never rewarded; less noise than 128 isn’t allowed at all.

Who enforces it?

  • Nodes, for every block at height 96,251 or above. It’s a soft fork: it only removes blocks that were valid before, so nodes that never upgraded still accept every block the new rules accept.
  • Not in templates. The rule reads the certificate’s public data, so it’s skipped wherever proof of work isn’t checked, such as block templates.
  • The miner and gateway, locally. Pearl’s reference miner refuses a configured rank below 128 (“blocks would be rejected by consensus”), and the gateway turns each job’s target into the penalized share target before handing it to the GPU. Pearl’s own CPU miner runs at rank 128.

It also explains a detail of Pearl’s hashrate: one “hash” equals one multiply-accumulate at rank 128. A miner using rank r > 128 needs r/128 multiply-accumulates per hash of credited work. See hashrate.

Code: the check and its tests

From sanity_checks.rs (zk-pow/src/api/sanity_checks.rs:161-200 at v1.5.2, on GitHub):

zk-pow/src/api/sanity_checks.rs Rust
pub const PENALTY_BASE_RANK: usize = 128;

pub fn check_rank_penalty(config: &MiningConfiguration, hash_jackpot: &Hash256, nbits: u32) -> Result<()> {
    let rank = config.rank as usize;
    ensure!(rank >= PENALTY_BASE_RANK, "Rank must be >= {PENALTY_BASE_RANK} || r={rank}");
    let adjustment_factor = penalized_adjustment_factor(config);
    ensure!(U256::from_little_endian(hash_jackpot) <= scale_target(nbits, adjustment_factor), /* … */);
    Ok(())
}

fn penalized_adjustment_factor(config: &MiningConfiguration) -> usize {
    let rank = config.rank as usize;
    tile_size(config) * (config.dot_product_length() / rank) * PENALTY_BASE_RANK
}

The tests (lines 281–302 (zk-pow/src/api/sanity_checks.rs:281-302 at v1.5.2, on GitHub)) pin the behaviour: neutral at rank 128, and the bar divided by 2, 4 and 8 at ranks 256, 512 and 1,024. pearld calls the check only from RankPenaltyForkHeight on, in CheckCertificateRules (node/blockchain/validate.go:549-556 at v1.5.2, on GitHub).

The codex

Suggested

Up and down arrows move through the results, Enter opens one, Escape closes. Anywhere on the site, slash or Command K opens this search, and the square brackets [ and ] go to the previous and next page.