Proovik
Verification specification · PVK-PROOF/1

How to verify a Proovik proof without trusting Proovik

This page describes, step by step and with real values, how anyone verifies by purely mathematical means that a document existed before a given date. No node, no API, no cooperation from us is required: only the proof file, a hash calculator and this page.

Version 1 Network: Kaspa mainnet Reference verifier: 248 lines of Python, no dependencies

What is claimed, exactly

Precision matters more than eloquence here, because whether the proof survives cross-examination depends on it.

What the proof establishes

That a byte string whose SHA-256 is a given value was placed into a Kaspa transaction, that this transaction was committed inside a block header, and that a valid proof of work was executed over that header, which includes a timestamp. Because the timestamp sits inside the data the work was done on, altering it invalidates the work. The date is nobody's declaration: it is a physical quantity already spent.

What the proof does NOT establish

Who created the document, whether its content is true, whether its author was entitled to create it, or that the document did not exist earlier. It establishes priority to an instant, not authorship or truthfulness. Proovik never sees the document: it only receives its hash.

On the hash

SHA-256 is a 256-bit digest function. Finding two different documents with the same digest has never been achieved and the estimated effort exceeds what humanity can compute. So if the digest of the submitted document matches the one on chain, the document is the same, with no practical room for doubt.

The four links

Verification is a chain. Each link connects to the next, and a single broken link makes the proof worthless. The values shown below are real: they belong to a certificate issued on 24 January 2026.

1

The transaction payload is the document digest

Links the document to the blockchain

Every Kaspa transaction accepts a free field, payload. Proovik writes there the 32 bytes of the document's SHA-256, and nothing else. The check is a literal comparison.

SHA-256(documento) == transaccion.payload
documento
8ea578dd12b3e8c55b8b53f3db80f6cd23fe1ed22a0d7af5c46fc8e25f45294c
payload
8ea578dd12b3e8c55b8b53f3db80f6cd23fe1ed22a0d7af5c46fc8e25f45294c
Matches
2

The transaction is committed in the block header

Links the transaction to the block · 64-byte witness

Every header on the chain records a commitment to the transactions that block accepts. Since KIP-15 took effect, that field, acceptedIdMerkleRoot, is not a plain Merkle root but a chained value: it combines the parent block's commitment with the root of the accepted transactions. A recomputation that ignores that chaining fails, and that is the single most common cause of failed verifications.

nodo(a,b) = blake2b-256( clave = "MerkleBranchHash", a ‖ b ) AIMR = raiz Merkle sobre las transacciones aceptadas, en orden canonico SeqCommit = nodo( SeqCommit_del_padre_seleccionado , AIMR ) valido ⟹ SeqCommit == cabecera.acceptedIdMerkleRoot
AIMR
b3bc256951640ff6468511fb62ec8fb280dafb99ebf7ecd00245d0cc3c9debb3
seq. padre
2b3265225d51e5b051e46b9f164dc116cf2e21d566827abc8a7b1ae036d8330d
calculado
7843221eda25f05ddf9872ed57b59fb95c4f15aa7bf15017105e9797ef09caa5
in header
7843221eda25f05ddf9872ed57b59fb95c4f15aa7bf15017105e9797ef09caa5
Matches
Two verified pitfalls

The order of the accepted transactions is the canonical order fixed by KIP-15, not the order an API returns them nor a by-hash order. And a block tree's leaves —when inclusion, not acceptance, is verified— are not the txid but the tx::hash, computed with the full encoding (signature scripts and the storage-mass commitment included).

3

The header produces the declared block hash

Links the block to its identity

A block's hash is the digest of its serialized header. Recomputing it proves the submitted header is exactly what it claims to be, and that none of its fields —the timestamp included— has been touched.

h = blake2b-256( clave = "BlockHash" ) h ← version u16 little-endian h ← numero de niveles u64 little-endian para cada nivel: h ← numero de padres u64 little-endian h ← cada hash de padre 32 bytes h ← hashMerkleRoot 32 bytes h ← acceptedIdMerkleRoot 32 bytes h ← utxoCommitment 32 bytes h ← timestamp u64 little-endian ← la marca temporal h ← bits u32 little-endian h ← nonce u64 little-endian h ← daaScore u64 little-endian h ← blueScore u64 little-endian h ← longitud de blueWork u64 little-endian h ← blueWork big-endian, sin ceros a la izquierda h ← pruningPoint 32 bytes
Non-negotiable requirement

The header commits to all parent levels —today 56 of them, with more than two hundred hashes—. If a single one is missing, the digest cannot be reconstructed. This is why the proof file must capture the whole header at anchoring time: after some thirty hours the network discards that data and public services return a truncated header.

4

A valid proof of work was executed over that header

Turns the block into a physical fact and dates the document

