v / vlib / x / crypto / chacha20 / xchacha.v
102 lines · 89 sloc · 3.86 KB · 9eef7ecd7bedf8a0a27b75ab7f95bb6b732ea0e8
Raw
1// Copyright © 2025 blackshirt.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4//
5// This file contains a building block for eXtended ChaCha20 stream cipher (XChaCha20) construction.
6// Its based on https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha-03
7// Note: so, its maybe outdated...
8// Beside above draft that defines XChaCha20 construction with 32-bit internal counter,
9// this XChaCha20 construction was expanded to support 64-bit counter.
10// There are nothing RFC draft or published standard that can be used as a reference.
11// Fortunatelly, this construct commonly implemented in popular chacha20 libraries.
12module chacha20
13
14import encoding.binary
15
16// HChaCha20 nonce size
17const h_nonce_size = 16
18
19// hchacha20 are intermediary step to build XChaCha20 and initialized the same way as the ChaCha20 cipher,
20// except hchacha20 use a 128-bit (16 byte) nonce and has no counter to derive subkey.
21// See https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha-03#section-2.2
22@[direct_array_access]
23fn hchacha20(key []u8, nonce []u8) ![]u8 {
24 // early bound check
25 if key.len != key_size {
26 return error('xchacha: Bad key size')
27 }
28 if nonce.len != h_nonce_size {
29 return error('xchacha: Bad nonce size')
30 }
31 // initializes ChaCha20 state
32 mut x := State{}
33 x[0] = cc0
34 x[1] = cc1
35 x[2] = cc2
36 x[3] = cc3
37
38 x[4] = binary.little_endian_u32(key[0..4])
39 x[5] = binary.little_endian_u32(key[4..8])
40 x[6] = binary.little_endian_u32(key[8..12])
41 x[7] = binary.little_endian_u32(key[12..16])
42
43 x[8] = binary.little_endian_u32(key[16..20])
44 x[9] = binary.little_endian_u32(key[20..24])
45 x[10] = binary.little_endian_u32(key[24..28])
46 x[11] = binary.little_endian_u32(key[28..32])
47
48 // we have no counter
49 x[12] = binary.little_endian_u32(nonce[0..4])
50 x[13] = binary.little_endian_u32(nonce[4..8])
51 x[14] = binary.little_endian_u32(nonce[8..12])
52 x[15] = binary.little_endian_u32(nonce[12..16])
53
54 // After initialization, proceed through the ChaCha20 rounds as usual.
55 x.qround(10)
56
57 // Once the 20 ChaCh20 rounds have been completed, the first 128 bits (16 bytes) and
58 // last 128 bits (16 bytes) of the ChaCha state (both little-endian) are
59 // concatenated, and this 256-bit (32 bytes) subkey is returned.
60 mut out := []u8{len: 32}
61 binary.little_endian_put_u32(mut out[0..4], x[0])
62 binary.little_endian_put_u32(mut out[4..8], x[1])
63 binary.little_endian_put_u32(mut out[8..12], x[2])
64 binary.little_endian_put_u32(mut out[12..16], x[3])
65
66 binary.little_endian_put_u32(mut out[16..20], x[12])
67 binary.little_endian_put_u32(mut out[20..24], x[13])
68 binary.little_endian_put_u32(mut out[24..28], x[14])
69 binary.little_endian_put_u32(mut out[28..32], x[15])
70
71 return out
72}
73
74// derive_xchacha20_key_nonce derives a new key and nonce for eXtended ChaCha20 construction.
75// It accepts boolean `flag64` flag as the last parameters.
76// When its set into true, it would be used as an indicator of a 64-bit counter construction.
77@[direct_array_access; inline]
78fn derive_xchacha20_key_nonce(key []u8, nonce []u8, flag64 bool) !([]u8, []u8) {
79 // Its only for x_nonce_size
80 if nonce.len != x_nonce_size {
81 return error('Bad nonce size for derive_xchacha20_key_nonce')
82 }
83 // derives a new key based on XChaCha20 construction
84 // first, use 16 bytes of nonce used to derive the key
85 new_key := hchacha20(key, nonce[0..16])!
86 remaining_nonce := nonce[16..24].clone()
87
88 // derive a new nonce based on the flag64 flag.
89 // If flag64 was true, its intended to build XChaCha20 original variant with 64-bit counter.
90 // Otherwise, its a XChaCha20 standard variant with 32-bit counter
91 new_nonce := if flag64 {
92 // use the remaining 8-bytes nonce
93 remaining_nonce
94 } else {
95 // and the last of 8 bytes of nonce copied into to build nonce_size length of new nonce.
96 mut nonce12 := []u8{len: nonce_size}
97 _ := copy(mut nonce12[4..12], remaining_nonce)
98 nonce12
99 }
100
101 return new_key, new_nonce
102}
103