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 key | Wallet-derived key | |
|---|---|---|
| What it is | An 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 decrypt | Whoever holds the crypto identity that registered the key | Only someone with the raw ed25519 secret key (the keypair itself) |
| Works for | Everyone, including browser wallets | Servers, 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 —
addMemberpicks the right key automatically. If you control onboarding, enforce key registration for browser users before the add.