From e18021cb9e30ef4a0f5f50d832895698c9d4a557 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 13 Sep 2022 16:06:40 +0300 Subject: [PATCH] fastapi --- core/settings.py | 10 ++ mint/app.py | 168 +++++++++++++++++++++------- mint/ledger.py | 23 ++-- poetry.lock | 286 ++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 5 + wallet/crud.py | 2 +- wallet/models.py | 20 ---- wallet/wallet.py | 57 +++++----- 8 files changed, 472 insertions(+), 99 deletions(-) delete mode 100644 wallet/models.py diff --git a/core/settings.py b/core/settings.py index 0afdcce..f6096f6 100644 --- a/core/settings.py +++ b/core/settings.py @@ -1 +1,11 @@ +from environs import Env # type: ignore + +env = Env() +env.read_env() + +DEBUG = env.bool("DEBUG", default=False) + +MINT_HOST = env.str("MINT_HOST", default="127.0.0.1") +MINT_PORT = env.int("MINT_PORT", default=3338) + MAX_ORDER = 64 diff --git a/mint/app.py b/mint/app.py index 46a6204..dc63580 100644 --- a/mint/app.py +++ b/mint/app.py @@ -1,67 +1,148 @@ -import hashlib - from ecc.curve import secp256k1, Point -from flask import Flask, request -import os +from fastapi import FastAPI +from fastapi.routing import APIRouter +from fastapi.params import Depends, Query, Body + +import sys import asyncio +import logging +import uvicorn +from loguru import logger 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") +ledger = Ledger("supersecretprivatekey", "data/mint") -class MyFlaskApp(Flask): - """ - We overload the Flask class so we can run a startup script (migration). - Stupid Flask. - """ +# 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() +# 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, - ) +# 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(f"Lightning balance: {balance} sat") - print("Mint started.") +# print("Mint started.") - loop = asyncio.get_event_loop() - loop.run_until_complete(create_tasks_func()) - loop.close() +# loop = asyncio.get_event_loop() +# loop.run_until_complete(create_tasks_func()) +# loop.close() - return super().__init__(*args, **kwargs) +# return super().__init__(*args, **kwargs) - def run(self, *args, **options): - super(MyFlaskApp, self).run(*args, **options) +# def run(self, *args, **options): +# super(MyFlaskApp, self).run(*args, **options) -app = MyFlaskApp(__name__) +def startup(app: FastAPI): + @app.on_event("startup") + async def load_ledger(): + await asyncio.wait([m001_initial(ledger.db)]) + await ledger.load_used_proofs() + + 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("Mint started.") -@app.route("/keys") +def create_app(config_object="core.settings") -> FastAPI: + def configure_logger() -> None: + class Formatter: + def __init__(self): + self.padding = 0 + self.minimal_fmt: str = "{time:YYYY-MM-DD HH:mm:ss.SS} | {level} | {message}\n" + if settings.DEBUG: + self.fmt: str = "{time:YYYY-MM-DD HH:mm:ss.SS} | {level: <4} | {name}:{function}:{line} | {message}\n" + else: + self.fmt: str = self.minimal_fmt + + def format(self, record): + function = "{function}".format(**record) + if function == "emit": # uvicorn logs + return self.minimal_fmt + return self.fmt + + class InterceptHandler(logging.Handler): + def emit(self, record): + try: + level = logger.level(record.levelname).name + except ValueError: + level = record.levelno + logger.log(level, record.getMessage()) + + logger.remove() + log_level: str = "INFO" + formatter = Formatter() + logger.add(sys.stderr, level=log_level, format=formatter.format) + + logging.getLogger("uvicorn").handlers = [InterceptHandler()] + logging.getLogger("uvicorn.access").handlers = [InterceptHandler()] + + configure_logger() + + app = FastAPI( + title="Cashu Mint", + description="Ecash wallet and mint.", + license_info={ + "name": "MIT License", + "url": "https://raw.githubusercontent.com/callebtc/cashu/main/LICENSE", + }, + ) + + startup(app) + return app + + +app = create_app() + + +@app.get("/") +async def root(): + return {"Hello": "world"} + + +@app.get("/keys") def keys(): return ledger.get_pubkeys() -@app.route("/mint", methods=["POST"]) -async def mint(): - payload = request.json +@app.post("/mint") +async def mint(payloads: MintPayloads): amounts = [] B_s = [] - for k, v in payload.items(): + for payload in payloads.payloads: + v = payload.dict() amounts.append(v["amount"]) - x = int(v["x"]) - y = int(v["y"]) + x = int(v["B_"]["x"]) + y = int(v["B_"]["y"]) B_ = Point(x, y, secp256k1) B_s.append(B_) try: @@ -71,13 +152,18 @@ async def mint(): return {"error": str(exc)} -@app.route("/split", methods=["POST"]) -async def split(): - proofs = request.json["proofs"] - amount = request.json["amount"] - output_data = request.json["output_data"] +@app.post("/split") +async def split(payload: SplitPayload): + v = payload.dict() + proofs = v["proofs"] + amount = v["amount"] + output_data = v["output_data"]["payloads"] try: fst_promises, snd_promises = await ledger.split(proofs, amount, output_data) return {"fst": fst_promises, "snd": snd_promises} except Exception as exc: return {"error": str(exc)} + + +# if __name__ == "__main__": +# uvicorn.run(app, host="127.0.0.1", port=5049) diff --git a/mint/ledger.py b/mint/ledger.py index ba2adb9..9c37262 100644 --- a/mint/ledger.py +++ b/mint/ledger.py @@ -18,9 +18,11 @@ from lightning import WALLET class Ledger: def __init__(self, secret_key: str, db: str): - self.master_key = secret_key self.proofs_used = set() + + self.master_key = secret_key self.keys = self._derive_keys(self.master_key) + self.pub_keys = self._derive_pubkeys(self.keys) self.db = Database("mint", db) async def load_used_proofs(self): @@ -40,6 +42,12 @@ class Ledger: for i in range(MAX_ORDER) } + @staticmethod + def _derive_pubkeys(keys): + return { + amt: keys[amt] * secp256k1.G for amt in [2**i for i in range(MAX_ORDER)] + } + async def _generate_promises(self, amounts, B_s): """Generates promises that sum to the given amount.""" return [ @@ -75,7 +83,7 @@ class Ledger: secrets = [p["secret"] for p in proofs] if len(secrets) != len(list(set(secrets))): return False - B_xs = [od["B'"]["x"] for od in output_data] + B_xs = [od["B_"]["x"] for od in output_data] if len(B_xs) != len(list(set(B_xs))): return False return True @@ -134,12 +142,9 @@ class Ledger: # Public methods def get_pubkeys(self): """Returns public keys for possible amounts.""" - return { - amt: self.keys[amt] * secp256k1.G - for amt in [2**i for i in range(MAX_ORDER)] - } + return self.pub_keys - async def mint(self, B_s, amounts, lightning=True): + async def mint(self, B_s, amounts, lightning=False): """Mints a promise for coins for B_.""" for amount in amounts: if amount not in [2**i for i in range(MAX_ORDER)]: @@ -183,8 +188,8 @@ class Ledger: outs_fst = amount_split(total - amount) outs_snd = amount_split(amount) - B_fst = [od["B'"] for od in output_data[: len(outs_fst)]] - B_snd = [od["B'"] for od in output_data[len(outs_fst) :]] + B_fst = [od["B_"] for od in output_data[: len(outs_fst)]] + B_snd = [od["B_"] for od in output_data[len(outs_fst) :]] prom_fst, prom_snd = await self._generate_promises( outs_fst, B_fst ), await self._generate_promises(outs_snd, B_snd) diff --git a/poetry.lock b/poetry.lock index 396bfb2..d1740bf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,20 @@ +[[package]] +name = "anyio" +version = "3.6.1" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "main" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"] +test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "contextlib2", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"] +trio = ["trio (>=0.16)"] + [[package]] name = "asgiref" version = "3.5.2" @@ -31,6 +48,28 @@ category = "main" optional = false python-versions = ">=3.5" +[[package]] +name = "black" +version = "22.8.0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + [[package]] name = "certifi" version = "2022.6.15.1" @@ -103,6 +142,42 @@ url = "https://github.com/lc6chang/ecc-pycrypto.git" reference = "v1.0.1" resolved_reference = "eb8b8c19a81a52d9cf705d90a597a78cdaf2b6f6" +[[package]] +name = "environs" +version = "9.5.0" +description = "simplified environment variable parsing" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +marshmallow = ">=3.0.0" +python-dotenv = "*" + +[package.extras] +dev = ["pytest", "dj-database-url", "dj-email-url", "django-cache-url", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)", "tox"] +django = ["dj-database-url", "dj-email-url", "django-cache-url"] +lint = ["flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)"] +tests = ["pytest", "dj-database-url", "dj-email-url", "django-cache-url"] + +[[package]] +name = "fastapi" +version = "0.83.0" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +category = "main" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" +starlette = "0.19.1" + +[package.extras] +all = ["requests (>=2.24.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<3.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"] +dev = ["python-jose[cryptography] (>=3.3.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)", "pre-commit (>=2.17.0,<3.0.0)"] +doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "typer (>=0.4.1,<0.5.0)", "pyyaml (>=5.3.1,<7.0.0)"] +test = ["pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "mypy (==0.910)", "flake8 (>=3.8.3,<6.0.0)", "black (==22.3.0)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "flask (>=1.1.2,<3.0.0)", "anyio[trio] (>=3.2.1,<4.0.0)", "types-ujson (==4.2.1)", "types-orjson (==3.6.2)", "types-dataclasses (==0.6.5)"] + [[package]] name = "flask" version = "2.2.2" @@ -122,6 +197,14 @@ Werkzeug = ">=2.2.2" async = ["asgiref (>=3.2)"] dotenv = ["python-dotenv"] +[[package]] +name = "h11" +version = "0.13.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "idna" version = "3.3" @@ -176,6 +259,21 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "loguru" +version = "0.6.0" +description = "Python logging made (stupidly) simple" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} +win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} + +[package.extras] +dev = ["sphinx-rtd-theme (>=0.4.3)", "sphinx-autobuild (>=0.7.1)", "Sphinx (>=4.1.1)", "isort (>=5.1.1)", "black (>=19.10b0)", "pytest-cov (>=2.7.1)", "pytest (>=4.6.2)", "tox (>=3.9.0)", "flake8 (>=3.7.7)", "docutils (==0.16)", "colorama (>=0.3.4)"] + [[package]] name = "markupsafe" version = "2.1.1" @@ -184,6 +282,31 @@ category = "main" optional = false python-versions = ">=3.7" +[[package]] +name = "marshmallow" +version = "3.17.1" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +packaging = ">=17.0" + +[package.extras] +dev = ["pytest", "pytz", "simplejson", "mypy (==0.971)", "flake8 (==5.0.4)", "flake8-bugbear (==22.8.22)", "pre-commit (>=2.4,<3.0)", "tox"] +docs = ["sphinx (==5.1.1)", "sphinx-issues (==3.0.1)", "alabaster (==0.7.12)", "sphinx-version-warning (==1.1.2)", "autodocsumm (==0.2.9)"] +lint = ["mypy (==0.971)", "flake8 (==5.0.4)", "flake8-bugbear (==22.8.22)", "pre-commit (>=2.4,<3.0)"] +tests = ["pytest", "pytz", "simplejson"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "outcome" version = "1.2.0" @@ -206,6 +329,26 @@ python-versions = ">=3.6" [package.dependencies] pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" +[[package]] +name = "pathspec" +version = "0.10.1" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "platformdirs" +version = "2.5.2" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] +test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] + [[package]] name = "pluggy" version = "1.0.0" @@ -302,6 +445,17 @@ pytest = ">=6.1.0" [package.extras] testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)", "flaky (>=3.5.0)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] +[[package]] +name = "python-dotenv" +version = "0.21.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +cli = ["click (>=5.0)"] + [[package]] name = "represent" version = "1.6.0.post0" @@ -342,6 +496,14 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +category = "main" +optional = false +python-versions = ">=3.7" + [[package]] name = "sqlalchemy" version = "1.3.24" @@ -380,6 +542,21 @@ test = ["pytest (>=5.4)", "pytest-asyncio (>=0.14)", "pytest-trio (>=0.6)"] test-noextras = ["pytest (>=5.4)", "pytest-asyncio (>=0.14)"] trio = ["trio (>=0.15)"] +[[package]] +name = "starlette" +version = "0.19.1" +description = "The little ASGI library that shines." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +anyio = ">=3.4.0,<5" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] + [[package]] name = "tomli" version = "2.0.1" @@ -409,6 +586,21 @@ 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"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +[[package]] +name = "uvicorn" +version = "0.18.3" +description = "The lightning-fast ASGI server." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" + +[package.extras] +standard = ["colorama (>=0.4)", "httptools (>=0.4.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.0)"] + [[package]] name = "werkzeug" version = "2.2.2" @@ -423,6 +615,17 @@ MarkupSafe = ">=2.1.1" [package.extras] watchdog = ["watchdog"] +[[package]] +name = "win32-setctime" +version = "1.1.0" +description = "A small Python utility to set file creation time on Windows" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] + [[package]] name = "zipp" version = "3.8.1" @@ -438,9 +641,13 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "d2c4df45eea8820487f68d4f8a5509956c1a114a0b65ebd7f707187253612a1a" +content-hash = "ed00e50dd88910eebcd5db652d99e722f77226f4f102e3bdd1032a4b1700dd99" [metadata.files] +anyio = [ + {file = "anyio-3.6.1-py3-none-any.whl", hash = "sha256:cb29b9c70620506a9a8f87a309591713446953302d7d995344d0d7c6c0c9a7be"}, + {file = "anyio-3.6.1.tar.gz", hash = "sha256:413adf95f93886e442aea925f3ee43baa5a765a64a0f52c6081894f9992fdd0b"}, +] asgiref = [ {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"}, {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"}, @@ -453,6 +660,31 @@ bech32 = [ {file = "bech32-1.2.0-py3-none-any.whl", hash = "sha256:990dc8e5a5e4feabbdf55207b5315fdd9b73db40be294a19b3752cde9e79d981"}, {file = "bech32-1.2.0.tar.gz", hash = "sha256:7d6db8214603bd7871fcfa6c0826ef68b85b0abd90fa21c285a9c5e21d2bd899"}, ] +black = [ + {file = "black-22.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce957f1d6b78a8a231b18e0dd2d94a33d2ba738cd88a7fe64f53f659eea49fdd"}, + {file = "black-22.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5107ea36b2b61917956d018bd25129baf9ad1125e39324a9b18248d362156a27"}, + {file = "black-22.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8166b7bfe5dcb56d325385bd1d1e0f635f24aae14b3ae437102dedc0c186747"}, + {file = "black-22.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd82842bb272297503cbec1a2600b6bfb338dae017186f8f215c8958f8acf869"}, + {file = "black-22.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d839150f61d09e7217f52917259831fe2b689f5c8e5e32611736351b89bb2a90"}, + {file = "black-22.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a05da0430bd5ced89176db098567973be52ce175a55677436a271102d7eaa3fe"}, + {file = "black-22.8.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a098a69a02596e1f2a58a2a1c8d5a05d5a74461af552b371e82f9fa4ada8342"}, + {file = "black-22.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5594efbdc35426e35a7defa1ea1a1cb97c7dbd34c0e49af7fb593a36bd45edab"}, + {file = "black-22.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a983526af1bea1e4cf6768e649990f28ee4f4137266921c2c3cee8116ae42ec3"}, + {file = "black-22.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b2c25f8dea5e8444bdc6788a2f543e1fb01494e144480bc17f806178378005e"}, + {file = "black-22.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:78dd85caaab7c3153054756b9fe8c611efa63d9e7aecfa33e533060cb14b6d16"}, + {file = "black-22.8.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:cea1b2542d4e2c02c332e83150e41e3ca80dc0fb8de20df3c5e98e242156222c"}, + {file = "black-22.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5b879eb439094751185d1cfdca43023bc6786bd3c60372462b6f051efa6281a5"}, + {file = "black-22.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a12e4e1353819af41df998b02c6742643cfef58282915f781d0e4dd7a200411"}, + {file = "black-22.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3a73f66b6d5ba7288cd5d6dad9b4c9b43f4e8a4b789a94bf5abfb878c663eb3"}, + {file = "black-22.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:e981e20ec152dfb3e77418fb616077937378b322d7b26aa1ff87717fb18b4875"}, + {file = "black-22.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8ce13ffed7e66dda0da3e0b2eb1bdfc83f5812f66e09aca2b0978593ed636b6c"}, + {file = "black-22.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:32a4b17f644fc288c6ee2bafdf5e3b045f4eff84693ac069d87b1a347d861497"}, + {file = "black-22.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ad827325a3a634bae88ae7747db1a395d5ee02cf05d9aa7a9bd77dfb10e940c"}, + {file = "black-22.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53198e28a1fb865e9fe97f88220da2e44df6da82b18833b588b1883b16bb5d41"}, + {file = "black-22.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:bc4d4123830a2d190e9cc42a2e43570f82ace35c3aeb26a512a2102bce5af7ec"}, + {file = "black-22.8.0-py3-none-any.whl", hash = "sha256:d2c21d439b2baf7aa80d6dd4e3659259be64c6f49dfd0f32091063db0e006db4"}, + {file = "black-22.8.0.tar.gz", hash = "sha256:792f7eb540ba9a17e8656538701d3eb1afcb134e3b45b71f20b25c77a8db7e6e"}, +] certifi = [ {file = "certifi-2022.6.15.1-py3-none-any.whl", hash = "sha256:43dadad18a7f168740e66944e4fa82c6611848ff9056ad910f8f7a3e46ab89e0"}, {file = "certifi-2022.6.15.1.tar.gz", hash = "sha256:cffdcd380919da6137f76633531a5817e3a9f268575c128249fb637e4f9e73fb"}, @@ -477,10 +709,22 @@ ecc = [ {file = "ecc-0.0.1.zip", hash = "sha256:4bbcd46e9963ca37422d3244ab503af9dce95cbd35f676f7f9a4dd6306e23538"}, ] ecc-pycrypto = [] +environs = [ + {file = "environs-9.5.0-py2.py3-none-any.whl", hash = "sha256:1e549569a3de49c05f856f40bce86979e7d5ffbbc4398e7f338574c220189124"}, + {file = "environs-9.5.0.tar.gz", hash = "sha256:a76307b36fbe856bdca7ee9161e6c466fd7fcffc297109a118c59b54e27e30c9"}, +] +fastapi = [ + {file = "fastapi-0.83.0-py3-none-any.whl", hash = "sha256:694a2b6c2607a61029a4be1c6613f84d74019cb9f7a41c7a475dca8e715f9368"}, + {file = "fastapi-0.83.0.tar.gz", hash = "sha256:96eb692350fe13d7a9843c3c87a874f0d45102975257dd224903efd6c0fde3bd"}, +] flask = [ {file = "Flask-2.2.2-py3-none-any.whl", hash = "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526"}, {file = "Flask-2.2.2.tar.gz", hash = "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b"}, ] +h11 = [ + {file = "h11-0.13.0-py3-none-any.whl", hash = "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442"}, + {file = "h11-0.13.0.tar.gz", hash = "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06"}, +] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, @@ -501,6 +745,10 @@ jinja2 = [ {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, ] +loguru = [ + {file = "loguru-0.6.0-py3-none-any.whl", hash = "sha256:4e2414d534a2ab57573365b3e6d0234dfb1d84b68b7f3b948e6fb743860a77c3"}, + {file = "loguru-0.6.0.tar.gz", hash = "sha256:066bd06758d0a513e9836fd9c6b5a75bfb3fd36841f4b996bc60b547a309d41c"}, +] markupsafe = [ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, @@ -543,6 +791,14 @@ markupsafe = [ {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, ] +marshmallow = [ + {file = "marshmallow-3.17.1-py3-none-any.whl", hash = "sha256:1172ce82765bf26c24a3f9299ed6dbeeca4d213f638eaa39a37772656d7ce408"}, + {file = "marshmallow-3.17.1.tar.gz", hash = "sha256:48e2d88d4ab431ad5a17c25556d9da529ea6e966876f2a38d274082e270287f0"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] outcome = [ {file = "outcome-1.2.0-py2.py3-none-any.whl", hash = "sha256:c4ab89a56575d6d38a05aa16daeaa333109c1f96167aba8901ab18b6b5e0f7f5"}, {file = "outcome-1.2.0.tar.gz", hash = "sha256:6f82bd3de45da303cf1f771ecafa1633750a358436a8bb60e06a1ceb745d2672"}, @@ -551,6 +807,14 @@ packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] +pathspec = [ + {file = "pathspec-0.10.1-py3-none-any.whl", hash = "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93"}, + {file = "pathspec-0.10.1.tar.gz", hash = "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"}, +] +platformdirs = [ + {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, + {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, +] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, @@ -670,6 +934,10 @@ pytest-asyncio = [ {file = "pytest-asyncio-0.19.0.tar.gz", hash = "sha256:ac4ebf3b6207259750bc32f4c1d8fcd7e79739edbc67ad0c58dd150b1d072fed"}, {file = "pytest_asyncio-0.19.0-py3-none-any.whl", hash = "sha256:7a97e37cfe1ed296e2e84941384bdd37c376453912d397ed39293e0916f521fa"}, ] +python-dotenv = [ + {file = "python-dotenv-0.21.0.tar.gz", hash = "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"}, + {file = "python_dotenv-0.21.0-py3-none-any.whl", hash = "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5"}, +] represent = [ {file = "Represent-1.6.0.post0-py2.py3-none-any.whl", hash = "sha256:99142650756ef1998ce0661568f54a47dac8c638fb27e3816c02536575dbba8c"}, {file = "Represent-1.6.0.post0.tar.gz", hash = "sha256:026c0de2ee8385d1255b9c2426cd4f03fe9177ac94c09979bc601946c8493aa0"}, @@ -682,6 +950,10 @@ six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +sniffio = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] sqlalchemy = [ {file = "SQLAlchemy-1.3.24-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:87a2725ad7d41cd7376373c15fd8bf674e9c33ca56d0b8036add2d634dba372e"}, {file = "SQLAlchemy-1.3.24-cp27-cp27m-win32.whl", hash = "sha256:f597a243b8550a3a0b15122b14e49d8a7e622ba1c9d29776af741f1845478d79"}, @@ -722,6 +994,10 @@ sqlalchemy-aio = [ {file = "sqlalchemy_aio-0.17.0-py3-none-any.whl", hash = "sha256:3f4aa392c38f032d6734826a4138a0f02ed3122d442ed142be1e5964f2a33b60"}, {file = "sqlalchemy_aio-0.17.0.tar.gz", hash = "sha256:f531c7982662d71dfc0b117e77bb2ed544e25cd5361e76cf9f5208edcfb71f7b"}, ] +starlette = [ + {file = "starlette-0.19.1-py3-none-any.whl", hash = "sha256:5a60c5c2d051f3a8eb546136aa0c9399773a689595e099e0877704d5888279bf"}, + {file = "starlette-0.19.1.tar.gz", hash = "sha256:c6d21096774ecb9639acad41b86b7706e52ba3bf1dc13ea4ed9ad593d47e24c7"}, +] tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, @@ -734,10 +1010,18 @@ urllib3 = [ {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, ] +uvicorn = [ + {file = "uvicorn-0.18.3-py3-none-any.whl", hash = "sha256:0abd429ebb41e604ed8d2be6c60530de3408f250e8d2d84967d85ba9e86fe3af"}, + {file = "uvicorn-0.18.3.tar.gz", hash = "sha256:9a66e7c42a2a95222f76ec24a4b754c158261c4696e683b9dadc72b590e0311b"}, +] werkzeug = [ {file = "Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"}, {file = "Werkzeug-2.2.2.tar.gz", hash = "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f"}, ] +win32-setctime = [ + {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, + {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, +] zipp = [ {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, diff --git a/pyproject.toml b/pyproject.toml index bb6752f..f9e1bb8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,8 +27,13 @@ asgiref = "^3.5.2" pydantic = "^1.10.2" bech32 = "^1.2.0" psycopg2-binary = "^2.9.3" +fastapi = "^0.83.0" +environs = "^9.5.0" +uvicorn = "^0.18.3" +loguru = "^0.6.0" [tool.poetry.dev-dependencies] +black = {version = "^22.8.0", allow-prereleases = true} [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/wallet/crud.py b/wallet/crud.py index f427adf..29d81e5 100644 --- a/wallet/crud.py +++ b/wallet/crud.py @@ -3,7 +3,7 @@ from typing import Optional from core.db import Connection, Database # from wallet import db -from wallet.models import Proof +from core.base import Proof async def store_proof( diff --git a/wallet/models.py b/wallet/models.py deleted file mode 100644 index 49630fc..0000000 --- a/wallet/models.py +++ /dev/null @@ -1,20 +0,0 @@ -from pydantic import BaseModel -from sqlite3 import Row - - -class Proof(dict): - amount: int - C_x: int - C_y: int - secret: str - - @classmethod - def from_row(cls, row: Row): - return dict( - amount=row[0], - C=dict( - x=int(row[1]), - y=int(row[2]), - ), - secret=row[3], - ) diff --git a/wallet/wallet.py b/wallet/wallet.py index 7ba7bc1..d83edee 100644 --- a/wallet/wallet.py +++ b/wallet/wallet.py @@ -4,7 +4,7 @@ import asyncio import requests from ecc.curve import secp256k1, Point from typing import List -from wallet.models import Proof +from core.base import Proof, BasePoint import core.b_dhke as b_dhke from core.db import Database @@ -12,6 +12,8 @@ from core.split import amount_split from wallet.crud import store_proof, invalidate_proof, get_proofs +from core.base import MintPayload, MintPayloads, SplitPayload + class LedgerAPI: def __init__(self, url): @@ -41,21 +43,14 @@ class LedgerAPI: for promise, (r, secret) in zip(promises, secrets): C_ = Point(promise["C'"]["x"], promise["C'"]["y"], secp256k1) C = b_dhke.step3_bob(C_, r, self.keys[promise["amount"]]) - proofs.append( - { - "amount": promise["amount"], - "C": { - "x": C.x, - "y": C.y, - }, - "secret": secret, - } - ) + c_point = BasePoint(x=C.x, y=C.y) + proof = Proof(amount=promise["amount"], C=c_point, secret=secret) + proofs.append(proof.dict()) return proofs def mint(self, amounts): """Mints new coins and returns a proof of promise.""" - payload = {} + payloads: MintPayloads = MintPayloads() secrets = [] rs = [] for i, amount in enumerate(amounts): @@ -63,14 +58,17 @@ class LedgerAPI: secrets.append(secret) B_, r = b_dhke.step1_bob(secret) rs.append(r) - payload[i] = {"amount": amount, "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) + payloads.payloads.append(payload) + print(payloads.dict()) promises = requests.post( self.url + "/mint", - json=payload, + json=payloads.dict(), ).json() - if "error" in promises: - raise Exception("Error: {}".format(promises["error"])) + if "detail" in promises: + raise Exception("Error: {}".format(promises["detail"])) return self._construct_proofs(promises, [(r, s) for r, s in zip(rs, secrets)]) def split(self, proofs, amount): @@ -81,23 +79,28 @@ class LedgerAPI: snd_outputs = amount_split(snd_amt) secrets = [] - output_data = [] + # output_data = [] + payloads: MintPayloads = MintPayloads() for output_amt in fst_outputs + snd_outputs: secret = str(random.getrandbits(128)) B_, r = b_dhke.step1_bob(secret) secrets.append((r, secret)) - output_data.append( - { - "amount": output_amt, - "B'": { - "x": B_.x, - "y": B_.y, - }, - } - ) + blinded_point = BasePoint(x=str(B_.x), y=str(B_.y)) + payload: MintPayload = MintPayload(amount=output_amt, B_=blinded_point) + payloads.payloads.append(payload) + # output_data.append( + # { + # "amount": output_amt, + # "B'": { + # "x": B_.x, + # "y": B_.y, + # }, + # } + # ) + split_payload = SplitPayload(proofs=proofs, amount=amount, output_data=payloads) promises = requests.post( self.url + "/split", - json={"proofs": proofs, "amount": amount, "output_data": output_data}, + json=split_payload.dict(), ).json() if "error" in promises: raise Exception("Error: {}".format(promises["error"]))