Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/matrix-org/matrix-js-sdk/llms.txt

Use this file to discover all available pages before exploring further.

Cross-signing allows users to verify each other — and their own devices — using a set of cryptographic master, self-signing, and user-signing keys. Once a user has verified another user via cross-signing, all devices cross-signed by that user are transitively trusted.

How cross-signing works

Each user has three cross-signing keys:
KeyPurpose
Master keyThe user’s root identity key. Used to sign the self-signing and user-signing keys.
Self-signing keySigned by the master key. Used to sign the user’s own devices.
User-signing keySigned by the master key. Used to sign other users’ master keys.
When you verify a user (via emoji or QR code), you sign their master key with your user-signing key. Devices that are signed by their owner’s self-signing key are considered verified through cross-signing. These key types are available as the CrossSigningKey enum:
export enum CrossSigningKey {
    Master = "master",
    SelfSigning = "self_signing",
    UserSigning = "user_signing",
}

Setting up cross-signing

Call CryptoApi.bootstrapCrossSigning() to create cross-signing keys and publish them to the homeserver. Like bootstrapSecretStorage, this is idempotent — safe to call unconditionally:
await matrixClient.getCrypto().bootstrapCrossSigning({
    authUploadDeviceSigningKeys: async (makeRequest) => {
        // makeRequest is a function that calls the upload endpoint.
        // You need to provide UIA (User Interactive Auth) credentials.
        return makeRequest(authDict);
    },
});

BootstrapCrossSigningOpts

export interface BootstrapCrossSigningOpts {
    /** Optional. Reset the cross-signing keys even if keys already exist. */
    setupNewCrossSigning?: boolean;

    /**
     * An application callback to collect the authentication data for uploading
     * the keys. If not given, the keys will not be uploaded to the server.
     */
    authUploadDeviceSigningKeys?: UIAuthCallback<void>;
}
The authUploadDeviceSigningKeys callback is required to upload newly generated public cross-signing keys to the server. It receives a makeRequest function that performs the actual upload; you must supply an authentication dictionary (UIA credentials) to authorize it.
If bootstrapSecretStorage has already been called, bootstrapCrossSigning will store the private cross-signing keys in secret storage automatically.

Checking cross-signing status

Use isCrossSigningReady() to verify that cross-signing is fully configured and trusted on the current device:
const ready = await crypto.isCrossSigningReady();
// true if cross-signing keys are available and trusted
For a detailed breakdown, use getCrossSigningStatus():
const status = await crypto.getCrossSigningStatus();
// {
//   publicKeysOnDevice: boolean,
//   privateKeysInSecretStorage: boolean,
//   privateKeysCachedLocally: {
//     masterKey: boolean,
//     selfSigningKey: boolean,
//     userSigningKey: boolean,
//   }
// }

User verification status

CryptoApi.getUserVerificationStatus(userId) returns a UserVerificationStatus instance:
export class UserVerificationStatus {
    /** True if this user is verified via cross signing */
    public isCrossSigningVerified(): boolean;

    /** True if this user is verified via any means */
    public isVerified(): boolean;

    /** True if we ever verified this user before */
    public wasCrossSigningVerified(): boolean;

    /**
     * True if the identity has changed in a way that needs user approval.
     * To resolve: conduct a new verification or call pinCurrentUserIdentity().
     */
    public readonly needsUserApproval: boolean;
}
Example:
const status = await crypto.getUserVerificationStatus("@alice:example.org");

if (status.needsUserApproval) {
    // Alice's identity has changed since we last verified her.
    // Show a warning and let the user decide how to proceed.
    await promptUserToReVerify("@alice:example.org");
} else if (status.isVerified()) {
    console.log("Alice is verified");
} else if (status.wasCrossSigningVerified()) {
    // Previously verified but not anymore. Show a warning.
    console.warn("Alice was previously verified but is not anymore");
}

Handling identity changes

If needsUserApproval is true, the user’s identity has changed since it was first seen and the new identity has not been verified. You have two options:
// Option 1: Conduct a new interactive verification
const request = await crypto.requestVerificationDM(userId, roomId);

// Option 2: Pin the new identity without re-verifying
await crypto.pinCurrentUserIdentity(userId);
If a user was previously verified but is no longer verifiable (e.g., they lost their keys), call withdrawVerificationRequirement:
await crypto.withdrawVerificationRequirement(userId);

