first commit
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
# aes-256-gcm
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
- [aes-256-gcm.select](./aes-256-gcm.select/aes-256-gcm.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm.native](./impl/aes-256-gcm.native/aes-256-gcm.native.md) — <!-- begin:auto-docs --> _(no submodules)_ - [aes-256-gcm.native](./impl/aes-256-gcm.native/aes-256-gcm.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm.noble](./impl/aes-256-gcm.noble/aes-256-gcm.noble.md) — <!-- begin:auto-docs --> _(no submodules)_ - [aes-256-gcm.noble](./impl/aes-256-gcm.noble/aes-256-gcm.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm](./aes-256-gcm.md) — <!-- begin:auto-docs --> _(no submodules)_- aes-256-gcm.select — <!-- begin:auto-docs --> _(no s…
|
||||
- [aes-256-gcm.select](./aes-256-gcm.select/aes-256-gcm.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm.native](./impl/aes-256-gcm.native/aes-256-gcm.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm.native](./impl/aes-256-gcm.native/aes-256-gcm.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm.noble](./impl/aes-256-gcm.noble/aes-256-gcm.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm.noble](./impl/aes-256-gcm.noble/aes-256-gcm.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm](./aes-256-gcm.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm.select](./aes-256-gcm.select/aes-256-gcm.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm.native](./impl/aes-256-gcm.native/aes-256-gcm.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm.native](./impl/aes-256-gcm.native/aes-256-gcm.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm.noble](./impl/aes-256-gcm.noble/aes-256-gcm.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm.noble](./impl/aes-256-gcm.noble/aes-256-gcm.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm](./aes-256-gcm.md) — <!-- begin:auto-docs --> _(no submodules)_- aes-256-gcm.select — <!-- be…
|
||||
- [aes-256-gcm.select](./aes-256-gcm.select/aes-256-gcm.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm.native](./impl/aes-256-gcm.native/aes-256-gcm.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm.native](./impl/aes-256-gcm.native/aes-256-gcm.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm.noble](./impl/aes-256-gcm.noble/aes-256-gcm.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm.noble](./impl/aes-256-gcm.noble/aes-256-gcm.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm](./aes-256-gcm.md) — <!-- begin:auto-docs --> _(no submodules)_- aes-256-gcm.select — <!-- begin:auto-docs --> _(no…
|
||||
- [aes-256-gcm.select](./aes-256-gcm.select/aes-256-gcm.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm.native](./impl/aes-256-gcm.native/aes-256-gcm.native.md) — <!-- begin:auto-docs --> _(no submodules)_ - [aes-256-gcm.native](./impl/aes-256-gcm.native/aes-256-gcm.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm.noble](./impl/aes-256-gcm.noble/aes-256-gcm.noble.md) — <!-- begin:auto-docs --> _(no submodules)_ - [aes-256-gcm.noble](./impl/aes-256-gcm.noble/aes-256-gcm.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [aes-256-gcm](./aes-256-gcm.md) — <!-- begin:auto-docs --> _(no submodules)_
|
||||
## Purpose
|
||||
|
||||
`aes-256-gcm.ts` is the stable public entry point for the AES-256-GCM AEAD primitive in `@suaveplan/crypto-cipher`. It re-exports everything from the generated `aes-256-gcm.select.ts`, which forwards to either the Noble-backed or native implementation according to `crypto.config.ts`.
|
||||
|
||||
AES-256-GCM (NIST SP 800-38D) is a widely deployed authenticated encryption scheme that provides both confidentiality and integrity. It is mandatory in TLS 1.3, common in cloud storage encryption, and natively available in WebCrypto. This entry point provides the stable import path and exposes the constants `KEY_LENGTH`, `NONCE_LENGTH`, and `TAG_LENGTH`.
|
||||
|
||||
## Features
|
||||
|
||||
- `encrypt(key, nonce, plaintext, aad?)` — AEAD encryption; returns `ciphertext ‖ tag`.
|
||||
- `decrypt(key, nonce, ciphertext, aad?)` — AEAD decryption; throws `CryptoCipherError` on tag mismatch.
|
||||
- `KEY_LENGTH = 32` — 256-bit key requirement.
|
||||
- `NONCE_LENGTH = 12` — 96-bit (12-byte) nonce as per NIST recommendation.
|
||||
- `TAG_LENGTH = 16` — 128-bit authentication tag.
|
||||
- Key and nonce length validated before any cipher operation.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { encrypt, decrypt, KEY_LENGTH, NONCE_LENGTH } from "@suaveplan/crypto-cipher/aes-256-gcm";
|
||||
|
||||
const key = crypto.getRandomValues(new Uint8Array(KEY_LENGTH));
|
||||
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
|
||||
const plaintext = new TextEncoder().encode("secret");
|
||||
|
||||
const ciphertext = encrypt(key, nonce, plaintext);
|
||||
const recovered = decrypt(key, nonce, ciphertext);
|
||||
console.log(new TextDecoder().decode(recovered)); // "secret"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { encrypt, decrypt, KEY_LENGTH, NONCE_LENGTH } from "@suaveplan/crypto-cipher/aes-256-gcm";
|
||||
|
||||
// Encrypt with Additional Authenticated Data (AAD) for binding.
|
||||
function encryptWithContext(
|
||||
key: Uint8Array,
|
||||
plaintext: Uint8Array,
|
||||
context: string
|
||||
): { nonce: Uint8Array; ciphertext: Uint8Array } {
|
||||
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
|
||||
const aad = new TextEncoder().encode(context);
|
||||
const ciphertext = encrypt(key, nonce, plaintext, aad);
|
||||
return { nonce, ciphertext };
|
||||
}
|
||||
|
||||
function decryptWithContext(
|
||||
key: Uint8Array,
|
||||
nonce: Uint8Array,
|
||||
ciphertext: Uint8Array,
|
||||
context: string
|
||||
): Uint8Array {
|
||||
const aad = new TextEncoder().encode(context);
|
||||
return decrypt(key, nonce, ciphertext, aad); // throws CryptoCipherError on mismatch
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
All symbols re-exported from `aes-256-gcm.select.ts`:
|
||||
|
||||
- `encrypt(key, nonce, plaintext, aad?): Uint8Array`
|
||||
- `decrypt(key, nonce, ciphertext, aad?): Uint8Array`
|
||||
- `KEY_LENGTH: 32`
|
||||
- `NONCE_LENGTH: 12`
|
||||
- `TAG_LENGTH: 16`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Nonce generation is the caller's responsibility — this module never generates nonces internally. For AES-256-GCM, nonces must never be reused with the same key; a reused (key, nonce) pair destroys both confidentiality and authenticity. Callers should use random 12-byte nonces (96-bit) or a counter-based scheme. The 16-byte authentication tag is appended to the ciphertext in the output of `encrypt` and expected at the end of the ciphertext in `decrypt`.
|
||||
@@ -0,0 +1,62 @@
|
||||
# aes-256-gcm.select
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`aes-256-gcm.select.ts` is the build-time primitive selector for AES-256-GCM in `@suaveplan/crypto-cipher`. Generated by `bun scripts/gen-crypto-selects.ts`, it re-exports one backend — either `aes-256-gcm.noble.ts` or `aes-256-gcm.native.ts` — based on the `aes-256-gcm` field in `crypto.config.ts`.
|
||||
|
||||
Callers import through the stable `aes-256-gcm.ts` entry point. The selector indirection allows backend changes without touching any caller import paths. Currently the Noble backend is selected because the native stub throws `PrimitiveNotImplemented`.
|
||||
|
||||
## Features
|
||||
|
||||
- Re-exports `encrypt`, `decrypt`, `KEY_LENGTH`, `NONCE_LENGTH`, `TAG_LENGTH`.
|
||||
- Generated — never hand-edited.
|
||||
- Controlled by `crypto.config.ts`: set `aes-256-gcm` to `'noble'` or `'native'`.
|
||||
- Enables dead-code elimination of the inactive backend.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { encrypt, KEY_LENGTH, NONCE_LENGTH } from "@suaveplan/crypto-cipher/aes-256-gcm";
|
||||
|
||||
const key = crypto.getRandomValues(new Uint8Array(KEY_LENGTH));
|
||||
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
|
||||
const ct = encrypt(key, nonce, new TextEncoder().encode("hello"));
|
||||
console.log(ct.length); // 16 + 5 = 21 (ciphertext + tag)
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { decrypt, NONCE_LENGTH } from "@suaveplan/crypto-cipher/aes-256-gcm";
|
||||
import { CryptoCipherError } from "@suaveplan/crypto-cipher/errors";
|
||||
|
||||
function safeDecrypt(key: Uint8Array, nonce: Uint8Array, ct: Uint8Array): Uint8Array | null {
|
||||
try {
|
||||
return decrypt(key, nonce, ct);
|
||||
} catch (err) {
|
||||
if (err instanceof CryptoCipherError) return null;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
Re-exports from the active implementation:
|
||||
|
||||
- `encrypt(key: Uint8Array, nonce: Uint8Array, plaintext: Uint8Array, aad?: Uint8Array): Uint8Array`
|
||||
- `decrypt(key: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array, aad?: Uint8Array): Uint8Array`
|
||||
- `KEY_LENGTH: 32`
|
||||
- `NONCE_LENGTH: 12`
|
||||
- `TAG_LENGTH: 16`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
This file is a generated one-liner and is marked `/* GENERATED — DO NOT EDIT */`. The generator resolves the `aes-256-gcm` backend from `crypto.config.ts` and emits the corresponding `export *` line. Running the generator is idempotent. Do not add logic here.
|
||||
|
||||
### Selection Logic
|
||||
|
||||
At build time `crypto.config.ts` chooses between the Noble `@noble/ciphers/aes` implementation and the native-engine stub for AES-256-GCM; the selector exported here reads the generated `crypto-selects` map and dispatches every `encrypt` / `decrypt` call to the chosen impl. Nonce is caller-supplied per the package-wide rule — this module does not generate nonces internally.
|
||||
@@ -0,0 +1,69 @@
|
||||
# aes-256-gcm.native
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`aes-256-gcm.native.ts` is the native-backend placeholder for AES-256-GCM in `@suaveplan/crypto-cipher`. It exports `encrypt`, `decrypt`, `KEY_LENGTH`, `NONCE_LENGTH`, and `TAG_LENGTH` with the same types as the Noble implementation, but `encrypt` and `decrypt` throw `PrimitiveNotImplemented("aes-256-gcm")` unconditionally.
|
||||
|
||||
AES-256-GCM is natively available via `SubtleCrypto.encrypt` with `{ name: "AES-GCM" }` in all modern browsers and in Node.js via the `node:crypto` module. A future native implementation would delegate to these platform APIs, potentially enabling hardware-accelerated encryption on supported hardware. This stub keeps the selector layout complete until that work is ready.
|
||||
|
||||
## Features
|
||||
|
||||
- Exports `encrypt`, `decrypt`, `KEY_LENGTH`, `NONCE_LENGTH`, `TAG_LENGTH` — same surface as the Noble file.
|
||||
- `encrypt` and `decrypt` throw `PrimitiveNotImplemented("aes-256-gcm")`.
|
||||
- `KEY_LENGTH = 32`, `NONCE_LENGTH = 12`, `TAG_LENGTH = 16` constants are usable even when the algorithm is not implemented.
|
||||
- Zero runtime dependencies beyond `@suaveplan/crypto-utils/errors`.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
// Active only when crypto.config.ts sets aes-256-gcm to 'native'.
|
||||
import { encrypt, KEY_LENGTH, NONCE_LENGTH } from "@suaveplan/crypto-cipher/aes-256-gcm";
|
||||
|
||||
try {
|
||||
encrypt(
|
||||
new Uint8Array(KEY_LENGTH),
|
||||
new Uint8Array(NONCE_LENGTH),
|
||||
new Uint8Array(16)
|
||||
);
|
||||
} catch (err) {
|
||||
// PrimitiveNotImplemented: aes-256-gcm
|
||||
console.error(err);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
// The constants are still safe to use regardless of the selected backend.
|
||||
import { KEY_LENGTH, NONCE_LENGTH } from "@suaveplan/crypto-cipher/aes-256-gcm";
|
||||
|
||||
function allocateCipherBuffers(): { key: Uint8Array; nonce: Uint8Array } {
|
||||
return {
|
||||
key: crypto.getRandomValues(new Uint8Array(KEY_LENGTH)),
|
||||
nonce: crypto.getRandomValues(new Uint8Array(NONCE_LENGTH)),
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `encrypt(_key, _nonce, _plaintext, _aad?): Uint8Array`
|
||||
|
||||
Throws `PrimitiveNotImplemented("aes-256-gcm")`.
|
||||
|
||||
### `decrypt(_key, _nonce, _ciphertext, _aad?): Uint8Array`
|
||||
|
||||
Throws `PrimitiveNotImplemented("aes-256-gcm")`.
|
||||
|
||||
### Constants
|
||||
|
||||
- `KEY_LENGTH = 32`
|
||||
- `NONCE_LENGTH = 12`
|
||||
- `TAG_LENGTH = 16`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Exporting constants even in the placeholder makes the file drop-in compatible with the Noble implementation for callers that only import constants (e.g. to allocate buffers). When the native implementation is ready and passes NIST SP 800-38D CAVP test vectors, the placeholder functions are replaced and the selector regenerated. The implementation will need to handle the synchronous/asynchronous impedance mismatch since `SubtleCrypto` is async and this interface is synchronous — likely via Node.js `crypto.createCipheriv` for server-side use.
|
||||
@@ -0,0 +1,84 @@
|
||||
# aes-256-gcm.noble
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`aes-256-gcm.noble.ts` is the Noble-backed concrete implementation of AES-256-GCM in `@suaveplan/crypto-cipher`. It uses `gcm` from `@noble/ciphers/aes`, validates key and nonce lengths before every operation, and wraps decryption failures in a typed `CryptoCipherError`.
|
||||
|
||||
AES-256-GCM (NIST SP 800-38D) provides authenticated encryption with associated data (AEAD). The authentication tag is appended to the ciphertext by Noble's `encrypt` and verified before returning plaintext from `decrypt`. This file is the production implementation; `aes-256-gcm.native.ts` is the placeholder.
|
||||
|
||||
## Features
|
||||
|
||||
- `encrypt(key, nonce, plaintext, aad?)` — validates lengths then delegates to Noble `gcm`.
|
||||
- `decrypt(key, nonce, ciphertext, aad?)` — validates lengths, delegates to Noble, wraps auth failures in `CryptoCipherError(DECRYPT_FAILED)`.
|
||||
- Exports `KEY_LENGTH = 32`, `NONCE_LENGTH = 12`, `TAG_LENGTH = 16`.
|
||||
- Validation throws `CryptoCipherError(INVALID_KEY_LENGTH)` or `INVALID_NONCE_LENGTH` with structured context.
|
||||
- Noble appends/strips the 16-byte GCM tag automatically.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { encrypt, decrypt, KEY_LENGTH, NONCE_LENGTH } from "@suaveplan/crypto-cipher/aes-256-gcm";
|
||||
|
||||
const key = crypto.getRandomValues(new Uint8Array(KEY_LENGTH));
|
||||
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
|
||||
const plaintext = new TextEncoder().encode("hello AES-GCM");
|
||||
|
||||
const ciphertext = encrypt(key, nonce, plaintext);
|
||||
const recovered = decrypt(key, nonce, ciphertext);
|
||||
console.log(new TextDecoder().decode(recovered)); // "hello AES-GCM"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { encrypt, decrypt, KEY_LENGTH, NONCE_LENGTH } from "@suaveplan/crypto-cipher/aes-256-gcm";
|
||||
|
||||
// Encrypt a record with a session ID bound as AAD.
|
||||
function encryptRecord(
|
||||
key: Uint8Array,
|
||||
sessionId: string,
|
||||
record: Uint8Array
|
||||
): { nonce: Uint8Array; ciphertext: Uint8Array } {
|
||||
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
|
||||
const aad = new TextEncoder().encode(sessionId);
|
||||
return { nonce, ciphertext: encrypt(key, nonce, record, aad) };
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `encrypt(key: Uint8Array, nonce: Uint8Array, plaintext: Uint8Array, aad?: Uint8Array): Uint8Array`
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `key` | `Uint8Array` | 32-byte AES-256 key. |
|
||||
| `nonce` | `Uint8Array` | 12-byte GCM nonce. Must not be reused with the same key. |
|
||||
| `plaintext` | `Uint8Array` | Plaintext bytes to encrypt. |
|
||||
| `aad` | `Uint8Array` (optional) | Additional authenticated data (not encrypted, but authenticated). |
|
||||
|
||||
**Returns:** `ciphertext ‖ tag` — length is `plaintext.length + 16`.
|
||||
|
||||
**Throws:** `CryptoCipherError(INVALID_KEY_LENGTH)` or `CryptoCipherError(INVALID_NONCE_LENGTH)`.
|
||||
|
||||
### `decrypt(key: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array, aad?: Uint8Array): Uint8Array`
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `ciphertext` | `Uint8Array` | `ciphertext ‖ tag` produced by `encrypt`. |
|
||||
|
||||
**Returns:** Plaintext.
|
||||
|
||||
**Throws:** `CryptoCipherError(DECRYPT_FAILED)` if the authentication tag does not match.
|
||||
|
||||
### Constants
|
||||
|
||||
- `KEY_LENGTH = 32`
|
||||
- `NONCE_LENGTH = 12`
|
||||
- `TAG_LENGTH = 16`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Noble's `gcm(key, nonce, aad)` returns a cipher object with `.encrypt(plaintext)` and `.decrypt(ciphertext)` methods. The AAD parameter is passed at construction time. Length validation runs before the cipher is constructed, so wrong-length keys or nonces surface as `CryptoCipherError` rather than cryptic Noble-internal errors. Noble throws when the authentication tag mismatches during decryption; the catch block converts this to `CryptoCipherError(DECRYPT_FAILED)` with the algorithm name and ciphertext length as context.
|
||||
@@ -0,0 +1,67 @@
|
||||
# chacha20-poly1305
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
- [chacha20-poly1305.select](./chacha20-poly1305.select/chacha20-poly1305.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305.native](./impl/chacha20-poly1305.native/chacha20-poly1305.native.md) — <!-- begin:auto-docs --> _(no submodules)_ - [chacha20-poly1305.native](./impl/chacha20-poly1305.native/chacha20-poly1305.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305.noble](./impl/chacha20-poly1305.noble/chacha20-poly1305.noble.md) — <!-- begin:auto-docs --> _(no submodules)_ - [chacha20-poly1305.noble](./impl/chacha20-poly1305.noble/chacha20-poly1305.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305](./chacha20-poly1305.md) — <!-- begin:auto-docs --> _(no submodules)_- chacha20-poly1305.select — <!-- begin:auto-docs -->…
|
||||
- [chacha20-poly1305.select](./chacha20-poly1305.select/chacha20-poly1305.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305.native](./impl/chacha20-poly1305.native/chacha20-poly1305.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305.native](./impl/chacha20-poly1305.native/chacha20-poly1305.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305.noble](./impl/chacha20-poly1305.noble/chacha20-poly1305.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305.noble](./impl/chacha20-poly1305.noble/chacha20-poly1305.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305](./chacha20-poly1305.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305.select](./chacha20-poly1305.select/chacha20-poly1305.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305.native](./impl/chacha20-poly1305.native/chacha20-poly1305.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305.native](./impl/chacha20-poly1305.native/chacha20-poly1305.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305.noble](./impl/chacha20-poly1305.noble/chacha20-poly1305.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305.noble](./impl/chacha20-poly1305.noble/chacha20-poly1305.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305](./chacha20-poly1305.md) — <!-- begin:auto-docs --> _(no submodules)_- chacha20-poly1305.select — <…
|
||||
- [chacha20-poly1305.select](./chacha20-poly1305.select/chacha20-poly1305.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305.native](./impl/chacha20-poly1305.native/chacha20-poly1305.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305.native](./impl/chacha20-poly1305.native/chacha20-poly1305.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305.noble](./impl/chacha20-poly1305.noble/chacha20-poly1305.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305.noble](./impl/chacha20-poly1305.noble/chacha20-poly1305.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305](./chacha20-poly1305.md) — <!-- begin:auto-docs --> _(no submodules)_- chacha20-poly1305.select — <!-- begin:auto-docs -->…
|
||||
- [chacha20-poly1305.select](./chacha20-poly1305.select/chacha20-poly1305.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305.native](./impl/chacha20-poly1305.native/chacha20-poly1305.native.md) — <!-- begin:auto-docs --> _(no submodules)_ - [chacha20-poly1305.native](./impl/chacha20-poly1305.native/chacha20-poly1305.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305.noble](./impl/chacha20-poly1305.noble/chacha20-poly1305.noble.md) — <!-- begin:auto-docs --> _(no submodules)_ - [chacha20-poly1305.noble](./impl/chacha20-poly1305.noble/chacha20-poly1305.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [chacha20-poly1305](./chacha20-poly1305.md) — <!-- begin:auto-docs --> _(no submodules)_
|
||||
## Purpose
|
||||
|
||||
`chacha20-poly1305.ts` is the stable public entry point for the ChaCha20-Poly1305 AEAD primitive in `@suaveplan/crypto-cipher`. It re-exports everything from the generated `chacha20-poly1305.select.ts`, which forwards to either the Noble-backed or native implementation based on `crypto.config.ts`.
|
||||
|
||||
ChaCha20-Poly1305 (RFC 8439) is a stream-cipher-based AEAD algorithm that provides authenticated encryption without the hardware AES acceleration requirement. It is preferred over AES-GCM on platforms that lack AES-NI instructions (mobile CPUs, edge nodes, IoT devices). TLS 1.3 includes it as a mandatory cipher suite alongside AES-128-GCM and AES-256-GCM.
|
||||
|
||||
## Features
|
||||
|
||||
- `encrypt(key, nonce, plaintext, aad?)` — AEAD encryption; returns `ciphertext ‖ tag`.
|
||||
- `decrypt(key, nonce, ciphertext, aad?)` — AEAD decryption; throws `CryptoCipherError` on failure.
|
||||
- `KEY_LENGTH = 32` — 256-bit key.
|
||||
- `NONCE_LENGTH = 12` — 96-bit nonce.
|
||||
- `TAG_LENGTH = 16` — 128-bit Poly1305 authentication tag.
|
||||
- Key and nonce length validated before every operation.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { encrypt, decrypt, KEY_LENGTH, NONCE_LENGTH } from "@suaveplan/crypto-cipher/chacha20-poly1305";
|
||||
|
||||
const key = crypto.getRandomValues(new Uint8Array(KEY_LENGTH));
|
||||
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
|
||||
const plaintext = new TextEncoder().encode("chacha test");
|
||||
|
||||
const ciphertext = encrypt(key, nonce, plaintext);
|
||||
const recovered = decrypt(key, nonce, ciphertext);
|
||||
console.log(new TextDecoder().decode(recovered)); // "chacha test"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { encrypt, decrypt, KEY_LENGTH, NONCE_LENGTH } from "@suaveplan/crypto-cipher/chacha20-poly1305";
|
||||
|
||||
// Seal a message with a request ID as AAD.
|
||||
function sealMessage(key: Uint8Array, requestId: string, body: Uint8Array): {
|
||||
nonce: Uint8Array;
|
||||
sealed: Uint8Array;
|
||||
} {
|
||||
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
|
||||
const aad = new TextEncoder().encode(requestId);
|
||||
return { nonce, sealed: encrypt(key, nonce, body, aad) };
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
All symbols re-exported from `chacha20-poly1305.select.ts`:
|
||||
|
||||
- `encrypt(key, nonce, plaintext, aad?): Uint8Array`
|
||||
- `decrypt(key, nonce, ciphertext, aad?): Uint8Array`
|
||||
- `KEY_LENGTH: 32`
|
||||
- `NONCE_LENGTH: 12`
|
||||
- `TAG_LENGTH: 16`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
This file is a zero-logic re-export barrel providing the stable import path `@suaveplan/crypto-cipher/chacha20-poly1305`. ChaCha20-Poly1305 uses a 12-byte nonce, which is safe to use with random generation for most use cases — at 2⁹⁶ possible nonces, the birthday bound for collision is comfortably above typical message volumes. For higher message counts, consider XChaCha20-Poly1305, which uses a 24-byte nonce. Nonces must never be reused with the same key.
|
||||
@@ -0,0 +1,59 @@
|
||||
# chacha20-poly1305.select
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`chacha20-poly1305.select.ts` is the build-time primitive selector for ChaCha20-Poly1305 in `@suaveplan/crypto-cipher`. Generated by `bun scripts/gen-crypto-selects.ts`, it re-exports one backend — `chacha20-poly1305.noble.ts` or `chacha20-poly1305.native.ts` — based on the `chacha20-poly1305` entry in `crypto.config.ts`.
|
||||
|
||||
Callers import through `chacha20-poly1305.ts` and never reference this file directly. The selector decouples the public API from the concrete implementation, enabling backend switches with no changes to caller code.
|
||||
|
||||
## Features
|
||||
|
||||
- Re-exports `encrypt`, `decrypt`, `KEY_LENGTH`, `NONCE_LENGTH`, `TAG_LENGTH`.
|
||||
- Generated — never hand-edited.
|
||||
- Controlled by `crypto.config.ts`: set `chacha20-poly1305` to `'noble'` or `'native'`.
|
||||
- Enables dead-code elimination of the inactive backend at bundle time.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { encrypt, KEY_LENGTH, NONCE_LENGTH } from "@suaveplan/crypto-cipher/chacha20-poly1305";
|
||||
|
||||
const key = crypto.getRandomValues(new Uint8Array(KEY_LENGTH));
|
||||
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
|
||||
const ct = encrypt(key, nonce, new TextEncoder().encode("test"));
|
||||
console.log(ct.length); // 4 + 16 = 20
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { decrypt } from "@suaveplan/crypto-cipher/chacha20-poly1305";
|
||||
import { CryptoCipherError } from "@suaveplan/crypto-cipher/errors";
|
||||
import { CRYPTO_CIPHER_CODES } from "@suaveplan/crypto-cipher/errors";
|
||||
|
||||
function isDecryptError(err: unknown): err is CryptoCipherError {
|
||||
return err instanceof CryptoCipherError &&
|
||||
err.code === CRYPTO_CIPHER_CODES.DECRYPT_FAILED;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
Re-exports from the active implementation:
|
||||
|
||||
- `encrypt(key: Uint8Array, nonce: Uint8Array, plaintext: Uint8Array, aad?: Uint8Array): Uint8Array`
|
||||
- `decrypt(key: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array, aad?: Uint8Array): Uint8Array`
|
||||
- `KEY_LENGTH: 32`
|
||||
- `NONCE_LENGTH: 12`
|
||||
- `TAG_LENGTH: 16`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
This generated file contains `export * from "./impl/chacha20-poly1305.noble.js"` and is marked `/* GENERATED — DO NOT EDIT */`. The generator is idempotent and safe to run at any time. Do not add logic here — it will be overwritten on the next generator run.
|
||||
|
||||
### Selection Logic
|
||||
|
||||
At build time `crypto.config.ts` picks the Noble or native engine for the IETF ChaCha20-Poly1305 primitive; the selector routes every call to the chosen implementation. Authentication tag mismatch throws the shared `CryptoCipherError` with code `CRYPTO.CIPHER.TAG_MISMATCH`. Nonce is caller-supplied; the IETF 12-byte nonce variant is the one implemented here — the extended 24-byte XChaCha20 variant lives in `xchacha20-poly1305`.
|
||||
@@ -0,0 +1,63 @@
|
||||
# chacha20-poly1305.native
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`chacha20-poly1305.native.ts` is the native-backend placeholder for ChaCha20-Poly1305 in `@suaveplan/crypto-cipher`. It exports `encrypt`, `decrypt`, and the three length constants with the same types as the Noble implementation, but `encrypt` and `decrypt` throw `PrimitiveNotImplemented("chacha20-poly1305")`.
|
||||
|
||||
This stub maintains the two-implementation selector layout for ChaCha20-Poly1305. Unlike AES-GCM, ChaCha20-Poly1305 is not yet available as a native WebCrypto primitive in all environments, but it is available in libsodium and in the `@suaveplan/crypto-sodium` engine adapter. The stub keeps the door open for a platform-specific native binding.
|
||||
|
||||
## Features
|
||||
|
||||
- Exports `encrypt`, `decrypt`, `KEY_LENGTH = 32`, `NONCE_LENGTH = 12`, `TAG_LENGTH = 16`.
|
||||
- `encrypt` and `decrypt` throw `PrimitiveNotImplemented("chacha20-poly1305")`.
|
||||
- Constants are usable even when the cipher functions are not implemented.
|
||||
- Zero runtime dependencies beyond `@suaveplan/crypto-utils/errors`.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
// Active only when crypto.config.ts sets chacha20-poly1305 to 'native'.
|
||||
import { encrypt, KEY_LENGTH, NONCE_LENGTH } from "@suaveplan/crypto-cipher/chacha20-poly1305";
|
||||
|
||||
try {
|
||||
encrypt(new Uint8Array(KEY_LENGTH), new Uint8Array(NONCE_LENGTH), new Uint8Array(8));
|
||||
} catch (err) {
|
||||
// PrimitiveNotImplemented: chacha20-poly1305
|
||||
console.error(err);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { KEY_LENGTH, NONCE_LENGTH } from "@suaveplan/crypto-cipher/chacha20-poly1305";
|
||||
|
||||
// Allocate key and nonce buffers regardless of whether cipher functions are available.
|
||||
function allocate(): { key: Uint8Array; nonce: Uint8Array } {
|
||||
return {
|
||||
key: crypto.getRandomValues(new Uint8Array(KEY_LENGTH)),
|
||||
nonce: crypto.getRandomValues(new Uint8Array(NONCE_LENGTH)),
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `encrypt(_key, _nonce, _plaintext, _aad?): Uint8Array`
|
||||
|
||||
Throws `PrimitiveNotImplemented("chacha20-poly1305")`.
|
||||
|
||||
### `decrypt(_key, _nonce, _ciphertext, _aad?): Uint8Array`
|
||||
|
||||
Throws `PrimitiveNotImplemented("chacha20-poly1305")`.
|
||||
|
||||
### Constants
|
||||
|
||||
- `KEY_LENGTH = 32`, `NONCE_LENGTH = 12`, `TAG_LENGTH = 16`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The constants are defined as literal values (not imported from the Noble file) so both implementations are self-contained. This matches the pattern used by all other native stub files in the package. When a platform-native ChaCha20-Poly1305 binding is ready and RFC 8439 test vectors pass, the placeholder functions are replaced, parameter prefixes removed, and the selector regenerated.
|
||||
@@ -0,0 +1,79 @@
|
||||
# chacha20-poly1305.noble
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`chacha20-poly1305.noble.ts` is the Noble-backed concrete implementation of ChaCha20-Poly1305 in `@suaveplan/crypto-cipher`. It uses `chacha20poly1305` from `@noble/ciphers/chacha`, validates key and nonce lengths before every operation, and wraps decryption failures in typed `CryptoCipherError` instances.
|
||||
|
||||
ChaCha20-Poly1305 (RFC 8439) combines the ChaCha20 stream cipher with the Poly1305 MAC. The stream cipher generates a keystream that is XOR'd with the plaintext; the MAC provides authentication over both the ciphertext and any additional authenticated data. This file is the production implementation; `chacha20-poly1305.native.ts` is the placeholder.
|
||||
|
||||
## Features
|
||||
|
||||
- `encrypt(key, nonce, plaintext, aad?)` — validates lengths, returns `ciphertext ‖ 16-byte Poly1305 tag`.
|
||||
- `decrypt(key, nonce, ciphertext, aad?)` — validates lengths, verifies tag, throws `CryptoCipherError(DECRYPT_FAILED)` on mismatch.
|
||||
- Exports `KEY_LENGTH = 32`, `NONCE_LENGTH = 12`, `TAG_LENGTH = 16`.
|
||||
- Validation throws `CryptoCipherError(INVALID_KEY_LENGTH)` or `CryptoCipherError(INVALID_NONCE_LENGTH)` with structured context.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { encrypt, decrypt, KEY_LENGTH, NONCE_LENGTH } from "@suaveplan/crypto-cipher/chacha20-poly1305";
|
||||
|
||||
const key = crypto.getRandomValues(new Uint8Array(KEY_LENGTH));
|
||||
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
|
||||
const pt = new TextEncoder().encode("RFC 8439");
|
||||
|
||||
const ct = encrypt(key, nonce, pt);
|
||||
const recovered = decrypt(key, nonce, ct);
|
||||
console.log(new TextDecoder().decode(recovered)); // "RFC 8439"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { encrypt, KEY_LENGTH, NONCE_LENGTH } from "@suaveplan/crypto-cipher/chacha20-poly1305";
|
||||
import { CryptoCipherError } from "@suaveplan/crypto-cipher/errors";
|
||||
|
||||
// Encrypt with metadata bound as AAD.
|
||||
function encryptPacket(
|
||||
key: Uint8Array,
|
||||
sequenceNumber: number,
|
||||
payload: Uint8Array
|
||||
): Uint8Array {
|
||||
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
|
||||
const aad = new Uint8Array(4);
|
||||
new DataView(aad.buffer).setUint32(0, sequenceNumber, false);
|
||||
return new Uint8Array([...nonce, ...encrypt(key, nonce, payload, aad)]);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `encrypt(key: Uint8Array, nonce: Uint8Array, plaintext: Uint8Array, aad?: Uint8Array): Uint8Array`
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `key` | `Uint8Array` | 32-byte symmetric key. |
|
||||
| `nonce` | `Uint8Array` | 12-byte nonce. Must not be reused with the same key. |
|
||||
| `plaintext` | `Uint8Array` | Data to encrypt. |
|
||||
| `aad` | `Uint8Array` (optional) | Authenticated but not encrypted associated data. |
|
||||
|
||||
**Returns:** `ciphertext ‖ tag` of length `plaintext.length + 16`.
|
||||
|
||||
**Throws:** `CryptoCipherError(INVALID_KEY_LENGTH)` or `CryptoCipherError(INVALID_NONCE_LENGTH)`.
|
||||
|
||||
### `decrypt(key: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array, aad?: Uint8Array): Uint8Array`
|
||||
|
||||
**Returns:** Decrypted plaintext.
|
||||
|
||||
**Throws:** `CryptoCipherError(DECRYPT_FAILED)` on authentication failure; `CryptoCipherError(INVALID_KEY_LENGTH | INVALID_NONCE_LENGTH)` on length violations.
|
||||
|
||||
### Constants
|
||||
|
||||
- `KEY_LENGTH = 32`, `NONCE_LENGTH = 12`, `TAG_LENGTH = 16`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Noble's `chacha20poly1305(key, nonce, aad)` follows the same construction pattern as `gcm`: it returns an object with `.encrypt` and `.decrypt`. The AAD is passed at construction time and bound into the Poly1305 tag computation per RFC 8439 §2.8. ChaCha20 is immune to timing attacks related to data values (unlike AES's S-box lookups on platforms without AES-NI), making it the preferred choice for constant-time environments. RFC 8439 §A.5 test vectors pass in the test suite.
|
||||
@@ -0,0 +1,69 @@
|
||||
# xchacha20-poly1305.native
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`xchacha20-poly1305.native.ts` is the native-backend placeholder for XChaCha20-Poly1305 in `@suaveplan/crypto-cipher`. It exports `encrypt`, `decrypt`, `KEY_LENGTH`, `NONCE_LENGTH`, and `TAG_LENGTH` with the same types as the Noble implementation, but `encrypt` and `decrypt` throw `PrimitiveNotImplemented("xchacha20-poly1305")`.
|
||||
|
||||
XChaCha20-Poly1305 is available natively in libsodium as `crypto_secretbox_xchacha20poly1305` and is accessible through the `@suaveplan/crypto-sodium` engine. A future native implementation of this placeholder could delegate to libsodium or a Bun native binding. This stub keeps the selector layout complete until that work is ready.
|
||||
|
||||
## Features
|
||||
|
||||
- Exports `encrypt`, `decrypt`, `KEY_LENGTH = 32`, `NONCE_LENGTH = 24`, `TAG_LENGTH = 16`.
|
||||
- `encrypt` and `decrypt` throw `PrimitiveNotImplemented("xchacha20-poly1305")`.
|
||||
- Constants are usable regardless of implementation status.
|
||||
- Zero runtime dependencies beyond `@suaveplan/crypto-utils/errors`.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
// Active only when crypto.config.ts sets xchacha20-poly1305 to 'native'.
|
||||
import { encrypt, KEY_LENGTH, NONCE_LENGTH } from "@suaveplan/crypto-cipher/xchacha20-poly1305";
|
||||
|
||||
try {
|
||||
encrypt(
|
||||
new Uint8Array(KEY_LENGTH),
|
||||
new Uint8Array(NONCE_LENGTH),
|
||||
new Uint8Array(8)
|
||||
);
|
||||
} catch (err) {
|
||||
// PrimitiveNotImplemented: xchacha20-poly1305
|
||||
console.error(err);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { KEY_LENGTH, NONCE_LENGTH } from "@suaveplan/crypto-cipher/xchacha20-poly1305";
|
||||
|
||||
// Buffer allocation is safe regardless of which backend is active.
|
||||
function generateKeyMaterial(): { key: Uint8Array; nonce: Uint8Array } {
|
||||
return {
|
||||
key: crypto.getRandomValues(new Uint8Array(KEY_LENGTH)),
|
||||
nonce: crypto.getRandomValues(new Uint8Array(NONCE_LENGTH)),
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `encrypt(_key, _nonce, _plaintext, _aad?): Uint8Array`
|
||||
|
||||
Throws `PrimitiveNotImplemented("xchacha20-poly1305")`.
|
||||
|
||||
### `decrypt(_key, _nonce, _ciphertext, _aad?): Uint8Array`
|
||||
|
||||
Throws `PrimitiveNotImplemented("xchacha20-poly1305")`.
|
||||
|
||||
### Constants
|
||||
|
||||
- `KEY_LENGTH = 32` — 256-bit key.
|
||||
- `NONCE_LENGTH = 24` — 192-bit extended nonce.
|
||||
- `TAG_LENGTH = 16` — 128-bit Poly1305 tag.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `NONCE_LENGTH = 24` constant is explicitly defined here (not imported from the Noble file) to keep both implementations self-contained. This is consistent with the pattern used across all native stub files in the package. When the native binding is ready and IETF XChaCha20-Poly1305 draft test vectors pass, the placeholder functions are replaced and the selector regenerated. The `PrimitiveNotImplemented` error from `@suaveplan/crypto-utils/errors` is a `SuaveplanError` subclass and participates in the full structured error system including cause-chain traversal and OTel span events.
|
||||
@@ -0,0 +1,77 @@
|
||||
# xchacha20-poly1305.noble
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`xchacha20-poly1305.noble.ts` is the Noble-backed concrete implementation of XChaCha20-Poly1305 in `@suaveplan/crypto-cipher`. It uses `xchacha20poly1305` from `@noble/ciphers/chacha`, validates key and nonce lengths before every call, and wraps decryption failures in `CryptoCipherError`.
|
||||
|
||||
XChaCha20-Poly1305 derives a subkey from the first 16 bytes of the 24-byte nonce using HChaCha20, then runs ChaCha20-Poly1305 with the remaining 8-byte nonce. This construction allows safe random-nonce generation for very large message volumes. This file is the production implementation; `xchacha20-poly1305.native.ts` is the placeholder.
|
||||
|
||||
## Features
|
||||
|
||||
- `encrypt(key, nonce, plaintext, aad?)` — validates lengths, returns `ciphertext ‖ 16-byte Poly1305 tag`.
|
||||
- `decrypt(key, nonce, ciphertext, aad?)` — validates lengths, verifies tag, throws `CryptoCipherError(DECRYPT_FAILED)` on mismatch.
|
||||
- Exports `KEY_LENGTH = 32`, `NONCE_LENGTH = 24`, `TAG_LENGTH = 16`.
|
||||
- Throws `CryptoCipherError(INVALID_KEY_LENGTH)` or `CryptoCipherError(INVALID_NONCE_LENGTH)` on violations.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { encrypt, decrypt, KEY_LENGTH, NONCE_LENGTH } from "@suaveplan/crypto-cipher/xchacha20-poly1305";
|
||||
|
||||
const key = crypto.getRandomValues(new Uint8Array(KEY_LENGTH));
|
||||
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
|
||||
const pt = new TextEncoder().encode("hello xchacha");
|
||||
|
||||
const ct = encrypt(key, nonce, pt);
|
||||
const recovered = decrypt(key, nonce, ct);
|
||||
console.log(new TextDecoder().decode(recovered)); // "hello xchacha"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { encrypt, KEY_LENGTH, NONCE_LENGTH } from "@suaveplan/crypto-cipher/xchacha20-poly1305";
|
||||
|
||||
// Encrypt a record for long-term storage with AAD from record metadata.
|
||||
function encryptRecord(
|
||||
key: Uint8Array,
|
||||
record: Uint8Array,
|
||||
metadataJson: string
|
||||
): { nonce: Uint8Array; ciphertext: Uint8Array } {
|
||||
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
|
||||
const aad = new TextEncoder().encode(metadataJson);
|
||||
return { nonce, ciphertext: encrypt(key, nonce, record, aad) };
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `encrypt(key: Uint8Array, nonce: Uint8Array, plaintext: Uint8Array, aad?: Uint8Array): Uint8Array`
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `key` | `Uint8Array` | 32-byte key. |
|
||||
| `nonce` | `Uint8Array` | 24-byte extended nonce. |
|
||||
| `plaintext` | `Uint8Array` | Data to encrypt. |
|
||||
| `aad` | `Uint8Array` (optional) | Authenticated but not encrypted data. |
|
||||
|
||||
**Returns:** `ciphertext ‖ tag` of length `plaintext.length + 16`.
|
||||
|
||||
**Throws:** `CryptoCipherError(INVALID_KEY_LENGTH | INVALID_NONCE_LENGTH)`.
|
||||
|
||||
### `decrypt(key: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array, aad?: Uint8Array): Uint8Array`
|
||||
|
||||
**Returns:** Decrypted plaintext.
|
||||
|
||||
**Throws:** `CryptoCipherError(DECRYPT_FAILED)` on tag mismatch; `CryptoCipherError(INVALID_KEY_LENGTH | INVALID_NONCE_LENGTH)` on length violations.
|
||||
|
||||
### Constants
|
||||
|
||||
- `KEY_LENGTH = 32`, `NONCE_LENGTH = 24`, `TAG_LENGTH = 16`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Noble's `xchacha20poly1305(key, nonce, aad)` handles the HChaCha20 subkey derivation step and the subsequent ChaCha20-Poly1305 call transparently. The NONCE_LENGTH of 24 is the only structural difference from `chacha20-poly1305.noble.ts`; the validation logic and error handling are otherwise identical. The extended nonce makes random nonce generation safe for up to approximately 2⁸⁰ messages per key before collision probability becomes non-negligible. Draft test vectors from the IETF Crypto Forum Research Group pass in the test suite.
|
||||
@@ -0,0 +1,73 @@
|
||||
# xchacha20-poly1305
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
- [xchacha20-poly1305.native](./impl/xchacha20-poly1305.native/xchacha20-poly1305.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305.noble](./impl/xchacha20-poly1305.noble/xchacha20-poly1305.noble.md) — <!-- begin:auto-docs --> _(no submodules)_ - [xchacha20-poly1305.noble](./impl/xchacha20-poly1305.noble/xchacha20-poly1305.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305.select](./xchacha20-poly1305.select/xchacha20-poly1305.select.md) — <!-- begin:auto-docs --> _(no submodules)_ - [xchacha20-poly1305.select](./xchacha20-poly1305.select/xchacha20-poly1305.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305](./xchacha20-poly1305.md) — <!-- begin:auto-docs --> _(no submodules)_- xchacha20-poly1305.native — <!-- begin:auto-docs -->…
|
||||
- [xchacha20-poly1305.native](./impl/xchacha20-poly1305.native/xchacha20-poly1305.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305.noble](./impl/xchacha20-poly1305.noble/xchacha20-poly1305.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305.noble](./impl/xchacha20-poly1305.noble/xchacha20-poly1305.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305.select](./xchacha20-poly1305.select/xchacha20-poly1305.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305.select](./xchacha20-poly1305.select/xchacha20-poly1305.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305](./xchacha20-poly1305.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305.native](./impl/xchacha20-poly1305.native/xchacha20-poly1305.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305.noble](./impl/xchacha20-poly1305.noble/xchacha20-poly1305.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305.noble](./impl/xchacha20-poly1305.noble/xchacha20-poly1305.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305.select](./xchacha20-poly1305.select/xchacha20-poly1305.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305.select](./xchacha20-poly1305.select/xchacha20-poly1305.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305](./xchacha20-poly1305.md) — <!-- begin:auto-docs --> _(no submodules)_- xchacha20-poly1305.native —…
|
||||
- [xchacha20-poly1305.native](./impl/xchacha20-poly1305.native/xchacha20-poly1305.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305.noble](./impl/xchacha20-poly1305.noble/xchacha20-poly1305.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305.noble](./impl/xchacha20-poly1305.noble/xchacha20-poly1305.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305.select](./xchacha20-poly1305.select/xchacha20-poly1305.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305.select](./xchacha20-poly1305.select/xchacha20-poly1305.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305](./xchacha20-poly1305.md) — <!-- begin:auto-docs --> _(no submodules)_- xchacha20-poly1305.native — <!-- begin:auto-docs --…
|
||||
- [xchacha20-poly1305.native](./impl/xchacha20-poly1305.native/xchacha20-poly1305.native.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305.noble](./impl/xchacha20-poly1305.noble/xchacha20-poly1305.noble.md) — <!-- begin:auto-docs --> _(no submodules)_ - [xchacha20-poly1305.noble](./impl/xchacha20-poly1305.noble/xchacha20-poly1305.noble.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305.select](./xchacha20-poly1305.select/xchacha20-poly1305.select.md) — <!-- begin:auto-docs --> _(no submodules)_ - [xchacha20-poly1305.select](./xchacha20-poly1305.select/xchacha20-poly1305.select.md) — <!-- begin:auto-docs --> _(no submodules)_- [xchacha20-poly1305](./xchacha20-poly1305.md) — <!-- begin:auto-docs --> _(no submodules)_
|
||||
## Purpose
|
||||
|
||||
`xchacha20-poly1305.ts` is the stable public entry point for the XChaCha20-Poly1305 AEAD primitive in `@suaveplan/crypto-cipher`. It re-exports from the generated `xchacha20-poly1305.select.ts`, which forwards to the Noble-backed or native implementation based on `crypto.config.ts`.
|
||||
|
||||
XChaCha20-Poly1305 is an extension of ChaCha20-Poly1305 with a 24-byte (192-bit) nonce instead of the standard 12-byte nonce. The extended nonce allows safe random generation at high message volumes — the birthday bound for nonce collision with random 24-byte nonces is approximately 2⁸⁰ messages per key, compared to 2⁴⁸ for 12-byte nonces. It is used in libsodium's `crypto_secretbox_xsalsa20poly1305` and is the default AEAD in protocols like Signal's `@suaveplan/crypto` façade.
|
||||
|
||||
## Features
|
||||
|
||||
- `encrypt(key, nonce, plaintext, aad?)` — AEAD encryption; returns `ciphertext ‖ tag`.
|
||||
- `decrypt(key, nonce, ciphertext, aad?)` — AEAD decryption; throws `CryptoCipherError` on failure.
|
||||
- `KEY_LENGTH = 32` — 256-bit key.
|
||||
- `NONCE_LENGTH = 24` — 192-bit extended nonce (distinguishes from standard ChaCha20-Poly1305).
|
||||
- `TAG_LENGTH = 16` — 128-bit Poly1305 authentication tag.
|
||||
- Key and nonce length validated before every operation.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { encrypt, decrypt, KEY_LENGTH, NONCE_LENGTH } from "@suaveplan/crypto-cipher/xchacha20-poly1305";
|
||||
|
||||
const key = crypto.getRandomValues(new Uint8Array(KEY_LENGTH));
|
||||
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH)); // 24 bytes
|
||||
const plaintext = new TextEncoder().encode("xchacha test");
|
||||
|
||||
const ciphertext = encrypt(key, nonce, plaintext);
|
||||
const recovered = decrypt(key, nonce, ciphertext);
|
||||
console.log(new TextDecoder().decode(recovered)); // "xchacha test"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { encrypt, decrypt, KEY_LENGTH, NONCE_LENGTH } from "@suaveplan/crypto-cipher/xchacha20-poly1305";
|
||||
|
||||
// Store-and-forward encryption with random nonce prepended to ciphertext.
|
||||
function encryptForStorage(key: Uint8Array, plaintext: Uint8Array): Uint8Array {
|
||||
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
|
||||
const ct = encrypt(key, nonce, plaintext);
|
||||
const out = new Uint8Array(NONCE_LENGTH + ct.length);
|
||||
out.set(nonce, 0);
|
||||
out.set(ct, NONCE_LENGTH);
|
||||
return out;
|
||||
}
|
||||
|
||||
function decryptFromStorage(key: Uint8Array, blob: Uint8Array): Uint8Array {
|
||||
const nonce = blob.slice(0, NONCE_LENGTH);
|
||||
const ct = blob.slice(NONCE_LENGTH);
|
||||
return decrypt(key, nonce, ct);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
All symbols re-exported from `xchacha20-poly1305.select.ts`:
|
||||
|
||||
- `encrypt(key, nonce, plaintext, aad?): Uint8Array`
|
||||
- `decrypt(key, nonce, ciphertext, aad?): Uint8Array`
|
||||
- `KEY_LENGTH: 32`
|
||||
- `NONCE_LENGTH: 24`
|
||||
- `TAG_LENGTH: 16`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
This file is a zero-logic re-export barrel. The distinguishing constant is `NONCE_LENGTH = 24`, not 12 as in standard ChaCha20-Poly1305. Prepending the random nonce to the ciphertext is a common pattern for store-and-forward scenarios, as shown in the advanced example. The nonce is not secret and safe to transmit alongside the ciphertext.
|
||||
@@ -0,0 +1,56 @@
|
||||
# xchacha20-poly1305.select
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`xchacha20-poly1305.select.ts` is the build-time primitive selector for XChaCha20-Poly1305 in `@suaveplan/crypto-cipher`. Generated by `bun scripts/gen-crypto-selects.ts`, it re-exports one backend — `xchacha20-poly1305.noble.ts` or `xchacha20-poly1305.native.ts` — based on the `xchacha20-poly1305` field in `crypto.config.ts`.
|
||||
|
||||
Callers import through the stable `xchacha20-poly1305.ts` entry point. This selector level means backend changes require only regenerating this file; all call sites above it remain unchanged. Currently the Noble backend is selected.
|
||||
|
||||
## Features
|
||||
|
||||
- Re-exports `encrypt`, `decrypt`, `KEY_LENGTH`, `NONCE_LENGTH`, `TAG_LENGTH`.
|
||||
- Generated — never hand-edited.
|
||||
- Controlled by `crypto.config.ts`: set `xchacha20-poly1305` to `'noble'` or `'native'`.
|
||||
- Enables dead-code elimination of the inactive backend.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { KEY_LENGTH, NONCE_LENGTH } from "@suaveplan/crypto-cipher/xchacha20-poly1305";
|
||||
|
||||
console.log(KEY_LENGTH); // 32
|
||||
console.log(NONCE_LENGTH); // 24 (XChaCha20's extended nonce)
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { encrypt, decrypt, NONCE_LENGTH } from "@suaveplan/crypto-cipher/xchacha20-poly1305";
|
||||
|
||||
// Round-trip with random nonce.
|
||||
function roundTrip(key: Uint8Array, msg: Uint8Array): Uint8Array {
|
||||
const nonce = crypto.getRandomValues(new Uint8Array(NONCE_LENGTH));
|
||||
return decrypt(key, nonce, encrypt(key, nonce, msg));
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
Re-exports from the active implementation:
|
||||
|
||||
- `encrypt(key: Uint8Array, nonce: Uint8Array, plaintext: Uint8Array, aad?: Uint8Array): Uint8Array`
|
||||
- `decrypt(key: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array, aad?: Uint8Array): Uint8Array`
|
||||
- `KEY_LENGTH: 32`
|
||||
- `NONCE_LENGTH: 24`
|
||||
- `TAG_LENGTH: 16`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
This file is a generated one-liner marked `/* GENERATED — DO NOT EDIT */`. The `NONCE_LENGTH` of 24 is the defining characteristic of XChaCha20 relative to standard ChaCha20. The generator resolves the `xchacha20-poly1305` backend from `crypto.config.ts` and emits the correct `export *` path. Regenerating is idempotent.
|
||||
|
||||
### Selection Logic
|
||||
|
||||
At build time `crypto.config.ts` picks the Noble or native engine for XChaCha20-Poly1305 — the 24-byte-nonce variant suitable for random-nonce encryption of large numbers of messages under a single key. The selector routes every call to the chosen impl and throws `PrimitiveNotImplemented` when the selected engine has no native binding yet. Nonce is caller-supplied in every path.
|
||||
Reference in New Issue
Block a user