• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

moonbitlang / x / 950

21 Jul 2026 05:37AM UTC coverage: 84.986%. First build
950

Pull #280

github

web-flow
Merge 86831e10a into 8741d5cbb
Pull Request #280: Optimize ChaCha with target-aware SIMD

80 of 171 new or added lines in 3 files covered. (46.78%)

2649 of 3117 relevant lines covered (84.99%)

254.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.92
/crypto/chacha.mbt
1
// Copyright 2025 International Digital Economy Academy
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
///|
16
fn flipWord(word : UInt) -> UInt {
17
  let b0 = (word & 0xff) << 24
65✔
18
  let b1 = (word & 0xff00) << 8
19
  let b2 = (word & 0xff0000) >> 8
20
  let b3 = (word & 0xff000000) >> 24
21
  b0 | b1 | b2 | b3
22
}
23

24
///|
25
test "flipWord" {
26
  let word = 0x12345678U
27
  let flipped = flipWord(word)
28
  inspect(flipped, content="2018915346")
29
}
30

31
///|
32
fn FixedArray::quarterRound(
33
  state : FixedArray[UInt],
34
  w : Int,
35
  x : Int,
36
  y : Int,
37
  z : Int,
38
) -> Unit {
39
  let mut a = state[w]
169✔
40
  let mut b = state[x]
41
  let mut c = state[y]
42
  let mut d = state[z]
43
  a += b
44
  d = d ^ a
45
  d = rotate_left_u(d, 16)
169✔
46
  c += d
47
  b = b ^ c
48
  b = rotate_left_u(b, 12)
169✔
49
  a += b
50
  d = d ^ a
51
  d = rotate_left_u(d, 8)
169✔
52
  c += d
53
  b = b ^ c
54
  b = rotate_left_u(b, 7)
169✔
55
  state[w] = a
56
  state[x] = b
57
  state[y] = c
58
  state[z] = d
59
}
60

61
///|
62
test "quarterRound" {
63
  let state = FixedArray::make(16, 0U)
64
  state[0] = 0x879531e0U
65
  state[1] = 0xc5ecf37dU
66
  state[2] = 0x516461b1U
67
  state[3] = 0xc9a62f8aU
68
  state[4] = 0x44c20ef3U
69
  state[5] = 0x3390af7fU
70
  state[6] = 0xd9fc690bU
71
  state[7] = 0x2a5f714cU
72
  state[8] = 0x53372767U
73
  state[9] = 0xb00a5631U
74
  state[10] = 0x974c541aU
75
  state[11] = 0x359e9963U
76
  state[12] = 0x5c971061U
77
  state[13] = 0x3d631689U
78
  state[14] = 0x2098d9d6U
79
  state[15] = 0x91dbd320U
80
  state.quarterRound(2, 7, 8, 13)
81
  debug_inspect(
82
    Array::from_iter(state.iter()),
83
    content=(
84
      #|[
85
      #|  2274701792,
86
      #|  3320640381,
87
      #|  3182986972,
88
      #|  3383111562,
89
      #|  1153568499,
90
      #|  865120127,
91
      #|  3657197835,
92
      #|  3484200914,
93
      #|  3832277632,
94
      #|  2953467441,
95
      #|  2538361882,
96
      #|  899586403,
97
      #|  1553404001,
98
      #|  3435166841,
99
      #|  546888150,
100
      #|  2447102752,
101
      #|]
102
    ),
103
  )
104
}
105

106
///|
107
fn FixedArray::chachaBlockRound(state : FixedArray[UInt]) -> Unit {
108
  state
21✔
109
  ..quarterRound(0, 4, 8, 12)
110
  ..quarterRound(1, 5, 9, 13)
111
  ..quarterRound(2, 6, 10, 14)
112
  ..quarterRound(3, 7, 11, 15)
113
  ..quarterRound(0, 5, 10, 15)
114
  ..quarterRound(1, 6, 11, 12)
115
  ..quarterRound(2, 7, 8, 13)
116
  .quarterRound(3, 4, 9, 14)
21✔
117
}
118

