1import { readFileSync } from "node:fs";2import { HKDF } from "@stablelib/hkdf";3import { SHA256 } from "@stablelib/sha256";4import { expect, it } from "vitest";56type Vector = {7 masterKeyHex: string;8 entryId: string;9 nonceHex: string;10 plaintext: string;11 envelopeBase64: string;12};1314const vector = JSON.parse(15 readFileSync("fixtures/golden/sync-entry-envelope-v1.json", "utf8"),16) as Vector;17const fromHex = (hex: string) =>18 Uint8Array.from(hex.match(/../g)!.map((byte) => Number.parseInt(byte, 16)));1920it("matches the cross-client sync-entry-v1 envelope vector", async () => {21 const info = new TextEncoder().encode(`${vector.entryId}\0sync-entry-v1`);22 const rawKey = new HKDF(23 SHA256,24 fromHex(vector.masterKeyHex),25 new Uint8Array(0),26 info,27 ).expand(32);28 const nonce = fromHex(vector.nonceHex);29 const additionalData = new TextEncoder().encode("peach-sync-entry-v1");30 const plaintext = new TextEncoder().encode(vector.plaintext);31 const key = await crypto.subtle.importKey(32 "raw",33 rawKey.buffer as ArrayBuffer,34 "AES-GCM",35 false,36 ["encrypt"],37 );38 const ciphertext = new Uint8Array(await crypto.subtle.encrypt(39 {40 name: "AES-GCM",41 iv: nonce.buffer as ArrayBuffer,42 additionalData: additionalData.buffer as ArrayBuffer,43 tagLength: 128,44 },45 key,46 plaintext.buffer as ArrayBuffer,47 ));48 const envelope = new Uint8Array(12 + ciphertext.length);49 envelope.set(nonce);50 envelope.set(ciphertext, 12);5152 expect(Buffer.from(envelope).toString("base64")).toBe(vector.envelopeBase64);53});