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.22
- 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 Builder Vault 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.comwith 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/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/versionIf 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.4For 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.
- Create a file
main.goin 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/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()))
}
- Replace the API keys for the MPC nodes in line 19-21 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.