Skip to content

Commit 5eb6690

Browse files
committed
Merge bitcoin/bitcoin#28100: crypto: more Span<std::byte> modernization & follow-ups
57cc136 crypto: make ChaCha20::SetKey wipe buffer (Pieter Wuille) da0ec62 tests: miscellaneous hex / std::byte improvements (Pieter Wuille) bdcbc85 fuzz: support std::byte in Consume{Fixed,Variable}LengthByteVector (Pieter Wuille) 7d1cd93 crypto: require key on ChaCha20 initialization (Pieter Wuille) 44c1176 random: simplify FastRandomContext::randbytes using fillrand (Pieter Wuille) 3da636e crypto: refactor ChaCha20 classes to use Span<std::byte> interface (Pieter Wuille) Pull request description: This modernizes the ChaCha20 and ChaCha20Aligned interfaces to be `Span<std::byte>` based, and other improvements. * Modifies all functions and constructors of `ChaCha20` and `ChaCha20Aligned` to be `Span<std::byte>` based (aligning them with `FSChaCha20`, `AEADChaCha20Poly1305`, and `FSChaCha20Poly1305`) * Remove default constructors, to make sure all call sites provide a key (suggested in bitcoin/bitcoin#26153 (comment)) * Wipe key material on rekey for security (suggested in bitcoin/bitcoin#26153 (comment)) * Use `HexStr` on byte vectors in tests (suggested in bitcoin/bitcoin#27993 (comment)) * Support `std::byte` vectors in `ConsumeRandomLengthByteVector` and `ConsumeFixedLengthByteVector`, and use it (suggested in bitcoin/bitcoin#27993 (comment)) * And a few more. While related, I don't see this as a necessary for BIP324. ACKs for top commit: stratospher: ACK 57cc136. theStack: re-ACK 57cc136 Tree-SHA512: 361da4ff003c8465a32eeac0983a8a6f047dbbf5b400168b409c8e3234e79d577fc854e0764389446585da3e12b964c94dd67fc0c9c1d1d092cec296121e05d4
2 parents e4a855c + 57cc136 commit 5eb6690

File tree

12 files changed

+242
-234
lines changed

12 files changed

+242
-234
lines changed

src/bench/chacha20.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ static const uint64_t BUFFER_SIZE_LARGE = 1024*1024;
1414

1515
static void CHACHA20(benchmark::Bench& bench, size_t buffersize)
1616
{
17-
std::vector<uint8_t> key(32,0);
18-
ChaCha20 ctx(key.data());
19-
ctx.Seek64({0, 0}, 0);
20-
std::vector<uint8_t> in(buffersize,0);
21-
std::vector<uint8_t> out(buffersize,0);
17+
std::vector<std::byte> key(32, {});
18+
ChaCha20 ctx(key);
19+
ctx.Seek({0, 0}, 0);
20+
std::vector<std::byte> in(buffersize, {});
21+
std::vector<std::byte> out(buffersize, {});
2222
bench.batch(in.size()).unit("byte").run([&] {
23-
ctx.Crypt(in.data(), out.data(), in.size());
23+
ctx.Crypt(in, out);
2424
});
2525
}
2626

src/crypto/chacha20.cpp

Lines changed: 73 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <crypto/common.h>
99
#include <crypto/chacha20.h>
1010
#include <support/cleanse.h>
11+
#include <span.h>
1112

1213
#include <algorithm>
1314
#include <string.h>
@@ -22,47 +23,47 @@ constexpr static inline uint32_t rotl32(uint32_t v, int c) { return (v << c) | (
2223

2324
#define REPEAT10(a) do { {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; } while(0)
2425

25-
void ChaCha20Aligned::SetKey32(const unsigned char* k)
26+
void ChaCha20Aligned::SetKey(Span<const std::byte> key) noexcept
2627
{
27-
input[0] = ReadLE32(k + 0);
28-
input[1] = ReadLE32(k + 4);
29-
input[2] = ReadLE32(k + 8);
30-
input[3] = ReadLE32(k + 12);
31-
input[4] = ReadLE32(k + 16);
32-
input[5] = ReadLE32(k + 20);
33-
input[6] = ReadLE32(k + 24);
34-
input[7] = ReadLE32(k + 28);
28+
assert(key.size() == KEYLEN);
29+
input[0] = ReadLE32(UCharCast(key.data() + 0));
30+
input[1] = ReadLE32(UCharCast(key.data() + 4));
31+
input[2] = ReadLE32(UCharCast(key.data() + 8));
32+
input[3] = ReadLE32(UCharCast(key.data() + 12));
33+
input[4] = ReadLE32(UCharCast(key.data() + 16));
34+
input[5] = ReadLE32(UCharCast(key.data() + 20));
35+
input[6] = ReadLE32(UCharCast(key.data() + 24));
36+
input[7] = ReadLE32(UCharCast(key.data() + 28));
3537
input[8] = 0;
3638
input[9] = 0;
3739
input[10] = 0;
3840
input[11] = 0;
3941
}
4042

41-
ChaCha20Aligned::ChaCha20Aligned()
42-
{
43-
memset(input, 0, sizeof(input));
44-
}
45-
4643
ChaCha20Aligned::~ChaCha20Aligned()
4744
{
4845
memory_cleanse(input, sizeof(input));
4946
}
5047

51-
ChaCha20Aligned::ChaCha20Aligned(const unsigned char* key32)
48+
ChaCha20Aligned::ChaCha20Aligned(Span<const std::byte> key) noexcept
5249
{
53-
SetKey32(key32);
50+
SetKey(key);
5451
}
5552

56-
void ChaCha20Aligned::Seek64(Nonce96 nonce, uint32_t block_counter)
53+
void ChaCha20Aligned::Seek(Nonce96 nonce, uint32_t block_counter) noexcept
5754
{
5855
input[8] = block_counter;
5956
input[9] = nonce.first;
6057
input[10] = nonce.second;
6158
input[11] = nonce.second >> 32;
6259
}
6360

64-
inline void ChaCha20Aligned::Keystream64(unsigned char* c, size_t blocks)
61+
inline void ChaCha20Aligned::Keystream(Span<std::byte> output) noexcept
6562
{
63+
unsigned char* c = UCharCast(output.data());
64+
size_t blocks = output.size() / BLOCKLEN;
65+
assert(blocks * BLOCKLEN == output.size());
66+
6667
uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
6768
uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
6869

@@ -154,12 +155,18 @@ inline void ChaCha20Aligned::Keystream64(unsigned char* c, size_t blocks)
154155
return;
155156
}
156157
blocks -= 1;
157-
c += 64;
158+
c += BLOCKLEN;
158159
}
159160
}
160161

161-
inline void ChaCha20Aligned::Crypt64(const unsigned char* m, unsigned char* c, size_t blocks)
162+
inline void ChaCha20Aligned::Crypt(Span<const std::byte> in_bytes, Span<std::byte> out_bytes) noexcept
162163
{
164+
assert(in_bytes.size() == out_bytes.size());
165+
const unsigned char* m = UCharCast(in_bytes.data());
166+
unsigned char* c = UCharCast(out_bytes.data());
167+
size_t blocks = out_bytes.size() / BLOCKLEN;
168+
assert(blocks * BLOCKLEN == out_bytes.size());
169+
163170
uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
164171
uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
165172

@@ -268,70 +275,75 @@ inline void ChaCha20Aligned::Crypt64(const unsigned char* m, unsigned char* c, s
268275
return;
269276
}
270277
blocks -= 1;
271-
c += 64;
272-
m += 64;
278+
c += BLOCKLEN;
279+
m += BLOCKLEN;
273280
}
274281
}
275282

