Node.js SDK

This tutorial has been tested on Linux and Mac OS. Some steps may be slightly different if you use another OS.

Prerequisites

  • Node.js version 20 or higher.
  • URLs and API keys for the MPC nodes in a running instance of the Builder Vault TSM. You can follow one of the tutorials Local Deployment, Hosted Sandbox, or AWS Marketplace to get access to a Builder Vault TSM.
  • Check our public repo here.

Step 1: Create a Node.js Project and Fetch the TSM SDK

In the following steps, we will create a small Node.js project that uses the Builder Vault Node.js SDK:

  • Create a new folder for your project on your local machine
  • Open a terminal window and go to the project folder.
  • Initiate a new Node.js project with default values by using the following command:
npm init -y
  • Set the public repository with the following command:
npm config set @sepior:registry=https://gitlab.com/api/v4/projects/56306653/packages/npm/
  • Install the TSM SDK by using the following command:
npm install @sepior/tsmsdkv2

📘

TSM Version

When you connect to an MPC node with the SDK, it is important that the version of the SDK matches the version of the MPC node. You can see the actual version of an MPC node in the demo TSM using this Linux command:

curl https://node1.tsm.sepior.net/version

If the returned version is for example 71.0.0, you can fetch the corresponding version of the SDK with this:

npm install @sepior/[email protected]

For the full list of SDK versions, please see here.

Step 2: Run the TSM SDK

In the following steps, we connect to three MPC nodes in the Builder Vault, each with its instance of the SDK. The SDKs are called concurrently to obtain a partial signature from each MPC node. These partial signatures are then combined to form the final signature.

  • Create a file example.js in the project folder with the following code:
const { TSMClient, Configuration, SessionConfig, curves } = require("@sepior/tsmsdkv2");
const crypto = require('crypto');

const main = async () => {

  // Create clients for each of the nodes

  const configs = [
    {
      url: "http://localhost:8500",
      apiKey: "apikey0",
    },
    {
      url: "http://localhost:8501",
      apiKey: "apikey1",
    },
    {
      url: "http://localhost:8502",
      apiKey: "apikey2",
    },
  ];

  const clients = [];

  for (const rawConfig of configs) {
    const config = await new Configuration(rawConfig.url);
    await config.withAPIKeyAuthentication(rawConfig.apiKey);
    const client = await TSMClient.withConfiguration(config);

    clients.push(client);
  }

  // Generate an ECDSA master key

  const threshold = 1; // The security threshold for this key
  const keygenPlayers = [0, 1, 2]; // The key should be secret shared among all three MPC nodes
  const keygenSessionConfig = await SessionConfig.newSessionConfig(
    await SessionConfig.GenerateSessionID(),
    new Uint32Array(keygenPlayers),
    {}
  );

  const keyIds = ["", "", ""];

  console.log(`Generating key using players ${keygenPlayers}`);

  const generateKeyPromises = [];

  for (const [i, client] of clients.entries()) {
    const func = async () => {
      const ecdsaApi = client.ECDSA();
      keyIds[i] = await ecdsaApi.generateKey(
        keygenSessionConfig,
        threshold,
        curves.SECP256K1,
        ""
      );
    };
    generateKeyPromises.push(func());
  }

  await Promise.all(generateKeyPromises);

  // Validate key IDs

  for (let i = 1; i < keyIds.length; i++) {
    if (keyIds[0] !== keyIds[i]) {
      console.log("Key ids do not match");
      return;
    }
  }

  const keyId = keyIds[0];

  console.log(`Generated key with id: ${keyId}`);

  // Get the public key

  const derivationPath = new Uint32Array([]); // We don't use key derivation in this example

  const publickeys = [];

  for (const client of clients) {
    const ecdsaApi = client.ECDSA();
    publickeys.push(await ecdsaApi.publicKey(keyId, derivationPath));
  }

  // Validate public keys

  for (let i = 1; i < publickeys.length; i++) {
    if (!Buffer.from(publickeys[0]).equals(publickeys[i])) {
      console.log("Public keys does not match");
      return;
    }
  }

  const publicKey = publickeys[0];

  console.log(`Public key: ${Buffer.from(publicKey).toString("hex")}`);

  // We can now sign with the created key

  const message = "This is a message to be signed";
  const messageHash = crypto.createHash("sha256").update(message).digest();

  const signPlayers = [0, 1]; // We want to sign with the first two MPC nodes
  const sessionId = await SessionConfig.GenerateSessionID();
  const signSessionConfig = await SessionConfig.newSessionConfig(
    sessionId,
    new Uint32Array(signPlayers),
    {}
  );

  console.log(`Creating signature using players ${signPlayers}`);

  const partialSignatures = [];

  const partialSignaturePromises = [];

  for (const playerIdx of signPlayers) {
    const func = async () => {
      const ecdsaApi = clients[playerIdx].ECDSA();

      const partialSignResult = await ecdsaApi.sign(
        signSessionConfig,
        keyId,
        new Uint32Array([]),
        messageHash
      );

      partialSignatures.push(partialSignResult);
    };

    partialSignaturePromises.push(func());
  }

  await Promise.all(partialSignaturePromises);

  const ecdsaApi = clients[0].ECDSA();

  const signature = await ecdsaApi.finalizeSignature(
    messageHash,
    partialSignatures
  );

  // Verify the signature relative to the signed message and the public key

  try {
    const result = await ecdsaApi.verifySignature(
      publicKey,
      messageHash,
      signature.signature
    );
    console.log(result);
    console.log(
      `Signature: ${Buffer.from(signature.signature).toString("hex")}`
    );
  } catch (e) {
    console.log(e);
  }
}

main()
  • Remember to replace the MPC node URLs and API keys above with the URLs and API keys for the actual Builder Vault TSM instance you use.
  • Run the following commands to execute the SDK:
node example.js

The code above should result in output like this:

Generating key using players 0,1,2
Generated key with id: RwmUOhDwMQ0SOqrDNTY7XYSEUpFD
...

This means you have successfully generated a new ECDSA key in the Builder Vault and used it for signing.

If you get error messages, go to our troubleshooting guide or contact our support team.