Java SDK

Obtaining the Java SDK

We use Maven to distribute our Java SDK. In order to download it, please add the Blockdaemon Builder Vault repository and Blockdaemon JNI dependency to your pom.xml as shown below.

And remember to provide Nexus credentials in your .m2/settings.xml.

<repositories>
  <repository>
    <id>sepior-server</id>
    <url>https://nexus.sepior.net/repository/sepior-jni</url>
  </repository>
</repositories>
 
<dependencies>
  <dependency>
    <groupId>com.sepior.jni</groupId>
    <artifactId>sepior-jni</artifactId>
    <version>50.0.7</version>
  </dependency>
<dependencies>

Notes on using the Java SDK

The Java SDK uses JNI to load the implementation of the methods. The Java code will do this by unpacking the shared library that matches the platform it is running to a temporary file, and then loads this into Java.

This can cause problems, e.g. on Linux if a separate /tmp/ partition is mounted, as this will typically have the noexec flag. This flag will prevent loading the .so file and cause the JNI library to fail.

There are two ways to solve this, either the tmp directory for java can be redirected using the JVM parameter:

java -Djava.io.tmpdir=/path/to/tmpdir <application>

or an environment variable can be set to do the same for the shared library:

export SEPIOR_NATIVE_DIRECTORY="/path/to/tmpdir"
java <application>

Example showing how to use the Java SDK

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import com.sepior.tsm.sdk.NativeSdk;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.ProviderException;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;

class Example {
    
    public static void main(String[] args) throws Exception {
        NativeSdk sdk = getNativeSdk();
        
        String keyID = generateKey(sdk);
        
        final byte[] hash = new byte[32];
        byte[] signature = sign(sdk, keyID, hash);
        
        verify(sdk, keyID, hash, signature);
        
        sdk.deleteKey(keyID);
    }
    
    static NativeSdk getNativeSdk() {
        NativeSdk s = new NativeSdk();
        s.init(getCredentialsJson());
        s.setNetworkTimeout(30);
        
        // Used to validate signatures, not the for the Sepior JCE SDK
        Security.addProvider(new BouncyCastleProvider());
        
        return s;
    }

    static String getCredentialsJson() {
        final String path = System.getenv("CREDENTIALS_PATH");
        if (path == null) {
            throw new ProviderException(String.format("Credentials string not set for %s", "CREDENTIALS_PATH"));
        }
        try {
            return new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8);
        } catch(IOException e){
            throw new ProviderException("Example could not read Credentials file " + path, e);
        }
    }

    static String generateKey(NativeSdk sdk) {
        final String keyID = sdk.ecdsaKeygenWithSessionID(sdk.generateSessionID(),"secp256k1");

        return keyID;
    }
    
    static byte[] sign(NativeSdk sdk, String keyID, byte[] hash) {
        final byte[] sigP = sdk.ecdsaPartialSign(sdk.generateSessionID(), keyID, null, hash);
        
        final byte[][] ps = new byte[][]{sigP};
        final NativeSdk.SignatureWithRecoveryID fs = sdk.ecdsaFinalize(ps);
        
        return fs.getSignature();
    }

    static void verify(NativeSdk sdk, String keyID, byte[] hash, byte[] signature) throws Exception {
        final byte[] publicKey = sdk.ecdsaPublicKey(keyID, null);
        sdk.ecdsaVerify(publicKey, hash, signature);
        
        bcValidateSignature(publicKey, hash, signature);
    }

    static void bcValidateSignature(byte[] pkb, byte[] message, byte[] signature) throws Exception {
        final X509EncodedKeySpec spec = new X509EncodedKeySpec(pkb);
        final KeyFactory kf = KeyFactory.getInstance("EC", "BC");
        PublicKey publicKey = kf.generatePublic(spec);
        
        Signature verifier = Signature.getInstance("noneWithECDSA", "BC");
        verifier.initVerify(publicKey);
        verifier.update(message);
        boolean result = verifier.verify(signature);
        if (!result) {
            throw new RuntimeException("Signature validation failed");
        }
	System.out.println("Successfully verified signature");
    }
}