119
///|
120
test "chachaBlockRound" {
121
  let state = FixedArray::make(16, 0U)
122
  state[0] = 0x61707865U
123
  state[1] = 0x3320646eU
124
  state[2] = 0x79622d32U
125
  state[3] = 0x6b206574U
126
  state[4] = 0x03020100U
127
  state[5] = 0x07060504U
128
  state[6] = 0x0b0a0908U
129
  state[7] = 0x0f0e0d0cU
130
  state[8] = 0x13121110U
131
  state[9] = 0x17161514U
132
  state[10] = 0x1b1a1918U
133
  state[11] = 0x1f1e1d1cU
134
  state[12] = 0x00000001U
135
  state[13] = 0x00000000U
136
  state[14] = 0x00000000U
137
  state[15] = 0x00000000U
138
  state.chachaBlockRound()
139
  debug_inspect(
140
    Array::from_iter(state.iter()),
141
    content=(
142
      #|[
143
      #|  986087425,
144
      #|  3489031050,
145
      #|  2890662805,
146
      #|  2683391196,
147
      #|  1720476390,
148
      #|  1116253759,
149
      #|  2262580386,
150
      #|  3212003942,
151
      #|  2202368212,
152
      #|  756352536,
153
      #|  496298475,
154
      #|  669838588,
155
      #|  567302638,
156
      #|  1860562437,
157
      #|  1434237441,
158
      #|  2097484794,
159
      #|]
160
    ),
161
  )
162
}
163

164
///|
165
fn FixedArray::chachaBlockLoop(state : FixedArray[UInt], n : UInt) -> Unit {
166
  for _ in 0U..<n {
4✔
167
    state.chachaBlockRound()
20✔
168
  }
169
}
170

171
///|
172
test "chachaBlockLoop" {
173
  let count = 1U
174
  let key = FixedArray::make(8, 0U)
175
  key[0] = 0x00010203U
176
  key[1] = 0x04050607U
177
  key[2] = 0x08090a0bU
178
  key[3] = 0x0c0d0e0fU
179
  key[4] = 0x10111213U
180
  key[5] = 0x14151617U
181
  key[6] = 0x18191a1bU
182
  key[7] = 0x1c1d1e1fU
183
  let state = FixedArray::make(16, 0U)
184
  state[0] = 0X61707865U
185
  state[1] = 0X3320646eU
186
  state[2] = 0X79622d32U
187
  state[3] = 0X6b206574U
188
  state[4] = flipWord(key[0])
189
  state[5] = flipWord(key[1])
190
  state[6] = flipWord(key[2])
191
  state[7] = flipWord(key[3])
192
  state[8] = flipWord(key[4])
193
  state[9] = flipWord(key[5])
194
  state[10] = flipWord(key[6])
195
  state[11] = flipWord(key[7])
196
  state[12] = count
197
  state[13] = 0
198
  state[14] = 0
199
  state[15] = 0
200
  debug_inspect(
201
    Array::from_iter(state.iter()),
202
    content=(
203
      #|[
204
      #|  1634760805,
205
      #|  857760878,
206
      #|  2036477234,
207
      #|  1797285236,
208
      #|  50462976,
209
      #|  117835012,
210
      #|  185207048,
211
      #|  252579084,
212
      #|  319951120,
213
      #|  387323156,
214
      #|  454695192,
215
      #|  522067228,
216
      #|  1,
217
      #|  0,
218
      #|  0,
219
      #|  0,
220
      #|]
221
    ),
222
  )
223
  state.chachaBlockLoop(4)
224
  debug_inspect(
225
    Array::from_iter(state.iter()),
226
    content=(
227
      #|[
228
      #|  2919080465,
229
      #|  647515738,
230
      #|  898727107,
231
      #|  777299107,
232
      #|  3407982512,
233
      #|  2489307765,
234
      #|  745530666,
235
      #|  2053399858,
236
      #|  1994399329,
237
      #|  139328223,
238
      #|  3709168053,
239
      #|  3118545354,
240
      #|  4170274417,
241
      #|  867477305,
242
      #|  1393604261,
243
      #|  3769539545,
244
      #|]
245
    ),
246
  )
247
}
248

