Commit 4b643f4b authored by Janos Guljas's avatar Janos Guljas

add keystore to manage libp2p and swarm private keys

parent a5467202
......@@ -19,10 +19,11 @@ func init() {
}
type command struct {
root *cobra.Command
config *viper.Viper
cfgFile string
homeDir string
root *cobra.Command
config *viper.Viper
passwordReader passwordReader
cfgFile string
homeDir string
}
type option func(*command)
......@@ -43,6 +44,9 @@ func newCommand(opts ...option) (c *command, err error) {
for _, o := range opts {
o(c)
}
if c.passwordReader == nil {
c.passwordReader = new(stdInPasswordReader)
}
// Find home directory.
if err := c.setHomeDir(); err != nil {
......
......@@ -7,8 +7,9 @@ package cmd
import "io"
type (
Command = command
Option = option
Command = command
Option = option
PasswordReader = passwordReader
)
var (
......@@ -18,6 +19,7 @@ var (
_ = WithCfgFile
_ = WithInput
_ = WithErrorOutput
_ = WithPasswordReader
)
func WithCfgFile(f string) func(c *Command) {
......@@ -55,3 +57,9 @@ func WithErrorOutput(w io.Writer) func(c *Command) {
c.root.SetErr(w)
}
}
func WithPasswordReader(r PasswordReader) func(c *Command) {
return func(c *Command) {
c.passwordReader = r
}
}
......@@ -5,9 +5,9 @@
package cmd
import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"os"
"os/signal"
......@@ -28,6 +28,8 @@ func (c *command) initStartCmd() (err error) {
const (
optionNameDataDir = "data-dir"
optionNamePassword = "password"
optionNamePasswordFile = "password-file"
optionNameAPIAddr = "api-addr"
optionNameP2PAddr = "p2p-addr"
optionNameP2PDisableWS = "p2p-disable-ws"
......@@ -68,34 +70,34 @@ func (c *command) initStartCmd() (err error) {
return fmt.Errorf("unknown verbosity level %q", v)
}
var libp2pPrivateKey, swarmPrivateKey io.ReadWriteCloser
if dataDir := c.config.GetString(optionNameDataDir); dataDir != "" {
if err := os.MkdirAll(dataDir, os.ModePerm); err != nil {
return err
}
libp2pKey, err := os.OpenFile(filepath.Join(dataDir, "libp2p.key"), os.O_CREATE|os.O_RDWR, 0600)
debugAPIAddr := c.config.GetString(optionNameDebugAPIAddr)
if !c.config.GetBool(optionNameEnableDebugAPI) {
debugAPIAddr = ""
}
var password string
if p := c.config.GetString(optionNamePassword); p != "" {
password = p
} else if pf := c.config.GetString(optionNamePasswordFile); pf != "" {
b, err := ioutil.ReadFile(pf)
if err != nil {
return err
}
libp2pPrivateKey = libp2pKey
swarmKey, err := os.OpenFile(filepath.Join(dataDir, "swarm.key"), os.O_CREATE|os.O_RDWR, 0600)
password = string(bytes.Trim(b, "\n"))
} else {
p, err := terminalPromptPassword(cmd, c.passwordReader, "Password")
if err != nil {
return err
}
swarmPrivateKey = swarmKey
}
debugAPIAddr := c.config.GetString(optionNameDebugAPIAddr)
if !c.config.GetBool(optionNameEnableDebugAPI) {
debugAPIAddr = ""
password = p
}
b, err := node.NewBee(node.Options{
PrivateKey: swarmPrivateKey,
DataDir: c.config.GetString(optionNameDataDir),
Password: password,
APIAddr: c.config.GetString(optionNameAPIAddr),
DebugAPIAddr: debugAPIAddr,
LibP2POptions: libp2p.Options{
PrivateKey: libp2pPrivateKey,
Addr: c.config.GetString(optionNameP2PAddr),
DisableWS: c.config.GetBool(optionNameP2PDisableWS),
DisableQUIC: c.config.GetBool(optionNameP2PDisableQUIC),
......@@ -152,6 +154,8 @@ func (c *command) initStartCmd() (err error) {
}
cmd.Flags().String(optionNameDataDir, filepath.Join(c.homeDir, ".bee"), "data directory")
cmd.Flags().String(optionNamePassword, "", "password for decrypting keys")
cmd.Flags().String(optionNamePasswordFile, "", "path to a file that contains password for decrypting keys")
cmd.Flags().String(optionNameAPIAddr, ":8080", "HTTP API listen address")
cmd.Flags().String(optionNameP2PAddr, ":7070", "P2P listen address")
cmd.Flags().Bool(optionNameP2PDisableWS, false, "disable P2P WebSocket protocol")
......
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cmd
import (
"os"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
)
type passwordReader interface {
ReadPassword() (password string, err error)
}
type stdInPasswordReader struct{}
func (stdInPasswordReader) ReadPassword() (password string, err error) {
v, err := terminal.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return "", err
}
return string(v), err
}
func terminalPromptPassword(cmd *cobra.Command, r passwordReader, title string) (password string, err error) {
cmd.Print(title + ": ")
password, err = r.ReadPassword()
cmd.Println()
if err != nil {
return "", err
}
return password, nil
}
......@@ -8,7 +8,6 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"encoding/json"
"fmt"
"github.com/btcsuite/btcd/btcec"
......@@ -16,51 +15,25 @@ import (
"golang.org/x/crypto/sha3"
)
var keyTypeSecp256k1 = "secp256k1"
// GenerateSecp256k1Key generates an ECDSA private key using
// secp256k1 elliptic curve.
func GenerateSecp256k1Key() (*ecdsa.PrivateKey, error) {
return ecdsa.GenerateKey(btcec.S256(), rand.Reader)
}
// NewAddress constructs a Swarm Address from ECDSA private key.
func NewAddress(p ecdsa.PublicKey) swarm.Address {
h := sha3.Sum256(elliptic.Marshal(btcec.S256(), p.X, p.Y))
return swarm.NewAddress(h[:])
}
// privateKey holds information about Swarm private key for marshaling.
type privateKey struct {
Type string `json:"type"`
Key []byte `json:"key"`
}
// MarshalSecp256k1PrivateKey marshals secp256k1 ECDSA private key
// that can be unmarshaled by UnmarshalPrivateKey.
func MarshalSecp256k1PrivateKey(k *ecdsa.PrivateKey) ([]byte, error) {
return json.Marshal(privateKey{
Type: keyTypeSecp256k1,
Key: (*btcec.PrivateKey)(k).Serialize(),
})
// GenerateSecp256k1Key generates an ECDSA private key using
// secp256k1 elliptic curve.
func GenerateSecp256k1Key() (*ecdsa.PrivateKey, error) {
return ecdsa.GenerateKey(btcec.S256(), rand.Reader)
}
// UnmarshalPrivateKey unmarshals ECDSA private key from encoded data.
func UnmarshalPrivateKey(data []byte) (*ecdsa.PrivateKey, error) {
var pk privateKey
if err := json.Unmarshal(data, &pk); err != nil {
return nil, err
}
switch t := pk.Type; t {
case keyTypeSecp256k1:
return decodeSecp256k1PrivateKey(pk.Key)
default:
return nil, fmt.Errorf("unknown key type %q", t)
}
// EncodeSecp256k1PrivateKey encodes raw ECDSA private key.
func EncodeSecp256k1PrivateKey(k *ecdsa.PrivateKey) []byte {
return (*btcec.PrivateKey)(k).Serialize()
}
// decodeSecp256k1PrivateKey decodes raw ECDSA private key.
func decodeSecp256k1PrivateKey(data []byte) (*ecdsa.PrivateKey, error) {
// DecodeSecp256k1PrivateKey decodes raw ECDSA private key.
func DecodeSecp256k1PrivateKey(data []byte) (*ecdsa.PrivateKey, error) {
if l := len(data); l != btcec.PrivKeyBytesLen {
return nil, fmt.Errorf("secp256k1 data size %d expected %d", l, btcec.PrivKeyBytesLen)
}
......
......@@ -43,20 +43,17 @@ func TestNewAddress(t *testing.T) {
}
}
func TestMarshalSecp256k1PrivateKey(t *testing.T) {
func TestEncodeSecp256k1PrivateKey(t *testing.T) {
k1, err := crypto.GenerateSecp256k1Key()
if err != nil {
t.Fatal(err)
}
d, err := crypto.MarshalSecp256k1PrivateKey(k1)
if err != nil {
t.Fatal(err)
}
k2, err := crypto.UnmarshalPrivateKey(d)
d := crypto.EncodeSecp256k1PrivateKey(k1)
k2, err := crypto.DecodeSecp256k1PrivateKey(d)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(k1.D.Bytes(), k2.D.Bytes()) {
t.Fatal("marshaled and unmarshaled keys are not equal")
t.Fatal("encoded and decoded keys are not equal")
}
}
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package file
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"github.com/ethersphere/bee/pkg/crypto"
"github.com/ethersphere/bee/pkg/keystore"
"github.com/ethersphere/bee/pkg/swarm"
"golang.org/x/crypto/scrypt"
"golang.org/x/crypto/sha3"
)
var _ keystore.Service = (*Service)(nil)
const (
keyHeaderKDF = "scrypt"
keyVersion = 1
scryptN = 1 << 15
scryptR = 8
scryptP = 1
scryptDKLen = 32
)
type encryptedKey struct {
Address swarm.Address `json:"address"`
Crypto keyCripto `json:"crypto"`
Version int `json:"version"`
}
type keyCripto struct {
Cipher string `json:"cipher"`
CipherText string `json:"ciphertext"`
CipherParams cipherParams `json:"cipherparams"`
KDF string `json:"kdf"`
KDFParams kdfParams `json:"kdfparams"`
MAC string `json:"mac"`
}
type cipherParams struct {
IV string `json:"iv"`
}
type kdfParams struct {
N int `json:"n"`
R int `json:"r"`
P int `json:"p"`
DKLen int `json:"dklen"`
Salt string `json:"salt"`
}
func encryptKey(k *ecdsa.PrivateKey, password string) ([]byte, error) {
data := crypto.EncodeSecp256k1PrivateKey(k)
kc, err := encryptData(data, []byte(password))
if err != nil {
return nil, err
}
return json.Marshal(encryptedKey{
Address: crypto.NewAddress(k.PublicKey),
Crypto: *kc,
Version: keyVersion,
})
}
func decryptKey(data []byte, password string) (*ecdsa.PrivateKey, error) {
var k encryptedKey
if err := json.Unmarshal(data, &k); err != nil {
return nil, err
}
if k.Version != keyVersion {
return nil, fmt.Errorf("unsupported key version: %v", k.Version)
}
d, err := decryptData(k.Crypto, password)
if err != nil {
return nil, err
}
return crypto.DecodeSecp256k1PrivateKey(d)
}
func encryptData(data, password []byte) (*keyCripto, error) {
salt := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
return nil, fmt.Errorf("read random data: %w", err)
}
derivedKey, err := scrypt.Key(password, salt, scryptN, scryptR, scryptP, scryptDKLen)
if err != nil {
return nil, err
}
encryptKey := derivedKey[:16]
iv := make([]byte, aes.BlockSize)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, fmt.Errorf("read random data: %w", err)
}
cipherText, err := aesCTRXOR(encryptKey, data, iv)
if err != nil {
return nil, err
}
mac := sha3.Sum256(append(derivedKey[16:32], cipherText...))
return &keyCripto{
Cipher: "aes-128-ctr",
CipherText: hex.EncodeToString(cipherText),
CipherParams: cipherParams{
IV: hex.EncodeToString(iv),
},
KDF: keyHeaderKDF,
KDFParams: kdfParams{
N: scryptN,
R: scryptR,
P: scryptP,
DKLen: scryptDKLen,
Salt: hex.EncodeToString(salt),
},
MAC: hex.EncodeToString(mac[:]),
}, nil
}
func decryptData(v keyCripto, password string) ([]byte, error) {
if v.Cipher != "aes-128-ctr" {
return nil, fmt.Errorf("unsupported cipher: %v", v.Cipher)
}
mac, err := hex.DecodeString(v.MAC)
if err != nil {
return nil, fmt.Errorf("hex decode mac: %s", err)
}
cipherText, err := hex.DecodeString(v.CipherText)
if err != nil {
return nil, fmt.Errorf("hex decode cipher text: %s", err)
}
derivedKey, err := getKDFKey(v, []byte(password))
if err != nil {
return nil, err
}
calculatedMAC := sha3.Sum256(append(derivedKey[16:32], cipherText...))
if !bytes.Equal(calculatedMAC[:], mac) {
return nil, keystore.ErrInvalidPassword
}
iv, err := hex.DecodeString(v.CipherParams.IV)
if err != nil {
return nil, fmt.Errorf("hex decode IV cipher parameter: %s", err)
}
data, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
if err != nil {
return nil, err
}
return data, nil
}
func aesCTRXOR(key, inText, iv []byte) ([]byte, error) {
aesBlock, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
stream := cipher.NewCTR(aesBlock, iv)
outText := make([]byte, len(inText))
stream.XORKeyStream(outText, inText)
return outText, nil
}
func getKDFKey(v keyCripto, password []byte) ([]byte, error) {
if v.KDF != keyHeaderKDF {
return nil, fmt.Errorf("unsupported KDF: %s", v.KDF)
}
salt, err := hex.DecodeString(v.KDFParams.Salt)
if err != nil {
return nil, fmt.Errorf("hex decode salt: %s", err)
}
return scrypt.Key(
password,
salt,
v.KDFParams.N,
v.KDFParams.R,
v.KDFParams.P,
v.KDFParams.DKLen,
)
}
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package file
import (
"crypto/ecdsa"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/ethersphere/bee/pkg/crypto"
)
type Service struct {
dir string
}
func New(dir string) *Service {
return &Service{dir: dir}
}
func (s *Service) Key(name, password string) (pk *ecdsa.PrivateKey, created bool, err error) {
filename := s.keyFilename(name)
data, err := ioutil.ReadFile(filename)
if err != nil && !os.IsNotExist(err) {
return nil, false, fmt.Errorf("read private key: %w", err)
}
if len(data) == 0 {
var err error
pk, err = crypto.GenerateSecp256k1Key()
if err != nil {
return nil, false, fmt.Errorf("generate secp256k1 key: %w", err)
}
d, err := encryptKey(pk, password)
if err != nil {
return nil, false, err
}
if err := os.MkdirAll(filepath.Dir(filename), 0700); err != nil {
return nil, false, err
}
if err := ioutil.WriteFile(filename, d, 0600); err != nil {
return nil, false, err
}
return pk, true, nil
}
pk, err = decryptKey(data, password)
if err != nil {
return nil, false, err
}
return pk, false, nil
}
func (s *Service) keyFilename(name string) string {
return filepath.Join(s.dir, fmt.Sprintf("%s.key", name))
}
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package file_test
import (
"io/ioutil"
"os"
"testing"
"github.com/ethersphere/bee/pkg/keystore/file"
"github.com/ethersphere/bee/pkg/keystore/test"
)
func TestService(t *testing.T) {
dir, err := ioutil.TempDir("", "bzz-keystore-file-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
test.Service(t, file.New(dir))
}
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package keystore
import (
"crypto/ecdsa"
"errors"
)
var ErrInvalidPassword = errors.New("invalid password")
type Service interface {
Key(name, password string) (k *ecdsa.PrivateKey, created bool, err error)
}
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mem
import (
"crypto/ecdsa"
"fmt"
"sync"
"github.com/ethersphere/bee/pkg/crypto"
"github.com/ethersphere/bee/pkg/keystore"
)
var _ keystore.Service = (*Service)(nil)
type Service struct {
m map[string]key
mu sync.Mutex
}
func New() *Service {
return &Service{
m: make(map[string]key),
}
}
func (s *Service) Key(name, password string) (pk *ecdsa.PrivateKey, created bool, err error) {
s.mu.Lock()
defer s.mu.Unlock()
k, ok := s.m[name]
if !ok {
pk, err := crypto.GenerateSecp256k1Key()
if err != nil {
return nil, false, fmt.Errorf("generate secp256k1 key: %w", err)
}
s.m[name] = key{
pk: pk,
password: password,
}
return pk, true, nil
}
if k.password != password {
return nil, false, keystore.ErrInvalidPassword
}
return k.pk, created, nil
}
type key struct {
pk *ecdsa.PrivateKey
password string
}
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mem_test
import (
"testing"
"github.com/ethersphere/bee/pkg/keystore/mem"
"github.com/ethersphere/bee/pkg/keystore/test"
)
func TestService(t *testing.T) {
test.Service(t, mem.New())
}
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package test
import (
"bytes"
"errors"
"testing"
"github.com/ethersphere/bee/pkg/keystore"
)
func Service(t *testing.T, s keystore.Service) {
// create a new swarm key
k1, created, err := s.Key("swarm", "pass123456")
if err != nil {
t.Fatal(err)
}
if !created {
t.Fatal("key is not created")
}
// get swarm key
k2, created, err := s.Key("swarm", "pass123456")
if err != nil {
t.Fatal(err)
}
if created {
t.Fatal("key is created, but should not be")
}
if !bytes.Equal(k1.D.Bytes(), k2.D.Bytes()) {
t.Fatal("two keys are not equal")
}
// invalid password
_, _, err = s.Key("swarm", "invalid password")
if !errors.Is(err, keystore.ErrInvalidPassword) {
t.Fatal(err)
}
// create a new libp2p key
k3, created, err := s.Key("libp2p", "p2p pass")
if err != nil {
t.Fatal(err)
}
if !created {
t.Fatal("key is not created")
}
if bytes.Equal(k1.D.Bytes(), k3.D.Bytes()) {
t.Fatal("two keys are equal, but should not be")
}
// get libp2p key
k4, created, err := s.Key("libp2p", "p2p pass")
if err != nil {
t.Fatal(err)
}
if created {
t.Fatal("key is created, but should not be")
}
if !bytes.Equal(k3.D.Bytes(), k4.D.Bytes()) {
t.Fatal("two keys are not equal")
}
}
......@@ -5,16 +5,13 @@
package node
import (
"bytes"
"context"
"crypto/ecdsa"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"path/filepath"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
......@@ -22,6 +19,9 @@ import (
"github.com/ethersphere/bee/pkg/api"
"github.com/ethersphere/bee/pkg/crypto"
"github.com/ethersphere/bee/pkg/debugapi"
"github.com/ethersphere/bee/pkg/keystore"
filekeystore "github.com/ethersphere/bee/pkg/keystore/file"
memkeystore "github.com/ethersphere/bee/pkg/keystore/mem"
"github.com/ethersphere/bee/pkg/logging"
"github.com/ethersphere/bee/pkg/metrics"
"github.com/ethersphere/bee/pkg/p2p/libp2p"
......@@ -37,7 +37,8 @@ type Bee struct {
}
type Options struct {
PrivateKey io.ReadWriteCloser
DataDir string
Password string
APIAddr string
DebugAPIAddr string
LibP2POptions libp2p.Options
......@@ -54,49 +55,37 @@ func NewBee(o Options) (*Bee, error) {
errorLogWriter: logger.WriterLevel(logrus.ErrorLevel),
}
var privateKey *ecdsa.PrivateKey
if o.PrivateKey != nil {
privateKeyData, err := ioutil.ReadAll(o.PrivateKey)
if err != nil && !os.IsNotExist(err) {
return nil, fmt.Errorf("read private key: %w", err)
}
if len(privateKeyData) == 0 {
var err error
privateKey, err = crypto.GenerateSecp256k1Key()
if err != nil {
return nil, fmt.Errorf("generate secp256k1 key: %w", err)
}
d, err := crypto.MarshalSecp256k1PrivateKey(privateKey)
if err != nil {
return nil, fmt.Errorf("encode private key: %w", err)
}
if _, err := io.Copy(o.PrivateKey, bytes.NewReader(d)); err != nil {
return nil, fmt.Errorf("write private key: %w", err)
}
} else {
var err error
privateKey, err = crypto.UnmarshalPrivateKey(privateKeyData)
if err != nil {
return nil, fmt.Errorf("decode private key: %w", err)
}
}
if err := o.PrivateKey.Close(); err != nil {
return nil, fmt.Errorf("close private key: %w", err)
}
var keyStore keystore.Service
if o.DataDir == "" {
keyStore = memkeystore.New()
logger.Warning("data directory not provided, keys are not persisted")
} else {
var err error
privateKey, err = crypto.GenerateSecp256k1Key()
if err != nil {
return nil, fmt.Errorf("generate secp256k1 key: %w", err)
}
keyStore = filekeystore.New(filepath.Join(o.DataDir, "keys"))
}
swarmPrivateKey, created, err := keyStore.Key("swarm", o.Password)
if err != nil {
return nil, fmt.Errorf("swarm key: %w", err)
}
address := crypto.NewAddress(swarmPrivateKey.PublicKey)
if created {
logger.Info("new swarm key created")
}
address := crypto.NewAddress(privateKey.PublicKey)
logger.Infof("address: %s", address)
// Construct P2P service.
libp2pPrivateKey, created, err := keyStore.Key("libp2p", o.Password)
if err != nil {
return nil, fmt.Errorf("libp2p key: %w", err)
}
if created {
logger.Infof("new libp2p key created")
}
libP2POptions := o.LibP2POptions
libP2POptions.Overlay = address
libP2POptions.PrivateKey = libp2pPrivateKey
p2ps, err := libp2p.New(p2pCtx, libP2POptions)
if err != nil {
return nil, fmt.Errorf("p2p service: %w", err)
......
......@@ -5,14 +5,11 @@
package libp2p
import (
"bytes"
"context"
"crypto/ecdsa"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"time"
"github.com/ethersphere/bee/pkg/logging"
......@@ -22,7 +19,7 @@ import (
"github.com/libp2p/go-libp2p"
autonat "github.com/libp2p/go-libp2p-autonat-svc"
connmgr "github.com/libp2p/go-libp2p-connmgr"
"github.com/libp2p/go-libp2p-core/crypto"
crypto "github.com/libp2p/go-libp2p-core/crypto"
"github.com/libp2p/go-libp2p-core/helpers"
"github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/network"
......@@ -47,7 +44,7 @@ type Service struct {
}
type Options struct {
PrivateKey io.ReadWriteCloser
PrivateKey *ecdsa.PrivateKey
Overlay swarm.Address
Addr string
DisableWS bool
......@@ -122,37 +119,8 @@ func New(ctx context.Context, o Options) (*Service, error) {
}
if o.PrivateKey != nil {
var privateKey crypto.PrivKey
privateKeyData, err := ioutil.ReadAll(o.PrivateKey)
if err != nil && !os.IsNotExist(err) {
return nil, fmt.Errorf("read private key: %w", err)
}
if len(privateKeyData) == 0 {
var err error
privateKey, _, err = crypto.GenerateSecp256k1Key(nil)
if err != nil {
return nil, fmt.Errorf("generate secp256k1 key: %w", err)
}
d, err := crypto.MarshalPrivateKey(privateKey)
if err != nil {
return nil, fmt.Errorf("encode private key: %w", err)
}
if _, err := io.Copy(o.PrivateKey, bytes.NewReader(d)); err != nil {
return nil, fmt.Errorf("write private key: %w", err)
}
} else {
var err error
privateKey, err = crypto.UnmarshalPrivateKey(privateKeyData)
if err != nil {
return nil, fmt.Errorf("decode private key: %w", err)
}
}
if err := o.PrivateKey.Close(); err != nil {
return nil, fmt.Errorf("close private key: %w", err)
}
opts = append(opts,
// Use the keypair we generated
libp2p.Identity(privateKey),
libp2p.Identity((*crypto.Secp256k1PrivateKey)(o.PrivateKey)),
)
}
......
......@@ -8,6 +8,7 @@ package swarm
import (
"bytes"
"encoding/hex"
"encoding/json"
)
// Address represents an address in Swarm metric space of
......@@ -60,5 +61,20 @@ func (a Address) Bytes() []byte {
return a.b
}
// UnmarshalJSON sets Address to a value from JSON-encoded representation.
func (a *Address) UnmarshalJSON(b []byte) (err error) {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
*a, err = ParseHexAddress(s)
return err
}
// MarshalJSON returns JSON-encoded representation of Address.
func (a Address) MarshalJSON() ([]byte, error) {
return json.Marshal(a.String())
}
// ZeroAddress is the address that has no value.
var ZeroAddress = NewAddress(nil)
......@@ -6,6 +6,7 @@ package swarm_test
import (
"encoding/hex"
"encoding/json"
"errors"
"testing"
......@@ -62,3 +63,21 @@ func TestAddress(t *testing.T) {
})
}
}
func TestAddress_jsonMarshalling(t *testing.T) {
a1 := swarm.MustParseHexAddress("24798dd5a470e927fa")
b, err := json.Marshal(a1)
if err != nil {
t.Fatal(err)
}
var a2 swarm.Address
if err := json.Unmarshal(b, &a2); err != nil {
t.Fatal(err)
}
if !a1.Equal(a2) {
t.Error("unmarshalled address is not equal to the original")
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment