Go SDK

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

Prerequisites

  • Go version 1.21
  • 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.

Step 1: Create a Go Project and Fetch the TSM SDK

In the following steps we will create a small Go project that uses the Builder Vault Go SDK:

  • Create a new folder for your project on your local machine
  • Open a terminal window and go to the project folder.
  • Initialize the Go module by executing the following command in the project folder (you can replace example.com) with the desired module path for your project):
go mod init example.com
  • Check out the latest version of the SDK, by using the following command:
go get gitlab.com/Blockdaemon/go-tsm-sdkv2

πŸ“˜

TSM Version

When you connect to a 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 62.1.0, you can fetch the corresponding version of the SDK with this:

go get gitlab.com/Blockdaemon/[email protected]

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

Step 2: Use the SDK for Key Generation and Signing

In the next steps we will create a small Go program that (1) generates an ECDSA key in the TSM; (2) prints the public ECDSA key; and (3) signs a message using the newly generated key.

  • Create a file main.go in the project folder, with the following contents. Don't worry about the details for now.
package main

import (
	"bytes"
	"context"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"gitlab.com/Blockdaemon/go-tsm-sdkv2/ec"
	"gitlab.com/Blockdaemon/go-tsm-sdkv2/tsm"
	"golang.org/x/sync/errgroup"
	"sync"
)

func main() {

	// Create clients for each of the nodes

	configs := []*tsm.Configuration{
		tsm.Configuration{URL: "https://node1.tsm.sepior.net"}.WithAPIKeyAuthentication("NODE1_API_KEY"),
		tsm.Configuration{URL: "https://node2.tsm.sepior.net"}.WithAPIKeyAuthentication("NODE2_API_KEY"),
		tsm.Configuration{URL: "https://node3.tsm.sepior.net"}.WithAPIKeyAuthentication("NODE3_API_KEY"),
	}

	clients := make([]*tsm.Client, len(configs))
	for i, config := range configs {
		var err error
		if clients[i], err = tsm.NewClient(config); err != nil {
			panic(err)
		}
	}

	// Generate an ECDSA key

	threshold := 1                  // The security threshold for this key
	keyGenPlayers := []int{0, 1, 2} // The key should be secret shared among all three MPC nodes
	keyGenSessionConfig := tsm.NewSessionConfig(tsm.GenerateSessionID(), keyGenPlayers, nil)

	fmt.Println("Generating key using players", keyGenPlayers)
	ctx := context.Background()
	keyIDs := make([]string, len(clients))
	var eg errgroup.Group
	for i, client := range clients {
		client, i := client, i
		eg.Go(func() error {
			var err error
			keyIDs[i], err = client.ECDSA().GenerateKey(ctx, keyGenSessionConfig, threshold, ec.Secp256k1.Name(), "")
			return err
		})
	}
	if err := eg.Wait(); err != nil {
		panic(err)
	}

	// Validate key IDs

	for i := 1; i < len(keyIDs); i++ {
		if keyIDs[0] != keyIDs[i] {
			panic("key IDs do not match")
		}
	}
	keyID := keyIDs[0]
	fmt.Println("Generated key with ID:", keyID)

	// Get the public key

	var derivationPath []uint32 = nil // We don't use key derivation in this example

	publicKeys := make([][]byte, len(clients))
	for i, client := range clients {
		var err error
		publicKeys[i], err = client.ECDSA().PublicKey(ctx, keyID, derivationPath)
		if err != nil {
			panic(err)
		}
	}

	// Validate public keys

	for i := 1; i < len(publicKeys); i++ {
		if bytes.Compare(publicKeys[0], publicKeys[i]) != 0 {
			panic("public keys do not match")
		}
	}
	publicKey := publicKeys[0]
	fmt.Println("Public key:", hex.EncodeToString(publicKey))

	// We can now sign with the created key

	message := []byte("This is a message to be signed")
	msgHash := sha256.Sum256(message)

	signPlayers := []int{0, 1} // We want to sign with the first two MPC nodes
	sessionID := tsm.GenerateSessionID()
	signSessionConfig := tsm.NewSessionConfig(sessionID, signPlayers, nil)

	fmt.Println("Creating signature using players", signPlayers)
	partialSignaturesLock := sync.Mutex{}
	var partialSignatures [][]byte
	for _, player := range signPlayers {
		player := player
		eg.Go(func() error {
			if partialSignResult, err := clients[player].ECDSA().Sign(ctx, signSessionConfig, keyID, derivationPath, msgHash[:]); err != nil {
				return err
			} else {
				partialSignaturesLock.Lock()
				partialSignatures = append(partialSignatures, partialSignResult.PartialSignature)
				partialSignaturesLock.Unlock()
				return nil
			}
		})
	}

	if err := eg.Wait(); err != nil {
		panic(err)
	}

	signature, err := tsm.ECDSAFinalizeSignature(msgHash[:], partialSignatures)
	if err != nil {
		panic(err)
	}

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

	if err = tsm.ECDSAVerifySignature(publicKey, msgHash[:], signature.ASN1()); err != nil {
		panic(err)
	}

	fmt.Println("Signature:", hex.EncodeToString(signature.ASN1()))
}
  • Replace the API keys for the MPC nodes in line 20-22 above with the actual API keys for your Builder Vault instance.
  • Execute the following commands in the project folder:
go mod tidy
go run main.go

You should now see some output similar to this:

Generating key using players [0 1 2]
Generated key with ID: ymdAAttlPmCqKp0vLgz7re4k87As
Public key: 3056301006072a8648ce3d020106052b8104000a03420004a50fbc4dbb0fb9157b26408fc3dbab1b42081668fc0212e1edd97bbd6906a3ee7b3fe69323e401b2b4117edff329879adc1f82e4ef6a1f0f89f084a59665df9f
Creating signature using players [0 1]
Signature: 304402204c33273dec7dac0ff510a1f98036ce62987da8174bb4d4a8456686732096449a022063ef69e1be58275c2cb341b0dcd17d586a28fe647453397d5c9cd5243577df63

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

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