| 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 | // build input strings of various lengths |
| 12 | const fua = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' |
| 13 | const bua = 'ZYXWVUTSRQPONMLKJIHGFEDCBA' |
| 14 | |
| 15 | const fla = 'abcdefghijklmnopqrstuvwxyz' |
| 16 | const bla = 'zyxwvutsrqponmlkjihgfedcba' |
| 17 | |
| 18 | const fnr = '0123456789' |
| 19 | const bnr = '9876543210' |
| 20 | |
| 21 | const ll = fua + ' ' + fla + ' ' + fnr + ' ' + bnr + ' ' + bla + ' ' + bua |
| 22 | |
| 23 | const lb = ll + '\n' + ll + '\n' + ll + '\n' + ll + '\n' + ll + '\n' + ll + '\n' + ll + '\n' + ll |
| 24 | |
| 25 | struct TestInput { |
| 26 | input_string string |
| 27 | key_words []u32 |
| 28 | chunk_number u64 |
| 29 | flags u32 |
| 30 | } |
| 31 | |
| 32 | struct TestCase { |
| 33 | input TestInput // inputs to the chunk's process_input method |
| 34 | results Chunk // the resulting chunk |
| 35 | words []u32 // the final output state as 32-bit words |
| 36 | } |
| 37 | |
| 38 | // Chunk structures after processing varying amounts of input |
| 39 | // |
| 40 | // The expected values were derived from instrumenting the C reference |
| 41 | // implementation and dumping the appropriate values from their variables |
| 42 | // when processing the same input as the test case. |
| 43 | const test_cases = [ |
| 44 | // chunk 0 with 0 bytes input |
| 45 | TestCase{ |
| 46 | input: TestInput{ |
| 47 | input_string: '' |
| 48 | key_words: iv |
| 49 | chunk_number: 0 |
| 50 | flags: 0 |
| 51 | } |
| 52 | results: Chunk{ |
| 53 | chunk_number: 0 |
| 54 | chaining_value: iv.clone() |
| 55 | block_words: [u32(0), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] |
| 56 | flags: 0x0b |
| 57 | } |
| 58 | words: [u32(0xb94913af), 0xa6a1f9f5, 0xea4d40a0, 0x49c9dc36, 0xc925cb9b, 0xb712c1ad, |
| 59 | 0xca939acc, 0x62321fe4, 0xe7030fe0, 0x6bf29ab6, 0x9ff0aa7f, 0x503033cd, 0xe0df8d33, |
| 60 | 0x86ccb885, 0x208ba99c, 0x3a24086c] |
| 61 | }, |
| 62 | // chunk 0 with 1 byte input |
| 63 | TestCase{ |
| 64 | input: TestInput{ |
| 65 | input_string: 'A' |
| 66 | key_words: iv |
| 67 | chunk_number: 0 |
| 68 | flags: 0 |
| 69 | } |
| 70 | results: Chunk{ |
| 71 | chunk_number: 0 |
| 72 | chaining_value: iv.clone() |
| 73 | block_words: [u32(0x00000041), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] |
| 74 | flags: 0x0b |
| 75 | } |
| 76 | words: [u32(0xfa4b6832), 0x4dc8c028, 0x1105216f, 0xfc0eceaa, 0x88c77151, 0x89ba4891, |
| 77 | 0xa25a8d20, 0x98fa0597, 0x4c0088e3, 0xafa7cbb8, 0xc586c5f7, 0x66dbef0e, 0x8c91d56a, |
| 78 | 0xa0b3daff, 0xe919be05, 0x997a1ce7] |
| 79 | }, |
| 80 | // chunk 0 with 3 bytes input |
| 81 | TestCase{ |
| 82 | input: TestInput{ |
| 83 | input_string: 'abc' |
| 84 | key_words: iv |
| 85 | chunk_number: 0 |
| 86 | flags: 0 |
| 87 | } |
| 88 | results: Chunk{ |
| 89 | chunk_number: 0 |
| 90 | chaining_value: iv.clone() |
| 91 | block_words: [u32(0x00636261), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] |
| 92 | flags: 0x0b |
| 93 | } |
| 94 | words: [u32(0xacb33764), 0x33514638, 0x753bb6ff, 0xb58d3a27, 0x4658c548, 0x03db795d, |
| 95 | 0x6c9c35fd, 0x859dbdd5, 0xae50b21f, 0xd0f59373, 0x5db61328, 0x490d1a52, 0x9ca09b2d, |
| 96 | 0x4c7fcef7, 0xf200d9ff, 0x0bbf7433] |
| 97 | }, |
| 98 | // chunk 0 with 63 bytes input |
| 99 | TestCase{ |
| 100 | input: TestInput{ |
| 101 | input_string: ll[..63] |
| 102 | key_words: iv |
| 103 | chunk_number: 0 |
| 104 | flags: 0 |
| 105 | } |
| 106 | results: Chunk{ |
| 107 | chunk_number: 0 |
| 108 | chaining_value: iv.clone() |
| 109 | block_words: [u32(0x44434241), 0x48474645, 0x4c4b4a49, 0x504f4e4d, 0x54535251, |
| 110 | 0x58575655, 0x61205a59, 0x65646362, 0x69686766, 0x6d6c6b6a, 0x71706f6e, 0x75747372, |
| 111 | 0x79787776, 0x3130207a, 0x35343332, 0x00383736] |
| 112 | flags: 0x0b |
| 113 | } |
| 114 | words: [u32(0x39c41fc6), 0x2dd7c57b, 0xb8b16421, 0x360cbedb, 0x462d5672, 0x56713bb5, |
| 115 | 0x15132543, 0x7a92c4ba, 0x15c4ac13, 0x2354f573, 0xdc14f100, 0x35be07e8, 0xf98b17b9, |
| 116 | 0x573f38f8, 0xc8d2fbf4, 0xc06588e5] |
| 117 | }, |
| 118 | // chunk 0 with 64 bytes input |
| 119 | TestCase{ |
| 120 | input: TestInput{ |
| 121 | input_string: ll[..64] |
| 122 | key_words: iv |
| 123 | chunk_number: 0 |
| 124 | flags: 0 |
| 125 | } |
| 126 | results: Chunk{ |
| 127 | chunk_number: 0 |
| 128 | chaining_value: iv.clone() |
| 129 | block_words: [u32(0x44434241), 0x48474645, 0x4c4b4a49, 0x504f4e4d, 0x54535251, |
| 130 | 0x58575655, 0x61205a59, 0x65646362, 0x69686766, 0x6d6c6b6a, 0x71706f6e, 0x75747372, |
| 131 | 0x79787776, 0x3130207a, 0x35343332, 0x39383736] |
| 132 | flags: 0x0b |
| 133 | } |
| 134 | words: [u32(0x6010817a), 0x21deb495, 0x0826485b, 0x6f895da5, 0x9363242a, 0x176b60a9, |
| 135 | 0x383215a7, 0x2b95570f, 0x57fe7082, 0x45b13a10, 0x007af189, 0x4b5e7ec7, 0x9574b5d8, |
| 136 | 0x109362a0, 0x282d14c2, 0x3a134380] |
| 137 | }, |
| 138 | // chunk 0 with 65 bytes input |
| 139 | TestCase{ |
| 140 | input: TestInput{ |
| 141 | input_string: ll[..65] |
| 142 | key_words: iv |
| 143 | chunk_number: 0 |
| 144 | flags: 0 |
| 145 | } |
| 146 | results: Chunk{ |
| 147 | chunk_number: 0 |
| 148 | chaining_value: [u32(0xbb99f549), 0x3b4b2903, 0x436d199e, 0x6eea5980, 0x82ebb968, |
| 149 | 0x33cc3c4a, 0x90f4944b, 0x9480e10a] |
| 150 | block_words: [u32(0x00000020), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] |
| 151 | flags: 0x0a |
| 152 | } |
| 153 | words: [u32(0xb06cf0f5), 0xd6f8f23d, 0x0f06389e, 0x20dec0a4, 0x69a20569, 0xdbbb4453, |
| 154 | 0x09f0bb52, 0xe22c6707, 0x6b530f62, 0x9ac8bfbd, 0xc0aa57b0, 0xdb30223c, 0xd6e52c79, |
| 155 | 0x42d84d38, 0xcfb4105f, 0xb42f1bd3] |
| 156 | }, |
| 157 | // chunk 0 with 127 bytes input |
| 158 | TestCase{ |
| 159 | input: TestInput{ |
| 160 | input_string: ll[..127] |
| 161 | key_words: iv |
| 162 | chunk_number: 0 |
| 163 | flags: 0 |
| 164 | } |
| 165 | results: Chunk{ |
| 166 | chunk_number: 0 |
| 167 | chaining_value: [u32(0xbb99f549), 0x3b4b2903, 0x436d199e, 0x6eea5980, 0x82ebb968, |
| 168 | 0x33cc3c4a, 0x90f4944b, 0x9480e10a] |
| 169 | block_words: [u32(0x37383920), 0x33343536, 0x20303132, 0x7778797a, 0x73747576, |
| 170 | 0x6f707172, 0x6b6c6d6e, 0x6768696a, 0x63646566, 0x5a206162, 0x56575859, 0x52535455, |
| 171 | 0x4e4f5051, 0x4a4b4c4d, 0x46474849, 0x00434445] |
| 172 | flags: 0x0a |
| 173 | } |
| 174 | words: [u32(0x589a304d), 0x49f8a607, 0x55a03867, 0xe4fec410, 0x1a6bb2f6, 0x11dfecb3, |
| 175 | 0xf9989552, 0xb2d18382, 0x9fc6329a, 0xcc93199f, 0x5431cfc5, 0x1f5bddc6, 0x1d039fc5, |
| 176 | 0x09af900e, 0x55ce0ba2, 0x9707b1e6] |
| 177 | }, |
| 178 | // chunk 0 with 128 bytes input |
| 179 | TestCase{ |
| 180 | input: TestInput{ |
| 181 | input_string: ll[..128] |
| 182 | key_words: iv |
| 183 | chunk_number: 0 |
| 184 | flags: 0 |
| 185 | } |
| 186 | results: Chunk{ |
| 187 | chunk_number: 0 |
| 188 | chaining_value: [u32(0xbb99f549), 0x3b4b2903, 0x436d199e, 0x6eea5980, 0x82ebb968, |
| 189 | 0x33cc3c4a, 0x90f4944b, 0x9480e10a] |
| 190 | block_words: [u32(0x37383920), 0x33343536, 0x20303132, 0x7778797a, 0x73747576, |
| 191 | 0x6f707172, 0x6b6c6d6e, 0x6768696a, 0x63646566, 0x5a206162, 0x56575859, 0x52535455, |
| 192 | 0x4e4f5051, 0x4a4b4c4d, 0x46474849, 0x42434445] |
| 193 | flags: 0x0a |
| 194 | } |
| 195 | words: [u32(0xd0d12158), 0x8802f9a4, 0x5bd125fb, 0xf2751b9d, 0x8fb2a4d2, 0x27744bfa, |
| 196 | 0x6ea287b1, 0xae9cfdb2, 0x8e0c2651, 0xeb2cfa50, 0x84654cbf, 0xb97b6f7b, 0xd2d737a2, |
| 197 | 0x46eaad72, 0x4d0235f0, 0xcaf8abb7] |
| 198 | }, |
| 199 | // chunk 0 with 129 bytes input |
| 200 | TestCase{ |
| 201 | input: TestInput{ |
| 202 | input_string: ll[..129] |
| 203 | key_words: iv |
| 204 | chunk_number: 0 |
| 205 | flags: 0 |
| 206 | } |
| 207 | results: Chunk{ |
| 208 | chunk_number: 0 |
| 209 | chaining_value: [u32(0xccf04979), 0x9cbf983e, 0x9e274997, 0xb88c707b, 0x482b00d8, |
| 210 | 0x7aedc034, 0x1efdc297, 0x4de9f7c5] |
| 211 | block_words: [u32(0x00000041), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] |
| 212 | flags: 0x0a |
| 213 | } |
| 214 | words: [u32(0xa896b53e), 0x1a64b264, 0xb08c7ea7, 0x09990e6d, 0x30470999, 0x762e9f2c, |
| 215 | 0xb6c7bf5f, 0x64fd723a, 0x6cb02e2f, 0xa3849bf2, 0xede8ea18, 0x14c88505, 0xfbf2ad67, |
| 216 | 0x6bc0a779, 0x8e731b77, 0x643a82c6] |
| 217 | }, |
| 218 | // chunk 0 with 1023 bytes input |
| 219 | TestCase{ |
| 220 | input: TestInput{ |
| 221 | input_string: lb[..1023] |
| 222 | key_words: iv |
| 223 | chunk_number: 0 |
| 224 | flags: 0 |
| 225 | } |
| 226 | results: Chunk{ |
| 227 | chunk_number: 0 |
| 228 | chaining_value: [u32(0x78fd494b), 0xcd7eeddd, 0x0cb98e9b, 0x7a6a754e, 0x38ff2d32, |
| 229 | 0x88c4ca4c, 0xbc7baf18, 0xf7684da9] |
| 230 | block_words: [u32(0x207a7978), 0x33323130, 0x37363534, 0x39203938, 0x35363738, |
| 231 | 0x31323334, 0x797a2030, 0x75767778, 0x71727374, 0x6d6e6f70, 0x696a6b6c, 0x65666768, |
| 232 | 0x61626364, 0x58595a20, 0x54555657, 0x00515253] |
| 233 | flags: 0x0a |
| 234 | } |
| 235 | words: [u32(0x96bcc611), 0x8ccfc351, 0x89ec78f7, 0x2f748832, 0xf75ee10a, 0xc739f876, |
| 236 | 0x6adddebb, 0xe28853ab, 0x6983883d, 0x1ca0378d, 0x11f4296c, 0x6638ad9a, 0x0c639f8a, |
| 237 | 0xebf03d1f, 0x2c0e3844, 0x0989b826] |
| 238 | }, |
| 239 | // chunk 0 with 1024 bytes input |
| 240 | TestCase{ |
| 241 | input: TestInput{ |
| 242 | input_string: lb[..1024] |
| 243 | key_words: iv |
| 244 | chunk_number: 0 |
| 245 | flags: 0 |
| 246 | } |
| 247 | results: Chunk{ |
| 248 | chunk_number: 0 |
| 249 | chaining_value: [u32(0x78fd494b), 0xcd7eeddd, 0x0cb98e9b, 0x7a6a754e, 0x38ff2d32, |
| 250 | 0x88c4ca4c, 0xbc7baf18, 0xf7684da9] |
| 251 | block_words: [u32(0x207a7978), 0x33323130, 0x37363534, 0x39203938, 0x35363738, |
| 252 | 0x31323334, 0x797a2030, 0x75767778, 0x71727374, 0x6d6e6f70, 0x696a6b6c, 0x65666768, |
| 253 | 0x61626364, 0x58595a20, 0x54555657, 0x50515253] |
| 254 | flags: 0x0a |
| 255 | } |
| 256 | words: [u32(0x50dbfcc6), 0x7dd05a7f, 0xa641cc37, 0x11721a4e, 0x6f33eea2, 0x834877a1, |
| 257 | 0x1cb36c9c, 0xf8d78dce, 0xb7539a0e, 0x7c7b57f4, 0xeef982da, 0x82c6c442, 0x8a451e9b, |
| 258 | 0xb9cc8414, 0xdef7ad58, 0x65ecfb6b] |
| 259 | }, |
| 260 | // chunk 1 with 1 byte input |
| 261 | // this is what the second chunk of input sees |
| 262 | // after the first 1024 bytes have been consumed |
| 263 | TestCase{ |
| 264 | input: TestInput{ |
| 265 | input_string: 'O' |
| 266 | key_words: iv |
| 267 | chunk_number: 1 |
| 268 | flags: 0 |
| 269 | } |
| 270 | results: Chunk{ |
| 271 | chunk_number: 1 |
| 272 | chaining_value: iv.clone() |
| 273 | block_words: [u32(0x0000004f), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] |
| 274 | flags: 0x0b |
| 275 | } |
| 276 | words: [u32(0xfd795319), 0x4448fd94, 0xa8054dbb, 0x526517ad, 0x7e8e2e4c, 0xf54cf835, |
| 277 | 0xb498c9a7, 0x341396fa, 0x753298b2, 0xd721328c, 0x4013c5d6, 0xaf64d891, 0x77893790, |
| 278 | 0xe486143b, 0x13764172, 0x0cae81d0] |
| 279 | }, |
| 280 | ] |
| 281 | |
| 282 | fn test_various_test_cases() { |
| 283 | for test_case in test_cases { |
| 284 | mut chunk := Chunk{} |
| 285 | input := test_case.input |
| 286 | chunk.process_input(input.input_string.bytes(), input.key_words, input.chunk_number, |
| 287 | input.flags, true) |
| 288 | |
| 289 | results := test_case.results |
| 290 | assert chunk.chunk_number == results.chunk_number |
| 291 | |
| 292 | for i, value in chunk.chaining_value { |
| 293 | assert value == results.chaining_value[i], 'i: ${i}, left: ${value:08x} right: ${results.chaining_value[i]:08x}' |
| 294 | } |
| 295 | |
| 296 | for i in 0 .. 16 { |
| 297 | assert chunk.block_words[i] == results.block_words[i], 'i: ${i}, left: ${chunk.block_words[i]:08x} right: ${results.block_words[i]}' |
| 298 | } |
| 299 | |
| 300 | assert chunk.flags == results.flags |
| 301 | } |
| 302 | } |
| 303 | |