1import { describe, expect, it } from "vitest";2import { sharedKey } from "@stablelib/x25519";3import {4 computeVaultId,5 deriveSyncIdentityKeypair,6 hexToPubkey,7 pubkeyToHex,8} from "./sync-identity";910const MASTER = Uint8Array.from(Array.from({ length: 32 }, (_, i) => (i + 1) & 0xff));11const OTHER_MASTER = Uint8Array.from(Array.from({ length: 32 }, (_, i) => (i * 3 + 5) & 0xff));1213describe("deriveSyncIdentityKeypair", () => {14 it("is deterministic across calls", () => {15 const a = deriveSyncIdentityKeypair(MASTER, "abc-123");16 const b = deriveSyncIdentityKeypair(MASTER, "abc-123");17 expect(a.publicKey).toEqual(b.publicKey);18 expect(a.secretKey).toEqual(b.secretKey);19 });2021 it("differs across device_id (same master_key)", () => {22 const a = deriveSyncIdentityKeypair(MASTER, "device-A");23 const b = deriveSyncIdentityKeypair(MASTER, "device-B");24 expect(a.publicKey).not.toEqual(b.publicKey);25 });2627 it("differs across master_key (same device_id)", () => {28 const a = deriveSyncIdentityKeypair(MASTER, "device-A");29 const b = deriveSyncIdentityKeypair(OTHER_MASTER, "device-A");30 expect(a.publicKey).not.toEqual(b.publicKey);31 });3233 it("produces 32-byte keys", () => {34 const kp = deriveSyncIdentityKeypair(MASTER, "x");35 expect(kp.publicKey.length).toBe(32);36 expect(kp.secretKey.length).toBe(32);37 });3839 it("yields a working X25519 keypair (DH commutes)", () => {40 const alice = deriveSyncIdentityKeypair(MASTER, "alice");41 const bob = deriveSyncIdentityKeypair(MASTER, "bob");42 const ab = sharedKey(alice.secretKey, bob.publicKey);43 const ba = sharedKey(bob.secretKey, alice.publicKey);44 expect(ab).toEqual(ba);45 });4647 it("rejects empty master key", () => {48 expect(() => deriveSyncIdentityKeypair(new Uint8Array(), "x")).toThrow();49 });5051 it("rejects empty device id", () => {52 expect(() => deriveSyncIdentityKeypair(MASTER, "")).toThrow();53 });54});5556describe("computeVaultId", () => {57 it("is deterministic for the same master key", async () => {58 const a = await computeVaultId(MASTER);59 const b = await computeVaultId(MASTER);60 expect(a).toBe(b);61 });6263 it("differs across master keys", async () => {64 const a = await computeVaultId(MASTER);65 const b = await computeVaultId(OTHER_MASTER);66 expect(a).not.toBe(b);67 });6869 it("returns 64 hex chars (full 32-byte BLAKE3 digest)", async () => {70 const id = await computeVaultId(MASTER);71 expect(id).toMatch(/^[0-9a-f]{64}$/);72 });73});7475describe("pubkeyToHex / hexToPubkey", () => {76 it("round-trips", () => {77 const pk = Uint8Array.from(Array.from({ length: 32 }, (_, i) => i));78 const hex = pubkeyToHex(pk);79 expect(hexToPubkey(hex)).toEqual(pk);80 });8182 it("rejects keys that are not exactly 32 bytes", () => {83 expect(() => hexToPubkey("abc")).toThrow();84 expect(() => hexToPubkey("00".repeat(31))).toThrow();85 expect(() => hexToPubkey("00".repeat(33))).toThrow();86 });8788 it("rejects invalid hex chars", () => {89 expect(() => hexToPubkey("zz".repeat(32))).toThrow();90 });91});