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.
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.
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.
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.
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
“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.”