Skip to main content

Member Keys

When the admin adds a member, the member's per-room BGW user secret is encrypted to that member and stored on their RoomMember account. Which key it is encrypted to — and whether the member can later decrypt it — depends on the member's key setup. Getting this right matters most for browser-wallet users.

The two key models

Registered packet keyWallet-derived key
What it isAn X25519 public key the wallet published on-chain (client.createKey / createKeyFromCrypto)The wallet's ed25519 public key converted to X25519 (SOLANA_ED25519_X25519) — no registration, derived from the wallet address alone
Who can decryptWhoever holds the crypto identity that registered the keyOnly someone with the raw ed25519 secret key (the keypair itself)
Works forEveryone, including browser walletsServers, CLIs, agents — anything holding a Keypair. Not browser wallets

RoomAdminClient.addMember resolves the member's registered key first and falls back to the wallet-derived key when none is registered.

Raw-keypair members (server / CLI / agents)

Nothing to register. The wallet keypair doubles as the decryption key:

client.useSolanaCryptoKeypair(keypair);

const room = await client.room({ id: roomId });
await room.myMember(); // decrypts the member secret with the keypair

The admin can add such a wallet without any preparation on the member's side — the wallet-derived fallback envelope is decryptable directly from the keypair.

Browser-wallet members

Browser wallets sign but never expose the raw secret key, so they cannot decrypt wallet-derived envelopes — the ed25519→x25519 conversion needs the secret scalar. They must use a derived identity and register it before being added to any room:

// 1. derive a deterministic encryption identity (wallet signature + password)
await client.useWalletPasswordCrypto({
signMessage: (message) => wallet.signMessage(message),
password: userPassword,
});

// 2. publish it as the wallet's registered packet key (one-time, on-chain)
await client.createKeyFromCrypto();

From then on, addMember finds the registered key and encrypts to it; the user re-derives the same identity on any device with the same wallet + password and can decrypt.

Order matters. The envelope is encrypted at add time to whatever key resolves then. If a browser-wallet user is added before registering, the fallback wallet-derived envelope is created — which they can never decrypt. The fix is to register the key and have the admin removeMember + addMember again (re-adding keeps the slot but re-encrypts the secret — note the affected user still won't see messages from before the re-add, since re-adds break the key chain).

How this differs from thread encryption

Threads encrypt content per recipient on every message, so a late key registration only affects new messages. Rooms encrypt the member secret once, at add time — everything else (epoch keys, message keys) flows from it. That single envelope is why the registration timing rule above exists.

Quick checklist

  • Server/CLI member: useSolanaCryptoKeypair(keypair) — done.
  • Browser member: useWalletPasswordCrypto(...)createKeyFromCrypto()then ask the admin to add you.
  • Admin: nothing special — addMember picks the right key automatically. If you control onboarding, enforce key registration for browser users before the add.