What this guide does

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:

  1. Is this archive unchanged since it was sealed?
  2. When was it sealed, and by whom?

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.

Before you start

What you need

  • The SALT bundle: a .zip file delivered to you
  • A computer running macOS, Linux, or Windows
  • Two free tools: openssl and sqlite3

Installing the tools

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.

What you don't need

  • You do not need any Sealed Ledger software, account, or service
  • You do not need an internet connection (verification is offline)
  • You do not need the original source-system credentials

Step-by-step verification

Step 1 of 5

Unzip the bundle

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 database
  • manifest.json — file inventory with hashes
  • manifest.json.sha256 — separate hash record
  • verify.sh and verify.js — automated verifier scripts (optional convenience)
  • VERIFY.md — quick-reference for the verifier
  • README.txt — bundle overview
  • LIMITATIONS.txt — explicit disclosure of what the seal does and does not claim
  • freetsa-cacert.pem — public certificate of the Time Stamp Authority
Step 2 of 5

Verify the database file's hash

The 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.

What this proves: The database file has not been modified since the bundle was created. Any change — even a single byte — would produce a different hash.
Step 3 of 5

Verify the time-stamp signature

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.

The TSA token is stored in manifest.json at captureSeal.tsa.tokenBase64. (It cannot be stored inside snapshot.db because the TSA signs the database's SHA-256, and any modification to the database — including adding the token — would break the signature.) Extract the token to a file:

python3 -c "import json; print(json.load(open('manifest.json'))['captureSeal']['tsa']['tokenBase64'])" \
  | base64 -d > token.tsr

If you don't have Python handy, the same value can be extracted with jq, grep, or any JSON tool:

jq -r '.captureSeal.tsa.tokenBase64' manifest.json | base64 -d > token.tsr

Then verify the token 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 and the TSA's identity:

python3 -c "import json; m = json.load(open('manifest.json')); print(m['captureSeal']['tsa']['tsaTime'], m['captureSeal']['tsa']['tsaName'])"
What this proves: The bundle existed in this exact form at the recorded timestamp, and the timestamp was issued by the named Time Stamp Authority — not by Sealed Ledger. The TSA is an independent third party.
Step 4 of 5

Review the structural integrity checks

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 basis
  • journal_debits_equal_credits.cash — same on cash basis
  • transaction_index_count_matches_typed_tables — unified transaction index covers every transaction record
  • linked_txns_targets_resolved — every linked-transaction reference points to a real record
  • raw_json_sha256_matches_recomputed — random sample of records have matching provenance hashes
What this proves: The data inside the archive is internally consistent. Failed checks at blocking severity indicate structural problems with the original capture; failed checks at lower severities are informational.
Step 5 of 5

Identify the archive

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.

What this proves: This archive is the one you expected — for the right company, captured at the right time.

Verification using the bundled scripts (alternative)

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.

How to interpret the results

If all steps pass

The archive is what it claims to be:

  • The encrypted blob (if applicable) is the one Sealed Ledger delivered
  • The bundle is unchanged since the timestamp recorded in the inner seal
  • An independent Time Stamp Authority — not Sealed Ledger — issued the seal
  • The data is internally consistent
  • The company identification matches

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.

If Step 2 (database hash) fails

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:

  • Accidental modification (a tool opened the file and changed metadata)
  • File corruption during transfer or storage
  • Deliberate tampering

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.

If Step 3 (TSA verification) fails

Possibilities, in order of likelihood:

  • The database hash from Step 2 doesn't match what the TSA originally signed (the bundle was modified)
  • The TSA certificate is missing or wrong
  • The token itself was modified

If Step 2 passed but Step 3 fails, the timestamp record was tampered with after capture. The data may still be the original capture, but the timestamp claim is broken.

If Step 4 (integrity checks) shows failures

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:

  • The source accounting file has its own integrity problems (out-of-balance journals, dangling references)
  • The capture itself encountered errors that were recorded but not blocking
  • The source platform returned incomplete data

These are findings to investigate, not verification failures. The seal is still valid; the underlying data has issues that predate the seal.

Querying the archive

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.

This guide is published as a draft and may be revised. A printable PDF version will be made available alongside future revisions.