VDM NexusDocs

Verify a receipt

How a third party can confirm a Nexus signed-inference receipt is real.

A signed-inference receipt is only useful if downstream actors can verify it without trusting the agent that received it. Here's how.

The five checks

A v: 2 receipt can be independently verified along five axes:

  1. Prompt hash matches. sha256(role:content joined by \n) over the messages equals receipt.prompt_hash.
  2. Response hash matches. sha256(response_text) equals receipt.response_hash.
  3. Nexus signature is valid. receipt.nexus_signature is an Ed25519 signature, by the Nexus operator key, over the canonical JSON of the receipt (with nexus_signature stripped).
  4. Payment exists on Solana. receipt.payment.tx_signature is a confirmed transaction with a USDC transfer of receipt.payment.amount_usdc to receipt.payment.pay_to.
  5. Payer matches. The fee-payer / first signer of that transaction equals receipt.agent_pubkey.

If all five pass, the receipt is end-to-end verifiable: the operator signed it, the agent actually paid for it, and the prompt/response you hold are the ones the operator saw.

One call

import { verifyReceipt } from "@vdm-nexus/x402";

const result = await verifyReceipt({
  receipt,
  prompt: originalMessages,
  response: openaiResponse,
  endpoint: "https://nexus.vdmnexus.com",
});

// result: {
//   ok: boolean,
//   checks: {
//     prompt_hash_ok: boolean,
//     response_hash_ok: boolean,
//     nexus_signature_ok: boolean,
//     payment_on_chain_ok: boolean,
//     payer_matches: boolean,
//   },
// }

Pass endpoint and verifyReceipt will fetch the current Nexus operator public key from GET /api/v1/operator-key. If you've pinned the key out-of-band, pass operatorKey (base58) directly and skip the fetch.

The Solana RPC defaults to public devnet/mainnet based on receipt.payment.network; override with rpc if you have a private RPC.

The operator key

curl https://nexus.vdmnexus.com/api/v1/operator-key
# { "pubkey": "...", "algorithm": "ed25519", "encoding": "base58" }

Pin this in your verifier if you want to detect key rotation explicitly rather than implicitly trusting whatever the endpoint currently serves.

Doing it by hand

If you don't want to depend on @vdm-nexus/x402, the checks are simple enough to reproduce:

import { createHash } from "node:crypto";
import nacl from "tweetnacl";
import bs58 from "bs58";

// 1 + 2: hashes
const promptStr = messages.map((m) => `${m.role}:${m.content}`).join("\n");
const promptOk =
  createHash("sha256").update(promptStr).digest("hex") === receipt.prompt_hash;
const respOk =
  createHash("sha256").update(response.choices[0].message.content).digest("hex")
  === receipt.response_hash;

// 3: nexus signature (over canonical JSON with nexus_signature stripped)
const { nexus_signature, ...rest } = receipt;
const canonical = canonicalize(rest); // sorted-key JSON, no whitespace
const sigOk = nacl.sign.detached.verify(
  new TextEncoder().encode(canonical),
  bs58.decode(nexus_signature),
  bs58.decode(operatorPubkey),
);

// 4 + 5: on-chain — fetch the tx, walk pre/postTokenBalances, confirm a
// USDC transfer of receipt.payment.amount_usdc to receipt.payment.pay_to;
// confirm the first signer is receipt.agent_pubkey.

The canonical JSON rule is "sort object keys recursively, no whitespace, primitives via JSON.stringify, arrays preserve order" — the same as JCS for the subset of values we serialize.

On this page