Device verification status

CryptoApi.getDeviceVerificationStatus(userId, deviceId) returns a DeviceVerificationStatus or null (if the device is unknown or has not published encryption keys):
export class DeviceVerificationStatus {
    /** True if this device has been signed by its owner */
    public readonly signedByOwner: boolean;

    /** True if this device has been verified via cross signing */
    public readonly crossSigningVerified: boolean;

    /** True if the device has been marked as locally verified */
    public readonly localVerified: boolean;

    /**
     * Returns true if the device is verified:
     * - manually via setDeviceVerified(), OR
     * - cross-signed AND setTrustCrossSignedDevices() is true
     */
    public isVerified(): boolean;
}
Example:
const deviceStatus = await crypto.getDeviceVerificationStatus(
    "@alice:example.org",
    "DEVICEID123",
);

if (deviceStatus === null) {
    console.log("Device is unknown or does not support encryption");
} else if (deviceStatus.isVerified()) {
    console.log("Device is verified");
} else if (deviceStatus.crossSigningVerified) {
    console.log("Device is cross-signed but trust of cross-signed devices is disabled");
} else {
    console.log("Device is not verified");
}

Locally verifying a device

If you have verified a device through an out-of-band mechanism, mark it as locally verified:
await crypto.setDeviceVerified("@alice:example.org", "DEVICEID123", true);
To cross-sign one of your own devices (after confirming it is genuine):
await crypto.crossSignDevice("MYOTHERDEVICEID");

Handling unverified devices in rooms

When encrypting a message, the SDK must decide which devices to share the session key with. The DeviceIsolationMode controls this behavior:
Keys are shared with all devices in the room. Unverified devices receive keys but the client UI should display warnings:
import { AllDevicesIsolationMode } from "matrix-js-sdk/lib/crypto-api";

crypto.setDeviceIsolationMode(new AllDevicesIsolationMode(false));
// Pass true to fail key sharing if a verified user has unverified devices

Unverified device warnings

The SDK design (from docs/warning-on-unverified-devices.md) recommends warning users when they try to send a message to a room that contains unverified devices they have not been notified about. The suggested approach is:
  • Track whether the user has already been warned about each unverified device in a given room.
  • When AllDevicesIsolationMode is used with errorOnVerifiedUserProblems: true, key-sharing will fail with an error if a previously-verified user has unverified devices — giving the client a hook to surface the warning.
  • Use getDeviceVerificationStatus to identify which specific devices are unverified, for display in membership lists or room info UI.
// Listen for changes to device trust state
matrixClient.on(CryptoEvent.DevicesUpdated, async (userIds) => {
    for (const userId of userIds) {
        const devices = await crypto.getUserDeviceInfo([userId]);
        for (const [deviceId] of devices.get(userId) ?? []) {
            const status = await crypto.getDeviceVerificationStatus(userId, deviceId);
            if (status && !status.isVerified()) {
                warnAboutUnverifiedDevice(userId, deviceId);
            }
        }
    }
});

Complete bootstrapCrossSigning example

import * as sdk from "matrix-js-sdk";

const matrixClient = sdk.createClient({
    baseUrl: "http://localhost:8008",
    accessToken: myAccessToken,
    userId: myUserId,
    cryptoCallbacks: {
        getSecretStorageKey: async ({ keys }) => {
            const key = await promptUserForRecoveryKey();
            return [Object.keys(keys)[0], key];
        },
    },
});

await matrixClient.initRustCrypto();
const crypto = matrixClient.getCrypto();

// Set up secret storage first so cross-signing keys can be stored there
await crypto.bootstrapSecretStorage({
    createSecretStorageKey: async () => {
        const key = await crypto.createRecoveryKeyFromPassphrase();
        await showRecoveryKeyToUser(key.encodedPrivateKey);
        return key;
    },
});

// Set up cross-signing
await crypto.bootstrapCrossSigning({
    authUploadDeviceSigningKeys: async (makeRequest) => {
        return makeRequest(authDict);
    },
});

// Check the result
const status = await crypto.getCrossSigningStatus();
console.log("Cross-signing status:", status);

// Listen for trust status changes
matrixClient.on(CryptoEvent.UserTrustStatusChanged, (userId, trustLevel) => {
    console.log(`User ${userId} trust changed:`, trustLevel.isVerified());
});

await matrixClient.startClient();