Java SDK

This tutorial has been tested on Linux and Mac OS. If you use another OS, some steps may be a bit different.

Prerequisites

Create a Project using the TSM Java SDK

In the following steps we will create a small Maven project that uses the Builder Vault Java SDK.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>com.example</groupId>
      <artifactId>my-app</artifactId>
      <version>1.0-SNAPSHOT</version>
      <properties>
          <maven.compiler.source>1.8</maven.compiler.source>
          <maven.compiler.target>1.8</maven.compiler.target>
          <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      </properties>

<repositories>
      <repository>
        <id>sepior-server</id>
        <url>https://nexus.sepior.net/repository/sepior-jni</url>
      </repository>
      <repository>
        <id>maven-public</id>
        <url>https://nexus.sepior.net/repository/maven-public</url>
      </repository>
    </repositories>

<dependencies>
          <dependency>
              <groupId>com.sepior.tsm.sdkv2</groupId>
              <artifactId>sdkv2</artifactId>
              <version>73.0.0</version>
          </dependency>
      </dependencies>

</project>

TSM Version

When you connect to the Builder Vault with the SDK, it is important that the version of the SDK matches the version of the Builder Vault TSM. In the above file, we specified that SDK version 73.0.0 should be fetched.

You can see the actual version of an MPC node in the Builder Vault TSM using this Linux command:

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

If the returned version differs from 73.0.0, then update the pom.xmlfile to fetch the new version.

For the full list of Builder Vault versions, please see here.

<settings>
    <servers>
        <server>
            <id>sepior-server</id>
            <username>NEXUS_USERNAME</username>
            <password>NEXUS_PASSWORD</password>
        </server>
        <server>
            <id>sepior-maven-mirror</id>
            <username>NEXUS_USERNAME</username>
            <password>NEXUS_PASSWORD</password>
        </server>
    </servers>
    <mirrors>
        <mirror>
            <id>sepior-maven-mirror</id>
            <name>Central mirror</name>
            <url>https://nexus.sepior.net/repository/maven-public</url>
            <mirrorOf>central</mirrorOf>
        </mirror>
    </mirrors>
</settings>

Remember to replace NEXUS_USERNAME and NEXUS_PASSWORD in the above file with the credentials supplied by our support team.

mkdir -p src/main/java/com/example
package com.example;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Supplier;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;

import com.sepior.tsm.sdkv2.Configuration;
import com.sepior.tsm.sdkv2.Client;
import com.sepior.tsm.sdkv2.Ecdsa;
import com.sepior.tsm.sdkv2.SessionConfig;
import com.sepior.tsm.sdkv2.EcdsaPartialSignResult;
import com.sepior.tsm.sdkv2.EcdsaSignature;

