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
SecureRandombytes, so a seededSecureRandomreproduces them. ECDH key generation uses WebCrypto's own randomness and cannot be seeded.
Algorithms
ECDH on P-256, with 32-byte shared secrets.
Signature
declare const ecdhP256: Ecdh<"P-256">ECDH on P-384, with 48-byte shared secrets.
Signature
declare const ecdhP384: Ecdh<"P-384">ECDH on P-521, with 66-byte shared secrets.
Signature
declare const ecdhP521: Ecdh<"P-521">X25519 (RFC 7748).
Signature
declare const x25519: X25519Constructors
privateKey
unstableBuilds 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>privateKeyFromCryptoKey
unstableBuilds 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>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>publicKeyFromCryptoKey
unstableBuilds 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
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
isPrivateKey
unstableChecks whether a value is a PrivateKey.
Signature
declare function isPrivateKey(u: unknown): u is PrivateKey<Algorithm>isPublicKey
unstableChecks whether a value is a PublicKey.
Signature
declare function isPublicKey(u: unknown): u is PublicKey<Algorithm>Layers
layerWebCrypto
unstableLayer 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
Signature
type Algorithm = X25519 | EcdhAn elliptic curve for ECDH.
Signature
type Curve = "P-256" | "P-384" | "P-521"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;}A private key and its public key.
Signature
interface KeyPair<out A extends Algorithm = Algorithm> { readonly privateKey: PrivateKey<A>; readonly publicKey: PublicKey<A>;}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;}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 (RFC 7748), with 32-byte shared secrets.
Signature
interface X25519 { readonly _tag: "X25519"; readonly name: "x25519";}Services
KeyAgreement
unstableService 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