diff options
Diffstat (limited to '44.md')
| -rw-r--r-- | 44.md | 66 |
1 files changed, 20 insertions, 46 deletions
| @@ -2,7 +2,7 @@ | |||
| 2 | 2 | ||
| 3 | ## Encrypted Payloads (Versioned) | 3 | ## Encrypted Payloads (Versioned) |
| 4 | 4 | ||
| 5 | `optional` `author:paulmillr` `author:staab` | 5 | `optional` |
| 6 | 6 | ||
| 7 | The NIP introduces a new data format for keypair-based encryption. This NIP is versioned | 7 | The NIP introduces a new data format for keypair-based encryption. This NIP is versioned |
| 8 | to allow multiple algorithm choices to exist simultaneously. | 8 | to allow multiple algorithm choices to exist simultaneously. |
| @@ -22,12 +22,11 @@ The scheme has a number of important shortcomings: | |||
| 22 | - Limited message size leak: padding only partially obscures true message length | 22 | - Limited message size leak: padding only partially obscures true message length |
| 23 | - No attachments: they are not supported | 23 | - No attachments: they are not supported |
| 24 | 24 | ||
| 25 | Lack of forward secrecy is partially mitigated: 1) the messages | 25 | Lack of forward secrecy is partially mitigated by these two factors: |
| 26 | should only be stored on relays, specified by the user, instead of a set of | 26 | 1. the messages should only be stored on relays, specified by the user, instead of a set of all public relays. |
| 27 | all public relays 2) the relays are supposed to regularly delete older messages. | 27 | 2. the relays are supposed to regularly delete older messages. |
| 28 | 28 | ||
| 29 | For risky situations, users should chat in specialized E2EE messaging software and limit use | 29 | For risky situations, users should chat in specialized E2EE messaging software and limit use of nostr to exchanging contacts. |
| 30 | of nostr to exchanging contacts. | ||
| 31 | 30 | ||
| 32 | ## Dependence on NIP-01 | 31 | ## Dependence on NIP-01 |
| 33 | 32 | ||
| @@ -35,14 +34,9 @@ It's not enough to use NIP-44 for encryption: the output must also be signed. | |||
| 35 | 34 | ||
| 36 | In nostr case, the payload is serialized and signed as per NIP-01 rules. | 35 | In nostr case, the payload is serialized and signed as per NIP-01 rules. |
| 37 | 36 | ||
| 38 | The same event can be serialized in two different ways, | 37 | The same event can be serialized in two different ways, resulting in two distinct signatures. So, it's important to ensure serialization rules, which are defined in NIP-01, are the same across different NIP-44 implementations. |
| 39 | resulting in two distinct signatures. So, it's important | ||
| 40 | to ensure serialization rules, which are defined in NIP-01, | ||
| 41 | are the same across different NIP-44 implementations. | ||
| 42 | 38 | ||
| 43 | After serialization, the event is signed by Schnorr signature over secp256k1, | 39 | After serialization, the event is signed by Schnorr signature over secp256k1, defined in BIP340. It's important to ensure the key and signature validity as per BIP340 rules. |
| 44 | defined in BIP340. It's important to ensure the key and signature validity as | ||
| 45 | per BIP340 rules. | ||
| 46 | 40 | ||
| 47 | ## Versions | 41 | ## Versions |
| 48 | 42 | ||
| @@ -56,18 +50,12 @@ Currently defined encryption algorithms: | |||
| 56 | 50 | ||
| 57 | The algorithm choices are justified in a following way: | 51 | The algorithm choices are justified in a following way: |
| 58 | 52 | ||
| 59 | - Encrypt-then-mac-then-sign instead of encrypt-then-sign-then-mac: | 53 | - Encrypt-then-mac-then-sign instead of encrypt-then-sign-then-mac: only events wrapped in NIP-01 signed envelope are currently accepted by nostr. |
| 60 | only events wrapped in NIP-01 signed envelope are currently accepted by nostr. | 54 | - ChaCha instead of AES: it's faster and has [better security against multi-key attacks](https://datatracker.ietf.org/doc/draft-irtf-cfrg-aead-limits/) |
| 61 | - ChaCha instead of AES: it's faster and has | 55 | - ChaCha instead of XChaCha: XChaCha has not been standardized. Also, we don't need xchacha's improved collision resistance of nonces: every message has a new (key, nonce) pair. |
| 62 | [better security against multi-key attacks](https://datatracker.ietf.org/doc/draft-irtf-cfrg-aead-limits/) | 56 | - HMAC-SHA256 instead of Poly1305: polynomial MACs are much easier to forge SHA256 instead of SHA3 or BLAKE: it is already used in nostr. Also blake's |
| 63 | - ChaCha instead of XChaCha: XChaCha has not been standardized. Also, we don't need xchacha's improved | 57 | speed advantage is smaller in non-parallel environments - Custom padding instead of padmé: better leakage reduction for small messages |
| 64 | collision resistance of nonces: every message has a new (key, nonce) pair. | 58 | - Base64 encoding instead of an other compression algorithm: it is widely available, and is already used in nostr |
| 65 | - HMAC-SHA256 instead of Poly1305: polynomial MACs are much easier to forge | ||
| 66 | - SHA256 instead of SHA3 or BLAKE: it is already used in nostr. Also blake's | ||
| 67 | speed advantage is smaller in non-parallel environments | ||
| 68 | - Custom padding instead of padmé: better leakage reduction for small messages | ||
| 69 | - Base64 encoding instead of an other compression algorithm: it is widely available, | ||
| 70 | and is already used in nostr | ||
| 71 | 59 | ||
| 72 | ### Functions and operations | 60 | ### Functions and operations |
| 73 | 61 | ||
| @@ -77,12 +65,7 @@ The algorithm choices are justified in a following way: | |||
| 77 | comprised of methods `hkdf_extract(IKM, salt)` and `hkdf_expand(OKM, info, L)` | 65 | comprised of methods `hkdf_extract(IKM, salt)` and `hkdf_expand(OKM, info, L)` |
| 78 | - `chacha20(key, nonce, data)` is ChaCha20 [(RFC 8439)](https://datatracker.ietf.org/doc/html/rfc8439), with starting counter set to 0 | 66 | - `chacha20(key, nonce, data)` is ChaCha20 [(RFC 8439)](https://datatracker.ietf.org/doc/html/rfc8439), with starting counter set to 0 |
| 79 | - `hmac_sha256(key, message)` is HMAC [(RFC 2104)](https://datatracker.ietf.org/doc/html/rfc2104) | 67 | - `hmac_sha256(key, message)` is HMAC [(RFC 2104)](https://datatracker.ietf.org/doc/html/rfc2104) |
| 80 | - `secp256k1_ecdh(priv_a, pub_b)` is multiplication of point B by | 68 | - `secp256k1_ecdh(priv_a, pub_b)` is multiplication of point B by scalar a (`a ⋅ B`), defined in [BIP340](https://github.com/bitcoin/bips/blob/e918b50731397872ad2922a1b08a5a4cd1d6d546/bip-0340.mediawiki). The operation produces shared point, and we encode the shared point's 32-byte x coordinate, using method `bytes(P)` from BIP340. Private and public keys must be validated as per BIP340: pubkey must be a valid, on-curve point, and private key must be a scalar in range `[1, secp256k1_order - 1]` |
| 81 | scalar a (`a ⋅ B`), defined in | ||
| 82 | [BIP340](https://github.com/bitcoin/bips/blob/e918b50731397872ad2922a1b08a5a4cd1d6d546/bip-0340.mediawiki). | ||
| 83 | The operation produces shared point, and we encode the shared point's 32-byte x coordinate, | ||
| 84 | using method `bytes(P)` from BIP340. Private and public keys must be validated | ||
| 85 | as per BIP340: pubkey must be a valid, on-curve point, and private key must be a scalar in range `[1, secp256k1_order - 1]` | ||
| 86 | - Operators | 69 | - Operators |
| 87 | - `x[i:j]`, where `x` is a byte array and `i, j <= 0`, | 70 | - `x[i:j]`, where `x` is a byte array and `i, j <= 0`, |
| 88 | returns a `(j - i)`-byte array with a copy of the `i`-th byte (inclusive) to the `j`-th byte (exclusive) of `x` | 71 | returns a `(j - i)`-byte array with a copy of the `i`-th byte (inclusive) to the `j`-th byte (exclusive) of `x` |
| @@ -225,15 +208,11 @@ def decrypt(payload, conversation_key): | |||
| 225 | - Validate that AAD (nonce) is 32 bytes | 208 | - Validate that AAD (nonce) is 32 bytes |
| 226 | 7. Base64-encode (with padding) params: `concat(version, nonce, ciphertext, mac)` | 209 | 7. Base64-encode (with padding) params: `concat(version, nonce, ciphertext, mac)` |
| 227 | 210 | ||
| 228 | After encryption, it's necessary to sign it. Use NIP-01 to serialize the event, | 211 | After encryption, it's necessary to sign it. Use NIP-01 to serialize the event, with result base64 assigned to event's `content`. Then, use NIP-01 to sign the event using schnorr signature scheme over secp256k1. |
| 229 | with result base64 assigned to event's `content`. Then, use NIP-01 to sign | ||
| 230 | the event using schnorr signature scheme over secp256k1. | ||
| 231 | 212 | ||
| 232 | #### Decryption | 213 | #### Decryption |
| 233 | 214 | ||
| 234 | Before decryption, it's necessary to validate the message's pubkey and signature. | 215 | Before decryption, it's necessary to validate the message's pubkey and signature. The public key must be a valid non-zero secp256k1 curve point, and signature must be valid secp256k1 schnorr signature. For exact validation rules, refer to BIP-340. |
| 235 | The public key must be a valid non-zero secp256k1 curve point, and signature must be valid | ||
| 236 | secp256k1 schnorr signature. For exact validation rules, refer to BIP-340. | ||
| 237 | 216 | ||
| 238 | 1. Check if first payload's character is `#` | 217 | 1. Check if first payload's character is `#` |
| 239 | - `#` is an optional future-proof flag that means non-base64 encoding is used | 218 | - `#` is an optional future-proof flag that means non-base64 encoding is used |
| @@ -260,11 +239,9 @@ secp256k1 schnorr signature. For exact validation rules, refer to BIP-340. | |||
| 260 | 239 | ||
| 261 | ## Tests and code | 240 | ## Tests and code |
| 262 | 241 | ||
| 263 | A collection of implementations in different languages is | 242 | A collection of implementations in different languages is available at https://github.com/paulmillr/nip44. |
| 264 | available [on GitHub](https://github.com/paulmillr/nip44). | ||
| 265 | 243 | ||
| 266 | We publish extensive test vectors. Instead of having it in the | 244 | We publish extensive test vectors. Instead of having it in the document directly, a sha256 checksum of vectors is provided: |
| 267 | document directly, a sha256 checksum of vectors is provided: | ||
| 268 | 245 | ||
| 269 | 269ed0f69e4c192512cc779e78c555090cebc7c785b609e338a62afc3ce25040 nip44.vectors.json | 246 | 269ed0f69e4c192512cc779e78c555090cebc7c785b609e338a62afc3ce25040 nip44.vectors.json |
| 270 | 247 | ||
| @@ -286,11 +263,8 @@ The file also contains intermediate values. A quick guidance with regards to its | |||
| 286 | - `valid.get_conversation_key`: calculate conversation_key from secret key sec1 and public key pub2 | 263 | - `valid.get_conversation_key`: calculate conversation_key from secret key sec1 and public key pub2 |
| 287 | - `valid.get_message_keys`: calculate chacha_key, chacha_nocne, hmac_key from conversation_key and nonce | 264 | - `valid.get_message_keys`: calculate chacha_key, chacha_nocne, hmac_key from conversation_key and nonce |
| 288 | - `valid.calc_padded_len`: take unpadded length (first value), calculate padded length (second value) | 265 | - `valid.calc_padded_len`: take unpadded length (first value), calculate padded length (second value) |
| 289 | - `valid.encrypt_decrypt`: emulate real conversation. Calculate | 266 | - `valid.encrypt_decrypt`: emulate real conversation. Calculate pub2 from sec2, verify conversation_key from (sec1, pub2), encrypt, verify payload, then calculate pub1 from sec1, verify conversation_key from (sec2, pub1), decrypt, verify plaintext. |
| 290 | pub2 from sec2, verify conversation_key from (sec1, pub2), encrypt, verify payload, | 267 | - `valid.encrypt_decrypt_long_msg`: same as previous step, but instead of a full plaintext and payload, their checksum is provided. |
| 291 | then calculate pub1 from sec1, verify conversation_key from (sec2, pub1), decrypt, verify plaintext. | ||
| 292 | - `valid.encrypt_decrypt_long_msg`: same as previous step, but instead of a full plaintext and payload, | ||
| 293 | their checksum is provided. | ||
| 294 | - `invalid.encrypt_msg_lengths` | 268 | - `invalid.encrypt_msg_lengths` |
| 295 | - `invalid.get_conversation_key`: calculating converastion_key must throw an error | 269 | - `invalid.get_conversation_key`: calculating converastion_key must throw an error |
| 296 | - `invalid.decrypt`: decrypting message content must throw an error | 270 | - `invalid.decrypt`: decrypting message content must throw an error |