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 2 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
34 changes: 7 additions & 27 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,6 +22,7 @@ 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]
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, secretKey)
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
}
178 changes: 178 additions & 0 deletions cmd/zetaclientd/import_relayer_keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package main

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

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

"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] [private-key] [password] [relayer-key-path]",
Short: "Import a relayer private key",
Example: `zetaclientd import-relayer-key --network=7 --private-key=3EMjCcCJg53fMEGVj13UPQpo6py9AKKyLE2qroR4yL1SvAN2tUznBvDKRYjntw7m6Jof1R2CSqjTddL27rEb6sFQ --password=my_password`,
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
RunE: ImportRelayerKey,
}

var CmdRelayerAddress = &cobra.Command{
Use: "relayer-address [network] [password] [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 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 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")
}

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

// create path (owner `rwx` permissions) if it does not exist
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

// construct the relayer key struct and write to file as json
keyData, err := json.Marshal(keys.RelayerKey{PrivateKey: ciphertext})
if err != nil {
return errors.Wrap(err, "failed to marshal relayer key")
}

// create relay key file (owner `rw` permissions)
err = os.WriteFile(fileName, keyData, 0o600)
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 {
// resolve the relayer key file path
_, fileName, err := resolveRelayerKeyPath(addressArgs.network, addressArgs.relayerKeyPath)
if err != nil {
return errors.Wrap(err, "failed to resolve relayer key file path")
}

// read the relayer key file
relayerKey, err := keys.ReadRelayerKeyFromFile(fileName)
if err != nil {
return err
}

// decrypt the private key
privateKey, err := crypto.DecryptAES256GCMBase64(relayerKey.PrivateKey, addressArgs.password)
if err != nil {
return errors.Wrap(err, "private key decryption failed")
}
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
relayerKey.PrivateKey = privateKey

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

return nil
}

// resolveRelayerKeyPath is a helper function to resolve the relayer key file path and name
func resolveRelayerKeyPath(network int32, relayerKeyPath string) (string, string, error) {
// get relayer key file name by network
name, err := keys.GetRelayerKeyFileByNetwork(network)
if err != nil {
return "", "", errors.Wrap(err, "failed to get relayer key file name")
}

// resolve relayer key path if it contains a tilde
keyPath, err := zetaos.ExpandHomeDir(relayerKeyPath)
if err != nil {
return "", "", errors.Wrap(err, "failed to resolve relayer key path")
}

// build file name
fileName := filepath.Join(keyPath, name)

return keyPath, fileName, err
}
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.

7 changes: 7 additions & 0 deletions cmd/zetae2e/config/localnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ policy_accounts:
bech32_address: "zeta142ds9x7raljv2qz9euys93e64gjmgdfnc47dwq"
evm_address: "0xAa9b029BC3EFe4c50045Cf0902c73aAa25b43533"
private_key: "0595CB0CD9BF5264A85A603EC8E43C30ADBB5FD2D9E2EF84C374EA4A65BB616C"
observer_relayer_accounts:
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
relayer_account_0:
solana_address: "2qBVcNBZCubcnSR3NyCnFjCfkCVUB3G7ECPoaW5rxVjx"
solana_private_key: "3EMjCcCJg53fMEGVj13UPQpo6py9AKKyLE2qroR4yL1SvAN2tUznBvDKRYjntw7m6Jof1R2CSqjTddL27rEb6sFQ"
relayer_account_1:
solana_address: "4kkCV8H38xirwQTkE5kL6FHNtYGHnMQQ7SkCjAxibHFK"
solana_private_key: "5SSv7jWzamtjWNKGiKf3gvCPHcq9mE5x6LhYgzJCKNSxoQ83gFpmMgmg2JS2zdKcBEdwy7y9bvWgX4LBiUpvnrPf"
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
rpcs:
zevm: "http://zetacore0:8545"
evm: "http://eth:8545"
Expand Down
2 changes: 2 additions & 0 deletions contrib/localnet/orchestrator/Dockerfile.fastbuild
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
FROM zetanode:latest as zeta
FROM ghcr.io/zeta-chain/ethereum-client-go:v1.10.26 as geth
FROM ghcr.io/zeta-chain/solana-docker:1.18.15 as solana
FROM ghcr.io/zeta-chain/golang:1.22.5-bookworm as orchestrator

RUN apt update && \
apt install -yq jq yq curl tmux python3 openssh-server iputils-ping iproute2 && \
rm -rf /var/lib/apt/lists/*

COPY --from=geth /usr/local/bin/geth /usr/local/bin/
COPY --from=solana /usr/bin/solana /usr/local/bin/
COPY --from=zeta /usr/local/bin/zetacored /usr/local/bin/zetaclientd /usr/local/bin/zetae2e /usr/local/bin/

COPY contrib/localnet/orchestrator/start-zetae2e.sh /work/
Expand Down
Loading
Loading