Skip to content
Effect 4.0 is here. One ecosystem. Zero dependencies.

Aead unstable

Authenticated encryption: AES-GCM, ChaCha20-Poly1305, and XChaCha20-Poly1305.

The Aead service generates keys, encrypts, and decrypts. The top-level key, fromCryptoKey, and toBytes functions build and export keys without a service.

Ciphertext is ciphertext‖tag with a 16-byte tag for every algorithm, the layout WebCrypto produces. A tampered ciphertext, tag, or additional data fails decryption with a DecryptionFailed reason that carries no detail.

AES-GCM runs on crypto.subtle. ChaCha20-Poly1305 and XChaCha20-Poly1305 run a pure TypeScript implementation vendored from @noble/ciphers 2.4.0, because WebCrypto does not provide them everywhere. Cure53 audited @noble/ciphers 1.0.0 in 2024.

Keys are opaque. They hold either key bytes or, for AES-GCM, a CryptoKey, display only their algorithm, and compare by reference.

Gotchas

Never reuse a nonce with the same key. Random 12-byte nonces risk a collision after about 2^32 messages under one key: use a counter, or XChaCha20-Poly1305, whose 24-byte nonces can be random.

15 exports Since v4.0.0 Source

Algorithms

aes128Gcm

unstable
Since v4.0.0

AES-128-GCM, with a 16-byte key and a 12-byte nonce.

Signature

declare const aes128Gcm: AesGcm

aes256Gcm

unstable
Since v4.0.0

AES-256-GCM, with a 32-byte key and a 12-byte nonce.

Signature

declare const aes256Gcm: AesGcm
Since v4.0.0

ChaCha20-Poly1305 (RFC 8439), with a 32-byte key and a 12-byte nonce.

Details

Runs the pure TypeScript implementation under layerWebCrypto, even where crypto.subtle provides it, so every runtime takes one code path.

Signature

declare const chacha20Poly1305: Algorithm
Since v4.0.0

XChaCha20-Poly1305 (draft-irtf-cfrg-xchacha-03), with a 32-byte key and a 24-byte nonce.

When to use

Use when nonces are generated randomly, since 24-byte nonces make collisions negligible. The output is compatible with libsodium's crypto_aead_xchacha20poly1305_ietf.

Signature

declare const xchacha20Poly1305: Algorithm

Constructors

Since v4.0.0

Builds an AES-GCM key from an existing CryptoKey, such as a non-extractable one.

Details

The CryptoKey must be a secret AES-GCM key of the algorithm's length with both the encrypt and decrypt usages, since a key reaches both operations. Otherwise the constructor fails with an InvalidKey reason. It must come from the Crypto object of the WebCrypto reference.

ChaCha20-Poly1305 and XChaCha20-Poly1305 never run on crypto.subtle, so passing them is a type error.

Signature

declare function fromCryptoKey<A extends AesGcm>(algorithm: A, cryptoKey: CryptoKey): Effect<Key<A>, CryptoError>

key

unstable
Since v4.0.0

Builds a key from raw key bytes.

Details

Copies the bytes, including Node.js Buffer inputs. A length other than the algorithm's key length fails with an InvalidKey reason. AES-192 is not supported.

Signature

declare function key<A extends Algorithm>(algorithm: A, bytes: Uint8Array): Effect<Key<A>, CryptoError>

Converting

toBytes

unstable
Since v4.0.0

Exports a key's bytes.

Details

Returns a fresh Uint8Array that shares no memory with the key. A CryptoKey-backed key is exported on the WebCrypto reference, and fails with a NotExtractable reason when the CryptoKey is not extractable.

Signature

declare function toBytes(key: Key): Effect<Uint8Array<ArrayBufferLike>, CryptoError>

Guards

isKey

unstable
Since v4.0.0

Checks whether a value is a Key.

Signature

declare function isKey(u: unknown): u is Key<Algorithm>

Layers

Since v4.0.0

Layer that provides Aead on the WebCrypto reference.

Details

AES-GCM runs on crypto.subtle. The layer imports a byte-backed key on first use and caches the CryptoKey. ChaCha20-Poly1305 and XChaCha20-Poly1305 always run the pure TypeScript implementation, with no feature detection. Generated keys come from SecureRandom, so a seeded SecureRandom gives reproducible keys.

Signature

declare const layerWebCrypto: Layer.Layer<Aead, never, SecureRandom.SecureRandom>
Since v4.0.0

Builds an Aead layer on the WebCrypto reference that runs the given implementations for byte-backed keys.

When to use

Use to run an algorithm on a runtime's native code, as NodeCrypto.layerAead does for ChaCha20-Poly1305 with node:crypto.

Details

implementations maps algorithm names to implementations that replace crypto.subtle or the algorithm's pure TypeScript code. They follow the Implementation contract and run only for byte-backed keys, after the layer has checked the nonce, plaintext, and ciphertext lengths, so errors are those of layerWebCrypto. CryptoKey-backed keys run on crypto.subtle, and generated keys come from SecureRandom. Without implementations, the layer is layerWebCrypto.

Gotchas

An implementation receives the key's own bytes, not a copy, so it must not modify or keep them.

Signature

declare function layerWebCryptoWith(options: {
readonly implementations?: Partial<Record<"aes128Gcm" | "aes256Gcm" | "chacha20Poly1305" | "xchacha20Poly1305", Implementation>>;
}): Layer<Aead, never, SecureRandom>

Example Running ChaCha20-Poly1305 natively

import { Layer } from "effect"
import { Aead, SecureRandom } from "effect/crypto"
declare const nativeChacha: Aead.Implementation
const layer = Aead.layerWebCryptoWith({
implementations: { chacha20Poly1305: nativeChacha }
}).pipe(Layer.provide(SecureRandom.layerWebCrypto))

Models

AesGcm

interface unstable
Since v4.0.0

AES-GCM, from aes128Gcm or aes256Gcm: the algorithms that run on crypto.subtle, and the only ones fromCryptoKey accepts.

Signature

interface AesGcm extends Algorithm {
readonly implementation: undefined;
readonly name: "aes128Gcm" | "aes256Gcm";
readonly nonceLength: 12;
}

Algorithm

interface unstable
Since v4.0.0

An authenticated encryption algorithm, such as aes256Gcm.

Details

keyLength and nonceLength are in bytes. implementation is the pure TypeScript code for algorithms that WebCrypto does not provide, which a layer calls when it has no native mapping for name.

Signature

interface Algorithm {
readonly implementation: Implementation | undefined;
readonly keyLength: 16 | 32;
readonly name: "aes128Gcm" | "aes256Gcm" | "chacha20Poly1305" | "xchacha20Poly1305";
readonly nonceLength: 12 | 24;
}

Implementation

interface unstable
Since v4.0.0

The synchronous code of an algorithm: the pure TypeScript code of one that WebCrypto does not provide, or a native implementation passed to layerWebCryptoWith.

Details

encrypt returns ciphertext‖tag. decrypt takes ciphertext‖tag and returns undefined when the tag does not match. Both receive arguments the layer has already checked.

Signature

interface Implementation {
readonly decrypt: (key: Uint8Array, nonce: Uint8Array, ciphertext: Uint8Array, aad: Uint8Array) => Uint8Array<ArrayBufferLike> | undefined;
readonly encrypt: (key: Uint8Array, nonce: Uint8Array, plaintext: Uint8Array, aad: Uint8Array) => Uint8Array;
}

Key

interface unstable
Since v4.0.0

An opaque authenticated encryption key for the algorithm A.

Details

The key holds either key bytes or a CryptoKey from fromCryptoKey. toString, toJSON, and inspect show only the algorithm. Keys compare by reference and do not implement Equal, because comparing key bytes is not constant time.

Signature

interface Key<out A extends Algorithm = Algorithm> extends Inspectable {
readonly "~effect/crypto/Aead/Key": "~effect/crypto/Aead/Key";
readonly algorithm: A;
}

Services

Aead

unstable
Since v4.0.0

Service that generates keys, encrypts, and decrypts with authentication.

Signature

declare class Aead extends Shape<"effect/crypto/Aead", {
readonly decrypt: (key: Key, ciphertext: Uint8Array, options: {
readonly aad?: Uint8Array<ArrayBufferLike>;
readonly nonce: Uint8Array;
}) => Effect<Uint8Array<ArrayBufferLike>, CryptoError>;
readonly encrypt: (key: Key, plaintext: Uint8Array, options: {
readonly aad?: Uint8Array<ArrayBufferLike>;
readonly nonce: Uint8Array;
}) => Effect<Uint8Array<ArrayBufferLike>, CryptoError>;
readonly generateKey: <A extends Algorithm>(algorithm: A) => Effect<Key<A>, CryptoError>;
}, this> {
constructor(_: never);
}

Example Encrypting with XChaCha20-Poly1305

import { Effect, Layer } from "effect"
import { Aead, SecureRandom } from "effect/crypto"
const program = Effect.gen(function*() {
const aead = yield* Aead.Aead
const random = yield* SecureRandom.SecureRandom
const key = yield* aead.generateKey(Aead.xchacha20Poly1305)
const nonce = yield* random.randomBytes(24)
const sealed = yield* aead.encrypt(key, new TextEncoder().encode("secret"), { nonce })
const opened = yield* aead.decrypt(key, sealed, { nonce })
return [sealed.length, new TextDecoder().decode(opened)]
})
const layer = Aead.layerWebCrypto.pipe(Layer.provideMerge(SecureRandom.layerWebCrypto))
await Effect.runPromise(program.pipe(Effect.provide(layer))) // => [22, "secret"]