mirror of
https://github.com/aljazceru/nutshell.git
synced 2026-02-03 15:54:20 +01:00
ability to specify the keyset for generating promises mint
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
from cashu.lightning.lnbits import LNbitsWallet
|
||||
# from cashu.lightning.lnbits import LNbitsWallet
|
||||
|
||||
WALLET = LNbitsWallet()
|
||||
# WALLET = LNbitsWallet()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user