Verify the master key against the database at startup (B-89, SEC-49, SEC-50, SEC-09)

The lifespan decrypts the key and compares a fixed sample (reserve/round pool
addresses, the 10 lowest and highest user addresses, unrevealed round secrets)
with the database, and refuses to serve on any mismatch without printing key
material. The key scripts refuse a new or imported key that would not reproduce
the stored addresses, and write 0600.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5.5 committed 2026-10-18 00:38:26 +02:00
1 parent 32df50cb75
commit 55d852fe08
14 files changed
+281 -46

No files matched your search

+9 -3
View File
@@ -25,7 +25,8 @@ import os
from embit.bip32 import HDKey
from app.config import settings
from app.wallet.keystore import encrypt_xprv
from app.wallet.keystore import encrypt_xprv, write_secret_file
from app.wallet.verify import account_key, load_sample, sample_problems
_DOCKER_COMPOSE_DIR = "./data/keys"
@@ -69,8 +70,13 @@ if __name__ == "__main__":
if not parsed.is_private:
raise SystemExit("This is a public key (xpub), not a private key (xprv) — refusing to encrypt it as one.")
# SEC-05/SEC-50: the version bytes cannot tell a PLM key from a Bitcoin one, the stored
# addresses can. A mismatch is refused even with --overwrite.
problems = sample_problems(account_key(xprv), load_sample())
if problems:
raise SystemExit("this xprv does not match the database — refusing to write it: " + "; ".join(problems))
os.makedirs(os.path.dirname(key_path) or ".", exist_ok=True)
with open(key_path, "wb") as f:
f.write(encrypt_xprv(xprv))
write_secret_file(key_path, encrypt_xprv(xprv))
print(f"Master key written (encrypted) to {key_path}")