Skip to content

Commit

Permalink
Merge branch 'cached-responses' of github.com:lollerfirst/nuts into c…
Browse files Browse the repository at this point in the history
…ached-responses
  • Loading branch information
lollerfirst committed Nov 24, 2024
2 parents df266f9 + e9fd20d commit c9ab361
Show file tree
Hide file tree
Showing 30 changed files with 904 additions and 287 deletions.
44 changes: 44 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Deploy MkDocs Site to GitHub Pages

on:
push:
branches:
- main

permissions: write-all

jobs:
build-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
ref: gh-pages-builder

- name: Copy contents to docs/
run: |
mkdir docs/
- name: Checkout main branch
uses: actions/checkout@v3
with:
ref: main
path: docs

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.x"

- name: Install Dependencies
run: pip install -r requirements.txt

- name: Build the Site
run: mkdocs build --clean

- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_branch: gh-pages
publish_dir: ./site
22 changes: 22 additions & 0 deletions .github/workflows/prettier.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: prettier

on: [push, pull_request]

jobs:
prettier:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Install dependencies
run: npm install -g prettier

- name: Run Prettier check
run: npx prettier --check .
39 changes: 21 additions & 18 deletions 00.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
NUT-00: Notation, Utilization, and Terminology
==========================
# NUT-00: Notation, Utilization, and Terminology

`mandatory`

Expand All @@ -11,8 +10,6 @@ This document details the notation and models used throughout the specification
- Receiving user: `Carol`
- Mint: `Bob`



# Blind Diffie-Hellmann key exchange (BDHKE)

## Variables
Expand All @@ -37,20 +34,21 @@ This document details the notation and models used throughout the specification
Deterministically maps a message to a public key point on the secp256k1 curve, utilizing a domain separator to ensure uniqueness.

`Y = PublicKey('02' || SHA256(msg_hash || counter))` where `msg_hash` is `SHA256(DOMAIN_SEPARATOR || x)`

- `Y` derived public key
- `DOMAIN_SEPARATOR` constant byte string `b"Secp256k1_HashToCurve_Cashu_"`
- `x` message to hash
- `counter` uint32 counter(byte order little endian) incremented from 0 until a point is found that lies on the curve

## Protocol

- Mint `Bob` publishes public key `K = kG`
- Mint `Bob` publishes public key `K = kG`
- `Alice` picks secret `x` and computes `Y = hash_to_curve(x)`
- `Alice` sends to `Bob`: `B_ = Y + rG` with `r` being a random blinding factor (**blinding**)
- `Bob` sends back to `Alice` blinded key: `C_ = kB_` (these two steps are the DH key exchange) (**signing**)
- `Alice` can calculate the unblinded key as `C_ - rK = kY + krG - krG = kY = C` (**unblinding**)
- Alice can take the pair `(x, C)` as a token and can send it to `Carol`.
- `Carol` can send `(x, C)` to `Bob` who then checks that `k*hash_to_curve(x) == C` (**verification**), and if so treats it as a valid spend of a token, adding `x` to the list of spent secrets.
- `Carol` can send `(x, C)` to `Bob` who then checks that `k*hash_to_curve(x) == C` (**verification**), and if so treats it as a valid spend of a token, adding `x` to the list of spent secrets.

## 0.1 - Models

