mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-22 03:24:18 +01:00
add spec wip
This commit is contained in:
91
docs/specs/wip/0 - Notation.md
Normal file
91
docs/specs/wip/0 - Notation.md
Normal file
@@ -0,0 +1,91 @@
|
||||
Sending user: `Alice`
|
||||
Receiving user: `Carol`
|
||||
Mint: `Bob`
|
||||
|
||||
## Bob (mint)
|
||||
- `k` private key of mint (one for each amount)
|
||||
- `K` public key of mint
|
||||
- `Q` promise (blinded signature)
|
||||
|
||||
## Alice (user)
|
||||
- `x` random string (secret message), corresponds to point `Y` on curve
|
||||
- `r` private key (blinding factor)
|
||||
- `T` blinded message
|
||||
- `Z` proof (unblinded signature)
|
||||
|
||||
# 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 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.
|
||||
|
||||
## 0.1 - Models
|
||||
|
||||
### `BlindedMessage`
|
||||
A encrypted ("blinded") secret and an amount sent from `Alice` to `Bob`.
|
||||
|
||||
```json
|
||||
{
|
||||
"amount": int,
|
||||
"B_": str
|
||||
}
|
||||
```
|
||||
|
||||
### `BlindedSignature`
|
||||
A signature on the `BlindedMessage` sent from `Bob` to `Alice`.
|
||||
|
||||
```json
|
||||
{
|
||||
"amount": int,
|
||||
"C_": str,
|
||||
"id": str | None
|
||||
}
|
||||
```
|
||||
|
||||
### `Proof`
|
||||
A `Proof` is also called a `Token` and has the following form:
|
||||
|
||||
```json
|
||||
{
|
||||
"amount": int,
|
||||
"secret": str,
|
||||
"C": str,
|
||||
"id": None | str,
|
||||
"script": P2SHScript | None,
|
||||
}
|
||||
```
|
||||
|
||||
### `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])
|
||||
|
||||
## 0.2 - Methods
|
||||
|
||||
### Serialization of `Proofs`
|
||||
To send and receive `Proofs`, wallets serialize them in a `base64_urlsafe` format.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "DSAl9nvvyfva",
|
||||
"amount": 8,
|
||||
"secret": "DbRKIya0etdwI5sFAN0AXQ",
|
||||
"C": "02df7f2fc29631b71a1db11c163b0b1cb40444aa2b3d253d43b68d77a72ed2d625"
|
||||
},
|
||||
{
|
||||
"id": "DSAl9nvvyfva",
|
||||
"amount": 16,
|
||||
"secret": "d_PPc5KpuAB2M60WYAW5-Q",
|
||||
"C": "0270e0a37f7a0b21eab43af751dd3c03f61f04c626c0448f603f1d1f5ae5a7d7e6"
|
||||
}
|
||||
```
|
||||
|
||||
becomes
|
||||
|
||||
```
|
||||
W3siaWQiOiAiRFNBbDludnZ5ZnZhIiwgImFtb3VudCI6IDgsICJzZWNyZXQiOiAiRGJSS0l5YTBldGR3STVzRkFOMEFYUSIsICJDIjogIjAyZGY3ZjJmYzI5NjMxYjcxYTFkYjExYzE2M2IwYjFjYjQwNDQ0YWEyYjNkMjUzZDQzYjY4ZDc3YTcyZWQyZDYyNSJ9LCB7ImlkIjogIkRTQWw5bnZ2eWZ2YSIsICJhbW91bnQiOiAxNiwgInNlY3JldCI6ICJkX1BQYzVLcHVBQjJNNjBXWUFXNS1RIiwgIkMiOiAiMDI3MGUwYTM3ZjdhMGIyMWVhYjQzYWY3NTFkZDNjMDNmNjFmMDRjNjI2YzA0NDhmNjAzZjFkMWY1YWU1YTdkN2U2In1d
|
||||
```
|
||||
32
docs/specs/wip/1 - Request public keys from mint.md
Normal file
32
docs/specs/wip/1 - Request public keys from mint.md
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
`Alice` receives public keys from mint `Bob` via `GET /keys` and stores them in a key-value store like a dictionary.
|
||||
|
||||
`Bob` responds with his **active** keyset [TODO: Link #2]. Note that a mint can support multiple keysets at the same time but will only respond with the active keyset. See [TODO: Link #2] for how a wallet deals with multiple keysets.
|
||||
|
||||
Keysets are received as a JSON of the form `{<amount_1> : <mint_pubkey_1>, <amount_2> : ...}` for each `<amount_i>` of the amounts the mint `Bob` supports and the corresponding public key `<mint_pubkey_1>`, that is `K_i` (see #0).
|
||||
|
||||
## Example
|
||||
|
||||
Request of `Alice`:
|
||||
|
||||
```http
|
||||
GET https://mint.host:3338/keys
|
||||
```
|
||||
|
||||
With curl:
|
||||
|
||||
```bash
|
||||
curl -X GET https://mint.host:3338/keys
|
||||
```
|
||||
|
||||
Response of `Bob`:
|
||||
|
||||
```json
|
||||
{
|
||||
"1": "03a40f20667ed53513075dc51e715ff2046cad64eb68960632269ba7f0210e38bc",
|
||||
"2": "03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de",
|
||||
"4": "02648eccfa4c026960966276fa5a4cae46ce0fd432211a4f449bf84f13aa5f8303",
|
||||
"8": "02fdfd6796bfeac490cbee12f778f867f0a2c68f6508d17c649759ea0dc3547528",
|
||||
...
|
||||
}
|
||||
```
|
||||
76
docs/specs/wip/2 - Keysets and keyset IDs.md
Normal file
76
docs/specs/wip/2 - Keysets and keyset IDs.md
Normal file
@@ -0,0 +1,76 @@
|
||||
A keyset is a set of public keys that the mint `Bob` generates and shares with its users. It refers to the set of public keys that each correspond to the amount values that the mint supports (e.g. 1, 2, 4, 8, ...) respectively.
|
||||
|
||||
## Requesting mint keyset IDs
|
||||
|
||||
A mint can have multiple active keysets at the same time but **MUST** have only one active keyset. A wallet can ask the mint for all active keyset IDs via the `GET /keysets` endpoint. A wallet **CAN** request the list of active keysets from the mint upon startup and, if it does so, **MUST** choose only tokens from its database that have a keyset ID supported by the mint to interact with it.
|
||||
|
||||
This is useful in the case a wallet interacts with multiple mints. That way, a wallet always knows which tokens it can use with the mint it is currently interacting with.
|
||||
|
||||
## Example
|
||||
|
||||
Request of `Alice`:
|
||||
|
||||
```http
|
||||
GET https://mint.host:3338/keysets
|
||||
```
|
||||
|
||||
With curl:
|
||||
|
||||
```bash
|
||||
curl -X GET https://mint.host:3338/keysets
|
||||
```
|
||||
|
||||
Response of `Bob`:
|
||||
|
||||
```json
|
||||
{
|
||||
"keysets": [
|
||||
"DSAl9nvvyfva",
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 2.1 - Generating a keyset
|
||||
|
||||
A keyset is generated by the mint by a single seed `s` from which the private keys `k_i` are derived, with `i` being the index of the amount value. The derivation for the elements `k_i` goes like this:
|
||||
|
||||
```python
|
||||
for i in range(MAX_ORDER):
|
||||
k_i = HASH_SHA256(s + D + i)[:32]
|
||||
```
|
||||
|
||||
Here, `MAX_ORDER` refers to the order of the maximum token value that the mint supports, i.e., `2^MAX_ORDER`. Typically, `MAX_ORDER = 64`. `D` refers to a derivation path that is chosen by the mint to rotate keys. `i` is the string representation of the index of the amount value.
|
||||
|
||||
## 2.2 - Keyset ID
|
||||
|
||||
A keyset ID is an identifier for a specific keyset. It can be derived by anyone who knows the set of public keys of a mint. The keyset ID **CAN** be stored in a Cashu token [TODO: Link to definition of token] if it was generated by a mint corresponding to the keyset.
|
||||
|
||||
A wallet can use the keyset ID in a token to recognize a mint it was issued by. For example, a wallet might store the `MINT_URL` together with the `keyset_id` in its database the first time it receives a keyset from that mint. That way, a wallet can know which mint to contact when it receives a token with a `keyset_id` of a mint that it has interacted with before.
|
||||
|
||||
### 2.2.1 - Deriving the keyset ID
|
||||
|
||||
The mint and its users can derive a keyset ID from the keyset of the mint. To derive the keyset ID of a mint, execute the following steps:
|
||||
|
||||
```
|
||||
1 - sort keyset by amount
|
||||
2 - concatenate all (sorted) public keys to one string
|
||||
3 - HASH_SHA256 the concatenated public keys
|
||||
4 - take the first 12 characters of the hash
|
||||
```
|
||||
|
||||
An example implementation in Python:
|
||||
|
||||
```python
|
||||
def derive_keyset_id(keys: Dict[int, PublicKey]):
|
||||
"""Deterministic derivation keyset_id from set of public keys."""
|
||||
sorted_keys = dict(sorted(keys.items()))
|
||||
pubkeys_concat = "".join([p.serialize().hex() for _, p in sorted_keys.items()])
|
||||
return base64.b64encode(
|
||||
hashlib.sha256((pubkeys_concat).encode("utf-8")).digest()
|
||||
).decode()[:12]
|
||||
```
|
||||
|
||||
### 2.2.2 - Storing the keyset ID in a token
|
||||
|
||||
A mint **CAN** add the keyset ID to a `BlindedSignature` during the minting process [TODO: link to blinded signature. TODO: link to /mint]. If a wallet receives a `BlindedSignature` with a keyset ID,
|
||||
28
docs/specs/wip/3 - Request minting.md
Normal file
28
docs/specs/wip/3 - Request minting.md
Normal file
@@ -0,0 +1,28 @@
|
||||
Minting tokens is a two-step process: requesting a mint and minting the tokens. Here, we describe the first step. A wallet requests the minting of tokens in exchange for paying a bolt11 Lightning invoice (typically generated by the mint to add funds to its reserves, and typically paid with another Lightning wallet).
|
||||
|
||||
To request the minting of tokens, a wallet `Alice` sends a `GET /mint&amount=<amount_sat>` request with the requested amount `<amount_sat>` in satoshis. The mint `Bob` then responds with a Lightning invoice.
|
||||
|
||||
## Example
|
||||
|
||||
Request of `Alice`:
|
||||
|
||||
```http
|
||||
GET https://mint.host:3338/mint&amount=1000
|
||||
```
|
||||
|
||||
With curl:
|
||||
|
||||
```bash
|
||||
curl -X GET https://mint.host:3338/mint&amount=1000
|
||||
```
|
||||
|
||||
Response of `Bob`:
|
||||
|
||||
```json
|
||||
{
|
||||
"pr": "lnbc100n1p3kdrv5sp5lpdxzghe5j67q...",
|
||||
"hash": "67d1d9ea6ada225c115418671b64a..."
|
||||
}
|
||||
```
|
||||
|
||||
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].
|
||||
92
docs/specs/wip/4 - Minting tokens.md
Normal file
92
docs/specs/wip/4 - Minting tokens.md
Normal file
@@ -0,0 +1,92 @@
|
||||
After requesting a mint (see #3 [TODO: Link]) and paying the invoice that was returned by the mint, a wallet proceeds with requesting tokens from the mint in return for paying the invoice.
|
||||
|
||||
For that, a wallet sends a `POST /mint&payment_hash=<hash>` request with a JSON body to the mint. The body **MUST** include `BlindedMessages` that are worth a maximum of `<amount_sat>` [TODO: Refer to BlindedMessages]. If successful (i.e. the invoice has been previously paid and the `BlindedMessages` are valid), the mint responds with `Promises` [TODO: Link Promises].
|
||||
|
||||
## Example
|
||||
|
||||
Request of `Alice`:
|
||||
|
||||
```http
|
||||
POST https://mint.host:3338/mint&payment_hash=67d1d9ea6ada225c115418671b64a
|
||||
```
|
||||
|
||||
With the data being of the form `MintRequest`:
|
||||
|
||||
```json
|
||||
{
|
||||
"blinded_messages":
|
||||
[
|
||||
BlindedMessage,
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
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"
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
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]
|
||||
|
||||
```json
|
||||
{
|
||||
"promises":
|
||||
[
|
||||
{
|
||||
"id": "DSAl9nvvyfva",
|
||||
"amount": 2,
|
||||
"C_": "03e61daa438fc7bcc53f6920ec6c8c357c24094fb04c1fc60e2606df4910b21ffb"
|
||||
},
|
||||
{
|
||||
"id": "DSAl9nvvyfva",
|
||||
"amount": 8,
|
||||
"C_": "03fd4ce5a16b65576145949e6f99f445f8249fee17c606b688b504a849cdc452de"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
If the invoice was not paid yet, `Bob` responds with an error. In that case, `Alice` **CAN** repeat the same response until the Lightning invoice is settled.
|
||||
|
||||
## 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.
|
||||
|
||||
A list multiple `Proof`'s is called `Proofs` and has the form:
|
||||
|
||||
```json
|
||||
{
|
||||
"proofs" :
|
||||
[
|
||||
{
|
||||
"id": "DSAl9nvvyfva",
|
||||
"amount": 2,
|
||||
"secret": "S+tDfc1Lfsrb06zaRdVTed6Izg",
|
||||
"C": "0242b0fb43804d8ba9a64ceef249ad7a60f42c15fe6d4907238b05e857527832a3"
|
||||
},
|
||||
{
|
||||
...
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
57
docs/specs/wip/5 - Melting tokens.md
Normal file
57
docs/specs/wip/5 - Melting tokens.md
Normal file
@@ -0,0 +1,57 @@
|
||||
Melting tokens is the opposite of minting them (see #4): the wallet `Alice` sends `Proofs` to the mint `Bob` together with a bolt11 Lightning invoice that `Alice` wants to be paid. To melt tokens, `Alice` sends a `POST /melt` request with a JSON body to the mint. The `Proofs` included in the request will be burned by the mint and the mint will pay the invoice in exchange.
|
||||
|
||||
`Alice`'s request **MUST** include a `MeltRequest` ([TODO: Link MeltRequest]) JSON body with `Proofs` that have at least the amount of the invoice to be paid.
|
||||
|
||||
## Example
|
||||
|
||||
**Request** of `Alice`:
|
||||
|
||||
```http
|
||||
POST https://mint.host:3338/melt
|
||||
```
|
||||
|
||||
With the data being of the form `MeltRequest`:
|
||||
|
||||
```json
|
||||
{
|
||||
"proofs":
|
||||
[
|
||||
Proof,
|
||||
...
|
||||
],
|
||||
"invoice": str
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
With curl:
|
||||
|
||||
```bash
|
||||
curl -X POST https://mint.host:3338/mint&payment_hash=67d1d9ea6ada225c115418671b64a -d \
|
||||
{
|
||||
"proofs" :
|
||||
[
|
||||
{
|
||||
"id": "DSAl9nvvyfva",
|
||||
"amount": 2,
|
||||
"secret": "S+tDfc1Lfsrb06zaRdVTed6Izg",
|
||||
"C": "0242b0fb43804d8ba9a64ceef249ad7a60f42c15fe6d4907238b05e857527832a3"
|
||||
},
|
||||
{
|
||||
...
|
||||
}
|
||||
],
|
||||
"invoice": "lnbc100n1p3kdrv5sp5lpdxzghe5j67q..."
|
||||
}
|
||||
```
|
||||
|
||||
**Response** `PostMeltResponse` from `Bob`:
|
||||
|
||||
```json
|
||||
{
|
||||
"paid": true,
|
||||
"preimage": "da225c115418671b64a67d1d9ea6a..."
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
66
docs/specs/wip/6 - Split.md
Normal file
66
docs/specs/wip/6 - Split.md
Normal file
@@ -0,0 +1,66 @@
|
||||
The split operation is the most important component of the Cashu system. The wallet `Alice` can use it to redeem tokens (i.e. receive new ones in return) that she received from `Carol`, or she can split her own tokens to a target amount she needs to send to `Carol`, if she does not have the necessary amounts to compose the target amount in her wallet already.
|
||||
|
||||
The basic idea is that `Alice` sends `Bob` a set of `Proof`'s and a set of `BlindedMessage`'s with an equal amount. Additionally, she specifies the `amount` at which she would like to have the split.
|
||||
|
||||
## 6.1 - Split to send
|
||||
|
||||
To make this more clear, we make an example of a typical case of sending tokens from `Alice` to `Carol`:
|
||||
|
||||
`Alice` has 64 satoshis in her wallet, composed of two tokens, one worth 32 sats and another two worth 16 sats. She wants to send `Carol` 40 sats but does not have the necessary tokens to combine them to reach the exact target amount of 40 sats. `Alice` requests a split from the mint. For that, she sends the mint `Bob` her tokens (`Proofs`) worth `[32, 16, 16]` and asks for a split at amount 40. The mint will then return her new tokens with the amounts `[32, 8, 16, 8]`. Notice that the first two tokens can now be combined to 40 sats. The original tokens that `Alice` sent to `Bob` are now invalidated.
|
||||
|
||||
## 6.2 - Split to receive
|
||||
|
||||
Another case of how split can be useful becomes apparent if we follow up the example above where `Alice` split her tokens ready to be sent to `Carol`. `Carol` can receive these tokens, which means to invalidate the tokens she receives and redeem them for new ones, using the same mechanism. Only if `Carol` redeems them for new tokens that only she can spend, `Alice` can't double-spend them anymore and this simple transaction can be considered settled. `Carol` requests a split of the tokens (`Proofs`) worth `[32, 8]` at the amount `40` (the total amount) to receive back new tokens with the same total amount.
|
||||
|
||||
## Example
|
||||
|
||||
**Request** of `Alice`:
|
||||
|
||||
```http
|
||||
POST https://mint.host:3338/split
|
||||
```
|
||||
|
||||
With the data being of the form `SplitRequest`:
|
||||
|
||||
```json
|
||||
{
|
||||
"proofs": Proofs,
|
||||
"outputs": MintRequest,
|
||||
"amount": int
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
If successful, `Bob` will respond
|
||||
Reference in New Issue
Block a user