GO SDK

This article will guide you through the process of connecting the TSM Node SDK V1 with our Docker Demo in just four straightforward steps. Before proceeding with these steps, please ensure that you have completed the prerequisites outlined below:

Prerequisites

  • Make sure that you are using either Linux or Mac OS to install SDK
  • Install GO
  • Install the Docker Desktop Application
  • Install Make
  • Contact our support team to get:

1. Launch the Demo TSM via Docker

To run the demo TSM via Docker, follow the steps below:

  1. Unzip the docker setup zip file (demo-tsm.zip) that you got from our support team.
  2. Open the folder in your terminal.
  3. Login to Docker by using the code below:
docker login -u <yourUsername> -p <yourPassword> nexus.sepior.net:19001

πŸ“˜

Note:

Replace the username and password in the provided configuration with the credentials supplied by our support team.

  1. Run the TSM locally by using the code below:
make setup
  1. The TSM runs locally with three MPC Nodes that have been configured before. The MPC nodes can be reached from the host machine using the following addresses:
    • TSM Node 1: http://localhost:8500/ping
    • TSM Node 2: http://localhost:8501/ping
    • TSM Node 3: http://localhost:8502/ping
  2. You have the flexibility to modify the Docker setup configuration file to suit your requirements. Feel free to make any desired edits. For a complete list of available configurations, please see here.
  3. Additionally, you have the option to employ the following command while executing your demo TSM:
docker compose up    # Start the TSM
docker compose stop  # Stop the TSM (without whipping the databases)
docker compose down  # Stop the TSM and wipe all databases
docker compose logs  # See tsm logs

2. Fetch the TSM SDK

To install and initialize the SDK on your local machine, follow the steps below:

  1. Create a new folder for your project.
  2. Open a terminal in your local machine.
  3. Set the GOPRIVATE environment variable to the gitlab.com/sepior in a Unix-like shell by using the command below:
export GOPRIVATE=gitlab.com/sepior
  1. Add a configuration entry for authenticating with gitlab.com/sepior by using the command below:
echo "machine gitlab.com login <yourUsername> password <yourPassword>" >> $HOME/.netrc

πŸ“˜

Note:

Replace the username and password in the provided configuration with the credentials supplied by our support team.

  1. Initialize the Go module on your newly installed SDK project, by using the code below:
go mod init example.com

πŸ“˜

Note:

You can replace 'example.com' with the desired module path for your project.

  1. Since the demo TSM is currently at version 54.2.0, please use the following code to check out this specific version of the SDK:
go get gitlab.com/sepior/[email protected]

πŸ“˜

Note:

For the full list of our SDK version, please see here.

3. Generate Credentials for the TSM

The local process requires you to generate your own credentials for the MPC nodes of the TSM. You can generate the credentials by following the steps below:

  1. In your new SDK folder, create a new GO file. For example, we named the file β€œinitialization.go”.
  2. Paste the code below into the newly created file:
package main

import (
	"fmt"
	"gitlab.com/sepior/go-tsm-sdk/sdk/tsm"
	"net/url"
	"os"
	"time"
)

func main() {

	const (
		playerCount = 3 // Number of MPC nodes in the TSM
		threhsold   = 1 // Security threshold
	)

	// Create an admin client that connects to all MPC nodes

	servers := []string{"http://localhost:8500", "http://localhost:8501", "http://localhost:8502"}
	var nodes []tsm.Node
	for _, s := range servers {
		u, err := url.Parse(s)
		if err != nil {
			panic(err)
		}
		nodes = append(nodes, tsm.NewURLNode(*u, tsm.NullAuthenticator{}))
	}
	client := tsm.NewClient(playerCount, threhsold, nodes)
	ac := tsm.NewAdminClient(client)

	version, err := ac.TSMVersion()
	if err != nil {
		fmt.Println("Could not ping. Retrying...")
		time.Sleep(time.Second)
		version, err = ac.TSMVersion()
	}
	if err != nil {
		fmt.Println("Could not ping servers")
		panic(err)
	}
	fmt.Printf("TSM version: %s\n", version)

	// Use the admin client to create an initial admin user and save credentials to 'admin.json'.

	uc := tsm.NewUsersClient(client)
	adminCreds, err := uc.CreateInitialAdmin()
	if err != nil {
		fmt.Printf("Could not create initial admin: %s\n", err)
		fmt.Println("Exiting. We expect the TSM has already been initialized.")
		return
	}
	fmt.Println("Created initial admin with user ID", adminCreds.UserID)
	adminJson, err := adminCreds.Encode()
	if err != nil {
		panic(err)
	}
	err = os.WriteFile("admin.json", []byte(adminJson), 0666)
	if err != nil {
		panic(err)
	}

	// Log in as the initial admin, create a regular user, and save the user's credentials in 'user.json'.

	admClient, err := tsm.NewPasswordClientFromCredentials(3, 1, adminCreds)
	if err != nil {
		panic(err)
	}
	uc = tsm.NewUsersClient(admClient)
	userCreds, err := uc.CreatePasswordUser("user", "")
	fmt.Println("Created regular user with user ID", userCreds.UserID)
	userJson, err := userCreds.Encode()
	if err != nil {
		panic(err)
	}
	err = os.WriteFile("user.json", []byte(userJson), 0666)
	if err != nil {
		panic(err)
	}
}

