Skip to content

Commit

Permalink
feat: topology encryption utils and refactor (#128)
Browse files Browse the repository at this point in the history
Co-authored-by: Matija Petrunić <matija.petrunic@gmail.com>
  • Loading branch information
P1sar and mpetrun5 authored Mar 17, 2023
1 parent a8fad65 commit 9be4953
Show file tree
Hide file tree
Showing 18 changed files with 254 additions and 62 deletions.
3 changes: 2 additions & 1 deletion cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package cli

import (
"github.com/ChainSafe/sygma-relayer/cli/topology"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
Expand All @@ -28,7 +29,7 @@ func init() {
}

func Execute() {
rootCMD.AddCommand(runCMD, peer.PeerCLI)
rootCMD.AddCommand(runCMD, peer.PeerCLI, topology.TopologyCLI)
if err := rootCMD.Execute(); err != nil {
log.Fatal().Err(err).Msg("failed to execute root cmd")
}
Expand Down
13 changes: 13 additions & 0 deletions cli/topology/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package topology

import "github.com/spf13/cobra"

var TopologyCLI = &cobra.Command{
Use: "topology",
Short: "utility commands that helps to encrypt and test p2p TopologyMap",
}

func init() {
TopologyCLI.AddCommand(encryptTopologyCMD)
TopologyCLI.AddCommand(testTopologyCMD)
}
71 changes: 71 additions & 0 deletions cli/topology/encryptTopology.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package topology

import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"os"

"github.com/ChainSafe/sygma-relayer/topology"

"github.com/spf13/cobra"
)

var (
encryptTopologyCMD = &cobra.Command{
Use: "encrypt",
Short: "encrypt provided topology with AES",
Long: "Algorithm used is AES CTR. IV and CT returned are in hex.",
RunE: encryptTopology,
}
)

var (
path string
encryptionKey string
)

func init() {
encryptTopologyCMD.PersistentFlags().StringVar(&path, "path", "", "path to json file with network topology")
_ = encryptTopologyCMD.MarkFlagRequired("path")
encryptTopologyCMD.PersistentFlags().StringVar(&encryptionKey, "encryption-key", "", "password to encrypt topology")
_ = encryptTopologyCMD.MarkFlagRequired("encryption-key")
}

func encryptTopology(cmd *cobra.Command, args []string) error {
cipherKey := []byte(encryptionKey)
aesEncryption, _ := topology.NewAESEncryption(cipherKey)
topologyFile, err := os.Open(path)
defer func() {
err := topologyFile.Close()
if err != nil {
panic(err)
}
}()
if err != nil {
return err
}
byteValue, err := ioutil.ReadAll(topologyFile)
if err != nil {
return err
}
// Testing that topology was well-formed
testTopology := topology.RawTopology{}
err = json.Unmarshal(byteValue, &testTopology)
if err != nil {
return fmt.Errorf("topology was wrong formed %s", err.Error())
}
ct, err := aesEncryption.Encrypt(byteValue)
if err != nil {
return err
}

fmt.Printf("Encrypted topology is: %x \n", ct)
h := sha256.New()
h.Write(ct)
eh := hex.EncodeToString(h.Sum(nil))
fmt.Printf("Hash of the topology %s", eh)
return nil
}
56 changes: 56 additions & 0 deletions cli/topology/testTopology.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package topology

import (
"fmt"
"net/http"

"github.com/spf13/cobra"

"github.com/ChainSafe/sygma-relayer/config/relayer"
"github.com/ChainSafe/sygma-relayer/topology"
)

var (
testTopologyCMD = &cobra.Command{
Use: "test",
Short: "Test topology url",
Long: "CLI tests does provided url contain topology that could be well " +
"decrypted with provided password and then parsed accordingly",
RunE: testTopology,
}
)

var (
url string
hash string
decryptionKey string
)

func init() {
testTopologyCMD.PersistentFlags().StringVar(&decryptionKey, "decryption-key", "", "password to decrypt topology")
_ = testTopologyCMD.MarkFlagRequired("decryption-key")
testTopologyCMD.PersistentFlags().StringVar(&url, "url", "", "url to fetch topology")
_ = testTopologyCMD.MarkFlagRequired("url")
testTopologyCMD.PersistentFlags().StringVar(&hash, "hash", "", "hash of topology")

}

func testTopology(cmd *cobra.Command, args []string) error {
config := relayer.TopologyConfiguration{
EncryptionKey: decryptionKey,
Url: url,
Path: "",
}
nt, err := topology.NewNetworkTopologyProvider(config, http.DefaultClient)
if err != nil {
return err
}
decryptedTopology, err := nt.NetworkTopology(hash)
if err != nil {
return err
}

fmt.Printf("Everything is fine your topology is \n")
fmt.Printf("%+v", decryptedTopology)
return nil
}
4 changes: 2 additions & 2 deletions comm/communication_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func (s *CommunicationIntegrationTestSuite) TestCommunication_BroadcastMessage_S

func (s *CommunicationIntegrationTestSuite) TestCommunication_BroadcastMessage_ErrorOnSendingMessageToExternalHost() {
privKeyForHost, _, _ := crypto.GenerateKeyPair(crypto.ECDSA, 1)
topology := topology.NetworkTopology{
topology := &topology.NetworkTopology{
Peers: []*peer.AddrInfo{},
}
externalHost, _ := p2p.NewHost(privKeyForHost, topology, p2p.NewConnectionGate(topology), uint16(4005))
Expand Down Expand Up @@ -200,7 +200,7 @@ func (s *CommunicationIntegrationTestSuite) TestCommunication_BroadcastMessage_S
Util function used for setting tests with multiple communications
*/
func InitializeHostsAndCommunications(numberOfActors int, protocolID protocol.ID) ([]host.Host, []comm.Communication) {
topology := topology.NetworkTopology{
topology := &topology.NetworkTopology{
Peers: []*peer.AddrInfo{},
}
privateKeys := []crypto.PrivKey{}
Expand Down
2 changes: 1 addition & 1 deletion comm/elector/bully_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (s *BullyTestSuite) SetupIndividualTest(c BullyTestCase) ([]elector.Coordin

numberOfTestHosts := len(c.testRelayers)

topology := topology.NetworkTopology{
topology := &topology.NetworkTopology{
Peers: []*peer.AddrInfo{},
}
privateKeys := []crypto.PrivKey{}
Expand Down
2 changes: 1 addition & 1 deletion comm/elector/static_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (s *CoordinatorElectorTestSuite) SetupTest() {
// create test hosts
for i := 0; i < numberOfTestHosts; i++ {
privKeyForHost, _, _ := crypto.GenerateKeyPair(crypto.ECDSA, 1)
topology := topology.NetworkTopology{
topology := &topology.NetworkTopology{
Peers: []*peer.AddrInfo{},
}
newHost, _ := p2p.NewHost(privKeyForHost, topology, p2p.NewConnectionGate(topology), uint16(4000+i))
Expand Down
6 changes: 3 additions & 3 deletions comm/p2p/gater.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ import (
// ConnectionGate implements libp2p ConnectionGater to prevent inbound and
// outbound requests to peers not specified in topology
type ConnectionGate struct {
topology topology.NetworkTopology
topology *topology.NetworkTopology
}

func NewConnectionGate(topology topology.NetworkTopology) *ConnectionGate {
func NewConnectionGate(topology *topology.NetworkTopology) *ConnectionGate {
return &ConnectionGate{
topology: topology,
}
}

func (cg *ConnectionGate) SetTopology(topology topology.NetworkTopology) {
func (cg *ConnectionGate) SetTopology(topology *topology.NetworkTopology) {
cg.topology = topology
}

Expand Down
2 changes: 1 addition & 1 deletion comm/p2p/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
)

// NewHost creates new host.Host from private key and relayer configuration
func NewHost(privKey crypto.PrivKey, networkTopology topology.NetworkTopology, cg *ConnectionGate, port uint16) (host.Host, error) {
func NewHost(privKey crypto.PrivKey, networkTopology *topology.NetworkTopology, cg *ConnectionGate, port uint16) (host.Host, error) {
if privKey == nil {
return nil, errors.New("unable to create libp2p host: private key not defined")
}
Expand Down
8 changes: 4 additions & 4 deletions comm/p2p/host_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (s *HostTestSuite) TestHost_NewHost_Success() {
p1, _ := peer.AddrInfoFromString(p1RawAddress)
p2, _ := peer.AddrInfoFromString(p2RawAddress)

topology := topology.NetworkTopology{
topology := &topology.NetworkTopology{
Peers: []*peer.AddrInfo{
p1, p2,
},
Expand All @@ -61,10 +61,10 @@ func (s *HostTestSuite) TestHost_NewHost_Success() {
func (s *HostTestSuite) TestHost_NewHost_InvalidPrivKey() {
host, err := p2p.NewHost(
nil,
topology.NetworkTopology{
&topology.NetworkTopology{
Peers: []*peer.AddrInfo{},
},
p2p.NewConnectionGate(topology.NetworkTopology{}),
p2p.NewConnectionGate(&topology.NetworkTopology{}),
2020,
)
s.Nil(host)
Expand All @@ -89,7 +89,7 @@ func (s *LoadPeersTestSuite) SetupTest() {
}
p1, _ := peer.AddrInfoFromString(p1RawAddress)
p2, _ := peer.AddrInfoFromString(p2RawAddress)
topology := topology.NetworkTopology{Peers: []*peer.AddrInfo{p1, p2}}
topology := &topology.NetworkTopology{Peers: []*peer.AddrInfo{p1, p2}}
host, err := p2p.NewHost(privKey, topology, p2p.NewConnectionGate(topology), 2020)
if err != nil {
panic(err)
Expand Down
2 changes: 1 addition & 1 deletion comm/p2p/libp2p_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func (s *Libp2pCommunicationTestSuite) TestLibp2pCommunication_SendReceiveMessag
portOffset := 0
protocolID := "/p2p/test"

topology := topology.NetworkTopology{
topology := &topology.NetworkTopology{
Peers: []*peer.AddrInfo{},
}

Expand Down
15 changes: 14 additions & 1 deletion docs/general/Topology.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,17 @@ To update the topology map, the map on the remote service needs to be updated. A
- SYG_RELAYER_MPCCONFIG_TOPOLOGYCONFIGURATION_ENCRYPTIONKEY - the key that is used to encrypt the topology map
- SYG_RELAYER_MPCCONFIG_TOPOLOGYCONFIGURATION_URL - topology map location
- SYG_RELAYER_MPCCONFIG_TOPOLOGYCONFIGURATION_PATH - local file where the topology map is stored after the download from the remote service


## Topology encryption/decryption details
Topology should be encrypted with AES using CTR mode.
IPFS should return hex formatted IV + data. To help you there are 2 utility CLI described below.

## Utility CLI

`./relayer topology encrypt --path ./topology.json --encryptionKey 123`
This command will encrypt provided topology and output corresponding hash and encrypted toplogy in hex representation (iv + data)

`./relayer topology test --hash 123 --url https://cloudflare-ipfs.com/ipfs/123 --decryptionKey 321`
This command will fetch topology from IPFS and test it according to Relayers topology initialization flow.
This allows to test correctnes of topology before actually calling `RefreshKey`

32 changes: 24 additions & 8 deletions topology/encryption.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
package topology

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/hex"
"crypto/rand"
)

type AESEncryption struct {
Expand All @@ -24,12 +25,27 @@ func NewAESEncryption(key []byte) (*AESEncryption, error) {
}, nil
}

func (ae *AESEncryption) Decrypt(data string) []byte {
iv := data[:aes.BlockSize]
bytes, _ := hex.DecodeString(data[aes.BlockSize:])

stream := cipher.NewCTR(ae.block, []byte(iv))
dst := make([]byte, len(bytes))
stream.XORKeyStream(dst, bytes)
func (ae *AESEncryption) Decrypt(ct []byte) []byte {
iv := ct[:aes.BlockSize]
stream := cipher.NewCTR(ae.block, iv)
dst := make([]byte, len(ct[aes.BlockSize:]))
stream.XORKeyStream(dst, ct[aes.BlockSize:])
return dst
}

// Encrypt is a function that encrypts provided bytes with AES in CTR mode
// Returned value is iv + ct
func (ae *AESEncryption) Encrypt(data []byte) ([]byte, error) {
dst := make([]byte, len(data))
iv := make([]byte, 16)
_, err := rand.Read(iv)
if err != nil {
return nil, err
}
stream := cipher.NewCTR(ae.block, iv)
stream.XORKeyStream(dst, data)

ct := bytes.NewBuffer(iv)
ct.Write(dst)
return ct.Bytes(), nil
}
16 changes: 12 additions & 4 deletions topology/encryption_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func (s *AESEncryptionTestSuite) SetupTest() {
s.aesEncryption, _ = topology.NewAESEncryption(cipherKey)
}

func (s *AESEncryptionTestSuite) Test_Decryption() {
func (s *AESEncryptionTestSuite) Test_EncrDecr() {
expectedTopology := topology.RawTopology{
Peers: []topology.RawPeer{
{PeerAddress: "/dns4/relayer2/tcp/9001/p2p/QmeTuMtdpPB7zKDgmobEwSvxodrf5aFVSmBXX3SQJVjJaT"},
Expand All @@ -35,11 +35,19 @@ func (s *AESEncryptionTestSuite) Test_Decryption() {
Threshold: "2",
}

encryptedData := "12345678123456786c49ea9bdd0d37f3ad3d266b4ef5d6ef243027b25bd5092091bcd26488e185c4f466c6795535593dc41b03fcd8997985a78bc784c9f561bac89683a0170e5632ec0fc9237a97ebe38f783067d7f0d19dfe708349ca10759e6091228de7899ee10d679c8b444132bfd8106e1d28e944facb21be60182b7c069f264244ab545871ee6d15a1f070cacada34647bc7d2404384b3ee54b9058ec14ae9e017610f392adeb05d33d524e10043908887d932e5a974c8200639c0dc8d77e1cfb65ecbd2f9c731c61212d1a928b5436f3540cfbd981070b5567ced664ef20cc795ebb792231df08f05987a5d9458664d34666995fb15a969440dfd28db35fbd79f9e11cbcfd42409259c4bb1006c0907d2d4b170698e90452ead9ab7f4e41309fe8c586ccee54cc9cfaf3ac22d00b6c6f583a3f7a1fe3ddd470aa12ad9cf63f072798dadb5a21004529fec4a5914d68a18fd0b3fc33079d4ff09af44416b732f024b75b40dd0"
decryptedData := s.aesEncryption.Decrypt(encryptedData)
pt, err := json.Marshal(expectedTopology)

s.Nil(err)

ct, err := s.aesEncryption.Encrypt(pt)
s.Nil(err)

resultingPt := s.aesEncryption.Decrypt(ct)

decryptedTopology := topology.RawTopology{}
_ = json.Unmarshal(decryptedData, &decryptedTopology)

err = json.Unmarshal(resultingPt, &decryptedTopology)
s.Nil(err)

s.Equal(expectedTopology, decryptedTopology)
}
6 changes: 3 additions & 3 deletions topology/mock/topology.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 9be4953

Please sign in to comment.