OpenE2EE

Profile keys

The hosted.profileKeys option, which carries the profile key in 1:1 messages and exchanges presence keys with no app code.

Under the contacts audience, a presence read needs the presence key of each account that it reads. The SDK derives the presence key from the 32-byte profile key of the account. With the hosted.profileKeys option, the SDK carries the profile key in each end-to-end encrypted 1:1 message and exchanges the presence keys. Your app sends no key.

Put every client of your app on SDK 7.0.0 or later before you set hosted.profileKeys. A receiver on an SDK older than 7.0.0 does not know the key-update message. It gives that message to onMessageDecrypted as a message with no text.

Set the option

Give createHostedSignalProtocolClient() a hosted.profileKeys object with two members:

MemberWhat it does
getOwnProfileKey()Returns the 32-byte profile key of this account, or null when the account has none.
contactsA MutableContactProfileStateStore from /profile. The SDK keeps the profile key of each contact in it.
import { createHostedSignalProtocolClient } from '@open-e2ee/signal-protocol-sdk';
import { getOrCreateOwnProfileKey } from '@open-e2ee/signal-protocol-sdk/profile';

const signal = await createHostedSignalProtocolClient({
  adapters,
  hosted: {
    getIdentityAssertion,
    relayUrl: process.env.OPEN_E2EE_RELAY_URL!,
    profileKeys: {
      getOwnProfileKey: () => getOrCreateOwnProfileKey(),
      contacts, // your MutableContactProfileStateStore
    },
  },
});

createHostedSignalProtocolClient() throws when getOwnProfileKey is not a function, or when contacts does not have storeContactProfileKey and updateUnidentifiedAccessMode.

Each device of an account must return the same profile key. The SDK reads the key before each 1:1 send and when the mailbox socket connects. A new key takes effect at the next of these. When the key does not have 32 bytes, the SDK logs a warning and sends the message without a key.

What the SDK sends

  • Each 1:1 DataMessage carries the profile key in the Signal DataMessage.profileKey field, inside the encrypted content.
  • Before a 1:1 string or byte send, the SDK sends one key-update DataMessage to a contact that has not received the current key. This message has the PROFILE_KEY_UPDATE flag (4) and carries only the profile key.
  • Group messages and messages to the other devices of this account do not carry the key.

The profile key goes only in encrypted content, and Relay never receives it. The presence key goes only in the presence frames.

What the SDK receives

The receiving SDK keeps the profile key of the sender in contacts. It then derives the presence key of the sender and calls grant(). It does not give a key-update message to onMessageDecrypted or to another application hook. The SDK ignores a profile key in group content.

Key rotation

The SDK calls accessKey() when the mailbox socket connects and at the next 1:1 send after the profile key changes. A new presence key revokes the old one. Each contact that holds only the old key reads null until the next message from this account delivers the new key.

When a registration fails with a retryable error, the SDK tries again at the next send or connect. When it fails with an error that is not retryable, the SDK does not register that key again. The SDK logs a warning for each failure.

Without the option

Without hosted.profileKeys, the SDK carries no profile key. Call accessKey() with the profile key after sign-in, send the presence key to each contact in an end-to-end encrypted message, and call grant() when a key arrives from a contact.

On this page