Coverage Report

Created: 2024-10-29 12:10

/root/bitcoin/src/crypto/chacha20.cpp
Line
Count
Source (jump to first uncovered line)
1
// Copyright (c) 2017-2022 The Bitcoin Core developers
2
// Distributed under the MIT software license, see the accompanying
3
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5
// Based on the public domain implementation 'merged' by D. J. Bernstein
6
// See https://cr.yp.to/chacha.html.
7
8
#include <crypto/common.h>
9
#include <crypto/chacha20.h>
10
#include <support/cleanse.h>
11
#include <span.h>
12
13
#include <algorithm>
14
#include <bit>
15
#include <string.h>
16
17
#define QUARTERROUND(a,b,c,d) \
18
  a += b; d = std::rotl(d ^ a, 16); \
19
  c += d; b = std::rotl(b ^ c, 12); \
20
  a += b; d = std::rotl(d ^ a, 8); \
21
  c += d; b = std::rotl(b ^ c, 7);
22
23
62.0k
#define REPEAT10(a) do { {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; } while(0)
24
25
void ChaCha20Aligned::SetKey(Span<const std::byte> key) noexcept
26
82.8k
{
27
82.8k
    assert(key.size() == KEYLEN);
28
82.8k
    input[0] = ReadLE32(UCharCast(key.data() + 0));
29
82.8k
    input[1] = ReadLE32(UCharCast(key.data() + 4));
30
82.8k
    input[2] = ReadLE32(UCharCast(key.data() + 8));
31
82.8k
    input[3] = ReadLE32(UCharCast(key.data() + 12));
32
82.8k
    input[4] = ReadLE32(UCharCast(key.data() + 16));
33
82.8k
    input[5] = ReadLE32(UCharCast(key.data() + 20));
34
82.8k
    input[6] = ReadLE32(UCharCast(key.data() + 24));
35
82.8k
    input[7] = ReadLE32(UCharCast(key.data() + 28));
36
82.8k
    input[8] = 0;
37
82.8k
    input[9] = 0;
38
82.8k
    input[10] = 0;
39
82.8k
    input[11] = 0;
40
82.8k
}
41
42
ChaCha20Aligned::~ChaCha20Aligned()
43
43.3k
{
44
43.3k
    memory_cleanse(input, sizeof(input));
45
43.3k
}
46
47
ChaCha20Aligned::ChaCha20Aligned(Span<const std::byte> key) noexcept
48
43.3k
{
49
43.3k
    SetKey(key);
50
43.3k
}
51
52
void ChaCha20Aligned::Seek(Nonce96 nonce, uint32_t block_counter) noexcept
53
0
{
54
0
    input[8] = block_counter;
55
0
    input[9] = nonce.first;
56
0
    input[10] = nonce.second;
57
0
    input[11] = nonce.second >> 32;
58
0
}
59
60
inline void ChaCha20Aligned::Keystream(Span<std::byte> output) noexcept
61
62.0k
{
62
62.0k
    unsigned char* c = UCharCast(output.data());
63
62.0k
    size_t blocks = output.size() / BLOCKLEN;
64
62.0k
    assert(blocks * BLOCKLEN == output.size());
65
66
62.0k
    uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
67
62.0k
    uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
68
69
62.0k
    if (!blocks) return;
70
71
62.0k
    j4 = input[0];
72
62.0k
    j5 = input[1];
73
62.0k
    j6 = input[2];
74
62.0k
    j7 = input[3];
75
62.0k
    j8 = input[4];
76
62.0k
    j9 = input[5];
77
62.0k
    j10 = input[6];
78
62.0k
    j11 = input[7];
79
62.0k
    j12 = input[8];
80
62.0k
    j13 = input[9];
81
62.0k
    j14 = input[10];
82
62.0k
    j15 = input[11];
83
84
62.0k
    for (;;) {
85
62.0k
        x0 = 0x61707865;
86
62.0k
        x1 = 0x3320646e;
87
62.0k
        x2 = 0x79622d32;
88
62.0k
        x3 = 0x6b206574;
89
62.0k
        x4 = j4;
90
62.0k
        x5 = j5;
91
62.0k
        x6 = j6;
92
62.0k
        x7 = j7;
93
62.0k
        x8 = j8;
94
62.0k
        x9 = j9;
95
62.0k
        x10 = j10;
96
62.0k
        x11 = j11;
97
62.0k
        x12 = j12;
98
62.0k
        x13 = j13;
99
62.0k
        x14 = j14;
100
62.0k
        x15 = j15;
101
102
        // The 20 inner ChaCha20 rounds are unrolled here for performance.
103
62.0k
        REPEAT10(
104
62.0k
            QUARTERROUND( x0, x4, x8,x12);
105
62.0k
            QUARTERROUND( x1, x5, x9,x13);
106
62.0k
            QUARTERROUND( x2, x6,x10,x14);
107
62.0k
            QUARTERROUND( x3, x7,x11,x15);
108
62.0k
            QUARTERROUND( x0, x5,x10,x15);
109
62.0k
            QUARTERROUND( x1, x6,x11,x12);
110
62.0k
            QUARTERROUND( x2, x7, x8,x13);
111
62.0k
            QUARTERROUND( x3, x4, x9,x14);
112
62.0k
        );
113
114
62.0k
        x0 += 0x61707865;
115
62.0k
        x1 += 0x3320646e;
116
62.0k
        x2 += 0x79622d32;
117
62.0k
        x3 += 0x6b206574;
118
62.0k
        x4 += j4;
119
62.0k
        x5 += j5;
120
62.0k
        x6 += j6;
121
62.0k
        x7 += j7;
122
62.0k
        x8 += j8;
123
62.0k
        x9 += j9;
124
62.0k
        x10 += j10;
125
62.0k
        x11 += j11;
126
62.0k
        x12 += j12;
127
62.0k
        x13 += j13;
128
62.0k
        x14 += j14;
129
62.0k
        x15 += j15;
130
131
62.0k
        ++j12;
132
62.0k
        if (!j12) ++j13;
133
134
62.0k
        WriteLE32(c + 0, x0);
135
62.0k
        WriteLE32(c + 4, x1);
136
62.0k
        WriteLE32(c + 8, x2);
137
62.0k
        WriteLE32(c + 12, x3);
138
62.0k
        WriteLE32(c + 16, x4);
139
62.0k
        WriteLE32(c + 20, x5);
140
62.0k
        WriteLE32(c + 24, x6);
141
62.0k
        WriteLE32(c + 28, x7);
142
62.0k
        WriteLE32(c + 32, x8);
143
62.0k
        WriteLE32(c + 36, x9);
144
62.0k
        WriteLE32(c + 40, x10);
145
62.0k
        WriteLE32(c + 44, x11);
146
62.0k
        WriteLE32(c + 48, x12);
147
62.0k
        WriteLE32(c + 52, x13);
148
62.0k
        WriteLE32(c + 56, x14);
149
62.0k
        WriteLE32(c + 60, x15);
150
151
62.0k
        if (blocks == 1) {
152
62.0k
            input[8] = j12;
153
62.0k
            input[9] = j13;
154
62.0k
            return;
155
62.0k
        }
156
0
        blocks -= 1;
157
0
        c += BLOCKLEN;
158
0
    }
159
62.0k
}
160
161
inline void ChaCha20Aligned::Crypt(Span<const std::byte> in_bytes, Span<std::byte> out_bytes) noexcept
162
0
{
163
0
    assert(in_bytes.size() == out_bytes.size());
164
0
    const unsigned char* m = UCharCast(in_bytes.data());
165
0
    unsigned char* c = UCharCast(out_bytes.data());
166
0
    size_t blocks = out_bytes.size() / BLOCKLEN;
167
0
    assert(blocks * BLOCKLEN == out_bytes.size());
168
169
0
    uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
170
0
    uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
171
172
0
    if (!blocks) return;
173
174
0
    j4 = input[0];
175
0
    j5 = input[1];
176
0
    j6 = input[2];
177
0
    j7 = input[3];
178
0
    j8 = input[4];
179
0
    j9 = input[5];
180
0
    j10 = input[6];
181
0
    j11 = input[7];
182
0
    j12 = input[8];
183
0
    j13 = input[9];
184
0
    j14 = input[10];
185
0
    j15 = input[11];
186
187
0
    for (;;) {
188
0
        x0 = 0x61707865;
189
0
        x1 = 0x3320646e;
190
0
        x2 = 0x79622d32;
191
0
        x3 = 0x6b206574;
192
0
        x4 = j4;
193
0
        x5 = j5;
194
0
        x6 = j6;
195
0
        x7 = j7;
196
0
        x8 = j8;
197
0
        x9 = j9;
198
0
        x10 = j10;
199
0
        x11 = j11;
200
0
        x12 = j12;
201
0
        x13 = j13;
202
0
        x14 = j14;
203
0
        x15 = j15;
204
205
        // The 20 inner ChaCha20 rounds are unrolled here for performance.
206
0
        REPEAT10(
207
0
            QUARTERROUND( x0, x4, x8,x12);
208
0
            QUARTERROUND( x1, x5, x9,x13);
209
0
            QUARTERROUND( x2, x6,x10,x14);
210
0
            QUARTERROUND( x3, x7,x11,x15);
211
0
            QUARTERROUND( x0, x5,x10,x15);
212
0
            QUARTERROUND( x1, x6,x11,x12);
213
0
            QUARTERROUND( x2, x7, x8,x13);
214
0
            QUARTERROUND( x3, x4, x9,x14);
215
0
        );
216
217
0
        x0 += 0x61707865;
218
0
        x1 += 0x3320646e;
219
0
        x2 += 0x79622d32;
220
0
        x3 += 0x6b206574;
221
0
        x4 += j4;
222
0
        x5 += j5;
223
0
        x6 += j6;
224
0
        x7 += j7;
225
0
        x8 += j8;
226
0
        x9 += j9;
227
0
        x10 += j10;
228
0
        x11 += j11;
229
0
        x12 += j12;
230
0
        x13 += j13;
231
0
        x14 += j14;
232
0
        x15 += j15;
233
234
0
        x0 ^= ReadLE32(m + 0);
235
0
        x1 ^= ReadLE32(m + 4);
236
0
        x2 ^= ReadLE32(m + 8);
237
0
        x3 ^= ReadLE32(m + 12);
238
0
        x4 ^= ReadLE32(m + 16);
239
0
        x5 ^= ReadLE32(m + 20);
240
0
        x6 ^= ReadLE32(m + 24);
241
0
        x7 ^= ReadLE32(m + 28);
242
0
        x8 ^= ReadLE32(m + 32);
243
0
        x9 ^= ReadLE32(m + 36);
244
0
        x10 ^= ReadLE32(m + 40);
245
0
        x11 ^= ReadLE32(m + 44);
246
0
        x12 ^= ReadLE32(m + 48);
247
0
        x13 ^= ReadLE32(m + 52);
248
0
        x14 ^= ReadLE32(m + 56);
249
0
        x15 ^= ReadLE32(m + 60);
250
251
0
        ++j12;
252
0
        if (!j12) ++j13;
253
254
0
        WriteLE32(c + 0, x0);
255
0
        WriteLE32(c + 4, x1);
256
0
        WriteLE32(c + 8, x2);
257
0
        WriteLE32(c + 12, x3);
258
0
        WriteLE32(c + 16, x4);
259
0
        WriteLE32(c + 20, x5);
260
0
        WriteLE32(c + 24, x6);
261
0
        WriteLE32(c + 28, x7);
262
0
        WriteLE32(c + 32, x8);
263
0
        WriteLE32(c + 36, x9);
264
0
        WriteLE32(c + 40, x10);
265
0
        WriteLE32(c + 44, x11);
266
0
        WriteLE32(c + 48, x12);
267
0
        WriteLE32(c + 52, x13);
268
0
        WriteLE32(c + 56, x14);
269
0
        WriteLE32(c + 60, x15);
270
271
0
        if (blocks == 1) {
272
0
            input[8] = j12;
273
0
            input[9] = j13;
274
0
            return;
275
0
        }
276
0
        blocks -= 1;
277
0
        c += BLOCKLEN;
278
0
        m += BLOCKLEN;
279
0
    }
280
0
}
281
282
void ChaCha20::Keystream(Span<std::byte> out) noexcept
283
99.6k
{
284
99.6k
    if (out.empty()) return;
285
99.6k
    if (m_bufleft) {
286
57.2k
        unsigned reuse = std::min<size_t>(m_bufleft, out.size());
287
57.2k
        std::copy(m_buffer.end() - m_bufleft, m_buffer.end() - m_bufleft + reuse, out.begin());
288
57.2k
        m_bufleft -= reuse;
289
57.2k
        out = out.subspan(reuse);
290
57.2k
    }
291
99.6k
    if (out.size() >= m_aligned.BLOCKLEN) {
292
0
        size_t blocks = out.size() / m_aligned.BLOCKLEN;
293
0
        m_aligned.Keystream(out.first(blocks * m_aligned.BLOCKLEN));
294
0
        out = out.subspan(blocks * m_aligned.BLOCKLEN);
295
0
    }
296
99.6k
    if (!out.empty()) {
297
62.0k
        m_aligned.Keystream(m_buffer);
298
62.0k
        std::copy(m_buffer.begin(), m_buffer.begin() + out.size(), out.begin());
299
62.0k
        m_bufleft = m_aligned.BLOCKLEN - out.size();
300
62.0k
    }
301
99.6k
}
302
303
void ChaCha20::Crypt(Span<const std::byte> input, Span<std::byte> output) noexcept
304
0
{
305
0
    assert(input.size() == output.size());
306
307
0
    if (!input.size()) return;
308
0
    if (m_bufleft) {
309
0
        unsigned reuse = std::min<size_t>(m_bufleft, input.size());
310
0
        for (unsigned i = 0; i < reuse; i++) {
311
0
            output[i] = input[i] ^ m_buffer[m_aligned.BLOCKLEN - m_bufleft + i];
312
0
        }
313
0
        m_bufleft -= reuse;
314
0
        output = output.subspan(reuse);
315
0
        input = input.subspan(reuse);
316
0
    }
317
0
    if (input.size() >= m_aligned.BLOCKLEN) {
318
0
        size_t blocks = input.size() / m_aligned.BLOCKLEN;
319
0
        m_aligned.Crypt(input.first(blocks * m_aligned.BLOCKLEN), output.first(blocks * m_aligned.BLOCKLEN));
320
0
        output = output.subspan(blocks * m_aligned.BLOCKLEN);
321
0
        input = input.subspan(blocks * m_aligned.BLOCKLEN);
322
0
    }
323
0
    if (!input.empty()) {
324
0
        m_aligned.Keystream(m_buffer);
325
0
        for (unsigned i = 0; i < input.size(); i++) {
326
0
            output[i] = input[i] ^ m_buffer[i];
327
0
        }
328
0
        m_bufleft = m_aligned.BLOCKLEN - input.size();
329
0
    }
330
0
}
331
332
ChaCha20::~ChaCha20()
333
43.3k
{
334
43.3k
    memory_cleanse(m_buffer.data(), m_buffer.size());
335
43.3k
}
336
337
void ChaCha20::SetKey(Span<const std::byte> key) noexcept
338
39.5k
{
339
39.5k
    m_aligned.SetKey(key);
340
39.5k
    m_bufleft = 0;
341
39.5k
    memory_cleanse(m_buffer.data(), m_buffer.size());
342
39.5k
}
343
344
FSChaCha20::FSChaCha20(Span<const std::byte> key, uint32_t rekey_interval) noexcept :
345
0
    m_chacha20(key), m_rekey_interval(rekey_interval)
346
0
{
347
0
    assert(key.size() == KEYLEN);
348
0
}
349
350
void FSChaCha20::Crypt(Span<const std::byte> input, Span<std::byte> output) noexcept
351
0
{
352
0
    assert(input.size() == output.size());
353
354
    // Invoke internal stream cipher for actual encryption/decryption.
355
0
    m_chacha20.Crypt(input, output);
356
357
    // Rekey after m_rekey_interval encryptions/decryptions.
358
0
    if (++m_chunk_counter == m_rekey_interval) {
359
        // Get new key from the stream cipher.
360
0
        std::byte new_key[KEYLEN];
361
0
        m_chacha20.Keystream(new_key);
362
        // Update its key.
363
0
        m_chacha20.SetKey(new_key);
364
        // Wipe the key (a copy remains inside m_chacha20, where it'll be wiped on the next rekey
365
        // or on destruction).
366
0
        memory_cleanse(new_key, sizeof(new_key));
367
        // Set the nonce for the new section of output.
368
0
        m_chacha20.Seek({0, ++m_rekey_counter}, 0);
369
        // Reset the chunk counter.
370
0
        m_chunk_counter = 0;
371
0
    }
372
0
}