-
Notifications
You must be signed in to change notification settings - Fork 645
First draft of NIP-44v3 #1838
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
base: master
Are you sure you want to change the base?
First draft of NIP-44v3 #1838
Conversation
Thank you for being sensible and implementing this as ver upgrade! Will review in a bit. |
This seems like a complete rewrite of v2. I'm concerned about complexity and subtle bugs.
Can't we just bump data limits and define new method to create conversation/message key? |
|
It only looks like that because of the new way to compute the conversation keys. The core encryption is exactly the same. We are adding a different encoding for the padding and the conversation key to decrypt can only be computed after decoding the payload. That's why the signature of the method has changed.
I did try to generalize it, but I am not sure if it is possible because this setup requires the publication of keys and the use of nonces. Everything is tightly coupled together. We could move the definition of the 10044 event kind out. While that would make things more flexible (other kinds could be used to publish keys), v3 would still require some form of key-nonce storage. It doesn't make much sense to leave a direct dependency undefined.
I am not sure what classifies as "real AAD", but this approach is similar to yours but expects signers to provide an |
hum... I just realized that by clearly marking which key was this encrypted for in the encoding we break the plausible deniability of NIP-17's Seals... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just my first few thoughts, I didn't spent much time yet.
"kind": 10044, | ||
"tags": [ | ||
["n", "<pubkey-in-lowercase-hex>", "<32-byte-nonce-in-lowercase-hex>"] | ||
... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are people searching for these 'n' tags? If not, no need for a single letter.
- Execute ECDH (scalar multiplication) of public key B by private key A | ||
Output `shared_x` must be unhashed, 32-byte encoded x coordinate of the shared point | ||
- Use HKDF-extract with sha256, `IKM=shared_x` and `salt=utf8_encode('nip44-v2')` | ||
- HKDF output will be a `conversation_key` between two users. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would you change this 'nip44-v2' salt to 'nip44-v3'?
from the source's main private_key as `sha256(hkdf(main_private_key, salt: 'nip88kd<kd-nonce-in-hex>'))` | ||
- Set `source-public-key` as the pubkey of the picked private key A. | ||
3. Calculate a conversation key | ||
- Execute ECDH (scalar multiplication) of public key B by private key A |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indicate this is same as v2 perhaps with (identical to v2)
(and to subsequent sections that haven't changed)
- Use HKDF-extract with sha256, `IKM=shared_x` and `salt=utf8_encode('nip44-v2')` | ||
- HKDF output will be a `conversation_key` between two users. | ||
- It is always the same, when key roles are swapped: `conv(a, B) == conv(b, A)` | ||
4. Generate a random 32-byte nonce |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indicate this is same as v2 perhaps with (identical to v2)
One other thing: I'd like to use v3 to encrypt a secret blob from the master key to each device key, as a starting point for deriving a sequence of encryption keys (according to ideas in the #1647 discussion). That means that the prose regarding which keys to select is too specific for NIP-44. NIP-44 should be just a post-key-selection encryption scheme, and not specify the key selection criteria itself. |
v2 was made as generic as possible - dev can put anything into conversation_key, also anything into payload. How about that? Just define 3 new methods:
and let everything else stay the same. This would make scheme composable and easy to reason about. |
I didn't notice the "real AAD" as in somet metadata readable by anyone before decryption (not really encrypted with payload) but that should be used and match when encrypting and decrypting. Thought you were referring to the part where you concat and base64 encoding some fixed extra params. |
AEAD (Authenticated Encryption with Associated Data) encrypts some data, and authenticates all the data even the data that wasn't encrypted, sometimes in a single cryptographic operation (like OCB mode that never got much use because Rogaway, my crypto professor, patented it). |
👀 |
FYI: I coded this but I am not happy with it. The main problem is that it forces all encryptions to document the source and destination of the message in the payload itself. And that means that it breaks privacy because you will always know the two keys that are talking to one another. On NIP-17, the GiftWrap encrypts and signs with a random key to a known destination. That is not a problem for this spec since the random key is known and the destination is also already known. But for Seals, the encryption and the event must not include a destination at all. Adding it to the NIP-44 payload breaks the message's metadata privacy if the sealed event leaks to a relay. I don't know how to solve that yet. |
It's hard to read this entire thing. If most things are exactly the same as v2 then it's confusing and weird to have v2 all copy-pasted below. It would be much easier to just notice the differences. |
Yeah, I didn't know how do we want to manage different algorithms for different versions. Since v2 is out there and people will need to decrypt it to see the past, I thought keeping the V2 text was a safe play. But we can override it as well. We can also redesign v3 as a wrapper around v2 itself, like @paulmillr suggested. |
I can't figure this out for myself, but this v3 would break all existing implementations, right? How are you supposed to know if you should encrypt using v2 or v3? That's why versions are stupid, you have to always break everything and that's horrible in a decentralized ecosystem like this. I haven't tried to do it myself, but I think all the changes proposed here can be added optionally on top of v2 while keeping old v2 clients still working (even if they don't support the new features). I hope that's what you mean by making it a wrapper around v2. |
@fiatjaf version is encoded in first byte. Existing clients will see this as "unsupported version" and can either show the popup to user asking to upgrade their client, or silently ignore it. |
In other words: the user will lose the message because their client won't have the new version. And by creating pressure to always be updating to a newer encryption version we make it harder for apps to compete and centralize the ecosystem into the fewer apps that can afford to be constantly being kept up to the date with the spec changes. This might be a big deal or may amount to nothing if we never do another version update. But in principle having this easy way to create new versions is a bad route for an open decentralized protocol. |
Another thing with this proposal -- that may or not may not be a problem (I can't decide) but it's worth noticing: if we're using an nsec to generate these keys, then we're really not decoupling encryption from identity and any compromised identity key also gives access to all encrypted messages. It's an improvement over the current state, but not really. And even though we can use some other secret other than the nsec in order to create the encryption keys that would still give us a "master" key that would have to be stored somewhere (and in the case of multisig bunkers that would have to be known by a central bunker operator). @vitorpamplona did you think about a standardized way to distribute these generated encryption keys between apps? |
Decoupling doesn't mean forward secrecy. We can decouple in a way that the nsec still loads everything up but the decryption keys can be exposed to clients or decouple and then have to deal with import & export a separate set of keys outside of Nostr. Because if they are inside of Nostr, there will be a way to save them even if the event is ephemeral. And that would break the desired forward secrecy.
In this scheme, with a non-FROST key, there is no need to distribute any keys. Each client can ask the key from the signer directly (new The main goal if this approach is to make sure the nsec alone is the only backup users need to have. If we start adding more stuff to the backup requirements in order to fully recover DMs and so on, we will be significantly changing how simple Nostr is. |
That's what I'm talking about. The bunker could implement this, it would work. Although it would still be bad as the coordinator would have access to all encryption keys but there is not much way around this anyway.
I think this is a confusing take. Nostr's simplicity was never associated with this value proposition of "always be able to recover everything with just the nsec" in my view. I think simplicity is more related to the protocol being simple to implement, that other thing is another feature and one that we never guaranteed, and we shouldn't, because ideally you won't broadcast your DMs to a thousand relays, you should keep them in 1 or 2 at most, and these can vanish and then you lose your DMs. Whatever. |
applying HKDF using a salt that is the concatenation of `nip44v3kd` in UTF-8 bytes with the kd-nonce in bytes: | ||
|
||
``` | ||
sha256(hkdf(private_key, salt: utf8_encode('nip44v3kd') + <kd-nonce-byte-array>)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using the user's nsec directly there is an alternative that would be immediately compatible with FROST and MuSig2 bunkers without requiring them to use a "root encryption key" different than their nsec: we make the private_key
in this context be equal to:
private_key = ecdh(nsec, nums_public_key)
The public key should be a NUMS otherwise it becomes possible for anyone to gather the root encryption key of anyone else. As a cool but suspiciously biased suggestion we could use the id of this event: https://njump.me/nevent1qqstnl4ddmhc0kzqpj7p543pvq9nvppc4laewc9x5ppucz7aagsa4dsppamhxue69uhkummnw3ezumt0d5pzpq35r7yzkm4te5460u00jz4djcw0qa90zku7739qn7wj4ralhe4z4huyw2, or, I don't know, something like the hash of Bitcoin block 900000.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually think we don't need a NUMS key here because we hash the thing afterwards. Consult a cryptographer before taking this seriously.
@paulmillr, what would it take to do a quantum resistant version of NIP-44 together in some v3 version? |
@vitorpamplona just sitting down and investing some time into it. ~1 week, perhaps more. Also some things are unknown.
|
This PR addresses 3 main problems of NIP-44v2:
It has a message size limit of 65Kb, which is unnecessarily small. And we need a new version to increase it. This PR raises the 65KB limit to ~4GB. 4GB is just an encoding upper limit, I don't expect us to have 4GB payloads. Discussion from this: [Nip-46] - Issue when using nip 44 for encrypting/decrypting bunker requests #1712
It forces the encrypting key to be the same as the event's signing key. Which forces multi-sig actors to share their main private key in order to encrypt the payload that would be later signed by the group. Decoupling singing and encryption keys, for both source and destination, is one of the goals of this version. Discussion from nip4e: decoupling encryption from identity #1647 (comment)
It offers no way to describe what's inside the encrypted blob before requesting the user's approval to decrypt and send the decrypted info back to the requesting application. This PR adds an alt description to allow decrypting signers to display a message and warn the user of what type of information the requesting application is receiving. The goal, for instance, is to let users block social media applications from decrypting health data and other types of information social apps should not touch. Long discussion here: Is NIP-07 decryptEvent(event) a good idea? #1439