blake3/
portable.rs

1use crate::{
2    counter_high, counter_low, CVBytes, CVWords, IncrementCounter, BLOCK_LEN, IV, MSG_SCHEDULE,
3    OUT_LEN,
4};
5use arrayref::{array_mut_ref, array_ref};
6
7#[inline(always)]
8fn g(state: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize, x: u32, y: u32) {
9    state[a] = state[a].wrapping_add(state[b]).wrapping_add(x);
10    state[d] = (state[d] ^ state[a]).rotate_right(16);
11    state[c] = state[c].wrapping_add(state[d]);
12    state[b] = (state[b] ^ state[c]).rotate_right(12);
13    state[a] = state[a].wrapping_add(state[b]).wrapping_add(y);
14    state[d] = (state[d] ^ state[a]).rotate_right(8);
15    state[c] = state[c].wrapping_add(state[d]);
16    state[b] = (state[b] ^ state[c]).rotate_right(7);
17}
18
19#[inline(always)]
20fn round(state: &mut [u32; 16], msg: &[u32; 16], round: usize) {
21    // Select the message schedule based on the round.
22    let schedule = MSG_SCHEDULE[round];
23
24    // Mix the columns.
25    g(state, 0, 4, 8, 12, msg[schedule[0]], msg[schedule[1]]);
26    g(state, 1, 5, 9, 13, msg[schedule[2]], msg[schedule[3]]);
27    g(state, 2, 6, 10, 14, msg[schedule[4]], msg[schedule[5]]);
28    g(state, 3, 7, 11, 15, msg[schedule[6]], msg[schedule[7]]);
29
30    // Mix the diagonals.
31    g(state, 0, 5, 10, 15, msg[schedule[8]], msg[schedule[9]]);
32    g(state, 1, 6, 11, 12, msg[schedule[10]], msg[schedule[11]]);
33    g(state, 2, 7, 8, 13, msg[schedule[12]], msg[schedule[13]]);
34    g(state, 3, 4, 9, 14, msg[schedule[14]], msg[schedule[15]]);
35}
36
37#[inline(always)]
38fn compress_pre(
39    cv: &CVWords,
40    block: &[u8; BLOCK_LEN],
41    block_len: u8,
42    counter: u64,
43    flags: u8,
44) -> [u32; 16] {
45    let block_words = crate::platform::words_from_le_bytes_64(block);
46
47    let mut state = [
48        cv[0],
49        cv[1],
50        cv[2],
51        cv[3],
52        cv[4],
53        cv[5],
54        cv[6],
55        cv[7],
56        IV[0],
57        IV[1],
58        IV[2],
59        IV[3],
60        counter_low(counter),
61        counter_high(counter),
62        block_len as u32,
63        flags as u32,
64    ];
65
66    round(&mut state, &block_words, 0);
67    round(&mut state, &block_words, 1);
68    round(&mut state, &block_words, 2);
69    round(&mut state, &block_words, 3);
70    round(&mut state, &block_words, 4);
71    round(&mut state, &block_words, 5);
72    round(&mut state, &block_words, 6);
73
74    state
75}
76
77pub fn compress_in_place(
78    cv: &mut CVWords,
79    block: &[u8; BLOCK_LEN],
80    block_len: u8,
81    counter: u64,
82    flags: u8,
83) {
84    let state = compress_pre(cv, block, block_len, counter, flags);
85
86    cv[0] = state[0] ^ state[8];
87    cv[1] = state[1] ^ state[9];
88    cv[2] = state[2] ^ state[10];
89    cv[3] = state[3] ^ state[11];
90    cv[4] = state[4] ^ state[12];
91    cv[5] = state[5] ^ state[13];
92    cv[6] = state[6] ^ state[14];
93    cv[7] = state[7] ^ state[15];
94}
95
96pub fn compress_xof(
97    cv: &CVWords,
98    block: &[u8; BLOCK_LEN],
99    block_len: u8,
100    counter: u64,
101    flags: u8,
102) -> [u8; 64] {
103    let mut state = compress_pre(cv, block, block_len, counter, flags);
104    state[0] ^= state[8];
105    state[1] ^= state[9];
106    state[2] ^= state[10];
107    state[3] ^= state[11];
108    state[4] ^= state[12];
109    state[5] ^= state[13];
110    state[6] ^= state[14];
111    state[7] ^= state[15];
112    state[8] ^= cv[0];
113    state[9] ^= cv[1];
114    state[10] ^= cv[2];
115    state[11] ^= cv[3];
116    state[12] ^= cv[4];
117    state[13] ^= cv[5];
118    state[14] ^= cv[6];
119    state[15] ^= cv[7];
120    crate::platform::le_bytes_from_words_64(&state)
121}
122
123pub fn hash1<const N: usize>(
124    input: &[u8; N],
125    key: &CVWords,
126    counter: u64,
127    flags: u8,
128    flags_start: u8,
129    flags_end: u8,
130    out: &mut CVBytes,
131) {
132    debug_assert_eq!(N % BLOCK_LEN, 0, "uneven blocks");
133    let mut cv = *key;
134    let mut block_flags = flags | flags_start;
135    let mut slice = &input[..];
136    while slice.len() >= BLOCK_LEN {
137        if slice.len() == BLOCK_LEN {
138            block_flags |= flags_end;
139        }
140        compress_in_place(
141            &mut cv,
142            array_ref!(slice, 0, BLOCK_LEN),
143            BLOCK_LEN as u8,
144            counter,
145            block_flags,
146        );
147        block_flags = flags;
148        slice = &slice[BLOCK_LEN..];
149    }
150    *out = crate::platform::le_bytes_from_words_32(&cv);
151}
152
153pub fn hash_many<const N: usize>(
154    inputs: &[&[u8; N]],
155    key: &CVWords,
156    mut counter: u64,
157    increment_counter: IncrementCounter,
158    flags: u8,
159    flags_start: u8,
160    flags_end: u8,
161    out: &mut [u8],
162) {
163    debug_assert!(out.len() >= inputs.len() * OUT_LEN, "out too short");
164    for (&input, output) in inputs.iter().zip(out.chunks_exact_mut(OUT_LEN)) {
165        hash1(
166            input,
167            key,
168            counter,
169            flags,
170            flags_start,
171            flags_end,
172            array_mut_ref!(output, 0, OUT_LEN),
173        );
174        if increment_counter.yes() {
175            counter += 1;
176        }
177    }
178}
179
180#[cfg(test)]
181pub mod test {
182    use super::*;
183
184    // This is basically testing the portable implementation against itself,
185    // but it also checks that compress_in_place and compress_xof are
186    // consistent. And there are tests against the reference implementation and
187    // against hardcoded test vectors elsewhere.
188    #[test]
189    fn test_compress() {
190        crate::test::test_compress_fn(compress_in_place, compress_xof);
191    }
192
193    // Ditto.
194    #[test]
195    fn test_hash_many() {
196        crate::test::test_hash_many_fn(hash_many, hash_many);
197    }
198}