Add accounts package and refactor key stores
* Add initial UserAccount and AccountManager structs * Add NewAccount, Sign and Accounts functions * Refactor key stores to use key address as main identifier while keeping the UUID. * Use key address as file/dir names instead of UUID
This commit is contained in:
		
							parent
							
								
									d792e95c21
								
							
						
					
					
						commit
						512ffa2bf4
					
				
							
								
								
									
										99
									
								
								accounts/account_manager.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								accounts/account_manager.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,99 @@ | ||||
| /* | ||||
| 	This file is part of go-ethereum | ||||
| 
 | ||||
| 	go-ethereum 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. | ||||
| 
 | ||||
| 	go-ethereum 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 General Public License for more details. | ||||
| 
 | ||||
| 	You should have received a copy of the GNU Lesser General Public License | ||||
| 	along with go-ethereum.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| /** | ||||
|  * @authors | ||||
|  * 	Gustav Simonsson <gustav.simonsson@gmail.com> | ||||
|  * @date 2015 | ||||
|  * | ||||
|  */ | ||||
| /* | ||||
| 
 | ||||
| This abstracts part of a user's interaction with an account she controls. | ||||
| It's not an abstraction of core Ethereum accounts data type / logic - | ||||
| for that see the core processing code of blocks / txs. | ||||
| 
 | ||||
| Currently this is pretty much a passthrough to the KeyStore2 interface, | ||||
| and accounts persistence is derived from stored keys' addresses | ||||
| 
 | ||||
| */ | ||||
| package accounts | ||||
| 
 | ||||
| import ( | ||||
| 	crand "crypto/rand" | ||||
| 	"github.com/ethereum/go-ethereum/crypto" | ||||
| 	"github.com/ethereum/go-ethereum/crypto/secp256k1" | ||||
| ) | ||||
| 
 | ||||
| // TODO: better name for this struct?
 | ||||
| type UserAccount struct { | ||||
| 	Addr []byte | ||||
| } | ||||
| 
 | ||||
| type AccountManager struct { | ||||
| 	keyStore crypto.KeyStore2 | ||||
| } | ||||
| 
 | ||||
| // TODO: get key by addr - modify KeyStore2 GetKey to work with addr
 | ||||
| 
 | ||||
| // TODO: pass through passphrase for APIs which require access to private key?
 | ||||
| func NewAccountManager(keyStore crypto.KeyStore2) AccountManager { | ||||
| 	am := &AccountManager{ | ||||
| 		keyStore: keyStore, | ||||
| 	} | ||||
| 	return *am | ||||
| } | ||||
| 
 | ||||
| func (am *AccountManager) Sign(fromAddr []byte, keyAuth string, toSign []byte) (signature []byte, err error) { | ||||
| 	key, err := am.keyStore.GetKey(fromAddr, keyAuth) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	privKey := crypto.FromECDSA(key.PrivateKey) | ||||
| 	// TODO: what is second value?
 | ||||
| 	signature, err = secp256k1.Sign(toSign, privKey) | ||||
| 	return signature, err | ||||
| } | ||||
| 
 | ||||
| func (am AccountManager) NewAccount(auth string) (*UserAccount, error) { | ||||
| 	key, err := am.keyStore.GenerateNewKey(crand.Reader, auth) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	ua := &UserAccount{ | ||||
| 		Addr: key.Address, | ||||
| 	} | ||||
| 	return ua, err | ||||
| } | ||||
| 
 | ||||
| // set of accounts == set of keys in given key store
 | ||||
| // TODO: do we need persistence of accounts as well?
 | ||||
| func (am *AccountManager) Accounts() ([]UserAccount, error) { | ||||
| 	addresses, err := am.keyStore.GetKeyAddresses() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	accounts := make([]UserAccount, len(addresses)) | ||||
| 
 | ||||
| 	for i, addr := range addresses { | ||||
| 		ua := &UserAccount{ | ||||
| 			Addr: addr, | ||||
| 		} | ||||
| 		accounts[i] = *ua | ||||
| 	} | ||||
| 	return accounts, err | ||||
| } | ||||
							
								
								
									
										19
									
								
								accounts/accounts_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								accounts/accounts_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | ||||
