Crypto
The Crypto service is the platform’s cryptographically secure source of
randomness, plus message digests and UUID generation. Reach for it when the
output must be unguessable: session tokens, password salts, API keys, secure
identifiers, or hashing file contents.
This is deliberately separate from the Random module. Random is a fast,
seedable pseudo-random generator — perfect for simulations, sampling, and
deterministic tests, but not safe for secrets. Crypto is backed by the
host platform’s CSPRNG (node:crypto on Node, the Web Crypto API in the
browser) and should be used whenever security matters.
import { NodeRuntime, NodeServices } from "@effect/platform-node"import { Crypto, Effect } from "effect"
const program = Effect.gen(function*() { const crypto = yield* Crypto.Crypto
// 16 cryptographically secure random bytes const bytes = yield* crypto.randomBytes(16)
// A sortable, secure identifier const id = yield* crypto.randomUUIDv7
return { bytes, id }})
// `NodeServices.layer` bundles Crypto with FileSystem, Path, Terminal, etc.NodeRuntime.runMain(program.pipe(Effect.provide(NodeServices.layer)))You retrieve the service with yield* Crypto.Crypto and provide it at the edge
of your program. On Node either provide the aggregate NodeServices.layer
(which includes Crypto) or the focused NodeCrypto.layer:
import { NodeCrypto, NodeRuntime } from "@effect/platform-node"import { Crypto, Effect } from "effect"
const program = Effect.gen(function*() { const crypto = yield* Crypto.Crypto return yield* crypto.randomUUIDv4})
// Provide just the Crypto serviceNodeRuntime.runMain(program.pipe(Effect.provide(NodeCrypto.layer)))Secure random bytes and tokens
Section titled “Secure random bytes and tokens”randomBytes(size) returns size cryptographically secure bytes as a
Uint8Array. To turn those bytes into a transmittable token, encode them with
the Encoding module — hex for fixed-width IDs,
base64url for compact URL-safe tokens.
import { Crypto, Effect, Encoding } from "effect"
const makeToken = Effect.gen(function*() { const crypto = yield* Crypto.Crypto
// 32 bytes = 256 bits of entropy: a solid default for session tokens const bytes = yield* crypto.randomBytes(32)
// Hex: 64 lowercase characters const hex = Encoding.encodeHex(bytes) // => "a3f1...c0" (64 chars)
// Base64url: shorter, safe in URLs and cookies (no +, /, or =) const urlSafe = Encoding.encodeBase64Url(bytes) // => "o_HK...wA" (~43 chars)
return { hex, urlSafe }})Hashing and digests
Section titled “Hashing and digests”digest(algorithm, data) computes a one-way cryptographic hash of a byte
array. The supported algorithms are described by the DigestAlgorithm
type: "SHA-1", "SHA-256", "SHA-384", and "SHA-512".
import { Crypto, Effect, Encoding } from "effect"
const hashContents = (data: Uint8Array) => Effect.gen(function*() { const crypto = yield* Crypto.Crypto
// SHA-256 produces 32 bytes const digest = yield* crypto.digest("SHA-256", data)
// Present the digest as a hex string, as most tools do return Encoding.encodeHex(digest) // => "e3b0c44298fc1c149afbf4c8996fb924..." (64 hex chars) })A realistic use is fingerprinting a file’s bytes (read with the FileSystem service):
import { Crypto, Effect, Encoding, FileSystem } from "effect"
const fileChecksum = (path: string) => Effect.gen(function*() { const fs = yield* FileSystem.FileSystem const crypto = yield* Crypto.Crypto
const bytes = yield* fs.readFile(path) const digest = yield* crypto.digest("SHA-256", bytes)
return Encoding.encodeHex(digest) })Secure UUIDs
Section titled “Secure UUIDs”Crypto generates UUIDs from secure random bytes. Two versions are available.
import { Crypto, Effect } from "effect"
const ids = Effect.gen(function*() { const crypto = yield* Crypto.Crypto
const v4 = yield* crypto.randomUUIDv4 // => "f47ac10b-58cc-4372-a567-0e02b2c3d479" (random)
const v7 = yield* crypto.randomUUIDv7 // => "018f6c1e-7b2a-7c3d-9e4f-0a1b2c3d4e5f" (time-ordered)
return { v4, v7 }})randomUUIDv4— fully random. Use it when you just need a unique, unpredictable identifier with no ordering requirement.randomUUIDv7— embeds a millisecond Unix timestamp in the high bits, so IDs generated later sort lexicographically after earlier ones. Prefer v7 for database primary keys and any place where roughly time-sortable IDs improve index locality. (The timestamp comes from Effect’sClock, so it isTestClock-controllable in tests.)
Secure numeric randomness
Section titled “Secure numeric randomness”Crypto exposes the same numeric helpers as the Random module, but every
value is drawn from the secure byte source rather than a seeded PRNG. Use these
when the number itself must be unpredictable (e.g. a random delay used as a
security measure, a shuffled draw with stakes).
import { Crypto, Effect } from "effect"
const draws = Effect.gen(function*() { const crypto = yield* Crypto.Crypto
const d = yield* crypto.random // 0 <= d < 1 const flip = yield* crypto.randomBoolean // true | false const die = yield* crypto.randomIntBetween(1, 6) // 1..6 inclusive const hand = yield* crypto.randomShuffle([1, 2, 3, 4, 5])
return { d, flip, die, hand }})Errors
Section titled “Errors”The fallible operations fail with PlatformError, the shared
error type for platform services. Its reason field is either a BadArgument
(invalid input rejected before the operation, e.g. a negative randomBytes
size) or a SystemError (a failure reported by the host platform). Match on it
like any tagged error:
import { Crypto, Effect } from "effect"
const program = Effect.gen(function*() { const crypto = yield* Crypto.Crypto return yield* crypto.randomBytes(-1) // rejected: not a valid size}).pipe( Effect.catchTag("PlatformError", (error) => // `error.reason` is a `BadArgument` or `SystemError` Effect.succeed(`crypto failed: ${error.reason.message}`) ))API reference
Section titled “API reference”The full service surface lives on the Crypto interface. Retrieve it with
const crypto = yield* Crypto.Crypto, then call these members.
randomBytes
Section titled “randomBytes”randomBytes(size: number): Effect<Uint8Array, PlatformError> — generates
size cryptographically secure random bytes. Fails with a PlatformError
(BadArgument) if size is not a non-negative safe integer.
import { Crypto, Effect } from "effect"
Effect.gen(function*() { const crypto = yield* Crypto.Crypto const bytes = yield* crypto.randomBytes(8) // => Uint8Array(8) [ 0x9c, 0x2f, ... ]})digest
Section titled “digest”digest(algorithm: DigestAlgorithm, data: Uint8Array): Effect<Uint8Array, PlatformError>
— computes a cryptographic hash of data. The output length depends on the
algorithm (SHA-256 → 32 bytes, SHA-512 → 64 bytes).
import { Crypto, Effect } from "effect"
Effect.gen(function*() { const crypto = yield* Crypto.Crypto const hash = yield* crypto.digest("SHA-512", new TextEncoder().encode("hello")) // => Uint8Array(64) [ ... ]})randomUUIDv4
Section titled “randomUUIDv4”randomUUIDv4: Effect<string, PlatformError> — a fully random version-4 UUID
string. An Effect value (not a function); yield* it directly.
import { Crypto, Effect } from "effect"
Effect.gen(function*() { const crypto = yield* Crypto.Crypto const id = yield* crypto.randomUUIDv4 // => "f47ac10b-58cc-4372-a567-0e02b2c3d479"})randomUUIDv7
Section titled “randomUUIDv7”randomUUIDv7: Effect<string, PlatformError> — a time-ordered version-7 UUID
string. The leading bits encode the current time (from Clock), so values are
roughly sortable by creation time. Prefer for database keys.
import { Crypto, Effect } from "effect"
Effect.gen(function*() { const crypto = yield* Crypto.Crypto const id = yield* crypto.randomUUIDv7 // => "018f6c1e-7b2a-7c3d-9e4f-0a1b2c3d4e5f"})random
Section titled “random”random: Effect<number> — a secure random float in [0, 1). The cryptographic
analogue of Math.random(). Never fails.
import { Crypto, Effect } from "effect"
Effect.gen(function*() { const crypto = yield* Crypto.Crypto const n = yield* crypto.random // => 0.5733... (0 <= n < 1)})randomBoolean
Section titled “randomBoolean”randomBoolean: Effect<boolean> — a secure random boolean (each outcome ~50%).
import { Crypto, Effect } from "effect"
Effect.gen(function*() { const crypto = yield* Crypto.Crypto const b = yield* crypto.randomBoolean // => true})randomInt
Section titled “randomInt”randomInt: Effect<number> — a secure random integer between
Number.MIN_SAFE_INTEGER and Number.MAX_SAFE_INTEGER, both inclusive.
import { Crypto, Effect } from "effect"
Effect.gen(function*() { const crypto = yield* Crypto.Crypto const n = yield* crypto.randomInt // => -4503599627370496})randomBetween
Section titled “randomBetween”randomBetween(min: number, max: number): Effect<number> — a secure random
float between min (inclusive) and max (exclusive).
import { Crypto, Effect } from "effect"
Effect.gen(function*() { const crypto = yield* Crypto.Crypto const n = yield* crypto.randomBetween(10, 20) // => 14.82... (10 <= n < 20)})randomIntBetween
Section titled “randomIntBetween”randomIntBetween(min: number, max: number, options?: { halfOpen?: boolean }): Effect<number>
— a secure random integer in a range. min is rounded up with Math.ceil, max
down with Math.floor. The range is inclusive by default; pass
{ halfOpen: true } to exclude the upper bound.
import { Crypto, Effect } from "effect"
Effect.gen(function*() { const crypto = yield* Crypto.Crypto
const inclusive = yield* crypto.randomIntBetween(1, 6) // => one of 1, 2, 3, 4, 5, 6
const halfOpen = yield* crypto.randomIntBetween(0, 10, { halfOpen: true }) // => one of 0..9 (10 excluded)})randomShuffle
Section titled “randomShuffle”randomShuffle<A>(elements: Iterable<A>): Effect<Array<A>> — returns a new
array with the elements shuffled using the secure generator (Fisher–Yates). The
input is not mutated.
import { Crypto, Effect } from "effect"
Effect.gen(function*() { const crypto = yield* Crypto.Crypto const shuffled = yield* crypto.randomShuffle(["a", "b", "c", "d"]) // => ["c", "a", "d", "b"]})nextIntUnsafe
Section titled “nextIntUnsafe”nextIntUnsafe(): number — a synchronous secure random integer between
Number.MIN_SAFE_INTEGER and Number.MAX_SAFE_INTEGER (inclusive). Plain
method, not an Effect; the Unsafe suffix marks that it runs side-effecting
work directly. Prefer randomInt inside effects.
import { Crypto, Effect } from "effect"
Effect.gen(function*() { const crypto = yield* Crypto.Crypto const n = crypto.nextIntUnsafe() // => 729348572349})nextDoubleUnsafe
Section titled “nextDoubleUnsafe”nextDoubleUnsafe(): number — a synchronous secure random float in
[0, 1). Plain method, not an Effect. Prefer random inside effects.
import { Crypto, Effect } from "effect"
Effect.gen(function*() { const crypto = yield* Crypto.Crypto const n = crypto.nextDoubleUnsafe() // => 0.118... (0 <= n < 1)})DigestAlgorithm
Section titled “DigestAlgorithm”The string-literal type of digest algorithms accepted by digest:
import { Crypto } from "effect"
type DigestAlgorithm = "SHA-1" | "SHA-256" | "SHA-384" | "SHA-512"
const algorithm: Crypto.DigestAlgorithm = "SHA-256"Crypto (service tag)
Section titled “Crypto (service tag)”Crypto.Crypto is the Context.Service tag for the platform cryptography
service. yield* it to retrieve the service; provide an implementation (such as
NodeCrypto.layer) to satisfy it.
import { Crypto, Effect, Layer } from "effect"
// The type of the requirement added to an effect that uses Crypto:type Requires = Crypto.Crypto
// Providing a layer satisfies `Crypto.Crypto`declare const myCryptoLayer: Layer.Layer<Crypto.Crypto>const provide = <A, E>(self: Effect.Effect<A, E, Crypto.Crypto>) => Effect.provide(self, myCryptoLayer)make(impl): Crypto builds a full Crypto service from two primitives: a
synchronous randomBytes(size) => Uint8Array and a digest(algorithm, data).
All the numeric helpers, shuffling, and UUID generation are derived from
randomBytes. Use it for platform integrations, custom runtimes, or test
layers.
import { Crypto, Effect, Layer } from "effect"
// A deterministic test layer — bytes are all-zero, digest is the identity.// Useful for asserting behavior without real entropy.const TestCrypto = Layer.succeed( Crypto.Crypto, Crypto.make({ randomBytes: (size) => new Uint8Array(size), digest: (_algorithm, data) => Effect.succeed(data) }))