A verification sidecar for ar.io gateways. Runs alongside your gateway and independently proves that data served from Arweave is authentic, untampered, and attributable to a specific owner.
When a user or application asks "is this data real?", the sidecar downloads the raw data from the gateway, reconstructs the cryptographic proof from scratch, and returns a verification result. If the gateway operator has configured a signing wallet, the result is also signed as an attestation — a cryptographic statement from the operator that they personally verified the data.
The verification UI is built into the ar.io Console at /verify, where users can paste a transaction ID and see the full verification report with a downloadable PDF certificate. The sidecar also ships its own standalone web UI at /verify/ and a REST API for programmatic access.
Verification happens in three stages. Each stage builds on the previous one, and the result is assigned a level based on the strongest proof achieved.
The sidecar confirms the transaction exists on the Arweave blockweave by querying the gateway's GraphQL index and checking /raw/ headers. If the transaction is found in a confirmed block, we know the data was accepted by the network at a specific block height and timestamp.
What this proves: the data exists on Arweave and was mined into a block.
The sidecar downloads the full raw data from the gateway and computes its SHA-256 hash independently. This fingerprint can be compared against the gateway's reported digest to confirm consistency.
What this proves: the data the gateway served has a known fingerprint. Note that this stage alone does not anchor the hash to anything on-chain — it confirms the gateway served consistent bytes, but the real integrity guarantee comes from Stage 3.
The sidecar reconstructs the exact message that the original signer signed (the "deep hash") from the transaction's constituent parts — owner, target, tags, and data. It then verifies the cryptographic signature against that message using the appropriate algorithm:
- RSA-PSS (type 1) — Arweave native wallets
- ED25519 (type 2) — Solana wallets
- ECDSA secp256k1 (type 3) — Ethereum wallets
For bundled data items (ANS-104), the sidecar fetches the binary header from the root bundle via a range request to get the exact original tag bytes, since re-encoded tags from GraphQL can differ from the signed original.
What this proves: the stated owner cryptographically signed this exact data. The data is authentic and has not been modified since signing. This is a mathematical proof, not a trust claim.
| Level | Name | What it proves |
|---|---|---|
| 3 | Verified | Digital signature confirmed — data is authentic and untampered |
| 2 | Partially Verified | Data fingerprint confirmed, signature could not be checked |
| 1 | Existence Confirmed | Data found on the network, full verification not yet possible |
When a gateway operator configures their Arweave wallet (WALLET_FILE), the sidecar signs the verification result with the operator's private key. This creates an attestation — a statement from a known operator on the ar.io network that they independently verified the data. Anyone can check this attestation by verifying the RSA-PSS signature against the operator's public key.
Attestation data is included in the JSON API response, the PDF certificate, and is available via a dedicated /attestation endpoint for programmatic verification.
The primary way most users interact with verification is through the ar.io Console, which embeds the verify feature at /verify. The Console points to this sidecar's API as its backend — when a user verifies a transaction in the Console, the request flows to this service.
The sidecar also ships its own standalone React web UI (served at /verify/ on the sidecar's port) for direct access without the Console. Both UIs use the same API.
git clone https://github.com/ar-io/ar-io-verify.git
cd ar-io-verify
pnpm install
pnpm run devRuns as a sidecar alongside an ar.io gateway on the same Docker network (ar-io-network).
cd deploy
cp .env.example .env # edit GATEWAY_URL, WALLET_FILE, GATEWAY_HOST
bash start.shThe table below covers the Docker Compose deployment (deploy/.env). If you're running the server directly without Docker, use SIGNING_KEY_PATH instead of WALLET_FILE (Compose maps one to the other) and PORT instead of VERIFY_PORT (the internal sidecar always listens on PORT; VERIFY_PORT just controls the public nginx port).
| Variable | Default | Description |
|---|---|---|
GATEWAY_URL |
http://ar-io-node-envoy-1:3000 |
ar.io gateway URL (must be reachable from the container) |
GATEWAY_HOST |
(empty) | Gateway hostname included in attestation payloads (e.g. vilenarios.com) |
PUBLIC_GATEWAY_URL |
(empty) | Browser-reachable gateway URL for image previews. Falls back to https://${GATEWAY_HOST}, then https://turbo-gateway.com |
WALLET_FILE |
(empty) | Host path to Arweave JWK wallet for signing attestations |
VERIFY_PORT |
4001 |
Public port for the verify UI and API |
GATEWAY_TIMEOUT_MS |
10000 |
Gateway request timeout |
LOG_LEVEL |
info |
Log level: debug, info, warn, error |
To expose via your domain:
location /local/verify/ {
proxy_pass http://your-server:4001/verify/;
proxy_set_header Host $host;
proxy_read_timeout 120s;
}Interactive docs: /api-docs/
| Method | Path | Description |
|---|---|---|
POST |
/api/v1/verify |
Verify a transaction |
GET |
/api/v1/verify/:id |
Get cached result |
GET |
/api/v1/verify/tx/:txId |
Get history for a transaction |
GET |
/api/v1/verify/:id/pdf |
Download PDF certificate |
GET |
/api/v1/verify/:id/attestation |
Get attestation for programmatic verification |
GET |
/api/config |
Runtime frontend config (public gateway URL) |
GET |
/health |
Health check |
GET |
/api-docs/ |
Swagger UI |
Results are cached in SQLite, so repeated lookups for the same transaction return instantly.
packages/
server/ Express API — verification pipeline, PDF certificates, SQLite cache
web/ React frontend — Vite, Tailwind CSS, served at /verify/
deploy/ Docker Compose + nginx reverse proxy
1. HEAD /raw/{txId} + GraphQL — parallel (~50ms)
2. Determine L1 tx vs data item
3. Download raw data + binary header fetch (range request)
4. Deep hash reconstruction → signature verify (RSA-PSS / ED25519 / ECDSA)
5. Operator attestation (if wallet configured)
When WALLET_FILE is set, the operator's wallet signs a canonical attestation payload:
{
"version": 1,
"txId": "...",
"dataHash": "...",
"signatureVerified": true,
"ownerAddress": "...",
"blockHeight": 888672,
"operator": "7p90oVDW...",
"gateway": "vilenarios.com",
"attestedAt": "2026-04-08T..."
}To verify: pass the canonical JSON (keys sorted alphabetically, no whitespace) to a standard RSA-PSS SHA-256 verifier with the operator's public key.
pnpm run testAGPL-3.0