| package accounts | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/ethereum/go-ethereum/crypto" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| func TestAccountManager(t *testing.T) { | ||||
| 	ks := crypto.NewKeyStorePlain(crypto.DefaultDataDir()) | ||||
| 	am := NewAccountManager(ks) | ||||
| 	pass := "" // not used but required by API
 | ||||
| 	a1, err := am.NewAccount(pass) | ||||
| 	toSign := make([]byte, 4, 4) | ||||
| 	toSign = []byte{0, 1, 2, 3} | ||||
| 	_, err = am.Sign(a1.Addr, pass, toSign) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| } | ||||
| @ -134,7 +134,7 @@ func ImportPreSaleKey(keyStore KeyStore2, keyJSON []byte, password string) (*Key | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	id := uuid.NewRandom() | ||||
| 	key.Id = &id | ||||
| 	key.Id = id | ||||
| 	err = keyStore.StoreKey(key, password) | ||||
| 	return key, err | ||||
| } | ||||
| @ -167,9 +167,10 @@ func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error | ||||
| 	ecKey := ToECDSA(ethPriv) | ||||
| 	key = &Key{ | ||||
| 		Id:         nil, | ||||
| 		Address:    pubkeyToAddress(ecKey.PublicKey), | ||||
| 		PrivateKey: ecKey, | ||||
| 	} | ||||
| 	derivedAddr := ethutil.Bytes2Hex(key.Address()) | ||||
| 	derivedAddr := ethutil.Bytes2Hex(key.Address) | ||||
| 	expectedAddr := preSaleKeyStruct.EthAddr | ||||
| 	if derivedAddr != expectedAddr { | ||||
| 		err = errors.New("decrypted addr not equal to expected addr") | ||||
| @ -223,3 +224,8 @@ func PKCS7Unpad(in []byte) []byte { | ||||
| 	} | ||||
| 	return in[:len(in)-int(padding)] | ||||
| } | ||||
| 
 | ||||
| func pubkeyToAddress(p ecdsa.PublicKey) []byte { | ||||
| 	pubBytes := FromECDSAPub(&p) | ||||
| 	return Sha3(pubBytes[1:])[12:] | ||||
| } | ||||
|  | ||||
| @ -33,7 +33,9 @@ import ( | ||||
| ) | ||||
| 
 | ||||
| type Key struct { | ||||
| 	Id *uuid.UUID // Version 4 "random" for unique id not derived from key data
 | ||||
| 	Id uuid.UUID // Version 4 "random" for unique id not derived from key data
 | ||||
| 	// to simplify lookups we also store the address
 | ||||
| 	Address []byte | ||||
| 	// we only store privkey as pubkey/address can be derived from it
 | ||||
| 	// privkey in this struct is always in plaintext
 | ||||
| 	PrivateKey *ecdsa.PrivateKey | ||||
| @ -41,6 +43,7 @@ type Key struct { | ||||
| 
 | ||||
| type plainKeyJSON struct { | ||||
| 	Id         []byte | ||||
| 	Address    []byte | ||||
| 	PrivateKey []byte | ||||
| } | ||||
| 
 | ||||
| @ -51,18 +54,15 @@ type cipherJSON struct { | ||||
| } | ||||
| 
 | ||||
| type encryptedKeyJSON struct { | ||||
| 	Id     []byte | ||||
| 	Crypto cipherJSON | ||||
| } | ||||
| 
 | ||||
| func (k *Key) Address() []byte { | ||||
| 	pubBytes := FromECDSAPub(&k.PrivateKey.PublicKey) | ||||
| 	return Sha3(pubBytes[1:])[12:] | ||||
| 	Id      []byte | ||||
| 	Address []byte | ||||
| 	Crypto  cipherJSON | ||||
| } | ||||
| 
 | ||||
| func (k *Key) MarshalJSON() (j []byte, err error) { | ||||
| 	jStruct := plainKeyJSON{ | ||||
| 		*k.Id, | ||||
| 		k.Id, | ||||
| 		k.Address, | ||||
| 		FromECDSA(k.PrivateKey), | ||||
| 	} | ||||
| 	j, err = json.Marshal(jStruct) | ||||
| @ -78,8 +78,8 @@ func (k *Key) UnmarshalJSON(j []byte) (err error) { | ||||
| 
 | ||||
| 	u := new(uuid.UUID) | ||||
| 	*u = keyJSON.Id | ||||
| 	k.Id = u | ||||
| 
 | ||||
| 	k.Id = *u | ||||
| 	k.Address = keyJSON.Address | ||||
| 	k.PrivateKey = ToECDSA(keyJSON.PrivateKey) | ||||
| 
 | ||||
| 	return err | ||||
| @ -101,7 +101,8 @@ func NewKey(rand io.Reader) *Key { | ||||
| 
 | ||||
| 	id := uuid.NewRandom() | ||||
| 	key := &Key{ | ||||
| 		Id:         &id, | ||||
| 		Id:         id, | ||||
| 		Address:    pubkeyToAddress(privateKeyECDSA.PublicKey), | ||||
| 		PrivateKey: privateKeyECDSA, | ||||
| 	} | ||||
| 	return key | ||||
|  | ||||
| @ -69,6 +69,7 @@ import ( | ||||
| 	"crypto/aes" | ||||
| 	"crypto/cipher" | ||||
| 	crand "crypto/rand" | ||||
| 	"encoding/hex" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"io" | ||||
| @ -96,18 +97,23 @@ func (ks keyStorePassphrase) GenerateNewKey(rand io.Reader, auth string) (key *K | ||||
| 	return GenerateNewKeyDefault(ks, rand, auth) | ||||
| } | ||||
| 
 | ||||
| func (ks keyStorePassphrase) GetKey(keyId *uuid.UUID, auth string) (key *Key, err error) { | ||||
| 	keyBytes, err := DecryptKey(ks, keyId, auth) | ||||
| func (ks keyStorePassphrase) GetKey(keyAddr []byte, auth string) (key *Key, err error) { | ||||
| 	keyBytes, keyId, err := DecryptKey(ks, keyAddr, auth) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	key = &Key{ | ||||
| 		Id:         keyId, | ||||
| 		Id:         uuid.UUID(keyId), | ||||
| 		Address:    keyAddr, | ||||
| 		PrivateKey: ToECDSA(keyBytes), | ||||
| 	} | ||||
| 	return key, err | ||||
| } | ||||
| 
 | ||||
| func (ks keyStorePassphrase) GetKeyAddresses() (addresses [][]byte, err error) { | ||||
| 	return GetKeyAddresses(ks.keysDirPath) | ||||
| } | ||||
| 
 | ||||
| func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) { | ||||
| 	authArray := []byte(auth) | ||||
| 	salt := getEntropyCSPRNG(32) | ||||
| @ -136,7 +142,8 @@ func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) { | ||||
| 		cipherText, | ||||
| 	} | ||||
| 	keyStruct := encryptedKeyJSON{ | ||||
| 		*key.Id, | ||||
| 		key.Id, | ||||
| 		key.Address, | ||||
| 		cipherStruct, | ||||
| 	} | ||||
| 	keyJSON, err := json.Marshal(keyStruct) | ||||
| @ -144,51 +151,50 @@ func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	return WriteKeyFile(key.Id.String(), ks.keysDirPath, keyJSON) | ||||
| 	return WriteKeyFile(key.Address, ks.keysDirPath, keyJSON) | ||||
| } | ||||
| 
 | ||||
| func (ks keyStorePassphrase) DeleteKey(keyId *uuid.UUID, auth string) (err error) { | ||||
| func (ks keyStorePassphrase) DeleteKey(keyAddr []byte, auth string) (err error) { | ||||
| 	// only delete if correct passphrase is given
 | ||||
| 	_, err = DecryptKey(ks, keyId, auth) | ||||
| 	_, _, err = DecryptKey(ks, keyAddr, auth) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	keyDirPath := path.Join(ks.keysDirPath, keyId.String()) | ||||
| 	keyDirPath := path.Join(ks.keysDirPath, hex.EncodeToString(keyAddr)) | ||||
| 	return os.RemoveAll(keyDirPath) | ||||
| } | ||||
| 
 | ||||
| func DecryptKey(ks keyStorePassphrase, keyId *uuid.UUID, auth string) (keyBytes []byte, err error) { | ||||
| 	fileContent, err := GetKeyFile(ks.keysDirPath, keyId) | ||||
| func DecryptKey(ks keyStorePassphrase, keyAddr []byte, auth string) (keyBytes []byte, keyId []byte, err error) { | ||||
| 	fileContent, err := GetKeyFile(ks.keysDirPath, keyAddr) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	keyProtected := new(encryptedKeyJSON) | ||||
| 	err = json.Unmarshal(fileContent, keyProtected) | ||||
| 
 | ||||
| 	keyId = keyProtected.Id | ||||
| 	salt := keyProtected.Crypto.Salt | ||||
| 
 | ||||
| 	iv := keyProtected.Crypto.IV | ||||
| 
 | ||||
| 	cipherText := keyProtected.Crypto.CipherText | ||||
| 
 | ||||
| 	authArray := []byte(auth) | ||||
| 	derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptr, scryptp, scryptdkLen) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	plainText, err := aesCBCDecrypt(derivedKey, cipherText, iv) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	keyBytes = plainText[:len(plainText)-32] | ||||
| 	keyBytesHash := plainText[len(plainText)-32:] | ||||
| 	if !bytes.Equal(Sha3(keyBytes), keyBytesHash) { | ||||
| 		err = errors.New("Decryption failed: checksum mismatch") | ||||
| 		return nil, err | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 	return keyBytes, err | ||||
| 	return keyBytes, keyId, err | ||||
| } | ||||
| 
 | ||||
| func getEntropyCSPRNG(n int) []byte { | ||||
|  | ||||
| @ -24,7 +24,7 @@ | ||||
| package crypto | ||||
| 
 | ||||
| import ( | ||||
| 	"code.google.com/p/go-uuid/uuid" | ||||
| 	"encoding/hex" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| @ -38,9 +38,10 @@ import ( | ||||
| type KeyStore2 interface { | ||||
| 	// create new key using io.Reader entropy source and optionally using auth string
 | ||||
| 	GenerateNewKey(io.Reader, string) (*Key, error) | ||||
| 	GetKey(*uuid.UUID, string) (*Key, error) // key from id and auth string
 | ||||
| 	StoreKey(*Key, string) error             // store key optionally using auth string
 | ||||
| 	DeleteKey(*uuid.UUID, string) error      // delete key by id and auth string
 | ||||
| 	GetKey([]byte, string) (*Key, error) // key from addr and auth string
 | ||||
| 	GetKeyAddresses() ([][]byte, error)  // get all addresses
 | ||||
| 	StoreKey(*Key, string) error         // store key optionally using auth string
 | ||||
| 	DeleteKey([]byte, string) error      // delete key by addr and auth string
 | ||||
| } | ||||
| 
 | ||||
| type keyStorePlain struct { | ||||
| @ -72,8 +73,8 @@ func GenerateNewKeyDefault(ks KeyStore2, rand io.Reader, auth string) (key *Key, | ||||
| 	return key, err | ||||
| } | ||||
| 
 | ||||
| func (ks keyStorePlain) GetKey(keyId *uuid.UUID, auth string) (key *Key, err error) { | ||||
| 	fileContent, err := GetKeyFile(ks.keysDirPath, keyId) | ||||
| func (ks keyStorePlain) GetKey(keyAddr []byte, auth string) (key *Key, err error) { | ||||
| 	fileContent, err := GetKeyFile(ks.keysDirPath, keyAddr) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @ -83,32 +84,50 @@ func (ks keyStorePlain) GetKey(keyId *uuid.UUID, auth string) (key *Key, err err | ||||
| 	return key, err | ||||
| } | ||||
| 
 | ||||
| func (ks keyStorePlain) GetKeyAddresses() (addresses [][]byte, err error) { | ||||
| 	return GetKeyAddresses(ks.keysDirPath) | ||||
| } | ||||
| 
 | ||||
| func (ks keyStorePlain) StoreKey(key *Key, auth string) (err error) { | ||||
| 	keyJSON, err := json.Marshal(key) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	err = WriteKeyFile(key.Id.String(), ks.keysDirPath, keyJSON) | ||||
| 	err = WriteKeyFile(key.Address, ks.keysDirPath, keyJSON) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (ks keyStorePlain) DeleteKey(keyId *uuid.UUID, auth string) (err error) { | ||||
| 	keyDirPath := path.Join(ks.keysDirPath, keyId.String()) | ||||
| func (ks keyStorePlain) DeleteKey(keyAddr []byte, auth string) (err error) { | ||||
| 	keyDirPath := path.Join(ks.keysDirPath, hex.EncodeToString(keyAddr)) | ||||
| 	err = os.RemoveAll(keyDirPath) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func GetKeyFile(keysDirPath string, keyId *uuid.UUID) (fileContent []byte, err error) { | ||||
| 	id := keyId.String() | ||||
| 	return ioutil.ReadFile(path.Join(keysDirPath, id, id)) | ||||
| func GetKeyFile(keysDirPath string, keyAddr []byte) (fileContent []byte, err error) { | ||||
| 	fileName := hex.EncodeToString(keyAddr) | ||||
| 	return ioutil.ReadFile(path.Join(keysDirPath, fileName, fileName)) | ||||
| } | ||||
| 
 | ||||
| func WriteKeyFile(id string, keysDirPath string, content []byte) (err error) { | ||||
| 	keyDirPath := path.Join(keysDirPath, id) | ||||
| 	keyFilePath := path.Join(keyDirPath, id) | ||||
| func WriteKeyFile(addr []byte, keysDirPath string, content []byte) (err error) { | ||||
| 	addrHex := hex.EncodeToString(addr) | ||||
| 	keyDirPath := path.Join(keysDirPath, addrHex) | ||||
| 	keyFilePath := path.Join(keyDirPath, addrHex) | ||||
| 	err = os.MkdirAll(keyDirPath, 0700) // read, write and dir search for user
 | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return ioutil.WriteFile(keyFilePath, content, 0600) // read, write for user
 | ||||
| } | ||||
| 
 | ||||
| func GetKeyAddresses(keysDirPath string) (addresses [][]byte, err error) { | ||||
| 	fileInfos, err := ioutil.ReadDir(keysDirPath) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	addresses = make([][]byte, len(fileInfos)) | ||||
| 	for i, fileInfo := range fileInfos { | ||||
| 		addresses[i] = make([]byte, 40) | ||||
| 		addresses[i] = []byte(fileInfo.Name()) | ||||
| 	} | ||||
| 	return addresses, err | ||||
| } | ||||
|  | ||||
| @ -15,12 +15,12 @@ func TestKeyStorePlain(t *testing.T) { | ||||
| 	} | ||||
| 
 | ||||
| 	k2 := new(Key) | ||||
| 	k2, err = ks.GetKey(k1.Id, pass) | ||||
| 	k2, err = ks.GetKey(k1.Address, pass) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	if !reflect.DeepEqual(k1.Id, k2.Id) { | ||||
| 	if !reflect.DeepEqual(k1.Address, k2.Address) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| @ -28,7 +28,7 @@ func TestKeyStorePlain(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = ks.DeleteKey(k2.Id, pass) | ||||
| 	err = ks.DeleteKey(k2.Address, pass) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -42,11 +42,11 @@ func TestKeyStorePassphrase(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	k2 := new(Key) | ||||
| 	k2, err = ks.GetKey(k1.Id, pass) | ||||
| 	k2, err = ks.GetKey(k1.Address, pass) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if !reflect.DeepEqual(k1.Id, k2.Id) { | ||||
| 	if !reflect.DeepEqual(k1.Address, k2.Address) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| @ -54,7 +54,7 @@ func TestKeyStorePassphrase(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = ks.DeleteKey(k2.Id, pass) // also to clean up created files
 | ||||
| 	err = ks.DeleteKey(k2.Address, pass) // also to clean up created files
 | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| @ -68,17 +68,17 @@ func TestKeyStorePassphraseDecryptionFail(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	_, err = ks.GetKey(k1.Id, "bar") // wrong passphrase
 | ||||
| 	_, err = ks.GetKey(k1.Address, "bar") // wrong passphrase
 | ||||
| 	if err == nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = ks.DeleteKey(k1.Id, "bar") // wrong passphrase
 | ||||
| 	err = ks.DeleteKey(k1.Address, "bar") // wrong passphrase
 | ||||
| 	if err == nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = ks.DeleteKey(k1.Id, pass) // to clean up
 | ||||
| 	err = ks.DeleteKey(k1.Address, pass) // to clean up
 | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user