public class EcdsaSignExample {

public static void main(String[] args) throws Exception {

// Create a client for each MPC node

Configuration[] configs = {
            new Configuration("http://localhost:8500"),
            new Configuration("http://localhost:8501"),
            new Configuration("http://localhost:8502"),
        };
        configs[0].withApiKeyAuthentication("apikey0");
        configs[1].withApiKeyAuthentication("apikey1");
        configs[2].withApiKeyAuthentication("apikey2");

Client[] clients = {
            new Client(configs[0]),
            new Client(configs[1]),
            new Client(configs[2]),
        };

// Generate an ECDSA key

final int[] keyGenPlayers = {0, 1, 2}; // The key should be secret shared among all three MPC nodes
        final int threshold = 1; // The security threshold for this key
        final String curveName = "secp256k1"; // We want the key to be a secp256k1 key (e.g., for Bitcoin)
        final int[] derivationPath = null; // In this example we do not use key derivation

String keyGenSessionId = SessionConfig.generateSessionId();
        final SessionConfig keyGenSessionConfig = SessionConfig.newSessionConfig(keyGenSessionId, keyGenPlayers, null);

System.out.println("Generating key using players " + Arrays.toString(keyGenPlayers));
        List<String> results = runConcurrent(
                () -> clients[0].getEcdsa().generateKey(keyGenSessionConfig, threshold, curveName, null),
                () -> clients[1].getEcdsa().generateKey(keyGenSessionConfig, threshold, curveName, null),
                () -> clients[2].getEcdsa().generateKey(keyGenSessionConfig, threshold, curveName, null));
        String keyId = results.get(0);

System.out.println("Generated key with ID: " + keyId);

// Get the public key from one of the MPC nodes

byte[] publicKey = clients[0].getEcdsa().publicKey(keyId, derivationPath);
        System.out.println("Public key: 0x" + bytesToHex(publicKey));

// Remember: Depending on your use case, you may need to check that all or at least threshold + 1 clients agree on the
        // public key, before using it, e.g. for creating a cryptocurrency account.

// Sign a message using the private key

final int[] signPlayers = {1, 2}; // We sign with threshold + 1 players, in this case MPC node 1, 2
        final byte[] msgHash = new byte[32];  // Normally, this is the SHA256 hash of the message.

System.out.println("Signing message with players " + Arrays.toString(signPlayers));
        String signSessionId = SessionConfig.generateSessionId();
        final SessionConfig signSessionConfig = SessionConfig.newSessionConfig(signSessionId, signPlayers, null);
        List<EcdsaPartialSignResult> signResults = runConcurrent(
                () -> clients[1].getEcdsa().sign(signSessionConfig, keyId, derivationPath, msgHash),
                () -> clients[2].getEcdsa().sign(signSessionConfig, keyId, derivationPath, msgHash));
        byte[][] partialSignatures = {signResults.get(0).getPartialSignature(), signResults.get(1).getPartialSignature()};
        EcdsaSignature signature = Ecdsa.finalizeSignature(msgHash, partialSignatures);
        System.out.println("Signature: 0x" + bytesToHex(signature.getSignature()));

// Validate the signature

boolean valid = Ecdsa.verifySignature(publicKey, msgHash, signature.getSignature());
        System.out.println("Signature validity: " + valid);

}

@SafeVarargs
    static <T> List<T> runConcurrent(Supplier<T>... players) throws Exception {
        List<T> result = new ArrayList<>(players.length);
        Queue<Exception> errors = new ConcurrentLinkedQueue<>();
        for (int i = 0; i < players.length; i++) {
            result.add(null);
        }
        Thread[] threads = new Thread[players.length];
        for (int i = 0; i < players.length; i++) {
            final int index = i;
            Thread thread = new Thread() {
                public void run() {
                    try {
                        T runResult = players[index].get();
                        result.set(index, runResult);
                    } catch (Exception e) {
                        errors.add(e);
                    }
                }
            };
            threads[i] = thread;
            thread.start();
        }
        for (int i = 0; i < players.length; i++) {
            threads[i].join();
        }
        if (!errors.isEmpty()) {
            throw new RuntimeException("One of the threads failed executing command", errors.remove());
        }
        return result;
    }

static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
    public static String bytesToHex(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = HEX_ARRAY[v >>> 4];
            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
        }
        return new String(hexChars);
    }

}
mvn install -s ./setting
mvn compile exec:java -Dexec.mainClass="com.example.EcdsaExample"

If everything went well, you should see output similar to this:

Generating key using players [0, 1, 2]
Generated key with ID: xKzoQHdFxpTHojTXiAyJJ6M3wkt7
public key: 0x3056301006072A8648CE3D020106052B8104000A034200046670B5786F24AADAD7C62C66AAD189CA30453AED83FAD5BB14F7F75E82B3B7CD346A9B928F2E3F5D6A4E271F1CCE232947ED371D1ED7BE878BEA1778D87349CE
Signing message with players [1, 2]
Signature: 0x3045022100BDE4D344AF93A33C997B3B50F25F0785AFDC7B5F84C12AD84CC7AC795A2D2255022056E10AC618F026132090817306FDF8708DA2A337C5BBFE040DD8797C53535E12
Signature validity: true

This means that you have successfully used the Builder Vault SDK to connect to the Builder Vault TSM, where you generated a new ECDSA key and used it for signing.

If you see error messages instead, you can consult our troubleshooting guide or contact our support team.