Emergency Recovery (EdDSA)

Emergency recovery works for EdDSA keys (Ed25519 and Ed448) exactly as it works for ECDSA keys described in the previous section.

The only difference, compared to the ECDSA example in the previous section, is that we now use the node.EdDSA() instead of node.ECDSA(), and when generating the key, we should use e.g., ec.Edwards25519.Name() instead of ec.Secp256k1.Name().

Using the Recovered Key

As described in our key derivation section, the TSM works with raw scalar keys and does not take into account the key derivation specified in RFC-8032. So the recovered EdDSA key is also a raw scalar value and not an RFC-8032 seed.

You can import the recovered scalar key into a new TSM and continue to generate signatures. But if you try to use the recovered (raw) key to generate signatures using 3rd party libraries, such as Noble, that follows RFC-8032, it will not work.

If you want to use the EdDSA key recovered from our ERS directly for signing, you must instead use a library that allows signing using the raw scalar private key.

Here is an example that shows how a recovered Ed25519 key can be used to sign messages, using a 3rd party library that lets us work with the raw scalar private key.

package main

import (
	"crypto/ed25519"
	"crypto/sha512"
	"encoding/hex"
	"filippo.io/edwards25519"
	"fmt"
)

func main() {

	// This is an example of a private key recovered using our ERS system
	privateKeyBytes, err := hex.DecodeString("08687f8a741cad9b34a6d2ccb232f5729e92d871e56a8f2e488404c1ed4525ac")
	if err != nil {
		panic(err)
	}

	// ERS recovers key in big endian, but filippo.io/edwards25519 expects little endian, so we convert here
	reverseSlice(privateKeyBytes)

	privateKey, err := edwards25519.NewScalar().SetCanonicalBytes(privateKeyBytes)
	if err != nil {
		panic(err)
	}

	// Public key is g^{privateKey} where g is the Ed25519 base point.
	publicKey := edwards25519.NewGeneratorPoint().ScalarBaseMult(privateKey)
	publicKeyBytes := publicKey.Bytes()

	message := []byte("hello world")

	signature, err := sign(publicKeyBytes, privateKeyBytes, message)
	if err != nil {
		panic(err)
	}

	// Verify works as usual; we can use the Golang standard lib method
	verified := ed25519.Verify(publicKeyBytes, message, signature)

	fmt.Println("private key..:", hex.EncodeToString(privateKeyBytes))
	fmt.Println("public key...:", hex.EncodeToString(publicKeyBytes))
	fmt.Println("message......:", hex.EncodeToString(message))
	fmt.Println("signature....:", hex.EncodeToString(signature))
	fmt.Println("verified.....:", verified)
}

// This is how we sign in the TSM (except that in the TSM the private key is secret shared throughout).
// It differs from the Golang standard lib ed25519.Sign() method (RFC-8032) in that we interpret the priavte key
// as a raw point, whereas RFC-8032 interprets it as a seed from which it derives the private key.
func sign(publicKey, privateKey, message []byte) (signature []byte, err error) {
	signature = make([]byte, 64)

	// The Golang ed25519.Sign() follows RFC80432 like this:
	// seed, publicKey := privateKey[:SeedSize], privateKey[SeedSize:]
	// h := sha512.Sum512(seed)
	// s, err := edwards25519.NewScalar().SetBytesWithClamping(h[:32])
	// prefix := h[32:]

	// We instead use the private key directly
	s, err := edwards25519.NewScalar().SetCanonicalBytes(privateKey)
	if err != nil {
		return nil, err
	}

	mh := sha512.New()

	// In the Golang std lib (RFC8032) the prefix is written here, but we use the actual public key
	// mh.Write(prefix)
	mh.Write(publicKey)

	mh.Write(message)
	messageDigest := make([]byte, 0, sha512.Size)
	messageDigest = mh.Sum(messageDigest)
	r, err := edwards25519.NewScalar().SetUniformBytes(messageDigest)
	if err != nil {
		panic("ed25519: internal error: setting scalar failed")
	}

	R := (&edwards25519.Point{}).ScalarBaseMult(r)

	kh := sha512.New()
	kh.Write(R.Bytes())
	kh.Write(publicKey)
	kh.Write(message)
	hramDigest := make([]byte, 0, sha512.Size)
	hramDigest = kh.Sum(hramDigest)
	k, err := edwards25519.NewScalar().SetUniformBytes(hramDigest)
	if err != nil {
		panic("ed25519: internal error: setting scalar failed")
	}

	S := edwards25519.NewScalar().MultiplyAdd(k, s, r)

	copy(signature[:32], R.Bytes())
	copy(signature[32:], S.Bytes())

	return signature, nil
}

func reverseSlice(b []byte) {
	l := len(b)
	for i := 0; i < l/2; i++ {
		tt := b[i]
		b[i] = b[l-1-i]
		b[l-1-i] = tt
	}
}

What’s Next