Skip to content

Commit

Permalink
Merge branch 'main' into add-fees
Browse files Browse the repository at this point in the history
  • Loading branch information
callebtc committed Apr 15, 2024
2 parents 72ff760 + bdaed84 commit cdc5908
Show file tree
Hide file tree
Showing 14 changed files with 130 additions and 225 deletions.
29 changes: 13 additions & 16 deletions cashu/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,6 @@ def __init__(
first_seen=None,
active=True,
input_fee_ppm=None,
use_deprecated_id=False, # BACKWARDS COMPATIBILITY < 0.15.0
):
self.valid_from = valid_from
self.valid_to = valid_to
Expand All @@ -433,19 +432,10 @@ def __init__(
else:
self.id = id

# BEGIN BACKWARDS COMPATIBILITY < 0.15.0
if use_deprecated_id:
logger.warning(
"Using deprecated keyset id derivation for backwards compatibility <"
" 0.15.0"
)
self.id = derive_keyset_id_deprecated(self.public_keys)
# END BACKWARDS COMPATIBILITY < 0.15.0

self.unit = Unit[unit]

logger.trace(f"Derived keyset id {self.id} from public keys.")
if id and id != self.id and use_deprecated_id:
if id and id != self.id:
logger.warning(
f"WARNING: Keyset id {self.id} does not match the given id {id}."
" Overwriting."
Expand Down Expand Up @@ -501,8 +491,6 @@ class MintKeyset:
version: Optional[str] = None
input_fee_ppm: Optional[int] = None

duplicate_keyset_id: Optional[str] = None # BACKWARDS COMPATIBILITY < 0.15.0

def __init__(
self,
*,
Expand Down Expand Up @@ -585,6 +573,12 @@ def generate_keys(self):
assert self.seed, "seed not set"
assert self.derivation_path, "derivation path not set"

# we compute the keyset id from the public keys only if it is not
# loaded from the database. This is to allow for backwards compatibility
# with old keysets with new id's and vice versa. This code can be removed
# if there are only new keysets in the mint (> 0.15.0)
id_in_db = self.id

if self.version_tuple < (0, 12):
# WARNING: Broken key derivation for backwards compatibility with < 0.12
self.private_keys = derive_keys_backwards_compatible_insecure_pre_0_12(
Expand All @@ -595,19 +589,22 @@ def generate_keys(self):
f"WARNING: Using weak key derivation for keyset {self.id} (backwards"
" compatibility < 0.12)"
)
self.id = derive_keyset_id_deprecated(self.public_keys) # type: ignore
# load from db or derive
self.id = id_in_db or derive_keyset_id_deprecated(self.public_keys) # type: ignore
elif self.version_tuple < (0, 15):
self.private_keys = derive_keys_sha256(self.seed, self.derivation_path)
logger.trace(
f"WARNING: Using non-bip32 derivation for keyset {self.id} (backwards"
" compatibility < 0.15)"
)
self.public_keys = derive_pubkeys(self.private_keys) # type: ignore
self.id = derive_keyset_id_deprecated(self.public_keys) # type: ignore
# load from db or derive
self.id = id_in_db or derive_keyset_id_deprecated(self.public_keys) # type: ignore
else:
self.private_keys = derive_keys(self.seed, self.derivation_path)
self.public_keys = derive_pubkeys(self.private_keys) # type: ignore
self.id = derive_keyset_id(self.public_keys) # type: ignore
# load from db or derive
self.id = id_in_db or derive_keyset_id(self.public_keys) # type: ignore


# ------- TOKEN -------
Expand Down
11 changes: 1 addition & 10 deletions cashu/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class MintSettings(CashuSettings):
mint_private_key: str = Field(default=None)
mint_seed_decryption_key: Optional[str] = Field(default=None)
mint_derivation_path: str = Field(default="m/0'/0'/0'")
mint_derivation_path_list: List[str] = Field(default=[])
mint_derivation_path_list: List[str] = Field(default=[""])
mint_listen_host: str = Field(default="127.0.0.1")
mint_listen_port: int = Field(default=3338)

Expand All @@ -62,14 +62,6 @@ class MintSettings(CashuSettings):
default={"sat": {"fee": 1, "batch": 10}}
)
mint_max_secret_length: int = Field(default=512)
mint_duplicate_old_keysets: bool = Field(
default=True,
title="Duplicate keysets",
description=(
"Whether to duplicate keysets for backwards compatibility before v1 API"
" (Nutshell 0.15.0)."
),
)


class MintBackends(MintSettings):
Expand Down Expand Up @@ -138,7 +130,6 @@ class MintInformation(CashuSettings):
mint_info_description: str = Field(default=None)
mint_info_description_long: str = Field(default=None)
mint_info_contact: List[List[str]] = Field(default=[["", ""]])
mint_info_nuts: List[str] = Field(default=["NUT-07", "NUT-08", "NUT-09"])
mint_info_motd: str = Field(default=None)


Expand Down
47 changes: 2 additions & 45 deletions cashu/mint/ledger.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import asyncio
import copy
import time
from typing import Dict, List, Mapping, Optional, Tuple

Expand All @@ -23,8 +22,6 @@
from ..core.crypto import b_dhke
from ..core.crypto.aes import AESCipher
from ..core.crypto.keys import (
derive_keyset_id,
derive_keyset_id_deprecated,
derive_pubkey,
random_hash,
)
Expand Down Expand Up @@ -233,29 +230,17 @@ async def activate_keyset(
# load the new keyset in self.keysets
self.keysets[keyset.id] = keyset

# BEGIN BACKWARDS COMPATIBILITY < 0.15.0
# set the deprecated id
if not keyset.public_keys:
raise KeysetError("no public keys for this keyset")
keyset.duplicate_keyset_id = derive_keyset_id_deprecated(keyset.public_keys)
# END BACKWARDS COMPATIBILITY < 0.15.0

logger.debug(f"Loaded keyset {keyset.id}")
return keyset

async def init_keysets(
self, autosave: bool = True, duplicate_keysets: Optional[bool] = None
) -> None:
async def init_keysets(self, autosave: bool = True) -> None:
"""Initializes all keysets of the mint from the db. Loads all past keysets from db
and generate their keys. Then activate the current keyset set by self.derivation_path.
Args:
autosave (bool, optional): Whether the current keyset should be saved if it is
not in the database yet. Will be passed to `self.activate_keyset` where it is
generated from `self.derivation_path`. Defaults to True.
duplicate_keysets (bool, optional): Whether to duplicate new keysets and compute
their old keyset id, and duplicate old keysets and compute their new keyset id.
Defaults to False.
"""
# load all past keysets from db, the keys will be generated at instantiation
tmp_keysets: List[MintKeyset] = await self.crud.get_keyset(db=self.db)
Expand All @@ -277,31 +262,6 @@ async def init_keysets(
if not any([k.active for k in self.keysets.values()]):
raise KeysetError("No active keyset found.")

# BEGIN BACKWARDS COMPATIBILITY < 0.15.0
# we duplicate new keysets and compute their old keyset id, and
# we duplicate old keysets and compute their new keyset id
if duplicate_keysets is not False and (
settings.mint_duplicate_old_keysets or duplicate_keysets
):
for _, keyset in copy.copy(self.keysets).items():
# if keyset.version_tuple >= (0, 15, 3) and not duplicate_keysets:
# # we do not duplicate keysets from version 0.15.3 and above if not forced by duplicate_keysets
# continue
keyset_copy = copy.copy(keyset)
if not keyset_copy.public_keys:
raise KeysetError("no public keys for this keyset")
if keyset.version_tuple >= (0, 15):
keyset_copy.id = derive_keyset_id_deprecated(
keyset_copy.public_keys
)
else:
keyset_copy.id = derive_keyset_id(keyset_copy.public_keys)
keyset_copy.duplicate_keyset_id = keyset.id
self.keysets[keyset_copy.id] = keyset_copy
# remember which keyset this keyset was duplicated from
logger.debug(f"Duplicated keyset id {keyset.id} -> {keyset_copy.id}")
# END BACKWARDS COMPATIBILITY < 0.15.0

def get_keyset(self, keyset_id: Optional[str] = None) -> Dict[int, str]:
"""Returns a dictionary of hex public keys of a specific keyset for each supported amount"""
if keyset_id and keyset_id not in self.keysets:
Expand Down Expand Up @@ -984,10 +944,7 @@ async def _generate_promises(
keyset = keyset or self.keysets[output.id]
if output.id not in self.keysets:
raise TransactionError(f"keyset {output.id} not found")
if output.id not in [
keyset.id,
keyset.duplicate_keyset_id,
]:
if output.id != keyset.id:
raise TransactionError("keyset id does not match output id")
if not keyset.active:
raise TransactionError("keyset is not active")
Expand Down
49 changes: 47 additions & 2 deletions cashu/mint/migrations.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from ..core.base import Proof
import copy

from ..core.base import MintKeyset, Proof
from ..core.crypto.keys import derive_keyset_id, derive_keyset_id_deprecated
from ..core.db import Connection, Database, table_with_schema, timestamp_now
from ..core.settings import settings

Expand Down Expand Up @@ -699,7 +702,7 @@ async def m017_foreign_keys_proof_tables(db: Database):
swap_id TEXT,
FOREIGN KEY (mint_quote) REFERENCES {table_with_schema(db, 'mint_quotes')}(quote),
UNIQUE (b_)
);
"""
Expand All @@ -718,3 +721,45 @@ async def m017_foreign_keys_proof_tables(db: Database):

# recreate indices
await m015_add_index_Y_to_proofs_used_and_pending(db)


async def m018_duplicate_deprecated_keyset_ids(db: Database):
async with db.connect() as conn:
rows = await conn.fetchall( # type: ignore
f"""
SELECT * from {table_with_schema(db, 'keysets')}
""",
)
keysets = [MintKeyset(**row) for row in rows]
duplicated_keysets: list[MintKeyset] = []
for keyset in keysets:
keyset_copy = copy.copy(keyset)
if not keyset_copy.public_keys:
raise Exception(f"keyset {keyset_copy.id} has no public keys")
if keyset.version_tuple < (0, 15):
keyset_copy.id = derive_keyset_id(keyset_copy.public_keys)
else:
keyset_copy.id = derive_keyset_id_deprecated(keyset_copy.public_keys)
duplicated_keysets.append(keyset_copy)

for keyset in duplicated_keysets:
await conn.execute(
f"""
INSERT INTO {table_with_schema(db, 'keysets')}
(id, derivation_path, valid_from, valid_to, first_seen, active, version, seed, unit, encrypted_seed, seed_encryption_method)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
keyset.id,
keyset.derivation_path,
keyset.valid_from,
keyset.valid_to,
keyset.first_seen,
keyset.active,
keyset.version,
keyset.seed,
keyset.unit.name,
keyset.encrypted_seed,
keyset.seed_encryption_method,
),
)
8 changes: 2 additions & 6 deletions cashu/mint/router_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ async def info() -> GetInfoResponse_deprecated:
description=settings.mint_info_description,
description_long=settings.mint_info_description_long,
contact=settings.mint_info_contact,
nuts=settings.mint_info_nuts,
nuts=["NUT-07", "NUT-08", "NUT-09"],
motd=settings.mint_info_motd,
parameter={
"max_peg_in": settings.mint_max_peg_in,
Expand Down Expand Up @@ -176,12 +176,8 @@ async def mint_deprecated(

# BEGIN BACKWARDS COMPATIBILITY < 0.15
# Mint expects "id" in outputs to know which keyset to use to sign them.
# use the deprecated version of the current keyset
assert ledger.keyset.duplicate_keyset_id
outputs: list[BlindedMessage] = [
BlindedMessage(
id=o.id or ledger.keyset.duplicate_keyset_id, **o.dict(exclude={"id"})
)
BlindedMessage(id=o.id or ledger.keyset.id, **o.dict(exclude={"id"}))
for o in payload.outputs
]
# END BACKWARDS COMPATIBILITY < 0.15
Expand Down
37 changes: 26 additions & 11 deletions cashu/wallet/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -1486,28 +1486,25 @@ async def balance_per_minturl(
balances_return[key]["unit"] = unit.name
return dict(sorted(balances_return.items(), key=lambda item: item[0])) # type: ignore

async def restore_wallet_from_mnemonic(
self, mnemonic: Optional[str], to: int = 2, batch: int = 25
async def restore_tokens_for_keyset(
self, keyset_id: str, to: int = 2, batch: int = 25
) -> None:
"""
Restores the wallet from a mnemonic.
Restores tokens for a given keyset_id.
Args:
mnemonic (Optional[str]): The mnemonic to restore the wallet from. If None, the mnemonic is loaded from the db.
keyset_id (str): The keyset_id to restore tokens for.
to (int, optional): The number of consecutive empty responses to stop restoring. Defaults to 2.
batch (int, optional): The number of proofs to restore in one batch. Defaults to 25.
"""
await self._init_private_key(mnemonic)
await self.load_mint()
print("Restoring tokens...")
stop_counter = 0
# we get the current secret counter and restore from there on
spendable_proofs = []
counter_before = await bump_secret_derivation(
db=self.db, keyset_id=self.keyset_id, by=0
db=self.db, keyset_id=keyset_id, by=0
)
if counter_before != 0:
print("This wallet has already been used. Restoring from it's last state.")
print("Keyset has already been used. Restoring from it's last state.")
i = counter_before
n_last_restored_proofs = 0
while stop_counter < to:
Expand All @@ -1528,16 +1525,34 @@ async def restore_wallet_from_mnemonic(
logger.debug(f"Reverting secret counter by {revert_counter_by}")
before = await bump_secret_derivation(
db=self.db,
keyset_id=self.keyset_id,
keyset_id=keyset_id,
by=-revert_counter_by,
)
logger.debug(
f"Secret counter reverted from {before} to {before - revert_counter_by}"
)
if n_last_restored_proofs == 0:
print("No tokens restored.")
print("No tokens restored for keyset.")
return

async def restore_wallet_from_mnemonic(
self, mnemonic: Optional[str], to: int = 2, batch: int = 25
) -> None:
"""
Restores the wallet from a mnemonic.
Args:
mnemonic (Optional[str]): The mnemonic to restore the wallet from. If None, the mnemonic is loaded from the db.
to (int, optional): The number of consecutive empty responses to stop restoring. Defaults to 2.
batch (int, optional): The number of proofs to restore in one batch. Defaults to 25.
"""
await self._init_private_key(mnemonic)
await self.load_mint()
print("Restoring tokens...")
keyset_ids = self.mint_keyset_ids
for keyset_id in keyset_ids:
await self.restore_tokens_for_keyset(keyset_id, to, batch)

async def restore_promises_from_to(
self, from_counter: int, to_counter: int
) -> List[Proof]:
Expand Down
5 changes: 1 addition & 4 deletions cashu/wallet/wallet_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,7 @@ async def _get_keys_deprecated(self, url: str) -> WalletKeyset:
int(amt): PublicKey(bytes.fromhex(val), raw=True)
for amt, val in keys.items()
}
keyset = WalletKeyset(
unit="sat", public_keys=keyset_keys, mint_url=url, use_deprecated_id=True
)
keyset = WalletKeyset(unit="sat", public_keys=keyset_keys, mint_url=url)
return keyset

@async_set_httpx_client
Expand Down Expand Up @@ -201,7 +199,6 @@ async def _get_keys_of_keyset_deprecated(
id=keyset_id,
public_keys=keyset_keys,
mint_url=url,
use_deprecated_id=True,
)
return keyset

Expand Down
Loading

0 comments on commit cdc5908

Please sign in to comment.