Check that a successful Base transaction contains your receipt's SHA-256 digest at its named 32-byte slot, and that transaction and block references agree. The verifier queries your chosen Base RPC endpoint without using the Quantum Notary API or database.
Prefer to see the check first? Watch the 3-minute demonstration: the browser and BaseScan route comes first, followed by the command-line route.
What this establishes
If every check passes, you have evidence that:
- the RPC endpoint identifies itself as Base mainnet, chain ID
8453; - it returns the receipt's transaction hash;
- the transaction input is a whole number of 32-byte SHA-256 digests, with no prefix, metadata, or trailing bytes, and the digest at the slot your receipt names is exactly the receipt's digest;
- the transaction and execution receipt agree with the receipt's block hash and block number;
- the execution receipt reports success (
status = 1); - the named block contains the transaction; and
- the node's latest Base L2 block provides the reported confirmation count.
This procedure does not establish what source data produced the digest. To make that connection, retain the exact source bytes—or the exact canonical representation used at submission—and recompute SHA-256 over those bytes.
One transaction can anchor more than one digest
A transaction's input is a flat array of 32-byte digests laid end to end, with no length prefix, no framing, and no Merkle root:
input = digest[0] ‖ digest[1] ‖ … ‖ digest[n-1]
Most transactions carry one digest. A shared transaction can include records from
different customers submitted in the same short window. Filenames, topics, tenants
and service identifiers remain off-chain. batchIndex names your slot;
batchSize gives the transaction's digest count.
- Check the named slot. A digest found elsewhere, including across slot boundaries, does not verify your record. Neighbouring digests do not weaken the proof: each record is checked against its own 32 bytes.
- Identical digests can occupy separate slots. Each receipt must still identify its own position.
Before you run it
Use Python 3.12 or newer. The verifier is tested on Python 3.12, 3.13, 3.14 and 3.15. It uses only the standard library, so no third-party package installation is needed.
- Download the Python verifier package and its SHA-256 checksum.
- Extract the ZIP and open a terminal in the extracted
quantum-notary-base-verifierfolder. Keep the folder intact: it includes the verifier, its required calldata module and a README. - Save your receipt or proof JSON on your computer.
You do not need access to our source repository, a Quantum Notary account or an API key. Keep a copy of the package and checksum with your evidence so you can run it later without downloading anything from Quantum Notary. The checksum detects a changed download when compared with a previously trusted copy; it is not a separate signature.
The commands below use macOS/Linux shell syntax and python3 for Python 3.12+.
On Windows, use py -3 and your shell's environment-variable syntax. Check the
selected version with python3 --version or py -3 --version before starting.
Use a receipt that contains all four required evidence fields, plus the two optional slot fields when it carries them:
{
"digest": "0x<64 hexadecimal characters>",
"txHash": "0x<64 hexadecimal characters>",
"blockHash": "0x<64 hexadecimal characters>",
"blockNumber": 12345678,
"batchIndex": 0,
"batchSize": 1
}
batchIndex and batchSize are optional and appear in verification results and the
record view. Without them, the verifier accepts only a single-digest transaction at
slot 0. For an older receipt with a shared transaction, re-fetch the receipt or
specify its slot with --batch-index.
The tool also accepts snake-case receipt fields and the nested proof
bundle shape (tx.hash, block.hash, block.number, batch.index, and
batch.size). It rejects pending, failed, non-SHA-256, and non-Base receipts
before making an RPC request.
Choose an HTTPS Base endpoint you trust. RPC credentials embedded in a URL are secrets: keep them out of shell history, screenshots, and retained command logs.
export BASE_L2_RPC_URL="https://mainnet.base.org"
This public Base mainnet endpoint requires no API key and is rate limited. You can replace it with another trusted Base mainnet HTTPS RPC endpoint.
Run the direct verifier
From the extracted quantum-notary-base-verifier folder:
python3 tools/verify_base_anchor.py \
--receipt /path/to/receipt.json \
--min-confirmations 5
Add --json for a machine-readable result:
python3 tools/verify_base_anchor.py \
--receipt /path/to/receipt.json \
--min-confirmations 5 \
--json > direct-base-verification.json
Add --batch-index when your receipt does not carry a slot but the transaction
holds more than one digest. It must agree with the receipt whenever the receipt
names one; a flag never silently overrides receipt content.
python3 tools/verify_base_anchor.py \
--receipt /path/to/receipt.json \
--batch-index 3
The process returns exit code 0 only when every check passes. A digest,
slot, transaction, block, status, chain, or confirmation mismatch returns 1.
Invalid input returns 2; RPC or transport failure returns 3.
Malformed chain input or a slot beyond the transaction's digest count returns 1.
Exit 2 means invalid local input; the chain was never queried.
The --json output reports where the digest was found:
"anchor": {
"algorithm": "sha256",
"digest": "0x…",
"batchIndex": 3,
"batchSize": 12,
"slotOffsetBytes": 96,
"transactionInput": "0x…"
}
slotOffsetBytes gives your digest's starting byte in transactionInput.
The output separates the Base block timestamp from product receipt timestamps.
Recompute the source digest
For a file that was hashed byte-for-byte, recompute SHA-256 locally:
python3 - /path/to/source-file <<'PY'
import hashlib
import pathlib
import sys
source = pathlib.Path(sys.argv[1])
with source.open("rb") as handle:
print("0x" + hashlib.file_digest(handle, "sha256").hexdigest())
PY
Compare this value with the receipt's digest. A one-byte change produces a
different digest.
For structured input, hash the exact canonical bytes used at submission. Key order, number representation, whitespace and Unicode handling can change those bytes. Keep the canonical bytes or a tested, versioned canonicalization procedure with the receipt.
The receipt's processing identifier tells you which rule to reproduce:
| Identifier | Bytes represented by the digest |
|---|---|
digest@1 | The caller supplied a digest. Quantum Notary cannot establish how the caller prepared the source. Use the caller's retained procedure. |
raw@1 | Raw bytes, unchanged. |
utf8@1 | The string encoded as UTF-8, without added Unicode normalization. |
base64@1 | Bytes decoded from an exact one-field { "base64": "..." } envelope. |
canonicaljson@2 | Python canonicaljson 2.0 encoding of a structured value. This is not an RFC 8785 claim. |
Use the original processing rule even if another representation looks identical.
Cross-check the RPC result
For higher-stakes evidence, repeat the verification against a second independently operated Base RPC endpoint. You can also open:
https://basescan.org/tx/<txHash>
On BaseScan, check the transaction status, block, and input data. The input must
be 0x followed by a whole number of 64-character digest groups, and the group
at your batchIndex must be your digest. Count from zero: skip
2 × 32 × batchIndex hexadecimal characters after the 0x, then read the next
64. For the ordinary single-digest anchor that is the whole input, unchanged.
An explorer is a convenient view, not an additional consensus source; it can be
unavailable or lag behind the chain.
The CLI makes these direct calls:
eth_chainIdeth_getTransactionByHasheth_getTransactionReceipteth_getBlockByHasheth_blockNumber
It does not send an API key, receipt, digest, document, or other content to Quantum Notary. The selected RPC provider does receive the public transaction and block identifiers in the JSON-RPC queries.
Read the time and finality evidence carefully
- Base block timestamp: returned with the containing L2 block. It records chain time for that block; it is not proof of when an off-chain event occurred.
- Service observation timestamps: receipt fields such as
includedAt,finalizedAt, oranchoredAtmay record when the service observed or persisted a lifecycle transition. This CLI does not validate them. - Base settlement: a successful L2 transaction receipt and additional L2 blocks show Base inclusion and confirmations. Base is an optimistic rollup; these checks do not independently prove Ethereum L1 batch publication, challenge-period completion, or irreversible L1 settlement.
--min-confirmations therefore means Base L2 block confirmations only. It
is useful for detecting a very recent or reorganized L2 view, but it must not be
described as an L1-settlement threshold. The withdrawal challenge period is a separate
concept from transaction finality; see Base’s finality definitions.
Limitations
For documented cases and the surrounding evidence that mattered, see Blockchain evidence in court.
- An RPC endpoint can be wrong, stale, censored, or unavailable. Cross-check independent infrastructure when the evidence matters.
- BaseScan and other explorers can also be unavailable. Preserve transaction and block references so another RPC or archival source can be used later.
- Matching the digest at its named slot proves that digest was placed in that transaction; it does not prove legal admissibility, source-data truth, authorship, identity, ownership, custody or confidentiality.
- A digest is public chain data, not encryption. Candidate data can be hashed and compared, especially when the possible source values are predictable.
- A signed acceptance receipt attests service acceptance, not transaction inclusion. This procedure checks chain evidence.
- A transaction that anchors several digests exposes that those records were submitted around the same time. Nothing else about them is on chain: topics, filenames, tenants, and service identifiers are never written to the transaction.
- Long-term verification depends on retaining the receipt, the original or exact canonical bytes, the hashing/canonicalization description, and access to suitable Base history.
Downloadable verifier and current limitations
The Python package above checks RPC evidence without a verification profile. The separate HTML verifier and its SHA-256 checksum are available for preservation. The currently published production and staging profiles have unbound relayer-epoch coverage, so this HTML tool cannot produce a positive chain-verification verdict. A cannot-check result for that reason is a profile limitation, not evidence that your file or record is wrong. Use the Python package above for the detailed independent RPC check.
Return to Verify a file for browser/service lookup, or try the interactive verification lesson.