diff --git a/accounts/abi/bind/auth.go b/accounts/abi/bind/auth.go index 624f995b0e..2cf22768cf 100644 --- a/accounts/abi/bind/auth.go +++ b/accounts/abi/bind/auth.go @@ -17,10 +17,12 @@ package bind import ( + "crypto/ecdsa" "errors" "io" "io/ioutil" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -33,23 +35,24 @@ func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) { if err != nil { return nil, err } - key, err := crypto.DecryptKey(json, passphrase) + key, err := accounts.DecryptKey(json, passphrase) if err != nil { return nil, err } - return NewKeyedTransactor(key), nil + return NewKeyedTransactor(key.PrivateKey), nil } // NewKeyedTransactor is a utility method to easily create a transaction signer -// from a plain go-ethereum crypto key. -func NewKeyedTransactor(key *crypto.Key) *TransactOpts { +// from a single private key. +func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts { + keyAddr := crypto.PubkeyToAddress(key.PublicKey) return &TransactOpts{ - From: key.Address, + From: keyAddr, Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { - if address != key.Address { + if address != keyAddr { return nil, errors.New("not authorized to sign this account") } - signature, err := crypto.Sign(tx.SigHash().Bytes(), key.PrivateKey) + signature, err := crypto.Sign(tx.SigHash().Bytes(), key) if err != nil { return nil, err } diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 3f02af0172..5c36bc48f4 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -167,11 +167,9 @@ var bindTests = []struct { `[{"constant":true,"inputs":[],"name":"transactString","outputs":[{"name":"","type":"string"}],"type":"function"},{"constant":true,"inputs":[],"name":"deployString","outputs":[{"name":"","type":"string"}],"type":"function"},{"constant":false,"inputs":[{"name":"str","type":"string"}],"name":"transact","outputs":[],"type":"function"},{"inputs":[{"name":"str","type":"string"}],"type":"constructor"}]`, ` // Generate a new random account and a funded simulator - key := crypto.NewKey(rand.Reader) - sim := backends.NewSimulatedBackend(core.GenesisAccount{Address: key.Address, Balance: big.NewInt(10000000000)}) - - // Convert the tester key to an authorized transactor for ease of use + key, _ := crypto.GenerateKey() auth := bind.NewKeyedTransactor(key) + sim := backends.NewSimulatedBackend(core.GenesisAccount{Address: auth.From, Balance: big.NewInt(10000000000)}) // Deploy an interaction tester contract and call a transaction on it _, _, interactor, err := DeployInteractor(auth, sim, "Deploy string") @@ -210,11 +208,9 @@ var bindTests = []struct { `[{"constant":true,"inputs":[],"name":"tuple","outputs":[{"name":"a","type":"string"},{"name":"b","type":"int256"},{"name":"c","type":"bytes32"}],"type":"function"}]`, ` // Generate a new random account and a funded simulator - key := crypto.NewKey(rand.Reader) - sim := backends.NewSimulatedBackend(core.GenesisAccount{Address: key.Address, Balance: big.NewInt(10000000000)}) - - // Convert the tester key to an authorized transactor for ease of use + key, _ := crypto.GenerateKey() auth := bind.NewKeyedTransactor(key) + sim := backends.NewSimulatedBackend(core.GenesisAccount{Address: auth.From, Balance: big.NewInt(10000000000)}) // Deploy a tuple tester contract and execute a structured call on it _, _, tupler, err := DeployTupler(auth, sim) @@ -252,11 +248,9 @@ var bindTests = []struct { `[{"constant":true,"inputs":[{"name":"input","type":"address[]"}],"name":"echoAddresses","outputs":[{"name":"output","type":"address[]"}],"type":"function"},{"constant":true,"inputs":[{"name":"input","type":"uint24[23]"}],"name":"echoFancyInts","outputs":[{"name":"output","type":"uint24[23]"}],"type":"function"},{"constant":true,"inputs":[{"name":"input","type":"int256[]"}],"name":"echoInts","outputs":[{"name":"output","type":"int256[]"}],"type":"function"},{"constant":true,"inputs":[{"name":"input","type":"bool[]"}],"name":"echoBools","outputs":[{"name":"output","type":"bool[]"}],"type":"function"}]`, ` // Generate a new random account and a funded simulator - key := crypto.NewKey(rand.Reader) - sim := backends.NewSimulatedBackend(core.GenesisAccount{Address: key.Address, Balance: big.NewInt(10000000000)}) - - // Convert the tester key to an authorized transactor for ease of use + key, _ := crypto.GenerateKey() auth := bind.NewKeyedTransactor(key) + sim := backends.NewSimulatedBackend(core.GenesisAccount{Address: auth.From, Balance: big.NewInt(10000000000)}) // Deploy a slice tester contract and execute a n array call on it _, _, slicer, err := DeploySlicer(auth, sim) @@ -265,10 +259,10 @@ var bindTests = []struct { } sim.Commit() - if out, err := slicer.EchoAddresses(nil, []common.Address{key.Address, common.Address{}}); err != nil { + if out, err := slicer.EchoAddresses(nil, []common.Address{auth.From, common.Address{}}); err != nil { t.Fatalf("Failed to call slice echoer: %v", err) - } else if !reflect.DeepEqual(out, []common.Address{key.Address, common.Address{}}) { - t.Fatalf("Slice return mismatch: have %v, want %v", out, []common.Address{key.Address, common.Address{}}) + } else if !reflect.DeepEqual(out, []common.Address{auth.From, common.Address{}}) { + t.Fatalf("Slice return mismatch: have %v, want %v", out, []common.Address{auth.From, common.Address{}}) } `, }, @@ -288,11 +282,9 @@ var bindTests = []struct { `[{"constant":true,"inputs":[],"name":"caller","outputs":[{"name":"","type":"address"}],"type":"function"}]`, ` // Generate a new random account and a funded simulator - key := crypto.NewKey(rand.Reader) - sim := backends.NewSimulatedBackend(core.GenesisAccount{Address: key.Address, Balance: big.NewInt(10000000000)}) - - // Convert the tester key to an authorized transactor for ease of use + key, _ := crypto.GenerateKey() auth := bind.NewKeyedTransactor(key) + sim := backends.NewSimulatedBackend(core.GenesisAccount{Address: auth.From, Balance: big.NewInt(10000000000)}) // Deploy a default method invoker contract and execute its default method _, _, defaulter, err := DeployDefaulter(auth, sim) @@ -306,8 +298,8 @@ var bindTests = []struct { if caller, err := defaulter.Caller(nil); err != nil { t.Fatalf("Failed to call address retriever: %v", err) - } else if (caller != key.Address) { - t.Fatalf("Address mismatch: have %v, want %v", caller, key.Address) + } else if (caller != auth.From) { + t.Fatalf("Address mismatch: have %v, want %v", caller, auth.From) } `, }, diff --git a/accounts/account_manager.go b/accounts/account_manager.go index 34cf0fa537..c85304066c 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -19,9 +19,6 @@ // This abstracts part of a user's interaction with an account she controls. package accounts -// Currently this is pretty much a passthrough to the KeyStore interface, -// and accounts persistence is derived from stored keys' addresses - import ( "crypto/ecdsa" crand "crypto/rand" @@ -49,19 +46,26 @@ func (acc *Account) MarshalJSON() ([]byte, error) { } type Manager struct { - keyStore crypto.KeyStore + keyStore keyStore unlocked map[common.Address]*unlocked mutex sync.RWMutex } type unlocked struct { - *crypto.Key + *Key abort chan struct{} } -func NewManager(keyStore crypto.KeyStore) *Manager { +func NewManager(keydir string, scryptN, scryptP int) *Manager { return &Manager{ - keyStore: keyStore, + keyStore: newKeyStorePassphrase(keydir, scryptN, scryptP), + unlocked: make(map[common.Address]*unlocked), + } +} + +func NewPlaintextManager(keydir string) *Manager { + return &Manager{ + keyStore: newKeyStorePlain(keydir), unlocked: make(map[common.Address]*unlocked), } } @@ -216,19 +220,23 @@ func (am *Manager) Export(path string, addr common.Address, keyAuth string) erro } func (am *Manager) Import(path string, keyAuth string) (Account, error) { - privateKeyECDSA, err := crypto.LoadECDSA(path) + priv, err := crypto.LoadECDSA(path) if err != nil { return Account{}, err } - key := crypto.NewKeyFromECDSA(privateKeyECDSA) - if err = am.keyStore.StoreKey(key, keyAuth); err != nil { + return am.ImportECDSA(priv, keyAuth) +} + +func (am *Manager) ImportECDSA(priv *ecdsa.PrivateKey, keyAuth string) (Account, error) { + key := newKeyFromECDSA(priv) + if err := am.keyStore.StoreKey(key, keyAuth); err != nil { return Account{}, err } return Account{Address: key.Address}, nil } func (am *Manager) Update(addr common.Address, authFrom, authTo string) (err error) { - var key *crypto.Key + var key *Key key, err = am.keyStore.GetKey(addr, authFrom) if err == nil { @@ -241,8 +249,8 @@ func (am *Manager) Update(addr common.Address, authFrom, authTo string) (err err } func (am *Manager) ImportPreSaleKey(keyJSON []byte, password string) (acc Account, err error) { - var key *crypto.Key - key, err = crypto.ImportPreSaleKey(am.keyStore, keyJSON, password) + var key *Key + key, err = importPreSaleKey(am.keyStore, keyJSON, password) if err != nil { return } diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go index 55ddecdea3..02dd74c8a2 100644 --- a/accounts/accounts_test.go +++ b/accounts/accounts_test.go @@ -21,17 +21,14 @@ import ( "os" "testing" "time" - - "github.com/ethereum/go-ethereum/crypto" ) var testSigData = make([]byte, 32) func TestSign(t *testing.T) { - dir, ks := tmpKeyStore(t, crypto.NewKeyStorePlain) + dir, am := tmpManager(t, false) defer os.RemoveAll(dir) - am := NewManager(ks) pass := "" // not used but required by API a1, err := am.NewAccount(pass) am.Unlock(a1.Address, "") @@ -43,10 +40,9 @@ func TestSign(t *testing.T) { } func TestTimedUnlock(t *testing.T) { - dir, ks := tmpKeyStore(t, crypto.NewKeyStorePlain) + dir, am := tmpManager(t, false) defer os.RemoveAll(dir) - am := NewManager(ks) pass := "foo" a1, err := am.NewAccount(pass) @@ -76,10 +72,9 @@ func TestTimedUnlock(t *testing.T) { } func TestOverrideUnlock(t *testing.T) { - dir, ks := tmpKeyStore(t, crypto.NewKeyStorePlain) + dir, am := tmpManager(t, false) defer os.RemoveAll(dir) - am := NewManager(ks) pass := "foo" a1, err := am.NewAccount(pass) @@ -115,11 +110,10 @@ func TestOverrideUnlock(t *testing.T) { // This test should fail under -race if signing races the expiration goroutine. func TestSignRace(t *testing.T) { - dir, ks := tmpKeyStore(t, crypto.NewKeyStorePlain) + dir, am := tmpManager(t, false) defer os.RemoveAll(dir) // Create a test account. - am := NewManager(ks) a1, err := am.NewAccount("") if err != nil { t.Fatal("could not create the test account", err) @@ -141,10 +135,14 @@ func TestSignRace(t *testing.T) { t.Errorf("Account did not lock within the timeout") } -func tmpKeyStore(t *testing.T, new func(string) crypto.KeyStore) (string, crypto.KeyStore) { +func tmpManager(t *testing.T, encrypted bool) (string, *Manager) { d, err := ioutil.TempDir("", "eth-keystore-test") if err != nil { t.Fatal(err) } + new := NewPlaintextManager + if encrypted { + new = func(kd string) *Manager { return NewManager(kd, LightScryptN, LightScryptP) } + } return d, new(d) } diff --git a/crypto/key.go b/accounts/key.go similarity index 82% rename from crypto/key.go rename to accounts/key.go index 8e2d8553bb..34fefa27cb 100644 --- a/crypto/key.go +++ b/accounts/key.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package crypto +package accounts import ( "bytes" @@ -25,6 +25,7 @@ import ( "strings" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/pborman/uuid" ) @@ -42,6 +43,16 @@ type Key struct { PrivateKey *ecdsa.PrivateKey } +type keyStore interface { + // create new key using io.Reader entropy source and optionally using auth string + GenerateNewKey(io.Reader, string) (*Key, error) + GetKey(common.Address, string) (*Key, error) // get key from addr and auth string + GetKeyAddresses() ([]common.Address, error) // get all addresses + StoreKey(*Key, string) error // store key optionally using auth string + DeleteKey(common.Address, string) error // delete key by addr and auth string + Cleanup(keyAddr common.Address) (err error) +} + type plainKeyJSON struct { Address string `json:"address"` PrivateKey string `json:"privatekey"` @@ -87,7 +98,7 @@ type scryptParamsJSON struct { func (k *Key) MarshalJSON() (j []byte, err error) { jStruct := plainKeyJSON{ hex.EncodeToString(k.Address[:]), - hex.EncodeToString(FromECDSA(k.PrivateKey)), + hex.EncodeToString(crypto.FromECDSA(k.PrivateKey)), k.Id.String(), version, } @@ -116,16 +127,16 @@ func (k *Key) UnmarshalJSON(j []byte) (err error) { } k.Address = common.BytesToAddress(addr) - k.PrivateKey = ToECDSA(privkey) + k.PrivateKey = crypto.ToECDSA(privkey) return nil } -func NewKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key { +func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key { id := uuid.NewRandom() key := &Key{ Id: id, - Address: PubkeyToAddress(privateKeyECDSA.PublicKey), + Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey), PrivateKey: privateKeyECDSA, } return key @@ -143,7 +154,7 @@ func NewKey(rand io.Reader) *Key { panic("key generation: ecdsa.GenerateKey failed: " + err.Error()) } - return NewKeyFromECDSA(privateKeyECDSA) + return newKeyFromECDSA(privateKeyECDSA) } // generate key whose address fits into < 155 bits so it can fit into @@ -160,7 +171,7 @@ func NewKeyForDirectICAP(rand io.Reader) *Key { if err != nil { panic("key generation: ecdsa.GenerateKey failed: " + err.Error()) } - key := NewKeyFromECDSA(privateKeyECDSA) + key := newKeyFromECDSA(privateKeyECDSA) if !strings.HasPrefix(key.Address.Hex(), "0x00") { return NewKeyForDirectICAP(rand) } diff --git a/crypto/key_store_passphrase.go b/accounts/key_store_passphrase.go similarity index 93% rename from crypto/key_store_passphrase.go rename to accounts/key_store_passphrase.go index 19e77de917..cb00b90af3 100644 --- a/crypto/key_store_passphrase.go +++ b/accounts/key_store_passphrase.go @@ -23,7 +23,7 @@ The crypto is documented at https://github.com/ethereum/wiki/wiki/Web3-Secret-St */ -package crypto +package accounts import ( "bytes" @@ -36,6 +36,7 @@ import ( "io" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/randentropy" "github.com/pborman/uuid" "golang.org/x/crypto/pbkdf2" @@ -63,12 +64,12 @@ type keyStorePassphrase struct { scryptP int } -func NewKeyStorePassphrase(path string, scryptN int, scryptP int) KeyStore { +func newKeyStorePassphrase(path string, scryptN int, scryptP int) keyStore { return &keyStorePassphrase{path, scryptN, scryptP} } func (ks keyStorePassphrase) GenerateNewKey(rand io.Reader, auth string) (key *Key, err error) { - return GenerateNewKeyDefault(ks, rand, auth) + return generateNewKeyDefault(ks, rand, auth) } func (ks keyStorePassphrase) GetKey(keyAddr common.Address, auth string) (key *Key, err error) { @@ -101,14 +102,14 @@ func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) { return nil, err } encryptKey := derivedKey[:16] - keyBytes := FromECDSA(key.PrivateKey) + keyBytes := crypto.FromECDSA(key.PrivateKey) iv := randentropy.GetEntropyCSPRNG(aes.BlockSize) // 16 cipherText, err := aesCTRXOR(encryptKey, keyBytes, iv) if err != nil { return nil, err } - mac := Keccak256(derivedKey[16:32], cipherText) + mac := crypto.Keccak256(derivedKey[16:32], cipherText) scryptParamsJSON := make(map[string]interface{}, 5) scryptParamsJSON["n"] = scryptN @@ -175,10 +176,10 @@ func DecryptKey(keyjson []byte, auth string) (*Key, error) { if err != nil { return nil, err } - key := ToECDSA(keyBytes) + key := crypto.ToECDSA(keyBytes) return &Key{ Id: uuid.UUID(keyId), - Address: PubkeyToAddress(key.PublicKey), + Address: crypto.PubkeyToAddress(key.PublicKey), PrivateKey: key, }, nil } @@ -230,7 +231,7 @@ func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byt return nil, nil, err } - calculatedMAC := Keccak256(derivedKey[16:32], cipherText) + calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText) if !bytes.Equal(calculatedMAC, mac) { return nil, nil, errors.New("Decryption failed: MAC mismatch") } @@ -264,12 +265,12 @@ func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byt return nil, nil, err } - calculatedMAC := Keccak256(derivedKey[16:32], cipherText) + calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText) if !bytes.Equal(calculatedMAC, mac) { return nil, nil, errors.New("Decryption failed: MAC mismatch") } - plainText, err := aesCBCDecrypt(Keccak256(derivedKey[:16])[:16], cipherText, iv) + plainText, err := aesCBCDecrypt(crypto.Keccak256(derivedKey[:16])[:16], cipherText, iv) if err != nil { return nil, nil, err } diff --git a/crypto/key_store_passphrase_test.go b/accounts/key_store_passphrase_test.go similarity index 99% rename from crypto/key_store_passphrase_test.go rename to accounts/key_store_passphrase_test.go index bcdd58ad99..afa751d449 100644 --- a/crypto/key_store_passphrase_test.go +++ b/accounts/key_store_passphrase_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package crypto +package accounts import ( "testing" diff --git a/crypto/key_store_plain.go b/accounts/key_store_plain.go similarity index 88% rename from crypto/key_store_plain.go rename to accounts/key_store_plain.go index 4ce789a30e..ca1d897577 100644 --- a/crypto/key_store_plain.go +++ b/accounts/key_store_plain.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package crypto +package accounts import ( "encoding/hex" @@ -29,29 +29,19 @@ import ( "github.com/ethereum/go-ethereum/common" ) -type KeyStore interface { - // create new key using io.Reader entropy source and optionally using auth string - GenerateNewKey(io.Reader, string) (*Key, error) - GetKey(common.Address, string) (*Key, error) // get key from addr and auth string - GetKeyAddresses() ([]common.Address, error) // get all addresses - StoreKey(*Key, string) error // store key optionally using auth string - DeleteKey(common.Address, string) error // delete key by addr and auth string - Cleanup(keyAddr common.Address) (err error) -} - type keyStorePlain struct { keysDirPath string } -func NewKeyStorePlain(path string) KeyStore { +func newKeyStorePlain(path string) keyStore { return &keyStorePlain{path} } func (ks keyStorePlain) GenerateNewKey(rand io.Reader, auth string) (key *Key, err error) { - return GenerateNewKeyDefault(ks, rand, auth) + return generateNewKeyDefault(ks, rand, auth) } -func GenerateNewKeyDefault(ks KeyStore, rand io.Reader, auth string) (key *Key, err error) { +func generateNewKeyDefault(ks keyStore, rand io.Reader, auth string) (key *Key, err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("GenerateNewKey error: %v", r) diff --git a/crypto/key_store_test.go b/accounts/key_store_test.go similarity index 89% rename from crypto/key_store_test.go rename to accounts/key_store_test.go index 5a44a6026e..62ace3720d 100644 --- a/crypto/key_store_test.go +++ b/accounts/key_store_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package crypto +package accounts import ( "encoding/hex" @@ -24,11 +24,12 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/randentropy" ) func TestKeyStorePlain(t *testing.T) { - ks := NewKeyStorePlain(common.DefaultDataDir()) + ks := newKeyStorePlain(common.DefaultDataDir()) pass := "" // not used but required by API k1, err := ks.GenerateNewKey(randentropy.Reader, pass) if err != nil { @@ -56,7 +57,7 @@ func TestKeyStorePlain(t *testing.T) { } func TestKeyStorePassphrase(t *testing.T) { - ks := NewKeyStorePassphrase(common.DefaultDataDir(), LightScryptN, LightScryptP) + ks := newKeyStorePassphrase(common.DefaultDataDir(), LightScryptN, LightScryptP) pass := "foo" k1, err := ks.GenerateNewKey(randentropy.Reader, pass) if err != nil { @@ -82,7 +83,7 @@ func TestKeyStorePassphrase(t *testing.T) { } func TestKeyStorePassphraseDecryptionFail(t *testing.T) { - ks := NewKeyStorePassphrase(common.DefaultDataDir(), LightScryptN, LightScryptP) + ks := newKeyStorePassphrase(common.DefaultDataDir(), LightScryptN, LightScryptP) pass := "foo" k1, err := ks.GenerateNewKey(randentropy.Reader, pass) if err != nil { @@ -110,16 +111,16 @@ func TestImportPreSaleKey(t *testing.T) { // python pyethsaletool.py genwallet // with password "foo" fileContent := "{\"encseed\": \"26d87f5f2bf9835f9a47eefae571bc09f9107bb13d54ff12a4ec095d01f83897494cf34f7bed2ed34126ecba9db7b62de56c9d7cd136520a0427bfb11b8954ba7ac39b90d4650d3448e31185affcd74226a68f1e94b1108e6e0a4a91cdd83eba\", \"ethaddr\": \"d4584b5f6229b7be90727b0fc8c6b91bb427821f\", \"email\": \"gustav.simonsson@gmail.com\", \"btcaddr\": \"1EVknXyFC68kKNLkh6YnKzW41svSRoaAcx\"}" - ks := NewKeyStorePassphrase(common.DefaultDataDir(), LightScryptN, LightScryptP) + ks := newKeyStorePassphrase(common.DefaultDataDir(), LightScryptN, LightScryptP) pass := "foo" - _, err := ImportPreSaleKey(ks, []byte(fileContent), pass) + _, err := importPreSaleKey(ks, []byte(fileContent), pass) if err != nil { t.Fatal(err) } } // Test and utils for the key store tests in the Ethereum JSON tests; -// tests/KeyStoreTests/basic_tests.json +// testdataKeyStoreTests/basic_tests.json type KeyStoreTestV3 struct { Json encryptedKeyJSONV3 Password string @@ -133,7 +134,7 @@ type KeyStoreTestV1 struct { } func TestV3_PBKDF2_1(t *testing.T) { - tests := loadKeyStoreTestV3("tests/v3_test_vector.json", t) + tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t) testDecryptV3(tests["wikipage_test_vector_pbkdf2"], t) } @@ -153,7 +154,7 @@ func TestV3_PBKDF2_4(t *testing.T) { } func TestV3_Scrypt_1(t *testing.T) { - tests := loadKeyStoreTestV3("tests/v3_test_vector.json", t) + tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t) testDecryptV3(tests["wikipage_test_vector_scrypt"], t) } @@ -163,12 +164,12 @@ func TestV3_Scrypt_2(t *testing.T) { } func TestV1_1(t *testing.T) { - tests := loadKeyStoreTestV1("tests/v1_test_vector.json", t) + tests := loadKeyStoreTestV1("testdata/v1_test_vector.json", t) testDecryptV1(tests["test1"], t) } func TestV1_2(t *testing.T) { - ks := NewKeyStorePassphrase("tests/v1", LightScryptN, LightScryptP) + ks := newKeyStorePassphrase("testdata/v1", LightScryptN, LightScryptP) addr := common.HexToAddress("cb61d5a9c4896fb9658090b597ef0e7be6f7b67e") k, err := ks.GetKey(addr, "g") if err != nil { @@ -178,7 +179,7 @@ func TestV1_2(t *testing.T) { t.Fatal(fmt.Errorf("Unexpected address: %v, expected %v", k.Address, addr)) } - privHex := hex.EncodeToString(FromECDSA(k.PrivateKey)) + privHex := hex.EncodeToString(crypto.FromECDSA(k.PrivateKey)) expectedHex := "d1b1178d3529626a1a93e073f65028370d14c7eb0936eb42abef05db6f37ad7d" if privHex != expectedHex { t.Fatal(fmt.Errorf("Unexpected privkey: %v, expected %v", privHex, expectedHex)) diff --git a/accounts/presale.go b/accounts/presale.go new file mode 100644 index 0000000000..8faa985585 --- /dev/null +++ b/accounts/presale.go @@ -0,0 +1,132 @@ +// 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 accounts + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/pborman/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) (*Key, error) { + key, err := decryptPreSaleKey(keyJSON, password) + if err != nil { + return nil, err + } + key.Id = uuid.NewRandom() + err = keyStore.StoreKey(key, password) + return 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) + 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.ToECDSA(ethPriv) + key = &Key{ + Id: nil, + 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 { + err = errors.New("Decryption failed: PKCS7Unpad failed after AES decryption") + } + 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/crypto/tests/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e b/accounts/testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e similarity index 100% rename from crypto/tests/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e rename to accounts/testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e diff --git a/crypto/tests/v1_test_vector.json b/accounts/testdata/v1_test_vector.json similarity index 100% rename from crypto/tests/v1_test_vector.json rename to accounts/testdata/v1_test_vector.json diff --git a/crypto/tests/v3_test_vector.json b/accounts/testdata/v3_test_vector.json similarity index 100% rename from crypto/tests/v3_test_vector.json rename to accounts/testdata/v3_test_vector.json diff --git a/cmd/geth/js_test.go b/cmd/geth/js_test.go index efdb9ab86c..77e40bb9a8 100644 --- a/cmd/geth/js_test.go +++ b/cmd/geth/js_test.go @@ -42,18 +42,17 @@ import ( const ( testSolcPath = "" solcVersion = "0.9.23" - - testKey = "e6fab74a43941f82d89cb7faa408e227cdad3153c4720e540e855c19b15e6674" - testAddress = "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" - testBalance = "10000000000000000000" + testAddress = "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" + testBalance = "10000000000000000000" // of empty string testHash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" ) var ( - versionRE = regexp.MustCompile(strconv.Quote(`"compilerVersion":"` + solcVersion + `"`)) - testNodeKey = crypto.ToECDSA(common.Hex2Bytes("4b50fa71f5c3eeb8fdc452224b2395af2fcc3d125e06c32c82e048c0559db03f")) - testGenesis = `{"` + testAddress[2:] + `": {"balance": "` + testBalance + `"}}` + versionRE = regexp.MustCompile(strconv.Quote(`"compilerVersion":"` + solcVersion + `"`)) + testNodeKey, _ = crypto.HexToECDSA("4b50fa71f5c3eeb8fdc452224b2395af2fcc3d125e06c32c82e048c0559db03f") + testAccount, _ = crypto.HexToECDSA("e6fab74a43941f82d89cb7faa408e227cdad3153c4720e540e855c19b15e6674") + testGenesis = `{"` + testAddress[2:] + `": {"balance": "` + testBalance + `"}}` ) type testjethre struct { @@ -99,12 +98,9 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *nod t.Fatalf("failed to create node: %v", err) } // Initialize and register the Ethereum protocol - keystore := crypto.NewKeyStorePlain(filepath.Join(tmp, "keystore")) - accman := accounts.NewManager(keystore) - + accman := accounts.NewPlaintextManager(filepath.Join(tmp, "keystore")) db, _ := ethdb.NewMemDatabase() core.WriteGenesisBlockForTesting(db, core.GenesisAccount{common.HexToAddress(testAddress), common.String2Big(testBalance)}) - ethConf := ð.Config{ ChainConfig: &core.ChainConfig{HomesteadBlock: new(big.Int)}, TestGenesisState: db, @@ -122,15 +118,11 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *nod t.Fatalf("failed to register ethereum protocol: %v", err) } // Initialize all the keys for testing - keyb, err := crypto.HexToECDSA(testKey) + a, err := accman.ImportECDSA(testAccount, "") if err != nil { t.Fatal(err) } - key := crypto.NewKeyFromECDSA(keyb) - if err := keystore.StoreKey(key, ""); err != nil { - t.Fatal(err) - } - if err := accman.Unlock(key.Address, ""); err != nil { + if err := accman.Unlock(a.Address, ""); err != nil { t.Fatal(err) } // Start the node and assemble the REPL tester diff --git a/cmd/gethrpctest/main.go b/cmd/gethrpctest/main.go index b8b92a5098..e203b75a14 100644 --- a/cmd/gethrpctest/main.go +++ b/cmd/gethrpctest/main.go @@ -106,18 +106,17 @@ func MakeSystemNode(keydir string, privkey string, test *tests.BlockTest) (*node return nil, err } // Create the keystore and inject an unlocked account if requested - keystore := crypto.NewKeyStorePassphrase(keydir, crypto.StandardScryptN, crypto.StandardScryptP) - accman := accounts.NewManager(keystore) - + accman := accounts.NewPlaintextManager(keydir) if len(privkey) > 0 { key, err := crypto.HexToECDSA(privkey) if err != nil { return nil, err } - if err := keystore.StoreKey(crypto.NewKeyFromECDSA(key), ""); err != nil { + a, err := accman.ImportECDSA(key, "") + if err != nil { return nil, err } - if err := accman.Unlock(crypto.NewKeyFromECDSA(key).Address, ""); err != nil { + if err := accman.Unlock(a.Address, ""); err != nil { return nil, err } } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 1d70245ab6..c87c2f76eb 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -551,20 +551,15 @@ func MakeDatabaseHandles() int { // MakeAccountManager creates an account manager from set command line flags. func MakeAccountManager(ctx *cli.Context) *accounts.Manager { // Create the keystore crypto primitive, light if requested - scryptN := crypto.StandardScryptN - scryptP := crypto.StandardScryptP - + scryptN := accounts.StandardScryptN + scryptP := accounts.StandardScryptP if ctx.GlobalBool(LightKDFFlag.Name) { - scryptN = crypto.LightScryptN - scryptP = crypto.LightScryptP + scryptN = accounts.LightScryptN + scryptP = accounts.LightScryptP } - // Assemble an account manager using the configured datadir - var ( - datadir = MustMakeDataDir(ctx) - keystoredir = MakeKeyStoreDir(datadir, ctx) - keystore = crypto.NewKeyStorePassphrase(keystoredir, scryptN, scryptP) - ) - return accounts.NewManager(keystore) + datadir := MustMakeDataDir(ctx) + keydir := MakeKeyStoreDir(datadir, ctx) + return accounts.NewManager(keydir, scryptN, scryptP) } // MakeAddress converts an account specified directly as a hex encoded string or diff --git a/crypto/crypto.go b/crypto/crypto.go index cd0e4e1014..b24d080109 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -17,8 +17,6 @@ package crypto import ( - "crypto/aes" - "crypto/cipher" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -30,7 +28,6 @@ import ( "os" "encoding/hex" - "encoding/json" "errors" "github.com/ethereum/go-ethereum/common" @@ -38,8 +35,6 @@ import ( "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/ethereum/go-ethereum/crypto/sha3" "github.com/ethereum/go-ethereum/rlp" - "github.com/pborman/uuid" - "golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/ripemd160" ) @@ -217,107 +212,6 @@ func Decrypt(prv *ecdsa.PrivateKey, ct []byte) ([]byte, error) { return key.Decrypt(rand.Reader, ct, nil, nil) } -// creates a Key and stores that in the given KeyStore by decrypting a presale key JSON -func ImportPreSaleKey(keyStore KeyStore, keyJSON []byte, password string) (*Key, error) { - key, err := decryptPreSaleKey(keyJSON, password) - if err != nil { - return nil, err - } - key.Id = uuid.NewRandom() - err = keyStore.StoreKey(key, password) - return 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) - 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 := Keccak256(plainText) - ecKey := ToECDSA(ethPriv) - key = &Key{ - Id: nil, - Address: 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 -} - -// AES-128 is selected due to size of encryptKey -func aesCTRXOR(key, inText, iv []byte) ([]byte, error) { - 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 { - err = errors.New("Decryption failed: PKCS7Unpad failed after AES decryption") - } - 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)] -} - func PubkeyToAddress(p ecdsa.PublicKey) common.Address { pubBytes := FromECDSAPub(&p) return common.BytesToAddress(Keccak256(pubBytes[1:])[12:]) diff --git a/eth/helper_test.go b/eth/helper_test.go index 13de186705..d6392f531b 100644 --- a/eth/helper_test.go +++ b/eth/helper_test.go @@ -4,6 +4,7 @@ package eth import ( + "crypto/ecdsa" "crypto/rand" "math/big" "sync" @@ -94,10 +95,9 @@ func (p *testTxPool) GetTransactions() types.Transactions { } // newTestTransaction create a new dummy transaction. -func newTestTransaction(from *crypto.Key, nonce uint64, datasize int) *types.Transaction { +func newTestTransaction(from *ecdsa.PrivateKey, nonce uint64, datasize int) *types.Transaction { tx := types.NewTransaction(nonce, common.Address{}, big.NewInt(0), big.NewInt(100000), big.NewInt(0), make([]byte, datasize)) - tx, _ = tx.SignECDSA(from.PrivateKey) - + tx, _ = tx.SignECDSA(from) return tx } diff --git a/eth/protocol_test.go b/eth/protocol_test.go index 372c7e2036..cac3657e7a 100644 --- a/eth/protocol_test.go +++ b/eth/protocol_test.go @@ -17,7 +17,6 @@ package eth import ( - "crypto/rand" "fmt" "sync" "testing" @@ -35,7 +34,7 @@ func init() { // glog.SetV(6) } -var testAccount = crypto.NewKey(rand.Reader) +var testAccount, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") // Tests that handshake failures are detected and reported correctly. func TestStatusMsgErrors61(t *testing.T) { testStatusMsgErrors(t, 61) }