§ 08

§ 08 · Mining

The certificate

From a winning tile to a proof anyone can check in milliseconds

Somewhere in a giant multiplication, one tile’s ticket has come in under the bar. The miner has won a block, but only it knows that. Everyone else has to be convinced, without being shown the matrices, which might be gigabytes of a company’s AI model and its users’ data.

That’s the job of the certificate. It turns “trust me, I won” into a compact proof of about 60 KB that any node checks in milliseconds. This page follows a winning tile from the GPU to every node’s verifier. The certificate’s bytes are on certificate (blocks), and the proof system itself on zero-knowledge proofs.

Try it

Checking can be far cheaper than doing. Catch a wrong matrix product with a few random vectors, then read on for why Pearl needs something stronger.

Freivalds’ check

Catch a wrong matrix product without redoing it

The prover is
Size n

A

B

claimed C · one cell may be off

Random 0/1 vector r · tap to flip

B·r

A·(B·r)

C·r

Press Check to compare A·(B·r) with C·r.

Confidence after 0 passing checks in a row

0% sure. A wrong C survives one random check with chance at most ½, so after 0 passing checks the chance it slipped through is at most 1 in 1.

Work per check

one check: B·r, A·(B·r), C·r
3n² = 48
redo the product
n³ = 64
at n = 8,192
201 M vs 550 G · 2,731× cheaper

Multiply-accumulates. The saving grows with n: at toy sizes it barely shows.

Intuition only This shows why checking can be far cheaper than doing. Pearl’s nodes don’t run Freivalds’ check: they verify a ~60 KB zero-knowledge certificate (a STARK wrapped in two Plonky2 layers) that proves the winning ticket came from the committed matrices, with the right noise, without ever seeing A or B.

Runs in your browser · nothing is sent src/lib/miner.ts · matMul

How does a winning tile become a certificate?

  1. The GPU spots a winner

    Every ticket is compared on the chip with an easier share target. One that passes is a share; one that also beats the network’s bar wins a block.

  2. The miner sends a plain proof

    For that tile, the miner’s software packages a plain proof: the sizes m, n, k and the noise rank, plus the tile’s h real rows of A and w real columns of B, as the 1 KiB chunks that contain them, with Merkle sibling hashes up to the fingerprints.

  3. The gateway checks it

    The gateway (or a pool’s equivalent) verifies the Merkle paths, regenerates the noise for those rows and columns, recomputes the tile’s diary and ticket, and checks it against the target.

  4. The gateway proves it

    It runs the zero-knowledge prover on the same inputs. This is the slow part: several seconds of CPU time.

  5. The header is finished

    From the public data it computes the proof commitment, SHA256d(version ‖ public data), and writes it into the header’s last 32 bytes. Now the header, and so the block hash, is final.

  6. The block is submitted

    Certificate (version, block hash, public data, proof), then the header, then the transactions, sent to the node, which checks it and relays it.

Plain proof, k = 2,048
140 KB
2 × 64 tile
Plain proof, k = 8,192
552 KB
2 × 64 tile
Certificate
59,768 B
any k

The plain proof reveals real data and grows with k. The certificate reveals nothing secret and stays the same size. That’s the trade: the gateway does seconds of work so that the whole network can check a smaller, private package.

What does the proof actually say?

In plain words: “I know some rows of A and some columns of B such that:

  1. They are the committed ones

    Placed at the declared positions, they hash, by keyed BLAKE3 as a Merkle tree with the job key as the key, to the public fingerprints hash_A and hash_B.

  2. They are in range

    Every entry is a whole number from −64 to 64.

  3. They produce this ticket

    With the public noise added, the tile’s running sums, folded every 128 steps, give a 64-byte diary whose BLAKE3 hash keyed with seed A is the claimed ticket.*”

What the proof keeps secret: the rows and columns themselves. What’s public: the 164 bytes of public data, meaning the settings, the two fingerprints, the ticket, the sizes and the tile’s position.

Some checks need no secrecy, so the verifier does them in the open instead of inside the proof: it rebuilds the job key, the noise seeds and the noise for the tile’s rows and columns, and it compares the ticket with the target. Rebuilding the noise is about two-thirds of that plain-arithmetic part of verification.

How is the proof built?

In three layers, each proving that the previous one verifies:

Layer System Role
0 A Starky STARK with hand-written circuits for BLAKE3, the matrix tiles and the ticket Proves the statement above.
1 Plonky2 Proves “the STARK verifies”; much smaller.
2 Plonky2, with zero-knowledge switched on Proves “layer 1 verifies” and hides everything. This is what’s published.

Only the last layer is zero-knowledge. The proof is bound to its block: its random challenges come from a BLAKE3 hash over the 76-byte header, the public data and the proof settings, so a proof made for one block fails for any other. How STARKs and recursion work is on zero-knowledge proofs.

How fast is it?

Proof
59,560 B
cap 60,000
Prove
5.7–17 s
warm, once per block found
Verify
7–21 ms
every node, every block

Measured for this site on an Apple M5 Max (18 CPU cores), with the reference 2 × 64 tile and rank 128:

k Prove, first run Prove, warm Verify
2,048 10.1 s 5.7 s 7.5–9.1 ms
8,192 15.1 s 17.0 s 10.9–21.0 ms

The proof was 59,560 bytes at both sizes. Nodes ship with the verifier’s circuits precompiled, so they never pay the setup cost. Light clients verify every header’s certificate too.

Make a guess

A pool's prover spends 17 seconds on a winning tile. Could someone grab the finished certificate and claim the block for themselves?

Think of your answer first, then check it.

Answer No.

Before publication, only the miner and its own pool or gateway have the plain proof. After publication, the certificate is useless for anyone else’s block: the proof is bound to the first 76 header bytes, and those include the Merkle root, which covers the coinbase that pays the winner. Change the payout and the job key changes, so the noise, the ticket and the proof no longer match. The copier would have to mine its own winning tile.

What does a node check, in order?

  1. The target is sane

    The header’s bits decode to a positive target no larger than Pearl’s limit.

  2. Certificate and header match

    The certificate’s block hash equals the header’s, and the header’s proof commitment equals SHA256d(version ‖ public data).

  3. The public data is sane

    The Rust verifier parses the public data using the version’s seed rules (salted for version 3) and checks the limits: rank, k, tile size, matrix sizes, tile position.

  4. The ticket beats the bar

    Ticket ≤ target × h·w·L, before any heavy work.

  5. The proof verifies

    The verifier rebuilds the job key, seeds and the tile’s noise, feeds them in as public inputs, and checks the three-layer proof.

  6. Height-dependent rules

    The certificate version must be the one required at this height; Mixture-of-Experts certificates are rejected from block 91,630; the rank penalty applies from block 96,251.

In pearld, the cheap checks on a new block (Merkle root, duplicate transactions) run first: “certificate verification is compute intensive, so we ensure it is performed last”.

What doesn’t the certificate prove?

  • That the work was useful. Random matrices pass. See useful output.
  • That there’s no shortcut. It proves this ticket came from this computation, not that the computation was the cheapest way to get a ticket. That rests on an argued conjecture. See tickets and target.
  • How the noise was derived. That part isn’t inside the proof at all: the verifier recomputes it in the open.
Code: the gateway's pipeline

From proof_generator.py (miner/pearl-gateway/src/pearl_gateway/proof_generator.py:21-46 at v1.5.2, on GitHub): the template decides the certificate version, the share is proved, and the header gets its commitment.

miner/pearl-gateway/src/pearl_gateway/proof_generator.py Python
# The certificate version is dictated by the block height via the template.
cert_version = template.required_cert_version
zk_proof = generate_proof_for_cert_version(
    cert_version, template.header.incomplete_header, plain_proof
)
# We need to copy because ZKCertificate assigns the proof_commitment to the header
header = copy(template.header)

Then ZKCertificate.from_pearl_header (miner/pearl-gateway/src/pearl_gateway/blockchain_utils/zk_certificate.py:133-160 at v1.5.2, on GitHub) sets header.proof_commitment = double_sha256(LE32(cert_version) ‖ public_data) and records the header’s hash in the certificate. The proof settings come from prove.rs (zk-pow/src/api/prove.rs:52-57 at v1.5.2, on GitHub): proof-of-work bits 18, 18, 22, and rate bits 1, 3, 7, or 2, 3, 7 when the STARK is small, which fits block 1’s proof, 12 12 16 02 03 07, made for a k = 4,096 job.

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.