diff --git a/02.md b/02.md index 64d726a1..be72756f 100644 --- a/02.md +++ b/02.md @@ -14,10 +14,14 @@ A mint can have multiple keysets at the same time. For example, it could have on ### 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). +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 **MAY** 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]). +To save space, a `Token`, as defined in [NUT-00][00], **SHOULD** contain an abbreviated version of the keyset `id` in the `Proof`, (the `s_id`). The length of the `s_id` **MUST** be eight bytes (i.e., the version byte and the first seven bytes of the hash: `id_bytes[:8]` or `id_hex[:16]`). A wallet **MUST** resolve the abbreviated keyset `id` to the full `id`. If the abbreviated `s_id` is ambiguous (i.e., multiple keyset `id`s are resolvable), the wallet **MUST** error. The full keyset `id` is only needed when the wallet interacts with the mint. + +A Wallet **SHOULD** save the full-length keyset `id` with proofs in its database. + ### 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. @@ -56,12 +60,34 @@ Notice that since transactions can spend inputs from different keysets, the sum ### Deriving the keyset ID -#### Keyset ID version +#### Keyset ID -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 `01`. 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 - add the unit string to the byte array (e.g. "unit:sat") +4 - If a final expiration is specified, convert it into a radix-10 string and add it (e.g "final_expiry:1896187313") +4 - HASH_SHA256 the concatenated byte array +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())) + keyset_id_bytes = b"".join([p.serialize() for p in sorted_keys.values()]) + keyset_id_bytes += b"unit:sat" + keyset_id_bytes += b"final_expiry:"+str(1896187313).encode("utf-8") + return "01" + hashlib.sha256(keyset_id_bytes).hexdigest() +``` + +##### Version 0 + ``` 1 - sort public keys by their amount in ascending order 2 - concatenate all public keys to one byte array @@ -79,6 +105,14 @@ def derive_keyset_id(keys: Dict[int, PublicKey]) -> str: return "00" + hashlib.sha256(pubkeys_concat).hexdigest()[:14] ``` +### Keyset Final Expiry + +A unix epoch number for a future point in time that represents the final expiry of the keyset. After the keyset's final expiry, the Mint is no longer obliged to fulfill promises signed with the keys from that keyset. + +This effectively implies that the Mint can irrevocably remove all of the nullifiers (`Y` values/spent ecash) associated with the expired keyset. + +The final expiry can be `null` if the keyset has no final-expiry. + ## Example: Get mint keysets A wallet can ask the mint for a list of all keysets via the `GET /v1/keysets` endpoint. @@ -105,6 +139,7 @@ Response `GetKeysetsResponse` of `Bob`: "unit": , "active": , "input_fee_ppk": , + "final_expiry": }, ... ] @@ -119,22 +154,25 @@ Here, `id` is the keyset ID, `unit` is the unit string (e.g. "sat") of the keyse { "keysets": [ { - "id": "009a1f293253e41e", + "id": "01c9c20fb8b348b389e296227c6cc7a63f77354b7388c720dbba6218f720f9b785", "unit": "sat", - "active": True, - "input_fee_ppk": 100 + "active": true, + "input_fee_ppk": 100, + "final_expiry": 1896187313 }, { - "id": "0042ade98b2a370a", + "id": "0188432103b12cec6361587d92bdfb798079c58b1c828c561b4daec6f4d465a810", "unit": "sat", - "active": False, - "input_fee_ppk": 100 + "active": false, + "input_fee_ppk": 100, + "final_expiry": 1896187313 }, { - "id": "00c074b96c7e2b0e", + "id": "01d0257bde6ff4cd55e49318a824bbe67e2f9faa248ff108203b5fe46581b14ffc", "unit": "usd", - "active": True, - "input_fee_ppk": 100 + "active": true, + "input_fee_ppk": 100, + "final_expiry": 1896187313 } ] } @@ -148,16 +186,16 @@ To receive the public keys of a specific keyset, a wallet can call the `GET /v1/ Request of `Alice`: -We request the keys for the keyset `009a1f293253e41e`. +We request the keys for the keyset `01c9c20fb8b348b389e296227c6cc7a63f77354b7388c720dbba6218f720f9b785`. ```http -GET https://mint.host:3338/v1/keys/009a1f293253e41e +GET https://mint.host:3338/v1/keys/01c9c20fb8b348b389e296227c6cc7a63f77354b7388c720dbba6218f720f9b785 ``` With curl: ```bash -curl -X GET https://mint.host:3338/v1/keys/009a1f293253e41e +curl -X GET https://mint.host:3338/v1/keys/01c9c20fb8b348b389e296227c6cc7a63f77354b7388c720dbba6218f720f9b785 ``` Response of `Bob` (same as [NUT-01][01]): @@ -165,7 +203,7 @@ Response of `Bob` (same as [NUT-01][01]): ```json { "keysets": [{ - "id": "009a1f293253e41e", + "id": "01c9c20fb8b348b389e296227c6cc7a63f77354b7388c720dbba6218f720f9b785", "unit": "sat", "keys": { "1": "02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104", diff --git a/13.md b/13.md index 8d4f2ae8..777879ae 100644 --- a/13.md +++ b/13.md @@ -43,7 +43,7 @@ The wallet starts with `counter_k := 0` upon encountering a new keyset and incre #### Keyset ID -The integer representation `keyset_id_int` of a keyset is calculated from its [hexadecimal ID][02] which has a length of 8 bytes or 16 hex characters. First, we convert the hex string to a big-endian sequence of bytes. This value is then modulo reduced by `2^31 - 1` to arrive at an integer that is a unique identifier `keyset_id_int`. +The integer representation `keyset_id_int` of a keyset is calculated from its [hexadecimal ID][02] which has a length of 8 bytes or 16 hex characters. First, we convert the hex string to a big-endian sequence of bytes. This value is then modulo reduced by `2^31 - 1` to arrive at an integer that is a unique identifier `keyset_id_int`. Keyset IDs with version prefix `01` should be shortened to the first 8 bytes before conversion. Example in Python: