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

Key distribution, rotation, and recovery #45

Open
catleeball opened this issue Dec 31, 2021 · 57 comments
Open

Key distribution, rotation, and recovery #45

catleeball opened this issue Dec 31, 2021 · 57 comments

Comments

@catleeball
Copy link

catleeball commented Dec 31, 2021

Howdy!

I was just skimming over your plans for nostr. I might have missed it, but do you have plans for:

  • how keys can be distributed and trusted
  • when/if keys should be rotated
  • how to deal with recovery of lost private keys

I'm not super experienced with security or cryptography, so I don't have a strong sense of how the above should be handled to offer any suggestions, but I'm interested to know how it might work.

edit: gonna unsubscribe from notifications for this

@fiatjaf
Copy link
Member

fiatjaf commented Jan 1, 2022

These are open questions. Any input is appreciated.

What do you mean by "distributed"? In my mind it will be primarily a manual process of talking to people etc. But there are some tricks that can be done.

What do you mean by key rotation? Keys are not meant to be rotated, what is the reasoning for wanting that?

For lost keys there are some ideas of making a best-effort transition to a new pubkey by notifying followers, but this is a somewhat impossible problem no matter how you tackle it. Somehow, somewhere, some keys must be kept safe. If you lose everything you lost everything. Right?

@scsibug
Copy link
Contributor

scsibug commented Jan 6, 2022

I was thinking about whether we needed an event type to signal key rotation had occurred. Giving guidance for client behavior is a bit tricky though. Should clients auto-switch following the new key and un-follow the old one? Should they accept any future events from the previous key?

I think the main use case is key compromise. If that happens, someone could use that key to publish an earlier (timestamped) event that rotated the key to one they controlled, so having clients do anything automatically in response to a compromised account isn't smart.

Perhaps the safest option is to have an event type that signals the account is compromised, or should no longer be trusted. Regardless of the timestamp, that should be a signal to clients to consider the pubkey compromised - that means any past or future dated events may be exposed, and any direct messages should be considered exposed as well.

There would have to be an exception to the deletion NIP-09 event kind, since you don't want the attacker issuing a delete against the "compromised pubkey" event.

My vote would be (eventually) having a "key compromise" event that clients (implementing the NIP) would use to visually alert the end user, and perhaps even do things like prevent DMs from being sent.

@kevinsmith
Copy link

What happens if I lose my private key or it gets leaked? is definitely the major obstacle for a moderately technical user here. An average user is going to have trouble even understanding an immutable private key vs a password they can update whenever they want. I suspect the concern for both types of users will typically be solved by a service abstracting away those details and allowing users to register accounts and post just like they do with the prominent social networks.

But it would still be great to solve for the problem of an exposed private key.

@fiatjaf
Copy link
Member

fiatjaf commented May 3, 2022

Disregarding the irony of posting a Twitter link here, here's the idea I have in my mind for a "key revocation" scheme that would work (although it requires the user to still have a "master key" saved somewhere): https://twitter.com/fiatjaf/status/1479608941197893635

More discussion on https://t.me/nostr_protocol/9790

@fiatjaf
Copy link
Member

fiatjaf commented May 3, 2022

But yes, for users that will not be able to store their keys safely I think there is no solution except trusting those keys to someone else.

@kevinsmith
Copy link

Quoting that tweet thread here for posterity in case those links ever 404:

What about this as a means of simultaneously revoking a compromised key and unambiguously telling everybody to jump to a new one?

If the idea is good credits to
@SomsenRuben, otherwise to me.

@pavolrusnak @EricSirion @takinbrrrr (because you commented on my previous question)

  1. Generate a sequence ABCD... of keypairs
  2. Generate a sequence A'B'C'D'... of keypairs such that A' = tweak(A, B'), B' = tweak(B, C') and so on — using http://bips.xyz/341 key tweaking functions (only replacing the taproot script hashes with the pubkeys in this case)
  3. Start using A' as a public identity
  4. if A' is compromised publish a proof that B' is contained inside A' (via the tweak) which will imply that B is the next key
  5. Start using B'

