Offline and Air-Gapped Verification
Some environments cannot reach the internet during a verification run. A regulated enclave, a forensic workstation, or a customer-controlled audit system may need to verify Asqav receipts with no outbound network access at all. This page covers how to do that.
The short version: snapshot the public key directory once while you are online, then all subsequent verification runs are local. ML-DSA-65 receipts verify offline the same way as any other receipt.
What you need to snapshot
Asqav publishes two things that a verifier needs. Fetch them once from a network-connected machine and store them alongside your receipts:
curl https://api.asqav.com/.well-known/jwks.json > jwks.json
That file contains the public signing keys. It is the only thing that requires a network call. A key does not rotate on a per-receipt basis, so a snapshot taken at deployment time stays valid for the full retention window of receipts signed under that key. Rotate the snapshot whenever you rotate keys.
The verifier source is the second thing to snapshot. It lives in the public asqav-sdk repository. The standalone verify_receipt.py file is licensed Apache-2.0; the rest of the repository is source-available under the Elastic License 2.0:
git clone https://github.com/jagmarques/asqav-sdk
# or download just the verifier file:
curl -O https://raw.githubusercontent.com/jagmarques/asqav-sdk/main/python/src/asqav/verifier/verify_receipt.py
Store both alongside your evidence pack. The verifier is a single Python file. Read it before trusting it.
Install the verify extra
The asqav[verify] extra pulls the two dependencies the verifier needs:
pip install "asqav[verify]"
That installs dilithium-py (pure-Python ML-DSA-65 / NIST FIPS 204, verify path uses only standard-library SHAKE) and cryptography (for Ed25519 formats). Nothing compiles. In an air-gapped environment, pre-download the wheels on a connected machine and transfer them:
# on a connected machine
pip download "asqav[verify]" -d ./wheels
# copy ./wheels/ to the air-gapped machine, then:
pip install --no-index --find-links ./wheels "asqav[verify]"
Verifying a receipt offline
Once you have the jwks.json snapshot and the verifier installed, pass --offline to prevent any network call:
python verify_receipt.py \
--receipt receipt.json \
--jwks jwks.json \
--offline
The --offline flag makes the tool refuse to reach out. If a key is missing from the local jwks.json, the issuer-key axis fails rather than fetching from the network. To verify the hash-chain link between two consecutive receipts, pass the predecessor:
python verify_receipt.py \
--receipt receipt.json \
--jwks jwks.json \
--predecessor previous.json \
--offline
Exit codes: 0 is PASS, 1 is FAIL (at least one axis failed), 2 is INCOMPLETE (a blocking axis was skipped, for example the post-quantum dependency is absent). The tool never prints PASS without verifying the signature.
ML-DSA-65 receipts verify offline
ML-DSA-65 (post-quantum, NIST FIPS 204) receipts follow exactly the same offline path as Ed25519 receipts. The algorithm identifier rides inside the signed envelope ("alg": "ML-DSA-65"), so the verifier reads which algorithm to use from the receipt itself. dilithium-py handles the ML-DSA-65 signature check without any native library. Nothing about offline verification changes when the algorithm is post-quantum.
Audit packs in air-gapped environments
For a batch of receipts, use the audit-pack verify command against a locally-exported bundle:
# export on a connected machine
asqav audit-pack export --start 2026-01-01 --end 2026-04-01 > q1.tar.gz
# verify on an air-gapped machine with the snapshotted key file
asqav audit-pack verify --pack q1.tar.gz --verify-key jwks.json
The tarball carries the receipts, the public verify key, anchor certificate material, and the format reference. No network call is needed for the verify step when you pass --verify-key pointing at the local snapshot.
Key rotation and snapshot freshness
A key snapshot stays valid for receipts signed under that key. When a key rotates:
- Fetch the new jwks.json from
/.well-known/jwks.json. - Update your snapshot.
- Receipts signed under the old key still verify against the old public key material, which should be retained in your evidence store.
The verify_key_id field in each receipt envelope names which key signed it. Your snapshot can hold multiple keys; the verifier resolves by key id.
Further reading
- Independent Verification - verifying a single receipt without an Asqav account
- Verify Without Asqav - running the open conformance corpus end to end
- Post-quantum Signed Receipts - ML-DSA-65 algorithm and threat model
- Key Custody and Disclosure - how Asqav holds keys