Verify a Receipt Without Asqav

A receipt that only its issuer can validate is self-attestation. This page walks a security reviewer through checking Asqav's verification stack on their own machine: the published open-source verifier, the public conformance vectors, and the tamper checks. None of it needs an Asqav account, an API key, or a call to Asqav. Plan on about ten minutes.

The point of the exercise is durability. Receipts conform to an open specification submitted to the IETF (draft-marques-asqav-compliance-receipts), the verifier is MIT-licensed in the public asqav-sdk repository, and the expected verdict for every test vector is pinned in that same repository. Evidence you hold today verifies with or without Asqav.

What you need

Step 1: install the published verifier

pip install "asqav[verify]"

The verify extra pulls two dependencies: dilithium-py, a pure-Python implementation of ML-DSA (NIST FIPS 204) whose verify path uses only standard-library SHAKE, and cryptography for the Ed25519 formats. Nothing compiles. The verifier itself ships inside the package at asqav/verifier/, so you can read every line of what you are about to run.

Step 2: fetch the open conformance corpus

git clone https://github.com/jagmarques/asqav-sdk

The corpus lives at asqav-sdk/verifier/conformance-vectors/: 44 vectors across five receipt formats (asqav-native, ACTA, AERF, AgentReceipts, AuthProof). It includes valid receipts, forged signatures, tampered payloads, and broken chain links. Each vector directory carries the receipt under test, its key material, and an expected.json naming the verdict the verifier must produce.

Step 3: run the whole corpus

ASQAV_CONFORMANCE_VECTORS=asqav-sdk/verifier/conformance-vectors \
python -m asqav.verifier.oracle.runner

The runner prints one line per vector and asserts that the actual verdict matches the expected one:

  [  ok] asqav-01-genesis-permit      expect=PASS  got=PASS
  [  ok] asqav-04-tamper-sig          expect=FAIL  got=FAIL
  ...
  => 44/44 vectors matched expected outcome

Exit code 0 means every vector matched. 23 of the 44 vectors expect FAIL; a verifier that waves everything through cannot pass this corpus.

Step 4: verify one receipt by hand

cd asqav-sdk/verifier/conformance-vectors
python -m asqav.verifier.oracle asqav-01-genesis-permit/receipt.json \
    --keys asqav-01-genesis-permit/jwks.json

The output is a JSON report with a per-axis breakdown (structure, signature, chain) and an overall verdict of PASS. The signature axis is the one that carries third-party trust: the tool reproduces the canonical bytes, resolves the public key from the key file you passed, and runs the signature math locally.

Step 5: prove it rejects tampering

Do not take the FAIL vectors on faith. Open asqav-01-genesis-permit/receipt.json, flip payload.decision from allow to deny, and rerun the command from step 4. The verdict drops to FAIL and the signature axis reports the mismatch, because the bytes you edited are not the bytes that were signed.

The corpus pins the same behaviour with dedicated vectors (asqav-04-tamper-sig, authproof-02-forged-sig, aerf-04-tamper-chain, and others). This step lets you reproduce it with your own hands instead.

What this proves, and what it does not

Running the corpus on your machine proves:

It does not, on its own, verify a receipt from your production account. For that, see independent verification: the asqav audit-pack export bundle ships your receipts in the same canonical envelope, with the verify key and anchor certificate material, so the procedure above scales to your own evidence.

Where everything lives

See independent verification for verifying your own production receipts offline, and the security page for how Asqav holds keys and what the cloud stores.