1
+ #![ doc = include_str ! ( concat!( env!( "CARGO_MANIFEST_DIR" ) , "/README.md" ) ) ]
2
+
1
3
// aarch64 assumes that NEON instructions are always present
2
4
#[ cfg( target_arch = "aarch64" ) ]
3
5
use crate :: chacha_neon:: chacha_neon;
4
6
5
7
#[ cfg( target_arch = "aarch64" ) ]
6
8
mod chacha_neon;
7
9
8
- #[ cfg( all( any( target_arch = "x86" , target_arch = "x86_64" ) , target_feature = "avx2" ) ) ]
10
+ #[ cfg( any(
11
+ all( target_arch = "x86_64" , feature = "std" ) ,
12
+ all( target_arch = "x86_64" , target_feature = "avx2" )
13
+ ) ) ]
9
14
mod chacha_avx2;
10
15
11
- #[ cfg( all( any( target_arch = "x86" , target_arch = "x86_64" ) , target_feature = "avx2" ) ) ]
16
+ #[ cfg( any(
17
+ all( target_arch = "x86_64" , feature = "std" ) ,
18
+ all( target_arch = "x86_64" , target_feature = "avx2" )
19
+ ) ) ]
12
20
use chacha_avx2:: chacha_avx2;
13
21
14
- #[ cfg( all( target_arch = "x86_64" , target_feature = "avx512f" ) ) ]
22
+ #[ cfg( any(
23
+ all( target_arch = "x86_64" , feature = "std" ) ,
24
+ all( target_arch = "x86_64" , target_feature = "avx512f" )
25
+ ) ) ]
15
26
mod chacha_avx512;
16
27
17
- #[ cfg( all( target_arch = "x86_64" , target_feature = "avx512f" ) ) ]
28
+ #[ cfg( any(
29
+ all( target_arch = "x86_64" , feature = "std" ) ,
30
+ all( target_arch = "x86_64" , target_feature = "avx512f" )
31
+ ) ) ]
18
32
use chacha_avx512:: chacha_avx512;
19
33
20
34
#[ cfg( all( target_arch = "wasm32" , target_feature = "simd128" ) ) ]
@@ -23,6 +37,9 @@ mod chacha_wasm_simd128;
23
37
#[ cfg( all( target_arch = "wasm32" , target_feature = "simd128" ) ) ]
24
38
use chacha_wasm_simd128:: chacha_wasm_simd128;
25
39
40
+ #[ cfg( feature = "zeroize" ) ]
41
+ use zeroize:: { Zeroize , ZeroizeOnDrop } ;
42
+
26
43
const CONSTANT : [ u32 ; 4 ] = [
27
44
0x61707865 , // "expa"
28
45
0x3320646e , // "nd 3"
@@ -33,6 +50,7 @@ const CONSTANT: [u32; 4] = [
33
50
/// The number of 32-bit words that compose ChaCha's state
34
51
const STATE_WORDS : usize = 16 ;
35
52
53
+ #[ cfg_attr( feature = "zeroize" , derive( Zeroize , ZeroizeOnDrop ) ) ]
36
54
pub struct ChaCha < const ROUNDS : usize > {
37
55
state : [ u32 ; STATE_WORDS ] ,
38
56
counter : u64 ,
@@ -47,7 +65,8 @@ pub struct ChaCha<const ROUNDS: usize> {
47
65
/// Then, when calling `xor_keystream` again, we first check if there is sone leftover form the last
48
66
/// keystream.
49
67
/// NOTE: the `last_keystream_block` is valid only if the previous call to `xor_keystream` had
50
- /// an input.len() % 64 != 0
68
+ /// an input.len() % 64 != 0.
69
+ /// Otherwise there is no need to preserve the last keystream block.
51
70
last_keystream_block : [ u8 ; 64 ] ,
52
71
last_keystream_block_index : usize ,
53
72
}
@@ -68,19 +87,21 @@ impl<const ROUNDS: usize> ChaCha<ROUNDS> {
68
87
state[ 14 ] = u32:: from_le_bytes ( nonce[ 0 ..4 ] . try_into ( ) . unwrap ( ) ) ;
69
88
state[ 15 ] = u32:: from_le_bytes ( nonce[ 4 ..8 ] . try_into ( ) . unwrap ( ) ) ;
70
89
71
- ChaCha {
90
+ return ChaCha {
72
91
state,
73
92
counter : 0 ,
74
93
last_keystream_block : [ 0u8 ; 64 ] ,
75
94
last_keystream_block_index : 0 ,
76
- }
95
+ } ;
77
96
}
78
97
98
+ /// XOR `plaintext` with the ChaCha keystream.
79
99
pub fn xor_keystream ( & mut self , mut plaintext : & mut [ u8 ] ) {
80
100
if plaintext. len ( ) == 0 {
81
101
return ;
82
102
}
83
103
104
+ // first, consume the keystream leftover, if any
84
105
if self . last_keystream_block_index != 0 {
85
106
let remaining_keystream = & self . last_keystream_block [ self . last_keystream_block_index ..] ;
86
107
@@ -102,36 +123,63 @@ impl<const ROUNDS: usize> ChaCha<ROUNDS> {
102
123
}
103
124
self . last_keystream_block_index = plaintext. len ( ) % 64 ;
104
125
126
+ // aarch64 assumes that NEON is always available
105
127
#[ cfg( target_arch = "aarch64" ) ]
106
128
if plaintext. len ( ) >= 128 {
107
129
self . counter = chacha_neon :: < ROUNDS > ( self . state , self . counter , plaintext, & mut self . last_keystream_block ) ;
108
130
return ;
109
131
}
110
132
111
- #[ cfg( all( target_arch = "x86_64 " , target_feature = "avx512f " ) ) ]
133
+ #[ cfg( all( target_arch = "wasm32 " , target_feature = "simd128 " ) ) ]
112
134
if plaintext. len ( ) >= 128 {
113
- self . counter = chacha_avx512 :: < ROUNDS > ( self . state , self . counter , plaintext, & mut self . last_keystream_block ) ;
135
+ self . counter =
136
+ chacha_wasm_simd128 :: < ROUNDS > ( self . state , self . counter , plaintext, & mut self . last_keystream_block ) ;
114
137
return ;
115
138
}
116
139
117
- #[ cfg( all( any( target_arch = "x86" , target_arch = "x86_64" ) , target_feature = "avx2" ) ) ]
140
+ // runtime detection of CPU features for x86 and x86_64 when the "std" feature is enabled
141
+ #[ cfg( feature = "std" ) ]
142
+ {
143
+ #[ cfg( target_arch = "x86_64" ) ]
144
+ if is_x86_feature_detected ! ( "avx512f" ) {
145
+ self . counter =
146
+ chacha_avx512 :: < ROUNDS > ( self . state , self . counter , plaintext, & mut self . last_keystream_block ) ;
147
+ return ;
148
+ }
149
+
150
+ #[ cfg( any( target_arch = "x86" , target_arch = "x86_64" ) ) ]
151
+ if is_x86_feature_detected ! ( "avx2" ) {
152
+ self . counter =
153
+ chacha_avx2 :: < ROUNDS > ( self . state , self . counter , plaintext, & mut self . last_keystream_block ) ;
154
+ return ;
155
+ }
156
+ }
157
+
158
+ // compile-time CPU detection
159
+ #[ cfg( all( target_arch = "x86_64" , target_feature = "avx512f" , not( feature = "std" ) ) ) ]
118
160
if plaintext. len ( ) >= 128 {
119
- self . counter = chacha_avx2 :: < ROUNDS > ( self . state , self . counter , plaintext, & mut self . last_keystream_block ) ;
161
+ self . counter = chacha_avx512 :: < ROUNDS > ( self . state , self . counter , plaintext, & mut self . last_keystream_block ) ;
120
162
return ;
121
163
}
122
164
123
- #[ cfg( all( target_arch = "wasm32" , target_feature = "simd128" ) ) ]
165
+ #[ cfg( all(
166
+ any( target_arch = "x86" , target_arch = "x86_64" ) ,
167
+ target_feature = "avx2" ,
168
+ not( feature = "std" )
169
+ ) ) ]
124
170
if plaintext. len ( ) >= 128 {
125
- self . counter =
126
- chacha_wasm_simd128 :: < ROUNDS > ( self . state , self . counter , plaintext, & mut self . last_keystream_block ) ;
171
+ self . counter = chacha_avx2 :: < ROUNDS > ( self . state , self . counter , plaintext, & mut self . last_keystream_block ) ;
127
172
return ;
128
173
}
129
174
130
175
self . counter = chacha_generic :: < ROUNDS > ( self . state , self . counter , plaintext, & mut self . last_keystream_block ) ;
131
176
}
132
177
178
+ /// Set the ChaCha counter (words 12 and 13). It can be used to move forward and backward in the
179
+ /// keystream.
133
180
pub fn set_counter ( & mut self , counter : u64 ) {
134
181
self . counter = counter;
182
+ // setting the counter "realigns" the keystream to the beginning of a block.
135
183
self . last_keystream_block_index = 0 ;
136
184
}
137
185
}
@@ -197,7 +245,9 @@ fn chacha_generic<const ROUNDS: usize>(
197
245
counter = counter. wrapping_add ( 1 ) ;
198
246
}
199
247
200
- last_keystream_block. copy_from_slice ( & keystream) ;
248
+ if plaintext. len ( ) % 64 != 0 {
249
+ last_keystream_block. copy_from_slice ( & keystream) ;
250
+ }
201
251
202
252
return counter;
203
253
}
0 commit comments