§ 08 · Mining
Rank penalty
More noise buys a stricter bar, not an easier one
On this page 7 sections
Pearl’s noise has a setting called its rankRank: A measure of how much independent information a matrix holds. A low-rank matrix can be built from two thin ones, which makes it cheap to create and to undo., 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 forkSoft fork: A rule change that only tightens the rules, so old software still accepts the new blocks. Pearl's soft forks came at blocks 91,630 and 96,251. added the rank penaltyRank penalty: A rule since block 96,251: the noise rank must be at least 128, and a higher rank makes the winning bar proportionally stricter, so it buys no advantage.: 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.
What does the rank control?
Two things, and the second is the one that matters here.
- 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.
- 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:
| 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:
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):
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).