Technical Overview

Cryptographic Architecture

How seQRets protects your secrets — algorithms, parameters, and design decisions. No marketing, just facts.

Processing Pipeline

Secret InputPassword + Optional KeyfilePad (192B buckets)Argon2id KDFXChaCha20-Poly1305 EncryptShamir Split (K-of-N)QR Encode (Qards)SHA-256 Verify

The secret enters memory, is encrypted under a key derived from your password (and optional keyfile), split into threshold shares, rendered as QR codes, integrity-verified with SHA-256, and then destroyed. In the desktop app, Rust zeroes the memory with compiler-fence zeroization.

Cryptographic Primitives

AlgorithmPurposeKey / Output SizeLibrary (Web)Library (Desktop)
XChaCha20-Poly1305Authenticated encryption256-bit key, 192-bit nonce@noble/cipherschacha20poly1305 (RustCrypto)
Argon2idKey derivation (KDF)256-bit output@noble/hashesargon2 (RustCrypto)
Shamir's Secret SharingThreshold splittingVariable (matches input)shamir-secret-sharing (Cure53 + Zellic audits)shamir-secret-sharing (Cure53 + Zellic audits)
BIP-32 (XFP only)Hardware-wallet verification32-bit master fingerprint@scure/bip32@scure/bip32
SHA-256Share integrity verification256-bit hash@noble/hashes@noble/hashes

Argon2id Parameters

ParameterValuePurpose
Memory cost64 MB (65536 KiB)Resists GPU/ASIC brute-force attacks by requiring large memory
Iterations (time cost)4Increases computation time per password attempt
Parallelism1Single-threaded derivation — deterministic output
Output length32 bytes (256 bits)Matches XChaCha20-Poly1305 key size
Salt16 bytes (random)Unique per encryption — prevents rainbow tables
Inputpassword_bytes || keyfile_bytesPassword and optional keyfile are concatenated before derivation

Optional Keyfile

seQRets supports an optional keyfile as a second authentication factor. When provided, the keyfile bytes are concatenated with the password bytes before being fed into Argon2id — the keyfile is not hashed separately or used as a pepper.

key = Argon2id(password_bytes || keyfile_bytes, salt, m=65536, t=4, p=1, dkLen=32)

Both the web app (TypeScript) and the desktop app (Rust) use identical concatenation logic. When no keyfile is provided, only the password is used. A wrong or missing keyfile causes the XChaCha20-Poly1305 authentication tag to fail, preventing decryption.

PropertyValue
Generated size32 bytes (256 bits) — CSPRNG
Accepted formats.bin, .key
Maximum upload size2 MB
Integration pointConcatenated with password before Argon2id
AvailabilityWeb app and Desktop app

What keyfiles defend against

  • Keyloggers — a binary file is never typed, so keystroke capture is useless
  • Shoulder surfing — nothing to observe visually during authentication
  • Weak password brute-force — a generated 256-bit keyfile makes brute-force computationally infeasible regardless of password strength
  • Physical coercion — if the keyfile is stored in a separate physical location, the user genuinely cannot decrypt on demand

Important: keyfile loss is irrecoverable

If you encrypt with a keyfile and lose it, the secret cannot be decrypted. There is no recovery mechanism. Back up your keyfile separately from your shares and password.

Shamir's Secret Sharing

Security Type

Information-theoretic

Security does not depend on computational hardness. Fewer than K shares provide zero bits of information about the secret, regardless of computing power.

Quantum Resistance

Yes

Shamir's SSS relies on polynomial interpolation over finite fields, not factoring or discrete logarithms. Quantum computers provide no advantage. While fewer than K shares are in an adversary's hands, the encrypted blob is never reconstructed, so the entire scheme is quantum-safe regardless of which cipher encrypts the underlying secret.

Threshold Semantics

K-of-N configurable

You choose K (threshold) and N (total shares). Any K shares reconstruct; K-1 or fewer reveal nothing. Common choices: 2-of-3, 3-of-5.

Field

GF(256)

Operations are performed in the Galois Field GF(2^8), enabling byte-level splitting with no data expansion.

QR Code Encoding

