46 lines · 2.0 KB
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<string> {
  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", () => {