249
///|
250
/// - chacha8: round = 4
251
/// - chacha12: round = 6
252
/// - chacha20: round = 10
253
#cfg(any(target="wasm", target="wasm-gc"))
254
fn chachaBlockBytes(
255
  key : FixedArray[UInt],
256
  count : UInt,
257
  nonce : FixedArray[UInt],
258
  round : UInt,
259
  _scratch : FixedArray[UInt],
260
  output : FixedArray[Byte],
261
) -> Unit {
262
  guard key.length() == 8
39✔
263
  guard nonce.length() == 3
39✔
264
  guard output.length() >= 64
39✔
265
  let initial_a = @v128.i32x4_const(
39✔
266
    0X61707865, 0X3320646e, 0X79622d32, 0X6b206574,
267
  )
268
  let initial_b = @v128.i32x4_const(key[0], key[1], key[2], key[3])
39✔
269
  let initial_c = @v128.i32x4_const(key[4], key[5], key[6], key[7])
39✔
270
  let initial_d = @v128.i32x4_const(count, nonce[0], nonce[1], nonce[2])
39✔
271
  let mut a = initial_a
272
  let mut b = initial_b
273
  let mut c = initial_c
274
  let mut d = initial_d
275
  for _ in 0U..<round {
276
    // Four column quarter-rounds in parallel.
277
    a = @v128.i32x4_add(a, b)
380✔
278
    let d_xor_a = @v128.v128_xor(d, a)
380✔
279
    d = @v128.i8x16_shuffle(
380✔
280
      d_xor_a, d_xor_a, 2, 3, 0, 1, 6, 7, 4, 5, 10, 11, 8, 9, 14, 15, 12, 13,
281
    )
282
    c = @v128.i32x4_add(c, d)
380✔
283
    let b_xor_c = @v128.v128_xor(b, c)
380✔
284
    b = @v128.v128_or_(
380✔
285
      @v128.i32x4_shl(b_xor_c, 12),
380✔
286
      @v128.i32x4_shr_u(b_xor_c, 20),
380✔
287
    )
288
    a = @v128.i32x4_add(a, b)
380✔
289
    let d_xor_a = @v128.v128_xor(d, a)
380✔
290
    d = @v128.i8x16_shuffle(
380✔
291
      d_xor_a, d_xor_a, 3, 0, 1, 2, 7, 4, 5, 6, 11, 8, 9, 10, 15, 12, 13, 14,
292
    )
293
    c = @v128.i32x4_add(c, d)
380✔
294
    let b_xor_c = @v128.v128_xor(b, c)
380✔
295
    b = @v128.v128_or_(
380✔
296
      @v128.i32x4_shl(b_xor_c, 7),
380✔
297
      @v128.i32x4_shr_u(b_xor_c, 25),
380✔
298
    )
299

300
    // Align the rows so the same operations perform four diagonal rounds.
301
    b = @v128.i8x16_shuffle(
380✔
302
      b, b, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3,
303
    )
304
    c = @v128.i8x16_shuffle(
380✔
305
      c, c, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7,
306
    )
307
    d = @v128.i8x16_shuffle(
380✔
308
      d, d, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
309
    )
310
    a = @v128.i32x4_add(a, b)
380✔
311
    let d_xor_a = @v128.v128_xor(d, a)
380✔
312
    d = @v128.i8x16_shuffle(
380✔
313
      d_xor_a, d_xor_a, 2, 3, 0, 1, 6, 7, 4, 5, 10, 11, 8, 9, 14, 15, 12, 13,
314
    )
315
    c = @v128.i32x4_add(c, d)
380✔
316
    let b_xor_c = @v128.v128_xor(b, c)
380✔
317
    b = @v128.v128_or_(
380✔
318
      @v128.i32x4_shl(b_xor_c, 12),
380✔
319
      @v128.i32x4_shr_u(b_xor_c, 20),
380✔
320
    )
321
    a = @v128.i32x4_add(a, b)
380✔
322
    let d_xor_a = @v128.v128_xor(d, a)
380✔
323
    d = @v128.i8x16_shuffle(
380✔
324
      d_xor_a, d_xor_a, 3, 0, 1, 2, 7, 4, 5, 6, 11, 8, 9, 10, 15, 12, 13, 14,
325
    )
326
    c = @v128.i32x4_add(c, d)
380✔
327
    let b_xor_c = @v128.v128_xor(b, c)
380✔
328
    b = @v128.v128_or_(
380✔
329
      @v128.i32x4_shl(b_xor_c, 7),
380✔
330
      @v128.i32x4_shr_u(b_xor_c, 25),
380✔
331
    )
332
    b = @v128.i8x16_shuffle(
380✔
333
      b, b, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
334
    )
335
    c = @v128.i8x16_shuffle(
380✔
336
      c, c, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7,
337
    )
338
    d = @v128.i8x16_shuffle(
380✔
339
      d, d, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3,
340
    )
341
  }
342
  a = @v128.i32x4_add(a, initial_a)
39✔
343
  b = @v128.i32x4_add(b, initial_b)
39✔
344
  c = @v128.i32x4_add(c, initial_c)
39✔
345
  d = @v128.i32x4_add(d, initial_d)
39✔
346
  @v128.v128_store(output, 0, a)
39✔
347
  @v128.v128_store(output, 16, b)
39✔
348
  @v128.v128_store(output, 32, c)
39✔
349
  @v128.v128_store(output, 48, d)
39✔
350
}
351

