Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Solana relayer (fee payer) key importer, encryption and decryption #2673

Merged
merged 17 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* [2597](https://github.com/zeta-chain/node/pull/2597) - Add generic rpc metrics to zetaclient
* [2538](https://github.com/zeta-chain/node/pull/2538) - add background worker routines to shutdown zetaclientd when needed for tss migration
* [2644](https://github.com/zeta-chain/node/pull/2644) - add created_timestamp to cctx status
* [2673](https://github.com/zeta-chain/node/pull/2673) - add relayer key importer, encryption and decryption

### Refactor

Expand Down
22 changes: 0 additions & 22 deletions cmd/zetaclientd-supervisor/lib.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package main

import (
"bufio"
"context"
"encoding/json"
"errors"
Expand All @@ -10,7 +9,6 @@ import (
"os"
"path"
"runtime"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -256,23 +254,3 @@ func (s *zetaclientdSupervisor) downloadZetaclientd(ctx context.Context, plan *u
}
return nil
}

func promptPasswords() (string, string, error) {
reader := bufio.NewReader(os.Stdin)
fmt.Print("HotKey Password: ")
hotKeyPass, err := reader.ReadString('\n')
if err != nil {
return "", "", err
}
fmt.Print("TSS Password: ")
tssKeyPass, err := reader.ReadString('\n')
if err != nil {
return "", "", err
}

//trim delimiters
hotKeyPass = strings.TrimSuffix(hotKeyPass, "\n")
tssKeyPass = strings.TrimSuffix(tssKeyPass, "\n")

return hotKeyPass, tssKeyPass, err
}
8 changes: 6 additions & 2 deletions cmd/zetaclientd-supervisor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import (
"os"
"os/exec"
"os/signal"
"strings"
"syscall"
"time"

"cosmossdk.io/errors"
"golang.org/x/sync/errgroup"

"github.com/zeta-chain/zetacore/app"
zetaos "github.com/zeta-chain/zetacore/pkg/os"
"github.com/zeta-chain/zetacore/zetaclient/config"
)

Expand All @@ -37,7 +39,9 @@ func main() {
shutdownChan := make(chan os.Signal, 1)
signal.Notify(shutdownChan, syscall.SIGINT, syscall.SIGTERM)

hotkeyPassword, tssPassword, err := promptPasswords()
// prompt for all necessary passwords
titles := []string{"HotKey", "TSS", "Solana Relayer Key"}
passwords, err := zetaos.PromptPasswords(titles)
if err != nil {
logger.Error().Err(err).Msg("unable to get passwords")
os.Exit(1)
Expand All @@ -64,7 +68,7 @@ func main() {
cmd.Stderr = os.Stderr
// must reset the passwordInputBuffer every iteration because reads are stateful (seek to end)
passwordInputBuffer := bytes.Buffer{}
passwordInputBuffer.Write([]byte(hotkeyPassword + "\n" + tssPassword + "\n"))
passwordInputBuffer.Write([]byte(strings.Join(passwords, "\n") + "\n"))
cmd.Stdin = &passwordInputBuffer

eg, ctx := errgroup.WithContext(ctx)
Expand Down
36 changes: 8 additions & 28 deletions cmd/zetaclientd/encrypt_tss.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
package main

import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/json"
"errors"
"io"
"os"
"path/filepath"

"github.com/pkg/errors"
"github.com/spf13/cobra"

"github.com/zeta-chain/zetacore/pkg/crypto"
)

var encTssCmd = &cobra.Command{
Expand All @@ -25,9 +22,10 @@ func init() {
RootCmd.AddCommand(encTssCmd)
}

// EncryptTSSFile encrypts the given file with the given secret key
func EncryptTSSFile(_ *cobra.Command, args []string) error {
filePath := args[0]
secretKey := args[1]
password := args[1]

filePath = filepath.Clean(filePath)
data, err := os.ReadFile(filePath)
Expand All @@ -39,29 +37,11 @@ func EncryptTSSFile(_ *cobra.Command, args []string) error {
return errors.New("file does not contain valid json, may already be encrypted")
}

block, err := aes.NewCipher(getFragmentSeed(secretKey))
if err != nil {
return err
}

// Creating GCM mode
gcm, err := cipher.NewGCM(block)
// encrypt the data
cipherText, err := crypto.EncryptAES256GCM(data, password)
if err != nil {
return err
}
// Generating random nonce
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return err
return errors.Wrap(err, "failed to encrypt data")
}

cipherText := gcm.Seal(nonce, nonce, data, nil)
return os.WriteFile(filePath, cipherText, 0o600)
}

func getFragmentSeed(password string) []byte {
h := sha256.New()
h.Write([]byte(password))
seed := h.Sum(nil)
return seed
}
153 changes: 153 additions & 0 deletions cmd/zetaclientd/import_relayer_keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package main

import (
"fmt"
"os"
"path/filepath"

"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"

"github.com/zeta-chain/zetacore/pkg/chains"
"github.com/zeta-chain/zetacore/pkg/crypto"
zetaos "github.com/zeta-chain/zetacore/pkg/os"
"github.com/zeta-chain/zetacore/zetaclient/keys"
)

var CmdImportRelayerKey = &cobra.Command{
Use: "import-relayer-key --network=<network> --private-key=<private-key> --password=<password> --relayer-key-path=<relayer-key-path>",
Short: "Import a relayer private key",
Example: `zetaclientd import-relayer-key --network=7 --private-key=<your_private_key> --password=<your_password>`,
RunE: ImportRelayerKey,
}

var CmdRelayerAddress = &cobra.Command{
Use: "relayer-address --network=<network> --password=<password> --relayer-key-path=<relayer-key-path>",
Short: "Show the relayer address",
Example: `zetaclientd relayer-address --network=7 --password=my_password`,
RunE: ShowRelayerAddress,
}

var importArgs = importRelayerKeyArguments{}
var addressArgs = relayerAddressArguments{}

// importRelayerKeyArguments is the struct that holds the arguments for the import command
type importRelayerKeyArguments struct {
network int32
privateKey string
password string
relayerKeyPath string
}

// relayerAddressArguments is the struct that holds the arguments for the show command
type relayerAddressArguments struct {
network int32
password string
relayerKeyPath string
}

func init() {
RootCmd.AddCommand(CmdImportRelayerKey)
RootCmd.AddCommand(CmdRelayerAddress)

// resolve default relayer key path
defaultRelayerKeyPath := "~/.zetacored/relayer-keys"
defaultRelayerKeyPath, err := zetaos.ExpandHomeDir(defaultRelayerKeyPath)
if err != nil {
log.Fatal().Err(err).Msg("failed to resolve default relayer key path")
}

CmdImportRelayerKey.Flags().Int32Var(&importArgs.network, "network", 7, "network id, (7: solana)")
CmdImportRelayerKey.Flags().
StringVar(&importArgs.privateKey, "private-key", "", "the relayer private key to import")
CmdImportRelayerKey.Flags().
StringVar(&importArgs.password, "password", "", "the password to encrypt the relayer private key")
CmdImportRelayerKey.Flags().
StringVar(&importArgs.relayerKeyPath, "relayer-key-path", defaultRelayerKeyPath, "path to relayer keys")

CmdRelayerAddress.Flags().Int32Var(&addressArgs.network, "network", 7, "network id, (7:solana)")
CmdRelayerAddress.Flags().
StringVar(&addressArgs.password, "password", "", "the password to decrypt the relayer private key")
CmdRelayerAddress.Flags().
StringVar(&addressArgs.relayerKeyPath, "relayer-key-path", defaultRelayerKeyPath, "path to relayer keys")
}

// ImportRelayerKey imports a relayer private key
func ImportRelayerKey(_ *cobra.Command, _ []string) error {
// validate private key and password
if importArgs.privateKey == "" {
return errors.New("must provide a private key")
}
if importArgs.password == "" {
return errors.New("must provide a password")
}
if !keys.IsRelayerPrivateKeyValid(importArgs.privateKey, chains.Network(importArgs.network)) {
return errors.New("invalid private key")
}

// resolve the relayer key file path
fileName, err := keys.ResolveRelayerKeyFile(importArgs.relayerKeyPath, chains.Network(importArgs.network))
if err != nil {
return errors.Wrap(err, "failed to resolve relayer key file path")
}

// create path (owner `rwx` permissions) if it does not exist
keyPath := filepath.Dir(fileName)
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
if err := os.MkdirAll(keyPath, 0o700); err != nil {
return errors.Wrapf(err, "failed to create relayer key path: %s", keyPath)
}
}

// avoid overwriting existing key file
if zetaos.FileExists(fileName) {
return errors.Errorf(
"relayer key %s already exists, please backup and remove it before importing a new key",
fileName,
)
}

// encrypt the private key
ciphertext, err := crypto.EncryptAES256GCMBase64(importArgs.privateKey, importArgs.password)
if err != nil {
return errors.Wrap(err, "private key encryption failed")
}
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved

// create the relayer key file
err = keys.WriteRelayerKeyToFile(fileName, keys.RelayerKey{PrivateKey: ciphertext})
if err != nil {
return errors.Wrapf(err, "failed to create relayer key file: %s", fileName)
}
fmt.Printf("successfully imported relayer key: %s\n", fileName)

return nil
}

// ShowRelayerAddress shows the relayer address
func ShowRelayerAddress(_ *cobra.Command, _ []string) error {
// try loading the relayer key if present
network := chains.Network(addressArgs.network)
relayerKey, err := keys.LoadRelayerKey(addressArgs.relayerKeyPath, network, addressArgs.password)
if err != nil {
return errors.Wrap(err, "failed to load relayer key")
}

// relayer key does not exist, return error
if relayerKey == nil {
return fmt.Errorf(
"relayer key not found for network %d in path: %s",
addressArgs.network,
addressArgs.relayerKeyPath,
)
}

// resolve the relayer address
networkName, address, err := relayerKey.ResolveAddress(network)
if err != nil {
return errors.Wrap(err, "failed to resolve relayer address")
}
fmt.Printf("relayer address (%s): %s\n", networkName, address)

return nil
}
16 changes: 4 additions & 12 deletions cmd/zetaclientd/init.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package main

import (
"path"

"github.com/rs/zerolog"
"github.com/spf13/cobra"

Expand Down Expand Up @@ -38,7 +36,7 @@ type initArguments struct {
KeyringBackend string
HsmMode bool
HsmHotKey string
SolanaKey string
RelayerKeyPath string
}

func init() {
Expand Down Expand Up @@ -72,7 +70,8 @@ func init() {
InitCmd.Flags().BoolVar(&initArgs.HsmMode, "hsm-mode", false, "enable hsm signer, default disabled")
InitCmd.Flags().
StringVar(&initArgs.HsmHotKey, "hsm-hotkey", "hsm-hotkey", "name of hotkey associated with hardware security module")
InitCmd.Flags().StringVar(&initArgs.SolanaKey, "solana-key", "solana-key.json", "solana key file name")
InitCmd.Flags().
StringVar(&initArgs.RelayerKeyPath, "relayer-key-path", "~/.zetacored/relayer-keys", "path to relayer keys")
}

func Initialize(_ *cobra.Command, _ []string) error {
Expand Down Expand Up @@ -110,16 +109,9 @@ func Initialize(_ *cobra.Command, _ []string) error {
configData.KeyringBackend = config.KeyringBackend(initArgs.KeyringBackend)
configData.HsmMode = initArgs.HsmMode
configData.HsmHotKey = initArgs.HsmHotKey
configData.SolanaKeyFile = initArgs.SolanaKey
configData.RelayerKeyPath = initArgs.RelayerKeyPath
configData.ComplianceConfig = testutils.ComplianceConfigTest()

// Save solana test fee payer key file
keyFile := path.Join(rootArgs.zetaCoreHome, initArgs.SolanaKey)
err = createSolanaTestKeyFile(keyFile)
if err != nil {
return err
}

// Save config file
return config.Save(&configData, rootArgs.zetaCoreHome)
}
37 changes: 0 additions & 37 deletions cmd/zetaclientd/solana_test_key.go

This file was deleted.

Loading
Loading