nostr-protocol / nips

Nostr Implementation Possibilities
2.33k stars 564 forks source link

Trusted public-key-bundle attestations for key rotation and group definition #123

Open vitorpamplona opened 1 year ago

vitorpamplona commented 1 year ago

Purpose

The purpose of this NIP is to provide users with a mechanism to define a group of related keys that speak on behalf of the user (e.g. multiple keys, each coming from a different device that is controlled by the same user) or on behalf of a subject (e.g. officers of a company, members of a club, etc).

For users, the goal is to use multiple private keys and allow revocable "hot" keys in less secure application-level contexts while keeping the root key that defines their identity in cold storage, only used for signing the attestation event described below.

For groups, the goal is to empower users to speak on behalf of the entity. The entity's keys are kept safe in cold storage, only used for signing the attestation event described below, while its officers use their hot keys for interactions with the protocol.

This NIP would not replace or attempt to improve upon NIP-26. That's an entirely different use case. This NIP does not impose any additional requirements on relays and does not change the base structure of the protocol.

Main Idea

A simple key management scheme where a root key broadcasts a replaceable, NIP-03 timestamped event that declares a list of public keys that can speak on the root's behalf. The root key can change the list at any time, adding and removing public keys as it wishes. Removing keys from the list is equivalent to expiring them. It's the responsibility of the client app to periodically review these records and react accordingly in both: new events and past events.

Client app users can choose to follow root keys, which will follow all authorized keys from the list. In this case, when the root updates its keys, clients must react accordingly:

  1. Stop following the removed keys.
  2. Start following the new keys.
  3. Reassign UI identifiers

Client app users can choose to message the root key. In that case:

  1. The app must create a private message to a group of keys (To be designed)
  2. The app must add and remove keys to that group.
  3. When keys are removed, past messages from those public keys should stay (they were trusted back then) but new messages must not appear in the group.
  4. Messages from all keys must be identified as the root (the chat window should not identify individual keys)

Client apps should have a dated log of changes to the key list to make sure the trust's historical context is preseved.

Details

Beware of long hierarchies and circular referencing when evaluating who is a root key. Apps should not assume keys in a group are controlled by the same entity or have consistent behavior.

The language of on behalf of is purposely used to make sure the key list is not misunderstood. Keys might not have the same authority as the root, they might not be the same person, and they might not be controlled by the same entity. They are simply authorized to speak on behalf of the user/group.

Event format

The event format follows a NIP-02 structure, with a distinct event type.

{
  "id": <32-bytes sha256 of the the serialized event data>
  "kind": <TBD>,
  "tags": [
    ["p", "91cf9..4e5ca"],
    ["p", "14aeb..8dad4"],
    ["p", "612ae..e610f"]
  ],
  "content": "",
  "pubkey": "root key", 
  "created_at": <unix timestamp in seconds>,
  "sig": <64-bytes signature of the sha256 hash of the serialized event data, which is the same as the "id" field>
}

Private keys should not be visible

This protocol enables the use of Secure Element chips and support the idea that private keys should never leave such devices and thus never be seen by anybody and never transferred around. Only public keys should be transferred to declare the authority to speak on behalf of the root.

Generalistic approach of identity without changing the base protocol.

This protocol makes sure relays stay stateless. It makes the syntax work without specifying semantics, which are left to client apps. By using the same format:

Because each key can be a different user, each key can have a different nostr profile. Keys must be able to speak for themselves as well as speak for each of the root keys that have added them (e.g. marketing accounts with users that speak on behalf of companies). In such cases, followers of the root key must see posts identified as the root profile and followers of the individual keys must see those posts identified as the key profiles.

Using it with NIP-05

Users can point to a root public key in order to specify their NIP05 identity (format TBD: nostr:<pubkey>?). Apps should validate if the user's public key is in the list authorized by the root. In this way, NIP-05 becomes independent of HTTP/DNS

Using it outside Nostr as a DID method

The root public key (did:nostr:<pubkey>) becomes the source for a DID resolver, so any DID-based application can use keys defined in NOSTR into their workflow.

fix commented 1 year ago

As i stated in another thread, i think ID management can't be tied to an application, otherwise the risk is to deploy this kind of 'heavy' tooling once per app, whereas this id management could be independent and feed applications with derived application keys.

On the content of the proposal, alternatively, there could be only 1 key being used, and when compromised, the root derive a new key and include the hash of the former certificate. certificate being defined by C(n) = [k(n), Hash(C(n-1))] C(0) = [k(0), null]

