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

Keyset ID V2 #182

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 4 additions & 4 deletions 00.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ Below is a TokenV4 JSON before CBOR and `base64_urlsafe` serialization.
{
"t": [
{
"i": h'00ffd48b8f5ecf80',
"i": h'01ff',
"p": [
{
"a": 1,
Expand All @@ -278,7 +278,7 @@ Below is a TokenV4 JSON before CBOR and `base64_urlsafe` serialization.
],
},
{
"i": h'00ad268c4d1f5826',
"i": h'01ad',
"p": [
{
"a": 2,
Expand All @@ -300,10 +300,10 @@ Below is a TokenV4 JSON before CBOR and `base64_urlsafe` serialization.

The `h''` values are `bytes` but displayed as hex strings here.

We serialize this JSON using CBOR which can be seen [here](https://cbor.nemo157.com/#type=hex&value=a3617482a261694800ffd48b8f5ecf80617081a36161016173784061636331323433356537623834383463336366313835303134393231386166393066373136613532626634613565643334376534386563633133663737333838616358210244538319de485d55bed3b29a642bee5879375ab9e7a620e11e48ba482421f3cfa261694800ad268c4d1f5826617082a3616102617378403133323364336434373037613538616432653233616461346539663166343966356135623461633762373038656230643631663733386634383330376538656561635821023456aa110d84b4ac747aebd82c3b005aca50bf457ebd5737a4414fac3ae7d94da36161016173784035366263626362623763633634303662336661356435376432313734663465666638623434303262313736393236643361353764336333646362623539643537616358210273129c5719e599379a974a626363c333c56cafc0e6d01abe46d5808280789c63616d75687474703a2f2f6c6f63616c686f73743a33333338617563736174). The resulting bytes are then serialized to a string using `base64_urlsafe` and the prefix `cashuB` is added. This leaves us with the following serialized TokenV4:
We serialize this JSON using CBOR which can be seen [here](https://cbor.nemo157.com/#type=hex&value=a3617482a261694201ff617081a36161016173784061636331323433356537623834383463336366313835303134393231386166393066373136613532626634613565643334376534386563633133663737333838616358210244538319de485d55bed3b29a642bee5879375ab9e7a620e11e48ba482421f3cfa261694201ad617082a3616102617378403133323364336434373037613538616432653233616461346539663166343966356135623461633762373038656230643631663733386634383330376538656561635821023456aa110d84b4ac747aebd82c3b005aca50bf457ebd5737a4414fac3ae7d94da36161016173784035366263626362623763633634303662336661356435376432313734663465666638623434303262313736393236643361353764336333646362623539643537616358210273129c5719e599379a974a626363c333c56cafc0e6d01abe46d5808280789c63616d75687474703a2f2f6c6f63616c686f73743a33333338617563736174). The resulting bytes are then serialized to a string using `base64_urlsafe` and the prefix `cashuB` is added. This leaves us with the following serialized TokenV4:

```
cashuBo2F0gqJhaUgA_9SLj17PgGFwgaNhYQFhc3hAYWNjMTI0MzVlN2I4NDg0YzNjZjE4NTAxNDkyMThhZjkwZjcxNmE1MmJmNGE1ZWQzNDdlNDhlY2MxM2Y3NzM4OGFjWCECRFODGd5IXVW-07KaZCvuWHk3WrnnpiDhHki6SCQh88-iYWlIAK0mjE0fWCZhcIKjYWECYXN4QDEzMjNkM2Q0NzA3YTU4YWQyZTIzYWRhNGU5ZjFmNDlmNWE1YjRhYzdiNzA4ZWIwZDYxZjczOGY0ODMwN2U4ZWVhY1ghAjRWqhENhLSsdHrr2Cw7AFrKUL9Ffr1XN6RBT6w659lNo2FhAWFzeEA1NmJjYmNiYjdjYzY0MDZiM2ZhNWQ1N2QyMTc0ZjRlZmY4YjQ0MDJiMTc2OTI2ZDNhNTdkM2MzZGNiYjU5ZDU3YWNYIQJzEpxXGeWZN5qXSmJjY8MzxWyvwObQGr5G1YCCgHicY2FtdWh0dHA6Ly9sb2NhbGhvc3Q6MzMzOGF1Y3NhdA
cashuBo2F0gqJhaUIB_2FwgaNhYQFhc3hAYWNjMTI0MzVlN2I4NDg0YzNjZjE4NTAxNDkyMThhZjkwZjcxNmE1MmJmNGE1ZWQzNDdlNDhlY2MxM2Y3NzM4OGFjWCECRFODGd5IXVW-07KaZCvuWHk3WrnnpiDhHki6SCQh88-iYWlCAa1hcIKjYWECYXN4QDEzMjNkM2Q0NzA3YTU4YWQyZTIzYWRhNGU5ZjFmNDlmNWE1YjRhYzdiNzA4ZWIwZDYxZjczOGY0ODMwN2U4ZWVhY1ghAjRWqhENhLSsdHrr2Cw7AFrKUL9Ffr1XN6RBT6w659lNo2FhAWFzeEA1NmJjYmNiYjdjYzY0MDZiM2ZhNWQ1N2QyMTc0ZjRlZmY4YjQ0MDJiMTc2OTI2ZDNhNTdkM2MzZGNiYjU5ZDU3YWNYIQJzEpxXGeWZN5qXSmJjY8MzxWyvwObQGr5G1YCCgHicY2FtdWh0dHA6Ly9sb2NhbGhvc3Q6MzMzOGF1Y3NhdA
```

[00]: 00.md
Expand Down
30 changes: 17 additions & 13 deletions 02.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ 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 definied in [NUT-00][00], **SHOULD** contain an abbreviated version of the keyset `id` in the `Proof`. The length of the abbreviated `id` **MUST** be at least two bytes (i.e., the version byte and first byte of the hash). However, when the token is serialized, the chosen `id` length **MUST** not be ambiguous within the mint's existing keyset `id`s. A wallet **MUST** resolve the abbreviated keyset `id` to the full `id`. If the abbreviated `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.
davidcaseria marked this conversation as resolved.
Show resolved Hide resolved

### 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.
Expand Down Expand Up @@ -65,8 +67,8 @@ The mint and the wallets of its users can derive a keyset ID from the keyset of
```
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
3 - add the unit string to the byte array (e.g. "unit:sat")
4 - HASH_SHA256 the concatenated public keys
5 - prefix it with a keyset ID version byte
```

Expand All @@ -75,8 +77,9 @@ 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]
keyset_id_bytes = b"".join([p.serialize() for p in sorted_keys.values()])
keyset_id_bytes += b"unit:sat"
Comment on lines +82 to +83
Copy link
Collaborator

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?

Copy link
Author

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?

Copy link
Contributor

@ok300 ok300 Nov 5, 2024

Choose a reason for hiding this comment

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

Once optimization could be to change b"unit:sat" -> b"u:sat" or even just b"sat".
(Edit: nevermind that, it will all be hashed in the end, so reducing the length of what's inside won't matter)

Might also be worth specifying the unit should be lowercased first.

Copy link
Author

Choose a reason for hiding this comment

The 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?

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Author

Choose a reason for hiding this comment

The 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()
```

## Example: Get mint keysets
Expand Down Expand Up @@ -119,19 +122,19 @@ 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
},
{
"id": "0042ade98b2a370a",
"id": "0188432103b12cec6361587d92bdfb798079c58b1c828c561b4daec6f4d465a810",
"unit": "sat",
"active": False,
"input_fee_ppk": 100
},
{
"id": "00c074b96c7e2b0e",
"id": "01d0257bde6ff4cd55e49318a824bbe67e2f9faa248ff108203b5fe46581b14ffc",
"unit": "usd",
"active": True,
"input_fee_ppk": 100
Expand All @@ -142,30 +145,31 @@ 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 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

Request of `Alice`:

We request the keys for the keyset `009a1f293253e41e`.
We request the keys for the keyset `01c9c20fb8b348b389e296227c6cc7a63f77354b7388c720dbba6218f720f9b785`.
Copy link
Contributor

Choose a reason for hiding this comment

The 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 GET v1/keys/{8-byte-keyset-id} request?

Copy link
Author

Choose a reason for hiding this comment

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

A request with a keyset ID prefix 00 should still be supported. A wallet that doesn't support keyset IDs with a prefix of 01 shouldn't get to where they are making this request since the keyset ID parsing will have already failed if the wallet is checking the version.

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",
Expand Down