352
///|
353
fn chachaBlock(
354
  key : FixedArray[UInt],
355
  count : UInt,
356
  nonce : FixedArray[UInt],
357
  round : UInt,
358
  state : FixedArray[UInt],
359
) -> Unit {
360
  guard key.length() == 8
3✔
361
  guard nonce.length() == 3
3✔
362
  guard state.length() == 16
3✔
363
  let initial_state = FixedArray::make(16, 0U)
3✔
364
  initial_state[0] = 0X61707865U
365
  initial_state[1] = 0X3320646eU
366
  initial_state[2] = 0X79622d32U
367
  initial_state[3] = 0X6b206574U
368
  key.blit_to(initial_state, len=8, dst_offset=4)
3✔
369
  initial_state[12] = count
370
  nonce.blit_to(initial_state, len=3, dst_offset=13)
3✔
371
  initial_state.blit_to(state, len=16)
3✔
372
  state.chachaBlockLoop(round)
3✔
373
  for i in 0..<16 {
374
    state[i] += initial_state[i]
48✔
375
  }
376
}
377

378
///|
379
#cfg(not(any(target="wasm", target="wasm-gc")))
380
fn chachaBlockBytes(
381
  key : FixedArray[UInt],
382
  count : UInt,
383
  nonce : FixedArray[UInt],
384
  round : UInt,
385
  scratch : FixedArray[UInt],
386
  output : FixedArray[Byte],
387
) -> Unit {
NEW
388
  chachaBlock(key, count, nonce, round, scratch)
×
389
  for group in 0..<4 {
NEW
390
    store_u32x4_le(scratch, group * 4, output, group * 16)
×
391
  }
392
}
393

