Presignatures

The TSM allows you to split signature generation into two steps:

  1. Presignature Generation This step consists of an MPC session between the nodes, that results in the generation of one or more presignatures. Each presignature consists of secret sharings of a number of intermediate values (including a secret sharing of the random signing nonce) that are used to later generate the final signature. The presignature generation is the main bulk of work, including several rounds of communication between the MPC nodes. But importantly, presignatures can be computed in advance, before knowing the messages to sign.
  2. Non-Interactive Signing Given a presignature and a message to be signed, the final signature can be generated efficiently. In particular, this does not involve an MPC session with interaction between the MPC nodes.

If, for example, you already generated an EdDSA key in the TSM identified by keyID, the SDK call for requesting an MPC session generating presignatures for that key, looks like this:

presignatureIDs, err = client.EdDSA().GeneratePresignatures(ctx, sessionConfig, keyID, presigCount)

The sessionConfig should include the same MPC nodes as was used when generating the key.

Presignature generation can be done in batches, and presigCount specifies the number of presignatures to generate. The SDK returns an array presignaureIDs that contains IDs for each presignature in this batch.

The presignatures can then be used for signing as follows:

presignatureID := presignatureIDs[0] // Use the first presignature in the batch
partialSignResult, err := client.EdDSA().SignWithPresignature(ctx, keyID, presignatureID, nil, message)

This will instruct the MPC node to generate a partial signature based on the presignature and the provided message. Note that this does not request an MPC session. Instead, it just involves a single roundtrip between the SDK and its MPC node. In order to produce a valid signature, the MPC nodes must agree on the presignature ID and the message.

As an alternative to keeping track of the presignature ID yourself, you can also call one of the SDKs without providing a presignature ID. The MPC node will then use a random presignature among the set of presignatures that it holds, and it will return the presignature ID as part of the sign result. The returned presignature ID can then be provided when calling SignWithPresignature() on the remaining SDKs.

partialSignResult, err := client.EdDSA().SignWithPresignature(ctx, keyID, nil, derivationPath, message)
presignatureID := partialSignResult.PresignatureID

Just as with regular interactive signing the resulting partial signatures can be combined into the final signature:

partialSignatures := [][]byte{ partialSignResult1.partialSignature, ...}
signature, err := tsm.EDDSAFinalizeSignature(message, partialSignatures)

📘

Preventing Presignature Reuse

A presignature essentially consists of a shaing of the random signing nonce. Generally, with ECDSA and Schnorr signatures (such as EdDSA) it holds that if the same nonce is used to sign two different messages, the key leaks right away. It is therefor important that a presignature is only used for a single signature.

The MPC nodes ensure this by deleting a presignature when it is used for signing. Trying to sign another message with the same presignature ID will then simply result in an error message.

Use Cases

Presignatures may be useful in two cases:

  • Performance Using presignatures, the major part of the work can be performed at any time (because presignatures do not depend on the messages to sign). This allows to “even out” the load on the system and prepare presignatures for throughput of signature generation during peak hours. In cases where the MPC nodes are located world-wide, generating a single signature may take a second or more, because several rounds of computation is required between the MPC nodes. By preparing a presignature in advance, the latency experienced by the user providing the message to be signed, is significantly reduced.
  • Cold Signing Presignatures are also useful in the case where one or more of the MPC nodes are disconnected from the network. In that case, normal interactive signing would be impractical because it requires several rounds of communication between the disconnected MPC nodes and the remaining system. But if presignatures have been prepared (e.g., as part of the key generation), then a single roundtrip to each of the disconnected MPC nodes is sufficient.

2-out-of-3 Signing with Presignatures

With normal interactive signing, the TSM allows signing with a subset of MPC nodes, usually with threshold+1 MPC nodes. If the key was for example generated as a secret sharing among three MPC (n=3) nodes with a security threshold of one (t=1), then we can do 2-of-3 signing.

But when using presignatures, the TSM requires all MPC nodes to participate. The reason for this is to prevent re-using the same presignature to sign two different messages, which would leak the signing key as described above.

Consider an example with security threshold 1 and three MPC nodes, P1, P2, P3, together holding a presignature sharing. Suppose one of them, say P1, is corrupt (which is exactly what the MPC is supposed to protect against). Then P1 could just do 2-of-3 non-interactive online signing with P2 using one message, and 2-of-3 non-interactive online signing with P3 using another message, but the same presignature. That would give him signatures of two different messages, signed using the same nonce. And from that he can extract the signing key. If it is really non-interactive, the two honest nodes P2 and P3 have no chance of knowing that they are signing different messages using the same presignature.

