sync-identity.test.ts

3.0 KB · raw

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});