Client State
ClientState contains all the information needed to operate an MLS group.
What is ClientState?
ClientState is the MLS group state object containing:
- Current encryption keys
- Member list and their credentials
- Group context (including MarmotGroupData)
- Epoch number
- Pending proposals
- Tree structure
Think of it as a snapshot of the group at a specific point in time.
Extracting Group Information
Get Marmot Group Data
typescript
import { getMarmotGroupData } from "@internet-privacy/marmot-ts";
const groupData = getMarmotGroupData(clientState);
console.log(groupData.name); // "Developer Chat"
console.log(groupData.adminPubkeys); // ["admin-hex"]
console.log(groupData.relays); // ["wss://..."]Get Group Identifiers
typescript
import { getGroupIdHex, getNostrGroupIdHex } from "@internet-privacy/marmot-ts";
// MLS group ID
const mlsGroupId = getGroupIdHex(clientState);
// Nostr group ID (from MarmotGroupData)
const nostrGroupId = getNostrGroupIdHex(clientState);Get Group Metadata
typescript
import { getEpoch, getMemberCount } from "@internet-privacy/marmot-ts";
const epoch = getEpoch(clientState); // Current epoch number
const memberCount = getMemberCount(clientState); // Number of membersSerialization
ClientState must be serialized for storage and deserialized when loading.
Serialize for Storage
typescript
import { serializeClientState } from "@internet-privacy/marmot-ts";
const serialized = serializeClientState(clientState);
// serialized is Uint8Array (TLS binary format)
// Store in your preferred storage
await storage.save(groupId, serialized);Deserialize from Storage
typescript
import { deserializeClientState } from "@internet-privacy/marmot-ts";
const serialized = await storage.load(groupId);
const clientState = deserializeClientState(serialized);Default Configuration
typescript
import { defaultMarmotClientConfig } from "@internet-privacy/marmot-ts";
// Default ts-mls configuration used by Marmot helpers.
console.log(defaultMarmotClientConfig);State Updates
ClientState is immutable - operations return a new state:
typescript
import { processMessage } from "ts-mls";
// Process a message (commit, proposal)
const result = await processMessage({
context: {
cipherSuite: ciphersuiteImpl,
authService,
externalPsks: {},
},
state: clientState,
message: mlsMessage,
});
if (result.kind === "newState") {
clientState = result.newState;
}
// Old state is unchanged; use result.newState going forward when returned.Epoch Advancement
The epoch advances with each commit:
typescript
import { getEpoch } from "@internet-privacy/marmot-ts";
console.log("Before commit:", getEpoch(clientState)); // 5
// Process commit
const result = await processMessage({
context: {
cipherSuite: ciphersuiteImpl,
authService,
externalPsks: {},
},
state: clientState,
message: commit,
});
if (result.kind === "newState") clientState = result.newState;
console.log("After commit:", getEpoch(clientState)); // 6Each epoch has unique encryption keys. Messages from epoch N can only be decrypted by members in epoch N.
Storage Considerations
Security
- ClientState contains secret key material
- Store encrypted and access-controlled
- Never transmit over unencrypted channels
Size
- Grows with group history and member count
- Binary format is compact (~few KB for typical groups)
- Consider periodic re-initialization for very large groups
Backup
- Always back up ClientState after changes
- Loss means inability to decrypt future messages
- Cannot recover old states (forward secrecy)
Example: Full State Lifecycle
typescript
import {
createGroup,
serializeClientState,
deserializeClientState,
getMarmotGroupData,
defaultMarmotClientConfig,
} from "@internet-privacy/marmot-ts";
// 1. Create group
const { clientState } = await createGroup({
creatorKeyPackage,
marmotGroupData,
ciphersuiteImpl,
});
// 2. Serialize and store
const serialized = serializeClientState(clientState);
await storage.save(groupId, serialized);
// 3. Later: load from storage
const loaded = await storage.load(groupId);
let restoredState = deserializeClientState(loaded);
// 4. Use the restored state
const groupData = getMarmotGroupData(restoredState);
console.log("Restored group:", groupData.name);