1import { describe, expect, it } from "vitest";2import { sharedKey } from "@stablelib/x25519";3import { computeVaultId, hexToPubkey, pubkeyToHex } from "./sync-identity";4import { deriveSyncIdentityKeypair } from "./legacy-identity-derivation.test-support";56const MASTER = Uint8Array.from(Array.from({ length: 32 }, (_, i) => (i + 1) & 0xff));7const OTHER_MASTER = Uint8Array.from(Array.from({ length: 32 }, (_, i) => (i * 3 + 5) & 0xff));89describe("deriveSyncIdentityKeypair", () => {10 it("is deterministic across calls", () => {11 const a = deriveSyncIdentityKeypair(MASTER, "abc-123");12 const b = deriveSyncIdentityKeypair(MASTER, "abc-123");13 expect(a.publicKey).toEqual(b.publicKey);14 expect(a.secretKey).toEqual(b.secretKey);15 });1617 it("differs across device_id (same master_key)", () => {18 const a = deriveSyncIdentityKeypair(MASTER, "device-A");19 const b = deriveSyncIdentityKeypair(MASTER, "device-B");20 expect(a.publicKey).not.toEqual(b.publicKey);21 });2223 it("differs across master_key (same device_id)", () => {24 const a = deriveSyncIdentityKeypair(MASTER, "device-A");25 const b = deriveSyncIdentityKeypair(OTHER_MASTER, "device-A");26 expect(a.publicKey).not.toEqual(b.publicKey);27 });2829 it("produces 32-byte keys", () => {30 const kp = deriveSyncIdentityKeypair(MASTER, "x");31 expect(kp.publicKey.length).toBe(32);32 expect(kp.secretKey.length).toBe(32);33 });3435 it("yields a working X25519 keypair (DH commutes)", () => {36 const alice = deriveSyncIdentityKeypair(MASTER, "alice");37 const bob = deriveSyncIdentityKeypair(MASTER, "bob");38 const ab = sharedKey(alice.secretKey, bob.publicKey);39 const ba = sharedKey(bob.secretKey, alice.publicKey);40 expect(ab).toEqual(ba);41 });4243 it("rejects empty master key", () => {44 expect(() => deriveSyncIdentityKeypair(new Uint8Array(), "x")).toThrow();45 });4647 it("rejects empty device id", () => {48 expect(() => deriveSyncIdentityKeypair(MASTER, "")).toThrow();49 });50});5152describe("computeVaultId", () => {53 it("is deterministic for the same master key", async () => {54 const a = await computeVaultId(MASTER);55 const b = await computeVaultId(MASTER);56 expect(a).toBe(b);57 });5859 it("differs across master keys", async () => {60 const a = await computeVaultId(MASTER);61 const b = await computeVaultId(OTHER_MASTER);62 expect(a).not.toBe(b);63 });6465 it("returns 64 hex chars (full 32-byte BLAKE3 digest)", async () => {66 const id = await computeVaultId(MASTER);67 expect(id).toMatch(/^[0-9a-f]{64}$/);68 });69});7071describe("pubkeyToHex / hexToPubkey", () => {72 it("round-trips", () => {73 const pk = Uint8Array.from(Array.from({ length: 32 }, (_, i) => i));74 const hex = pubkeyToHex(pk);75 expect(hexToPubkey(hex)).toEqual(pk);76 });7778 it("rejects keys that are not exactly 32 bytes", () => {79 expect(() => hexToPubkey("abc")).toThrow();80 expect(() => hexToPubkey("00".repeat(31))).toThrow();81 expect(() => hexToPubkey("00".repeat(33))).toThrow();82 });8384 it("rejects invalid hex chars", () => {85 expect(() => hexToPubkey("zz".repeat(32))).toThrow();86 });87});