Three layers: a short note at the top, the key lines with our take in the middle, the full source at the bottom.
Test
c2-encrypt-encoding.test.ts
Encryption-at-rest encoding tests. Each ciphertext blob must round-trip exactly.
Repo path apps/api/tests/c2-encrypt-encoding.test.tsLanguage TypeScript
What this is
A test that pins the exact byte layout of the encrypted blob shared between your device and the server. If the server-side encoding ever drifts from the client-side encoding, the test fails. The server and the client must agree byte for byte.
What it proves
Backs the promise that a locked scan can be reopened on any of your devices. Both sides of the wire are pinned to one canonical layout. Read the promise →
What to look for in the source below
- A fixture with a known input and a known output — both committed to the file.
- A round-trip assertion that the encoded bytes match the expected output exactly.
- A version byte at the start of every blob, so a future change can be detected without breaking old documents.
Show the full file (107 lines)
106 lines
// Wave C2.3 — the wire-encoding contract between the server
// encrypt seam (queue.ts) and the future client decrypt path
// (C2.4). A mismatch here = permanently undecryptable documents,
// so it is pinned end-to-end against the real crypto core.
import { describe, it, expect } from "vitest";
import { b64urlEncode, b64urlDecode } from "../src/lib/queue";
import {
generateIdentityKeyPair,
wrapToPublicKey,
unwrapWithSecretKey,
aesEncrypt,
aesDecrypt,
} from "@muntin/recovery-crypto";
describe("b64url codec (queue.ts) round-trips", () => {
it("encode→decode is identity for random byte lengths", () => {
for (const n of [0, 1, 12, 16, 31, 32, 48, 80, 1000]) {
const b = new Uint8Array(n);
for (let i = 0; i < n; i++) b[i] = (i * 37 + 11) & 0xff;
expect([...b64urlDecode(b64urlEncode(b))]).toEqual([...b]);
}
});
it("emits url-safe, unpadded alphabet only", () => {
const b = new Uint8Array([251, 252, 253, 254, 255, 0, 62, 63]);
const s = b64urlEncode(b);
expect(s).toMatch(/^[A-Za-z0-9_-]+$/);
expect(s).not.toContain("=");
});
});
describe("server wire format → client reversal (the catastrophic path)", () => {
it("epk||ciphertext packing + b64url survives a full encrypt→decrypt", async () => {
// Mirror EXACTLY what queue.ts writes to document_deks.
const { publicKey, secretKey } = generateIdentityKeyPair();
const dek = new Uint8Array(32);
crypto.getRandomValues(dek);
const docId = "doc_round_trip";
const aad = new TextEncoder().encode(docId);
const plaintext = new TextEncoder().encode(
"the original invoice bytes — only the owner may read this",
);
// --- server side (queue.ts encrypt seam) ---
const body = await aesEncrypt(dek, plaintext, aad);
const sealed = await wrapToPublicKey(publicKey, dek, aad);
const packed = new Uint8Array(sealed.epk.length + sealed.ciphertext.length);
packed.set(sealed.epk, 0);
packed.set(sealed.ciphertext, sealed.epk.length);
const wire = {
wrapped_dek: b64urlEncode(packed),
wrap_nonce: b64urlEncode(sealed.nonce),
document_nonce: b64urlEncode(body.nonce),
ciphertext: b64urlEncode(body.ciphertext), // (R2 object body)
wrap_aad: docId,
};
// --- client side (C2.4 will do exactly this) ---
const packedBack = b64urlDecode(wire.wrapped_dek);
const epk = packedBack.slice(0, 32); // fixed 32-byte X25519 prefix
const ct = packedBack.slice(32);
const dekBack = await unwrapWithSecretKey(
secretKey,
{ epk, ciphertext: ct, nonce: b64urlDecode(wire.wrap_nonce) },
new TextEncoder().encode(wire.wrap_aad),
);
expect([...dekBack]).toEqual([...dek]);
const recovered = await aesDecrypt(
dekBack,
{
ciphertext: b64urlDecode(wire.ciphertext),
nonce: b64urlDecode(wire.document_nonce),
},
new TextEncoder().encode(wire.wrap_aad),
);
expect(new TextDecoder().decode(recovered)).toBe(
"the original invoice bytes — only the owner may read this",
);
});
it("a different recipient secret key cannot unwrap (confidentiality)", async () => {
const alice = generateIdentityKeyPair();
const mallory = generateIdentityKeyPair();
const dek = new Uint8Array(32);
crypto.getRandomValues(dek);
const aad = new TextEncoder().encode("doc_x");
const sealed = await wrapToPublicKey(alice.publicKey, dek, aad);
const packed = new Uint8Array(32 + sealed.ciphertext.length);
packed.set(sealed.epk, 0);
packed.set(sealed.ciphertext, 32);
const back = b64urlDecode(b64urlEncode(packed));
await expect(
unwrapWithSecretKey(
mallory.secretKey,
{
epk: back.slice(0, 32),
ciphertext: back.slice(32),
nonce: sealed.nonce,
},
aad,
),
).rejects.toThrow();
});
});See also
This is the file as it lives at the moment of this build. The canonical history lives in git. If you want the full history or a specific commit, write to hello@muntin.digital.