Public verification

Unlock your backup with your own hands

The decryption procedure on this page is not a black box: the same crypto primitives (key wrapping, Argon2id/PBKDF2 derivation, AES-GCM) are open source at github.com/tacet-ink/journal-core (MIT), and this page's own source is fully readable. Inspect it line by line, or run it yourself.

Paste the encrypted backup exported from "Backup and take it with you" in the app into the box below, and enter that passphrase. If your backup thus shows its text, it proves: the key to this data is decided by the passphrase alone, and the whole process needs none of us.

Network requests made by this page: 0. All computation happens in your browser.

What is being verified

This page replays the decryption procedure of the encrypted backup itself (a key derived by PBKDF2 with 600,000 iterations, AES-GCM opening the package and the text). The code lives in this page's source and in the explanation below; review it before you use it. A successful decryption proves two things:

  1. Your passphrase really is the only key that opens this data, and it works on your device without any server.
  2. The jr1b.-prefixed text stored in the server's storage layer (legacy jr1. and the pure-local era's jr1u. are equivalent) is just noise without your passphrase.

Proof of zero network

This page's counter intercepts every request the browser makes. No passphrase upload, no telemetry, no external scripts; you can re-check it in your developer tools' network panel, or open this page offline and run it there with the same result.

Format notes

An encrypted backup file contains: each entry's ciphertext (jr1b. prefix, older files jr1. / jr1u., AES-GCM with AAD bound to the note id), a salt, and the key wrapped with your passphrase (jr1w. prefix). The v2 format also carries image attachments: each image's ciphertext has a jr1c. prefix with AAD bound to the note and attachment ids (jr1a:), encrypted with the same key as the text; once decrypted, images render locally in your browser as blob: URLs, still with zero network. Decryption order: passphrase and salt go through PBKDF2 (600,000 rounds, SHA-256) to derive the key-wrapping key (KEK), the package is opened to get noteKey, then noteKey opens every entry and every image. The prefixes are a version contract: when KDF parameters are upgraded in the future, a new prefix appears, and old files stay decodable under the old rules.

One more clarification: the backup export format has been jr1w. (PBKDF2) since its first version; this page honors that same contract, so old backups stay decryptable forever. The app's "account login package" was upgraded to memory-hard Argon2id in 2026-09 (older accounts upgrade automatically at their next login); that is independent of the backup format, and both coexist. See the threat model for details.

The same code runs in the app itself (React) and on this page (plain JavaScript); that the verification environment and the app environment behave identically is exactly the point of this page.

The limits of this method

It verifies that "ciphertext and package open when the passphrase is right and do not open when it is wrong", which is the unreadability of the storage layer. It cannot prove the server will never serve tampered code (see the honest limit in the threat model), and it will not remember your passphrase for you. If you enter a wrong passphrase, this page shows "cannot decrypt", with no retry hints beyond that. The next layer of evidence, covering deployment, is the reproducible build section below.

Why reproducible builds

The honest limit of the threat model says this: if the operator were malicious, they could in theory serve tampered code (poisoned JavaScript) to harvest keys. The mitigation back then was "the encryption layer's code is public; this mitigates but cannot eliminate it". A reproducible build turns that mitigation into hard evidence: not only is the code public, the code actually running online can be rebuilt from source by any third party. When the rebuilt output matches the live files byte for byte, the online files are the ones built from that source: if the operator tried to sneak in anything at deploy time, changing a single byte, the comparison fails. It is still a mitigation, not a cure (limits at the end of this section), but it swaps "do you trust the operator's conscience" for "do you trust the hash you can produce yourself".

The current online revision

Current production main bundle index-D3DGhNVl.js That file's SHA-256 hash f71ac791c36a3eaea791b2cd4e8112b328b35baa436b86b0848966819c119dca

These two lines are stamped in automatically by the deployment pipeline at build and deploy time, never copied by hand. What your browser shows when it loads this page is the main bundle filename you are downloading and its SHA-256; compare against them after rebuilding by hand. The revision stamp has the shape v0.1.0+<7-character git revision> (with .d appended when the working tree has uncommitted changes), generated from git by the build pipeline and baked into the program.

Steps to reproduce it yourself

The steps below condense REPRODUCING.md (repository root) into an operable checklist. Verified in practice: the same revision, the same lockfile, a fresh build in a different directory, and the output matches the live files byte for byte. Required environment and versions: node 26.8.1, npm 11.19.0 (engines pinned exactly, .nvmrc in sync); dependencies are fully locked by package-lock.json, installed with npm ci (do not use npm install, which floats within ^ ranges).

  1. A single clone is enough: the crypto core is installed along with the dependencies by npm ci (package @tacet-ink/journal-core; the version is pinned by package-lock.json). No special directory layout is required. The core is open source (MIT): github.com/tacet-ink/journal-core.
  2. In the tacet directory: nvm use (reads .nvmrc for 26.8.1).
  3. npm ci: restore dependencies strictly per package-lock.json.
  4. npm run build.
  5. Compare hashes: compute the sha256 of dist/assets/index-*.js; then fetch the live main bundle (the filename in "The current online revision" above) and compare byte for byte. The two SHA-256 strings being equal means they match.
mkdir tacet-build && cd tacet-build
git clone <tacet-repo-url> tacet
git clone https://github.com/tacet-ink/journal-core.git core
cd tacet
nvm use
npm ci
npm run build
find dist -type f | sort | xargs sha256sum > dist.sha256
# Compare with the filename and SHA-256 in "The current online revision" above

Honest limits (reproducible build)

Honest disclosure

This mechanism mitigates; it does not eliminate. You still need to trust the steps and numbers written on this page: this page itself, and its deployment, come from the operator's servers too; a malicious operator could in theory display fake hashes on the page. A recursion limit exists: thorough third-party verification cannot stop at reading this page; it means running the steps yourself once and comparing your own computed hash against the live files. Only after an independent third-party replay does this trust land. Also, the tacet application-layer source is not yet public (the tacet-ink/journal-core crypto core is open source; this very page replays the decryption of encrypted backups locally in the browser). Reproduction across node/vite major versions and operating systems is unverified; the current baseline is Linux x64.

Start writing