Files
plm-lottery/app/wallet/hd.py
T
davideandClaude Sonnet 5 f1261584ff Add wallet key derivation and PSBT building
BIP84 derivation of per-user P2WPKH addresses and the pool address from
the encrypted master xprv (app/wallet/hd.py, keystore.py), the PLM
mainnet chain params (plm_network.py), and PSBT construction for bets/
payouts/withdrawals with change-output and fee-estimation logic
(psbt_builder.py).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 10:25:29 +02:00

53 lines
1.9 KiB
Python

import os
from embit import script
from embit.bip32 import HDKey
from app.config import settings
from app.wallet.keystore import decrypt_xprv, encrypt_xprv
from app.wallet.plm_network import ACCOUNT_PATH, PLM_MAINNET
_account_key: HDKey | None = None
def generate_master_key(overwrite: bool = False) -> None:
"""One-time ops bootstrap: create a random master seed, encrypt it, write it to
disk. Not exposed via any API endpoint — run manually before first launch."""
if os.path.exists(settings.master_key_path) and not overwrite:
raise FileExistsError(f"{settings.master_key_path} already exists")
root = HDKey.from_seed(os.urandom(32), version=PLM_MAINNET["xprv"])
with open(settings.master_key_path, "wb") as f:
f.write(encrypt_xprv(root.to_base58(version=PLM_MAINNET["xprv"])))
def _load_account_key() -> HDKey:
global _account_key
if _account_key is None:
with open(settings.master_key_path, "rb") as f:
token = f.read()
root = HDKey.from_base58(decrypt_xprv(token))
_account_key = root.derive(ACCOUNT_PATH)
return _account_key
def derive_user_key(derivation_index: int) -> HDKey:
return _load_account_key().derive(f"0/{derivation_index}")
def derive_user_address(derivation_index: int) -> str:
pub = derive_user_key(derivation_index).to_public()
return script.p2wpkh(pub).address(network=PLM_MAINNET)
def derive_pool_key() -> HDKey:
"""The "indirizzo padre" from the flowchart: all bets are sent here, and
payouts are signed with this key. Reserved on branch 1 of the account (branch 0
is user addresses), index 0 — not a spec requirement, an implementation choice
to keep it in the same encrypted master key rather than a separate secret."""
return _load_account_key().derive("1/0")
def derive_pool_address() -> str:
pub = derive_pool_key().to_public()
return script.p2wpkh(pub).address(network=PLM_MAINNET)