import click try: from ..core.crypto.aes import AESCipher except ImportError: # for the CLI to work from cashu.core.crypto.aes import AESCipher import asyncio from functools import wraps from cashu.core.db import Database from cashu.core.migrations import migrate_databases from cashu.core.settings import settings from cashu.mint import migrations from cashu.mint.crud import LedgerCrudSqlite from cashu.mint.ledger import Ledger # https://github.com/pallets/click/issues/85#issuecomment-503464628 def coro(f): @wraps(f) def wrapper(*args, **kwargs): return asyncio.run(f(*args, **kwargs)) return wrapper @click.group() def cli(): """Ledger Decrypt CLI""" pass @cli.command() @click.option("--message", prompt=True, help="The message to encrypt.") @click.option( "--key", prompt=True, hide_input=True, confirmation_prompt=True, help="The encryption key.", ) def encrypt(message, key): """Encrypt a message.""" aes = AESCipher(key) encrypted_message = aes.encrypt(message.encode()) click.echo(f"Encrypted message: {encrypted_message}") @cli.command() @click.option("--encrypted", prompt=True, help="The encrypted message to decrypt.") @click.option( "--key", prompt=True, hide_input=True, help="The decryption key.", ) def decrypt(encrypted, key): """Decrypt a message.""" aes = AESCipher(key) decrypted_message = aes.decrypt(encrypted) click.echo(f"Decrypted message: {decrypted_message}") # command to migrate the database to encrypted seeds @cli.command() @coro @click.option("--no-dry-run", is_flag=True, help="Dry run.", default=False) async def migrate(no_dry_run): """Migrate the database to encrypted seeds.""" click.echo(f"Database: directory: {settings.mint_database}") assert settings.mint_seed_decryption_key, "MINT_SEED_DECRYPTION_KEY not set." assert ( len(settings.mint_seed_decryption_key) > 12 ), "MINT_SEED_DECRYPTION_KEY is too short, must be at least 12 characters." click.echo( "Decryption key:" f" {settings.mint_seed_decryption_key[0]}{'*'*10}{settings.mint_seed_decryption_key[-1]}" ) click.echo( f"Seed: {settings.mint_private_key[0]}{'*'*10}{settings.mint_private_key[-1]}" ) ledger = Ledger( db=Database("mint", settings.mint_database), seed=settings.mint_private_key, derivation_path=settings.mint_derivation_path, backends={}, crud=LedgerCrudSqlite(), ) aes = AESCipher(settings.mint_seed_decryption_key) click.echo("Making sure that db is migrated to latest version first.") await migrate_databases(ledger.db, migrations) # get all keysets async with ledger.db.connect() as conn: rows = await conn.fetchall( f"SELECT * FROM {ledger.db.table_with_schema('keysets')} WHERE seed IS NOT" " NULL" ) click.echo(f"Found {len(rows)} keysets in database.") keysets_all = [dict(**row) for row in rows] keysets_migrate = [] # encrypt the seeds for keyset_dict in keysets_all: if keyset_dict["seed"] and not keyset_dict["encrypted_seed"]: keyset_dict["encrypted_seed"] = aes.encrypt(keyset_dict["seed"].encode()) keyset_dict["seed_encryption_method"] = "aes" keysets_migrate.append(keyset_dict) else: click.echo(f"Skipping keyset {keyset_dict['id']}: already migrated.") click.echo(f"There are {len(keysets_migrate)} keysets to migrate.") for keyset_dict in keysets_migrate: click.echo(f"Keyset {keyset_dict['id']}") click.echo(f" Encrypted seed: {keyset_dict['encrypted_seed']}") click.echo(f" Encryption method: {keyset_dict['seed_encryption_method']}") decryption_success_str = ( "✅" if aes.decrypt(keyset_dict["encrypted_seed"]) == keyset_dict["seed"] else "❌" ) click.echo(f" Seed decryption test: {decryption_success_str}") if not no_dry_run: click.echo( "This was a dry run. Use --no-dry-run to apply the changes to the database." ) if no_dry_run and keysets_migrate: click.confirm( "Are you sure you want to continue? Before you continue, make sure to have" " a backup of your keysets database table.", abort=True, ) click.echo("Updating keysets in the database.") async with ledger.db.connect() as conn: for keyset_dict in keysets_migrate: click.echo(f"Updating keyset {keyset_dict['id']}") await conn.execute( f"UPDATE {ledger.db.table_with_schema('keysets')} SET seed=''," " encrypted_seed = ?, seed_encryption_method = ? WHERE id = ?", ( keyset_dict["encrypted_seed"], keyset_dict["seed_encryption_method"], keyset_dict["id"], ), ) click.echo("Initializing mint with encrypted seeds.") encrypted_mint_private_key = aes.encrypt(settings.mint_private_key.encode()) ledger = Ledger( db=Database("mint", settings.mint_database), seed=encrypted_mint_private_key, seed_decryption_key=settings.mint_seed_decryption_key, derivation_path=settings.mint_derivation_path, backends={}, crud=LedgerCrudSqlite(), ) click.echo("✅ Migration complete.") if __name__ == "__main__": cli()