Files
2026-05-27 23:47:53 +01:00

3.3 KiB

streaming-aead-xchacha20

(no submodules)

Purpose

streaming-aead-xchacha20.ts is the XChaCha20-Poly1305 adapter that satisfies the StreamingAeadEngine contract used by encryptStreamAead / decryptStreamAead. XChaCha20-Poly1305 is preferred over AES-GCM when the deployment runs without hardware AES acceleration or when the 24-byte extended nonce makes random-nonce schemes safer (the 2^96 random-nonce birthday-bound is comfortable, where AES-GCM's 2^48 is not).

The engine declares a 20-byte caller-supplied base nonce; the per-chunk 24-byte nonce is baseNonce ‖ counter_be32. The high bit of the 32-bit counter carries the final-flag so terminator and data chunks at the same counter index produce different nonces. This is the default streaming engine — callers who do not pass engine: to encryptStreamAead / decryptStreamAead get this one.

Features

  • Implements the StreamingAeadEngine contract — drop-in for the streaming generators.
  • 20-byte base nonce / 24-byte per-chunk nonce / 16-byte AEAD tag.
  • Counter-discipline guard: rejects negative or > 2^32 - 1 counters with STREAM_OUT_OF_ORDER.
  • Base-nonce length guard: rejects any base nonce whose length is not exactly 20 bytes with STREAM_INVALID_NONCE_BASE.
  • Tag-mismatch promotion: open-side tag failures route to STREAM_TAG_MISMATCH (with the original DECRYPT_FAILED as cause) so streaming callers see the streaming-shaped error.

Usage: Basic Example

import { xchacha20StreamingEngine } from "@suaveplan/crypto-cipher/streaming/streaming-aead-xchacha20";
import { encryptStreamAead } from "@suaveplan/crypto-cipher/streaming/streaming-aead";

const key = crypto.getRandomValues(new Uint8Array(32));
// xchacha20StreamingEngine is the default — passing it explicitly is optional.
for await (const chunk of encryptStreamAead(key, source(), {
    engine: xchacha20StreamingEngine,
})) {
    sink.write(chunk);
}

Usage: Advanced Example

import { xchacha20StreamingEngine } from "@suaveplan/crypto-cipher/streaming/streaming-aead-xchacha20";

// Use the engine directly without the framing layer to embed XChaCha20-Poly1305
// into a custom transport. The 20-byte base nonce gives plenty of headroom for
// random nonces if the caller derives one per-stream rather than per-chunk.
const baseNonce = crypto.getRandomValues(new Uint8Array(20));

const ct = await xchacha20StreamingEngine.seal({
    key,
    baseNonce,
    counter: 0,
    finalFlag: 0,
    data: plaintext,
    aad: contextBytes,
});

API Reference

  • xchacha20StreamingEngine: StreamingAeadEngine — frozen engine instance.
  • BASE_NONCE_LENGTH: 20
  • NONCE_LENGTH: 24
  • TAG_LENGTH: 16

Errors: STREAM_INVALID_NONCE_BASE, STREAM_OUT_OF_ORDER, STREAM_TAG_MISMATCH.

Implementation Notes

XChaCha20-Poly1305 (libsodium specification, RFC draft) extends the ChaCha20 nonce from 12 to 24 bytes via HChaCha20 sub-key derivation. The 24-byte nonce makes random nonces safe (2^96 random nonces have a ~10^-19 collision probability), which is the basis for the streaming layer using a random base nonce plus a counter. Counter-overflow at 2^32 chunks short-circuits before reuse can occur. This adapter does not generate the base nonce — the streaming generator supplies it (random by default; deterministic via the baseNonce option for tests).