1#![no_std]
8
9#[cfg(test)]
10mod tests;
11
12pub type ChaCha8Block = [u32; 16];
14
15#[inline(always)]
17#[cfg_attr(feature = "no-panic", no_panic::no_panic)]
18pub fn block_to_bytes(block: &ChaCha8Block) -> [u8; 64] {
19 unsafe { block.as_ptr().cast::<[u8; 64]>().read() }
21}
22
23#[inline(always)]
25#[cfg_attr(feature = "no-panic", no_panic::no_panic)]
26pub fn bytes_to_block(bytes: &[u8; 64]) -> ChaCha8Block {
27 unsafe { bytes.as_ptr().cast::<ChaCha8Block>().read_unaligned() }
29}
30
31#[derive(Debug, Copy, Clone)]
33pub struct ChaCha8State {
34 data: ChaCha8Block,
35}
36
37impl ChaCha8State {
38 const ROUNDS: usize = 8;
39
40 #[inline]
42 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
43 pub fn init(key: &[u8; 32], nonce: &[u8; 12]) -> Self {
44 let mut data = [0u32; 16];
45 data[0] = 0x6170_7865;
46 data[1] = 0x3320_646e;
47 data[2] = 0x7962_2d32;
48 data[3] = 0x6b20_6574;
49
50 for (i, &chunk) in key.as_chunks::<4>().0.iter().enumerate() {
51 data[4 + i] = u32::from_le_bytes(chunk);
52 }
53
54 for (i, &chunk) in nonce.as_chunks::<4>().0.iter().enumerate() {
57 data[13 + i] = u32::from_le_bytes(chunk);
58 }
59
60 Self { data }
61 }
62
63 #[inline(always)]
65 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
66 pub fn to_repr(self) -> ChaCha8Block {
67 self.data
68 }
69
70 #[inline(always)]
72 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
73 pub fn from_repr(data: ChaCha8Block) -> Self {
74 Self { data }
75 }
76
77 #[inline(always)]
81 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
82 pub fn compute_block(mut self, counter: u32) -> ChaCha8Block {
83 self.data[12] = counter;
84 let initial = self.data;
87
88 for _ in 0..Self::ROUNDS / 2 {
89 self.quarter_round(0, 4, 8, 12);
90 self.quarter_round(1, 5, 9, 13);
91 self.quarter_round(2, 6, 10, 14);
92 self.quarter_round(3, 7, 11, 15);
93
94 self.quarter_round(0, 5, 10, 15);
95 self.quarter_round(1, 6, 11, 12);
96 self.quarter_round(2, 7, 8, 13);
97 self.quarter_round(3, 4, 9, 14);
98 }
99
100 #[cfg_attr(
106 target_arch = "spirv",
107 expect(
108 clippy::needless_range_loop,
109 reason = "Intentional workaround for rust-gpu"
110 )
111 )]
112 for i in 0..16 {
113 self.data[i] = self.data[i].wrapping_add(initial[i]);
114 }
115
116 self.data
117 }
118
119 #[inline(always)]
120 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
121 fn quarter_round(&mut self, a: usize, b: usize, c: usize, d: usize) {
122 self.data[a] = self.data[a].wrapping_add(self.data[b]);
123 self.data[d] ^= self.data[a];
124 self.data[d] = self.data[d].rotate_left(16);
125
126 self.data[c] = self.data[c].wrapping_add(self.data[d]);
127 self.data[b] ^= self.data[c];
128 self.data[b] = self.data[b].rotate_left(12);
129
130 self.data[a] = self.data[a].wrapping_add(self.data[b]);
131 self.data[d] ^= self.data[a];
132 self.data[d] = self.data[d].rotate_left(8);
133
134 self.data[c] = self.data[c].wrapping_add(self.data[d]);
135 self.data[b] ^= self.data[c];
136 self.data[b] = self.data[b].rotate_left(7);
137 }
138}