394
///|
395
test "chachaBlock" {
396
  let key = FixedArray::make(8, 0U)
397
  key[0] = flipWord(0x00010203U)
398
  key[1] = flipWord(0x04050607U)
399
  key[2] = flipWord(0x08090a0bU)
400
  key[3] = flipWord(0x0c0d0e0fU)
401
  key[4] = flipWord(0x10111213U)
402
  key[5] = flipWord(0x14151617U)
403
  key[6] = flipWord(0x18191a1bU)
404
  key[7] = flipWord(0x1c1d1e1fU)
405
  let keyStream = FixedArray::make(16, 0U)
406
  chachaBlock(key, 1, [0, 0, 0], 4, keyStream)
407
  debug_inspect(
408
    Array::from_iter(keyStream.map(flipWord).iter()),
409
    content=(
410
      #|[
411
      #|  1981443599,
412
      #|  3367155801,
413
      #|  4121555886,
414
      #|  386561433,
415
      #|  2964333518,
416
      #|  2044159387,
417
      #|  854489399,
418
      #|  1047687817,
419
      #|  1898967689,
420
      #|  4077872159,
421
      #|  3447861240,
422
      #|  3864461272,
423
      #|  1918276088,
424
      #|  967291955,
425
      #|  2780172371,
426
      #|  3650858720,
427
      #|]
428
    ),
429
  )
430

431
  // chachaBlock with different count and nonce
432
  chachaBlock(key, 2, [1, 1, 1], 6, keyStream)
433
  debug_inspect(
434
    Array::from_iter(keyStream.map(flipWord).iter()),
435
    content=(
436
      #|[
437
      #|  3045418931,
438
      #|  1870601267,
439
      #|  1791152559,
440
      #|  1128609701,
441
      #|  2412731038,
442
      #|  2282963828,
443
      #|  1154847987,
444
      #|  1925524179,
445
      #|  4130821097,
446
      #|  3948066013,
447
      #|  1837610591,
448
      #|  4125132907,
449
      #|  3665873803,
450
      #|  4179097700,
451
      #|  1171003195,
452
      #|  102570666,
453
      #|]
454
    ),
455
  )
456
  chachaBlock(key, 2, [10, 10, 10], 6, keyStream)
457
  debug_inspect(
458
    Array::from_iter(keyStream.map(flipWord).iter()),
459
    content=(
460
      #|[
461
      #|  1655341353,
462
      #|  1070113534,
463
      #|  1698503531,
464
      #|  4049551328,
465
      #|  4260831884,
466
      #|  3845590894,
467
      #|  3707238589,
468
      #|  4243437253,
469
      #|  44481274,
470
      #|  2151594893,
471
      #|  3326073229,
472
      #|  3492192335,
473
      #|  3505455555,
474
      #|  2232294460,
475
      #|  127722793,
476
      #|  1386042532,
477
      #|]
478
    ),
479
  )
480
}
481

482
///|
483
fn stateToBytes(state : FixedArray[UInt]) -> FixedArray[Byte] {
484
  let result = FixedArray::make(4 * state.length(), b'\x00')
1✔
485
  for i = 0; i < 16; i = i + 1 {
16✔
486
    let word = state[i]
16✔
487
    result[i * 4 + 0] = word.to_byte()
16✔
488
    result[i * 4 + 1] = (word >> 8).to_byte()
16✔
489
    result[i * 4 + 2] = (word >> 16).to_byte()
16✔
490
    result[i * 4 + 3] = (word >> 24).to_byte()
16✔
491
  }
492
  result
493
}
494

495
///|
496
#cfg(target="js")
497
#inline
498
fn[Data : ByteSource] chacha_xor_into(
499
  data : Data,
500
  input_offset : Int,
501
  target : FixedArray[Byte],
502
  target_offset : Int,
503
  key_stream : FixedArray[Byte],
504
  key_stream_offset : Int,
505
  length : Int,
506
) -> Unit {
NEW
507
  for i in 0..<length {
×
NEW
508
    target.unsafe_set(
×
509
      target_offset + i,
NEW
510
      data[input_offset + i] ^ key_stream.unsafe_get(key_stream_offset + i),
×
511
    )
512
  }
513
}
514

515
///|
516
#cfg(not(target="js"))
517
#inline
518
fn[Data : ByteSource] chacha_xor_into(
519
  data : Data,
520
  input_offset : Int,
521
  target : FixedArray[Byte],
522
  target_offset : Int,
523
  key_stream : FixedArray[Byte],
524
  key_stream_offset : Int,
525
  length : Int,
526
) -> Unit {
527
  data.blit_to(
7✔
528
    target,
529
    len=length,
530
    src_offset=input_offset,
531
    dst_offset=target_offset,
532
  )
533
  xor_byte_ranges_in_place(
7✔
534
    target, target_offset, key_stream, key_stream_offset, length,
535
  )
536
}
537

