For auditors, lawyers, and forensic accountants who need to verify a sealed archive. No engineering background required.
This guide walks you through verifying that a SALT-sealed accounting archive is authentic and unmodified. It is written for non-engineers — auditors, lawyers, forensic accountants — and uses standard tools available on any modern computer.
The verification answers two questions:
If both answers come back clean, the archive is what it claims to be: a tamper-evident snapshot of accounting data captured at a specific time, signed by an independent authority.
.zip.enc) or unencrypted .zip delivered to youopenssl and sqlite3age: also install age (github.com/FiloSottile/age)macOS: Both tools are pre-installed. Open Terminal (Applications → Utilities → Terminal). No additional installation needed.
Linux: Both tools are pre-installed on most distributions. If not, your package manager has them: sudo apt install openssl sqlite3 on Debian/Ubuntu.
Windows: The simplest path is to install Git for Windows (git-scm.com/downloads), which includes a compatible terminal called Git Bash with both tools available. Open Git Bash to use the commands in this guide.
SALT bundles are typically delivered encrypted with a passphrase the user chose at delivery. Steps 0 and 1 below cover decryption. If your bundle is unencrypted (a .zip file you can unzip directly without a passphrase), skip to Step 2.
Before decrypting, confirm the encrypted bundle hasn't been tampered with in transit or storage. The encrypted bundle is delivered alongside an outer-manifest.json file containing the SHA-256 hash and a TSA timestamp over the encrypted blob.
sha256sum bundle.zip.enc cat outer-manifest.json # Confirm the recorded sha256 matches the value sha256sum just printed. # Verify the outer TSA timestamp signs that hash: ENCHASH=$(sha256sum bundle.zip.enc | cut -d' ' -f1) openssl ts -verify -digest "$ENCHASH" \ -in outer-manifest.tsr \ -CAfile freetsa-cacert.pem
You should see: Verification: OK
Use whichever tool the bundle was encrypted with. The bundle's README.txt records which tool was used.
If encrypted with openssl:
openssl enc -d -aes-256-gcm -pbkdf2 -iter 600000 \ -in bundle.zip.enc -out bundle.zip # You will be prompted for the passphrase.
If encrypted with age:
age -d -o bundle.zip bundle.zip.enc # You will be prompted for the passphrase.
The decrypted file is now a standard .zip bundle. Continue with Step 2.
Move the bundle's .zip file to a folder of your choice and unzip it.
cd /path/to/your/folder unzip qbosnapshot-CompanyName-2026-XX-XX-bundle.zip cd qbosnapshot-CompanyName-2026-XX-XX-bundle
Inside, you should find:
snapshot.db — the accounting databasemanifest.json — file inventory with hashesmanifest.json.sha256 — separate hash recordverify.sh and verify.js — automated verifier scripts (optional convenience)VERIFY.md — quick-reference for the verifierREADME.txt — bundle overviewLIMITATIONS.txt — explicit disclosure of what the seal does and does not claimfreetsa-cacert.pem — public certificate of the Time Stamp AuthorityThe bundle records the database's expected SHA-256 hash inside manifest.json. Compute the actual hash and compare:
sha256sum snapshot.db
Then look at the value recorded in the manifest:
cat manifest.json
Find the entry for snapshot.db and confirm the sha256 value matches what sha256sum just produced.
The bundle includes a cryptographic timestamp from an external Time Stamp Authority (FreeTSA by default). The timestamp signs the database's hash, proving the bundle existed in its current form at a specific moment.
First, extract the embedded timestamp token:
sqlite3 snapshot.db "SELECT tsa_timestamp_token FROM _seal" | base64 -d > token.tsr
Then verify it against the database's hash, using the bundled TSA certificate:
DBHASH=$(sha256sum snapshot.db | cut -d' ' -f1) openssl ts -verify -digest "$DBHASH" -in token.tsr -CAfile freetsa-cacert.pem
You should see: Verification: OK
To see the timestamp itself:
sqlite3 snapshot.db "SELECT tsa_timestamp_at, tsa_authority FROM _seal"
At capture time, SALT runs structural validity checks against the data and records the results. Any reviewer can inspect them:
sqlite3 snapshot.db "SELECT check_name, status, severity, actual_value FROM _integrity_checks"
Look for any rows where status is not pass:
sqlite3 snapshot.db "SELECT check_name, status, severity, actual_value FROM _integrity_checks WHERE status != 'pass'"
Common checks include:
journal_debits_equal_credits.accrual — total debits match total credits on accrual basisjournal_debits_equal_credits.cash — same on cash basistransaction_index_count_matches_typed_tables — unified transaction index covers every transaction recordlinked_txns_targets_resolved — every linked-transaction reference points to a real recordraw_json_sha256_matches_recomputed — random sample of records have matching provenance hashesblocking severity indicate structural problems with the original capture; failed checks at lower severities are informational.
Confirm the archive matches the company and capture you expect:
sqlite3 snapshot.db "SELECT * FROM _company" sqlite3 snapshot.db "SELECT * FROM _capture"
The _company table identifies the source-system company (name, country, EIN if present). The _capture table records when the capture started and completed, what mode it ran in, and how many entities were captured.
For convenience, every SALT bundle includes a verifier that performs the steps above automatically. The bundled scripts handle decryption (if needed) and all integrity checks, producing a pass/fail summary:
./verify.sh # or node verify.js
The manual steps above are equivalent and let you inspect each check individually.
The archive is what it claims to be:
What this does not prove: that the source accounting system itself was authentic and complete at capture time. SALT cannot verify the source; it can only attest to what the source returned. See the threat model for a full description of what is and is not within scope.
The encrypted blob has been modified or replaced since Sealed Ledger delivered it. Do not proceed with decryption — request a fresh copy.
Either the passphrase is incorrect, or the ciphertext was modified. Authenticated encryption (AES-GCM, age) refuses to decrypt corrupted ciphertext rather than producing garbage output, which is the safe behavior. Verify the passphrase first; if it's correct and decryption still fails, treat it as evidence of tampering and request a fresh copy.
The database file inside the unzipped bundle has been modified since the bundle was created. The archive cannot be trusted in its current form. Possibilities:
If a clean copy of the bundle exists elsewhere, retrieve it. If not, the archive's seal is broken and its evidentiary value is compromised.
Possibilities, in order of likelihood:
If Step 3 passed but Step 4 fails, the timestamp record was tampered with after capture. The data may still be the original capture, but the timestamp claim is broken.
Failed integrity checks indicate problems with the data at capture time. They do not indicate post-capture tampering — that's what the seal steps cover. Failed structural checks may indicate:
These are findings to investigate, not verification failures. The seal is still valid; the underlying data has issues that predate the seal.
Once verified, the archive can be queried like any SQLite database. The complete schema is published at /schema.html. A few starting points:
-- See every transaction in chronological order SELECT entity_type, txn_date, doc_number, total_cents FROM transaction_index ORDER BY txn_date; -- Open invoices (unpaid AR) by aging SELECT customer_name, doc_number, balance_cents, days_overdue FROM open_ar_items WHERE balance_cents > 0 ORDER BY days_overdue DESC; -- Search transactions by keyword SELECT * FROM search_transactions WHERE search_transactions MATCH 'check OR refund' LIMIT 50; -- Source-system reports (e.g., Profit & Loss, Balance Sheet) -- as the source platform produced them SELECT report_name, basis, fy_end_year FROM report_snapshots;
For deeper exploration, any SQLite-compatible tool — DB Browser for SQLite, DBeaver, or your preferred tool — can browse the database visually.