Nukez

§ 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.

4 levels · independentSHA-256 · Ed25519 · secp256k1No account required

§ 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 forged

Combined 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_000

The 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

  1. Get the tx_signature from the attestation response (or from the receipt's switchboard.tx field).
  2. Look it up: https://explorer.solana.com/tx/{tx_signature}.
  3. Find the “Program Log: Memo” entry.
  4. Confirm the memo contains the expected receipt_id and merkle_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.

AttackWhat changesDetection
Modify a file's contentContent hash changes → leaf changes → root changesRoot mismatch vs. on-chain attestation
Delete a fileLeaf count drops → tree structure changesRoot mismatch
Add a fileLeaf count increases → new rootRoot mismatch
Swap a file (same name, different content)Content hash changes → leaf changesRoot mismatch
Reorder filesLeaves are sorted by filename — order is canonicalNot possible by construction
Forge the manifestMerkle root changes; receipt signature covers the rootRoot mismatch or signature verification fails

§ extending the chain

Verification further down.

§ cross-agent

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.

§ gateway self-verify

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.

AlgorithmChainKey formatUse case
Ed25519SolanaBase58 public keySolana-native wallets and agents
secp256k1EVM / MonadEthereum address (0x…)EVM wallets, Monad chain payments

§ cheat sheet

Five questions. Five answers.

QuestionHow to checkWhat it proves
Is my downloaded file authentic?SHA-256 locally; compare to manifest content_hashFile is byte-identical to the stored version
Is the manifest intact?Rebuild merkle tree from file entries; compare to merkle_rootNo files added, removed, or modified since attestation
Was the receipt forged?Ed25519 verification against receipt_signer_pubkey (hex, 64 chars) — pinned out of bandReceipt 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 MemoMerkle 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 pinThe 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.