Merge pull request #33 from callebtc/fix/hash_to_curve

Fix/hash to curve
This commit is contained in:
calle
2022-10-10 21:54:25 +02:00
committed by GitHub
14 changed files with 119 additions and 42 deletions

View File

@@ -6,7 +6,7 @@ Alice:
A = a*G A = a*G
return A return A
Bob: Bob:
Y = hash_to_point(secret_message) Y = hash_to_curve(secret_message)
r = random blinding factor r = random blinding factor
B'= Y + r*G B'= Y + r*G
return B' return B'
@@ -20,7 +20,7 @@ C = C' - r*A
(= a*Y) (= a*Y)
return C, secret_message return C, secret_message
Alice: Alice:
Y = hash_to_point(secret_message) Y = hash_to_curve(secret_message)
C == a*Y C == a*Y
If true, C must have originated from Alice If true, C must have originated from Alice
""" """
@@ -30,28 +30,22 @@ import hashlib
from secp256k1 import PrivateKey, PublicKey from secp256k1 import PrivateKey, PublicKey
def hash_to_point(secret_msg): def hash_to_curve(message: bytes):
"""Generates x coordinate from the message hash and checks if the point lies on the curve. """Generates a point from the message hash and checks if the point lies on the curve.
If it does not, it tries computing again a new x coordinate from the hash of the coordinate.""" If it does not, it tries computing a new point from the hash."""
point = None point = None
msg = secret_msg msg_to_hash = message
while point is None: while point is None:
_hash = hashlib.sha256(msg).hexdigest().encode("utf-8")
try: try:
# We construct compressed pub which has x coordinate encoded with even y _hash = hashlib.sha256(msg_to_hash).digest()
_hash = list(_hash[:33]) # take the 33 bytes and get a list of bytes point = PublicKey(b"\x02" + _hash, raw=True)
_hash[0] = 0x02 # set first byte to represent even y coord
_hash = bytes(_hash)
point = PublicKey(_hash, raw=True)
except: except:
msg = _hash msg_to_hash = _hash
return point return point
def step1_alice(secret_msg): def step1_alice(secret_msg: str):
secret_msg = secret_msg.encode("utf-8") Y = hash_to_curve(secret_msg.encode("utf-8"))
Y = hash_to_point(secret_msg)
r = PrivateKey() r = PrivateKey()
B_ = Y + r.pubkey B_ = Y + r.pubkey
return B_, r return B_, r
@@ -68,7 +62,7 @@ def step3_alice(C_, r, A):
def verify(a, C, secret_msg): def verify(a, C, secret_msg):
Y = hash_to_point(secret_msg.encode("utf-8")) Y = hash_to_curve(secret_msg.encode("utf-8"))
return C == Y.mult(a) return C == Y.mult(a)

View File

@@ -247,7 +247,7 @@ class MintKeyset:
first_seen=None, first_seen=None,
active=None, active=None,
seed: Union[None, str] = None, seed: Union[None, str] = None,
derivation_path: str = "0", derivation_path: str = None,
): ):
self.derivation_path = derivation_path self.derivation_path = derivation_path
self.id = id self.id = id

27
cashu/core/legacy.py Normal file
View File

@@ -0,0 +1,27 @@
import hashlib
from secp256k1 import PublicKey
def hash_to_point_pre_0_3_3(secret_msg):
"""Generates x coordinate from the message hash and checks if the point lies on the curve.
If it does not, it tries computing again a new x coordinate from the hash of the coordinate."""
point = None
msg = secret_msg
while point is None:
_hash = hashlib.sha256(msg).hexdigest().encode("utf-8")
try:
# We construct compressed pub which has x coordinate encoded with even y
_hash = list(_hash[:33]) # take the 33 bytes and get a list of bytes
_hash[0] = 0x02 # set first byte to represent even y coord
_hash = bytes(_hash)
point = PublicKey(_hash, raw=True)
except:
msg = _hash
return point
def verify_pre_0_3_3(a, C, secret_msg):
Y = hash_to_point_pre_0_3_3(secret_msg.encode("utf-8"))
return C == Y.mult(a)

View File

@@ -48,4 +48,4 @@ LNBITS_ENDPOINT = env.str("LNBITS_ENDPOINT", default=None)
LNBITS_KEY = env.str("LNBITS_KEY", default=None) LNBITS_KEY = env.str("LNBITS_KEY", default=None)
MAX_ORDER = 64 MAX_ORDER = 64
VERSION = "0.3.2" VERSION = "0.3.3"

View File

@@ -1,4 +1,4 @@
from cashu.core.settings import MINT_PRIVATE_KEY from cashu.core.settings import MINT_PRIVATE_KEY
from cashu.mint.ledger import Ledger from cashu.mint.ledger import Ledger
ledger = Ledger(MINT_PRIVATE_KEY, "data/mint") ledger = Ledger(MINT_PRIVATE_KEY, "data/mint", derivation_path="0/0/0/0")

View File

@@ -1,19 +1,30 @@
import asyncio
import logging import logging
import sys import sys
from fastapi import FastAPI from fastapi import FastAPI
from loguru import logger from loguru import logger
from starlette.middleware import Middleware
from starlette.middleware.base import BaseHTTPMiddleware
from starlette_context import context
from starlette_context.middleware import RawContextMiddleware
from cashu.core.settings import DEBUG, VERSION from cashu.core.settings import DEBUG, VERSION
from cashu.lightning import WALLET
from cashu.mint.migrations import m001_initial
from . import ledger
from .router import router from .router import router
from .startup import load_ledger from .startup import load_ledger
class CustomHeaderMiddleware(BaseHTTPMiddleware):
"""
Middleware for starlette that can set the context from request headers
"""
async def dispatch(self, request, call_next):
context["client-version"] = request.headers.get("Client-version")
response = await call_next(request)
return response
def create_app(config_object="core.settings") -> FastAPI: def create_app(config_object="core.settings") -> FastAPI:
def configure_logger() -> None: def configure_logger() -> None:
class Formatter: class Formatter:
@@ -49,6 +60,13 @@ def create_app(config_object="core.settings") -> FastAPI:
configure_logger() configure_logger()
middleware = [
Middleware(
RawContextMiddleware,
),
Middleware(CustomHeaderMiddleware),
]
app = FastAPI( app = FastAPI(
title="Cashu Mint", title="Cashu Mint",
description="Ecash wallet and mint with Bitcoin Lightning support.", description="Ecash wallet and mint with Bitcoin Lightning support.",
@@ -57,8 +75,8 @@ def create_app(config_object="core.settings") -> FastAPI:
"name": "MIT License", "name": "MIT License",
"url": "https://raw.githubusercontent.com/callebtc/cashu/main/LICENSE", "url": "https://raw.githubusercontent.com/callebtc/cashu/main/LICENSE",
}, },
middleware=middleware,
) )
return app return app

View File

@@ -6,9 +6,11 @@ import math
from typing import Dict, List, Set from typing import Dict, List, Set
from loguru import logger from loguru import logger
from starlette_context import context
import cashu.core.b_dhke as b_dhke import cashu.core.b_dhke as b_dhke
import cashu.core.bolt11 as bolt11 import cashu.core.bolt11 as bolt11
import cashu.core.legacy as legacy
from cashu.core.base import ( from cashu.core.base import (
BlindedMessage, BlindedMessage,
BlindedSignature, BlindedSignature,
@@ -55,11 +57,12 @@ class Ledger:
seed=self.master_key, derivation_path=self.derivation_path seed=self.master_key, derivation_path=self.derivation_path
) )
# check if current keyset is stored in db and store if not # check if current keyset is stored in db and store if not
logger.debug(f"Loading keyset {self.keyset.id} from db.")
current_keyset_local: List[MintKeyset] = await get_keyset( current_keyset_local: List[MintKeyset] = await get_keyset(
id=self.keyset.id, db=self.db id=self.keyset.id, db=self.db
) )
if not len(current_keyset_local): if not len(current_keyset_local):
logger.debug(f"Storing keyset {self.keyset.id}") logger.debug(f"Storing keyset {self.keyset.id}.")
await store_keyset(keyset=self.keyset, db=self.db) await store_keyset(keyset=self.keyset, db=self.db)
# load all past keysets from db # load all past keysets from db
@@ -112,6 +115,11 @@ class Ledger:
secret_key = self.keysets.keysets[proof.id].private_keys[proof.amount] secret_key = self.keysets.keysets[proof.id].private_keys[proof.amount]
C = PublicKey(bytes.fromhex(proof.C), raw=True) C = PublicKey(bytes.fromhex(proof.C), raw=True)
# backwards compatibility with old hash_to_curve
# old clients do not send a version
if not context.get("client-version"):
return legacy.verify_pre_0_3_3(secret_key, C, proof.secret)
return b_dhke.verify(secret_key, C, proof.secret) return b_dhke.verify(secret_key, C, proof.secret)
def _verify_script(self, idx: int, proof: Proof): def _verify_script(self, idx: int, proof: Proof):

View File

@@ -20,6 +20,10 @@ from cashu.mint import ledger
router: APIRouter = APIRouter() router: APIRouter = APIRouter()
from starlette.requests import Request
from starlette_context import context
@router.get("/keys") @router.get("/keys")
def keys(): def keys():
"""Get the public keys of the mint""" """Get the public keys of the mint"""
@@ -49,7 +53,6 @@ async def request_mint(amount: int = 0):
@router.post("/mint") @router.post("/mint")
async def mint( async def mint(
payloads: MintRequest, payloads: MintRequest,
bolt11: Union[str, None] = None,
payment_hash: Union[str, None] = None, payment_hash: Union[str, None] = None,
): ):
""" """
@@ -70,7 +73,7 @@ async def mint(
@router.post("/melt") @router.post("/melt")
async def melt(payload: MeltRequest): async def melt(request: Request, payload: MeltRequest):
""" """
Requests tokens to be destroyed and sent out via Lightning. Requests tokens to be destroyed and sent out via Lightning.
""" """
@@ -97,7 +100,7 @@ async def check_fees(payload: CheckFeesRequest):
@router.post("/split") @router.post("/split")
async def split(payload: SplitRequest): async def split(request: Request, payload: SplitRequest):
""" """
Requetst a set of tokens with amount "total" to be split into two Requetst a set of tokens with amount "total" to be split into two
newly minted sets with amount "split" and "total-split". newly minted sets with amount "split" and "total-split".

View File

@@ -30,7 +30,7 @@ from cashu.core.script import (
step2_carol_sign_tx, step2_carol_sign_tx,
) )
from cashu.core.secp import PublicKey from cashu.core.secp import PublicKey
from cashu.core.settings import DEBUG from cashu.core.settings import DEBUG, VERSION
from cashu.core.split import amount_split from cashu.core.split import amount_split
from cashu.wallet.crud import ( from cashu.wallet.crud import (
get_keyset, get_keyset,
@@ -52,7 +52,10 @@ class LedgerAPI:
self.url = url self.url = url
async def _get_keys(self, url): async def _get_keys(self, url):
resp = requests.get(url + "/keys").json() resp = requests.get(
url + "/keys",
headers={"Client-version": VERSION},
).json()
keys = resp keys = resp
assert len(keys), Exception("did not receive any keys") assert len(keys), Exception("did not receive any keys")
keyset_keys = { keyset_keys = {
@@ -63,7 +66,10 @@ class LedgerAPI:
return keyset return keyset
async def _get_keysets(self, url): async def _get_keysets(self, url):
keysets = requests.get(url + "/keysets").json() keysets = requests.get(
url + "/keysets",
headers={"Client-version": VERSION},
).json()
assert len(keysets), Exception("did not receive any keysets") assert len(keysets), Exception("did not receive any keysets")
return keysets return keysets
@@ -177,6 +183,7 @@ class LedgerAPI:
self.url + "/mint", self.url + "/mint",
json=payloads.dict(), json=payloads.dict(),
params={"payment_hash": payment_hash}, params={"payment_hash": payment_hash},
headers={"Client-version": VERSION},
) )
resp.raise_for_status() resp.raise_for_status()
try: try:
@@ -235,6 +242,7 @@ class LedgerAPI:
resp = requests.post( resp = requests.post(
self.url + "/split", self.url + "/split",
json=split_payload.dict(include=_splitrequest_include_fields(proofs)), json=split_payload.dict(include=_splitrequest_include_fields(proofs)),
headers={"Client-version": VERSION},
) )
resp.raise_for_status() resp.raise_for_status()
try: try:
@@ -260,6 +268,7 @@ class LedgerAPI:
resp = requests.post( resp = requests.post(
self.url + "/check", self.url + "/check",
json=payload.dict(), json=payload.dict(),
headers={"Client-version": VERSION},
) )
resp.raise_for_status() resp.raise_for_status()
return_dict = resp.json() return_dict = resp.json()
@@ -272,6 +281,7 @@ class LedgerAPI:
resp = requests.post( resp = requests.post(
self.url + "/checkfees", self.url + "/checkfees",
json=payload.dict(), json=payload.dict(),
headers={"Client-version": VERSION},
) )
resp.raise_for_status() resp.raise_for_status()
@@ -293,6 +303,7 @@ class LedgerAPI:
resp = requests.post( resp = requests.post(
self.url + "/melt", self.url + "/melt",
json=payload.dict(include=_meltequest_include_fields(proofs)), json=payload.dict(include=_meltequest_include_fields(proofs)),
headers={"Client-version": VERSION},
) )
resp.raise_for_status() resp.raise_for_status()

View File

@@ -17,9 +17,9 @@ Mint: `Bob`
# Blind Diffie-Hellmann key exchange (BDH) # Blind Diffie-Hellmann key exchange (BDH)
- Mint `Bob` publishes `K = kG` - Mint `Bob` publishes `K = kG`
- `Alice` picks secret `x` and computes `Y = hash_to_point(x)` - `Alice` picks secret `x` and computes `Y = hash_to_curve(x)`
- `Alice` sends to `Bob`: `T = Y + rG` with `r` being a random nonce - `Alice` sends to `Bob`: `T = Y + rG` with `r` being a random nonce
- `Bob` sends back to `Alice` blinded key: `Q = kT` (these two steps are the DH key exchange) - `Bob` sends back to `Alice` blinded key: `Q = kT` (these two steps are the DH key exchange)
- `Alice` can calculate the unblinded key as `Q - rK = kY + krG - krG = kY = Z` - `Alice` can calculate the unblinded key as `Q - rK = kY + krG - krG = kY = Z`
- Alice can take the pair `(x, Z)` as a token and can send it to `Carol`. - Alice can take the pair `(x, Z)` as a token and can send it to `Carol`.
- `Carol` can send `(x, Z)` to `Bob` who then checks that `k*hash_to_point(x) == Z`, and if so treats it as a valid spend of a token, adding `x` to the list of spent secrets. - `Carol` can send `(x, Z)` to `Bob` who then checks that `k*hash_to_curve(x) == Z`, and if so treats it as a valid spend of a token, adding `x` to the list of spent secrets.

View File

@@ -17,12 +17,12 @@ Mint: `Bob`
# Blind Diffie-Hellmann key exchange (BDH) # Blind Diffie-Hellmann key exchange (BDH)
- Mint `Bob` publishes `K = kG` - Mint `Bob` publishes `K = kG`
- `Alice` picks secret `x` and computes `Y = hash_to_point(x)` - `Alice` picks secret `x` and computes `Y = hash_to_curve(x)`
- `Alice` sends to `Bob`: `T = Y + rG` with `r` being a random nonce - `Alice` sends to `Bob`: `T = Y + rG` with `r` being a random nonce
- `Bob` sends back to `Alice` blinded key: `Q = kT` (these two steps are the DH key exchange) - `Bob` sends back to `Alice` blinded key: `Q = kT` (these two steps are the DH key exchange)
- `Alice` can calculate the unblinded key as `Q - rK = kY + krG - krG = kY = Z` - `Alice` can calculate the unblinded key as `Q - rK = kY + krG - krG = kY = Z`
- Alice can take the pair `(x, Z)` as a token and can send it to `Carol`. - Alice can take the pair `(x, Z)` as a token and can send it to `Carol`.
- `Carol` can send `(x, Z)` to `Bob` who then checks that `k*hash_to_point(x) == Z`, and if so treats it as a valid spend of a token, adding `x` to the list of spent secrets. - `Carol` can send `(x, Z)` to `Bob` who then checks that `k*hash_to_curve(x) == Z`, and if so treats it as a valid spend of a token, adding `x` to the list of spent secrets.
# Cashu client protocol # Cashu client protocol

21
poetry.lock generated
View File

@@ -167,8 +167,8 @@ starlette = "0.19.1"
[package.extras] [package.extras]
all = ["email_validator (>=1.1.1,<2.0.0)", "itsdangerous (>=1.1.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.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)", "uvicorn[standard] (>=0.12.0,<0.18.0)"] all = ["email_validator (>=1.1.1,<2.0.0)", "itsdangerous (>=1.1.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.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)", "uvicorn[standard] (>=0.12.0,<0.18.0)"]
dev = ["autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "pre-commit (>=2.17.0,<3.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"] dev = ["autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "pre-commit (>=2.17.0,<3.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"]
doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.2)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer (>=0.4.1,<0.5.0)"] doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer (>=0.4.1,<0.5.0)"]
test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.3.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "email_validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "orjson (>=3.2.1,<4.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "types-dataclasses (==0.6.5)", "types-orjson (==3.6.2)", "types-ujson (==4.2.1)", "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)"] test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.3.0)", "databases[sqlite] (>=0.3.3,<0.6.0)", "email_validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "orjson (>=3.2.1,<4.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "types-dataclasses (==0.6.5)", "types-orjson (==3.6.2)", "types-ujson (==4.2.1)", "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)"]
[[package]] [[package]]
name = "h11" name = "h11"
@@ -553,6 +553,17 @@ typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""
[package.extras] [package.extras]
full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"]
[[package]]
name = "starlette-context"
version = "0.3.4"
description = "Access context in Starlette"
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
starlette = "*"
[[package]] [[package]]
name = "tomli" name = "tomli"
version = "2.0.1" version = "2.0.1"
@@ -632,7 +643,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.7" python-versions = "^3.7"
content-hash = "b4e980ee90226bab07750b1becc8c69df7752f6d168d200a79c782aa1efe61da" content-hash = "14ff9c57ca971c645f1a075b5c6fa0a84a38eaf6399d14afa724136728a3da03"
[metadata.files] [metadata.files]
anyio = [ anyio = [
@@ -999,6 +1010,10 @@ starlette = [
{file = "starlette-0.19.1-py3-none-any.whl", hash = "sha256:5a60c5c2d051f3a8eb546136aa0c9399773a689595e099e0877704d5888279bf"}, {file = "starlette-0.19.1-py3-none-any.whl", hash = "sha256:5a60c5c2d051f3a8eb546136aa0c9399773a689595e099e0877704d5888279bf"},
{file = "starlette-0.19.1.tar.gz", hash = "sha256:c6d21096774ecb9639acad41b86b7706e52ba3bf1dc13ea4ed9ad593d47e24c7"}, {file = "starlette-0.19.1.tar.gz", hash = "sha256:c6d21096774ecb9639acad41b86b7706e52ba3bf1dc13ea4ed9ad593d47e24c7"},
] ]
starlette-context = [
{file = "starlette_context-0.3.4-py37-none-any.whl", hash = "sha256:b16bf17bd3ead7ded2f458aebf7f913744b9cf28305e16c69b435a6c6ddf1135"},
{file = "starlette_context-0.3.4.tar.gz", hash = "sha256:2d28e1838302fb5d5adacadc10fb73fb2d5cca1f0aa1e279698701cc96f1567c"},
]
tomli = [ tomli = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "cashu" name = "cashu"
version = "0.3.2" version = "0.3.3"
description = "Ecash wallet and mint." description = "Ecash wallet and mint."
authors = ["calle <callebtc@protonmail.com>"] authors = ["calle <callebtc@protonmail.com>"]
license = "MIT" license = "MIT"
@@ -22,6 +22,7 @@ bitstring = "^3.1.9"
secp256k1 = "^0.14.0" secp256k1 = "^0.14.0"
sqlalchemy-aio = "^0.17.0" sqlalchemy-aio = "^0.17.0"
python-bitcoinlib = "^0.11.2" python-bitcoinlib = "^0.11.2"
starlette-context = "^0.3.4"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
black = {version = "^22.8.0", allow-prereleases = true} black = {version = "^22.8.0", allow-prereleases = true}

View File

@@ -13,7 +13,7 @@ entry_points = {"console_scripts": ["cashu = cashu.wallet.cli:cli"]}
setuptools.setup( setuptools.setup(
name="cashu", name="cashu",
version="0.3.2", version="0.3.3",
description="Ecash wallet and mint with Bitcoin Lightning support", description="Ecash wallet and mint with Bitcoin Lightning support",
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",