This commit is contained in:
callebtc
2022-09-13 21:36:18 +03:00
parent 075fb57093
commit f2228e6a38
16 changed files with 251 additions and 131 deletions

31
cashu
View File

@@ -1,17 +1,16 @@
#!/usr/bin/env python #!/usr/bin/env python
from wallet.wallet import Wallet as Wallet
from wallet.migrations import m001_initial
import asyncio import asyncio
import click
import json
import base64 import base64
from bech32 import bech32_encode, bech32_decode, convertbits import json
import asyncio
from functools import wraps from functools import wraps
import click
from bech32 import bech32_decode, bech32_encode, convertbits
from wallet.migrations import m001_initial
from wallet.wallet import Wallet as Wallet
# https://github.com/pallets/click/issues/85#issuecomment-503464628 # https://github.com/pallets/click/issues/85#issuecomment-503464628
def coro(f): def coro(f):
@@ -23,20 +22,26 @@ def coro(f):
@click.command("mint") @click.command("mint")
@click.option("--host", default="http://localhost:3338", help="Mint tokens.") @click.option("--host", default="http://localhost:3338", help="Mint hostname.")
@click.option("--wallet", default="wallet", help="Mint tokens.") @click.option("--wallet", default="wallet", help="Wallet to use.")
@click.option("--mint", default=0, help="Mint tokens.") @click.option("--mint", default=0, help="Mint tokens.")
@click.option("--hash", default="", help="Hash of the paid invoice.")
@click.option("--send", default=0, help="Send tokens.") @click.option("--send", default=0, help="Send tokens.")
@click.option("--receive", default="", help="Receive tokens.") @click.option("--receive", default="", help="Receive tokens.")
@click.option("--invalidate", default="", help="Invalidate tokens.") @click.option("--invalidate", default="", help="Invalidate tokens.")
@coro @coro
async def main(host, wallet, mint, send, receive, invalidate): async def main(host, wallet, mint, hash, send, receive, invalidate):
wallet = Wallet(host, f"data/{wallet}", wallet) wallet = Wallet(host, f"data/{wallet}", wallet)
await m001_initial(db=wallet.db) await m001_initial(db=wallet.db)
await wallet.load_proofs() await wallet.load_proofs()
if mint: if mint and not hash:
print(f"Balance: {wallet.balance}") print(f"Balance: {wallet.balance}")
await wallet.mint(mint) r = await wallet.request_mint(mint)
print(r)
if mint and hash:
print(f"Balance: {wallet.balance}")
await wallet.mint(mint, hash)
print(f"Balance: {wallet.balance}") print(f"Balance: {wallet.balance}")
if send: if send:

View File

@@ -30,9 +30,9 @@ If true, C must have originated from Alice
""" """
import hashlib import hashlib
from ecc.curve import secp256k1, Point
from ecc.key import gen_keypair
from ecc.curve import Point, secp256k1
from ecc.key import gen_keypair
G = secp256k1.G G = secp256k1.G

View File

@@ -1,6 +1,7 @@
from pydantic import BaseModel
from typing import List
from sqlite3 import Row from sqlite3 import Row
from typing import List
from pydantic import BaseModel
class BasePoint(BaseModel): class BasePoint(BaseModel):
@@ -27,6 +28,22 @@ class Proof(BaseModel):
) )
class Invoice(BaseModel):
amount: int
pr: str
hash: str
issued: bool = False
@classmethod
def from_row(cls, row: Row):
return cls(
amount=int(row[0]),
pr=str(row[1]),
hash=str(row[2]),
issued=bool(row[3]),
)
class MintPayload(BaseModel): class MintPayload(BaseModel):
amount: int amount: int
B_: BasePoint B_: BasePoint

View File

@@ -9,7 +9,6 @@ from sqlalchemy import create_engine
from sqlalchemy_aio.base import AsyncConnection from sqlalchemy_aio.base import AsyncConnection
from sqlalchemy_aio.strategy import ASYNCIO_STRATEGY # type: ignore from sqlalchemy_aio.strategy import ASYNCIO_STRATEGY # type: ignore
POSTGRES = "POSTGRES" POSTGRES = "POSTGRES"
COCKROACH = "COCKROACH" COCKROACH = "COCKROACH"
SQLITE = "SQLITE" SQLITE = "SQLITE"

View File

