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
- Oracle Java SDK version 1.8 or later
- Maven2
- Credentials for accessing https://nexus.sepior.net. Contact the Blockdaemon support team to get the required credentials. In the following we will use NEXUS_USERNAME and NEXUS_PASSWORD, but remember to replace these with the actual credentials obtained.
- 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 the public repo for more details.
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.
-
Create a new folder for your project on your local machine.
-
Open a terminal window and go to the project folder.
-
Create a file
pom.xml
with the following contents in the project folder: -
<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>68.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 68.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 68.0.0, then update the
pom.xml
file to fetch the new version.For the full list of Builder Vault versions, please see here.
- Create a file
settings.xml
in the project folder, containing the following:
<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.
- Next, create the folders
src/main/java/com/example
, e.g., by running this in the project folder:
mkdir -p src/main/java/com/example
- Create a file
EcdsaExample.java
in theexample
subfolder you just created, with the following contents:
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 EcdsaExample {
public static void main(String[] args) throws Exception {
// Create a client for each MPC node
Configuration[] configs = {
new Configuration("https://node1.tsm.sepior.net"),
new Configuration("https://node2.tsm.sepior.net"),
new Configuration("https://node3.tsm.sepior.net"),
};
configs[0].withApiKeyAuthentication("NODE1_API_KEY");
configs[1].withApiKeyAuthentication("NODE2_API_KEY");
configs[2].withApiKeyAuthentication("NODE3_API_KEY");
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[] chainPath = null; // In this example we do not use key derivation
String keyGenSessionId = SessionConfig.generateSessionId();
Map<Integer, byte[]> dynamicPublicKeys = new HashMap<Integer, byte[]>();
dynamicPublicKeys.put(0, null);
final SessionConfig keyGenSessionConfig = SessionConfig.createDynamicConfig(keyGenSessionId, keyGenPlayers, dynamicPublicKeys);
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
byte[] publicKey = clients[0].getEcdsa().publicKey(keyId, chainPath);
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.
String signSessionId = SessionConfig.generateSessionId();
final SessionConfig signSessionConfig = SessionConfig.createDynamicConfig(signSessionId, signPlayers, dynamicPublicKeys);
System.out.println("Signing message with players " + Arrays.toString(signPlayers));
List<EcdsaPartialSignResult> signResults = runConcurrent(
() -> clients[1].getEcdsa().sign(signSessionConfig, keyId, chainPath, msgHash),
() -> clients[2].getEcdsa().sign(signSessionConfig, keyId, chainPath, 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<T>(players.length);
Queue<Exception> errors = new ConcurrentLinkedQueue<Exception>();
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);
}
}
- Replace the API keys in the code above with the API keys for the MPC nodes in the Builder Vault instance you are using.
- Now fetch the Java SDK by running this command in the project folder:
mvn install -s ./setting
- Finally, you can run the example using:
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.
Updated 8 days ago