From b7ef514226b0536d16c21fdeeb22bd2525b1f1ec Mon Sep 17 00:00:00 2001
From: Wolfgang Welz <welzwo@gmail.com>
Date: Fri, 17 Jan 2020 17:23:10 +0100
Subject: [PATCH] Feat: Add option for node identity seed (#142)

* feat: add option to configure the private key seed

* Improve log messages
---
 packages/autopeering/peer/local.go      | 22 +++++++++++++++++-----
 packages/autopeering/peer/mapdb.go      |  9 +++++++++
 packages/autopeering/peer/peerdb.go     |  9 ++++++++-
 plugins/autopeering/local/local.go      | 24 ++++++++++++++++++++++--
 plugins/autopeering/local/parameters.go |  2 ++
 5 files changed, 58 insertions(+), 8 deletions(-)

diff --git a/packages/autopeering/peer/local.go b/packages/autopeering/peer/local.go
index 32a7a9df..19595a32 100644
--- a/packages/autopeering/peer/local.go
+++ b/packages/autopeering/peer/local.go
@@ -42,13 +42,25 @@ func newLocal(key PrivateKey, serviceRecord *service.Record, db DB) *Local {
 }
 
 // NewLocal creates a new local peer linked to the provided db.
-func NewLocal(network string, address string, db DB) (*Local, error) {
-	key, err := db.LocalPrivateKey()
-	if err != nil {
-		return nil, err
+// If an optional seed is provided, the seed is used to generate the private key. Without a seed,
+// the provided key is loaded from the provided database and generated if not stored there.
+func NewLocal(network string, address string, db DB, seed ...[]byte) (*Local, error) {
+	var key PrivateKey
+	if len(seed) > 0 {
+		key = PrivateKey(ed25519.NewKeyFromSeed(seed[0]))
+		if err := db.UpdateLocalPrivateKey(key); err != nil {
+			return nil, err
+		}
+	} else {
+		var err error
+		key, err = db.LocalPrivateKey()
+		if err != nil {
+			return nil, err
+		}
 	}
+
 	if l := len(key); l != ed25519.PrivateKeySize {
-		return nil, fmt.Errorf("invalid key length: %d, need %d", l, ed25519.PublicKeySize)
+		return nil, fmt.Errorf("invalid key length: %d, need %d", l, ed25519.PrivateKeySize)
 	}
 	// update the external address used for the peering
 	serviceRecord := service.New()
diff --git a/packages/autopeering/peer/mapdb.go b/packages/autopeering/peer/mapdb.go
index df3ead3f..a3ebbb1a 100644
--- a/packages/autopeering/peer/mapdb.go
+++ b/packages/autopeering/peer/mapdb.go
@@ -67,6 +67,15 @@ func (db *mapDB) LocalPrivateKey() (PrivateKey, error) {
 	return db.key, nil
 }
 
+// UpdateLocalPrivateKey stores the provided key in the database.
+func (db *mapDB) UpdateLocalPrivateKey(key PrivateKey) error {
+	db.mutex.Lock()
+	defer db.mutex.Unlock()
+
+	db.key = key
+	return nil
+}
+
 // LastPing returns that property for the given peer ID and address.
 func (db *mapDB) LastPing(id ID, address string) time.Time {
 	db.mutex.RLock()
diff --git a/packages/autopeering/peer/peerdb.go b/packages/autopeering/peer/peerdb.go
index 106517aa..def6344b 100644
--- a/packages/autopeering/peer/peerdb.go
+++ b/packages/autopeering/peer/peerdb.go
@@ -28,6 +28,8 @@ const (
 type DB interface {
 	// LocalPrivateKey returns the private key stored in the database or creates a new one.
 	LocalPrivateKey() (PrivateKey, error)
+	// UpdateLocalPrivateKey stores the provided key in the database.
+	UpdateLocalPrivateKey(key PrivateKey) error
 
 	// Peer retrieves a peer from the database.
 	Peer(id ID) *Peer
@@ -175,7 +177,7 @@ func (db *persistentDB) LocalPrivateKey() (PrivateKey, error) {
 	if err == database.ErrKeyNotFound {
 		key, err = generatePrivateKey()
 		if err == nil {
-			err = db.db.Set(localFieldKey(dbLocalKey), key)
+			err = db.UpdateLocalPrivateKey(key)
 		}
 		return key, err
 	}
@@ -186,6 +188,11 @@ func (db *persistentDB) LocalPrivateKey() (PrivateKey, error) {
 	return key, nil
 }
 
+// UpdateLocalPrivateKey stores the provided key in the database.
+func (db *persistentDB) UpdateLocalPrivateKey(key PrivateKey) error {
+	return db.db.Set(localFieldKey(dbLocalKey), key)
+}
+
 // LastPing returns that property for the given peer ID and address.
 func (db *persistentDB) LastPing(id ID, address string) time.Time {
 	return time.Unix(db.getInt64(nodeFieldKey(id, address, dbNodePing)), 0)
diff --git a/plugins/autopeering/local/local.go b/plugins/autopeering/local/local.go
index 71e358c0..ac43be56 100644
--- a/plugins/autopeering/local/local.go
+++ b/plugins/autopeering/local/local.go
@@ -1,6 +1,8 @@
 package local
 
 import (
+	"crypto/ed25519"
+	"encoding/base64"
 	"fmt"
 	"io/ioutil"
 	"net"
@@ -23,7 +25,7 @@ func configureLocal() *peer.Local {
 
 	ip := net.ParseIP(parameter.NodeConfig.GetString(CFG_ADDRESS))
 	if ip == nil {
-		log.Fatalf("Invalid IP address: %s", parameter.NodeConfig.GetString(CFG_ADDRESS))
+		log.Fatalf("Invalid %s address: %s", CFG_ADDRESS, parameter.NodeConfig.GetString(CFG_ADDRESS))
 	}
 	if ip.IsUnspecified() {
 		log.Info("Querying public IP ...")
@@ -40,7 +42,25 @@ func configureLocal() *peer.Local {
 	// create a new local node
 	db := peer.NewPersistentDB(log)
 
-	local, err := peer.NewLocal("udp", net.JoinHostPort(ip.String(), port), db)
+	// the private key seed of the current local can be returned the following way:
+	// key, _ := db.LocalPrivateKey()
+	// fmt.Println(base64.StdEncoding.EncodeToString(ed25519.PrivateKey(key).Seed()))
+
+	// set the private key from the seed provided in the config
+	var seed [][]byte
+	if parameter.NodeConfig.IsSet(CFG_SEED) {
+		str := parameter.NodeConfig.GetString(CFG_SEED)
+		bytes, err := base64.StdEncoding.DecodeString(str)
+		if err != nil {
+			log.Fatalf("Invalid %s: %s", CFG_SEED, err)
+		}
+		if l := len(bytes); l != ed25519.SeedSize {
+			log.Fatalf("Invalid %s length: %d, need %d", CFG_SEED, l, ed25519.SeedSize)
+		}
+		seed = append(seed, bytes)
+	}
+
+	local, err := peer.NewLocal("udp", net.JoinHostPort(ip.String(), port), db, seed...)
 	if err != nil {
 		log.Fatalf("Error creating local: %s", err)
 	}
diff --git a/plugins/autopeering/local/parameters.go b/plugins/autopeering/local/parameters.go
index 1bb7847d..ab7629e8 100644
--- a/plugins/autopeering/local/parameters.go
+++ b/plugins/autopeering/local/parameters.go
@@ -7,9 +7,11 @@ import (
 const (
 	CFG_ADDRESS = "autopeering.address"
 	CFG_PORT    = "autopeering.port"
+	CFG_SEED    = "autopeering.seed"
 )
 
 func init() {
 	flag.String(CFG_ADDRESS, "0.0.0.0", "address to bind for incoming peering requests")
 	flag.Int(CFG_PORT, 14626, "udp port for incoming peering requests")
+	flag.BytesBase64(CFG_SEED, nil, "private key seed used to derive the node identity; optional Base64 encoded 256-bit string")
 }
-- 
GitLab