Typescript implementation of Messaging Layer Security (RFC 9420, MLS).
This project aims to be a full implementation of RFC 9420 and focuses on immutability and type safety. It is suitable for browsers, Node.js, or serverless environments and supports the recently standardized Post Quantum public-key algorithms (FIPS-203, FIPS-204) as well as the X-Wing hybrid KEM combining X25519 and ML-KEM.
Node.js Requirement: Node.js 19+ is required when using this library in Node.js environments due to the Web Crypto API dependency.
# npm
npm install ts-mls
# yarn
yarn add ts-mls
# pnpm
pnpm add ts-mlsThis project currently only has a single dependency, @hpke/core. However, to support different Ciphersuites, you may need to install other libraries. As an example, to use the MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 Ciphersuite, you would also have to install @noble/curves:
# npm
npm install @noble/curves
# yarn
yarn add @noble/curves
# pnpm
pnpm add @noble/curvesPlease refer to the subsequent table to understand which additional dependencies are required to install for each Ciphersuite.
The following cipher suites are supported:
| KEM | AEAD | KDF | Hash | Signature | Name | ID | Dependencies | 
|---|---|---|---|---|---|---|---|
| DHKEM-X25519-HKDF-SHA256 | AES128GCM | HKDF-SHA256 | SHA-256 | Ed25519 | MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 | 1 | @noble/curves | 
| DHKEM-P256-HKDF-SHA256 | AES128GCM | HKDF-SHA256 | SHA-256 | P256 | MLS_128_DHKEMP256_AES128GCM_SHA256_P256 | 2 | @noble/curves | 
| DHKEM-X25519-HKDF-SHA256 | CHACHA20POLY1305 | HKDF-SHA256 | SHA-256 | Ed25519 | MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_Ed25519 | 3 | @hpke/chacha20poly1305, @noble/curves | 
| DHKEM-X448-HKDF-SHA512 | AES256GCM | HKDF-SHA512 | SHA-512 | Ed448 | MLS_256_DHKEMX448_AES256GCM_SHA512_Ed448 | 4 | @noble/curves | 
| DHKEM-P521-HKDF-SHA512 | AES256GCM | HKDF-SHA512 | SHA-512 | P521 | MLS_256_DHKEMP521_AES256GCM_SHA512_P521 | 5 | @noble/curves | 
| DHKEM-X448-HKDF-SHA512 | CHACHA20POLY1305 | HKDF-SHA512 | SHA-512 | Ed448 | MLS_256_DHKEMX448_CHACHA20POLY1305_SHA512_Ed448 | 6 | @hpke/chacha20poly1305, @noble/curves | 
| DHKEM-P384-HKDF-SHA384 | AES256GCM | HKDF-SHA384 | SHA-384 | P384 | MLS_256_DHKEMP384_AES256GCM_SHA384_P384 | 7 | @noble/curves | 
| ML-KEM-512 | AES256GCM | HKDF-SHA256 | SHA-256 | Ed25519 | MLS_128_MLKEM512_AES128GCM_SHA256_Ed25519 | 77 | @hpke/ml-kem, @noble/curves | 
| ML-KEM-512 | CHACHA20POLY1305 | HKDF-SHA256 | SHA-256 | Ed25519 | MLS_128_MLKEM512_CHACHA20POLY1305_SHA256_Ed25519 | 78 | @hpke/ml-kem, @hpke/chacha20poly1305, @noble/curves | 
| ML-KEM-768 | AES256GCM | HKDF-SHA384 | SHA-384 | Ed25519 | MLS_256_MLKEM768_AES256GCM_SHA384_Ed25519 | 79 | @hpke/ml-kem, @noble/curves | 
| ML-KEM-768 | CHACHA20POLY1305 | HKDF-SHA384 | SHA-384 | Ed25519 | MLS_256_MLKEM768_CHACHA20POLY1305_SHA384_Ed25519 | 80 | @hpke/ml-kem, @hpke/chacha20poly1305, @noble/curves | 
| ML-KEM-1024 | AES256GCM | HKDF-SHA512 | SHA-512 | Ed25519 | MLS_256_MLKEM1024_AES256GCM_SHA512_Ed25519 | 81 | @hpke/ml-kem, @noble/curves | 
| ML-KEM-1024 | CHACHA20POLY1305 | HKDF-SHA512 | SHA-512 | Ed25519 | MLS_256_MLKEM1024_CHACHA20POLY1305_SHA512_Ed25519 | 82 | @hpke/ml-kem, @hpke/chacha20poly1305, @noble/curves | 
| X-Wing | AES256GCM | HKDF-SHA512 | SHA-512 | Ed25519 | MLS_256_XWING_AES256GCM_SHA512_Ed25519 | 83 | @hpke/hybridkem-x-wing, @noble/curves | 
| X-Wing | CHACHA20POLY1305 | HKDF-SHA512 | SHA-512 | Ed25519 | MLS_256_XWING_CHACHA20POLY1305_SHA512_Ed25519 | 84 | @hpke/hybridkem-x-wing, @hpke/chacha20poly1305, @noble/curves | 
| ML-KEM-1024 | AES256GCM | HKDF-SHA512 | SHA-512 | ML-DSA-87 | MLS_256_MLKEM1024_AES256GCM_SHA512_MLDSA78 | 85 | @hpke/ml-kem, @noble/post-quantum | 
| ML-KEM-1024 | CHACHA20POLY1305 | HKDF-SHA512 | SHA-512 | ML-DSA-87 | MLS_256_MLKEM1024_CHACHA20POLY1305_SHA512_MLDSA78 | 86 | @hpke/ml-kem, @hpke/chacha20poly1305, @noble/post-quantum | 
| X-Wing | AES256GCM | HKDF-SHA512 | SHA-512 | ML-DSA-87 | MLS_256_XWING_AES256GCM_SHA512_MLDSA78 | 87 | @hpke/hybridkem-x-wing, @noble/post-quantum | 
| X-Wing | CHACHA20POLY1305 | HKDF-SHA512 | SHA-512 | ML-DSA-87 | MLS_256_XWING_CHACHA20POLY1305_SHA512_MLDSA78 | 88 | @hpke/hybridkem-x-wing, @hpke/chacha20poly1305, @noble/post-quantum | 
This library has not undergone a formal security audit. While care has been taken to implement the MLS protocol correctly and securely, it may contain undiscovered vulnerabilities. If you plan to use this library in a production or security-critical context, proceed with caution and consider conducting an independent security review.
import {
  createApplicationMessage,
  createCommit,
  createGroup,
  joinGroup,
  processPrivateMessage,
  getCiphersuiteImpl,
  getCiphersuiteFromName,
  Credential,
  defaultCapabilities,
  defaultLifetime,
  emptyPskIndex,
  generateKeyPackage,
  encodeMlsMessage,
  decodeMlsMessage,
  Proposal,
} from "ts-mls"
const impl = await getCiphersuiteImpl(getCiphersuiteFromName("MLS_256_XWING_AES256GCM_SHA512_Ed25519"))
// alice generates her key package
const aliceCredential: Credential = { credentialType: "basic", identity: new TextEncoder().encode("alice") }
const alice = await generateKeyPackage(aliceCredential, defaultCapabilities(), defaultLifetime, [], impl)
const groupId = new TextEncoder().encode("group1")
// alice creates a new group
let aliceGroup = await createGroup(groupId, alice.publicPackage, alice.privatePackage, [], impl)
// bob generates his key package
const bobCredential: Credential = { credentialType: "basic", identity: new TextEncoder().encode("bob") }
const bob = await generateKeyPackage(bobCredential, defaultCapabilities(), defaultLifetime, [], impl)
// bob sends keyPackage to alice
const keyPackageMessage = encodeMlsMessage({
  keyPackage: bob.publicPackage,
  wireformat: "mls_key_package",
  version: "mls10",
})
// alice decodes bob's keyPackage
const decodedKeyPackage = decodeMlsMessage(keyPackageMessage, 0)![0]
if (decodedKeyPackage.wireformat !== "mls_key_package") throw new Error("Expected key package")
// alice creates proposal to add bob
const addBobProposal: Proposal = {
  proposalType: "add",
  add: {
    keyPackage: decodedKeyPackage.keyPackage,
  },
}
// alice commits
const commitResult = await createCommit({ state: aliceGroup, cipherSuite: impl }, { extraProposals: [addBobProposal] })
aliceGroup = commitResult.newState
// alice sends welcome message to bob
const encodedWelcome = encodeMlsMessage({
  welcome: commitResult.welcome!,
  wireformat: "mls_welcome",
  version: "mls10",
})
// bob decodes the welcome message
const decodedWelcome = decodeMlsMessage(encodedWelcome, 0)![0]
if (decodedWelcome.wireformat !== "mls_welcome") throw new Error("Expected welcome")
// bob creates his own group state
let bobGroup = await joinGroup(
  decodedWelcome.welcome,
  bob.publicPackage,
  bob.privatePackage,
  emptyPskIndex,
  impl,
  aliceGroup.ratchetTree,
)
const messageToBob = new TextEncoder().encode("Hello bob!")
// alice creates a message to the group
const aliceCreateMessageResult = await createApplicationMessage(aliceGroup, messageToBob, impl)
aliceGroup = aliceCreateMessageResult.newState
// alice sends the message to bob
const encodedPrivateMessageAlice = encodeMlsMessage({
  privateMessage: aliceCreateMessageResult.privateMessage,
  wireformat: "mls_private_message",
  version: "mls10",
})
// bob decodes the message
const decodedPrivateMessageAlice = decodeMlsMessage(encodedPrivateMessageAlice, 0)![0]
if (decodedPrivateMessageAlice.wireformat !== "mls_private_message") throw new Error("Expected private message")
// bob receives the message
const bobProcessMessageResult = await processPrivateMessage(
  bobGroup,
  decodedPrivateMessageAlice.privateMessage,
  emptyPskIndex,
  impl,
)
bobGroup = bobProcessMessageResult.newState
if (bobProcessMessageResult.kind === "newState") throw new Error("Expected application message")
console.log(bobProcessMessageResult.message)Please visit the /docs directory for further documentation on different scenarios.
We welcome contributions! Please read our CONTRIBUTING.md for guidelines on how to set up your environment, run checks, and submit changes.