538
///|
539
test "stateToBytes" {
540
  let state = FixedArray::make(16, 0U)
541
  state[0] = 0x879531e0
542
  state[1] = 0xc5ecf37d
543
  state[2] = 0x516461b1
544
  state[3] = 0xc9a62f8a
545
  state[4] = 0x44c20ef3
546
  state[5] = 0x3390af7f
547
  state[6] = 0xd9fc690b
548
  state[7] = 0x2a5f714c
549
  state[8] = 0x53372767
550
  state[9] = 0xb00a5631
551
  state[10] = 0x974c541a
552
  state[11] = 0x359e9963
553
  state[12] = 0x5c971061
554
  state[13] = 0x3d631689
555
  state[14] = 0x2098d9d6
556
  state[15] = 0x91dbd320
557
  let bytes = stateToBytes(state)
558
  inspect(
559
    bytes_to_hex_string(bytes),
560
    content="e03195877df3ecc5b16164518a2fa6c9f30ec2447faf90330b69fcd94c715f2a6727375331560ab01a544c9763999e356110975c8916633dd6d9982020d3db91",
561
  )
562
}
563

564
///|
565
/// Encrypts a block of data using the ChaCha8 algorithm.
566
/// - [key] must be 8 32-bit unsigned integers.
567
/// - [counter] is the counter value.
568
/// - [block] is the block of data to be encrypted.
569
/// - [nonce] is default to 0
570
/// - Returns the encrypted block of data.
571
#deprecated("Use ChaCha::chacha8 and ChaCha::transform instead")
572
pub fn[Data : ByteSource] chacha8(
573
  key : FixedArray[UInt],
574
  counter : UInt,
575
  block : Data,
576
  nonce? : UInt = 0,
577
) -> FixedArray[Byte] raise Error {
578
  if key.length() != 8 {
579
    fail("Invalid key length -- key must be 8 32-bit unsigned integers")
580
  }
581
  chacha(key.map(flipWord), counter, block, 4, [nonce, nonce, nonce])
582
}
583

584
///|
585
/// Encrypts a block of data using the ChaCha12 algorithm.
586
/// - [key] must be 8 32-bit unsigned integers.
587
/// - [counter] is the counter value.
588
/// - [block] is the block of data to be encrypted.
589
/// - [nonce] is default to 0
590
/// - Returns the encrypted block of data.
591
#deprecated("Use ChaCha::chacha12 and ChaCha::transform instead")
592
pub fn[Data : ByteSource] chacha12(
593
  key : FixedArray[UInt],
594
  counter : UInt,
595
  block : Data,
596
  nonce? : UInt = 0,
597
) -> FixedArray[Byte] raise Error {
598
  if key.length() != 8 {
599
    fail("Invalid key length -- key must be 8 32-bit unsigned integers")
600
  }
601
  chacha(key.map(flipWord), counter, block, 6, [nonce, nonce, nonce])
602
}
603

604
///|
605
/// Encrypts a block of data using the ChaCha20 algorithm.
606
/// - [key] must be 8 32-bit unsigned integers.
607
/// - [counter] is the counter value.
608
/// - [block] is the block of data to be encrypted.
609
/// - [nonce] is default to 0
610
/// - Returns the encrypted block of data.
611
#deprecated("Use ChaCha::chacha20 and ChaCha::transform instead")
612
pub fn[Data : ByteSource] chacha20(
613
  key : FixedArray[UInt],
614
  counter : UInt,
615
  block : Data,
616
  nonce? : UInt = 0,
617
) -> FixedArray[Byte] raise Error {
618
  if key.length() != 8 {
619
    fail("Invalid key length -- key must be 8 32-bit unsigned integers")
620
  }
621
  chacha(key.map(flipWord), counter, block, 10, [nonce, nonce, nonce])
622
}
623

624
///|
625
#coverage.skip
626
fn[Data : ByteSource] chacha(
627
  key : FixedArray[UInt],
628
  counter : UInt,
629
  block : Data,
630
  round : UInt,
631
  nonce : FixedArray[UInt],
632
) -> FixedArray[Byte] {
633
  if block.length() == 0 {
634
    return FixedArray::make(0, Byte::default())
635
  }
636
  let buffer = FixedArray::make(block.length(), Byte::default())
637
  let keyStreamWords = FixedArray::make(16, 0U)
638
  let keyStream = FixedArray::make(64, b'\x00')
639
  let bulkKeyStream = FixedArray::make(256, b'\x00')
640
  let mut i = 0
641
  for ; chacha4_enabled() && i + 256 <= block.length(); {
642
    chacha4BlockBytes(
643
      key,
644
      counter + i.reinterpret_as_uint() / 64,
645
      nonce,
646
      round,
647
      keyStreamWords,
648
      keyStream,
649
      bulkKeyStream,
650
    )
651
    chacha_xor_into(block, i, buffer, i, bulkKeyStream, 0, 256)
652
    i += 256
653
  }
654
  for ; i < block.length(); {
655
    chachaBlockBytes(
656
      key,
657
      counter + i.reinterpret_as_uint() / 64,
658
      nonce,
659
      round,
660
      keyStreamWords,
661
      keyStream,
662
    )
663
    let len = @cmp.minimum(block.length() - i, 64)
664
    chacha_xor_into(block, i, buffer, i, keyStream, 0, len)
665
    i += 64
666
  }
667
  buffer
668
}
669

670
///|
671
struct ChaCha {
672
  key : FixedArray[UInt]
673
  nonce : FixedArray[UInt]
674
  key_stream_words : FixedArray[UInt]
675
  key_stream : FixedArray[Byte]
676
  bulk_key_stream : FixedArray[Byte]
677
  mut counter : UInt
678
  mut offset : Int
679
  round : UInt
680
}
681

682
///|
683
fn[K : ByteSource, N : ByteSource] ChaCha::new(
684
  key : K,
685
  nonce : N,
686
  round : UInt,
687
  counter : UInt,
688
) -> ChaCha raise Error {
689
  if key.length() != 32 {
8✔
690
    fail("Invalid key length -- key must be 256 bits")
1✔
691
  }
692
  if nonce.length() != 12 {
7✔
693
    fail("Invalid nonce length -- nonce must be 96 bits")
1✔
694
  }
695
  let key_ = FixedArray::make(8, 0U)
6✔
696
  for i in 0..<8 {
697
    key_[i] = (key[i * 4].to_uint() << 0) |
48✔
698
      (key[i * 4 + 1].to_uint() << 8) |
48✔
699
      (key[i * 4 + 2].to_uint() << 16) |
48✔
700
      (key[i * 4 + 3].to_uint() << 24)
48✔
701
  }
702
  let nonce_ = FixedArray::make(3, 0U)
6✔
703
  for i in 0..<3 {
704
    nonce_[i] = nonce[i * 4].to_uint() |
18✔
705
      (nonce[i * 4 + 1].to_uint() << 8) |
18✔
706
      (nonce[i * 4 + 2].to_uint() << 16) |
18✔
707
      (nonce[i * 4 + 3].to_uint() << 24)
18✔
708
  }
709
  {
710
    key: key_,
711
    nonce: nonce_,
712
    key_stream_words: FixedArray::make(16, 0U),
6✔
713
    key_stream: FixedArray::make(64, b'\x00'),
6✔
714
    bulk_key_stream: FixedArray::make(256, b'\x00'),
6✔
715
    round,
716
    offset: 64,
717
    counter,
718
  }
719
}
720

721
///|
722
/// Creates a ChaCha8 encryption context following the RFC 8439 standard.
723
/// - [key] must be 256-bit (32 bytes), in little-endian order.
724
/// - [nonce] must be a 96-bit (12 bytes) bytes, in little-endian order.
725
/// - [counter] is the counter value, defaulting to 0.
726
///
727
/// raise Error if the length of key or nonce is invalid.
728
pub fn[K : ByteSource, N : ByteSource] ChaCha::chacha8(
729
  key : K,
730
  nonce : N,
731
  counter? : UInt = 0,
732
) -> ChaCha raise Error {
733
  ChaCha::new(key, nonce, 4, counter)
3✔
734
}
735

