import { describe, expect, it } from "vitest"; import { SoftwareSigner, buildSigner, buildVerifier } from "./signer.js"; // The signer is half of the anti-fraud chain (the other half is event-log's hashing). // These tests pin: a sign/verify round-trip, rejection of any tamper, constant-time // length handling, and the keyId rotation contract that lets one chain span keys. describe("SoftwareSigner", () => { it("verifies its own signature (round-trip)", () => { const s = new SoftwareSigner("a-test-secret-key"); const sig = s.sign("hello world"); expect(s.verify("hello world", sig)).toBe(true); }); it("rejects a signature over different content (tamper-evidence)", () => { const s = new SoftwareSigner("a-test-secret-key"); const sig = s.sign("amount=100"); // Flip the signed content — the whole point of signing the payload. expect(s.verify("amount=9999", sig)).toBe(false); }); it("rejects a signature made under a different key (forgery)", () => { const real = new SoftwareSigner("the-real-host-key"); const forger = new SoftwareSigner("an-attacker-guess"); const forged = forger.sign("amount=100"); expect(real.verify("amount=100", forged)).toBe(false); }); it("rejects a malformed / wrong-length signature without throwing", () => { const s = new SoftwareSigner("a-test-secret-key"); // timingSafeEqual throws on length mismatch; verify() must guard it. expect(() => s.verify("x", "deadbeef")).not.toThrow(); expect(s.verify("x", "deadbeef")).toBe(false); expect(s.verify("x", "")).toBe(false); }); it("is deterministic — same key + payload yields the same signature", () => { const a = new SoftwareSigner("k").sign("p"); const b = new SoftwareSigner("k").sign("p"); expect(a).toBe(b); }); it("defaults to the v2 keyId", () => { expect(new SoftwareSigner("k").keyId).toBe("sw-hmac-v2"); }); }); describe("buildSigner", () => { // vitest.config.ts sets EVENT_SIGNING_KEY + JWT_SECRET for the whole run. it("prefers EVENT_SIGNING_KEY (keyId sw-hmac-v2)", () => { const s = buildSigner(); expect(s.keyId).toBe("sw-hmac-v2"); const sig = s.sign("x"); expect(s.verify("x", sig)).toBe(true); }); }); describe("buildVerifier (key rotation)", () => { it("returns a working verifier for the configured v2 key", () => { const v = buildVerifier("sw-hmac-v2"); expect(v).toBeDefined(); const signer = new SoftwareSigner(process.env.EVENT_SIGNING_KEY!, "sw-hmac-v2"); expect(v!.verify("x", signer.sign("x"))).toBe(true); }); it("resolves the jwtfallback key when present", () => { const v = buildVerifier("sw-hmac-jwtfallback"); expect(v).toBeDefined(); const signer = new SoftwareSigner(process.env.JWT_SECRET!, "sw-hmac-jwtfallback"); expect(v!.verify("x", signer.sign("x"))).toBe(true); }); it("returns undefined for an unknown keyId (key gone, not a false tamper)", () => { expect(buildVerifier("atecc608-slot0")).toBeUndefined(); expect(buildVerifier("nonsense")).toBeUndefined(); }); });