Independently verify that a signed BlockSign PDF has not been altered since it was stamped on the Solana blockchain.
blocksign-verify is the reference implementation of the BlockSign v0 document-signing protocol. It is published openly so that any party — recipients, auditors, courts, archival systems — can confirm the integrity of a signed document without trusting the BlockSign platform itself.
The package ships as both a library and a CLI:
blocksign-verify contract.pdf
# VERIFIED tx 5J7zXq9G3a1bN4mLrPzVxYwR9k7Mc2VfHd5JuBwGpEtT3sLfA1jW8oN3eQpHy4xR2zS9bV6t
# document 0e6e3c1a-2f9b-4f4a-9a4d-c0a8b5e90f24 (v1)
# status completed
# signed 2025-11-04T14:00:00.000Z- Why this exists
- Installation
- Quick start
- How verification works
- The v0 protocol at a glance
- Error model
- Using a private RPC
- Security model
- Compatibility
- Contributing
- License
BlockSign is tamper-evident document signing on Solana. The platform's central claim is that a signed document can be independently verified by anyone — without asking BlockSign for permission, without trusting BlockSign's servers, and without a future BlockSign outage taking the verification capability with it. Cryptographic, not photographic.
To make that claim credible, the verification path has to be public. This repository is that path:
- Open algorithm. Every step — how the PDF is hashed, how the stamp is encoded, how the memo is parsed — is documented in
docs/PROTOCOL.mdand implemented here. - Open key. The v0 protocol's XOR obfuscation key is a published public constant. It keeps casual on-chain scrapers from indexing BlockSign memos verbatim — it is not a confidentiality primitive.
- No platform calls. Verification reads only the PDF bytes and the public Solana RPC. The BlockSign platform is never contacted; the verifier keeps working if
blocksign.inkis down forever.
If you are evaluating BlockSign for a regulated workflow, this package is the artifact your security team should audit.
Only the verifier is open source — this package, the protocol specification, and the related tooling under the blocksign-verify GitHub organization. The BlockSign platform (the web app at blocksign.ink, its signing workflow, dashboard, RLS schema, payer service, and product surface) is closed source.
This split is deliberate: verification is the part of the system that has to be auditable by anyone for the on-chain proof to carry weight. Everything else is product surface that does not need to be public to be trustworthy.
Clone the repository and build it locally:
git clone https://github.com/blocksign-verify/verify.git
cd verify
pnpm install
pnpm buildRequires Node 20 or newer and pnpm 10+. The package is pure ESM.
Use it as a git dependency from another project:
pnpm add github:blocksign-verify/verifyThe simplest call. The transaction signature is recovered from the PDF's own embedded metadata block.
import { readFile } from 'node:fs/promises';
import { verifyPdf } from 'blocksign-verify';
const bytes = await readFile('contract.pdf');
const result = await verifyPdf(bytes);
switch (result.verdict) {
case 'verified':
console.log('Document matches its on-chain stamp.');
break;
case 'tampered':
console.warn('Document has been altered since it was signed.');
break;
case 'unverifiable':
console.error(`Could not verify: ${result.error.code}`);
break;
}When you don't trust the PDF's embedded metadata — for example, the transaction signature was shared with you out of band — pass it explicitly:
import { verifyByTransaction } from 'blocksign-verify';
const result = await verifyByTransaction(
bytes,
'5J7zXq9G3a1bN4mLrPzVxYwR9k7Mc2VfHd5JuBwGpEtT3sLfA1jW8oN3eQpHy4xR2zS9bV6t',
);When verifying many documents, construct a BlockSignVerifier once and reuse its RPC connection:
import { BlockSignVerifier } from 'blocksign-verify';
const verifier = new BlockSignVerifier({
rpcUrl: 'https://your-private-rpc.example.com',
timeoutMs: 15_000,
});
for (const path of paths) {
const bytes = await readFile(path);
console.log(path, (await verifier.verify(bytes)).verdict);
}$ blocksign-verify --help
Usage: blocksign-verify [options] <path-to-pdf>
Options:
--tx <signature> Verify against a specific transaction signature instead
of the one embedded in the PDF.
--rpc <url> Override the Solana RPC endpoint (default: public mainnet).
--cluster <name> One of mainnet-beta | devnet | testnet.
--json Emit machine-readable JSON instead of human output.
-h, --help Print this message.
Exit codes follow the standard pattern so you can chain blocksign-verify into shell pipelines:
| Code | Meaning |
|---|---|
| 0 | Document verified |
| 1 | Document tampered (on-chain hash does not match) |
| 2 | Document unverifiable (missing metadata, RPC error, etc.) |
| 126 | Bad invocation |
signed.pdf Solana
────────── ──────
│
│ readBlockSignMetadata()
▼
/BlockSign_Metadata ──── transaction signature ────▶ getParsedTransaction
│ │
│ │ memo payload
│ ▼
│ "v0:<base64>"
│ │
│ │ parseV0Memo
│ ▼
│ base64 payload
│ │
│ │ XOR + msgpack
│ ▼
│ DocumentStamp
│ │
│ extractContentStreams │
▼ │
sha256 contentHash ───── timingSafeEqual ─────────────▶ stamp.contentHash
│
▼
┌─────────────────────┐
│ verified | tampered │
└─────────────────────┘
The verifier never contacts the BlockSign platform. The only network call is to the configured Solana RPC.
Full specification: docs/PROTOCOL.md.
A BlockSign-stamped PDF consists of two coupled artifacts:
- A Solana memo transaction carrying the encoded
DocumentStampas its sole instruction payload. - A PDF with an embedded
BlockSign_Metadatadictionary containing the memo's transaction signature.
The memo body is:
memo := "v0:" || base64( xor( msgpack( shortKey( stamp ) ), KEY ) )
Where:
shortKeyrewrites long field names to two-letter shortcuts and substitutes enum strings for integer codes, fitting the stamp under Solana's 1000-byte memo cap.msgpackis RFC-compliant MessagePack.xoris a repeating-key XOR with the publishedBLOCKSIGN_V0_PROTOCOL_KEY. This is obfuscation, not encryption — its purpose is to prevent casual on-chain indexers from treating BlockSign memos as plaintext.
The integrity of the stamp itself rests on SHA-256 and Solana's immutable on-chain ordering — not on the secrecy of the XOR key.
Every failure surface returns a typed VerificationError whose code field is one of:
| Code | Meaning |
|---|---|
invalid_pdf |
Bytes are not a parseable PDF document. |
missing_metadata |
PDF lacks a /BlockSign_Metadata dictionary. |
memo_not_found |
The referenced transaction has no SPL-memo instruction. |
memo_malformed |
Memo body is not in "v<n>:<payload>" form. |
unsupported_protocol_version |
Memo declares a protocol this verifier does not understand. |
stamp_schema_invalid |
Decoded payload does not match the v0 schema. |
rpc_unavailable |
Solana RPC call failed or timed out. |
invalid_signature_format |
Transaction signature is not valid base58. |
import { isVerificationError } from 'blocksign-verify';
if (result.verdict === 'unverifiable' && result.error.code === 'memo_not_found') {
// Maybe this PDF was signed on devnet, not mainnet.
}The default RPC is https://api.mainnet-beta.solana.com. It is rate-limited and not suitable for production verification at scale. For sustained use, pass a private RPC URL:
import { BlockSignVerifier } from 'blocksign-verify';
const verifier = new BlockSignVerifier({
rpcUrl: 'https://my-rpc.helius-rpc.com/?api-key=...',
});Or, if you already have a Connection instance, hand it in directly:
import { Connection } from '@solana/web3.js';
import { BlockSignVerifier } from 'blocksign-verify';
const connection = new Connection(myRpc, 'confirmed');
const verifier = new BlockSignVerifier({ connection });What this package proves on a successful verified result:
- The PDF bytes you passed in produce the same SHA-256 content-stream digest as the digest the BlockSign platform committed to the Solana memo program at the time of signing.
- The committed memo exists on chain and is referenced by the transaction signature embedded in the PDF (or supplied explicitly).
- The Solana memo transaction has been observed at the configured commitment level (
confirmedby default).
What this package does not prove:
- That the signers named in the stamp are who they say they are. Signer identity is established at sign time by the producer; this verifier does not re-validate it.
- That the document has not been altered in ways that affect only the PDF metadata block or annotation layer. The
contentHashcovers page content streams only. - That the BlockSign platform itself was not compromised at sign time. Tamper-evidence is a different property from tamper-resistance.
For the full threat model, see docs/PROTOCOL.md.
To report a security vulnerability privately, see SECURITY.md.
| Verifier version | Protocol versions | Status |
|---|---|---|
0.1.x |
v0 |
active |
The verifier follows semantic versioning. Adding support for a new protocol version is a minor-version bump; dropping support for an old protocol version is a major-version bump.
Contributions are welcome — see CONTRIBUTING.md. The protocol spec in docs/PROTOCOL.md is the source of truth; any change to wire format requires a spec edit before the implementation lands.
MIT — see LICENSE.