ability to specify the keyset for generating promises mint

This commit is contained in:
callebtc
2022-10-13 20:07:04 +02:00
parent 22854f4fb2
commit 3437af041e
6 changed files with 103 additions and 60 deletions

View File

@@ -1,3 +1,3 @@
from cashu.lightning.lnbits import LNbitsWallet
# from cashu.lightning.lnbits import LNbitsWallet
WALLET = LNbitsWallet()
# WALLET = LNbitsWallet()

View File

@@ -26,28 +26,27 @@ from cashu.core.script import verify_script
from cashu.core.secp import PublicKey
from cashu.core.settings import LIGHTNING, MAX_ORDER, VERSION
from cashu.core.split import amount_split
from cashu.lightning import WALLET
from cashu.mint.crud import LedgerCrud
# from cashu.mint.crud import (
# get_keyset,
# get_lightning_invoice,
# get_proofs_used,
# invalidate_proof,
# store_keyset,
# store_lightning_invoice,
# store_promise,
# update_lightning_invoice,
# )
from cashu.lightning.lnbits import LNbitsWallet
class Ledger:
def __init__(self, db: Database, seed: str, derivation_path="", crud=LedgerCrud):
def __init__(
self,
db: Database,
seed: str,
derivation_path="",
crud=LedgerCrud,
lightning=LNbitsWallet(),
):
self.proofs_used: Set[str] = set()
self.master_key = seed
self.derivation_path = derivation_path
self.db = db
self.crud = crud
self.lightning = lightning
async def load_used_proofs(self):
"""Load all used proofs from database."""
@@ -81,19 +80,28 @@ class Ledger:
if len(self.keysets.keysets):
logger.debug(f"Loaded {len(self.keysets.keysets)} keysets from db.")
async def _generate_promises(self, amounts: List[int], B_s: List[str]):
async def _generate_promises(
self, B_s: List[BlindedMessage], keyset: MintKeyset = None
):
"""Generates promises that sum to the given amount."""
return [
await self._generate_promise(amount, PublicKey(bytes.fromhex(B_), raw=True))
for (amount, B_) in zip(amounts, B_s)
await self._generate_promise(
b.amount, PublicKey(bytes.fromhex(b.B_), raw=True), keyset
)
for b in B_s
]
async def _generate_promise(self, amount: int, B_: PublicKey):
async def _generate_promise(
self, amount: int, B_: PublicKey, keyset: MintKeyset = None
):
"""Generates a promise for given amount and returns a pair (amount, C')."""
private_key_amount = self.keyset.private_keys[amount] # Get the correct key
if keyset:
private_key_amount = keyset.private_keys[amount]
else:
private_key_amount = self.keyset.private_keys[amount] # Get the correct key
C_ = b_dhke.step2_bob(B_, private_key_amount)
await self.crud.store_promise(
amount, B_=B_.serialize().hex(), C_=C_.serialize().hex(), db=self.db
amount=amount, B_=B_.serialize().hex(), C_=C_.serialize().hex(), db=self.db
)
return BlindedSignature(amount=amount, C_=C_.serialize().hex())
@@ -215,18 +223,21 @@ class Ledger:
async def _request_lightning_invoice(self, amount: int):
"""Returns an invoice from the Lightning backend."""
error, balance = await WALLET.status()
error, balance = await self.lightning.status()
if error:
raise Exception(f"Lightning wallet not responding: {error}")
ok, checking_id, payment_request, error_message = await WALLET.create_invoice(
amount, "cashu deposit"
)
(
ok,
checking_id,
payment_request,
error_message,
) = await self.lightning.create_invoice(amount, "cashu deposit")
return payment_request, checking_id
async def _check_lightning_invoice(self, amounts, payment_hash: str):
"""Checks with the Lightning backend whether an invoice with this payment_hash was paid."""
invoice: Invoice = await self.crud.get_lightning_invoice(
payment_hash, db=self.db
hash=payment_hash, db=self.db
)
if invoice.issued:
raise Exception("tokens already issued for this invoice.")
@@ -235,21 +246,25 @@ class Ledger:
raise Exception(
f"Requested amount too high: {total_requested}. Invoice amount: {invoice.amount}"
)
status = await WALLET.get_invoice_status(payment_hash)
status = await self.lightning.get_invoice_status(payment_hash)
if status.paid:
await self.crud.update_lightning_invoice(
payment_hash, issued=True, db=self.db
hash=payment_hash, issued=True, db=self.db
)
return status.paid
async def _pay_lightning_invoice(self, invoice: str, fees_msat: int):
"""Returns an invoice from the Lightning backend."""
error, _ = await WALLET.status()
error, _ = await self.lightning.status()
if error:
raise Exception(f"Lightning wallet not responding: {error}")
ok, checking_id, fee_msat, preimage, error_message = await WALLET.pay_invoice(
invoice, fee_limit_msat=fees_msat
)
(
ok,
checking_id,
fee_msat,
preimage,
error_message,
) = await self.lightning.pay_invoice(invoice, fee_limit_msat=fees_msat)
return ok, preimage
async def _invalidate_proofs(self, proofs: List[Proof]):
@@ -259,7 +274,7 @@ class Ledger:
self.proofs_used |= proof_msgs
# store in db
for p in proofs:
await self.crud.invalidate_proof(p, db=self.db)
await self.crud.invalidate_proof(proof=p, db=self.db)
def _serialize_pubkeys(self):
"""Returns public keys for possible amounts."""
@@ -277,11 +292,17 @@ class Ledger:
)
if not payment_request or not checking_id:
raise Exception(f"Could not create Lightning invoice.")
await self.crud.store_lightning_invoice(invoice, db=self.db)
await self.crud.store_lightning_invoice(invoice=invoice, db=self.db)
return payment_request, checking_id
async def mint(self, B_s: List[PublicKey], amounts: List[int], payment_hash=None):
async def mint(
self,
B_s: List[BlindedMessage],
payment_hash=None,
keyset: MintKeyset = None,
):
"""Mints a promise for coins for B_."""
amounts = [b.amount for b in B_s]
# check if lightning invoice was paid
if LIGHTNING:
try:
@@ -295,9 +316,10 @@ class Ledger:
if amount not in [2**i for i in range(MAX_ORDER)]:
raise Exception(f"Can only mint amounts up to {2**MAX_ORDER}.")
promises = [
await self._generate_promise(amount, B_) for B_, amount in zip(B_s, amounts)
]
# promises = [
# await self._generate_promise(amount, B_) for B_, amount in zip(B_s, amounts)
# ]
promises = await self._generate_promises(B_s, keyset)
return promises
async def melt(self, proofs: List[Proof], invoice: str):
@@ -329,13 +351,17 @@ class Ledger:
amount = math.ceil(decoded_invoice.amount_msat / 1000)
# hack: check if it's internal, if it exists, it will return paid = False,
# if id does not exist (not internal), it returns paid = None
paid = await WALLET.get_invoice_status(decoded_invoice.payment_hash)
paid = await self.lightning.get_invoice_status(decoded_invoice.payment_hash)
internal = paid.paid == False
fees_msat = fee_reserve(amount * 1000, internal)
return fees_msat
async def split(
self, proofs: List[Proof], amount: int, outputs: List[BlindedMessage]
self,
proofs: List[Proof],
amount: int,
outputs: List[BlindedMessage],
keyset: MintKeyset = None,
):
"""Consumes proofs and prepares new promises based on the amount split."""
total = sum_proofs(proofs)
@@ -370,8 +396,8 @@ class Ledger:
B_fst = [od.B_ for od in outputs[: len(outs_fst)]]
B_snd = [od.B_ for od in outputs[len(outs_fst) :]]
prom_fst, prom_snd = await self._generate_promises(
outs_fst, B_fst
), await self._generate_promises(outs_snd, B_snd)
outs_fst, B_fst, keyset
), await self._generate_promises(outs_snd, B_snd, keyset)
# verify amounts in produced proofs
self._verify_equation_balanced(proofs, prom_fst + prom_snd)
return prom_fst, prom_snd

View File

@@ -25,13 +25,14 @@ router: APIRouter = APIRouter()
@router.get("/keys")
async def keys() -> dict[int, str]:
"""Get the public keys of the mint"""
return ledger.get_keyset()
keyset = ledger.get_keyset()
return keyset
@router.get("/keysets")
async def keysets() -> dict[str, list[str]]:
"""Get all active keysets of the mint"""
return {"keysets": await ledger.keysets.get_ids()}
return {"keysets": ledger.keysets.get_ids()}
@router.get("/mint")
@@ -50,7 +51,7 @@ async def request_mint(amount: int = 0) -> GetMintResponse:
@router.post("/mint")
async def mint(
payloads: MintRequest,
mint_request: MintRequest,
payment_hash: Union[str, None] = None,
) -> Union[List[BlindedSignature], CashuError]:
"""
@@ -58,13 +59,10 @@ async def mint(
Call this endpoint after `GET /mint`.
"""
amounts = []
B_s = []
for payload in payloads.blinded_messages:
amounts.append(payload.amount)
B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
try:
promises = await ledger.mint(B_s, amounts, payment_hash=payment_hash)
promises = await ledger.mint(
mint_request.blinded_messages, payment_hash=payment_hash
)
return promises
except Exception as exc:
return CashuError(error=str(exc))

