/* RFC-0921: Unit tests for the shared signing core — sign/verify round-trip, key generation, canonicalization. Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only. RFC-0921: initial signing core unit tests. */ import { describe, it, expect } from "vitest"; import { generateKeyPair, toHex, fromHex, toPem, fromPem, canonicalBytes, signBytes, verifyBytes, sign, verify, } from "../signing/index.ts"; describe("signing core — key generation", () => { it("generates a valid Ed25519 keypair (32-byte private + 32-byte public)", async () => { const keyPair = await generateKeyPair(); expect(keyPair.privateKey).toBeInstanceOf(Uint8Array); expect(keyPair.publicKey).toBeInstanceOf(Uint8Array); expect(keyPair.privateKey.length).toBe(32); expect(keyPair.publicKey.length).toBe(32); }); it("generates unique keypairs on each call", async () => { const a = await generateKeyPair(); const b = await generateKeyPair(); expect(toHex(a.privateKey)).not.toBe(toHex(b.privateKey)); expect(toHex(a.publicKey)).not.toBe(toHex(b.publicKey)); }); }); describe("signing core — encoding helpers", () => { it("toHex / fromHex round-trip", () => { const bytes = new Uint8Array([0, 1, 2, 255, 128, 64]); const hex = toHex(bytes); expect(hex).toBe("000102ff8040"); expect(fromHex(hex)).toEqual(bytes); }); it("toPem / fromPem round-trip for private key", () => { const bytes = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); const pem = toPem(bytes, "private"); expect(pem).toContain("-----BEGIN" + " PRIVATE KEY-----"); expect(pem).toContain("-----END" + " PRIVATE KEY-----"); expect(fromPem(pem)).toEqual(bytes); }); it("toPem / fromPem round-trip for public key", () => { const bytes = new Uint8Array([10, 20, 30, 40, 50, 60, 70, 80]); const pem = toPem(bytes, "public"); expect(pem).toContain("-----BEGIN" + " PUBLIC KEY-----"); expect(pem).toContain("-----END" + " PUBLIC KEY-----"); expect(fromPem(pem)).toEqual(bytes); }); }); describe("signing core — sign/verify", () => { it("sign and verify round-trip with payload", async () => { const keyPair = await generateKeyPair(); const payload = { buildId: "test-123", signedAt: "2026-08-22T00:00:00Z", outputsDigest: "abc" }; const signature = await sign(keyPair.privateKey, payload); expect(signature).toBeInstanceOf(Uint8Array); expect(signature.length).toBe(64); const valid = await verify(keyPair.publicKey, payload, signature); expect(valid).toBe(true); }); it("verification fails with tampered payload", async () => { const keyPair = await generateKeyPair(); const payload = { buildId: "test-123", signedAt: "2026-08-22" }; const signature = await sign(keyPair.privateKey, payload); const tampered = { buildId: "tampered", signedAt: "2026-08-22" }; const valid = await verify(keyPair.publicKey, tampered, signature); expect(valid).toBe(false); }); it("verification fails with wrong public key", async () => { const keyPairA = await generateKeyPair(); const keyPairB = await generateKeyPair(); const payload = { test: "data" }; const signature = await sign(keyPairA.privateKey, payload); const valid = await verify(keyPairB.publicKey, payload, signature); expect(valid).toBe(false); }); it("signBytes / verifyBytes round-trip with raw message", async () => { const keyPair = await generateKeyPair(); const message = new TextEncoder().encode("hello world"); const signature = await signBytes(keyPair.privateKey, message); expect(signature.length).toBe(64); const valid = await verifyBytes(keyPair.publicKey, message, signature); expect(valid).toBe(true); }); it("verifyBytes returns false on invalid signature (not throw)", async () => { const keyPair = await generateKeyPair(); const message = new TextEncoder().encode("test"); const badSignature = new Uint8Array(64); const valid = await verifyBytes(keyPair.publicKey, message, badSignature); expect(valid).toBe(false); }); }); describe("signing core — canonicalBytes", () => { it("produces deterministic bytes regardless of key order", () => { const a = canonicalBytes({ b: 2, a: 1, c: 3 }); const b = canonicalBytes({ a: 1, c: 3, b: 2 }); expect(a).toEqual(b); }); it("produces different bytes for different payloads", () => { const a = canonicalBytes({ a: 1 }); const b = canonicalBytes({ a: 2 }); expect(a).not.toEqual(b); }); });