Grin Payment Proof Verifier
Grin's answer to “prove you paid me”. Both parties sign the same 73 bytes — amount, kernel excess and the payer's key — and the kernel then settles on chain. This page checks both halves and tells you which one holds.
Two separate questions, two separate answers. A valid signature over a kernel
that never confirmed is not a settled payment, and a confirmed kernel with a broken signature
says nothing about who paid whom. You will always see both results — never one merged verdict.
Payment proof (JSON)
…or drop the .json file anywhere on this page.
↑ Checked on this server · not logged
What a valid result proves — and what it does not
- The amount is attested, never verified. Grin amounts are confidential: the chain proves a transaction happened, not what it was worth. The amount is true only insofar as both parties signed it, which is why it is marked attested above and never presented as chain data.
- It is a mutual attestation. Either signature alone is self-serving — a recipient can sign any amount/kernel/sender triple it likes, and a sender can sign over any kernel already on the chain. The pair is what carries weight, because neither party can produce the other's half.
- A proof where sender and recipient are the same address passes. Both checks use the same message, so one key signing twice satisfies both. That is a self-attestation, not a payment — this page flags it rather than hiding it.
- Nothing binds the sender address to the transaction's inputs. The proof says “this key claims this kernel”, not “this key funded it”.
- Mainnet vs testnet is a claim, not evidence. The
grin1…/tgrin1…prefix comes from the exporting wallet and is not covered by either signature — editing it leaves both signatures valid. The kernel lookup is the real network evidence, and this explorer only ever asks mainnet. - The signature check is done with Node's ed25519, grin-wallet's with ed25519-dalek. They agree on every honest proof. On a deliberately crafted key or signature — a small-order point, a non-canonical encoding — whether the two libraries agree has not been established here, so treat a hostile file's verdict as this page's answer, not as grin-wallet's.
How to produce one, and what is inside it
- The payer asks for it when sending. A payment proof only exists if the
send was addressed to a Slatepack address and the recipient's wallet signed the receipt.
Export it afterwards with
grin-wallet export_proof --id <tx id> proof.json, and check your own copy withgrin-wallet verify_proof proof.json. - Six keys, no envelope. The file is one JSON object:
amount(nanogrin),excess(the 33-byte kernel commitment), and an address plus signature for each side. No version field, no timestamp, no transaction id — the kernel excess is the identifier. - The signed message is 73 bytes. The amount as a big-endian
u64, then the 33-byte excess, then the payer's 32-byte key — concatenated, with no hashing and no domain separator. Both signatures cover exactly that, which is why re-pointing a proof at a different payer breaks the recipient's signature. - The kernel half works on a pruned node. Kernels are kept in full at every height, unlike block bodies — so “found on chain” here is a reliable answer, not a horizon artefact.
What this page does with your file
- It is sent to this server. Unlike the
Slate Inspector and the Wallet Checker,
which never transmit what you paste, this tool posts the proof to
/api/proof/verify: the kernel lookup needs the node, and browser ed25519 is not yet dependable enough to rely on. - Nothing about it is logged — not the addresses, the amount, the excess, or the file. It is verified and the response is thrown away.
- A payment proof is not a secret, but it is private. It names both parties and an amount. Nothing here puts it in the URL, so it stays out of your browser history and out of any referrer.