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

NIP-102: Subkey Attestation #1450

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

ynniv
Copy link

@ynniv ynniv commented Aug 27, 2024

This NIP defines a way to separate identity from authentication using hierarchical deterministic (HD) keys. This allows people to use one key pair to issue independent key pairs for different apps. If a key is compromised, the root key pair can publish an event revoking that key as of a specific time.

HD keys (BIP-32, BIP-39, BIP-44) provide a means of creating new key pairs from a parent in such a way that the new pubkey can be verified to be a child of the parent's pubkey. A message signed by this new key can be rapidly and locally verified as an authentic subkey of the claimed parent.

@bezysoftware
Copy link
Contributor

FYI NIP-100 is already used in this PR: #1411

@ynniv ynniv changed the title NIP-100: Hierarchical Deterministic subkeys NIP-102: Hierarchical Deterministic subkeys Aug 27, 2024
@ynniv
Copy link
Author

ynniv commented Aug 27, 2024

FYI NIP-100 is already used in this PR: #1411

Moved to NIP-102

Copy link
Member

@staab staab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a benefit to using HD keys rather than just arbitrary sub-keys? The reason I ask is the sub-keys have to be published anyway, so the deterministic connection doesn't seem to matter much.

The challenge with this sort of thing has always been the soft-fork nature of making clients aware of relationships between keys. Not a terribly difficult problem, but it would be some time before this could be really usable.

102.md Outdated
Comment on lines 25 to 37
The parent key can publish a `Kind 10102` event for subkey management. This event lists subkeys that have been revoked, as well as those that are currently active, and a preference for how valid subkeys that are not listed should be treated. This is only a preference, as it may not always be immediately available to clients and relays.

