diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py index 81b18bc70..6e19eb6e6 100644 --- a/lnbits/extensions/cashu/__init__.py +++ b/lnbits/extensions/cashu/__init__.py @@ -11,13 +11,16 @@ db = Database("ext_cashu") import sys +cashu_static_files = [ + { + "path": "/cashu/static", + "app": StaticFiles(directory="lnbits/extensions/cashu/static"), + "name": "cashu_static", + } +] sys.path.append("/Users/cc/git/cashu") from cashu.mint.ledger import Ledger -# from .crud import LedgerCrud - -# db = Database("ext_cashu", LNBITS_DATA_FOLDER) - ledger = Ledger( db=db, # seed=MINT_PRIVATE_KEY, @@ -26,17 +29,6 @@ ledger = Ledger( ) cashu_ext: APIRouter = APIRouter(prefix="/api/v1/cashu", tags=["cashu"]) -# from cashu.mint.router import router as cashu_router - -# cashu_ext.include_router(router=cashu_router) - -cashu_static_files = [ - { - "path": "/cashu/static", - "app": StaticFiles(directory="lnbits/extensions/cashu/static"), - "name": "cashu_static", - } -] def cashu_renderer(): diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py index a56eadf0e..2057f6ff6 100644 --- a/lnbits/extensions/cashu/crud.py +++ b/lnbits/extensions/cashu/crud.py @@ -19,46 +19,6 @@ from cashu.core.base import MintKeyset from lnbits.db import Database, Connection -# class LedgerCrud: -# """ -# Database interface for Cashu mint. - -# This class needs to be overloaded by any app that imports the Cashu mint. -# """ - -# async def get_keyset(*args, **kwags): - -# return await get_keyset(*args, **kwags) - -# async def get_lightning_invoice(*args, **kwags): - -# return await get_lightning_invoice(*args, **kwags) - -# async def get_proofs_used(*args, **kwags): - -# return await get_proofs_used(*args, **kwags) - -# async def invalidate_proof(*args, **kwags): - -# return await invalidate_proof(*args, **kwags) - -# async def store_keyset(*args, **kwags): - -# return await store_keyset(*args, **kwags) - -# async def store_lightning_invoice(*args, **kwags): - -# return await store_lightning_invoice(*args, **kwags) - -# async def store_promise(*args, **kwags): - -# return await store_promise(*args, **kwags) - -# async def update_lightning_invoice(*args, **kwags): - -# return await update_lightning_invoice(*args, **kwags) - - async def create_cashu( cashu_id: str, keyset_id: str, wallet_id: str, data: Cashu ) -> Cashu: @@ -85,23 +45,23 @@ async def create_cashu( return cashu -async def update_cashu_keys(cashu_id, wif: str = None) -> Optional[Cashu]: - entropy = bytes([random.getrandbits(8) for i in range(16)]) - mnemonic = bip39.mnemonic_from_bytes(entropy) - seed = bip39.mnemonic_to_seed(mnemonic) - root = bip32.HDKey.from_seed(seed, version=NETWORKS["main"]["xprv"]) +# async def update_cashu_keys(cashu_id, wif: str = None) -> Optional[Cashu]: +# entropy = bytes([random.getrandbits(8) for i in range(16)]) +# mnemonic = bip39.mnemonic_from_bytes(entropy) +# seed = bip39.mnemonic_to_seed(mnemonic) +# root = bip32.HDKey.from_seed(seed, version=NETWORKS["main"]["xprv"]) - bip44_xprv = root.derive("m/44h/1h/0h") - bip44_xpub = bip44_xprv.to_public() +# bip44_xprv = root.derive("m/44h/1h/0h") +# bip44_xpub = bip44_xprv.to_public() - await db.execute( - "UPDATE cashu.cashu SET prv = ?, pub = ? WHERE id = ?", - bip44_xprv.to_base58(), - bip44_xpub.to_base58(), - cashu_id, - ) - row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,)) - return Cashu(**row) if row else None +# await db.execute( +# "UPDATE cashu.cashu SET prv = ?, pub = ? WHERE id = ?", +# bip44_xprv.to_base58(), +# bip44_xpub.to_base58(), +# cashu_id, +# ) +# row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,)) +# return Cashu(**row) if row else None async def get_cashu(cashu_id) -> Optional[Cashu]: @@ -130,103 +90,103 @@ async def delete_cashu(cashu_id) -> None: # ########################################## -async def store_promises( - amounts: List[int], B_s: List[str], C_s: List[str], cashu_id: str -): - for amount, B_, C_ in zip(amounts, B_s, C_s): - await store_promise(amount, B_, C_, cashu_id) +# async def store_promises( +# amounts: List[int], B_s: List[str], C_s: List[str], cashu_id: str +# ): +# for amount, B_, C_ in zip(amounts, B_s, C_s): +# await store_promise(amount, B_, C_, cashu_id) -async def store_promise(amount: int, B_: str, C_: str, cashu_id: str): - promise_id = urlsafe_short_hash() +# async def store_promise(amount: int, B_: str, C_: str, cashu_id: str): +# promise_id = urlsafe_short_hash() - await db.execute( - """ - INSERT INTO cashu.promises - (id, amount, B_b, C_b, cashu_id) - VALUES (?, ?, ?, ?, ?) - """, - (promise_id, amount, str(B_), str(C_), cashu_id), - ) +# await db.execute( +# """ +# INSERT INTO cashu.promises +# (id, amount, B_b, C_b, cashu_id) +# VALUES (?, ?, ?, ?, ?) +# """, +# (promise_id, amount, str(B_), str(C_), cashu_id), +# ) -async def get_promises(cashu_id) -> Optional[Cashu]: - row = await db.fetchall( - "SELECT * FROM cashu.promises WHERE cashu_id = ?", (cashu_id,) - ) - return Promises(**row) if row else None +# async def get_promises(cashu_id) -> Optional[Cashu]: +# row = await db.fetchall( +# "SELECT * FROM cashu.promises WHERE cashu_id = ?", (cashu_id,) +# ) +# return Promises(**row) if row else None -async def get_proofs_used( - db: Database, - conn: Optional[Connection] = None, -): +# async def get_proofs_used( +# db: Database, +# conn: Optional[Connection] = None, +# ): - rows = await (conn or db).fetchall( - """ - SELECT secret from cashu.proofs_used - """ - ) - return [row[0] for row in rows] +# rows = await (conn or db).fetchall( +# """ +# SELECT secret from cashu.proofs_used +# """ +# ) +# return [row[0] for row in rows] -async def invalidate_proof(cashu_id: str, proof: Proof): - invalidate_proof_id = urlsafe_short_hash() - await db.execute( - """ - INSERT INTO cashu.proofs_used - (id, amount, C, secret, cashu_id) - VALUES (?, ?, ?, ?, ?) - """, - (invalidate_proof_id, proof.amount, str(proof.C), str(proof.secret), cashu_id), - ) +# async def invalidate_proof(cashu_id: str, proof: Proof): +# invalidate_proof_id = urlsafe_short_hash() +# await db.execute( +# """ +# INSERT INTO cashu.proofs_used +# (id, amount, C, secret, cashu_id) +# VALUES (?, ?, ?, ?, ?) +# """, +# (invalidate_proof_id, proof.amount, str(proof.C), str(proof.secret), cashu_id), +# ) -######################################## -############ MINT INVOICES ############# -######################################## +# ######################################## +# ############ MINT INVOICES ############# +# ######################################## -async def store_lightning_invoice(cashu_id: str, invoice: Invoice): - await db.execute( - """ - INSERT INTO cashu.invoices - (cashu_id, amount, pr, hash, issued) - VALUES (?, ?, ?, ?, ?) - """, - ( - cashu_id, - invoice.amount, - invoice.pr, - invoice.hash, - invoice.issued, - ), - ) +# async def store_lightning_invoice(cashu_id: str, invoice: Invoice): +# await db.execute( +# """ +# INSERT INTO cashu.invoices +# (cashu_id, amount, pr, hash, issued) +# VALUES (?, ?, ?, ?, ?) +# """, +# ( +# cashu_id, +# invoice.amount, +# invoice.pr, +# invoice.hash, +# invoice.issued, +# ), +# ) -async def get_lightning_invoice(cashu_id: str, hash: str): - row = await db.fetchone( - """ - SELECT * from cashu.invoices - WHERE cashu_id =? AND hash = ? - """, - ( - cashu_id, - hash, - ), - ) - return Invoice.from_row(row) +# async def get_lightning_invoice(cashu_id: str, hash: str): +# row = await db.fetchone( +# """ +# SELECT * from cashu.invoices +# WHERE cashu_id =? AND hash = ? +# """, +# ( +# cashu_id, +# hash, +# ), +# ) +# return Invoice.from_row(row) -async def update_lightning_invoice(cashu_id: str, hash: str, issued: bool): - await db.execute( - "UPDATE cashu.invoices SET issued = ? WHERE cashu_id = ? AND hash = ?", - ( - issued, - cashu_id, - hash, - ), - ) +# async def update_lightning_invoice(cashu_id: str, hash: str, issued: bool): +# await db.execute( +# "UPDATE cashu.invoices SET issued = ? WHERE cashu_id = ? AND hash = ?", +# ( +# issued, +# cashu_id, +# hash, +# ), +# ) ############################## diff --git a/lnbits/extensions/cashu/migrations.py b/lnbits/extensions/cashu/migrations.py index 3f7995343..ec5207658 100644 --- a/lnbits/extensions/cashu/migrations.py +++ b/lnbits/extensions/cashu/migrations.py @@ -30,118 +30,3 @@ async def m001_initial(db): ); """ ) - - -# async def m001_initial(db): -# await db.execute( -# """ -# CREATE TABLE IF NOT EXISTS cashu.promises ( -# amount INTEGER NOT NULL, -# B_b TEXT NOT NULL, -# C_b TEXT NOT NULL, - -# UNIQUE (B_b) - -# ); -# """ -# ) - -# await db.execute( -# """ -# CREATE TABLE IF NOT EXISTS cashu.proofs_used ( -# amount INTEGER NOT NULL, -# C TEXT NOT NULL, -# secret TEXT NOT NULL, - -# UNIQUE (secret) - -# ); -# """ -# ) - -# await db.execute( -# """ -# CREATE TABLE IF NOT EXISTS cashu.invoices ( -# amount INTEGER NOT NULL, -# pr TEXT NOT NULL, -# hash TEXT NOT NULL, -# issued BOOL NOT NULL, - -# UNIQUE (hash) - -# ); -# """ -# ) - -# await db.execute( -# """ -# CREATE VIEW IF NOT EXISTS cashu.balance_issued AS -# SELECT COALESCE(SUM(s), 0) AS balance FROM ( -# SELECT SUM(amount) AS s -# FROM cashu.promises -# WHERE amount > 0 -# ); -# """ -# ) - -# await db.execute( -# """ -# CREATE VIEW IF NOT EXISTS cashu.balance_used AS -# SELECT COALESCE(SUM(s), 0) AS balance FROM ( -# SELECT SUM(amount) AS s -# FROM cashu.proofs_used -# WHERE amount > 0 -# ); -# """ -# ) - -# await db.execute( -# """ -# CREATE VIEW IF NOT EXISTS cashu.balance AS -# SELECT s_issued - s_used AS balance FROM ( -# SELECT bi.balance AS s_issued, bu.balance AS s_used -# FROM cashu.balance_issued bi -# CROSS JOIN balance_used bu -# ); -# """ -# ) - - -# async def m003_mint_keysets(db): -# """ -# Stores mint keysets from different mints and epochs. -# """ -# await db.execute( -# f""" -# CREATE TABLE IF NOT EXISTS cashu.keysets ( -# id TEXT NOT NULL, -# derivation_path TEXT, -# valid_from TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, -# valid_to TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, -# first_seen TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, -# active BOOL DEFAULT TRUE, - -# UNIQUE (derivation_path) - -# ); -# """ -# ) -# await db.execute( -# f""" -# CREATE TABLE IF NOT EXISTS cashu.mint_pubkeys ( -# id TEXT NOT NULL, -# amount INTEGER NOT NULL, -# pubkey TEXT NOT NULL, - -# UNIQUE (id, pubkey) - -# ); -# """ -# ) - - -# async def m004_keysets_add_version(db): -# """ -# Column that remembers with which version -# """ -# await db.execute("ALTER TABLE cashu.keysets ADD COLUMN version TEXT") diff --git a/lnbits/extensions/cashu/mint.py b/lnbits/extensions/cashu/mint.py deleted file mode 100644 index 4153fb302..000000000 --- a/lnbits/extensions/cashu/mint.py +++ /dev/null @@ -1,155 +0,0 @@ -import math -from typing import List, Set - -from lnbits import bolt11 -from lnbits.core.services import check_transaction_status, fee_reserve, pay_invoice -from lnbits.wallets.base import PaymentStatus - -from .core.b_dhke import step2_bob -from .core.base import BlindedMessage, BlindedSignature, Proof -from .core.secp import PublicKey -from .core.split import amount_split -from .crud import get_proofs_used, invalidate_proof -from .mint_helper import ( - derive_keys, - derive_pubkeys, - verify_equation_balanced, - verify_no_duplicates, - verify_outputs, - verify_proof, - verify_secret_criteria, - verify_split_amount, -) -from .models import Cashu - -# todo: extract const -MAX_ORDER = 64 - - -def get_pubkeys(xpriv: str): - """Returns public keys for possible amounts.""" - - keys = derive_keys(xpriv) - pub_keys = derive_pubkeys(keys) - - return {a: p.serialize().hex() for a, p in pub_keys.items()} - - -async def generate_promises( - master_prvkey: str, amounts: List[int], B_s: List[PublicKey] -): - """Mints a promise for coins for B_.""" - - for amount in amounts: - 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 generate_promise(master_prvkey, amount, B_) - for B_, amount in zip(B_s, amounts) - ] - return promises - - -async def generate_promise(master_prvkey: str, amount: int, B_: PublicKey): - """Generates a promise for given amount and returns a pair (amount, C').""" - secret_key = derive_keys(master_prvkey)[amount] # Get the correct key - C_ = step2_bob(B_, secret_key) - return BlindedSignature(amount=amount, C_=C_.serialize().hex()) - - -async def melt(cashu: Cashu, proofs: List[Proof], invoice: str): - """Invalidates proofs and pays a Lightning invoice.""" - # Verify proofs - proofs_used: Set[str] = set(await get_proofs_used(cashu.id)) - for p in proofs: - await verify_proof(cashu.prvkey, proofs_used, p) - - total_provided = sum([p["amount"] for p in proofs]) - invoice_obj = bolt11.decode(invoice) - amount = math.ceil(invoice_obj.amount_msat / 1000) - - fees_msat = await check_fees(cashu.wallet, invoice_obj) - assert total_provided >= amount + fees_msat / 1000, Exception( - f"Provided proofs ({total_provided} sats) not enough for Lightning payment ({amount + fees_msat} sats)." - ) - - await pay_invoice( - wallet_id=cashu.wallet, - payment_request=invoice, - description=f"pay cashu invoice", - extra={"tag": "cashu", "cahsu_name": cashu.name}, - ) - - status: PaymentStatus = await check_transaction_status( - cashu.wallet, invoice_obj.payment_hash - ) - if status.paid == True: - await invalidate_proofs(cashu.id, proofs) - return status.paid, status.preimage - return False, "" - - -async def check_fees(wallet_id: str, decoded_invoice): - """Returns the fees (in msat) required to pay this pr.""" - amount = math.ceil(decoded_invoice.amount_msat / 1000) - status: PaymentStatus = await check_transaction_status( - wallet_id, decoded_invoice.payment_hash - ) - fees_msat = fee_reserve(amount * 1000) if status.paid != True else 0 - return fees_msat - - -async def split( - cashu: Cashu, proofs: List[Proof], amount: int, outputs: List[BlindedMessage] -): - """Consumes proofs and prepares new promises based on the amount split.""" - total = sum([p.amount for p in proofs]) - - # verify that amount is kosher - verify_split_amount(amount) - # verify overspending attempt - if amount > total: - raise Exception( - f"split amount ({amount}) is higher than the total sum ({total})." - ) - - # Verify secret criteria - if not all([verify_secret_criteria(p) for p in proofs]): - raise Exception("secrets do not match criteria.") - # verify that only unique proofs and outputs were used - if not verify_no_duplicates(proofs, outputs): - raise Exception("duplicate proofs or promises.") - # verify that outputs have the correct amount - if not verify_outputs(total, amount, outputs): # ? - raise Exception("split of promises is not as expected.") - # Verify proofs - # Verify proofs - proofs_used: Set[str] = set(await get_proofs_used(cashu.id)) - for p in proofs: - await verify_proof(cashu.prvkey, proofs_used, p) - - # Mark proofs as used and prepare new promises - await invalidate_proofs(cashu.id, proofs) - - outs_fst = amount_split(total - amount) - outs_snd = amount_split(amount) - B_fst = [ - PublicKey(bytes.fromhex(od.B_), raw=True) for od in outputs[: len(outs_fst)] - ] - B_snd = [ - PublicKey(bytes.fromhex(od.B_), raw=True) for od in outputs[len(outs_fst) :] - ] - # PublicKey(bytes.fromhex(payload.B_), raw=True) - prom_fst, prom_snd = await generate_promises( - cashu.prvkey, outs_fst, B_fst - ), await generate_promises(cashu.prvkey, outs_snd, B_snd) - # verify amounts in produced proofs - verify_equation_balanced(proofs, prom_fst + prom_snd) - return prom_fst, prom_snd - - -async def invalidate_proofs(cashu_id: str, proofs: List[Proof]): - """Adds secrets of proofs to the list of knwon secrets and stores them in the db.""" - for p in proofs: - await invalidate_proof(cashu_id, p) diff --git a/lnbits/extensions/cashu/mint_helper.py b/lnbits/extensions/cashu/mint_helper.py deleted file mode 100644 index 8e7e22751..000000000 --- a/lnbits/extensions/cashu/mint_helper.py +++ /dev/null @@ -1,97 +0,0 @@ -import base64 -import hashlib -from typing import List, Set - -from .core.b_dhke import verify -from .core.base import BlindedSignature -from .core.secp import PrivateKey, PublicKey -from .core.split import amount_split -from .models import BlindedMessage, Proof - -# todo: extract const -MAX_ORDER = 64 - - -def derive_keys(master_key: str): - """Deterministic derivation of keys for 2^n values.""" - return { - 2 - ** i: PrivateKey( - hashlib.sha256((str(master_key) + str(i)).encode("utf-8")) - .hexdigest() - .encode("utf-8")[:32], - raw=True, - ) - for i in range(MAX_ORDER) - } - - -def derive_pubkeys(keys: List[PrivateKey]): - return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]} - - -# async required? -async def verify_proof(master_prvkey: str, proofs_used: Set[str], proof: Proof): - """Verifies that the proof of promise was issued by this ledger.""" - if proof.secret in proofs_used: - raise Exception(f"tokens already spent. Secret: {proof.secret}") - - secret_key = derive_keys(master_prvkey)[ - proof.amount - ] # Get the correct key to check against - C = PublicKey(bytes.fromhex(proof.C), raw=True) - secret = base64.standard_b64decode(proof.secret) - print("### secret", secret) - validMintSig = verify(secret_key, C, secret) - if validMintSig != True: - raise Exception(f"tokens not valid. Secret: {proof.secret}") - - -def verify_split_amount(amount: int): - """Split amount like output amount can't be negative or too big.""" - try: - verify_amount(amount) - except: - # For better error message - raise Exception("invalid split amount: " + str(amount)) - - -def verify_secret_criteria(proof: Proof): - if proof.secret is None or proof.secret == "": - raise Exception("no secret in proof.") - return True - - -def verify_no_duplicates(proofs: List[Proof], outputs: List[BlindedMessage]): - secrets = [p.secret for p in proofs] - if len(secrets) != len(list(set(secrets))): - return False - B_s = [od.B_ for od in outputs] - if len(B_s) != len(list(set(B_s))): - return False - return True - - -def verify_outputs(total: int, amount: int, outputs: List[BlindedMessage]): - """Verifies the expected split was correctly computed""" - frst_amt, scnd_amt = total - amount, amount # we have two amounts to split to - frst_outputs = amount_split(frst_amt) - scnd_outputs = amount_split(scnd_amt) - expected = frst_outputs + scnd_outputs - given = [o.amount for o in outputs] - return given == expected - - -def verify_amount(amount: int): - """Any amount used should be a positive integer not larger than 2^MAX_ORDER.""" - valid = isinstance(amount, int) and amount > 0 and amount < 2**MAX_ORDER - if not valid: - raise Exception("invalid amount: " + str(amount)) - return amount - - -def verify_equation_balanced(proofs: List[Proof], outs: List[BlindedSignature]): - """Verify that Σoutputs - Σinputs = 0.""" - sum_inputs = sum(verify_amount(p.amount) for p in proofs) - sum_outputs = sum(verify_amount(p.amount) for p in outs) - assert sum_outputs - sum_inputs == 0 diff --git a/lnbits/extensions/cashu/tasks.py b/lnbits/extensions/cashu/tasks.py index 03bba895d..dddd1ef10 100644 --- a/lnbits/extensions/cashu/tasks.py +++ b/lnbits/extensions/cashu/tasks.py @@ -1,17 +1,11 @@ import asyncio import json -from lnbits.core import db as core_db -from lnbits.core.crud import create_payment from lnbits.core.models import Payment -from lnbits.helpers import urlsafe_short_hash -from lnbits.tasks import internal_invoice_queue, register_invoice_listener +from lnbits.tasks import register_invoice_listener from .crud import get_cashu -import sys - -sys.path.append("/Users/cc/git/cashu") from cashu.mint import migrations from cashu.core.migrations import migrate_databases from . import db, ledger @@ -35,51 +29,6 @@ async def wait_for_paid_invoices(): async def on_invoice_paid(payment: Payment) -> None: - if payment.extra.get("tag") == "cashu" and payment.extra.get("tipSplitted"): - # already splitted, ignore + if not payment.extra.get("tag") == "cashu": return - - # now we make some special internal transfers (from no one to the receiver) - cashu = await get_cashu(payment.extra.get("cashuId")) - tipAmount = payment.extra.get("tipAmount") - - if tipAmount is None: - # no tip amount - return - - tipAmount = tipAmount * 1000 - amount = payment.amount - tipAmount - - # mark the original payment with one extra key, "splitted" - # (this prevents us from doing this process again and it's informative) - # and reduce it by the amount we're going to send to the producer - await core_db.execute( - """ - UPDATE apipayments - SET extra = ?, amount = ? - WHERE hash = ? - AND checking_id NOT LIKE 'internal_%' - """, - ( - json.dumps(dict(**payment.extra, tipSplitted=True)), - amount, - payment.payment_hash, - ), - ) - - # perform the internal transfer using the same payment_hash - internal_checking_id = f"internal_{urlsafe_short_hash()}" - await create_payment( - wallet_id=cashu.tip_wallet, - checking_id=internal_checking_id, - payment_request="", - payment_hash=payment.payment_hash, - amount=tipAmount, - memo=f"Tip for {payment.memo}", - pending=False, - extra={"tipSplitted": True}, - ) - - # manually send this for now - await internal_invoice_queue.put(internal_checking_id) return diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py index 2d28c78bc..6315a765e 100644 --- a/lnbits/extensions/cashu/views_api.py +++ b/lnbits/extensions/cashu/views_api.py @@ -2,6 +2,7 @@ import json from http import HTTPStatus from typing import Union import math +from typing import Dict, List, Union import httpx from fastapi import Query @@ -25,38 +26,22 @@ from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key from lnbits.wallets.base import PaymentStatus from lnbits.helpers import urlsafe_short_hash from lnbits.core.crud import check_internal + +# --------- extension imports + from . import cashu_ext -from .core.base import CashuError, PostSplitResponse, SplitRequest from .crud import ( create_cashu, delete_cashu, get_cashu, get_cashus, - get_lightning_invoice, - store_lightning_invoice, - store_promise, - update_lightning_invoice, ) -# from .ledger import mint, request_mint -from .mint import generate_promises, get_pubkeys, melt, split -from .models import ( - Cashu, - CheckPayload, - Invoice, - MeltPayload, - MintPayloads, - PayLnurlWData, - Pegs, - SplitPayload, -) +from .models import Cashu +from . import ledger -############### IMPORT CALLE -from typing import Dict, List, Union - -from secp256k1 import PublicKey - +# -------- cashu imports from cashu.core.base import ( Proof, BlindedSignature, @@ -69,9 +54,8 @@ from cashu.core.base import ( MintRequest, PostSplitResponse, SplitRequest, + Invoice, ) -from cashu.core.errors import CashuError -from . import db, ledger LIGHTNING = False @@ -165,7 +149,7 @@ async def mint_coins( data: MintRequest, cashu_id: str = Query(None), payment_hash: str = Query(None), -): +) -> List[BlindedSignature]: """ Requests the minting of tokens belonging to a paid payment request. Call this endpoint after `GET /mint`. @@ -220,7 +204,9 @@ async def mint_coins( @cashu_ext.post("/{cashu_id}/melt") -async def melt_coins(payload: MeltRequest, cashu_id: str = Query(None)): +async def melt_coins( + payload: MeltRequest, cashu_id: str = Query(None) +) -> GetMeltResponse: """Invalidates proofs and pays a Lightning invoice.""" cashu: Union[None, Cashu] = await get_cashu(cashu_id) if cashu is None: @@ -229,8 +215,6 @@ async def melt_coins(payload: MeltRequest, cashu_id: str = Query(None)): ) proofs = payload.proofs invoice = payload.invoice - # async def melt(cashu: Cashu, proofs: List[Proof], invoice: str): - # """Invalidates proofs and pays a Lightning invoice.""" # !!!!!!! MAKE SURE THAT PROOFS ARE ONLY FROM THIS CASHU KEYSET ID # THIS IS NECESSARY BECAUSE THE CASHU BACKEND WILL ACCEPT ANY VALID @@ -271,18 +255,7 @@ async def melt_coins(payload: MeltRequest, cashu_id: str = Query(None)): ) if status.paid == True: await ledger._invalidate_proofs(proofs) - return status.paid, status.preimage - return False, "" - - -@cashu_ext.post("/melt") -async def melt(payload: MeltRequest) -> GetMeltResponse: - """ - Requests tokens to be destroyed and sent out via Lightning. - """ - ok, preimage = await ledger.melt(payload.proofs, payload.invoice) - resp = GetMeltResponse(paid=ok, preimage=preimage) - return resp + return GetMeltResponse(paid=status.paid, preimage=status.preimage) @cashu_ext.post("/check") @@ -298,29 +271,46 @@ async def check_fees(payload: CheckFeesRequest) -> CheckFeesResponse: Used by wallets for figuring out the fees they need to supply. This is can be useful for checking whether an invoice is internal (Cashu-to-Cashu). """ - fees_msat = await ledger.check_fees(payload.pr) + invoice_obj = bolt11.decode(payload.pr) + internal_checking_id = await check_internal(invoice_obj.payment_hash) + + if not internal_checking_id: + fees_msat = fee_reserve(invoice_obj.amount_msat) + else: + fees_msat = 0 return CheckFeesResponse(fee=fees_msat / 1000) -@cashu_ext.post("/split") +@cashu_ext.post("/{cashu_id}/split") async def split( - payload: SplitRequest, -) -> Union[CashuError, PostSplitResponse]: + payload: SplitRequest, cashu_id: str = Query(None) +) -> PostSplitResponse: """ Requetst a set of tokens with amount "total" to be split into two newly minted sets with amount "split" and "total-split". """ + cashu: Union[None, Cashu] = await get_cashu(cashu_id) + if cashu is None: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist." + ) proofs = payload.proofs amount = payload.amount - outputs = payload.outputs.blinded_messages if payload.outputs else None + outputs = payload.outputs.blinded_messages # backwards compatibility with clients < v0.2.2 assert outputs, Exception("no outputs provided.") try: - split_return = await ledger.split(proofs, amount, outputs) + split_return = await ledger.split(proofs, amount, outputs, cashu.keyset_id) except Exception as exc: - return CashuError(error=str(exc)) + HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail=str(exc), + ) if not split_return: - return CashuError(error="there was an error with the split") + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail="there was an error with the split", + ) frst_promises, scnd_promises = split_return resp = PostSplitResponse(fst=frst_promises, snd=scnd_promises) return resp