sync-entry-envelope.kat.test.ts

1.5 KB · raw

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