NCW GENERATE_MPC_KEYS fails with TIMEOUT in Sandbox

Note: I created a new ticket because in my case, the first wallet was succesful created and I even did a success transaction from the custodial wallet to the non custodial wallet, unfortunatelly I didn’t do a backup after that and my storage was in memory. Further atempts to create a new asset for a new wallet with ney keys was unsuccessful.

Actual problem: Only the first attemp to create a NCW was successfull, after that, even cleaning the local storage from the browser, or repeating all the process with a different deviceId and physical device Id fails with TIMEOUT in Sandbox, there is a limit for how many NCW wallets and assets can be created? I coud reset such limit -if exists- or do a cleanup from the Sandbox’s web console? Thanks in advance

handleOutgoingMessage: {"method":"broadcast_mpc_msg","headers":{"sdkVersion":"12.5.4","mpcVersion":6,"physicalDeviceId":"12677a10-270e-4458-9479-cff1bdaab54b","platformType":"Web"},"params":[{"msg":{"payload":{"sender":"265454847268969","transaction":"","phase":"MPC_ERROR_SETUP","payload":"{\"keyId\":\"93078615-464a-474f-9ba7-993dde9a7a44\",\"txId\":\"\",\"reason\":\"TIMEOUT\"}","recipients":[],"sig":"82db0ccd0aa04ec562af718c7970a4cfe9d01fc5fd57c27f3eab3f41de9004eb79c61894db0cac746faed50f143c06495a987a0af8d0a3504e9bd1767d185db585e60a5501fdee082e36c49f7a24b45bc0b0bce977e8e7ed61c8891192000fe12dd24f03a9bd6c132af391aceb233fed701ec0d2f4c4e603c7ef2ffe40cd97dac03e1491b3981db97390916261a5e8dba308242e6f13105f99a2fc603970192fc1c94f96ce36f6dd944c13035186957c01f74f7cf90070a962e2a3bafbc56ab896d7bc12c9412106c854f6c8c11a7e78b0945271f61df7c7d4b1ced312ced7db5a677ee678cd995cf5e3fd63972b7fcb3d513ce4806b3b57b8e8b1c9da5cad639b7d5c0d70967c344336888d4cf1f116c4eb1d729fdbaa4d160453cc6d4e8992c2ceef0f443502569efed89217d0a5b268ad11258be4604b465291fabe07fed809f2672bab59867a49d6f99ed3175be37a764d78ef9af8b0f5a139cc2a1c966ff14f731bb68312a7eed949454016627dde52399687be684fc19f5a8d160b0c7e5dd392e38daf1129457ba05aa27de4772c08f1f290dcb6a4b942393105b46f6a32c5fea285b9700df4fae99165467540994eaaa809cbd7876281b0b34fbc9538af8d9c2f9ca8d881fc4c110469f4d8da5a8008864c6775a5cdeea9143b96af78fc407d6c6552a91bf976b63d64e0739ce97a0853db3bbb846721adbcae462605","version":6,"cosignerVersion":6}},"ack":true,"poll":true}]}

SETUP

In the backend I created a wallet:

const { walletId } = await admin.NCW.createWallet();
const { accountId } = await admin.NCW.createWalletAccount(walletId);

Then in the frontend I created the FireblocksNCW

export const createFireblocksNCW = async (deviceId: string) => {
  const secureStorageProvider = new PasswordEncryptedLocalStorage(
    deviceId,
    () => {
      const password = prompt("Enter password", "");
      if (password === null) {
        return Promise.reject(new Error("Rejected by user"));
      }
      return Promise.resolve(password || "");
    }
  );

  const messagesHandler: IMessagesHandler = {
    handleOutgoingMessage: async <T>(message: string): Promise<T> => {
      console.log(`handleOutgoingMessage: ${message}`);
      return invokeRpc<T>(deviceId, message);
    },
  };

  const eventsHandler: IEventsHandler = {
    handleEvent: (event: TEvent) => {
      console.log("handleEvent:");
      console.log(JSON.stringify(event, null, 2));
    },
  };

  return await FireblocksNCWFactory({
    env: "sandbox",
    deviceId,
    messagesHandler,
    eventsHandler,
    secureStorageProvider,
  });
};

When firts created it initializes different things in the browser such as NCW-GLOBAL_physicalDeviceId

Then it do a request to the backend as part of it’s initialization process:

payload: {"method":"get_service_certificates","headers":{"sdkVersion":"12.5.4","mpcVersion":6,"physicalDeviceId":"12677a10-270e-4458-9479-cff1bdaab54b","platformType":"Web"},"params":[{"names":["nckms","signing_service","zona_service","policy_service","ncw-service"]}]}

payload: {"method":"get_cloud_cosigner_certificate","headers":{"sdkVersion":"12.5.4","mpcVersion":6,"physicalDeviceId":"12677a10-270e-4458-9479-cff1bdaab54b","platformType":"Web"}}

payload: {"method":"get_all_keys","headers":{"sdkVersion":"12.5.4","mpcVersion":6,"physicalDeviceId":"12677a10-270e-4458-9479-cff1bdaab54b","platformType":"Web"}}

After that I try to start the generateMPCKeys process:

const algorithms: Set<TMPCAlgorithm> = new Set(["MPC_CMP_ECDSA_SECP256K1"]);
const keyDescriptor: Set<IKeyDescriptor> = await fireblocksNCW.generateMPCKeys(algorithms);

