-
Notifications
You must be signed in to change notification settings - Fork 52
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
Keyset ID V2 #182
base: main
Are you sure you want to change the base?
Keyset ID V2 #182
Changes from all commits
f3f0280
61af756
d3d01d6
ae8f6d1
abae74b
4668545
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,32 @@ 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 - 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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Might also be worth specifying the unit should be lowercased first. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we have specified the units as case-insensitive yet. We should probably also do that if we include this change, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the keyset ID commits to the unit, then it would make sense IMO. If a client derives the keyset ID using a different capitalization, or if the mint changes the capitalization later on, the keyset ID would be invalid. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @callebtc what do you think about units being case-insensitive and hashing the lowercase unit value? |
||
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 | ||
|
@@ -119,21 +143,21 @@ 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, | ||
"active": true, | ||
"input_fee_ppk": 100 | ||
}, | ||
{ | ||
"id": "0042ade98b2a370a", | ||
"id": "0188432103b12cec6361587d92bdfb798079c58b1c828c561b4daec6f4d465a810", | ||
"unit": "sat", | ||
"active": False, | ||
"active": false, | ||
"input_fee_ppk": 100 | ||
}, | ||
{ | ||
"id": "00c074b96c7e2b0e", | ||
"id": "01d0257bde6ff4cd55e49318a824bbe67e2f9faa248ff108203b5fe46581b14ffc", | ||
"unit": "usd", | ||
"active": True, | ||
"active": true, | ||
"input_fee_ppk": 100 | ||
} | ||
] | ||
|
@@ -148,24 +172,24 @@ 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`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens if a wallet that does not yet support V2 keysets makes a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A request with a keyset ID prefix If a wallet doesn't check the version and sends a request for an 8-byte keyset ID from a token, we can consider having the mint resolve it for the wallet from the URL. But that feels like an optional requirement for the mint. Does that address your question? |
||
|
||
```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]): | ||
|
||
```json | ||
{ | ||
"keysets": [{ | ||
"id": "009a1f293253e41e", | ||
"id": "01c9c20fb8b348b389e296227c6cc7a63f77354b7388c720dbba6218f720f9b785", | ||
"unit": "sat", | ||
"keys": { | ||
"1": "02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104", | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am wondering ... since we already depend for CBOR (for Token v4), maybe we can use CBOR here too instead of this custom serialization?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would the structure of the CBOR be?