aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFelix Lange <fjl@twurst.com>2016-03-02 20:57:15 +0800
committerFelix Lange <fjl@twurst.com>2016-04-12 21:56:49 +0800
commit85e6c40c0081bd0db80448640db648887804010c (patch)
tree326a2c3bc115a445b481624cb20f00b28e44f92a
parentdff9b4246f3ef9e6c254b57eef6d0433809f16b9 (diff)
downloadgo-tangerine-85e6c40c0081bd0db80448640db648887804010c.tar.gz
go-tangerine-85e6c40c0081bd0db80448640db648887804010c.tar.zst
go-tangerine-85e6c40c0081bd0db80448640db648887804010c.zip
accounts, crypto: move keystore to package accounts
The account management API was originally implemented as a thin layer around crypto.KeyStore, on the grounds that several kinds of key stores would be implemented later on. It turns out that this won't happen so KeyStore is a superflous abstraction. In this commit crypto.KeyStore and everything related to it moves to package accounts and is unexported.
-rw-r--r--accounts/abi/bind/auth.go17
-rw-r--r--accounts/abi/bind/bind_test.go34
-rw-r--r--accounts/account_manager.go34
-rw-r--r--accounts/accounts_test.go20
-rw-r--r--accounts/key.go (renamed from crypto/key.go)25
-rw-r--r--accounts/key_store_passphrase.go (renamed from crypto/key_store_passphrase.go)21
-rw-r--r--accounts/key_store_passphrase_test.go (renamed from crypto/key_store_passphrase_test.go)2
-rw-r--r--accounts/key_store_plain.go (renamed from crypto/key_store_plain.go)18
-rw-r--r--accounts/key_store_test.go (renamed from crypto/key_store_test.go)25
-rw-r--r--accounts/presale.go132
-rw-r--r--accounts/testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e (renamed from crypto/tests/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e)0
-rw-r--r--accounts/testdata/v1_test_vector.json (renamed from crypto/tests/v1_test_vector.json)0
-rw-r--r--accounts/testdata/v3_test_vector.json (renamed from crypto/tests/v3_test_vector.json)0
-rw-r--r--cmd/geth/js_test.go26
-rw-r--r--cmd/gethrpctest/main.go9
-rw-r--r--cmd/utils/flags.go19
-rw-r--r--crypto/crypto.go106
-rw-r--r--eth/helper_test.go6
-rw-r--r--eth/protocol_test.go3
19 files changed, 256 insertions, 241 deletions
diff --git a/accounts/abi/bind/auth.go b/accounts/abi/bind/auth.go
index 624f995b0..2cf22768c 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 3f02af017..5c36bc48f 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 34cf0fa53..c85304066 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: newKeyStorePassphrase(keydir, scryptN, scryptP),
+ unlocked: make(map[common.Address]*unlocked),
+ }
+}
+
+func NewPlaintextManager(keydir string) *Manager {
return &Manager{
- keyStore: keyStore,
+ 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 55ddecdea..02dd74c8a 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
index 8e2d8553b..34fefa27c 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 <http://www.gnu.org/licenses/>.
-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
index 19e77de91..cb00b90af 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
index bcdd58ad9..afa751d44 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 <http://www.gnu.org/licenses/>.
-package crypto
+package accounts
import (
"testing"
diff --git a/crypto/key_store_plain.go b/accounts/key_store_plain.go
index 4ce789a30..ca1d89757 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 <http://www.gnu.org/licenses/>.
-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
index 5a44a6026..62ace3720 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 <http://www.gnu.org/licenses/>.
-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 000000000..8faa98558
--- /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 <http://www.gnu.org/licenses/>.
+
+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
index 498d8131e..498d8131e 100644
--- a/crypto/tests/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e
+++ b/accounts/testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e
diff --git a/crypto/tests/v1_test_vector.json b/accounts/testdata/v1_test_vector.json
index 3d09b55b5..3d09b55b5 100644
--- a/crypto/tests/v1_test_vector.json
+++ b/accounts/testdata/v1_test_vector.json
diff --git a/crypto/tests/v3_test_vector.json b/accounts/testdata/v3_test_vector.json
index e9d7b62f0..e9d7b62f0 100644
--- a/crypto/tests/v3_test_vector.json
+++ b/accounts/testdata/v3_test_vector.json
diff --git a/cmd/geth/js_test.go b/cmd/geth/js_test.go
index efdb9ab86..77e40bb9a 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 := &eth.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 b8b92a509..e203b75a1 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 1d70245ab..c87c2f76e 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 cd0e4e101..b24d08010 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 13de18670..d6392f531 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 372c7e203..cac3657e7 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) }