Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fees to NUT-02 #126

Merged
merged 8 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 64 additions & 25 deletions 02.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,53 @@
NUT-02: Keysets and keyset ID
==========================
# NUT-02: Keysets and fees

`mandatory`

---

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.

Each keyset additionally indicates its keyset `id`, the currency `unit`, and whether the keyset is `active` or not.
Each keyset indicates its keyset `id`, the currency `unit`, whether the keyset is `active`, and an `input_fee_ppk` that determines the fees for spending ecash from this keyset.

## Multiple keysets
A mint can have multiple keysets at the same time. For example, it could have one keyset for each currency `unit` that it supports. Wallets should support multiple keysets. They must respect the `active` and the `input_fee_ppk` properties of the keysets they use.

#### Active keysets
Mints can have multiple keysets at the same time but **MUST** have at least one `active` keyset (see [NUT-01][01]). `Proofs` from inactive keysets are still accepted but new outputs (`BlindedMessages` and `BlindSignatures`) **MUST** be redeemed from `active` keysets only.
## Keyset properties

#### Currency unit
Mints **MUST** generate a keysets for each `unit` they support.
### Active keysets

#### Wallet input and output construction
Mints can have multiple keysets at the same time but **MUST** have at least one `active` keyset (see [NUT-01][01]). The `active` property determines whether the mint allows generating new ecash from this keyset. `Proofs` from inactive keysets with `active=false` are still accepted as inputs but new outputs (`BlindedMessages` and `BlindSignatures`) **MUST** be from `active` keysets only.

Wallets **SHOULD** store keysets the first time they encounter them along with the URL of the mint they are from. That way, wallets can choose `Proofs` of all keysets of a mint from their database when selecting inputs for an operation. Wallets **SHOULD** spend `Proofs` of inactive keysets first. When constructing outputs for an operation, wallets **MUST** choose only `active` keysets (see [NUT-00][00]).
To rotate keysets, a mint can generate a new active keyset and inactive an old one. If the `active` flag of an old keyset is set to `false`, no new ecash from this keyset can be generated and the outstanding ecash supply of that keyset can be taken out of circulation as wallets rotate their ecash to active keysets.

Wallets **SHOULD** prioritize swaps with `Proofs` from inactive keysets (see [NUT-03][03]) so they can quickly get rid of them. Wallets **CAN** swap their entire balance from an inactive keyset to an active one as soon as they detect that the keyset was inactivated. When constructing outputs for a transaction, wallets **MUST** choose only `active` keysets (see [NUT-00][00]).

### Fees

Keysets indicate the fee `input_fee_ppk` that is charged when a `Proof` of that keyset is spent as an input to a transaction. The fee is given in parts per thousand (ppk) per input measured in the `unit` of the keyset. The total fee for a transaction is the sum of all fees per input rounded up to the next larger integer (that that can be represented with the keyest).

As an example, we construct a transaction spending 3 inputs (`Proofs`) from a keyset with unit `sat` and `input_fee_ppk` of `100`. A fee of `100 ppk` means `0.1 sat` per input. The sum of the individual fees are 300 ppk for this transaction. Rounded up to the next smallest denomination, the mint charges `1 sat` in total fees, i.e. `fees = ceil(0.3) == 1`. In this case, the fees for spending 1-10 inputs is 1 sat, 11-20 inputs is 2 sat and so on.

#### Wallet transaction construction

When constructing a transaction with ecash `inputs` (example: `/v1/swap` or `/v1/melt`), wallets **MUST** add fees to the inputs or, vice versa, subtract from the outputs. The mint checks the following equation:

callebtc marked this conversation as resolved.
Show resolved Hide resolved
```python
sum(inputs) - fees == sum(outputs)
```

Here, `sum(inputs)` and `sum(outputs)` mean the sum of the amounts of the inputs and outputs respectively. `fees` is calculated from the sum of each input's fee and rounded up to the next larger integer:

```python
def fees(inputs: List[Proof]) -> int:
sum_fees = 0
for proof in inputs:
sum_fees += keysets[proof.id].input_fee_ppk

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still missing proof.amount here:

Suggested change
sum_fees += keysets[proof.id].input_fee_ppk
sum_fees += proof.amount * keysets[proof.id].input_fee_ppk

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still missing proof.amount here:

The fees are not amount-dependent.

return ceil(sum_fees / 1000)
callebtc marked this conversation as resolved.
Show resolved Hide resolved
```
Notice that since transactions spending inputs from different keysets are allowed, the sum considers the fee for each `Proof` indexed by the keyset ID individually.