@@ -5,10 +5,15 @@ env.read_env()
DEBUG = env.bool("DEBUG", default=False) DEBUG = env.bool("DEBUG", default=False)
MINT_PRIVATE_KEY = env.str("MINT_PRIVATE_KEY")
MINT_SERVER_HOST = env.str("MINT_SERVER_HOST", default="127.0.0.1") MINT_SERVER_HOST = env.str("MINT_SERVER_HOST", default="127.0.0.1")
MINT_SERVER_PORT = env.int("MINT_SERVER_PORT", default=3338) MINT_SERVER_PORT = env.int("MINT_SERVER_PORT", default=3338)
MINT_HOST = env.str("MINT_HOST", default="127.0.0.1") MINT_HOST = env.str("MINT_HOST", default="127.0.0.1")
MINT_PORT = env.int("MINT_PORT", default=3338) MINT_PORT = env.int("MINT_PORT", default=3338)
LNBITS_ENDPOINT = env.str("LNBITS_ENDPOINT", default=None)
LNBITS_KEY = env.str("LNBITS_KEY", default=None)
MAX_ORDER = 64 MAX_ORDER = 64

View File

@@ -6,22 +6,19 @@ from typing import AsyncGenerator, Dict, Optional
import requests import requests
from .base import ( from core.settings import LNBITS_ENDPOINT, LNBITS_KEY
InvoiceResponse,
PaymentResponse, from .base import (InvoiceResponse, PaymentResponse, PaymentStatus,
PaymentStatus, StatusResponse, Wallet)
StatusResponse,
Wallet,
)
class LNbitsWallet(Wallet): class LNbitsWallet(Wallet):
"""https://github.com/lnbits/lnbits""" """https://github.com/lnbits/lnbits"""
def __init__(self): def __init__(self):
self.endpoint = getenv("LNBITS_ENDPOINT") self.endpoint = LNBITS_ENDPOINT
key = getenv("LNBITS_KEY") key = LNBITS_KEY
self.key = {"X-Api-Key": key} self.key = {"X-Api-Key": key}
self.s = requests.Session() self.s = requests.Session()
self.s.auth = ("user", "pass") self.s.auth = ("user", "pass")

View File

@@ -1,58 +1,23 @@
from ecc.curve import secp256k1, Point
from fastapi import FastAPI
from fastapi.routing import APIRouter
from fastapi.params import Depends, Query, Body
import sys
import asyncio import asyncio
import logging import logging
import uvicorn import sys
from ast import Param
from typing import Union
from ecc.curve import Point, secp256k1
from fastapi import FastAPI
from fastapi.params import Body, Depends, Query
from fastapi.routing import APIRouter
from loguru import logger from loguru import logger
import core.settings as settings
from core.base import MintPayloads, SplitPayload
from core.settings import MINT_PRIVATE_KEY
from lightning import WALLET
from mint.ledger import Ledger from mint.ledger import Ledger
from mint.migrations import m001_initial from mint.migrations import m001_initial
from lightning import WALLET
import core.settings as settings
from core.base import MintPayload, MintPayloads, SplitPayload ledger = Ledger(MINT_PRIVATE_KEY, "data/mint")
# from .app import create_app
# Ledger pubkey
ledger = Ledger("supersecretprivatekey", "data/mint")
# class MyFlaskApp(Flask):
# """
# We overload the Flask class so we can run a startup script (migration).
# Stupid Flask.
# """
# def __init__(self, *args, **kwargs):
# async def create_tasks_func():
# await asyncio.wait([m001_initial(ledger.db)])
# await ledger.load_used_proofs()
# error_message, balance = await WALLET.status()
# if error_message:
# print(
# f"The backend for {WALLET.__class__.__name__} isn't working properly: '{error_message}'",
# RuntimeWarning,
# )
# print(f"Lightning balance: {balance} sat")
# print("Mint started.")
# loop = asyncio.get_event_loop()
# loop.run_until_complete(create_tasks_func())
# loop.close()
# return super().__init__(*args, **kwargs)
# def run(self, *args, **options):
# super(MyFlaskApp, self).run(*args, **options)
def startup(app: FastAPI): def startup(app: FastAPI):
@@ -124,18 +89,39 @@ def create_app(config_object="core.settings") -> FastAPI:
app = create_app() app = create_app()
@app.get("/")
async def root():
return {"Hello": "world"}
@app.get("/keys") @app.get("/keys")
def keys(): def keys():
"""Get the public keys of the mint"""
return ledger.get_pubkeys() return ledger.get_pubkeys()
@app.get("/mint")
async def request_mint(amount: int = 0):
"""Request minting of tokens. Server responds with a Lightning invoice."""
payment_request, payment_hash = await ledger.request_mint(amount)
print(f"Lightning invoice: {payment_request}")
return {"pr": payment_request, "hash": payment_hash}
@app.post("/mint") @app.post("/mint")
async def mint(payloads: MintPayloads): async def mint(payloads: MintPayloads, payment_hash: Union[str, None] = None):
"""
Requests the minting of tokens belonging to a paid payment request.
Parameters:
pr: payment_request of the Lightning paid invoice.
Body (JSON):
payloads: contains a list of blinded messages waiting to be signed.
NOTE:
- This needs to be replaced by the preimage otherwise someone knowing
the payment_request can request the tokens instead of the rightful
owner.
- The blinded message should ideally be provided to the server *before* payment
in the GET /mint endpoint so that the server knows to sign only these tokens
when the invoice is paid.
"""
amounts = [] amounts = []
B_s = [] B_s = []
for payload in payloads.payloads: for payload in payloads.payloads:
@@ -146,7 +132,7 @@ async def mint(payloads: MintPayloads):
B_ = Point(x, y, secp256k1) B_ = Point(x, y, secp256k1)
B_s.append(B_) B_s.append(B_)
try: try:
promises = await ledger.mint(B_s, amounts) promises = await ledger.mint(B_s, amounts, payment_hash=payment_hash)
return promises return promises
except Exception as exc: except Exception as exc:
return {"error": str(exc)} return {"error": str(exc)}

