Skip to content

Commit

Permalink
Merge branch 'main' into auth-server
Browse files Browse the repository at this point in the history
  • Loading branch information
callebtc committed Jan 20, 2025
2 parents c90dad7 + 2f19485 commit 9ebbb1b
Show file tree
Hide file tree
Showing 51 changed files with 1,914 additions and 830 deletions.
13 changes: 8 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ MINT_INFO_MOTD="Message to users"
MINT_INFO_ICON_URL="https://mint.host/icon.jpg"
MINT_INFO_URLS=["https://mint.host", "http://mint8gv0sq5ul602uxt2fe0t80e3c2bi9fy0cxedp69v1vat6ruj81wv.onion"]

MINT_PRIVATE_KEY=supersecretprivatekey
# This is used to derive your mint's private keys. Store it securely.
# MINT_PRIVATE_KEY=<openssl rand -hex 32>

# Increment derivation path to rotate to a new keyset
# Example: m/0'/0'/0' -> m/0'/0'/1'
Expand Down Expand Up @@ -70,6 +71,12 @@ MINT_BACKEND_BOLT11_SAT=FakeWallet
# MINT_BACKEND_BOLT11_USD=FakeWallet
# MINT_BACKEND_BOLT11_EUR=FakeWallet

# NUT-19 Cached responses
# Enable these settings to cache responses in Redis
#
# MINT_REDIS_CACHE_ENABLED=TRUE
# MINT_REDIS_CACHE_URL="redis://localhost:6379"

# Funding source settings

# Use with LndRPCWallet
Expand All @@ -83,10 +90,6 @@ MINT_LND_REST_CERT="/home/lnd/.lnd/tls.cert"
MINT_LND_REST_MACAROON="/home/lnd/.lnd/data/chain/bitcoin/regtest/admin.macaroon"
MINT_LND_REST_CERT_VERIFY=True

# Use with LND
# This setting enables MPP support for Partial multi-path payments (NUT-15)
MINT_LND_ENABLE_MPP=TRUE

# Use with CLNRestWallet
MINT_CLNREST_URL=https://localhost:3010
MINT_CLNREST_CERT="./clightning-2/regtest/ca.pem"
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/prepare/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ inputs:
default: "3.10"
poetry-version:
description: "Poetry Version"
default: "1.7.1"
default: "1.8.5"

runs:
using: "composite"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
default: "3.10.4"
type: string
poetry-version:
default: "1.7.1"
default: "1.8.5"
type: string

jobs:
Expand Down
21 changes: 19 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ on:
jobs:
checks:
uses: ./.github/workflows/checks.yml

tests:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
python-version: ["3.10"]
poetry-version: ["1.7.1"]
poetry-version: ["1.8.5"]
mint-only-deprecated: ["false", "true"]
mint-database: ["./test_data/test_mint", "postgres://cashu:cashu@localhost:5432/cashu"]
backend-wallet-class: ["FakeWallet"]
Expand All @@ -25,13 +26,29 @@ jobs:
poetry-version: ${{ matrix.poetry-version }}
mint-only-deprecated: ${{ matrix.mint-only-deprecated }}
mint-database: ${{ matrix.mint-database }}

tests_redis_cache:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
python-version: ["3.10"]
poetry-version: ["1.8.5"]
mint-database: ["./test_data/test_mint", "postgres://cashu:cashu@localhost:5432/cashu"]
uses: ./.github/workflows/tests_redis_cache.yml
with:
os: ${{ matrix.os }}
python-version: ${{ matrix.python-version }}
poetry-version: ${{ matrix.poetry-version }}
mint-database: ${{ matrix.mint-database }}

regtest:
uses: ./.github/workflows/regtest.yml
strategy:
fail-fast: false
matrix:
python-version: ["3.10"]
poetry-version: ["1.7.1"]
poetry-version: ["1.8.5"]
backend-wallet-class:
["LndRPCWallet", "LndRestWallet", "CLNRestWallet", "CoreLightningRestWallet", "LNbitsWallet"]
mint-database: ["./test_data/test_mint", "postgres://cashu:cashu@localhost:5432/cashu"]
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pypi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
strategy:
matrix:
python-version: ["3.10"]
poetry-version: ["1.7.1"]
poetry-version: ["1.8.5"]
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down
7 changes: 0 additions & 7 deletions .github/workflows/regtest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,6 @@ jobs:
MINT_LND_RPC_ENDPOINT: localhost:10009
MINT_LND_RPC_CERT: ./regtest/data/lnd-3/tls.cert
MINT_LND_RPC_MACAROON: ./regtest/data/lnd-3/data/chain/bitcoin/regtest/admin.macaroon

MINT_LND_ENABLE_MPP: true
# LND_GRPC_ENDPOINT: localhost
# LND_GRPC_PORT: 10009
# LND_GRPC_CERT: ./regtest/data/lnd-3/tls.cert
# LND_GRPC_MACAROON: ./regtest/data/lnd-3/data/chain/bitcoin/regtest/admin.macaroon
# CoreLightningRestWallet
MINT_CORELIGHTNING_REST_URL: https://localhost:3001
MINT_CORELIGHTNING_REST_MACAROON: ./regtest/data/clightning-2-rest/access.macaroon
Expand All @@ -77,7 +71,6 @@ jobs:
MINT_CLNREST_URL: https://localhost:3010
MINT_CLNREST_RUNE: ./regtest/data/clightning-2/rune
MINT_CLNREST_CERT: ./regtest/data/clightning-2/regtest/ca.pem
MINT_CLNREST_ENABLE_MPP: false
run: |
sudo chmod -R 777 .
make test
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
default: "3.10.4"
type: string
poetry-version:
default: "1.7.1"
default: "1.8.5"
type: string
mint-database:
default: ""
Expand Down
63 changes: 63 additions & 0 deletions .github/workflows/tests_redis_cache.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: tests_redis_cache

on:
workflow_call:
inputs:
python-version:
default: "3.10.4"
type: string
poetry-version:
default: "1.8.5"
type: string
mint-database:
default: ""
type: string
os:
default: "ubuntu-latest"
type: string
mint-only-deprecated:
default: "false"
type: string

jobs:
poetry:
name: Run (db ${{ inputs.mint-database }}, deprecated api ${{ inputs.mint-only-deprecated }})
runs-on: ${{ inputs.os }}
steps:
- name: Start PostgreSQL service
if: contains(inputs.mint-database, 'postgres')
run: |
docker run -d --name postgres -e POSTGRES_USER=cashu -e POSTGRES_PASSWORD=cashu -e POSTGRES_DB=cashu -p 5432:5432 postgres:latest
until docker exec postgres pg_isready; do sleep 1; done
- name: Checkout repository
uses: actions/checkout@v2

- name: Prepare environment
uses: ./.github/actions/prepare
with:
python-version: ${{ inputs.python-version }}
poetry-version: ${{ inputs.poetry-version }}

- name: Start Redis service
run: |
docker compose -f docker/docker-compose.yml up -d redis
- name: Run tests
env:
MINT_BACKEND_BOLT11_SAT: FakeWallet
WALLET_NAME: test_wallet
MINT_HOST: localhost
MINT_PORT: 3337
MINT_TEST_DATABASE: ${{ inputs.mint-database }}
TOR: false
MINT_REDIS_CACHE_ENABLED: true
MINT_REDIS_CACHE_URL: redis://localhost:6379
run: |
poetry run pytest tests/test_mint_api_cache.py -v --cov=mint --cov-report=xml
- name: Stop and clean up Docker Compose
run: |
docker compose -f docker/docker-compose.yml down
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
6 changes: 5 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ repos:
rev: v1.6.0
hooks:
- id: mypy
args: [--ignore-missing, --check-untyped-defs]
args:
- --ignore-missing
- --check-untyped-defs
additional_dependencies:
- types-redis
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ RUN apt-get install -y libpq-dev
# Deps for building secp256k1-py
RUN apt-get install -y build-essential automake pkg-config libtool libffi-dev

RUN curl -sSL https://install.python-poetry.org | python3 -
RUN curl -sSL https://install.python-poetry.org | python3 - --version 1.8.5
ENV PATH="/root/.local/bin:$PATH"
WORKDIR /app
COPY . .
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ pyenv init
pyenv install 3.10.4

# install poetry
curl -sSL https://install.python-poetry.org | python3 -
curl -sSL https://install.python-poetry.org | python3 - --version 1.8.5
echo export PATH=\"$HOME/.local/bin:$PATH\" >> ~/.bashrc
source ~/.bashrc
```
Expand Down Expand Up @@ -183,7 +183,7 @@ This command runs the mint on your local computer. Skip this step if you want to
## Docker

```
docker run -d -p 3338:3338 --name nutshell -e MINT_BACKEND_BOLT11_SAT=FakeWallet -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.16.3 poetry run mint
docker run -d -p 3338:3338 --name nutshell -e MINT_BACKEND_BOLT11_SAT=FakeWallet -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.16.4 poetry run mint
```

## From this repository
Expand Down
11 changes: 11 additions & 0 deletions cashu/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,8 @@ class MintQuote(LedgerEvent):
paid_time: Union[int, None] = None
expiry: Optional[int] = None
mint: Optional[str] = None
privkey: Optional[str] = None
pubkey: Optional[str] = None

@classmethod
def from_row(cls, row: Row):
Expand All @@ -439,6 +441,8 @@ def from_row(cls, row: Row):
state=MintQuoteState(row["state"]),
created_time=created_time,
paid_time=paid_time,
pubkey=row["pubkey"] if "pubkey" in row.keys() else None,
privkey=row["privkey"] if "privkey" in row.keys() else None,
)

@classmethod
Expand All @@ -461,6 +465,7 @@ def from_resp_wallet(cls, mint_quote_resp, mint: str, amount: int, unit: str):
mint=mint,
expiry=mint_quote_resp.expiry,
created_time=int(time.time()),
pubkey=mint_quote_resp.pubkey,
)

@property
Expand Down Expand Up @@ -740,6 +745,12 @@ def __init__(
input_fee_ppk: Optional[int] = None,
id: str = "",
):
DEFAULT_SEED = "supersecretprivatekey"
if seed == DEFAULT_SEED:
raise Exception(
f"Seed is set to default value '{DEFAULT_SEED}'. Please change it."
)

self.derivation_path = derivation_path

if encrypted_seed and not settings.mint_seed_decryption_key:
Expand Down
16 changes: 16 additions & 0 deletions cashu/core/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,19 @@ class QuoteNotPaidError(CashuError):

def __init__(self):
super().__init__(self.detail, code=2001)


class QuoteSignatureInvalidError(CashuError):
detail = "Signature for mint request invalid"
code = 20008

def __init__(self):
super().__init__(self.detail, code=20008)


class QuoteRequiresPubkeyError(CashuError):
detail = "Pubkey required for mint quote"
code = 20009

def __init__(self):
super().__init__(self.detail, code=20009)
2 changes: 1 addition & 1 deletion cashu/core/mint_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from .base import Method, Unit
from .models import MintInfoContact, MintInfoProtectedEndpoint, Nut15MppSupport
from .nuts import BLIND_AUTH_NUT, CLEAR_AUTH_NUT, MPP_NUT, WEBSOCKETS_NUT
from .nuts.nuts import BLIND_AUTH_NUT, CLEAR_AUTH_NUT, MPP_NUT, WEBSOCKETS_NUT


class MintInfo(BaseModel):
Expand Down
16 changes: 11 additions & 5 deletions cashu/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ def preprocess_deprecated_contact_field(cls, values: dict):
class Nut15MppSupport(BaseModel):
method: str
unit: str
mpp: bool


class GetInfoResponse_deprecated(BaseModel):
Expand Down Expand Up @@ -134,21 +133,25 @@ class PostMintQuoteRequest(BaseModel):
description: Optional[str] = Field(
default=None, max_length=settings.mint_max_request_length
) # invoice description
pubkey: Optional[str] = Field(
default=None, max_length=settings.mint_max_request_length
) # NUT-20 quote lock pubkey


class PostMintQuoteResponse(BaseModel):
quote: str # quote id
request: str # input payment request
paid: Optional[bool] # DEPRECATED as per NUT-04 PR #141
state: Optional[str] # state of the quote
state: Optional[str] # state of the quote (optional for backwards compat)
expiry: Optional[int] # expiry of the quote
pubkey: Optional[str] = None # NUT-20 quote lock pubkey
paid: Optional[bool] = None # DEPRECATED as per NUT-04 PR #141

@classmethod
def from_mint_quote(self, mint_quote: MintQuote) -> "PostMintQuoteResponse":
def from_mint_quote(cls, mint_quote: MintQuote) -> "PostMintQuoteResponse":
to_dict = mint_quote.dict()
# turn state into string
to_dict["state"] = mint_quote.state.value
return PostMintQuoteResponse.parse_obj(to_dict)
return cls.parse_obj(to_dict)


# ------- API: MINT -------
Expand All @@ -159,6 +162,9 @@ class PostMintRequest(BaseModel):
outputs: List[BlindedMessage] = Field(
..., max_items=settings.mint_max_request_length
)
signature: Optional[str] = Field(
default=None, max_length=settings.mint_max_request_length
) # NUT-20 quote signature


class PostMintResponse(BaseModel):
Expand Down
Empty file added cashu/core/nuts/__init__.py
Empty file.
41 changes: 41 additions & 0 deletions cashu/core/nuts/nut20.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from hashlib import sha256
from typing import List

from ..base import BlindedMessage
from ..crypto.secp import PrivateKey, PublicKey


def generate_keypair() -> tuple[str, str]:
privkey = PrivateKey()
assert privkey.pubkey
pubkey = privkey.pubkey
return privkey.serialize(), pubkey.serialize(True).hex()


def construct_message(quote_id: str, outputs: List[BlindedMessage]) -> bytes:
serialized_outputs = b"".join([o.B_.encode("utf-8") for o in outputs])
msgbytes = sha256(quote_id.encode("utf-8") + serialized_outputs).digest()
return msgbytes


def sign_mint_quote(
quote_id: str,
outputs: List[BlindedMessage],
private_key: str,
) -> str:
privkey = PrivateKey(bytes.fromhex(private_key), raw=True)
msgbytes = construct_message(quote_id, outputs)
sig = privkey.schnorr_sign(msgbytes, None, raw=True)
return sig.hex()


def verify_mint_quote(
quote_id: str,
outputs: List[BlindedMessage],
public_key: str,
signature: str,
) -> bool:
pubkey = PublicKey(bytes.fromhex(public_key), raw=True)
msgbytes = construct_message(quote_id, outputs)
sig = bytes.fromhex(signature)
return pubkey.schnorr_verify(msgbytes, sig, None, raw=True)
Loading

0 comments on commit 9ebbb1b

Please sign in to comment.