W2QR

How Encryption Works

The cryptographic details behind W2QR — honey encryption, Argon2id, and multi-factor key derivation.

Honey Encryption

Traditional encryption (AES, ChaCha20) produces random garbage when the wrong password is used. An attacker brute-forcing passwords immediately knows when the correct one is found — the output suddenly makes sense.

Honey encryption (Juels & Ristenpart, Eurocrypt 2014) eliminates this signal. W2QR maps your mnemonic to its BIP-39 entropy bits, then XOR-masks them with an Argon2id-derived key. Because BIP-39 entropy is uniformly distributed, every key produces valid entropy — and therefore a valid mnemonic.

An attacker trying a million passwords gets a million real-looking seed phrases, each controlling a different wallet. To identify the real one, they must derive addresses from each mnemonic and check them against the blockchain — economically infeasible at scale.

Key Derivation — Argon2id

Your password is processed through Argon2id — a memory-hard key derivation function that is the OWASP-recommended choice for password hashing. W2QR uses:

  • - 64 MB memory (makes GPU/ASIC attacks expensive)
  • - 3 iterations (increases sequential time cost)
  • - 1 parallel lane (consistent across devices)
  • - 32-byte output (256-bit derived key)

These parameters are stored in the QR code, so future versions can increase them without breaking existing backups.

Basic Mode Encryption

The Basic mode key derivation is straightforward:

A = Argon2id(password, salt, 64MB, 3 iterations)
K = A[0..entropyLength]
ciphertext = XOR(mnemonicEntropy, K)

Every password produces valid BIP-39 entropy — no password verifier exists.

Advanced Mode Encryption

Advanced mode adds a 256-bit authorization secret (S) to the key derivation, using HKDF-SHA-256 for domain separation:

A = Argon2id(password, passwordSalt, params)
K_n = HKDF-SHA-256(
  IKM  = A || S,
  salt = keySalt,
  info = "W2QR-v2/normal"
)
C_HE = XOR(mnemonicEntropy, K_n)

The key K_n depends on both the password (via A) and the authorization secret S. Without the correct S, a wrong K_n is produced — but because of the XOR/DTE property, the result is still valid BIP-39 entropy. Wrong password, wrong S, or both — every combination yields a plausible mnemonic.

Recovery Encryption (Advanced only)

Advanced mode also creates an authenticated recovery ciphertext (C_REC) encrypted under a recovery key that is completely independent of your password:

R = CSPRNG(256 bits)  // random recovery root
K_r = HKDF-SHA-256(R, recoverySalt, "W2QR-v2/recovery")
C_REC = AES-256-GCM(K_r, mnemonicEntropy, headerAAD)

C_REC uses authenticated encryption (AES-256-GCM with associated data), which is safe because R is random and high-entropy — it cannot be brute-forced. R is then split into 3 shares using Shamir Secret Sharing: any 2 of the 3 shares can reconstruct R and decrypt C_REC.

No Password Verifier

W2QR never stores a password hash, password-derived MAC, AEAD tag under a password-derived key, known plaintext sentinel, or wallet address. There is no way to check if a password is correct without deriving addresses and checking the blockchain. This is the core security guarantee of honey encryption.