View File

@@ -1,5 +1,7 @@
import secrets import secrets
from typing import Optional from typing import Optional
from core.base import Invoice
from core.db import Connection, Database from core.db import Connection, Database
@@ -62,3 +64,52 @@ async def invalidate_proof(
str(proof["secret"]), str(proof["secret"]),
), ),
) )
async def store_lightning_invoice(
invoice: Invoice,
db: Database,
conn: Optional[Connection] = None,
):
await (conn or db).execute(
"""
INSERT INTO invoices
(amount, pr, hash, issued)
VALUES (?, ?, ?, ?)
""",
(
invoice.amount,
invoice.pr,
invoice.hash,
invoice.issued,
),
)
async def get_lightning_invoice(
hash: str,
db: Database,
conn: Optional[Connection] = None,
):
row = await (conn or db).fetchone(
"""
SELECT * from invoices
WHERE hash = ?
""",
hash,
)
return Invoice.from_row(row)
async def update_lightning_invoice(
hash: str,
issued: bool,
db: Database,
conn: Optional[Connection] = None,
):
await (conn or db).execute(
"UPDATE invoices SET issued = ? WHERE hash = ?",
(issued, hash),
)

View File

@@ -5,15 +5,18 @@ Implementation of https://gist.github.com/phyro/935badc682057f418842c72961cf096c
import hashlib import hashlib
import time import time
from ecc.curve import secp256k1, Point from ecc.curve import Point, secp256k1
from ecc.key import gen_keypair from ecc.key import gen_keypair
import core.b_dhke as b_dhke import core.b_dhke as b_dhke
from core.base import Invoice
from core.db import Database from core.db import Database
from core.split import amount_split
from core.settings import MAX_ORDER from core.settings import MAX_ORDER
from mint.crud import store_promise, invalidate_proof, get_proofs_used from core.split import amount_split
from lightning import WALLET from lightning import WALLET
from mint.crud import (get_lightning_invoice, get_proofs_used,
invalidate_proof, store_lightning_invoice,
store_promise, update_lightning_invoice)
class Ledger: class Ledger:
@@ -65,7 +68,7 @@ class Ledger:
def _verify_proof(self, proof): def _verify_proof(self, proof):
"""Verifies that the proof of promise was issued by this ledger.""" """Verifies that the proof of promise was issued by this ledger."""
if proof["secret"] in self.proofs_used: if proof["secret"] in self.proofs_used:
raise Exception(f"Already spent. Secret: {proof['secret']}") raise Exception(f"tokens already spent. Secret: {proof['secret']}")
secret_key = self.keys[proof["amount"]] # Get the correct key to check against secret_key = self.keys[proof["amount"]] # Get the correct key to check against
C = Point(proof["C"]["x"], proof["C"]["y"], secp256k1) C = Point(proof["C"]["x"], proof["C"]["y"], secp256k1)
return b_dhke.verify(secret_key, C, proof["secret"]) return b_dhke.verify(secret_key, C, proof["secret"])
@@ -94,13 +97,13 @@ class Ledger:
self._verify_amount(amount) self._verify_amount(amount)
except: except:
# For better error message # For better error message
raise Exception("Invalid split amount: " + str(amount)) raise Exception("invalid split amount: " + str(amount))
def _verify_amount(self, amount): def _verify_amount(self, amount):
"""Any amount used should be a positive integer not larger than 2^MAX_ORDER.""" """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 valid = isinstance(amount, int) and amount > 0 and amount < 2**MAX_ORDER
if not valid: if not valid:
raise Exception("Invalid amount: " + str(amount)) raise Exception("invalid amount: " + str(amount))
return amount return amount
def _verify_equation_balanced(self, proofs, outs): def _verify_equation_balanced(self, proofs, outs):
@@ -119,42 +122,62 @@ class Ledger:
rv.append(2**pos) rv.append(2**pos)
return rv return rv
async def _request_lightning(self, amount): async def _request_lightning_invoice(self, amount):
error, balance = await WALLET.status() error, balance = await WALLET.status()
if error: if error:
raise Exception(f"Lightning wallet not responding: {error}") raise Exception(f"Lightning wallet not responding: {error}")
ok, checking_id, payment_request, error_message = await WALLET.create_invoice( ok, checking_id, payment_request, error_message = await WALLET.create_invoice(
amount, "cashu deposit" amount, "cashu deposit"
) )
print(payment_request) return payment_request, checking_id
timeout = time.time() + 60 # 1 minute to pay invoice async def _check_lightning_invoice(self, payment_hash):
while True: invoice: Invoice = await get_lightning_invoice(payment_hash, db=self.db)
status = await WALLET.get_invoice_status(checking_id) if invoice.issued:
if status.pending and time.time() > timeout: raise Exception("tokens already issued for this invoice")
print("Timeout") status = await WALLET.get_invoice_status(payment_hash)
return False if status.paid:
if not status.pending: await update_lightning_invoice(payment_hash, issued=True, db=self.db)
print("paid") return status.paid
return True
time.sleep(5) # async def _wait_for_lightning_invoice(self, amount):
# timeout = time.time() + 60 # 1 minute to pay invoice
# while True:
# status = await WALLET.get_invoice_status(checking_id)
# if status.pending and time.time() > timeout:
# print("Timeout")
# return False
# if not status.pending:
# print("paid")
# return True
# time.sleep(5)
# Public methods # Public methods
def get_pubkeys(self): def get_pubkeys(self):
"""Returns public keys for possible amounts.""" """Returns public keys for possible amounts."""
return self.pub_keys return self.pub_keys
async def mint(self, B_s, amounts, lightning=False): async def request_mint(self, amount):
"""Returns Lightning invoice and stores it in the db."""
payment_request, checking_id = await self._request_lightning_invoice(amount)
invoice = Invoice(
amount=amount, pr=payment_request, hash=checking_id, issued=False
)
if not payment_request or not checking_id:
raise Exception(f"Could not create Lightning invoice.")
await store_lightning_invoice(invoice, db=self.db)
return payment_request, checking_id
async def mint(self, B_s, amounts, payment_hash=None):
"""Mints a promise for coins for B_.""" """Mints a promise for coins for B_."""
# check if lightning invoice was paid
if payment_hash and not await self._check_lightning_invoice(payment_hash):
raise Exception("Lightning invoice not paid yet.")
for amount in amounts: for amount in amounts:
if amount not in [2**i for i in range(MAX_ORDER)]: if amount not in [2**i for i in range(MAX_ORDER)]:
raise Exception(f"Can only mint amounts up to {2**MAX_ORDER}.") raise Exception(f"Can only mint amounts up to {2**MAX_ORDER}.")
if lightning:
paid = await self._request_lightning(sum(amounts))
if not paid:
raise Exception(f"Did not receive payment in time.")
promises = [] promises = []
for B_, amount in zip(B_s, amounts): for B_, amount in zip(B_s, amounts):
split = amount_split(amount) split = amount_split(amount)

View File

@@ -33,6 +33,20 @@ async def m001_initial(db: Database):
""" """
) )
await db.execute(
"""
CREATE TABLE IF NOT EXISTS invoices (
amount INTEGER NOT NULL,
pr TEXT NOT NULL,
hash TEXT NOT NULL,
issued BOOL NOT NULL,
UNIQUE (hash)
);
"""
)
await db.execute( await db.execute(
""" """
CREATE VIEW IF NOT EXISTS balance_issued AS CREATE VIEW IF NOT EXISTS balance_issued AS

20
poetry.lock generated
View File

@@ -237,6 +237,20 @@ category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
[[package]]
name = "isort"
version = "5.10.1"
description = "A Python utility / library to sort Python imports."
category = "dev"
optional = false
python-versions = ">=3.6.1,<4.0"
[package.extras]
pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
requirements_deprecated_finder = ["pipreqs", "pip-api"]
colors = ["colorama (>=0.4.3,<0.5.0)"]
plugins = ["setuptools"]
[[package]] [[package]]
name = "itsdangerous" name = "itsdangerous"
version = "2.1.1" version = "2.1.1"
@@ -641,7 +655,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.8" python-versions = "^3.8"
content-hash = "ed00e50dd88910eebcd5db652d99e722f77226f4f102e3bdd1032a4b1700dd99" content-hash = "6c846ea3f88e2a806b202709a65d5e825cd2e5c8ea42892b1731fdeb7c718124"
[metadata.files] [metadata.files]
anyio = [ anyio = [
@@ -737,6 +751,10 @@ iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
] ]
isort = [
{file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
{file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
]
itsdangerous = [ itsdangerous = [
{file = "itsdangerous-2.1.1-py3-none-any.whl", hash = "sha256:935642cd4b987cdbee7210080004033af76306757ff8b4c0a506a4b6e06f02cf"}, {file = "itsdangerous-2.1.1-py3-none-any.whl", hash = "sha256:935642cd4b987cdbee7210080004033af76306757ff8b4c0a506a4b6e06f02cf"},
{file = "itsdangerous-2.1.1.tar.gz", hash = "sha256:7b7d3023cd35d9cb0c1fd91392f8c95c6fa02c59bf8ad64b8849be3401b95afb"}, {file = "itsdangerous-2.1.1.tar.gz", hash = "sha256:7b7d3023cd35d9cb0c1fd91392f8c95c6fa02c59bf8ad64b8849be3401b95afb"},

View File

@@ -34,6 +34,7 @@ loguru = "^0.6.0"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
black = {version = "^22.8.0", allow-prereleases = true} black = {version = "^22.8.0", allow-prereleases = true}
isort = "^5.10.1"
[build-system] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=1.0.0"]

View File

@@ -1,10 +1,9 @@
import asyncio import asyncio
from core.helpers import async_unwrap from core.helpers import async_unwrap
from wallet.migrations import m001_initial
from wallet.wallet import Wallet as Wallet1 from wallet.wallet import Wallet as Wallet1
from wallet.wallet import Wallet as Wallet2 from wallet.wallet import Wallet as Wallet2
from wallet.migrations import m001_initial
SERVER_ENDPOINT = "http://localhost:3338" SERVER_ENDPOINT = "http://localhost:3338"
@@ -56,7 +55,7 @@ async def run_test():
# print(exc.args[0]) # print(exc.args[0])
await assert_err( await assert_err(
wallet1.split(wallet1.proofs + proofs, 20), wallet1.split(wallet1.proofs + proofs, 20),
f"Error: Already spent. Secret: {proofs[0]['secret']}", f"Error: tokens already spent. Secret: {proofs[0]['secret']}",
) )
assert wallet1.balance == 63 + 64 assert wallet1.balance == 63 + 64
wallet1.status() wallet1.status()
@@ -73,7 +72,7 @@ async def run_test():
# Error: We try to double-spend and it fails # Error: We try to double-spend and it fails
await assert_err( await assert_err(
wallet1.split([proofs[0]], 10), wallet1.split([proofs[0]], 10),
f"Error: Already spent. Secret: {proofs[0]['secret']}", f"Error: tokens already spent. Secret: {proofs[0]['secret']}",
) )
assert wallet1.balance == 63 + 64 assert wallet1.balance == 63 + 64
@@ -102,7 +101,7 @@ async def run_test():
# Error: We try to double-spend and it fails # Error: We try to double-spend and it fails
await assert_err( await assert_err(
wallet1.split(w1_snd_proofs, 5), wallet1.split(w1_snd_proofs, 5),
f"Error: Already spent. Secret: {w1_snd_proofs[0]['secret']}", f"Error: tokens already spent. Secret: {w1_snd_proofs[0]['secret']}",
) )
assert wallet1.balance == 63 + 64 - 20 assert wallet1.balance == 63 + 64 - 20
@@ -113,7 +112,7 @@ async def run_test():
await assert_err( await assert_err(
wallet1.split(w1_snd_proofs, -500), wallet1.split(w1_snd_proofs, -500),
"Error: Invalid split amount: -500", "Error: invalid split amount: -500",
) )

View File

@@ -1,3 +1,3 @@
# from core.db import Database import sys
# db = Database("database", "data/wallet") sys.tracebacklimit = None

View File

@@ -1,9 +1,9 @@
import secrets import secrets
from typing import Optional from typing import Optional
from core.db import Connection, Database
# from wallet import db # from wallet import db
from core.base import Proof from core.base import Proof
from core.db import Connection, Database
async def store_proof( async def store_proof(

View File

@@ -1,18 +1,15 @@
import random
import asyncio import asyncio
import random
from typing import List
import requests import requests
from ecc.curve import secp256k1, Point from ecc.curve import Point, secp256k1
from typing import List
from core.base import Proof, BasePoint
import core.b_dhke as b_dhke import core.b_dhke as b_dhke
from core.base import BasePoint, MintPayload, MintPayloads, Proof, SplitPayload
from core.db import Database from core.db import Database
from core.split import amount_split from core.split import amount_split
from wallet.crud import get_proofs, invalidate_proof, store_proof
from wallet.crud import store_proof, invalidate_proof, get_proofs
from core.base import MintPayload, MintPayloads, SplitPayload
class LedgerAPI: class LedgerAPI:
@@ -48,7 +45,12 @@ class LedgerAPI:
proofs.append(proof.dict()) proofs.append(proof.dict())
return proofs return proofs
def mint(self, amounts): def request_mint(self, amount):
"""Requests a mint from the server and returns Lightning invoice."""
r = requests.get(self.url + "/mint", params={"amount": amount})
return r.json()
def mint(self, amounts, payment_hash=None):
"""Mints new coins and returns a proof of promise.""" """Mints new coins and returns a proof of promise."""
payloads: MintPayloads = MintPayloads() payloads: MintPayloads = MintPayloads()
secrets = [] secrets = []
@@ -61,13 +63,13 @@ class LedgerAPI:
blinded_point = BasePoint(x=str(B_.x), y=str(B_.y)) blinded_point = BasePoint(x=str(B_.x), y=str(B_.y))
payload: MintPayload = MintPayload(amount=amount, B_=blinded_point) payload: MintPayload = MintPayload(amount=amount, B_=blinded_point)
payloads.payloads.append(payload) payloads.payloads.append(payload)
promises = requests.post( promises = requests.post(
self.url + "/mint", self.url + "/mint",
json=payloads.dict(), json=payloads.dict(),
params={"payment_hash": payment_hash},
).json() ).json()
if "detail" in promises: if "error" in promises:
raise Exception("Error: {}".format(promises["detail"])) raise Exception("Error: {}".format(promises["error"]))
return self._construct_proofs(promises, [(r, s) for r, s in zip(rs, secrets)]) return self._construct_proofs(promises, [(r, s) for r, s in zip(rs, secrets)])
def split(self, proofs, amount): def split(self, proofs, amount):
@@ -122,9 +124,12 @@ class Wallet(LedgerAPI):
for proof in proofs: for proof in proofs:
await store_proof(proof, db=self.db) await store_proof(proof, db=self.db)
async def mint(self, amount): async def request_mint(self, amount):
return super().request_mint(amount)
async def mint(self, amount, payment_hash=None):
split = amount_split(amount) split = amount_split(amount)
proofs = super().mint(split) proofs = super().mint(split, payment_hash)
if proofs == []: if proofs == []:
raise Exception("received no proofs") raise Exception("received no proofs")
await self._store_proofs(proofs) await self._store_proofs(proofs)
@@ -154,7 +159,7 @@ class Wallet(LedgerAPI):
try: try:
await self.split(proofs, sum(p["amount"] for p in proofs)) await self.split(proofs, sum(p["amount"] for p in proofs))
except Exception as exc: except Exception as exc:
assert exc.args[0].startswith("Error: Already spent."), Exception( assert exc.args[0].startswith("Error: tokens already spent."), Exception(
"invalidating unspent tokens" "invalidating unspent tokens"
) )