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

KeyAgreement unstable

Key agreement: X25519, and ECDH on P-256, P-384 and P-521.

The KeyAgreement service generates key pairs, derives a shared secret from a private key and a peer's public key, and computes a private key's public key. The top-level privateKey, publicKey, privateKeyFromCryptoKey, publicKeyFromCryptoKey, and toBytes functions build and export keys without a service.

Keys carry their algorithm in their type, so deriving a secret from keys on different curves is a type error. The shared secret is plain bytes: pass it through Kdf before using it as a key.

Keys are opaque. They hold either bytes or a CryptoKey, display only their algorithm, and compare by reference. The bytes are:

  • X25519: the 32-byte private key and the 32-byte public key;
  • ECDH: PKCS#8 for private keys, and the uncompressed SEC1 point for public keys.

Only X25519 private keys and X25519 and ECDH public keys have a raw constructor. ECDH private keys come from KeyAgreement.generateKeyPair, CryptoKeys, or key formats.

Gotchas

  • X25519 needs Chrome 133, Firefox 130, or Safari 17. There is no fallback for older browsers.
  • X25519 private keys are generated from SecureRandom bytes, so a seeded SecureRandom reproduces them. ECDH key generation uses WebCrypto's own randomness and cannot be seeded.
20 exports Since v4.0.0 Source

Algorithms

ecdhP256

unstable
Since v4.0.0

ECDH on P-256, with 32-byte shared secrets.

Signature

declare const ecdhP256: Ecdh<"P-256">

ecdhP384

unstable
Since v4.0.0

ECDH on P-384, with 48-byte shared secrets.

Signature

declare const ecdhP384: Ecdh<"P-384">

ecdhP521

unstable
Since v4.0.0

ECDH on P-521, with 66-byte shared secrets.

Signature

declare const ecdhP521: Ecdh<"P-521">

x25519

unstable
Since v4.0.0

X25519 (RFC 7748).

Signature

declare const x25519: X25519

Constructors

privateKey

unstable
Since v4.0.0

Builds an X25519 private key from its 32 bytes.

Details

The bytes are copied. Another length fails with an InvalidKey reason, including the 64-byte private keyโ€–public key form libsodium uses: pass its first 32 bytes. ECDH private keys have no raw form, so passing them is a type error.

Signature

declare function privateKey<A extends X25519>(algorithm: A, bytes: Uint8Array): Effect<PrivateKey<A>, CryptoError>

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

Details

The CryptoKey must be a private key of the algorithm's WebCrypto name and curve, with the deriveBits usage. Otherwise the constructor fails with an InvalidKey reason. The key must come from the Crypto object of the WebCrypto reference.

Signature

declare function privateKeyFromCryptoKey<A extends Algorithm>(algorithm: A, cryptoKey: CryptoKey): Effect<PrivateKey<A>, CryptoError>

publicKey

unstable
Since v4.0.0

Builds a public key from its raw form: 32 bytes for X25519, or an uncompressed SEC1 point for ECDH.

Details

The bytes are copied. A wrong length, or a compressed point, fails with an InvalidKey reason. Only uncompressed points are taken because Safari and Firefox before 146 reject compressed ones in raw import, though they accept them inside SPKI: decode a compressed point with KeyFormat.spki. Whether a point is on the curve is checked when the key is first used.

Signature

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

Builds a public key from an existing CryptoKey.

Details

The CryptoKey must be a public key of the algorithm's WebCrypto name and curve. Public keys need no usages. Otherwise the constructor fails with an InvalidKey reason. The key must come from the Crypto object of the WebCrypto reference.

Signature

declare function publicKeyFromCryptoKey<A extends Algorithm>(algorithm: A, cryptoKey: CryptoKey): Effect<PublicKey<A>, CryptoError>

Converting

toBytes

unstable
Since v4.0.0

Exports a key's stored bytes.

Details

Returns a fresh Uint8Array: the raw key for X25519, and PKCS#8 or the uncompressed point for ECDH. 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: PrivateKey<Algorithm> | PublicKey<Algorithm>): Effect<Uint8Array<ArrayBufferLike>, CryptoError>

Guards

Since v4.0.0

