usawa

Signed, immutable accounting.
Log | Files | Refs | Submodules | LICENSE

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()