nip44.c (13152B)
1 2 #include "base64.h" 3 #include "secp256k1.h" 4 #include "secp256k1_ecdh.h" 5 #include "secp256k1_schnorrsig.h" 6 #include "hmac_sha256.h" 7 #include "hkdf_sha256.h" 8 #include "nip44.h" 9 #include "random.h" 10 #include "cursor.h" 11 #include "sodium/crypto_stream_chacha20.h" 12 #include <string.h> 13 14 /* NIP44 payload encryption/decryption */ 15 16 static int copyx(unsigned char *output, const unsigned char *x32, 17 const unsigned char *y32, void *data) 18 { 19 memcpy(output, x32, 32); 20 return 1; 21 } 22 23 static enum ndb_decrypt_result 24 calculate_shared_secret(secp256k1_context *ctx, 25 const unsigned char *seckey, 26 const unsigned char *pubkey, 27 unsigned char *shared_secret) 28 { 29 secp256k1_pubkey parsed_pubkey; 30 unsigned char compressed_pubkey[33]; 31 compressed_pubkey[0] = 2; 32 memcpy(&compressed_pubkey[1], pubkey, 32); 33 34 if (!secp256k1_ec_seckey_verify(ctx, seckey)) { 35 return NIP44_ERR_SECKEY_VERIFY_FAILED; 36 } 37 38 if (!secp256k1_ec_pubkey_parse(ctx, &parsed_pubkey, compressed_pubkey, sizeof(compressed_pubkey))) { 39 return NIP44_ERR_PUBKEY_PARSE_FAILED; 40 } 41 42 if (!secp256k1_ecdh(ctx, shared_secret, &parsed_pubkey, seckey, copyx, NULL)) { 43 return NIP44_ERR_ECDH_FAILED; 44 } 45 46 return NIP44_OK; 47 } 48 49 struct message_keys { 50 unsigned char key[32]; 51 unsigned char nonce[12]; 52 unsigned char auth[32]; 53 }; 54 55 static void hmac_aad(struct hmac_sha256 *out, 56 unsigned char hmac[32], unsigned char *aad, 57 const unsigned char *msg, size_t msgsize) 58 { 59 struct hmac_sha256_ctx ctx; 60 hmac_sha256_init(&ctx, hmac, 32); 61 hmac_sha256_update(&ctx, aad, 32); 62 hmac_sha256_update(&ctx, msg, msgsize); 63 hmac_sha256_done(&ctx, out); 64 } 65 66 enum ndb_decrypt_result 67 nip44_decode_payload(struct nip44_payload *decoded, 68 unsigned char *buf, size_t bufsize, 69 const char *payload, size_t payload_len) 70 { 71 size_t decoded_len; 72 73 /* NOTE(jb55): we use the variant that doesn't have an 74 * upper size limit 75 */ 76 if (payload_len < 132 /*|| plen > 87472*/) { 77 return NIP44_ERR_INVALID_PAYLOAD; 78 } 79 80 /* 81 1. Check if first payload's character is `#` 82 83 - `#` is an optional future-proof flag that means non-base64 84 encoding is used 85 86 - The `#` is not present in base64 alphabet, but, instead of 87 throwing `base64 is invalid`, implementations MUST indicate that 88 the encryption version is not yet supported 89 */ 90 if (payload[0] == '#') { 91 return NIP44_ERR_UNSUPPORTED_ENCODING; 92 } 93 94 /* 95 2. Decode base64 96 - Base64 is decoded into `version, nonce, ciphertext, mac` 97 98 - If the version is unknown, implementations must indicate that the 99 encryption version is not supported 100 101 - Validate length of base64 message to prevent DoS on base64 102 decoder: it can be in range from 132 to 87472 chars 103 104 - Validate length of decoded message to verify output of the 105 decoder: it can be in range from 99 to 65603 bytes 106 */ 107 decoded_len = base64_decode((char*)buf, bufsize, payload, payload_len); 108 if (decoded_len == -1) { 109 return NIP44_ERR_BASE64_DECODE; 110 } else if (decoded_len < 99 /*|| decoded_len > 65603*/) { 111 return NIP44_ERR_INVALID_PAYLOAD; 112 } 113 114 decoded->version = buf[0]; 115 decoded->nonce = &buf[1]; 116 decoded->ciphertext = &buf[33]; 117 decoded->ciphertext_len = decoded_len - 65; 118 decoded->mac = &buf[decoded_len-32]; 119 120 return NIP44_OK; 121 } 122 123 static inline uint16_t next_pow2_16(uint16_t v) 124 { 125 if (v <= 1) 126 return 1; 127 128 v--; /* round down from v to (v-1) */ 129 v |= v >> 1; 130 v |= v >> 2; 131 v |= v >> 4; 132 v |= v >> 8; 133 v++; /* now v is next power of two */ 134 135 return v; 136 } 137 138 static int calc_padded_len(uint16_t unpadded_len) 139 { 140 uint16_t chunk; 141 142 /* enforce minimum of 32 */ 143 if (unpadded_len <= 32) 144 return 32; 145 146 /* For <= 256, always use 32-byte chunks. */ 147 if (unpadded_len <= 256) { 148 chunk = 32; 149 } else { 150 /* next_power / 8 */ 151 chunk = next_pow2_16(unpadded_len) >> 3; 152 } 153 154 chunk--; 155 156 // Round up to the next multiple of chunk (chunk is power of two) 157 return (unpadded_len + chunk) & ~chunk; 158 } 159 160 static int unpad(unsigned char *padded_buf, size_t len, uint16_t *unpadded_len) 161 { 162 struct cursor c; 163 unsigned char *decoded_end; 164 uint16_t decoded_len, decoded_end_len; 165 166 make_cursor(padded_buf, padded_buf+len, &c); 167 168 if (!cursor_pull_b16(&c, &decoded_len)) { 169 fprintf(stderr, "unpad: couldn't pull decoded len\n"); 170 return 0; 171 } 172 173 decoded_end_len = decoded_len + 2; 174 decoded_end = padded_buf + decoded_end_len; 175 176 if (decoded_end > c.end) { 177 fprintf(stderr, "decode debug: '%.*s'\n", (int)len-2, (const char *)(padded_buf + 2)); 178 fprintf(stderr, "unpad: decoded end (%d) is larger then original buf (%ld)\n", 179 decoded_end_len, len); 180 return 0; 181 } 182 183 c.end = decoded_end; 184 185 *unpadded_len = (uint16_t)cursor_remaining_capacity(&c); 186 187 if (*unpadded_len != decoded_len) { 188 fprintf(stderr, "unpadded_len(%d) != decoded_len(%d)\n", 189 *unpadded_len, decoded_len); 190 return 0; 191 } 192 193 if (decoded_len == 0 || len != (2 + calc_padded_len(decoded_len))) { 194 fprintf(stderr, "padding size is wrong\n"); 195 return 0; 196 } 197 198 return 1; 199 } 200 201 const char *nip44_err_msg(enum ndb_decrypt_result res) 202 { 203 switch (res) { 204 case NIP44_OK: 205 return "ok"; 206 case NIP44_ERR_FILL_RANDOM_FAILED: 207 return "fill random failed"; 208 case NIP44_ERR_INVALID_MAC: 209 return "invalid mac"; 210 case NIP44_ERR_SECKEY_VERIFY_FAILED: 211 return "seckey verify failed"; 212 case NIP44_ERR_PUBKEY_PARSE_FAILED: 213 return "pubkey parse failed"; 214 case NIP44_ERR_ECDH_FAILED: 215 return "ecdh failed"; 216 case NIP44_ERR_INVALID_PAYLOAD: 217 return "invalid payload"; 218 case NIP44_ERR_UNSUPPORTED_ENCODING: 219 return "unsupported encoding"; 220 case NIP44_ERR_BASE64_DECODE: 221 return "error during base64 decoding"; 222 case NIP44_ERR_INVALID_PADDING: 223 return "invalid padding"; 224 case NIP44_ERR_BUFFER_TOO_SMALL: 225 return "buffer too small"; 226 } 227 228 return "unknown"; 229 } 230 231 /* ### Decryption 232 * Before decryption, the event's pubkey and signature MUST be validated as 233 * defined in NIP 01. The public key MUST be a valid non-zero secp256k1 curve 234 * point, and the signature must be valid secp256k1 schnorr signature. For exact 235 * validation rules, refer to BIP-340. 236 */ 237 enum ndb_decrypt_result 238 nip44_decrypt_raw(void *secp, 239 const unsigned char *sender_pubkey, 240 const unsigned char *receiver_seckey, 241 struct nip44_payload *decoded, 242 unsigned char **decrypted, uint16_t *decrypted_len) 243 { 244 struct hmac_sha256 conversation_key; 245 struct hmac_sha256 calculated_mac; 246 enum ndb_decrypt_result rc; 247 unsigned char shared_secret[32]; 248 struct message_keys keys; 249 secp256k1_context *context = (secp256k1_context *)secp; 250 251 /* 252 3. Calculate a conversation key 253 - Execute ECDH (scalar multiplication) of public key B by private 254 key A Output `shared_x` must be unhashed, 32-byte encoded x 255 coordinate of the shared point 256 - Use HKDF-extract with sha256, `IKM=shared_x` and 257 `salt=utf8_encode('nip44-v2')` 258 - HKDF output will be a `conversation_key` between two users. 259 */ 260 if ((rc = calculate_shared_secret(context, receiver_seckey, 261 sender_pubkey, shared_secret))) { 262 return rc; 263 } 264 265 hmac_sha256(&conversation_key, "nip44-v2", 8, shared_secret, 32); 266 267 /* 268 5. Calculate message keys 269 - The keys are generated from `conversation_key` and `nonce`. 270 Validate that both are 32 bytes long 271 - Use HKDF-expand, with sha256, `PRK=conversation_key`, 272 `info=nonce` and `L=76` 273 - Slice 76-byte HKDF output into: `chacha_key` (bytes 0..32), 274 `chacha_nonce` (bytes 32..44), `hmac_key` (bytes 44..76) 275 */ 276 assert(sizeof(keys) == 76); 277 assert(sizeof(conversation_key) == 32); 278 279 hkdf_expand(&keys, sizeof(keys), 280 &conversation_key, sizeof(conversation_key), 281 decoded->nonce, 32); 282 283 /* 284 6. Calculate MAC (message authentication code) with AAD and compare 285 - Stop and throw an error if MAC doesn't match the decoded one from 286 step 2 287 - Use constant-time comparison algorithm 288 */ 289 hmac_aad(&calculated_mac, keys.auth, decoded->nonce, 290 decoded->ciphertext, decoded->ciphertext_len); 291 292 /* TODO(jb55): spec says this needs to be constant time memcmp, 293 * not sure why? 294 */ 295 if (memcmp(calculated_mac.sha.u.u8, decoded->mac, 32)) { 296 return NIP44_ERR_INVALID_MAC; 297 } 298 299 300 /* 301 6. Decrypt ciphertext 302 - Use ChaCha20 with key and nonce from step 3 303 */ 304 crypto_stream_chacha20_ietf_xor_ic(decoded->ciphertext, 305 decoded->ciphertext, 306 decoded->ciphertext_len, 307 keys.nonce, 0, keys.key); 308 309 /* 310 7. Remove padding 311 */ 312 if (!unpad(decoded->ciphertext, decoded->ciphertext_len, decrypted_len)) { 313 return NIP44_ERR_INVALID_PADDING; 314 } 315 316 *decrypted = decoded->ciphertext + 2; 317 318 return NIP44_OK; 319 } 320 321 enum ndb_decrypt_result 322 nip44_decrypt(void *secp, 323 const unsigned char *sender_pubkey, 324 const unsigned char *receiver_seckey, 325 const char *payload, int payload_len, 326 unsigned char *buf, size_t bufsize, 327 unsigned char **decrypted, uint16_t *decrypted_len) 328 { 329 struct nip44_payload decoded; 330 enum ndb_decrypt_result rc; 331 332 /* decode payload! */ 333 if ((rc = nip44_decode_payload(&decoded, buf, bufsize, 334 payload, payload_len))) { 335 return rc; 336 } 337 338 return nip44_decrypt_raw(secp, sender_pubkey, receiver_seckey, 339 &decoded, decrypted, decrypted_len); 340 } 341 342 /* Encryption */ 343 enum ndb_decrypt_result 344 nip44_encrypt(void *secp, const unsigned char *sender_seckey, 345 const unsigned char *receiver_pubkey, 346 const unsigned char *plaintext, uint16_t plaintext_size, 347 unsigned char *buf, size_t bufsize, 348 char **out, ssize_t *out_len) 349 { 350 int rc; 351 struct cursor cursor; 352 struct hmac_sha256 auth, conversation_key; 353 unsigned char shared_secret[32]; 354 unsigned char nonce[32]; 355 unsigned char *ciphertext; 356 struct message_keys keys; 357 uint16_t ciphertext_len; 358 359 make_cursor(buf, buf+bufsize, &cursor); 360 361 /* 362 1. Calculate a conversation key 363 - Execute ECDH (scalar multiplication) of public key B by private 364 key A Output `shared_x` must be unhashed, 32-byte encoded x 365 coordinate of the shared point 366 367 - Use HKDF-extract with sha256, `IKM=shared_x` and 368 `salt=utf8_encode('nip44-v2')` 369 370 - HKDF output will be a `conversation_key` between two users. 371 372 - It is always the same, when key roles are swapped: 373 `conv(a, B) == conv(b, A)` 374 */ 375 if ((rc = calculate_shared_secret(secp, sender_seckey, 376 receiver_pubkey, shared_secret))) { 377 return rc; 378 } 379 380 hmac_sha256(&conversation_key, "nip44-v2", 8, shared_secret, 32); 381 /* 382 2. Generate a random 32-byte nonce 383 - Always use CSPRNG 384 - Don't generate a nonce from message content 385 - Don't re-use the same nonce between messages: doing so would make 386 them decryptable, but won't leak the long-term key 387 */ 388 if (!fill_random(nonce, sizeof(nonce))) { 389 return NIP44_ERR_FILL_RANDOM_FAILED; 390 } 391 392 /* 393 3. Calculate message keys 394 - The keys are generated from `conversation_key` and `nonce`. 395 Validate that both are 32 bytes long 396 - Use HKDF-expand, with sha256, `PRK=conversation_key`, `info=nonce` 397 and `L=76` 398 - Slice 76-byte HKDF output into: `chacha_key` (bytes 0..32), 399 `chacha_nonce` (bytes 32..44), `hmac_key` (bytes 44..76) 400 */ 401 hkdf_expand(&keys, sizeof(keys), 402 &conversation_key, sizeof(conversation_key), 403 nonce, 32); 404 405 /* 406 4. Add padding 407 - Content must be encoded from UTF-8 into byte array 408 - Validate plaintext length. Minimum is 1 byte, maximum is 65535 bytes 409 - Padding format is: `[plaintext_length:u16][plaintext][zero_bytes]` 410 - Padding algorithm is related to powers-of-two, with min padded msg 411 size of 32 bytes 412 - Plaintext length is encoded in big-endian as first 2 bytes of the 413 padded blob 414 */ 415 if (!cursor_push_byte(&cursor, 0x02)) 416 return NIP44_ERR_BUFFER_TOO_SMALL; 417 if (!cursor_push(&cursor, nonce, 32)) 418 return NIP44_ERR_BUFFER_TOO_SMALL; 419 420 ciphertext = cursor.p; 421 422 if (!cursor_push_b16(&cursor, plaintext_size)) 423 return NIP44_ERR_BUFFER_TOO_SMALL; 424 if (!cursor_push(&cursor, (unsigned char*)plaintext, plaintext_size)) 425 return NIP44_ERR_BUFFER_TOO_SMALL; 426 if (!cursor_memset(&cursor, 0, calc_padded_len(plaintext_size) - plaintext_size)) 427 return NIP44_ERR_BUFFER_TOO_SMALL; 428 429 ciphertext_len = cursor.p - ciphertext; 430 assert(ciphertext_len >= 132); 431 /* 432 5. Encrypt padded content 433 - Use ChaCha20, with key and nonce from step 3 434 */ 435 crypto_stream_chacha20_ietf_xor_ic(ciphertext, ciphertext, 436 ciphertext_len, keys.nonce, 0, 437 keys.key); 438 439 /* 440 6. Calculate MAC (message authentication code) 441 - AAD (additional authenticated data) is used - instead of 442 calculating MAC on ciphertext, it's calculated over a concatenation 443 of `nonce` and `ciphertext` 444 445 - Validate that AAD (nonce) is 32 bytes 446 */ 447 hmac_aad(&auth, keys.auth, nonce, ciphertext, ciphertext_len); 448 449 if (!cursor_push(&cursor, auth.sha.u.u8, 32)) 450 return NIP44_ERR_BUFFER_TOO_SMALL; 451 452 /* 453 7. Base64-encode (with padding) params using `concat(version, nonce, 454 ciphertext, mac)` 455 */ 456 *out = (char*)cursor.p; 457 *out_len = base64_encode((char*)cursor.p, 458 cursor_remaining_capacity(&cursor), 459 (const char*)cursor.start, 460 cursor.p - cursor.start); 461 462 if (*out_len == -1) 463 return NIP44_ERR_BUFFER_TOO_SMALL; 464 return NIP44_OK; 465 } 466