upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/44.md
diff options
context:
space:
mode:
authorhodlbod <jstaab@protonmail.com>2023-12-22 06:56:51 -0800
committerGitHub <noreply@github.com>2023-12-22 06:56:51 -0800
commitffc32c43e6b08ac54d63b21c8887f0f073c716ba (patch)
tree1a38c6bcb3d53d27101ea93de39edaea6badb53d /44.md
parent5ed4232584f3ab34192291daf985742248fb14ea (diff)
parent2b78cc9304f775b8391f62b7fe61e99a3fdc905b (diff)
Merge pull request #939 from coracle-social/nip44-tweaks
Clean up NIP 44
Diffstat (limited to '44.md')
-rw-r--r--44.md241
1 files changed, 130 insertions, 111 deletions
diff --git a/44.md b/44.md
index d69c688..8bc4038 100644
--- a/44.md
+++ b/44.md
@@ -1,74 +1,150 @@
1# NIP-44 1NIP-44
2=====
2 3
3## Encrypted Payloads (Versioned) 4Encrypted Payloads (Versioned)
5------------------------------
4 6
5`optional` 7`optional`
6 8
7The NIP introduces a new data format for keypair-based encryption. This NIP is versioned 9The NIP introduces a new data format for keypair-based encryption. This NIP is versioned
8to allow multiple algorithm choices to exist simultaneously. 10to allow multiple algorithm choices to exist simultaneously. This format may be used for
11many things, but MUST be used in the context of a signed event as described in NIP 01.
9 12
10Nostr is a key directory. Every nostr user has their own public key, which solves key 13*Note*: this format DOES NOT define any `kind`s related to a new direct messaging standard,
11distribution problems present in other solutions. The goal of this NIP is to have a 14only the encryption required to define one. It SHOULD NOT be used as a drop-in replacement
12simple way to send messages between nostr accounts that cannot be read by everyone. 15for NIP 04 payloads.
13 16
14The scheme has a number of important shortcomings: 17## Versions
15 18
16- No deniability: it is possible to prove the event was signed by a particular key 19Currently defined encryption algorithms:
17- No forward secrecy: when a user key is compromised, it is possible to decrypt all previous conversations
18- No post-compromise security: when a user key is compromised, it is possible to decrypt all future conversations
19- No post-quantum security: a powerful quantum computer would be able to decrypt the messages
20- IP address leak: user IP may be seen by relays and all intermediaries between user and relay
21- Date leak: the message date is public, since it is a part of NIP 01 event
22- Limited message size leak: padding only partially obscures true message length
23- No attachments: they are not supported
24 20
25Lack of forward secrecy is partially mitigated by these two factors: 21- `0x00` - Reserved
26 1. the messages should only be stored on relays, specified by the user, instead of a set of all public relays. 22- `0x01` - Deprecated and undefined
27 2. the relays are supposed to regularly delete older messages. 23- `0x02` - secp256k1 ECDH, HKDF, padding, ChaCha20, HMAC-SHA256, base64
28 24
29For risky situations, users should chat in specialized E2EE messaging software and limit use of nostr to exchanging contacts. 25## Limitations
30 26
31## Dependence on NIP-01 27Every nostr user has their own public key, which solves key distribution problems present
28in other solutions. However, nostr's relay-based architecture makes it difficult to implement
29more robust private messaging protocols with things like metadata hiding, forward secrecy,
30and post compromise secrecy.
32 31
33It's not enough to use NIP-44 for encryption: the output must also be signed. 32The goal of this NIP is to have a _simple_ way to encrypt payloads used in the context of a signed
33event. When applying this NIP to any use case, it's important to keep in mind your users' threat
34model and this NIP's limitations. For high-risk situations, users should chat in specialized E2EE
35messaging software and limit use of nostr to exchanging contacts.
34 36
35In nostr case, the payload is serialized and signed as per NIP-01 rules. 37On its own, messages sent using this scheme have a number of important shortcomings:
36 38
37The 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- No deniability: it is possible to prove an event was signed by a particular key
40- No forward secrecy: when a key is compromised, it is possible to decrypt all previous conversations
41- No post-compromise security: when a key is compromised, it is possible to decrypt all future conversations
42- No post-quantum security: a powerful quantum computer would be able to decrypt the messages
43- IP address leak: user IP may be seen by relays and all intermediaries between user and relay
44- Date leak: `created_at` is public, since it is a part of NIP 01 event
45- Limited message size leak: padding only partially obscures true message length
46- No attachments: they are not supported
38 47
39After 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. 48Lack of forward secrecy may be partially mitigated by only sending messages to trusted relays, and asking
49relays to delete stored messages after a certain duration has elapsed.
40 50
41## Versions 51## Version 2
42 52
43Currently defined encryption algorithms: 53NIP-44 version 2 has the following design characteristics:
54
55- Payloads are authenticated using a MAC before signing rather than afterwards because events are assumed
56 to be signed as specified in NIP-01. The outer signature serves to authenticate the full payload, and MUST
57 be validated before decrypting.
58- ChaCha is used instead of AES because it's faster and has
59 [better security against multi-key attacks](https://datatracker.ietf.org/doc/draft-irtf-cfrg-aead-limits/).
60- ChaCha is used instead of XChaCha because XChaCha has not been standardized. Also, xChaCha's improved collision
61 resistance of nonces isn't necessary since every message has a new (key, nonce) pair.
62- HMAC-SHA256 is used instead of Poly1305 because polynomial MACs are much easier to forge.
63- SHA256 is used instead of SHA3 or BLAKE because it is already used in nostr. Also BLAKE's speed advantage
64 is smaller in non-parallel environments.
65- A custom padding scheme is used instead of padmé because it provides better leakage reduction for small messages.
66- Base64 encoding is used instead of another compression algorithm because it is widely available, and is already used in nostr.
67
68### Encryption
69
701. Calculate a conversation key
71 - Execute ECDH (scalar multiplication) of public key B by private key A
72 Output `shared_x` must be unhashed, 32-byte encoded x coordinate of the shared point
73 - Use HKDF-extract with sha256, `IKM=shared_x` and `salt=utf8_encode('nip44-v2')`
74 - HKDF output will be a `conversation_key` between two users.
75 - It is always the same, when key roles are swapped: `conv(a, B) == conv(b, A)`
762. Generate a random 32-byte nonce
77 - Always use [CSPRNG](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator)
78 - Don't generate a nonce from message content
79 - Don't re-use the same nonce between messages: doing so would make them decryptable,
80 but won't leak the long-term key
813. Calculate message keys
82 - The keys are generated from `conversation_key` and `nonce`. Validate that both are 32 bytes long
83 - Use HKDF-expand, with sha256, `OKM=conversation_key`, `info=nonce` and `L=76`
84 - Slice 76-byte HKDF output into: `chacha_key` (bytes 0..32), `chacha_nonce` (bytes 32..44), `hmac_key` (bytes 44..76)
854. Add padding
86 - Content must be encoded from UTF-8 into byte array
87 - Validate plaintext length. Minimum is 1 byte, maximum is 65535 bytes
88 - Padding format is: `[plaintext_length: u16][plaintext][zero_bytes]`
89 - Padding algorithm is related to powers-of-two, with min padded msg size of 32
90 - Plaintext length is encoded in big-endian as first 2 bytes of the padded blob
915. Encrypt padded content
92 - Use ChaCha20, with key and nonce from step 3
936. Calculate MAC (message authentication code)
94 - AAD (additional authenticated data) is used - instead of calculating MAC on ciphertext,
95 it's calculated over a concatenation of `nonce` and `ciphertext`
96 - Validate that AAD (nonce) is 32 bytes
977. Base64-encode (with padding) params using `concat(version, nonce, ciphertext, mac)`
44 98
45- `0x00` - Reserved 99Encrypted payloads MUST be included in an event's payload, hashed, and signed as defined in NIP 01, using schnorr
46- `0x01` - Deprecated and undefined 100signature scheme over secp256k1.
47- `0x02` - secp256k1 ECDH, HKDF, padding, ChaCha20, HMAC-SHA256, base64
48 101
49## Version 2 102### Decryption
50 103
51The algorithm choices are justified in a following way: 104Before decryption, the event's pubkey and signature MUST be validated as defined in NIP 01. The public key MUST be
105a valid non-zero secp256k1 curve point, and the signature must be valid secp256k1 schnorr signature. For exact
106validation rules, refer to BIP-340.
52 107
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. 1081. Check if first payload's character is `#`
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/) 109 - `#` is an optional future-proof flag that means non-base64 encoding is used
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. 110 - The `#` is not present in base64 alphabet, but, instead of throwing `base64 is invalid`,
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 111 implementations MUST indicate that the encryption version is not yet supported
57 speed advantage is smaller in non-parallel environments - Custom padding instead of padmé: better leakage reduction for small messages 1122. Decode base64
58- Base64 encoding instead of an other compression algorithm: it is widely available, and is already used in nostr 113 - Base64 is decoded into `version, nonce, ciphertext, mac`
114 - If the version is unknown, implementations must indicate that the encryption version is not supported
115 - Validate length of base64 message to prevent DoS on base64 decoder: it can be in range from 132 to 87472 chars
116 - Validate length of decoded message to verify output of the decoder: it can be in range from 99 to 65603 bytes
1173. Calculate conversation key
118 - See step 1 of (encryption)[#Encryption]
1194. Calculate message keys
120 - See step 3 of (encryption)[#Encryption]
1215. Calculate MAC (message authentication code) with AAD and compare
122 - Stop and throw an error if MAC doesn't match the decoded one from step 2
123 - Use constant-time comparison algorithm
1246. Decrypt ciphertext
125 - Use ChaCha20 with key and nonce from step 3
1267. Remove padding
127 - Read the first two BE bytes of plaintext that correspond to plaintext length
128 - Verify that the length of sliced plaintext matches the value of the two BE bytes
129 - Verify that calculated padding from step 3 of the (encryption)[#Encryption] process matches the actual padding
59 130
60### Functions and operations 131### Details
61 132
62- Cryptographic methods 133- Cryptographic methods
63 - `secure_random_bytes(length)` fetches randomness from CSPRNG 134 - `secure_random_bytes(length)` fetches randomness from CSPRNG.
64 - `hkdf(IKM, salt, info, L)` represents HKDF [(RFC 5869)](https://datatracker.ietf.org/doc/html/rfc5869) with SHA256 hash function, 135 - `hkdf(IKM, salt, info, L)` represents HKDF [(RFC 5869)](https://datatracker.ietf.org/doc/html/rfc5869)
65 comprised of methods `hkdf_extract(IKM, salt)` and `hkdf_expand(OKM, info, L)` 136 with SHA256 hash function comprised of methods `hkdf_extract(IKM, salt)` and `hkdf_expand(OKM, info, L)`.
66 - `chacha20(key, nonce, data)` is ChaCha20 [(RFC 8439)](https://datatracker.ietf.org/doc/html/rfc8439), with starting counter set to 0 137 - `chacha20(key, nonce, data)` is ChaCha20 [(RFC 8439)](https://datatracker.ietf.org/doc/html/rfc8439) with
67 - `hmac_sha256(key, message)` is HMAC [(RFC 2104)](https://datatracker.ietf.org/doc/html/rfc2104) 138 starting counter set to 0.
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]` 139 - `hmac_sha256(key, message)` is HMAC [(RFC 2104)](https://datatracker.ietf.org/doc/html/rfc2104).
140 - `secp256k1_ecdh(priv_a, pub_b)` is multiplication of point B by scalar a (`a ⋅ B`), defined in
141 [BIP340](https://github.com/bitcoin/bips/blob/e918b50731397872ad2922a1b08a5a4cd1d6d546/bip-0340.mediawiki).
142 The operation produces a shared point, and we encode the shared point's 32-byte x coordinate, using method
143 `bytes(P)` from BIP340. Private and public keys must be validated as per BIP340: pubkey must be a valid,
144 on-curve point, and private key must be a scalar in range `[1, secp256k1_order - 1]`.
69- Operators 145- Operators
70 - `x[i:j]`, where `x` is a byte array and `i, j <= 0`, 146 - `x[i:j]`, where `x` is a byte array and `i, j <= 0` returns a `(j - i)`-byte array with a copy of the
71 returns a `(j - i)`-byte array with a copy of the `i`-th byte (inclusive) to the `j`-th byte (exclusive) of `x` 147 `i`-th byte (inclusive) to the `j`-th byte (exclusive) of `x`.
72- Constants `c`: 148- Constants `c`:
73 - `min_plaintext_size` is 1. 1b msg is padded to 32b. 149 - `min_plaintext_size` is 1. 1b msg is padded to 32b.
74 - `max_plaintext_size` is 65535 (64kb - 1). It is padded to 65536. 150 - `max_plaintext_size` is 65535 (64kb - 1). It is padded to 65536.
@@ -82,7 +158,10 @@ The algorithm choices are justified in a following way:
82 - `zeros(length)` creates byte array of length `length >= 0`, filled with zeros 158 - `zeros(length)` creates byte array of length `length >= 0`, filled with zeros
83 - `floor(number)` and `log2(number)` are well-known mathematical methods 159 - `floor(number)` and `log2(number)` are well-known mathematical methods
84 160
85User-defined functions: 161### Implementation pseudocode
162
163The following is a collection of python-like pseudocode functions which implement the above primitives,
164intended to guide impelmenters. A collection of implementations in different languages is available at https://github.com/paulmillr/nip44.
86 165
87```py 166```py
88# Calculates length of the padded byte array. 167# Calculates length of the padded byte array.
@@ -177,73 +256,13 @@ def decrypt(payload, conversation_key):
177# 'hello world' == decrypt(payload, conversation_key) 256# 'hello world' == decrypt(payload, conversation_key)
178``` 257```
179 258
180#### Encryption 259### Audit
181
1821. Calculate conversation key
183 - Execute ECDH (scalar multiplication) of public key B by private key A.
184 Output `shared_x` must be unhashed, 32-byte encoded x coordinate of the shared point.
185 - Use HKDF-extract with sha256, `IKM=shared_x` and `salt=utf8_encode('nip44-v2')`
186 - HKDF output will be `conversation_key` between two users
187 - It is always the same, when key roles are swapped: `conv(a, B) == conv(b, A)`
1882. Generate random 32-byte nonce
189 - Always use [CSPRNG](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator)
190 - Don't generate nonce from message content
191 - Don't re-use the same nonce between messages: doing so would make them decryptable,
192 but won't leak long-term key
1933. Calculate message keys
194 - The keys are generated from `conversation_key` and `nonce`. Validate that both are 32 bytes
195 - Use HKDF-expand, with sha256, `OKM=conversation_key`, `info=nonce` and `L=76`
196 - Slice 76-byte HKDF output into: `chacha_key` (bytes 0..32), `chacha_nonce` (bytes 32..44), `hmac_key` (bytes 44..76)
1974. Add padding
198 - Content must be encoded from UTF-8 into byte array
199 - Validate plaintext length. Minimum is 1 byte, maximum is 65535 bytes
200 - Padding format is: `[plaintext_length: u16][plaintext][zero_bytes]`
201 - Padding algorithm is related to powers-of-two, with min padded msg size of 32
202 - Plaintext length is encoded in big-endian as first 2 bytes of the padded blob
2035. Encrypt padded content
204 - Use ChaCha20, with key and nonce from step 3
2056. Calculate MAC (message authentication code) with AAD
206 - AAD is used: instead of calculating MAC on ciphertext,
207 it's calculated over a concatenation of `nonce` and `ciphertext`
208 - Validate that AAD (nonce) is 32 bytes
2097. Base64-encode (with padding) params: `concat(version, nonce, ciphertext, mac)`
210
211After 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.
212
213#### Decryption
214
215Before 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.
216
2171. Check if first payload's character is `#`
218 - `#` is an optional future-proof flag that means non-base64 encoding is used
219 - The `#` is not present in base64 alphabet, but, instead of throwing `base64 is invalid`,
220 an app must say the encryption version is not yet supported
2212. Decode base64
222 - Base64 is decoded into `version, nonce, ciphertext, mac`
223 - If the version is unknown, the app, an app must say the encryption version is not yet supported
224 - Validate length of base64 message to prevent DoS on base64 decoder: it can be in range from 132 to 87472 chars
225 - Validate length of decoded message to verify output of the decoder: it can be in range from 99 to 65603 bytes
2263. Calculate conversation key
227 - See step 1 of Encryption
2284. Calculate message keys
229 - See step 3 of Encryption
2305. Calculate MAC (message authentication code) with AAD and compare
231 - Stop and throw an error if MAC doesn't match the decoded one from step 2
232 - Use constant-time comparison algorithm
2336. Decrypt ciphertext
234 - Use ChaCha20 with key and nonce from step 3
2357. Remove padding
236 - Read the first two BE bytes of plaintext that correspond to plaintext length
237 - Verify that the length of sliced plaintext matches the value of the two BE bytes
238 - Verify that calculated padding from encryption's step 3 matches the actual padding
239
240## Audit
241 260
242The v2 of the standard has been subject to an audit by [Cure53](https://cure53.de) in December 2023. 261The v2 of the standard was audited by [Cure53](https://cure53.de) in December 2023.
243Check out [audit-2023.12.pdf](https://github.com/paulmillr/nip44/blob/ce63c2eaf345e9f7f93b48f829e6bdeb7e7d7964/audit-2023.12.pdf) 262Check out [audit-2023.12.pdf](https://github.com/paulmillr/nip44/blob/ce63c2eaf345e9f7f93b48f829e6bdeb7e7d7964/audit-2023.12.pdf)
244and [auditor's website](https://cure53.de/audit-report_nip44-implementations.pdf). 263and [auditor's website](https://cure53.de/audit-report_nip44-implementations.pdf).
245 264
246## Tests and code 265### Tests and code
247 266
248A collection of implementations in different languages is available at https://github.com/paulmillr/nip44. 267A collection of implementations in different languages is available at https://github.com/paulmillr/nip44.
249 268
@@ -251,7 +270,7 @@ We publish extensive test vectors. Instead of having it in the document directly
251 270
252 269ed0f69e4c192512cc779e78c555090cebc7c785b609e338a62afc3ce25040 nip44.vectors.json 271 269ed0f69e4c192512cc779e78c555090cebc7c785b609e338a62afc3ce25040 nip44.vectors.json
253 272
254Example of test vector from the file: 273Example of a test vector from the file:
255 274
256```json 275```json
257{ 276{