Browse Source

bridge: implement bridge key serialization

ghstack-source-id: f218021514618ae1eb8f03d7cc158b1114c45297
Pull Request resolved: https://github.com/certusone/wormhole/pull/90
Leo 5 years ago
parent
commit
d9f8174d76

+ 28 - 1
bridge/cmd/guardiand/bridge.go

@@ -9,6 +9,7 @@ import (
 	"syscall"
 
 	eth_common "github.com/ethereum/go-ethereum/common"
+	ethcrypto "github.com/ethereum/go-ethereum/crypto"
 	"github.com/libp2p/go-libp2p-core/crypto"
 	"github.com/libp2p/go-libp2p-core/peer"
 	"github.com/spf13/cobra"
@@ -37,6 +38,8 @@ var (
 
 	nodeKeyPath *string
 
+	bridgeKeyPath *string
+
 	ethRPC           *string
 	ethContract      *string
 	ethConfirmations *uint64
@@ -64,6 +67,8 @@ func init() {
 
 	nodeKeyPath = BridgeCmd.Flags().String("nodeKey", "", "Path to node key (will be generated if it doesn't exist)")
 
+	bridgeKeyPath = BridgeCmd.Flags().String("bridgeKey", "", "Path to guardian key (required)")
+
 	ethRPC = BridgeCmd.Flags().String("ethRPC", "", "Ethereum RPC URL")
 	ethContract = BridgeCmd.Flags().String("ethContract", "", "Ethereum bridge contract address")
 	ethConfirmations = BridgeCmd.Flags().Uint64("ethConfirmations", 15, "Ethereum confirmation count requirement")
@@ -184,6 +189,9 @@ func runBridge(cmd *cobra.Command, args []string) {
 	if *nodeKeyPath == "" && !*unsafeDevMode { // In devnet mode, keys are deterministically generated.
 		logger.Fatal("Please specify -nodeKey")
 	}
+	if *bridgeKeyPath == "" {
+		logger.Fatal("Please specify -bridgeKey")
+	}
 	if *agentRPC == "" {
 		logger.Fatal("Please specify -agentRPC")
 	}
@@ -216,8 +224,27 @@ func runBridge(cmd *cobra.Command, args []string) {
 
 	ethContractAddr := eth_common.HexToAddress(*ethContract)
 
+	// In devnet mode, we generate a deterministic guardian key and write it to disk.
+	if *unsafeDevMode {
+		gk, err := generateDevnetGuardianKey()
+		if err != nil {
+			logger.Fatal("failed to generate devnet guardian key", zap.Error(err))
+		}
+
+		err = writeGuardianKey(gk, "auto-generated deterministic devnet key", *bridgeKeyPath)
+		if err != nil {
+			logger.Fatal("failed to write devnet guardian key", zap.Error(err))
+		}
+	}
+
 	// Guardian key
-	gk := loadGuardianKey(logger)
+	gk, err := loadGuardianKey(*bridgeKeyPath)
+	if err != nil {
+		logger.Fatal("failed to load guardian key", zap.Error(err))
+	}
+
+	logger.Info("Loaded guardian key", zap.String(
+		"address", ethcrypto.PubkeyToAddress(gk.PublicKey).String()))
 
 	// Node's main lifecycle context.
 	rootCtx, rootCtxCancel = context.WithCancel(context.Background())

+ 66 - 0
bridge/cmd/guardiand/bridgekey.go

@@ -0,0 +1,66 @@
+package guardiand
+
+import (
+	"crypto/ecdsa"
+	"fmt"
+	"io/ioutil"
+
+	ethcrypto "github.com/ethereum/go-ethereum/crypto"
+	"google.golang.org/protobuf/encoding/prototext"
+
+	"github.com/certusone/wormhole/bridge/pkg/devnet"
+	nodev1 "github.com/certusone/wormhole/bridge/pkg/proto/node/v1"
+)
+
+// loadGuardianKey loads a serialized guardian key from disk.
+func loadGuardianKey(filename string) (*ecdsa.PrivateKey, error) {
+	b, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read guardian private key from disk: %w", err)
+	}
+
+	var m nodev1.GuardianKey
+	err = prototext.Unmarshal(b, &m)
+	if err != nil {
+		return nil, fmt.Errorf("failed to deserialize private key from disk: %w", err)
+	}
+
+	gk, err := ethcrypto.ToECDSA(m.Data)
+	if err != nil {
+		return nil, fmt.Errorf("failed to deserialize key data: %w", err)
+	}
+
+	return gk, nil
+}
+
+// writeGuardianKey serializes a guardian key and writes it to disk.
+func writeGuardianKey(key *ecdsa.PrivateKey, description string, filename string) error {
+	m := &nodev1.GuardianKey{
+		Description: description,
+		Data:        ethcrypto.FromECDSA(key),
+		Pubkey:      ethcrypto.PubkeyToAddress(key.PublicKey).String(),
+	}
+
+	b, err := prototext.MarshalOptions{Multiline: true, EmitASCII: true}.Marshal(m)
+	if err != nil {
+		panic(err)
+	}
+
+	if err := ioutil.WriteFile(filename, b, 0600); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// generateDevnetGuardianKey returns a deterministic testnet key.
+func generateDevnetGuardianKey() (*ecdsa.PrivateKey, error) {
+	// Figure out our devnet index
+	idx, err := devnet.GetDevnetIndex()
+	if err != nil {
+		return nil, err
+	}
+
+	// Generate guardian key
+	return devnet.DeterministicEcdsaKeyByIndex(ethcrypto.S256(), uint64(idx)), nil
+}

+ 0 - 26
bridge/cmd/guardiand/nodekeys.go → bridge/cmd/guardiand/nodekey.go

@@ -1,40 +1,14 @@
 package guardiand
 
 import (
-	"crypto/ecdsa"
 	"fmt"
 	"io/ioutil"
 	"os"
 
-	ethcrypto "github.com/ethereum/go-ethereum/crypto"
 	p2pcrypto "github.com/libp2p/go-libp2p-core/crypto"
 	"go.uber.org/zap"
-
-	"github.com/certusone/wormhole/bridge/pkg/devnet"
 )
 
-func loadGuardianKey(logger *zap.Logger) *ecdsa.PrivateKey {
-	var gk *ecdsa.PrivateKey
-
-	if *unsafeDevMode {
-		// Figure out our devnet index
-		idx, err := devnet.GetDevnetIndex()
-		if err != nil {
-			logger.Fatal("Failed to parse hostname - are we running in devnet?")
-		}
-
-		// Generate guardian key
-		gk = devnet.DeterministicEcdsaKeyByIndex(ethcrypto.S256(), uint64(idx))
-	} else {
-		panic("not implemented") // TODO
-	}
-
-	logger.Info("Loaded guardian key", zap.String(
-		"address", ethcrypto.PubkeyToAddress(gk.PublicKey).String()))
-
-	return gk
-}
-
 func getOrCreateNodeKey(logger *zap.Logger, path string) (p2pcrypto.PrivKey, error) {
 	b, err := ioutil.ReadFile(path)
 	if err != nil {

+ 2 - 0
devnet/bridge.yaml

@@ -76,6 +76,8 @@ spec:
             - --ethConfirmations
             - '3'
             - --unsafeDevMode
+            - --bridgeKey
+            - /tmp/bridge.key
 #            - --logLevel=debug
           securityContext:
             capabilities:

+ 13 - 1
proto/node/v1/node.proto

@@ -6,4 +6,16 @@ option go_package = "proto/node/v1;nodev1";
 
 import "google/api/annotations.proto";
 
-service Node {}
+service Node {
+}
+
+message GuardianKey {
+    // description is an optional, free-form description text set by the operator.
+    string description = 1;
+
+    // data is the binary representation of the secp256k1 private key.
+    bytes data = 2;
+
+    // pubkey is a human-readable representation of the key, included for operator convenience.
+    string pubkey = 3;
+}