From 4de6a69931f918b6608f60d7d566805d06ba2b18 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Sun, 4 May 2025 07:00:09 -0300 Subject: [PATCH] allow NIP-44 to encrypt more than 65535 bytes. --- 44.md | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/44.md b/44.md index a7c13f12..a6d1325b 100644 --- a/44.md +++ b/44.md @@ -84,10 +84,12 @@ NIP-44 version 2 has the following design characteristics: - Slice 76-byte HKDF output into: `chacha_key` (bytes 0..32), `chacha_nonce` (bytes 32..44), `hmac_key` (bytes 44..76) 4. Add padding - Content must be encoded from UTF-8 into byte array - - Validate plaintext length. Minimum is 1 byte, maximum is 65535 bytes + - Validate plaintext length. Minimum is 1 byte, maximum is 4294967296 bytes - Padding format is: `[plaintext_length: u16][plaintext][zero_bytes]` - Padding algorithm is related to powers-of-two, with min padded msg size of 32 bytes - - Plaintext length is encoded in big-endian as first 2 bytes of the padded blob + - Plaintext length is encoded in big-endian: + - if smaller than 65536, as a u16 in the first 2 bytes of the padded blob; + - if greater than 65536, the first the first 6 bytes of the padded blob, the first 2 being zero and the other 4 being the actual encoded length as u32 5. Encrypt padded content - Use ChaCha20, with key and nonce from step 3 6. Calculate MAC (message authentication code) @@ -124,7 +126,9 @@ validation rules, refer to BIP-340. 6. Decrypt ciphertext - Use ChaCha20 with key and nonce from step 3 7. Remove padding - - Read the first two BE bytes of plaintext that correspond to plaintext length + - Read the first 2 bytes, + - if they're zero, read the next 4 bytes as the u32 big-endian plaintext length; + - otherwise interpret those 2 bytes as the u16 plaintext length - Verify that the length of sliced plaintext matches the value of the two BE bytes - Verify that calculated padding from step 3 of the [encryption](#Encryption) process matches the actual padding @@ -148,8 +152,6 @@ validation rules, refer to BIP-340. - `x[i:j]`, where `x` is a byte array and `i, j <= 0` returns a `(j - i)`-byte array with a copy of the `i`-th byte (inclusive) to the `j`-th byte (exclusive) of `x`. - Constants `c`: - - `min_plaintext_size` is 1. 1 byte msg is padded to 32 bytes. - - `max_plaintext_size` is 65535 (64kB - 1). It is padded to 65536 bytes. - Functions - `base64_encode(string)` and `base64_decode(bytes)` are Base64 ([RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648), with padding) - `concat` refers to byte array concatenation @@ -182,16 +184,27 @@ def calc_padded_len(unpadded_len): def pad(plaintext): unpadded = utf8_encode(plaintext) unpadded_len = len(plaintext) - if (unpadded_len < c.min_plaintext_size or - unpadded_len > c.max_plaintext_size): raise Exception('invalid plaintext length') - prefix = write_u16_be(unpadded_len) + if (unpadded_len < 1 or + unpadded_len > 4294967295): raise Exception('invalid plaintext length') + if unpadded_len > 65536: + prefix = concat( + [0, 0], + write_u32_be(unpadded_len), + ) + else: + prefix = write_u16_be(unpadded_len) suffix = zeros(calc_padded_len(unpadded_len) - unpadded_len) return concat(prefix, unpadded, suffix) # Converts padded bytearray to unpadded plaintext def unpad(padded): unpadded_len = read_uint16_be(padded[0:2]) - unpadded = padded[2:2+unpadded_len] + if unpadded_len == 0: + unpadded_len = read_uint32_be(padded[2:6]) + unpadded = padded[6:6+unpadded_len] + else: + unpadded = padded[2:2+unpadded_len] + if (unpadded_len == 0 or len(unpadded) != unpadded_len or len(padded) != 2 + calc_padded_len(unpadded_len)): raise Exception('invalid padding')