Skip to content

Commit e45b57c

Browse files
authored
common: add test for hmac and sha2. (#599)
1 parent 8bb2312 commit e45b57c

File tree

5 files changed

+825
-0
lines changed

5 files changed

+825
-0
lines changed

packages/common/src/utils/noble.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,23 @@ export function hexToBytes(hex: string): Uint8Array {
210210
return array;
211211
}
212212

213+
/**
214+
* Converts string to bytes using UTF8 encoding.
215+
* @example utf8ToBytes('abc') // Uint8Array.from([97, 98, 99])
216+
*/
217+
export function utf8ToBytes(str: string): Uint8Array {
218+
if (typeof str !== "string") throw new Error("string expected");
219+
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
220+
}
221+
222+
/**
223+
* Converts bytes to string using UTF8 encoding.
224+
* @example bytesToUtf8(Uint8Array.from([97, 98, 99])) // 'abc'
225+
*/
226+
export function bytesToUtf8(bytes: Uint8Array): string {
227+
return new TextDecoder().decode(bytes);
228+
}
229+
213230
export function numberToHexUnpadded(num: number | bigint): string {
214231
const hex = abignumer(num).toString(16);
215232
return hex.length & 1 ? "0" + hex : hex;
@@ -249,6 +266,23 @@ export function copyBytes(bytes: Uint8Array): Uint8Array {
249266
return Uint8Array.from(bytes);
250267
}
251268

269+
/** Copies several Uint8Arrays into one. */
270+
export function concatBytes(...arrays: Uint8Array[]): Uint8Array {
271+
let sum = 0;
272+
for (let i = 0; i < arrays.length; i++) {
273+
const a = arrays[i];
274+
abytes(a);
275+
sum += a.length;
276+
}
277+
const res = new Uint8Array(sum);
278+
for (let i = 0, pad = 0; i < arrays.length; i++) {
279+
const a = arrays[i];
280+
res.set(a, pad);
281+
pad += a.length;
282+
}
283+
return res;
284+
}
285+
252286
/**
253287
* Decodes 7-bit ASCII string to Uint8Array, throws on non-ascii symbols
254288
* Should be safe to use for things expected to be ASCII.
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
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

Comments
 (0)