52 lines
1.9 KiB
Python
52 lines
1.9 KiB
Python
"""One-time ops recovery: decrypt and print the server's master xprv.
|
|||
|
|
|
||
|
|
This is the single secret the entire custodial wallet derives from (every
|
||
|
|
user address, the pool address) — treat the output like a root password.
|
||
|
|
Not exposed via any API endpoint or the admin panel, by design; run manually,
|
||
|
|
locally, only when you actually need it (e.g. disaster-recovery backup).
|
||
|
|
|
||
|
|
Usage:
|
||
|
|
python scripts/decrypt_master_key.py
|
||
|
|
|
||
|
|
Requires XPRV_ENCRYPTION_KEY to already be set (.env), same as the running
|
||
|
|
server. Looks for the encrypted key at MASTER_KEY_PATH (.env) — if that path
|
||
|
|
doesn't exist, falls back to ./data/keys/master.xprv.enc, the location
|
||
|
|
docker-compose.yml bind-mounts it to, since .env's default (./master.xprv.enc)
|
||
|
|
often doesn't match wherever the running server actually reads it from.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import os
|
||
|
|
|
||
|
|
from app.config import settings
|
||
|
|
from app.wallet.keystore import decrypt_xprv
|
||
|
|
|
||
|
|
_DOCKER_COMPOSE_PATH = "./data/keys/master.xprv.enc"
|
||
|
|
|
||
|
|
|
||
|
|
def _resolve_key_path() -> str:
|
||
|
|
if os.path.exists(settings.master_key_path):
|
||
|
|
return settings.master_key_path
|
||
|
|
if os.path.exists(_DOCKER_COMPOSE_PATH):
|
||
|
|
print(f"{settings.master_key_path} not found — using {_DOCKER_COMPOSE_PATH} instead.")
|
||
|
|
return _DOCKER_COMPOSE_PATH
|
||
|
|
raise SystemExit(f"No master key found at {settings.master_key_path} or {_DOCKER_COMPOSE_PATH}")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
if not settings.xprv_encryption_key:
|
||
|
|
raise SystemExit("XPRV_ENCRYPTION_KEY is not set")
|
||
|
|
|
||
|
|
key_path = _resolve_key_path()
|
||
|
|
|
||
|
|
print(f"About to decrypt and print the master xprv from {key_path}.")
|
||
|
|
print("This key controls every user's funds and the pool. Make sure this")
|
||
|
|
print("terminal isn't logged/recorded and no one is watching your screen.")
|
||
|
|
confirm = input("Type 'yes' to continue: ")
|
||
|
|
if confirm.strip().lower() != "yes":
|
||
|
|
raise SystemExit("Aborted.")
|
||
|
|
|
||
|
|
with open(key_path, "rb") as f:
|
||
|
|
token = f.read()
|
||
|
|
|
||
|
|
print(decrypt_xprv(token))
|