| 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 | // size256 is the size, in bytes, of a Blake3 256 checksum. |
| 14 | pub const size256 = 32 |
| 15 | |
| 16 | // key_length is the length, in bytes, of a Blake3 key |
| 17 | pub const key_length = 32 |
| 18 | |
| 19 | // block_size is the block size, in bytes, of the Blake3 hash functions. |
| 20 | pub const block_size = 64 |
| 21 | |
| 22 | // chunk_size is the chunk size, in bytes, of the Blake3 hash functions. |
| 23 | // A chunk consists of 16 blocks. |
| 24 | pub const chunk_size = 1024 |
| 25 | |
| 26 | // G rotation constants |
| 27 | const r1 = 16 |
| 28 | const r2 = 12 |
| 29 | const r3 = 8 |
| 30 | const r4 = 7 |
| 31 | |
| 32 | // negative G rotation constants so we can rotate right. |
| 33 | const nr1 = -1 * r1 |
| 34 | const nr2 = -1 * r2 |
| 35 | const nr3 = -1 * r3 |
| 36 | const nr4 = -1 * r4 |
| 37 | |
| 38 | // initialization vector |
| 39 | const iv = [ |
| 40 | u32(0x6a09e667), |
| 41 | u32(0xbb67ae85), |
| 42 | u32(0x3c6ef372), |
| 43 | u32(0xa54ff53a), |
| 44 | u32(0x510e527f), |
| 45 | u32(0x9b05688c), |
| 46 | u32(0x1f83d9ab), |
| 47 | u32(0x5be0cd19), |
| 48 | ] |
| 49 | |
| 50 | // message word schedule permutations |
| 51 | const sigma = [ |
| 52 | [u8(0), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]!, |
| 53 | [u8(2), 6, 3, 10, 7, 0, 4, 13, 1, 11, 12, 5, 9, 14, 15, 8]!, |
| 54 | [u8(3), 4, 10, 12, 13, 2, 7, 14, 6, 5, 9, 0, 11, 15, 8, 1]!, |
| 55 | [u8(10), 7, 12, 9, 14, 3, 13, 15, 4, 0, 11, 2, 5, 8, 1, 6]!, |
| 56 | [u8(12), 13, 9, 11, 15, 10, 14, 8, 7, 2, 5, 3, 0, 1, 6, 4]!, |
| 57 | [u8(9), 14, 11, 5, 8, 12, 15, 1, 13, 3, 0, 10, 2, 6, 4, 7]!, |
| 58 | [u8(11), 15, 5, 0, 1, 9, 8, 6, 14, 10, 2, 12, 3, 4, 7, 13]!, |
| 59 | ]! |
| 60 | |
| 61 | // internal flags |
| 62 | enum Flags as u32 { |
| 63 | chunk_start = 1 << 0 |
| 64 | chunk_end = 1 << 1 |
| 65 | parent = 1 << 2 |
| 66 | root = 1 << 3 |
| 67 | keyed_hash = 1 << 4 |
| 68 | derive_key_context = 1 << 5 |
| 69 | derive_key_material = 1 << 6 |
| 70 | } |
| 71 | |
| 72 | struct KeyWordsLengthError { |
| 73 | Error |
| 74 | length u32 |
| 75 | } |
| 76 | |
| 77 | fn (err KeyWordsLengthError) msg() string { |
| 78 | return 'key_words length, ${err.length} bytes, must be 8 32-bit words' |
| 79 | } |
| 80 | |
| 81 | struct DigestFlagsError { |
| 82 | Error |
| 83 | flags u32 |
| 84 | } |
| 85 | |
| 86 | fn (err DigestFlagsError) msg() string { |
| 87 | lines := [ |
| 88 | 'Digest flags ${err.flags:08x} must either be 0 or have only one bit set.', |
| 89 | 'legal bits are:', |
| 90 | ' keyed_hash ${u32(Flags.keyed_hash):08x}', |
| 91 | ' derive_key_context ${u32(Flags.derive_key_context):08x}', |
| 92 | ' derive_key_material ${u32(Flags.derive_key_material):08x}', |
| 93 | ] |
| 94 | |
| 95 | return lines.join('\n') |
| 96 | } |
| 97 | |
| 98 | // empty tree node |
| 99 | struct Empty {} |
| 100 | |
| 101 | // parent node containing the propagated chaining value |
| 102 | struct Node { |
| 103 | chaining_value []u32 |
| 104 | } |
| 105 | |
| 106 | fn (n Node) str() string { |
| 107 | return 'Node chaining_value: ${n.chaining_value[0]:08x} ${n.chaining_value[1]:08x} ${n.chaining_value[2]:08x} ${n.chaining_value[3]:08x} ${n.chaining_value[4]:08x} ${n.chaining_value[5]:08x} ${n.chaining_value[6]:08x} ${n.chaining_value[7]:08x}' |
| 108 | } |
| 109 | |
| 110 | type TreeNode = Empty | Node |
| 111 | |
| 112 | // data needed to generate arbitrary amounts of hash output |
| 113 | struct HashState { |
| 114 | mut: |
| 115 | words []u32 |
| 116 | chaining_value []u32 |
| 117 | block_words []u32 |
| 118 | block_len u32 |
| 119 | flags u32 |
| 120 | } |
| 121 | |
| 122 | // Digest holds the state needed to compute a Blake3 hash |
| 123 | struct Digest { |
| 124 | key_words []u32 // these form the initial chaining value of a chunk |
| 125 | flags u32 // only the keyed_hash, derive_key_context, or derive_key_material bits |
| 126 | mut: |
| 127 | chunk_counter u64 // number of the next chunk to be created |
| 128 | input []u8 // unconsumed input |
| 129 | binary_edge []TreeNode // the right-hand edge of the binary tree |
| 130 | } |
| 131 | |
| 132 | // Digest.new_hash initializes a Digest structure for a Blake3 hash |
| 133 | pub fn Digest.new_hash() !Digest { |
| 134 | return Digest.new(iv, 0) |
| 135 | } |
| 136 | |
| 137 | // Digest.new_keyed_hash initializes a Digest structure for a Blake3 keyed hash |
| 138 | pub fn Digest.new_keyed_hash(key []u8) !Digest { |
| 139 | // treat the key bytes as little endian u32 values |
| 140 | mut key_words := []u32{len: 8} |
| 141 | for i in 0 .. 8 { |
| 142 | key_words[i] = binary.little_endian_u32_at(key, i * 4) |
| 143 | } |
| 144 | |
| 145 | return Digest.new(key_words, u32(Flags.keyed_hash)) |
| 146 | } |
| 147 | |
| 148 | // Digest.new_derive_key_hash initializes a Digest structure for deriving a Blake3 key |
| 149 | pub fn Digest.new_derive_key_hash(context []u8) !Digest { |
| 150 | mut context_digest := Digest.new(iv, u32(Flags.derive_key_context))! |
| 151 | |
| 152 | context_digest.write(context)! |
| 153 | context_key := context_digest.checksum_internal(key_length) |
| 154 | |
| 155 | // treat the context key bytes as little endian u32 values |
| 156 | mut key_words := []u32{len: 8} |
| 157 | for i in 0 .. 8 { |
| 158 | key_words[i] = binary.little_endian_u32_at(context_key, i * 4) |
| 159 | } |
| 160 | |
| 161 | return Digest.new(key_words, u32(Flags.derive_key_material)) |
| 162 | } |
| 163 | |
| 164 | fn Digest.new(key_words []u32, flags u32) !Digest { |
| 165 | if key_words.len != 8 { |
| 166 | return KeyWordsLengthError{ |
| 167 | length: u32(key_words.len) |
| 168 | } |
| 169 | } |
| 170 | |
| 171 | // flags must be 0 for performing a blake3 hash. Other bits modify |
| 172 | // the type of hash performed. Only 1 of the keyed_hash, |
| 173 | // derive_key_context, or derive_key_material bits can be set. |
| 174 | // Having other bits or multiple bits set is invalid at the |
| 175 | // Digest level. |
| 176 | match flags { |
| 177 | u32(0) {} |
| 178 | u32(Flags.keyed_hash) {} |
| 179 | u32(Flags.derive_key_context) {} |
| 180 | u32(Flags.derive_key_material) {} |
| 181 | else { |
| 182 | return DigestFlagsError{ |
| 183 | flags: flags |
| 184 | } |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | return Digest{ |
| 189 | key_words: key_words |
| 190 | flags: flags |
| 191 | chunk_counter: 0 |
| 192 | input: []u8{} |
| 193 | binary_edge: []TreeNode{} |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | // write adds bytes to the hash |
| 198 | pub fn (mut d Digest) write(data []u8) ! { |
| 199 | // if no data is being added to the hash, just return. |
| 200 | if data.len == 0 { |
| 201 | return |
| 202 | } |
| 203 | |
| 204 | d.input << data |
| 205 | |
| 206 | // if we have more than 1024 bytes in the input, |
| 207 | // process it in chunks. |
| 208 | |
| 209 | for d.input.len > chunk_size { |
| 210 | mut chunk := Chunk{} |
| 211 | words := chunk.process_input(d.input[..chunk_size], d.key_words, d.chunk_counter, d.flags, |
| 212 | false) |
| 213 | |
| 214 | d.add_node(Node{ chaining_value: words[..8] }, 0) |
| 215 | |
| 216 | d.chunk_counter += 1 |
| 217 | d.input = d.input[chunk_size..] |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | // checksum finalizes the hash and returns the generated bytes. |
| 222 | // |
| 223 | // This is the point in the hashing operation that we need to know |
| 224 | // how many bytes of hash to generate. Normally this is 32 but can |
| 225 | // be any size up to 2**64. |
| 226 | pub fn (mut d Digest) checksum(size u64) []u8 { |
| 227 | return d.checksum_internal(size) |
| 228 | } |
| 229 | |
| 230 | fn (mut d Digest) checksum_internal(size u64) []u8 { |
| 231 | // process the last of the input |
| 232 | mut chunk := Chunk{} |
| 233 | |
| 234 | root := d.chunk_counter == 0 |
| 235 | mut words := chunk.process_input(d.input, d.key_words, d.chunk_counter, d.flags, root) |
| 236 | |
| 237 | // starting with the current (last) chunk, work your way up to the |
| 238 | // top of the tree. |
| 239 | |
| 240 | mut state := HashState{ |
| 241 | words: words |
| 242 | chaining_value: chunk.chaining_value |
| 243 | block_words: chunk.block_words |
| 244 | block_len: chunk.block_len |
| 245 | flags: chunk.flags |
| 246 | } |
| 247 | |
| 248 | mut right_node := Node{ |
| 249 | chaining_value: words[..8].clone() |
| 250 | } |
| 251 | |
| 252 | for i, left_node in d.binary_edge { |
| 253 | match left_node { |
| 254 | Empty { |
| 255 | // nothing to do here, just skip it |
| 256 | } |
| 257 | Node { |
| 258 | mut block_words := left_node.chaining_value.clone() |
| 259 | block_words << right_node.chaining_value |
| 260 | |
| 261 | mut flags := d.flags | u32(Flags.parent) |
| 262 | flags |= if i == d.binary_edge.len - 1 { u32(Flags.root) } else { u32(0) } |
| 263 | |
| 264 | words = f(d.key_words, block_words, u64(0), block_size, flags) |
| 265 | |
| 266 | state.words = words |
| 267 | state.chaining_value = d.key_words |
| 268 | state.block_words = block_words |
| 269 | state.block_len = block_size |
| 270 | state.flags = flags |
| 271 | |
| 272 | right_node = Node{ |
| 273 | chaining_value: words[..8].clone() |
| 274 | } |
| 275 | } |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | return root_output_bytes(state, size) |
| 280 | } |
| 281 | |
| 282 | fn root_output_bytes(state HashState, size u64) []u8 { |
| 283 | mut output := []u8{cap: int(size)} |
| 284 | mut bytes_needed := size |
| 285 | |
| 286 | mut block_number := u64(0) |
| 287 | mut words := state.words.clone() |
| 288 | |
| 289 | for bytes_needed > 0 { |
| 290 | for word in words { |
| 291 | mut hash_bytes := []u8{len: 4, cap: 4} |
| 292 | binary.little_endian_put_u32(mut hash_bytes, word) |
| 293 | |
| 294 | for hash_byte in hash_bytes { |
| 295 | output << hash_byte |
| 296 | bytes_needed -= 1 |
| 297 | |
| 298 | if bytes_needed == 0 { |
| 299 | return output |
| 300 | } |
| 301 | } |
| 302 | } |
| 303 | |
| 304 | block_number += 1 |
| 305 | words = f(state.chaining_value, state.block_words, block_number, state.block_len, |
| 306 | state.flags) |
| 307 | } |
| 308 | |
| 309 | return output |
| 310 | } |
| 311 | |
| 312 | @[direct_array_access] |
| 313 | fn (mut d Digest) add_node(node Node, level u8) { |
| 314 | // if we are above the highst level, |
| 315 | // just add the node at the top |
| 316 | if d.binary_edge.len == level { |
| 317 | d.binary_edge << node |
| 318 | return |
| 319 | } |
| 320 | |
| 321 | edge_node := d.binary_edge[level] |
| 322 | |
| 323 | match edge_node { |
| 324 | Empty { |
| 325 | d.binary_edge[level] = node |
| 326 | } |
| 327 | Node { |
| 328 | mut block_words := edge_node.chaining_value.clone() |
| 329 | block_words << node.chaining_value |
| 330 | |
| 331 | words := f(d.key_words, block_words, u64(0), block_size, d.flags | u32(Flags.parent)) |
| 332 | parent_node := Node{ |
| 333 | chaining_value: words[..8] |
| 334 | } |
| 335 | |
| 336 | d.binary_edge[level] = Empty{} |
| 337 | |
| 338 | d.add_node(parent_node, level + 1) |
| 339 | } |
| 340 | } |
| 341 | |
| 342 | return |
| 343 | } |
| 344 | |
| 345 | // sum256 returns the Blake3 256 bit hash of the data. |
| 346 | pub fn sum256(data []u8) []u8 { |
| 347 | mut d := Digest.new_hash() or { panic(err) } |
| 348 | d.write(data) or { panic(err) } |
| 349 | return d.checksum_internal(size256) |
| 350 | } |
| 351 | |
| 352 | // sum_keyed256 returns the Blake3 256 bit keyed hash of the data. |
| 353 | pub fn sum_keyed256(data []u8, key []u8) []u8 { |
| 354 | mut d := Digest.new_keyed_hash(key) or { panic(err) } |
| 355 | d.write(data) or { panic(err) } |
| 356 | return d.checksum_internal(size256) |
| 357 | } |
| 358 | |
| 359 | // sum_derived_key256 returns the Blake3 256 bit derived key hash of the key material |
| 360 | pub fn sum_derive_key256(context []u8, key_material []u8) []u8 { |
| 361 | mut d := Digest.new_derive_key_hash(context) or { panic(err) } |
| 362 | d.write(key_material) or { panic(err) } |
| 363 | return d.checksum_internal(size256) |
| 364 | } |
| 365 | |