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 each attestation on Solana. Current anchors are published by the gateway's own key. Historical anchors were published through the Switchboard attestation pipeline, and they keep their original meaning.

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

Records anchored today also carry a version-two evidence envelope. The gateway hashes the canonical JSON of envelope.body (RFC 8785) with SHA-256, and the result is the statement digest. The signature covers the ASCII bytes NUKEZ_CONTENT_ENVELOPE_V2, then one zero byte, then the 32 bytes of the statement digest. The verifying key is the same gateway key, and the gateway lists it at GET /v1/keys. Pin it out of band, as the caveat below explains.

import hashlib

# The envelope comes from GET /v1/storage/evidence-bundle?receipt_id=...
envelope = bundle["envelope"]

# canonicalize() is RFC 8785 canonical JSON. The envelope uses no floats.
digest = hashlib.sha256(canonicalize(envelope["body"])).digest()
assert digest.hex() == envelope["statement_digest"]

message = b"NUKEZ_CONTENT_ENVELOPE_V2\x00" + digest
vk.verify(message, binascii.unhexlify(envelope["signature"]))  # 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 every attestation on Solana. How the anchor was written depends on when the record was anchored, and every proof response names the kind in anchor.type. Current anchors have the type solana_sas_v1 or solana_memo_v2. Historical anchors have the type switchboard_v2. They were never rewritten, and they keep their original meaning.

Current anchors: a signed memo and an attestation account

The gateway first signs the version-two evidence envelope described in Level 3. It then sends one Solana transaction, signed by its publication key, which the response reports as anchor.signer. The transaction carries an SPL Memo with a compact statement: the public proof identifier, the snapshot identifier, the statement digest, and the Merkle root. The memo never carries the receipt ID, a filename, or any access handle.

{
    "s": "nukez/anchor/v2",
    "pid": "<proof_id, 32 hex characters>",
    "sid": "<snapshot_id, 64 hex characters>",
    "dg": "<statement digest, 64 hex characters>",
    "root": "<merkle root, 64 hex characters, no sha256: prefix>",
    "n": <file count>,
    "seq": <sequence number of this statement>,
    "t": <issued_at as unix seconds>,
    "k": "ed25519:<first 16 hex characters of the signing key>"
}

When the anchor type is solana_sas_v1, the same transaction also writes a Solana Attestation Service account for the receipt. The response reports its address as anchor.sas.attestation. The account holds the receipt's current commitment: the proof identifier, the snapshot identifier, the statement digest, and the Merkle root. In the default mode a later snapshot replaces the account, so the memo transactions remain the permanent history.

What this anchor proves: the gateway's published key committed this exact snapshot on Solana at this slot, and, with the attestation account, the current commitment is readable on-chain at a predictable address. It does not prove that a third party inspected the files.

Historical anchors: a Switchboard feed and a memo

Records with the anchor type switchboard_v2 were anchored through two complementary mechanisms, described in the next two sections exactly as they were published. 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 transaction signature from anchor.tx in the verify response. Use the legacy switchboard_tx field only when the response has no anchor object.
  2. Look it up: https://explorer.solana.com/tx/{tx_signature}.
  3. Find the “Program Log: Memo” entry.
  4. For a current anchor, confirm that the memo's dg and root values equal the statement digest and the Merkle root that you verified. For a historical anchor, confirm that the memo contains the expected receipt_id and merkle_root.
  5. For a solana_sas_v1 anchor, open anchor.sas.attestation in the explorer and compare the statement digest, the snapshot identifier, and the Merkle root held in that account.

The evidence bundle and nukez-verify

GET /v1/storage/evidence-bundle returns everything an independent verifier needs in one document: the file list, the commitment, the signed envelope, the anchor, the separate verification facts, and the gateway's signing keys. It needs no authentication. Save the bundle and check it whenever you like with the nukez-verify command. The command reports each fact separately as passed, failed, not_checked, or unavailable, and it never folds them into a single verified flag.

curl -sS "https://api.nukez.xyz/v1/storage/evidence-bundle?receipt_id=$RECEIPT_ID" > bundle.json

# Offline checks: the Merkle root, the statement digest, and the signature.
# Pin the Nukez signing key yourself. GET /v1/keys lists the current keys.
nukez-verify bundle.json --pin <ed25519 public key hex>

# Add your own copies of the files and a Solana RPC endpoint that you choose.
nukez-verify bundle.json --pin <ed25519 public key hex> --files ./my-files --rpc <your rpc url>

A key list fetched from the same gateway that produced the bundle proves self-consistency only, and the command says so when you give it no pinned key. The on-chain checks are online by nature, because a saved transaction alone does not prove finality.

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