§ proof · verification guide
The Canonical Attestation & Verification Guide
Each Nukez receipt is a gateway-signed, content-addressed JSON record cryptographically bound to the purchasing keypair. A receipt serves as a durable authorization token and as proof of title. External verification is enabled by anchoring receipts on-chain via the Switchboard attestation pipeline.
§ the four levels
The Verification Gates
Each level proves a specific thing. You don't need to do them all — any single one that passes gives you a specific guarantee.
Did the bytes change?
Hash the file you downloaded. Compare to the manifest's content_hash. Match means byte-identical to what was stored.
Did the set of files change?
Rebuild the merkle tree from the manifest entries. Compare to the stored root. Match means no add, remove, modify, or reorder since attestation.
Did the gateway sign it?
Verify the Ed25519 attestation signature over the merkle root using the gateway's public key. Match means the attestation is authentic.
Did Solana see the same root?
Read the Switchboard feed and the SPL Memo on Solana. Compare the on-chain merkle root to the receipt's. Match means the root was committed at a specific slot.
§ level 1 · file integrity
Did the bytes change?
After downloading a file from a Nukez locker, compute its SHA-256 hash locally and compare against the content_hash recorded in the locker manifest. Be clear about where the bytes come from: the verifier holds the receipt (or the verification bundle built from it) and fetches the file through the receipt-scoped endpoints, such as GET /v1/r/{receipt_id}/f/{filename}. This check is permissionless in the sense that no Nukez account is needed — possession of the receipt is what grants read access. It is not anonymous access to arbitrary lockers.
import hashlib, urllib.request
# The verifier holds the receipt (or its verification bundle). Fetch the
# bytes through the receipt-scoped file proxy — no Nukez account needed.
url = f"https://api.nukez.xyz/v1/r/{receipt_id}/f/{filename}"
file_bytes = urllib.request.urlopen(url).read()
# Compute SHA-256
local_hash = "sha256:" + hashlib.sha256(file_bytes).hexdigest()
# Compare against the manifest entry from the verification bundle
assert local_hash == manifest_entry["content_hash"]Match — the file you hold is byte-identical to what was stored. Mismatch — the content has been modified since storage.
§ level 2 · manifest + merkle integrity
Did the set of files change?
The manifest lists every file with filename, size, and content hash. The merkle root is computed deterministically from these entries — any change to any file produces a different root. The normative algorithm and test vectors live in the Merkle V1 spec.
Merkle leaf
leaf = SHA256("{filename}:{size_bytes}:{content_hash}")content_hash is the raw hex digest in the leaf computation (no sha256: prefix).
Merkle tree (bottom-up)
- Sort all leaves alphabetically by filename.
- Compute SHA-256 of each leaf string.
- Pair leaves left-to-right; if odd, the last leaf pairs with itself.
- Parent = SHA-256(left_hex + right_hex) — concatenate the hex strings, then hash.
- Repeat until one root remains.
import hashlib
def build_merkle_root(file_entries):
"""
file_entries: list of dicts with 'filename', 'size_bytes', 'content_hash'
content_hash values should be raw hex (no 'sha256:' prefix)
"""
if not file_entries:
raise ValueError("empty file lists are invalid for Nukez attestations")
sorted_entries = sorted(file_entries, key=lambda e: e["filename"])
# Compute leaf hashes
leaves = []
for entry in sorted_entries:
raw_hash = entry["content_hash"].replace("sha256:", "")
leaf_data = f"{entry['filename']}:{entry['size_bytes']}:{raw_hash}"
leaf_hash = hashlib.sha256(leaf_data.encode("utf-8")).hexdigest()
leaves.append(leaf_hash)
# Build tree bottom-up
level = leaves
while len(level) > 1:
next_level = []
for i in range(0, len(level), 2):
left = level[i]
right = level[i + 1] if i + 1 < len(level) else level[i]
combined = hashlib.sha256((left + right).encode("utf-8")).hexdigest()
next_level.append(combined)
level = next_level
return level[0]Compare your computed root against the merkle_root in the attestation. Match means no file has been added, removed, modified, or reordered since attestation.
Skip the loop: recompute-verify
Rather than rebuilding the tree yourself, you can ask the gateway to do it. This is a convenience, not a trust dependency — you can always recompute locally. The endpoint requires a payer-signed envelope with the locker:read operation, because it makes the gateway re-download file bytes from storage; byte retrieval is only ever triggered by a request the payer keypair (or an authorized operator) signed. The recompute itself is strictly read-only: it happens in memory and never overwrites the stored attestation. In PyNukez, client.recompute_verify(receipt_id) builds and signs the envelope for you.
recompute-verify is different from /v1/storage/verify. Plain /verify is a cheap, cached snapshot: it trusts the manifest's recorded content hashes and reports the last attested Merkle root. The recompute-verify endpoint does real byte-level work — it re-downloads every file from storage, re-hashes the bytes, rebuilds the tree, and compares to the anchored root. Use /verify for quick status checks; reach for recompute-verify when you need to prove the actual stored bytes still match what was attested. Cost scales with locker size — that's the intended trade-off.
# Signed request — ops ["locker:read"], envelope bound to this GET,
# its query string, and the empty-body hash; the envelope receipt_id
# must match the query receipt_id.
GET /v1/storage/recompute-verify?receipt_id={receipt_id}
X-Nukez-Envelope: <base64url canonical envelope>
X-Nukez-Signature: <signature over the envelope bytes>
# Or, in PyNukez (signs internally):
# result = client.recompute_verify(receipt_id)
# Response
{
"match": true,
"receipt_id": "8239fc15efc46042",
"locker_id": "locker_62e358731c6f",
"computed": "sha256:6a0c80f5c0a3...",
"stored": "sha256:6a0c80f5c0a3...",
"file_count": 1,
"recompute_ms": 1040
}If match is false, either the locker contents changed since attestation, or the stored attestation was tampered with. Either way — you know.
§ level 3 · attestation signature
Did the gateway sign it?
Two Ed25519 signatures cover the proof objects, and both verify against the same key. The attestation carries manifest_signature, signed over the bare merkle root (the 64-character hex digest without its sha256: prefix, UTF-8 encoded). The receipt carries receipt_sig, signed over the canonical JSON of the receipt plus its manifest. The verifying key is receipt_signer_pubkey, returned by GET /v1/receipts/{receipt_id}. All of these are hex-encoded: the public key is 64 hex characters and each signature is 128 hex characters.
from nacl.signing import VerifyKey
import binascii
attestation = get_attestation(receipt_id)
# receipt_signer_pubkey comes from GET /v1/receipts/{receipt_id} —
# hex, 64 characters. A robust verifier PINS this key out of band
# (see the caveat below) instead of trusting the fetched copy.
pubkey = pinned_receipt_signer_pubkey
sig = attestation["manifest_signature"] # hex, 128 characters
# The signed payload is the bare merkle_root hex string, UTF-8 encoded.
signed_payload = attestation["merkle_root"].removeprefix("sha256:").encode("utf-8")
vk = VerifyKey(binascii.unhexlify(pubkey))
vk.verify(signed_payload, binascii.unhexlify(sig)) # raises if forgedCombined with Level 2 — which binds the manifest to the merkle root — this transitively binds the manifest to the signer. Any modification to the manifest produces a different root, and the signature no longer covers it.
The key-pinning caveat. A signature check is only as independent as your copy of the public key. If you fetch receipt_signer_pubkey from the same gateway response you are verifying, a tampering gateway could serve a forged object together with a key that matches it. A robust verifier therefore pins the receipt signer key out of band — recorded once from a trusted exchange and reused thereafter. Do not substitute the key from /v1/short-url/verify-key: that endpoint serves a different key (the short-URL signer), and it will not verify receipt or attestation signatures. The on-chain anchor in Level 4 is the complementary defense, because a forged root will not match the root committed on Solana.
§ level 4 · on-chain attestation
Did Solana see the same root?
Nukez anchors attestations on Solana through two complementary mechanisms. Either is sufficient on its own; together they give you both a numerical comparison and a permanent log entry.
Switchboard oracle feed
The attestation code (att_code) is pushed to a Switchboard PullFeed account on Solana. It is an integer of at most nine digits, derived from the result_hash (never from merkle_root) — so anyone with the verification bundle can recompute it and compare.
def att_code_from_hash(result_hash):
"""First 12 hex chars (48 bits) as int, clamped to 9 digits."""
h = result_hash.removeprefix("sha256:")
return int(h[:12], 16) % 1_000_000_000The oracle's Ed25519 signature over the quote is verified by Solana's Ed25519 precompile — no trust in the oracle itself is needed.
SPL Memo
The full attestation metadata is written as an SPL Memo in the same transaction — permanently readable in the Solana transaction log via any block explorer.
{
"schema": "nukez/attestation/v1",
"receipt_id": "8239fc15efc46042",
"merkle_root": "sha256:abc123...",
"file_count": 42,
"attested_at": "2026-02-11T..."
}Verifying on-chain
- Get the
tx_signaturefrom the attestation response (or from the receipt'sswitchboard.txfield). - Look it up:
https://explorer.solana.com/tx/{tx_signature}. - Find the “Program Log: Memo” entry.
- Confirm the memo contains the expected
receipt_idandmerkle_root.
§ tamper evidence
Every attack vector. Detected.
Every modification produces a different merkle root. There is no way to modify content and preserve the root — the math does not allow it.
| Attack | What changes | Detection |
|---|---|---|
| Modify a file's content | Content hash changes → leaf changes → root changes | Root mismatch vs. on-chain attestation |
| Delete a file | Leaf count drops → tree structure changes | Root mismatch |
| Add a file | Leaf count increases → new root | Root mismatch |
| Swap a file (same name, different content) | Content hash changes → leaf changes | Root mismatch |
| Reorder files | Leaves are sorted by filename — order is canonical | Not possible by construction |
| Forge the manifest | Merkle root changes; receipt signature covers the root | Root mismatch or signature verification fails |
§ extending the chain
Verification further down.
No shared keys. No shared accounts.
Agent A stores data and gets a receipt_id. Agent A passes it to Agent B. Agent B verifies locally using only the receipt ID: it fetches the verification bundle, downloads the bytes through the receipt-scoped endpoints, and recomputes the hashes and the root. Agent B now has independent cryptographic proof of what Agent A stored, when, and that it hasn't been tampered with. The server-side recompute-verify audit is separate — it requires an envelope signed by the payer keypair or an authorized operator, so it is a tool for the locker owner rather than for an arbitrary receipt holder.
Verifying the verifier.
The gateway hashes its own running source against an on-chain attestation. If the gateway's code has been modified after deployment, the verification fails.
GET /v1/self-verify
Supported signatures
Both algorithms produce the same receipt structure, the same verification path, and the same attestation chain. Neither is primary — both are first-class.
| Algorithm | Chain | Key format | Use case |
|---|---|---|---|
| Ed25519 | Solana | Base58 public key | Solana-native wallets and agents |
| secp256k1 | EVM / Monad | Ethereum address (0x…) | EVM wallets, Monad chain payments |
§ cheat sheet
Five questions. Five answers.
| Question | How to check | What it proves |
|---|---|---|
| Is my downloaded file authentic? | SHA-256 locally; compare to manifest content_hash | File is byte-identical to the stored version |
| Is the manifest intact? | Rebuild merkle tree from file entries; compare to merkle_root | No files added, removed, or modified since attestation |
| Was the receipt forged? | Ed25519 verification against receipt_signer_pubkey (hex, 64 chars) — pinned out of band | Receipt was produced by the holder of the pinned signer key, unaltered |
| Is the attestation on-chain? | Look up tx_signature on Solana Explorer; read SPL Memo | Merkle root was committed to Solana at a specific slot |
| Can I verify without trusting Nukez? | Yes, with one caveat: pin receipt_signer_pubkey out of band. The hash and merkle math use public data and standard cryptography; the on-chain root cross-check catches a forged bundle even without the pin | The entire point — provided the signer key is pinned |
§ next
Verify a real receipt.
Paste a receipt ID into the public verifier to inspect the proof checks in one place — or jump to the integration docs to wire verification into your stack.
