Merge pull request #57 from filecoin-project/feat/repo-wallet
Add keystore to the repo
This commit is contained in:
commit
cbd21756f3
@ -2,11 +2,11 @@ package test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/filecoin-project/go-lotus/api"
|
"github.com/filecoin-project/go-lotus/api"
|
||||||
"github.com/filecoin-project/go-lotus/build"
|
"github.com/filecoin-project/go-lotus/build"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIBuilder is a function which is invoked in test suite to provide
|
// APIBuilder is a function which is invoked in test suite to provide
|
||||||
@ -49,9 +49,7 @@ func (ts *testSuite) testID(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(id.Pretty(), "Qm") {
|
assert.Regexp(t, "^12", id.Pretty())
|
||||||
t.Error("expected identity to be Qm..")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ts *testSuite) testConnectTwo(t *testing.T) {
|
func (ts *testSuite) testConnectTwo(t *testing.T) {
|
||||||
|
19
chain/types/keystore.go
Normal file
19
chain/types/keystore.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
// KeyInfo is used for storying keys in KeyStore
|
||||||
|
type KeyInfo struct {
|
||||||
|
Type string
|
||||||
|
PrivateKey []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyStore is used for storying secret keys
|
||||||
|
type KeyStore interface {
|
||||||
|
// List lists all the keys stored in the KeyStore
|
||||||
|
List() ([]string, error)
|
||||||
|
// Get gets a key out of keystore and returns KeyInfo coresponding to named key
|
||||||
|
Get(string) (KeyInfo, error)
|
||||||
|
// Put saves a key info under given name
|
||||||
|
Put(string, KeyInfo) error
|
||||||
|
// Delete removes a key from keystore
|
||||||
|
Delete(string) error
|
||||||
|
}
|
155
chain/wallet.go
155
chain/wallet.go
@ -4,25 +4,36 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/filecoin-project/go-lotus/chain/address"
|
"github.com/filecoin-project/go-lotus/chain/address"
|
||||||
|
"github.com/filecoin-project/go-lotus/chain/types"
|
||||||
"github.com/filecoin-project/go-lotus/lib/bls-signatures"
|
"github.com/filecoin-project/go-lotus/lib/bls-signatures"
|
||||||
"github.com/filecoin-project/go-lotus/lib/crypto"
|
"github.com/filecoin-project/go-lotus/lib/crypto"
|
||||||
|
|
||||||
"github.com/minio/blake2b-simd"
|
"github.com/minio/blake2b-simd"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
KNamePrefix = "wallet-"
|
||||||
|
|
||||||
KTSecp256k1 = "secp256k1"
|
KTSecp256k1 = "secp256k1"
|
||||||
KTBLS = "bls"
|
KTBLS = "bls"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Wallet struct {
|
type Wallet struct {
|
||||||
keys map[address.Address]*KeyInfo
|
keys map[address.Address]*Key
|
||||||
|
keystore types.KeyStore
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWallet() *Wallet {
|
func NewWallet(keystore types.KeyStore) (*Wallet, error) {
|
||||||
return &Wallet{keys: make(map[address.Address]*KeyInfo)}
|
w := &Wallet{
|
||||||
|
keys: make(map[address.Address]*Key),
|
||||||
|
keystore: keystore,
|
||||||
|
}
|
||||||
|
|
||||||
|
return w, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Signature struct {
|
type Signature struct {
|
||||||
@ -118,12 +129,21 @@ func (w *Wallet) Sign(addr address.Address, msg []byte) (*Signature, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Wallet) findKey(addr address.Address) (*KeyInfo, error) {
|
func (w *Wallet) findKey(addr address.Address) (*Key, error) {
|
||||||
ki, ok := w.keys[addr]
|
k, ok := w.keys[addr]
|
||||||
if !ok {
|
if ok {
|
||||||
return nil, fmt.Errorf("key not for given address not found in wallet")
|
return k, nil
|
||||||
}
|
}
|
||||||
return ki, nil
|
ki, err := w.keystore.Get(KNamePrefix + addr.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("getting from keystore: %w", err)
|
||||||
|
}
|
||||||
|
k, err = NewKey(ki)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("decoding from keystore: %w", err)
|
||||||
|
}
|
||||||
|
w.keys[k.Address] = k
|
||||||
|
return k, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Wallet) Export(addr address.Address) ([]byte, error) {
|
func (w *Wallet) Export(addr address.Address) ([]byte, error) {
|
||||||
@ -134,74 +154,107 @@ func (w *Wallet) Import(kdata []byte) (address.Address, error) {
|
|||||||
panic("nyi")
|
panic("nyi")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Wallet) ListAddrs() []address.Address {
|
func (w *Wallet) ListAddrs() ([]address.Address, error) {
|
||||||
var out []address.Address
|
all, err := w.keystore.List()
|
||||||
for a := range w.keys {
|
if err != nil {
|
||||||
out = append(out, a)
|
return nil, xerrors.Errorf("listing keystore: %w", err)
|
||||||
}
|
}
|
||||||
sort.Slice(out, func(i, j int) bool {
|
|
||||||
return out[i].String() < out[j].String()
|
sort.Strings(all)
|
||||||
})
|
|
||||||
return out
|
out := make([]address.Address, 0, len(all))
|
||||||
|
for _, a := range all {
|
||||||
|
if strings.HasPrefix(a, KNamePrefix) {
|
||||||
|
name := strings.TrimPrefix(a, KNamePrefix)
|
||||||
|
addr, err := address.NewFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("converting name to address: %w", err)
|
||||||
|
}
|
||||||
|
out = append(out, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Wallet) GenerateKey(typ string) (address.Address, error) {
|
func (w *Wallet) GenerateKey(typ string) (address.Address, error) {
|
||||||
|
var k *Key
|
||||||
switch typ {
|
switch typ {
|
||||||
case KTSecp256k1:
|
case KTSecp256k1:
|
||||||
k, err := crypto.GenerateKey()
|
priv, err := crypto.GenerateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return address.Undef, err
|
return address.Undef, err
|
||||||
}
|
}
|
||||||
ki := &KeyInfo{
|
ki := types.KeyInfo{
|
||||||
PrivateKey: k,
|
|
||||||
Type: typ,
|
Type: typ,
|
||||||
|
PrivateKey: priv,
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := ki.Address()
|
k, err = NewKey(ki)
|
||||||
w.keys[addr] = ki
|
if err != nil {
|
||||||
return addr, nil
|
return address.Undef, err
|
||||||
|
}
|
||||||
case KTBLS:
|
case KTBLS:
|
||||||
priv := bls.PrivateKeyGenerate()
|
priv := bls.PrivateKeyGenerate()
|
||||||
|
ki := types.KeyInfo{
|
||||||
ki := &KeyInfo{
|
Type: typ,
|
||||||
PrivateKey: priv[:],
|
PrivateKey: priv[:],
|
||||||
Type: KTBLS,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := ki.Address()
|
var err error
|
||||||
w.keys[addr] = ki
|
k, err = NewKey(ki)
|
||||||
return addr, nil
|
|
||||||
default:
|
|
||||||
return address.Undef, fmt.Errorf("invalid key type: %s", typ)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type KeyInfo struct {
|
|
||||||
PrivateKey []byte
|
|
||||||
|
|
||||||
Type string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ki *KeyInfo) Address() address.Address {
|
|
||||||
switch ki.Type {
|
|
||||||
case KTSecp256k1:
|
|
||||||
pub := crypto.PublicKey(ki.PrivateKey)
|
|
||||||
addr, err := address.NewSecp256k1Address(pub)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return address.Undef, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return address.Undef, xerrors.Errorf("invalid key type: %s", typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := w.keystore.Put(KNamePrefix+k.Address.String(), k.KeyInfo)
|
||||||
|
if err != nil {
|
||||||
|
return address.Undef, xerrors.Errorf("saving to keystore: %w", err)
|
||||||
|
}
|
||||||
|
w.keys[k.Address] = k
|
||||||
|
return k.Address, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Key struct {
|
||||||
|
types.KeyInfo
|
||||||
|
|
||||||
|
PublicKey []byte
|
||||||
|
Address address.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKey(keyinfo types.KeyInfo) (*Key, error) {
|
||||||
|
k := &Key{
|
||||||
|
KeyInfo: keyinfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch k.Type {
|
||||||
|
case KTSecp256k1:
|
||||||
|
k.PublicKey = crypto.PublicKey(k.PrivateKey)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
k.Address, err = address.NewSecp256k1Address(k.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("converting Secp256k1 to address: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return addr
|
|
||||||
case KTBLS:
|
case KTBLS:
|
||||||
var pk bls.PrivateKey
|
var pk bls.PrivateKey
|
||||||
copy(pk[:], ki.PrivateKey)
|
copy(pk[:], k.PrivateKey)
|
||||||
pub := bls.PrivateKeyPublicKey(pk)
|
pub := bls.PrivateKeyPublicKey(pk)
|
||||||
a, err := address.NewBLSAddress(pub[:])
|
k.PublicKey = pub[:]
|
||||||
|
|
||||||
|
var err error
|
||||||
|
k.Address, err = address.NewBLSAddress(k.PublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, xerrors.Errorf("converting BLS to address: %w", err)
|
||||||
}
|
}
|
||||||
return a
|
|
||||||
default:
|
default:
|
||||||
panic("unsupported key type")
|
return nil, xerrors.Errorf("unknown key type")
|
||||||
}
|
}
|
||||||
|
return k, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
1
go.mod
1
go.mod
@ -48,6 +48,7 @@ require (
|
|||||||
github.com/libp2p/go-maddr-filter v0.0.5
|
github.com/libp2p/go-maddr-filter v0.0.5
|
||||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1
|
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
|
github.com/multiformats/go-base32 v0.0.3
|
||||||
github.com/multiformats/go-multiaddr v0.0.4
|
github.com/multiformats/go-multiaddr v0.0.4
|
||||||
github.com/multiformats/go-multiaddr-dns v0.0.3
|
github.com/multiformats/go-multiaddr-dns v0.0.3
|
||||||
github.com/multiformats/go-multiaddr-net v0.0.1
|
github.com/multiformats/go-multiaddr-net v0.0.1
|
||||||
|
@ -110,7 +110,7 @@ func (a *API) WalletNew(ctx context.Context, typ string) (address.Address, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *API) WalletList(ctx context.Context) ([]address.Address, error) {
|
func (a *API) WalletList(ctx context.Context) ([]address.Address, error) {
|
||||||
return a.Wallet.ListAddrs(), nil
|
return a.Wallet.ListAddrs()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *API) NetConnect(ctx context.Context, p peer.AddrInfo) error {
|
func (a *API) NetConnect(ctx context.Context, p peer.AddrInfo) error {
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
|
|
||||||
"github.com/filecoin-project/go-lotus/api"
|
"github.com/filecoin-project/go-lotus/api"
|
||||||
"github.com/filecoin-project/go-lotus/chain"
|
"github.com/filecoin-project/go-lotus/chain"
|
||||||
|
"github.com/filecoin-project/go-lotus/chain/types"
|
||||||
"github.com/filecoin-project/go-lotus/node/config"
|
"github.com/filecoin-project/go-lotus/node/config"
|
||||||
"github.com/filecoin-project/go-lotus/node/hello"
|
"github.com/filecoin-project/go-lotus/node/hello"
|
||||||
"github.com/filecoin-project/go-lotus/node/modules"
|
"github.com/filecoin-project/go-lotus/node/modules"
|
||||||
@ -115,11 +116,6 @@ var defConf = config.Default()
|
|||||||
func defaults() []Option {
|
func defaults() []Option {
|
||||||
return []Option{
|
return []Option{
|
||||||
Override(new(helpers.MetricsCtx), context.Background),
|
Override(new(helpers.MetricsCtx), context.Background),
|
||||||
|
|
||||||
randomIdentity(),
|
|
||||||
|
|
||||||
Override(new(datastore.Batching), testing.MapDatastore),
|
|
||||||
Override(new(blockstore.Blockstore), testing.MapBlockstore), // NOT on top of ds above
|
|
||||||
Override(new(record.Validator), modules.RecordValidator),
|
Override(new(record.Validator), modules.RecordValidator),
|
||||||
|
|
||||||
// Filecoin modules
|
// Filecoin modules
|
||||||
@ -227,6 +223,8 @@ func Repo(r repo.Repo) Option {
|
|||||||
Override(new(ci.PrivKey), pk),
|
Override(new(ci.PrivKey), pk),
|
||||||
Override(new(ci.PubKey), ci.PrivKey.GetPublic),
|
Override(new(ci.PubKey), ci.PrivKey.GetPublic),
|
||||||
Override(new(peer.ID), peer.IDFromPublicKey),
|
Override(new(peer.ID), peer.IDFromPublicKey),
|
||||||
|
|
||||||
|
Override(new(types.KeyStore), modules.KeyStore),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
|
|
||||||
"github.com/filecoin-project/go-lotus/chain"
|
"github.com/filecoin-project/go-lotus/chain"
|
||||||
|
"github.com/filecoin-project/go-lotus/chain/types"
|
||||||
"github.com/filecoin-project/go-lotus/node/modules/helpers"
|
"github.com/filecoin-project/go-lotus/node/modules/helpers"
|
||||||
"github.com/filecoin-project/go-lotus/node/repo"
|
"github.com/filecoin-project/go-lotus/node/repo"
|
||||||
)
|
)
|
||||||
@ -65,6 +66,10 @@ func LockedRepo(lr repo.LockedRepo) func(lc fx.Lifecycle) repo.LockedRepo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func KeyStore(lr repo.LockedRepo) (types.KeyStore, error) {
|
||||||
|
return lr.KeyStore()
|
||||||
|
}
|
||||||
|
|
||||||
func Datastore(r repo.LockedRepo) (datastore.Batching, error) {
|
func Datastore(r repo.LockedRepo) (datastore.Batching, error) {
|
||||||
return r.Datastore("/metadata")
|
return r.Datastore("/metadata")
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/filecoin-project/go-lotus/api/test"
|
"github.com/filecoin-project/go-lotus/api/test"
|
||||||
"github.com/filecoin-project/go-lotus/lib/jsonrpc"
|
"github.com/filecoin-project/go-lotus/lib/jsonrpc"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/go-lotus/node/repo"
|
||||||
mocknet "github.com/libp2p/go-libp2p/p2p/net/mock"
|
mocknet "github.com/libp2p/go-libp2p/p2p/net/mock"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,6 +26,7 @@ func builder(t *testing.T, n int) []api.API {
|
|||||||
var err error
|
var err error
|
||||||
out[i], err = node.New(ctx,
|
out[i], err = node.New(ctx,
|
||||||
node.Online(),
|
node.Online(),
|
||||||
|
node.Repo(repo.NewMemory(nil)),
|
||||||
MockHost(mn),
|
MockHost(mn),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@ -15,10 +16,12 @@ import (
|
|||||||
logging "github.com/ipfs/go-log"
|
logging "github.com/ipfs/go-log"
|
||||||
"github.com/libp2p/go-libp2p-core/crypto"
|
"github.com/libp2p/go-libp2p-core/crypto"
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
|
"github.com/multiformats/go-base32"
|
||||||
"github.com/multiformats/go-multiaddr"
|
"github.com/multiformats/go-multiaddr"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/go-lotus/chain/types"
|
||||||
"github.com/filecoin-project/go-lotus/node/config"
|
"github.com/filecoin-project/go-lotus/node/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,6 +31,7 @@ const (
|
|||||||
fsDatastore = "datastore"
|
fsDatastore = "datastore"
|
||||||
fsLibp2pKey = "libp2p.priv"
|
fsLibp2pKey = "libp2p.priv"
|
||||||
fsLock = "repo.lock"
|
fsLock = "repo.lock"
|
||||||
|
fsKeystore = "keystore"
|
||||||
)
|
)
|
||||||
|
|
||||||
var log = logging.Logger("repo")
|
var log = logging.Logger("repo")
|
||||||
@ -55,14 +59,28 @@ func NewFS(path string) (*FsRepo, error) {
|
|||||||
|
|
||||||
func (fsr *FsRepo) Init() error {
|
func (fsr *FsRepo) Init() error {
|
||||||
if _, err := os.Stat(fsr.path); err == nil {
|
if _, err := os.Stat(fsr.path); err == nil {
|
||||||
return ErrRepoExists
|
return fsr.initKeystore()
|
||||||
} else if !os.IsNotExist(err) {
|
} else if !os.IsNotExist(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("Initializing repo at '%s'", fsr.path)
|
log.Infof("Initializing repo at '%s'", fsr.path)
|
||||||
|
err := os.Mkdir(fsr.path, 0755) //nolint: gosec
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fsr.initKeystore()
|
||||||
|
|
||||||
return os.Mkdir(fsr.path, 0755) //nolint: gosec
|
}
|
||||||
|
|
||||||
|
func (fsr *FsRepo) initKeystore() error {
|
||||||
|
kstorePath := filepath.Join(fsr.path, fsKeystore)
|
||||||
|
if _, err := os.Stat(kstorePath); err == nil {
|
||||||
|
return ErrRepoExists
|
||||||
|
} else if !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.Mkdir(kstorePath, 0700)
|
||||||
}
|
}
|
||||||
|
|
||||||
// APIEndpoint returns endpoint of API in this repo
|
// APIEndpoint returns endpoint of API in this repo
|
||||||
@ -169,6 +187,9 @@ func (fsr *fsLockedRepo) Config() (*config.Root, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fsr *fsLockedRepo) Libp2pIdentity() (crypto.PrivKey, error) {
|
func (fsr *fsLockedRepo) Libp2pIdentity() (crypto.PrivKey, error) {
|
||||||
|
if err := fsr.stillValid(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
kpath := fsr.join(fsLibp2pKey)
|
kpath := fsr.join(fsLibp2pKey)
|
||||||
stat, err := os.Stat(kpath)
|
stat, err := os.Stat(kpath)
|
||||||
|
|
||||||
@ -224,6 +245,131 @@ func (fsr *fsLockedRepo) SetAPIEndpoint(ma multiaddr.Multiaddr) error {
|
|||||||
return ioutil.WriteFile(fsr.join(fsAPI), []byte(ma.String()), 0644)
|
return ioutil.WriteFile(fsr.join(fsAPI), []byte(ma.String()), 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fsr *fsLockedRepo) Wallet() (interface{}, error) {
|
func (fsr *fsLockedRepo) KeyStore() (types.KeyStore, error) {
|
||||||
panic("not implemented")
|
if err := fsr.stillValid(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return fsr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var kstrPermissionMsg = "permissions of key: '%s' are too relaxed, " +
|
||||||
|
"required: 0600, got: %#o"
|
||||||
|
|
||||||
|
// List lists all the keys stored in the KeyStore
|
||||||
|
func (fsr *fsLockedRepo) List() ([]string, error) {
|
||||||
|
if err := fsr.stillValid(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
kstorePath := fsr.join(fsKeystore)
|
||||||
|
dir, err := os.Open(kstorePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("opening dir to list keystore: %w", err)
|
||||||
|
}
|
||||||
|
files, err := dir.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("reading keystore dir: %w", err)
|
||||||
|
}
|
||||||
|
keys := make([]string, 0, len(files))
|
||||||
|
for _, f := range files {
|
||||||
|
if f.Mode()&0077 != 0 {
|
||||||
|
return nil, xerrors.Errorf(kstrPermissionMsg, f.Name(), f.Mode())
|
||||||
|
}
|
||||||
|
name, err := base32.RawStdEncoding.DecodeString(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("decoding key: '%s': %w", f.Name(), err)
|
||||||
|
}
|
||||||
|
keys = append(keys, string(name))
|
||||||
|
}
|
||||||
|
return keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gets a key out of keystore and returns types.KeyInfo coresponding to named key
|
||||||
|
func (fsr *fsLockedRepo) Get(name string) (types.KeyInfo, error) {
|
||||||
|
if err := fsr.stillValid(); err != nil {
|
||||||
|
return types.KeyInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
encName := base32.RawStdEncoding.EncodeToString([]byte(name))
|
||||||
|
keyPath := fsr.join(fsKeystore, encName)
|
||||||
|
|
||||||
|
fstat, err := os.Stat(keyPath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return types.KeyInfo{}, xerrors.Errorf("opening key '%s': %w", name, ErrKeyNotFound)
|
||||||
|
} else if err != nil {
|
||||||
|
return types.KeyInfo{}, xerrors.Errorf("opening key '%s': %w", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fstat.Mode()&0077 != 0 {
|
||||||
|
return types.KeyInfo{}, xerrors.Errorf(kstrPermissionMsg, name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(keyPath)
|
||||||
|
if err != nil {
|
||||||
|
return types.KeyInfo{}, xerrors.Errorf("opening key '%s': %w", name, err)
|
||||||
|
}
|
||||||
|
defer file.Close() //nolint: errcheck // read only op
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return types.KeyInfo{}, xerrors.Errorf("reading key '%s': %w", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var res types.KeyInfo
|
||||||
|
err = json.Unmarshal(data, &res)
|
||||||
|
if err != nil {
|
||||||
|
return types.KeyInfo{}, xerrors.Errorf("decoding key '%s': %w", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put saves key info under given name
|
||||||
|
func (fsr *fsLockedRepo) Put(name string, info types.KeyInfo) error {
|
||||||
|
if err := fsr.stillValid(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
encName := base32.RawStdEncoding.EncodeToString([]byte(name))
|
||||||
|
keyPath := fsr.join(fsKeystore, encName)
|
||||||
|
|
||||||
|
_, err := os.Stat(keyPath)
|
||||||
|
if err == nil {
|
||||||
|
return xerrors.Errorf("checking key before put '%s': %w", name, ErrKeyExists)
|
||||||
|
} else if !os.IsNotExist(err) {
|
||||||
|
return xerrors.Errorf("checking key before put '%s': %w", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyData, err := json.Marshal(info)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("encoding key '%s': %w", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(keyPath, keyData, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("writing key '%s': %w", name, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fsr *fsLockedRepo) Delete(name string) error {
|
||||||
|
if err := fsr.stillValid(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
encName := base32.RawStdEncoding.EncodeToString([]byte(name))
|
||||||
|
keyPath := fsr.join(fsKeystore, encName)
|
||||||
|
|
||||||
|
_, err := os.Stat(keyPath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return xerrors.Errorf("checking key before delete '%s': %w", name, ErrKeyNotFound)
|
||||||
|
} else if err != nil {
|
||||||
|
return xerrors.Errorf("checking key before delete '%s': %w", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Remove(keyPath)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("deleting key '%s': %w", name, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import "io/ioutil"
|
import (
|
||||||
import "os"
|
"io/ioutil"
|
||||||
import "testing"
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func genFsRepo(t *testing.T) (*FsRepo, func()) {
|
func genFsRepo(t *testing.T) (*FsRepo, func()) {
|
||||||
path, err := ioutil.TempDir("", "lotus-repo-*")
|
path, err := ioutil.TempDir("", "lotus-repo-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -14,6 +16,11 @@ func genFsRepo(t *testing.T) (*FsRepo, func()) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = repo.Init()
|
||||||
|
if err != ErrRepoExists && err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
return repo, func() {
|
return repo, func() {
|
||||||
_ = os.RemoveAll(path)
|
_ = os.RemoveAll(path)
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,23 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/ipfs/go-datastore"
|
"github.com/ipfs/go-datastore"
|
||||||
"github.com/libp2p/go-libp2p-core/crypto"
|
"github.com/libp2p/go-libp2p-core/crypto"
|
||||||
"github.com/multiformats/go-multiaddr"
|
"github.com/multiformats/go-multiaddr"
|
||||||
"golang.org/x/xerrors"
|
|
||||||
|
|
||||||
|
"github.com/filecoin-project/go-lotus/chain/types"
|
||||||
"github.com/filecoin-project/go-lotus/node/config"
|
"github.com/filecoin-project/go-lotus/node/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrNoAPIEndpoint = xerrors.New("API not running (no endpoint)")
|
ErrNoAPIEndpoint = errors.New("API not running (no endpoint)")
|
||||||
ErrRepoAlreadyLocked = xerrors.New("repo is already locked")
|
ErrRepoAlreadyLocked = errors.New("repo is already locked")
|
||||||
ErrClosedRepo = xerrors.New("repo is no longer open")
|
ErrClosedRepo = errors.New("repo is no longer open")
|
||||||
|
|
||||||
|
ErrKeyExists = errors.New("key already exists")
|
||||||
|
ErrKeyNotFound = errors.New("key not found")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Repo interface {
|
type Repo interface {
|
||||||
@ -40,8 +45,8 @@ type LockedRepo interface {
|
|||||||
// so it can be read by API clients
|
// so it can be read by API clients
|
||||||
SetAPIEndpoint(multiaddr.Multiaddr) error
|
SetAPIEndpoint(multiaddr.Multiaddr) error
|
||||||
|
|
||||||
// Wallet returns store of private keys for Filecoin transactions
|
// KeyStore returns store of private keys for Filecoin transactions
|
||||||
Wallet() (interface{}, error)
|
KeyStore() (types.KeyStore, error)
|
||||||
|
|
||||||
// Path returns absolute path of the repo (or empty string if in-memory)
|
// Path returns absolute path of the repo (or empty string if in-memory)
|
||||||
Path() string
|
Path() string
|
||||||
|
@ -9,7 +9,9 @@ import (
|
|||||||
dssync "github.com/ipfs/go-datastore/sync"
|
dssync "github.com/ipfs/go-datastore/sync"
|
||||||
"github.com/libp2p/go-libp2p-core/crypto"
|
"github.com/libp2p/go-libp2p-core/crypto"
|
||||||
"github.com/multiformats/go-multiaddr"
|
"github.com/multiformats/go-multiaddr"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/go-lotus/chain/types"
|
||||||
"github.com/filecoin-project/go-lotus/node/config"
|
"github.com/filecoin-project/go-lotus/node/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,7 +27,7 @@ type MemRepo struct {
|
|||||||
datastore datastore.Datastore
|
datastore datastore.Datastore
|
||||||
configF func() *config.Root
|
configF func() *config.Root
|
||||||
libp2pKey crypto.PrivKey
|
libp2pKey crypto.PrivKey
|
||||||
wallet interface{}
|
keystore map[string]types.KeyInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
type lockedMemRepo struct {
|
type lockedMemRepo struct {
|
||||||
@ -46,7 +48,7 @@ type MemRepoOptions struct {
|
|||||||
Ds datastore.Datastore
|
Ds datastore.Datastore
|
||||||
ConfigF func() *config.Root
|
ConfigF func() *config.Root
|
||||||
Libp2pKey crypto.PrivKey
|
Libp2pKey crypto.PrivKey
|
||||||
Wallet interface{}
|
KeyStore map[string]types.KeyInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func genLibp2pKey() (crypto.PrivKey, error) {
|
func genLibp2pKey() (crypto.PrivKey, error) {
|
||||||
@ -77,6 +79,9 @@ func NewMemory(opts *MemRepoOptions) *MemRepo {
|
|||||||
}
|
}
|
||||||
opts.Libp2pKey = pk
|
opts.Libp2pKey = pk
|
||||||
}
|
}
|
||||||
|
if opts.KeyStore == nil {
|
||||||
|
opts.KeyStore = make(map[string]types.KeyInfo)
|
||||||
|
}
|
||||||
|
|
||||||
return &MemRepo{
|
return &MemRepo{
|
||||||
repoLock: make(chan struct{}, 1),
|
repoLock: make(chan struct{}, 1),
|
||||||
@ -84,7 +89,7 @@ func NewMemory(opts *MemRepoOptions) *MemRepo {
|
|||||||
datastore: opts.Ds,
|
datastore: opts.Ds,
|
||||||
configF: opts.ConfigF,
|
configF: opts.ConfigF,
|
||||||
libp2pKey: opts.Libp2pKey,
|
libp2pKey: opts.Libp2pKey,
|
||||||
wallet: opts.Wallet,
|
keystore: opts.KeyStore,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,9 +177,73 @@ func (lmem *lockedMemRepo) SetAPIEndpoint(ma multiaddr.Multiaddr) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lmem *lockedMemRepo) Wallet() (interface{}, error) {
|
func (lmem *lockedMemRepo) KeyStore() (types.KeyStore, error) {
|
||||||
if err := lmem.checkToken(); err != nil {
|
if err := lmem.checkToken(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return lmem.mem.wallet, nil
|
return lmem, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement KeyStore on the same instance
|
||||||
|
|
||||||
|
// List lists all the keys stored in the KeyStore
|
||||||
|
func (lmem *lockedMemRepo) List() ([]string, error) {
|
||||||
|
if err := lmem.checkToken(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lmem.RLock()
|
||||||
|
defer lmem.RUnlock()
|
||||||
|
|
||||||
|
res := make([]string, 0, len(lmem.mem.keystore))
|
||||||
|
for k := range lmem.mem.keystore {
|
||||||
|
res = append(res, k)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gets a key out of keystore and returns types.KeyInfo coresponding to named key
|
||||||
|
func (lmem *lockedMemRepo) Get(name string) (types.KeyInfo, error) {
|
||||||
|
if err := lmem.checkToken(); err != nil {
|
||||||
|
return types.KeyInfo{}, err
|
||||||
|
}
|
||||||
|
lmem.RLock()
|
||||||
|
defer lmem.RUnlock()
|
||||||
|
|
||||||
|
key, ok := lmem.mem.keystore[name]
|
||||||
|
if !ok {
|
||||||
|
return types.KeyInfo{}, xerrors.Errorf("getting key '%s': %w", name, ErrKeyNotFound)
|
||||||
|
}
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put saves key info under given name
|
||||||
|
func (lmem *lockedMemRepo) Put(name string, key types.KeyInfo) error {
|
||||||
|
if err := lmem.checkToken(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lmem.Lock()
|
||||||
|
defer lmem.Unlock()
|
||||||
|
|
||||||
|
_, isThere := lmem.mem.keystore[name]
|
||||||
|
if isThere {
|
||||||
|
return xerrors.Errorf("putting key '%s': %w", name, ErrKeyExists)
|
||||||
|
}
|
||||||
|
|
||||||
|
lmem.mem.keystore[name] = key
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lmem *lockedMemRepo) Delete(name string) error {
|
||||||
|
if err := lmem.checkToken(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lmem.Lock()
|
||||||
|
defer lmem.Unlock()
|
||||||
|
|
||||||
|
_, isThere := lmem.mem.keystore[name]
|
||||||
|
if !isThere {
|
||||||
|
return xerrors.Errorf("deleting key '%s': %w", name, ErrKeyNotFound)
|
||||||
|
}
|
||||||
|
delete(lmem.mem.keystore, name)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,9 @@ import (
|
|||||||
|
|
||||||
"github.com/multiformats/go-multiaddr"
|
"github.com/multiformats/go-multiaddr"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/go-lotus/chain/types"
|
||||||
"github.com/filecoin-project/go-lotus/node/config"
|
"github.com/filecoin-project/go-lotus/node/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -62,4 +64,56 @@ func basicTest(t *testing.T, repo Repo) {
|
|||||||
assert.Equal(t, ErrNoAPIEndpoint, err, "after closing repo, api should be nil")
|
assert.Equal(t, ErrNoAPIEndpoint, err, "after closing repo, api should be nil")
|
||||||
}
|
}
|
||||||
assert.Nil(t, apima, "with closed repo, apima should be set back to nil")
|
assert.Nil(t, apima, "with closed repo, apima should be set back to nil")
|
||||||
|
|
||||||
|
k1 := types.KeyInfo{Type: "foo"}
|
||||||
|
k2 := types.KeyInfo{Type: "bar"}
|
||||||
|
|
||||||
|
lrepo, err = repo.Lock()
|
||||||
|
assert.NoError(t, err, "should be able to relock")
|
||||||
|
assert.NotNil(t, lrepo, "locked repo shouldn't be nil")
|
||||||
|
|
||||||
|
kstr, err := lrepo.KeyStore()
|
||||||
|
assert.NoError(t, err, "should be able to get keystore")
|
||||||
|
assert.NotNil(t, lrepo, "keystore shouldn't be nil")
|
||||||
|
|
||||||
|
list, err := kstr.List()
|
||||||
|
assert.NoError(t, err, "should be able to list key")
|
||||||
|
assert.Empty(t, list, "there should be no keys")
|
||||||
|
|
||||||
|
err = kstr.Put("k1", k1)
|
||||||
|
assert.NoError(t, err, "should be able to put k1")
|
||||||
|
|
||||||
|
err = kstr.Put("k1", k1)
|
||||||
|
if assert.Error(t, err, "putting key under the same name should error") {
|
||||||
|
assert.True(t, xerrors.Is(err, ErrKeyExists), "returned error is ErrKeyExists")
|
||||||
|
}
|
||||||
|
|
||||||
|
k1prim, err := kstr.Get("k1")
|
||||||
|
assert.NoError(t, err, "should be able to get k1")
|
||||||
|
assert.Equal(t, k1, k1prim, "returned key should be the same")
|
||||||
|
|
||||||
|
k2prim, err := kstr.Get("k2")
|
||||||
|
if assert.Error(t, err, "should not be able to get k2") {
|
||||||
|
assert.True(t, xerrors.Is(err, ErrKeyNotFound), "returned error is ErrKeyNotFound")
|
||||||
|
}
|
||||||
|
assert.Empty(t, k2prim, "there should be no output for k2")
|
||||||
|
|
||||||
|
err = kstr.Put("k2", k2)
|
||||||
|
assert.NoError(t, err, "should be able to put k2")
|
||||||
|
|
||||||
|
list, err = kstr.List()
|
||||||
|
assert.NoError(t, err, "should be able to list keys")
|
||||||
|
assert.ElementsMatch(t, []string{"k1", "k2"}, list, "returned elements match")
|
||||||
|
|
||||||
|
err = kstr.Delete("k2")
|
||||||
|
assert.NoError(t, err, "should be able to delete key")
|
||||||
|
|
||||||
|
list, err = kstr.List()
|
||||||
|
assert.NoError(t, err, "should be able to list keys")
|
||||||
|
assert.ElementsMatch(t, []string{"k1"}, list, "returned elements match")
|
||||||
|
|
||||||
|
err = kstr.Delete("k2")
|
||||||
|
if assert.Error(t, err) {
|
||||||
|
assert.True(t, xerrors.Is(err, ErrKeyNotFound), "returned errror is ErrKeyNotFound")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user