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

NIP-0b - On-Behalf of (Simple Sub-Key Management) #1482

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
273 changes: 273 additions & 0 deletions 0b.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
NIP-0B
======

On-Behalf of - Simple Sub-Key Management
-----

`draft` `optional`

This NIP defines a simple way for an Identity Sub-Key management system, including active, inactive and revoked public keys, for publishing On-Behalf of a master identity.

### Motivation

The state of art now has two methods for enabling sub-key management:
- [NIP-26](https://github.com/nostr-protocol/nips/blob/master/26.md): Multiple key delegation and no revocation (just time bound)
- [NIP-41](https://github.com/nostr-protocol/nips/pull/1032): More complete single key delegation and revocation (complex)

Each of these methods has their own pros and cons, but there's a very common use case that is not covered by any of these methods completely, which is having a master identity that is secure (possibly in cold storage), that can whitelist and blacklist multiple public keys (one for each device or app, for example), that can be used individually (less common but interesting for media management companies) and can publish on-behalf of one or several master identities.

Using [NIP-26](https://github.com/nostr-protocol/nips/blob/master/26.md) for this purpose doesn't allow for blacklisting (revocation), or alternatively requires time bound delegation, with new delegation signing at every expiry. On the other hand [NIP-41](https://github.com/nostr-protocol/nips/pull/1032) is a more complete and complex identity management solution, but only allows for one active sub-key at any point in time, and that sub-key's only purpose is bound to the main identity.

### References

This proposal came together with inputs from many other proposals and discussions, but these are the main ones:

[NIP-26: Delegated Event Signing](https://github.com/nostr-protocol/nips/blob/master/26.md)<br>
[NIP-41: Identity Management](https://github.com/nostr-protocol/nips/pull/1032)<br>
[NIP-06: Basic Key Derivation from Mnemonic Seed Phrase](https://github.com/nostr-protocol/nips/blob/master/06.md)<br>
[NIP-51: Lists](https://github.com/nostr-protocol/nips/blob/master/51.md)

[Why I don’t like NIP-26 as a solution for key management](https://fiatjaf.com/4c79fd7b.html)<br>
[Thoughts on Nostr key management](https://fiatjaf.com/72f5d1e4.html)

[Stateless key rotation using a series of hidden commitments](https://github.com/nostr-protocol/nips/issues/103)<br>
[Key distribution, rotation, and recovery](https://github.com/nostr-protocol/nostr/issues/45)<br>
[Key rotation verified through root key attestation](https://github.com/nostr-protocol/nips/issues/116)<br>
[Trusted public-key-bundle attestations for key rotation and group definition](https://github.com/nostr-protocol/nips/issues/123)

### Identity types

For clarity in this NIP, we'll use the following identities to describe the actors that are involved in the examples and use cases:

- **master**: the master account, the content owner or author
- **active**: an active sub account, device account, media manager or publisher account
- **inactive**: a sub account that has been inactivated (but not compromised)
- **revoked**: a sub account that has been revoked (it may have been compromised, and all events posted on-behalf of master need to be invalidated)

### On-Behalf of Attestations List

This NIP introduces a replaceable event with `kind 10100`, which is used to keep a [NIP-51](https://github.com/nostr-protocol/nips/blob/master/51.md) list of `active`, `inactive` or `revoked` public keys, for publishing events on-behalf of a master account. The only valid tag in this list is the `p` tag as defined below.

`.content` fields is unused and can be ignored if present.

This list is owned by the master account, and can only be updated by an event signed by the master account itself, so never using a `b` on-behalf tag (see below), neither by [NIP-41](https://github.com/nostr-protocol/nips/pull/1032) `p` tag in `kind 0`, nor using a [NIP-26](https://github.com/nostr-protocol/nips/blob/master/26.md) `delegation` tag.

This list is ever growing, so each update has to include all previous attestations unchanged, and add one or more attestations to the list, otherwise they should be considered invalid and ignored by clients and relays.

### Tag `b` (on-Behalf) for events

This NIP introduces a new tag: `b`, which is indexable by relays, and can be present in active account events, if attested at the on-behalf list above, formatted as follows:

```json
[ "b", <pubkey of the master account> ]
```

To prevent spam, only one `b` tag can be present in an event.

If present and valid, the `b` tag will represent that clients and relays should consider that event as published by the master account specified in the `b` tag, instead of the one in the event `.pubkey` field.

If an invalid `b` tag is present, clients and relays should disregard the event, as if it had an invalid signature.

### Tag `p` (with attestation string) for `kind 10100` list

This NIP also introduces a new attestation string for `p` tag. The `p` tag can be present multiple times in master account's `kind 10100` On-Behalf Of attestations list, formatted as follows:

```json
[
"p", <pubkey>, <main relay URL>, <attestation string>
]
```

Field `main relay URL` can be omitted if not required or needed for accessing the sub account events, and in this case an empty string should be used in the second field of the `p` tag.

#### Active Attestation String

The **Active Attestation** should be a string in the following format:

```
"active:<timestamp>:<kinds comma separated list, optional>"
```

Last `:` is only required if an optional kinds list is present. Without a kinds list, the attestation is valid for all event kinds besides `kind 10100`.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Kinds MUST be required, because otherwise subkeys can delete posts of the main account and can also change kind:10100 of the main account.

There are many kinds that don't work well with delegation or that the delegation gets ambiguous. For instance, what should kinds that use encryption, like DMs, do? Encrypt to the main account or keep working as if there is no delegation?

Can subkeys have their own subkeys ad infinitum by having multiple bs to build the stack? And if so, do verifiers need to verify the complete chain?

Copy link
Author

Choose a reason for hiding this comment

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

Kinds MUST be required, because otherwise subkeys can delete posts of the main account and can also change kind:10100 of the main account.

Not against sub-keys deleting posts by master or any other sub-account. The kind 10100 is not allowed to be signed by any delegation mechanism, only the master account can sign those events.

There are many kinds that don't work well with delegation or that the delegation gets ambiguous. For instance, what should kinds that use encryption, like DMs, do? Encrypt to the main account or keep working as if there is no delegation?

Those kinds shouldn't take b tag into account... see my reply below about DM's etc.

Can subkeys have their own subkeys ad infinitum by having multiple bs to build the stack? And if so, do verifiers need to verify the complete chain?

Don't like this idea, I think that only one level is enough. Maybe that should be more explicit in the NIP?


#### Inactive and Revoked Attestation String

The **Inactive** or **Revoked Attestations** should be a string in the following formats:

```
"inactive:<timestamp>"
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should not be a timestamp as the inactive key can still post in the past, during the active period. It should contain a bloomfilter with all the valid event_ids made during the time the key was active.

The main account gathers all valid events and merges their IDs in a bloom filter and posts it, likely as a separate event because this will get big very quicky (Imagine corporate accounts)

Verification will check if the event id is in the list or not.

Copy link
Author

Choose a reason for hiding this comment

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

The idea of the inactive attestation is for sub-accounts that were not compromised, just inactivated. Imagine the use case of an iPhone that you want to replace, you request to the master account to inactivate that sub-account as the key is removed from the device, effectively destroying the key. If the key was leaked by any means, than the revocation is needed, not the inactivation.

```

or

```
"revoked:<timestamp>"
Copy link
Collaborator

Choose a reason for hiding this comment

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

What's the purpose of the revoked timestamp if it is going to invalidate everything?

It's easier to just delete the key from the list.

Copy link
Author

Choose a reason for hiding this comment

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

The list has to keep all history to keep track of revocations, exactly to avoid replay attacks of past events

```

To purposely limit the creative use cases, so that it doesn't become very demanding to compute the final state of an account, `inactive` and `revoked` attestations invalidate all previous `active` attestations, and subsequent `active` attestations are considered invalid as well.

> This way the master account can decide, depending on the use case and criticality, if it makes sense to keep the published content valid, by adding an `inactive` attestation, or if it's preferable to invalidate all content published by that account, on its behalf, by adding a `revoked` attestation.

##### Subsequent Attestations Resolution

As the attestations are all timestamped, it is convention that a subsequent attestation overrides a prior one, but in the rare event of the same timestamp in attestations for the same public key, the latest in the tags array order should override the previous.

### Examples

- Considering:
```
master account:
8e0d3d3eb2881ec137a11debe736a9086715a8c8beeeda615780064d68bc25dd

active account:
477318cfb5427b9cfc66a9fa376150c1ddbc62115ae27cef72417eb959691396
```

- Allow active account to publish `kind 1` and `kind 7` events on-behalf of the master account:

```json
{
"id": "567b41fc9060c758c4216fe5f8d3df7c57daad7ae757fa4606f0c39d4dd220ef",
"kind": "10100",
"pubkey": "8e0d3d3eb2881ec137a11debe736a9086715a8c8beeeda615780064d68bc25dd",
"tags": [
[
"p",
"477318cfb5427b9cfc66a9fa376150c1ddbc62115ae27cef72417eb959691396",
"",
"active:1674834236:1,7"
]
],
"content": "",
...
"sig": "a9a4e2192eede77e6c9d24ddfab95ba3ff7c03fbd07ad011fff245abea431fb4d3787c2d04aad001cb039cb8de91d83ce30e9a94f82ac3c5a2372aa1294a96bd"
}
```

- The active account, while the master account has a valid active attestation at its `kind 10100` list, can publish on-behalf of the master account, using the `b` tag on the event:

```json
{
"id": "e93c6095c3db1c31d15ac771f8fc5fb672f6e52cd25505099f62cd055523224f",
"pubkey": "477318cfb5427b9cfc66a9fa376150c1ddbc62115ae27cef72417eb959691396",
"created_at": 1677426298,
"kind": 1,
"tags": [
[
"b",
"8e0d3d3eb2881ec137a11debe736a9086715a8c8beeeda615780064d68bc25dd"
]
],
"content": "Hello, world!",
"sig": "633db60e2e7082c13a47a6b19d663d45b2a2ebdeaf0b4c35ef83be2738030c54fc7fd56d139652937cdca875ee61b51904a1d0d0588a6acd6168d7be2909d693"
}
```

The event should be considered as published on-behalf of the master account, if at the timestamp `1677426298` the master account had a valid active attestation on its `kind 10100` list. If the `b` tag is not validated by an active attestation, that content is considered invalid and relays and clients can ignore it.

- When master account decides to allow on-behalf active for all kinds:

```json
{
"id": "567b41fc9060c758c4216fe5f8d3df7c57daad7ae757fa4606f0c39d4dd220ef",
"kind": "10100",
"pubkey": "8e0d3d3eb2881ec137a11debe736a9086715a8c8beeeda615780064d68bc25dd",
"tags": [
["p",
"477318cfb5427b9cfc66a9fa376150c1ddbc62115ae27cef72417eb959691396",
"",
"active:1674834236:1,7"],
["p",
"477318cfb5427b9cfc66a9fa376150c1ddbc62115ae27cef72417eb959691396",
"",
"active:1721934607"]
],
"content": "",
...
"sig": "a9a4e2192eede77e6c9d24ddfab95ba3ff7c03fbd07ad011fff245abea431fb4d3787c2d04aad001cb039cb8de91d83ce30e9a94f82ac3c5a2372aa1294a96bd"
}
```

- Or otherwise, when master account decides to not allow on-behalf for `kind 7`, but keep `kind 1` active:

```json
{
"id": "567b41fc9060c758c4216fe5f8d3df7c57daad7ae757fa4606f0c39d4dd220ef",
"kind": "10100",
"pubkey": "8e0d3d3eb2881ec137a11debe736a9086715a8c8beeeda615780064d68bc25dd",
"tags": [
["p",
"477318cfb5427b9cfc66a9fa376150c1ddbc62115ae27cef72417eb959691396",
"",
"active:1674834236:1,7"],
["p",
"477318cfb5427b9cfc66a9fa376150c1ddbc62115ae27cef72417eb959691396",
"",
"active:1721934607:1"]
],
"content": "",
...
"sig": "a9a4e2192eede77e6c9d24ddfab95ba3ff7c03fbd07ad011fff245abea431fb4d3787c2d04aad001cb039cb8de91d83ce30e9a94f82ac3c5a2372aa1294a96bd"
}
```

- In the case that master decides to not permit on-behalf publishing for that active account anymore, but keep the already published events, for instance if the device that was using that active account is being substituted, and that key safely destroyed:

```json
{
"id": "567b41fc9060c758c4216fe5f8d3df7c57daad7ae757fa4606f0c39d4dd220ef",
"kind": "10100",
"pubkey": "8e0d3d3eb2881ec137a11debe736a9086715a8c8beeeda615780064d68bc25dd",
"tags": [
["p",
"477318cfb5427b9cfc66a9fa376150c1ddbc62115ae27cef72417eb959691396",
"",
"active:1674834236:1,7"],
["p",
"477318cfb5427b9cfc66a9fa376150c1ddbc62115ae27cef72417eb959691396",
"",
"active:1721934607:1"],
["p",
"477318cfb5427b9cfc66a9fa376150c1ddbc62115ae27cef72417eb959691396",
"",
"inactive:1722343578"]
],
"content": "",
...
"sig": "a9a4e2192eede77e6c9d24ddfab95ba3ff7c03fbd07ad011fff245abea431fb4d3787c2d04aad001cb039cb8de91d83ce30e9a94f82ac3c5a2372aa1294a96bd"
}
```

- Or instead, if the active account got compromised, and it is best to consider all content published from that account,on-behalf of master account, to be compromised as well:

```json
{
"id": "567b41fc9060c758c4216fe5f8d3df7c57daad7ae757fa4606f0c39d4dd220ef",
"kind": "10100",
"pubkey": "8e0d3d3eb2881ec137a11debe736a9086715a8c8beeeda615780064d68bc25dd",
"tags": [
["p",
"477318cfb5427b9cfc66a9fa376150c1ddbc62115ae27cef72417eb959691396",
"",
"active:1674834236:1,7"],
["p",
"477318cfb5427b9cfc66a9fa376150c1ddbc62115ae27cef72417eb959691396",
"",
"active:1721934607:1"],
["p",
"477318cfb5427b9cfc66a9fa376150c1ddbc62115ae27cef72417eb959691396",
"",
"revoked:1722343578"]
],
"content": "",
...
"sig": "a9a4e2192eede77e6c9d24ddfab95ba3ff7c03fbd07ad011fff245abea431fb4d3787c2d04aad001cb039cb8de91d83ce30e9a94f82ac3c5a2372aa1294a96bd"
}
```


### Relay & Client Support

Relays should answer requests such as `["REQ", "", {"authors": ["A"]}]` by querying both the `pubkey` and on-behalf `#b` tags `[1]` value.

Copy link
Collaborator

Choose a reason for hiding this comment

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

NIP-42 MUST allow the use of the subkey to login if authorized.

Copy link
Author

@ice-orestes ice-orestes Sep 25, 2024

Choose a reason for hiding this comment

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

This is cool. So you mean that kind 22242 ephemeral event on the AUTH message could use the b tag to reference the master account, and the relays should authenticate the main account when such b tag is present?

Relays SHOULD allow the master account to delete the events published by the active, inactive or revoked accounts, whenever the events have the on-behalf `b` tag present.
1 change: 1 addition & 0 deletions 51.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ For example, _mute list_ can contain the public keys of spammers and bad actors
| Interests | 10015 | topics a user may be interested in and pointers | `"t"` (hashtags) and `"a"` (kind:30015 interest set) |
| Emojis | 10030 | user preferred emojis and pointers to emoji sets | `"emoji"` (see [NIP-30](30.md)) and `"a"` (kind:30030 emoji set) |
| DM relays | 10050 | Where to receive [NIP-17](17.md) direct messages | `"relay"` (see [NIP-17](17.md)) |
| On-Behalf of | 10100 | [NIP-0b](0b.md) attestations list | `"p"` (pubkeys, with [NIP-0b](0b.md) attestation string) |
| Good wiki authors | 10101 | [NIP-54](54.md) user recommended wiki authors | `"p"` (pubkeys) |
| Good wiki relays | 10102 | [NIP-54](54.md) relays deemed to only host useful articles | `"relay"` (relay URLs) |

Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
- [NIP-07: `window.nostr` capability for web browsers](07.md)
- [NIP-08: Handling Mentions](08.md) --- **unrecommended**: deprecated in favor of [NIP-27](27.md)
- [NIP-09: Event Deletion Request](09.md)
- [NIP-0b: On-Behalf of (Simple Sub-Key Management)](0b.md)
- [NIP-10: Conventions for clients' use of `e` and `p` tags in text events](10.md)
- [NIP-11: Relay Information Document](11.md)
- [NIP-13: Proof of Work](13.md)
Expand Down Expand Up @@ -160,6 +161,7 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
| `10050` | Relay list to receive DMs | [51](51.md), [17](17.md) |
| `10063` | User server list | [blossom] |
| `10096` | File storage server list | [96](96.md) |
| `10100` | On-Behalf of Attestations List | [0b](0b.md) |
| `13194` | Wallet Info | [47](47.md) |
| `21000` | Lightning Pub RPC | [Lightning.Pub][lnpub] |
| `22242` | Client Authentication | [42](42.md) |
Expand Down Expand Up @@ -247,10 +249,11 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
| name | value | other parameters | NIP |
| ----------------- | ------------------------------------ | ------------------------------- | ------------------------------------- |
| `e` | event id (hex) | relay URL, marker, pubkey (hex) | [01](01.md), [10](10.md) |
| `p` | pubkey (hex) | relay URL, petname | [01](01.md), [02](02.md) |
| `p` | pubkey (hex) | relay URL, petname / attestation | [01](01.md), [02](02.md), [0b](0b.md) |
| `a` | coordinates to an event | relay URL | [01](01.md) |
| `d` | identifier | -- | [01](01.md) |
| `-` | -- | -- | [70](70.md) |
| `b` | pubkey (hex) | -- | [0b](0b.md) |
| `g` | geohash | -- | [52](52.md) |
| `h` | group id | -- | [29](29.md) |
| `i` | external identity | proof, url hint | [39](39.md), [73](73.md) |
Expand Down