mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-20 18:44:20 +01:00
isort
This commit is contained in:
31
cashu
31
cashu
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
21
core/base.py
21
core/base.py
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
96
mint/app.py
96
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 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)}
|
||||||
|
|||||||
51
mint/crud.py
51
mint/crud.py
@@ -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),
|
||||||
|
)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
20
poetry.lock
generated
@@ -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"},
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
@@ -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",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
# from core.db import Database
|
import sys
|
||||||
|
|
||||||
# db = Database("database", "data/wallet")
|
sys.tracebacklimit = None
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user