import { readFileSync } from "node:fs"; import { HKDF } from "@stablelib/hkdf"; import { SHA256 } from "@stablelib/sha256"; import { expect, it } from "vitest"; type Vector = { masterKeyHex: string; entryId: string; nonceHex: string; plaintext: string; envelopeBase64: string; }; const vector = JSON.parse( readFileSync("fixtures/golden/sync-entry-envelope-v1.json", "utf8"), ) as Vector; const fromHex = (hex: string) => Uint8Array.from(hex.match(/../g)!.map((byte) => Number.parseInt(byte, 16))); it("matches the cross-client sync-entry-v1 envelope vector", async () => { const info = new TextEncoder().encode(`${vector.entryId}\0sync-entry-v1`); const rawKey = new HKDF( SHA256, fromHex(vector.masterKeyHex), new Uint8Array(0), info, ).expand(32); const nonce = fromHex(vector.nonceHex); const additionalData = new TextEncoder().encode("peach-sync-entry-v1"); const plaintext = new TextEncoder().encode(vector.plaintext); const key = await crypto.subtle.importKey( "raw", rawKey.buffer as ArrayBuffer, "AES-GCM", false, ["encrypt"], ); const ciphertext = new Uint8Array(await crypto.subtle.encrypt( { name: "AES-GCM", iv: nonce.buffer as ArrayBuffer, additionalData: additionalData.buffer as ArrayBuffer, tagLength: 128, }, key, plaintext.buffer as ArrayBuffer, )); const envelope = new Uint8Array(12 + ciphertext.length); envelope.set(nonce); envelope.set(ciphertext, 12); expect(Buffer.from(envelope).toString("base64")).toBe(vector.envelopeBase64); });