SDK Initialization

The Fireblocks Non-Custodial Wallet (NCW) SDK should be initialized only after Device ID value creation and assignment to a user.

Generate Device ID

The Device ID is a logical identifier that represents an entity that stores a user key share and can participate in MPC operations for a given wallet. It is a unique identifier per SDK instance.

πŸ“˜

Note

NCW SDK exposes a static method to generate a random deviceId - fireblocksNCW.generateDeviceId()

The code below will be changed to use this method in the near future.

const DEVICE_ID_KEY = "DEMO_APP:deviceId";

export const generateDeviceId = () => crypto.randomUUID();

export const getDeviceId = () => {
  return localStorage.getItem(DEVICE_ID_KEY);
};

export const getOrCreateDeviceId = () => {
  const deviceId = getDeviceId();
  if (deviceId) {
    return deviceId;
  }

  const uuid = generateDeviceId();
  setDeviceId(uuid);
  return uuid;
};

export const setDeviceId = (deviceId: string) => {
  localStorage.setItem(DEVICE_ID_KEY, deviceId);
};

SDK Initialization

When initializing the SDK it is required to pass the deviceId, storage provider, message handler, and optionally the event handler. In this section, we are going to cover how each was implemented in our demo application. The full code is in: /src/AppStore.ts

Outgoing Message Handler

MessageHandler is the implementation of the outgoing message handling object for the asynchronous MPC messaging flow. For more information, please visit Outgoing/Incoming Message Handling guide:

...

const messagesHandler: IMessagesHandler = {
  handleOutgoingMessage: (message: string) => {
    if (!apiService) {
      throw new Error("apiService is not initialized");
    }
    return apiService.sendMessage(deviceId, message);
  },
};

...

Incoming Message Handler

πŸ“˜

Incoming messages change

Beginning with [email protected], incoming messages and the required implementation to fetch them continuously will no longer be required. This process will be facilitated by the SDKs.

In order to see an example of handling incoming messages on mobile, check out these open-source demo Fireblocks published:

Storage Provider

The storage provider is being called by the NCW SDK to access the MPC keys stored on the end user's device.

In this demo application, we have implemented password-protected storage that extends the built-in BrowserLocalStorageProvider and encrypts the key share and stores in the local storage of the browser:

import { md } from "node-forge";
import {
  ISecureStorageProvider,
  BrowserLocalStorageProvider,
  decryptAesGCM,
  encryptAesGCM,
} from "@fireblocks/ncw-js-sdk";

export type GetUserPasswordFunc = () => Promise<string>;

/// This secure storage implementations creates an encryption key on-demand based on a user password

export class PasswordEncryptedLocalStorage extends BrowserLocalStorageProvider implements ISecureStorageProvider {
  private encKey: string | null = null;
  constructor(
    private deviceId: string,
    private getPassword: GetUserPasswordFunc,
  ) {
    super(`secure-${deviceId}`);
  }

  public async unlock(): Promise<void> {
    this.encKey = await this._generateEncryptionKey();
  }

  public async lock(): Promise<void> {
    this.encKey = null;
  }

  public async get(key: string): Promise<string | null> {
    if (!this.encKey) {
      throw new Error("Storage locked");
    }

    const encryptedData = await super.get(key);
    if (!encryptedData) {
      return null;
    }

    return decryptAesGCM(encryptedData, this.encKey, this.deviceId);
  }

  public async set(key: string, data: string): Promise<void> {
    if (!this.encKey) {
      throw new Error("Storage locked");
    }

    const encryptedData = await encryptAesGCM(data, this.encKey, this.deviceId);
    await super.set(key, encryptedData);
  }

  private async _generateEncryptionKey(): Promise<string> {
    let key = await this.getPassword();
    const md5 = md.md5.create();

    for (let i = 0; i < 1000; ++i) {
      md5.update(key);
      key = md5.digest().toHex();
    }

    return key;
  }
}

This secure storage provider is being passed to the NCW SDK initialization call:

...

const secureStorageProvider = new PasswordEncryptedLocalStorage(deviceId, () => {
  const password = prompt("Enter password", "");
  return Promise.resolve(password || "");
});

...

Event Handler

The event handler is optional. We have implemented it in our demo app to get the MPC Key generation status in order to present it to the end user:

...

const eventsHandler: IEventsHandler = {
  handleEvent: (event: TEvent) => {
    switch (event.type) {
      case "key_descriptor_changed":
        const keysStatus: Record<TMPCAlgorithm, IKeyDescriptor> =
          get().keysStatus ?? ({} as Record<TMPCAlgorithm, IKeyDescriptor>);
        keysStatus[event.keyDescriptor.algorithm] = event.keyDescriptor;
        set((state) => ({ ...state, keysStatus }));
        break;
    }
  },
};

...

Initialize SDK

And finally, let's initialize our NCW SDK instance with all the required values:

...

const fireblocksNCW = await FireblocksNCW.initialize({
  env: ENV_CONFIG.NCW_SDK_ENV,
  deviceId,
  messagesHandler,
  eventsHandler,
  secureStorageProvider,
  logger: new ConsoleLogger(),
});

...

Note that we are passing here a few additional values:

  • Optional:env - the Fireblocks environment that we are working on. Default is sandbox.
  • Optional: logger - your implementation of a logging mechanism.