View File

@@ -7,7 +7,8 @@ from loguru import logger
from cashu.core.migrations import migrate_databases
from cashu.core.settings import CASHU_DIR, LIGHTNING
from cashu.lightning import WALLET
# from cashu.lightning import WALLET
from cashu.mint import migrations
from cashu.mint.ledger import Ledger
@@ -28,14 +29,14 @@ async def start_mint_init():
await ledger.load_used_proofs()
await ledger.init_keysets()
if LIGHTNING:
error_message, balance = await WALLET.status()
if error_message:
logger.warning(
f"The backend for {WALLET.__class__.__name__} isn't working properly: '{error_message}'",
RuntimeWarning,
)
logger.info(f"Lightning balance: {balance} sat")
# if LIGHTNING:
# error_message, balance = await WALLET.status()
# if error_message:
# logger.warning(
# f"The backend for {WALLET.__class__.__name__} isn't working properly: '{error_message}'",
# RuntimeWarning,
# )
# logger.info(f"Lightning balance: {balance} sat")
logger.info(f"Data dir: {CASHU_DIR}")
logger.info("Mint started.")

View File

@@ -173,7 +173,7 @@ class LedgerAPI:
async def _get_keysets(self, url):
resp = self.s.get(
url + "/keysets",
).json()
)
resp.raise_for_status()
keysets = resp.json()
assert len(keysets), Exception("did not receive any keysets")

View File

@@ -11,6 +11,7 @@ from cashu.wallet import migrations
from cashu.wallet.wallet import Wallet
from cashu.wallet.wallet import Wallet as Wallet1
from cashu.wallet.wallet import Wallet as Wallet2
from cashu.core.settings import MAX_ORDER
SERVER_ENDPOINT = "http://localhost:3338"
@@ -48,6 +49,23 @@ async def wallet2():
yield wallet2
@pytest.mark.asyncio
async def test_get_keys(wallet1: Wallet):
assert len(wallet1.keys) == MAX_ORDER
keyset = await wallet1._get_keys(wallet1.url)
assert type(keyset.id) == str
assert len(keyset.id) > 0
@pytest.mark.asyncio
async def test_get_keysets(wallet1: Wallet):
keyset = await wallet1._get_keysets(wallet1.url)
assert type(keyset) == dict
assert type(keyset["keysets"]) == list
assert len(keyset["keysets"]) > 0
assert keyset["keysets"][0] == wallet1.keyset_id
@pytest.mark.asyncio
async def test_mint(wallet1: Wallet):
await wallet1.mint(64)