1use super::{Blake3Algorithm, DeriveKeyXof, ExtendableOutputFunction, KeyedXof};
72use crate::error::{validate, Error, Result};
73use crate::xof::XofAlgorithm;
74use dcrypt_common::security::SecretBuffer;
75use dcrypt_internal::zeroing::{
76 boxed_bytes_zeroed, Zeroize, ZeroizeOnDrop, Zeroizing, ZeroizingBytes,
77};
78
79#[cfg(not(feature = "std"))]
80use alloc::{boxed::Box, vec};
81
82const OUT_LEN: usize = 32; const KEY_LEN: usize = 32; const BLOCK_LEN: usize = 64; const CHUNK_LEN: usize = 1024; type ProtectedChainingValue = Zeroizing<[u32; 8]>;
89type ProtectedBlockWords = Zeroizing<[u32; 16]>;
90
91const CHUNK_START: u32 = 1 << 0; const CHUNK_END: u32 = 1 << 1; const PARENT: u32 = 1 << 2; const ROOT: u32 = 1 << 3; const KEYED_HASH: u32 = 1 << 4; const DERIVE_KEY_CONTEXT: u32 = 1 << 5; const DERIVE_KEY_MATERIAL: u32 = 1 << 6; const IV: [u32; 8] = [
104 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19,
105];
106
107const MSG_PERMUTATION: [usize; 16] = [2, 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8];
110
111fn words_from_little_endian_bytes(bytes: &[u8], words: &mut [u32]) {
113 debug_assert_eq!(bytes.len(), 4 * words.len());
114 for i in 0..words.len() {
115 let offset = i * 4;
116 words[i] = u32::from(bytes[offset])
117 | (u32::from(bytes[offset + 1]) << 8)
118 | (u32::from(bytes[offset + 2]) << 16)
119 | (u32::from(bytes[offset + 3]) << 24);
120 }
121}
122
123fn words_to_little_endian_bytes(words: &[u32], bytes: &mut [u8]) {
125 debug_assert_eq!(bytes.len(), 4 * words.len());
126 for i in 0..words.len() {
127 for byte in 0..4 {
128 bytes[i * 4 + byte] = (words[i] >> (byte * 8)) as u8;
129 }
130 }
131}
132
133#[inline(always)]
137fn g(state: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize, mx: u32, my: u32) {
138 state[a] = state[a].wrapping_add(state[b]).wrapping_add(mx);
139 state[d] = (state[d] ^ state[a]).rotate_right(16);
140 state[c] = state[c].wrapping_add(state[d]);
141 state[b] = (state[b] ^ state[c]).rotate_right(12);
142
143 state[a] = state[a].wrapping_add(state[b]).wrapping_add(my);
144 state[d] = (state[d] ^ state[a]).rotate_right(8);
145 state[c] = state[c].wrapping_add(state[d]);
146 state[b] = (state[b] ^ state[c]).rotate_right(7);
147}
148
149fn round(state: &mut [u32; 16], m: &[u32; 16]) {
151 g(state, 0, 4, 8, 12, m[0], m[1]);
153 g(state, 1, 5, 9, 13, m[2], m[3]);
154 g(state, 2, 6, 10, 14, m[4], m[5]);
155 g(state, 3, 7, 11, 15, m[6], m[7]);
156
157 g(state, 0, 5, 10, 15, m[8], m[9]);
159 g(state, 1, 6, 11, 12, m[10], m[11]);
160 g(state, 2, 7, 8, 13, m[12], m[13]);
161 g(state, 3, 4, 9, 14, m[14], m[15]);
162}
163
164fn permute(m: &mut [u32; 16]) {
166 let mut permuted = Zeroizing::new([0u32; 16]);
167 for i in 0..16 {
168 permuted[i] = m[MSG_PERMUTATION[i]];
169 }
170 m.copy_from_slice(&*permuted);
171}
172
173fn compress(
185 chaining_value: &[u32; 8],
186 block_words: &[u32; 16],
187 counter: u64,
188 block_len: u32,
189 flags: u32,
190) -> ProtectedBlockWords {
191 let counter_low = counter as u32;
192 let counter_high = (counter >> 32) as u32;
193
194 let mut state = Zeroizing::new([
196 chaining_value[0],
197 chaining_value[1],
198 chaining_value[2],
199 chaining_value[3],
200 chaining_value[4],
201 chaining_value[5],
202 chaining_value[6],
203 chaining_value[7],
204 IV[0],
205 IV[1],
206 IV[2],
207 IV[3],
208 counter_low,
209 counter_high,
210 block_len,
211 flags,
212 ]);
213
214 let mut block = Zeroizing::new(*block_words);
215
216 for r in 0..7 {
218 round(&mut state, &block);
220
221 if r < 6 {
223 permute(&mut block);
224 }
225 }
226
227 let mut output = Zeroizing::new([0u32; 16]);
229
230 for i in 0..8 {
232 output[i] = state[i] ^ state[i + 8];
233 }
234
235 for i in 0..8 {
237 output[i + 8] = state[i + 8] ^ chaining_value[i];
238 }
239
240 output
241}
242
243fn first_8_words(compression_output: &[u32; 16]) -> ProtectedChainingValue {
245 let mut result = Zeroizing::new([0u32; 8]);
246 result.copy_from_slice(&compression_output[0..8]);
247 result
248}
249
250#[derive(Clone)]
252struct Output {
253 input_chaining_value: [u32; 8],
254 block_words: [u32; 16],
255 counter: u64,
256 block_len: u32,
257 flags: u32,
258}
259
260impl Zeroize for Output {
261 fn zeroize(&mut self) {
262 self.input_chaining_value.zeroize();
263 self.block_words.zeroize();
264 self.counter.zeroize();
265 self.block_len.zeroize();
266 self.flags.zeroize();
267 }
268}
269
270impl Drop for Output {
271 fn drop(&mut self) {
272 self.zeroize();
273 }
274}
275
276impl ZeroizeOnDrop for Output {}
277
278impl Output {
279 fn chaining_value(&self) -> ProtectedChainingValue {
280 let compression_output = compress(
281 &self.input_chaining_value,
282 &self.block_words,
283 self.counter,
284 self.block_len,
285 self.flags,
286 );
287 first_8_words(&compression_output)
288 }
289
290 fn root_output_bytes(&self, out_slice: &mut [u8]) {
291 for (output_block_counter, out_block) in out_slice.chunks_mut(2 * OUT_LEN).enumerate() {
292 let words = compress(
293 &self.input_chaining_value,
294 &self.block_words,
295 output_block_counter as u64,
296 self.block_len,
297 self.flags | ROOT,
298 );
299
300 for (i, word) in words.iter().enumerate() {
302 let start = i * 4;
303 if start >= out_block.len() {
304 break;
305 }
306 let end = core::cmp::min((i + 1) * 4, out_block.len());
307 for (offset, byte) in out_block[start..end].iter_mut().enumerate() {
308 *byte = (word >> (offset * 8)) as u8;
309 }
310 }
311 }
312 }
313}
314
315#[derive(Clone)]
317struct ChunkState {
318 chaining_value: [u32; 8],
319 chunk_counter: u64,
320 block: [u8; BLOCK_LEN],
321 block_len: u8,
322 blocks_compressed: u8,
323 flags: u32,
324}
325
326impl Zeroize for ChunkState {
327 fn zeroize(&mut self) {
328 self.chaining_value.zeroize();
329 self.chunk_counter.zeroize();
330 self.block.zeroize();
331 self.block_len.zeroize();
332 self.blocks_compressed.zeroize();
333 self.flags.zeroize();
334 }
335}
336
337impl Drop for ChunkState {
338 fn drop(&mut self) {
339 self.zeroize();
340 }
341}
342
343impl ZeroizeOnDrop for ChunkState {}
344
345impl ChunkState {
346 fn new(key_words: &[u32; 8], chunk_counter: u64, flags: u32) -> Self {
347 let mut state = Self {
348 chaining_value: [0; 8],
349 chunk_counter,
350 block: [0; BLOCK_LEN],
351 block_len: 0,
352 blocks_compressed: 0,
353 flags,
354 };
355 state.chaining_value.copy_from_slice(key_words);
356 state
357 }
358
359 fn len(&self) -> usize {
360 (self.blocks_compressed as usize) * BLOCK_LEN + (self.block_len as usize)
361 }
362
363 fn start_flag(&self) -> u32 {
364 if self.blocks_compressed == 0 {
365 CHUNK_START
366 } else {
367 0
368 }
369 }
370
371 fn update_internal(&mut self, mut input: &[u8]) -> Result<()> {
373 if self.len() + input.len() > CHUNK_LEN {
375 let want = CHUNK_LEN - self.len();
376 self.update_internal(&input[..want])?;
377 return Ok(());
378 }
379
380 while !input.is_empty() {
381 if self.block_len as usize == BLOCK_LEN {
383 let mut block_words = Zeroizing::new([0u32; 16]);
384 words_from_little_endian_bytes(&self.block, &mut block_words[..]);
385
386 let compression_output = compress(
387 &self.chaining_value,
388 &block_words,
389 self.chunk_counter,
390 BLOCK_LEN as u32,
391 self.flags | self.start_flag(),
392 );
393 let chaining_value = first_8_words(&compression_output);
394 self.chaining_value.copy_from_slice(&*chaining_value);
395
396 self.blocks_compressed += 1;
397 self.block.zeroize();
398 self.block_len = 0;
399 }
400
401 let want = BLOCK_LEN - self.block_len as usize;
403 let take = core::cmp::min(want, input.len());
404
405 self.block[self.block_len as usize..self.block_len as usize + take]
406 .copy_from_slice(&input[..take]);
407
408 self.block_len += take as u8;
409 input = &input[take..];
410 }
411
412 Ok(())
413 }
414
415 #[cfg(test)]
417 pub fn update(&mut self, input: &[u8]) -> Result<()> {
418 self.update_internal(input)
419 }
420
421 fn output(&self) -> Output {
422 let mut block_words = Zeroizing::new([0u32; 16]);
424 words_from_little_endian_bytes(&self.block, &mut block_words[..]);
425
426 Output {
427 input_chaining_value: self.chaining_value,
428 block_words: *block_words,
429 counter: self.chunk_counter,
430 block_len: self.block_len as u32,
431 flags: self.flags | self.start_flag() | CHUNK_END,
432 }
433 }
434}
435
436fn parent_output(
438 left_child_cv: &[u32; 8],
439 right_child_cv: &[u32; 8],
440 key_words: &[u32; 8],
441 flags: u32,
442) -> Output {
443 let mut block_words = Zeroizing::new([0u32; 16]);
444 block_words[..8].copy_from_slice(left_child_cv);
445 block_words[8..].copy_from_slice(right_child_cv);
446
447 let mut input_chaining_value = Zeroizing::new([0u32; 8]);
448 input_chaining_value.copy_from_slice(key_words);
449 Output {
450 input_chaining_value: *input_chaining_value,
451 block_words: *block_words,
452 counter: 0,
453 block_len: BLOCK_LEN as u32,
454 flags: PARENT | flags,
455 }
456}
457
458fn parent_cv(
460 left_child_cv: &[u32; 8],
461 right_child_cv: &[u32; 8],
462 key_words: &[u32; 8],
463 flags: u32,
464) -> ProtectedChainingValue {
465 parent_output(left_child_cv, right_child_cv, key_words, flags).chaining_value()
466}
467
468#[derive(Clone)]
495pub struct Blake3Xof {
496 chunk_state: ChunkState,
497 key_words: SecretBuffer<32>, cv_stack: Zeroizing<Box<[[u32; 8]]>>,
499 flags: u32,
500}
501
502impl Drop for Blake3Xof {
504 fn drop(&mut self) {
505 self.zeroize();
506 }
507}
508
509impl ZeroizeOnDrop for Blake3Xof {}
510
511impl Zeroize for Blake3Xof {
513 fn zeroize(&mut self) {
514 self.chunk_state.zeroize();
515 self.key_words.zeroize();
516 self.cv_stack.zeroize();
517 self.flags = 0;
518 }
519}
520
521impl Blake3Xof {
522 fn get_key_words(&self) -> ProtectedChainingValue {
524 let mut words = Zeroizing::new([0u32; 8]);
525 let key_bytes = self.key_words.as_ref();
526 words_from_little_endian_bytes(key_bytes, &mut words[..]);
527 words
528 }
529
530 fn push_stack(&mut self, cv: ProtectedChainingValue) {
531 let current_len = self.cv_stack.len();
532 let mut replacement = Zeroizing::new(vec![[0u32; 8]; current_len + 1].into_boxed_slice());
533 replacement[..current_len].copy_from_slice(&self.cv_stack);
534 replacement[current_len].copy_from_slice(&*cv);
535 self.cv_stack = replacement;
536 }
537
538 fn pop_stack(&mut self) -> Result<ProtectedChainingValue> {
539 let current_len = self.cv_stack.len();
540 if current_len == 0 {
541 return Err(Error::Processing {
542 operation: "BLAKE3",
543 details: "Stack underflow",
544 });
545 }
546 let value = Zeroizing::new(self.cv_stack[current_len - 1]);
547 let mut replacement = Zeroizing::new(vec![[0u32; 8]; current_len - 1].into_boxed_slice());
548 replacement.copy_from_slice(&self.cv_stack[..current_len - 1]);
549 self.cv_stack = replacement;
550 Ok(value)
551 }
552
553 fn add_chunk_chaining_value(
554 &mut self,
555 mut new_cv: ProtectedChainingValue,
556 mut total_chunks: u64,
557 ) -> Result<()> {
558 while total_chunks & 1 == 0 {
559 let left_cv = self.pop_stack()?;
560 let key_words = self.get_key_words();
561 new_cv = parent_cv(&left_cv, &new_cv, &key_words, self.flags);
562 total_chunks >>= 1;
563 }
564 self.push_stack(new_cv);
565 Ok(())
566 }
567
568 fn finalize(&mut self, out_slice: &mut [u8]) -> Result<()> {
569 let mut output = self.chunk_state.output();
570 let mut parent_nodes_remaining = self.cv_stack.len();
571
572 while parent_nodes_remaining > 0 {
573 parent_nodes_remaining -= 1;
574 let right_cv = output.chaining_value();
575 let key_words = self.get_key_words();
576 output = parent_output(
577 &self.cv_stack[parent_nodes_remaining],
578 &right_cv,
579 &key_words,
580 self.flags,
581 );
582 }
583
584 output.root_output_bytes(out_slice);
585 Ok(())
586 }
587
588 pub fn generate(data: &[u8], len: usize) -> Result<ZeroizingBytes> {
609 Blake3Algorithm::validate_output_length(len)?;
610
611 let mut xof = Self::new();
612 xof.update(data)?;
613 let mut result = Zeroizing::new(boxed_bytes_zeroed(len));
614 xof.squeeze(&mut result)?;
615 Ok(result)
616 }
617}
618
619impl ExtendableOutputFunction for Blake3Xof {
620 fn new() -> Self {
625 let mut key_bytes = Zeroizing::new([0u8; 32]);
627 words_to_little_endian_bytes(&IV, &mut key_bytes[..]);
628
629 Self {
630 chunk_state: ChunkState::new(&IV, 0, 0),
631 key_words: SecretBuffer::new(*key_bytes),
632 cv_stack: Zeroizing::new(Box::default()),
633 flags: 0,
634 }
635 }
636
637 fn update(&mut self, mut input: &[u8]) -> Result<()> {
638 while !input.is_empty() {
639 if self.chunk_state.len() == CHUNK_LEN {
640 let chunk_cv = self.chunk_state.output().chaining_value();
641 let total_chunks = self.chunk_state.chunk_counter + 1;
642 self.add_chunk_chaining_value(chunk_cv, total_chunks)?;
643 let key_words = self.get_key_words();
644 self.chunk_state = ChunkState::new(&key_words, total_chunks, self.flags);
645 }
646
647 let want = CHUNK_LEN - self.chunk_state.len();
648 let take = core::cmp::min(want, input.len());
649 self.chunk_state.update_internal(&input[..take])?;
650 input = &input[take..];
651 }
652
653 Ok(())
654 }
655
656 fn finalize(&mut self) -> Result<()> {
657 Ok(())
658 }
659
660 fn squeeze(&mut self, output: &mut [u8]) -> Result<()> {
661 Blake3Algorithm::validate_output_length(output.len())?;
662 self.finalize(output)
663 }
664
665 fn squeeze_into_vec(&mut self, len: usize) -> Result<ZeroizingBytes> {
666 Blake3Algorithm::validate_output_length(len)?;
667 let mut result = Zeroizing::new(boxed_bytes_zeroed(len));
668 self.squeeze(&mut result)?;
669 Ok(result)
670 }
671
672 fn reset(&mut self) -> Result<()> {
673 *self = Self::new();
674 Ok(())
675 }
676
677 fn security_level() -> usize {
678 Blake3Algorithm::SECURITY_LEVEL
679 }
680}
681
682impl KeyedXof for Blake3Xof {
683 fn with_key(key: &[u8]) -> Result<Self> {
696 validate::length("BLAKE3 key", key.len(), KEY_LEN)?;
697
698 let mut key_bytes = Zeroizing::new([0u8; KEY_LEN]);
700 key_bytes.copy_from_slice(key);
701 let key_buf = SecretBuffer::new(*key_bytes);
702
703 let mut key_words = Zeroizing::new([0u32; 8]);
705 words_from_little_endian_bytes(key, &mut key_words[..]);
706
707 let instance = Self {
708 chunk_state: ChunkState::new(&key_words, 0, KEYED_HASH),
709 key_words: key_buf,
710 cv_stack: Zeroizing::new(Box::default()),
711 flags: KEYED_HASH,
712 };
713
714 Ok(instance)
715 }
716}
717
718impl DeriveKeyXof for Blake3Xof {
719 fn for_derive_key(context: &[u8]) -> Result<Self> {
734 let mut context_hasher = Self::new();
735 context_hasher.update(context)?;
736
737 let mut context_key = Zeroizing::new([0u8; KEY_LEN]);
739 let mut output = context_hasher.chunk_state.output();
740 output.flags |= DERIVE_KEY_CONTEXT;
741 output.root_output_bytes(&mut *context_key);
742
743 let key_buf = SecretBuffer::new(*context_key);
745
746 let mut key_words = Zeroizing::new([0u32; 8]);
748 words_from_little_endian_bytes(context_key.as_ref(), &mut key_words[..]);
749
750 let instance = Self {
751 chunk_state: ChunkState::new(&key_words, 0, DERIVE_KEY_MATERIAL),
752 key_words: key_buf,
753 cv_stack: Zeroizing::new(Box::default()),
754 flags: DERIVE_KEY_MATERIAL,
755 };
756
757 Ok(instance)
758 }
759}
760
761#[cfg(test)]
762mod tests;