Skip to content

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 service
NodeRuntime.runMain(program.pipe(Effect.provide(NodeCrypto.layer)))

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 }
})

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)
})

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’s Clock, so it is TestClock-controllable in tests.)

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 }
})

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}`)
)
)

The full service surface lives on the Crypto interface. Retrieve it with const crypto = yield* Crypto.Crypto, then call these members.

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(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: 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: 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: 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: 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: 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(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(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<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(): 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(): 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)
})

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.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)
})
)