wallet.py (3325B)
1 import argparse 2 import getpass 3 import logging 4 import os 5 from pathlib import Path 6 from nacl.signing import SigningKey 7 from nacl.secret import SecretBox 8 from nacl.pwhash import argon2i 9 from whee.valkey import ValkeyStore 10 from whee.fs import FsStore 11 from xdg_base_dirs import xdg_data_home 12 13 from usawa.store import KeyStore 14 from usawa.crypto import DemoWallet 15 from usawa.config import load_config 16 17 logging.basicConfig(level=logging.WARNING) 18 logg = logging.getLogger() 19 20 PRIVATEKEY_FILE = "privatekey.box" 21 PUBLICKEY_FILE = "publickey.bin" 22 23 # TODO: change to xdg_data_dir 24 DEFAULT_WALLET_DIR = ( 25 Path(os.environ.get("XDG_DATA_HOME", Path.home() / ".local/share")) / "usawa" 26 ) 27 28 29 def encrypt_seed(seed: bytes, passphrase: str) -> bytes: 30 salt = os.urandom(argon2i.SALTBYTES) 31 key = argon2i.kdf( 32 SecretBox.KEY_SIZE, 33 passphrase.encode(), 34 salt, 35 ) 36 box = SecretBox(key) 37 encrypted = box.encrypt(seed) 38 39 # Prepend salt so we can re-derive the key on decrypt 40 return salt + encrypted 41 42 43 def setup_wallet(cfg, wallet_dir=None): 44 db = None 45 store_type = cfg.get('STORE_TYPE') 46 if store_type == 'valkey': 47 db = ValkeyStore( 48 "", 49 host=cfg.get("VALKEY_HOST"), 50 port=cfg.get("VALKEY_PORT"), 51 ) 52 elif store_type == 'fs': 53 db = FsStore( 54 base=cfg.get('FSSTORE_BASE', xdg_data_home()), 55 dbname='usawa', 56 ) 57 else: 58 raise ValueError('invalid store type: ' + store_type) 59 60 store = KeyStore(db) 61 62 wallet_dir = Path(wallet_dir) if wallet_dir else DEFAULT_WALLET_DIR 63 wallet_dir.mkdir(parents=True, exist_ok=True) 64 logg.info("wallet directory: %s", wallet_dir) 65 66 privatekey_path = wallet_dir / PRIVATEKEY_FILE 67 publickey_path = wallet_dir / PUBLICKEY_FILE 68 69 passphrase = getpass.getpass("Enter wallet passphrase: ") 70 passphrase_confirm = getpass.getpass("Confirm wallet passphrase: ") 71 if passphrase != passphrase_confirm: 72 logg.error("passphrases do not match") 73 return 1 74 75 random_bytes = os.urandom(32) 76 logg.debug("generated 32-byte seed") 77 78 encrypted = encrypt_seed(random_bytes, passphrase) 79 with open(privatekey_path, "wb") as f: 80 f.write(encrypted) 81 logg.info("encrypted key material saved to: %s", privatekey_path) 82 83 sk = SigningKey(random_bytes) 84 pk = sk.verify_key 85 with open(publickey_path, "wb") as f: 86 f.write(pk.encode()) 87 logg.info("public key saved to: %s", publickey_path) 88 ops = int(cfg.get('WALLET_OPSLIMIT', 0)) 89 mem = int(cfg.get('WALLET_MEMLIMIT', 0)) 90 91 wallet = DemoWallet(privatekey=random_bytes) 92 store.add_key(wallet, passphrase=passphrase_confirm, opslimit=ops, memlimit=mem) 93 logg.info("key written to store") 94 95 logg.info("setup complete.") 96 logg.info("your 32-byte public key (hex): %s", pk.encode().hex()) 97 98 return 0 99 100 101 def main(): 102 argp = argparse.ArgumentParser() 103 argp.add_argument('-c', type=str, help='override config dir') 104 argp.add_argument('-v', type=str, choices=['info','debug','warning','error'], help='be verbose') 105 args = argp.parse_args() 106 107 if args.v: 108 logg.setLevel(getattr(logging, args.v.upper())) 109 110 cfg = load_config(config_dir=args.c) 111 setup_wallet(cfg) 112 113 114 if __name__ == '__main__': 115 main()