mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-24 03:54:21 +01:00
Merge pull request #93 from cashubtc/SpringClean
PostMintRequest format change
This commit is contained in:
@@ -115,7 +115,7 @@ cashu info
|
||||
|
||||
Returns:
|
||||
```bash
|
||||
Version: 0.7.1
|
||||
Version: 0.8
|
||||
Debug: False
|
||||
Cashu dir: /home/user/.cashu
|
||||
Wallet: wallet
|
||||
|
||||
@@ -6,6 +6,8 @@ from pydantic import BaseModel
|
||||
from cashu.core.crypto import derive_keys, derive_keyset_id, derive_pubkeys
|
||||
from cashu.core.secp import PrivateKey, PublicKey
|
||||
|
||||
# ------- PROOFS -------
|
||||
|
||||
|
||||
class P2SHScript(BaseModel):
|
||||
script: str
|
||||
@@ -16,7 +18,7 @@ class P2SHScript(BaseModel):
|
||||
class Proof(BaseModel):
|
||||
id: Union[
|
||||
None, str
|
||||
] = "" # NOTE: None for backwards compatibility of old clients < 0.3
|
||||
] = "" # NOTE: None for backwards compatibility for old clients that do not include the keyset id < 0.3
|
||||
amount: int = 0
|
||||
secret: str = ""
|
||||
C: str = ""
|
||||
@@ -39,10 +41,7 @@ class Proof(BaseModel):
|
||||
self.__setattr__(key, val)
|
||||
|
||||
|
||||
class Proofs(BaseModel):
|
||||
"""TODO: Use this model"""
|
||||
|
||||
proofs: List[Proof]
|
||||
# ------- LIGHTNING INVOICE -------
|
||||
|
||||
|
||||
class Invoice(BaseModel):
|
||||
@@ -56,9 +55,21 @@ class Invoice(BaseModel):
|
||||
time_paid: Union[None, str, int, float] = ""
|
||||
|
||||
|
||||
class BlindedMessage(BaseModel):
|
||||
amount: int
|
||||
B_: str
|
||||
# ------- API -------
|
||||
|
||||
|
||||
# ------- API: KEYS -------
|
||||
|
||||
|
||||
class KeysResponse(BaseModel):
|
||||
__root__: Dict[str, str]
|
||||
|
||||
|
||||
class KeysetsResponse(BaseModel):
|
||||
keysets: list[str]
|
||||
|
||||
|
||||
# ------- API: MINT -------
|
||||
|
||||
|
||||
class BlindedSignature(BaseModel):
|
||||
@@ -67,8 +78,13 @@ class BlindedSignature(BaseModel):
|
||||
C_: str
|
||||
|
||||
|
||||
class MintRequest(BaseModel):
|
||||
blinded_messages: List[BlindedMessage] = []
|
||||
class PostMintResponseLegacy(BaseModel):
|
||||
# NOTE: Backwards compability for < 0.8 where we used a simple list and not a key-value dictionary
|
||||
__root__: List[BlindedSignature] = []
|
||||
|
||||
|
||||
class PostMintResponse(BaseModel):
|
||||
promises: List[BlindedSignature] = []
|
||||
|
||||
|
||||
class GetMintResponse(BaseModel):
|
||||
@@ -76,18 +92,38 @@ class GetMintResponse(BaseModel):
|
||||
hash: str
|
||||
|
||||
|
||||
# ------- API: MELT -------
|
||||
|
||||
|
||||
class MeltRequest(BaseModel):
|
||||
proofs: List[Proof]
|
||||
invoice: str
|
||||
|
||||
|
||||
class GetMeltResponse(BaseModel):
|
||||
paid: Union[bool, None]
|
||||
preimage: Union[str, None]
|
||||
|
||||
|
||||
# ------- API: SPLIT -------
|
||||
|
||||
|
||||
class BlindedMessage(BaseModel):
|
||||
amount: int
|
||||
B_: str
|
||||
|
||||
|
||||
class BlindedMessages(BaseModel):
|
||||
blinded_messages: List[BlindedMessage] = []
|
||||
|
||||
|
||||
class SplitRequest(BaseModel):
|
||||
proofs: List[Proof]
|
||||
amount: int
|
||||
output_data: Union[
|
||||
MintRequest, None
|
||||
] = None # backwards compatibility with clients < v0.2.2
|
||||
outputs: Union[MintRequest, None] = None
|
||||
BlindedMessages, None
|
||||
] = None # backwards compatibility with clients that called this output_data and not outputs < v0.2.2
|
||||
outputs: Union[BlindedMessages, None] = None
|
||||
|
||||
def __init__(self, **data):
|
||||
super().__init__(**data)
|
||||
@@ -105,6 +141,9 @@ class PostSplitResponse(BaseModel):
|
||||
snd: List[BlindedSignature]
|
||||
|
||||
|
||||
# ------- API: CHECK -------
|
||||
|
||||
|
||||
class CheckRequest(BaseModel):
|
||||
proofs: List[Proof]
|
||||
|
||||
@@ -117,29 +156,35 @@ class CheckFeesResponse(BaseModel):
|
||||
fee: Union[int, None]
|
||||
|
||||
|
||||
class MeltRequest(BaseModel):
|
||||
proofs: List[Proof]
|
||||
invoice: str
|
||||
# ------- KEYSETS -------
|
||||
|
||||
|
||||
class KeyBase(BaseModel):
|
||||
"""
|
||||
Public key from a keyset id for a given amount.
|
||||
"""
|
||||
|
||||
id: str
|
||||
amount: int
|
||||
pubkey: str
|
||||
|
||||
|
||||
class WalletKeyset:
|
||||
id: str
|
||||
public_keys: Dict[int, PublicKey]
|
||||
"""
|
||||
Contains the keyset from the wallets's perspective.
|
||||
"""
|
||||
|
||||
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 +198,24 @@ 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
|
||||
"""
|
||||
Contains the keyset from the mint's perspective.
|
||||
"""
|
||||
|
||||
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,37 +243,50 @@ 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
|
||||
}
|
||||
|
||||
|
||||
class MintKeysets:
|
||||
"""
|
||||
Collection of keyset IDs and the corresponding keyset of the mint.
|
||||
"""
|
||||
|
||||
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()]
|
||||
|
||||
|
||||
class TokenMintJson(BaseModel):
|
||||
# ------- TOKEN -------
|
||||
|
||||
|
||||
class TokenV1(BaseModel):
|
||||
# NOTE: not used in Pydantic validation
|
||||
__root__: List[Proof]
|
||||
|
||||
|
||||
class TokenMintV2(BaseModel):
|
||||
url: str
|
||||
ks: List[str]
|
||||
|
||||
|
||||
class TokenJson(BaseModel):
|
||||
tokens: List[Proof]
|
||||
mints: Optional[Dict[str, TokenMintJson]] = None
|
||||
class TokenV2(BaseModel):
|
||||
proofs: List[Proof]
|
||||
mints: Optional[Dict[str, TokenMintV2]] = None
|
||||
|
||||
def to_dict(self):
|
||||
return dict(
|
||||
tokens=[p.to_dict() for p in self.tokens],
|
||||
proofs=[p.to_dict() for p in self.proofs],
|
||||
mints={k: v.dict() for k, v in self.mints.items()}, # type: ignore
|
||||
)
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -57,4 +57,4 @@ NOSTR_PRIVATE_KEY = env.str("NOSTR_PRIVATE_KEY", default=None)
|
||||
NOSTR_RELAYS = env.list("NOSTR_RELAYS", default=["wss://nostr-pub.wellorder.net"])
|
||||
|
||||
MAX_ORDER = 64
|
||||
VERSION = "0.7.1"
|
||||
VERSION = "0.8"
|
||||
|
||||
@@ -4,14 +4,17 @@ from fastapi import APIRouter
|
||||
from secp256k1 import PublicKey
|
||||
|
||||
from cashu.core.base import (
|
||||
BlindedMessages,
|
||||
BlindedSignature,
|
||||
CheckFeesRequest,
|
||||
CheckFeesResponse,
|
||||
CheckRequest,
|
||||
GetMeltResponse,
|
||||
GetMintResponse,
|
||||
KeysetsResponse,
|
||||
KeysResponse,
|
||||
MeltRequest,
|
||||
MintRequest,
|
||||
PostMintResponse,
|
||||
PostSplitResponse,
|
||||
SplitRequest,
|
||||
)
|
||||
@@ -22,28 +25,31 @@ 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 = KeysetsResponse(keysets=ledger.keysets.get_ids())
|
||||
return keysets
|
||||
|
||||
|
||||
@router.get("/mint")
|
||||
@@ -62,9 +68,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 +80,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 +110,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 +131,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
|
||||
|
||||
@@ -48,7 +48,7 @@ from cashu.wallet.crud import (
|
||||
)
|
||||
from cashu.wallet.wallet import Wallet as Wallet
|
||||
|
||||
from .cli_helpers import (
|
||||
from .clihelpers import (
|
||||
get_mint_wallet,
|
||||
print_mint_balances,
|
||||
proofs_to_token,
|
||||
@@ -309,6 +309,7 @@ async def send(ctx, amount: int, lock: str, legacy: bool):
|
||||
@click.option("--lock", "-l", default=None, help="Lock tokens (P2SH).", type=str)
|
||||
@click.option(
|
||||
"--legacy",
|
||||
"-l",
|
||||
default=False,
|
||||
is_flag=True,
|
||||
help="Print legacy token without mint information.",
|
||||
@@ -387,7 +388,11 @@ async def receive(ctx, token: str, lock: str):
|
||||
# deserialize token
|
||||
dtoken = json.loads(base64.urlsafe_b64decode(token))
|
||||
|
||||
assert "tokens" in dtoken, Exception("no proofs in token")
|
||||
# backwards compatibility wallet to wallet < 0.8: V2 tokens renamed "tokens" field to "proofs"
|
||||
if "tokens" in dtoken:
|
||||
dtoken["proofs"] = dtoken.pop("tokens")
|
||||
|
||||
assert "proofs" in dtoken, Exception("no proofs in token")
|
||||
includes_mint_info: bool = "mints" in dtoken and dtoken.get("mints") is not None
|
||||
|
||||
# if there is a `mints` field in the token
|
||||
@@ -402,7 +407,7 @@ async def receive(ctx, token: str, lock: str):
|
||||
await wallet.load_proofs()
|
||||
else:
|
||||
# no mint information present, we extract the proofs and use wallet's default mint
|
||||
proofs = [Proof(**p) for p in dtoken["tokens"]]
|
||||
proofs = [Proof(**p) for p in dtoken["proofs"]]
|
||||
_, _ = await wallet.redeem(proofs, script, signature)
|
||||
|
||||
wallet.status()
|
||||
@@ -494,15 +499,23 @@ async def burn(ctx, token: str, all: bool, force: bool):
|
||||
else:
|
||||
# check only the specified ones
|
||||
proofs = [Proof(**p) for p in json.loads(base64.urlsafe_b64decode(token))]
|
||||
wallet.status()
|
||||
|
||||
await wallet.invalidate(proofs)
|
||||
wallet.status()
|
||||
|
||||
|
||||
@cli.command("pending", help="Show pending tokens.")
|
||||
@click.option(
|
||||
"--legacy",
|
||||
"-l",
|
||||
default=False,
|
||||
is_flag=True,
|
||||
help="Print legacy token without mint information.",
|
||||
type=bool,
|
||||
)
|
||||
@click.pass_context
|
||||
@coro
|
||||
async def pending(ctx):
|
||||
async def pending(ctx, legacy):
|
||||
wallet: Wallet = ctx.obj["WALLET"]
|
||||
reserved_proofs = await get_reserved_proofs(wallet.db)
|
||||
if len(reserved_proofs):
|
||||
@@ -513,7 +526,7 @@ async def pending(ctx):
|
||||
):
|
||||
grouped_proofs = list(value)
|
||||
token = await wallet.serialize_proofs(grouped_proofs)
|
||||
token_hidden_secret = await wallet.serialize_proofs(grouped_proofs)
|
||||
# token_hidden_secret = await wallet.serialize_proofs(grouped_proofs)
|
||||
reserved_date = datetime.utcfromtimestamp(
|
||||
int(grouped_proofs[0].time_reserved)
|
||||
).strftime("%Y-%m-%d %H:%M:%S")
|
||||
@@ -521,9 +534,15 @@ async def pending(ctx):
|
||||
f"#{i} Amount: {sum_proofs(grouped_proofs)} sat Time: {reserved_date} ID: {key}\n"
|
||||
)
|
||||
print(f"{token}\n")
|
||||
|
||||
if legacy:
|
||||
token_legacy = await wallet.serialize_proofs(
|
||||
grouped_proofs,
|
||||
legacy=True,
|
||||
)
|
||||
print(f"{token_legacy}\n")
|
||||
print(f"--------------------------\n")
|
||||
print("To remove all spent tokens use: cashu burn -a")
|
||||
wallet.status()
|
||||
|
||||
|
||||
@cli.command("lock", help="Generate receiving lock.")
|
||||
|
||||
@@ -3,7 +3,7 @@ import urllib.parse
|
||||
|
||||
import click
|
||||
|
||||
from cashu.core.base import Proof, TokenJson, TokenMintJson, WalletKeyset
|
||||
from cashu.core.base import Proof, TokenMintV2, TokenV2, WalletKeyset
|
||||
from cashu.core.settings import CASHU_DIR, MINT_URL
|
||||
from cashu.wallet.crud import get_keyset
|
||||
from cashu.wallet.wallet import Wallet as Wallet
|
||||
@@ -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
|
||||
@@ -63,7 +63,7 @@ async def redeem_multimint(ctx, dtoken, script, signature):
|
||||
|
||||
# redeem proofs of this keyset
|
||||
redeem_proofs = [
|
||||
Proof(**p) for p in dtoken["tokens"] if Proof(**p).id == keyset
|
||||
Proof(**p) for p in dtoken["proofs"] if Proof(**p).id == keyset
|
||||
]
|
||||
_, _ = await keyset_wallet.redeem(
|
||||
redeem_proofs, scnd_script=script, scnd_siganture=signature
|
||||
@@ -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
|
||||
@@ -151,7 +152,7 @@ async def proofs_to_token(wallet, proofs, url: str):
|
||||
Ingests proofs and
|
||||
"""
|
||||
# and add url and keyset id to token
|
||||
token: TokenJson = await wallet._make_token(proofs, include_mints=False)
|
||||
token: TokenV2 = await wallet._make_token(proofs, include_mints=False)
|
||||
token.mints = {}
|
||||
|
||||
# get keysets of proofs
|
||||
@@ -161,12 +162,12 @@ 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
|
||||
)
|
||||
|
||||
token.mints[url] = TokenMintJson(url=url, ks=keysets) # type: ignore
|
||||
token.mints[url] = TokenMintV2(url=url, ks=keysets) # type: ignore
|
||||
token_serialized = await wallet._serialize_token_base64(token)
|
||||
return token_serialized
|
||||
@@ -14,17 +14,21 @@ import cashu.core.b_dhke as b_dhke
|
||||
import cashu.core.bolt11 as bolt11
|
||||
from cashu.core.base import (
|
||||
BlindedMessage,
|
||||
BlindedMessages,
|
||||
BlindedSignature,
|
||||
CheckFeesRequest,
|
||||
CheckRequest,
|
||||
GetMintResponse,
|
||||
Invoice,
|
||||
KeysetsResponse,
|
||||
MeltRequest,
|
||||
MintRequest,
|
||||
P2SHScript,
|
||||
PostMintResponse,
|
||||
PostMintResponseLegacy,
|
||||
Proof,
|
||||
SplitRequest,
|
||||
TokenJson,
|
||||
TokenMintJson,
|
||||
TokenMintV2,
|
||||
TokenV2,
|
||||
WalletKeyset,
|
||||
)
|
||||
from cashu.core.bolt11 import Invoice as InvoiceBolt11
|
||||
@@ -130,6 +134,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 +146,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 +166,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 +204,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 +223,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 +244,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,10 +259,17 @@ class LedgerAPI:
|
||||
params={"payment_hash": payment_hash},
|
||||
)
|
||||
resp.raise_for_status()
|
||||
promises_list = resp.json()
|
||||
self.raise_on_error(promises_list)
|
||||
reponse_dict = resp.json()
|
||||
self.raise_on_error(reponse_dict)
|
||||
try:
|
||||
# backwards compatibility: parse promises < 0.8 with no "promises" field
|
||||
promises = PostMintResponseLegacy.parse_obj(reponse_dict).__root__
|
||||
logger.warning(
|
||||
"Parsing token with no promises field. Please upgrade mint to 0.8"
|
||||
)
|
||||
except:
|
||||
promises = PostMintResponse.parse_obj(reponse_dict).promises
|
||||
|
||||
promises = [BlindedSignature(**p) for p in promises_list]
|
||||
return self._construct_proofs(promises, secrets, rs)
|
||||
|
||||
async def split(self, proofs, amount, scnd_secret: Optional[str] = None):
|
||||
@@ -304,7 +319,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 +352,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 +390,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 +426,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:
|
||||
@@ -489,27 +506,27 @@ class Wallet(LedgerAPI):
|
||||
|
||||
async def _make_token(self, proofs: List[Proof], include_mints=True):
|
||||
"""
|
||||
Takes list of proofs and produces a TokenJson by looking up
|
||||
Takes list of proofs and produces a TokenV2 by looking up
|
||||
the keyset id and mint URLs from the database.
|
||||
"""
|
||||
# build token
|
||||
token = TokenJson(tokens=proofs)
|
||||
token = TokenV2(proofs=proofs)
|
||||
|
||||
# add mint information to the token, if requested
|
||||
if include_mints:
|
||||
# hold information about the mint
|
||||
mints: Dict[str, TokenMintJson] = dict()
|
||||
# dummy object to hold information about the mint
|
||||
mints: Dict[str, TokenMintV2] = dict()
|
||||
# iterate through all proofs and add their keyset to `mints`
|
||||
for proof in proofs:
|
||||
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:
|
||||
# mint information
|
||||
id = TokenMintJson(
|
||||
id = TokenMintV2(
|
||||
url=keyset.mint_url,
|
||||
ks=[keyset.id],
|
||||
)
|
||||
@@ -518,13 +535,15 @@ class Wallet(LedgerAPI):
|
||||
# if a mint has multiple keysets, append to the existing list
|
||||
if keyset.id not in mints[placeholder_mint_id].ks:
|
||||
mints[placeholder_mint_id].ks.append(keyset.id)
|
||||
|
||||
if len(mints) > 0:
|
||||
# add dummy object to token
|
||||
token.mints = mints
|
||||
return token
|
||||
|
||||
async def _serialize_token_base64(self, token: TokenJson):
|
||||
async def _serialize_token_base64(self, token: TokenV2):
|
||||
"""
|
||||
Takes a TokenJson and serializes it in urlsafe_base64.
|
||||
Takes a TokenV2 and serializes it in urlsafe_base64.
|
||||
"""
|
||||
# encode the token as a base64 string
|
||||
token_base64 = base64.urlsafe_b64encode(
|
||||
@@ -588,7 +607,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(
|
||||
|
||||
108
docs/specs/00.md
108
docs/specs/00.md
@@ -17,10 +17,10 @@ Mint: `Bob`
|
||||
- `T` blinded message
|
||||
- `Z` proof (unblinded signature)
|
||||
|
||||
## Blind Diffie-Hellmann key exchange (BDHKE)
|
||||
# Blind Diffie-Hellmann key exchange (BDHKE)
|
||||
|
||||
- Mint `Bob` publishes `K = kG`
|
||||
- `Alice` picks secret `x` and computes `Y = hash_to_curve(x)`
|
||||
- Mint `Bob` publishes `K = kG`
|
||||
- `Alice` picks secret `x` and computes `Y = hash_to_curve(x)`
|
||||
- `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)
|
||||
- `Alice` can calculate the unblinded key as `Q - rK = kY + krG - krG = kY = Z`
|
||||
@@ -31,52 +31,62 @@ Mint: `Bob`
|
||||
|
||||
### `BlindedMessage`
|
||||
|
||||
A encrypted ("blinded") secret and an amount sent from `Alice` to `Bob`.
|
||||
An encrypted ("blinded") secret and an amount sent from `Alice` to `Bob` before [minting new tokens][04]
|
||||
|
||||
```json
|
||||
{
|
||||
"amount": int,
|
||||
"B_": str
|
||||
"amount": int,
|
||||
"B_": str
|
||||
}
|
||||
```
|
||||
|
||||
`amount` is the value of the requested token and `B_` is the encrypted secret message generated by `Alice`.
|
||||
|
||||
### `BlindedSignature`
|
||||
|
||||
A signature on the `BlindedMessage` sent from `Bob` to `Alice`.
|
||||
A signature on the `BlindedMessage` sent from `Bob` to `Alice` after [minting new tokens][04].
|
||||
|
||||
```json
|
||||
{
|
||||
"amount": int,
|
||||
"C_": str,
|
||||
"id": str | None
|
||||
"amount": int,
|
||||
"C_": str,
|
||||
"id": str | None
|
||||
}
|
||||
```
|
||||
|
||||
`amount` is the value of the blinded token, `C_` is the blinded signature on the secret message `B_` sent in the previous step. `id` is the [keyset id][02] of the mint public keys that signed the token.
|
||||
|
||||
### `Proof`
|
||||
|
||||
A `Proof` is also called a `Token` and has the following form:
|
||||
A `Proof` is also called a `Token` in its serialized form. `Alice` sends the serialized to `Carol` to initiate a payment. Upon receiving the token, `Carol` deserializes it and requests a split from `Bob` to exchange it for new `BlindedSignature`'s. `Carol` sends the `Proof` to `Bob` together with new `BlindedMessage`'s that she wants to have signed.
|
||||
|
||||
```json
|
||||
{
|
||||
"amount": int,
|
||||
"secret": str,
|
||||
"C": str,
|
||||
"id": None | str,
|
||||
"script": P2SHScript | None,
|
||||
"amount": int,
|
||||
"secret": str,
|
||||
"C": str,
|
||||
"id": None | str,
|
||||
"script": P2SHScript | None,
|
||||
}
|
||||
```
|
||||
|
||||
`amount` is the value of the `Proof`, `secret` is the secret message, `C` is the unblinded signature on `secret`, `id` is the [keyset id][02] of the mint public keys that signed the token. `script` is a `P2SHScript` that specifies the spending condition for this `Proof` [TODO: P2SH documentation].
|
||||
|
||||
### `Proofs`
|
||||
|
||||
A list of `Proof`'s. In general, this will be used for most operations instead of a single `Proof`. `Proofs` can be serialized (see Methods/Serialization [TODO: Link Serialization])
|
||||
An array (list) of `Proof`'s. In general, this will be used for most operations instead of a single `Proof`. `Proofs` must be serialized before sending between wallets (see [Serialization](#serialization-of-proofs).
|
||||
|
||||
## 0.2 - Methods
|
||||
|
||||
### Serialization of `Proofs`
|
||||
|
||||
To send and receive `Proofs`, wallets serialize them in a `base64_urlsafe` format.
|
||||
To send and receive `Proofs`, wallets serialize them in a `base64_urlsafe` format. There are two versions of the serialization format.
|
||||
|
||||
Example:
|
||||
#### 0.2.1 - V1 tokens
|
||||
|
||||
This token format is a list of `Proof`s. Each `Proof` contains the keyset id in the field `id` that can be used by a wallet to identify the mint of this token. A wallet that encounters an unknown `id`, it CAN ask the user to enter the mint url of this yet unknown mint. The wallet SHOULD explicity ask the user whether they trust the mint.
|
||||
|
||||
##### Example JSON:
|
||||
|
||||
```json
|
||||
[
|
||||
@@ -95,8 +105,66 @@ Example:
|
||||
]
|
||||
```
|
||||
|
||||
becomes
|
||||
When serialized, this becomes:
|
||||
|
||||
```
|
||||
W3siaWQiOiAiRFNBbDludnZ5ZnZhIiwgImFtb3VudCI6IDgsICJzZWNyZXQiOiAiRGJSS0l5YTBldGR3STVzRkFOMEFYUSIsICJDIjogIjAyZGY3ZjJmYzI5NjMxYjcxYTFkYjExYzE2M2IwYjFjYjQwNDQ0YWEyYjNkMjUzZDQzYjY4ZDc3YTcyZWQyZDYyNSJ9LCB7ImlkIjogIkRTQWw5bnZ2eWZ2YSIsICJhbW91bnQiOiAxNiwgInNlY3JldCI6ICJkX1BQYzVLcHVBQjJNNjBXWUFXNS1RIiwgIkMiOiAiMDI3MGUwYTM3ZjdhMGIyMWVhYjQzYWY3NTFkZDNjMDNmNjFmMDRjNjI2YzA0NDhmNjAzZjFkMWY1YWU1YTdkN2U2In1d
|
||||
```
|
||||
|
||||
#### 0.2.2 - V2 tokens
|
||||
|
||||
This token format includes information about the mint as well. The field `proofs` is like a V1 token. Additionally, the field `mints` can include a list of multiple mints from which the `proofs` are from. The `url` field is the URL of the mint. `ks` is a list of the keyset IDs belonging to this mint. All keyset IDs of the `proofs` must be present here to allow a wallet to map each proof to a mint.
|
||||
|
||||
##### Example JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"proofs": [
|
||||
{
|
||||
"id": "DSAl9nvvyfva",
|
||||
"amount": 2,
|
||||
"secret": "bdYCbHGONundLeYvv1P5dQ",
|
||||
"C": "02e6117fb1b1633a8c1657ed34ab25ecf8d4974091179c4773ec59f85f4e3991cf"
|
||||
},
|
||||
{
|
||||
"id": "DSAl9nvvyfva",
|
||||
"amount": 8,
|
||||
"secret": "KxyUPt5Mur_-RV8pCECJ6A",
|
||||
"C": "03b9dcdb7f195e07218b95b7c2dadc8289159fc44047439830f765b8c50bfb6bda"
|
||||
}
|
||||
],
|
||||
"mints": {
|
||||
"MINT_NAME": {
|
||||
"url": "http://server.host:3339",
|
||||
"ks": ["DSAl9nvvyfva"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When serialized, this becomes:
|
||||
|
||||
```
|
||||
eyJwcm9vZnMiOlt7ImlkIjoiRFNBbDludnZ5ZnZhIiwiYW1vdW50IjoyLCJzZWNyZXQiOiJiZFlDYkhHT051bmRMZVl2djFQNWRRIiwiQyI6IjAyZTYxMTdmYjFiMTYzM2E4YzE2NTdlZDM0YWIyNWVjZjhkNDk3NDA5MTE3OWM0NzczZWM1OWY4NWY0ZTM5OTFjZiJ9LHsiaWQiOiJEU0FsOW52dnlmdmEiLCJhbW91bnQiOjgsInNlY3JldCI6Ikt4eVVQdDVNdXJfLVJWOHBDRUNKNkEiLCJDIjoiMDNiOWRjZGI3ZjE5NWUwNzIxOGI5NWI3YzJkYWRjODI4OTE1OWZjNDQwNDc0Mzk4MzBmNzY1YjhjNTBiZmI2YmRhIn1dLCJtaW50cyI6eyJNSU5UX05BTUUiOnsidXJsIjoiaHR0cDovL3NlcnZlci5ob3N0OjMzMzkiLCJrcyI6WyJEU0FsOW52dnlmdmEiXX19fQ==
|
||||
```
|
||||
|
||||
[00]: 00.md
|
||||
[01]: 02.md
|
||||
[03]: 03.md
|
||||
[04]: 04.md
|
||||
[05]: 05.md
|
||||
[06]: 06.md
|
||||
[07]: 07.md
|
||||
[08]: 08.md
|
||||
[09]: 09.md
|
||||
[10]: 10.md
|
||||
[11]: 11.md
|
||||
[12]: 12.md
|
||||
[13]: 13.md
|
||||
[14]: 14.md
|
||||
[15]: 15.md
|
||||
[16]: 16.md
|
||||
[17]: 17.md
|
||||
[18]: 18.md
|
||||
[19]: 19.md
|
||||
[20]: 20.md
|
||||
@@ -22,8 +22,8 @@ Response of `Bob`:
|
||||
|
||||
```json
|
||||
{
|
||||
"pr": "lnbc100n1p3kdrv5sp5lpdxzghe5j67q...",
|
||||
"hash": "67d1d9ea6ada225c115418671b64a..."
|
||||
"pr": "lnbc100n1p3kdrv5sp5lpdxzghe5j67q...",
|
||||
"hash": "67d1d9ea6ada225c115418671b64a..."
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -12,15 +12,15 @@ 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
|
||||
{
|
||||
"blinded_messages":
|
||||
[
|
||||
BlindedMessage,
|
||||
...
|
||||
]
|
||||
"blinded_messages":
|
||||
[
|
||||
BlindedMessage,
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -30,18 +30,18 @@ With curl:
|
||||
```bash
|
||||
curl -X POST https://mint.host:3338/mint&payment_hash=67d1d9ea6ada225c115418671b64a -d \
|
||||
{
|
||||
"blinded_messages":
|
||||
[
|
||||
{
|
||||
"amount": 2,
|
||||
"B_": "02634a2c2b34bec9e8a4aba4361f6bf202d7fa2365379b0840afe249a7a9d71239"
|
||||
},
|
||||
{
|
||||
"amount": 8,
|
||||
"B_": "03b54ab451b15005f2c64d38fc512fca695914c8fd5094ee044e5724ad41fda247"
|
||||
|
||||
}
|
||||
]
|
||||
"blinded_messages":
|
||||
[
|
||||
{
|
||||
"amount": 2,
|
||||
"B_": "02634a2c2b34bec9e8a4aba4361f6bf202d7fa2365379b0840afe249a7a9d71239"
|
||||
},
|
||||
{
|
||||
"amount": 8,
|
||||
"B_": "03b54ab451b15005f2c64d38fc512fca695914c8fd5094ee044e5724ad41fda247"
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -52,18 +52,18 @@ If the invoice was successfully paid, `Bob` responds with a `PostMintResponse` w
|
||||
```json
|
||||
{
|
||||
"promises":
|
||||
[
|
||||
{
|
||||
"id": "DSAl9nvvyfva",
|
||||
"amount": 2,
|
||||
"C_": "03e61daa438fc7bcc53f6920ec6c8c357c24094fb04c1fc60e2606df4910b21ffb"
|
||||
},
|
||||
{
|
||||
"id": "DSAl9nvvyfva",
|
||||
"amount": 8,
|
||||
"C_": "03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de"
|
||||
},
|
||||
]
|
||||
[
|
||||
{
|
||||
"id": "DSAl9nvvyfva",
|
||||
"amount": 2,
|
||||
"C_": "03e61daa438fc7bcc53f6920ec6c8c357c24094fb04c1fc60e2606df4910b21ffb"
|
||||
},
|
||||
{
|
||||
"id": "DSAl9nvvyfva",
|
||||
"amount": 8,
|
||||
"C_": "03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
@@ -79,16 +79,16 @@ A list multiple `Proof`'s is called `Proofs` and has the form:
|
||||
```json
|
||||
{
|
||||
"proofs" :
|
||||
[
|
||||
{
|
||||
"id": "DSAl9nvvyfva",
|
||||
"amount": 2,
|
||||
"secret": "S+tDfc1Lfsrb06zaRdVTed6Izg",
|
||||
"C": "0242b0fb43804d8ba9a64ceef249ad7a60f42c15fe6d4907238b05e857527832a3"
|
||||
},
|
||||
{
|
||||
...
|
||||
}
|
||||
]
|
||||
[
|
||||
{
|
||||
"id": "DSAl9nvvyfva",
|
||||
"amount": 2,
|
||||
"secret": "S+tDfc1Lfsrb06zaRdVTed6Izg",
|
||||
"C": "0242b0fb43804d8ba9a64ceef249ad7a60f42c15fe6d4907238b05e857527832a3"
|
||||
},
|
||||
{
|
||||
...
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -16,12 +16,12 @@ With the data being of the form `MeltRequest`:
|
||||
|
||||
```json
|
||||
{
|
||||
"proofs":
|
||||
[
|
||||
Proof,
|
||||
...
|
||||
],
|
||||
"invoice": str
|
||||
"proofs":
|
||||
[
|
||||
Proof,
|
||||
...
|
||||
],
|
||||
"invoice": str
|
||||
}
|
||||
```
|
||||
|
||||
@@ -32,17 +32,17 @@ With curl:
|
||||
curl -X POST https://mint.host:3338/mint&payment_hash=67d1d9ea6ada225c115418671b64a -d \
|
||||
{
|
||||
"proofs" :
|
||||
[
|
||||
{
|
||||
"id": "DSAl9nvvyfva",
|
||||
"amount": 2,
|
||||
"secret": "S+tDfc1Lfsrb06zaRdVTed6Izg",
|
||||
"C": "0242b0fb43804d8ba9a64ceef249ad7a60f42c15fe6d4907238b05e857527832a3"
|
||||
},
|
||||
{
|
||||
...
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"id": "DSAl9nvvyfva",
|
||||
"amount": 2,
|
||||
"secret": "S+tDfc1Lfsrb06zaRdVTed6Izg",
|
||||
"C": "0242b0fb43804d8ba9a64ceef249ad7a60f42c15fe6d4907238b05e857527832a3"
|
||||
},
|
||||
{
|
||||
...
|
||||
}
|
||||
],
|
||||
"invoice": "lnbc100n1p3kdrv5sp5lpdxzghe5j67q..."
|
||||
}
|
||||
```
|
||||
|
||||
@@ -26,9 +26,9 @@ With the data being of the form `SplitRequest`:
|
||||
|
||||
```json
|
||||
{
|
||||
"proofs": Proofs,
|
||||
"outputs": MintRequest,
|
||||
"amount": int
|
||||
"proofs": Proofs,
|
||||
"outputs": BlindedMessages,
|
||||
"amount": int
|
||||
}
|
||||
```
|
||||
|
||||
@@ -37,31 +37,31 @@ With curl:
|
||||
```bash
|
||||
curl -X POST https://mint.host:3338/split -d \
|
||||
{
|
||||
"proofs":
|
||||
[
|
||||
{
|
||||
"id": "DSAl9nvvyfva",
|
||||
"amount": 2,
|
||||
"secret": "S+tDfc1Lfsrb06zaRdVTed6Izg",
|
||||
"C": "0242b0fb43804d8ba9a64ceef249ad7a60f42c15fe6d4907238b05e857527832a3"
|
||||
},
|
||||
{
|
||||
...
|
||||
}
|
||||
],
|
||||
"outputs":{
|
||||
"blinded_messages":
|
||||
[
|
||||
{
|
||||
"amount": 2,
|
||||
"B_": "02634a2c2b34bec9e8a4aba4361f6bf202d7fa2365379b0840afe249a7a9d71239"
|
||||
},
|
||||
{
|
||||
...
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": 40
|
||||
"proofs":
|
||||
[
|
||||
{
|
||||
"id": "DSAl9nvvyfva",
|
||||
"amount": 2,
|
||||
"secret": "S+tDfc1Lfsrb06zaRdVTed6Izg",
|
||||
"C": "0242b0fb43804d8ba9a64ceef249ad7a60f42c15fe6d4907238b05e857527832a3"
|
||||
},
|
||||
{
|
||||
...
|
||||
}
|
||||
],
|
||||
"outputs":{
|
||||
"blinded_messages":
|
||||
[
|
||||
{
|
||||
"amount": 2,
|
||||
"B_": "02634a2c2b34bec9e8a4aba4361f6bf202d7fa2365379b0840afe249a7a9d71239"
|
||||
},
|
||||
{
|
||||
...
|
||||
}
|
||||
]
|
||||
},
|
||||
"amount": 40
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "cashu"
|
||||
version = "0.7.1"
|
||||
version = "0.8"
|
||||
description = "Ecash wallet and mint."
|
||||
authors = ["calle <callebtc@protonmail.com>"]
|
||||
license = "MIT"
|
||||
|
||||
6
setup.py
6
setup.py
@@ -13,11 +13,11 @@ entry_points = {"console_scripts": ["cashu = cashu.wallet.cli:cli"]}
|
||||
|
||||
setuptools.setup(
|
||||
name="cashu",
|
||||
version="0.7.1",
|
||||
description="Ecash wallet and mint with Bitcoin Lightning support",
|
||||
version="0.8",
|
||||
description="Ecash wallet and mint for Bitcoin Lightning",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://github.com/callebtc/cashu",
|
||||
url="https://github.com/cashubtc/cashu",
|
||||
author="Calle",
|
||||
author_email="calle@protonmail.com",
|
||||
license="MIT",
|
||||
|
||||
@@ -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