-
Notifications
You must be signed in to change notification settings - Fork 308
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
Comments
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? |
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. |
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. |
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 |
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. |
Quoting that tweet thread here for posterity in case those links ever 404:
|
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. |
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.
Keys can be repeated e.g. |
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. |
I very much liked this argument:
With that in mind
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:
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.
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. |
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? |
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. I was short in writing, but I’ll be glad to reply I’d something is not clear. |
Would it be possible to make nostr protocol to support multiple identity protocols? Imagine some interface for an identity module, and using Edit: skimming this DID spec, @alvistar is this "DID" specification trying to achieve this? So, nostr/bcmr/keybase would be different methods in the 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. |
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? |
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. |
Hi, In DID world you can always create a new identifier, this will solve this issue, but it's like your change your identity. And by the way, there are DID schemas that are already made of static private keys. Regarding Sidetree ION, key rotation is in the specs. Have a look at You can update all the DID document, including keys. Summary, using a DID instead of plain pub key:
Happy to get back if needed. |
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 |
Hi, 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. |
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. |
Can someone please point to a simple example implementation of a DID? |
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 Users can then delete the key from the json when it leaks. |
And here's an implementation of the aforementioned DID:KEY method in rust: https://github.com/decentralized-identity/did-key.rs |
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. |
What kind of load this would this add to the relays/clients to have to resolve every single message? |
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. |
Interesting point.
|
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. |
3 is complex. |
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. |
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. |
Correct me if I'm wrong, but I don't believe that'll work for us. nostr-protocol/nips#105 (comment) |
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. |
I'm not sure I agree with @fiatjaf there. From the NIP itself:
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? |
? |
|
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. |
Crazy idea: why not simply create a new event type where the 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 To modify and remove a key (key-2), just {
"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. |
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. |
The only issue is the consensus in allowing DIDs in the "pubkey" attribute. I would migrate all non dids to a |
Would be interested to hear @markharding's thoughts on that.
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. |
I'd suggest this new event with a DID document should be a replaceable event and be timestamped. |
Apologies, that's what I meant.
Good enough. No history of DID Docs but maybe there is no need for a historic recollection in nostr. |
@csuwildcat Hey, I have been silent, but I am following you and your work since long 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... |
@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. |
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. |
Good point. |
Migrated the comment originally posted here to nostr-protocol/nips#116 |
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. |
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. |
Howdy!
I was just skimming over your plans for nostr. I might have missed it, but do you have plans for:
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
The text was updated successfully, but these errors were encountered: