import { describe, expect, test } from "bun:test"; import { bytesToHex, hexToBytes, hkdfSha256, hmacSha256, sha256, utf8 } from "./sync-hash"; async function webHmac(key: string, message: string): Promise { const cryptoKey = await crypto.subtle.importKey("raw", utf8(key), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]); return bytesToHex(new Uint8Array(await crypto.subtle.sign("HMAC", cryptoKey, utf8(message)))); } describe("sync sha256", () => { test("matches known vectors", () => { expect(bytesToHex(sha256(utf8("")))).toBe("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); expect(bytesToHex(sha256(utf8("abc")))).toBe("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"); }); test("matches WebCrypto across block boundaries", async () => { for (const length of [0, 1, 55, 56, 63, 64, 65, 119, 120, 1000]) { const message = "x".repeat(length); const expected = bytesToHex(new Uint8Array(await crypto.subtle.digest("SHA-256", utf8(message)))); expect(bytesToHex(sha256(utf8(message)))).toBe(expected); } }); test("hmac matches WebCrypto for short and long keys", async () => { for (const key of ["k", "k".repeat(64), "k".repeat(200)]) { expect(bytesToHex(hmacSha256(utf8(key), utf8("payload")))).toBe(await webHmac(key, "payload")); } }); test("hkdf matches WebCrypto HKDF-SHA256", async () => { const secret = utf8("master-secret"); const key = await crypto.subtle.importKey("raw", secret, "HKDF", false, ["deriveBits"]); const bits = await crypto.subtle.deriveBits( { name: "HKDF", hash: "SHA-256", salt: utf8("salt"), info: utf8("info") }, key, 256, ); expect(bytesToHex(hkdfSha256(secret, "salt", "info"))).toBe(bytesToHex(new Uint8Array(bits))); }); test("hex helpers round-trip and reject odd input", () => { expect(bytesToHex(hexToBytes("00ff10") ?? new Uint8Array())).toBe("00ff10"); expect(hexToBytes("abc")).toBeNull(); expect(hexToBytes("zz")).toBeNull(); }); });