diff --git a/accounts/abi/bind/auth.go b/accounts/abi/bind/auth.go
index aa045dff46..ec313dffbb 100644
--- a/accounts/abi/bind/auth.go
+++ b/accounts/abi/bind/auth.go
@@ -33,10 +33,10 @@ import (
"io"
"math/big"
- "github.com/ava-labs/coreth/accounts/keystore"
"github.com/ava-labs/coreth/core/types"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/external"
+ "github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
diff --git a/accounts/keystore/account_cache.go b/accounts/keystore/account_cache.go
deleted file mode 100644
index 7e45bd78a3..0000000000
--- a/accounts/keystore/account_cache.go
+++ /dev/null
@@ -1,318 +0,0 @@
-// (c) 2019-2020, Ava Labs, Inc.
-//
-// This file is a derived work, based on the go-ethereum library whose original
-// notices appear below.
-//
-// It is distributed under a license compatible with the licensing terms of the
-// original code from which it is derived.
-//
-// Much love to the original authors for their work.
-// **********
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package keystore
-
-import (
- "bufio"
- "encoding/json"
- "fmt"
- "os"
- "path/filepath"
- "sort"
- "strings"
- "sync"
- "time"
-
- mapset "github.com/deckarep/golang-set/v2"
- "github.com/ethereum/go-ethereum/accounts"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/log"
- "golang.org/x/exp/slices"
-)
-
-// Minimum amount of time between cache reloads. This limit applies if the platform does
-// not support change notifications. It also applies if the keystore directory does not
-// exist yet, the code will attempt to create a watcher at most this often.
-const minReloadInterval = 2 * time.Second
-
-// byURL defines the sorting order for accounts.
-func byURL(a, b accounts.Account) int {
- return a.URL.Cmp(b.URL)
-}
-
-// AmbiguousAddrError is returned when attempting to unlock
-// an address for which more than one file exists.
-type AmbiguousAddrError struct {
- Addr common.Address
- Matches []accounts.Account
-}
-
-func (err *AmbiguousAddrError) Error() string {
- files := ""
- for i, a := range err.Matches {
- files += a.URL.Path
- if i < len(err.Matches)-1 {
- files += ", "
- }
- }
- return fmt.Sprintf("multiple keys match address (%s)", files)
-}
-
-// accountCache is a live index of all accounts in the keystore.
-type accountCache struct {
- keydir string
- watcher *watcher
- mu sync.Mutex
- all []accounts.Account
- byAddr map[common.Address][]accounts.Account
- throttle *time.Timer
- notify chan struct{}
- fileC fileCache
-}
-
-func newAccountCache(keydir string) (*accountCache, chan struct{}) {
- ac := &accountCache{
- keydir: keydir,
- byAddr: make(map[common.Address][]accounts.Account),
- notify: make(chan struct{}, 1),
- fileC: fileCache{all: mapset.NewThreadUnsafeSet[string]()},
- }
- ac.watcher = newWatcher(ac)
- return ac, ac.notify
-}
-
-func (ac *accountCache) accounts() []accounts.Account {
- ac.maybeReload()
- ac.mu.Lock()
- defer ac.mu.Unlock()
- cpy := make([]accounts.Account, len(ac.all))
- copy(cpy, ac.all)
- return cpy
-}
-
-func (ac *accountCache) hasAddress(addr common.Address) bool {
- ac.maybeReload()
- ac.mu.Lock()
- defer ac.mu.Unlock()
- return len(ac.byAddr[addr]) > 0
-}
-
-func (ac *accountCache) add(newAccount accounts.Account) {
- ac.mu.Lock()
- defer ac.mu.Unlock()
-
- i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Cmp(newAccount.URL) >= 0 })
- if i < len(ac.all) && ac.all[i] == newAccount {
- return
- }
- // newAccount is not in the cache.
- ac.all = append(ac.all, accounts.Account{})
- copy(ac.all[i+1:], ac.all[i:])
- ac.all[i] = newAccount
- ac.byAddr[newAccount.Address] = append(ac.byAddr[newAccount.Address], newAccount)
-}
-
-// note: removed needs to be unique here (i.e. both File and Address must be set).
-func (ac *accountCache) delete(removed accounts.Account) {
- ac.mu.Lock()
- defer ac.mu.Unlock()
-
- ac.all = removeAccount(ac.all, removed)
- if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 {
- delete(ac.byAddr, removed.Address)
- } else {
- ac.byAddr[removed.Address] = ba
- }
-}
-
-// deleteByFile removes an account referenced by the given path.
-func (ac *accountCache) deleteByFile(path string) {
- ac.mu.Lock()
- defer ac.mu.Unlock()
- i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Path >= path })
-
- if i < len(ac.all) && ac.all[i].URL.Path == path {
- removed := ac.all[i]
- ac.all = append(ac.all[:i], ac.all[i+1:]...)
- if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 {
- delete(ac.byAddr, removed.Address)
- } else {
- ac.byAddr[removed.Address] = ba
- }
- }
-}
-
-// watcherStarted returns true if the watcher loop started running (even if it
-// has since also ended).
-func (ac *accountCache) watcherStarted() bool {
- ac.mu.Lock()
- defer ac.mu.Unlock()
- return ac.watcher.running || ac.watcher.runEnded
-}
-
-func removeAccount(slice []accounts.Account, elem accounts.Account) []accounts.Account {
- for i := range slice {
- if slice[i] == elem {
- return append(slice[:i], slice[i+1:]...)
- }
- }
- return slice
-}
-
-// find returns the cached account for address if there is a unique match.
-// The exact matching rules are explained by the documentation of accounts.Account.
-// Callers must hold ac.mu.
-func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) {
- // Limit search to address candidates if possible.
- matches := ac.all
- if (a.Address != common.Address{}) {
- matches = ac.byAddr[a.Address]
- }
- if a.URL.Path != "" {
- // If only the basename is specified, complete the path.
- if !strings.ContainsRune(a.URL.Path, filepath.Separator) {
- a.URL.Path = filepath.Join(ac.keydir, a.URL.Path)
- }
- for i := range matches {
- if matches[i].URL == a.URL {
- return matches[i], nil
- }
- }
- if (a.Address == common.Address{}) {
- return accounts.Account{}, ErrNoMatch
- }
- }
- switch len(matches) {
- case 1:
- return matches[0], nil
- case 0:
- return accounts.Account{}, ErrNoMatch
- default:
- err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))}
- copy(err.Matches, matches)
- slices.SortFunc(err.Matches, byURL)
- return accounts.Account{}, err
- }
-}
-
-func (ac *accountCache) maybeReload() {
- ac.mu.Lock()
-
- if ac.watcher.running {
- ac.mu.Unlock()
- return // A watcher is running and will keep the cache up-to-date.
- }
- if ac.throttle == nil {
- ac.throttle = time.NewTimer(0)
- } else {
- select {
- case <-ac.throttle.C:
- default:
- ac.mu.Unlock()
- return // The cache was reloaded recently.
- }
- }
- // No watcher running, start it.
- ac.watcher.start()
- ac.throttle.Reset(minReloadInterval)
- ac.mu.Unlock()
- ac.scanAccounts()
-}
-
-func (ac *accountCache) close() {
- ac.mu.Lock()
- ac.watcher.close()
- if ac.throttle != nil {
- ac.throttle.Stop()
- }
- if ac.notify != nil {
- close(ac.notify)
- ac.notify = nil
- }
- ac.mu.Unlock()
-}
-
-// scanAccounts checks if any changes have occurred on the filesystem, and
-// updates the account cache accordingly
-func (ac *accountCache) scanAccounts() error {
- // Scan the entire folder metadata for file changes
- creates, deletes, updates, err := ac.fileC.scan(ac.keydir)
- if err != nil {
- log.Debug("Failed to reload keystore contents", "err", err)
- return err
- }
- if creates.Cardinality() == 0 && deletes.Cardinality() == 0 && updates.Cardinality() == 0 {
- return nil
- }
- // Create a helper method to scan the contents of the key files
- var (
- buf = new(bufio.Reader)
- key struct {
- Address string `json:"address"`
- }
- )
- readAccount := func(path string) *accounts.Account {
- fd, err := os.Open(path)
- if err != nil {
- log.Trace("Failed to open keystore file", "path", path, "err", err)
- return nil
- }
- defer fd.Close()
- buf.Reset(fd)
- // Parse the address.
- key.Address = ""
- err = json.NewDecoder(buf).Decode(&key)
- addr := common.HexToAddress(key.Address)
- switch {
- case err != nil:
- log.Debug("Failed to decode keystore key", "path", path, "err", err)
- case addr == common.Address{}:
- log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address")
- default:
- return &accounts.Account{
- Address: addr,
- URL: accounts.URL{Scheme: KeyStoreScheme, Path: path},
- }
- }
- return nil
- }
- // Process all the file diffs
- start := time.Now()
-
- for _, path := range creates.ToSlice() {
- if a := readAccount(path); a != nil {
- ac.add(*a)
- }
- }
- for _, path := range deletes.ToSlice() {
- ac.deleteByFile(path)
- }
- for _, path := range updates.ToSlice() {
- ac.deleteByFile(path)
- if a := readAccount(path); a != nil {
- ac.add(*a)
- }
- }
- end := time.Now()
-
- select {
- case ac.notify <- struct{}{}:
- default:
- }
- log.Trace("Handled keystore changes", "time", end.Sub(start))
- return nil
-}
diff --git a/accounts/keystore/account_cache_test.go b/accounts/keystore/account_cache_test.go
deleted file mode 100644
index dc3015593c..0000000000
--- a/accounts/keystore/account_cache_test.go
+++ /dev/null
@@ -1,419 +0,0 @@
-// (c) 2019-2020, Ava Labs, Inc.
-//
-// This file is a derived work, based on the go-ethereum library whose original
-// notices appear below.
-//
-// It is distributed under a license compatible with the licensing terms of the
-// original code from which it is derived.
-//
-// Much love to the original authors for their work.
-// **********
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package keystore
-
-import (
- "errors"
- "fmt"
- "math/rand"
- "os"
- "path/filepath"
- "reflect"
- "testing"
- "time"
-
- "github.com/cespare/cp"
- "github.com/davecgh/go-spew/spew"
- "github.com/ethereum/go-ethereum/accounts"
- "github.com/ethereum/go-ethereum/common"
- "golang.org/x/exp/slices"
-)
-
-var (
- cachetestDir, _ = filepath.Abs(filepath.Join("testdata", "keystore"))
- cachetestAccounts = []accounts.Account{
- {
- Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
- URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8")},
- },
- {
- Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
- URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "aaa")},
- },
- {
- Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"),
- URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "zzz")},
- },
- }
-)
-
-// waitWatcherStart waits up to 1s for the keystore watcher to start.
-func waitWatcherStart(ks *KeyStore) bool {
- // On systems where file watch is not supported, just return "ok".
- if !ks.cache.watcher.enabled() {
- return true
- }
- // The watcher should start, and then exit.
- for t0 := time.Now(); time.Since(t0) < 1*time.Second; time.Sleep(100 * time.Millisecond) {
- if ks.cache.watcherStarted() {
- return true
- }
- }
- return false
-}
-
-func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
- var list []accounts.Account
- for t0 := time.Now(); time.Since(t0) < 5*time.Second; time.Sleep(100 * time.Millisecond) {
- list = ks.Accounts()
- if reflect.DeepEqual(list, wantAccounts) {
- // ks should have also received change notifications
- select {
- case <-ks.changes:
- default:
- return errors.New("wasn't notified of new accounts")
- }
- return nil
- }
- }
- return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts)
-}
-
-func TestWatchNewFile(t *testing.T) {
- t.Parallel()
-
- dir, ks := tmpKeyStore(t, false)
-
- // Ensure the watcher is started before adding any files.
- ks.Accounts()
- if !waitWatcherStart(ks) {
- t.Fatal("keystore watcher didn't start in time")
- }
- // Move in the files.
- wantAccounts := make([]accounts.Account, len(cachetestAccounts))
- for i := range cachetestAccounts {
- wantAccounts[i] = accounts.Account{
- Address: cachetestAccounts[i].Address,
- URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, filepath.Base(cachetestAccounts[i].URL.Path))},
- }
- if err := cp.CopyFile(wantAccounts[i].URL.Path, cachetestAccounts[i].URL.Path); err != nil {
- t.Fatal(err)
- }
- }
-
- // ks should see the accounts.
- if err := waitForAccounts(wantAccounts, ks); err != nil {
- t.Error(err)
- }
-}
-
-func TestWatchNoDir(t *testing.T) {
- t.Parallel()
- // Create ks but not the directory that it watches.
- dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watchnodir-test-%d-%d", os.Getpid(), rand.Int()))
- ks := NewKeyStore(dir, LightScryptN, LightScryptP)
- list := ks.Accounts()
- if len(list) > 0 {
- t.Error("initial account list not empty:", list)
- }
- // The watcher should start, and then exit.
- if !waitWatcherStart(ks) {
- t.Fatal("keystore watcher didn't start in time")
- }
- // Create the directory and copy a key file into it.
- os.MkdirAll(dir, 0700)
- defer os.RemoveAll(dir)
- file := filepath.Join(dir, "aaa")
- if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil {
- t.Fatal(err)
- }
-
- // ks should see the account.
- wantAccounts := []accounts.Account{cachetestAccounts[0]}
- wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
- for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 {
- list = ks.Accounts()
- if reflect.DeepEqual(list, wantAccounts) {
- // ks should have also received change notifications
- select {
- case <-ks.changes:
- default:
- t.Fatalf("wasn't notified of new accounts")
- }
- return
- }
- time.Sleep(d)
- }
- t.Errorf("\ngot %v\nwant %v", list, wantAccounts)
-}
-
-func TestCacheInitialReload(t *testing.T) {
- t.Parallel()
- cache, _ := newAccountCache(cachetestDir)
- accounts := cache.accounts()
- if !reflect.DeepEqual(accounts, cachetestAccounts) {
- t.Fatalf("got initial accounts: %swant %s", spew.Sdump(accounts), spew.Sdump(cachetestAccounts))
- }
-}
-
-func TestCacheAddDeleteOrder(t *testing.T) {
- t.Parallel()
- cache, _ := newAccountCache("testdata/no-such-dir")
- cache.watcher.running = true // prevent unexpected reloads
-
- accs := []accounts.Account{
- {
- Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
- URL: accounts.URL{Scheme: KeyStoreScheme, Path: "-309830980"},
- },
- {
- Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"),
- URL: accounts.URL{Scheme: KeyStoreScheme, Path: "ggg"},
- },
- {
- Address: common.HexToAddress("8bda78331c916a08481428e4b07c96d3e916d165"),
- URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzzzzz-the-very-last-one.keyXXX"},
- },
- {
- Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
- URL: accounts.URL{Scheme: KeyStoreScheme, Path: "SOMETHING.key"},
- },
- {
- Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
- URL: accounts.URL{Scheme: KeyStoreScheme, Path: "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8"},
- },
- {
- Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
- URL: accounts.URL{Scheme: KeyStoreScheme, Path: "aaa"},
- },
- {
- Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"),
- URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzz"},
- },
- }
- for _, a := range accs {
- cache.add(a)
- }
- // Add some of them twice to check that they don't get reinserted.
- cache.add(accs[0])
- cache.add(accs[2])
-
- // Check that the account list is sorted by filename.
- wantAccounts := make([]accounts.Account, len(accs))
- copy(wantAccounts, accs)
- slices.SortFunc(wantAccounts, byURL)
- list := cache.accounts()
- if !reflect.DeepEqual(list, wantAccounts) {
- t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts))
- }
- for _, a := range accs {
- if !cache.hasAddress(a.Address) {
- t.Errorf("expected hasAccount(%x) to return true", a.Address)
- }
- }
- if cache.hasAddress(common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) {
- t.Errorf("expected hasAccount(%x) to return false", common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"))
- }
-
- // Delete a few keys from the cache.
- for i := 0; i < len(accs); i += 2 {
- cache.delete(wantAccounts[i])
- }
- cache.delete(accounts.Account{Address: common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"), URL: accounts.URL{Scheme: KeyStoreScheme, Path: "something"}})
-
- // Check content again after deletion.
- wantAccountsAfterDelete := []accounts.Account{
- wantAccounts[1],
- wantAccounts[3],
- wantAccounts[5],
- }
- list = cache.accounts()
- if !reflect.DeepEqual(list, wantAccountsAfterDelete) {
- t.Fatalf("got accounts after delete: %s\nwant %s", spew.Sdump(list), spew.Sdump(wantAccountsAfterDelete))
- }
- for _, a := range wantAccountsAfterDelete {
- if !cache.hasAddress(a.Address) {
- t.Errorf("expected hasAccount(%x) to return true", a.Address)
- }
- }
- if cache.hasAddress(wantAccounts[0].Address) {
- t.Errorf("expected hasAccount(%x) to return false", wantAccounts[0].Address)
- }
-}
-
-func TestCacheFind(t *testing.T) {
- t.Parallel()
- dir := filepath.Join("testdata", "dir")
- cache, _ := newAccountCache(dir)
- cache.watcher.running = true // prevent unexpected reloads
-
- accs := []accounts.Account{
- {
- Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
- URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "a.key")},
- },
- {
- Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"),
- URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "b.key")},
- },
- {
- Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
- URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c.key")},
- },
- {
- Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
- URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c2.key")},
- },
- }
- for _, a := range accs {
- cache.add(a)
- }
-
- nomatchAccount := accounts.Account{
- Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
- URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "something")},
- }
- tests := []struct {
- Query accounts.Account
- WantResult accounts.Account
- WantError error
- }{
- // by address
- {Query: accounts.Account{Address: accs[0].Address}, WantResult: accs[0]},
- // by file
- {Query: accounts.Account{URL: accs[0].URL}, WantResult: accs[0]},
- // by basename
- {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(accs[0].URL.Path)}}, WantResult: accs[0]},
- // by file and address
- {Query: accs[0], WantResult: accs[0]},
- // ambiguous address, tie resolved by file
- {Query: accs[2], WantResult: accs[2]},
- // ambiguous address error
- {
- Query: accounts.Account{Address: accs[2].Address},
- WantError: &AmbiguousAddrError{
- Addr: accs[2].Address,
- Matches: []accounts.Account{accs[2], accs[3]},
- },
- },
- // no match error
- {Query: nomatchAccount, WantError: ErrNoMatch},
- {Query: accounts.Account{URL: nomatchAccount.URL}, WantError: ErrNoMatch},
- {Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(nomatchAccount.URL.Path)}}, WantError: ErrNoMatch},
- {Query: accounts.Account{Address: nomatchAccount.Address}, WantError: ErrNoMatch},
- }
- for i, test := range tests {
- a, err := cache.find(test.Query)
- if !reflect.DeepEqual(err, test.WantError) {
- t.Errorf("test %d: error mismatch for query %v\ngot %q\nwant %q", i, test.Query, err, test.WantError)
- continue
- }
- if a != test.WantResult {
- t.Errorf("test %d: result mismatch for query %v\ngot %v\nwant %v", i, test.Query, a, test.WantResult)
- continue
- }
- }
-}
-
-// TestUpdatedKeyfileContents tests that updating the contents of a keystore file
-// is noticed by the watcher, and the account cache is updated accordingly
-func TestUpdatedKeyfileContents(t *testing.T) {
- t.Parallel()
-
- // Create a temporary keystore to test with
- dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int()))
- ks := NewKeyStore(dir, LightScryptN, LightScryptP)
-
- list := ks.Accounts()
- if len(list) > 0 {
- t.Error("initial account list not empty:", list)
- }
- if !waitWatcherStart(ks) {
- t.Fatal("keystore watcher didn't start in time")
- }
- // Create the directory and copy a key file into it.
- os.MkdirAll(dir, 0700)
- defer os.RemoveAll(dir)
- file := filepath.Join(dir, "aaa")
-
- // Place one of our testfiles in there
- if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil {
- t.Fatal(err)
- }
-
- // ks should see the account.
- wantAccounts := []accounts.Account{cachetestAccounts[0]}
- wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
- if err := waitForAccounts(wantAccounts, ks); err != nil {
- t.Error(err)
- return
- }
- // needed so that modTime of `file` will be greater than its current value after forceCopyFile
- time.Sleep(time.Second)
-
- // Now replace file contents
- if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil {
- t.Fatal(err)
- return
- }
- wantAccounts = []accounts.Account{cachetestAccounts[1]}
- wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
- if err := waitForAccounts(wantAccounts, ks); err != nil {
- t.Errorf("First replacement failed")
- t.Error(err)
- return
- }
-
- // needed so that modTime of `file` will be greater than its current value after forceCopyFile
- time.Sleep(time.Second)
-
- // Now replace file contents again
- if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil {
- t.Fatal(err)
- return
- }
- wantAccounts = []accounts.Account{cachetestAccounts[2]}
- wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
- if err := waitForAccounts(wantAccounts, ks); err != nil {
- t.Errorf("Second replacement failed")
- t.Error(err)
- return
- }
-
- // needed so that modTime of `file` will be greater than its current value after os.WriteFile
- time.Sleep(time.Second)
-
- // Now replace file contents with crap
- if err := os.WriteFile(file, []byte("foo"), 0600); err != nil {
- t.Fatal(err)
- return
- }
- if err := waitForAccounts([]accounts.Account{}, ks); err != nil {
- t.Errorf("Emptying account file failed")
- t.Error(err)
- return
- }
-}
-
-// forceCopyFile is like cp.CopyFile, but doesn't complain if the destination exists.
-func forceCopyFile(dst, src string) error {
- data, err := os.ReadFile(src)
- if err != nil {
- return err
- }
- return os.WriteFile(dst, data, 0644)
-}
diff --git a/accounts/keystore/file_cache.go b/accounts/keystore/file_cache.go
deleted file mode 100644
index ab24b5c59d..0000000000
--- a/accounts/keystore/file_cache.go
+++ /dev/null
@@ -1,115 +0,0 @@
-// (c) 2019-2020, Ava Labs, Inc.
-//
-// This file is a derived work, based on the go-ethereum library whose original
-// notices appear below.
-//
-// It is distributed under a license compatible with the licensing terms of the
-// original code from which it is derived.
-//
-// Much love to the original authors for their work.
-// **********
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package keystore
-
-import (
- "os"
- "path/filepath"
- "strings"
- "sync"
- "time"
-
- mapset "github.com/deckarep/golang-set/v2"
- "github.com/ethereum/go-ethereum/log"
-)
-
-// fileCache is a cache of files seen during scan of keystore.
-type fileCache struct {
- all mapset.Set[string] // Set of all files from the keystore folder
- lastMod time.Time // Last time instance when a file was modified
- mu sync.Mutex
-}
-
-// scan performs a new scan on the given directory, compares against the already
-// cached filenames, and returns file sets: creates, deletes, updates.
-func (fc *fileCache) scan(keyDir string) (mapset.Set[string], mapset.Set[string], mapset.Set[string], error) {
- t0 := time.Now()
-
- // List all the files from the keystore folder
- files, err := os.ReadDir(keyDir)
- if err != nil {
- return nil, nil, nil, err
- }
- t1 := time.Now()
-
- fc.mu.Lock()
- defer fc.mu.Unlock()
-
- // Iterate all the files and gather their metadata
- all := mapset.NewThreadUnsafeSet[string]()
- mods := mapset.NewThreadUnsafeSet[string]()
-
- var newLastMod time.Time
- for _, fi := range files {
- path := filepath.Join(keyDir, fi.Name())
- // Skip any non-key files from the folder
- if nonKeyFile(fi) {
- log.Trace("Ignoring file on account scan", "path", path)
- continue
- }
- // Gather the set of all and freshly modified files
- all.Add(path)
-
- info, err := fi.Info()
- if err != nil {
- return nil, nil, nil, err
- }
- modified := info.ModTime()
- if modified.After(fc.lastMod) {
- mods.Add(path)
- }
- if modified.After(newLastMod) {
- newLastMod = modified
- }
- }
- t2 := time.Now()
-
- // Update the tracked files and return the three sets
- deletes := fc.all.Difference(all) // Deletes = previous - current
- creates := all.Difference(fc.all) // Creates = current - previous
- updates := mods.Difference(creates) // Updates = modified - creates
-
- fc.all, fc.lastMod = all, newLastMod
- t3 := time.Now()
-
- // Report on the scanning stats and return
- log.Debug("FS scan times", "list", t1.Sub(t0), "set", t2.Sub(t1), "diff", t3.Sub(t2))
- return creates, deletes, updates, nil
-}
-
-// nonKeyFile ignores editor backups, hidden files and folders/symlinks.
-func nonKeyFile(fi os.DirEntry) bool {
- // Skip editor backups and UNIX-style hidden files.
- if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") {
- return true
- }
- // Skip misc special files, directories (yes, symlinks too).
- if fi.IsDir() || !fi.Type().IsRegular() {
- return true
- }
- return false
-}
diff --git a/accounts/keystore/key.go b/accounts/keystore/key.go
deleted file mode 100644
index d3241f74fc..0000000000
--- a/accounts/keystore/key.go
+++ /dev/null
@@ -1,251 +0,0 @@
-// (c) 2019-2020, Ava Labs, Inc.
-//
-// This file is a derived work, based on the go-ethereum library whose original
-// notices appear below.
-//
-// It is distributed under a license compatible with the licensing terms of the
-// original code from which it is derived.
-//
-// Much love to the original authors for their work.
-// **********
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package keystore
-
-import (
- "bytes"
- "crypto/ecdsa"
- "encoding/hex"
- "encoding/json"
- "fmt"
- "io"
- "os"
- "path/filepath"
- "strings"
- "time"
-
- "github.com/ethereum/go-ethereum/accounts"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/google/uuid"
-)
-
-const (
- version = 3
-)
-
-type Key struct {
- Id uuid.UUID // Version 4 "random" for unique id not derived from key data
- // to simplify lookups we also store the address
- Address common.Address
- // we only store privkey as pubkey/address can be derived from it
- // privkey in this struct is always in plaintext
- PrivateKey *ecdsa.PrivateKey
-}
-
-type keyStore interface {
- // Loads and decrypts the key from disk.
- GetKey(addr common.Address, filename string, auth string) (*Key, error)
- // Writes and encrypts the key.
- StoreKey(filename string, k *Key, auth string) error
- // Joins filename with the key directory unless it is already absolute.
- JoinPath(filename string) string
-}
-
-type plainKeyJSON struct {
- Address string `json:"address"`
- PrivateKey string `json:"privatekey"`
- Id string `json:"id"`
- Version int `json:"version"`
-}
-
-type encryptedKeyJSONV3 struct {
- Address string `json:"address"`
- Crypto CryptoJSON `json:"crypto"`
- Id string `json:"id"`
- Version int `json:"version"`
-}
-
-type encryptedKeyJSONV1 struct {
- Address string `json:"address"`
- Crypto CryptoJSON `json:"crypto"`
- Id string `json:"id"`
- Version string `json:"version"`
-}
-
-type CryptoJSON struct {
- Cipher string `json:"cipher"`
- CipherText string `json:"ciphertext"`
- CipherParams cipherparamsJSON `json:"cipherparams"`
- KDF string `json:"kdf"`
- KDFParams map[string]interface{} `json:"kdfparams"`
- MAC string `json:"mac"`
-}
-
-type cipherparamsJSON struct {
- IV string `json:"iv"`
-}
-
-func (k *Key) MarshalJSON() (j []byte, err error) {
- jStruct := plainKeyJSON{
- hex.EncodeToString(k.Address[:]),
- hex.EncodeToString(crypto.FromECDSA(k.PrivateKey)),
- k.Id.String(),
- version,
- }
- j, err = json.Marshal(jStruct)
- return j, err
-}
-
-func (k *Key) UnmarshalJSON(j []byte) (err error) {
- keyJSON := new(plainKeyJSON)
- err = json.Unmarshal(j, &keyJSON)
- if err != nil {
- return err
- }
-
- u := new(uuid.UUID)
- *u, err = uuid.Parse(keyJSON.Id)
- if err != nil {
- return err
- }
- k.Id = *u
- addr, err := hex.DecodeString(keyJSON.Address)
- if err != nil {
- return err
- }
- privkey, err := crypto.HexToECDSA(keyJSON.PrivateKey)
- if err != nil {
- return err
- }
-
- k.Address = common.BytesToAddress(addr)
- k.PrivateKey = privkey
-
- return nil
-}
-
-func NewKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key {
- return newKeyFromECDSA(privateKeyECDSA)
-}
-
-func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key {
- id, err := uuid.NewRandom()
- if err != nil {
- panic(fmt.Sprintf("Could not create random uuid: %v", err))
- }
- key := &Key{
- Id: id,
- Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey),
- PrivateKey: privateKeyECDSA,
- }
- return key
-}
-
-// NewKeyForDirectICAP generates a key whose address fits into < 155 bits so it can fit
-// into the Direct ICAP spec. for simplicity and easier compatibility with other libs, we
-// retry until the first byte is 0.
-func NewKeyForDirectICAP(rand io.Reader) *Key {
- randBytes := make([]byte, 64)
- _, err := rand.Read(randBytes)
- if err != nil {
- panic("key generation: could not read from random source: " + err.Error())
- }
- reader := bytes.NewReader(randBytes)
- privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), reader)
- if err != nil {
- panic("key generation: ecdsa.GenerateKey failed: " + err.Error())
- }
- key := newKeyFromECDSA(privateKeyECDSA)
- if !strings.HasPrefix(key.Address.Hex(), "0x00") {
- return NewKeyForDirectICAP(rand)
- }
- return key
-}
-
-func NewKey(rand io.Reader) (*Key, error) {
- privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand)
- if err != nil {
- return nil, err
- }
- return newKeyFromECDSA(privateKeyECDSA), nil
-}
-
-func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Account, error) {
- key, err := NewKey(rand)
- if err != nil {
- return nil, accounts.Account{}, err
- }
- a := accounts.Account{
- Address: key.Address,
- URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.JoinPath(keyFileName(key.Address))},
- }
- if err := ks.StoreKey(a.URL.Path, key, auth); err != nil {
- zeroKey(key.PrivateKey)
- return nil, a, err
- }
- return key, a, err
-}
-
-func writeTemporaryKeyFile(file string, content []byte) (string, error) {
- // Create the keystore directory with appropriate permissions
- // in case it is not present yet.
- const dirPerm = 0700
- if err := os.MkdirAll(filepath.Dir(file), dirPerm); err != nil {
- return "", err
- }
- // Atomic write: create a temporary hidden file first
- // then move it into place. TempFile assigns mode 0600.
- f, err := os.CreateTemp(filepath.Dir(file), "."+filepath.Base(file)+".tmp")
- if err != nil {
- return "", err
- }
- if _, err := f.Write(content); err != nil {
- f.Close()
- os.Remove(f.Name())
- return "", err
- }
- f.Close()
- return f.Name(), nil
-}
-
-func writeKeyFile(file string, content []byte) error {
- name, err := writeTemporaryKeyFile(file, content)
- if err != nil {
- return err
- }
- return os.Rename(name, file)
-}
-
-// keyFileName implements the naming convention for keyfiles:
-// UTC---
-func keyFileName(keyAddr common.Address) string {
- ts := time.Now().UTC()
- return fmt.Sprintf("UTC--%s--%s", toISO8601(ts), hex.EncodeToString(keyAddr[:]))
-}
-
-func toISO8601(t time.Time) string {
- var tz string
- name, offset := t.Zone()
- if name == "UTC" {
- tz = "Z"
- } else {
- tz = fmt.Sprintf("%03d00", offset/3600)
- }
- return fmt.Sprintf("%04d-%02d-%02dT%02d-%02d-%02d.%09d%s",
- t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), tz)
-}
diff --git a/accounts/keystore/keystore.go b/accounts/keystore/keystore.go
deleted file mode 100644
index 9b7fd48f7c..0000000000
--- a/accounts/keystore/keystore.go
+++ /dev/null
@@ -1,525 +0,0 @@
-// (c) 2019-2020, Ava Labs, Inc.
-//
-// This file is a derived work, based on the go-ethereum library whose original
-// notices appear below.
-//
-// It is distributed under a license compatible with the licensing terms of the
-// original code from which it is derived.
-//
-// Much love to the original authors for their work.
-// **********
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-// Package keystore implements encrypted storage of secp256k1 private keys.
-//
-// Keys are stored as encrypted JSON files according to the Web3 Secret Storage specification.
-// See https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition for more information.
-package keystore
-
-import (
- "crypto/ecdsa"
- crand "crypto/rand"
- "errors"
- "math/big"
- "os"
- "path/filepath"
- "reflect"
- "runtime"
- "sync"
- "time"
-
- "github.com/ava-labs/coreth/core/types"
- "github.com/ethereum/go-ethereum/accounts"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/event"
-)
-
-var (
- ErrLocked = accounts.NewAuthNeededError("password or unlock")
- ErrNoMatch = errors.New("no key for given address or file")
- ErrDecrypt = errors.New("could not decrypt key with given password")
-
- // ErrAccountAlreadyExists is returned if an account attempted to import is
- // already present in the keystore.
- ErrAccountAlreadyExists = errors.New("account already exists")
-)
-
-// KeyStoreType is the reflect type of a keystore backend.
-var KeyStoreType = reflect.TypeOf(&KeyStore{})
-
-// KeyStoreScheme is the protocol scheme prefixing account and wallet URLs.
-const KeyStoreScheme = "keystore"
-
-// Maximum time between wallet refreshes (if filesystem notifications don't work).
-const walletRefreshCycle = 3 * time.Second
-
-// KeyStore manages a key storage directory on disk.
-type KeyStore struct {
- storage keyStore // Storage backend, might be cleartext or encrypted
- cache *accountCache // In-memory account cache over the filesystem storage
- changes chan struct{} // Channel receiving change notifications from the cache
- unlocked map[common.Address]*unlocked // Currently unlocked account (decrypted private keys)
-
- wallets []accounts.Wallet // Wallet wrappers around the individual key files
- updateFeed event.Feed // Event feed to notify wallet additions/removals
- updateScope event.SubscriptionScope // Subscription scope tracking current live listeners
- updating bool // Whether the event notification loop is running
-
- mu sync.RWMutex
- importMu sync.Mutex // Import Mutex locks the import to prevent two insertions from racing
-}
-
-type unlocked struct {
- *Key
- abort chan struct{}
-}
-
-// NewKeyStore creates a keystore for the given directory.
-func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore {
- keydir, _ = filepath.Abs(keydir)
- ks := &KeyStore{storage: &keyStorePassphrase{keydir, scryptN, scryptP, false}}
- ks.init(keydir)
- return ks
-}
-
-// NewPlaintextKeyStore creates a keystore for the given directory.
-// Deprecated: Use NewKeyStore.
-func NewPlaintextKeyStore(keydir string) *KeyStore {
- keydir, _ = filepath.Abs(keydir)
- ks := &KeyStore{storage: &keyStorePlain{keydir}}
- ks.init(keydir)
- return ks
-}
-
-func (ks *KeyStore) init(keydir string) {
- // Lock the mutex since the account cache might call back with events
- ks.mu.Lock()
- defer ks.mu.Unlock()
-
- // Initialize the set of unlocked keys and the account cache
- ks.unlocked = make(map[common.Address]*unlocked)
- ks.cache, ks.changes = newAccountCache(keydir)
-
- // TODO: In order for this finalizer to work, there must be no references
- // to ks. addressCache doesn't keep a reference but unlocked keys do,
- // so the finalizer will not trigger until all timed unlocks have expired.
- runtime.SetFinalizer(ks, func(m *KeyStore) {
- m.cache.close()
- })
- // Create the initial list of wallets from the cache
- accs := ks.cache.accounts()
- ks.wallets = make([]accounts.Wallet, len(accs))
- for i := 0; i < len(accs); i++ {
- ks.wallets[i] = &keystoreWallet{account: accs[i], keystore: ks}
- }
-}
-
-// Wallets implements accounts.Backend, returning all single-key wallets from the
-// keystore directory.
-func (ks *KeyStore) Wallets() []accounts.Wallet {
- // Make sure the list of wallets is in sync with the account cache
- ks.refreshWallets()
-
- ks.mu.RLock()
- defer ks.mu.RUnlock()
-
- cpy := make([]accounts.Wallet, len(ks.wallets))
- copy(cpy, ks.wallets)
- return cpy
-}
-
-// refreshWallets retrieves the current account list and based on that does any
-// necessary wallet refreshes.
-func (ks *KeyStore) refreshWallets() {
- // Retrieve the current list of accounts
- ks.mu.Lock()
- accs := ks.cache.accounts()
-
- // Transform the current list of wallets into the new one
- var (
- wallets = make([]accounts.Wallet, 0, len(accs))
- events []accounts.WalletEvent
- )
-
- for _, account := range accs {
- // Drop wallets while they were in front of the next account
- for len(ks.wallets) > 0 && ks.wallets[0].URL().Cmp(account.URL) < 0 {
- events = append(events, accounts.WalletEvent{Wallet: ks.wallets[0], Kind: accounts.WalletDropped})
- ks.wallets = ks.wallets[1:]
- }
- // If there are no more wallets or the account is before the next, wrap new wallet
- if len(ks.wallets) == 0 || ks.wallets[0].URL().Cmp(account.URL) > 0 {
- wallet := &keystoreWallet{account: account, keystore: ks}
-
- events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived})
- wallets = append(wallets, wallet)
- continue
- }
- // If the account is the same as the first wallet, keep it
- if ks.wallets[0].Accounts()[0] == account {
- wallets = append(wallets, ks.wallets[0])
- ks.wallets = ks.wallets[1:]
- continue
- }
- }
- // Drop any leftover wallets and set the new batch
- for _, wallet := range ks.wallets {
- events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped})
- }
- ks.wallets = wallets
- ks.mu.Unlock()
-
- // Fire all wallet events and return
- for _, event := range events {
- ks.updateFeed.Send(event)
- }
-}
-
-// Subscribe implements accounts.Backend, creating an async subscription to
-// receive notifications on the addition or removal of keystore wallets.
-func (ks *KeyStore) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription {
- // We need the mutex to reliably start/stop the update loop
- ks.mu.Lock()
- defer ks.mu.Unlock()
-
- // Subscribe the caller and track the subscriber count
- sub := ks.updateScope.Track(ks.updateFeed.Subscribe(sink))
-
- // Subscribers require an active notification loop, start it
- if !ks.updating {
- ks.updating = true
- go ks.updater()
- }
- return sub
-}
-
-// updater is responsible for maintaining an up-to-date list of wallets stored in
-// the keystore, and for firing wallet addition/removal events. It listens for
-// account change events from the underlying account cache, and also periodically
-// forces a manual refresh (only triggers for systems where the filesystem notifier
-// is not running).
-func (ks *KeyStore) updater() {
- for {
- // Wait for an account update or a refresh timeout
- select {
- case <-ks.changes:
- case <-time.After(walletRefreshCycle):
- }
- // Run the wallet refresher
- ks.refreshWallets()
-
- // If all our subscribers left, stop the updater
- ks.mu.Lock()
- if ks.updateScope.Count() == 0 {
- ks.updating = false
- ks.mu.Unlock()
- return
- }
- ks.mu.Unlock()
- }
-}
-
-// HasAddress reports whether a key with the given address is present.
-func (ks *KeyStore) HasAddress(addr common.Address) bool {
- return ks.cache.hasAddress(addr)
-}
-
-// Accounts returns all key files present in the directory.
-func (ks *KeyStore) Accounts() []accounts.Account {
- return ks.cache.accounts()
-}
-
-// Delete deletes the key matched by account if the passphrase is correct.
-// If the account contains no filename, the address must match a unique key.
-func (ks *KeyStore) Delete(a accounts.Account, passphrase string) error {
- // Decrypting the key isn't really necessary, but we do
- // it anyway to check the password and zero out the key
- // immediately afterwards.
- a, key, err := ks.getDecryptedKey(a, passphrase)
- if key != nil {
- zeroKey(key.PrivateKey)
- }
- if err != nil {
- return err
- }
- // The order is crucial here. The key is dropped from the
- // cache after the file is gone so that a reload happening in
- // between won't insert it into the cache again.
- err = os.Remove(a.URL.Path)
- if err == nil {
- ks.cache.delete(a)
- ks.refreshWallets()
- }
- return err
-}
-
-// SignHash calculates a ECDSA signature for the given hash. The produced
-// signature is in the [R || S || V] format where V is 0 or 1.
-func (ks *KeyStore) SignHash(a accounts.Account, hash []byte) ([]byte, error) {
- // Look up the key to sign with and abort if it cannot be found
- ks.mu.RLock()
- defer ks.mu.RUnlock()
-
- unlockedKey, found := ks.unlocked[a.Address]
- if !found {
- return nil, ErrLocked
- }
- // Sign the hash using plain ECDSA operations
- return crypto.Sign(hash, unlockedKey.PrivateKey)
-}
-
-// SignTx signs the given transaction with the requested account.
-func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
- // Look up the key to sign with and abort if it cannot be found
- ks.mu.RLock()
- defer ks.mu.RUnlock()
-
- unlockedKey, found := ks.unlocked[a.Address]
- if !found {
- return nil, ErrLocked
- }
- // Depending on the presence of the chain ID, sign with 2718 or homestead
- signer := types.LatestSignerForChainID(chainID)
- return types.SignTx(tx, signer, unlockedKey.PrivateKey)
-}
-
-// SignHashWithPassphrase signs hash if the private key matching the given address
-// can be decrypted with the given passphrase. The produced signature is in the
-// [R || S || V] format where V is 0 or 1.
-func (ks *KeyStore) SignHashWithPassphrase(a accounts.Account, passphrase string, hash []byte) (signature []byte, err error) {
- _, key, err := ks.getDecryptedKey(a, passphrase)
- if err != nil {
- return nil, err
- }
- defer zeroKey(key.PrivateKey)
- return crypto.Sign(hash, key.PrivateKey)
-}
-
-// SignTxWithPassphrase signs the transaction if the private key matching the
-// given address can be decrypted with the given passphrase.
-func (ks *KeyStore) SignTxWithPassphrase(a accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
- _, key, err := ks.getDecryptedKey(a, passphrase)
- if err != nil {
- return nil, err
- }
- defer zeroKey(key.PrivateKey)
- // Depending on the presence of the chain ID, sign with or without replay protection.
- signer := types.LatestSignerForChainID(chainID)
- return types.SignTx(tx, signer, key.PrivateKey)
-}
-
-// Unlock unlocks the given account indefinitely.
-func (ks *KeyStore) Unlock(a accounts.Account, passphrase string) error {
- return ks.TimedUnlock(a, passphrase, 0)
-}
-
-// Lock removes the private key with the given address from memory.
-func (ks *KeyStore) Lock(addr common.Address) error {
- ks.mu.Lock()
- if unl, found := ks.unlocked[addr]; found {
- ks.mu.Unlock()
- ks.expire(addr, unl, time.Duration(0)*time.Nanosecond)
- } else {
- ks.mu.Unlock()
- }
- return nil
-}
-
-// TimedUnlock unlocks the given account with the passphrase. The account
-// stays unlocked for the duration of timeout. A timeout of 0 unlocks the account
-// until the program exits. The account must match a unique key file.
-//
-// If the account address is already unlocked for a duration, TimedUnlock extends or
-// shortens the active unlock timeout. If the address was previously unlocked
-// indefinitely the timeout is not altered.
-func (ks *KeyStore) TimedUnlock(a accounts.Account, passphrase string, timeout time.Duration) error {
- a, key, err := ks.getDecryptedKey(a, passphrase)
- if err != nil {
- return err
- }
-
- ks.mu.Lock()
- defer ks.mu.Unlock()
- u, found := ks.unlocked[a.Address]
- if found {
- if u.abort == nil {
- // The address was unlocked indefinitely, so unlocking
- // it with a timeout would be confusing.
- zeroKey(key.PrivateKey)
- return nil
- }
- // Terminate the expire goroutine and replace it below.
- close(u.abort)
- }
- if timeout > 0 {
- u = &unlocked{Key: key, abort: make(chan struct{})}
- go ks.expire(a.Address, u, timeout)
- } else {
- u = &unlocked{Key: key}
- }
- ks.unlocked[a.Address] = u
- return nil
-}
-
-// Find resolves the given account into a unique entry in the keystore.
-func (ks *KeyStore) Find(a accounts.Account) (accounts.Account, error) {
- ks.cache.maybeReload()
- ks.cache.mu.Lock()
- a, err := ks.cache.find(a)
- ks.cache.mu.Unlock()
- return a, err
-}
-
-func (ks *KeyStore) getDecryptedKey(a accounts.Account, auth string) (accounts.Account, *Key, error) {
- a, err := ks.Find(a)
- if err != nil {
- return a, nil, err
- }
- key, err := ks.storage.GetKey(a.Address, a.URL.Path, auth)
- return a, key, err
-}
-
-func (ks *KeyStore) expire(addr common.Address, u *unlocked, timeout time.Duration) {
- t := time.NewTimer(timeout)
- defer t.Stop()
- select {
- case <-u.abort:
- // just quit
- case <-t.C:
- ks.mu.Lock()
- // only drop if it's still the same key instance that dropLater
- // was launched with. we can check that using pointer equality
- // because the map stores a new pointer every time the key is
- // unlocked.
- if ks.unlocked[addr] == u {
- zeroKey(u.PrivateKey)
- delete(ks.unlocked, addr)
- }
- ks.mu.Unlock()
- }
-}
-
-// NewAccount generates a new key and stores it into the key directory,
-// encrypting it with the passphrase.
-func (ks *KeyStore) NewAccount(passphrase string) (accounts.Account, error) {
- _, account, err := storeNewKey(ks.storage, crand.Reader, passphrase)
- if err != nil {
- return accounts.Account{}, err
- }
- // Add the account to the cache immediately rather
- // than waiting for file system notifications to pick it up.
- ks.cache.add(account)
- ks.refreshWallets()
- return account, nil
-}
-
-// Export exports as a JSON key, encrypted with newPassphrase.
-func (ks *KeyStore) Export(a accounts.Account, passphrase, newPassphrase string) (keyJSON []byte, err error) {
- _, key, err := ks.getDecryptedKey(a, passphrase)
- if err != nil {
- return nil, err
- }
- var N, P int
- if store, ok := ks.storage.(*keyStorePassphrase); ok {
- N, P = store.scryptN, store.scryptP
- } else {
- N, P = StandardScryptN, StandardScryptP
- }
- return EncryptKey(key, newPassphrase, N, P)
-}
-
-// Import stores the given encrypted JSON key into the key directory.
-func (ks *KeyStore) Import(keyJSON []byte, passphrase, newPassphrase string) (accounts.Account, error) {
- key, err := DecryptKey(keyJSON, passphrase)
- if key != nil && key.PrivateKey != nil {
- defer zeroKey(key.PrivateKey)
- }
- if err != nil {
- return accounts.Account{}, err
- }
- ks.importMu.Lock()
- defer ks.importMu.Unlock()
-
- if ks.cache.hasAddress(key.Address) {
- return accounts.Account{
- Address: key.Address,
- }, ErrAccountAlreadyExists
- }
- return ks.importKey(key, newPassphrase)
-}
-
-// ImportECDSA stores the given key into the key directory, encrypting it with the passphrase.
-func (ks *KeyStore) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (accounts.Account, error) {
- ks.importMu.Lock()
- defer ks.importMu.Unlock()
-
- key := newKeyFromECDSA(priv)
- if ks.cache.hasAddress(key.Address) {
- return accounts.Account{
- Address: key.Address,
- }, ErrAccountAlreadyExists
- }
- return ks.importKey(key, passphrase)
-}
-
-func (ks *KeyStore) importKey(key *Key, passphrase string) (accounts.Account, error) {
- a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.storage.JoinPath(keyFileName(key.Address))}}
- if err := ks.storage.StoreKey(a.URL.Path, key, passphrase); err != nil {
- return accounts.Account{}, err
- }
- ks.cache.add(a)
- ks.refreshWallets()
- return a, nil
-}
-
-// Update changes the passphrase of an existing account.
-func (ks *KeyStore) Update(a accounts.Account, passphrase, newPassphrase string) error {
- a, key, err := ks.getDecryptedKey(a, passphrase)
- if err != nil {
- return err
- }
- return ks.storage.StoreKey(a.URL.Path, key, newPassphrase)
-}
-
-// ImportPreSaleKey decrypts the given Ethereum presale wallet and stores
-// a key file in the key directory. The key file is encrypted with the same passphrase.
-func (ks *KeyStore) ImportPreSaleKey(keyJSON []byte, passphrase string) (accounts.Account, error) {
- a, _, err := importPreSaleKey(ks.storage, keyJSON, passphrase)
- if err != nil {
- return a, err
- }
- ks.cache.add(a)
- ks.refreshWallets()
- return a, nil
-}
-
-// isUpdating returns whether the event notification loop is running.
-// This method is mainly meant for tests.
-func (ks *KeyStore) isUpdating() bool {
- ks.mu.RLock()
- defer ks.mu.RUnlock()
- return ks.updating
-}
-
-// zeroKey zeroes a private key in memory.
-func zeroKey(k *ecdsa.PrivateKey) {
- b := k.D.Bits()
- for i := range b {
- b[i] = 0
- }
-}
diff --git a/accounts/keystore/keystore_test.go b/accounts/keystore/keystore_test.go
deleted file mode 100644
index f6c36a6d42..0000000000
--- a/accounts/keystore/keystore_test.go
+++ /dev/null
@@ -1,477 +0,0 @@
-// (c) 2019-2020, Ava Labs, Inc.
-//
-// This file is a derived work, based on the go-ethereum library whose original
-// notices appear below.
-//
-// It is distributed under a license compatible with the licensing terms of the
-// original code from which it is derived.
-//
-// Much love to the original authors for their work.
-// **********
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package keystore
-
-import (
- "math/rand"
- "os"
- "runtime"
- "strings"
- "sync"
- "sync/atomic"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/accounts"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/event"
- "golang.org/x/exp/slices"
-)
-
-var testSigData = make([]byte, 32)
-
-func TestKeyStore(t *testing.T) {
- t.Parallel()
- dir, ks := tmpKeyStore(t, true)
-
- a, err := ks.NewAccount("foo")
- if err != nil {
- t.Fatal(err)
- }
- if !strings.HasPrefix(a.URL.Path, dir) {
- t.Errorf("account file %s doesn't have dir prefix", a.URL)
- }
- stat, err := os.Stat(a.URL.Path)
- if err != nil {
- t.Fatalf("account file %s doesn't exist (%v)", a.URL, err)
- }
- if runtime.GOOS != "windows" && stat.Mode() != 0600 {
- t.Fatalf("account file has wrong mode: got %o, want %o", stat.Mode(), 0600)
- }
- if !ks.HasAddress(a.Address) {
- t.Errorf("HasAccount(%x) should've returned true", a.Address)
- }
- if err := ks.Update(a, "foo", "bar"); err != nil {
- t.Errorf("Update error: %v", err)
- }
- if err := ks.Delete(a, "bar"); err != nil {
- t.Errorf("Delete error: %v", err)
- }
- if common.FileExist(a.URL.Path) {
- t.Errorf("account file %s should be gone after Delete", a.URL)
- }
- if ks.HasAddress(a.Address) {
- t.Errorf("HasAccount(%x) should've returned true after Delete", a.Address)
- }
-}
-
-func TestSign(t *testing.T) {
- t.Parallel()
- _, ks := tmpKeyStore(t, true)
-
- pass := "" // not used but required by API
- a1, err := ks.NewAccount(pass)
- if err != nil {
- t.Fatal(err)
- }
- if err := ks.Unlock(a1, ""); err != nil {
- t.Fatal(err)
- }
- if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err != nil {
- t.Fatal(err)
- }
-}
-
-func TestSignWithPassphrase(t *testing.T) {
- t.Parallel()
- _, ks := tmpKeyStore(t, true)
-
- pass := "passwd"
- acc, err := ks.NewAccount(pass)
- if err != nil {
- t.Fatal(err)
- }
-
- if _, unlocked := ks.unlocked[acc.Address]; unlocked {
- t.Fatal("expected account to be locked")
- }
-
- _, err = ks.SignHashWithPassphrase(acc, pass, testSigData)
- if err != nil {
- t.Fatal(err)
- }
-
- if _, unlocked := ks.unlocked[acc.Address]; unlocked {
- t.Fatal("expected account to be locked")
- }
-
- if _, err = ks.SignHashWithPassphrase(acc, "invalid passwd", testSigData); err == nil {
- t.Fatal("expected SignHashWithPassphrase to fail with invalid password")
- }
-}
-
-func TestTimedUnlock(t *testing.T) {
- t.Parallel()
- _, ks := tmpKeyStore(t, true)
-
- pass := "foo"
- a1, err := ks.NewAccount(pass)
- if err != nil {
- t.Fatal(err)
- }
-
- // Signing without passphrase fails because account is locked
- _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
- if err != ErrLocked {
- t.Fatal("Signing should've failed with ErrLocked before unlocking, got ", err)
- }
-
- // Signing with passphrase works
- if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil {
- t.Fatal(err)
- }
-
- // Signing without passphrase works because account is temp unlocked
- _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
- if err != nil {
- t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
- }
-
- // Signing fails again after automatic locking
- time.Sleep(250 * time.Millisecond)
- _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
- if err != ErrLocked {
- t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err)
- }
-}
-
-func TestOverrideUnlock(t *testing.T) {
- t.Parallel()
- _, ks := tmpKeyStore(t, false)
-
- pass := "foo"
- a1, err := ks.NewAccount(pass)
- if err != nil {
- t.Fatal(err)
- }
-
- // Unlock indefinitely.
- if err = ks.TimedUnlock(a1, pass, 5*time.Minute); err != nil {
- t.Fatal(err)
- }
-
- // Signing without passphrase works because account is temp unlocked
- _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
- if err != nil {
- t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
- }
-
- // reset unlock to a shorter period, invalidates the previous unlock
- if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil {
- t.Fatal(err)
- }
-
- // Signing without passphrase still works because account is temp unlocked
- _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
- if err != nil {
- t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
- }
-
- // Signing fails again after automatic locking
- time.Sleep(250 * time.Millisecond)
- _, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
- if err != ErrLocked {
- t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err)
- }
-}
-
-// This test should fail under -race if signing races the expiration goroutine.
-func TestSignRace(t *testing.T) {
- t.Parallel()
- _, ks := tmpKeyStore(t, false)
-
- // Create a test account.
- a1, err := ks.NewAccount("")
- if err != nil {
- t.Fatal("could not create the test account", err)
- }
-
- if err := ks.TimedUnlock(a1, "", 15*time.Millisecond); err != nil {
- t.Fatal("could not unlock the test account", err)
- }
- end := time.Now().Add(500 * time.Millisecond)
- for time.Now().Before(end) {
- if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err == ErrLocked {
- return
- } else if err != nil {
- t.Errorf("Sign error: %v", err)
- return
- }
- time.Sleep(1 * time.Millisecond)
- }
- t.Errorf("Account did not lock within the timeout")
-}
-
-// waitForKsUpdating waits until the updating-status of the ks reaches the
-// desired wantStatus.
-// It waits for a maximum time of maxTime, and returns false if it does not
-// finish in time
-func waitForKsUpdating(t *testing.T, ks *KeyStore, wantStatus bool, maxTime time.Duration) bool {
- t.Helper()
- // Wait max 250 ms, then return false
- for t0 := time.Now(); time.Since(t0) < maxTime; {
- if ks.isUpdating() == wantStatus {
- return true
- }
- time.Sleep(25 * time.Millisecond)
- }
- return false
-}
-
-// Tests that the wallet notifier loop starts and stops correctly based on the
-// addition and removal of wallet event subscriptions.
-func TestWalletNotifierLifecycle(t *testing.T) {
- t.Parallel()
- // Create a temporary keystore to test with
- _, ks := tmpKeyStore(t, false)
-
- // Ensure that the notification updater is not running yet
- time.Sleep(250 * time.Millisecond)
-
- if ks.isUpdating() {
- t.Errorf("wallet notifier running without subscribers")
- }
- // Subscribe to the wallet feed and ensure the updater boots up
- updates := make(chan accounts.WalletEvent)
-
- subs := make([]event.Subscription, 2)
- for i := 0; i < len(subs); i++ {
- // Create a new subscription
- subs[i] = ks.Subscribe(updates)
- if !waitForKsUpdating(t, ks, true, 250*time.Millisecond) {
- t.Errorf("sub %d: wallet notifier not running after subscription", i)
- }
- }
- // Close all but one sub
- for i := 0; i < len(subs)-1; i++ {
- // Close an existing subscription
- subs[i].Unsubscribe()
- }
- // Check that it is still running
- time.Sleep(250 * time.Millisecond)
-
- if !ks.isUpdating() {
- t.Fatal("event notifier stopped prematurely")
- }
- // Unsubscribe the last one and ensure the updater terminates eventually.
- subs[len(subs)-1].Unsubscribe()
- if !waitForKsUpdating(t, ks, false, 4*time.Second) {
- t.Errorf("wallet notifier didn't terminate after unsubscribe")
- }
-}
-
-type walletEvent struct {
- accounts.WalletEvent
- a accounts.Account
-}
-
-// Tests that wallet notifications and correctly fired when accounts are added
-// or deleted from the keystore.
-func TestWalletNotifications(t *testing.T) {
- t.Parallel()
- _, ks := tmpKeyStore(t, false)
-
- // Subscribe to the wallet feed and collect events.
- var (
- events []walletEvent
- updates = make(chan accounts.WalletEvent)
- sub = ks.Subscribe(updates)
- )
- defer sub.Unsubscribe()
- go func() {
- for {
- select {
- case ev := <-updates:
- events = append(events, walletEvent{ev, ev.Wallet.Accounts()[0]})
- case <-sub.Err():
- close(updates)
- return
- }
- }
- }()
-
- // Randomly add and remove accounts.
- var (
- live = make(map[common.Address]accounts.Account)
- wantEvents []walletEvent
- )
- for i := 0; i < 1024; i++ {
- if create := len(live) == 0 || rand.Int()%4 > 0; create {
- // Add a new account and ensure wallet notifications arrives
- account, err := ks.NewAccount("")
- if err != nil {
- t.Fatalf("failed to create test account: %v", err)
- }
- live[account.Address] = account
- wantEvents = append(wantEvents, walletEvent{accounts.WalletEvent{Kind: accounts.WalletArrived}, account})
- } else {
- // Delete a random account.
- var account accounts.Account
- for _, a := range live {
- account = a
- break
- }
- if err := ks.Delete(account, ""); err != nil {
- t.Fatalf("failed to delete test account: %v", err)
- }
- delete(live, account.Address)
- wantEvents = append(wantEvents, walletEvent{accounts.WalletEvent{Kind: accounts.WalletDropped}, account})
- }
- }
-
- // Shut down the event collector and check events.
- sub.Unsubscribe()
- for ev := range updates {
- events = append(events, walletEvent{ev, ev.Wallet.Accounts()[0]})
- }
- checkAccounts(t, live, ks.Wallets())
- checkEvents(t, wantEvents, events)
-}
-
-// TestImportExport tests the import functionality of a keystore.
-func TestImportECDSA(t *testing.T) {
- t.Parallel()
- _, ks := tmpKeyStore(t, true)
- key, err := crypto.GenerateKey()
- if err != nil {
- t.Fatalf("failed to generate key: %v", key)
- }
- if _, err = ks.ImportECDSA(key, "old"); err != nil {
- t.Errorf("importing failed: %v", err)
- }
- if _, err = ks.ImportECDSA(key, "old"); err == nil {
- t.Errorf("importing same key twice succeeded")
- }
- if _, err = ks.ImportECDSA(key, "new"); err == nil {
- t.Errorf("importing same key twice succeeded")
- }
-}
-
-// TestImportECDSA tests the import and export functionality of a keystore.
-func TestImportExport(t *testing.T) {
- t.Parallel()
- _, ks := tmpKeyStore(t, true)
- acc, err := ks.NewAccount("old")
- if err != nil {
- t.Fatalf("failed to create account: %v", acc)
- }
- json, err := ks.Export(acc, "old", "new")
- if err != nil {
- t.Fatalf("failed to export account: %v", acc)
- }
- _, ks2 := tmpKeyStore(t, true)
- if _, err = ks2.Import(json, "old", "old"); err == nil {
- t.Errorf("importing with invalid password succeeded")
- }
- acc2, err := ks2.Import(json, "new", "new")
- if err != nil {
- t.Errorf("importing failed: %v", err)
- }
- if acc.Address != acc2.Address {
- t.Error("imported account does not match exported account")
- }
- if _, err = ks2.Import(json, "new", "new"); err == nil {
- t.Errorf("importing a key twice succeeded")
- }
-}
-
-// TestImportRace tests the keystore on races.
-// This test should fail under -race if importing races.
-func TestImportRace(t *testing.T) {
- t.Parallel()
- _, ks := tmpKeyStore(t, true)
- acc, err := ks.NewAccount("old")
- if err != nil {
- t.Fatalf("failed to create account: %v", acc)
- }
- json, err := ks.Export(acc, "old", "new")
- if err != nil {
- t.Fatalf("failed to export account: %v", acc)
- }
- _, ks2 := tmpKeyStore(t, true)
- var atom atomic.Uint32
- var wg sync.WaitGroup
- wg.Add(2)
- for i := 0; i < 2; i++ {
- go func() {
- defer wg.Done()
- if _, err := ks2.Import(json, "new", "new"); err != nil {
- atom.Add(1)
- }
- }()
- }
- wg.Wait()
- if atom.Load() != 1 {
- t.Errorf("Import is racy")
- }
-}
-
-// checkAccounts checks that all known live accounts are present in the wallet list.
-func checkAccounts(t *testing.T, live map[common.Address]accounts.Account, wallets []accounts.Wallet) {
- if len(live) != len(wallets) {
- t.Errorf("wallet list doesn't match required accounts: have %d, want %d", len(wallets), len(live))
- return
- }
- liveList := make([]accounts.Account, 0, len(live))
- for _, account := range live {
- liveList = append(liveList, account)
- }
- slices.SortFunc(liveList, byURL)
- for j, wallet := range wallets {
- if accs := wallet.Accounts(); len(accs) != 1 {
- t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs))
- } else if accs[0] != liveList[j] {
- t.Errorf("wallet %d: account mismatch: have %v, want %v", j, accs[0], liveList[j])
- }
- }
-}
-
-// checkEvents checks that all events in 'want' are present in 'have'. Events may be present multiple times.
-func checkEvents(t *testing.T, want []walletEvent, have []walletEvent) {
- for _, wantEv := range want {
- nmatch := 0
- for ; len(have) > 0; nmatch++ {
- if have[0].Kind != wantEv.Kind || have[0].a != wantEv.a {
- break
- }
- have = have[1:]
- }
- if nmatch == 0 {
- t.Fatalf("can't find event with Kind=%v for %x", wantEv.Kind, wantEv.a.Address)
- }
- }
-}
-
-func tmpKeyStore(t *testing.T, encrypted bool) (string, *KeyStore) {
- d := t.TempDir()
- newKs := NewPlaintextKeyStore
- if encrypted {
- newKs = func(kd string) *KeyStore { return NewKeyStore(kd, veryLightScryptN, veryLightScryptP) }
- }
- return d, newKs(d)
-}
diff --git a/accounts/keystore/passphrase.go b/accounts/keystore/passphrase.go
deleted file mode 100644
index 2fc56ca376..0000000000
--- a/accounts/keystore/passphrase.go
+++ /dev/null
@@ -1,378 +0,0 @@
-// (c) 2019-2020, Ava Labs, Inc.
-//
-// This file is a derived work, based on the go-ethereum library whose original
-// notices appear below.
-//
-// It is distributed under a license compatible with the licensing terms of the
-// original code from which it is derived.
-//
-// Much love to the original authors for their work.
-// **********
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-/*
-
-This key store behaves as KeyStorePlain with the difference that
-the private key is encrypted and on disk uses another JSON encoding.
-
-The crypto is documented at https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
-
-*/
-
-package keystore
-
-import (
- "bytes"
- "crypto/aes"
- "crypto/rand"
- "crypto/sha256"
- "encoding/hex"
- "encoding/json"
- "fmt"
- "io"
- "os"
- "path/filepath"
-
- "github.com/ethereum/go-ethereum/accounts"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/math"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/google/uuid"
- "golang.org/x/crypto/pbkdf2"
- "golang.org/x/crypto/scrypt"
-)
-
-const (
- keyHeaderKDF = "scrypt"
-
- // StandardScryptN is the N parameter of Scrypt encryption algorithm, using 256MB
- // memory and taking approximately 1s CPU time on a modern processor.
- StandardScryptN = 1 << 18
-
- // StandardScryptP is the P parameter of Scrypt encryption algorithm, using 256MB
- // memory and taking approximately 1s CPU time on a modern processor.
- StandardScryptP = 1
-
- // LightScryptN is the N parameter of Scrypt encryption algorithm, using 4MB
- // memory and taking approximately 100ms CPU time on a modern processor.
- LightScryptN = 1 << 12
-
- // LightScryptP is the P parameter of Scrypt encryption algorithm, using 4MB
- // memory and taking approximately 100ms CPU time on a modern processor.
- LightScryptP = 6
-
- scryptR = 8
- scryptDKLen = 32
-)
-
-type keyStorePassphrase struct {
- keysDirPath string
- scryptN int
- scryptP int
- // skipKeyFileVerification disables the security-feature which does
- // reads and decrypts any newly created keyfiles. This should be 'false' in all
- // cases except tests -- setting this to 'true' is not recommended.
- skipKeyFileVerification bool
-}
-
-func (ks keyStorePassphrase) GetKey(addr common.Address, filename, auth string) (*Key, error) {
- // Load the key from the keystore and decrypt its contents
- keyjson, err := os.ReadFile(filename)
- if err != nil {
- return nil, err
- }
- key, err := DecryptKey(keyjson, auth)
- if err != nil {
- return nil, err
- }
- // Make sure we're really operating on the requested key (no swap attacks)
- if key.Address != addr {
- return nil, fmt.Errorf("key content mismatch: have account %x, want %x", key.Address, addr)
- }
- return key, nil
-}
-
-// StoreKey generates a key, encrypts with 'auth' and stores in the given directory
-func StoreKey(dir, auth string, scryptN, scryptP int) (accounts.Account, error) {
- _, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP, false}, rand.Reader, auth)
- return a, err
-}
-
-func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) error {
- keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP)
- if err != nil {
- return err
- }
- // Write into temporary file
- tmpName, err := writeTemporaryKeyFile(filename, keyjson)
- if err != nil {
- return err
- }
- if !ks.skipKeyFileVerification {
- // Verify that we can decrypt the file with the given password.
- _, err = ks.GetKey(key.Address, tmpName, auth)
- if err != nil {
- msg := "An error was encountered when saving and verifying the keystore file. \n" +
- "This indicates that the keystore is corrupted. \n" +
- "The corrupted file is stored at \n%v\n" +
- "Please file a ticket at:\n\n" +
- "https://github.com/ethereum/go-ethereum/issues." +
- "The error was : %s"
- //lint:ignore ST1005 This is a message for the user
- return fmt.Errorf(msg, tmpName, err)
- }
- }
- return os.Rename(tmpName, filename)
-}
-
-func (ks keyStorePassphrase) JoinPath(filename string) string {
- if filepath.IsAbs(filename) {
- return filename
- }
- return filepath.Join(ks.keysDirPath, filename)
-}
-
-// EncryptDataV3 encrypts the data given as 'data' with the password 'auth'.
-func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) {
- salt := make([]byte, 32)
- if _, err := io.ReadFull(rand.Reader, salt); err != nil {
- panic("reading from crypto/rand failed: " + err.Error())
- }
- derivedKey, err := scrypt.Key(auth, salt, scryptN, scryptR, scryptP, scryptDKLen)
- if err != nil {
- return CryptoJSON{}, err
- }
- encryptKey := derivedKey[:16]
-
- iv := make([]byte, aes.BlockSize) // 16
- if _, err := io.ReadFull(rand.Reader, iv); err != nil {
- panic("reading from crypto/rand failed: " + err.Error())
- }
- cipherText, err := aesCTRXOR(encryptKey, data, iv)
- if err != nil {
- return CryptoJSON{}, err
- }
- mac := crypto.Keccak256(derivedKey[16:32], cipherText)
-
- scryptParamsJSON := make(map[string]interface{}, 5)
- scryptParamsJSON["n"] = scryptN
- scryptParamsJSON["r"] = scryptR
- scryptParamsJSON["p"] = scryptP
- scryptParamsJSON["dklen"] = scryptDKLen
- scryptParamsJSON["salt"] = hex.EncodeToString(salt)
- cipherParamsJSON := cipherparamsJSON{
- IV: hex.EncodeToString(iv),
- }
-
- cryptoStruct := CryptoJSON{
- Cipher: "aes-128-ctr",
- CipherText: hex.EncodeToString(cipherText),
- CipherParams: cipherParamsJSON,
- KDF: keyHeaderKDF,
- KDFParams: scryptParamsJSON,
- MAC: hex.EncodeToString(mac),
- }
- return cryptoStruct, nil
-}
-
-// EncryptKey encrypts a key using the specified scrypt parameters into a json
-// blob that can be decrypted later on.
-func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
- keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32)
- cryptoStruct, err := EncryptDataV3(keyBytes, []byte(auth), scryptN, scryptP)
- if err != nil {
- return nil, err
- }
- encryptedKeyJSONV3 := encryptedKeyJSONV3{
- hex.EncodeToString(key.Address[:]),
- cryptoStruct,
- key.Id.String(),
- version,
- }
- return json.Marshal(encryptedKeyJSONV3)
-}
-
-// DecryptKey decrypts a key from a json blob, returning the private key itself.
-func DecryptKey(keyjson []byte, auth string) (*Key, error) {
- // Parse the json into a simple map to fetch the key version
- m := make(map[string]interface{})
- if err := json.Unmarshal(keyjson, &m); err != nil {
- return nil, err
- }
- // Depending on the version try to parse one way or another
- var (
- keyBytes, keyId []byte
- err error
- )
- if version, ok := m["version"].(string); ok && version == "1" {
- k := new(encryptedKeyJSONV1)
- if err := json.Unmarshal(keyjson, k); err != nil {
- return nil, err
- }
- keyBytes, keyId, err = decryptKeyV1(k, auth)
- } else {
- k := new(encryptedKeyJSONV3)
- if err := json.Unmarshal(keyjson, k); err != nil {
- return nil, err
- }
- keyBytes, keyId, err = decryptKeyV3(k, auth)
- }
- // Handle any decryption errors and return the key
- if err != nil {
- return nil, err
- }
- key, err := crypto.ToECDSA(keyBytes)
- if err != nil {
- return nil, fmt.Errorf("invalid key: %w", err)
- }
- id, err := uuid.FromBytes(keyId)
- if err != nil {
- return nil, fmt.Errorf("invalid UUID: %w", err)
- }
- return &Key{
- Id: id,
- Address: crypto.PubkeyToAddress(key.PublicKey),
- PrivateKey: key,
- }, nil
-}
-
-func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) {
- if cryptoJson.Cipher != "aes-128-ctr" {
- return nil, fmt.Errorf("cipher not supported: %v", cryptoJson.Cipher)
- }
- mac, err := hex.DecodeString(cryptoJson.MAC)
- if err != nil {
- return nil, err
- }
-
- iv, err := hex.DecodeString(cryptoJson.CipherParams.IV)
- if err != nil {
- return nil, err
- }
-
- cipherText, err := hex.DecodeString(cryptoJson.CipherText)
- if err != nil {
- return nil, err
- }
-
- derivedKey, err := getKDFKey(cryptoJson, auth)
- if err != nil {
- return nil, err
- }
-
- calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
- if !bytes.Equal(calculatedMAC, mac) {
- return nil, ErrDecrypt
- }
-
- plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
- if err != nil {
- return nil, err
- }
- return plainText, err
-}
-
-func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) {
- if keyProtected.Version != version {
- return nil, nil, fmt.Errorf("version not supported: %v", keyProtected.Version)
- }
- keyUUID, err := uuid.Parse(keyProtected.Id)
- if err != nil {
- return nil, nil, err
- }
- keyId = keyUUID[:]
- plainText, err := DecryptDataV3(keyProtected.Crypto, auth)
- if err != nil {
- return nil, nil, err
- }
- return plainText, keyId, err
-}
-
-func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byte, keyId []byte, err error) {
- keyUUID, err := uuid.Parse(keyProtected.Id)
- if err != nil {
- return nil, nil, err
- }
- keyId = keyUUID[:]
- mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
- if err != nil {
- return nil, nil, err
- }
-
- iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
- if err != nil {
- return nil, nil, err
- }
-
- cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
- if err != nil {
- return nil, nil, err
- }
-
- derivedKey, err := getKDFKey(keyProtected.Crypto, auth)
- if err != nil {
- return nil, nil, err
- }
-
- calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
- if !bytes.Equal(calculatedMAC, mac) {
- return nil, nil, ErrDecrypt
- }
-
- plainText, err := aesCBCDecrypt(crypto.Keccak256(derivedKey[:16])[:16], cipherText, iv)
- if err != nil {
- return nil, nil, err
- }
- return plainText, keyId, err
-}
-
-func getKDFKey(cryptoJSON CryptoJSON, auth string) ([]byte, error) {
- authArray := []byte(auth)
- salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
- if err != nil {
- return nil, err
- }
- dkLen := ensureInt(cryptoJSON.KDFParams["dklen"])
-
- if cryptoJSON.KDF == keyHeaderKDF {
- n := ensureInt(cryptoJSON.KDFParams["n"])
- r := ensureInt(cryptoJSON.KDFParams["r"])
- p := ensureInt(cryptoJSON.KDFParams["p"])
- return scrypt.Key(authArray, salt, n, r, p, dkLen)
- } else if cryptoJSON.KDF == "pbkdf2" {
- c := ensureInt(cryptoJSON.KDFParams["c"])
- prf := cryptoJSON.KDFParams["prf"].(string)
- if prf != "hmac-sha256" {
- return nil, fmt.Errorf("unsupported PBKDF2 PRF: %s", prf)
- }
- key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New)
- return key, nil
- }
-
- return nil, fmt.Errorf("unsupported KDF: %s", cryptoJSON.KDF)
-}
-
-// TODO: can we do without this when unmarshalling dynamic JSON?
-// why do integers in KDF params end up as float64 and not int after
-// unmarshal?
-func ensureInt(x interface{}) int {
- res, ok := x.(int)
- if !ok {
- res = int(x.(float64))
- }
- return res
-}
diff --git a/accounts/keystore/passphrase_test.go b/accounts/keystore/passphrase_test.go
deleted file mode 100644
index 179e7d0883..0000000000
--- a/accounts/keystore/passphrase_test.go
+++ /dev/null
@@ -1,71 +0,0 @@
-// (c) 2019-2020, Ava Labs, Inc.
-//
-// This file is a derived work, based on the go-ethereum library whose original
-// notices appear below.
-//
-// It is distributed under a license compatible with the licensing terms of the
-// original code from which it is derived.
-//
-// Much love to the original authors for their work.
-// **********
-// Copyright 2016 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package keystore
-
-import (
- "os"
- "testing"
-
- "github.com/ethereum/go-ethereum/common"
-)
-
-const (
- veryLightScryptN = 2
- veryLightScryptP = 1
-)
-
-// Tests that a json key file can be decrypted and encrypted in multiple rounds.
-func TestKeyEncryptDecrypt(t *testing.T) {
- t.Parallel()
- keyjson, err := os.ReadFile("testdata/very-light-scrypt.json")
- if err != nil {
- t.Fatal(err)
- }
- password := ""
- address := common.HexToAddress("45dea0fb0bba44f4fcf290bba71fd57d7117cbb8")
-
- // Do a few rounds of decryption and encryption
- for i := 0; i < 3; i++ {
- // Try a bad password first
- if _, err := DecryptKey(keyjson, password+"bad"); err == nil {
- t.Errorf("test %d: json key decrypted with bad password", i)
- }
- // Decrypt with the correct password
- key, err := DecryptKey(keyjson, password)
- if err != nil {
- t.Fatalf("test %d: json key failed to decrypt: %v", i, err)
- }
- if key.Address != address {
- t.Errorf("test %d: key address mismatch: have %x, want %x", i, key.Address, address)
- }
- // Recrypt with a new password and start over
- password += "new data appended" // nolint: gosec
- if keyjson, err = EncryptKey(key, password, veryLightScryptN, veryLightScryptP); err != nil {
- t.Errorf("test %d: failed to re-encrypt key %v", i, err)
- }
- }
-}
diff --git a/accounts/keystore/plain.go b/accounts/keystore/plain.go
deleted file mode 100644
index 181eeb2ec0..0000000000
--- a/accounts/keystore/plain.go
+++ /dev/null
@@ -1,71 +0,0 @@
-// (c) 2019-2020, Ava Labs, Inc.
-//
-// This file is a derived work, based on the go-ethereum library whose original
-// notices appear below.
-//
-// It is distributed under a license compatible with the licensing terms of the
-// original code from which it is derived.
-//
-// Much love to the original authors for their work.
-// **********
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package keystore
-
-import (
- "encoding/json"
- "fmt"
- "os"
- "path/filepath"
-
- "github.com/ethereum/go-ethereum/common"
-)
-
-type keyStorePlain struct {
- keysDirPath string
-}
-
-func (ks keyStorePlain) GetKey(addr common.Address, filename, auth string) (*Key, error) {
- fd, err := os.Open(filename)
- if err != nil {
- return nil, err
- }
- defer fd.Close()
- key := new(Key)
- if err := json.NewDecoder(fd).Decode(key); err != nil {
- return nil, err
- }
- if key.Address != addr {
- return nil, fmt.Errorf("key content mismatch: have address %x, want %x", key.Address, addr)
- }
- return key, nil
-}
-
-func (ks keyStorePlain) StoreKey(filename string, key *Key, auth string) error {
- content, err := json.Marshal(key)
- if err != nil {
- return err
- }
- return writeKeyFile(filename, content)
-}
-
-func (ks keyStorePlain) JoinPath(filename string) string {
- if filepath.IsAbs(filename) {
- return filename
- }
- return filepath.Join(ks.keysDirPath, filename)
-}
diff --git a/accounts/keystore/plain_test.go b/accounts/keystore/plain_test.go
deleted file mode 100644
index 7d19909fec..0000000000
--- a/accounts/keystore/plain_test.go
+++ /dev/null
@@ -1,228 +0,0 @@
-// (c) 2019-2020, Ava Labs, Inc.
-//
-// This file is a derived work, based on the go-ethereum library whose original
-// notices appear below.
-//
-// It is distributed under a license compatible with the licensing terms of the
-// original code from which it is derived.
-//
-// Much love to the original authors for their work.
-// **********
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package keystore
-
-import (
- "crypto/rand"
- "encoding/hex"
- "fmt"
- "path/filepath"
- "reflect"
- "strings"
- "testing"
-
- "github.com/ethereum/go-ethereum/common"
-)
-
-func tmpKeyStoreIface(t *testing.T, encrypted bool) (dir string, ks keyStore) {
- d := t.TempDir()
- if encrypted {
- ks = &keyStorePassphrase{d, veryLightScryptN, veryLightScryptP, true}
- } else {
- ks = &keyStorePlain{d}
- }
- return d, ks
-}
-
-func TestKeyStorePlain(t *testing.T) {
- t.Parallel()
- _, ks := tmpKeyStoreIface(t, false)
-
- pass := "" // not used but required by API
- k1, account, err := storeNewKey(ks, rand.Reader, pass)
- if err != nil {
- t.Fatal(err)
- }
- k2, err := ks.GetKey(k1.Address, account.URL.Path, pass)
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(k1.Address, k2.Address) {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) {
- t.Fatal(err)
- }
-}
-
-func TestKeyStorePassphrase(t *testing.T) {
- t.Parallel()
- _, ks := tmpKeyStoreIface(t, true)
-
- pass := "foo"
- k1, account, err := storeNewKey(ks, rand.Reader, pass)
- if err != nil {
- t.Fatal(err)
- }
- k2, err := ks.GetKey(k1.Address, account.URL.Path, pass)
- if err != nil {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(k1.Address, k2.Address) {
- t.Fatal(err)
- }
- if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) {
- t.Fatal(err)
- }
-}
-
-func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
- t.Parallel()
- _, ks := tmpKeyStoreIface(t, true)
-
- pass := "foo"
- k1, account, err := storeNewKey(ks, rand.Reader, pass)
- if err != nil {
- t.Fatal(err)
- }
- if _, err = ks.GetKey(k1.Address, account.URL.Path, "bar"); err != ErrDecrypt {
- t.Fatalf("wrong error for invalid password\ngot %q\nwant %q", err, ErrDecrypt)
- }
-}
-
-func TestImportPreSaleKey(t *testing.T) {
- t.Parallel()
- dir, ks := tmpKeyStoreIface(t, true)
-
- // file content of a presale key file generated with:
- // python pyethsaletool.py genwallet
- // with password "foo"
- fileContent := "{\"encseed\": \"26d87f5f2bf9835f9a47eefae571bc09f9107bb13d54ff12a4ec095d01f83897494cf34f7bed2ed34126ecba9db7b62de56c9d7cd136520a0427bfb11b8954ba7ac39b90d4650d3448e31185affcd74226a68f1e94b1108e6e0a4a91cdd83eba\", \"ethaddr\": \"d4584b5f6229b7be90727b0fc8c6b91bb427821f\", \"email\": \"gustav.simonsson@gmail.com\", \"btcaddr\": \"1EVknXyFC68kKNLkh6YnKzW41svSRoaAcx\"}"
- pass := "foo"
- account, _, err := importPreSaleKey(ks, []byte(fileContent), pass)
- if err != nil {
- t.Fatal(err)
- }
- if account.Address != common.HexToAddress("d4584b5f6229b7be90727b0fc8c6b91bb427821f") {
- t.Errorf("imported account has wrong address %x", account.Address)
- }
- if !strings.HasPrefix(account.URL.Path, dir) {
- t.Errorf("imported account file not in keystore directory: %q", account.URL)
- }
-}
-
-// Test and utils for the key store tests in the Ethereum JSON tests;
-// testdataKeyStoreTests/basic_tests.json
-type KeyStoreTestV3 struct {
- Json encryptedKeyJSONV3
- Password string
- Priv string
-}
-
-type KeyStoreTestV1 struct {
- Json encryptedKeyJSONV1
- Password string
- Priv string
-}
-
-func TestV3_PBKDF2_1(t *testing.T) {
- t.Parallel()
- tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t)
- testDecryptV3(tests["wikipage_test_vector_pbkdf2"], t)
-}
-
-var testsSubmodule = filepath.Join("..", "..", "tests", "testdata", "KeyStoreTests")
-
-func skipIfSubmoduleMissing(t *testing.T) {
- if !common.FileExist(testsSubmodule) {
- t.Skipf("can't find JSON tests from submodule at %s", testsSubmodule)
- }
-}
-
-func TestV3_PBKDF2_2(t *testing.T) {
- skipIfSubmoduleMissing(t)
- t.Parallel()
- tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
- testDecryptV3(tests["test1"], t)
-}
-
-func TestV3_PBKDF2_3(t *testing.T) {
- skipIfSubmoduleMissing(t)
- t.Parallel()
- tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
- testDecryptV3(tests["python_generated_test_with_odd_iv"], t)
-}
-
-func TestV3_PBKDF2_4(t *testing.T) {
- skipIfSubmoduleMissing(t)
- t.Parallel()
- tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
- testDecryptV3(tests["evilnonce"], t)
-}
-
-func TestV3_Scrypt_1(t *testing.T) {
- t.Parallel()
- tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t)
- testDecryptV3(tests["wikipage_test_vector_scrypt"], t)
-}
-
-func TestV3_Scrypt_2(t *testing.T) {
- skipIfSubmoduleMissing(t)
- t.Parallel()
- tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
- testDecryptV3(tests["test2"], t)
-}
-
-func testDecryptV3(test KeyStoreTestV3, t *testing.T) {
- privBytes, _, err := decryptKeyV3(&test.Json, test.Password)
- if err != nil {
- t.Fatal(err)
- }
- privHex := hex.EncodeToString(privBytes)
- if test.Priv != privHex {
- t.Fatal(fmt.Errorf("Decrypted bytes not equal to test, expected %v have %v", test.Priv, privHex))
- }
-}
-
-func loadKeyStoreTestV3(file string, t *testing.T) map[string]KeyStoreTestV3 {
- tests := make(map[string]KeyStoreTestV3)
- err := common.LoadJSON(file, &tests)
- if err != nil {
- t.Fatal(err)
- }
- return tests
-}
-
-func TestKeyForDirectICAP(t *testing.T) {
- t.Parallel()
- key := NewKeyForDirectICAP(rand.Reader)
- if !strings.HasPrefix(key.Address.Hex(), "0x00") {
- t.Errorf("Expected first address byte to be zero, have: %s", key.Address.Hex())
- }
-}
-
-func TestV3_31_Byte_Key(t *testing.T) {
- t.Parallel()
- tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t)
- testDecryptV3(tests["31_byte_key"], t)
-}
-
-func TestV3_30_Byte_Key(t *testing.T) {
- t.Parallel()
- tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t)
- testDecryptV3(tests["30_byte_key"], t)
-}
diff --git a/accounts/keystore/presale.go b/accounts/keystore/presale.go
deleted file mode 100644
index e861151697..0000000000
--- a/accounts/keystore/presale.go
+++ /dev/null
@@ -1,160 +0,0 @@
-// (c) 2019-2020, Ava Labs, Inc.
-//
-// This file is a derived work, based on the go-ethereum library whose original
-// notices appear below.
-//
-// It is distributed under a license compatible with the licensing terms of the
-// original code from which it is derived.
-//
-// Much love to the original authors for their work.
-// **********
-// Copyright 2016 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package keystore
-
-import (
- "crypto/aes"
- "crypto/cipher"
- "crypto/sha256"
- "encoding/hex"
- "encoding/json"
- "errors"
- "fmt"
-
- "github.com/ethereum/go-ethereum/accounts"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/google/uuid"
- "golang.org/x/crypto/pbkdf2"
-)
-
-// creates a Key and stores that in the given KeyStore by decrypting a presale key JSON
-func importPreSaleKey(keyStore keyStore, keyJSON []byte, password string) (accounts.Account, *Key, error) {
- key, err := decryptPreSaleKey(keyJSON, password)
- if err != nil {
- return accounts.Account{}, nil, err
- }
- key.Id, err = uuid.NewRandom()
- if err != nil {
- return accounts.Account{}, nil, err
- }
- a := accounts.Account{
- Address: key.Address,
- URL: accounts.URL{
- Scheme: KeyStoreScheme,
- Path: keyStore.JoinPath(keyFileName(key.Address)),
- },
- }
- err = keyStore.StoreKey(a.URL.Path, key, password)
- return a, key, err
-}
-
-func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error) {
- preSaleKeyStruct := struct {
- EncSeed string
- EthAddr string
- Email string
- BtcAddr string
- }{}
- err = json.Unmarshal(fileContent, &preSaleKeyStruct)
- if err != nil {
- return nil, err
- }
- encSeedBytes, err := hex.DecodeString(preSaleKeyStruct.EncSeed)
- if err != nil {
- return nil, errors.New("invalid hex in encSeed")
- }
- if len(encSeedBytes) < 16 {
- return nil, errors.New("invalid encSeed, too short")
- }
- iv := encSeedBytes[:16]
- cipherText := encSeedBytes[16:]
- /*
- See https://github.com/ethereum/pyethsaletool
-
- pyethsaletool generates the encryption key from password by
- 2000 rounds of PBKDF2 with HMAC-SHA-256 using password as salt (:().
- 16 byte key length within PBKDF2 and resulting key is used as AES key
- */
- passBytes := []byte(password)
- derivedKey := pbkdf2.Key(passBytes, passBytes, 2000, 16, sha256.New)
- plainText, err := aesCBCDecrypt(derivedKey, cipherText, iv)
- if err != nil {
- return nil, err
- }
- ethPriv := crypto.Keccak256(plainText)
- ecKey := crypto.ToECDSAUnsafe(ethPriv)
-
- key = &Key{
- Id: uuid.UUID{},
- Address: crypto.PubkeyToAddress(ecKey.PublicKey),
- PrivateKey: ecKey,
- }
- derivedAddr := hex.EncodeToString(key.Address.Bytes()) // needed because .Hex() gives leading "0x"
- expectedAddr := preSaleKeyStruct.EthAddr
- if derivedAddr != expectedAddr {
- err = fmt.Errorf("decrypted addr '%s' not equal to expected addr '%s'", derivedAddr, expectedAddr)
- }
- return key, err
-}
-
-func aesCTRXOR(key, inText, iv []byte) ([]byte, error) {
- // AES-128 is selected due to size of encryptKey.
- 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, err
-}
-
-func aesCBCDecrypt(key, cipherText, iv []byte) ([]byte, error) {
- aesBlock, err := aes.NewCipher(key)
- if err != nil {
- return nil, err
- }
- decrypter := cipher.NewCBCDecrypter(aesBlock, iv)
- paddedPlaintext := make([]byte, len(cipherText))
- decrypter.CryptBlocks(paddedPlaintext, cipherText)
- plaintext := pkcs7Unpad(paddedPlaintext)
- if plaintext == nil {
- return nil, ErrDecrypt
- }
- return plaintext, err
-}
-
-// From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes
-func pkcs7Unpad(in []byte) []byte {
- if len(in) == 0 {
- return nil
- }
-
- padding := in[len(in)-1]
- if int(padding) > len(in) || padding > aes.BlockSize {
- return nil
- } else if padding == 0 {
- return nil
- }
-
- for i := len(in) - 1; i > len(in)-int(padding)-1; i-- {
- if in[i] != padding {
- return nil
- }
- }
- return in[:len(in)-int(padding)]
-}
diff --git a/accounts/keystore/testdata/dupes/1 b/accounts/keystore/testdata/dupes/1
deleted file mode 100644
index a3868ec6d5..0000000000
--- a/accounts/keystore/testdata/dupes/1
+++ /dev/null
@@ -1 +0,0 @@
-{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
\ No newline at end of file
diff --git a/accounts/keystore/testdata/dupes/2 b/accounts/keystore/testdata/dupes/2
deleted file mode 100644
index a3868ec6d5..0000000000
--- a/accounts/keystore/testdata/dupes/2
+++ /dev/null
@@ -1 +0,0 @@
-{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
\ No newline at end of file
diff --git a/accounts/keystore/testdata/dupes/foo b/accounts/keystore/testdata/dupes/foo
deleted file mode 100644
index c57060aea0..0000000000
--- a/accounts/keystore/testdata/dupes/foo
+++ /dev/null
@@ -1 +0,0 @@
-{"address":"7ef5a6135f1fd6a02593eedc869c6d41d934aef8","crypto":{"cipher":"aes-128-ctr","ciphertext":"1d0839166e7a15b9c1333fc865d69858b22df26815ccf601b28219b6192974e1","cipherparams":{"iv":"8df6caa7ff1b00c4e871f002cb7921ed"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"e5e6ef3f4ea695f496b643ebd3f75c0aa58ef4070e90c80c5d3fb0241bf1595c"},"mac":"6d16dfde774845e4585357f24bce530528bc69f4f84e1e22880d34fa45c273e5"},"id":"950077c7-71e3-4c44-a4a1-143919141ed4","version":3}
\ No newline at end of file
diff --git a/accounts/keystore/testdata/keystore/.hiddenfile b/accounts/keystore/testdata/keystore/.hiddenfile
deleted file mode 100644
index d91faccdeb..0000000000
--- a/accounts/keystore/testdata/keystore/.hiddenfile
+++ /dev/null
@@ -1 +0,0 @@
-{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
diff --git a/accounts/keystore/testdata/keystore/README b/accounts/keystore/testdata/keystore/README
deleted file mode 100644
index 6af9ac3f1b..0000000000
--- a/accounts/keystore/testdata/keystore/README
+++ /dev/null
@@ -1,21 +0,0 @@
-This directory contains accounts for testing.
-The password that unlocks them is "foobar".
-
-The "good" key files which are supposed to be loadable are:
-
-- File: UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8
- Address: 0x7ef5a6135f1fd6a02593eedc869c6d41d934aef8
-- File: aaa
- Address: 0xf466859ead1932d743d622cb74fc058882e8648a
-- File: zzz
- Address: 0x289d485d9771714cce91d3393d764e1311907acc
-
-The other files (including this README) are broken in various ways
-and should not be picked up by package accounts:
-
-- File: no-address (missing address field, otherwise same as "aaa")
-- File: garbage (file with random data)
-- File: empty (file with no content)
-- File: swapfile~ (should be skipped)
-- File: .hiddenfile (should be skipped)
-- File: foo/... (should be skipped because it is a directory)
diff --git a/accounts/keystore/testdata/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 b/accounts/keystore/testdata/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8
deleted file mode 100644
index c57060aea0..0000000000
--- a/accounts/keystore/testdata/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8
+++ /dev/null
@@ -1 +0,0 @@
-{"address":"7ef5a6135f1fd6a02593eedc869c6d41d934aef8","crypto":{"cipher":"aes-128-ctr","ciphertext":"1d0839166e7a15b9c1333fc865d69858b22df26815ccf601b28219b6192974e1","cipherparams":{"iv":"8df6caa7ff1b00c4e871f002cb7921ed"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"e5e6ef3f4ea695f496b643ebd3f75c0aa58ef4070e90c80c5d3fb0241bf1595c"},"mac":"6d16dfde774845e4585357f24bce530528bc69f4f84e1e22880d34fa45c273e5"},"id":"950077c7-71e3-4c44-a4a1-143919141ed4","version":3}
\ No newline at end of file
diff --git a/accounts/keystore/testdata/keystore/aaa b/accounts/keystore/testdata/keystore/aaa
deleted file mode 100644
index a3868ec6d5..0000000000
--- a/accounts/keystore/testdata/keystore/aaa
+++ /dev/null
@@ -1 +0,0 @@
-{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
\ No newline at end of file
diff --git a/accounts/keystore/testdata/keystore/empty b/accounts/keystore/testdata/keystore/empty
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/accounts/keystore/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e b/accounts/keystore/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e
deleted file mode 100644
index 309841e524..0000000000
--- a/accounts/keystore/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e
+++ /dev/null
@@ -1 +0,0 @@
-{"address":"fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e","crypto":{"cipher":"aes-128-ctr","ciphertext":"8124d5134aa4a927c79fd852989e4b5419397566f04b0936a1eb1d168c7c68a5","cipherparams":{"iv":"e2febe17176414dd2cda28287947eb2f"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":4096,"p":6,"r":8,"salt":"44b415ede89f3bdd6830390a21b78965f571b347a589d1d943029f016c5e8bd5"},"mac":"5e149ff25bfd9dd45746a84bb2bcd2f015f2cbca2b6d25c5de8c29617f71fe5b"},"id":"d6ac5452-2b2c-4d3c-ad80-4bf0327d971c","version":3}
\ No newline at end of file
diff --git a/accounts/keystore/testdata/keystore/garbage b/accounts/keystore/testdata/keystore/garbage
deleted file mode 100644
index ff45091e71..0000000000
Binary files a/accounts/keystore/testdata/keystore/garbage and /dev/null differ
diff --git a/accounts/keystore/testdata/keystore/no-address b/accounts/keystore/testdata/keystore/no-address
deleted file mode 100644
index ad51269ead..0000000000
--- a/accounts/keystore/testdata/keystore/no-address
+++ /dev/null
@@ -1 +0,0 @@
-{"crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
\ No newline at end of file
diff --git a/accounts/keystore/testdata/keystore/zero b/accounts/keystore/testdata/keystore/zero
deleted file mode 100644
index b52617f8ae..0000000000
--- a/accounts/keystore/testdata/keystore/zero
+++ /dev/null
@@ -1 +0,0 @@
-{"address":"0000000000000000000000000000000000000000","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
\ No newline at end of file
diff --git a/accounts/keystore/testdata/keystore/zzz b/accounts/keystore/testdata/keystore/zzz
deleted file mode 100644
index cfd8a47017..0000000000
--- a/accounts/keystore/testdata/keystore/zzz
+++ /dev/null
@@ -1 +0,0 @@
-{"address":"289d485d9771714cce91d3393d764e1311907acc","crypto":{"cipher":"aes-128-ctr","ciphertext":"faf32ca89d286b107f5e6d842802e05263c49b78d46eac74e6109e9a963378ab","cipherparams":{"iv":"558833eec4a665a8c55608d7d503407d"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"d571fff447ffb24314f9513f5160246f09997b857ac71348b73e785aab40dc04"},"mac":"21edb85ff7d0dab1767b9bf498f2c3cb7be7609490756bd32300bb213b59effe"},"id":"3279afcf-55ba-43ff-8997-02dcc46a6525","version":3}
\ No newline at end of file
diff --git a/accounts/keystore/testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e b/accounts/keystore/testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e
deleted file mode 100644
index 498d8131e8..0000000000
--- a/accounts/keystore/testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e
+++ /dev/null
@@ -1 +0,0 @@
-{"address":"cb61d5a9c4896fb9658090b597ef0e7be6f7b67e","Crypto":{"cipher":"aes-128-cbc","ciphertext":"6143d3192db8b66eabd693d9c4e414dcfaee52abda451af79ccf474dafb35f1bfc7ea013aa9d2ee35969a1a2e8d752d0","cipherparams":{"iv":"35337770fc2117994ecdcad026bccff4"},"kdf":"scrypt","kdfparams":{"n":262144,"r":8,"p":1,"dklen":32,"salt":"9afcddebca541253a2f4053391c673ff9fe23097cd8555d149d929e4ccf1257f"},"mac":"3f3d5af884b17a100b0b3232c0636c230a54dc2ac8d986227219b0dd89197644","version":"1"},"id":"e25f7c1f-d318-4f29-b62c-687190d4d299","version":"1"}
\ No newline at end of file
diff --git a/accounts/keystore/testdata/v1_test_vector.json b/accounts/keystore/testdata/v1_test_vector.json
deleted file mode 100644
index 3d09b55b5e..0000000000
--- a/accounts/keystore/testdata/v1_test_vector.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "test1": {
- "json": {
- "Crypto": {
- "cipher": "aes-128-cbc",
- "cipherparams": {
- "iv": "35337770fc2117994ecdcad026bccff4"
- },
- "ciphertext": "6143d3192db8b66eabd693d9c4e414dcfaee52abda451af79ccf474dafb35f1bfc7ea013aa9d2ee35969a1a2e8d752d0",
- "kdf": "scrypt",
- "kdfparams": {
- "dklen": 32,
- "n": 262144,
- "p": 1,
- "r": 8,
- "salt": "9afcddebca541253a2f4053391c673ff9fe23097cd8555d149d929e4ccf1257f"
- },
- "mac": "3f3d5af884b17a100b0b3232c0636c230a54dc2ac8d986227219b0dd89197644",
- "version": "1"
- },
- "address": "cb61d5a9c4896fb9658090b597ef0e7be6f7b67e",
- "id": "e25f7c1f-d318-4f29-b62c-687190d4d299",
- "version": "1"
- },
- "password": "g",
- "priv": "d1b1178d3529626a1a93e073f65028370d14c7eb0936eb42abef05db6f37ad7d"
- }
-}
diff --git a/accounts/keystore/testdata/v3_test_vector.json b/accounts/keystore/testdata/v3_test_vector.json
deleted file mode 100644
index 1e7f790c05..0000000000
--- a/accounts/keystore/testdata/v3_test_vector.json
+++ /dev/null
@@ -1,97 +0,0 @@
-{
- "wikipage_test_vector_scrypt": {
- "json": {
- "crypto" : {
- "cipher" : "aes-128-ctr",
- "cipherparams" : {
- "iv" : "83dbcc02d8ccb40e466191a123791e0e"
- },
- "ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c",
- "kdf" : "scrypt",
- "kdfparams" : {
- "dklen" : 32,
- "n" : 262144,
- "r" : 1,
- "p" : 8,
- "salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19"
- },
- "mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097"
- },
- "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
- "version" : 3
- },
- "password": "testpassword",
- "priv": "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"
- },
- "wikipage_test_vector_pbkdf2": {
- "json": {
- "crypto" : {
- "cipher" : "aes-128-ctr",
- "cipherparams" : {
- "iv" : "6087dab2f9fdbbfaddc31a909735c1e6"
- },
- "ciphertext" : "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46",
- "kdf" : "pbkdf2",
- "kdfparams" : {
- "c" : 262144,
- "dklen" : 32,
- "prf" : "hmac-sha256",
- "salt" : "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"
- },
- "mac" : "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2"
- },
- "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
- "version" : 3
- },
- "password": "testpassword",
- "priv": "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"
- },
- "31_byte_key": {
- "json": {
- "crypto" : {
- "cipher" : "aes-128-ctr",
- "cipherparams" : {
- "iv" : "e0c41130a323adc1446fc82f724bca2f"
- },
- "ciphertext" : "9517cd5bdbe69076f9bf5057248c6c050141e970efa36ce53692d5d59a3984",
- "kdf" : "scrypt",
- "kdfparams" : {
- "dklen" : 32,
- "n" : 2,
- "r" : 8,
- "p" : 1,
- "salt" : "711f816911c92d649fb4c84b047915679933555030b3552c1212609b38208c63"
- },
- "mac" : "d5e116151c6aa71470e67a7d42c9620c75c4d23229847dcc127794f0732b0db5"
- },
- "id" : "fecfc4ce-e956-48fd-953b-30f8b52ed66c",
- "version" : 3
- },
- "password": "foo",
- "priv": "fa7b3db73dc7dfdf8c5fbdb796d741e4488628c41fc4febd9160a866ba0f35"
- },
- "30_byte_key": {
- "json": {
- "crypto" : {
- "cipher" : "aes-128-ctr",
- "cipherparams" : {
- "iv" : "3ca92af36ad7c2cd92454c59cea5ef00"
- },
- "ciphertext" : "108b7d34f3442fc26ab1ab90ca91476ba6bfa8c00975a49ef9051dc675aa",
- "kdf" : "scrypt",
- "kdfparams" : {
- "dklen" : 32,
- "n" : 2,
- "r" : 8,
- "p" : 1,
- "salt" : "d0769e608fb86cda848065642a9c6fa046845c928175662b8e356c77f914cd3b"
- },
- "mac" : "75d0e6759f7b3cefa319c3be41680ab6beea7d8328653474bd06706d4cc67420"
- },
- "id" : "a37e1559-5955-450d-8075-7b8931b392b2",
- "version" : 3
- },
- "password": "foo",
- "priv": "81c29e8142bb6a81bef5a92bda7a8328a5c85bb2f9542e76f9b0f94fc018"
- }
-}
diff --git a/accounts/keystore/testdata/very-light-scrypt.json b/accounts/keystore/testdata/very-light-scrypt.json
deleted file mode 100644
index d23b9b2b91..0000000000
--- a/accounts/keystore/testdata/very-light-scrypt.json
+++ /dev/null
@@ -1 +0,0 @@
-{"address":"45dea0fb0bba44f4fcf290bba71fd57d7117cbb8","crypto":{"cipher":"aes-128-ctr","ciphertext":"b87781948a1befd247bff51ef4063f716cf6c2d3481163e9a8f42e1f9bb74145","cipherparams":{"iv":"dc4926b48a105133d2f16b96833abf1e"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":2,"p":1,"r":8,"salt":"004244bbdc51cadda545b1cfa43cff9ed2ae88e08c61f1479dbb45410722f8f0"},"mac":"39990c1684557447940d4c69e06b1b82b2aceacb43f284df65c956daf3046b85"},"id":"ce541d8d-c79b-40f8-9f8c-20f59616faba","version":3}
diff --git a/accounts/keystore/wallet.go b/accounts/keystore/wallet.go
deleted file mode 100644
index ebd4019cb5..0000000000
--- a/accounts/keystore/wallet.go
+++ /dev/null
@@ -1,160 +0,0 @@
-// (c) 2019-2020, Ava Labs, Inc.
-//
-// This file is a derived work, based on the go-ethereum library whose original
-// notices appear below.
-//
-// It is distributed under a license compatible with the licensing terms of the
-// original code from which it is derived.
-//
-// Much love to the original authors for their work.
-// **********
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package keystore
-
-import (
- "math/big"
-
- "github.com/ava-labs/coreth/core/types"
- "github.com/ava-labs/coreth/interfaces"
- "github.com/ethereum/go-ethereum/accounts"
- "github.com/ethereum/go-ethereum/crypto"
-)
-
-// keystoreWallet implements the accounts.Wallet interface for the original
-// keystore.
-type keystoreWallet struct {
- account accounts.Account // Single account contained in this wallet
- keystore *KeyStore // Keystore where the account originates from
-}
-
-// URL implements accounts.Wallet, returning the URL of the account within.
-func (w *keystoreWallet) URL() accounts.URL {
- return w.account.URL
-}
-
-// Status implements accounts.Wallet, returning whether the account held by the
-// keystore wallet is unlocked or not.
-func (w *keystoreWallet) Status() (string, error) {
- w.keystore.mu.RLock()
- defer w.keystore.mu.RUnlock()
-
- if _, ok := w.keystore.unlocked[w.account.Address]; ok {
- return "Unlocked", nil
- }
- return "Locked", nil
-}
-
-// Open implements accounts.Wallet, but is a noop for plain wallets since there
-// is no connection or decryption step necessary to access the list of accounts.
-func (w *keystoreWallet) Open(passphrase string) error { return nil }
-
-// Close implements accounts.Wallet, but is a noop for plain wallets since there
-// is no meaningful open operation.
-func (w *keystoreWallet) Close() error { return nil }
-
-// Accounts implements accounts.Wallet, returning an account list consisting of
-// a single account that the plain keystore wallet contains.
-func (w *keystoreWallet) Accounts() []accounts.Account {
- return []accounts.Account{w.account}
-}
-
-// Contains implements accounts.Wallet, returning whether a particular account is
-// or is not wrapped by this wallet instance.
-func (w *keystoreWallet) Contains(account accounts.Account) bool {
- return account.Address == w.account.Address && (account.URL == (accounts.URL{}) || account.URL == w.account.URL)
-}
-
-// Derive implements accounts.Wallet, but is a noop for plain wallets since there
-// is no notion of hierarchical account derivation for plain keystore accounts.
-func (w *keystoreWallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
- return accounts.Account{}, accounts.ErrNotSupported
-}
-
-// SelfDerive implements accounts.Wallet, but is a noop for plain wallets since
-// there is no notion of hierarchical account derivation for plain keystore accounts.
-func (w *keystoreWallet) SelfDerive(bases []accounts.DerivationPath, chain interfaces.ChainStateReader) {
-}
-
-// signHash attempts to sign the given hash with
-// the given account. If the wallet does not wrap this particular account, an
-// error is returned to avoid account leakage (even though in theory we may be
-// able to sign via our shared keystore backend).
-func (w *keystoreWallet) signHash(account accounts.Account, hash []byte) ([]byte, error) {
- // Make sure the requested account is contained within
- if !w.Contains(account) {
- return nil, accounts.ErrUnknownAccount
- }
- // Account seems valid, request the keystore to sign
- return w.keystore.SignHash(account, hash)
-}
-
-// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed.
-func (w *keystoreWallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
- return w.signHash(account, crypto.Keccak256(data))
-}
-
-// SignDataWithPassphrase signs keccak256(data). The mimetype parameter describes the type of data being signed.
-func (w *keystoreWallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
- // Make sure the requested account is contained within
- if !w.Contains(account) {
- return nil, accounts.ErrUnknownAccount
- }
- // Account seems valid, request the keystore to sign
- return w.keystore.SignHashWithPassphrase(account, passphrase, crypto.Keccak256(data))
-}
-
-// SignText implements accounts.Wallet, attempting to sign the hash of
-// the given text with the given account.
-func (w *keystoreWallet) SignText(account accounts.Account, text []byte) ([]byte, error) {
- return w.signHash(account, accounts.TextHash(text))
-}
-
-// SignTextWithPassphrase implements accounts.Wallet, attempting to sign the
-// hash of the given text with the given account using passphrase as extra authentication.
-func (w *keystoreWallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
- // Make sure the requested account is contained within
- if !w.Contains(account) {
- return nil, accounts.ErrUnknownAccount
- }
- // Account seems valid, request the keystore to sign
- return w.keystore.SignHashWithPassphrase(account, passphrase, accounts.TextHash(text))
-}
-
-// SignTx implements accounts.Wallet, attempting to sign the given transaction
-// with the given account. If the wallet does not wrap this particular account,
-// an error is returned to avoid account leakage (even though in theory we may
-// be able to sign via our shared keystore backend).
-func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
- // Make sure the requested account is contained within
- if !w.Contains(account) {
- return nil, accounts.ErrUnknownAccount
- }
- // Account seems valid, request the keystore to sign
- return w.keystore.SignTx(account, tx, chainID)
-}
-
-// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
-// transaction with the given account using passphrase as extra authentication.
-func (w *keystoreWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
- // Make sure the requested account is contained within
- if !w.Contains(account) {
- return nil, accounts.ErrUnknownAccount
- }
- // Account seems valid, request the keystore to sign
- return w.keystore.SignTxWithPassphrase(account, passphrase, tx, chainID)
-}
diff --git a/accounts/keystore/watch.go b/accounts/keystore/watch.go
deleted file mode 100644
index 156424c97b..0000000000
--- a/accounts/keystore/watch.go
+++ /dev/null
@@ -1,144 +0,0 @@
-// (c) 2019-2020, Ava Labs, Inc.
-//
-// This file is a derived work, based on the go-ethereum library whose original
-// notices appear below.
-//
-// It is distributed under a license compatible with the licensing terms of the
-// original code from which it is derived.
-//
-// Much love to the original authors for their work.
-// **********
-// Copyright 2016 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-//go:build (darwin && !ios && cgo) || freebsd || (linux && !arm64) || netbsd || solaris
-// +build darwin,!ios,cgo freebsd linux,!arm64 netbsd solaris
-
-package keystore
-
-import (
- "os"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
- "github.com/fsnotify/fsnotify"
-)
-
-type watcher struct {
- ac *accountCache
- running bool // set to true when runloop begins
- runEnded bool // set to true when runloop ends
- starting bool // set to true prior to runloop starting
- quit chan struct{}
-}
-
-func newWatcher(ac *accountCache) *watcher {
- return &watcher{
- ac: ac,
- quit: make(chan struct{}),
- }
-}
-
-// enabled returns false on systems not supported.
-func (*watcher) enabled() bool { return true }
-
-// starts the watcher loop in the background.
-// Start a watcher in the background if that's not already in progress.
-// The caller must hold w.ac.mu.
-func (w *watcher) start() {
- if w.starting || w.running {
- return
- }
- w.starting = true
- go w.loop()
-}
-
-func (w *watcher) close() {
- close(w.quit)
-}
-
-func (w *watcher) loop() {
- defer func() {
- w.ac.mu.Lock()
- w.running = false
- w.starting = false
- w.runEnded = true
- w.ac.mu.Unlock()
- }()
- logger := log.New("path", w.ac.keydir)
-
- // Create new watcher.
- watcher, err := fsnotify.NewWatcher()
- if err != nil {
- log.Error("Failed to start filesystem watcher", "err", err)
- return
- }
- defer watcher.Close()
- if err := watcher.Add(w.ac.keydir); err != nil {
- if !os.IsNotExist(err) {
- logger.Warn("Failed to watch keystore folder", "err", err)
- }
- return
- }
-
- logger.Trace("Started watching keystore folder", "folder", w.ac.keydir)
- defer logger.Trace("Stopped watching keystore folder")
-
- w.ac.mu.Lock()
- w.running = true
- w.ac.mu.Unlock()
-
- // Wait for file system events and reload.
- // When an event occurs, the reload call is delayed a bit so that
- // multiple events arriving quickly only cause a single reload.
- var (
- debounceDuration = 500 * time.Millisecond
- rescanTriggered = false
- debounce = time.NewTimer(0)
- )
- // Ignore initial trigger
- if !debounce.Stop() {
- <-debounce.C
- }
- defer debounce.Stop()
- for {
- select {
- case <-w.quit:
- return
- case _, ok := <-watcher.Events:
- if !ok {
- return
- }
- // Trigger the scan (with delay), if not already triggered
- if !rescanTriggered {
- debounce.Reset(debounceDuration)
- rescanTriggered = true
- }
- // The fsnotify library does provide more granular event-info, it
- // would be possible to refresh individual affected files instead
- // of scheduling a full rescan. For most cases though, the
- // full rescan is quick and obviously simplest.
- case err, ok := <-watcher.Errors:
- if !ok {
- return
- }
- log.Info("Filesystem watcher error", "err", err)
- case <-debounce.C:
- w.ac.scanAccounts()
- rescanTriggered = false
- }
- }
-}
diff --git a/accounts/keystore/watch_fallback.go b/accounts/keystore/watch_fallback.go
deleted file mode 100644
index 520ff41993..0000000000
--- a/accounts/keystore/watch_fallback.go
+++ /dev/null
@@ -1,45 +0,0 @@
-// (c) 2019-2020, Ava Labs, Inc.
-//
-// This file is a derived work, based on the go-ethereum library whose original
-// notices appear below.
-//
-// It is distributed under a license compatible with the licensing terms of the
-// original code from which it is derived.
-//
-// Much love to the original authors for their work.
-// **********
-// Copyright 2016 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-//go:build (darwin && !cgo) || ios || (linux && arm64) || windows || (!darwin && !freebsd && !linux && !netbsd && !solaris)
-// +build darwin,!cgo ios linux,arm64 windows !darwin,!freebsd,!linux,!netbsd,!solaris
-
-// This is the fallback implementation of directory watching.
-// It is used on unsupported platforms.
-
-package keystore
-
-type watcher struct {
- running bool
- runEnded bool
-}
-
-func newWatcher(*accountCache) *watcher { return new(watcher) }
-func (*watcher) start() {}
-func (*watcher) close() {}
-
-// enabled returns false on systems not supported.
-func (*watcher) enabled() bool { return false }
diff --git a/go.mod b/go.mod
index 23b4d388a7..544da47960 100644
--- a/go.mod
+++ b/go.mod
@@ -5,15 +5,11 @@ go 1.22.8
require (
github.com/VictoriaMetrics/fastcache v1.12.1
github.com/ava-labs/avalanchego v1.11.12-rc.3
- github.com/cespare/cp v0.1.0
github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233
github.com/davecgh/go-spew v1.1.1
github.com/deckarep/golang-set/v2 v2.1.0
github.com/ethereum/go-ethereum v1.13.14
- github.com/fsnotify/fsnotify v1.6.0
- github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08
github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46
- github.com/google/uuid v1.6.0
github.com/gorilla/rpc v1.2.0
github.com/gorilla/websocket v1.5.0
github.com/hashicorp/go-bexpr v0.1.10
@@ -30,7 +26,6 @@ require (
github.com/spf13/cast v1.5.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.12.0
- github.com/status-im/keycard-go v0.2.0
github.com/stretchr/testify v1.8.4
github.com/tyler-smith/go-bip39 v1.1.0
github.com/urfave/cli/v2 v2.25.7
@@ -40,7 +35,6 @@ require (
golang.org/x/exp v0.0.0-20231127185646-65229373498e
golang.org/x/sync v0.7.0
golang.org/x/sys v0.19.0
- golang.org/x/text v0.14.0
golang.org/x/time v0.3.0
google.golang.org/protobuf v1.34.2
gopkg.in/natefinch/lumberjack.v2 v2.0.0
@@ -68,6 +62,8 @@ require (
github.com/dlclark/regexp2 v1.7.0 // indirect
github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 // indirect
github.com/ethereum/c-kzg-4844 v0.4.0 // indirect
+ github.com/fsnotify/fsnotify v1.6.0 // indirect
+ github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect
github.com/getsentry/sentry-go v0.18.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
@@ -79,6 +75,7 @@ require (
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
github.com/google/renameio/v2 v2.0.0 // indirect
+ github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
@@ -106,6 +103,7 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
+ github.com/status-im/keycard-go v0.2.0 // indirect
github.com/subosito/gotenv v1.3.0 // indirect
github.com/supranational/blst v0.3.11 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect
@@ -126,6 +124,7 @@ require (
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/term v0.19.0 // indirect
+ golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.17.0 // indirect
gonum.org/v1/gonum v0.11.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect
diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go
index b482149557..70742f2b80 100644
--- a/internal/ethapi/api.go
+++ b/internal/ethapi/api.go
@@ -36,7 +36,6 @@ import (
"time"
"github.com/ava-labs/avalanchego/ids"
- "github.com/ava-labs/coreth/accounts/keystore"
"github.com/ava-labs/coreth/consensus"
"github.com/ava-labs/coreth/core"
"github.com/ava-labs/coreth/core/state"
@@ -47,6 +46,7 @@ import (
"github.com/ava-labs/coreth/trie"
"github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/accounts"
+ "github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/accounts/scwallet"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go
index d4796f2ce0..b83fd88d58 100644
--- a/internal/ethapi/api_test.go
+++ b/internal/ethapi/api_test.go
@@ -41,7 +41,6 @@ import (
"testing"
"time"
- "github.com/ava-labs/coreth/accounts/keystore"
"github.com/ava-labs/coreth/consensus"
"github.com/ava-labs/coreth/consensus/dummy"
"github.com/ava-labs/coreth/core"
@@ -54,6 +53,7 @@ import (
"github.com/ava-labs/coreth/rpc"
"github.com/ava-labs/coreth/utils"
"github.com/ethereum/go-ethereum/accounts"
+ "github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/vm"
diff --git a/node/config.go b/node/config.go
index 05098b180e..c0b675a45d 100644
--- a/node/config.go
+++ b/node/config.go
@@ -31,9 +31,9 @@ import (
"os"
"path/filepath"
- "github.com/ava-labs/coreth/accounts/keystore"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/external"
+ "github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/log"
)
diff --git a/plugin/evm/syncervm_test.go b/plugin/evm/syncervm_test.go
index 3ecfcb517c..c7e114aaea 100644
--- a/plugin/evm/syncervm_test.go
+++ b/plugin/evm/syncervm_test.go
@@ -27,7 +27,6 @@ import (
"github.com/ava-labs/avalanchego/utils/set"
"github.com/ava-labs/avalanchego/utils/units"
- "github.com/ava-labs/coreth/accounts/keystore"
"github.com/ava-labs/coreth/consensus/dummy"
"github.com/ava-labs/coreth/constants"
"github.com/ava-labs/coreth/core"
@@ -40,6 +39,7 @@ import (
"github.com/ava-labs/coreth/sync/statesync"
"github.com/ava-labs/coreth/trie"
"github.com/ava-labs/coreth/triedb"
+ "github.com/ava-labs/coreth/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
@@ -426,7 +426,7 @@ type syncVMSetup struct {
serverAppSender *enginetest.Sender
includedAtomicTxs []*Tx
- fundedAccounts map[*keystore.Key]*types.StateAccount
+ fundedAccounts map[*utils.Key]*types.StateAccount
syncerVM *VM
syncerDB database.Database
diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go
index 777d362812..81ad45cf35 100644
--- a/plugin/evm/vm_test.go
+++ b/plugin/evm/vm_test.go
@@ -64,7 +64,6 @@ import (
"github.com/ava-labs/coreth/rpc"
avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp"
- accountKeystore "github.com/ava-labs/coreth/accounts/keystore"
)
var (
@@ -1382,7 +1381,7 @@ func TestSetPreferenceRace(t *testing.T) {
}
func TestConflictingTransitiveAncestryWithGap(t *testing.T) {
- key, err := accountKeystore.NewKey(rand.Reader)
+ key, err := utils.NewKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
@@ -3595,7 +3594,7 @@ func TestAtomicTxBuildBlockDropsConflicts(t *testing.T) {
testShortIDAddrs[1]: importAmount,
testShortIDAddrs[2]: importAmount,
})
- conflictKey, err := accountKeystore.NewKey(rand.Reader)
+ conflictKey, err := utils.NewKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
diff --git a/scripts/geth-allowed-packages.txt b/scripts/geth-allowed-packages.txt
index 4c20721d34..06383561cd 100644
--- a/scripts/geth-allowed-packages.txt
+++ b/scripts/geth-allowed-packages.txt
@@ -1,5 +1,6 @@
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/external"
+"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/accounts/scwallet"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/bitutil"
diff --git a/sync/statesync/test_sync.go b/sync/statesync/test_sync.go
index 0a5ec45164..9fdcb24f01 100644
--- a/sync/statesync/test_sync.go
+++ b/sync/statesync/test_sync.go
@@ -8,11 +8,11 @@ import (
"math/rand"
"testing"
- "github.com/ava-labs/coreth/accounts/keystore"
"github.com/ava-labs/coreth/core/rawdb"
"github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/coreth/sync/syncutils"
"github.com/ava-labs/coreth/triedb"
+ "github.com/ava-labs/coreth/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
@@ -116,7 +116,7 @@ func fillAccountsWithStorage(t *testing.T, serverDB ethdb.Database, serverTrieDB
// returns the new trie root and a map of funded keys to StateAccount structs.
func FillAccountsWithOverlappingStorage(
t *testing.T, trieDB *triedb.Database, root common.Hash, numAccounts int, numOverlappingStorageRoots int,
-) (common.Hash, map[*keystore.Key]*types.StateAccount) {
+) (common.Hash, map[*utils.Key]*types.StateAccount) {
storageRoots := make([]common.Hash, 0, numOverlappingStorageRoots)
for i := 0; i < numOverlappingStorageRoots; i++ {
storageRoot, _, _ := syncutils.GenerateTrie(t, trieDB, 16, common.HashLength)
diff --git a/sync/syncutils/test_trie.go b/sync/syncutils/test_trie.go
index b66e53e905..d2f7706d09 100644
--- a/sync/syncutils/test_trie.go
+++ b/sync/syncutils/test_trie.go
@@ -10,11 +10,11 @@ import (
"testing"
"github.com/ava-labs/avalanchego/utils/wrappers"
- "github.com/ava-labs/coreth/accounts/keystore"
"github.com/ava-labs/coreth/core/types"
"github.com/ava-labs/coreth/trie"
"github.com/ava-labs/coreth/trie/trienode"
"github.com/ava-labs/coreth/triedb"
+ "github.com/ava-labs/coreth/utils"
"github.com/holiman/uint256"
"github.com/ethereum/go-ethereum/common"
@@ -147,12 +147,12 @@ func CorruptTrie(t *testing.T, diskdb ethdb.Batcher, tr *trie.Trie, n int) {
func FillAccounts(
t *testing.T, trieDB *triedb.Database, root common.Hash, numAccounts int,
onAccount func(*testing.T, int, types.StateAccount) types.StateAccount,
-) (common.Hash, map[*keystore.Key]*types.StateAccount) {
+) (common.Hash, map[*utils.Key]*types.StateAccount) {
var (
minBalance = uint256.NewInt(3000000000000000000)
randBalance = uint256.NewInt(1000000000000000000)
maxNonce = 10
- accounts = make(map[*keystore.Key]*types.StateAccount, numAccounts)
+ accounts = make(map[*utils.Key]*types.StateAccount, numAccounts)
)
tr, err := trie.NewStateTrie(trie.TrieID(root), trieDB)
@@ -176,7 +176,7 @@ func FillAccounts(
t.Fatalf("failed to rlp encode account: %v", err)
}
- key, err := keystore.NewKey(cryptoRand.Reader)
+ key, err := utils.NewKey(cryptoRand.Reader)
if err != nil {
t.Fatal(err)
}
diff --git a/utils/key.go b/utils/key.go
new file mode 100644
index 0000000000..a6515af062
--- /dev/null
+++ b/utils/key.go
@@ -0,0 +1,32 @@
+// (c) 2021-2022, Ava Labs, Inc. All rights reserved.
+// See the file LICENSE for licensing terms.
+
+package utils
+
+import (
+ "crypto/ecdsa"
+ "io"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+)
+
+type Key struct {
+ Address common.Address
+ PrivateKey *ecdsa.PrivateKey
+}
+
+func NewKey(rand io.Reader) (*Key, error) {
+ privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand)
+ if err != nil {
+ return nil, err
+ }
+ return NewKeyFromECDSA(privateKeyECDSA), nil
+}
+
+func NewKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key {
+ return &Key{
+ Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey),
+ PrivateKey: privateKeyECDSA,
+ }
+}