This is where the proof stops being an assertion and becomes a finding of fact. Kaspa requires the result of applying its work function (kHeavyHash) to the header to fall below a threshold. Reaching it takes quadrillions of attempts: real energy, irreversibly spent.

pre = hash_de_cabecera( con timestamp = 0 y nonce = 0 ) M = matriz 64x64 generada con xoshiro256++ sembrado con pre, regenerada hasta que su rango sea 64 p = cSHAKE256("ProofOfWorkHash")( pre ‖ timestamp ‖ 32 ceros ‖ nonce ) r = cSHAKE256("HeavyHash")( (M · nibbles(p)) ⊕ p ) valido ⟹ entero_little_endian(r) ≤ objetivo(bits)
pre
6e4f2ca8feb279a54f2c4ac8d57c867917f1766a7936a5bd7bd9351557d3c6f7
result
0x15222b53caabb3b049ec84e25d0a0c7a35512f9d557adb4bc20
target
0x2345b0000000000000000000000000000000000000000000000
Valid work

Why the date is a physical quantity, not a declaration

This is the part cross-examination usually demands, so it is better shown than argued. The timestamp sits inside the header, and the header is the input to the proof of work. Change the time and you change the input, and the work is no longer valid. It is checked experimentally by altering the header verified in step 4 and repeating the computation:

Header Work result Verdict
Original, untouched0x15222b53caabb3b049ec…Valid
nonce + 10x6775abc19ba960657f66…Invalid
timestamp moved forward one second0xaee1e8274b1d268866ed…Invalid
The consequence

To present the document with a different date one would have to redo that header's proof of work against the network's real difficulty. This is not a cost barrier: it is a compute barrier on the order of the whole network's aggregate power over the interval one intends to falsify. Nobody declares the date. The date is paid for.

Will it still work after future hardforks?

The honest answer is yes, and it is worth understanding why. A captured proof is not a live query to the network: it is a historical fact, verified under the rules in force at the moment it was anchored. Kaspa changes over time —the Crescendo/Toccata transition, for instance, altered the DAA cadence and the difficulty count— but that does not erase the past: it just means a January 2026 block is verified with January 2026 rules, and one three years from now with the rules of three years from now.

The reference verifier is built exactly for this: it distinguishes eras. It knows the activation point of each consensus change (for example, the CRESCENDO_DAA_MAINNET constant) and applies to each block the rules of its epoch, not today's. A later hardfork adds a new era; it does not invalidate the earlier ones.

The permanence of the proof then rests on three conditions, all under the control of whoever holds it:

01
Capture within the window

Save the whole header at anchoring time, before the network discards the full data (some 30-40 hours).

02
Keep the proof

The proof file (or the PDF that embeds it) is self-sufficient: it points to no server and no online lookup.

03
Keep the reference verifier

248 lines of open code, no dependencies, re-derivable from Kaspa's public source. Even if lost, it can be rewritten.

Verify it yourself

The reference verifier is 248 lines of Python with no external dependencies: it implements keyed blake2b, the Keccak-f[1600] permutation, the xoshiro256++ generator and the matrix arithmetic. It reads in an afternoon and audits in two.

$ python3 verificador_kaspa.py prueba.json 8ea578dd12b3e8c55b8b53f3db80f6cd23fe1ed22a0d7af5c46fc8e25f45294c

  OK    1. El payload on-chain es el hash del documento
  OK    2. La transaccion esta en el arbol Merkle del bloque
  OK    3. La cabecera produce el hash de bloque declarado
  OK    4. La prueba de trabajo de esa cabecera es valida

RESULTADO: PRUEBA VALIDA
Download the reference verifier

Two files. Put them in the same folder and run the first. They install nothing and never reach the network.

Anyone who prefers not to run someone else's code can reimplement it: the four algorithms are described above without ambiguity, and every parameter comes from rusty-kaspa's public source. It is equally legitimate to look the transaction up in any public Kaspa explorer as a cross-check, but the difference matters: the explorer is testimony; the computation is a demonstration. This specification exists so as not to depend on the former.

Legal scope

Proovik provides a non-qualified electronic time stamp service within the meaning of Article 3.16(i) of Regulation (EU) 910/2014. It is worth being explicit about what that means.

  • Under Article 41.1 of that Regulation, an electronic time stamp shall not be denied legal effect or admissibility as evidence in legal proceedings solely on the grounds that it is in electronic form or does not meet the requirements of a qualified stamp.
  • A non-qualified stamp does not enjoy the presumption of accuracy of the date and time of Article 41.2, which is reserved for qualified ones. If challenged, the burden of substantiating the proof falls on the party presenting it.
  • That is precisely why this specification exists: so that this burden is met with an arithmetic check anyone can reproduce, rather than with the provider's declaration.

Proovik does not verify the content, authorship or lawfulness of documents, and never accesses them: it receives only their cryptographic digest. What is established is a proof of existence and prior date to a given instant.