I'm not sure what this proof would look like? Just publishing A and B' perhaps? That would allow anyone to verify that tweak(A, B') = A, which should be enough, right?

@kevinsmith
Copy link

I like the idea behind it, but that would mean a limited number of keys to begin with since they would all have to be generated before A' could be used.

What about GPG's concept of subkeys and revocation certificates? Create a primary key pair that is only used for creating and revoking subkeys. Use the subkeys for signing all messages. If one gets compromised, publish a revocation certificate and create a new subkey. Reputation remains entact.

@A60AB5450353F40E
Copy link

This might be of interest: https://github.com/bitjson/chip-bcmr

It requires a blockchain for timestamping so order of events cannot be forged, but is basically an off-chain convention for identity management.

An "authchain" is a chain of signatures where each key signs for the next key in the chain (or sign for the same key again) + some metadata. Only the latest key is considered the identity's key, and signatures (using old keys) published after authhead has been updated with a new key should be disregarded.

key0 -> sign(key0, key1+data1) -> sign(key1, key2+data2) -> ... -> sign(keyN-1, keyN+dataN)

Keys can be repeated e.g. key1=key0 if one only wants to sign for some data. The first key is "authbase" and last key is "authhead". The data can be whatever you want - it can be used for a web of trust where you use an authhead to attest that some other authchain is linked to an identity.

@kevinsmith
Copy link

This might be of interest: https://github.com/bitjson/chip-bcmr

It requires a blockchain for timestamping so order of events cannot be forged, but is basically an off-chain convention for identity management.

An "authchain" is a chain of signatures where each key signs for the next key in the chain (or sign for the same key again) + some metadata. Only the latest key is considered the identity's key, and signatures (using old keys) published after authhead has been updated with a new key should be disregarded.

key0 -> sign(key0, key1+data1) -> sign(key1, key2+data2) -> ... -> sign(keyN-1, keyN+dataN)

Keys can be repeated e.g. key1=key0 if one only wants to sign for some data. The first key is "authbase" and last key is "authhead". The data can be whatever you want - it can be used for a web of trust where you use an authhead to attest that some other authchain is linked to an identity.

Correct me if I'm misunderstanding, but wouldn't this just mean that if an attacker got hold of your private key, all they'd need to do is sign for another key and they'd have full control of your identity? The way it works right now, if an attacker got hold of your private key, at least they couldn't shut you out of publishing under that identity.

@A60AB5450353F40E
Copy link

I very much liked this argument:

For lost keys there are some ideas of making a best-effort transition to a new pubkey by notifying followers, but this is a somewhat impossible problem no matter how you tackle it. Somehow, somewhere, some keys must be kept safe. If you lose everything you lost everything. Right?

With that in mind

Correct me if I'm misunderstanding, but wouldn't this just mean that if an attacker got hold of your private key, all they'd need to do is sign for another key and they'd have full control of your identity?

Yes, you understood right.

I'm going to have to go a little off-topic here, going to quote what the author (Jason Dreyzehner) of BCMR had said when publicly discussing the proposal:

If you get hacked and the attacker steals your identity, you have to "revoke" it socially – announce on your website, talk to contacts, get various trusted registries to update to your new authbase, etc. It's much, much easier to keep your identity secure (like with funds), but identities necessarily have a social context, so they're actually more "recoverable" than stolen BCH/tokens.

Another notable use case would be for Tor websites to have persistent identities over time. A lot of hidden services use PGP keys, but if the service gets hacked, you can no longer tell the difference between the owner and the hacker. If you use proper on-chain BCH identities, you can leave control of the identity in multisig or cold storage, and only sign with a "warmer" key. If you get hacked, just broadcast the next update to the identity to revoke the old key (and even broadcast a new .onion address)

An important feature of this system is that historical keys don't matter: if a key holder leaves your company and you rotate them out of the multisig (spend the identity output forward to a new multisig contract), their old key is useless. You could even (as a policy) publish all former keys to ensure old identity outputs are never used for social engineering ("here's an off-chain message that was signed by X's identity!").

Note that we are not using "addresses", only outputs. Every output can be spent only once, and the chain mediates disputes. Bitcoin (Cash) has the verifiable revocation system that PGP always lacked 😄

So, it doesn't have to be 1 user 1 authchain. You could have a cold-storage authchain used to prove that some other authchain is yours. If you lose access to all of it, you'd have to reach out to your social circle, and have them use their authchains to attest that they have talked to you and confirmed that your identity was stolen.

The way it works right now, if an attacker got hold of your private key, at least they couldn't shut you out of publishing under that identity.

But it also prevents you from moving to a more highly-secured key, and maybe one day to a quantum-secure key if your identity auth chain will have lasted that long.

@kevinsmith
Copy link

kevinsmith commented Dec 18, 2022

I think there's something to your point about social attestation and web of trust. That's ultimately the most resilient, scalable solution to verified identity, even before we get to the problem of revoking and issuing new keys. That's how it works in meatspace, anyway. The people I trust confirmed that this person is who they say they are.

I can imagine the need for 2 new NIPs.

The first new NIP would provide a mechanism for someone to vouch for another person's pubkey/username combo (both properties are important). The OpenPGP trust model might be instructive for us here, both in terms of the way public key/user ID combos are endorsed by other trusted keys and the number of first-hand or second-hand endorsements that are required to consider a key trusted. Vouch events should be NIP-03 timestamped, perhaps even with some NIP-13 proof of work to make them at least a little costly. This NIP would be valuable on its own, providing decentralized verification that a key is who they say they are without relying on any authority outside the nostr network.

The second NIP would provide a mechanism for publishing a key revocation and optionally issuing a new one. These should be NIP-03 timestamped as well, and again, perhaps require some NIP-13 proof of work. For followers of the revoked key, clients should add the new key to the contact list. It might be a good idea for clients to display some sort of warning on events published by the revoked key after it was revoked, and to display an "awaiting proof of identity" warning on the new key until the requisite number of vouch events are published. Once the threshold is met, the client should reject all new events published by the revoked key and make it clear that the old and new keys constitute the continuation of a single identity.

We've already got NIP-05 which lets people validate their keys based on their web address. Might be good to have another one or more NIPs that let people validate their keys based on their social identities (essentially formalizing what https://www.nostr.directory is doing). With these plus the 2 new NIPs outlined above, we'd have several paths to identity verification with varying degrees of anonymity. These methods would provide users on the network confidence that the purported identity of the keys is valid.

Thoughts @fiatjaf @catleeball @scsibug @A60AB5450353F40E?

@alvistar
Copy link

Hi, everybody, I just read about Nostr protocol from Twitter messages and went reading immediately the specs.

I immediately saw issues in just using pub key as identities because this is static, as already posted above.

I do suggest to have a look at decentralized identifiers did - https://www.w3.org/TR/did-core/

To look at some promising implementation look for ION - https://github.com/decentralized-identity/ion

And guess what, that”s the same used by https://developer.tbd.website/projects/web5/

anyway, the idea would be including the did in the event instead of the pub key.
Then relay should resolve the did, check which is the current valid pub key for signing and verify the signature.

I was short in writing, but I’ll be glad to reply I’d something is not clear.

@A60AB5450353F40E
Copy link

A60AB5450353F40E commented Dec 19, 2022

Would it be possible to make nostr protocol to support multiple identity protocols? Imagine some interface for an identity module, and using nostr as the default module, but make it possible to add others? You could then have namespaces like nostr:username, bcmr:username, keybase:username, ion:username.

Edit: skimming this DID spec, @alvistar is this "DID" specification trying to achieve this? So, nostr/bcmr/keybase would be different methods in the did:method:id scheme?

Your base proposal relies on keys, however consider the BCMR blockchain protocol which relies on spending TX output0 (whatever its locking script is) - this means you can temporarily delegate posting to a contract and revoke it after some time or other condition. I don't know if you favor a particular blockhain or not, but consider that BCH has covenants (through TX introspection opcodes), so you can, for example, enforce that 1 key from the multisig carries over so you can allow some other key to spend from it to make posts on nostr but it will be unable to "steal" the identity since the other, master, key will always be part of multisig.

@kevinsmith
Copy link

Hi, everybody, I just read about Nostr protocol from Twitter messages and went reading immediately the specs.

I immediately saw issues in just using pub key as identities because this is static, as already posted above.

I do suggest to have a look at decentralized identifiers did - https://www.w3.org/TR/did-core/

To look at some promising implementation look for ION - https://github.com/decentralized-identity/ion

And guess what, that”s the same used by https://developer.tbd.website/projects/web5/

anyway, the idea would be including the did in the event instead of the pub key. Then relay should resolve the did, check which is the current valid pub key for signing and verify the signature.

I was short in writing, but I’ll be glad to reply I’d something is not clear.

I'm familiar with DID and was initially excited about ION, but the more I thought about it, I'm not sure I want a single identity connected to everything across the Internet. There's a real threat of getting added to a blocklist that governments or corporations force network participants to abide by (think OFAC sanctions). If you have a problem in one corner of the Internet, now your identity is at risk everywhere.

That aside, I can't find anything in ION that details how key rotation would work. Is there something specific you could point to regarding DID key rotation?

@kevinsmith
Copy link

Thinking more about it, I don't think we need the first NIP mentioned above, where users vouch for each other. Vouching for various trust areas of a key should be handled by a reputation scheme like nostr-protocol/nips#46. Valuable, but not necessary for the purpose being discussed here.

Instead, we just need users to sign the revocation NIP's event itself, essentially endorsing the claim being made. Yes, this old pubkey is replaced by this new pubkey. That's a far more narrow responsibility without any crosscutting concerns, like also endorsing the pubkey's current username.

Given that discussions surrounding possible NIPs seems to have moved to the nips repo, I'll start a discussion over there.

@alvistar
Copy link

Hi, everybody, I just read about Nostr protocol from Twitter messages and went reading immediately the specs.
I immediately saw issues in just using pub key as identities because this is static, as already posted above.
I do suggest to have a look at decentralized identifiers did - https://www.w3.org/TR/did-core/
To look at some promising implementation look for ION - https://github.com/decentralized-identity/ion
And guess what, that”s the same used by https://developer.tbd.website/projects/web5/
anyway, the idea would be including the did in the event instead of the pub key. Then relay should resolve the did, check which is the current valid pub key for signing and verify the signature.
I was short in writing, but I’ll be glad to reply I’d something is not clear.

I'm familiar with DID and was initially excited about ION, but the more I thought about it, I'm not sure I want a single identity connected to everything across the Internet. There's a real threat of getting added to a blocklist that governments or corporations force network participants to abide by (think OFAC sanctions). If you have a problem in one corner of the Internet, now your identity is at risk everywhere.

That aside, I can't find anything in ION that details how key rotation would work. Is there something specific you could point to regarding DID key rotation?

Hi,
regarding first problem , I think it's a no problem.
In the current things of NOSTR the same will apply using the pub key, they can blocklist all messages coming from that pubkey.
Yes, of course, you can change pub key but then it's like your change your identity. How can you prove you are the same person signing with old pub key? If you manage to prove it to the public, also adversaries will know it an block the new key.

In DID world you can always create a new identifier, this will solve this issue, but it's like your change your identity.
There are no personal data attached to DID, it's just some bytes that are meant to be unique inside a DID method.

And by the way, there are DID schemas that are already made of static private keys.
See https://w3c-ccg.github.io/did-method-key/

Regarding Sidetree ION, key rotation is in the specs. Have a look at
https://identity.foundation/sidetree/spec/#update

You can update all the DID document, including keys.

Summary, using a DID instead of plain pub key:

  • you have the same advantages of what you are doing today - you can just use did-method-key
  • you will be able to support all the advancements in DID
  • you need to simply be able to resolve the DIDs, from an identifier, resolve the pub key and then validate the signature

Happy to get back if needed.

@fiatjaf
Copy link
Member

fiatjaf commented Dec 20, 2022

If clients and relays had to support the full range of DIDs they would be so bloated no one would have ever written any. Even if they only supported the ION scheme that would still be basically impossible unless (I imagine) we used their JavaScript libraries that talk to a central server.

Also it is not a settled matter that ION works as advertised.

You can already say Nostr is using DIDs, if a bare public key is a DID. Nothing prevents you from calling your Nostr public key a did:bip340:<key> or something like that.

@alvistar
Copy link

alvistar commented Dec 20, 2022

If clients and relays had to support the full range of DIDs they would be so bloated no one would have ever written any. Even if they only supported the ION scheme that would still be basically impossible unless (I imagine) we used their JavaScript libraries that talk to a central server.

Also it is not a settled matter that ION works as advertised.

You can already say Nostr is using DIDs, if a bare public key is a DID. Nothing prevents you from calling your Nostr public key a did:bip340:<key> or something like that.

Hi,
this problem has been already risen by web browser company. And the simple way of resolving it is:

https://github.com/decentralized-identity/universal-resolver

Actually this should be external from clients, relays. All you need to implement is single API call to resolve DID to pubkey. That's it. You should not care about details of DID methods.

As analogy thinks about how you resolve a URL to ip address via DNS. You are not implementing DNS Server inside your app.

Regarding ION, that's true. It still need to be more deployed to check if it fulfills its promises. Anyway it's the DID method I found more scalable and with more adoption so far.

And yes, if you instead of just putting pub key, a method is used like did:key: you are already using DIDs. As simple as that.

@vitorpamplona
Copy link

vitorpamplona commented Dec 21, 2022

DIDs seem to be a more stable solution in the long run. The idea is to "outsource" the key management to DID managers. By using DIDs the protocol opens itself up to a lot of new key management schemas. People can activate, deactivate keys as they wish, create multisig controllers, x509 PKDs, etc.

To avoid coding a universal resolver, create a NIP to specify a subset of DID methods (start with just one) accepted by the nostr community. As more DID tooling becomes available new methods can be added.

DIDs are a more interesting solution to verification than the current NIP05, since NIP05 requires a trusted DNS+HTTPS protocol.

@kevinsmith
Copy link

Can someone please point to a simple example implementation of a DID?

@vitorpamplona
Copy link

vitorpamplona commented Dec 21, 2022

I have a DIDWeb resolver in Kotlin here: https://github.com/WorldHealthOrganization/ddcc-validator/blob/main/trust-didweb/src/main/java/org/who/ddccverifier/trust/didweb/DIDWebResolver.kt and in JavaScript here: https://github.com/Path-Check/did-web-resolver/blob/main/lib/DIDWebResolver.js

DID:WEB is basically a json in the well-known directory of a HTTP address, looking like this (3 keys in it):

{
  "id": "did:web:example.com",
  "verificationMethod": [
    {
      "id": "did:web:example.com#key-0",
      "type": "JsonWebKey2020",
      "controller": "did:web:example.com",
      "publicKeyJwk": {
        "kty": "OKP",
        "crv": "Ed25519",
        "x": "0-e2i2_Ua1S5HbTYnVB0lj2Z2ytXu2-tYmDFf8f5NjU"
      }
    },
    {
      "id": "did:web:example.com#key-1",
      "type": "JsonWebKey2020",
      "controller": "did:web:example.com",
      "publicKeyJwk": {
        "kty": "OKP",
        "crv": "X25519",
        "x": "9GXjPGGvmRq9F6Ng5dQQ_s31mfhxrcNZxRGONrmH30k"
      }
    },
    {
      "id": "did:web:example.com#key-2",
      "type": "JsonWebKey2020",
      "controller": "did:web:example.com",
      "publicKeyJwk": {
        "kty": "EC",
        "crv": "P-256",
        "x": "38M1FDts7Oea7urmseiugGW7tWc3mLpJh6rKe7xINZ8",
        "y": "nDQW6XZ7b_u2Sy9slofYLlG03sOEoug3I0aAPQ0exs4"
      }
    },
  ],
  "authentication": [
    "did:web:example.com#key-0",
    "did:web:example.com#key-2"
  ],
  "assertionMethod": [
    "did:web:example.com#key-0",
    "did:web:example.com#key-2"
  ],
  "keyAgreement": [
    "did:web:example.com#key-1", 
    "did:web:example.com#key-2"
  ]
}

Nostr's public key field then would be: "did:web:example.com#key-2", which would resolve to https://example.com/.well-known/did.json and take key-2 in the file.

Users can then delete the key from the json when it leaks.

@vitorpamplona
Copy link

And here's an implementation of the aforementioned DID:KEY method in rust: https://github.com/decentralized-identity/did-key.rs

@vitorpamplona
Copy link

Here's a question. Let's say Nostr uses DIDs instead of public keys, a person's key leaks and they remove it from the DID Document. Shall relays delete all past events that were signed with that key since they are not verifiable anymore?

One can argue we lose the history of that person, but the attacker can insert past events into a relay. So, nothing is reliable anymore.

Maybe this is a good way to implement the right to be forgotten.

@kevinsmith
Copy link

What kind of load this would this add to the relays/clients to have to resolve every single message?

@vitorpamplona
Copy link

vitorpamplona commented Dec 21, 2022

It depends on the method. DID:KEY doesn't need to download anything. DID:WEB does require a download, but people generally implement a local cache for them (refreshing once in a while, similar to NIP05). Other DID methods like DID:IPID for IPFS resolvers or DID:ION, rely on local nodes. Caching is resolved at the node level.

That's why I advocate for a NIP to specify which DID Methods should be implemented by relays and clients.

@alvistar
Copy link

Here's a question. Let's say Nostr uses DIDs instead of public keys, a person's key leaks and they remove it from the DID Document. Shall relays delete all past events that were signed with that key since they are not verifiable anymore?

One can argue we lose the history of that person, but the attacker can insert past events into a relay. So, nothing is reliable anymore.

Maybe this is a good way to implement the right to be forgotten.

Interesting point.
I see 3 way out of it

  1. RIght of be forgotten as you mention
  2. Trust on the relay timestamps
  3. Commitment to a blockchain, like ION is doing. Basically using a blockchain as clock to prove somebody existed at time X

@vitorpamplona
Copy link

I would hope we choose to never have to trust relays.

On 3, the event date is unreliable (the attacker can post in the past). So, you would have to rely on the date of receipt of the event by the relay or by the client. And use that date to ping ION. Resharing an unverifiable event would fail unless you trust the date of the receipt by the relay/client, which feels shaky.

1 is the easiest option.

@alvistar
Copy link

3 is complex.
But it would work in different way. A relay would need to hash all different messages received, compute something like a merkle tree, and inserting the resulting hash in a something like a bitcoin transaction.
Then you can't forge any message "in the past" with compromised keys as even if the signature is valid, there is no proof in the blockchain.
That's more or less the way sidetree (ion) is working.

@alvistar
Copy link

Yes, I have got it. For that can I simply suggest to use BIP32. You have a hierarchical tree of keys and there already hardware secure storage like Ledger supporting it.

@vitorpamplona
Copy link

How does nostr-protocol/nips#103 integrate with the secure chips in people's phones?

Assuming that would be a large section of the user base, the protocol should facilitate the use of those chips. From what I understand, that proposal requires the use of fixed private keys created elsewhere and manually inputted into apps as opposed to creating keys inside the chip and simply adding their public keys to a list of keys the holder controls in a DID Document.

I think the UX should be considered here.

@kevinsmith
Copy link

Yes, I have got it. For that can I simply suggest to use BIP32. You have a hierarchical tree of keys and there already hardware secure storage like Ledger supporting it.

Correct me if I'm wrong, but I don't believe that'll work for us. nostr-protocol/nips#105 (comment)

@kevinsmith
Copy link

How does nostr-protocol/nips#103 integrate with the secure chips in people's phones?

Assuming that would be a large section of the user base, the protocol should facilitate the use of those chips. From what I understand, that proposal requires the use of fixed private keys created elsewhere and manually inputted into apps as opposed to creating keys inside the chip and simply adding their public keys to a list of keys the holder controls in a DID Document.

I think the UX should be considered here.

NIP-26

@vitorpamplona
Copy link

vitorpamplona commented Dec 21, 2022

How does nostr-protocol/nips#103 integrate with the secure chips in people's phones?
Assuming that would be a large section of the user base, the protocol should facilitate the use of those chips. From what I understand, that proposal requires the use of fixed private keys created elsewhere and manually inputted into apps as opposed to creating keys inside the chip and simply adding their public keys to a list of keys the holder controls in a DID Document.
I think the UX should be considered here.

NIP-26

It's a different use case: nostr-protocol/nips#103 (comment)

would we have then a seed + a set of keys from phones? this is getting complicated.

@kevinsmith
Copy link

kevinsmith commented Dec 21, 2022

It's a different use case: nostr-protocol/nips#103 (comment)

I'm not sure I agree with @fiatjaf there. From the NIP itself:

For example, a user could generate new keypairs for each client they wish to use and authorize those keypairs to generate events on behalf of their root pubkey, where the root keypair is stored in cold storage.

would we have then a seed + a set of keys from phones? this is getting complicated.

Either with NIP-26 or DID, a user would have to copy the public key from the phone and save or publish it somewhere signed by their root key to effectively declare that this pubkey is them. In this regard, I'm not sure how it's any more or less complicated either way.

@vitorpamplona
Copy link

Either with NIP-26 or DID, a user would have to copy the public key from the phone and save or publish it somewhere signed by their root key to effectively declare that this pubkey is them. In this regard, I'm not sure how it's any more or less complicated either way.

Correct, but the DID makes both the SEED-key-expiring scheme and NIP-26 obsolete. Why do two complicated things if you can solve both with one?

@kevinsmith
Copy link

SEED-key-expiring scheme

?

@vitorpamplona
Copy link

SEED-key-expiring scheme

?

nostr-protocol/nips#103

@alvistar
Copy link

Yes, I have got it. For that can I simply suggest to use BIP32. You have a hierarchical tree of keys and there already hardware secure storage like Ledger supporting it.

Correct me if I'm wrong, but I don't believe that'll work for us. nostr-protocol/nips#105 (comment)

Read it, but not sure what it does mean.

The overall idea here would be keep a parent key(1) safe in hardware, put the child private key(1/1) in client. Make the pub key(1) used as identity. Use private keys (1/1/x) for signing.

@vitorpamplona
Copy link

vitorpamplona commented Dec 21, 2022

Crazy idea: why not simply create a new event type where the content is a DID Document and a DID:NOSTR:ID resolver that points to that nostr DID Document? In that way, there are no external DID resolvers to implement/cache and we solve the key rotation problem by adding and removing keys inside the DID Document.

It would be an evolution of NIP-26, allowing multiple keys and behavior types.

This can have use cases even outside nostr with a full-blown did method that accesses a key through a nostr relay.

So, a master key with 2 devices is represented as:

{
  "id": "did:nostr:<id>",
  "verificationMethod": [
    {
      "id": "did:nostr:<id>#key-0",
      "type": "JsonWebKey2020",
      "controller": "did:nostr:<id>",
      "publicKeyJwk": {
        "kty": "OKP",
        "crv": "Ed25519",
        "x": "0-e2i2_Ua1S5HbTYnVB0lj2Z2ytXu2-tYmDFf8f5NjU"
      }
    },
    {
      "id": "did:nostr:<id>#key-1",
      "type": "JsonWebKey2020",
      "controller": "did:nostr:<id>",
      "publicKeyJwk": {
        "kty": "OKP",
        "crv": "X25519",
        "x": "9GXjPGGvmRq9F6Ng5dQQ_s31mfhxrcNZxRGONrmH30k"
      }
    },
    {
      "id": "did:nostr:<id>#key-2",
      "type": "JsonWebKey2020",
      "controller": "did:nostr:<id>",
      "publicKeyJwk": {
        "kty": "EC",
        "crv": "P-256",
        "x": "38M1FDts7Oea7urmseiugGW7tWc3mLpJh6rKe7xINZ8",
        "y": "nDQW6XZ7b_u2Sy9slofYLlG03sOEoug3I0aAPQ0exs4"
      }
    }
  ],
  "authentication": [
    "did:nostr:<id>#key-0"
  ],
  "assertionMethod": [
    "did:nostr:<id>#key-1",
    "did:nostr:<id>#key-2"
  ]
}

Which encoded becomes:

{
  "id": <id>
  "pubkey": "did:nostr:<id>#key-0",
  "kind": 666,
  "created_at": 1671551112
  "content": "{ \"id\": \"did:nostr:<id>\", \"verificationMethod\": [ { \"id\": \"did:nostr:<id>#key-0\", \"type\": \"JsonWebKey2020\", \"controller\": \"did:nostr:<id>\", \"publicKeyJwk\": {\"kty\": \"OKP\",\"crv\": \"Ed25519\",\"x\": \"0-e2i2_Ua1S5HbTYnVB0lj2Z2ytXu2-tYmDFf8f5NjU\" } }, { \"id\": \"did:nostr:<id>#key-1\", \"type\": \"JsonWebKey2020\", \"controller\": \"did:nostr:<id>\", \"publicKeyJwk\": {\"kty\": \"OKP\",\"crv\": \"X25519\",\"x\": \"9GXjPGGvmRq9F6Ng5dQQ_s31mfhxrcNZxRGONrmH30k\" } }, { \"id\": \"did:nostr:<id>#key-2\", \"type\": \"JsonWebKey2020\", \"controller\": \"did:nostr:<id>\", \"publicKeyJwk\": {\"kty\": \"EC\",\"crv\": \"P-256\",\"x\": \"38M1FDts7Oea7urmseiugGW7tWc3mLpJh6rKe7xINZ8\",\"y\": \"nDQW6XZ7b_u2Sy9slofYLlG03sOEoug3I0aAPQ0exs4\" } } ], \"authentication\": [ \"did:nostr:<id>#key-0\" ], \"assertionMethod\": [ \"did:nostr:<id>#key-1\", \"did:nostr:<id>#key-2\" ]}"
  ...
}

And after posting that, users can use did:nostr:<id>#key-1, did:nostr:<id>#key-2 to represent did:nostr:<id>#key-0

To modify and remove a key (key-2), just tag the previous one:

{
  "id": <newId>
  "pubkey": "did:nostr:<id>#key-0",
  "kind": 666,
  "created_at": 1671551113
  "tags": ["e", <id>] 
  "content": "{ \"id\": \"did:nostr:<id>\", \"verificationMethod\": [ { \"id\": \"did:nostr:<id>#key-0\", \"type\": \"JsonWebKey2020\", \"controller\": \"did:nostr:<id>\", \"publicKeyJwk\": { \"kty\": \"OKP\", \"crv\": \"Ed25519\", \"x\": \"0-e2i2_Ua1S5HbTYnVB0lj2Z2ytXu2-tYmDFf8f5NjU\" } }, { \"id\": \"did:nostr:<id>#key-1\", \"type\": \"JsonWebKey2020\", \"controller\": \"did:nostr:<id>\", \"publicKeyJwk\": { \"kty\": \"OKP\", \"crv\": \"X25519\", \"x\": \"9GXjPGGvmRq9F6Ng5dQQ_s31mfhxrcNZxRGONrmH30k\" } } ], \"authentication\": [ \"did:nostr:<id>#key-0\" ], \"assertionMethod\": [ \"did:nostr:<id>#key-1\" ] }"
  ...
}

Created at guarantees the order of key changes.

@kevinsmith
Copy link

That's funny, on the other thread I was just about to suggest that you take a stab at something like this. I'm not familiar enough with DID specs to understand everything that's going on here yet. But at a high level, this feels like the right way of going about it. Just make it an event type and everything remains self-contained to the network. 👍

I'll dig into this to better understand it.

@vitorpamplona
Copy link

vitorpamplona commented Dec 21, 2022

The only issue is the consensus in allowing DIDs in the "pubkey" attribute. I would migrate all non dids to a did:bip340: as previously described and allow a new did:nostr in the field.

@kevinsmith
Copy link

It would be an evolution of NIP-26, allowing multiple keys and behavior types.

Would be interested to hear @markharding's thoughts on that.

The only issue is the consensus in allowing DIDs in the "pubkey" attribute. I would migrate all non dids to a did:bip340: as previously described and allow a new did:nostr in the field.

Backwards compatibility is highly prized around here, so might just have to stick with the recommendation that the lack of a did prefix means the current method prevails.

@kevinsmith
Copy link

I'd suggest this new event with a DID document should be a replaceable event and be timestamped.

@csuwildcat
Copy link

Eating-Popcorn-Soda.gif

@vitorpamplona
Copy link

Backwards compatibility is highly prized around here, so might just have to stick with the recommendation that the lack of a did prefix means the current method prevails.

Apologies, that's what I meant.

I'd suggest this new event with a DID document should be a replaceable event and be timestamped.

Good enough. No history of DID Docs but maybe there is no need for a historic recollection in nostr.

@alvistar
Copy link

@csuwildcat Hey, I have been silent, but I am following you and your work since long time.
It's an honour to see you here.
As you see, I am spreading the word of DIDs and ION, no necessity to reinvent the wheel every time.

How is going at Block? Between one popcorn and another, it would be interesting to hear your point of view of nostr... btw I jumped in the discussion after I see Dorsey tweets...

@csuwildcat
Copy link

@alvistar it's nice to see other proponents of DIDs and related technologies showing up in places like this. I don't want to speak on this group's project to maintain a level of respect I hope they'll reciprocate (past interactions haven't been great). Our work at Block on Web5 is going well, let's catch up on a different medium about it.

@vitorpamplona
Copy link

Correct me if I am wrong, but the pubkey rotation means you cannot decrypt regular NIP04 PMs with the old address anymore, right?

In that sense, rotating keys through nostr-protocol/nips#103 solves a key leak issue (discard the past), but it doesn't help the implementation of a regular key rotation policy where the old key is still safe and thus PMs should be visible to the user and valid. It's also not helpful when using different private keys per device. PMs will not be able to be read by different devices even though they are safely controlled by the same user.

@fiatjaf
Copy link
Member

fiatjaf commented Dec 22, 2022

Good point.

@kevinsmith
Copy link

kevinsmith commented Dec 22, 2022

Migrated the comment originally posted here to nostr-protocol/nips#116

@burdiyan
Copy link

burdiyan commented Jun 7, 2024

The problem of key delegation and rotation has been on the back of my mind for a few years now, and indeed there seem to be no perfect solution.

For a system like Nostr, where each individual event is "free-floating" without anything that would aggregate them into a single "dataset" defining causal relationships between events in a verifiable way, any key rotation or delegation/revocation scheme is prone to the problem of users not knowing what to do with old data when they receive the revocation event, as they might later receive some old event that is signed by the revoked key while it was still valid.

Even if you delegate signing permissions from some master key to children keys, you can still compromise or lose access to your master key, so being able to rotate the master key would also be a nice to have.

GNUNet has some interesting ideas about revocation: when they generate the key they also start mining a revocation certificate in the background, with a PoW of difficulty that would take a few days to mine. Then you are supposed to broadcast this revocation if the key gets compromised. Still, I believe for the best user experience you'd have to broadcast the dataset of "still-good" existing data signed by revoked key, so that if the attacker publishes new data with old timestamps, people would have a way to verify that by looking up in this dataset. Of course all of this complicates the entire procedure quite a bit (as any other alternate solution does).

Overall, Web of Trust and social proofs (like publishing keys on web domains, and social media platforms) seems like the most pragmatic solution to the problem.

@vitorpamplona
Copy link

There is just no way to develop a cryptographically secure migration scheme from the way current keys are generated/used.

If we have something like nostr-protocol/nips#1056 implemented, then other NIPs can offer seed-based revocation schemes or ratchet mechanisms with brand-new key generation structures.

We have to start offering ways to migrate current keys into new ones. Users should be rotating keys periodically.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants