1 // Package chacha20 implements the ChaCha20 stream cipher as specified in
2 // RFC 8439.
3 //
4 // The block counter starts at zero, as required by NIP-44. The package is
5 // pure Go and uses no architecture-specific code, so it builds with TinyGo.
6 package chacha20
7 8 import (
9 "encoding/binary"
10 "math/bits"
11 )
12 13 const (
14 // KeySize is the length of a ChaCha20 key in bytes.
15 KeySize = 32
16 // NonceSize is the length of an RFC 8439 nonce in bytes.
17 NonceSize = 12
18 // BlockSize is the number of keystream bytes produced per block.
19 BlockSize = 64
20 )
21 22 // sigma holds the ChaCha20 constant "expand 32-byte k", one word per
23 // little-endian byte quadruple.
24 var sigma = [4]uint32{0x61707865, 0x3320646e, 0x79622d32, 0x6b206574}
25 26 // quarter performs one ChaCha20 quarter round on four state words.
27 func quarter(a, b, c, d uint32) (uint32, uint32, uint32, uint32) {
28 a += b
29 d ^= a
30 d = bits.RotateLeft32(d, 16)
31 c += d
32 b ^= c
33 b = bits.RotateLeft32(b, 12)
34 a += b
35 d ^= a
36 d = bits.RotateLeft32(d, 8)
37 c += d
38 b ^= c
39 b = bits.RotateLeft32(b, 7)
40 return a, b, c, d
41 }
42 43 // initState builds the initial ChaCha20 state for the given key, counter and
44 // nonce.
45 func initState(key *[KeySize]byte, counter uint32, nonce *[NonceSize]byte) [16]uint32 {
46 var s [16]uint32
47 s[0], s[1], s[2], s[3] = sigma[0], sigma[1], sigma[2], sigma[3]
48 for i := 0; i < 8; i++ {
49 s[4+i] = binary.LittleEndian.Uint32(key[4*i : 4*i+4])
50 }
51 s[12] = counter
52 for i := 0; i < 3; i++ {
53 s[13+i] = binary.LittleEndian.Uint32(nonce[4*i : 4*i+4])
54 }
55 return s
56 }
57 58 // Block returns the 64-byte ChaCha20 keystream block for the given key,
59 // block counter and nonce.
60 func Block(key *[KeySize]byte, counter uint32, nonce *[NonceSize]byte) [BlockSize]byte {
61 state := initState(key, counter, nonce)
62 x := state
63 64 // Ten double rounds: column rounds followed by diagonal rounds.
65 for i := 0; i < 10; i++ {
66 x[0], x[4], x[8], x[12] = quarter(x[0], x[4], x[8], x[12])
67 x[1], x[5], x[9], x[13] = quarter(x[1], x[5], x[9], x[13])
68 x[2], x[6], x[10], x[14] = quarter(x[2], x[6], x[10], x[14])
69 x[3], x[7], x[11], x[15] = quarter(x[3], x[7], x[11], x[15])
70 x[0], x[5], x[10], x[15] = quarter(x[0], x[5], x[10], x[15])
71 x[1], x[6], x[11], x[12] = quarter(x[1], x[6], x[11], x[12])
72 x[2], x[7], x[8], x[13] = quarter(x[2], x[7], x[8], x[13])
73 x[3], x[4], x[9], x[14] = quarter(x[3], x[4], x[9], x[14])
74 }
75 76 var out [BlockSize]byte
77 for i := 0; i < 16; i++ {
78 binary.LittleEndian.PutUint32(out[4*i:4*i+4], x[i]+state[i])
79 }
80 return out
81 }
82 83 // XORKeyStream XORs each byte of src with the ChaCha20 keystream derived from
84 // key and nonce, writing the result to dst. The block counter starts at zero
85 // and is incremented for every 64-byte block.
86 //
87 // dst must be at least as long as src. dst and src may be the same slice, or
88 // may be non-overlapping; they must not partially overlap.
89 func XORKeyStream(key *[KeySize]byte, nonce *[NonceSize]byte, dst, src []byte) {
90 if len(dst) < len(src) {
91 panic("chacha20: output smaller than input")
92 }
93 var counter uint32
94 for len(src) > 0 {
95 ks := Block(key, counter, nonce)
96 counter++
97 n := len(src)
98 if n > BlockSize {
99 n = BlockSize
100 }
101 for i := 0; i < n; i++ {
102 dst[i] = src[i] ^ ks[i]
103 }
104 dst = dst[n:]
105 src = src[n:]
106 }
107 }
108