From f2228e6a38ae99752b281f20666ae4caf8f09036 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 13 Sep 2022 21:36:18 +0300 Subject: [PATCH 1/4] isort --- cashu | 31 +++++++++------ core/b_dhke.py | 4 +- core/base.py | 21 +++++++++- core/db.py | 1 - core/settings.py | 5 +++ lightning/lnbits.py | 15 +++---- mint/app.py | 96 +++++++++++++++++++-------------------------- mint/crud.py | 51 ++++++++++++++++++++++++ mint/ledger.py | 71 +++++++++++++++++++++------------ mint/migrations.py | 14 +++++++ poetry.lock | 20 +++++++++- pyproject.toml | 1 + test_wallet.py | 11 +++--- wallet/__init__.py | 4 +- wallet/crud.py | 2 +- wallet/wallet.py | 35 ++++++++++------- 16 files changed, 251 insertions(+), 131 deletions(-) diff --git a/cashu b/cashu index 3b1963a..e182b6b 100755 --- a/cashu +++ b/cashu @@ -1,17 +1,16 @@ #!/usr/bin/env python -from wallet.wallet import Wallet as Wallet -from wallet.migrations import m001_initial import asyncio -import click -import json import base64 -from bech32 import bech32_encode, bech32_decode, convertbits - - -import asyncio +import json 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 def coro(f): @@ -23,20 +22,26 @@ def coro(f): @click.command("mint") -@click.option("--host", default="http://localhost:3338", help="Mint tokens.") -@click.option("--wallet", default="wallet", help="Mint tokens.") +@click.option("--host", default="http://localhost:3338", help="Mint hostname.") +@click.option("--wallet", default="wallet", help="Wallet to use.") @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("--receive", default="", help="Receive tokens.") @click.option("--invalidate", default="", help="Invalidate tokens.") @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) await m001_initial(db=wallet.db) await wallet.load_proofs() - if mint: + if mint and not hash: 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}") if send: diff --git a/core/b_dhke.py b/core/b_dhke.py index 87caa4c..986ade7 100644 --- a/core/b_dhke.py +++ b/core/b_dhke.py @@ -30,9 +30,9 @@ If true, C must have originated from Alice """ 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 diff --git a/core/base.py b/core/base.py index d0bab2e..6fba403 100644 --- a/core/base.py +++ b/core/base.py @@ -1,6 +1,7 @@ -from pydantic import BaseModel -from typing import List from sqlite3 import Row +from typing import List + +from pydantic import 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): amount: int B_: BasePoint diff --git a/core/db.py b/core/db.py index cf4baa6..c691ca5 100644 --- a/core/db.py +++ b/core/db.py @@ -9,7 +9,6 @@ from sqlalchemy import create_engine from sqlalchemy_aio.base import AsyncConnection from sqlalchemy_aio.strategy import ASYNCIO_STRATEGY # type: ignore - POSTGRES = "POSTGRES" COCKROACH = "COCKROACH" SQLITE = "SQLITE" diff --git a/core/settings.py b/core/settings.py index f5ccc08..af9ced9 100644 --- a/core/settings.py +++ b/core/settings.py @@ -5,10 +5,15 @@ env.read_env() 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_PORT = env.int("MINT_SERVER_PORT", default=3338) MINT_HOST = env.str("MINT_HOST", default="127.0.0.1") 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 diff --git a/lightning/lnbits.py b/lightning/lnbits.py index 88d1cd8..4063030 100644 --- a/lightning/lnbits.py +++ b/lightning/lnbits.py @@ -6,22 +6,19 @@ from typing import AsyncGenerator, Dict, Optional import requests -from .base import ( - InvoiceResponse, - PaymentResponse, - PaymentStatus, - StatusResponse, - Wallet, -) +from core.settings import LNBITS_ENDPOINT, LNBITS_KEY + +from .base import (InvoiceResponse, PaymentResponse, PaymentStatus, + StatusResponse, Wallet) class LNbitsWallet(Wallet): """https://github.com/lnbits/lnbits""" 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.s = requests.Session() self.s.auth = ("user", "pass") diff --git a/mint/app.py b/mint/app.py index dc63580..7171267 100644 --- a/mint/app.py +++ b/mint/app.py @@ -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 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 +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.migrations import m001_initial -from lightning import WALLET -import core.settings as settings -from core.base import MintPayload, MintPayloads, SplitPayload - -# 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) +ledger = Ledger(MINT_PRIVATE_KEY, "data/mint") def startup(app: FastAPI): @@ -124,18 +89,39 @@ def create_app(config_object="core.settings") -> FastAPI: app = create_app() -@app.get("/") -async def root(): - return {"Hello": "world"} - - @app.get("/keys") def keys(): + """Get the public keys of the mint""" 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") -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 = [] B_s = [] for payload in payloads.payloads: @@ -146,7 +132,7 @@ async def mint(payloads: MintPayloads): B_ = Point(x, y, secp256k1) B_s.append(B_) try: - promises = await ledger.mint(B_s, amounts) + promises = await ledger.mint(B_s, amounts, payment_hash=payment_hash) return promises except Exception as exc: return {"error": str(exc)} diff --git a/mint/crud.py b/mint/crud.py index ae82ef8..d53fe10 100644 --- a/mint/crud.py +++ b/mint/crud.py @@ -1,5 +1,7 @@ import secrets from typing import Optional + +from core.base import Invoice from core.db import Connection, Database @@ -62,3 +64,52 @@ async def invalidate_proof( 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), + ) diff --git a/mint/ledger.py b/mint/ledger.py index 9c37262..4fcd55a 100644 --- a/mint/ledger.py +++ b/mint/ledger.py @@ -5,15 +5,18 @@ Implementation of https://gist.github.com/phyro/935badc682057f418842c72961cf096c import hashlib import time -from ecc.curve import secp256k1, Point +from ecc.curve import Point, secp256k1 from ecc.key import gen_keypair import core.b_dhke as b_dhke +from core.base import Invoice from core.db import Database -from core.split import amount_split 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 mint.crud import (get_lightning_invoice, get_proofs_used, + invalidate_proof, store_lightning_invoice, + store_promise, update_lightning_invoice) class Ledger: @@ -65,7 +68,7 @@ class Ledger: def _verify_proof(self, proof): """Verifies that the proof of promise was issued by this ledger.""" 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 C = Point(proof["C"]["x"], proof["C"]["y"], secp256k1) return b_dhke.verify(secret_key, C, proof["secret"]) @@ -94,13 +97,13 @@ class Ledger: self._verify_amount(amount) except: # For better error message - raise Exception("Invalid split amount: " + str(amount)) + raise Exception("invalid split amount: " + str(amount)) def _verify_amount(self, amount): """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)) + raise Exception("invalid amount: " + str(amount)) return amount def _verify_equation_balanced(self, proofs, outs): @@ -119,42 +122,62 @@ class Ledger: rv.append(2**pos) return rv - async def _request_lightning(self, amount): + async def _request_lightning_invoice(self, amount): error, balance = await WALLET.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" ) - print(payment_request) + return payment_request, checking_id - 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) + async def _check_lightning_invoice(self, payment_hash): + invoice: Invoice = await get_lightning_invoice(payment_hash, db=self.db) + if invoice.issued: + raise Exception("tokens already issued for this invoice") + status = await WALLET.get_invoice_status(payment_hash) + if status.paid: + await update_lightning_invoice(payment_hash, issued=True, db=self.db) + return status.paid + + # 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 def get_pubkeys(self): """Returns public keys for possible amounts.""" 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_.""" + # 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: if amount not in [2**i for i in range(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 = [] for B_, amount in zip(B_s, amounts): split = amount_split(amount) diff --git a/mint/migrations.py b/mint/migrations.py index 236b7f0..d4c2435 100644 --- a/mint/migrations.py +++ b/mint/migrations.py @@ -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( """ CREATE VIEW IF NOT EXISTS balance_issued AS diff --git a/poetry.lock b/poetry.lock index d1740bf..5849131 100644 --- a/poetry.lock +++ b/poetry.lock @@ -237,6 +237,20 @@ category = "main" optional = false 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]] name = "itsdangerous" version = "2.1.1" @@ -641,7 +655,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "ed00e50dd88910eebcd5db652d99e722f77226f4f102e3bdd1032a4b1700dd99" +content-hash = "6c846ea3f88e2a806b202709a65d5e825cd2e5c8ea42892b1731fdeb7c718124" [metadata.files] anyio = [ @@ -737,6 +751,10 @@ iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {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 = [ {file = "itsdangerous-2.1.1-py3-none-any.whl", hash = "sha256:935642cd4b987cdbee7210080004033af76306757ff8b4c0a506a4b6e06f02cf"}, {file = "itsdangerous-2.1.1.tar.gz", hash = "sha256:7b7d3023cd35d9cb0c1fd91392f8c95c6fa02c59bf8ad64b8849be3401b95afb"}, diff --git a/pyproject.toml b/pyproject.toml index f9e1bb8..cede4ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ loguru = "^0.6.0" [tool.poetry.dev-dependencies] black = {version = "^22.8.0", allow-prereleases = true} +isort = "^5.10.1" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/test_wallet.py b/test_wallet.py index b7ff5fa..1ce535a 100644 --- a/test_wallet.py +++ b/test_wallet.py @@ -1,10 +1,9 @@ import asyncio 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 Wallet2 -from wallet.migrations import m001_initial - SERVER_ENDPOINT = "http://localhost:3338" @@ -56,7 +55,7 @@ async def run_test(): # print(exc.args[0]) await assert_err( 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 wallet1.status() @@ -73,7 +72,7 @@ async def run_test(): # Error: We try to double-spend and it fails await assert_err( 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 @@ -102,7 +101,7 @@ async def run_test(): # Error: We try to double-spend and it fails await assert_err( 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 @@ -113,7 +112,7 @@ async def run_test(): await assert_err( wallet1.split(w1_snd_proofs, -500), - "Error: Invalid split amount: -500", + "Error: invalid split amount: -500", ) diff --git a/wallet/__init__.py b/wallet/__init__.py index c4af010..bf11b51 100644 --- a/wallet/__init__.py +++ b/wallet/__init__.py @@ -1,3 +1,3 @@ -# from core.db import Database +import sys -# db = Database("database", "data/wallet") +sys.tracebacklimit = None diff --git a/wallet/crud.py b/wallet/crud.py index 29d81e5..a15353b 100644 --- a/wallet/crud.py +++ b/wallet/crud.py @@ -1,9 +1,9 @@ import secrets from typing import Optional -from core.db import Connection, Database # from wallet import db from core.base import Proof +from core.db import Connection, Database async def store_proof( diff --git a/wallet/wallet.py b/wallet/wallet.py index 8f2ec0d..70bdd51 100644 --- a/wallet/wallet.py +++ b/wallet/wallet.py @@ -1,18 +1,15 @@ -import random import asyncio +import random +from typing import List import requests -from ecc.curve import secp256k1, Point -from typing import List -from core.base import Proof, BasePoint +from ecc.curve import Point, secp256k1 import core.b_dhke as b_dhke +from core.base import BasePoint, MintPayload, MintPayloads, Proof, SplitPayload from core.db import Database from core.split import amount_split - -from wallet.crud import store_proof, invalidate_proof, get_proofs - -from core.base import MintPayload, MintPayloads, SplitPayload +from wallet.crud import get_proofs, invalidate_proof, store_proof class LedgerAPI: @@ -48,7 +45,12 @@ class LedgerAPI: proofs.append(proof.dict()) 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.""" payloads: MintPayloads = MintPayloads() secrets = [] @@ -61,13 +63,13 @@ class LedgerAPI: blinded_point = BasePoint(x=str(B_.x), y=str(B_.y)) payload: MintPayload = MintPayload(amount=amount, B_=blinded_point) payloads.payloads.append(payload) - promises = requests.post( self.url + "/mint", json=payloads.dict(), + params={"payment_hash": payment_hash}, ).json() - if "detail" in promises: - raise Exception("Error: {}".format(promises["detail"])) + if "error" in promises: + raise Exception("Error: {}".format(promises["error"])) return self._construct_proofs(promises, [(r, s) for r, s in zip(rs, secrets)]) def split(self, proofs, amount): @@ -122,9 +124,12 @@ class Wallet(LedgerAPI): for proof in proofs: 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) - proofs = super().mint(split) + proofs = super().mint(split, payment_hash) if proofs == []: raise Exception("received no proofs") await self._store_proofs(proofs) @@ -154,7 +159,7 @@ class Wallet(LedgerAPI): try: await self.split(proofs, sum(p["amount"] for p in proofs)) 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" ) From 0245ce00dedf0c4c7698b239634cc772e799b91f Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 13 Sep 2022 21:36:58 +0300 Subject: [PATCH 2/4] env example --- .env.example | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..dcdd9b0 --- /dev/null +++ b/.env.example @@ -0,0 +1,15 @@ +DEBUG = true + +MINT_PRIVATE_KEY=supersecretprivatekey + +# for the mint +MINT_SERVER_HOST=127.0.0.1 +MINT_SERVER_PORT=3338 + +# for the wallet +MINT_HOST=127.0.0.1 +MINT_PORT=3338 + +# LnbitsWallet +LNBITS_ENDPOINT=https://legend.lnbits.com +LNBITS_KEY=yourkeyasdasdasd \ No newline at end of file From dbb1e1d391613bdce6a9b6bd89e75e6c4ac01d3b Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 13 Sep 2022 21:38:00 +0300 Subject: [PATCH 3/4] readme --- README.md | 7 +++++++ mint/app.py | 3 --- mint/ledger.py | 12 ++++++++---- wallet/wallet.py | 1 - 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 75948f1..e2adac6 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,13 @@ pyenv local 3.9.13 poetry install ``` +### Configuration +```bash +mv .env.example .env +# edit .env file +vim .env +``` + ## Run mint ```bash poetry run uvicorn mint.app:app --port 3338 diff --git a/mint/app.py b/mint/app.py index 7171267..da0189d 100644 --- a/mint/app.py +++ b/mint/app.py @@ -1,13 +1,10 @@ import asyncio import logging 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 import core.settings as settings diff --git a/mint/ledger.py b/mint/ledger.py index 4fcd55a..f4027c7 100644 --- a/mint/ledger.py +++ b/mint/ledger.py @@ -3,7 +3,6 @@ Implementation of https://gist.github.com/phyro/935badc682057f418842c72961cf096c """ import hashlib -import time from ecc.curve import Point, secp256k1 from ecc.key import gen_keypair @@ -14,9 +13,14 @@ from core.db import Database from core.settings import MAX_ORDER from core.split import amount_split from lightning import WALLET -from mint.crud import (get_lightning_invoice, get_proofs_used, - invalidate_proof, store_lightning_invoice, - store_promise, update_lightning_invoice) +from mint.crud import ( + get_lightning_invoice, + get_proofs_used, + invalidate_proof, + store_lightning_invoice, + store_promise, + update_lightning_invoice, +) class Ledger: diff --git a/wallet/wallet.py b/wallet/wallet.py index 70bdd51..311abdb 100644 --- a/wallet/wallet.py +++ b/wallet/wallet.py @@ -1,4 +1,3 @@ -import asyncio import random from typing import List From 3b0ebf9f6b8eda17c8e454bf2a49d8904ac50402 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 13 Sep 2022 21:55:42 +0300 Subject: [PATCH 4/4] update --- .env.example | 15 ++++++++------- README.md | 11 ++++++++++- cashu | 5 +++-- core/settings.py | 5 +++++ poetry.lock | 13 ++++++------- pyproject.toml | 2 +- 6 files changed, 33 insertions(+), 18 deletions(-) diff --git a/.env.example b/.env.example index dcdd9b0..accc011 100644 --- a/.env.example +++ b/.env.example @@ -1,15 +1,16 @@ DEBUG = true -MINT_PRIVATE_KEY=supersecretprivatekey +# WALLET -# for the mint -MINT_SERVER_HOST=127.0.0.1 -MINT_SERVER_PORT=3338 - -# for the wallet MINT_HOST=127.0.0.1 MINT_PORT=3338 -# LnbitsWallet +# MINT + +MINT_PRIVATE_KEY=supersecretprivatekey + +MINT_SERVER_HOST=127.0.0.1 +MINT_SERVER_PORT=3338 + LNBITS_ENDPOINT=https://legend.lnbits.com LNBITS_KEY=yourkeyasdasdasd \ No newline at end of file diff --git a/README.md b/README.md index e2adac6..ff81e0b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **The author is NOT a cryptographer and has not tested the libraries used or the code nor has anyone reviewed the work. This means it's very likely a fatal flaw somewhere. This is meant only as educational and is not production ready.** -Ecash implementation based on David Wagner's variant of Chaumian blinding. Token logic based on [minicash](https://github.com/phyro/minicash) ([description](https://gist.github.com/phyro/935badc682057f418842c72961cf096c)) which implements a [Blind Diffie-Hellman Key Exchange](https://cypherpunks.venona.com/date/1996/03/msg01848.html) scheme written down by Ruben Somsen [here](https://gist.github.com/RubenSomsen/be7a4760dd4596d06963d67baf140406). The database mechanics and the Lightning backend is inspired by [LNbits](https://github.com/lnbits/lnbits-legend). +Ecash implementation based on David Wagner's variant of Chaumian blinding. Token logic based on [minicash](https://github.com/phyro/minicash) ([description](https://gist.github.com/phyro/935badc682057f418842c72961cf096c)) which implements a [Blind Diffie-Hellman Key Exchange](https://cypherpunks.venona.com/date/1996/03/msg01848.html) scheme written down by Ruben Somsen [here](https://gist.github.com/RubenSomsen/be7a4760dd4596d06963d67baf140406). The database mechanics and the Lightning backend uses parts from [LNbits](https://github.com/lnbits/lnbits-legend). Big thanks to [phyro](https://github.com/phyro) for their work and further discussions and improvements. @@ -53,5 +53,14 @@ poetry run uvicorn mint.app:app --port 3338 poetry run ./cashu --wallet=wallet --mint=420 ``` +## Test instance +*Warning: this instance is just for demonstrations only. Currently, only Lightning deposits work but not withdrawals. The server could vanish at any moment so consider any Satoshis you deposit a donation.* + +Change the appropriate `.env` file settings to +```bash +MINT_HOST=8333.space +MINT_PORT=3338 +``` + ## Screenshot ![screenshot](https://user-images.githubusercontent.com/93376500/189533335-68a863e2-bacd-47c1-aecc-e4fb09883d11.jpg) diff --git a/cashu b/cashu index e182b6b..739b543 100755 --- a/cashu +++ b/cashu @@ -10,7 +10,7 @@ from bech32 import bech32_decode, bech32_encode, convertbits from wallet.migrations import m001_initial from wallet.wallet import Wallet as Wallet - +from core.settings import MINT_URL # https://github.com/pallets/click/issues/85#issuecomment-503464628 def coro(f): @@ -22,7 +22,7 @@ def coro(f): @click.command("mint") -@click.option("--host", default="http://localhost:3338", help="Mint hostname.") +@click.option("--host", default=MINT_URL, help="Mint hostname.") @click.option("--wallet", default="wallet", help="Wallet to use.") @click.option("--mint", default=0, help="Mint tokens.") @click.option("--hash", default="", help="Hash of the paid invoice.") @@ -31,6 +31,7 @@ def coro(f): @click.option("--invalidate", default="", help="Invalidate tokens.") @coro async def main(host, wallet, mint, hash, send, receive, invalidate): + print(host) wallet = Wallet(host, f"data/{wallet}", wallet) await m001_initial(db=wallet.db) await wallet.load_proofs() diff --git a/core/settings.py b/core/settings.py index af9ced9..c848330 100644 --- a/core/settings.py +++ b/core/settings.py @@ -13,6 +13,11 @@ MINT_SERVER_PORT = env.int("MINT_SERVER_PORT", default=3338) MINT_HOST = env.str("MINT_HOST", default="127.0.0.1") MINT_PORT = env.int("MINT_PORT", default=3338) +if MINT_HOST == "127.0.0.1": + MINT_URL = f"http://{MINT_HOST}:{MINT_PORT}" +else: + MINT_URL = f"https://{MINT_HOST}:{MINT_PORT}" + LNBITS_ENDPOINT = env.str("LNBITS_ENDPOINT", default=None) LNBITS_KEY = env.str("LNBITS_KEY", default=None) diff --git a/poetry.lock b/poetry.lock index 5849131..1aaaaeb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -589,15 +589,14 @@ python-versions = ">=3.7" [[package]] name = "urllib3" -version = "1.26.9" +version = "1.23" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" [package.extras] -brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +secure = ["pyOpenSSL (>=0.14,<18.0.0)", "pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] @@ -655,7 +654,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "6c846ea3f88e2a806b202709a65d5e825cd2e5c8ea42892b1731fdeb7c718124" +content-hash = "d45268f8d5612bc589a73025d9191d26eee50ebf335d836b4ad3c10e6c72b95c" [metadata.files] anyio = [ @@ -1025,8 +1024,8 @@ typing-extensions = [ {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, ] urllib3 = [ - {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, - {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, + {file = "urllib3-1.23-py2.py3-none-any.whl", hash = "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"}, + {file = "urllib3-1.23.tar.gz", hash = "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf"}, ] uvicorn = [ {file = "uvicorn-0.18.3-py3-none-any.whl", hash = "sha256:0abd429ebb41e604ed8d2be6c60530de3408f250e8d2d84967d85ba9e86fe3af"}, diff --git a/pyproject.toml b/pyproject.toml index cede4ec..0ab6164 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ idna = "3.3" itsdangerous = "2.1.1" Jinja2 = "3.0.3" MarkupSafe = "2.1.1" -urllib3 = "1.26.9" +urllib3 = "1.23" Werkzeug = "2.2.2" ecc-pycrypto = {git = "https://github.com/lc6chang/ecc-pycrypto.git", rev = "v1.0.1"} asgiref = "^3.5.2"