This is not an issue with normal (interactive) signing, since the two honest players interact and will detect that they are about to use the same presignature to sign two different messages, and they will then just abort.

If 2-out-of-3 signing with presignaures is really needed, it can be done securely by generating separate presignatures (which will have different nonces) for each subset of 2 players. But this triples the amount of required presigs. And the number of required presigs grows exponentially for genereal n/2-of-n signing. So we chose not to support this directly.

Code Example

The following is a self-contained code example, showing how to generate a batch of presignatures and use them for non-interactive online signing.

package main

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

func main() {

	// Create clients for two of the nodes

	configs := []*tsm.Configuration{
		tsm.Configuration{URL: "http://localhost:8500"}.WithAPIKeyAuthentication("Node0LoginPassword"),
		tsm.Configuration{URL: "http://localhost:8502"}.WithAPIKeyAuthentication("Node2LoginPassword"),
	}

	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 ECDSA key

	threshold := 1         // Security threshold for this key
	players := []int{0, 2} // We want to use MPC node 0 and 2
	sessionID := tsm.GenerateSessionID()
	keyGenSessionConfig := tsm.NewSessionConfig(sessionID, players, nil)

	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(context.TODO(), 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, "on MPC nodes", players)

	// Generate ten presignatures

	presigSessionID := tsm.GenerateSessionID()
	presigSessionConfig := tsm.NewSessionConfig(presigSessionID, players, nil)
	presignatureIDs := make([][]string, len(players))

	for i, client := range clients {
		i, client := i, client
		eg.Go(func() error {
			var err error
			presignatureIDs[i], err = client.ECDSA().GeneratePresignatures(context.TODO(), presigSessionConfig, keyID, 10)
			return err
		})
	}

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

	// Validate presignature IDs

	for i := 1; i < len(presignatureIDs); i++ {
		if len(presignatureIDs[0]) != len(presignatureIDs[i]) {
			panic(fmt.Sprintf("size mismatch between index 0 (%d) and %d (%d)", len(presignatureIDs[0]), i, len(presignatureIDs[i])))
		}
		for j := 0; j < len(presignatureIDs[i]); j++ {
			if presignatureIDs[0][j] != presignatureIDs[i][j] {
				panic(fmt.Sprintf("values mismatch between index 0,%d (%s) and %d,%d (%s)", j, presignatureIDs[0][j], i, j, presignatureIDs[i][j]))
			}
		}
	}

	// We can now sign with the created key and one of the presignatures ;
	// this does not require MPC nodes to interact, so we don't have to do this concurrently.

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

	partialSignatures := make([][]byte, 0)
	for _, client := range clients {
		client := client
		partialSignResult, err := client.ECDSA().SignWithPresignature(context.TODO(), keyID, presignatureIDs[0][0], nil, msgHash[:])
		if err != nil {
			panic(err)
		}
		partialSignatures = append(partialSignatures, partialSignResult.PartialSignature)
	}

	// Combine partial signatures from each of the MPC nodes into the final signature

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

	fmt.Println("Signature:", hex.EncodeToString(signature.ASN1()))

}
package com.example;

import com.sepior.tsm.sdkv2.*;

import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Supplier;

public class EcdsaSignWithPresigExample {

    public static void main(String[] args) throws Exception {

        // Create a client for each MPC node

        Configuration[] configs = {
                new Configuration("http://localhost:8500"),
                new Configuration("http://localhost:8501"),
                new Configuration("http://localhost:8502"),
        };
        configs[0].withApiKeyAuthentication("Node0LoginPassword");
        configs[1].withApiKeyAuthentication("Node1LoginPassword");
        configs[2].withApiKeyAuthentication("Node2LoginPassword");

        Client[] clients = {
                new Client(configs[0]),
                new Client(configs[1]),
                new Client(configs[2]),
        };


        // Generate an ECDSA key

        final int[] keyGenPlayers = {0, 1, 2}; // The key should be secret shared among all three MPC nodes
        final int threshold = 1; // The security threshold for this key
        final String curveName = "secp256k1"; // We want the key to be a secp256k1 key (e.g., for Bitcoin)

        String keyGenSessionId = SessionConfig.generateSessionId();
        final SessionConfig keyGenSessionConfig = SessionConfig.newSessionConfig(keyGenSessionId, keyGenPlayers, null);

        System.out.println("Generating key using players " + Arrays.toString(keyGenPlayers));
        List<String> keyGenResults = runConcurrent(
                () -> clients[0].getEcdsa().generateKey(keyGenSessionConfig, threshold, curveName, null),
                () -> clients[1].getEcdsa().generateKey(keyGenSessionConfig, threshold, curveName, null),
                () -> clients[2].getEcdsa().generateKey(keyGenSessionConfig, threshold, curveName, null));
        String keyId = keyGenResults.get(0);
        System.out.println("Generated key with ID: " + keyId);

        // Generate ten presignatures for the key

        int[] presigGenPlayers = {0, 1, 2}; // We must use same set of players as when we generated the key
        int presigCount = 10;

        System.out.println("Generating " + presigCount + " presignatures using players " + Arrays.toString(presigGenPlayers));
        String presigGenSessionId = SessionConfig.generateSessionId();
        SessionConfig presigGenSessionConfig = SessionConfig.newSessionConfig(presigGenSessionId, presigGenPlayers, null);
        List<String[]> presigGenResults = runConcurrent(
                () -> clients[0].getEcdsa().generatePresignatures(presigGenSessionConfig, keyId, presigCount),
                () -> clients[1].getEcdsa().generatePresignatures(presigGenSessionConfig, keyId, presigCount),
                () -> clients[2].getEcdsa().generatePresignatures(presigGenSessionConfig, keyId, presigCount));
        String[] presigIds = presigGenResults.get(0);

        // We can now sign a message with the created key and one of the presignatures ;
        // this does not require MPC nodes to interact, so we don't have to do this concurrently.

        byte[] messageHash = new byte[32];  // Normally, this is the SHA256 hash of the message.
        int[] signPlayers = {0, 1, 2}; // All players needed for signing when using presignatures
        int[] derivationPath = null; // In this example we do not use key derivation

        System.out.println("Signing message with players " + Arrays.toString(signPlayers) + " and using presig " + presigIds[0]);

        // No MPC node interaction required; no need to do this in parallel
        byte[][] partialSignatures = {
                clients[0].getEcdsa().signWithPresignature(keyId, presigIds[0], derivationPath, messageHash).getPartialSignature(),
                clients[1].getEcdsa().signWithPresignature(keyId, presigIds[0], derivationPath, messageHash).getPartialSignature(),
                clients[2].getEcdsa().signWithPresignature(keyId, presigIds[0], derivationPath, messageHash).getPartialSignature(),
        };

        // Combine partial signatures from each of the MPC nodes into the final signature and validate it against the message

        EcdsaSignature signature = com.sepior.tsm.sdkv2.Ecdsa.finalizeSignature(messageHash, partialSignatures);
        System.out.println("Signature: 0x" + bytesToHex(signature.getSignature()));

    }


    @SafeVarargs
    static <T> List<T> runConcurrent(Supplier<T>... players) throws Exception {
        List<T> result = new ArrayList<T>(players.length);
        Queue<Exception> errors = new ConcurrentLinkedQueue<Exception>();
        for (int i = 0; i < players.length; i++) {
            result.add(null);
        }
        Thread[] threads = new Thread[players.length];
        for (int i = 0; i < players.length; i++) {
            final int index = i;
            Thread thread = new Thread() {
                public void run() {
                    try {
                        T runResult = players[index].get();
                        result.set(index, runResult);
                    } catch (Exception e) {
                        errors.add(e);
                    }
                }
            };
            threads[i] = thread;
            thread.start();
        }
        for (int i = 0; i < players.length; i++) {
            threads[i].join();
        }
        if (!errors.isEmpty()) {
            throw new RuntimeException("One of the threads failed executing command", errors.remove());
        }
        return result;
    }

    static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();

    public static String bytesToHex(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = HEX_ARRAY[v >>> 4];
            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
        }
        return new String(hexChars);
    }

}

This will generate output like this:

Generating key using players [0, 1, 2]
Generated key with ID: TtqCRe0IZOBPJUyA0k4tKRCQlKhv
Generating 10 presignatures using players [0, 1, 2]
Signing message with players [0, 1, 2] and using presig 2ygmejczidMcuPcAGJp3sW000000
Signature: 0x304402201556793A53443C3A5129C32DE390E60D695E79D71B1E8462426CEF9EDBD1B93C02207FD3606973A5E856651D0E23140BE2E898D01396F8A8323DE6E730F0895448AE