Technical Overview
Cryptographic Architecture
How seQRets protects your secrets — algorithms, parameters, and design decisions. No marketing, just facts.
Processing Pipeline
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
| Algorithm | Purpose | Key / Output Size | Library (Web) | Library (Desktop) |
|---|---|---|---|---|
| XChaCha20-Poly1305 | Authenticated encryption | 256-bit key, 192-bit nonce | @noble/ciphers | chacha20poly1305 (RustCrypto) |
| Argon2id | Key derivation (KDF) | 256-bit output | @noble/hashes | argon2 (RustCrypto) |
| Shamir's Secret Sharing | Threshold splitting | Variable (matches input) | shamir-secret-sharing (Cure53 + Zellic audits) | shamir-secret-sharing (Cure53 + Zellic audits) |
| BIP-32 (XFP only) | Hardware-wallet verification | 32-bit master fingerprint | @scure/bip32 | @scure/bip32 |
| SHA-256 | Share integrity verification | 256-bit hash | @noble/hashes | @noble/hashes |
Argon2id Parameters
| Parameter | Value | Purpose |
|---|---|---|
| Memory cost | 64 MB (65536 KiB) | Resists GPU/ASIC brute-force attacks by requiring large memory |
| Iterations (time cost) | 4 | Increases computation time per password attempt |
| Parallelism | 1 | Single-threaded derivation — deterministic output |
| Output length | 32 bytes (256 bits) | Matches XChaCha20-Poly1305 key size |
| Salt | 16 bytes (random) | Unique per encryption — prevents rainbow tables |
| Input | password_bytes || keyfile_bytes | Password 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.
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.
| Property | Value |
|---|---|
| Generated size | 32 bytes (256 bits) — CSPRNG |
| Accepted formats | .bin, .key |
| Maximum upload size | 2 MB |
| Integration point | Concatenated with password before Argon2id |
| Availability | Web 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
| Property | Value |
|---|---|
| Data format | Base64-encoded ciphertext share |
| Error correction | Level M (15% recovery) |
| Maximum capacity | ~2,953 bytes (Version 40, Level L) — practical limit depends on error correction level |
| Output | Printable QR code (Qard) — designed for physical distribution |
| Metadata | Share index, threshold, total shares — included in the QR payload, not stored externally |
| Integrity hash | Optional SHA-256 hash appended as a 4th pipe-delimited segment for tamper detection |
Share Format & Integrity Verification
Each share is a pipe-delimited string with an optional SHA-256 integrity hash. The hash covers the first three segments and is verified automatically at generation and restoration.
The 4th segment is optional for backward compatibility — legacy 3-part shares are still fully supported. The SHA-256 hash is one-way and reveals nothing about the share contents.
| Property | Value |
|---|---|
| Hash function | SHA-256 via @noble/hashes/sha256 |
| Hash input | SHA-256("seQRets|salt|data") — covers the 3-part share string |
| Hash output | 64 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 fingerprint | Desktop: truncated hash (xxxxxxxx...xxxxxxxx) displayed on exported Qard cards |
| Backward compatibility | Legacy 3-part shares without hashes are fully supported |
| Security | One-way — the hash cannot be reversed to recover share data |
Manual verification
Users can independently verify any share in a terminal:
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.
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.
| Property | Value |
|---|---|
| Format | 8 uppercase hex characters (e.g. 73C5DA0A) |
| Library | @scure/bip32 (audited, same author as @scure/bip39) |
| Applies to | BIP-39 mnemonic secrets only (single or multi-mnemonic/multisig) |
| Privacy | Derived from the master public key — reveals nothing about the seed |
| Memory | Seed buffer is zeroized after computation |
| Passphrase caveat | If a BIP-39 passphrase is applied at wallet-import time, the on-device XFP will differ — the value shown here assumes no passphrase |
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.