Skip to content

Commit ef6cea6

Browse files
committed
nip44: add encrypt
we're not using this here, but i needed this for creating giftwrap test cases in nostril Signed-off-by: William Casarin <[email protected]>
1 parent 7d9e1b8 commit ef6cea6

File tree

3 files changed

+129
-2
lines changed

3 files changed

+129
-2
lines changed

src/nip44.c

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,24 @@ nip44_decrypt(void *secp,
384384
}
385385

386386
/* Encryption */
387-
int nip44_encrypt() {
387+
enum ndb_decrypt_result
388+
nip44_encrypt(void *secp, const unsigned char *sender_seckey,
389+
const unsigned char *receiver_pubkey,
390+
const unsigned char *plaintext, uint16_t plaintext_size,
391+
unsigned char *buf, size_t bufsize,
392+
char **out, ssize_t *out_len)
393+
{
394+
int rc;
395+
struct cursor cursor;
396+
struct hmac_sha256 auth, conversation_key;
397+
unsigned char shared_secret[32];
398+
unsigned char nonce[32];
399+
unsigned char *ciphertext;
400+
struct message_keys keys;
401+
uint16_t ciphertext_len;
402+
403+
make_cursor(buf, buf+bufsize, &cursor);
404+
388405
/*
389406
1. Calculate a conversation key
390407
- Execute ECDH (scalar multiplication) of public key B by private
@@ -399,14 +416,22 @@ int nip44_encrypt() {
399416
- It is always the same, when key roles are swapped:
400417
`conv(a, B) == conv(b, A)`
401418
*/
419+
if ((rc = calculate_shared_secret(secp, sender_seckey,
420+
receiver_pubkey, shared_secret))) {
421+
return rc;
422+
}
402423

424+
hmac_sha256(&conversation_key, "nip44-v2", 8, shared_secret, 32);
403425
/*
404426
2. Generate a random 32-byte nonce
405427
- Always use CSPRNG
406428
- Don't generate a nonce from message content
407429
- Don't re-use the same nonce between messages: doing so would make
408430
them decryptable, but won't leak the long-term key
409431
*/
432+
if (!fill_random(nonce, sizeof(nonce))) {
433+
return NIP44_ERR_FILL_RANDOM_FAILED;
434+
}
410435

411436
/*
412437
3. Calculate message keys
@@ -417,6 +442,9 @@ int nip44_encrypt() {
417442
- Slice 76-byte HKDF output into: `chacha_key` (bytes 0..32),
418443
`chacha_nonce` (bytes 32..44), `hmac_key` (bytes 44..76)
419444
*/
445+
hkdf_expand(&keys, sizeof(keys),
446+
&conversation_key, sizeof(conversation_key),
447+
nonce, 32);
420448

421449
/*
422450
4. Add padding
@@ -428,11 +456,31 @@ int nip44_encrypt() {
428456
- Plaintext length is encoded in big-endian as first 2 bytes of the
429457
padded blob
430458
*/
459+
if (!cursor_push_byte(&cursor, 0x02))
460+
return NIP44_ERR_BUFFER_TOO_SMALL;
461+
if (!cursor_push(&cursor, nonce, 32))
462+
return NIP44_ERR_BUFFER_TOO_SMALL;
463+
464+
ciphertext = cursor.p;
465+
466+
if (!cursor_push_b16(&cursor, plaintext_size))
467+
return NIP44_ERR_BUFFER_TOO_SMALL;
468+
if (!cursor_push(&cursor, (unsigned char*)plaintext, plaintext_size))
469+
return NIP44_ERR_BUFFER_TOO_SMALL;
470+
if (!cursor_memset(&cursor, 0, calc_padded_len(plaintext_size) - plaintext_size))
471+
return NIP44_ERR_BUFFER_TOO_SMALL;
472+
473+
ciphertext_len = cursor.p - ciphertext;
474+
printf("plaintext_size %d ciphertext '%.*s'\n", plaintext_size,
475+
plaintext_size, plaintext);
431476

432477
/*
433478
5. Encrypt padded content
434479
- Use ChaCha20, with key and nonce from step 3
435480
*/
481+
crypto_stream_chacha20_ietf_xor_ic(ciphertext, ciphertext,
482+
ciphertext_len, keys.nonce, 0,
483+
keys.key);
436484

437485
/*
438486
6. Calculate MAC (message authentication code)
@@ -442,11 +490,23 @@ int nip44_encrypt() {
442490
443491
- Validate that AAD (nonce) is 32 bytes
444492
*/
493+
hmac_aad(&auth, keys.auth, nonce, ciphertext, ciphertext_len);
494+
495+
if (!cursor_push(&cursor, auth.sha.u.u8, 32))
496+
return NIP44_ERR_BUFFER_TOO_SMALL;
445497

446498
/*
447499
7. Base64-encode (with padding) params using `concat(version, nonce,
448500
ciphertext, mac)`
449501
*/
450-
return 0;
502+
*out = (char*)cursor.p;
503+
*out_len = base64_encode((char*)cursor.p,
504+
cursor_remaining_capacity(&cursor),
505+
(const char*)cursor.start,
506+
cursor.p - cursor.start);
507+
508+
if (*out_len == -1)
509+
return NIP44_ERR_BUFFER_TOO_SMALL;
510+
return NIP44_OK;
451511
}
452512

src/nip44.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ nip44_decrypt(void *secp_context,
3333
unsigned char *buf, size_t bufsize,
3434
unsigned char **decrypted, uint16_t *decrypted_len);
3535

36+
enum ndb_decrypt_result
37+
nip44_encrypt(void *secp, const unsigned char *sender_seckey,
38+
const unsigned char *receiver_pubkey,
39+
const unsigned char *plaintext, uint16_t plaintext_size,
40+
unsigned char *buf, size_t bufsize,
41+
char **out, ssize_t *out_len);
42+
3643
enum ndb_decrypt_result
3744
nip44_decrypt_raw(void *secp,
3845
const unsigned char *sender_pubkey,

test.c

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,65 @@ static void test_nip44_decrypt()
285285
secp256k1_context_destroy(context);
286286
}
287287

288+
static void test_nip44_round_trip()
289+
{
290+
static const unsigned char send_sec[32] = {
291+
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1
292+
};
293+
static const unsigned char recv_sec[32] = {
294+
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2
295+
};
296+
static const unsigned char send_pub[32] = {
297+
0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0,
298+
0x62, 0x95, 0xce, 0x87, 0x0b, 0x07, 0x02, 0x9b, 0xfc, 0xdb,
299+
0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2, 0x81, 0x5b, 0x16, 0xf8,
300+
0x17, 0x98
301+
};
302+
static const unsigned char recv_pub[32] = {
303+
0xc6, 0x04, 0x7f, 0x94, 0x41, 0xed, 0x7d, 0x6d, 0x30, 0x45,
304+
0x40, 0x6e, 0x95, 0xc0, 0x7c, 0xd8, 0x5c, 0x77, 0x8e, 0x4b,
305+
0x8c, 0xef, 0x3c, 0xa7, 0xab, 0xac, 0x09, 0xb9, 0x5c, 0x70,
306+
0x9e, 0xe5
307+
};
308+
static const char plaintext[] = "hello, world";
309+
310+
unsigned char buf[1024];
311+
unsigned char buf2[1024];
312+
char *out, *plaintext_out;
313+
ssize_t out_len;
314+
int ok;
315+
uint16_t plaintext_len;
316+
secp256k1_context *context;
317+
318+
319+
context = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
320+
321+
ok = nip44_encrypt(context, send_sec, recv_pub,
322+
(const unsigned char *)plaintext, sizeof(plaintext)-1,
323+
buf, sizeof(buf), &out, &out_len);
324+
325+
if (ok != NIP44_OK)
326+
printf("nip44 encrypt err: %s\n", nip44_err_msg(ok));
327+
assert(ok == NIP44_OK);
328+
printf("encrypted '%.*s' out_len: %ld\n", (int)out_len, out, out_len);
329+
330+
ok = nip44_decrypt(context, send_pub, recv_sec,
331+
out, out_len,
332+
buf2, sizeof(buf2),
333+
(unsigned char**)&plaintext_out, &plaintext_len);
334+
335+
if (ok != NIP44_OK)
336+
printf("nip44 decrypt err: %s\n", nip44_err_msg(ok));
337+
assert(ok == NIP44_OK);
338+
printf("plaintext_len %d, sizeof plaintext(%ld)\n", plaintext_len, sizeof(plaintext));
339+
assert(plaintext_len == sizeof(plaintext)-1);
340+
341+
342+
assert(!strcmp(plaintext, plaintext_out));
343+
344+
secp256k1_context_destroy(context);
345+
}
346+
288347
static void test_giftwrap_unwrap()
289348
{
290349
/*
@@ -2393,6 +2452,7 @@ void test_replay_attack() {
23932452
int main(int argc, const char *argv[]) {
23942453
delete_test_db();
23952454

2455+
test_nip44_round_trip();
23962456
test_nip44_test_vector();
23972457
test_nip44_decrypt();
23982458
test_replay_attack();

0 commit comments

Comments
 (0)