Encrypted in your browser
AES-256-GCM with a key derived from your vault passphrase via scrypt. The plaintext never leaves your device — not to the server, not to a CDN, not to logs.
Starting…
Client-side encrypted — zero-knowledge by design
StealthLedger is a portfolio tracker that never sees your portfolio. Your holdings are sealed with AES-256-GCM in your browser; the server stores an opaque blob it has no key to open.
Signups need a one-time code from someone who already has one.
AES-256-GCM with a key derived from your vault passphrase via scrypt. The plaintext never leaves your device — not to the server, not to a CDN, not to logs.
Face ID, Touch ID, Windows Hello, or a hardware key. WebAuthn PRF becomes your login secret — nothing to type, nothing to phish, nothing for an attacker to grind against.
Every write is bound to a monotonic sequence and authenticated with the same key that decrypts it. Rollback and cross-vault splice attacks both fail the same authentication check.
A stolen database gives an attacker no shortcut. The server holds a scrypt verifier and ciphertext; the key that unlocks either only ever lives in your device's memory.
Bring your Koinly holdings in as a live section — read-only, on your device, cached against your API key. Or don't; the app works entirely with hand-entered holdings if you'd rather nothing third-party ever sees your ledger.
Track crypto and cash in one view, priced in your base currency (AUD, USD, whatever). Bank balances live next to XMR bags; the grand total is one number.
See it in action
This is the actual app after unlock. Every number below was rendered client-side from a locally decrypted vault. The server watched ciphertext go by.
How it works
Pick a login — a passkey (Face ID / Touch ID / Windows Hello) is the quickest, a passphrase works everywhere. Set a vault passphrase: it's the only key that decrypts your holdings, derived locally with scrypt and never transmitted. Add a passkey for unlocking too and the whole flow becomes biometric — no typing on normal use.
Add positions, prices, and notes in a private spreadsheet. Every keystroke is encrypted in your browser before it syncs to storage under a monotonic sequence number.
Sign in from another device, present your login credential, unlock with your vault passphrase (or a passkey you registered for unlocking on that device), and the browser decrypts locally. Lose both the device and the passphrase and the ciphertext stays sealed forever — which is why the recovery code exists.
Under the hood
Standard primitives, boringly configured. Nothing custom, nothing clever — the encryption layer and threat model are open and published for review.
FAQ
Yes, at the level we can prove in the browser. Your holdings are encrypted on your device with AES-256-GCM before they ever leave. The server sees only a blob of ciphertext plus a monotonic sequence number. The key never touches the network.
Default to passkey. Your device (Face ID, Touch ID, Windows Hello, a hardware key) holds a private key that never leaves it; on login the browser asks the device for a per-site secret and we stretch that with the same scrypt KDF the passphrase path uses. Nothing to remember, nothing to phish.
Pick passphrase if you'd rather memorise a phrase, or if you plan to log in from browsers where passkey roaming is awkward.
There are actually two secrets, doing two different jobs. Login proves who you are to the server so it hands over your encrypted file. Unlock then decrypts the file inside your browser — the server never sees the unlock key. Both can be passkeys, or one passkey and one passphrase, or both passphrases. The most common setup is: login passkey + unlock passkey, so a normal visit is a single Face ID prompt.
Your vault passphrase stays as a fallback either way — it's what protects your holdings if you ever lose every device that holds a passkey.
If your passkey syncs (iCloud Keychain, Google Password Manager, 1Password, etc.) it comes back with your account. If it doesn't — say it lived only on one hardware key — the account is stuck. StealthLedger can't reset it because it has no shared secret to reset to.
Practical mitigations, in order of effort:
No — the Koinly import is a bookmarklet you drag onto your desktop browser's bookmarks bar, and mobile browsers don't have one. Run the import once on a laptop or desktop and the resulting Koinly section syncs to your vault; from then on you can view it on mobile like any other section.
The server never stores your login secret. It stores a scrypt verifier with N=65536, r=8, p=1 — about 64 MB of memory per guess. A stolen database gives an attacker no help decrypting any vault; vault contents need the vault passphrase, which the server has never seen.
Your holdings are gone. Not "wait a while" gone — gone. There is no reset flow because there is no reset key. Set up a recovery code when you create the vault; that's the escape hatch.
Because the site serves you the JavaScript that touches your vault passphrase, a determined operator could push a poisoned build. We mitigate that with a strict Content-Security-Policy (no inline scripts, no third-party origins), a small hand-written codebase, and a published crypto spec you can diff against what your browser loads. The application code itself is proprietary; the encryption layer, threat model, and audit-log format are open for review.
None. No trackers, no pings, no third-party scripts.
Support the project
StealthLedger has no VC, no ads, no tracking, no upsell tier. If it saves you an hour or a headache, an XMR tip keeps the servers up and the roadmap ours to write.
Signups are invite-only right now. If a friend already has an account, ask them to mint you a one-time invite from their admin panel — the link pre-fills the code so you're two minutes and one passkey prompt away.
Paste the access token for your vault. This only lets the device reach the encrypted file — it can't decrypt anything on its own.
It's the VAULT_ACCESS_TOKEN you set in your Netlify
environment variables. Opening the install link from your desktop
(#t=…) fills this in automatically and then wipes it from
the address bar.
Your encrypted holdings, on a server that can’t read them.
Welcome back, . Your session expired — log in to reach your vault. Not you? Sign in to a different account
New here? Create an account · How does this work?
Welcome to the StealthLedger private beta. You’re one of a small group testing the privacy-first crypto tracker — your feedback shapes what ships publicly.
Pick a username, tap once with Face ID / Touch ID / Windows Hello, and write down the recovery code we show you. That's it.
Already have an account? Log in · How does this work?
Yes, at the level we can prove in the browser. Your holdings are encrypted on your device with AES-256-GCM before they ever leave. The server sees only a blob of ciphertext plus a monotonic sequence number. The key never touches the network.
Because they do different jobs and only one of them can involve the server.
So there can be up to four credentials on a full account: a login passkey, a login passphrase, an unlock passkey, and a vault passphrase. Most people set one on each side and forget about it. Settings groups them under “Login — how you prove it’s you” and “Unlock — how the vault decrypts” for exactly this reason.
Practical rule of thumb: if a screen is titled Log in, it wants a login credential. If it’s titled Unlock your vault, it wants an unlock credential. Adding a passkey on one side doesn’t add it on the other — they’re independent.
Default to passkey for both sides. Your device (Face ID, Touch ID, Windows Hello, a hardware key) holds a private key that never leaves it; on login the browser asks the device for a per-site secret, and the same trick works client-side to unwrap the vault key. Nothing to remember, nothing to phish.
Pick passphrase if you’d rather memorise a phrase, or if you plan to sign in from browsers where passkey roaming is awkward. You can add either one to an existing account later from Settings, so this isn’t a one-way door.
You can also mix: passkey on the login side, passphrase on the unlock side (or vice versa). They’re independent.
You added it on one side; the other side doesn’t have one yet. The most common case: you added a login passkey, signed in with a tap, and then the unlock screen asked for your passphrase because the vault itself doesn’t know about a passkey yet.
Fix it in Settings → Unlock — how the vault decrypts → Add passkey for unlocking. That wraps the vault key with a passkey-derived secret and appends it to the vault’s unlockers, so next time the unlock screen offers a passkey button instead of the passphrase field.
Restoring a backup carries over whatever unlockers the backup had, which is usually just the original passphrase. Add an unlock passkey once, after restore, and you’re done.
If your passkey syncs (iCloud Keychain, Google Password Manager, 1Password, etc.) it comes back with your account. If it doesn't — say it lived only on one hardware key — the account is stuck. StealthLedger can't reset it because it has no shared secret to reset to.
Practical mitigations, in order of effort:
The server never stores your login secret — passphrase or passkey. It stores a scrypt verifier with N=65536, r=8, p=1 (about 64 MB per guess). A stolen database gives an attacker no help decrypting any vault — vault contents need the vault passphrase, which the server has never seen.
Passkey accounts are quietly stronger against dumps because the secret going into scrypt is a 32-byte high-entropy value from the authenticator, not something a human typed — offline cracking has essentially nothing to guess against.
Your holdings are gone. Not “wait a while” gone — gone. There is no reset flow because there is no reset key. Set up a recovery code when you create the vault; that’s the escape hatch.
A backup file is your encrypted vault as it was on the day you exported it: same vaultId, same unlockers, same holdings. Restoring it into a new account overwrites whatever vault that account currently has and hands you back the old one. You’ll unlock it with the backup’s vault passphrase or recovery code — not the passphrase you just used to sign up.
The usual flow when moving between accounts or coming from the old single-tenant version:
If the restore screen refuses your file, the fastest reset is Settings → Forget access token, log in again with the account passphrase, then retry. That gets you into a clean state where the client and server agree on which account you’re acting as.
Right now, the account is stuck. If we later add an email-based reset it will only reset the login, not decrypt anything — the vault contents are still gated by the vault passphrase.
Because the site serves you the JavaScript that touches your vault passphrase, a determined operator could push a poisoned build. We mitigate that with a strict Content-Security-Policy (no inline scripts, no third-party origins), a small hand-written codebase, and a published crypto spec you can diff against what your browser loads. The application itself is proprietary; the encryption layer, threat model, and audit-log format are open for review.
Same idea, narrower scope. Purpose-built encrypted spreadsheet for crypto holdings. Fewer features, smaller codebase. Bitwarden and 1Password are fully open source; StealthLedger publishes its crypto layer and threat model rather than the whole app.
Yes, any time. Settings → Account → Danger zone → Delete my account. Type your username to confirm. It's a hard delete — encrypted vault, sessions, audit log, all of it. No grace period, no undo. If you want a copy of your data first, export it from Settings before you press the button.
None. No trackers, no pings, no third-party scripts.
Paste your operator token. Stored in this tab only.
Loading…
Runtime toggle. “Use env default” clears the override and falls back to the SIGNUP_MODE env var.
One shared code, capped total redemptions, one signup per IP. Set the mode above to Preview code to make the code the gate for new signups. Rotating the code doesn’t wipe the redeemers list — use Reset redemptions to open a fresh pool.
Signup link — auto-fills the code:
Loading…
Monero-only for now. Empty this field to hide the donate card on
the FAQ page. Standard mainnet addresses start with 4
or 8 and are 95 characters long; testnet or unusual
address types are rejected.
CoinGecko is called at most once per fresh window per coin+currency combo, shared across every open tab and every function instance via the blob store. If CoinGecko rate-limits us (HTTP 429), we keep serving the last good price for up to the stale window before showing an error — nobody’s screen goes blank. All knobs here take effect within ~30 seconds; no redeploy needed.
Loading cache stats…
Loading…
Signed in as . Pick how you'll unlock this vault on this device — your face, your fingerprint, or a passphrase you remember.
Everything is encrypted on this device before it's uploaded. Nobody operating the server can read it — including us. That also means if you lose your unlock method AND your recovery code, it's unrecoverable.
Your recovery code. It's the only way back in if you forget the passphrase and lose your passkey. It is shown once and never stored anywhere in readable form.
Your data is decrypted locally. Nothing on our servers can read it without your passphrase or passkey.
Signed in as
or
Deriving key…
—
The app code on the server has changed since this device pinned it. Nothing new runs until you accept it.
A short hash of the app files this device has pinned. If it changes unexpectedly, nothing new runs until you accept the update — so a silent replacement of the code between logins is visible here.
The value is computed and shown by the app itself, which means it detects unexpected changes rather than proving any single load was safe.
No private coins yet.
Total —
Read-only here. To refresh, open Settings → Koinly import and click
your bookmarklet from an app.koinly.io tab. Your Koinly
cookies never leave that tab.
Total —
No cash entries yet.
Total —