Skip to content

#84 Enhancement: Use a default password to encrypt in disk#86

Merged
andycreed0x merged 3 commits into
developfrom
84-default-password-encryption
Jun 1, 2026
Merged

#84 Enhancement: Use a default password to encrypt in disk#86
andycreed0x merged 3 commits into
developfrom
84-default-password-encryption

Conversation

@andycreed0x

@andycreed0x andycreed0x commented May 27, 2026

Copy link
Copy Markdown
Collaborator

Purpose

Encrypt every mnemonic at rest, even when the user provides no password. Previously, wallets imported without a password stored the mnemonic as base64-only plaintext (plain: prefix), making it trivially readable by any process scanning for BIP39 word patterns. A hardcoded default password is now applied automatically so no mnemonic ever lands in plaintext on disk.

Closes #84 Enhancement: Use a default password to encrypt in disk

Description

This is obfuscation, not full security — the default password lives in source code and won't stop a targeted attacker with code access. The stated goal is to defeat automated plaintext-seed scanners. Wallets encrypted with a user-supplied password are unaffected and continue to require that password for signing.

Lazy migration: existing plain: wallets are silently re-encrypted to default:1: the first time their mnemonic is read (signing or load), with an atomic write-back. Write-back failures are logged as warnings but never break signing.

Main Changes

  • New prefix-tagged storage format: default:1: (default-encrypted), user: (user-password), plain: (legacy read-only), untagged (legacy user-password)
  • New Storage.read_and_migrate_mnemonic(wallet, password) — single lazy-migration choke point wired at wallet.py:load_wallet and bitcoin.py:send
  • Rename is_mnemonic_encryptedrequires_user_password across 9 production call sites (wallet.py, bitcoin.py, lightning.py, sideshift.py, changelly.py, sideswap.py) — correctly reflects that default-encrypted wallets do not need a user password
  • Versioned default:N: prefix from day one — default:1: today, rotation-ready via version integer
  • _DEFAULT_MNEMONIC_PASSWORD + _DEFAULT_PWD_VERSION = 1 constants in storage.py; unsupported versions raise ValueError (no silent fallback)
  • 21 new unit tests: prefix detection, lazy migration round-trip, predicate truth table, signing flows for default/user wallets, atomicity, perms (0o600) preservation, write-failure tolerance via caplog, bitcoin-side migration wiring

Backwards compatibility

  • Existing plain: wallets auto-migrate to default:1: on next read — no user action required
  • Existing user-password wallets (untagged legacy) continue working identically
  • Watch-only wallets unaffected (no mnemonic)

⚠️ Breaking Changes

  • Storage.is_mnemonic_encrypted is removed; callers must use Storage.requires_user_password (different semantics: returns False for default: blobs).
  • New wallets imported without a password now store default:1:... instead of plain:.... Tests asserting the plain: prefix must be updated (already done in this PR for test_tools.py and test_cli.py).

Checklist

  • Added/updated tests (21 new + 5 updated)
  • Merged develop (includes Fix lightning send test to neutralize AQUA_PASSWORD from .env #85 lightning test fix — full suite now 809 passed, 25 skipped, 0 failed)
  • Added/updated relevant documentation (server.py LLM prompt copy around lines 1060/1089/1095 still references old "ask user about encryption" flow — follow-up PR)

…ssword

All mnemonics are now encrypted on disk even when the user provides no
password. New prefix-tagged storage format (`default:1:`, `user:`, `plain:`
legacy read-only, untagged legacy) makes each blob self-describing.
Lazy migration converts existing `plain:` wallets to `default:1:` on next
read via a single choke point (`Storage.read_and_migrate_mnemonic`).
Renames `is_mnemonic_encrypted` → `requires_user_password` across all
9 production + 7 test call sites to correctly distinguish on-disk encryption
from user-password requirement. Adds 21 unit tests covering prefix detection,
migration round-trip, truth table, signing flows, atomicity, and write-failure
tolerance.
@andycreed0x andycreed0x requested a review from marinate305 May 29, 2026 18:45
@andycreed0x andycreed0x marked this pull request as ready for review May 29, 2026 18:45
Cover the four malformed prefix shapes that retrieve_mnemonic must
reject with a "Malformed default-encrypted mnemonic prefix" error
("default:", "default:novalidversion", "default::blob", "default:abc:blob"),
plus a regression test that the error message truncates arbitrarily
long inputs to avoid leaking ciphertext in logs.
@andycreed0x andycreed0x merged commit 0659eb4 into develop Jun 1, 2026
4 checks passed
@andycreed0x andycreed0x deleted the 84-default-password-encryption branch June 1, 2026 17:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant