|
| 1 | +/** |
| 2 | + * This file is based on noble-hashes (https://github.com/paulmillr/noble-hashes). |
| 3 | + * |
| 4 | + * noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) |
| 5 | + * |
| 6 | + * The original file is located at: |
| 7 | + * https://github.com/paulmillr/noble-hashes/blob/4e358a46d682adfb005ae6314ec999f2513086b9/test/u64.test.ts |
| 8 | + */ |
| 9 | + |
| 10 | +import { assertEquals, assertThrows } from "@std/assert"; |
| 11 | +import { describe, it } from "@std/testing/bdd"; |
| 12 | +import { createHash, createHmac } from "node:crypto"; |
| 13 | +import { sha256, sha384, sha512 } from "../src/hash/sha2.ts"; |
| 14 | +// prettier-ignore |
| 15 | +import { hmac } from "../src/hash/hmac.ts"; |
| 16 | +import { concatBytes, hexToBytes, utf8ToBytes } from "../src/utils/noble.ts"; |
| 17 | +import { repeat, TYPE_TEST } from "./utils.ts"; |
| 18 | + |
| 19 | +// NIST test vectors (https://www.di-mgt.com.au/sha_testvectors.html) |
| 20 | +const NIST_VECTORS = [ |
| 21 | + [1, utf8ToBytes("abc")], |
| 22 | + [1, utf8ToBytes("")], |
| 23 | + [1, utf8ToBytes("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")], |
| 24 | + [ |
| 25 | + 1, |
| 26 | + utf8ToBytes( |
| 27 | + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", |
| 28 | + ), |
| 29 | + ], |
| 30 | + [1000000, utf8ToBytes("a")], |
| 31 | + // Very slow, 1GB |
| 32 | + //[16777216, utf8ToBytes('abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno')], |
| 33 | +].map(([r, buf]) => [r, buf, repeat(buf as Uint8Array, r as number)]); |
| 34 | + |
| 35 | +// Main idea: write 16k buffer with different values then test sliding window against node-js implementation |
| 36 | +const testBuf = new Uint8Array(4096); |
| 37 | +for (let i = 0; i < testBuf.length; i++) testBuf[i] = i; |
| 38 | + |
| 39 | +const HASHES = { |
| 40 | + SHA256: { |
| 41 | + name: "SHA256", |
| 42 | + fn: sha256, |
| 43 | + obj: sha256.create, |
| 44 | + node: (buf: Uint8Array) => |
| 45 | + Uint8Array.from(createHash("sha256").update(buf).digest()), |
| 46 | + node_obj: () => createHash("sha256"), |
| 47 | + nist: [ |
| 48 | + "ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad", |
| 49 | + "e3b0c442 98fc1c14 9afbf4c8 996fb924 27ae41e4 649b934c a495991b 7852b855", |
| 50 | + "248d6a61 d20638b8 e5c02693 0c3e6039 a33ce459 64ff2167 f6ecedd4 19db06c1", |
| 51 | + "cf5b16a7 78af8380 036ce59e 7b049237 0b249b11 e8f07a51 afac4503 7afee9d1", |
| 52 | + "cdc76e5c 9914fb92 81a1c7e2 84d73e67 f1809a48 a497200e 046d39cc c7112cd0", |
| 53 | + "50e72a0e 26442fe2 552dc393 8ac58658 228c0cbf b1d2ca87 2ae43526 6fcd055e", |
| 54 | + ], |
| 55 | + }, |
| 56 | + SHA384: { |
| 57 | + name: "SHA384", |
| 58 | + fn: sha384, |
| 59 | + obj: sha384.create, |
| 60 | + node: (buf: Uint8Array) => |
| 61 | + Uint8Array.from(createHash("sha384").update(buf).digest()), |
| 62 | + node_obj: () => createHash("sha384"), |
| 63 | + nist: [ |
| 64 | + "cb00753f45a35e8b b5a03d699ac65007 272c32ab0eded163 1a8b605a43ff5bed 8086072ba1e7cc23 58baeca134c825a7", |
| 65 | + "38b060a751ac9638 4cd9327eb1b1e36a 21fdb71114be0743 4c0cc7bf63f6e1da 274edebfe76f65fb d51ad2f14898b95b", |
| 66 | + "3391fdddfc8dc739 3707a65b1b470939 7cf8b1d162af05ab fe8f450de5f36bc6 b0455a8520bc4e6f 5fe95b1fe3c8452b", |
| 67 | + "09330c33f71147e8 3d192fc782cd1b47 53111b173b3b05d2 2fa08086e3b0f712 fcc7c71a557e2db9 66c3e9fa91746039", |
| 68 | + "9d0e1809716474cb 086e834e310a4a1c ed149e9c00f24852 7972cec5704c2a5b 07b8b3dc38ecc4eb ae97ddd87f3d8985", |
| 69 | + "5441235cc0235341 ed806a64fb354742 b5e5c02a3c5cb71b 5f63fb793458d8fd ae599c8cd8884943 c04f11b31b89f023", |
| 70 | + ], |
| 71 | + }, |
| 72 | + SHA512: { |
| 73 | + name: "SHA512", |
| 74 | + fn: sha512, |
| 75 | + obj: sha512.create, |
| 76 | + node: (buf: Uint8Array) => |
| 77 | + Uint8Array.from(createHash("sha512").update(buf).digest()), |
| 78 | + node_obj: () => createHash("sha512"), |
| 79 | + nist: [ |
| 80 | + "ddaf35a193617aba cc417349ae204131 12e6fa4e89a97ea2 0a9eeee64b55d39a 2192992a274fc1a8 36ba3c23a3feebbd 454d4423643ce80e 2a9ac94fa54ca49f", |
| 81 | + "cf83e1357eefb8bd f1542850d66d8007 d620e4050b5715dc 83f4a921d36ce9ce 47d0d13c5d85f2b0 ff8318d2877eec2f 63b931bd47417a81 a538327af927da3e", |
| 82 | + "204a8fc6dda82f0a 0ced7beb8e08a416 57c16ef468b228a8 279be331a703c335 96fd15c13b1b07f9 aa1d3bea57789ca0 31ad85c7a71dd703 54ec631238ca3445", |
| 83 | + "8e959b75dae313da 8cf4f72814fc143f 8f7779c6eb9f7fa1 7299aeadb6889018 501d289e4900f7e4 331b99dec4b5433a c7d329eeb6dd2654 5e96e55b874be909", |
| 84 | + "e718483d0ce76964 4e2e42c7bc15b463 8e1f98b13b204428 5632a803afa973eb de0ff244877ea60a 4cb0432ce577c31b eb009c5c2c49aa2e 4eadb217ad8cc09b", |
| 85 | + "b47c933421ea2db1 49ad6e10fce6c7f9 3d0752380180ffd7 f4629a712134831d 77be6091b819ed35 2c2967a2e2d4fa50 50723c9630691f1a 05a7281dbe6c1086", |
| 86 | + ], |
| 87 | + }, |
| 88 | + // Hmac as hash |
| 89 | + "HMAC-SHA256": { |
| 90 | + name: "HMAC-SHA256", |
| 91 | + fn: hmac.bind(null, sha256, new Uint8Array()), |
| 92 | + obj: hmac.create.bind(null, sha256, new Uint8Array()), |
| 93 | + node: (buf: Uint8Array) => |
| 94 | + Uint8Array.from( |
| 95 | + createHmac("sha256", new Uint8Array()).update(buf).digest(), |
| 96 | + ), |
| 97 | + node_obj: () => createHmac("sha256", new Uint8Array()), |
| 98 | + // There is no official vectors, so we created them via: |
| 99 | + // > NIST_VECTORS.map((i) => createHmac('sha256', new Uint8Array()).update(i[2]).digest().toString('hex')) |
| 100 | + nist: [ |
| 101 | + "fd7adb152c05ef80dccf50a1fa4c05d5a3ec6da95575fc312ae7c5d091836351", |
| 102 | + "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad", |
| 103 | + "e31c6a8c54f60655956375893317d0fb2c55615355747b0379bb3772d27d59d4", |
| 104 | + "b303b8328d855cc51960c6f56cd98a12c5100d570b52019f54639a09e15bafaa", |
| 105 | + "cc9b6be49d1512557cef495770bb61e46fce6e83af89d385a038c8c050f4609d", |
| 106 | + ], |
| 107 | + }, |
| 108 | + "HMAC-SHA512": { |
| 109 | + name: "HMAC-SHA512", |
| 110 | + fn: hmac.bind(null, sha512, new Uint8Array()), |
| 111 | + obj: hmac.create.bind(null, sha512, new Uint8Array()), |
| 112 | + node: (buf: Uint8Array) => |
| 113 | + Uint8Array.from( |
| 114 | + createHmac("sha512", new Uint8Array()).update(buf).digest(), |
| 115 | + ), |
| 116 | + node_obj: () => createHmac("sha512", new Uint8Array()), |
| 117 | + // There is no official vectors, so we created them via: |
| 118 | + // > NIST_VECTORS.map((i) => createHmac('sha512', new Uint8Array()).update(i[2]).digest().toString('hex')) |
| 119 | + nist: [ |
| 120 | + "29689f6b79a8dd686068c2eeae97fd8769ad3ba65cb5381f838358a8045a358ee3ba1739c689c7805e31734fb6072f87261d1256995370d55725cba00d10bdd0", |
| 121 | + "b936cee86c9f87aa5d3c6f2e84cb5a4239a5fe50480a6ec66b70ab5b1f4ac6730c6c515421b327ec1d69402e53dfb49ad7381eb067b338fd7b0cb22247225d47", |
| 122 | + "e0657364f9603a276d94930f90a6b19f3ce4001ab494c4fdf7ff541609e05d2e48ca6454a4390feb12b8eacebb503ba2517f5e2454d7d77e8b44d7cca8f752cd", |
| 123 | + "ece33db7448f63f4d460ac8b86bdf02fa6f5c3279a2a5d59df26827bec5315a44eb85d40ee4df3a7272a9596a0bc27091466724e9357183e554c9ec5fdf6d099", |
| 124 | + "59064f29e00b6a5cc55a3b69d9cfd3457ae70bd169b2b714036ae3a965805eb25a99ca221ade1aecebe6111d70697d1174a288cd1bb177de4a14f06eacc631d8", |
| 125 | + ], |
| 126 | + }, |
| 127 | +}; |
| 128 | + |
| 129 | +const BUF_768 = new Uint8Array(256 * 3); |
| 130 | +// Fill with random data |
| 131 | +for (let i = 0; i < (256 * 3) / 32; i++) { |
| 132 | + BUF_768.set(createHash("sha256").update(new Uint8Array(i)).digest(), i * 32); |
| 133 | +} |
| 134 | + |
| 135 | +Object.values(HASHES).forEach((hash) => |
| 136 | + describe(hash.name, () => { |
| 137 | + // All hashes has NIST vectors, some generated manually |
| 138 | + it("NIST vectors", () => { |
| 139 | + for (let i = 0; i < NIST_VECTORS.length; i++) { |
| 140 | + if (!NIST_VECTORS[i]) continue; |
| 141 | + const [r, rbuf, buf] = NIST_VECTORS[i] as [ |
| 142 | + number, |
| 143 | + Uint8Array, |
| 144 | + Uint8Array, |
| 145 | + ]; |
| 146 | + assertEquals( |
| 147 | + hash.obj().update(buf).digest(), |
| 148 | + hexToBytes(hash.nist[i].replace(/ /g, "")), |
| 149 | + `vector ${i}`, |
| 150 | + ); |
| 151 | + const tmp = hash.obj(); |
| 152 | + for (let j = 0; j < r; j++) tmp.update(rbuf); |
| 153 | + assertEquals( |
| 154 | + tmp.digest(), |
| 155 | + hexToBytes(hash.nist[i].replace(/ /g, "")), |
| 156 | + `partial vector ${i}`, |
| 157 | + ); |
| 158 | + } |
| 159 | + }); |
| 160 | + it("accept data in compact call form (Uint8Array)", () => { |
| 161 | + assertEquals( |
| 162 | + hash.fn(utf8ToBytes("abc")), |
| 163 | + hexToBytes(hash.nist[0].replace(/ /g, "")), |
| 164 | + ); |
| 165 | + }); |
| 166 | + it("throw on update after digest", async () => { |
| 167 | + const tmp = hash.obj(); |
| 168 | + tmp.update(utf8ToBytes("abc")).digest(); |
| 169 | + await assertThrows( |
| 170 | + () => tmp.update(utf8ToBytes("abc")), |
| 171 | + Error, |
| 172 | + ); |
| 173 | + }); |
| 174 | + it("throw on second digest call", async () => { |
| 175 | + const tmp = hash.obj(); |
| 176 | + tmp.update(utf8ToBytes("abc")).digest(); |
| 177 | + await assertThrows( |
| 178 | + () => tmp.digest(), |
| 179 | + Error, |
| 180 | + ); |
| 181 | + }); |
| 182 | + it("throw on wrong argument type", async () => { |
| 183 | + // Allowed only: undefined (for compact form only), string, Uint8Array |
| 184 | + for (const t of TYPE_TEST.bytes) { |
| 185 | + await assertThrows( |
| 186 | + () => hash.fn(t), |
| 187 | + Error, |
| 188 | + ); |
| 189 | + await assertThrows( |
| 190 | + () => hash.obj().update(t).digest(), |
| 191 | + Error, |
| 192 | + ); |
| 193 | + } |
| 194 | + await assertThrows( |
| 195 | + () => hash.fn(undefined as unknown as Uint8Array), |
| 196 | + Error, |
| 197 | + ); |
| 198 | + await assertThrows( |
| 199 | + () => hash.obj().update(undefined as unknown as Uint8Array).digest(), |
| 200 | + Error, |
| 201 | + ); |
| 202 | + // for (const t of TYPE_TEST.opts) { |
| 203 | + // await assertThrows( |
| 204 | + // () => hash.fn(undefined as unknown as Uint8Array, t), |
| 205 | + // Error, |
| 206 | + // ); |
| 207 | + // } |
| 208 | + }); |
| 209 | + |
| 210 | + it("clone", () => { |
| 211 | + const exp = hash.fn(BUF_768); |
| 212 | + const t = hash.obj(); |
| 213 | + t.update(BUF_768.subarray(0, 10)); |
| 214 | + const t2 = t.clone(); |
| 215 | + t2.update(BUF_768.subarray(10)); |
| 216 | + assertEquals(t2.digest(), exp); |
| 217 | + t.update(BUF_768.subarray(10)); |
| 218 | + assertEquals(t.digest(), exp); |
| 219 | + }); |
| 220 | + |
| 221 | + it("partial", () => { |
| 222 | + const fnH = hash.fn(BUF_768); |
| 223 | + for (let i = 0; i < 256; i++) { |
| 224 | + const b1 = BUF_768.subarray(0, i); |
| 225 | + for (let j = 0; j < 256; j++) { |
| 226 | + const b2 = BUF_768.subarray(i, i + j); |
| 227 | + const b3 = BUF_768.subarray(i + j); |
| 228 | + assertEquals(concatBytes(b1, b2, b3), BUF_768); |
| 229 | + assertEquals( |
| 230 | + hash.obj().update(b1).update(b2).update(b3).digest(), |
| 231 | + fnH, |
| 232 | + ); |
| 233 | + } |
| 234 | + } |
| 235 | + }); |
| 236 | + // Same as before, but creates copy of each slice, which changes dataoffset of typed array |
| 237 | + // Catched bug in blake2 |
| 238 | + it("partial (copy): partial", () => { |
| 239 | + const fnH = hash.fn(BUF_768); |
| 240 | + for (let i = 0; i < 256; i++) { |
| 241 | + const b1 = BUF_768.subarray(0, i).slice(); |
| 242 | + for (let j = 0; j < 256; j++) { |
| 243 | + const b2 = BUF_768.subarray(i, i + j).slice(); |
| 244 | + const b3 = BUF_768.subarray(i + j).slice(); |
| 245 | + assertEquals(concatBytes(b1, b2, b3), BUF_768); |
| 246 | + assertEquals( |
| 247 | + hash.obj().update(b1).update(b2).update(b3).digest(), |
| 248 | + fnH, |
| 249 | + ); |
| 250 | + } |
| 251 | + } |
| 252 | + }); |
| 253 | + if (hash.node) { |
| 254 | + // if (!!process.versions.bun && ["BLAKE2s", "BLAKE2b"].includes(h)) { |
| 255 | + // return; |
| 256 | + // } |
| 257 | + it("node.js cross-test", () => { |
| 258 | + for (let i = 0; i < testBuf.length; i++) { |
| 259 | + assertEquals( |
| 260 | + hash.obj().update(testBuf.subarray(0, i)).digest(), |
| 261 | + hash.node(testBuf.subarray(0, i)), |
| 262 | + ); |
| 263 | + } |
| 264 | + }); |
| 265 | + it("node.js cross-test chained", () => { |
| 266 | + const b = new Uint8Array([1, 2, 3]); |
| 267 | + let nodeH = hash.node(b); |
| 268 | + let nobleH = hash.fn(b); |
| 269 | + for (let i = 0; i < 256; i++) { |
| 270 | + nodeH = hash.node(nodeH); |
| 271 | + nobleH = hash.fn(nobleH); |
| 272 | + assertEquals(nodeH, nobleH); |
| 273 | + } |
| 274 | + }); |
| 275 | + it("node.js cross-test partial", () => { |
| 276 | + assertEquals(hash.fn(BUF_768), hash.node(BUF_768)); |
| 277 | + }); |
| 278 | + } |
| 279 | + }) |
| 280 | +); |
0 commit comments