PropertyValue
Data formatBase64-encoded ciphertext share
Error correctionLevel M (15% recovery)
Maximum capacity~2,953 bytes (Version 40, Level L) — practical limit depends on error correction level
OutputPrintable QR code (Qard) — designed for physical distribution
MetadataShare index, threshold, total shares — included in the QR payload, not stored externally
Integrity hashOptional SHA-256 hash appended as a 4th pipe-delimited segment for tamper detection

Share Format & Integrity Verification

Each share is a pipe-delimited string. The SHA-256 hash always sits last and covers everything before it — so the hash input is simply the whole string up to |sha256:. It is verified automatically at generation and restoration.

Legacy (pre-v1.11): seQRets|<salt>|<data>|sha256:<64-char hex>
v1.14+ / toggle on: seQRets|<salt>|<data>|v=1|t=<K>|n=<N>|i=<I>|sha256:<64-char hex>

Backward compatible: legacy 3-part shares without hashes still decode, 4-part shares (sha256 only) still decode, and v1.11+ shares add threshold (K), total (N), and 1-based card index (I) — these drive a per-set live countdown during restore. Since v1.14 every new share also carries a v=1 format-version marker as its first metadata segment. All metadata is hash-covered so it cannot be tampered with. The toggle is opt-out (default on); anyone scanning a metadata-enabled Qard learns K and N. Without the password that's not enough to recover anything, but it does narrow what an attacker is searching for, so users wanting maximum opacity can disable the toggle when generating shares. The SHA-256 hash is one-way and reveals nothing about share contents.

PropertyValue
Hash functionSHA-256 via @noble/hashes/sha256
Hash inputEverything before |sha256: — the hash always sits last. Legacy/4-part shares: SHA-256("seQRets|salt|data"). v1.14+ shares with recovery metadata: SHA-256("seQRets|salt|data|v=1|t=K|n=N|i=I") — the version marker and metadata are included in the hash so they cannot be tampered with.
Hash output64 hex characters (~71 chars with sha256: prefix)
Verification (generation)All shares are round-trip verified before being presented
Verification (restore)Desktop: auto-verified on scan/import with visible shield icon. Web: verified silently in the background (no UI) — the hash is still checked round-trip, there's just no indicator shown to the user.
Printed fingerprintDesktop: truncated hash (xxxxxxxx...xxxxxxxx) printed on the face of exported Qards. Enables out-of-band tamper detection when paired with an external record (e.g., inheritance ledger), plus print/scan corruption detection and visual disambiguation across stacked cards.
Backward compatibilityLegacy 3-part shares without hashes are fully supported
SecurityOne-way — the hash cannot be reversed to recover share data

Manual verification

Users can independently verify any share in a terminal. For legacy or 4-part shares (no recovery metadata):

echo -n "seQRets|salt|data" | shasum -a 256

For v1.14+ shares, include the version marker and any metadata segments — i.e. everything before |sha256::

echo -n "seQRets|salt|data|v=1|t=K|n=N|i=I" | shasum -a 256

Length Privacy & Format Version (v1.14+)

Padding — a Qard doesn't leak its secret's size

Stream ciphers produce ciphertext the same length as their input, so before v1.14 a Qard's size correlated with the size of the secret inside it. Since v1.14 the compressed payload is zero-padded up to a multiple of 192 bytes before encryption. Every common secret — a 12-word seed, a 24-word seed, a labeled backup — lands in the same first bucket and produces an identically sized Qard, so an observer holding a Qard learns only "at most N buckets," which for typical secrets is nothing at all.

Padding is applied after compression (compression would collapse it), is covered by the authentication tag, and needs no unpad step on restore — gzip streams are self-terminating and both deployed decompressors ignore trailing zero bytes. That is also why pre-v1.14 apps and previously downloaded copies of recover.html restore padded Qards without any update. Padding applies to Qard share payloads only; vault and plan files are updatable disk artifacts.

Format-version marker — built for decades