Example with multiple SDKs with one node each and presignatures

import com.sepior.tsm.sdk.NativeSdk;

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.ProviderException;
import java.util.List;

public class Example2 {

    public static void main(String[] args) throws Exception {
        // Setup
        System.out.println("Setting up SDKs");
        NativeSdk sdk0 = getNativeSdk(0);
        NativeSdk sdk1 = getNativeSdk(1);
        NativeSdk sdk2 = getNativeSdk(2);

        String sessionId1 = sdk0.generateSessionID();
        String sessionId2 = sdk0.generateSessionID();
        
        // Create and run setup threads
        System.out.println("Run key gen and presig generation");
        RunSetup setup0 = new RunSetup(sdk0, sessionId1, sessionId2);
        RunSetup setup1 = new RunSetup(sdk1, sessionId1, sessionId2);
        RunSetup setup2 = new RunSetup(sdk2, sessionId1, sessionId2);
        
        setup0.start();
        setup1.start();
        setup2.start();
        
        // Wait for threads to finish
        System.out.println("Waiting to finish");
        setup0.join();
        setup1.join();
        setup2.join();
        
        // Create signatures using presigs
        System.out.println("Create signature shares");
        byte[] hash = new byte[32];
        String keyId = setup0.getKeyId();
        List<String> presigIds = setup0.getPresigIds();
        NativeSdk.PartialSignatureWithPublicKeyWithPresigID sig0 = sdk0.ecdsaPartialSignWithPresig(keyId, presigIds.get(0), null, hash);
        NativeSdk.PartialSignatureWithPublicKeyWithPresigID sig1 = sdk1.ecdsaPartialSignWithPresig(keyId, presigIds.get(0), null, hash);
        NativeSdk.PartialSignatureWithPublicKeyWithPresigID sig2 = sdk2.ecdsaPartialSignWithPresig(keyId, presigIds.get(0), null, hash);
        
        System.out.println("Collect signature");
        final byte[][] ps = new byte[][] {
            sig0.getPartialSignature(),
            sig1.getPartialSignature(),
            sig2.getPartialSignature()
        };
        final NativeSdk.SignatureWithRecoveryID fs = sdk0.ecdsaFinalize(ps);
        
        System.out.println("Verify");
        verify(sdk0, keyId, hash, fs.getSignature());
        
        System.out.println("Done");
    }
    
    static NativeSdk getNativeSdk(int id) throws URISyntaxException {
        NativeSdk s = new NativeSdk();
        s.init(getCredentialsJson(id));
        s.setNetworkTimeout(30);
        
        return s;
    }

    static String getCredentialsJson(int id) throws URISyntaxException {
        Path path = Paths.get(".", String.format("/creds%d.json", id));
       
        try {
            return new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
        } catch(IOException e){
            throw new ProviderException("Example could not read Credentials file " + path, e);
        }
    }

    static void verify(NativeSdk sdk, String keyID, byte[] hash, byte[] signature) throws Exception {
        final byte[] publicKey = sdk.ecdsaPublicKey(keyID, null);
        sdk.ecdsaVerify(publicKey, hash, signature);
    }

    static class RunSetup extends Thread {
        private final NativeSdk sdk;
        private final String sessionId1, sessionId2;
        private String keyId;
        private List<String> presigIds;

        RunSetup(NativeSdk sdk, String sessionId1, String sessionId2) {
            this.sdk = sdk;
            this.sessionId1 = sessionId1;
            this.sessionId2 = sessionId2;
        }

        public void run() {
            keyId = sdk.ecdsaKeygenWithSessionID(sessionId1, "secp256k1");
            
            presigIds = sdk.ecdsaPresigGenWithSessionID(sessionId2, keyId, 20);
        }

        public String getKeyId() {
            return keyId;
        }

        public List<String> getPresigIds() {
            return presigIds;
        }
    }
}