diff --git a/validator/accounts/testing/mock.go b/validator/accounts/testing/mock.go index f4d8379e86be..2fc52606682c 100644 --- a/validator/accounts/testing/mock.go +++ b/validator/accounts/testing/mock.go @@ -25,6 +25,7 @@ type Wallet struct { WalletPassword string UnlockAccounts bool lock sync.RWMutex + HasWriteFileError bool } // AccountNames -- @@ -57,6 +58,11 @@ func (w *Wallet) Password() string { func (w *Wallet) WriteFileAtPath(_ context.Context, pathName, fileName string, data []byte) error { w.lock.Lock() defer w.lock.Unlock() + if w.HasWriteFileError { + // reset the flag to not contaminate other tests + w.HasWriteFileError = false + return errors.New("could not write keystore file for accounts") + } if w.Files[pathName] == nil { w.Files[pathName] = make(map[string][]byte) } diff --git a/validator/keymanager/local/BUILD.bazel b/validator/keymanager/local/BUILD.bazel index 779dcc5a5f92..d06b41cac2c8 100644 --- a/validator/keymanager/local/BUILD.bazel +++ b/validator/keymanager/local/BUILD.bazel @@ -66,6 +66,7 @@ go_test( "//testing/require:go_default_library", "//validator/accounts/testing:go_default_library", "//validator/keymanager:go_default_library", + "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", "@com_github_google_uuid//:go_default_library", "@com_github_sirupsen_logrus//hooks/test:go_default_library", "@com_github_wealdtech_go_eth2_wallet_encryptor_keystorev4//:go_default_library", diff --git a/validator/keymanager/local/delete.go b/validator/keymanager/local/delete.go index 3a6f33ffabc7..028961e985b8 100644 --- a/validator/keymanager/local/delete.go +++ b/validator/keymanager/local/delete.go @@ -3,10 +3,7 @@ package local import ( "bytes" "context" - "encoding/json" - "fmt" - "github.com/pkg/errors" fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams" "github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service" @@ -16,17 +13,22 @@ import ( // DeleteKeystores takes in public keys and removes the accounts from the wallet. // This includes their disk keystore and cached keystore, but maintains the slashing // protection history in the database. +// 1) Copy the in memory keystore +// 2) Delete the keys from copied in memory keystore +// 3) Save the copy to disk +// 4) Reinitialize account store and updating the keymanager +// 5) Return API response func (km *Keymanager) DeleteKeystores( ctx context.Context, publicKeys [][]byte, ) ([]*ethpbservice.DeletedKeystoreStatus, error) { // Check for duplicate keys and filter them out. trackedPublicKeys := make(map[[fieldparams.BLSPubkeyLength]byte]bool) statuses := make([]*ethpbservice.DeletedKeystoreStatus, 0, len(publicKeys)) - var store *AccountsKeystoreRepresentation - var err error deletedKeys := make([][]byte, 0, len(publicKeys)) + // 1) Copy the in memory keystore + storeCopy := km.accountsStore.Copy() for _, publicKey := range publicKeys { - // Check if the key in the request is a duplicate. + // Check if the key in the request is a duplicate or not found if _, ok := trackedPublicKeys[bytesutil.ToBytes48(publicKey)]; ok { statuses = append(statuses, ðpbservice.DeletedKeystoreStatus{ Status: ethpbservice.DeletedKeystoreStatus_NOT_ACTIVE, @@ -35,7 +37,7 @@ func (km *Keymanager) DeleteKeystores( } var index int var found bool - for j, pubKey := range km.accountsStore.PublicKeys { + for j, pubKey := range storeCopy.PublicKeys { if bytes.Equal(pubKey, publicKey) { index = j found = true @@ -48,14 +50,11 @@ func (km *Keymanager) DeleteKeystores( }) continue } - deletedPublicKey := km.accountsStore.PublicKeys[index] + // 2) Delete the keys from copied in memory keystore + deletedPublicKey := storeCopy.PublicKeys[index] deletedKeys = append(deletedKeys, deletedPublicKey) - km.accountsStore.PrivateKeys = append(km.accountsStore.PrivateKeys[:index], km.accountsStore.PrivateKeys[index+1:]...) - km.accountsStore.PublicKeys = append(km.accountsStore.PublicKeys[:index], km.accountsStore.PublicKeys[index+1:]...) - store, err = km.CreateAccountsKeystore(ctx, km.accountsStore.PrivateKeys, km.accountsStore.PublicKeys) - if err != nil { - return nil, errors.Wrap(err, "could not rewrite accounts keystore") - } + storeCopy.PrivateKeys = append(storeCopy.PrivateKeys[:index], storeCopy.PrivateKeys[index+1:]...) + storeCopy.PublicKeys = append(storeCopy.PublicKeys[:index], storeCopy.PublicKeys[index+1:]...) statuses = append(statuses, ðpbservice.DeletedKeystoreStatus{ Status: ethpbservice.DeletedKeystoreStatus_DELETED, }) @@ -64,35 +63,14 @@ func (km *Keymanager) DeleteKeystores( if len(deletedKeys) == 0 { return statuses, nil } - var deletedKeysStr string - for i, k := range deletedKeys { - if i == 0 { - deletedKeysStr += fmt.Sprintf("%#x", bytesutil.Trunc(k)) - } else if i == len(deletedKeys)-1 { - deletedKeysStr += fmt.Sprintf("%#x", bytesutil.Trunc(k)) - } else { - deletedKeysStr += fmt.Sprintf(",%#x", bytesutil.Trunc(k)) - } - } - - log.WithFields(logrus.Fields{ - "publicKeys": deletedKeysStr, - }).Info("Successfully deleted validator key(s)") - - // Write the encoded keystore. - encoded, err := json.MarshalIndent(store, "", "\t") - if err != nil { + // 3 & 4) save to disk and re-initializes keystore + if err := km.SaveStoreAndReInitialize(ctx, storeCopy); err != nil { return nil, err } - if err := km.wallet.WriteFileAtPath(ctx, AccountsPath, AccountsKeystoreFileName, encoded); err != nil { - return nil, errors.Wrap(err, "could not write keystore file for accounts") - } - err = km.initializeKeysCachesFromKeystore() - if err != nil { - return nil, errors.Wrap(err, "failed to initialize key caches") - } + log.WithFields(logrus.Fields{ - "publicKeys": deletedKeysStr, + "publicKeys": CreatePrintoutOfKeys(deletedKeys), }).Info("Successfully deleted validator key(s)") + // 5) Return API response return statuses, nil } diff --git a/validator/keymanager/local/delete_test.go b/validator/keymanager/local/delete_test.go index 8b20cf70c0a0..d4bf16703ba8 100644 --- a/validator/keymanager/local/delete_test.go +++ b/validator/keymanager/local/delete_test.go @@ -50,6 +50,17 @@ func TestLocalKeymanager_DeleteKeystores(t *testing.T) { require.Equal(t, ethpbservice.DeletedKeystoreStatus_NOT_FOUND, statuses[0].Status) require.Equal(t, ethpbservice.DeletedKeystoreStatus_NOT_FOUND, statuses[1].Status) }) + t.Run("file write errors should not lead to updated local keystore or cache", func(t *testing.T) { + wallet.HasWriteFileError = true + accountToRemove := uint64(2) + accountPubKey := accounts[accountToRemove] + require.NotEqual(t, len(dr.accountsStore.PublicKeys), 0) + copyStore := dr.accountsStore.Copy() + statuses, err := dr.DeleteKeystores(ctx, [][]byte{accountPubKey[:]}) + require.ErrorContains(t, "could not write keystore file for accounts", err) + require.Equal(t, len(statuses), 0) + require.DeepEqual(t, dr.accountsStore, copyStore) + }) t.Run("deletes properly", func(t *testing.T) { accountToRemove := uint64(2) accountPubKey := accounts[accountToRemove] @@ -83,6 +94,7 @@ func TestLocalKeymanager_DeleteKeystores(t *testing.T) { require.LogsContain(t, hook, fmt.Sprintf("%#x", bytesutil.Trunc(accountPubKey[:]))) require.LogsContain(t, hook, "Successfully deleted validator key(s)") }) + t.Run("returns NOT_ACTIVE status for duplicate public key in request", func(t *testing.T) { accountToRemove := uint64(3) accountPubKey := accounts[accountToRemove] @@ -127,4 +139,5 @@ func TestLocalKeymanager_DeleteKeystores(t *testing.T) { require.LogsContain(t, hook, fmt.Sprintf("%#x", bytesutil.Trunc(accountPubKey[:]))) require.LogsContain(t, hook, "Successfully deleted validator key(s)") }) + } diff --git a/validator/keymanager/local/import.go b/validator/keymanager/local/import.go index 0a41d54ee37f..e68c2d90d599 100644 --- a/validator/keymanager/local/import.go +++ b/validator/keymanager/local/import.go @@ -3,7 +3,6 @@ package local import ( "context" "encoding/hex" - "encoding/json" "fmt" "strings" @@ -13,10 +12,16 @@ import ( ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service" "github.com/prysmaticlabs/prysm/v4/validator/keymanager" "github.com/schollz/progressbar/v3" + "github.com/sirupsen/logrus" keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4" ) // ImportKeystores into the local keymanager from an external source. +// 1) Copy the in memory keystore +// 2) Update copied keystore with new keys +// 3) Save the copy to disk +// 4) Reinitialize account store and updating the keymanager +// 5) Return Statuses func (km *Keymanager) ImportKeystores( ctx context.Context, keystores []*keymanager.Keystore, @@ -33,7 +38,13 @@ func (km *Keymanager) ImportKeystores( keys := map[string]string{} statuses := make([]*ethpbservice.ImportedKeystoreStatus, len(keystores)) var err error - + // 1) Copy the in memory keystore + storeCopy := km.accountsStore.Copy() + importedKeys := make([][]byte, 0) + existingPubKeys := make(map[string]bool) + for i := 0; i < len(storeCopy.PrivateKeys); i++ { + existingPubKeys[string(storeCopy.PublicKeys[i])] = true + } for i := 0; i < len(keystores); i++ { var privKeyBytes []byte var pubKeyBytes []byte @@ -49,52 +60,67 @@ func (km *Keymanager) ImportKeystores( log.Error(err) } // if key exists prior to being added then output log that duplicate key was found - if _, ok := keys[string(pubKeyBytes)]; ok { + _, isDuplicateInArray := keys[string(pubKeyBytes)] + _, isDuplicateInExisting := existingPubKeys[string(pubKeyBytes)] + if isDuplicateInArray || isDuplicateInExisting { log.Warnf("Duplicate key in import will be ignored: %#x", pubKeyBytes) statuses[i] = ðpbservice.ImportedKeystoreStatus{ Status: ethpbservice.ImportedKeystoreStatus_DUPLICATE, } continue } + keys[string(pubKeyBytes)] = string(privKeyBytes) + importedKeys = append(importedKeys, pubKeyBytes) statuses[i] = ðpbservice.ImportedKeystoreStatus{ Status: ethpbservice.ImportedKeystoreStatus_IMPORTED, } } - privKeys := make([][]byte, 0) - pubKeys := make([][]byte, 0) - for pubKey, privKey := range keys { - pubKeys = append(pubKeys, []byte(pubKey)) - privKeys = append(privKeys, []byte(privKey)) - } - - // Write the accounts to disk into a single keystore. - accountsKeystore, err := km.CreateAccountsKeystore(ctx, privKeys, pubKeys) - if err != nil { - return nil, err + if len(importedKeys) == 0 { + log.Warn("no keys were imported") + return statuses, nil } - encodedAccounts, err := json.MarshalIndent(accountsKeystore, "", "\t") - if err != nil { - return nil, err + // 2) Update copied keystore with new keys,clear duplicates in existing set + // duplicates,errored ones are already skipped + for pubKey, privKey := range keys { + storeCopy.PublicKeys = append(storeCopy.PublicKeys, []byte(pubKey)) + storeCopy.PrivateKeys = append(storeCopy.PrivateKeys, []byte(privKey)) } - if err := km.wallet.WriteFileAtPath(ctx, AccountsPath, AccountsKeystoreFileName, encodedAccounts); err != nil { + //3 & 4) save to disk and re-initializes keystore + if err := km.SaveStoreAndReInitialize(ctx, storeCopy); err != nil { return nil, err } + + log.WithFields(logrus.Fields{ + "publicKeys": CreatePrintoutOfKeys(importedKeys), + }).Info("Successfully imported validator key(s)") + + // 5) Return Statuses return statuses, nil } // ImportKeypairs directly into the keymanager. func (km *Keymanager) ImportKeypairs(ctx context.Context, privKeys, pubKeys [][]byte) error { - // Write the accounts to disk into a single keystore. - accountsKeystore, err := km.CreateAccountsKeystore(ctx, privKeys, pubKeys) - if err != nil { - return errors.Wrap(err, "could not import account keypairs") + if len(privKeys) != len(pubKeys) { + return fmt.Errorf( + "number of private keys and public keys is not equal: %d != %d", len(privKeys), len(pubKeys), + ) + } + // 1) Copy the in memory keystore + storeCopy := km.accountsStore.Copy() + + // 2) Update store and remove duplicates + updateAccountsStoreKeys(storeCopy, privKeys, pubKeys) + + // 3 & 4) save to disk and re-initializes keystore + if err := km.SaveStoreAndReInitialize(ctx, storeCopy); err != nil { + return err } - encodedAccounts, err := json.MarshalIndent(accountsKeystore, "", "\t") - if err != nil { - return errors.Wrap(err, "could not marshal accounts keystore into JSON") + // 5) verify if store was not updated + if len(km.accountsStore.PublicKeys) < len(storeCopy.PublicKeys) { + return fmt.Errorf("keys were not imported successfully, expected %d got %d", len(storeCopy.PublicKeys), len(km.accountsStore.PublicKeys)) } - return km.wallet.WriteFileAtPath(ctx, AccountsPath, AccountsKeystoreFileName, encodedAccounts) + return nil } // Retrieves the private key and public key from an EIP-2335 keystore file diff --git a/validator/keymanager/local/import_test.go b/validator/keymanager/local/import_test.go index 18553f9956e7..89c938fbdba4 100644 --- a/validator/keymanager/local/import_test.go +++ b/validator/keymanager/local/import_test.go @@ -6,13 +6,16 @@ import ( "strconv" "testing" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/google/uuid" "github.com/prysmaticlabs/prysm/v4/crypto/bls" + "github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" ethpbservice "github.com/prysmaticlabs/prysm/v4/proto/eth/service" "github.com/prysmaticlabs/prysm/v4/testing/assert" "github.com/prysmaticlabs/prysm/v4/testing/require" mock "github.com/prysmaticlabs/prysm/v4/validator/accounts/testing" "github.com/prysmaticlabs/prysm/v4/validator/keymanager" + logTest "github.com/sirupsen/logrus/hooks/test" keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4" ) @@ -94,6 +97,7 @@ func TestLocalKeymanager_NoDuplicates(t *testing.T) { } func TestLocalKeymanager_ImportKeystores(t *testing.T) { + hook := logTest.NewGlobal() ctx := context.Background() // Setup the keymanager. wallet := &mock.Wallet{ @@ -123,6 +127,7 @@ func TestLocalKeymanager_ImportKeystores(t *testing.T) { for _, status := range statuses { require.Equal(t, ethpbservice.ImportedKeystoreStatus_IMPORTED, status.Status) } + require.LogsContain(t, hook, "Successfully imported validator key(s)") }) t.Run("each imported keystore with a different password succeeds", func(t *testing.T) { numKeystores := 5 @@ -143,6 +148,7 @@ func TestLocalKeymanager_ImportKeystores(t *testing.T) { for _, status := range statuses { require.Equal(t, ethpbservice.ImportedKeystoreStatus_IMPORTED, status.Status) } + require.LogsContain(t, hook, "Successfully imported validator key(s)") }) t.Run("some succeed, some fail to decrypt, some duplicated", func(t *testing.T) { keystores := make([]*keymanager.Keystore, 0) @@ -189,5 +195,70 @@ func TestLocalKeymanager_ImportKeystores(t *testing.T) { fmt.Sprintf("incorrect password for key 0x%s", keystores[2].Pubkey), statuses[2].Message, ) + b, err := hexutil.Decode("0x" + keystore1.Pubkey) + require.NoError(t, err) + require.LogsContain(t, hook, fmt.Sprintf("%#x", bytesutil.Trunc(b))) + require.LogsContain(t, hook, "Successfully imported validator key(s)") + }) + t.Run("All fail or duplicated", func(t *testing.T) { + // First keystore is normal. + keystore1 := createRandomKeystore(t, password) + // First Import successfully + statuses, err := dr.ImportKeystores( + ctx, + []*keymanager.Keystore{keystore1}, + []string{password}, + ) + require.NoError(t, err) + require.Equal(t, len(statuses), 1) + + keystores := make([]*keymanager.Keystore, 0) + passwords := make([]string, 0) + // Second keystore is a duplicate of the first. + keystores = append(keystores, keystore1) + passwords = append(passwords, password) + + // Third keystore has a wrong password. + keystore3 := createRandomKeystore(t, password) + keystores = append(keystores, keystore3) + passwords = append(passwords, "foobar") + + statuses, err = dr.ImportKeystores( + ctx, + keystores, + passwords, + ) + require.NoError(t, err) + require.Equal(t, len(keystores), len(statuses)) + require.Equal( + t, + ethpbservice.ImportedKeystoreStatus_DUPLICATE, + statuses[0].Status, + ) + require.Equal( + t, + ethpbservice.ImportedKeystoreStatus_ERROR, + statuses[1].Status, + ) + require.Equal( + t, + fmt.Sprintf("incorrect password for key 0x%s", keystores[1].Pubkey), + statuses[1].Message, + ) + require.LogsContain(t, hook, "no keys were imported") + }) + t.Run("file write fails during import", func(t *testing.T) { + wallet.HasWriteFileError = true + copyStore := dr.accountsStore.Copy() + keystore1 := createRandomKeystore(t, password) + statuses, err := dr.ImportKeystores( + ctx, + []*keymanager.Keystore{keystore1}, + []string{password}, + ) + require.ErrorContains(t, "could not write keystore file for accounts", err) + require.Equal(t, len(statuses), 0) + // local copy did not update due to bad file write + require.DeepEqual(t, dr.accountsStore, copyStore) }) } diff --git a/validator/keymanager/local/keymanager.go b/validator/keymanager/local/keymanager.go index 5c971c477d3a..5f870003f66b 100644 --- a/validator/keymanager/local/keymanager.go +++ b/validator/keymanager/local/keymanager.go @@ -59,6 +59,14 @@ type accountStore struct { PublicKeys [][]byte `json:"public_keys"` } +// Copy creates a deep copy of accountStore +func (a *accountStore) Copy() *accountStore { + storeCopy := &accountStore{} + storeCopy.PrivateKeys = bytesutil.SafeCopy2dBytes(a.PrivateKeys) + storeCopy.PublicKeys = bytesutil.SafeCopy2dBytes(a.PublicKeys) + return storeCopy +} + // AccountsKeystoreRepresentation defines an internal Prysm representation // of validator accounts, encrypted according to the EIP-2334 standard. type AccountsKeystoreRepresentation struct { @@ -256,17 +264,71 @@ func (km *Keymanager) initializeAccountKeystore(ctx context.Context) error { } // CreateAccountsKeystore creates a new keystore holding the provided keys. -func (km *Keymanager) CreateAccountsKeystore( +func (km *Keymanager) CreateAccountsKeystore(ctx context.Context, privateKeys [][]byte, publicKeys [][]byte) (*AccountsKeystoreRepresentation, error) { + if err := km.CreateOrUpdateInMemoryAccountsStore(ctx, privateKeys, publicKeys); err != nil { + return nil, err + } + return CreateAccountsKeystoreRepresentation(ctx, km.accountsStore, km.wallet.Password()) +} + +// SaveStoreAndReInitialize saves the store to disk and re-initializes the account keystore from file +func (km *Keymanager) SaveStoreAndReInitialize(ctx context.Context, store *accountStore) error { + // Save the copy to disk + accountsKeystore, err := CreateAccountsKeystoreRepresentation(ctx, store, km.wallet.Password()) + if err != nil { + return err + + } + encodedAccounts, err := json.MarshalIndent(accountsKeystore, "", "\t") + if err != nil { + return err + } + if err := km.wallet.WriteFileAtPath(ctx, AccountsPath, AccountsKeystoreFileName, encodedAccounts); err != nil { + return err + } + + // Reinitialize account store and cache + // This will update the in-memory information instead of reading from the file itself for safety concerns + km.accountsStore = store + err = km.initializeKeysCachesFromKeystore() + if err != nil { + return errors.Wrap(err, "failed to initialize keys caches") + } + return err +} + +// CreateAccountsKeystoreRepresentation is a pure function that takes an accountStore and wallet password and returns the encrypted formatted json version for local writing. +func CreateAccountsKeystoreRepresentation( _ context.Context, - privateKeys, publicKeys [][]byte, + store *accountStore, + walletPW string, ) (*AccountsKeystoreRepresentation, error) { encryptor := keystorev4.New() id, err := uuid.NewRandom() if err != nil { return nil, err } + encodedStore, err := json.MarshalIndent(store, "", "\t") + if err != nil { + return nil, err + } + cryptoFields, err := encryptor.Encrypt(encodedStore, walletPW) + if err != nil { + return nil, errors.Wrap(err, "could not encrypt accounts") + } + return &AccountsKeystoreRepresentation{ + Crypto: cryptoFields, + ID: id.String(), + Version: encryptor.Version(), + Name: encryptor.Name(), + }, nil +} + +// CreateOrUpdateInMemoryAccountsStore will set or update the local accounts store and update the local cache. +// This function DOES NOT save the accounts store to disk. +func (km *Keymanager) CreateOrUpdateInMemoryAccountsStore(_ context.Context, privateKeys, publicKeys [][]byte) error { if len(privateKeys) != len(publicKeys) { - return nil, fmt.Errorf( + return fmt.Errorf( "number of private keys and public keys is not equal: %d != %d", len(privateKeys), len(publicKeys), ) } @@ -276,44 +338,35 @@ func (km *Keymanager) CreateAccountsKeystore( PublicKeys: publicKeys, } } else { - existingPubKeys := make(map[string]bool) - existingPrivKeys := make(map[string]bool) - for i := 0; i < len(km.accountsStore.PrivateKeys); i++ { - existingPrivKeys[string(km.accountsStore.PrivateKeys[i])] = true - existingPubKeys[string(km.accountsStore.PublicKeys[i])] = true - } - // We append to the accounts store keys only - // if the private/secret key do not already exist, to prevent duplicates. - for i := 0; i < len(privateKeys); i++ { - sk := privateKeys[i] - pk := publicKeys[i] - _, privKeyExists := existingPrivKeys[string(sk)] - _, pubKeyExists := existingPubKeys[string(pk)] - if privKeyExists || pubKeyExists { - continue - } - km.accountsStore.PublicKeys = append(km.accountsStore.PublicKeys, pk) - km.accountsStore.PrivateKeys = append(km.accountsStore.PrivateKeys, sk) - } + updateAccountsStoreKeys(km.accountsStore, privateKeys, publicKeys) } - err = km.initializeKeysCachesFromKeystore() + err := km.initializeKeysCachesFromKeystore() if err != nil { - return nil, errors.Wrap(err, "failed to initialize keys caches") + return errors.Wrap(err, "failed to initialize keys caches") } - encodedStore, err := json.MarshalIndent(km.accountsStore, "", "\t") - if err != nil { - return nil, err + return nil +} + +func updateAccountsStoreKeys(store *accountStore, privateKeys, publicKeys [][]byte) { + existingPubKeys := make(map[string]bool) + existingPrivKeys := make(map[string]bool) + for i := 0; i < len(store.PrivateKeys); i++ { + existingPrivKeys[string(store.PrivateKeys[i])] = true + existingPubKeys[string(store.PublicKeys[i])] = true } - cryptoFields, err := encryptor.Encrypt(encodedStore, km.wallet.Password()) - if err != nil { - return nil, errors.Wrap(err, "could not encrypt accounts") + // We append to the accounts store keys only + // if the private/secret key do not already exist, to prevent duplicates. + for i := 0; i < len(privateKeys); i++ { + sk := privateKeys[i] + pk := publicKeys[i] + _, privKeyExists := existingPrivKeys[string(sk)] + _, pubKeyExists := existingPubKeys[string(pk)] + if privKeyExists || pubKeyExists { + continue + } + store.PublicKeys = append(store.PublicKeys, pk) + store.PrivateKeys = append(store.PrivateKeys, sk) } - return &AccountsKeystoreRepresentation{ - Crypto: cryptoFields, - ID: id.String(), - Version: encryptor.Version(), - Name: encryptor.Name(), - }, nil } func (km *Keymanager) ListKeymanagerAccounts(ctx context.Context, cfg keymanager.ListKeymanagerAccountConfig) error { @@ -369,3 +422,17 @@ func (km *Keymanager) ListKeymanagerAccounts(ctx context.Context, cfg keymanager fmt.Println("") return nil } + +func CreatePrintoutOfKeys(keys [][]byte) string { + var keysStr string + for i, k := range keys { + if i == 0 { + keysStr += fmt.Sprintf("%#x", bytesutil.Trunc(k)) + } else if i == len(keys)-1 { + keysStr += fmt.Sprintf("%#x", bytesutil.Trunc(k)) + } else { + keysStr += fmt.Sprintf(",%#x", bytesutil.Trunc(k)) + } + } + return keysStr +}