EdDSA Key Derivation
We currently only support non-hardened key derivation for EdDSA (Ed25519 and Ed448).
Note that our EdDSA key derivation scheme is not SLIP10 compliant. SLIP10 only defines hardened key derivation for EdDSA. This is because SLIP10 assumes deterministic signing as specified in RFC-8032. In deterministic signing, the "raw" private key as well as a prefix is derived from a seed. And when signing, the signing nonce is computed as a hash of the public key, the message, and the prefix. This approach is not compatible with non-hardened derivation.
During key generation we instead directly generate a random secret sharing of the raw private key. Then, when signing, we use a fresh random nonce for each signature generation. This approach lets us do non-hardened derivation in much the same way as in BIP32. As for BIP32, non-hardened derivation for EdDSA keys does not affect security in our case, since the derived keys are protected in the same way (by secret sharing) as the master key.
For the non-hardened EdDSA derivation we use multiplicative offset, contrary to BIP32 which uses an additive offset for the derived keys. See this for more information about the difference between additive and multiplicative offsets in key derivation.
Code Example
package main
import (
"encoding/hex"
"fmt"
"gitlab.com/sepior/go-tsm-sdk/sdk/tsm"
"golang.org/x/sync/errgroup"
"log"
"os"
"sync"
)
func main() {
// Configure your TSM here
const credentials string = `
{
"userID": "my-user-id",
"urls": [ "https://my-tsm-node1.tsm.sepior.net", "https://my-tsm-node2.tsm.sepior.net", "https://my-tsm-node3.tsm.sepior.net" ],
"passwords": [ "password1", "password2", "password3" ]
}
`
creds, err := tsm.DecodePasswordCredentials(credentials)
if err != nil {
log.Fatal(err)
}
// Create one SDK for each MPC node in the TSM
playerCount := len(creds.URLs)
eddsaClients := make([]tsm.EDDSAClient, 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(credsPlayer)
if err != nil {
log.Fatal(err)
}
eddsaClients[player] = tsm.NewEDDSAClient(client)
}
// Generate an Ed25519 master key
sessionID := tsm.GenerateSessionID()
var keyID string
var eg errgroup.Group
for _, eddsaClient := range eddsaClients {
eddsaClient := eddsaClient
eg.Go(func() error {
var err error
keyID, err = eddsaClient.KeygenWithSessionID(sessionID, "ED-25519")
return err
})
}
if err := eg.Wait(); err != nil {
log.Fatal(err)
}
// Get the public key for a given soft derivation path
chainPath := []uint32{1, 2, 34}
publicKey, err := eddsaClients[0].PublicKey(keyID, chainPath)
if err != nil {
log.Fatal(err)
}
fmt.Println("Public Key:", hex.EncodeToString(publicKey))
// Sign using the given chain path
partialSignatures := make([][]byte, playerCount)
lock := sync.Mutex{}
message := []byte{4, 3, 2, 5}
sessionID = tsm.GenerateSessionID()
for i, eddsaClient := range eddsaClients {
i := i
eddsaClient := eddsaClient
eg.Go(func() error {
var err error
partialSignature, err := eddsaClient.PartialSign(sessionID, keyID, chainPath, message)
if err != nil {
return err
}
lock.Lock() // avoid concurrent updates to the map
partialSignatures[i] = partialSignature
lock.Unlock()
return err
})
}
if err := eg.Wait(); err != nil {
log.Fatal(err)
}
// Combine partial signatures into the final signature
signature, err := tsm.EDDSAFinalize(partialSignatures...)
if err != nil {
log.Fatal(err)
}
fmt.Println("Signature:", hex.EncodeToString(signature))
// Validate signature
err = tsm.EDDSAVerify25519(publicKey, message, signature)
if err != nil {
log.Fatal(err)
}
fmt.Println("Signature is valid")
}
Updated over 1 year ago