nips/4e.md

4.6 KiB

NIP-4e

Decoupling encryption from identity

optional draft

This NIP describes a system for users to share private data between their own devices that doesn't rely on all devices holding the user account private key.

The problem

Currently many NIPs rely on encrypting data from the user to themselves -- such that the data can be accessed later on a different device -- using NIP-04 or NIP-44 and the users as both the sender and the receiver, e.g. NIP-51 and NIP-60. This works fine, but it assumes all devices have direct or indirect access to the same secret key. This assumption cannot be fulfilled in the case of approaches where the key isn't known, such as when using bunkers powered by FROST, MuSig2 or hosted secure enclaves.

Also, in some use cases having the encryption key be on device can drastically increase performance of encrypting and decrypting stuff, and such a thing is not possible to do while also using NIP-46 for keeping the user's main Nostr key safer. It's also not possible to perform any encryption while offline if the encryption keys live in a remote bunker.

There are probably other advantages to not tying the user's identity to the keys used for more mundane things such as encryption, which we can write here later.

The solution

  1. Every client can generate a new client key and store it locally, while making its public key public in a Nostr event.
  2. The first client to come into the world will generate a random encryption key.
  3. When another client's client key is spotted, the client that knows the original encryption key encrypts that key to the target client's client key using NIP-44 and sends it out.
  4. Encryption and decryption are performed using the encryption key, not the user's identity key.

The protocol flow

  1. Alice creates a keypair (a, A) (a is the secret key, A is the public key) on some onboarding website, say jump.nostrstart.com.
  2. A is Alice's main identity on Nostr, her npub will be, say, npub1A;
  3. Alice installs a client called Cope, Cope somehow realizes Alice can't use her a secret key for encryption because it's behind a FROST bunker, so Cope creates an encryption keypair (e, E). This doesn't change Alice's identity, it will only be used for encryption.
  4. Cope publishes an event (kind:10044) to announce this to the world:
{
  "kind": 10044,
  "pubkey": "<A>",
  "tags": [
    ["n", "<E>"] // `n` is for "encryption", doesn't matter
  ]
}
  1. Now Bob (keypairs (b, B)) will send a DM to Alice. Because Bob's client fetched Alice's kind:10044 event, instead of computing the conversation key with ecdh(b, A) he does ecdh(b, E) = S
  2. Because Alice knows e Alice can decrypt Bob's message doing ecdh(e, B) = S and all is good
  3. Now the fun part starts: Alice has decided to use a client called Tortilla to chat on her phone, and *Tortilla wants to do encryption stuff.
  4. Tortilla sees that Alice has a kind:10044 published, which means Tortilla won't create a new key, Tortilla will have to ask for Cope to share that key securely. So Tortilla generates a local keypair (t, T) that won't be shown or leave the device ever, and Tortilla publishes an announcement (kind:4454) for that local key (signed by Alice):
{
  "kind": 4454,
  "pubkey": "<A>",
  "tags": [
    ["client", "Tortilla on Android"],
    ["pubkey", "<T>"]
  ]
}
  1. Tortilla cannot proceed without known the secret key e, so it has to tell the user to turn Cope on.
  2. Alice opens up Cope and Cope immediately looks for all kind:4454 events from Alice, and sees that there is this app called "Tortilla on Android" signed by Alice herself, so Cope publishes the secret key e nip44-encrypted to ecdh(c, T) -- in which c is the secret key of a keypair that Cope has just generated locally. Cope does that using a new event, kind:4455:
{
  "kind": 4455,
  "pubkey": "<A>"
  "tags": [
    ["P", "<C>"]
    ["p", "<T>"]
  ],
  "content": "<nip44(content=e, conversationkey=get_conversation_key(c, T))>"
}
  1. Immediately Tortilla wakes up and sees the kind:4455 that had just been published by Cope, decrypts the content using ecdh(t, C) and now Tortilla also knows the secret key e. Tortilla can now decrypt and encrypt the same things Cope could before.

The protocol flow again, now in a colorful infographic