[ CASE STUDY · 01 / 03 ] ——— SPLITPASS · V1.3.0 · SHIPPED
satoshi@case-studies ~ $ cat splitpass.brief

High-value secrets,
split so no single place can lose them.

A mobile vault for passwords, payment cards and BIP39 seed phrases — with an original shard-recovery system so a single compromised device, backup or custodian can’t reveal the secret.

Privacy engineering Cryptography iOS / Android Browser ext. NFC / QR
[01]
AT_A_GLANCE/
0
single points of failure
by design — shards required
3
secret formats supported
password · card · seed phrase
v3
encryption envelope
Argon2id → AES-GCM
telemetry / analytics
nothing leaves the device
[02]
THE_PROBLEM.MD

Password managers trust one place. That’s the whole problem.

A user’s master vault is worth everything: logins, bank cards, the 24-word phrase behind their wallet. Yet it’s usually protected by one passphrase, on one cloud, with one vendor holding the keys.

We wanted the opposite: a vault where the worst-case breach still tells an attacker nothing — and a recovery path that doesn’t depend on any company being alive in five years.

A.
Single-vendor risk
If the provider is breached, coerced, or sunset, the user is exposed or locked out.
B.
Single-device risk
Lose the phone → lose the vault. Back it up to the cloud → centralise the risk you were avoiding.
C.
Plaintext recovery
Seed phrases and “security questions” end up on paper, in notes apps, or on screenshots in Drive.
D.
Branded backups
Exported archive files advertise what they contain, turning every backup into a target.
[03]
APPROACH.MD
// FOUR MOVES

Local-first, split-by-default, opaque on the wire.

We refused three things: a server, a single secret, and a file format that reveals anything before decryption. What’s left is a device-held vault that normalises every secret into the same numeric payload, then treats splitting and scanning as first-class operations.

[01]
Device-held vault
AsyncStorage + Argon2id-derived keys. The master passphrase never leaves RAM, and we never see it.
[02]
One payload format
Passwords, cards, and 12/24-word BIP39 phrases all normalise to a numeric payload. Split and scan work on any of them.
[03]
Shard-based recovery
A secret can be split into pieces that live separately — QR, NFC, paper. No single piece reveals anything.
[04]
Opaque on disk
Backup archives carry no SplitPass branding, version marker, or hint of vault content before the passphrase is entered.
[04]
SPLIT_RECOVERY/

The core trick: normalise, then split.

Every secret — a web password, a card number, a 24-word mnemonic — is converted into a deterministic numeric payload. That payload can then be stored locally, encrypted, or carved into recoverable pieces for QR or NFC transport.

Reconstruction is the exact inverse: the app scans or reads enough material, decrypts it if needed, and decodes it back. Legacy PIN-protected payloads stay readable so older scans never orphan a user.

// RECOVERY INVARIANT
Any single shard — paper, phone, QR photo, NFC tag — reveals nothing. Only the agreed combination rebuilds the secret. The app never assumes it can recover on behalf of the user.
FIG. 01 — PAYLOAD LIFECYCLEsatoshi-ltd/splitpass
[05]
CRYPTO_CORE/
// V3 ENVELOPE

Argon2id for the key, AES-GCM for the data, no branding on the wire.

A native Argon2id bridge derives the vault key from the user’s master passphrase. The derived key stays in memory only while the vault is unlocked. Every record and every exported backup is wrapped in an opaque v3 envelope — no magic bytes, no readable headers, no version strings outside the ciphertext.

Legacy v1 and v2 records stay readable and migrate forward after a correct unlock, so upgrades never stranded a vault.

// vault/encrypt.ts — simplified

const salt = crypto.randomBytes(16);
const key  = await argon2id(passphrase, salt, {
  iterations: 3,
  memory:     64 * 1024,   // 64 MiB
  parallelism: 2,
  hashLength:  32,
});

const iv        = crypto.randomBytes(12);
const cipher    = aesGcm(key, iv);
const payload   = cipher.encrypt(plaintextSecret);

// opaque v3 envelope — no magic bytes, no labels
return concat(salt, iv, payload.authTag, payload.ciphertext);

// on disk:  [ salt │ iv │ tag │ ciphertext ]  ← looks like noise
KDF
Argon2id
native bridge, 64 MiB, t=3, p=2
CIPHER
AES-256-GCM
authenticated; per-record nonce
ENVELOPE
opaque v3
no markers, no branding
AUTO-LOCK
~immediate
backgrounded → vault wiped from RAM
BIOMETRIC
opt-in gate
re-enter passphrase to enable
COMPAT
v1 / v2 / v3
legacy payloads migrate after unlock
[06]
SURFACES/
Mobile app
Expo / React Native 0.83 on iOS & Android. Camera-based QR scanner, NFC read/write, biometric unlock, encrypted local backups via document picker.
Expo SDK 55iOS 16+Android 10+
Browser extension
Companion extension for Chromium and Safari: scans QR codes surfaced in a page and fills password fields from a paired vault.
Manifest v3Safari Web Ext.Jest
Recovery protocol
A compact numeric payload, a B-type envelope that can optionally ship the username alongside the secret, and full backward compatibility with earlier PIN-era payloads.
QRNFCPaper shards
[07]
OUTCOMES.TXT
  • Shipped to production on both stores
    v1.3.0, Expo SDK 55, reproducible EAS builds.
  • Zero plaintext on disk after setup
    every record lives inside the v3 envelope; backups are opaque archives.
  • Backward compatible since day one
    v1 / v2 records and legacy PIN QR payloads still decrypt and migrate forward.
  • Offline-first, zero telemetry
    no analytics SDKs, no crash reporters phoning home, no accounts.
// WHAT WE LEARNED
“The hardest part wasn’t the crypto. It was keeping the product honest about what it couldn’t do — recover a lost passphrase, vouch for a shard you threw away, or know more than it’s told.”
— satoshi-ltd engineering notes
← BACK
All case studies
satoshi-ltd / case-studies