import { describe, expect, it } from "vitest"; import { constantTimeEqualBytes, constantTimeEqualHex } from "./constant-time"; describe("constantTimeEqualBytes", () => { it("matches identical contents and rejects a single differing byte", () => { const a = Uint8Array.from([1, 2, 3, 4]); expect(constantTimeEqualBytes(a, Uint8Array.from([1, 2, 3, 4]))).toBe(true); expect(constantTimeEqualBytes(a, Uint8Array.from([1, 2, 3, 5]))).toBe(false); expect(constantTimeEqualBytes(a, Uint8Array.from([9, 2, 3, 4]))).toBe(false); }); it("rejects a length mismatch even when the shared prefix matches", () => { // The out-of-range reads fold in as 0, so a trailing zero byte must not // make a longer value compare equal to its own prefix. const prefix = Uint8Array.from([1, 2, 3]); expect(constantTimeEqualBytes(prefix, Uint8Array.from([1, 2, 3, 0]))).toBe(false); expect(constantTimeEqualBytes(Uint8Array.from([1, 2, 3, 0]), prefix)).toBe(false); expect(constantTimeEqualBytes(new Uint8Array(0), new Uint8Array(0))).toBe(true); }); it("scans the whole buffer rather than stopping at the first difference", () => { // Same number of iterations regardless of where the mismatch sits — the // property the callers depend on. Verified by behaviour, not by timing. const left = new Uint8Array(64); const early = new Uint8Array(64); early[0] = 1; const late = new Uint8Array(64); late[63] = 1; expect(constantTimeEqualBytes(left, early)).toBe(false); expect(constantTimeEqualBytes(left, late)).toBe(false); }); }); describe("constantTimeEqualHex", () => { it("compares hex case-insensitively", () => { expect(constantTimeEqualHex("ABcd12", "abCD12")).toBe(true); expect(constantTimeEqualHex("abcd12", "abcd13")).toBe(false); }); it("rejects differing lengths", () => { expect(constantTimeEqualHex("abcd", "abcd00")).toBe(false); expect(constantTimeEqualHex("", "")).toBe(true); }); });