276-
void ChaCha20::Keystream(unsigned char* c, size_t bytes)
283+
void ChaCha20::Keystream(Span<std::byte> out) noexcept
277284
{
278-
if (!bytes) return;
285+
if (out.empty()) return;
279286
if (m_bufleft) {
280-
unsigned reuse = std::min<size_t>(m_bufleft, bytes);
281-
memcpy(c, m_buffer + 64 - m_bufleft, reuse);
287+
unsigned reuse = std::min<size_t>(m_bufleft, out.size());
288+
std::copy(m_buffer.end() - m_bufleft, m_buffer.end() - m_bufleft + reuse, out.begin());
282289
m_bufleft -= reuse;
283-
bytes -= reuse;
284-
c += reuse;
290+
out = out.subspan(reuse);
285291
}
286-
if (bytes >= 64) {
287-
size_t blocks = bytes / 64;
288-
m_aligned.Keystream64(c, blocks);
289-
c += blocks * 64;
290-
bytes -= blocks * 64;
292+
if (out.size() >= m_aligned.BLOCKLEN) {
293+
size_t blocks = out.size() / m_aligned.BLOCKLEN;
294+
m_aligned.Keystream(out.first(blocks * m_aligned.BLOCKLEN));
295+
out = out.subspan(blocks * m_aligned.BLOCKLEN);
291296
}
292-
if (bytes) {
293-
m_aligned.Keystream64(m_buffer, 1);
294-
memcpy(c, m_buffer, bytes);
295-
m_bufleft = 64 - bytes;
297+
if (!out.empty()) {
298+
m_aligned.Keystream(m_buffer);
299+
std::copy(m_buffer.begin(), m_buffer.begin() + out.size(), out.begin());
300+
m_bufleft = m_aligned.BLOCKLEN - out.size();
296301
}
297302
}
298303

299-
void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes)
304+
void ChaCha20::Crypt(Span<const std::byte> input, Span<std::byte> output) noexcept
300305
{
301-
if (!bytes) return;
306+
assert(input.size() == output.size());
307+
308+
if (!input.size()) return;
302309
if (m_bufleft) {
303-
unsigned reuse = std::min<size_t>(m_bufleft, bytes);
310+
unsigned reuse = std::min<size_t>(m_bufleft, input.size());
304311
for (unsigned i = 0; i < reuse; i++) {
305-
c[i] = m[i] ^ m_buffer[64 - m_bufleft + i];
312+
output[i] = input[i] ^ m_buffer[m_aligned.BLOCKLEN - m_bufleft + i];
306313
}
307314
m_bufleft -= reuse;
308-
bytes -= reuse;
309-
c += reuse;
310-
m += reuse;
315+
output = output.subspan(reuse);
316+
input = input.subspan(reuse);
311317
}
312-
if (bytes >= 64) {
313-
size_t blocks = bytes / 64;
314-
m_aligned.Crypt64(m, c, blocks);
315-
c += blocks * 64;
316-
m += blocks * 64;
317-
bytes -= blocks * 64;
318+
if (input.size() >= m_aligned.BLOCKLEN) {
319+
size_t blocks = input.size() / m_aligned.BLOCKLEN;
320+
m_aligned.Crypt(input.first(blocks * m_aligned.BLOCKLEN), output.first(blocks * m_aligned.BLOCKLEN));
321+
output = output.subspan(blocks * m_aligned.BLOCKLEN);
322+
input = input.subspan(blocks * m_aligned.BLOCKLEN);
318323
}
319-
if (bytes) {
320-
m_aligned.Keystream64(m_buffer, 1);
321-
for (unsigned i = 0; i < bytes; i++) {
322-
c[i] = m[i] ^ m_buffer[i];
324+
if (!input.empty()) {
325+
m_aligned.Keystream(m_buffer);
326+
for (unsigned i = 0; i < input.size(); i++) {
327+
output[i] = input[i] ^ m_buffer[i];
323328
}
324-
m_bufleft = 64 - bytes;
329+
m_bufleft = m_aligned.BLOCKLEN - input.size();
325330
}
326331
}
327332

328333
ChaCha20::~ChaCha20()
329334
{
330-
memory_cleanse(m_buffer, sizeof(m_buffer));
335+
memory_cleanse(m_buffer.data(), m_buffer.size());
336+
}
337+
338+
void ChaCha20::SetKey(Span<const std::byte> key) noexcept
339+
{
340+
m_aligned.SetKey(key);
341+
m_bufleft = 0;
342+
memory_cleanse(m_buffer.data(), m_buffer.size());
331343
}
332344

333345
FSChaCha20::FSChaCha20(Span<const std::byte> key, uint32_t rekey_interval) noexcept :
334-
m_chacha20(UCharCast(key.data())), m_rekey_interval(rekey_interval)
346+
m_chacha20(key), m_rekey_interval(rekey_interval)
335347
{
336348
assert(key.size() == KEYLEN);
337349
}
@@ -341,20 +353,20 @@ void FSChaCha20::Crypt(Span<const std::byte> input, Span<std::byte> output) noex
341353
assert(input.size() == output.size());
342354

343355
// Invoke internal stream cipher for actual encryption/decryption.
344-
m_chacha20.Crypt(UCharCast(input.data()), UCharCast(output.data()), input.size());
356+
m_chacha20.Crypt(input, output);
345357

346358
// Rekey after m_rekey_interval encryptions/decryptions.
347359
if (++m_chunk_counter == m_rekey_interval) {
348360
// Get new key from the stream cipher.
349361
std::byte new_key[KEYLEN];
350-
m_chacha20.Keystream(UCharCast(new_key), sizeof(new_key));
362+
m_chacha20.Keystream(new_key);
351363
// Update its key.
352-
m_chacha20.SetKey32(UCharCast(new_key));
364+
m_chacha20.SetKey(new_key);
353365
// Wipe the key (a copy remains inside m_chacha20, where it'll be wiped on the next rekey
354366
// or on destruction).
355367
memory_cleanse(new_key, sizeof(new_key));
356368
// Set the nonce for the new section of output.
357-
m_chacha20.Seek64({0, ++m_rekey_counter}, 0);
369+
m_chacha20.Seek({0, ++m_rekey_counter}, 0);
358370
// Reset the chunk counter.
359371
m_chunk_counter = 0;
360372
}

src/crypto/chacha20.h

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,23 @@ class ChaCha20Aligned
2828
uint32_t input[12];
2929

3030
public:
31-
ChaCha20Aligned();
31+
/** Expected key length in constructor and SetKey. */
32+
static constexpr unsigned KEYLEN{32};
33+
34+
/** Block size (inputs/outputs to Keystream / Crypt should be multiples of this). */
35+
static constexpr unsigned BLOCKLEN{64};
36+
37+
/** For safety, disallow initialization without key. */
38+
ChaCha20Aligned() noexcept = delete;
3239

3340
/** Initialize a cipher with specified 32-byte key. */
34-
ChaCha20Aligned(const unsigned char* key32);
41+
ChaCha20Aligned(Span<const std::byte> key) noexcept;
3542

3643
/** Destructor to clean up private memory. */
3744
~ChaCha20Aligned();
3845

39-
/** set 32-byte key. */
40-
void SetKey32(const unsigned char* key32);
46+
/** Set 32-byte key, and seek to nonce 0 and block position 0. */
47+
void SetKey(Span<const std::byte> key) noexcept;
4148

4249
/** Type for 96-bit nonces used by the Set function below.
4350
*
@@ -51,61 +58,63 @@ class ChaCha20Aligned
5158

5259
/** Set the 96-bit nonce and 32-bit block counter.
5360
*
54-
* Block_counter selects a position to seek to (to byte 64*block_counter). After 256 GiB, the
55-
* block counter overflows, and nonce.first is incremented.
61+
* Block_counter selects a position to seek to (to byte BLOCKLEN*block_counter). After 256 GiB,
62+
* the block counter overflows, and nonce.first is incremented.
5663
*/
57-
void Seek64(Nonce96 nonce, uint32_t block_counter);
64+
void Seek(Nonce96 nonce, uint32_t block_counter) noexcept;
5865

59-
/** outputs the keystream of size <64*blocks> into <c> */
60-
void Keystream64(unsigned char* c, size_t blocks);
66+
/** outputs the keystream into out, whose length must be a multiple of BLOCKLEN. */
67+
void Keystream(Span<std::byte> out) noexcept;
6168

62-
/** enciphers the message <input> of length <64*blocks> and write the enciphered representation into <output>
63-
* Used for encryption and decryption (XOR)
69+
/** en/deciphers the message <input> and write the result into <output>
70+
*
71+
* The size of input and output must be equal, and be a multiple of BLOCKLEN.
6472
*/
65-
void Crypt64(const unsigned char* input, unsigned char* output, size_t blocks);
73+
void Crypt(Span<const std::byte> input, Span<std::byte> output) noexcept;
6674
};
6775

6876
/** Unrestricted ChaCha20 cipher. */
6977
class ChaCha20
7078
{
7179
private:
7280
ChaCha20Aligned m_aligned;
73-
unsigned char m_buffer[64] = {0};
81+
std::array<std::byte, ChaCha20Aligned::BLOCKLEN> m_buffer;
7482
unsigned m_bufleft{0};
7583

7684
public:
77-
ChaCha20() = default;
85+
/** Expected key length in constructor and SetKey. */
86+
static constexpr unsigned KEYLEN = ChaCha20Aligned::KEYLEN;
87+
88+
/** For safety, disallow initialization without key. */
89+
ChaCha20() noexcept = delete;
7890

7991
/** Initialize a cipher with specified 32-byte key. */
80-
ChaCha20(const unsigned char* key32) : m_aligned(key32) {}
92+
ChaCha20(Span<const std::byte> key) noexcept : m_aligned(key) {}
8193

8294
/** Destructor to clean up private memory. */
8395
~ChaCha20();
8496

85-
/** set 32-byte key. */
86-
void SetKey32(const unsigned char* key32)
87-
{
88-
m_aligned.SetKey32(key32);
89-
m_bufleft = 0;
90-
}
97+
/** Set 32-byte key, and seek to nonce 0 and block position 0. */
98+
void SetKey(Span<const std::byte> key) noexcept;
9199

92100
/** 96-bit nonce type. */
93101
using Nonce96 = ChaCha20Aligned::Nonce96;
94102

95-
/** Set the 96-bit nonce and 32-bit block counter. */
96-
void Seek64(Nonce96 nonce, uint32_t block_counter)
103+
/** Set the 96-bit nonce and 32-bit block counter. See ChaCha20Aligned::Seek. */
104+
void Seek(Nonce96 nonce, uint32_t block_counter) noexcept
97105
{
98-
m_aligned.Seek64(nonce, block_counter);
106+
m_aligned.Seek(nonce, block_counter);
99107
m_bufleft = 0;
100108
}
101109

102-
/** outputs the keystream of size <bytes> into <c> */
103-
void Keystream(unsigned char* c, size_t bytes);
104-
105-
/** enciphers the message <input> of length <bytes> and write the enciphered representation into <output>
106-
* Used for encryption and decryption (XOR)
110+
/** en/deciphers the message <in_bytes> and write the result into <out_bytes>
111+
*
112+
* The size of in_bytes and out_bytes must be equal.
107113
*/
108-
void Crypt(const unsigned char* input, unsigned char* output, size_t bytes);
114+
void Crypt(Span<const std::byte> in_bytes, Span<std::byte> out_bytes) noexcept;
115+
116+
/** outputs the keystream to out. */
117+
void Keystream(Span<std::byte> out) noexcept;
109118
};
110119

111120
/** Forward-secure ChaCha20

0 commit comments

Comments
 (0)