Skip to content

Commit

Permalink
implementation of relayer key importer, encryption and decryption
Browse files Browse the repository at this point in the history
  • Loading branch information
ws4charlie committed Aug 9, 2024
1 parent 2d225c8 commit 5da629e
Show file tree
Hide file tree
Showing 14 changed files with 690 additions and 66 deletions.
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`,
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")
}

// 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")
}
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
}
20 changes: 10 additions & 10 deletions contrib/localnet/orchestrator/start-zetae2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,52 +44,52 @@ sleep 2
# unlock the default account account
address=$(yq -r '.default_account.evm_address' config.yml)
echo "funding deployer address ${address} with 10000 Ether"
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null

# unlock erc20 tester accounts
address=$(yq -r '.additional_accounts.user_erc20.evm_address' config.yml)
echo "funding erc20 address ${address} with 10000 Ether"
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null

# unlock zeta tester accounts
address=$(yq -r '.additional_accounts.user_zeta_test.evm_address' config.yml)
echo "funding zeta tester address ${address} with 10000 Ether"
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null

# unlock zevm message passing tester accounts
address=$(yq -r '.additional_accounts.user_zevm_mp_test.evm_address' config.yml)
echo "funding zevm mp tester address ${address} with 10000 Ether"
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null

# unlock bitcoin tester accounts
address=$(yq -r '.additional_accounts.user_bitcoin.evm_address' config.yml)
echo "funding bitcoin tester address ${address} with 10000 Ether"
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null

# unlock solana tester accounts
address=$(yq -r '.additional_accounts.user_solana.evm_address' config.yml)
echo "funding solana tester address ${address} with 10000 Ether"
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null

# unlock ethers tester accounts
address=$(yq -r '.additional_accounts.user_ether.evm_address' config.yml)
echo "funding ether tester address ${address} with 10000 Ether"
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null

# unlock miscellaneous tests accounts
address=$(yq -r '.additional_accounts.user_misc.evm_address' config.yml)
echo "funding misc tester address ${address} with 10000 Ether"
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null

# unlock admin erc20 tests accounts
address=$(yq -r '.additional_accounts.user_admin.evm_address' config.yml)
echo "funding admin tester address ${address} with 10000 Ether"
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null

# unlock migration tests accounts
address=$(yq -r '.additional_accounts.user_migration.evm_address' config.yml)
echo "funding migration tester address ${address} with 10000 Ether"
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545
geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null

# unlock local solana relayer accounts
solana_url=$(yq -r '.rpcs.solana' config.yml)
Expand Down
6 changes: 6 additions & 0 deletions pkg/chains/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,12 @@ func (chain Chain) IsEmpty() bool {
return strings.TrimSpace(chain.String()) == ""
}

// GetNetworkName returns the network name from the network ID
func GetNetworkName(network int32) (string, bool) {
name, found := Network_name[network]
return name, found
}

// GetChainFromChainID returns the chain from the chain ID
// additionalChains is a list of additional chains to search from
// in practice, it is used in the protocol to dynamically support new chains without doing an upgrade
Expand Down
10 changes: 9 additions & 1 deletion pkg/chains/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,11 +396,19 @@ func TestChain_IsEmpty(t *testing.T) {
require.False(t, chains.ZetaChainMainnet.IsEmpty())
}

func TestGetNetworkName(t *testing.T) {
network := int32(chains.Network_solana)
name, found := chains.GetNetworkName(network)
nameExpected, foundExpected := chains.Network_name[network]
require.Equal(t, nameExpected, name)
require.Equal(t, foundExpected, found)
}

func TestGetChainFromChainID(t *testing.T) {
chain, found := chains.GetChainFromChainID(chains.ZetaChainMainnet.ChainId, []chains.Chain{})
require.EqualValues(t, chains.ZetaChainMainnet, chain)
require.True(t, found)
chain, found = chains.GetChainFromChainID(9999, []chains.Chain{})
_, found = chains.GetChainFromChainID(9999, []chains.Chain{})
require.False(t, found)
}

Expand Down
Loading

0 comments on commit 5da629e

Please sign in to comment.