From ee810990431d6ab86c6f94d4542b3f3561a22a78 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 27 May 2024 09:44:01 +0200 Subject: [PATCH 1/8] add fees to NUT-02 --- 02.md | 52 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/02.md b/02.md index b832f49..6a2c14c 100644 --- a/02.md +++ b/02.md @@ -7,19 +7,34 @@ NUT-02: Keysets and keyset ID 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 additionally indicates its keyset `id`, the currency `unit`, whether the keyset is `active` or not, 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 such as one keyset for each currency `unit` it supports. Wallets should support multiple keysets. -#### 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 +Mints can have multiple keysets at the same time but **MUST** have at least one `active` keyset (see [NUT-01][01]). The `active` flag determines whether it is allowed to generate new ecash from this keyset. `Proofs` from inactive keysets are still accepted but new outputs (`BlindedMessages` and `BlindSignatures`) **MUST** be redeemed from `active` keysets only. + +To rotate keys, 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 current supply can be taken out of circulation as wallets rotate their ecash to other active keysets. + +Wallets **SHOULD** spend `Proofs` of inactive keysets first. When constructing outputs for an operation, wallets **MUST** choose only `active` keysets (see [NUT-00][00]). + +### Fees (parts per thousand) + +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 and the sum is rounded up to the next larger integer. + +As an example, if 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 fees would be 300 ppk for this transaction and the mint would charge `1 sat` in fees (`ceil(0.3) == 1`). The fees for spending 1-10 inputs is 1 sat, 11-20 inputs is 2 sat and so on. #### Wallet input and output construction -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]). +When constructing a transaction with ecash inputs (example: `/v1/swap` or `/v1/melt`), wallets **MUST** add fees to the inputs (or subtract from the outputs) if they spent ecash from a keyset with fees. The mint checks the following equation: + +```python +sum(inputs) - sum(fees) == sum(outputs) +``` + +The `fees` are calculated for each input individually (by summing the fee from the keyset they are from) and then rounded up to the next integer. ## Example @@ -45,7 +60,8 @@ Response `GetKeysetsResponse` of `Bob`: { "id": , "unit": , - "active": + "active": , + "input_fee_ppk": , }, ... ] @@ -59,25 +75,35 @@ 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. +### 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. + +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 ## Keyset ID From 4abd431ef0e6412258c58b605e953732449da5e3 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 27 May 2024 10:00:14 +0200 Subject: [PATCH 2/8] edit --- 02.md | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/02.md b/02.md index 6a2c14c..49977ce 100644 --- a/02.md +++ b/02.md @@ -1,5 +1,4 @@ -NUT-02: Keysets and keyset ID -========================== +# NUT-02: Keysets and keyset ID `mandatory` @@ -14,17 +13,18 @@ A mint can have multiple keysets at the same time such as one keyset for each cu ## Keyset properties ### Active keysets -Mints can have multiple keysets at the same time but **MUST** have at least one `active` keyset (see [NUT-01][01]). The `active` flag determines whether it is allowed to generate new ecash from this keyset. `Proofs` from inactive keysets are still accepted but new outputs (`BlindedMessages` and `BlindSignatures`) **MUST** be redeemed from `active` keysets only. -To rotate keys, 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 current supply can be taken out of circulation as wallets rotate their ecash to other active keysets. +Mints can have multiple keysets at the same time but **MUST** have at least one `active` keyset (see [NUT-01][01]). The `active` flag determines whether it is allowed to generate new ecash from this keyset. `Proofs` from inactive keysets are still accepted but new outputs (`BlindedMessages` and `BlindSignatures`) **MUST** be redeemed from `active` keysets only. + +To rotate keys, 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 current supply can be taken out of circulation as wallets rotate their ecash to other active keysets. Wallets **SHOULD** spend `Proofs` of inactive keysets first. When constructing outputs for an operation, wallets **MUST** choose only `active` keysets (see [NUT-00][00]). ### Fees (parts per thousand) -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 and the sum is rounded up to the next larger integer. +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 and the sum is rounded up to the next larger integer. -As an example, if 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 fees would be 300 ppk for this transaction and the mint would charge `1 sat` in fees (`ceil(0.3) == 1`). The fees for spending 1-10 inputs is 1 sat, 11-20 inputs is 2 sat and so on. +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 fees would be 300 ppk for this transaction and the mint would charge `1 sat` in fees (`ceil(0.3) == 1`). The fees for spending 1-10 inputs is 1 sat, 11-20 inputs is 2 sat and so on. #### Wallet input and output construction @@ -38,7 +38,7 @@ The `fees` are calculated for each input individually (by summing the fee from t ## 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 keyset IDs via the `GET /v1/keysets` endpoint. Request of `Alice`: @@ -67,6 +67,7 @@ Response `GetKeysetsResponse` of `Bob`: ] } ``` + ## Example response ```json @@ -89,22 +90,11 @@ Response `GetKeysetsResponse` of `Bob`: "unit": "usd", "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 as added a new current keyset or whether it has changed the `active` flag of an existing one. - -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 - ## 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. @@ -113,7 +103,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 @@ -137,7 +127,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 @@ -174,6 +165,17 @@ Response of `Bob` (same as [NUT-01][01]): } ``` +## 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. + +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 From 83471bae898d351858ca0d4725975e4066db7b0f Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 27 May 2024 10:00:52 +0200 Subject: [PATCH 3/8] adjust title --- 02.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/02.md b/02.md index 49977ce..4b818be 100644 --- a/02.md +++ b/02.md @@ -1,4 +1,4 @@ -# NUT-02: Keysets and keyset ID +# NUT-02: Keysets and fees `mandatory` From cac1e90c7d66d7b51c9f64508a7a705dd55eb459 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 27 May 2024 10:01:10 +0200 Subject: [PATCH 4/8] change title in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1160137..5f21f43 100644 --- a/README.md +++ b/README.md @@ -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 | From 51a42e1c2b417a8104cebf4d4dae6972415b9cc6 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Mon, 27 May 2024 10:06:16 +0200 Subject: [PATCH 5/8] clarify fee can be null --- 02.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/02.md b/02.md index 4b818be..e25cf8b 100644 --- a/02.md +++ b/02.md @@ -58,16 +58,18 @@ Response `GetKeysetsResponse` of `Bob`: { "keysets": [ { - "id": , - "unit": , + "id": , + "unit": , "active": , - "input_fee_ppk": , + "input_fee_ppk": , }, ... ] } ``` +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 From 15e1603e774f8e4d64a63d8060b58954b4621322 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 28 May 2024 22:11:42 +0200 Subject: [PATCH 6/8] edit language --- 02.md | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/02.md b/02.md index e25cf8b..376df0b 100644 --- a/02.md +++ b/02.md @@ -6,39 +6,48 @@ 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`, whether the keyset is `active` or not, and an `input_fee_ppk` that determines the fees for spending ecash from this keyset. +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. -A mint can have multiple keysets at the same time such as one keyset for each currency `unit` it supports. Wallets should support 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. ## Keyset properties ### Active keysets -Mints can have multiple keysets at the same time but **MUST** have at least one `active` keyset (see [NUT-01][01]). The `active` flag determines whether it is allowed to generate new ecash from this keyset. `Proofs` from inactive keysets are still accepted but new outputs (`BlindedMessages` and `BlindSignatures`) **MUST** be redeemed from `active` keysets only. +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. -To rotate keys, 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 current supply can be taken out of circulation as wallets rotate their ecash to other active keysets. +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** spend `Proofs` of inactive keysets first. When constructing outputs for an operation, wallets **MUST** choose only `active` keysets (see [NUT-00][00]). +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 (parts per thousand) +### 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 and the sum is rounded up to the next larger integer. +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 fees would be 300 ppk for this transaction and the mint would charge `1 sat` in fees (`ceil(0.3) == 1`). The fees for spending 1-10 inputs is 1 sat, 11-20 inputs is 2 sat and so on. +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 input and output construction +#### Wallet transaction construction -When constructing a transaction with ecash inputs (example: `/v1/swap` or `/v1/melt`), wallets **MUST** add fees to the inputs (or subtract from the outputs) if they spent ecash from a keyset with fees. The mint checks the following equation: +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: ```python -sum(inputs) - sum(fees) == sum(outputs) +sum(inputs) - fees == sum(outputs) ``` -The `fees` are calculated for each input individually (by summing the fee from the keyset they are from) and then rounded up to the next integer. +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 + return ceil(sum_fees / 1000) +``` +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`: @@ -161,7 +170,7 @@ Response of `Bob` (same as [NUT-01][01]): "4": "0366be6e026e42852498efb82014ca91e89da2e7a5bd3761bdad699fa2aec9fe09", "8": "0253de5237f189606f29d8a690ea719f74d65f617bb1cb6fbea34f2bc4f930016d", ... - } + }, }, ... ] } From b7d64fdcae5b9e4225181154c8bd07a979314a2f Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 16 Jun 2024 15:35:59 +0200 Subject: [PATCH 7/8] address conduitions comment on floating point division --- 02.md | 74 +++++++++++++++++++++++++++++++---------------------------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/02.md b/02.md index 376df0b..60703de 100644 --- a/02.md +++ b/02.md @@ -12,6 +12,12 @@ A mint can have multiple keysets at the same time. For example, it could have on ## Keyset properties +### 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 (see below). + +The keyset `id` is in each `Proof` so it can be used by wallets to identify which mint and keyset it was generated from. The keyset field `id` is also present in the `BlindedMessages` sent to the mint and `BlindSignatures` returned from the mint (see [NUT-00][00]). + ### Active keysets 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. @@ -41,11 +47,39 @@ def fees(inputs: List[Proof]) -> int: sum_fees = 0 for proof in inputs: sum_fees += keysets[proof.id].input_fee_ppk - return ceil(sum_fees / 1000) + 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. + + +### Deriving the keyset ID + +#### Keyset ID version + +Keyset IDs have a version byte (two hexadecimal characters). The currently used version byte is `00`. + +The mint and the wallets of its users can derive a keyset ID from the keyset of the mint. The keyset ID is a lower-case hex string. To derive the keyset ID of a keyset, execute the following steps: + ``` -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. +1 - sort public keys by their amount in ascending order +2 - concatenate all public keys to one byte array +3 - HASH_SHA256 the concatenated public keys +4 - take the first 14 characters of the hex-encoded hash +5 - prefix it with a keyset ID version byte +``` + +An example implementation in Python: -## Example +```python +def derive_keyset_id(keys: Dict[int, PublicKey]) -> str: + sorted_keys = dict(sorted(keys.items())) + pubkeys_concat = b"".join([p.serialize() for p in sorted_keys.values()]) + return "00" + hashlib.sha256(pubkeys_concat).hexdigest()[:14] +``` + +## Example: Get mint keysets A wallet can ask the mint for a list of all keysets via the `GET /v1/keysets` endpoint. @@ -79,7 +113,7 @@ Response `GetKeysetsResponse` of `Bob`: 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 +### Example response ```json { @@ -106,42 +140,12 @@ Here, `id` is the keyset ID, `unit` is the unit string (e.g. "sat") of the keyse } ``` -## 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. - -The keyset `id` is in each `Proof` so it can be used by wallets to identify which mint and keyset it was generated from. The keyset `id` is also stored in `BlindedMessages` sent to the mint and `BlindSignatures` returned from the mint (see [NUT-00][00]). - -### Keyset ID version - -Keyset IDs have a version byte (two hexadecimal characters). The currently used version byte is `00`. - -### Deriving the keyset ID - -The mint and the wallets of its users can derive a keyset ID from the keyset of the mint. The keyset ID is a lower-case hex string. To derive the keyset ID of a keyset, execute the following steps: - -``` -1 - sort public keys by their amount in ascending order -2 - concatenate all public keys to one byte array -3 - HASH_SHA256 the concatenated public keys -4 - take the first 14 characters of the hex-encoded hash -5 - prefix it with a keyset ID version byte -``` - -An example implementation in Python: - -```python -def derive_keyset_id(keys: Dict[int, PublicKey]) -> str: - sorted_keys = dict(sorted(keys.items())) - pubkeys_concat = b"".join([p.serialize() for p in sorted_keys.values()]) - return "00" + hashlib.sha256(pubkeys_concat).hexdigest()[:14] -``` ## 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. -## Example +### Example Request of `Alice`: From 02dbb688e85598aef292f28056d26b9ad9fa0b53 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sun, 16 Jun 2024 17:08:25 +0200 Subject: [PATCH 8/8] Update 02.md Co-authored-by: gandlafbtc <123852829+gandlafbtc@users.noreply.github.com> --- 02.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/02.md b/02.md index 60703de..b467307 100644 --- a/02.md +++ b/02.md @@ -182,7 +182,7 @@ Response of `Bob` (same as [NUT-01][01]): ## 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. +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 added a new current keyset or whether it has changed the `active` flag of an existing one. A useful flow is: