1#include <crypto/chacha20.h>
2#include <math/min_max.h>
3#include <stdint.h>
4#include <string.h>
5
6#define ROTL32(v, n) ((v << n) | (v >> (32 - n)))
7#define CHACHA20_BLOCK_SIZE UINT64_C(64)
8
9// quarter round
10#define QR(a, b, c, d) \
11 a += b; \
12 d ^= a; \
13 d = ROTL32(d, 16); \
14 c += d; \
15 b ^= c; \
16 b = ROTL32(b, 12); \
17 a += b; \
18 d ^= a; \
19 d = ROTL32(d, 8); \
20 c += d; \
21 b ^= c; \
22 b = ROTL32(b, 7)
23
24static const char *sigma = "expand 32-byte k";
25
26static uint32_t load32_le(const uint8_t *src) {
27 return ((uint32_t) src[0]) | ((uint32_t) src[1] << 8) |
28 ((uint32_t) src[2] << 16) | ((uint32_t) src[3] << 24);
29}
30
31static void store32_le(uint8_t *dst, uint32_t val) {
32 dst[0] = val & 0xff;
33 dst[1] = (val >> 8) & 0xff;
34 dst[2] = (val >> 16) & 0xff;
35 dst[3] = (val >> 24) & 0xff;
36}
37
38static void chacha20_init_state(uint32_t state[16], const uint8_t key[32],
39 const uint8_t nonce[12], uint32_t counter) {
40 const uint8_t *constants = (const uint8_t *) sigma;
41
42 state[0] = load32_le(src: constants + 0);
43 state[1] = load32_le(src: constants + 4);
44 state[2] = load32_le(src: constants + 8);
45 state[3] = load32_le(src: constants + 12);
46
47 for (int i = 0; i < 8; i++) {
48 state[4 + i] = load32_le(src: key + i * 4);
49 }
50
51 state[12] = counter;
52 state[13] = load32_le(src: nonce + 0);
53 state[14] = load32_le(src: nonce + 4);
54 state[15] = load32_le(src: nonce + 8);
55}
56
57static void chacha20_block(uint8_t output[64], const uint32_t input[16]) {
58 uint32_t x[16];
59 memcpy(x, input, sizeof(x));
60
61 for (int i = 0; i < 10; i++) {
62 // Column rounds
63 QR(x[0], x[4], x[8], x[12]);
64 QR(x[1], x[5], x[9], x[13]);
65 QR(x[2], x[6], x[10], x[14]);
66 QR(x[3], x[7], x[11], x[15]);
67 // Diagonal rounds
68 QR(x[0], x[5], x[10], x[15]);
69 QR(x[1], x[6], x[11], x[12]);
70 QR(x[2], x[7], x[8], x[13]);
71 QR(x[3], x[4], x[9], x[14]);
72 }
73
74 for (int i = 0; i < 16; i++) {
75 x[i] += input[i];
76 store32_le(dst: output + 4 * i, val: x[i]);
77 }
78}
79
80// Encrypt or decrypt (it's symmetric)
81void chacha20_encrypt(const uint8_t key[32], const uint8_t nonce[12],
82 uint32_t counter, const uint8_t *in, uint8_t *out,
83 uint64_t len) {
84 uint32_t state[16];
85 uint8_t keystream[64];
86 uint64_t offset = 0;
87
88 while (len > 0) {
89 chacha20_init_state(state, key, nonce, counter);
90 chacha20_block(output: keystream, input: state);
91 counter++;
92
93 uint64_t block_len = MIN(len, CHACHA20_BLOCK_SIZE);
94 for (uint64_t i = 0; i < block_len; i++) {
95 out[offset + i] = in[offset + i] ^ keystream[i];
96 }
97
98 offset += block_len;
99 len -= block_len;
100 }
101}
102