Skip to content

Commit 1e0198b

Browse files
committed
Merge bitcoin/bitcoin#26153: Reduce wasted pseudorandom bytes in ChaCha20 + various improvements
511aa4f Add unit test for ChaCha20's new caching (Pieter Wuille) fb243d2 Improve test vectors for ChaCha20 (Pieter Wuille) 93aee8b Inline ChaCha20 32-byte specific constants (Pieter Wuille) 62ec713 Only support 32-byte keys in ChaCha20{,Aligned} (Pieter Wuille) f21994a Use ChaCha20Aligned in MuHash3072 code (Pieter Wuille) 5d16f75 Use ChaCha20 caching in FastRandomContext (Pieter Wuille) 38eaece Add fuzz test for testing that ChaCha20 works as a stream (Pieter Wuille) 5f05b27 Add xoroshiro128++ PRNG (Martin Leitner-Ankerl) 12ff724 Make unrestricted ChaCha20 cipher not waste keystream bytes (Pieter Wuille) 6babf40 Rename ChaCha20::Seek -> Seek64 to clarify multiple of 64 (Pieter Wuille) e37bcaa Split ChaCha20 into aligned/unaligned variants (Pieter Wuille) Pull request description: This is an alternative to #25354 (by my benchmarking, somewhat faster), subsumes #25712, and adds additional test vectors. It separates the multiple-of-64-bytes-only "core" logic (which becomes simpler) from a layer around which performs caching/slicing to support arbitrary byte amounts. Both have their uses (in particular, the MuHash3072 code can benefit from multiple-of-64-bytes assumptions), plus the separation results in more readable code. Also, since FastRandomContext effectively had its own (more naive) caching on top of ChaCha20, that can be dropped in favor of ChaCha20's new built-in caching. I thought about rebasing #25712 on top of this, but the changes before are fairly extensive, so redid it instead. ACKs for top commit: ajtowns: ut reACK 511aa4f dhruv: tACK crACK 511aa4f Tree-SHA512: 3aa80971322a93e780c75a8d35bd39da3a9ea570fbae4491eaf0c45242f5f670a24a592c50ad870d5fd09b9f88ec06e274e8aa3cefd9561d623c63f7198cf2c7
2 parents 2b0cd76 + 511aa4f commit 1e0198b

15 files changed

+582
-225
lines changed

src/Makefile.test.include

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ BITCOIN_TESTS =\
162162
test/validation_flush_tests.cpp \
163163
test/validation_tests.cpp \
164164
test/validationinterface_tests.cpp \
165-
test/versionbits_tests.cpp
165+
test/versionbits_tests.cpp \
166+
test/xoroshiro128plusplus_tests.cpp
166167

167168
if ENABLE_WALLET
168169
BITCOIN_TESTS += \

src/Makefile.test_util.include

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ TEST_UTIL_H = \
1919
test/util/str.h \
2020
test/util/transaction_utils.h \
2121
test/util/txmempool.h \
22-
test/util/validation.h
22+
test/util/validation.h \
23+
test/util/xoroshiro128plusplus.h
2324

2425
if ENABLE_WALLET
2526
TEST_UTIL_H += wallet/test/util.h

src/bench/chacha20.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ static const uint64_t BUFFER_SIZE_LARGE = 1024*1024;
1414
static void CHACHA20(benchmark::Bench& bench, size_t buffersize)
1515
{
1616
std::vector<uint8_t> key(32,0);
17-
ChaCha20 ctx(key.data(), key.size());
17+
ChaCha20 ctx(key.data());
1818
ctx.SetIV(0);
19-
ctx.Seek(0);
19+
ctx.Seek64(0);
2020
std::vector<uint8_t> in(buffersize,0);
2121
std::vector<uint8_t> out(buffersize,0);
2222
bench.batch(in.size()).unit("byte").run([&] {

src/crypto/chacha20.cpp

Lines changed: 133 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <crypto/common.h>
99
#include <crypto/chacha20.h>
1010

11+
#include <algorithm>
1112
#include <string.h>
1213

1314
constexpr static inline uint32_t rotl32(uint32_t v, int c) { return (v << c) | (v >> (32 - c)); }
@@ -20,95 +21,69 @@ constexpr static inline uint32_t rotl32(uint32_t v, int c) { return (v << c) | (
2021

2122
#define REPEAT10(a) do { {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; {a}; } while(0)
2223

23-
static const unsigned char sigma[] = "expand 32-byte k";
24-
static const unsigned char tau[] = "expand 16-byte k";
25-
26-
void ChaCha20::SetKey(const unsigned char* k, size_t keylen)
24+
void ChaCha20Aligned::SetKey32(const unsigned char* k)
2725
{
28-
const unsigned char *constants;
29-
30-
input[4] = ReadLE32(k + 0);
31-
input[5] = ReadLE32(k + 4);
32-
input[6] = ReadLE32(k + 8);
33-
input[7] = ReadLE32(k + 12);
34-
if (keylen == 32) { /* recommended */
35-
k += 16;
36-
constants = sigma;
37-
} else { /* keylen == 16 */
38-
constants = tau;
39-
}
40-
input[8] = ReadLE32(k + 0);
41-
input[9] = ReadLE32(k + 4);
42-
input[10] = ReadLE32(k + 8);
43-
input[11] = ReadLE32(k + 12);
44-
input[0] = ReadLE32(constants + 0);
45-
input[1] = ReadLE32(constants + 4);
46-
input[2] = ReadLE32(constants + 8);
47-
input[3] = ReadLE32(constants + 12);
48-
input[12] = 0;
49-
input[13] = 0;
50-
input[14] = 0;
51-
input[15] = 0;
26+
input[0] = ReadLE32(k + 0);
27+
input[1] = ReadLE32(k + 4);
28+
input[2] = ReadLE32(k + 8);
29+
input[3] = ReadLE32(k + 12);
30+
input[4] = ReadLE32(k + 16);
31+
input[5] = ReadLE32(k + 20);
32+
input[6] = ReadLE32(k + 24);
33+
input[7] = ReadLE32(k + 28);
34+
input[8] = 0;
35+
input[9] = 0;
36+
input[10] = 0;
37+
input[11] = 0;
5238
}
5339

54-
ChaCha20::ChaCha20()
40+
ChaCha20Aligned::ChaCha20Aligned()
5541
{
5642
memset(input, 0, sizeof(input));
5743
}
5844

59-
ChaCha20::ChaCha20(const unsigned char* k, size_t keylen)
45+
ChaCha20Aligned::ChaCha20Aligned(const unsigned char* key32)
6046
{
61-
SetKey(k, keylen);
47+
SetKey32(key32);
6248
}
6349

64-
void ChaCha20::SetIV(uint64_t iv)
50+
void ChaCha20Aligned::SetIV(uint64_t iv)
6551
{
66-
input[14] = iv;
67-
input[15] = iv >> 32;
52+
input[10] = iv;
53+
input[11] = iv >> 32;
6854
}
6955

70-
void ChaCha20::Seek(uint64_t pos)
56+
void ChaCha20Aligned::Seek64(uint64_t pos)
7157
{
72-
input[12] = pos;
73-
input[13] = pos >> 32;
58+
input[8] = pos;
59+
input[9] = pos >> 32;
7460
}
7561

76-
void ChaCha20::Keystream(unsigned char* c, size_t bytes)
62+
inline void ChaCha20Aligned::Keystream64(unsigned char* c, size_t blocks)
7763
{
7864
uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
79-
uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
80-
unsigned char *ctarget = nullptr;
81-
unsigned char tmp[64];
82-
unsigned int i;
83-
84-
if (!bytes) return;
85-
86-
j0 = input[0];
87-
j1 = input[1];
88-
j2 = input[2];
89-
j3 = input[3];
90-
j4 = input[4];
91-
j5 = input[5];
92-
j6 = input[6];
93-
j7 = input[7];
94-
j8 = input[8];
95-
j9 = input[9];
96-
j10 = input[10];
97-
j11 = input[11];
98-
j12 = input[12];
99-
j13 = input[13];
100-
j14 = input[14];
101-
j15 = input[15];
65+
uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
66+
67+
if (!blocks) return;
68+
69+
j4 = input[0];
70+
j5 = input[1];
71+
j6 = input[2];
72+
j7 = input[3];
73+
j8 = input[4];
74+
j9 = input[5];
75+
j10 = input[6];
76+
j11 = input[7];
77+
j12 = input[8];
78+
j13 = input[9];
79+
j14 = input[10];
80+
j15 = input[11];
10281

10382
for (;;) {
104-
if (bytes < 64) {
105-
ctarget = c;
106-
c = tmp;
107-
}
108-
x0 = j0;
109-
x1 = j1;
110-
x2 = j2;
111-
x3 = j3;
83+
x0 = 0x61707865;
84+
x1 = 0x3320646e;
85+
x2 = 0x79622d32;
86+
x3 = 0x6b206574;
11287
x4 = j4;
11388
x5 = j5;
11489
x6 = j6;
@@ -134,10 +109,10 @@ void ChaCha20::Keystream(unsigned char* c, size_t bytes)
134109
QUARTERROUND( x3, x4, x9,x14);
135110
);
136111

137-
x0 += j0;
138-
x1 += j1;
139-
x2 += j2;
140-
x3 += j3;
112+
x0 += 0x61707865;
113+
x1 += 0x3320646e;
114+
x2 += 0x79622d32;
115+
x3 += 0x6b206574;
141116
x4 += j4;
142117
x5 += j5;
143118
x6 += j6;
@@ -171,59 +146,41 @@ void ChaCha20::Keystream(unsigned char* c, size_t bytes)
171146
WriteLE32(c + 56, x14);
172147
WriteLE32(c + 60, x15);
173148

174-
if (bytes <= 64) {
175-
if (bytes < 64) {
176-
for (i = 0;i < bytes;++i) ctarget[i] = c[i];
177-
}
178-
input[12] = j12;
179-
input[13] = j13;
149+
if (blocks == 1) {
150+
input[8] = j12;
151+
input[9] = j13;
180152
return;
181153
}
182-
bytes -= 64;
154+
blocks -= 1;
183155
c += 64;
184156
}
185157
}
186158

187-
void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes)
159+
inline void ChaCha20Aligned::Crypt64(const unsigned char* m, unsigned char* c, size_t blocks)
188160
{
189161
uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
190-
uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
191-
unsigned char *ctarget = nullptr;
192-
unsigned char tmp[64];
193-
unsigned int i;
194-
195-
if (!bytes) return;
196-
197-
j0 = input[0];
198-
j1 = input[1];
199-
j2 = input[2];
200-
j3 = input[3];
201-
j4 = input[4];
202-
j5 = input[5];
203-
j6 = input[6];
204-
j7 = input[7];
205-
j8 = input[8];
206-
j9 = input[9];
207-
j10 = input[10];
208-
j11 = input[11];
209-
j12 = input[12];
210-
j13 = input[13];
211-
j14 = input[14];
212-
j15 = input[15];
162+
uint32_t j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15;
163+
164+
if (!blocks) return;
165+
166+
j4 = input[0];
167+
j5 = input[1];
168+
j6 = input[2];
169+
j7 = input[3];
170+
j8 = input[4];
171+
j9 = input[5];
172+
j10 = input[6];
173+
j11 = input[7];
174+
j12 = input[8];
175+
j13 = input[9];
176+
j14 = input[10];
177+
j15 = input[11];
213178

214179
for (;;) {
215-
if (bytes < 64) {
216-
// if m has fewer than 64 bytes available, copy m to tmp and
217-
// read from tmp instead
218-
for (i = 0;i < bytes;++i) tmp[i] = m[i];
219-
m = tmp;
220-
ctarget = c;
221-
c = tmp;
222-
}
223-
x0 = j0;
224-
x1 = j1;
225-
x2 = j2;
226-
x3 = j3;
180+
x0 = 0x61707865;
181+
x1 = 0x3320646e;
182+
x2 = 0x79622d32;
183+
x3 = 0x6b206574;
227184
x4 = j4;
228185
x5 = j5;
229186
x6 = j6;
@@ -249,10 +206,10 @@ void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes)
249206
QUARTERROUND( x3, x4, x9,x14);
250207
);
251208

252-
x0 += j0;
253-
x1 += j1;
254-
x2 += j2;
255-
x3 += j3;
209+
x0 += 0x61707865;
210+
x1 += 0x3320646e;
211+
x2 += 0x79622d32;
212+
x3 += 0x6b206574;
256213
x4 += j4;
257214
x5 += j5;
258215
x6 += j6;
@@ -303,16 +260,65 @@ void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes)
303260
WriteLE32(c + 56, x14);
304261
WriteLE32(c + 60, x15);
305262

306-
if (bytes <= 64) {
307-
if (bytes < 64) {
308-
for (i = 0;i < bytes;++i) ctarget[i] = c[i];
309-
}
310-
input[12] = j12;
311-
input[13] = j13;
263+
if (blocks == 1) {
264+
input[8] = j12;
265+
input[9] = j13;
312266
return;
313267
}
314-
bytes -= 64;
268+
blocks -= 1;
315269
c += 64;
316270
m += 64;
317271
}
318272
}
273+
274+
void ChaCha20::Keystream(unsigned char* c, size_t bytes)
275+
{
276+
if (!bytes) return;
277+
if (m_bufleft) {
278+
unsigned reuse = std::min<size_t>(m_bufleft, bytes);
279+
memcpy(c, m_buffer + 64 - m_bufleft, reuse);
280+
m_bufleft -= reuse;
281+
bytes -= reuse;
282+
c += reuse;
283+
}
284+
if (bytes >= 64) {
285+
size_t blocks = bytes / 64;
286+
m_aligned.Keystream64(c, blocks);
287+
c += blocks * 64;
288+
bytes -= blocks * 64;
289+
}
290+
if (bytes) {
291+
m_aligned.Keystream64(m_buffer, 1);
292+
memcpy(c, m_buffer, bytes);
293+
m_bufleft = 64 - bytes;
294+
}
295+
}
296+
297+
void ChaCha20::Crypt(const unsigned char* m, unsigned char* c, size_t bytes)
298+
{
299+
if (!bytes) return;
300+
if (m_bufleft) {
301+
unsigned reuse = std::min<size_t>(m_bufleft, bytes);
302+
for (unsigned i = 0; i < reuse; i++) {
303+
c[i] = m[i] ^ m_buffer[64 - m_bufleft + i];
304+
}
305+
m_bufleft -= reuse;
306+
bytes -= reuse;
307+
c += reuse;
308+
m += reuse;
309+
}
310+
if (bytes >= 64) {
311+
size_t blocks = bytes / 64;
312+
m_aligned.Crypt64(m, c, blocks);
313+
c += blocks * 64;
314+
m += blocks * 64;
315+
bytes -= blocks * 64;
316+
}
317+
if (bytes) {
318+
m_aligned.Keystream64(m_buffer, 1);
319+
for (unsigned i = 0; i < bytes; i++) {
320+
c[i] = m[i] ^ m_buffer[i];
321+
}
322+
m_bufleft = 64 - bytes;
323+
}
324+
}

0 commit comments

Comments
 (0)