§ 07 · Blocks
Certificate
The ~60 KB proof of work that rides in front of every block
On this page 6 sections
A Bitcoin block proves its own work: anyone can hash its header and see that the result is small enough. A Pearl block can’t, because its work is a huge matrix multiplication on private data. So every Pearl block carries a separate proof, its certificateCertificate: The proof of work that travels in front of every block: about 60 KB, mostly a zero-knowledge proof. Any node can check it in milliseconds without seeing the miner's matrices..
Think of it as a sealed envelope with a label. The label, the public dataPublic data: The 164-byte readable part of a certificate: the mining settings, fingerprints of both matrices, the winning ticket, the matrix sizes and the tile's position. The header's proof commitment is a hash of it., says in 164 bytes which puzzle was solved and which lottery ticket won. The envelope holds a zero-knowledge proofZero-knowledge proof: A proof that convinces you something is true without showing the private data behind it. Pearl's certificate proves a ticket came from real matrix work without revealing the matrices., about 60 KB, that the label is true. Nodes read the label, check the proof, and never see the matrices.
This page is about the bytes. How the certificate is made and what the proof proves are on the certificate (mining) and zero-knowledge proofs.
What’s in a certificate?
Block 130,825’s certificate, field by field. The proof is collapsed to its first bytes; it would fill this page many times over.
| Field | Bytes | Value |
|---|---|---|
| Certificate version certificate.version | 4 @0 | 3 u32 LE. V1 before block 71,935, V2 until 98,999, V3 (salted noise seeds) from 99,000. 0 = no certificate. |
| Block hash certificate.blockHash | 32 @4 | 3fc851763893a0135ea756d5dcbe355f375e613957930e7f401fafc8ee5533ae SHA256d of the 108-byte header this certificate proves (stored byte-reversed); must equal the header’s hash. |
| Public data length certificate.publicDataLength | 4 @36 | 164 164 for dense jobs; longer means MoE (invalid since 91,630). |
| Common dimension k certificate.publicData.k | 4 @40 | 8192 u32 LE: the shared dimension of A (m×k) and B (k×n). |
| Noise rank r certificate.publicData.rank | 2 @44 | 128 u16 LE: rank of the low-rank noise; must be ≥ 128 since block 96,251. |
| MMA type certificate.publicData.mmaType | 2 @46 | 0 0 = Int7 × Int7 → Int32. |
| Rows pattern certificate.publicData.rowsPattern | 6 @48 | [0,8] (h=2) Periodic row offsets of the hash tile, stored as (factor−1, length−1) × 3. |
| Columns pattern certificate.publicData.colsPattern | 6 @54 | 64 columns from [0,1,8,9,16,17,…] (w=64) Periodic column offsets of the hash tile. |
| Experts e certificate.publicData.experts | 2 @60 | 0 Number of MoE experts; 0 = dense (MoE is invalid on mainnet since 91,630). |
| Top-k certificate.publicData.topK | 2 @62 | 0 0 for dense jobs. |
| Reserved certificate.publicData.reserved | 28 @64 | 00000000000000000000000000000000000000000000000000000000 Must be zero. |
| hash_A certificate.publicData.hashA | 32 @92 | 8e17f731a0c6f8e09a7a29f19732fc29f65932b63886013274b920b057e6930f Keyed-BLAKE3 Merkle root of matrix A (key = job key). |
| hash_B certificate.publicData.hashB | 32 @124 | c2e4cb51cff3f95861c45248b6369f5c2dbd623895a3146be39134d060837c8d Keyed-BLAKE3 Merkle root of Bᵀ (B column-major). |
| hash_jackpot certificate.publicData.hashJackpot | 32 @156 | b4be6b12fbad2ac915cbc0f80f94a1b3fbe55786f3a2912ded00000000000000 The winning ticket: BLAKE3 keyed with seed_A over the tile transcript; read as a little-endian number and compared with the bound. |
| m (rows of A) certificate.publicData.m | 4 @188 | 131072 u32 LE; bound into the noise seeds since certificate V3. |
| n (columns of B) certificate.publicData.n | 4 @192 | 262144 u32 LE; bound into the noise seeds since certificate V3. |
| Tile row offset certificate.publicData.tRows | 4 @196 | 103168 Smallest row index of the winning hash tile. |
| Tile column offset certificate.publicData.tCols | 4 @200 | 188674 Smallest column index of the winning hash tile. |
| Proof length certificate.proofLength | 4 @204 | 59560 u32 LE; at most 60,000 bytes. |
| ZK proof (first 32 of 59,560 bytes) certificate.proof | 32 @208 | 59,560 bytes, collapsed pow_bits(3) ‖ rate_bits(3) ‖ zeta(16) ‖ Plonky2 proof: a STARK wrapped in two Plonky2 recursions. Not covered by the block hash. |
| Field | Size | Type | Description |
|---|---|---|---|
| Version | 4 | uint32 LE | 03000000 since block 99,000. |
| Block hash | 32 | hash | SHA256d of the header this certificate proves, in internal byte order. Must match the header. |
| Public data length | 4 | uint32 LE | 164 (a4000000) for a dense job. Not present in version 1. |
| Public data | 164 | bytes | What was mined and which ticket won (table below). The header’s proof commitment is a hash of it. |
| Proof length | 4 | uint32 LE | 59,560 (a8e80000) on mainnet today. At most 60,000. |
| Proof | 59,560 | bytes | The zero-knowledge proof: a STARK wrapped in two Plonky2 layers. Not covered by the block hash. |
Which versions exist?
Three real ones, each required in its own range of heights, plus an empty placeholder:
| Version | Blocks | Layout after the 4-byte version | Size on mainnet |
|---|---|---|---|
| 0 | none (placeholder) | nothing | 4 bytes, 00000000 |
| 1 | 1 to 71,934 | block hash (32) ‖ public data (164, fixed) ‖ proof length (4) ‖ proof | 59,764 bytes (block 1) |
| 2 | 71,935 to 98,999 | block hash (32) ‖ public data length (4) ‖ public data ‖ proof length (4) ‖ proof | about 59.8 KB |
| 3 | 99,000 onward | same bytes as version 2 | 59,768 bytes |
- Version 2 came with the Mixture-of-Experts hard fork at block 71,935. Its public data could be longer than 164 bytes, up to 4,807, to describe expert routing; hence the length field. From block 91,630 a soft fork rejects anything longer than 164 again, so mainnet certificates are dense.
- Version 3 came with the salted noise-seed hard fork at block 99,000. Not one byte of the format changed. What changed is how the verifier reads it: the noise seeds now also commit to the matrix sizes, and the proof commitment hashes a 3. See noise seeds.
- Version 0 is a bare
00000000: “no certificate here”. It stands in for certificates in block templates and inheadersreplies sent without certificates.
The genesis blockGenesis block: Block 0, written into the software rather than mined. Pearl's is dated 27 April 2026, pays no reward, and quotes a Bitcoin block hash to show it wasn't made earlier. has a stand-in version 1 certificate: its block hash, 164 zero bytes of public data and a 4-byte “proof”, deadbeef. That’s 208 bytes, and it’s never checked.
What’s in the public data?
Twelve fields in 164 bytes. Here they are for block 130,825, decoded:
| Field | Size | Type | Description |
|---|---|---|---|
| k | 4 | u32 | Shared dimension of A (m × k) and B (k × n): 8,192. |
| Noise rank r | 2 | u16 | 128. At least 128 since block 96,251. |
| MMA type | 2 | u16 | 0 = 7-bit × 7-bit integers into 32-bit sums. |
| Rows pattern | 6 | pattern | 070100000000: rows 0 and 8 of the tile, so h = 2. |
| Columns pattern | 6 | pattern | 0001031f0000: column pairs every 8 columns, 0, 1, 8, 9, … 248, 249, so w = 64. |
| Experts, top-k | 4 | u16 × 2 | 0, 0: a dense job, not Mixture-of-Experts. |
| Reserved | 28 | zeros | Must be zero. |
| hash_A | 32 | hash | Keyed-BLAKE3 Merkle root of matrix A: 8e17f731…57e6930f. |
| hash_B | 32 | hash | The same for B, stored column by column: c2e4cb51…60837c8d. |
| hash_jackpot | 32 | hash | The winning ticket, b4be6b12…a2912ded followed by seven zero bytes. Read little-endian and compared with the target. |
| m | 4 | u32 | Rows of A: 131,072. |
| n | 4 | u32 | Columns of B: 262,144. |
| Tile row | 4 | u32 | Where the winning tile sits: row 103,168. |
| Tile column | 4 | u32 | Column 188,674. |
The first 52 bytes are the mining configuration; the job key hashes them along with the header. Together the fields say: “A 131,072 × 8,192 matrix times an 8,192 × 262,144 matrix, with rank-128 noise, cut into 2 × 64 tiles; the tile starting at row 103,168, column 188,674 produced this ticket.” That’s a multiplication of about 2.8 × 10¹⁴ multiply-accumulates, of which the winning tile is 1,048,576.
How a 6-byte pattern becomes a list of offsets
Each pattern is three (stride, count) pairs, stored as (factor − 1, count − 1). The first stride is just its factor; each later stride is its factor times the span of everything before it.
- Rows
07 01 00 00 00 00: stride (7 + 1) × 1 = 8, count 1 + 1 = 2, so offsets 0, 8. The other pairs add nothing. - Columns
00 01 03 1f 00 00: stride 1, count 2 gives 0, 1 (span 2); then stride (3 + 1) × 2 = 8, count 31 + 1 = 32, repeats that pair every 8 columns: 0, 1, 8, 9, …, 248, 249, 64 offsets.
That’s exactly the reference GPU miner’s pattern, which matches the sums one GPU thread holds in its registers. Block 130,827’s certificate decodes to a
different shape, 8 × 16, from rows 07 01 01 03 00 00 (0, 8, 32, 40, 64, 72, 96, 104) and columns 00 01 0f 07 00 00
(0, 1, 32, 33, …, 224, 225). Both are 128 cells. Miners choose. See GPUs and tiles.
What’s in the proof bytes?
A short preamble, then the proof itself:
| Size | Field | Block 130,825 |
|---|---|---|
| 3 | proof-of-work bits for the three proof layers | 12 12 16 = 18, 18, 22 |
| 3 | rate bits for the three layers | 01 03 07 = 1, 3, 7 |
| 16 | zeta, a challenge point (two 64-bit field elements) | bbbb2399… |
| 59,538 | the Plonky2 proof | … |
These are settings of the proof system, not of the mining. Block 1’s proof starts 12 12 16 02 03 07: the same, except a rate of 2 for the first layer. (Comments in Pearl’s code describe a 32-byte “preprocessed digest” in the preamble; the actual format carries the 16-byte zeta instead.)
How big can it be, and does it count?
- Certificate today
- 59,768 B
- 4 + 32 + 4 + 164 + 4 + 59,560
- Proof today
- 59,560 B
- same at k = 2,048 and 8,192
- Public data
- 164 B
- dense; MoE was longer
- Proof cap
- 60,000 B
- MaxZKProofSize
- Certificate cap
- 65,000 B
- CertificateMaxSize
- Counted in vsize
- 0 vB
- excluded by consensus
The proof barely changes size with the size of the multiplication. In the research for this site, proofs for k = 2,048 and k = 8,192 were both exactly 59,560 bytes.
The block size limit, 1,000,000 vbytes, counts the header and transactions (witness bytes at a quarter) and not the certificate. Pearl’s code says why in one line: the certificate is “proof metadata, not block data”. The certificate sits on top of the 4,000,000-byte cap for header and transactions, so the largest possible block message is 4,065,000 bytes.
That’s why sizes disagree. Block 130,827 is 64,379 bytes on disk and on explorers like Blockbook, but only 2,795 vbytes to consensus. A block holding nothing but its coinbase shows up as roughly 60 KB.
Make a guess
Could a miner pad its certificate with extra bytes to slow down other nodes?
Think of your answer first, then check it.
Answer Only up to 65,000 bytes, and the padding must still be a valid proof.
The decoder refuses any certificate over 65,000 bytes or any proof over 60,000, before trying to verify it. And the bytes are a proof: a node runs the verifier over them, so garbage fails. The caps leave a little headroom over today’s 59,768 bytes.
Where does the certificate travel?
Everywhere the header goes:
- In blocks, first: certificate, then header, then transactions. See blocks.
- In header sync. A
headersmessage carries up to 100 entries, each a certificate (or00000000) followed by a 108-byte header. Nodes verify every certificate while syncing headers. - To light clients, which also verify each header’s certificate. So a Pearl light client downloads about 60 KB per block header, where a Bitcoin light client needs 80 bytes.
Code: the wire format
The layouts are documented at the top of certificate.go (node/wire/certificate.go:18-35 at v1.5.2, on GitHub),
and a V2/V3 certificate is written in certificate_v2.go (node/wire/certificate_v2.go:80-98 at v1.5.2, on GitHub)
(the version itself is written first by the MsgCertificate wrapper):
func (c *CertificateV2) Serialize(w io.Writer) error {
w.Write(c.Hash[:]) // 32
binary.Write(w, binary.LittleEndian, c.PublicDataLen) // 4
w.Write(c.PublicDataBytes()) // 164
binary.Write(w, binary.LittleEndian, uint32(len(c.ProofData))) // 4
w.Write(c.ProofData) // 59,560
return nil
} The public data fields are listed in proof.rs (zk-pow/src/api/proof.rs:76-105 at v1.5.2, on GitHub), and the
vsize rule that leaves the certificate out is in vsize.go (node/blockchain/vsize.go:39-52 at v1.5.2, on GitHub).