Every v1.14+ Qard carries a hash-covered v=1 marker as its first metadata segment. Qards are frozen artifacts — printed cards, steel plates — so the marker exists for longevity: it lets future software tell "this backup is damaged" (checksum mismatch) apart from "this software predates this Qard's format", and show an heir a clear "update your app" message instead of a false corruption error. Shares with no v= segment are pre-v1.14 and parse under the original rules; old parsers ignore unknown key=value segments and still hash them correctly, so v=1 shares restore in pre-v1.14 software.

Label blind export

Labels are always encrypted inside the payload. By default they are also printed on card faces and used in file names, which is convenient for telling Qards apart. The "Show label on Qards & file names" switch (on by default) gates every plaintext surface: turn it off for a blind export, where cards, files, and smart-card entries show only a card number and set ID. Useful whenever a third party handles your cards — a print shop or an etching service learns nothing but a number.

Hardware-Wallet Verification (BIP-32 XFP)

When restoring a BIP-39 mnemonic, the reveal dialog's SeedQR tab displays the BIP-32 master fingerprint (XFP) beneath the QR — an 8-character hex string derived from the master public key. Many hardware wallets (Jade, Coldcard, Trezor, and others) initialize without ever displaying the mnemonic, but almost all of them show the XFP on the home screen after import. Matching the two proves the correct seed was loaded.

XFP = HDKey.fromMasterSeed(mnemonicToSeedSync(phrase, "")).fingerprint (hex, uppercase)

The seed buffer is zeroized immediately after fingerprint computation. The XFP is derived from the master public key and reveals nothing about the seed — it is safe to display outside the QR's blur halo so users can verify without exposing the QR to bystanders.

PropertyValue
Format8 uppercase hex characters (e.g. 73C5DA0A)
Library@scure/bip32 (audited, same author as @scure/bip39)
Applies toBIP-39 mnemonic secrets only (single or multi-mnemonic/multisig)
PrivacyDerived from the master public key — reveals nothing about the seed
MemorySeed buffer is zeroized after computation
Passphrase caveatIf a BIP-39 passphrase is applied at wallet-import time, the on-device XFP will differ — the value shown here assumes no passphrase

SLIP-39 Detection (Trezor Recovery Shares)

Trezor-style SLIP-39 recovery shares (20 or 33 words, including multi-share sets entered one per line) are recognized on entry and validated against their built-in RS1024 checksum — any single mistyped word is caught before encryption, and the checksum is verified again after restore.

Unlike BIP-39, SLIP-39 phrases are stored as plain text rather than converted to entropy: each share carries metadata (identifier, group parameters, iteration exponent) that must be reproduced exactly. The detection module is validation-only — it never splits, combines, or otherwise handles key material. No SeedQR is offered (SeedQR is a BIP-39-only format); restored shares display as a numbered word grid for typing into a hardware wallet.

PropertyValue
Detected shares20- or 33-word SLIP-39 shares, including multi-share sets (one per line)
ValidationRS1024 checksum — a mistyped word is caught before encryption and re-verified after restore
Moduleslip39.ts — validation-only, zero dependencies, embeds the official 1024-word SLIP-39 list
Test vectorsVerified against all 45 official SatoshiLabs SLIP-39 test vectors
StorageStored as plain text (not converted to entropy) to preserve each share's metadata
Restore outputNumbered word grid for re-entry into a hardware wallet — no SeedQR (BIP-39-only)
Key materialNever splits, combines, or reconstructs shares — reads and verifies word sequences only
AvailabilityWeb and desktop

Zero-Knowledge Claims

What "zero-knowledge" means for seQRets

  • No server — all processing happens locally on your device
  • No account — no registration, no login, no user database
  • No cloud storage — nothing is uploaded, synced, or backed up
  • No analytics — no tracking pixels, error reporters, or usage telemetry
  • No network dependency — the app works fully offline after initial load (web) or always (desktop)

Explicit Caveats

  • Bob AI assistant (optional) — if you provide a Google Gemini API key and ask Bob a question, your question is sent to Google's Gemini API. No secret data is included.
  • BTC price display (optional) — the app fetches the current Bitcoin price from a public API for display purposes. No user data is transmitted. Disabled when offline.

Source Code

The full source code is available for audit and independent verification.

For the full web vs. desktop security comparison, see the Security page.