# crypto-sync _(no submodules)_ ## Purpose `crypto-sync.ts` provides the AEAD-encrypted envelope used by `SyncEngine` and `SyncQueue` to put plaintext sync payloads on the wire. Encryption is XChaCha20-Poly1305 from `@suaveplan/crypto-cipher` with a 24-byte nonce and an AAD-bound version string (`"v1"`). The version binding means a future revision can change the wire layout without risk that a tampered v2 payload is silently decoded as v1. The module enforces a 10 MiB plaintext cap to prevent memory abuse from oversized snapshots. ## Features - `encryptSyncPayload(plaintext, key, options?)` — XChaCha20-Poly1305 AEAD - `decryptSyncPayload(payload, key, options?)` — verified decrypt with AAD - AAD = `"v1"` || optional caller-supplied bytes - Random 24-byte nonce sourced via `@suaveplan/crypto-utils.randomBytes` - Injectable `byteSource` for deterministic test nonces - Hard cap of 10 MiB on plaintext (`SYNC.PAYLOAD_TOO_LARGE`) - Structured errors: `SYNC.PAYLOAD_TAMPERED`, `SYNC.PAYLOAD_INVALID_VERSION` - OTel histogram + outcome counter (no key bytes, no plaintext, no IDs) ## Usage: Basic Example ```ts import { decryptSyncPayload, encryptSyncPayload } from "@suaveplan/vault-sync"; const key = new Uint8Array(32); // 32 bytes — ChaCha20 key crypto.getRandomValues(key); const plaintext = new TextEncoder().encode(JSON.stringify({ items: [] })); const envelope = encryptSyncPayload(plaintext, key); const recovered = decryptSyncPayload(envelope, key); console.log(new TextDecoder().decode(recovered)); ``` ## Usage: Advanced Example ```ts import { encryptSyncPayload } from "@suaveplan/vault-sync"; import { decryptSyncPayload } from "@suaveplan/vault-sync"; import { VaultSyncError } from "@suaveplan/vault-sync"; import { VAULT_SYNC_CODES } from "@suaveplan/vault-sync/errors"; import { zeroize } from "@suaveplan/crypto-utils"; const key = crypto.getRandomValues(new Uint8Array(32)); const plaintext = new TextEncoder().encode("snapshot"); const aad = new TextEncoder().encode("vault-id-binding"); // domain separator const envelope = encryptSyncPayload(plaintext, key, { additionalData: aad }); try { // Caller MUST pass the same additionalData on decrypt — otherwise the // tag check fails with SYNC.PAYLOAD_TAMPERED. const round = decryptSyncPayload(envelope, key, { additionalData: aad }); zeroize(round); // clear plaintext when done } catch (err) { if ( err instanceof VaultSyncError && err.code === VAULT_SYNC_CODES["SYNC.PAYLOAD_TAMPERED"] ) { // Telemetry already recorded; trigger a fresh full pull. } throw err; } ``` ## API Reference - `encryptSyncPayload(plaintext: Uint8Array, key: Uint8Array, options?: EncryptSyncPayloadOptions): EncryptedSyncPayload` — encrypts with XChaCha20-Poly1305. Throws `VaultSyncError(SYNC.PAYLOAD_TOO_LARGE)` if `plaintext.length` exceeds `MAX_SYNC_PAYLOAD_BYTES`. - `decryptSyncPayload(payload: EncryptedSyncPayload, key: Uint8Array, options?): Uint8Array` — validates the envelope shape via `EncryptedSyncPayloadSchema` then verifies and decrypts. Throws `VaultSyncError(SYNC.PAYLOAD_INVALID_VERSION)` for shape failures and `VaultSyncError(SYNC.PAYLOAD_TAMPERED)` for tag mismatch (wrong key, flipped byte, mismatched AAD). - `EncryptSyncPayloadOptions` — `{ byteSource?, additionalData? }`. - `MAX_SYNC_PAYLOAD_BYTES` — `10 * 1024 * 1024` (10 MiB). - `SYNC_PAYLOAD_VERSION` — literal `"v1"`. ## Implementation Notes - The package never generates a nonce internally beyond delegating to `randomBytes` from `@suaveplan/crypto-utils`. Callers wanting determinism in tests pass a custom `byteSource`. There is no notion of nonce reuse detection at this layer — XChaCha20's 192-bit nonce makes random selection collision-safe at any realistic call rate, and higher-level wrapping is left to the consumer if needed. - The plaintext returned by `decryptSyncPayload` is a fresh `Uint8Array`. The caller is responsible for `zeroize`-ing it after use; the module cannot do this safely because it does not know when the caller is done reading the value. - Both shape failures and tag failures emit `payload.outcome.total{status: "tampered"}` so a single counter captures the integrity-violation rate. - Decryption-side schema validation uses the same `EncryptedSyncPayloadSchema` exposed publicly, ensuring schema and runtime checks are perfectly aligned.