| 1 | // Copyright (c) 2023 Kim Shrier. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | // Package blake3 implements the Blake3 cryptographic hash |
| 5 | // as described in: |
| 6 | // https://github.com/BLAKE3-team/BLAKE3-specs/blob/master/blake3.pdf |
| 7 | // Version 20211102173700 |
| 8 | |
| 9 | module blake3 |
| 10 | |
| 11 | import encoding.binary |
| 12 | |
| 13 | struct Chunk { |
| 14 | mut: |
| 15 | chunk_number u64 |
| 16 | chaining_value []u32 |
| 17 | block_words []u32 |
| 18 | block_len u32 |
| 19 | flags u32 |
| 20 | } |
| 21 | |
| 22 | fn (c Chunk) str() string { |
| 23 | return 'Chunk {\n chunk_number: ${c.chunk_number}\n chaining_value: ${c.chaining_value[0]:08x} ${c.chaining_value[1]:08x} ${c.chaining_value[2]:08x} ${c.chaining_value[3]:08x} ${c.chaining_value[4]:08x} ${c.chaining_value[5]:08x} ${c.chaining_value[6]:08x} ${c.chaining_value[7]:08x}\n block_words: ${c.block_words[0]:08x} ${c.block_words[1]:08x} ${c.block_words[2]:08x} ${c.block_words[3]:08x} ${c.block_words[4]:08x} ${c.block_words[5]:08x} ${c.block_words[6]:08x} ${c.block_words[7]:08x}\n ${c.block_words[8]:08x} ${c.block_words[9]:08x} ${c.block_words[10]:08x} ${c.block_words[11]:08x} ${c.block_words[12]:08x} ${c.block_words[13]:08x} ${c.block_words[14]:08x} ${c.block_words[15]:08x}\n block_len: ${c.block_len}\n flags: ${c.flags:08x}' |
| 24 | } |
| 25 | |
| 26 | // process_input handles up to 1024 bytes of input |
| 27 | // |
| 28 | // A chunk consists of 0 to 1024 bytes of input data. This |
| 29 | // method is only called when we have 1024 bytes of data |
| 30 | // or when we are processing the last chunk and there is |
| 31 | // less than 1024 bytes. |
| 32 | // |
| 33 | // The only time that it is legal to have 0 bytes input is |
| 34 | // when we are processing chunk 0. If we are processing |
| 35 | // any other chunk, we panic because this is a private |
| 36 | // method and if we are passing in 0 bytes on some chunk |
| 37 | // other than 0, there is an internal algorithm bug. |
| 38 | // |
| 39 | // If this method is passed more than 1024 bytes of input data, |
| 40 | // we panic because this also is an internal algorithm bug. |
| 41 | // |
| 42 | // After this method returns, all the input data is processed |
| 43 | // into the chunk and the 16 32-bit words of the compression |
| 44 | // state is returned. These 16 words can either form the |
| 45 | // chaining value for the chunk or the block_words used in |
| 46 | // generating the output hash from the root node. |
| 47 | // |
| 48 | // If this chunk is also the root node, the caller needs to |
| 49 | // set root to true. |
| 50 | // |
| 51 | // As a potential speed up, we could try spawning this function |
| 52 | // in a concurrent task and see if it is worth the overhead. |
| 53 | @[direct_array_access] |
| 54 | fn (mut c Chunk) process_input(input []u8, key_words []u32, counter u64, flags u32, root bool) []u32 { |
| 55 | mut remaining_input := unsafe { input[..] } |
| 56 | |
| 57 | if remaining_input.len == 0 && counter != 0 { |
| 58 | panic('trying to process 0 bytes in chunk ${counter}') |
| 59 | } |
| 60 | |
| 61 | if remaining_input.len > chunk_size { |
| 62 | panic('trying to process ${remaining_input.len} bytes in chunk ${counter}') |
| 63 | } |
| 64 | |
| 65 | c.chunk_number = counter |
| 66 | c.chaining_value = key_words.clone() |
| 67 | c.block_words = []u32{len: 16} |
| 68 | |
| 69 | for i in 0 .. 16 { |
| 70 | c.block_len = u32(block_size) |
| 71 | c.flags = flags | if i == 0 { u32(Flags.chunk_start) } else { u32(0) } |
| 72 | |
| 73 | if remaining_input.len <= block_size { |
| 74 | c.block_len = u32(remaining_input.len) |
| 75 | |
| 76 | for remaining_input.len < block_size { |
| 77 | remaining_input << u8(0) |
| 78 | } |
| 79 | |
| 80 | c.flags |= u32(Flags.chunk_end) | if root { u32(Flags.root) } else { u32(0) } |
| 81 | } |
| 82 | |
| 83 | for j in 0 .. 16 { |
| 84 | c.block_words[j] = binary.little_endian_u32_at(remaining_input, j * 4) |
| 85 | } |
| 86 | |
| 87 | remaining_input = unsafe { remaining_input[block_size..] } |
| 88 | |
| 89 | words := f(c.chaining_value, c.block_words, c.chunk_number, c.block_len, c.flags) |
| 90 | |
| 91 | if c.flags & u32(Flags.chunk_end) == 0 { |
| 92 | c.chaining_value = words[..8] |
| 93 | } else { |
| 94 | return words |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | panic('processing more than 16 ${block_size} byte blocks in chunk ${c.chunk_number}') |
| 99 | } |
| 100 | |