BlindedMessages is now List[BlindedMessage] (no blinded_messages field) and PostMintRequest (new) is now with outputs field

This commit is contained in:
callebtc
2023-01-14 21:23:06 +01:00
parent f0f12a442c
commit 3cbdebf5a5
9 changed files with 171 additions and 67 deletions

View File

@@ -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):

View File

@@ -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:

View File

@@ -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):

View File

@@ -21,11 +21,11 @@ Mint: `Bob`
- 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` 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

View File

@@ -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

View File

@@ -28,3 +28,24 @@ 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].
[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

View File

@@ -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:
@@ -92,3 +88,24 @@ A list multiple `Proof`'s is called `Proofs` and has the form:
]
}
```
[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

View File

@@ -57,3 +57,24 @@ 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.
[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

View File

@@ -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
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