πŸ“˜

Note:

Make sure to substitute the URLs with the addresses of the TSM Nodes you've deployed using Docker, especially if you decide to deploy them on different URLs than the ones we've provided above.

  1. Call the command below on your terminal to run the code above:
go run initialization.go
  1. This process will generate two distinct JSON-format files: one containing admin keys and the other containing user keys for connecting to the TSM.

4. Generate Keys and Signatures using the TSM

You can run the TSM with a separate SDK for each MPC node. This ensures that crucial tasks require everyone, or a majority of designated operators, to give the green light. This adds an extra layer of security and control. It's like a group decision, making sure that important actions meet certain criteria, like transaction amounts, before they're allowed to proceed. Follow the steps below to generate a key in the TSM and use it to sign:

  1. Create a new go file in your SDK folder, then use the code provided below:
package main

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"gitlab.com/sepior/go-tsm-sdk/sdk/tsm"
	"golang.org/x/sync/errgroup"
	"os"
)

func main() {

	const (
		playerCount = 3 // Number of MPC nodes in the TSM
		threshold   = 1 // The security threshold
	)

	credsBytes, err := os.ReadFile("user.json")
	if err != nil {
		panic(err)
	}
	var creds tsm.PasswordCredentials
	if err := creds.UnmarshalJSON(credsBytes); err != nil {
		panic(err)
	}

	// Create individual clients for each MPC node

	ecdsaClients := make([]tsm.ECDSAClient, playerCount)
	for player := 0; player < playerCount; player++ {
		credsPlayer := tsm.PasswordCredentials{
			UserID:    creds.UserID,
			URLs:      []string{creds.URLs[player]},
			Passwords: []string{creds.Passwords[player]},
		}
		client, err := tsm.NewPasswordClientFromCredentials(playerCount, threshold, credsPlayer)
		if err != nil {
			panic(err)
		}
		ecdsaClients[player] = tsm.NewECDSAClient(client)
	}

	// Generate ECSDA key

	keyGenSessionID := tsm.GenerateSessionID()
	var keyID string
	var eg errgroup.Group
	for i := 0; i < playerCount; i++ {
		i := i
		eg.Go(func() error {
			var err error
			keyID, err = ecdsaClients[i].KeygenWithSessionID(keyGenSessionID, "secp256k1")
			return err
		})
	}
	if err = eg.Wait(); err != nil {
		panic(err)
	}
	fmt.Println("Generated key with ID:", keyID)

	// Generate partial signatures using the key

	message := []byte("This is the message to be signed")
	msgHash := sha256.Sum256(message)
	chainPath := []uint32{2, 5} // Sign using the derived key m/2/5

	players := []int{0, 2} // Choose a subset of threshold+1 players to participate in signature generation
	partialSignatures := make([][]byte, len(players))

	// The call to PartialSign is blocking, so we must call each ecdsaClient concurrently.
	signSessionID := ecdsaClients[0].GenerateSessionID()
	fmt.Println("Generating signature using players", players)
	for i, player := range players {
		i, player := i, player
		eg.Go(func() error {
			var err error
			partialSignatures[i], err = ecdsaClients[player].PartialSign(signSessionID, keyID, chainPath, msgHash[:], players...)
			return err
		})
	}
	if err := eg.Wait(); err != nil {
		panic(err)
	}

	// Combine the partial signatures into an actual signature

	signature, _, err := tsm.ECDSAFinalize(partialSignatures...)
	if err != nil {
		panic(err)
	}

	fmt.Println("Signature:", hex.EncodeToString(signature))
}
  1. The provided SDK code will autonomously establish connections with all MPC nodes, acquire signature shares from each node, combine them, and then provide the final signature and keyID as shown below:
Generated key with ID: AIWKvP7mXI5h9SMN1dvDMK12jdzl
Generating signature using players [0 2]
Signature: 3045022100cf8493ed985d3ef03b0fd283ca9ff84b12a50e93762aabc4a7b91f7ad033897e02207bbd8dff6f7ef052a8ac11fd81dad0e3d846002cc6c73e26ae7f830bbb77c55d