nips
nips copied to clipboard
Stateless key rotation using a series of hidden commitments
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:
So what if the secondary key is simply committed inside the primary key similar to a tapscript. Opening the commitment proves the relationship.
It's basically:
A' = A + hash(A||B')*G B' = B + hash(B||C')*G C' = C + hash(C||D')*G
etc.
A' can be overruled by B' by revealing A and B', which allows the commitment to B' inside of A' to be verified. A, B, and C have completely independent entropy
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.
I believe the series of keys A
, B
, C
and so on could be generated using something like NIP-06 (i.e. BIP-32) and from the first, say, 1000 keys (or less, I don't know what is the ideal number here), the sequence A'
, B'
, C'
and so on could be generated.
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:
- Community norms to only use a NIP-07 extension for web clients and never, ever enter your private key into a web app because it's impossible for a web app to guarantee protection from XSS. (If the app can see your private key, an attacker can too.)
- NIP-26
- This proposal + a new NIP for announcing key rotation
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:
A' = A + hash(A||B')*G
B' = B + hash(B||C')*G
C' = C + hash(C||D')*G
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 (a', A')
is compromised you can revoke it by publishing A
and B'
(and then everybody assumes B'
is your next key). The compromised private key is a' = a + hash(A||B')
and that is never revealed because you never reveal a
.
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 B'
with the contents of it being A
, and from that alone everything would be settled. Very easy to handle by clients.
everything is stateless, each event is supposed to be valid by itself
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.
First let's assume keys won't be revoked and rotated all the time.
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 B' = B + hash(B||C)*G
this allows for generation of keys without having to compute an entire chain with a finite amount of keys.
to overrule A'
, you publish from B'
the value of A
, B
and hash(B||C)
. you can verify A' = A + hash(B||C)*G
and B' = B + x*G
other method
to overrule A
, you publish from B
the value of A
and hash(B||C)
. you can verify A' = A + hash(B||C)*G
and the next key becomes B' = B + x*G
why can't you do this?
Because it requires publishing the private key a'
when you want to revoke it.
Crazy idea: what if users don't follow keys, but the NIP05 identifier? Everything else stays the same.
In that way, users can change their keys at any time and UIs would automatically change to the new one. Chats would be migrated since they follow the NIP05, not the pubkey. Followers are marked by NIP05, not by keys.
An extremely simple solution that uses existing tools many apps already support.
Since the hidden commitments proposal will not be implemented by relays (to block misuse of rotated keys), and will have to be implemented by clients, why require more complexity on their side if they already have a working solution?
first, the protocol has no way to follow nip05, and even if it did, the provider could maliciously change the pubkey with another one and send you spam. the authority is the pubkey, not the verifier.