Checks whether a value is a PrivateKey.

Signature

declare function isPrivateKey(u: unknown): u is PrivateKey<Algorithm>

isPublicKey

unstable
Since v4.0.0

Checks whether a value is a PublicKey.

Signature

declare function isPublicKey(u: unknown): u is PublicKey<Algorithm>

Layers

Since v4.0.0

Layer that provides KeyAgreement on the WebCrypto reference.

Details

Every algorithm runs on crypto.subtle. The layer imports a byte-backed key on first use and caches the CryptoKey. X25519 private keys come from SecureRandom, so a seeded SecureRandom reproduces them.

Signature

declare const layerWebCrypto: Layer.Layer<KeyAgreement, never, SecureRandom.SecureRandom>

Models

Algorithm

type unstable
Since v4.0.0

A key agreement algorithm, such as x25519 or ecdhP256.

Signature

type Algorithm = X25519 | Ecdh

Curve

type unstable
Since v4.0.0

An elliptic curve for ECDH.

Signature

type Curve = "P-256" | "P-384" | "P-521"

Ecdh

interface unstable
Since v4.0.0

ECDH on a NIST curve, with shared secrets of the curve's coordinate size.

Signature

interface Ecdh<C extends Curve = Curve> {
readonly _tag: "Ecdh";
readonly name: {
readonly "P-256": "ecdhP256";
readonly "P-384": "ecdhP384";
readonly "P-521": "ecdhP521";
}[C];
readonly namedCurve: C;
}

KeyPair

interface unstable
Since v4.0.0

A private key and its public key.

Signature

interface KeyPair<out A extends Algorithm = Algorithm> {
readonly privateKey: PrivateKey<A>;
readonly publicKey: PublicKey<A>;
}

PrivateKey

interface unstable
Since v4.0.0

An opaque key agreement private key for the algorithm A.

Details

The key holds either bytes or a CryptoKey from privateKeyFromCryptoKey. toString, toJSON, and inspect show only the algorithm. Keys compare by reference and do not implement Equal.

Signature

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

PublicKey

interface unstable
Since v4.0.0

An opaque key agreement public key for the algorithm A.

Details

The key holds either bytes or a CryptoKey from publicKeyFromCryptoKey. Keys compare by reference and do not implement Equal.

Signature

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

X25519

interface unstable
Since v4.0.0

X25519 (RFC 7748), with 32-byte shared secrets.

Signature

interface X25519 {
readonly _tag: "X25519";
readonly name: "x25519";
}

Services

Since v4.0.0

Service that generates key pairs, derives shared secrets, and computes public keys.

Signature

declare class KeyAgreement extends Shape<"effect/crypto/KeyAgreement", {
readonly deriveSharedSecret: <A extends Algorithm>(privateKey: PrivateKey<A>, publicKey: PublicKey<A>) => Effect<Uint8Array<ArrayBufferLike>, CryptoError>;
readonly generateKeyPair: <A extends Algorithm>(algorithm: A) => Effect<KeyPair<A>, CryptoError>;
readonly publicKeyOf: <A extends Algorithm>(privateKey: PrivateKey<A>) => Effect<PublicKey<A>, CryptoError>;
}, this> {
constructor(_: never);
}

Example Agreeing on a secret with X25519

import { Effect, Layer } from "effect"
import { KeyAgreement, SecureRandom } from "effect/crypto"
const program = Effect.gen(function*() {
const keyAgreement = yield* KeyAgreement.KeyAgreement
const alice = yield* keyAgreement.generateKeyPair(KeyAgreement.x25519)
const bob = yield* keyAgreement.generateKeyPair(KeyAgreement.x25519)
const aliceSecret = yield* keyAgreement.deriveSharedSecret(alice.privateKey, bob.publicKey)
const bobSecret = yield* keyAgreement.deriveSharedSecret(bob.privateKey, alice.publicKey)
return aliceSecret.every((byte, i) => byte === bobSecret[i])
})
const layer = KeyAgreement.layerWebCrypto.pipe(Layer.provide(SecureRandom.layerWebCrypto))
await Effect.runPromise(program.pipe(Effect.provide(layer))) // => true