Expand All @@ -66,7 +64,7 @@ An encrypted ("blinded") secret and an amount is sent from `Alice` to `Bob` for
}
```

`amount` is the value for the requested `BlindSignature`, `id` is the requested keyset ID from which we expect a signature, and `B_` is the blinded secret message generated by `Alice`. An array `[BlindedMessage]` is also referred to as `BlindedMessages`.
`amount` is the value for the requested `BlindSignature`, `id` is the requested keyset ID from which we expect a signature, and `B_` is the blinded secret message generated by `Alice`. An array `[BlindedMessage]` is also referred to as `BlindedMessages`.

### `BlindSignature`

Expand All @@ -84,11 +82,11 @@ A `BlindSignature` is sent from `Bob` to `Alice` after [minting tokens][04] or a

### `Proof`

A `Proof` is also called an input and is generated by `Alice` from a `BlindSignature` it received. An array `[Proof]` is called `Proofs`. `Alice` sends `Proofs` to `Bob` for [melting tokens][05]. [Serialized](#serialization-of-tokens) `Proofs` can also be sent from `Alice` to `Carol`. Upon receiving the token, `Carol` deserializes it and requests a [swap][03] from `Bob` to receive new `Proofs`.
A `Proof` is also called an input and is generated by `Alice` from a `BlindSignature` it received. An array `[Proof]` is called `Proofs`. `Alice` sends `Proofs` to `Bob` for [melting tokens][05]. [Serialized](#serialization-of-tokens) `Proofs` can also be sent from `Alice` to `Carol`. Upon receiving the token, `Carol` deserializes it and requests a [swap][03] from `Bob` to receive new `Proofs`.

```json
{
"amount": int,
"amount": int,
"id": hex_str,
"secret": str,
"C": hex_str,
Expand All @@ -100,6 +98,7 @@ A `Proof` is also called an input and is generated by `Alice` from a `BlindSigna
## 0.2 - Protocol

### Errors

In case of an error, mints respond with the HTTP status code `400` and include the following data in their response:

```json
Expand All @@ -108,6 +107,7 @@ In case of an error, mints respond with the HTTP status code `400` and include t
"code": 1337
}
```

Here, `detail` is the error message, and `code` is the error code. Error codes are to be defined in the documents concerning the use of a certain API endpoint.

## 0.3 - Methods
Expand All @@ -134,9 +134,10 @@ cashu:cashuAeyJwcm9vZn...

### V3 tokens

> *V3 tokens are deprecated and the use of the more space-efficient V4 tokens is encouraged.*
> _V3 tokens are deprecated and the use of the more space-efficient V4 tokens is encouraged._
##### Version

This token format has the `[version]` value `A`.

##### Format
Expand Down Expand Up @@ -166,12 +167,12 @@ The deserialized `base64_token_json` is
"memo": str <optional>
}
```
`mint` is the mint URL. The mint URL must be stripped of any trailing slashes (`/`). `Proofs` is an array of `Proof` objects. The next two elements are only for displaying the receiving user appropriate information: `unit` is the currency unit of the token keysets (see [Keysets][01] for supported units), and `memo` is an optional text memo from the sender.

`mint` is the mint URL. The mint URL must be stripped of any trailing slashes (`/`). `Proofs` is an array of `Proof` objects. The next two elements are only for displaying the receiving user appropriate information: `unit` is the currency unit of the token keysets (see [Keysets][01] for supported units), and `memo` is an optional text memo from the sender.

##### Example

Below is a TokenV3 JSON before `base64_urlsafe` serialization.
Below is a TokenV3 JSON before `base64_urlsafe` serialization.

```json
{
Expand Down Expand Up @@ -200,15 +201,17 @@ Below is a TokenV3 JSON before `base64_urlsafe` serialization.
```

When serialized, this becomes:

```
cashuAeyJ0b2tlbiI6W3sibWludCI6Imh0dHBzOi8vODMzMy5zcGFjZTozMzM4IiwicHJvb2ZzIjpbeyJhbW91bnQiOjIsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6IjQwNzkxNWJjMjEyYmU2MWE3N2UzZTZkMmFlYjRjNzI3OTgwYmRhNTFjZDA2YTZhZmMyOWUyODYxNzY4YTc4MzciLCJDIjoiMDJiYzkwOTc5OTdkODFhZmIyY2M3MzQ2YjVlNDM0NWE5MzQ2YmQyYTUwNmViNzk1ODU5OGE3MmYwY2Y4NTE2M2VhIn0seyJhbW91bnQiOjgsImlkIjoiMDA5YTFmMjkzMjUzZTQxZSIsInNlY3JldCI6ImZlMTUxMDkzMTRlNjFkNzc1NmIwZjhlZTBmMjNhNjI0YWNhYTNmNGUwNDJmNjE0MzNjNzI4YzcwNTdiOTMxYmUiLCJDIjoiMDI5ZThlNTA1MGI4OTBhN2Q2YzA5NjhkYjE2YmMxZDVkNWZhMDQwZWExZGUyODRmNmVjNjlkNjEyOTlmNjcxMDU5In1dfV0sInVuaXQiOiJzYXQiLCJtZW1vIjoiVGhhbmsgeW91LiJ9
```

### V4 tokens

V4 tokens are a space-efficient way of serializing tokens using the CBOR binary format. All field are single characters and hex strings are encoded in binary. V4 tokens can only hold proofs from a single mint.
V4 tokens are a space-efficient way of serializing tokens using the CBOR binary format. All field are single characters and hex strings are encoded in binary. V4 tokens can only hold proofs from a single mint.

##### Version

This token format has the `[version]` value `B`.

##### Format
Expand All @@ -227,7 +230,7 @@ The deserialized `base64_token_cbor` is a JSON of the same form as a TokenV4 but
{
"m": str, // mint URL
"u": str, // unit
"d": str <optional>, // memo
"d": str <optional>, // memo
"t": [
{
"i": bytes, // keyset ID
Expand All @@ -250,15 +253,16 @@ The deserialized `base64_token_cbor` is a JSON of the same form as a TokenV4 but
],
}
```
`m` is the mint URL. The mint URL must be stripped of any trailing slashes (`/`). `u` is the currency unit of the token keysets (see [Keysets][01] for supported units), and `d` is an optional text memo from the sender.

`m` is the mint URL. The mint URL must be stripped of any trailing slashes (`/`). `u` is the currency unit of the token keysets (see [Keysets][01] for supported units), and `d` is an optional text memo from the sender.

`i` is the [short keyset ID][02] of the proofs in `p`, which is an array of `Proof` objects without the `id` field. We extracted the keyset ID `id` from each proof and grouped all proofs by their keyset ID `i` one level above (in `p`).

Note that all fields of the `bytes` type encode hex strings in the original representation of `Proof`'s.
Note that all fields of the `bytes` type encode hex strings in the original representation of `Proof`'s.

##### Example

Below is a TokenV4 JSON before CBOR and `base64_urlsafe` serialization.
Below is a TokenV4 JSON before CBOR and `base64_urlsafe` serialization.

```json
{
Expand Down Expand Up @@ -302,7 +306,6 @@ We serialize this JSON using CBOR which can be seen [here](https://cbor.nemo157.
cashuBo2F0gqJhaUgA_9SLj17PgGFwgaNhYQFhc3hAYWNjMTI0MzVlN2I4NDg0YzNjZjE4NTAxNDkyMThhZjkwZjcxNmE1MmJmNGE1ZWQzNDdlNDhlY2MxM2Y3NzM4OGFjWCECRFODGd5IXVW-07KaZCvuWHk3WrnnpiDhHki6SCQh88-iYWlIAK0mjE0fWCZhcIKjYWECYXN4QDEzMjNkM2Q0NzA3YTU4YWQyZTIzYWRhNGU5ZjFmNDlmNWE1YjRhYzdiNzA4ZWIwZDYxZjczOGY0ODMwN2U4ZWVhY1ghAjRWqhENhLSsdHrr2Cw7AFrKUL9Ffr1XN6RBT6w659lNo2FhAWFzeEA1NmJjYmNiYjdjYzY0MDZiM2ZhNWQ1N2QyMTc0ZjRlZmY4YjQ0MDJiMTc2OTI2ZDNhNTdkM2MzZGNiYjU5ZDU3YWNYIQJzEpxXGeWZN5qXSmJjY8MzxWyvwObQGr5G1YCCgHicY2FtdWh0dHA6Ly9sb2NhbGhvc3Q6MzMzOGF1Y3NhdA
```


[00]: 00.md
[01]: 01.md
[02]: 02.md
Expand Down
9 changes: 4 additions & 5 deletions 01.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
NUT-01: Mint public key exchange
==========================
# NUT-01: Mint public key exchange

`mandatory`
`mandatory`

---

This document outlines the exchange of the public keys of the mint `Bob` with the wallet user `Alice`. `Alice` uses the keys to unblind `Bob`'s blind signatures (see [NUT-00][00]).

## Description

Wallet user `Alice` receives public keys from mint `Bob` via `GET /v1/keys`. The set of all public keys for a set of amounts is called a *keyset*.
Wallet user `Alice` receives public keys from mint `Bob` via `GET /v1/keys`. The set of all public keys for a set of amounts is called a _keyset_.

The mint responds only with its `active` keysets. Keyset are `active` if the mint will sign promises with it. The mint will accept tokens from inactive keysets as inputs but will not sign with them for new outputs. The `active` keysets can change over time, for example due to key rotation. A list of all keysets, active and inactive, can be requested separately (see [NUT-02][02]).

Note that a mint can support multiple keysets at the same time but will only respond with the active keysets on the endpoint `GET /v1/keys`. A wallet can ask for the keys of a specific (active or inactive) keyset via the endpoint `GET /v1/keys/{keyset_id}` (see [NUT-02][02]).


## Keyset generation

Keysets are generated by the mint. The mint is free to use any key generation method they like. Each keyset is identified by its keyset `id` which can be computed by anyone from its public keys (see [NUT-02][02]).
Expand Down Expand Up @@ -54,6 +52,7 @@ Response `GetKeysResponse` of `Bob`:
```

## Example response

```json
{
"keysets": [
Expand Down
5 changes: 2 additions & 3 deletions 02.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ def fees(inputs: List[Proof]) -> int:
sum_fees += keysets[proof.id].input_fee_ppk
return (sum_fees + 999) // 1000
```
Here, the `//` operator in `(sum_fees + 999) // 1000` denotes an integer division operator (aka floor division operator) that rounds down `sum_fees + 999` to the next lower integer. Alternatively, we could round up the sum using a floating point division with `ceil(sum_fees / 1000)` although it is not recommended to do so due to the non-deterministic behavior of floating point division.

Notice that since transactions can spend inputs from different keysets, the sum considers the fee for each `Proof` indexed by the keyset ID individually.
Here, the `//` operator in `(sum_fees + 999) // 1000` denotes an integer division operator (aka floor division operator) that rounds down `sum_fees + 999` to the next lower integer. Alternatively, we could round up the sum using a floating point division with `ceil(sum_fees / 1000)` although it is not recommended to do so due to the non-deterministic behavior of floating point division.

Notice that since transactions can spend inputs from different keysets, the sum considers the fee for each `Proof` indexed by the keyset ID individually.

### Deriving the keyset ID

Expand Down Expand Up @@ -146,7 +146,6 @@ Here, `id` is the keyset ID, `unit` is the unit string (e.g. "sat") of the keyse
}
```


## Requesting public keys for a specific keyset

To receive the public keys of a specific keyset, a wallet can call the `GET /v1/keys/{keyset_id}` endpoint where `keyset_id` is a keyset ID or a short keyset ID.
Expand Down
18 changes: 11 additions & 7 deletions 03.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
NUT-03: Swap tokens
==========================
# NUT-03: Swap tokens

`mandatory`

---

The swap operation is the most important component of the Cashu system. A swap operation consists of multiple inputs (`Proofs`) and outputs (`BlindedMessages`). Mints verify and invalidate the inputs and issue new promises (`BlindSignatures`). These are then used by the wallet to generate new `Proofs` (see [NUT-00][00]).

The swap operation can serve multiple use cases. The first use case is that `Alice` can use it to split her 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 second one is that `Carols`'s wallet can use it to receive tokens from `Alice` by sending them as inputs to the mint and receive new outputs in return.
The swap operation can serve multiple use cases. The first use case is that `Alice` can use it to split her 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 second one is that `Carols`'s wallet can use it to receive tokens from `Alice` by sending them as inputs to the mint and receive new outputs in return.

## Swap to send

Expand All @@ -17,7 +16,6 @@ To make this more clear, we present an example of a typical case of sending toke

Note: In order to preserve privacy around the amount that a client might want to send to another user and keep the rest as change, the client **SHOULD** ensure that the list requested outputs is ordered by amount in ascending order. As an example of what to avoid, a request for outputs expressed like so: `[16, 8, 2, 64, 8]` might imply the client is preparing a payment for 26 sat; the client should instead order the list like so: `[2, 8, 8, 16, 64]` to mitigate this privacy leak to the mint.


## Swap to receive

Another useful case for the swap operation follows up the example above where `Alice` has swapped her `Proofs` ready to be sent to `Carol`. `Carol` can receive these `Proofs` using the same operation by using them as inputs to invalidate them and request new outputs from `Bob`. Only if `Carol` has redeemed new outputs, `Alice` can't double-spend the `Proofs` anymore and the transaction is settled. To continue our example, `Carol` requests a swap with input `Proofs` worth `[32, 8]` to receive new outputs (of an arbitrary distribution) with the same total amount.
Expand All @@ -44,7 +42,7 @@ With curl:
```bash
curl -X POST https://mint.host:3338/v1/swap -d \
{
"inputs":
"inputs":
[
{
"amount": 2,
Expand All @@ -59,7 +57,7 @@ curl -X POST https://mint.host:3338/v1/swap -d \
"outputs":
[
{
"amount": 2,
"amount": 2,
"id": "009a1f293253e41e",
"B_": "02634a2c2b34bec9e8a4aba4361f6bf202d7fa2365379b0840afe249a7a9d71239"
},
Expand All @@ -70,14 +68,20 @@ curl -X POST https://mint.host:3338/v1/swap -d \
}
```

If successful, `Bob` will respond with a `PostSwapResponse`
If successful, `Bob` will respond with a `PostSwapResponse`

```json
{
"signatures": <Array[BlindSignature]>
}
```

## Cached Responses

`Bob` **MUST** cache every successful `PostSwapResponse` using `PostSwapRequest` as the key.
Each cache entry has a TTL (Time-To-Live) of at least 1800 seconds.
In the event of a network error, `Alice` **CAN** replay the same exact request and get the same response.

[00]: 00.md
[01]: 01.md
[02]: 02.md
Expand Down
Loading

0 comments on commit c9ab361

Please sign in to comment.