mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-20 18:44:20 +01:00
multiple keysets per mint
This commit is contained in:
@@ -29,14 +29,8 @@ class KeyBase(BaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
from typing import Optional
|
class WalletKeyset:
|
||||||
|
|
||||||
from cashu.core.db import Connection, Database
|
|
||||||
|
|
||||||
|
|
||||||
class Keyset:
|
|
||||||
id: str
|
id: str
|
||||||
private_keys: Dict[int, PrivateKey]
|
|
||||||
public_keys: Dict[int, PublicKey]
|
public_keys: Dict[int, PublicKey]
|
||||||
mint_url: Union[str, None] = None
|
mint_url: Union[str, None] = None
|
||||||
valid_from: Union[str, None] = None
|
valid_from: Union[str, None] = None
|
||||||
@@ -46,17 +40,24 @@ class Keyset:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
seed: Union[None, str] = None,
|
pubkeys: Dict[int, PublicKey] = None,
|
||||||
derivation_path: str = "0",
|
mint_url=None,
|
||||||
pubkeys: Union[None, Dict[int, PublicKey]] = None,
|
id=None,
|
||||||
|
valid_from=None,
|
||||||
|
valid_to=None,
|
||||||
|
first_seen=None,
|
||||||
|
active=None,
|
||||||
):
|
):
|
||||||
if seed:
|
self.id = id
|
||||||
self.private_keys = derive_keys(seed, derivation_path)
|
self.valid_from = valid_from
|
||||||
self.public_keys = derive_pubkeys(self.private_keys)
|
self.valid_to = valid_to
|
||||||
|
self.first_seen = first_seen
|
||||||
|
self.active = active
|
||||||
|
self.mint_url = mint_url
|
||||||
if pubkeys:
|
if pubkeys:
|
||||||
self.public_keys = pubkeys
|
self.public_keys = pubkeys
|
||||||
self.id = derive_keyset_id(self.public_keys)
|
self.id = derive_keyset_id(self.public_keys)
|
||||||
logger.debug(f"Mint keyset id: {self.id}")
|
logger.debug(f"Wallet keyset id: {self.id}")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_row(cls, row: Row):
|
def from_row(cls, row: Row):
|
||||||
@@ -71,6 +72,56 @@ class Keyset:
|
|||||||
active=row[5],
|
active=row[5],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MintKeyset:
|
||||||
|
id: str
|
||||||
|
derivation_path: str
|
||||||
|
private_keys: Dict[int, PrivateKey]
|
||||||
|
public_keys: Dict[int, PublicKey] = None
|
||||||
|
valid_from: Union[str, None] = None
|
||||||
|
valid_to: Union[str, None] = None
|
||||||
|
first_seen: Union[str, None] = None
|
||||||
|
active: bool = True
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
id=None,
|
||||||
|
valid_from=None,
|
||||||
|
valid_to=None,
|
||||||
|
first_seen=None,
|
||||||
|
active=None,
|
||||||
|
seed: Union[None, str] = None,
|
||||||
|
derivation_path: str = "0",
|
||||||
|
):
|
||||||
|
self.derivation_path = derivation_path
|
||||||
|
self.id = id
|
||||||
|
self.valid_from = valid_from
|
||||||
|
self.valid_to = valid_to
|
||||||
|
self.first_seen = first_seen
|
||||||
|
self.active = active
|
||||||
|
# generate keys from seed
|
||||||
|
if seed:
|
||||||
|
self.generate_keys(seed)
|
||||||
|
|
||||||
|
def generate_keys(self, seed):
|
||||||
|
self.private_keys = derive_keys(seed, self.derivation_path)
|
||||||
|
self.public_keys = derive_pubkeys(self.private_keys)
|
||||||
|
self.id = derive_keyset_id(self.public_keys)
|
||||||
|
logger.debug(f"Mint keyset id: {self.id}")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_row(cls, row: Row):
|
||||||
|
if row is None:
|
||||||
|
return cls
|
||||||
|
return cls(
|
||||||
|
id=row[0],
|
||||||
|
derivation_path=row[1],
|
||||||
|
valid_from=row[2],
|
||||||
|
valid_to=row[3],
|
||||||
|
first_seen=row[4],
|
||||||
|
active=row[5],
|
||||||
|
)
|
||||||
|
|
||||||
def get_keybase(self):
|
def get_keybase(self):
|
||||||
return {
|
return {
|
||||||
k: KeyBase(id=self.id, amount=k, pubkey=v.serialize().hex())
|
k: KeyBase(id=self.id, amount=k, pubkey=v.serialize().hex())
|
||||||
@@ -78,6 +129,16 @@ class Keyset:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MintKeysets:
|
||||||
|
keysets: Dict[str, MintKeyset]
|
||||||
|
|
||||||
|
def __init__(self, keysets: List[MintKeyset]):
|
||||||
|
self.keysets: Dict[str, MintKeyset] = {k.id: k for k in keysets}
|
||||||
|
|
||||||
|
def get_ids(self):
|
||||||
|
return [k for k, _ in self.keysets.items()]
|
||||||
|
|
||||||
|
|
||||||
class P2SHScript(BaseModel):
|
class P2SHScript(BaseModel):
|
||||||
script: str
|
script: str
|
||||||
signature: str
|
signature: str
|
||||||
|
|||||||
@@ -1,74 +1,74 @@
|
|||||||
from typing import Optional
|
# from typing import Optional
|
||||||
|
|
||||||
from cashu.core.base import KeyBase, Keyset
|
# from cashu.core.base import KeyBase, Keyset
|
||||||
from cashu.core.db import Connection, Database
|
# from cashu.core.db import Connection, Database
|
||||||
|
|
||||||
|
|
||||||
async def store_keyset(
|
# async def store_keyset(
|
||||||
keyset: Keyset,
|
# keyset: Keyset,
|
||||||
mint_url: str = None,
|
# mint_url: str = None,
|
||||||
db: Database = None,
|
# db: Database = None,
|
||||||
conn: Optional[Connection] = None,
|
# conn: Optional[Connection] = None,
|
||||||
):
|
# ):
|
||||||
|
|
||||||
await (conn or db).execute(
|
# await (conn or db).execute(
|
||||||
"""
|
# """
|
||||||
INSERT INTO keysets
|
# INSERT INTO keysets
|
||||||
(id, mint_url, valid_from, valid_to, first_seen, active)
|
# (id, mint_url, valid_from, valid_to, first_seen, active)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
# VALUES (?, ?, ?, ?, ?, ?)
|
||||||
""",
|
# """,
|
||||||
(
|
# (
|
||||||
keyset.id,
|
# keyset.id,
|
||||||
mint_url or keyset.mint_url,
|
# mint_url or keyset.mint_url,
|
||||||
keyset.valid_from,
|
# keyset.valid_from,
|
||||||
keyset.valid_to,
|
# keyset.valid_to,
|
||||||
keyset.first_seen,
|
# keyset.first_seen,
|
||||||
True,
|
# True,
|
||||||
),
|
# ),
|
||||||
)
|
# )
|
||||||
|
|
||||||
|
|
||||||
async def get_keyset(
|
# async def get_keyset(
|
||||||
id: str = None,
|
# id: str = None,
|
||||||
mint_url: str = None,
|
# mint_url: str = None,
|
||||||
db: Database = None,
|
# db: Database = None,
|
||||||
conn: Optional[Connection] = None,
|
# conn: Optional[Connection] = None,
|
||||||
):
|
# ):
|
||||||
clauses = []
|
# clauses = []
|
||||||
values = []
|
# values = []
|
||||||
clauses.append("active = ?")
|
# clauses.append("active = ?")
|
||||||
values.append(True)
|
# values.append(True)
|
||||||
if id:
|
# if id:
|
||||||
clauses.append("id = ?")
|
# clauses.append("id = ?")
|
||||||
values.append(id)
|
# values.append(id)
|
||||||
if mint_url:
|
# if mint_url:
|
||||||
clauses.append("mint_url = ?")
|
# clauses.append("mint_url = ?")
|
||||||
values.append(mint_url)
|
# values.append(mint_url)
|
||||||
where = ""
|
# where = ""
|
||||||
if clauses:
|
# if clauses:
|
||||||
where = f"WHERE {' AND '.join(clauses)}"
|
# where = f"WHERE {' AND '.join(clauses)}"
|
||||||
|
|
||||||
row = await (conn or db).fetchone(
|
# row = await (conn or db).fetchone(
|
||||||
f"""
|
# f"""
|
||||||
SELECT * from keysets
|
# SELECT * from keysets
|
||||||
{where}
|
# {where}
|
||||||
""",
|
# """,
|
||||||
tuple(values),
|
# tuple(values),
|
||||||
)
|
# )
|
||||||
return Keyset.from_row(row) if row is not None else None
|
# return Keyset.from_row(row) if row is not None else None
|
||||||
|
|
||||||
|
|
||||||
async def store_mint_pubkey(
|
# async def store_mint_pubkey(
|
||||||
key: KeyBase,
|
# key: KeyBase,
|
||||||
db: Database,
|
# db: Database,
|
||||||
conn: Optional[Connection] = None,
|
# conn: Optional[Connection] = None,
|
||||||
):
|
# ):
|
||||||
|
|
||||||
await (conn or db).execute(
|
# await (conn or db).execute(
|
||||||
"""
|
# """
|
||||||
INSERT INTO mint_pubkeys
|
# INSERT INTO mint_pubkeys
|
||||||
(id, amount, pubkey)
|
# (id, amount, pubkey)
|
||||||
VALUES (?, ?, ?)
|
# VALUES (?, ?, ?)
|
||||||
""",
|
# """,
|
||||||
(key.id, key.amount, key.pubkey),
|
# (key.id, key.amount, key.pubkey),
|
||||||
)
|
# )
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from cashu.core.base import Invoice, Proof
|
from cashu.core.base import Invoice, Proof, MintKeyset
|
||||||
from cashu.core.db import Connection, Database
|
from cashu.core.db import Connection, Database
|
||||||
|
|
||||||
|
|
||||||
@@ -110,3 +110,57 @@ async def update_lightning_invoice(
|
|||||||
hash,
|
hash,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def store_keyset(
|
||||||
|
keyset: MintKeyset,
|
||||||
|
mint_url: str = None,
|
||||||
|
db: Database = None,
|
||||||
|
conn: Optional[Connection] = None,
|
||||||
|
):
|
||||||
|
|
||||||
|
await (conn or db).execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO keysets
|
||||||
|
(id, derivation_path, valid_from, valid_to, first_seen, active)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
keyset.id,
|
||||||
|
keyset.derivation_path,
|
||||||
|
keyset.valid_from,
|
||||||
|
keyset.valid_to,
|
||||||
|
keyset.first_seen,
|
||||||
|
True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_keyset(
|
||||||
|
id: str = None,
|
||||||
|
derivation_path: str = None,
|
||||||
|
db: Database = None,
|
||||||
|
conn: Optional[Connection] = None,
|
||||||
|
):
|
||||||
|
clauses = []
|
||||||
|
values = []
|
||||||
|
clauses.append("active = ?")
|
||||||
|
values.append(True)
|
||||||
|
if id:
|
||||||
|
clauses.append("id = ?")
|
||||||
|
values.append(id)
|
||||||
|
if derivation_path:
|
||||||
|
clauses.append("derivation_path = ?")
|
||||||
|
values.append(derivation_path)
|
||||||
|
where = ""
|
||||||
|
if clauses:
|
||||||
|
where = f"WHERE {' AND '.join(clauses)}"
|
||||||
|
|
||||||
|
rows = await (conn or db).fetchall(
|
||||||
|
f"""
|
||||||
|
SELECT * from keysets
|
||||||
|
{where}
|
||||||
|
""",
|
||||||
|
tuple(values),
|
||||||
|
)
|
||||||
|
return [MintKeyset.from_row(row) for row in rows]
|
||||||
|
|||||||
@@ -3,12 +3,18 @@ Implementation of https://gist.github.com/phyro/935badc682057f418842c72961cf096c
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import math
|
import math
|
||||||
from typing import List, Set
|
from typing import List, Set, Dict
|
||||||
|
from loguru import logger
|
||||||
import cashu.core.b_dhke as b_dhke
|
import cashu.core.b_dhke as b_dhke
|
||||||
import cashu.core.bolt11 as bolt11
|
import cashu.core.bolt11 as bolt11
|
||||||
from cashu.core.base import BlindedMessage, BlindedSignature, Invoice, Keyset, Proof
|
from cashu.core.base import (
|
||||||
from cashu.core.crud import get_keyset, store_keyset
|
BlindedMessage,
|
||||||
|
BlindedSignature,
|
||||||
|
Invoice,
|
||||||
|
MintKeyset,
|
||||||
|
MintKeysets,
|
||||||
|
Proof,
|
||||||
|
)
|
||||||
from cashu.core.crypto import derive_keys, derive_keyset_id, derive_pubkeys
|
from cashu.core.crypto import derive_keys, derive_keyset_id, derive_pubkeys
|
||||||
from cashu.core.db import Database
|
from cashu.core.db import Database
|
||||||
from cashu.core.helpers import fee_reserve
|
from cashu.core.helpers import fee_reserve
|
||||||
@@ -24,6 +30,8 @@ from cashu.mint.crud import (
|
|||||||
store_lightning_invoice,
|
store_lightning_invoice,
|
||||||
store_promise,
|
store_promise,
|
||||||
update_lightning_invoice,
|
update_lightning_invoice,
|
||||||
|
get_keyset,
|
||||||
|
store_keyset,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -31,15 +39,26 @@ class Ledger:
|
|||||||
def __init__(self, secret_key: str, db: str):
|
def __init__(self, secret_key: str, db: str):
|
||||||
self.proofs_used: Set[str] = set()
|
self.proofs_used: Set[str] = set()
|
||||||
self.master_key = secret_key
|
self.master_key = secret_key
|
||||||
self.keyset = Keyset(self.master_key)
|
self.keyset = MintKeyset(seed=self.master_key, derivation_path="1")
|
||||||
self.db: Database = Database("mint", db)
|
self.db: Database = Database("mint", db)
|
||||||
|
|
||||||
async def load_used_proofs(self):
|
async def load_used_proofs(self):
|
||||||
self.proofs_used = set(await get_proofs_used(db=self.db))
|
self.proofs_used = set(await get_proofs_used(db=self.db))
|
||||||
|
|
||||||
async def store_keyset(self):
|
async def init_keysets(self):
|
||||||
keyset_local: Keyset = await get_keyset(self.keyset.id, db=self.db)
|
"""Loads all past keysets and stores the active one if not already in db"""
|
||||||
if keyset_local is None:
|
# get all past keysets
|
||||||
|
tmp_keysets: List[MintKeyset] = await get_keyset(db=self.db)
|
||||||
|
self.keysets = MintKeysets(tmp_keysets)
|
||||||
|
for _, v in self.keysets.keysets.items():
|
||||||
|
v.generate_keys(self.master_key)
|
||||||
|
if len(self.keysets.keysets):
|
||||||
|
logger.debug(f"Loaded {len(self.keysets.keysets)} keysets from db.")
|
||||||
|
current_keyset_local: List[MintKeyset] = await get_keyset(
|
||||||
|
id=self.keyset.id, db=self.db
|
||||||
|
)
|
||||||
|
if not len(current_keyset_local):
|
||||||
|
logger.debug(f"Storing keyset {self.keyset.id}")
|
||||||
await store_keyset(keyset=self.keyset, db=self.db)
|
await store_keyset(keyset=self.keyset, db=self.db)
|
||||||
|
|
||||||
async def _generate_promises(self, amounts: List[int], B_s: List[str]):
|
async def _generate_promises(self, amounts: List[int], B_s: List[str]):
|
||||||
@@ -76,9 +95,12 @@ class Ledger:
|
|||||||
"""Verifies that the proof of promise was issued by this ledger."""
|
"""Verifies that the proof of promise was issued by this ledger."""
|
||||||
if not self._check_spendable(proof):
|
if not self._check_spendable(proof):
|
||||||
raise Exception(f"tokens already spent. Secret: {proof.secret}")
|
raise Exception(f"tokens already spent. Secret: {proof.secret}")
|
||||||
secret_key = self.keyset.private_keys[
|
# if no keyset id is given in proof, assume the current one
|
||||||
proof.amount
|
if not proof.id:
|
||||||
] # Get the correct key to check against
|
secret_key = self.keyset.private_keys[proof.amount]
|
||||||
|
else:
|
||||||
|
secret_key = self.keysets.keysets[proof.id].private_keys[proof.amount]
|
||||||
|
|
||||||
C = PublicKey(bytes.fromhex(proof.C), raw=True)
|
C = PublicKey(bytes.fromhex(proof.C), raw=True)
|
||||||
return b_dhke.verify(secret_key, C, proof.secret)
|
return b_dhke.verify(secret_key, C, proof.secret)
|
||||||
|
|
||||||
|
|||||||
@@ -95,13 +95,13 @@ async def m003_mint_keysets(db: Database):
|
|||||||
f"""
|
f"""
|
||||||
CREATE TABLE IF NOT EXISTS keysets (
|
CREATE TABLE IF NOT EXISTS keysets (
|
||||||
id TEXT NOT NULL,
|
id TEXT NOT NULL,
|
||||||
mint_url TEXT,
|
derivation_path TEXT,
|
||||||
valid_from TIMESTAMP DEFAULT {db.timestamp_now},
|
valid_from TIMESTAMP DEFAULT {db.timestamp_now},
|
||||||
valid_to TIMESTAMP DEFAULT {db.timestamp_now},
|
valid_to TIMESTAMP DEFAULT {db.timestamp_now},
|
||||||
first_seen TIMESTAMP DEFAULT {db.timestamp_now},
|
first_seen TIMESTAMP DEFAULT {db.timestamp_now},
|
||||||
active BOOL DEFAULT TRUE,
|
active BOOL DEFAULT TRUE,
|
||||||
|
|
||||||
UNIQUE (id, mint_url)
|
UNIQUE (derivation_path)
|
||||||
|
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -26,6 +26,12 @@ def keys():
|
|||||||
return ledger.get_keyset()
|
return ledger.get_keyset()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/keysets")
|
||||||
|
def keysets():
|
||||||
|
"""Get all active keysets of the mint"""
|
||||||
|
return {"keysets": ledger.keysets.get_ids()}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/mint")
|
@router.get("/mint")
|
||||||
async def request_mint(amount: int = 0):
|
async def request_mint(amount: int = 0):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ async def load_ledger():
|
|||||||
await migrate_databases(ledger.db, migrations)
|
await migrate_databases(ledger.db, migrations)
|
||||||
# await asyncio.wait([m001_initial(ledger.db)])
|
# await asyncio.wait([m001_initial(ledger.db)])
|
||||||
await ledger.load_used_proofs()
|
await ledger.load_used_proofs()
|
||||||
await ledger.store_keyset()
|
await ledger.init_keysets()
|
||||||
|
|
||||||
if LIGHTNING:
|
if LIGHTNING:
|
||||||
error_message, balance = await WALLET.status()
|
error_message, balance = await WALLET.status()
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ from typing import Any, List, Optional
|
|||||||
from cashu.core.base import P2SHScript, Proof
|
from cashu.core.base import P2SHScript, Proof
|
||||||
from cashu.core.db import Connection, Database
|
from cashu.core.db import Connection, Database
|
||||||
|
|
||||||
|
from cashu.core.base import KeyBase, WalletKeyset
|
||||||
|
from cashu.core.db import Connection, Database
|
||||||
|
|
||||||
|
|
||||||
async def store_proof(
|
async def store_proof(
|
||||||
proof: Proof,
|
proof: Proof,
|
||||||
@@ -180,3 +183,57 @@ async def update_p2sh_used(
|
|||||||
f"UPDATE proofs SET {', '.join(clauses)} WHERE address = ?",
|
f"UPDATE proofs SET {', '.join(clauses)} WHERE address = ?",
|
||||||
(*values, str(p2sh.address)),
|
(*values, str(p2sh.address)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def store_keyset(
|
||||||
|
keyset: WalletKeyset,
|
||||||
|
mint_url: str = None,
|
||||||
|
db: Database = None,
|
||||||
|
conn: Optional[Connection] = None,
|
||||||
|
):
|
||||||
|
|
||||||
|
await (conn or db).execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO keysets
|
||||||
|
(id, mint_url, valid_from, valid_to, first_seen, active)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
keyset.id,
|
||||||
|
mint_url or keyset.mint_url,
|
||||||
|
keyset.valid_from,
|
||||||
|
keyset.valid_to,
|
||||||
|
keyset.first_seen,
|
||||||
|
True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_keyset(
|
||||||
|
id: str = None,
|
||||||
|
mint_url: str = None,
|
||||||
|
db: Database = None,
|
||||||
|
conn: Optional[Connection] = None,
|
||||||
|
):
|
||||||
|
clauses = []
|
||||||
|
values = []
|
||||||
|
clauses.append("active = ?")
|
||||||
|
values.append(True)
|
||||||
|
if id:
|
||||||
|
clauses.append("id = ?")
|
||||||
|
values.append(id)
|
||||||
|
if mint_url:
|
||||||
|
clauses.append("mint_url = ?")
|
||||||
|
values.append(mint_url)
|
||||||
|
where = ""
|
||||||
|
if clauses:
|
||||||
|
where = f"WHERE {' AND '.join(clauses)}"
|
||||||
|
|
||||||
|
row = await (conn or db).fetchone(
|
||||||
|
f"""
|
||||||
|
SELECT * from keysets
|
||||||
|
{where}
|
||||||
|
""",
|
||||||
|
tuple(values),
|
||||||
|
)
|
||||||
|
return WalletKeyset.from_row(row) if row is not None else None
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ async def m004_p2sh_locks(db: Database):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def m005_mint_keysets(db: Database):
|
async def m005_wallet_keysets(db: Database):
|
||||||
"""
|
"""
|
||||||
Stores mint keysets from different mints and epochs.
|
Stores mint keysets from different mints and epochs.
|
||||||
"""
|
"""
|
||||||
@@ -109,28 +109,28 @@ async def m005_mint_keysets(db: Database):
|
|||||||
CREATE TABLE IF NOT EXISTS keysets (
|
CREATE TABLE IF NOT EXISTS keysets (
|
||||||
id TEXT NOT NULL,
|
id TEXT NOT NULL,
|
||||||
mint_url TEXT NOT NULL,
|
mint_url TEXT NOT NULL,
|
||||||
valid_from TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
|
valid_from TIMESTAMP DEFAULT {db.timestamp_now},
|
||||||
valid_to TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
|
valid_to TIMESTAMP DEFAULT {db.timestamp_now},
|
||||||
first_seen TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
|
first_seen TIMESTAMP DEFAULT {db.timestamp_now},
|
||||||
active BOOL NOT NULL DEFAULT TRUE,
|
active BOOL DEFAULT TRUE,
|
||||||
|
|
||||||
UNIQUE (id, mint_url)
|
UNIQUE (id, mint_url)
|
||||||
|
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
await db.execute(
|
# await db.execute(
|
||||||
f"""
|
# f"""
|
||||||
CREATE TABLE IF NOT EXISTS mint_pubkeys (
|
# CREATE TABLE IF NOT EXISTS mint_pubkeys (
|
||||||
id TEXT NOT NULL,
|
# id TEXT NOT NULL,
|
||||||
amount INTEGER NOT NULL,
|
# amount INTEGER NOT NULL,
|
||||||
pubkey TEXT NOT NULL,
|
# pubkey TEXT NOT NULL,
|
||||||
|
|
||||||
UNIQUE (id, pubkey)
|
# UNIQUE (id, pubkey)
|
||||||
|
|
||||||
);
|
# );
|
||||||
"""
|
# """
|
||||||
)
|
# )
|
||||||
|
|
||||||
await db.execute("ALTER TABLE proofs ADD COLUMN id TEXT")
|
await db.execute("ALTER TABLE proofs ADD COLUMN id TEXT")
|
||||||
await db.execute("ALTER TABLE proofs_used ADD COLUMN id TEXT")
|
await db.execute("ALTER TABLE proofs_used ADD COLUMN id TEXT")
|
||||||
|
|||||||
@@ -14,14 +14,13 @@ from cashu.core.base import (
|
|||||||
BlindedSignature,
|
BlindedSignature,
|
||||||
CheckFeesRequest,
|
CheckFeesRequest,
|
||||||
CheckRequest,
|
CheckRequest,
|
||||||
Keyset,
|
WalletKeyset,
|
||||||
MeltRequest,
|
MeltRequest,
|
||||||
MintRequest,
|
MintRequest,
|
||||||
P2SHScript,
|
P2SHScript,
|
||||||
Proof,
|
Proof,
|
||||||
SplitRequest,
|
SplitRequest,
|
||||||
)
|
)
|
||||||
from cashu.core.crud import get_keyset, store_keyset
|
|
||||||
from cashu.core.db import Database
|
from cashu.core.db import Database
|
||||||
from cashu.core.script import (
|
from cashu.core.script import (
|
||||||
step0_carol_checksig_redeemscrip,
|
step0_carol_checksig_redeemscrip,
|
||||||
@@ -39,6 +38,8 @@ from cashu.wallet.crud import (
|
|||||||
store_p2sh,
|
store_p2sh,
|
||||||
store_proof,
|
store_proof,
|
||||||
update_proof_reserved,
|
update_proof_reserved,
|
||||||
|
get_keyset,
|
||||||
|
store_keyset,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -57,9 +58,14 @@ class LedgerAPI:
|
|||||||
int(amt): PublicKey(bytes.fromhex(val), raw=True)
|
int(amt): PublicKey(bytes.fromhex(val), raw=True)
|
||||||
for amt, val in keys.items()
|
for amt, val in keys.items()
|
||||||
}
|
}
|
||||||
keyset = Keyset(pubkeys=keyset_keys)
|
keyset = WalletKeyset(pubkeys=keyset_keys, mint_url=url)
|
||||||
return keyset
|
return keyset
|
||||||
|
|
||||||
|
async def _get_keysets(self, url):
|
||||||
|
keysets = requests.get(url + "/keysets").json()
|
||||||
|
assert len(keysets), Exception("did not receive any keysets")
|
||||||
|
return keysets
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_output_split(amount):
|
def _get_output_split(amount):
|
||||||
"""Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8]."""
|
"""Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8]."""
|
||||||
@@ -97,7 +103,8 @@ class LedgerAPI:
|
|||||||
self.url
|
self.url
|
||||||
), "Ledger not initialized correctly: mint URL not specified yet. "
|
), "Ledger not initialized correctly: mint URL not specified yet. "
|
||||||
keyset = await self._get_keys(self.url)
|
keyset = await self._get_keys(self.url)
|
||||||
keyset_local: Keyset = await get_keyset(keyset.id, db=self.db)
|
keysets = await self._get_keysets(self.url)
|
||||||
|
keyset_local: WalletKeyset = await get_keyset(keyset.id, db=self.db)
|
||||||
if keyset_local is None:
|
if keyset_local is None:
|
||||||
await store_keyset(keyset=keyset, db=self.db)
|
await store_keyset(keyset=keyset, db=self.db)
|
||||||
# keyset_local: Keyset = await get_keyset(keyset.id, self.url, db=self.db)
|
# keyset_local: Keyset = await get_keyset(keyset.id, self.url, db=self.db)
|
||||||
@@ -105,6 +112,7 @@ class LedgerAPI:
|
|||||||
# await store_keyset(keyset=keyset, db=self.db)
|
# await store_keyset(keyset=keyset, db=self.db)
|
||||||
self.keys = keyset.public_keys
|
self.keys = keyset.public_keys
|
||||||
self.keyset_id = keyset.id
|
self.keyset_id = keyset.id
|
||||||
|
self.keysets = keysets["keysets"]
|
||||||
assert len(self.keys) > 0, "did not receive keys from mint."
|
assert len(self.keys) > 0, "did not receive keys from mint."
|
||||||
|
|
||||||
def request_mint(self, amount):
|
def request_mint(self, amount):
|
||||||
@@ -370,8 +378,14 @@ class Wallet(LedgerAPI):
|
|||||||
return token
|
return token
|
||||||
|
|
||||||
async def _get_spendable_proofs(self, proofs: List[Proof]):
|
async def _get_spendable_proofs(self, proofs: List[Proof]):
|
||||||
|
"""
|
||||||
|
Selects proofs that can be used with the current mint.
|
||||||
|
Chooses:
|
||||||
|
1) Proofs that are not marked as reserved
|
||||||
|
2) Proofs that have a keyset id that is in self.keysets (active keysets of mint) - !!! optional for backwards compatibility with legacy clients
|
||||||
|
"""
|
||||||
proofs = [
|
proofs = [
|
||||||
p for p in proofs if p.id == self.keyset_id or not p.id
|
p for p in proofs if p.id in self.keysets or not p.id
|
||||||
] # "or not p.id" is for backwards compatibility with proofs without a keyset id
|
] # "or not p.id" is for backwards compatibility with proofs without a keyset id
|
||||||
proofs = [p for p in proofs if not p.reserved]
|
proofs = [p for p in proofs if not p.reserved]
|
||||||
return proofs
|
return proofs
|
||||||
|
|||||||
Reference in New Issue
Block a user