diff --git a/utils/store/distributed.go b/utils/store/distributed.go new file mode 100644 index 0000000..6031873 --- /dev/null +++ b/utils/store/distributed.go @@ -0,0 +1,226 @@ +package store + +import ( + "context" + "fmt" + "regexp" + + "github.com/p2p-org/dkc/utils/crypto/bls" + "github.com/pkg/errors" + "github.com/spf13/viper" + e2wallet "github.com/wealdtech/go-eth2-wallet" + types "github.com/wealdtech/go-eth2-wallet-types/v2" + "golang.org/x/exp/maps" +) + +type Peers map[uint64]string +type Threshold uint32 +type DistributedStore struct { + Type string + Path string + Store map[uint64]types.Store + Peers Peers + Threshold Threshold + Passphrases [][]byte + Ctx context.Context +} + +func (s *DistributedStore) Create() error { + for id, peer := range s.Peers { + res, err := regexp.Compile(`:.*`) + if err != nil { + return err + } + store, err := createStore(s.Path + "/" + res.ReplaceAllString(peer, "")) + if err != nil { + return err + } + s.Store[id] = store + } + + return nil +} + +func (s *DistributedStore) GetWalletsAccountsMap() (WalletAccountsMap, error) { + // We assume that all distributed wallets in the store have the same accounts + peers := maps.Values(s.Peers) + res, err := regexp.Compile(`:.*`) + if err != nil { + return nil, err + } + wa, err := getWalletsAccountsMap(s.Ctx, s.Path+"/"+res.ReplaceAllString(peers[0], "")) + if err != nil { + return nil, err + } + + return wa, nil +} + +func (s *DistributedStore) CreateWallet(name string) error { + for _, store := range s.Store { + err := e2wallet.UseStore(store) + if err != nil { + return err + } + _, err = e2wallet.CreateWallet(name, e2wallet.WithType(s.Type)) + if err != nil { + return err + } + } + return nil +} + +func (s *DistributedStore) GetPK(w string, a string) ([]byte, error) { + accounts := map[uint64][]byte{} + res, err := regexp.Compile(`:.*`) + if err != nil { + return nil, err + } + + for id, peer := range s.Peers { + wallet, err := getWallet(s.Path+"/"+res.ReplaceAllString(peer, ""), w) + if err != nil { + return nil, err + } + err = wallet.(types.WalletLocker).Unlock(s.Ctx, nil) + if err != nil { + return nil, errors.Wrap(err, "test") + } + + defer func() { + err = wallet.(types.WalletLocker).Lock(s.Ctx) + }() + + account, err := wallet.(types.WalletAccountByNameProvider).AccountByName(s.Ctx, a) + if err != nil { + return nil, err + } + + key, err := getAccountPK(account, s.Ctx, s.Passphrases) + if err != nil { + return nil, err + } + + accounts[id] = key + + } + key, err := bls.Combine(s.Ctx, accounts) + if err != nil { + return nil, err + } + return key, nil +} + +func (s *DistributedStore) SavePKToWallet(w string, a []byte, n string) error { + res, err := regexp.Compile(`:.*`) + if err != nil { + return err + } + // Spliting PK to shards and get Public and Private Keys for each shard + masterSKs, masterPKs, err := bls.Split(s.Ctx, a, uint32(s.Threshold)) + if err != nil { + return err + } + + peersIDs := maps.Keys(s.Peers) + participants, err := bls.SetupParticipants(masterSKs, masterPKs, peersIDs, len(s.Peers)) + if err != nil { + return err + } + + for id, peer := range s.Peers { + wallet, err := getWallet(s.Path+"/"+res.ReplaceAllString(peer, ""), w) + if err != nil { + return err + } + err = wallet.(types.WalletLocker).Unlock(s.Ctx, nil) + if err != nil { + return errors.Wrap(err, "test") + } + + defer func() { + err = wallet.(types.WalletLocker).Lock(s.Ctx) + }() + + _, err = wallet.(types.WalletDistributedAccountImporter).ImportDistributedAccount( + s.Ctx, + n, + participants[id], + uint32(s.Threshold), + masterPKs, + s.Peers, + // Always use the firts password + s.Passphrases[0], + ) + if err != nil { + return err + } + } + + return nil +} + +func newDistributedStore(t string) (DistributedStore, error) { + s := DistributedStore{} + //Parse Wallet Type + wt := viper.GetString(fmt.Sprintf("%s.wallet.type", t)) + + s.Type = wt + + //Parse Store Path + storePath := viper.GetString(fmt.Sprintf("%s.store.path", t)) + if storePath == "" { + return s, errors.New("timeout is required") + } + s.Path = storePath + + //Parse Passphrases + passphrases, err := getAccountsPasswords(viper.GetString(fmt.Sprintf("%s.wallet.passphrases.path", t))) + if err != nil { + return s, err + } + if len(passphrases) == 0 { + return s, errors.New("timeout is requried") + } + s.Passphrases = passphrases + + //Parse Peers + var peers Peers + err = viper.UnmarshalKey(fmt.Sprintf("%s.wallet.peers", t), &peers) + if err != nil { + return s, err + } + + //Peers list must be >= 2 + if len(peers) < 2 { + return s, errors.New("timeout is required") + } + s.Peers = peers + + //Parse Threshold + var threshold Threshold + err = viper.UnmarshalKey(fmt.Sprintf("%s.wallet.threshold", t), &threshold) + if err != nil { + return s, err + } + + //Check number of peers and threshold + if uint32(threshold) <= uint32(len(peers)/2) { + return s, errors.New("timeout is required") + } + if uint32(threshold) > uint32(len(peers)) { + return s, errors.New("timeout is required") + } + + s.Threshold = threshold + + return s, nil +} + +func (s *DistributedStore) GetPath() string { + return s.Path +} + +func (s *DistributedStore) GetType() string { + return s.Type +} diff --git a/utils/store/hd.go b/utils/store/hd.go new file mode 100644 index 0000000..e1af4b5 --- /dev/null +++ b/utils/store/hd.go @@ -0,0 +1,105 @@ +package store + +import ( + "context" + "fmt" + + "github.com/pkg/errors" + "github.com/spf13/viper" + e2wallet "github.com/wealdtech/go-eth2-wallet" + types "github.com/wealdtech/go-eth2-wallet-types/v2" +) + +type HDStore struct { + Type string + Store types.Store + Path string + Passphrases [][]byte + Wallets map[string][]string + Ctx context.Context +} + +func (s *HDStore) Create() error { + store, err := createStore(s.Path) + if err != nil { + return err + } + + s.Store = store + + return nil +} +func (s *HDStore) GetWalletsAccountsMap() (WalletAccountsMap, error) { + wa, err := getWalletsAccountsMap(s.Ctx, s.Path) + if err != nil { + return nil, err + } + + return wa, nil +} + +func (s *HDStore) CreateWallet(name string) (types.Wallet, error) { + err := e2wallet.UseStore(s.Store) + if err != nil { + return nil, err + } + wallet, err := e2wallet.CreateWallet(name, e2wallet.WithType(s.Type)) + if err != nil { + return nil, errors.Wrap(err, "test") + } + return wallet, nil +} + +func (s *HDStore) GetPK(w string, a string) ([]byte, error) { + wallet, err := getWallet(s.Path, w) + if err != nil { + return nil, err + } + account, err := wallet.(types.WalletAccountByNameProvider).AccountByName(s.Ctx, a) + if err != nil { + return nil, err + } + + key, err := getAccountPK(account, s.Ctx, s.Passphrases) + if err != nil { + return nil, err + } + + return key, nil +} + +func (s *HDStore) GetPath() string { + return s.Path +} + +func (s *HDStore) GetType() string { + return s.Type +} + +func newHDStore(t string) (HDStore, error) { + s := HDStore{} + //Parse Wallet Type + wt := viper.GetString(fmt.Sprintf("%s.wallet.type", t)) + s.Type = wt + + //Parse Store Path + storePath := viper.GetString(fmt.Sprintf("%s.store.path", t)) + if storePath == "" { + return s, errors.New("timeout is required") + } + s.Path = storePath + + //Parse Passphrases + passphrases, err := getAccountsPasswords( + viper.GetString(fmt.Sprintf("%s.wallet.passphrases.path", t)), + ) + if err != nil { + return s, errors.New("timeout is required") + } + if len(passphrases) == 0 { + return s, errors.New("timeout is requried") + } + s.Passphrases = passphrases + + return s, nil +} diff --git a/utils/store/helpers.go b/utils/store/helpers.go new file mode 100644 index 0000000..333d354 --- /dev/null +++ b/utils/store/helpers.go @@ -0,0 +1,127 @@ +package store + +import ( + "bytes" + "context" + "os" + + "github.com/pkg/errors" + e2wallet "github.com/wealdtech/go-eth2-wallet" + filesystem "github.com/wealdtech/go-eth2-wallet-store-filesystem" + types "github.com/wealdtech/go-eth2-wallet-types/v2" +) + +type WalletAccountsMap map[string][]string + +func lockAccount(ctx context.Context, acc types.Account) error { + if locker, isLocker := acc.(types.AccountLocker); isLocker { + unlocked, err := locker.IsUnlocked(ctx) + if err != nil { + return err + } + if unlocked { + err := locker.Lock(ctx) + if err != nil { + return err + } + } + } + return nil +} + +func unlockAccount(ctx context.Context, acc types.Account, passphrases [][]byte) (types.Account, error) { + account := acc + if locker, isLocker := account.(types.AccountLocker); isLocker { + unlocked, err := locker.IsUnlocked(ctx) + if err != nil { + return nil, err + } + if !unlocked { + for _, passphrase := range passphrases { + err = locker.Unlock(ctx, passphrase) + if err == nil { + unlocked = true + break + } + } + if !unlocked { + err = errors.Wrap(nil, "test") + return nil, err + } + } + } + + return account, nil +} + +func getAccountsPasswords(path string) ([][]byte, error) { + + content, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + accountsPasswords := bytes.Split(content, []byte{'\n'}) + if len(accountsPasswords) == 0 { + return nil, errors.Wrap(err, "test") + } + return accountsPasswords, nil +} + +func createStore(path string) (types.Store, error) { + store := filesystem.New(filesystem.WithLocation(path)) + return store, nil +} + +func getWalletsAccountsMap(ctx context.Context, location string) (WalletAccountsMap, error) { + wa := WalletAccountsMap{} + store := filesystem.New(filesystem.WithLocation(location)) + if err := e2wallet.UseStore(store); err != nil { + return wa, err + } + for w := range e2wallet.Wallets() { + for a := range w.Accounts(ctx) { + wa[w.Name()] = append(wa[w.Name()], a.Name()) + } + } + + return wa, nil +} + +func getAccountPK(account types.Account, ctx context.Context, passphrases [][]byte) ([]byte, error) { + privateKeyProvider, isPrivateKeyProvider := account.(types.AccountPrivateKeyProvider) + if !isPrivateKeyProvider { + err := errors.Wrap(nil, "test") + return nil, err + } + + _, err := unlockAccount(ctx, account, passphrases) + if err != nil { + return nil, err + } + + key, err := privateKeyProvider.PrivateKey(ctx) + if err != nil { + return nil, err + } + + // Lock Account + defer func() { + err = lockAccount(ctx, account) + }() + + return key.Marshal(), nil +} + +func getWallet(location string, n string) (types.Wallet, error) { + store := filesystem.New(filesystem.WithLocation(location)) + if err := e2wallet.UseStore(store); err != nil { + return nil, err + } + w, err := e2wallet.OpenWallet(n) + if err != nil { + return nil, err + } + + return w, nil +} diff --git a/utils/store/nd.go b/utils/store/nd.go new file mode 100644 index 0000000..a05fb44 --- /dev/null +++ b/utils/store/nd.go @@ -0,0 +1,129 @@ +package store + +import ( + "context" + "fmt" + + "github.com/pkg/errors" + "github.com/spf13/viper" + e2wallet "github.com/wealdtech/go-eth2-wallet" + types "github.com/wealdtech/go-eth2-wallet-types/v2" +) + +type NDStore struct { + Type string + Store types.Store + Path string + Passphrases [][]byte + Wallets map[string][]string + Ctx context.Context +} + +func (s *NDStore) Create() error { + store, err := createStore(s.Path) + if err != nil { + return err + } + s.Store = store + + return nil +} + +func (s *NDStore) GetWalletsAccountsMap() (WalletAccountsMap, error) { + wa, err := getWalletsAccountsMap(s.Ctx, s.Path) + if err != nil { + return nil, err + } + + return wa, nil +} + +func (s *NDStore) CreateWallet(name string) error { + err := e2wallet.UseStore(s.Store) + if err != nil { + return err + } + _, err = e2wallet.CreateWallet(name, e2wallet.WithType(s.Type)) + if err != nil { + return errors.Wrap(err, "test") + } + return nil +} + +func (s *NDStore) GetPK(w string, a string) ([]byte, error) { + wallet, err := getWallet(s.Path, w) + if err != nil { + return nil, err + } + account, err := wallet.(types.WalletAccountByNameProvider).AccountByName(s.Ctx, a) + if err != nil { + return nil, err + } + + key, err := getAccountPK(account, s.Ctx, s.Passphrases) + if err != nil { + return nil, err + } + + return key, nil +} + +func (s *NDStore) SavePKToWallet(w string, a []byte, n string) error { + wallet, err := getWallet(s.Path, w) + if err != nil { + return err + } + err = wallet.(types.WalletLocker).Unlock(context.Background(), nil) + if err != nil { + return errors.Wrap(err, "test") + } + + defer func() { + err = wallet.(types.WalletLocker).Lock(context.Background()) + }() + + _, err = wallet.(types.WalletAccountImporter).ImportAccount(s.Ctx, + n, + a, + s.Passphrases[0], + ) + if err != nil { + return errors.Wrap(err, "test") + } + + return nil +} + +func (s *NDStore) GetPath() string { + return s.Path +} + +func (s *NDStore) GetType() string { + return s.Type +} + +func newNDStore(t string) (NDStore, error) { + s := NDStore{} + //Parse Wallet Type + wt := viper.GetString(fmt.Sprintf("%s.wallet.type", t)) + s.Type = wt + + //Parse Store Path + storePath := viper.GetString(fmt.Sprintf("%s.store.path", t)) + if storePath == "" { + return s, errors.New("timeout is required") + } + s.Path = storePath + + //Parse Passphrases + passphrases, err := getAccountsPasswords(viper.GetString(fmt.Sprintf("%s.wallet.passphrases.path", t))) + if err != nil { + return s, errors.New("timeout is required") + } + if len(passphrases) == 0 { + return s, errors.New("timeout is requried") + } + s.Passphrases = passphrases + + return s, nil +} diff --git a/utils/store/stores.go b/utils/store/stores.go new file mode 100644 index 0000000..5432cb4 --- /dev/null +++ b/utils/store/stores.go @@ -0,0 +1,82 @@ +package store + +import ( + "context" + + "github.com/pkg/errors" +) + +type IStore interface { + // Get Wallets Names And Account Names + GetWalletsAccountsMap() (WalletAccountsMap, error) + // Get Private Key From Wallet Using Account Name + GetPK(a string, w string) ([]byte, error) + // Get Store Type + GetType() string + // Get Store Path + GetPath() string +} + +type OStore interface { + // Create Store + Create() error + // Create New Wallet + CreateWallet(name string) error + // Save Private Key To Wallet + SavePKToWallet(w string, a []byte, n string) error + // Get Store Type + GetType() string + // Get Store Path + GetPath() string +} + +func InputStoreInit(ctx context.Context, t string) (IStore, error) { + switch t { + case "distributed": + s, err := newDistributedStore("input") + if err != nil { + return nil, err + } + s.Ctx = ctx + return &s, nil + case "hierarchical deterministic": + s, err := newHDStore("input") + if err != nil { + return nil, err + } + s.Ctx = ctx + return &s, nil + case "non-deterministic": + s, err := newNDStore("input") + if err != nil { + return nil, err + } + s.Ctx = ctx + return &s, nil + default: + return nil, errors.New("incorrect wallet type") + } + +} + +func OutputStoreInit(ctx context.Context, t string) (OStore, error) { + switch t { + case "distributed": + s, err := newDistributedStore("output") + if err != nil { + return nil, err + } + s.Ctx = ctx + return &s, nil + case "non-deterministic": + s, err := newNDStore("output") + if err != nil { + return nil, err + } + s.Ctx = ctx + return &s, nil + default: + return nil, errors.New("incorrect wallet type") + } + +}