Files
skills/docs/foundation/data/packer/src/chunked/crypto/aead.md
2026-05-29 08:20:56 +01:00

2.9 KiB

aead

(no submodules)

Purpose

Wraps @suaveplan/crypto.aeadEncrypt / aeadDecrypt (XChaCha20-Poly1305) into helpers that match the v1 chunk wire format, where the ciphertext and Poly1305 tag are stored in separate fields rather than as the noble-style concatenated ciphertext || tag buffer. Also provides the deterministic per-chunk nonce derivation used by the chunked packer.

Features

  • aeadSeal(key, nonce, plaintext) — encrypts and returns { ciphertext, aeadTag } as detached Uint8Array instances.
  • aeadOpen(key, nonce, ciphertext, aeadTag) — re-joins the two and decrypts; throws on tag mismatch.
  • deriveChunkNonce(baseNonce, chunkId) — baseNonce[0..4] XOR chunkId-as-u32-BE, deterministic and unique per chunk.
  • All inputs validated; failures wrapped as PackerError(PACKER.ENCRYPTION_FAILED).

Usage: Basic Example

import { aeadSeal, aeadOpen, deriveChunkNonce } from "@suaveplan/packer/chunked";

const fileKey = new Uint8Array(32);                   // caller-supplied
const baseNonce = crypto.getRandomValues(new Uint8Array(24));
const nonce = deriveChunkNonce(baseNonce, 0);
const { ciphertext, aeadTag } = aeadSeal(fileKey, nonce, plaintext);
const back = aeadOpen(fileKey, nonce, ciphertext, aeadTag);

Usage: Advanced Example

import { aeadSeal, deriveChunkNonce } from "@suaveplan/packer/chunked";

// Seal each chunk with a derived nonce so the manifest only stores baseNonce.
function sealAll(plaintexts: Uint8Array[], fileKey: Uint8Array, baseNonce: Uint8Array) {
    return plaintexts.map((p, i) => {
        const nonce = deriveChunkNonce(baseNonce, i);
        const { ciphertext, aeadTag } = aeadSeal(fileKey, nonce, p);
        return { ciphertext, aeadTag, nonce };
    });
}

API Reference

  • AeadSealResult — { ciphertext: Uint8Array; aeadTag: Uint8Array }.
  • aeadSeal(key, nonce, plaintext): AeadSealResult — key 32 B, nonce 24 B. Returns ciphertext (same length as plaintext) and 16-byte tag.
  • aeadOpen(key, nonce, ciphertext, aeadTag): Uint8Array — returns plaintext. Throws PackerError(PACKER.ENCRYPTION_FAILED) on tag mismatch or invalid lengths.
  • deriveChunkNonce(baseNonce, chunkId): Uint8Array — 24-byte nonce, deterministic per (baseNonce, chunkId) pair.
  • Constants: AEAD_KEY_LEN = 32, AEAD_NONCE_LEN = 24, AEAD_TAG_LEN = 16, FILE_ID_LEN = 32.

Implementation Notes

The seal helper detaches the returned ciphertext and aeadTag from the underlying combined buffer with new Uint8Array(view) so callers can mutate one without aliasing the other. Nonce derivation XORs only the first 32 bits to keep the rest of baseNonce intact — the manifest's baseNonce plus a chunkId is sufficient to reconstruct any chunk's nonce. The tag length is fixed at 16 (Poly1305); AEAD_TAG_LEN is exported for callers writing their own decoders.