# Verify an export bundle {#verify-export-bundle}

An account owner can download a zip of that account's agent events for a date range. The zip holds the events file, a manifest, and, when a signing key is configured, a detached signature. This page is how to check that zip offline. It does not choose a regulator's filing format.

## What is in the zip

- `events.jsonl` or `events.csv`. One account, one time range.
- `manifest.json`. SHA-256 and byte length of each events file, the record count, the time range, the account id, and the export time.
- `manifest.sig`. Present only when the manifest says `"signed": true`. It is the base64 Ed25519 signature over the exact bytes of `manifest.json`.

`manifest.signature.status` is `signed` or `unsigned`. An unsigned bundle has no `manifest.sig`. Do not treat it as signed.

The public key, when one is configured, is published at [/.well-known/cyphrex-export-key](https://cyphrex.io/.well-known/cyphrex-export-key). If that URL says no key is configured, bundles are unsigned.

## Check it

Download [verify_export_bundle.js](/tools/verify_export_bundle.js). It uses Node built-ins only.

```text
curl -fsS -o verify_export_bundle.js https://cyphrex.io/tools/verify_export_bundle.js
curl -fsS -o export-public.pem https://cyphrex.io/.well-known/cyphrex-export-key
node verify_export_bundle.js cyphrex-export.zip --public-key export-public.pem
```

The script prints `SIGNATURE OK` only when the detached signature matches the manifest bytes and the file hashes match the manifest. It prints `UNSIGNED` when the manifest says there is no signature and the file hashes still match. Any other result is a mismatch.

You can point the script at the unzipped directory instead of the zip. The signature covers `manifest.json` as stored. Do not reformat that file before checking it.
