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

Step 1: Create a Go Project and Fetch the Builder Vault SDK

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

go mod init example.com
go get gitlab.com/Blockdaemon/go-tsm-sdkv2/v73

📘

Builder Vault 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. If the MPC node has the URL https://mpc.node.net, you can see which version the node runs with this Linux command:

curl https://mpc.node.net/version

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

go get gitlab.com/Blockdaemon/go-tsm-sdkv2/v70@v70.2.4

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

Once fetched, you can import the SDK in your code like this:

import (
    "gitlab.com/Blockdaemon/go-tsm-sdkv2/v73/tsm"
    "gitlab.com/Blockdaemon/go-tsm-sdkv2/v73/tsm/tsmutils"
)

Step 2: Use the SDK to Generate a Key and Sign

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.

package main

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

func main() {

// Create clients for each of the nodes

configs := []*tsm.Configuration{
        tsm.Configuration{URL: "http://localhost:8500"}.WithAPIKeyAuthentication("apikey0"),
        tsm.Configuration{URL: "http://localhost:8501"}.WithAPIKeyAuthentication("apikey1"),
        tsm.Configuration{URL: "http://localhost:8502"}.WithAPIKeyAuthentication("apikey2"),
    }

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, "secp256k1", "")
            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.Equal(publicKeys[0], publicKeys[i]) {
            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()))
}
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.