-
Notifications
You must be signed in to change notification settings - Fork 597
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
Stateless key rotation using a series of hidden commitments #103
Comments
I believe the series of keys |
It's better than what we have now, certainly. And more reliable than the social endorsements I described in #101. But it requires a complete set of possible keys to be generated before use, which means there's a chance of running out at some point. (Though I guess you could generate a million keys and it'd be extremely unlikely that you'd ever use those all up.) A user would need to store all possible keys along with their seed in that super-secure offline vault, right? It would be great to be able to generate an indefinite number of private keys that could all somehow show proof that they're derived from the same master, but there are people far more adept at cryptography than me who haven't come up with it, so I'm assuming it's not possible. |
No, I believe you would only have to store one seed and from that you can derive all the keys. You would have to pregenerate all before using the first though, but I don't think that is a big problem. By the way, this is not supposed to solve all the issues with key management in the entire world. You still have to take care of your keys, you can't just rotate every hour to a new key, that will not scale well. |
Since the real problem here is the sensitivity of a single, precious private key in an ecosystem where one is basically required to give it out to publish to the protocol, maybe NIP-26 is the solution? By the way, just wanted to thank you for everything you've done to make nostr possible, @fiatjaf. Digging into it this year has been exciting. It's taken me back decades and made it feel like it did when I was discovering the young Internet, full of possibilities and fun problems to solve. |
@markharding any thoughts here? |
I think the use case for NIP-26 is different and these two solutions can live together. My impression from NIP-26 is that you're not meant to use one different pubkey for each micro Nostr app you decide to try, but mostly for when you have to handle a private key to someone else or for a use case like Minds in which they control the key already and you're just adopting it. But maybe this wasn't very clear and other people have other ideas. |
When discussing the problem of private keys being compromised on a podcast recently, @jb55 pointed to NIP-26 as a likely solution. Maybe he can provide his angle on it here. |
After sleeping on it, I think @fiatjaf's comment about several solutions working together is spot on for secure key management. This feels like a good set:
I'd personally be comfortable with this proposal as long as I could generate an absurd number of keys beforehand such that I could never realistically use them all up. |
IMHO, this feels like an overly complex cryptographic scheme (requires cryptographic review to make sure it's doing the right thing in each new language) which when combined with NIP-26 (two seeds delegating derived keys from one another to constantly verify) and NIP-05 (verification using trusted protocols DNS+HTTP) makes account management of Nostr quite complicated. As more and more NIPs are created to solve the details of key management/verification/recovery, I see more and more reasons to kill them all in favor of a simpler DID process, reusing tools that already exist for other applications. |
Was just about to suggest you take a stab at creating a nostr DID method that could live in an event, but you beat me to it! https://github.com/nostr-protocol/nostr/issues/45#issuecomment-1362102659 |
If I’m reading the notation correctly, this scheme:
Requires that you reveal the compromised key. As mentioned earlier, it also requires that you generate keys upfront because each key contains a commitment to the next key (iow, you need to generate C before you generate B, so you need a finite set generated upfront). an alternative approach would be to have some reference to a “revocation key” (maybe in a NIP-5 identifier, maybe in some other construction). That revocation key can sign a message that says “this key shouldn’t be used anymore, use this one instead”. One nice thing about a scheme like that is that the future key could have its revocation key based on the same seed, or different entropy all together. Users still have to manage keys, but if you wanted to, you could generate both signing keys and revocation seed from the same seed with different derivations, or you could keep them separate. |
It doesn't require you to publish the compromised key. For example, If the keypair It does require you to generate keys upfront, but that isn't a big issue since you can generate a thousand keys from a single seed and store just that seed. Having a separate revocation key is worse because Nostr doesn't have this key infrastructure that ties one key to the other and so on, everything is stateless, each event is supposed to be valid by itself. In this scheme the revocation event would be just an event published by |
That sounds false since you will have to keep up with revocations. That is a stateful service. Events using a revoked key (which come later) will not be valid anymore. Clients and relays will have to manage a stateful active key service. |
Not at all. If A is following B1 and gets a revocation event from B2 then A stops following B1 and starts following B2. Now A won't be getting new events from B1 at all. If A sees an event from B1 on the open network somewhere (e.g. as a reply to someone) they will treat that as a normal unknown key. The only stateful thing is the list of people you're following, but that already exists, so no changes here. |
Oh so you are saying that relays still accept B1 as a valid key as normal, even though the relay got confirmation from the owner the key has been rotated. Meaning if the key was stolen, relays will continue to work with the attacker. Should stored NIP04 PMs just disappear as well? Or how would clients know messages aren't being inserted in the past? |
DMs are a problem none of the schemes I've seen readily address, including the DIDs discussed elsewhere. |
First let's assume keys won't be revoked and rotated all the time. This suggestion here is just for catastrophic events. I am not trying to say everything will be awesome when a private key is compromised. Yes, relays can still accept the B1 keys even if they are now controlled by the attacker. Relays may want to keep track of revoked keys and block those, but that will be their choice and the protocol shouldn't specify that. If clients are storing old DMs somewhere in a local database or whatnot they can still keep those and assign them to the new user so the DM history looks the same. If they are relying on public relays to store the DMs or on a private backup relay they can do other tricks to prevent the attacker from inserting new DMs in the history, but again this is client's implementation choice. The simplest behavior of automatically nuking or hiding DMs once you get a revocation key is good enough to me too. It will save lives. |
The way DIDs address DM is by blocking access to the key. Messages clients have stored remain visible (with a cached key if the client software must reverify the past). But since the key is not in the DID Document anymore, the key cannot be used to receive or send new signed messages by anyone (even if you have a cached key somewhere). The protocol fails the cryptographic verification because it simply cannot find the key. There is no way for the attacker to insert past messages because clients (and relays) use the receiving date time, not the date time inside the payload. |
So basically the same UX, except DIDs have a central registry clients can query to fetch all this data, so the implementation is arguably simpler. |
Not necessarily a central registry. The key difference is the control over access to the keys. In Nostr, the key is itself in the payload. If it leaks, there is no way to control it. In a DID scheme, everyone agrees the author has control and can nuke the key from existence. The proposed scheme here would be similar to DIDs if relays nuked old keys (and blocked the new use of old keys) as soon as they see a rotation. Clients can freeze PMs after they receive a rotation and they have to block the new use of old keys as well. |
I don't think this is realistic. Passwords should be changed from time to time in any app. Corporate policies always have some periodic rotation (90 days for health care). If you use a T2 chip with the key being generated by the phone in such a way that it never leaves the chip, the private key will need to change every 1-2 years on average when the user changes the phone. |
That means Nostr keys shouldn't be treated like passwords or subject to corporate policies, they should be safely stored like Bitcoin keys. In any case if this scheme would be implemented and people would rotate keys every month it would still require the user to keep a seed in a safe place, so the point you just raised would apply regardless. |
People might be required by law to rotate private keys. For instance, if I use nostr professionally ... say to talk to my customers (I sell medical devices)..., I must have a periodic key rotation policy in place by law. So, what's the best security scheme here? Right now, I have the same private key in 15 nostr apps at the moment, between my laptop, my desktop, and my phone. It feels wrong reusing the private key. It probably already leaked even though I am trying to be careful. I only expect the number of apps to grow as micro apps are becoming more common. Maybe the best is to have the seed, get the current key, and then delegate new keys to 15 devices via NIP-26? If the current key leaks, get a new key and invalidate all 15 delegations. If one of the 15 devices leak, rotate that key. Keep the seed completely offline, in a hardware device. The generated private key though will need to be transferred to a laptop for use, or we need to find a hardware device that is able to sign nostr transactions directly on device. In a DID world, I would have an account with a DID provider (cold or hot) and insert/delete new public keys into that record. So, the "seed" that needs protection is the key to access the DID provider and change the DID Document (probably in a hardware device). In this case, since private keys are never visible or transferred anywhere, it's harder to have a leak. |
I understand that you are trying to achieve the perfect UX and security, but really that is not possible. It can't be done on Nostr. NIP-26 delegation is not a silver bullet, it can't be used indiscriminately. This suggestion here is also just a best-effort mitigation strategy that fits well with the rest of the protocol. Unless there is a hidden breakthrough somewhere I don't think we will be able to get the perfect key management UX here. DIDs do not solve that either. Also I don't see think Nostr should care about the law. Imagine if Bitcoin had cared about the law? It would have been a protocol for money to printed by the central banks and fully controlled by these. |
I am not searching for the perfect UX. We are not even close to discussing the perfect UX. This is just the very, VERY basics of key management. What people need to do to use Nostr in a safe way in practical reality. DIDs don't solve everything, but they do solve the issue at hand (key rotation) extremely well. |
why can't you do this? A' = A + hash(A||B)*G this allows for generation of keys without having to compute an entire chain with a finite amount of keys. other methodto overrule |
Because it requires publishing the private key |
The protocol doesn't need to follow NIP05. That's the key part. The client would not follow a public key. The attacker can change the NIP record to whatever it wants, it won't matter. The app will follow whatever key is listed in NIP05 identifiers chosen by the user. It will follow all keys listed there. So if a user has a key per device, all keys are being followed from a single follow(NIP05) in the app. |
my head is twisting right now.. if I understand correctly what you're saying is you're fetching all names from the user kind-0 nip05 field domain json? but that only works if the provider is the same user, otherwise you will get random keys. what am I missing? |
Point taken. We could change NIP 05 to be an array of keys per user. But maybe it makes sense to add all keys if the client's user only types the main domain name. It would work for those of us that are using our own domain to place our keys. |
This still doesn’t get past the problem of requiring users to trust a third party to authorize using their identity. Even if you hold the domain, you’re still trusting your registrar and ICANN not to rug you. |
Sure, but only because right now NIP05 requires DNS/HTTP (which is a major flaw). It doesn't need to. The same JSON can be downloaded directly from IP, placed behind Tor, and maybe even from a lightning node. It can be a nostr event from another Nostr pubkey, where the latest payload's contents are the exact JSON as defined in NIP-05. Or, It can even be a converted DID:ION address if people want it to be. It's all the same. No DNS/HTTP is required. The strongest point of this idea is that it separates the cryptographic verification of a message (which continues to be the same and allows the simplicity of the protocol to shine) from the need to identify if a given public key is currently trusted by the client's user. Those are two very different goals. Either way, the fact a client or a relay can verify a package doesn't mean much. Neither in this new idea nor in the original hidden-commitments proposal. Additional work to identify if the used public key is trusted by the user must be made. And since everybody is already using NIP05 for that, maybe that is the best place for it. |
Interesting read! Can I sum up the requirements here so it is clear to me, and getting everything right
Interestingly there have been recently a keyring protocol addressing a similar issue: one time use of bitcoin wallet: BIP32/BIP43/BIP44 implemented in hardware (ledger, coldwallet, trezor...) as well in many cryptocurrencies tooling. However reusing this as such is not enough: most of the time this keyring is just for allowing yourself followup your wallets and nobody else. Here we need everybody able to "guess" which key is the next one. Note the BIP32 is not an easy beast for security. If you want to open for public guessing of the next pubkey, you need a non hardened derivation, leading to catastrophic failure if only one of the private key being compromised Now to the proposed protocol, here is the attack model:
One interface for the hacker is to delay the propagation of the revocation. Worse, with time revocation database are getting huge, so this would make hard to query and open up to other attack scheme via simple DoS so it make hard to keep 'old' keys so an attacker could reuse the one that are dropped. In this case publishing could become a nightmare to manage. I would advocate for some revocation time to prevent this, ie keys are always distributed with (npub, expiration). Ok now we are just reinventing the wheel (except the rotation algorithm), because this is exactly what PGP has done with revocation, already signed revocation (if you lose your private key), web of trusts WKS (you know things like https://intevation.de/.well-known/openpgpkey/hu/it5sewh54rxz33fwmr8u6dy4bbz8itz4) etc... and it did not work because this is hard for the user to manage. My point here is we need to be better if we don't want to have the same issues. So in a nutshell my minimum requirement for a good keyring would be:
If we see this in a different point of view:
looks like a pretty hard challenge huh. |
No you don't. assuming we use this
how does proving
|
@Semisol Sorry, I read your proposal backwards. So in your scheme you start with Actually no, you can generate So when you're moving to |
@fix I am not sure I follow the revocation list stuff. Why do we need revocation lists? We don't. The relays themselves are the revocation lists. Or there could third-party services hosting these revocation events, but these services don't have to be trusted, so they're in the same category as the relays. Also this is not intended to be a standard routine key rotation procedure, it is just to minimize damage for when keys are compromised -- and make it so the next key is deterministic and negate the possibility of an attacker announcing its own key as the next one from the compromised key. Also two corrections:
|
On revocation list:
on the two corrections:
|
@Semisol I think it your scheme is broken because if to revoke
Then anyone can immediately publish
And say that the next key is |
lol, was about to post this way of hijacking the key stream |
I'm new and I have a lot of catching up to do so I hope it's ok if I join in on the discussion. What if instead of trying to revoke or cycle keys bitcoin is used as a clock. Using your seed you can generate a key and sign a message with the public key and block height at the time of signing which you use as your ID. If you would like to broadcast a new key include your old pub key, new pub key and signature of the new pub key and current block height. That way if your key is compromised it has a global "date" so clients can easily distinguish which events are valid. One last thing with respect to that is if a key was compromised at block 769082 and the victim finds out at 769092, the clients interacting with the attacker now has now been communicating for the span of time it took to complete 10 blocks. If the victim knows the last time they shared a valid message sent from them then they can figure out what the blockheight would have been and use that block height to generate the new signature which would invalidate all the prior messages keeping dm's and the like in tact. The extreme of this could be using a private key with the ability to spend a btc utxo's. In that case you can do the same thing but include proof of ownership which would allow you to revoke a whole set of keys when spending btc to another wallet if that was something clients wanted to incorporate. Thank you all for sparking this flame. It's exciting to be here and I hope I can help |
hi @makinTheStuff
On the security side, all users SHOULD check bitcoin every time they verify a signature, so in the end, everybody SHOULD have a bitcoin node too. If all these aspects are respected, yes this is likely the most secure way of dealing with your keys. There are interesting projects going on leveraging this technique, the main one being ION backed by microsoft which is a protocol doing exactly that (you can check one of my impervious key there) and for general user you can check Impervious browser built around it. |
This creates a dependency on Bitcoin which is undesirable. |
Agree. |
I'm torn on the issue of Bitcoin dependence. I understand wanting to avoid the image of nostr being just a hangout place just for Bitcoin enthusiasts, but the Bitcoin blockchain is a unique resource in the history of the internet, if you're looking for an objective source of truth. And this problem is just screaming for an objective source of truth somewhere in the solution. |
Wherever things land, I'm ready to build air-gapped Nostr key generation/delegation/rotation/signing into SeedSigner. Currently just playing with key generation and export, but helping to build out python-nostr so I'll have the necessary tools ready to just plug in and go. |
hi there, I have a proposal: I agree that until we are dependent on DNS we're kinda stuck even with DIDs... also we always forget about things like the nonce used for signing messages etc.... we have no control over the client's source code and whether they are doxing or not Let's have 2 npubs per profile. The master Npub is generated (preferably on airgapped seedsigner) and is the only one that is allowed to modify the master npub associated with the profile. The child npub is the one used for everyday signing and so on If/when the child npub is compromised simply publish a profile update with new master and child npub. optionnally stamp blockheight+UTC after which the user wants to signal compromission ideally the Nostr note publishing the new profile update should be QR coded from airgapped device I know it's not perfect but it makes it already that much harder to break |
@test7813 just want to correct this statement a bit: "we are dependent on DNS we're kinda stuck even with DIDs" - robustly designed DID Methods already exist that are not dependent on DNS or other centralized intermediaries, and address the various ID-related issues raised here and on other threads. |
I think the most difficult part of this proposal is that cryptographic protocols for key rotation need at least one unit of state storage. This then leads to a problem of synchronization, that if one element of state must be stored, then it is no less practical to use a larger state store that uses the most efficient number of elements. This is a clear situation where the use of blockchain anchoring or other method of reliable, complete broadcast becomes necessary. The Nostr protocol in general has a problem that by its nature it must have weak consistency and some problems, like this one, require elements that have strong consistency. I think the need for a distributed, consistent database to back Nostr is pretty clear, it's jujst a question of which one to use. As a bitcoin maxi, I want to say "not bitcoin" because the data is really too ephemeral for this use case to be worth the block space. edit: By the way, DMs contain encrypted payloads and could include a fresh new public key to reply on, if clients were able to store this state. |
Couldn't we just publish a proof of N backup keys as an open timestamp? Then when a compromise happens there is some nip that clients and relays follow to handle the identity replacement? |
I think we need to go with 2 solutions:
Most of the current keys might have already been leaked. This means that any automated key migration solution needs to take into account the idea that attackers can have a database of keys ready to create these open timestamp events BEFORE the owner does. This generally means the attacker can use the migration system to capture the key and, because of that, it's not a good migration scheme. But if we can separate these solutions, the Web of Trust migrations can take care of the current uncertain state of Nostr keys WHILE new key derivation schemes can automatically provide trustless ways to migrate. Current users will swap their keys using Web of Trust migrations and once the new key is seed-derived (and the seed remains offline), we will have a more secure environment to run automated key rotations at will. |
So the idea here is that Nostr apps could generate a series of keys for each user, all based on an initial seed. And they would show the seed and the first key to the user. The seed must be kept safe at maximum security, while the user can be a little more relaxed with that first key.
Then if for any reason the first key was compromised the user could use the seed, maybe in a very safe fully-offline device in a vault inside a nuclear bunker, and use it to get the second key, then use that second key to sign an event telling all interested parties that the first key was compromised.
The event from the second key alone would be sufficient for all supporting clients to verify that that second key was indeed owned by the same person who owned the first key, and that the first key was irrevocably compromised. Upon seeing this event all clients could easily automatically stop following the first key and start following the second, and (if this is applicable) even move their internal state (chats, metadata and so on) from the first key profile to the second key profile.
Once upon a time I asked @RubenSomsen to come up with a way to do this and he invented the following scheme:
A more naïve scheme would be to just make
A = hash(B)
,B = hash(C)
and so on (or something like that), but this would require revealing the compromise private key, which we don't want to do since that could increase the damage. Other, safer methods would involve doing zero-knowledge proofs, but the scheme above seems to be much better since it is simple and clients can probably implement it easily.Even for clients that do not implement it, a third-party "compromised keys directory" web app could be created so users could manually go there and use that to verify if someone was compromised or not.
The text was updated successfully, but these errors were encountered: