mirror of
https://github.com/aljazceru/nutshell.git
synced 2026-02-03 15:54:20 +01:00
refactor
This commit is contained in:
@@ -67,10 +67,22 @@ class BlindedSignature(BaseModel):
|
||||
C_: str
|
||||
|
||||
|
||||
class MintRequest(BaseModel):
|
||||
class KeysResponse(BaseModel):
|
||||
__root__: Dict[str, str]
|
||||
|
||||
|
||||
class KeysetsResponse(BaseModel):
|
||||
keysets: list[str]
|
||||
|
||||
|
||||
class BlindedMessages(BaseModel):
|
||||
blinded_messages: List[BlindedMessage] = []
|
||||
|
||||
|
||||
class PostMintResponse(BaseModel):
|
||||
promises: List[BlindedSignature] = []
|
||||
|
||||
|
||||
class GetMintResponse(BaseModel):
|
||||
pr: str
|
||||
hash: str
|
||||
@@ -85,9 +97,9 @@ class SplitRequest(BaseModel):
|
||||
proofs: List[Proof]
|
||||
amount: int
|
||||
output_data: Union[
|
||||
MintRequest, None
|
||||
BlindedMessages, None
|
||||
] = None # backwards compatibility with clients < v0.2.2
|
||||
outputs: Union[MintRequest, None] = None
|
||||
outputs: Union[BlindedMessages, None] = None
|
||||
|
||||
def __init__(self, **data):
|
||||
super().__init__(**data)
|
||||
@@ -129,17 +141,17 @@ class KeyBase(BaseModel):
|
||||
|
||||
|
||||
class WalletKeyset:
|
||||
id: str
|
||||
public_keys: Dict[int, PublicKey]
|
||||
id: Union[str, None]
|
||||
public_keys: Union[Dict[int, PublicKey], None]
|
||||
mint_url: Union[str, None] = None
|
||||
valid_from: Union[str, None] = None
|
||||
valid_to: Union[str, None] = None
|
||||
first_seen: Union[str, None] = None
|
||||
active: bool = True
|
||||
active: Union[bool, None] = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
pubkeys: Dict[int, PublicKey] = None,
|
||||
public_keys=None,
|
||||
mint_url=None,
|
||||
id=None,
|
||||
valid_from=None,
|
||||
@@ -153,20 +165,20 @@ class WalletKeyset:
|
||||
self.first_seen = first_seen
|
||||
self.active = active
|
||||
self.mint_url = mint_url
|
||||
if pubkeys:
|
||||
self.public_keys = pubkeys
|
||||
if public_keys:
|
||||
self.public_keys = public_keys
|
||||
self.id = derive_keyset_id(self.public_keys)
|
||||
|
||||
|
||||
class MintKeyset:
|
||||
id: str
|
||||
id: Union[str, None]
|
||||
derivation_path: str
|
||||
private_keys: Dict[int, PrivateKey]
|
||||
public_keys: Dict[int, PublicKey] = {}
|
||||
public_keys: Union[Dict[int, PublicKey], None] = None
|
||||
valid_from: Union[str, None] = None
|
||||
valid_to: Union[str, None] = None
|
||||
first_seen: Union[str, None] = None
|
||||
active: bool = True
|
||||
active: Union[bool, None] = True
|
||||
version: Union[str, None] = None
|
||||
|
||||
def __init__(
|
||||
@@ -194,13 +206,14 @@ class MintKeyset:
|
||||
def generate_keys(self, seed):
|
||||
"""Generates keys of a keyset from a seed."""
|
||||
self.private_keys = derive_keys(seed, self.derivation_path)
|
||||
self.public_keys = derive_pubkeys(self.private_keys)
|
||||
self.id = derive_keyset_id(self.public_keys)
|
||||
self.public_keys = derive_pubkeys(self.private_keys) # type: ignore
|
||||
self.id = derive_keyset_id(self.public_keys) # type: ignore
|
||||
|
||||
def get_keybase(self):
|
||||
assert self.id is not None
|
||||
return {
|
||||
k: KeyBase(id=self.id, amount=k, pubkey=v.serialize().hex())
|
||||
for k, v in self.public_keys.items()
|
||||
for k, v in self.public_keys.items() # type: ignore
|
||||
}
|
||||
|
||||
|
||||
@@ -208,7 +221,7 @@ class MintKeysets:
|
||||
keysets: Dict[str, MintKeyset]
|
||||
|
||||
def __init__(self, keysets: List[MintKeyset]):
|
||||
self.keysets: Dict[str, MintKeyset] = {k.id: k for k in keysets}
|
||||
self.keysets = {k.id: k for k in keysets} # type: ignore
|
||||
|
||||
def get_ids(self):
|
||||
return [k for k, _ in self.keysets.items()]
|
||||
|
||||
@@ -2,25 +2,20 @@ from pydantic import BaseModel
|
||||
|
||||
|
||||
class CashuError(BaseModel):
|
||||
code = "000"
|
||||
error = "CashuError"
|
||||
code: int
|
||||
error: str
|
||||
|
||||
|
||||
# class CashuError(Exception, BaseModel):
|
||||
# code = "000"
|
||||
# error = "CashuError"
|
||||
class MintException(CashuError):
|
||||
code = 100
|
||||
error = "Mint"
|
||||
|
||||
|
||||
# class MintException(CashuError):
|
||||
# code = 100
|
||||
# error = "Mint"
|
||||
class LightningException(MintException):
|
||||
code = 200
|
||||
error = "Lightning"
|
||||
|
||||
|
||||
# class LightningException(MintException):
|
||||
# code = 200
|
||||
# error = "Lightning"
|
||||
|
||||
|
||||
# class InvoiceNotPaidException(LightningException):
|
||||
# code = 201
|
||||
# error = "invoice not paid."
|
||||
class InvoiceNotPaidException(LightningException):
|
||||
code = 201
|
||||
error = "invoice not paid."
|
||||
|
||||
@@ -5,13 +5,16 @@ from secp256k1 import PublicKey
|
||||
|
||||
from cashu.core.base import (
|
||||
BlindedSignature,
|
||||
KeysResponse,
|
||||
KeysetsResponse,
|
||||
CheckFeesRequest,
|
||||
CheckFeesResponse,
|
||||
CheckRequest,
|
||||
GetMeltResponse,
|
||||
BlindedMessages,
|
||||
GetMintResponse,
|
||||
PostMintResponse,
|
||||
MeltRequest,
|
||||
MintRequest,
|
||||
PostSplitResponse,
|
||||
SplitRequest,
|
||||
)
|
||||
@@ -22,28 +25,32 @@ router: APIRouter = APIRouter()
|
||||
|
||||
|
||||
@router.get("/keys")
|
||||
async def keys() -> dict[int, str]:
|
||||
async def keys() -> KeysResponse:
|
||||
"""Get the public keys of the mint of the newest keyset"""
|
||||
keyset = ledger.get_keyset()
|
||||
return keyset
|
||||
keys = KeysResponse.parse_obj(keyset)
|
||||
return keys
|
||||
|
||||
|
||||
@router.get("/keys/{idBase64Urlsafe}")
|
||||
async def keyset_keys(idBase64Urlsafe: str) -> dict[int, str]:
|
||||
async def keyset_keys(idBase64Urlsafe: str) -> KeysResponse:
|
||||
"""
|
||||
Get the public keys of the mint of a specificy keyset id.
|
||||
The id is encoded in base64_urlsafe and needs to be converted back to
|
||||
Get the public keys of the mint of a specific keyset id.
|
||||
The id is encoded in idBase64Urlsafe and needs to be converted back to
|
||||
normal base64 before it can be processed.
|
||||
"""
|
||||
id = idBase64Urlsafe.replace("-", "+").replace("_", "/")
|
||||
keyset = ledger.get_keyset(keyset_id=id)
|
||||
return keyset
|
||||
keys = KeysResponse.parse_obj(keyset)
|
||||
return keys
|
||||
|
||||
|
||||
@router.get("/keysets")
|
||||
async def keysets() -> dict[str, list[str]]:
|
||||
"""Get all active keysets of the mint"""
|
||||
return {"keysets": ledger.keysets.get_ids()}
|
||||
async def keysets() -> KeysetsResponse:
|
||||
"""Get all active keyset ids of the mint"""
|
||||
# keysets = {"keysets": ledger.keysets.get_ids()}
|
||||
keysets = KeysetsResponse(keysets=ledger.keysets.get_ids())
|
||||
return keysets
|
||||
|
||||
|
||||
@router.get("/mint")
|
||||
@@ -62,9 +69,9 @@ async def request_mint(amount: int = 0) -> GetMintResponse:
|
||||
|
||||
@router.post("/mint")
|
||||
async def mint(
|
||||
mint_request: MintRequest,
|
||||
mint_request: BlindedMessages,
|
||||
payment_hash: Union[str, None] = None,
|
||||
) -> Union[List[BlindedSignature], CashuError]:
|
||||
) -> Union[PostMintResponse, CashuError]:
|
||||
"""
|
||||
Requests the minting of tokens belonging to a paid payment request.
|
||||
|
||||
@@ -74,9 +81,10 @@ async def mint(
|
||||
promises = await ledger.mint(
|
||||
mint_request.blinded_messages, payment_hash=payment_hash
|
||||
)
|
||||
return promises
|
||||
blinded_signatures = PostMintResponse(promises=promises)
|
||||
return blinded_signatures
|
||||
except Exception as exc:
|
||||
return CashuError(error=str(exc))
|
||||
return CashuError(code=0, error=str(exc))
|
||||
|
||||
|
||||
@router.post("/melt")
|
||||
@@ -103,7 +111,7 @@ async def check_fees(payload: CheckFeesRequest) -> CheckFeesResponse:
|
||||
This is can be useful for checking whether an invoice is internal (Cashu-to-Cashu).
|
||||
"""
|
||||
fees_msat = await ledger.check_fees(payload.pr)
|
||||
return CheckFeesResponse(fee=fees_msat / 1000)
|
||||
return CheckFeesResponse(fee=fees_msat // 1000)
|
||||
|
||||
|
||||
@router.post("/split")
|
||||
@@ -124,9 +132,9 @@ async def split(
|
||||
try:
|
||||
split_return = await ledger.split(proofs, amount, outputs)
|
||||
except Exception as exc:
|
||||
return CashuError(error=str(exc))
|
||||
return CashuError(code=0, error=str(exc))
|
||||
if not split_return:
|
||||
return CashuError(error="there was an error with the split")
|
||||
return CashuError(code=0, error="there was an error with the split")
|
||||
frst_promises, scnd_promises = split_return
|
||||
resp = PostSplitResponse(fst=frst_promises, snd=scnd_promises)
|
||||
return resp
|
||||
|
||||
@@ -508,7 +508,7 @@ async def pending(ctx):
|
||||
reserved_proofs = await get_reserved_proofs(wallet.db)
|
||||
if len(reserved_proofs):
|
||||
print(f"--------------------------\n")
|
||||
sorted_proofs = sorted(reserved_proofs, key=itemgetter("send_id"))
|
||||
sorted_proofs = sorted(reserved_proofs, key=itemgetter("send_id")) # type: ignore
|
||||
for i, (key, value) in enumerate(
|
||||
groupby(sorted_proofs, key=itemgetter("send_id"))
|
||||
):
|
||||
|
||||
@@ -19,7 +19,7 @@ async def verify_mints(ctx, dtoken):
|
||||
mint_url, os.path.join(CASHU_DIR, ctx.obj["WALLET_NAME"])
|
||||
)
|
||||
# make sure that this mint supports this keyset
|
||||
mint_keysets = await keyset_wallet._get_keysets(mint_url)
|
||||
mint_keysets = await keyset_wallet._get_keyset_ids(mint_url)
|
||||
assert keyset in mint_keysets["keysets"], "mint does not have this keyset."
|
||||
|
||||
# we validate the keyset id by fetching the keys from the mint
|
||||
@@ -119,6 +119,7 @@ async def get_mint_wallet(ctx):
|
||||
mint_keysets: WalletKeyset = await get_keyset(mint_url=mint_url, db=mint_wallet.db) # type: ignore
|
||||
|
||||
# load the keys
|
||||
assert mint_keysets.id
|
||||
await mint_wallet.load_mint(keyset_id=mint_keysets.id)
|
||||
|
||||
return mint_wallet
|
||||
@@ -161,7 +162,7 @@ async def proofs_to_token(wallet, proofs, url: str):
|
||||
# check whether we know the mint urls for these proofs
|
||||
for k in keysets:
|
||||
ks = await get_keyset(id=k, db=wallet.db)
|
||||
url = ks.mint_url if ks is not None else None
|
||||
url = ks.mint_url if ks and ks.mint_url else ""
|
||||
|
||||
url = url or (
|
||||
input(f"Enter mint URL (press enter for default {MINT_URL}): ") or MINT_URL
|
||||
|
||||
@@ -16,10 +16,13 @@ from cashu.core.base import (
|
||||
BlindedMessage,
|
||||
BlindedSignature,
|
||||
CheckFeesRequest,
|
||||
KeysetsResponse,
|
||||
GetMintResponse,
|
||||
PostMintResponse,
|
||||
CheckRequest,
|
||||
Invoice,
|
||||
MeltRequest,
|
||||
MintRequest,
|
||||
BlindedMessages,
|
||||
P2SHScript,
|
||||
Proof,
|
||||
SplitRequest,
|
||||
@@ -130,6 +133,8 @@ class LedgerAPI:
|
||||
keyset = await self._get_keys(self.url)
|
||||
|
||||
# store current keyset
|
||||
assert keyset.public_keys
|
||||
assert keyset.id
|
||||
assert len(keyset.public_keys) > 0, "did not receive keys from mint."
|
||||
|
||||
# check if current keyset is in db
|
||||
@@ -140,7 +145,7 @@ class LedgerAPI:
|
||||
# get all active keysets of this mint
|
||||
mint_keysets = []
|
||||
try:
|
||||
keysets_resp = await self._get_keysets(self.url)
|
||||
keysets_resp = await self._get_keyset_ids(self.url)
|
||||
mint_keysets = keysets_resp["keysets"]
|
||||
# store active keysets
|
||||
except:
|
||||
@@ -160,7 +165,7 @@ class LedgerAPI:
|
||||
assert len(amounts) == len(
|
||||
secrets
|
||||
), f"len(amounts)={len(amounts)} not equal to len(secrets)={len(secrets)}"
|
||||
payloads: MintRequest = MintRequest()
|
||||
payloads: BlindedMessages = BlindedMessages()
|
||||
rs = []
|
||||
for secret, amount in zip(secrets, amounts):
|
||||
B_, r = b_dhke.step1_alice(secret)
|
||||
@@ -198,7 +203,7 @@ class LedgerAPI:
|
||||
int(amt): PublicKey(bytes.fromhex(val), raw=True)
|
||||
for amt, val in keys.items()
|
||||
}
|
||||
keyset = WalletKeyset(pubkeys=keyset_keys, mint_url=url)
|
||||
keyset = WalletKeyset(public_keys=keyset_keys, mint_url=url)
|
||||
return keyset
|
||||
|
||||
async def _get_keyset(self, url: str, keyset_id: str):
|
||||
@@ -217,18 +222,19 @@ class LedgerAPI:
|
||||
int(amt): PublicKey(bytes.fromhex(val), raw=True)
|
||||
for amt, val in keys.items()
|
||||
}
|
||||
keyset = WalletKeyset(pubkeys=keyset_keys, mint_url=url)
|
||||
keyset = WalletKeyset(public_keys=keyset_keys, mint_url=url)
|
||||
return keyset
|
||||
|
||||
async def _get_keysets(self, url: str):
|
||||
async def _get_keyset_ids(self, url: str):
|
||||
self.s = self._set_requests()
|
||||
resp = self.s.get(
|
||||
url + "/keysets",
|
||||
)
|
||||
resp.raise_for_status()
|
||||
keysets = resp.json()
|
||||
assert len(keysets), Exception("did not receive any keysets")
|
||||
return keysets
|
||||
keysets_dict = resp.json()
|
||||
keysets = KeysetsResponse.parse_obj(keysets_dict)
|
||||
assert len(keysets.keysets), Exception("did not receive any keysets")
|
||||
return keysets.dict()
|
||||
|
||||
def request_mint(self, amount):
|
||||
"""Requests a mint from the server and returns Lightning invoice."""
|
||||
@@ -237,7 +243,8 @@ class LedgerAPI:
|
||||
resp.raise_for_status()
|
||||
return_dict = resp.json()
|
||||
self.raise_on_error(return_dict)
|
||||
return Invoice(amount=amount, pr=return_dict["pr"], hash=return_dict["hash"])
|
||||
mint_response = GetMintResponse.parse_obj(return_dict)
|
||||
return Invoice(amount=amount, pr=mint_response.pr, hash=mint_response.hash)
|
||||
|
||||
async def mint(self, amounts, payment_hash=None):
|
||||
"""Mints new coins and returns a proof of promise."""
|
||||
@@ -251,11 +258,10 @@ class LedgerAPI:
|
||||
params={"payment_hash": payment_hash},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
promises_list = resp.json()
|
||||
self.raise_on_error(promises_list)
|
||||
|
||||
promises = [BlindedSignature(**p) for p in promises_list]
|
||||
return self._construct_proofs(promises, secrets, rs)
|
||||
reponse_dict = resp.json()
|
||||
self.raise_on_error(reponse_dict)
|
||||
promises = PostMintResponse.parse_obj(reponse_dict)
|
||||
return self._construct_proofs(promises.promises, secrets, rs)
|
||||
|
||||
async def split(self, proofs, amount, scnd_secret: Optional[str] = None):
|
||||
"""Consume proofs and create new promises based on amount split.
|
||||
@@ -304,7 +310,7 @@ class LedgerAPI:
|
||||
self.s = self._set_requests()
|
||||
resp = self.s.post(
|
||||
self.url + "/split",
|
||||
json=split_payload.dict(include=_splitrequest_include_fields(proofs)),
|
||||
json=split_payload.dict(include=_splitrequest_include_fields(proofs)), # type: ignore
|
||||
)
|
||||
resp.raise_for_status()
|
||||
promises_dict = resp.json()
|
||||
@@ -337,7 +343,7 @@ class LedgerAPI:
|
||||
self.s = self._set_requests()
|
||||
resp = self.s.post(
|
||||
self.url + "/check",
|
||||
json=payload.dict(include=_check_spendable_include_fields(proofs)),
|
||||
json=payload.dict(include=_check_spendable_include_fields(proofs)), # type: ignore
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return_dict = resp.json()
|
||||
@@ -375,7 +381,7 @@ class LedgerAPI:
|
||||
self.s = self._set_requests()
|
||||
resp = self.s.post(
|
||||
self.url + "/melt",
|
||||
json=payload.dict(include=_meltequest_include_fields(proofs)),
|
||||
json=payload.dict(include=_meltequest_include_fields(proofs)), # type: ignore
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return_dict = resp.json()
|
||||
@@ -411,7 +417,9 @@ class Wallet(LedgerAPI):
|
||||
for id in set([p.id for p in proofs]):
|
||||
if id is None:
|
||||
continue
|
||||
keyset: WalletKeyset = await get_keyset(id=id, db=self.db)
|
||||
keyset_crud = await get_keyset(id=id, db=self.db)
|
||||
assert keyset_crud is not None, "keyset not found"
|
||||
keyset: WalletKeyset = keyset_crud
|
||||
if keyset.mint_url not in ret:
|
||||
ret[keyset.mint_url] = [p for p in proofs if p.id == id]
|
||||
else:
|
||||
@@ -504,7 +512,7 @@ class Wallet(LedgerAPI):
|
||||
if proof.id:
|
||||
# load the keyset from the db
|
||||
keyset = await get_keyset(id=proof.id, db=self.db)
|
||||
if keyset and keyset.mint_url:
|
||||
if keyset and keyset.mint_url and keyset.id:
|
||||
# TODO: replace this with a mint pubkey
|
||||
placeholder_mint_id = keyset.mint_url
|
||||
if placeholder_mint_id not in mints:
|
||||
@@ -577,7 +585,8 @@ class Wallet(LedgerAPI):
|
||||
Splits proofs such that a Lightning invoice can be paid.
|
||||
"""
|
||||
amount, _ = await self.get_pay_amount_with_fees(invoice)
|
||||
_, send_proofs = await self.split_to_send(self.proofs, amount)
|
||||
# TODO: fix mypy asyncio return multiple values
|
||||
_, send_proofs = await self.split_to_send(self.proofs, amount) # type: ignore
|
||||
return send_proofs
|
||||
|
||||
async def split_to_send(
|
||||
|
||||
@@ -52,8 +52,8 @@ Here we see how `Alice` generates `N` blinded messages `T_i`. The following step
|
||||
- `Alice` remembers `r` for the construction of the proof in Step 5.
|
||||
|
||||
### Step 4: Request tokens
|
||||
- `Alice` constructs JSON `MintRequest = {"blinded_messages" : ["amount" : <amount>, "B_" : <blinded_message>] }` [NOTE: rename "blinded_messages", rename "B_", rename "MintRequest"]
|
||||
- `Alice` requests tokens via `POST /mint?payment_hash=<payment_hash>` with body `MintRequest` [NOTE: rename MintRequest]
|
||||
- `Alice` constructs JSON `BlindedMessages = {"blinded_messages" : ["amount" : <amount>, "B_" : <blinded_message>] }` [NOTE: rename "blinded_messages", rename "B_", rename "BlindedMessages"]
|
||||
- `Alice` requests tokens via `POST /mint?payment_hash=<payment_hash>` with body `BlindedMessages` [NOTE: rename BlindedMessages]
|
||||
- `Alice` receives from `Bob` a list of blinded signatures `List[BlindedSignature]`, one for each token, e.g. `[{"amount" : <amount>, "C_" : <blinded_signature>}, ...]` [NOTE: rename C_]
|
||||
- If an error occured, `Alice` receives JSON `{"error" : <error_reason>}}`[*TODO: Specify case of error*]
|
||||
|
||||
@@ -121,5 +121,5 @@ Here we describe how `Alice` can request from `Bob` to make a Lightning payment
|
||||
|
||||
# Todo:
|
||||
- Call subsections 1. and 1.2 etc so they can be referenced
|
||||
- Define objets like `MintRequest` and `SplitRequests` once when they appear and reuse them.
|
||||
- Define objets like `BlindedMessages` and `SplitRequests` once when they appear and reuse them.
|
||||
- Clarify whether a `TOKEN` is a single Proof or a list of Proofs
|
||||
|
||||
@@ -10,7 +10,7 @@ Request of `Alice`:
|
||||
POST https://mint.host:3338/mint&payment_hash=67d1d9ea6ada225c115418671b64a
|
||||
```
|
||||
|
||||
With the data being of the form `MintRequest`:
|
||||
With the data being of the form `BlindedMessages`:
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@ With the data being of the form `SplitRequest`:
|
||||
```json
|
||||
{
|
||||
"proofs": Proofs,
|
||||
"outputs": MintRequest,
|
||||
"outputs": BlindedMessages,
|
||||
"amount": int
|
||||
}
|
||||
```
|
||||
|
||||
@@ -4,7 +4,7 @@ import requests
|
||||
from cashu.tor.tor import TorProxy
|
||||
|
||||
|
||||
# @pytest.mark.skip
|
||||
@pytest.mark.skip
|
||||
def test_tor_setup():
|
||||
s = requests.Session()
|
||||
|
||||
|
||||
@@ -68,8 +68,8 @@ async def test_get_keyset(wallet1: Wallet):
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_keysets(wallet1: Wallet):
|
||||
keyset = await wallet1._get_keysets(wallet1.url)
|
||||
async def test_get_keyset_ids(wallet1: Wallet):
|
||||
keyset = await wallet1._get_keyset_ids(wallet1.url)
|
||||
assert type(keyset) == dict
|
||||
assert type(keyset["keysets"]) == list
|
||||
assert len(keyset["keysets"]) > 0
|
||||
|
||||
Reference in New Issue
Block a user