and k(n) = f(root, n) where knowing f(root, n-1) does not give information about f(root, n) (think hardened BIP32)

vitorpamplona commented 1 year ago

As i stated in another thread, i think ID management can't be tied to an application, otherwise the risk is to deploy this kind of 'heavy' tooling once per app, whereas this id management could be independent and feed applications with derived application keys.

Agree. But it looks like Nostr folks are not welcome to the idea of separating the two (which was my initial proposal of simply using external DIDs as public key).

On the content of the proposal, alternatively, there could be only 1 key being used, and when compromised, the root derive a new key and include the hash of the former certificate. certificate being defined by ...

Yes, one could do the same by following root certificates. But it doesn't look like Nostr people like dealing with certificates.

Would the certificate stack be limited to only 1 active key at a time?

fix commented 1 year ago

Ideally a certificate could be derived from a former certificate, so the stack would be simplified.

like f(root, n) = F(f(root, n-1)) but with no way to derive private key from known (compromised) private key... Sounds like a complex cryptographic issue

vitorpamplona commented 1 year ago

I know DIDs are overengineered. But if you really think ID management can't be tied to an application/protocol, then the ID manager must be able to support a vast range of use cases, programming languages and hardware devices in the way those things want. It's can't rely on individual cryptographic schemes, like your certificate renewal idea. Frankly, it can't even rely on supporting certificates at all.

fix commented 1 year ago

well i see this differently:

vitorpamplona commented 1 year ago

But if the manager only signs and everything else would be delegated to an app, then you are talking about a signing device, not an Identity Manager. The identity manager is the one responsible for key rotation schemes, not the app.

fix commented 1 year ago

In my understanding, Identity Manager is a device, creating application keys that are used by the apps to sign.

The way the keys are created should not be reproducible by anybody else (hence the idea of derivation instead of signature/certificate).

To clarify, the word certificate in my original message is not about something signed, it is more about something generated that can't be generated by 'anybody else'

vitorpamplona commented 1 year ago

So, the identity manager doesn't need to create keys. Generally, that is made by the signing device because singing devices have a Secure Element chip that can store a private key correctly in such a way that it never leaves the device. The manager receives the public key from the signing device and uses the key to maintain the multiple identities you might have. Sometimes the signing device is included inside the ID manager, but these are generally two different concepts.

Then the Identity Manager assembles credential issuance, credential and key rotations, attestations, trusted registries and so on. The identity manager generally connects the keys together with additional information (like a government ID)

fix commented 1 year ago

Well not sure to follow. Trying to sum up my point:

vitorpamplona commented 1 year ago

So, points 1 or 2 are good. The 3rd one is not my usual procedure. I generally work in the opposite way. Let's say you want a new application key for Facebook on your phone. The phone uses the Secure Element to create a private and public key pair, keeps the private key secure in the device, and shares the public key with your ID Manager. Your ID Manager associates that new key with your ID and gives it access to your Facebook account. Next time you use the key on that phone, Facebook checks if the pub key of the message signed by the phone is inside your ID Manager and if it hasn't been revoked. If so, it proceeds with the login.

It's kind of a dream though. It's really hard to make real-world apps collaborate like that. It generally only works in closed systems.

fix commented 1 year ago

Agree 100%, point 3 is by far the most difficult aspect for general adoption. I mainly agree on your point 3 procedure, except i see ID Manager as center app, delegating, if requested, the crypto aspect to third part (SE, passkey, ledger and whatnot). Right now I have a working MVP, that will eventually be open sourced in 6 month or so.

optout21 commented 1 year ago

Very valuable proposal!

This proposal and NIP-26 have very similar goals, overlapping usecases. Here’s my comparison of the two:

The two approaches could be combined! Here's what I have in mind:

This way it is possible to bring best of both worlds together: possible revocation, but also independent verification and filter conditions. The price is more complexity and more information.

The proposed note with current delegations would also be very helpful for the UX flow, as it solves getting the delegation from delegator client to delegatee client (i.e., no need for copy/paste). See also https://github.com/nostr-protocol/nips/issues/247 .

An alternative is to keep the two approaches separate (current delegations in notes from this proposal, delegation tag in each note NIP-26), and let the implementors decide which one to support (one the other or both), but this can lead to fragmentation and lack of interoperability between clients.