Content:
```json
{
"keys": {
"<hex-subkey0-pubkey>": { "active_at": "<epoch-timestamp>" },
"<hex-subkey1-pubkey>": { "active_at": "<epoch-timestamp>", "revoked_at": "<epoch-timestamp>" },
"<hex-subkey2-pubkey>": { "active_at": "<epoch-timestamp>" }
},
"default_policy": "allow"
}
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd model this using single-letter tags so that they can be indexed. That would then obviate the need for adding an account tag to every event published by a subkey, since client could just fetch any 10102s for pubkeys they don't recognize.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that I understand how using a single letter tag would change whether or not we need a tag with the accounts npub. My suspicion is that the pipeline stalls caused by constantly fetching new 10102s would outweigh the space savings. Is the efficiency of single-letter vs word tags due to how relays are optimized?

@ynniv
Copy link
Author

ynniv commented Aug 28, 2024

HD subkkeys don't strictly need to be published beforehand. Any key can claim to be a subkey of an npub, but only those actually derived from it will validate. This lets you take a trust-but-verify (that it hasn't been revoked) approach. For non-destructive/competitive events, we can first assume that a key has not been revoked, then get around to verifying it later (same as purple checks). For replacements and deletes, we would want to verify first.

As far as client and relay adoption goes, if there are no major objections to this proposal I'll look into how to implement it for key projects.

@melvincarvalho
Copy link

I have been using subkeys for a while, and I love them. Subkeys should be a standalone nip, and not coupled to use cases such as user locking. While I personally prefer open ended subkeys (maybe that's still possible, here?) I can live with this solution as a way of working. I do already have subkeys to my master key, so it would be nice if it would be possible for me to link those still, rather than have to start a new hierarchy.

@alexgleason
Copy link
Member

I wish I had done this for the Mostr Bridge.

@vitorpamplona
Copy link
Collaborator

Instead of a list of active keys why not just do a bloomfilter (or equivalent structure)? In that way, users don't need to expose all keys their master accounts use. Yes, people could still collect ALL events and filter all account to a given key, but events that are protected in some relays or inside encrypted wraps won't be public. Some keys (your health care data keys, for instance) will never be used in public.

The active dates don't really work. If they are based on created_ats then the attacker can post in the past. If they are based on the receipt date, they are not needed (you can just remove keys from the list). Either way, if a key has leaked, nothing from that key can be trusted. There is no way to separate the author's original event set from an attacker's set based on dates because the precise date/time the attacker got the keys is unknown.

@ynniv
Copy link
Author

ynniv commented Aug 28, 2024

Instead of a list of active keys why not just do a bloomfilter (or equivalent structure)? In that way, users don't need to expose all keys their master accounts use. Yes, people could still collect ALL events and filter all account to a given key, but events that are protected in some relays or inside encrypted wraps won't be public. Some keys (your health care data keys, for instance) will never be used in public.

The goal of active is really to facilitate DM's. If you want to be able to read them using multiple clients, they'll need to be sent in a way that any of those clients will be able to read them (so, individually, or as some type of group messaging). Any valid subkey claiming to be part of the account (that hasn't been revoked) would still be part of the account.

The active dates don't really work. If they are based on created_ats then the attacker can post in the past. If they are based on the receipt date, they are not needed (you can just remove keys from the list). Either way, if a key has leaked, nothing from that key can be trusted. There is no way to separate the author's original event set from an attacker's set based on dates because the precise date/time the attacker got the keys is unknown.

Agreed. I'll clarify that these attributes are non-authoritative, but I think it's good to include some record of the expected timing.

@ynniv
Copy link
Author

ynniv commented Aug 28, 2024

I do already have subkeys to my master key, so it would be nice if it would be possible for me to link those still, rather than have to start a new hierarchy.

If they were created using the same derivation (m/44'/1237'/0'/<subkey-index>/0), then clients and relays that support this NIP should automatically begin treating them as part of the subkey0 account.

@vitorpamplona
Copy link
Collaborator

The goal of active is really to facilitate DM's.

Make an "active" set just for DMs, then. DMs are not going to be in every client. There will be two/three orders of magnitude more keys than the ones used by DM-enabled clients.

I'll clarify that these attributes are non-authoritative, but I think it's good to include some record of the expected timing.

Sounds like wishful thinking. There is nothing anyone can reliably do with those dates. It only adds insecurity to the proposal.

@ynniv
Copy link
Author

ynniv commented Aug 28, 2024

Make an "active" set just for DMs, then

What would that look like? Renaming active to dm?

Sounds like wishful thinking. There is nothing anyone can reliably do with those dates. It only adds insecurity to the proposal.

That's fair. If an account wants to be able to revoke a key while preserving historical content, they could aggressively rotate them. Aside from the hassle of rotating keys securely, there's nothing that should prevent someone from using a new subkey for every message.

@vitorpamplona
Copy link
Collaborator

vitorpamplona commented Aug 28, 2024

What would that look like? Renaming active to dm?

Sure. I think more key sets will show up as new NIPs arrive. Thus, I would align the name to the thing that the group of keys is supposed to be used for... in this case, to group keys as a chat group and encrypt to them.

We discussed key aliases for DMs in the past: #1306 Your idea is somewhat similar, but instead of senders just picking one, as in #1306, they have to encrypt all of them. #1306 is an OR filter, here it is an AND.

@melvincarvalho
Copy link

I do already have subkeys to my master key, so it would be nice if it would be possible for me to link those still, rather than have to start a new hierarchy.

If they were created using the same derivation (m/44'/1237'/0'/<subkey-index>/0), then clients and relays that support this NIP should automatically begin treating them as part of the subkey0 account.

I didnt use that no. There's quite a few different KDFs possible. Then there are random keys for completely unlinked personae.

@ynniv ynniv requested a review from staab August 30, 2024 04:21
…r tag . Clarify migration path. Add a note about defensive coding.
@mikedilger
Copy link
Contributor

First read just now and I'm kicking myself that I didn't think of this. I had thought that subkeys required some kind of signed document that tied the master key to the subkey. I forgot you could derive a subkey algorithmically... so I'm very glad to have seen this NIP.

This is a little bit like NIP-26 delegated event signing, except no conditions, actually the same principal not really delegated. But it has that tag saying which real identity the event author is.

Copy link
Collaborator

@Semisol Semisol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Knowledge of the parent xpub and a child private key being compromised causes compromise of all keys derived from the xpub.

@ynniv
Copy link
Author

ynniv commented Sep 2, 2024

Knowledge of the parent xpub and a child private key being compromised causes compromise of all keys derived from the xpub.

This is the best reason for code reviews. I did some research, and it is unfortunately correct.

The only property of HD keys that's essential here is the ability to validate their derivation from the claimed identity pubkey without any additional information. This keeps things fast in the common case, though we still need to do a lookup to confirm non-redaction before making destructive changes (eg, replaceable events, or revocation events). We can implement this same property with a non-HD key by including an attestation that this key is part of the main identity, signed by the main key. This adds more data to each message, and makes this NIP look very similar to NIP-26.

So, what's wrong with NIP-26? Adoption has been low enough to soft-deprecate it and remove the code from some clients, even though it seems to solve an important problem that still doesn't have a satisfying solution. An obvious criticism is that it's too complicated, and adds a lot of data to each message. This comes from the idea that keys aren't merely revokable tokens, but have specific authorizations based the attributes they are allowed to publish: kind, created_at, etc. @vitorpamplona pointed out the limited value of making decisions on created_at. Both these rules and the attestation need to be included in every message, even though they are of limited use.

If we've given up on NIP-26, does it make sense to re-imagine a lighter version? Specifically one that looks exactly like this proposal, with the addition of the smallest possible attestation from the already included I keypair saying that this pubkey is equivalent to I (until revoked), and removal of "other_keys" from Kind 10102 since this is now the common case.

@mikedilger
Copy link
Contributor

NIP-26 had 3 problems. It had conditions of questionable use. It had a non-searchable tag. And it required both relays and clients to support it before it became useful... there was no easy path through partial support. Solve those three problems and you'll have a winner.

BTW, you can prove part of the BIP-32 tree belongs to your pub without releasing your xpub.

@paulmillr
Copy link
Contributor

Just my 5 cents here.

HD keys are terrible. Anything is better. For example, child_key = hkdf(main_key, salt: "nip-102", info: "reason-5") is better security-wise and complexity-wise.

Knowledge of the parent xpub and a child private key being compromised causes compromise of all keys derived from the xpub.

That quote by @Semisol is true. Only hardened hd keys don't suffer from this.

@ynniv
Copy link
Author

ynniv commented Sep 16, 2024

The problem with hardened keys is that there's no way to validate that a public key is derived from the parent's public key, so there's no point in using them. I've been mulling on an attestation based variant that I just pushed. It's similar to NIP-26 delegation without conditions, and I think it's feasible without significant relay support.

"kind": 1,
"tags": [
["I", "<hex-account0-pubkey>"],
["Ia", "<subkey1-attestation>"]
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is correct because we won't search for the attestation. If there are other reasons to use single characters I was considering J.

@ynniv ynniv requested a review from Semisol September 16, 2024 18:49
@ynniv ynniv changed the title NIP-102: Hierarchical Deterministic subkeys NIP-102: Subkey Attestation Sep 19, 2024
@devsou-com
Copy link

devsou-com commented Sep 29, 2024

Just a reminder: I'm a newbie Nostr developer and I have no experience with public/private key infrastructure.

I understand that NIP-26 only deals with delegation, that is, it does not deals with token revocation or token management (it is quite similar to JWT), and worst, its attestation token is valid forever! In my opinion, it can be deprecated right now #1051

I was sad to know that using hierarchical deterministic subkeys is problematic. It would be very important to validate that the public key is derived from the parent's public key. But at the same time I'm happy, because this opens up the possibility for an existing account to be able to manage its own events and events on behalf of another account at the same time!

@ynniv I liked your proposal, simple and direct (aligned with the Fiatjaf's philosophy/methodology). It deals with key management and revocation. In my opinion, it is ready to be approved. Just a small change, I would love to use the I and J tags (I didn't like Ia).

I am interested in this approval, as I already have plans to use it in the client I am developing 😁

addendum:

@fiatjaf
Copy link
Member

fiatjaf commented Sep 29, 2024

This introduces absurd complexity that makes Nostr unusable.

@ynniv
Copy link
Author

ynniv commented Sep 29, 2024

This introduces absurd complexity that makes Nostr unusable

While I agree that it could be confusing without significant client adoption, I think backwards compatibility allows for some clients to lead the way. Impact on the relays is minimal, and primarily about replaceable events and deletion requests.

To me, key management is critical, and coming up with a way forward is worth the effort.

@ice-orestes
Copy link

ice-orestes commented Sep 30, 2024

Sub-key management is very important, and will become even more important with more adoption coming.

We also made a suggestion for sub-key management in this PR #1482 if you want to check it out.

Whatever the option, Nostr needs a way for sub-key delegation and revocation/undelegation! NIP-26 is clearly not enough and a simpler and more usable option is very important. Even to replace NIP-26 completely, why not?

@devsou-com
Copy link

@ice-orestes I analyzed your proposal. @vitorpamplona asked you about the use of timestamps. You said that this would prevent replay attacks. @ynniv proposal does not use timestamps and it seems to work very well. Since these are replaceable events, you just need to rely on the "created_at" attribute.

Am I wrong in my reasoning? (reminder, I'm a newbie Nostr developer).

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

Successfully merging this pull request may close these issues.