Files
nutshell/cashu/mint/encrypt.py
callebtc 6a0a370ba5 Mint: table locks (#566)
* clean up db

* db: table lock

* db.table_with_schema

* fix encrypt.py

* postgres nowait

* add timeout to lock

* melt quote state in db

* kinda working

* kinda working with postgres

* remove dispose

* getting there

* porperly clean up db for tests

* faster tests

* configure connection pooling

* try github with connection pool

* invoice dispatcher does not lock db

* fakewallet: pay_if_regtest waits

* pay fakewallet invoices

* add more

* faster

* slower

* pay_if_regtest async

* do not lock the invoice dispatcher

* test: do I get disk I/O errors if we disable the invoice_callback_dispatcher?

* fix fake so it workss without a callback dispatchert

* test on github

* readd tasks

* refactor

* increase time for lock invoice disatcher

* try avoiding a race

* remove task

* github actions: test regtest with postgres

* mint per module

* no connection pool for testing

* enable pool

* do not resend paid event

* reuse connection

* close db connections

* sessions

* enable debug

* dispose engine

* disable connection pool for tests

* enable connection pool for postgres only

* clean up shutdown routine

* remove wait for lightning fakewallet lightning invoice

* cancel invoice listener tasks on shutdown

* fakewallet conftest: decrease outgoing delay

* delay payment and set postgres only if needed

* disable fail fast for regtest

* clean up regtest.yml

* change order of tests_db.py

* row-specific mint_quote locking

* refactor

* fix lock statement

* refactor swap

* refactor

* remove psycopg2

* add connection string example to .env.example

* remove unnecessary pay

* shorter sleep in test_wallet_subscription_swap
2024-07-08 18:05:57 +02:00

165 lines
5.5 KiB
Python

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