From 4330d849d0fbad26372eb2c8d38408c40da4db31 Mon Sep 17 00:00:00 2001 From: jazkamer Date: Tue, 14 Nov 2023 19:22:03 +0200 Subject: [PATCH] init --- cmd/{combine.go => convert.go} | 16 ++-- cmd/convert/input.go | 48 +++++++++++ cmd/convert/process.go | 53 +++++++++++++ cmd/convert/run.go | 27 +++++++ cmd/split.go | 27 ------- config.yaml | 21 +++++ utils/config.go | 136 ++++++++++++++++++++------------ utils/stores.go | 63 --------------- utils/stores/distributed.go | 58 ++++++++++++++ utils/stores/hd.go | 55 +++++++++++++ utils/stores/nd.go | 55 +++++++++++++ utils/stores/stores.go | 70 ++++++++++++++++ utils/wallets.go | 55 ------------- 13 files changed, 481 insertions(+), 203 deletions(-) rename cmd/{combine.go => convert.go} (51%) create mode 100644 cmd/convert/input.go create mode 100644 cmd/convert/process.go create mode 100644 cmd/convert/run.go delete mode 100644 cmd/split.go create mode 100644 config.yaml delete mode 100644 utils/stores.go create mode 100644 utils/stores/distributed.go create mode 100644 utils/stores/hd.go create mode 100644 utils/stores/nd.go create mode 100644 utils/stores/stores.go delete mode 100644 utils/wallets.go diff --git a/cmd/combine.go b/cmd/ convert.go similarity index 51% rename from cmd/combine.go rename to cmd/ convert.go index 1deab5c..d01a6e1 100644 --- a/cmd/combine.go +++ b/cmd/ convert.go @@ -1,21 +1,21 @@ package cmd import ( - "github.com/p2p-org/dkc/cmd/combine" + "github.com/p2p-org/dkc/cmd/convert" "github.com/p2p-org/dkc/utils" "github.com/spf13/cobra" "github.com/spf13/viper" ) -var combineCmd = &cobra.Command{ - Use: "combine", - Short: "Combine distributed wallets to keystore", - Long: `Allow to combine distributed wallets to keystore`, +var convertCmd = &cobra.Command{ + Use: "convert", + Short: "Convert from distributed|hd|nd wallet types to distributed or nd wallets types", + Long: `Allow to convert wallets types between each other`, Run: func(cmd *cobra.Command, args []string) { utils.Log.Info().Msgf("starting DKC-%s", viper.Get("version")) utils.Log.Info().Msgf("using config file: %s", viper.ConfigFileUsed()) - utils.LogCombine.Info().Msg("starting combine function") - err := combine.Run() + utils.LogCombine.Info().Msg("starting convert function") + err := convert.Run() if err != nil { utils.LogCombine.Fatal().Err(nil).Send() } @@ -23,5 +23,5 @@ var combineCmd = &cobra.Command{ } func init() { - rootCmd.AddCommand(combineCmd) + rootCmd.AddCommand(convertCmd) } diff --git a/cmd/convert/input.go b/cmd/convert/input.go new file mode 100644 index 0000000..0be1eca --- /dev/null +++ b/cmd/convert/input.go @@ -0,0 +1,48 @@ +package convert + +import ( + "context" + + "github.com/p2p-org/dkc/utils" + "github.com/pkg/errors" +) + +type dataIn struct { + ctx context.Context + InputW utils.W + OutputW utils.W +} + +func (d *dataIn) validate() error { + if d.InputW.Path == d.OutputW.Path { + return errors.New("timeout is required") + } + + if d.InputW.Type == d.OutputW.Type { + return errors.New("timeout is required") + } + + return nil +} + +func input(ctx context.Context) (*dataIn, error) { + var err error + data := &dataIn{ + InputW: utils.W{}, + OutputW: utils.W{}, + } + + //Parse Input Config + data.InputW, err = utils.ParseCfg("input") + if err != nil { + return nil, errors.New("timeout is required") + } + + //Parse Output Config + data.OutputW, err = utils.ParseCfg("output") + if err != nil { + return nil, errors.New("timeout is required") + } + + return data, nil +} diff --git a/cmd/convert/process.go b/cmd/convert/process.go new file mode 100644 index 0000000..f25499d --- /dev/null +++ b/cmd/convert/process.go @@ -0,0 +1,53 @@ +package convert + +import ( + "context" + + "github.com/p2p-org/dkc/utils" + "golang.org/x/sync/errgroup" +) + +func process(ctx context.Context, data *dataIn) error { + //Init Stores + iStore, err := utils.InputStoreInit(data.InputW.Type) + if err != nil { + return err + } + + oStore, err := utils.OutputStoreInit(data.OutputW.Type) + if err != nil { + return err + } + + // Get Wallets List From Input Store + err = iStore.Load(data.ctx, data.InputW.Path, data.InputW.Passphrases, data.InputW.Peers) + if err != nil { + return err + } + + // Create Output Store + err = oStore.Create(data.ctx, data.OutputW.Path) + if err != nil { + return err + } + + // Convert Wallets + eg := errgroup.Group{} + + for _, wallet := range iStore.GetWallets() { + eg.Go(func() error { + w, err := oStore.CreateWallet(wallet) + if err != nil { + return err + } + }) + } + + // Only First Error Will Be Displayed + err = eg.Wait() + if err != nil { + return err + } + + return nil +} diff --git a/cmd/convert/run.go b/cmd/convert/run.go new file mode 100644 index 0000000..2136ebd --- /dev/null +++ b/cmd/convert/run.go @@ -0,0 +1,27 @@ +package convert + +import ( + "context" + + "github.com/pkg/errors" +) + +func Run() error { + ctx := context.Background() + dataIn, err := input(ctx) + if err != nil { + return errors.Wrap(err, "failed to obtain input") + } + + err = dataIn.validate() + if err != nil { + return errors.Wrap(err, "failed to obtain input") + } + + err = process(ctx, dataIn) + if err != nil { + return errors.Wrap(err, "failed to process") + } + + return nil +} diff --git a/cmd/split.go b/cmd/split.go deleted file mode 100644 index fd8b4df..0000000 --- a/cmd/split.go +++ /dev/null @@ -1,27 +0,0 @@ -package cmd - -import ( - "github.com/p2p-org/dkc/cmd/split" - "github.com/p2p-org/dkc/utils" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -var splitCmd = &cobra.Command{ - Use: "split", - Short: "Split keystore to distributed wallets", - Long: `Allow to split keystore to distributed wallets`, - Run: func(cmd *cobra.Command, args []string) { - utils.Log.Info().Msgf("starting DKC-%s", viper.Get("version")) - utils.Log.Info().Msgf("using config file: %s", viper.ConfigFileUsed()) - utils.LogSplit.Info().Msg("starting split function") - err := split.Run() - if err != nil { - utils.LogSplit.Fatal().Err(nil).Send() - } - }, -} - -func init() { - rootCmd.AddCommand(splitCmd) -} diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..899d8bc --- /dev/null +++ b/config.yaml @@ -0,0 +1,21 @@ +input: + store: + path: DISTRIBUTED_WALLETS + wallet: + type: distributed + passphrases: + path: ./input_wallet_passphrases.txt + threshold: 2 + peers: + 10: old1:9091 + 20: old2:9091 + 30: old3:9091 +output: + store: + path: ND_WALLETS + wallet: + type: nd + passphrases: + path: ./output_wallet_passphrases.txt + +log-level: debug \ No newline at end of file diff --git a/utils/config.go b/utils/config.go index da35530..0d514d8 100644 --- a/utils/config.go +++ b/utils/config.go @@ -2,78 +2,114 @@ package utils import ( "bytes" + "fmt" "os" "github.com/pkg/errors" + "github.com/spf13/viper" ) -type NDWalletConfig struct { - Path string - Passphrases string -} +const ( + errorFailedToCreateWalletWrapper = "failed to create wallet" +) -type DWalletConfig struct { +type Peers map[string]uint64 + +type W struct { + Type string Path string - Passphrases string - Peers Peers - Threshold uint32 - WalletName string + Passphrases [][]byte + + //Distributed Wallets fields + Peers Peers + Threshold uint32 } -func GetAccountsPasswords(path string) ([][]byte, error) { +var validWalletTypes = map[string]map[string]bool{ + "input": { + "distributed": true, + "hierarchical deterministic": true, + "non-deterministic": true, + }, + "output": { + "distributed": true, + "non-deterministic": true, + }, +} - content, err := os.ReadFile(path) - if err != nil { - return nil, err +func (w W) ParseCfg(t string) (W, error) { + //Check For correct input + if t != "input" || t != "output" { + return w, errors.Wrap(nil, "incorrect") } - accountsPasswords := bytes.Split(content, []byte{'\n'}) - if len(accountsPasswords) == 0 { - err := ErrorPassphrasesField - return nil, errors.Wrap(err, ErrorDWalletStructWrapper) + //Parse Wallet Type + wt := viper.GetString(fmt.Sprintf("%s.wallet.type", t)) + if !validWalletTypes[t][wt] { + return w, errors.New("timeout is required") } - return accountsPasswords, nil -} + w.Type = wt -func (data *NDWalletConfig) Validate() error { - if data.Path == "" { - err := ErrorPathField - return errors.Wrap(err, ErrorNDWalletStructWrapper) + //Parse Store Path + storePath := viper.GetString(fmt.Sprintf("%s.store.path", t)) + if storePath == "" { + return w, errors.New("timeout is required") } + w.Path = storePath - if data.Passphrases == "" { - err := ErrorPassphrasesField - return errors.Wrap(err, ErrorNDWalletStructWrapper) + //Parse Passphrases + passphrases, err := getAccountsPasswords(viper.GetString(fmt.Sprintf("%s.wallet.passphrases.path", t))) + if err != nil { + return w, errors.New("timeout is required") } - - return nil -} - -func (data *DWalletConfig) Validate() error { - if data.Path == "" { - err := ErrorPathField - return errors.Wrap(err, ErrorDWalletStructWrapper) + if len(passphrases) == 0 { + return w, errors.New("timeout is requried") } - - if data.Passphrases == "" { - err := ErrorPassphrasesField - return errors.Wrap(err, ErrorDWalletStructWrapper) + w.Passphrases = passphrases + + //Parse Distributed Wallet + switch wt { + case "distributed": + //Parse Peers + var peers Peers + viper.UnmarshalKey(fmt.Sprintf("%s.wallet.peers", t), peers) + if err != nil { + return w, errors.New("timeout is required") + } + //Peers list must be >= 2 + if len(peers) < 2 { + return w, errors.New("timeout is required") + } + w.Peers = peers + + //Parse Threshold + threshold := viper.GetUint32(fmt.Sprintf("%s.wallet.threshold", t)) + + //Check number of peers and threshold + if threshold <= uint32(len(peers)/2) { + return w, errors.New("timeout is required") + } + if threshold > uint32(len(peers)) { + return w, errors.New("timeout is required") + } + + w.Threshold = threshold } - if len(data.Peers) == 0 { - err := ErrorPeersField - return errors.Wrap(err, ErrorDWalletStructWrapper) - } + return w, nil +} - if data.Threshold == 0 { - err := ErrorThresholdField - return errors.Wrap(err, ErrorDWalletStructWrapper) - } +func getAccountsPasswords(path string) ([][]byte, error) { - if len(data.Peers) < int(data.Threshold) { - err := ErrorNotEnoughPeers - return errors.Wrap(err, ErrorDWalletStructWrapper) + content, err := os.ReadFile(path) + if err != nil { + return nil, err } - return nil + accountsPasswords := bytes.Split(content, []byte{'\n'}) + if len(accountsPasswords) == 0 { + err := ErrorPassphrasesField + return nil, errors.Wrap(err, ErrorDWalletStructWrapper) + } + return accountsPasswords, nil } diff --git a/utils/stores.go b/utils/stores.go deleted file mode 100644 index 62ac42e..0000000 --- a/utils/stores.go +++ /dev/null @@ -1,63 +0,0 @@ -package utils - -import ( - "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 DirkStore struct { - Location string - Wallets []types.Wallet -} - -type Peers = map[uint64]string - -type Account struct { - ID uint64 - Key []byte - Signature []byte -} - -func CreateStore(path string) (types.Store, error) { - store := filesystem.New(filesystem.WithLocation(path)) - return store, nil -} - -func LoadStores(ctx context.Context, walletDir string, passphrases [][]byte) ([]DirkStore, error) { - var stores []DirkStore - - dirs, err := os.ReadDir(walletDir) - if err != nil { - return nil, errors.Wrap(err, ErrorWalletDirWrapper) - } - for _, f := range dirs { - if f.IsDir() { - store, err := LoadStore(ctx, walletDir+"/"+f.Name(), passphrases) - if err != nil { - return nil, errors.Wrap(err, ErrorLoadStoreWrapper) - } - stores = append(stores, *store) - } - } - return stores, nil -} - -func LoadStore(ctx context.Context, location string, passphrases [][]byte) (*DirkStore, error) { - dirkStore := DirkStore{} - dirkStore.Location = location - var wallets []types.Wallet - store := filesystem.New(filesystem.WithLocation(location)) - if err := e2wallet.UseStore(store); err != nil { - return nil, errors.Wrap(err, ErrorUseStoreWrapper) - } - for wallet := range e2wallet.Wallets() { - wallets = append(wallets, wallet) - } - dirkStore.Wallets = wallets - return &dirkStore, nil -} diff --git a/utils/stores/distributed.go b/utils/stores/distributed.go new file mode 100644 index 0000000..8ff6535 --- /dev/null +++ b/utils/stores/distributed.go @@ -0,0 +1,58 @@ +package utils + +import ( + "context" + + "github.com/pkg/errors" + e2wallet "github.com/wealdtech/go-eth2-wallet" + types "github.com/wealdtech/go-eth2-wallet-types/v2" + "golang.org/x/exp/maps" +) + +type DistributedStore struct { + Type string + Store types.Store + Wallets map[string][]types.Wallet +} + +func (s DistributedStore) GetWallets() []string { + //Get Only First Peer, we assume that all peers have the same number of wallets and wallets have the same accounts + var wallets []string + peers := maps.Keys(s.Wallets) + for _, w := range s.Wallets[peers[0]] { + wallets = append(wallets, w.Name()) + } + return wallets +} + +func (s DistributedStore) Create(path string) error { + store, err := createStore(path) + if err != nil { + return err + } + s.Store = store + return nil +} + +func (s DistributedStore) Load(ctx context.Context, location string, passphrases [][]byte, peers Peers) error { + for peer := range peers { + w, err := loadStore(ctx, location+"/"+peer, passphrases) + if err != nil { + return err + } + s.Wallets[peer] = w + } + return nil +} + +func (s DistributedStore) 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 +} diff --git a/utils/stores/hd.go b/utils/stores/hd.go new file mode 100644 index 0000000..d99a408 --- /dev/null +++ b/utils/stores/hd.go @@ -0,0 +1,55 @@ +package utils + +import ( + "context" + + "github.com/pkg/errors" + e2wallet "github.com/wealdtech/go-eth2-wallet" + types "github.com/wealdtech/go-eth2-wallet-types/v2" +) + +type HDStore struct { + Type string + Store types.Store + Wallets []types.Wallet +} + +func (s HDStore) GetWallets() []string { + var wallets []string + for _, w := range s.Wallets { + wallets = append(wallets, w.Name()) + } + return wallets +} + +func (s HDStore) Create(path string) error { + store, err := createStore(path) + if err != nil { + return err + } + s.Store = store + return nil +} + +func (s HDStore) Load(ctx context.Context, location string, passphrases [][]byte, peers Peers) error { + wallets, err := loadStore(ctx, location, passphrases) + if err != nil { + return err + } + + s.Wallets = wallets + + return 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 +} diff --git a/utils/stores/nd.go b/utils/stores/nd.go new file mode 100644 index 0000000..3391aa1 --- /dev/null +++ b/utils/stores/nd.go @@ -0,0 +1,55 @@ +package utils + +import ( + "context" + + "github.com/pkg/errors" + e2wallet "github.com/wealdtech/go-eth2-wallet" + types "github.com/wealdtech/go-eth2-wallet-types/v2" +) + +type NDStore struct { + Type string + Store types.Store + Wallets []types.Wallet +} + +func (s NDStore) GetWallets() []string { + var wallets []string + for _, w := range s.Wallets { + wallets = append(wallets, w.Name()) + } + return wallets +} + +func (s NDStore) Create(path string) error { + store, err := createStore(path) + if err != nil { + return err + } + + s.Store = store + return nil +} + +func (s NDStore) Load(ctx context.Context, location string, passphrases [][]byte, peers Peers) error { + wallets, err := loadStore(ctx, location, passphrases) + if err != nil { + return err + } + + s.Wallets = wallets + return nil +} + +func (s NDStore) 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 +} diff --git a/utils/stores/stores.go b/utils/stores/stores.go new file mode 100644 index 0000000..59fe191 --- /dev/null +++ b/utils/stores/stores.go @@ -0,0 +1,70 @@ +package utils + +import ( + "context" + + "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 Peers = map[string]uint64 + +type IStore interface { + Load(ctx context.Context, location string, passphrases [][]byte, peers Peers) error + GetWallets() []string +} + +type OStore interface { + Create(path string) error + CreateWallet(name string) (types.Wallet, error) +} + +func InputStoreInit(t string) (IStore, error) { + switch t { + case "distributed": + return DistributedStore{Type: "distributed"}, nil + case "hierarchical deterministic": + return HDStore{Type: "hierarchical deterministic"}, nil + case "non-deterministic": + return NDStore{Type: "non-deterministic"}, nil + default: + return nil, errors.Wrap(nil, "filed") + } + +} + +func OutputStoreInit(t string) (OStore, error) { + switch t { + case "distributed": + return DistributedStore{Type: "distributed"}, nil + case "hierarchical deterministic": + return HDStore{Type: "hierarchical deterministic"}, nil + case "non-deterministic": + return NDStore{Type: "non-deterministic"}, nil + default: + return nil, errors.Wrap(nil, "filed") + } + +} + +func createStore(path string) (types.Store, error) { + store := filesystem.New(filesystem.WithLocation(path)) + return store, nil +} + +func loadStore(ctx context.Context, location string, passphrases [][]byte) ([]types.Wallet, error) { + var wallets []types.Wallet + store := filesystem.New(filesystem.WithLocation(location)) + if err := e2wallet.UseStore(store); err != nil { + return nil, errors.Wrap(err, "test") + } + for wallet := range e2wallet.Wallets() { + wallets = append(wallets, wallet) + } + if len(wallets) == 0 { + return nil, errors.Wrap(nil, "test") + } + return wallets, nil +} diff --git a/utils/wallets.go b/utils/wallets.go deleted file mode 100644 index 83a8094..0000000 --- a/utils/wallets.go +++ /dev/null @@ -1,55 +0,0 @@ -package utils - -import ( - "github.com/google/uuid" - "github.com/pkg/errors" - e2wallet "github.com/wealdtech/go-eth2-wallet" - types "github.com/wealdtech/go-eth2-wallet-types/v2" -) - -const ( - errorFailedToCreateWalletWrapper = "failed to create wallet" -) - -type NDWallet interface { - types.WalletAccountImporter - types.WalletLocker -} - -type DWallet interface { - types.WalletDistributedAccountImporter - types.WalletLocker -} - -func CreateNDWallet(store types.Store) (NDWallet, error) { - walletName := uuid.New().String() - wallet, err := createWallet(store, "non-deterministic", walletName) - if err != nil { - return nil, err - } - ndWallet := wallet.(NDWallet) - - return ndWallet, err -} - -func CreateDWallet(store types.Store, walletName string) (DWallet, error) { - wallet, err := createWallet(store, "distributed", walletName) - if err != nil { - return nil, err - } - dWallet := wallet.(DWallet) - - return dWallet, nil -} - -func createWallet(store types.Store, wType string, walletName string) (types.Wallet, error) { - err := e2wallet.UseStore(store) - if err != nil { - return nil, err - } - wallet, err := e2wallet.CreateWallet(walletName, e2wallet.WithType(wType)) - if err != nil { - return nil, errors.Wrap(err, errorFailedToCreateWalletWrapper) - } - return wallet, nil -}