During the process it ask for a password so I’ll give a simple password 123456

Then it do a request to the backend again:

payload: {"method":"enroll_player","headers":{"sdkVersion":"12.5.4","mpcVersion":6,"physicalDeviceId":"12677a10-270e-4458-9479-cff1bdaab54b","platformType":"Web"},"params":[{"playerId":"265454847268969","csr":"-----BEGIN CERTIFICATE REQUEST-----\r\nMIIEoTCCAokCAQAwXDELMAkGA1UEBhMCQ0ExCzAJBgNVBAgTAkJDMRIwEAYDVQQH\r\nEwlWYW5jb3V2ZXIxEjAQBgNVBAoTCUR5bmFtc29mdDEYMBYGA1UEAxMPMjY1NDU0\r\nODQ3MjY4OTY5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAu3fk+gE5\r\nKsfX+i3VAK6MbfUDd3puMqiJ/GcIQO56dGlwfLXG7LudLzUyq5YlqXCETO3ic5LP\r\nEv767igzI5h6yuOwnJJhLtwApbwJgKlKdNOYu0SZGNrvVrg0h/cbZaNMBJRvSndV\r\njJZ5w+rXFMiuLqy5+ooCfUDbdWTxIfhboZZTo5tRyA6mt7FHMp53TV2kEPNrUzUE\r\n27y6VYwaBMqFgYbcMVWOqs1ibrD0mI89ZuaOU3tVVJES13JD63UY8zxDy9CcWwFi\r\nN4TG39sekF0TaWCpO4ioT8CA2BsMXWEK2R9r5sZw+UPBGtlJm7bJLAPsPKgLS+1I\r\nFR3bgr63pca7NPDZdXfo4UgyeWPDyy8BgtrhW1WA8zaH4tP6i4qQftEmnYt4F0gP\r\nqF47sZ+4duQ3cTZ+ka/pk9NXoSYGknDYfEwfWD9weui4v1aSd/EMkCrU91Z1cTOF\r\nmSjr5pZGPH3Dnt1b1sikhAuikB4aN/V7IdCJy3EgsJnqNcG3cS1B4I1e6//MmPXV\r\nM2s9iUZ/aOJAWgztkGvyOzhRhIDqbcQU7iawgDfCTODHHb6eJ13LqGIGtq1E1IO/\r\nXPAXzbo87VCvqumPmT1MRId8vInSbnEQ1bSRQvZ0B5m8yjAZHhX6RFLmWR/zyCNA\r\nSKvh3L32HLkDCoMfVsak6pAfjSkm8tICIPMCAwEAAaAAMA0GCSqGSIb3DQEBBQUA\r\nA4ICAQBcixqJB/QV2Fk0dbZOX4hHeLRVgfw05+7KQl228bx+Bp4DbPnOiUftKJNe\r\nMNAW/2xxqnI3czkQy8Q8GIOGzIZqzySMRFmNnC2GuAg9FOAl1a3xG2K+nOe3kNAP\r\n8Y/uE+qsKVkdIAyG8nWick+2g2ZE6koJMFe1nJ1nQLTBLPlfwZYZnwyr+0dxWnTo\r\nnxTYW2xzYjM216O4K1yh4BoM79KLNDl/ej8xNZK/VXco3AFPH2kk4cjqV58H4/9b\r\nygTSvcduiIKoQSfYpBthF7Uuex/wGCudIuCTGxw5csfcz97/++7aQ2MdfiAQ7PJR\r\nFGfFsH2QXjha5pqPg9a+erSvrQnrG50zi+0rxP1quNkCDeiQmnElLylPJm7gQnBe\r\n3joRrmEiLK88KZJo5VWPEdPnZSsD38Nv7cn181HBorNTRjwFBiBxHwhp5HjbbK4m\r\n8rU0GYYA2BngPxxM4F+6CCFn+xvucJf7degS/hsi0W5nynrH0t5tPEnqULBbz5+A\r\nZMLFPq5c/YG6ToxbOElcTdq5+uKGKg8C1sgqBvoCdL0kqKMNv/+XCYSnIh0FeYON\r\nhTxRApqSDWUMvzrl+HfK9h1ifxV/lVAD5JUQO2A0TAlCjsuwxNsuu6awwVSv8rLl\r\n6Tle6Nsh316utT6CukKYywOst+EqWdydV6cG4EcTi79xTsrrCw==\r\n-----END CERTIFICATE REQUEST-----\r\n"}]}

The implementation for the PasswordEncryptedLocalStorage is this:

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

export type GetUserPasswordCallback = () => 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 _salt: string,
    private _getPassword: GetUserPasswordCallback
  ) {
    super();
  }

  public async getAccess(): Promise<TReleaseSecureStorageCallback> {
    this.encKey = await this._generateEncryptionKey();
    return async () => {
      await this._release();
    };
  }

  private async _release(): 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._salt);
  }

  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._salt);
    await super.set(key, encryptedData);
  }
  public getAllKeys(): Promise<string[]> {
    // TODO: implement
    return Promise.resolve([]);
  }

  public clear(key: string) {
    // TODO: implement
    console.log(key);
    return Promise.resolve();
  }

  private async _generateEncryptionKey(): Promise<string> {
    let key = await this._getPassword();
    const sha = sha256.create();

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

    return key;
  }
}

I have tried again today and it generate the MPC keys succesfuly, and it was fast