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](https://github.com/nostr-protocol/nips/blob/master/04.md) 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](https://github.com/motorina0/nips/blob/republish_events/705.md)). ## 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](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) 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](https://github.com/nostr-protocol/nips/blob/master/06.md)) - `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
See Example If Alice wants to build he dm parent key for Bob then she has to:
We notate the above derived `direct-message parent key` with `dmpk`. Then we can define paths of the form `dmpk//index`. | Action Name | Value | Path | Derive keys for | |-----------------------|--------|---------------------|-----------------------------------| | **init** | `0` | `dmpk/0/0` | initialize the `direct messages` flow| | **send** | `1` | `dmpk/1/` | sending `direct messages` | | **receive** | `2` | `dmpk/2/` | receiving `direct messages` | | `draft`: republish | `3` | `dmpk/3/` | sending `republish events` | | `draft`: market-order | `4500` | `dmpk/4500/` | sending NIP45 `market orders` | The client (creator of the `dmpk`) must: - use a new send key (`dmpk/1/`) 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/`). It is recommended to listen for the next `10` public keys and increment the index once a key is used (see [BIP-44 address gap logic](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#user-content-Address_gap_limit)). ## 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: ```json { "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": , "receive_index": , } ``` > **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](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#user-content-Address_gap_limit)). - publish a `Parameterized Replaceable Event` ([NIP-33](https://github.com/nostr-protocol/nips/blob/master/33.md)) of the form: ```json { ... "pubkey": <32-bytes lowercase hex-encoded "init" public key derived using `dmpk/0/0`>, "kind": 35709, "content": , "tags:" [ "d": <32-bytes lowercase hex-encoded public key of Bob>, "p": <32-bytes lowercase hex-encoded public key of Bob> ] } ``` > **Note**: `Alice` must use the "init" (`dmpk/0/0`) public key for signing the event so that there is no visible interaction between her and `Bob` If `Bob` supports this NIP then he must: - subscribe to "init" events for him: ```json { "kind": 35709, "#p": [<32-bytes lowercase hex-encoded public key of Bob>] } ``` - when an event is received, decrypt 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` Alice must also listen for `"kind": 35709` to her. 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.