From 3cbdebf5a5c116fbecfd53673cecf39672d5028e Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 14 Jan 2023 21:23:06 +0100 Subject: [PATCH] BlindedMessages is now List[BlindedMessage] (no blinded_messages field) and PostMintRequest (new) is now with outputs field --- cashu/core/base.py | 51 ++++++++++++++++++++---------------------- cashu/mint/router.py | 12 +++++----- cashu/wallet/wallet.py | 21 ++++++++--------- docs/specs/00.md | 10 ++++----- docs/specs/02.md | 20 +++++++++++++++++ docs/specs/03.md | 23 ++++++++++++++++++- docs/specs/04.md | 39 +++++++++++++++++++++++--------- docs/specs/05.md | 23 ++++++++++++++++++- docs/specs/06.md | 39 +++++++++++++++++++++++++++----- 9 files changed, 171 insertions(+), 67 deletions(-) diff --git a/cashu/core/base.py b/cashu/core/base.py index d03ab98..30db781 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -41,6 +41,27 @@ class Proof(BaseModel): self.__setattr__(key, val) +class Proofs(BaseModel): + # NOTE: not used in Pydantic validation + __root__: List[Proof] + + +class BlindedMessage(BaseModel): + amount: int + B_: str + + +class BlindedSignature(BaseModel): + id: Union[str, None] = None + amount: int + C_: str + + +class BlindedMessages(BaseModel): + # NOTE: not used in Pydantic validation + __root__: List[BlindedMessage] = [] + + # ------- LIGHTNING INVOICE ------- @@ -72,10 +93,8 @@ class KeysetsResponse(BaseModel): # ------- API: MINT ------- -class BlindedSignature(BaseModel): - id: Union[str, None] = None - amount: int - C_: str +class PostMintRequest(BaseModel): + outputs: List[BlindedMessage] class PostMintResponseLegacy(BaseModel): @@ -108,32 +127,10 @@ class GetMeltResponse(BaseModel): # ------- 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[ - 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) - self.backwards_compatibility_v021() - - def backwards_compatibility_v021(self): - # before v0.2.2: output_data, after: outputs - if self.output_data: - self.outputs = self.output_data - self.output_data = None + outputs: Union[List[BlindedMessage], None] = None class PostSplitResponse(BaseModel): diff --git a/cashu/mint/router.py b/cashu/mint/router.py index ecf6881..5744412 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -4,7 +4,7 @@ from fastapi import APIRouter from secp256k1 import PublicKey from cashu.core.base import ( - BlindedMessages, + BlindedMessage, BlindedSignature, CheckFeesRequest, CheckFeesResponse, @@ -14,6 +14,7 @@ from cashu.core.base import ( KeysetsResponse, KeysResponse, MeltRequest, + PostMintRequest, PostMintResponse, PostSplitResponse, SplitRequest, @@ -68,7 +69,7 @@ async def request_mint(amount: int = 0) -> GetMintResponse: @router.post("/mint") async def mint( - mint_request: BlindedMessages, + payload: PostMintRequest, payment_hash: Union[str, None] = None, ) -> Union[PostMintResponse, CashuError]: """ @@ -77,9 +78,7 @@ async def mint( Call this endpoint after `GET /mint`. """ try: - promises = await ledger.mint( - mint_request.blinded_messages, payment_hash=payment_hash - ) + promises = await ledger.mint(payload.outputs, payment_hash=payment_hash) blinded_signatures = PostMintResponse(promises=promises) return blinded_signatures except Exception as exc: @@ -124,8 +123,7 @@ async def split( proofs = payload.proofs amount = payload.amount - # NOTE: backwards compatibility with clients < v0.2.2 - outputs = payload.outputs.blinded_messages if payload.outputs else None + outputs = payload.outputs or None assert outputs, Exception("no outputs provided.") try: diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index 461481f..985a269 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -14,7 +14,6 @@ import cashu.core.b_dhke as b_dhke import cashu.core.bolt11 as bolt11 from cashu.core.base import ( BlindedMessage, - BlindedMessages, BlindedSignature, CheckFeesRequest, CheckRequest, @@ -23,6 +22,7 @@ from cashu.core.base import ( KeysetsResponse, MeltRequest, P2SHScript, + PostMintRequest, PostMintResponse, PostMintResponseLegacy, Proof, @@ -162,20 +162,20 @@ class LedgerAPI: @staticmethod def _construct_outputs(amounts: List[int], secrets: List[str]): """Takes a list of amounts and secrets and returns outputs. - Outputs are blinded messages `payloads` and blinding factors `rs`""" + Outputs are blinded messages `outputs` and blinding factors `rs`""" assert len(amounts) == len( secrets ), f"len(amounts)={len(amounts)} not equal to len(secrets)={len(secrets)}" - payloads: BlindedMessages = BlindedMessages() + outputs: List[BlindedMessage] = [] rs = [] for secret, amount in zip(secrets, amounts): B_, r = b_dhke.step1_alice(secret) rs.append(r) - payload: BlindedMessage = BlindedMessage( + output: BlindedMessage = BlindedMessage( amount=amount, B_=B_.serialize().hex() ) - payloads.blinded_messages.append(payload) - return payloads, rs + outputs.append(output) + return outputs, rs async def _check_used_secrets(self, secrets): for s in secrets: @@ -251,11 +251,12 @@ class LedgerAPI: """Mints new coins and returns a proof of promise.""" secrets = [self._generate_secret() for s in range(len(amounts))] await self._check_used_secrets(secrets) - payloads, rs = self._construct_outputs(amounts, secrets) + outputs, rs = self._construct_outputs(amounts, secrets) + outputs_payload = PostMintRequest(outputs=outputs) self.s = self._set_requests() resp = self.s.post( self.url + "/mint", - json=payloads.dict(), + json=outputs_payload.dict(), params={"payment_hash": payment_hash}, ) resp.raise_for_status() @@ -303,8 +304,8 @@ class LedgerAPI: amounts ), "number of secrets does not match number of outputs" await self._check_used_secrets(secrets) - payloads, rs = self._construct_outputs(amounts, secrets) - split_payload = SplitRequest(proofs=proofs, amount=amount, outputs=payloads) + outputs, rs = self._construct_outputs(amounts, secrets) + split_payload = SplitRequest(proofs=proofs, amount=amount, outputs=outputs) # construct payload def _splitrequest_include_fields(proofs): diff --git a/docs/specs/00.md b/docs/specs/00.md index 1d36d2e..efc23e8 100644 --- a/docs/specs/00.md +++ b/docs/specs/00.md @@ -20,12 +20,12 @@ Mint: `Bob` # Blind Diffie-Hellmann key exchange (BDHKE) - 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` +- `Alice` picks secret `x` and computes `Y = hash_to_curve(x)` +- `Alice` sends to `Bob`: `T = Y + rG` with `r` being a random nonce (**blinding**) +- `Bob` sends back to `Alice` blinded key: `Q = kT` (these two steps are the DH key exchange) (**signing**) +- `Alice` can calculate the unblinded key as `Q - rK = kY + krG - krG = kY = Z` (**unblinding**) - 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_curve(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` (**verification**), and if so treats it as a valid spend of a token, adding `x` to the list of spent secrets. ## 0.1 - Models diff --git a/docs/specs/02.md b/docs/specs/02.md index 57dbbf2..6ad2f2b 100644 --- a/docs/specs/02.md +++ b/docs/specs/02.md @@ -77,3 +77,23 @@ def derive_keyset_id(keys: Dict[int, PublicKey]): ).decode()[:12] ``` +[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 \ No newline at end of file diff --git a/docs/specs/03.md b/docs/specs/03.md index 0106358..f0c3f6c 100644 --- a/docs/specs/03.md +++ b/docs/specs/03.md @@ -27,4 +27,25 @@ Response of `Bob`: } ``` -with `pr` being the bolt11 payment request and `hash` the hash of the invoice. A wallet **MUST** store the `hash` and `amount_sat` in its database to later request the tokens upon paying the invoice. A wallet **SHOULD** then present the payment request (for example via QR code) to the user such that they can pay the invoice with another Lightning wallet. After the user has paid the invoice, a wallet **MUST** continue with #4 - Minting tokens [TODO: Link to #4]. \ No newline at end of file +with `pr` being the bolt11 payment request and `hash` the hash of the invoice. A wallet **MUST** store the `hash` and `amount_sat` in its database to later request the tokens upon paying the invoice. A wallet **SHOULD** then present the payment request (for example via QR code) to the user such that they can pay the invoice with another Lightning wallet. After the user has paid the invoice, a wallet **MUST** continue with #4 - Minting tokens [TODO: Link to #4]. + +[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 \ No newline at end of file diff --git a/docs/specs/04.md b/docs/specs/04.md index e1c3761..c9ec0db 100644 --- a/docs/specs/04.md +++ b/docs/specs/04.md @@ -12,25 +12,22 @@ Request of `Alice`: POST https://mint.host:3338/mint&payment_hash=67d1d9ea6ada225c115418671b64a ``` -With the data being of the form `BlindedMessages`: +With the json payload data being of the form `PostMintRequest`: ```json { - "blinded_messages": - [ - BlindedMessage, - ... - ] + "outputs": `BlindedMessages` } ``` +`BlindedMessages` is a list (array) of `BlindedMessage`s (see [NUT-0][00]). With curl: ```bash curl -X POST https://mint.host:3338/mint&payment_hash=67d1d9ea6ada225c115418671b64a -d \ { - "blinded_messages": + "outputs": [ { "amount": 2, @@ -39,7 +36,6 @@ curl -X POST https://mint.host:3338/mint&payment_hash=67d1d9ea6ada225c115418671b { "amount": 8, "B_": "03b54ab451b15005f2c64d38fc512fca695914c8fd5094ee044e5724ad41fda247" - } ] } @@ -47,7 +43,7 @@ curl -X POST https://mint.host:3338/mint&payment_hash=67d1d9ea6ada225c115418671b Response of `Bob`: -If the invoice was successfully paid, `Bob` responds with a `PostMintResponse` which is essentially a list of `BlindedSignature`'s [TODO: Link PostMintResponse] +If the invoice was successfully paid, `Bob` responds with a `PostMintResponse` which is a list of `BlindedSignature`'s (see [NUT-0][00]). ```json { @@ -72,7 +68,7 @@ If the invoice was not paid yet, `Bob` responds with an error. In that case, `Al ## Unblinding signatures -Upon receiving the `PostMintResponse` with the list of `BlindedSignature`'s from the mint `Bob`, a wallet `Alice` **MUST** then unblind the `BlindedSignature`'s from `Bob` (see #0 Notation [TODO: Link to unblinding]) to generate a list of `Proof`'s. A `Proof` is effectively an ecash `Token` and can later be used to redeem the token. The wallet **MUST** store the `Proof` in its database. +Upon receiving the `PostMintResponse` with the list of `BlindedSignature`'s from the mint `Bob`, a wallet `Alice` **MUST** then unblind the `BlindedSignature`'s from `Bob` (see BDHKE [NUT-0][00]) to generate a list of `Proof`'s. A `Proof` is effectively an ecash `Token` and can later be used to redeem the token. The wallet **MUST** store the `Proof` in its database. A list multiple `Proof`'s is called `Proofs` and has the form: @@ -91,4 +87,25 @@ A list multiple `Proof`'s is called `Proofs` and has the form: } ] } -``` \ No newline at end of file +``` + +[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 \ No newline at end of file diff --git a/docs/specs/05.md b/docs/specs/05.md index 70c1415..b6209ce 100644 --- a/docs/specs/05.md +++ b/docs/specs/05.md @@ -56,4 +56,25 @@ curl -X POST https://mint.host:3338/mint&payment_hash=67d1d9ea6ada225c115418671b } ``` -Only if the `paid==true`, the wallet `Alice` **MUST** delete the `Proofs` from her database (or move them to a history). If `paid==false`, `Alice` **CAN** repeat the same multiple times until the payment is successful. \ No newline at end of file +Only if the `paid==true`, the wallet `Alice` **MUST** delete the `Proofs` from her database (or move them to a history). If `paid==false`, `Alice` **CAN** repeat the same multiple times until the payment is successful. + +[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 \ No newline at end of file diff --git a/docs/specs/06.md b/docs/specs/06.md index 2ad3fe8..f728380 100644 --- a/docs/specs/06.md +++ b/docs/specs/06.md @@ -32,6 +32,8 @@ With the data being of the form `SplitRequest`: } ``` +`BlindedMessages` is a list (array) of `BlindedMessage`s (see [NUT-0][00]). + With curl: ```bash @@ -49,8 +51,7 @@ curl -X POST https://mint.host:3338/split -d \ ... } ], - "outputs":{ - "blinded_messages": + "outputs": [ { "amount": 2, @@ -59,10 +60,38 @@ curl -X POST https://mint.host:3338/split -d \ { ... } - ] - }, + ], "amount": 40 } ``` -If successful, `Bob` will respond \ No newline at end of file +If successful, `Bob` will respond with a `PostSplitResponse` + +```python +class PostSplitResponse(BaseModel): + fst: BlindedSignatures + snd: BlindedSignatures +``` + +`BlindedSignatures` is a list (array) of `BlindedSignature`s (see [NUT-0][00]). + +[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 \ No newline at end of file