Coverage Report

Created: 2026-08-25 19:02

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