## Example

A wallet can ask the mint for a list of all keyset IDs via the `GET /v1/keysets` endpoint.
A wallet can ask the mint for a list of all keysets via the `GET /v1/keysets` endpoint.

Request of `Alice`:

Expand All @@ -43,14 +67,18 @@ Response `GetKeysetsResponse` of `Bob`:
{
"keysets": [
{
"id": <keyset_id_hex_str>,
"unit": <currency_unit_str>,
"active": <bool>
"id": <hex_str>,
"unit": <str>,
"active": <bool>,
"input_fee_ppk": <int|null>,
},
...
]
}
```

Here, `id` is the keyset ID, `unit` is the unit string (e.g. "sat") of the keyset, `active` indicates whether new ecash can be minted with this keyset, and `input_fee_ppk` is the fee (per thousand units) to spend one input spent from this keyset. If `input_fee_ppk` is not given, we assume it to be `0`.

## Example response

```json
Expand All @@ -59,26 +87,25 @@ Response `GetKeysetsResponse` of `Bob`:
{
"id": "009a1f293253e41e",
"unit": "sat",
"active": True
"active": True,
"input_fee_ppk": 100
},
{
"id": "0042ade98b2a370a",
"unit": "sat",
"active": False
"active": False,
"input_fee_ppk": 100
},
{
"id": "00c074b96c7e2b0e",
"unit": "usd",
"active": True
}
"active": True,
"input_fee_ppk": 100
}
]
}
```

#### Wallet implementation notes
Wallets can request the list of keyset IDs from the mint upon startup and load only tokens from its database that have a keyset ID supported by the mint it interacts with. This also helps wallets to determine whether the mint has rotated to a new current keyset (i.e. added new active keysets and inactivated old ones) and whether the wallet should recycle all tokens from inactive keysets to currently `active` ones.


## 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. Wallets **CAN** compute the keyset `id` for a given keyset by themselves to confirm that the mint is supplying the correct keyset ID.
Expand All @@ -87,7 +114,7 @@ The keyset `id` is in each `Proof` so it can be used by wallets to identify whic

### Keyset ID version

Keyset IDs have a version byte (two hexadecimal characters). The currently used version byte is `00`.
Keyset IDs have a version byte (two hexadecimal characters). The currently used version byte is `00`.

### Deriving the keyset ID

Expand All @@ -111,7 +138,8 @@ def derive_keyset_id(keys: Dict[int, PublicKey]) -> str:
```

## 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 the keyset ID.

To receive the public keys of a specific keyset, a wallet can call the `GET /v1/keys/{keyset_id}` endpoint where `keyset_id` is the keyset ID.

## Example

Expand Down Expand Up @@ -142,12 +170,23 @@ Response of `Bob` (same as [NUT-01][01]):
"4": "0366be6e026e42852498efb82014ca91e89da2e7a5bd3761bdad699fa2aec9fe09",
"8": "0253de5237f189606f29d8a690ea719f74d65f617bb1cb6fbea34f2bc4f930016d",
...
}
},
}, ...
]
}
```

## Wallet implementation notes

Wallets can request the list of keyset IDs from the mint upon startup and load only tokens from its database that have a keyset ID supported by the mint it interacts with. This also helps wallets to determine whether the mint as added a new current keyset or whether it has changed the `active` flag of an existing one.
callebtc marked this conversation as resolved.
Show resolved Hide resolved

A useful flow is:

- If we don't have any keys from this mint yet, get all keys: `GET /v1/keys` and store them
- Get all keysets with `GET /v1/keysets`
- For all new keyset returned here which we don't have yet, get it using `GET /v1/keys/{keyset_id}` and store it
- If any of the keysets has changed its `active` flag, update it in the db and use the keyset accordingly

[00]: 00.md
[01]: 01.md
[02]: 02.md
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Wallets and mints `MUST` implement all mandatory specs and `CAN` implement optio
|----------|-----------------------------------|
| [00][00] | Cryptography and Models |
| [01][01] | Mint public keys |
| [02][02] | Keysets and keyset IDs |
| [02][02] | Keysets and fees |
| [03][03] | Swapping tokens |
| [04][04] | Minting tokens |
| [05][05] | Melting tokens |
Expand Down