nips/704.md
2023-04-10 17:01:44 +03:00

5.8 KiB

NIP-704

More private Encrypted Direct Messages

draft optional author:motorina0

This NIP defines a way for two clients to derive and share one-use-only keys for sending and recieving kind:4 events.

Motivation

The content of Direct Messages NIP-04 is encrypted, but everyone can see who is chatting with whom. Privacy wise this is far from ideal.

This NIP describes a way to obfuscate DM communications from the "general public", it does not deal with the relay tracking of clients (for that see NIP-705).

Suggestion

For the maximum of privacy the two participants of a Direct Message exchange SHOULD use a different public key for each kind:4 event. This means that each participant has to:

  • build a direct-message parent key from which it will derive keys-to-send and keys-to-receive (listen for) kind:4 events
  • share this direct-message parent key with its DM peer

Each client has a master key (denoted with m). This key can be the profile nsec..., but it is not mandatory.

Derive the direct-message parent key

A client must generate multiple direct-message parent keys, one for each peer that it is communicating with. The BIP32 structure to be used is:

m / purpose' / conin_type' / part1' / part2' / ... / part8'
  • this NIP defines the purpose 25709' (dm -> 0x646d -> 25709) for deriving Direct Messages related keys
  • nostr coin_type' is 1237' (see NIP-06)
  • part1' / part2' / ... / part8' is the public key hex string (of the peer) split in 8 chunks:
    • the reason for using the peer's (Bob) public key is to always arive at the same value even if prio state is lost
    • the reason for splitting the public key is that each level of the path can have a max value of 232-1
Example If Alice wants to build he dm parent key for Bob then she has to:
  • get the public key of `Bob` (in hex). Eg: 3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d
  • split the public key hex string in 8 chunks:
  • - 3bf0c63f, cb934634, 07af97a5, e5ee64fa, 883d107e, f9e55847, 2c4eb9aa, aefa459d
  • derive the dm parent key: m/25709'/1237'/3bf0c63f'/cb934634'/.../aefa459d'

We notate the above derived direct-message parent key with dmpk. Then we can define paths of the form dmpk/<action>/index.

Action Name Value Path Derive keys for
init 0 dmpk/0/0 initialize the direct messages flow
send 1 dmpk/1/<index> sending direct messages
receive 2 dmpk/2/<index> receiving direct messages
draft: republish 3 dmpk/3/<index> sending republish events
draft: market-order 4500 dmpk/4500/<index> sending NIP45 market orders

The client (creator of the dmpk) must:

  • use a new send key (dmpk/1/<index>) for each event it signs. It starts from 0 and increments after an event is signed.
  • create filters for the public keys it expects to receive messages to (dmpk/2/<index>). It is recommended to listen for the next 10 keys and increment the index once a key is used (see BIP-44 address gap logic).

Exchange the direct-message parent key

If Alice wants to signal Bob that she is ready to use this NIP (for more privacy) she must:

  • build a JSON data of the form:
 {
  "pubkey": <32-bytes lowercase hex-encoded public key of Alice (public profile key)>
  "dmpk": <32-bytes lowercase hex-encoded direct-message parent key>,
  "shared_secret_hash": <32-bytes lowercase hex-encoded sha256 of the shared secret>
  "send_index": <integer (optional), the index of the last key used to sign an event>,
  "receive_index": <integer (optional), the index of the last key an event was received to>,
 }

Note send_index and receive_index are optional, but they help the client a lot in knowing what the state is. Otherwise (when an account is retored) the client would have to scan the public keys until unused ones are found (similar to BIP-44 address gap logic).

  • publish a Parameterized Replaceable Event (NIP-33) of the form:
 {
  ...
  "pubkey": <32-bytes lowercase hex-encoded "init" public key derived using `dmpk/0/0`>,
  "kind": 35709,
  "content": <NIP-04 encrypted content of the JSON data>,
  "tags:" [
    "d": <32-bytes lowercase hex-encoded public key of Bob>,
    "p": <32-bytes lowercase hex-encoded public key of Bob>
  ]
 }

If Bob supports this NIP then he must:

  • subscribe to "init" events for him:
{
  "kind": 35709,
  "#p": [<32-bytes lowercase hex-encoded public key of Bob>]
}
  • when an event is received, descrypt the content, verify the shared_secret_hash against Alice's public key
  • decide if it wants to communicate with Alice. If yes it should publish its own kind:35709 for Alice

After both Alice and Bob have published the kind: 35709 event, they can start to publish and listen to events using the one-use-only keys.