736
///|
737
/// Creates a ChaCha12 encryption context following the RFC 8439 standard.
738
/// - [key] must be 256-bit (32 bytes), in little-endian order.
739
/// - [nonce] must be a 96-bit (12 bytes) bytes, in little-endian order.
740
/// - [counter] is the counter value, defaulting to 0.
741
///
742
/// raise Error if the length of key or nonce is invalid.
743
pub fn[K : ByteSource, N : ByteSource] ChaCha::chacha12(
744
  key : K,
745
  nonce : N,
746
  counter? : UInt = 0,
747
) -> ChaCha raise Error {
748
  ChaCha::new(key, nonce, 6, counter)
1✔
749
}
750

751
///|
752
/// Creates a ChaCha20 encryption context following the RFC 8439 standard.
753
/// - [key] must be 256-bit (32 bytes), in little-endian order.
754
/// - [nonce] must be a 96-bit (12 bytes) bytes, in little-endian order.
755
/// - [counter] is the counter value, defaulting to 0.
756
///
757
/// raise Error if the length of key or nonce is invalid.
758
pub fn[K : ByteSource, N : ByteSource] ChaCha::chacha20(
759
  key : K,
760
  nonce : N,
761
  counter? : UInt = 0,
762
) -> ChaCha raise Error {
763
  ChaCha::new(key, nonce, 10, counter)
4✔
764
}
765

766
///|
767
/// Transforms the given data using the ChaCha encryption algorithm.
768
/// - [data] is the data to be transformed.
769
/// - [target] is the output buffer to store the transformed data.
770
/// - [offset] is the offset in the target buffer where the transformed data will be written
771
/// 
772
/// If the length of [data] is less then the length of [target] minus [offset], it will panic.
773
pub fn[D : ByteSource] ChaCha::transform(
774
  self : Self,
775
  data : D,
776
  target : FixedArray[Byte],
777
  offset? : Int = 0,
778
) -> Unit {
779
  let length = data.length()
29✔
780
  let mut input_offset = 0
781
  if self.offset < 64 {
782
    let prefix_length = @cmp.minimum(length, 64 - self.offset)
22✔
783
    for i in 0..<prefix_length {
784
      target[offset + i] = self.key_stream[self.offset + i] ^ data[i]
586✔
785
    }
786
    self.offset += prefix_length
787
    input_offset = prefix_length
788
  }
789
  if input_offset == length {
790
    return
9✔
791
  }
792
  for ; chacha4_enabled() && input_offset + 256 <= length; {
24✔
793
    chacha4BlockBytes(
4✔
794
      self.key,
795
      self.counter,
796
      self.nonce,
797
      self.round,
798
      self.key_stream_words,
799
      self.key_stream,
800
      self.bulk_key_stream,
801
    )
802
    self.counter += 4
803
    chacha_xor_into(
4✔
804
      data,
805
      input_offset,
806
      target,
807
      offset + input_offset,
808
      self.bulk_key_stream,
809
      0,
810
      256,
811
    )
812
    input_offset += 256
813
  }
814
  for ; input_offset + 64 <= length; {
815
    chachaBlockBytes(
3✔
816
      self.key,
817
      self.counter,
818
      self.nonce,
819
      self.round,
820
      self.key_stream_words,
821
      self.key_stream,
822
    )
823
    self.counter += 1
824
    chacha_xor_into(
3✔
825
      data,
826
      input_offset,
827
      target,
828
      offset + input_offset,
829
      self.key_stream,
830
      0,
831
      64,
832
    )
833
    input_offset += 64
834
  }
835
  if input_offset < length {
836
    chachaBlockBytes(
20✔
837
      self.key,
838
      self.counter,
839
      self.nonce,
840
      self.round,
841
      self.key_stream_words,
842
      self.key_stream,
843
    )
844
    self.counter += 1
845
    self.offset = length - input_offset
846
    for i in input_offset..<length {
847
      target[offset + i] = self.key_stream[i - input_offset] ^ data[i]
392✔
848
    }
849
  } else {
NEW
850
    self.offset = 64
×
851
  }
852
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc