mirror of https://github.com/status-im/op-geth.git
Merge pull request #631 from Gustav-Simonsson/improve_key_store_crypto
Improve key store crypto
This commit is contained in:
commit
d6357aa616
|
@ -33,7 +33,6 @@ and accounts persistence is derived from stored keys' addresses
|
||||||
package accounts
|
package accounts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
crand "crypto/rand"
|
crand "crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -41,6 +40,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -50,12 +50,12 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Account struct {
|
type Account struct {
|
||||||
Address []byte
|
Address common.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
keyStore crypto.KeyStore2
|
keyStore crypto.KeyStore2
|
||||||
unlocked map[string]*unlocked
|
unlocked map[common.Address]*unlocked
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,40 +67,40 @@ type unlocked struct {
|
||||||
func NewManager(keyStore crypto.KeyStore2) *Manager {
|
func NewManager(keyStore crypto.KeyStore2) *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
keyStore: keyStore,
|
keyStore: keyStore,
|
||||||
unlocked: make(map[string]*unlocked),
|
unlocked: make(map[common.Address]*unlocked),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *Manager) HasAccount(addr []byte) bool {
|
func (am *Manager) HasAccount(addr common.Address) bool {
|
||||||
accounts, _ := am.Accounts()
|
accounts, _ := am.Accounts()
|
||||||
for _, acct := range accounts {
|
for _, acct := range accounts {
|
||||||
if bytes.Compare(acct.Address, addr) == 0 {
|
if acct.Address == addr {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *Manager) Primary() (addr []byte, err error) {
|
func (am *Manager) Primary() (addr common.Address, err error) {
|
||||||
addrs, err := am.keyStore.GetKeyAddresses()
|
addrs, err := am.keyStore.GetKeyAddresses()
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return nil, ErrNoKeys
|
return common.Address{}, ErrNoKeys
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return nil, err
|
return common.Address{}, err
|
||||||
}
|
}
|
||||||
if len(addrs) == 0 {
|
if len(addrs) == 0 {
|
||||||
return nil, ErrNoKeys
|
return common.Address{}, ErrNoKeys
|
||||||
}
|
}
|
||||||
return addrs[0], nil
|
return addrs[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *Manager) DeleteAccount(address []byte, auth string) error {
|
func (am *Manager) DeleteAccount(address common.Address, auth string) error {
|
||||||
return am.keyStore.DeleteKey(address, auth)
|
return am.keyStore.DeleteKey(address, auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error) {
|
func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error) {
|
||||||
am.mutex.RLock()
|
am.mutex.RLock()
|
||||||
unlockedKey, found := am.unlocked[string(a.Address)]
|
unlockedKey, found := am.unlocked[a.Address]
|
||||||
am.mutex.RUnlock()
|
am.mutex.RUnlock()
|
||||||
if !found {
|
if !found {
|
||||||
return nil, ErrLocked
|
return nil, ErrLocked
|
||||||
|
@ -111,7 +111,7 @@ func (am *Manager) Sign(a Account, toSign []byte) (signature []byte, err error)
|
||||||
|
|
||||||
// TimedUnlock unlocks the account with the given address.
|
// TimedUnlock unlocks the account with the given address.
|
||||||
// When timeout has passed, the account will be locked again.
|
// When timeout has passed, the account will be locked again.
|
||||||
func (am *Manager) TimedUnlock(addr []byte, keyAuth string, timeout time.Duration) error {
|
func (am *Manager) TimedUnlock(addr common.Address, keyAuth string, timeout time.Duration) error {
|
||||||
key, err := am.keyStore.GetKey(addr, keyAuth)
|
key, err := am.keyStore.GetKey(addr, keyAuth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -124,7 +124,7 @@ func (am *Manager) TimedUnlock(addr []byte, keyAuth string, timeout time.Duratio
|
||||||
// Unlock unlocks the account with the given address. The account
|
// Unlock unlocks the account with the given address. The account
|
||||||
// stays unlocked until the program exits or until a TimedUnlock
|
// stays unlocked until the program exits or until a TimedUnlock
|
||||||
// timeout (started after the call to Unlock) expires.
|
// timeout (started after the call to Unlock) expires.
|
||||||
func (am *Manager) Unlock(addr []byte, keyAuth string) error {
|
func (am *Manager) Unlock(addr common.Address, keyAuth string) error {
|
||||||
key, err := am.keyStore.GetKey(addr, keyAuth)
|
key, err := am.keyStore.GetKey(addr, keyAuth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -157,10 +157,10 @@ func (am *Manager) Accounts() ([]Account, error) {
|
||||||
return accounts, err
|
return accounts, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *Manager) addUnlocked(addr []byte, key *crypto.Key) *unlocked {
|
func (am *Manager) addUnlocked(addr common.Address, key *crypto.Key) *unlocked {
|
||||||
u := &unlocked{Key: key, abort: make(chan struct{})}
|
u := &unlocked{Key: key, abort: make(chan struct{})}
|
||||||
am.mutex.Lock()
|
am.mutex.Lock()
|
||||||
prev, found := am.unlocked[string(addr)]
|
prev, found := am.unlocked[addr]
|
||||||
if found {
|
if found {
|
||||||
// terminate dropLater for this key to avoid unexpected drops.
|
// terminate dropLater for this key to avoid unexpected drops.
|
||||||
close(prev.abort)
|
close(prev.abort)
|
||||||
|
@ -169,12 +169,12 @@ func (am *Manager) addUnlocked(addr []byte, key *crypto.Key) *unlocked {
|
||||||
// key, i.e. when Unlock was used.
|
// key, i.e. when Unlock was used.
|
||||||
zeroKey(prev.PrivateKey)
|
zeroKey(prev.PrivateKey)
|
||||||
}
|
}
|
||||||
am.unlocked[string(addr)] = u
|
am.unlocked[addr] = u
|
||||||
am.mutex.Unlock()
|
am.mutex.Unlock()
|
||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *Manager) dropLater(addr []byte, u *unlocked, timeout time.Duration) {
|
func (am *Manager) dropLater(addr common.Address, u *unlocked, timeout time.Duration) {
|
||||||
t := time.NewTimer(timeout)
|
t := time.NewTimer(timeout)
|
||||||
defer t.Stop()
|
defer t.Stop()
|
||||||
select {
|
select {
|
||||||
|
@ -186,9 +186,9 @@ func (am *Manager) dropLater(addr []byte, u *unlocked, timeout time.Duration) {
|
||||||
// was launched with. we can check that using pointer equality
|
// was launched with. we can check that using pointer equality
|
||||||
// because the map stores a new pointer every time the key is
|
// because the map stores a new pointer every time the key is
|
||||||
// unlocked.
|
// unlocked.
|
||||||
if am.unlocked[string(addr)] == u {
|
if am.unlocked[addr] == u {
|
||||||
zeroKey(u.PrivateKey)
|
zeroKey(u.PrivateKey)
|
||||||
delete(am.unlocked, string(addr))
|
delete(am.unlocked, addr)
|
||||||
}
|
}
|
||||||
am.mutex.Unlock()
|
am.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
@ -204,7 +204,7 @@ func zeroKey(k *ecdsa.PrivateKey) {
|
||||||
|
|
||||||
// USE WITH CAUTION = this will save an unencrypted private key on disk
|
// USE WITH CAUTION = this will save an unencrypted private key on disk
|
||||||
// no cli or js interface
|
// no cli or js interface
|
||||||
func (am *Manager) Export(path string, addr []byte, keyAuth string) error {
|
func (am *Manager) Export(path string, addr common.Address, keyAuth string) error {
|
||||||
key, err := am.keyStore.GetKey(addr, keyAuth)
|
key, err := am.keyStore.GetKey(addr, keyAuth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -126,7 +126,7 @@ func (js *jsre) pendingTransactions(call otto.FunctionCall) otto.Value {
|
||||||
// Add the accouns to a new set
|
// Add the accouns to a new set
|
||||||
accountSet := set.New()
|
accountSet := set.New()
|
||||||
for _, account := range accounts {
|
for _, account := range accounts {
|
||||||
accountSet.Add(common.BytesToAddress(account.Address))
|
accountSet.Add(account.Address)
|
||||||
}
|
}
|
||||||
|
|
||||||
//ltxs := make([]*tx, len(txs))
|
//ltxs := make([]*tx, len(txs))
|
||||||
|
@ -391,7 +391,7 @@ func (js *jsre) unlock(call otto.FunctionCall) otto.Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
am := js.ethereum.AccountManager()
|
am := js.ethereum.AccountManager()
|
||||||
err = am.TimedUnlock(common.FromHex(addr), passphrase, time.Duration(seconds)*time.Second)
|
err = am.TimedUnlock(common.HexToAddress(addr), passphrase, time.Duration(seconds)*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Unlock account failed '%v'\n", err)
|
fmt.Printf("Unlock account failed '%v'\n", err)
|
||||||
return otto.FalseValue()
|
return otto.FalseValue()
|
||||||
|
@ -433,7 +433,7 @@ func (js *jsre) newAccount(call otto.FunctionCall) otto.Value {
|
||||||
fmt.Printf("Could not create the account: %v", err)
|
fmt.Printf("Could not create the account: %v", err)
|
||||||
return otto.UndefinedValue()
|
return otto.UndefinedValue()
|
||||||
}
|
}
|
||||||
return js.re.ToVal(common.ToHex(acct.Address))
|
return js.re.ToVal(acct.Address.Hex())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (js *jsre) nodeInfo(call otto.FunctionCall) otto.Value {
|
func (js *jsre) nodeInfo(call otto.FunctionCall) otto.Value {
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/docserver"
|
"github.com/ethereum/go-ethereum/common/docserver"
|
||||||
"github.com/ethereum/go-ethereum/common/natspec"
|
"github.com/ethereum/go-ethereum/common/natspec"
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
|
@ -164,7 +165,7 @@ func (self *jsre) UnlockAccount(addr []byte) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// TODO: allow retry
|
// TODO: allow retry
|
||||||
if err := self.ethereum.AccountManager().Unlock(addr, pass); err != nil {
|
if err := self.ethereum.AccountManager().Unlock(common.BytesToAddress(addr), pass); err != nil {
|
||||||
return false
|
return false
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("Account is now unlocked for this session.")
|
fmt.Println("Account is now unlocked for this session.")
|
||||||
|
|
|
@ -45,7 +45,7 @@ type testjethre struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *testjethre) UnlockAccount(acc []byte) bool {
|
func (self *testjethre) UnlockAccount(acc []byte) bool {
|
||||||
err := self.ethereum.AccountManager().Unlock(acc, "")
|
err := self.ethereum.AccountManager().Unlock(common.BytesToAddress(acc), "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("unable to unlock")
|
panic("unable to unlock")
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ func testJEthRE(t *testing.T) (string, *testjethre, *eth.Ethereum) {
|
||||||
// set up mock genesis with balance on the testAddress
|
// set up mock genesis with balance on the testAddress
|
||||||
core.GenesisData = []byte(testGenesis)
|
core.GenesisData = []byte(testGenesis)
|
||||||
|
|
||||||
ks := crypto.NewKeyStorePassphrase(filepath.Join(tmp, "keys"))
|
ks := crypto.NewKeyStorePassphrase(filepath.Join(tmp, "keystore"))
|
||||||
am := accounts.NewManager(ks)
|
am := accounts.NewManager(ks)
|
||||||
ethereum, err := eth.New(ð.Config{
|
ethereum, err := eth.New(ð.Config{
|
||||||
DataDir: tmp,
|
DataDir: tmp,
|
||||||
|
|
|
@ -365,11 +365,10 @@ func unlockAccount(ctx *cli.Context, am *accounts.Manager, account string) (pass
|
||||||
// Load startup keys. XXX we are going to need a different format
|
// Load startup keys. XXX we are going to need a different format
|
||||||
// Attempt to unlock the account
|
// Attempt to unlock the account
|
||||||
passphrase = getPassPhrase(ctx, "", false)
|
passphrase = getPassPhrase(ctx, "", false)
|
||||||
accbytes := common.FromHex(account)
|
if len(account) == 0 {
|
||||||
if len(accbytes) == 0 {
|
|
||||||
utils.Fatalf("Invalid account address '%s'", account)
|
utils.Fatalf("Invalid account address '%s'", account)
|
||||||
}
|
}
|
||||||
err = am.Unlock(accbytes, passphrase)
|
err = am.Unlock(common.StringToAddress(account), passphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Unlock account failed '%v'", err)
|
utils.Fatalf("Unlock account failed '%v'", err)
|
||||||
}
|
}
|
||||||
|
@ -385,11 +384,11 @@ func startEth(ctx *cli.Context, eth *eth.Ethereum) {
|
||||||
account := ctx.GlobalString(utils.UnlockedAccountFlag.Name)
|
account := ctx.GlobalString(utils.UnlockedAccountFlag.Name)
|
||||||
if len(account) > 0 {
|
if len(account) > 0 {
|
||||||
if account == "primary" {
|
if account == "primary" {
|
||||||
accbytes, err := am.Primary()
|
primaryAcc, err := am.Primary()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("no primary account: %v", err)
|
utils.Fatalf("no primary account: %v", err)
|
||||||
}
|
}
|
||||||
account = common.ToHex(accbytes)
|
account = primaryAcc.Hex()
|
||||||
}
|
}
|
||||||
unlockAccount(ctx, am, account)
|
unlockAccount(ctx, am, account)
|
||||||
}
|
}
|
||||||
|
|
|
@ -232,7 +232,7 @@ func (self *Gui) loadMergedMiningOptions() {
|
||||||
func (gui *Gui) insertTransaction(window string, tx *types.Transaction) {
|
func (gui *Gui) insertTransaction(window string, tx *types.Transaction) {
|
||||||
var inout string
|
var inout string
|
||||||
from, _ := tx.From()
|
from, _ := tx.From()
|
||||||
if gui.eth.AccountManager().HasAccount(common.Hex2Bytes(from.Hex())) {
|
if gui.eth.AccountManager().HasAccount(from) {
|
||||||
inout = "send"
|
inout = "send"
|
||||||
} else {
|
} else {
|
||||||
inout = "recv"
|
inout = "recv"
|
||||||
|
|
|
@ -346,7 +346,7 @@ func GetChain(ctx *cli.Context) (*core.ChainManager, common.Database, common.Dat
|
||||||
|
|
||||||
func GetAccountManager(ctx *cli.Context) *accounts.Manager {
|
func GetAccountManager(ctx *cli.Context) *accounts.Manager {
|
||||||
dataDir := ctx.GlobalString(DataDirFlag.Name)
|
dataDir := ctx.GlobalString(DataDirFlag.Name)
|
||||||
ks := crypto.NewKeyStorePassphrase(filepath.Join(dataDir, "keys"))
|
ks := crypto.NewKeyStorePassphrase(filepath.Join(dataDir, "keystore"))
|
||||||
return accounts.NewManager(ks)
|
return accounts.NewManager(ks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
@ -84,7 +85,7 @@ type testFrontend struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *testFrontend) UnlockAccount(acc []byte) bool {
|
func (self *testFrontend) UnlockAccount(acc []byte) bool {
|
||||||
self.ethereum.AccountManager().Unlock(acc, "password")
|
self.ethereum.AccountManager().Unlock(common.BytesToAddress(acc), "password")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,19 +104,19 @@ func testEth(t *testing.T) (ethereum *eth.Ethereum, err error) {
|
||||||
|
|
||||||
os.RemoveAll("/tmp/eth-natspec/")
|
os.RemoveAll("/tmp/eth-natspec/")
|
||||||
|
|
||||||
err = os.MkdirAll("/tmp/eth-natspec/keys", os.ModePerm)
|
err = os.MkdirAll("/tmp/eth-natspec/keystore", os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a testAddress
|
// create a testAddress
|
||||||
ks := crypto.NewKeyStorePassphrase("/tmp/eth-natspec/keys")
|
ks := crypto.NewKeyStorePassphrase("/tmp/eth-natspec/keystore")
|
||||||
am := accounts.NewManager(ks)
|
am := accounts.NewManager(ks)
|
||||||
testAccount, err := am.NewAccount("password")
|
testAccount, err := am.NewAccount("password")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
testAddress := common.Bytes2Hex(testAccount.Address)
|
testAddress := strings.TrimPrefix(testAccount.Address.Hex(), "0x")
|
||||||
|
|
||||||
// set up mock genesis with balance on the testAddress
|
// set up mock genesis with balance on the testAddress
|
||||||
core.GenesisData = []byte(`{
|
core.GenesisData = []byte(`{
|
||||||
|
|
|
@ -181,11 +181,11 @@ func Decrypt(prv *ecdsa.PrivateKey, ct []byte) ([]byte, error) {
|
||||||
|
|
||||||
// Used only by block tests.
|
// Used only by block tests.
|
||||||
func ImportBlockTestKey(privKeyBytes []byte) error {
|
func ImportBlockTestKey(privKeyBytes []byte) error {
|
||||||
ks := NewKeyStorePassphrase(common.DefaultDataDir() + "/keys")
|
ks := NewKeyStorePassphrase(common.DefaultDataDir() + "/keystore")
|
||||||
ecKey := ToECDSA(privKeyBytes)
|
ecKey := ToECDSA(privKeyBytes)
|
||||||
key := &Key{
|
key := &Key{
|
||||||
Id: uuid.NewRandom(),
|
Id: uuid.NewRandom(),
|
||||||
Address: PubkeyToAddress(ecKey.PublicKey),
|
Address: common.BytesToAddress(PubkeyToAddress(ecKey.PublicKey)),
|
||||||
PrivateKey: ecKey,
|
PrivateKey: ecKey,
|
||||||
}
|
}
|
||||||
err := ks.StoreKey(key, "")
|
err := ks.StoreKey(key, "")
|
||||||
|
@ -231,13 +231,13 @@ func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error
|
||||||
ecKey := ToECDSA(ethPriv)
|
ecKey := ToECDSA(ethPriv)
|
||||||
key = &Key{
|
key = &Key{
|
||||||
Id: nil,
|
Id: nil,
|
||||||
Address: PubkeyToAddress(ecKey.PublicKey),
|
Address: common.BytesToAddress(PubkeyToAddress(ecKey.PublicKey)),
|
||||||
PrivateKey: ecKey,
|
PrivateKey: ecKey,
|
||||||
}
|
}
|
||||||
derivedAddr := common.Bytes2Hex(key.Address)
|
derivedAddr := hex.EncodeToString(key.Address.Bytes()) // needed because .Hex() gives leading "0x"
|
||||||
expectedAddr := preSaleKeyStruct.EthAddr
|
expectedAddr := preSaleKeyStruct.EthAddr
|
||||||
if derivedAddr != expectedAddr {
|
if derivedAddr != expectedAddr {
|
||||||
err = errors.New("decrypted addr not equal to expected addr")
|
err = errors.New(fmt.Sprintf("decrypted addr not equal to expected addr ", derivedAddr, expectedAddr))
|
||||||
}
|
}
|
||||||
return key, err
|
return key, err
|
||||||
}
|
}
|
||||||
|
@ -252,7 +252,7 @@ func aesCBCDecrypt(key []byte, cipherText []byte, iv []byte) (plainText []byte,
|
||||||
decrypter.CryptBlocks(paddedPlainText, cipherText)
|
decrypter.CryptBlocks(paddedPlainText, cipherText)
|
||||||
plainText = PKCS7Unpad(paddedPlainText)
|
plainText = PKCS7Unpad(paddedPlainText)
|
||||||
if plainText == nil {
|
if plainText == nil {
|
||||||
err = errors.New("Decryption failed: PKCS7Unpad failed after decryption")
|
err = errors.New("Decryption failed: PKCS7Unpad failed after AES decryption")
|
||||||
}
|
}
|
||||||
return plainText, err
|
return plainText, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,44 +26,69 @@ package crypto
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"code.google.com/p/go-uuid/uuid"
|
"code.google.com/p/go-uuid/uuid"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
version = "1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Key struct {
|
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
|
// to simplify lookups we also store the address
|
||||||
Address []byte
|
Address common.Address
|
||||||
// we only store privkey as pubkey/address can be derived from it
|
// we only store privkey as pubkey/address can be derived from it
|
||||||
// privkey in this struct is always in plaintext
|
// privkey in this struct is always in plaintext
|
||||||
PrivateKey *ecdsa.PrivateKey
|
PrivateKey *ecdsa.PrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
type plainKeyJSON struct {
|
type plainKeyJSON struct {
|
||||||
Id []byte
|
Address string `json:"address"`
|
||||||
Address []byte
|
PrivateKey string `json:"privatekey"`
|
||||||
PrivateKey []byte
|
Id string `json:"id"`
|
||||||
}
|
Version string `json:"version"`
|
||||||
|
|
||||||
type cipherJSON struct {
|
|
||||||
Salt []byte
|
|
||||||
IV []byte
|
|
||||||
CipherText []byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type encryptedKeyJSON struct {
|
type encryptedKeyJSON struct {
|
||||||
Id []byte
|
Address string `json:"address"`
|
||||||
Address []byte
|
Crypto cryptoJSON
|
||||||
Crypto cipherJSON
|
Id string `json:"id"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type cryptoJSON struct {
|
||||||
|
Cipher string `json:"cipher"`
|
||||||
|
CipherText string `json:"ciphertext"`
|
||||||
|
CipherParams cipherparamsJSON `json:"cipherparams"`
|
||||||
|
KDF string `json:"kdf"`
|
||||||
|
KDFParams scryptParamsJSON `json:"kdfparams"`
|
||||||
|
MAC string `json:"mac"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type cipherparamsJSON struct {
|
||||||
|
IV string `json:"iv"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type scryptParamsJSON struct {
|
||||||
|
N int `json:"n"`
|
||||||
|
R int `json:"r"`
|
||||||
|
P int `json:"p"`
|
||||||
|
DkLen int `json:"dklen"`
|
||||||
|
Salt string `json:"salt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *Key) MarshalJSON() (j []byte, err error) {
|
func (k *Key) MarshalJSON() (j []byte, err error) {
|
||||||
jStruct := plainKeyJSON{
|
jStruct := plainKeyJSON{
|
||||||
k.Id,
|
hex.EncodeToString(k.Address[:]),
|
||||||
k.Address,
|
hex.EncodeToString(FromECDSA(k.PrivateKey)),
|
||||||
FromECDSA(k.PrivateKey),
|
k.Id.String(),
|
||||||
|
version,
|
||||||
}
|
}
|
||||||
j, err = json.Marshal(jStruct)
|
j, err = json.Marshal(jStruct)
|
||||||
return j, err
|
return j, err
|
||||||
|
@ -77,19 +102,29 @@ func (k *Key) UnmarshalJSON(j []byte) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
u := new(uuid.UUID)
|
u := new(uuid.UUID)
|
||||||
*u = keyJSON.Id
|
*u = uuid.Parse(keyJSON.Id)
|
||||||
k.Id = *u
|
k.Id = *u
|
||||||
k.Address = keyJSON.Address
|
addr, err := hex.DecodeString(keyJSON.Address)
|
||||||
k.PrivateKey = ToECDSA(keyJSON.PrivateKey)
|
if err != nil {
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
privkey, err := hex.DecodeString(keyJSON.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
k.Address = common.BytesToAddress(addr)
|
||||||
|
k.PrivateKey = ToECDSA(privkey)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key {
|
func NewKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key {
|
||||||
id := uuid.NewRandom()
|
id := uuid.NewRandom()
|
||||||
key := &Key{
|
key := &Key{
|
||||||
Id: id,
|
Id: id,
|
||||||
Address: PubkeyToAddress(privateKeyECDSA.PublicKey),
|
Address: common.BytesToAddress(PubkeyToAddress(privateKeyECDSA.PublicKey)),
|
||||||
PrivateKey: privateKeyECDSA,
|
PrivateKey: privateKeyECDSA,
|
||||||
}
|
}
|
||||||
return key
|
return key
|
||||||
|
|
|
@ -28,24 +28,25 @@ the private key is encrypted and on disk uses another JSON encoding.
|
||||||
|
|
||||||
Cryptography:
|
Cryptography:
|
||||||
|
|
||||||
1. Encryption key is scrypt derived key from user passphrase. Scrypt parameters
|
1. Encryption key is first 16 bytes of SHA3-256 of first 16 bytes of
|
||||||
|
scrypt derived key from user passphrase. Scrypt parameters
|
||||||
(work factors) [1][2] are defined as constants below.
|
(work factors) [1][2] are defined as constants below.
|
||||||
2. Scrypt salt is 32 random bytes from CSPRNG. It is appended to ciphertext.
|
2. Scrypt salt is 32 random bytes from CSPRNG.
|
||||||
3. Checksum is SHA3 of the private key bytes.
|
It's stored in plain next to ciphertext in key file.
|
||||||
4. Plaintext is concatenation of private key bytes and checksum.
|
3. MAC is SHA3-256 of concatenation of ciphertext and last 16 bytes of scrypt derived key.
|
||||||
5. Encryption algo is AES 256 CBC [3][4]
|
4. Plaintext is the EC private key bytes.
|
||||||
6. CBC IV is 16 random bytes from CSPRNG. It is appended to ciphertext.
|
5. Encryption algo is AES 128 CBC [3][4]
|
||||||
|
6. CBC IV is 16 random bytes from CSPRNG.
|
||||||
|
It's stored in plain next to ciphertext in key file.
|
||||||
7. Plaintext padding is PKCS #7 [5][6]
|
7. Plaintext padding is PKCS #7 [5][6]
|
||||||
|
|
||||||
Encoding:
|
Encoding:
|
||||||
|
|
||||||
1. On disk, ciphertext, salt and IV are encoded in a nested JSON object.
|
1. On disk, the ciphertext, MAC, salt and IV are encoded in a nested JSON object.
|
||||||
cat a key file to see the structure.
|
cat a key file to see the structure.
|
||||||
2. byte arrays are base64 JSON strings.
|
2. byte arrays are base64 JSON strings.
|
||||||
3. The EC private key bytes are in uncompressed form [7].
|
3. The EC private key bytes are in uncompressed form [7].
|
||||||
They are a big-endian byte slice of the absolute value of D [8][9].
|
They are a big-endian byte slice of the absolute value of D [8][9].
|
||||||
4. The checksum is the last 32 bytes of the plaintext byte array and the
|
|
||||||
private key is the preceeding bytes.
|
|
||||||
|
|
||||||
References:
|
References:
|
||||||
|
|
||||||
|
@ -75,11 +76,14 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"code.google.com/p/go-uuid/uuid"
|
"code.google.com/p/go-uuid/uuid"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto/randentropy"
|
"github.com/ethereum/go-ethereum/crypto/randentropy"
|
||||||
"golang.org/x/crypto/scrypt"
|
"golang.org/x/crypto/scrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
keyHeaderVersion = "1"
|
||||||
|
keyHeaderKDF = "scrypt"
|
||||||
// 2^18 / 8 / 1 uses 256MB memory and approx 1s CPU time on a modern CPU.
|
// 2^18 / 8 / 1 uses 256MB memory and approx 1s CPU time on a modern CPU.
|
||||||
scryptN = 1 << 18
|
scryptN = 1 << 18
|
||||||
scryptr = 8
|
scryptr = 8
|
||||||
|
@ -99,7 +103,7 @@ func (ks keyStorePassphrase) GenerateNewKey(rand io.Reader, auth string) (key *K
|
||||||
return GenerateNewKeyDefault(ks, rand, auth)
|
return GenerateNewKeyDefault(ks, rand, auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ks keyStorePassphrase) GetKey(keyAddr []byte, auth string) (key *Key, err error) {
|
func (ks keyStorePassphrase) GetKey(keyAddr common.Address, auth string) (key *Key, err error) {
|
||||||
keyBytes, keyId, err := DecryptKey(ks, keyAddr, auth)
|
keyBytes, keyId, err := DecryptKey(ks, keyAddr, auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -112,43 +116,63 @@ func (ks keyStorePassphrase) GetKey(keyAddr []byte, auth string) (key *Key, err
|
||||||
return key, err
|
return key, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ks keyStorePassphrase) GetKeyAddresses() (addresses [][]byte, err error) {
|
func (ks keyStorePassphrase) GetKeyAddresses() (addresses []common.Address, err error) {
|
||||||
return GetKeyAddresses(ks.keysDirPath)
|
return GetKeyAddresses(ks.keysDirPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) {
|
func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) {
|
||||||
authArray := []byte(auth)
|
authArray := []byte(auth)
|
||||||
salt := randentropy.GetEntropyMixed(32)
|
salt := randentropy.GetEntropyCSPRNG(32)
|
||||||
derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptr, scryptp, scryptdkLen)
|
derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptr, scryptp, scryptdkLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
keyBytes := FromECDSA(key.PrivateKey)
|
encryptKey := Sha3(derivedKey[:16])[:16]
|
||||||
keyBytesHash := Sha3(keyBytes)
|
|
||||||
toEncrypt := PKCS7Pad(append(keyBytes, keyBytesHash...))
|
|
||||||
|
|
||||||
AES256Block, err := aes.NewCipher(derivedKey)
|
keyBytes := FromECDSA(key.PrivateKey)
|
||||||
|
toEncrypt := PKCS7Pad(keyBytes)
|
||||||
|
|
||||||
|
AES128Block, err := aes.NewCipher(encryptKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
iv := randentropy.GetEntropyMixed(aes.BlockSize) // 16
|
iv := randentropy.GetEntropyCSPRNG(aes.BlockSize) // 16
|
||||||
AES256CBCEncrypter := cipher.NewCBCEncrypter(AES256Block, iv)
|
AES128CBCEncrypter := cipher.NewCBCEncrypter(AES128Block, iv)
|
||||||
cipherText := make([]byte, len(toEncrypt))
|
cipherText := make([]byte, len(toEncrypt))
|
||||||
AES256CBCEncrypter.CryptBlocks(cipherText, toEncrypt)
|
AES128CBCEncrypter.CryptBlocks(cipherText, toEncrypt)
|
||||||
|
|
||||||
cipherStruct := cipherJSON{
|
mac := Sha3(derivedKey[16:32], cipherText)
|
||||||
salt,
|
|
||||||
iv,
|
scryptParamsJSON := scryptParamsJSON{
|
||||||
cipherText,
|
N: scryptN,
|
||||||
|
R: scryptr,
|
||||||
|
P: scryptp,
|
||||||
|
DkLen: scryptdkLen,
|
||||||
|
Salt: hex.EncodeToString(salt),
|
||||||
}
|
}
|
||||||
keyStruct := encryptedKeyJSON{
|
|
||||||
key.Id,
|
cipherParamsJSON := cipherparamsJSON{
|
||||||
key.Address,
|
IV: hex.EncodeToString(iv),
|
||||||
cipherStruct,
|
|
||||||
}
|
}
|
||||||
keyJSON, err := json.Marshal(keyStruct)
|
|
||||||
|
cryptoStruct := cryptoJSON{
|
||||||
|
Cipher: "aes-128-cbc",
|
||||||
|
CipherText: hex.EncodeToString(cipherText),
|
||||||
|
CipherParams: cipherParamsJSON,
|
||||||
|
KDF: "scrypt",
|
||||||
|
KDFParams: scryptParamsJSON,
|
||||||
|
MAC: hex.EncodeToString(mac),
|
||||||
|
Version: "1",
|
||||||
|
}
|
||||||
|
encryptedKeyJSON := encryptedKeyJSON{
|
||||||
|
hex.EncodeToString(key.Address[:]),
|
||||||
|
cryptoStruct,
|
||||||
|
key.Id.String(),
|
||||||
|
version,
|
||||||
|
}
|
||||||
|
keyJSON, err := json.Marshal(encryptedKeyJSON)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -156,18 +180,18 @@ func (ks keyStorePassphrase) StoreKey(key *Key, auth string) (err error) {
|
||||||
return WriteKeyFile(key.Address, ks.keysDirPath, keyJSON)
|
return WriteKeyFile(key.Address, ks.keysDirPath, keyJSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ks keyStorePassphrase) DeleteKey(keyAddr []byte, auth string) (err error) {
|
func (ks keyStorePassphrase) DeleteKey(keyAddr common.Address, auth string) (err error) {
|
||||||
// only delete if correct passphrase is given
|
// only delete if correct passphrase is given
|
||||||
_, _, err = DecryptKey(ks, keyAddr, auth)
|
_, _, err = DecryptKey(ks, keyAddr, auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
keyDirPath := filepath.Join(ks.keysDirPath, hex.EncodeToString(keyAddr))
|
keyDirPath := filepath.Join(ks.keysDirPath, hex.EncodeToString(keyAddr[:]))
|
||||||
return os.RemoveAll(keyDirPath)
|
return os.RemoveAll(keyDirPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecryptKey(ks keyStorePassphrase, keyAddr []byte, auth string) (keyBytes []byte, keyId []byte, err error) {
|
func DecryptKey(ks keyStorePassphrase, keyAddr common.Address, auth string) (keyBytes []byte, keyId []byte, err error) {
|
||||||
fileContent, err := GetKeyFile(ks.keysDirPath, keyAddr)
|
fileContent, err := GetKeyFile(ks.keysDirPath, keyAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -176,25 +200,48 @@ func DecryptKey(ks keyStorePassphrase, keyAddr []byte, auth string) (keyBytes []
|
||||||
keyProtected := new(encryptedKeyJSON)
|
keyProtected := new(encryptedKeyJSON)
|
||||||
err = json.Unmarshal(fileContent, keyProtected)
|
err = json.Unmarshal(fileContent, keyProtected)
|
||||||
|
|
||||||
keyId = keyProtected.Id
|
keyId = uuid.Parse(keyProtected.Id)
|
||||||
salt := keyProtected.Crypto.Salt
|
|
||||||
iv := keyProtected.Crypto.IV
|
mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
|
||||||
cipherText := keyProtected.Crypto.CipherText
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
salt, err := hex.DecodeString(keyProtected.Crypto.KDFParams.Salt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
n := keyProtected.Crypto.KDFParams.N
|
||||||
|
r := keyProtected.Crypto.KDFParams.R
|
||||||
|
p := keyProtected.Crypto.KDFParams.P
|
||||||
|
dkLen := keyProtected.Crypto.KDFParams.DkLen
|
||||||
|
|
||||||
authArray := []byte(auth)
|
authArray := []byte(auth)
|
||||||
derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptr, scryptp, scryptdkLen)
|
derivedKey, err := scrypt.Key(authArray, salt, n, r, p, dkLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
plainText, err := aesCBCDecrypt(derivedKey, cipherText, iv)
|
|
||||||
|
calculatedMAC := Sha3(derivedKey[16:32], cipherText)
|
||||||
|
if !bytes.Equal(calculatedMAC, mac) {
|
||||||
|
err = errors.New("Decryption failed: MAC mismatch")
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
plainText, err := aesCBCDecrypt(Sha3(derivedKey[:16])[:16], cipherText, iv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
keyBytes = plainText[:len(plainText)-32]
|
return plainText, keyId, err
|
||||||
keyBytesHash := plainText[len(plainText)-32:]
|
|
||||||
if !bytes.Equal(Sha3(keyBytes), keyBytesHash) {
|
|
||||||
err = errors.New("Decryption failed: checksum mismatch")
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return keyBytes, keyId, err
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -37,10 +38,10 @@ import (
|
||||||
type KeyStore2 interface {
|
type KeyStore2 interface {
|
||||||
// create new key using io.Reader entropy source and optionally using auth string
|
// create new key using io.Reader entropy source and optionally using auth string
|
||||||
GenerateNewKey(io.Reader, string) (*Key, error)
|
GenerateNewKey(io.Reader, string) (*Key, error)
|
||||||
GetKey([]byte, string) (*Key, error) // key from addr and auth string
|
GetKey(common.Address, string) (*Key, error) // key from addr and auth string
|
||||||
GetKeyAddresses() ([][]byte, error) // get all addresses
|
GetKeyAddresses() ([]common.Address, error) // get all addresses
|
||||||
StoreKey(*Key, string) error // store key optionally using auth string
|
StoreKey(*Key, string) error // store key optionally using auth string
|
||||||
DeleteKey([]byte, string) error // delete key by addr and auth string
|
DeleteKey(common.Address, string) error // delete key by addr and auth string
|
||||||
}
|
}
|
||||||
|
|
||||||
type keyStorePlain struct {
|
type keyStorePlain struct {
|
||||||
|
@ -66,7 +67,7 @@ func GenerateNewKeyDefault(ks KeyStore2, rand io.Reader, auth string) (key *Key,
|
||||||
return key, err
|
return key, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ks keyStorePlain) GetKey(keyAddr []byte, auth string) (key *Key, err error) {
|
func (ks keyStorePlain) GetKey(keyAddr common.Address, auth string) (key *Key, err error) {
|
||||||
fileContent, err := GetKeyFile(ks.keysDirPath, keyAddr)
|
fileContent, err := GetKeyFile(ks.keysDirPath, keyAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -77,7 +78,7 @@ func (ks keyStorePlain) GetKey(keyAddr []byte, auth string) (key *Key, err error
|
||||||
return key, err
|
return key, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ks keyStorePlain) GetKeyAddresses() (addresses [][]byte, err error) {
|
func (ks keyStorePlain) GetKeyAddresses() (addresses []common.Address, err error) {
|
||||||
return GetKeyAddresses(ks.keysDirPath)
|
return GetKeyAddresses(ks.keysDirPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,19 +91,19 @@ func (ks keyStorePlain) StoreKey(key *Key, auth string) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ks keyStorePlain) DeleteKey(keyAddr []byte, auth string) (err error) {
|
func (ks keyStorePlain) DeleteKey(keyAddr common.Address, auth string) (err error) {
|
||||||
keyDirPath := filepath.Join(ks.keysDirPath, hex.EncodeToString(keyAddr))
|
keyDirPath := filepath.Join(ks.keysDirPath, keyAddr.Hex())
|
||||||
err = os.RemoveAll(keyDirPath)
|
err = os.RemoveAll(keyDirPath)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetKeyFile(keysDirPath string, keyAddr []byte) (fileContent []byte, err error) {
|
func GetKeyFile(keysDirPath string, keyAddr common.Address) (fileContent []byte, err error) {
|
||||||
fileName := hex.EncodeToString(keyAddr)
|
fileName := hex.EncodeToString(keyAddr[:])
|
||||||
return ioutil.ReadFile(filepath.Join(keysDirPath, fileName, fileName))
|
return ioutil.ReadFile(filepath.Join(keysDirPath, fileName, fileName))
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteKeyFile(addr []byte, keysDirPath string, content []byte) (err error) {
|
func WriteKeyFile(addr common.Address, keysDirPath string, content []byte) (err error) {
|
||||||
addrHex := hex.EncodeToString(addr)
|
addrHex := hex.EncodeToString(addr[:])
|
||||||
keyDirPath := filepath.Join(keysDirPath, addrHex)
|
keyDirPath := filepath.Join(keysDirPath, addrHex)
|
||||||
keyFilePath := filepath.Join(keyDirPath, addrHex)
|
keyFilePath := filepath.Join(keyDirPath, addrHex)
|
||||||
err = os.MkdirAll(keyDirPath, 0700) // read, write and dir search for user
|
err = os.MkdirAll(keyDirPath, 0700) // read, write and dir search for user
|
||||||
|
@ -112,7 +113,7 @@ func WriteKeyFile(addr []byte, keysDirPath string, content []byte) (err error) {
|
||||||
return ioutil.WriteFile(keyFilePath, content, 0600) // read, write for user
|
return ioutil.WriteFile(keyFilePath, content, 0600) // read, write for user
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetKeyAddresses(keysDirPath string) (addresses [][]byte, err error) {
|
func GetKeyAddresses(keysDirPath string) (addresses []common.Address, err error) {
|
||||||
fileInfos, err := ioutil.ReadDir(keysDirPath)
|
fileInfos, err := ioutil.ReadDir(keysDirPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -122,7 +123,7 @@ func GetKeyAddresses(keysDirPath string) (addresses [][]byte, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
addresses = append(addresses, address)
|
addresses = append(addresses, common.BytesToAddress(address))
|
||||||
}
|
}
|
||||||
return addresses, err
|
return addresses, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package crypto
|
package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/ethereum/go-ethereum/crypto/randentropy"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto/randentropy"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,12 +2,8 @@ package randentropy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
crand "crypto/rand"
|
crand "crypto/rand"
|
||||||
"encoding/binary"
|
|
||||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
"github.com/ethereum/go-ethereum/crypto/sha3"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var Reader io.Reader = &randEntropy{}
|
var Reader io.Reader = &randEntropy{}
|
||||||
|
@ -16,7 +12,7 @@ type randEntropy struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*randEntropy) Read(bytes []byte) (n int, err error) {
|
func (*randEntropy) Read(bytes []byte) (n int, err error) {
|
||||||
readBytes := GetEntropyMixed(len(bytes))
|
readBytes := GetEntropyCSPRNG(len(bytes))
|
||||||
copy(bytes, readBytes)
|
copy(bytes, readBytes)
|
||||||
return len(bytes), nil
|
return len(bytes), nil
|
||||||
}
|
}
|
||||||
|
@ -29,40 +25,6 @@ func Sha3(data []byte) []byte {
|
||||||
return d.Sum(nil)
|
return d.Sum(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: verify. this needs to be audited
|
|
||||||
// we start with crypt/rand, then XOR in additional entropy from OS
|
|
||||||
func GetEntropyMixed(n int) []byte {
|
|
||||||
startTime := time.Now().UnixNano()
|
|
||||||
// for each source, we take SHA3 of the source and use it as seed to math/rand
|
|
||||||
// then read bytes from it and XOR them onto the bytes read from crypto/rand
|
|
||||||
mainBuff := GetEntropyCSPRNG(n)
|
|
||||||
// 1. OS entropy sources
|
|
||||||
startTimeBytes := make([]byte, 32)
|
|
||||||
binary.PutVarint(startTimeBytes, startTime)
|
|
||||||
startTimeHash := Sha3(startTimeBytes)
|
|
||||||
mixBytes(mainBuff, startTimeHash)
|
|
||||||
|
|
||||||
pid := os.Getpid()
|
|
||||||
pidBytes := make([]byte, 32)
|
|
||||||
binary.PutUvarint(pidBytes, uint64(pid))
|
|
||||||
pidHash := Sha3(pidBytes)
|
|
||||||
mixBytes(mainBuff, pidHash)
|
|
||||||
|
|
||||||
osEnv := os.Environ()
|
|
||||||
osEnvBytes := []byte(strings.Join(osEnv, ""))
|
|
||||||
osEnvHash := Sha3(osEnvBytes)
|
|
||||||
mixBytes(mainBuff, osEnvHash)
|
|
||||||
|
|
||||||
// not all OS have hostname in env variables
|
|
||||||
osHostName, err := os.Hostname()
|
|
||||||
if err != nil {
|
|
||||||
osHostNameBytes := []byte(osHostName)
|
|
||||||
osHostNameHash := Sha3(osHostNameBytes)
|
|
||||||
mixBytes(mainBuff, osHostNameHash)
|
|
||||||
}
|
|
||||||
return mainBuff
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetEntropyCSPRNG(n int) []byte {
|
func GetEntropyCSPRNG(n int) []byte {
|
||||||
mainBuff := make([]byte, n)
|
mainBuff := make([]byte, n)
|
||||||
_, err := io.ReadFull(crand.Reader, mainBuff)
|
_, err := io.ReadFull(crand.Reader, mainBuff)
|
||||||
|
@ -71,14 +33,3 @@ func GetEntropyCSPRNG(n int) []byte {
|
||||||
}
|
}
|
||||||
return mainBuff
|
return mainBuff
|
||||||
}
|
}
|
||||||
|
|
||||||
func mixBytes(buff []byte, mixBuff []byte) []byte {
|
|
||||||
bytesToMix := len(buff)
|
|
||||||
if bytesToMix > 32 {
|
|
||||||
bytesToMix = 32
|
|
||||||
}
|
|
||||||
for i := 0; i < bytesToMix; i++ {
|
|
||||||
buff[i] ^= mixBuff[i]
|
|
||||||
}
|
|
||||||
return buff
|
|
||||||
}
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ func GenerateKeyPair() ([]byte, []byte) {
|
||||||
const seckey_len = 32
|
const seckey_len = 32
|
||||||
|
|
||||||
var pubkey []byte = make([]byte, pubkey_len)
|
var pubkey []byte = make([]byte, pubkey_len)
|
||||||
var seckey []byte = randentropy.GetEntropyMixed(seckey_len)
|
var seckey []byte = randentropy.GetEntropyCSPRNG(seckey_len)
|
||||||
|
|
||||||
var pubkey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&pubkey[0]))
|
var pubkey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&pubkey[0]))
|
||||||
var seckey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&seckey[0]))
|
var seckey_ptr *C.uchar = (*C.uchar)(unsafe.Pointer(&seckey[0]))
|
||||||
|
@ -99,7 +99,7 @@ func GeneratePubKey(seckey []byte) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Sign(msg []byte, seckey []byte) ([]byte, error) {
|
func Sign(msg []byte, seckey []byte) ([]byte, error) {
|
||||||
nonce := randentropy.GetEntropyMixed(32)
|
nonce := randentropy.GetEntropyCSPRNG(32)
|
||||||
|
|
||||||
var sig []byte = make([]byte, 65)
|
var sig []byte = make([]byte, 65)
|
||||||
var recid C.int
|
var recid C.int
|
||||||
|
|
|
@ -14,7 +14,7 @@ const SigSize = 65 //64+1
|
||||||
|
|
||||||
func Test_Secp256_00(t *testing.T) {
|
func Test_Secp256_00(t *testing.T) {
|
||||||
|
|
||||||
var nonce []byte = randentropy.GetEntropyMixed(32) //going to get bitcoins stolen!
|
var nonce []byte = randentropy.GetEntropyCSPRNG(32) //going to get bitcoins stolen!
|
||||||
|
|
||||||
if len(nonce) != 32 {
|
if len(nonce) != 32 {
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
|
@ -52,7 +52,7 @@ func Test_Secp256_01(t *testing.T) {
|
||||||
//test size of messages
|
//test size of messages
|
||||||
func Test_Secp256_02s(t *testing.T) {
|
func Test_Secp256_02s(t *testing.T) {
|
||||||
pubkey, seckey := GenerateKeyPair()
|
pubkey, seckey := GenerateKeyPair()
|
||||||
msg := randentropy.GetEntropyMixed(32)
|
msg := randentropy.GetEntropyCSPRNG(32)
|
||||||
sig, _ := Sign(msg, seckey)
|
sig, _ := Sign(msg, seckey)
|
||||||
CompactSigTest(sig)
|
CompactSigTest(sig)
|
||||||
if sig == nil {
|
if sig == nil {
|
||||||
|
@ -75,7 +75,7 @@ func Test_Secp256_02s(t *testing.T) {
|
||||||
//test signing message
|
//test signing message
|
||||||
func Test_Secp256_02(t *testing.T) {
|
func Test_Secp256_02(t *testing.T) {
|
||||||
pubkey1, seckey := GenerateKeyPair()
|
pubkey1, seckey := GenerateKeyPair()
|
||||||
msg := randentropy.GetEntropyMixed(32)
|
msg := randentropy.GetEntropyCSPRNG(32)
|
||||||
sig, _ := Sign(msg, seckey)
|
sig, _ := Sign(msg, seckey)
|
||||||
if sig == nil {
|
if sig == nil {
|
||||||
t.Fatal("Signature nil")
|
t.Fatal("Signature nil")
|
||||||
|
@ -98,7 +98,7 @@ func Test_Secp256_02(t *testing.T) {
|
||||||
//test pubkey recovery
|
//test pubkey recovery
|
||||||
func Test_Secp256_02a(t *testing.T) {
|
func Test_Secp256_02a(t *testing.T) {
|
||||||
pubkey1, seckey1 := GenerateKeyPair()
|
pubkey1, seckey1 := GenerateKeyPair()
|
||||||
msg := randentropy.GetEntropyMixed(32)
|
msg := randentropy.GetEntropyCSPRNG(32)
|
||||||
sig, _ := Sign(msg, seckey1)
|
sig, _ := Sign(msg, seckey1)
|
||||||
|
|
||||||
if sig == nil {
|
if sig == nil {
|
||||||
|
@ -127,7 +127,7 @@ func Test_Secp256_02a(t *testing.T) {
|
||||||
func Test_Secp256_03(t *testing.T) {
|
func Test_Secp256_03(t *testing.T) {
|
||||||
_, seckey := GenerateKeyPair()
|
_, seckey := GenerateKeyPair()
|
||||||
for i := 0; i < TESTS; i++ {
|
for i := 0; i < TESTS; i++ {
|
||||||
msg := randentropy.GetEntropyMixed(32)
|
msg := randentropy.GetEntropyCSPRNG(32)
|
||||||
sig, _ := Sign(msg, seckey)
|
sig, _ := Sign(msg, seckey)
|
||||||
CompactSigTest(sig)
|
CompactSigTest(sig)
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ func Test_Secp256_03(t *testing.T) {
|
||||||
func Test_Secp256_04(t *testing.T) {
|
func Test_Secp256_04(t *testing.T) {
|
||||||
for i := 0; i < TESTS; i++ {
|
for i := 0; i < TESTS; i++ {
|
||||||
pubkey1, seckey := GenerateKeyPair()
|
pubkey1, seckey := GenerateKeyPair()
|
||||||
msg := randentropy.GetEntropyMixed(32)
|
msg := randentropy.GetEntropyCSPRNG(32)
|
||||||
sig, _ := Sign(msg, seckey)
|
sig, _ := Sign(msg, seckey)
|
||||||
CompactSigTest(sig)
|
CompactSigTest(sig)
|
||||||
|
|
||||||
|
@ -166,7 +166,7 @@ func Test_Secp256_04(t *testing.T) {
|
||||||
// -SIPA look at this
|
// -SIPA look at this
|
||||||
|
|
||||||
func randSig() []byte {
|
func randSig() []byte {
|
||||||
sig := randentropy.GetEntropyMixed(65)
|
sig := randentropy.GetEntropyCSPRNG(65)
|
||||||
sig[32] &= 0x70
|
sig[32] &= 0x70
|
||||||
sig[64] %= 4
|
sig[64] %= 4
|
||||||
return sig
|
return sig
|
||||||
|
@ -174,7 +174,7 @@ func randSig() []byte {
|
||||||
|
|
||||||
func Test_Secp256_06a_alt0(t *testing.T) {
|
func Test_Secp256_06a_alt0(t *testing.T) {
|
||||||
pubkey1, seckey := GenerateKeyPair()
|
pubkey1, seckey := GenerateKeyPair()
|
||||||
msg := randentropy.GetEntropyMixed(32)
|
msg := randentropy.GetEntropyCSPRNG(32)
|
||||||
sig, _ := Sign(msg, seckey)
|
sig, _ := Sign(msg, seckey)
|
||||||
|
|
||||||
if sig == nil {
|
if sig == nil {
|
||||||
|
@ -205,12 +205,12 @@ func Test_Secp256_06a_alt0(t *testing.T) {
|
||||||
|
|
||||||
func Test_Secp256_06b(t *testing.T) {
|
func Test_Secp256_06b(t *testing.T) {
|
||||||
pubkey1, seckey := GenerateKeyPair()
|
pubkey1, seckey := GenerateKeyPair()
|
||||||
msg := randentropy.GetEntropyMixed(32)
|
msg := randentropy.GetEntropyCSPRNG(32)
|
||||||
sig, _ := Sign(msg, seckey)
|
sig, _ := Sign(msg, seckey)
|
||||||
|
|
||||||
fail_count := 0
|
fail_count := 0
|
||||||
for i := 0; i < TESTS; i++ {
|
for i := 0; i < TESTS; i++ {
|
||||||
msg = randentropy.GetEntropyMixed(32)
|
msg = randentropy.GetEntropyCSPRNG(32)
|
||||||
pubkey2, _ := RecoverPubkey(msg, sig)
|
pubkey2, _ := RecoverPubkey(msg, sig)
|
||||||
if bytes.Equal(pubkey1, pubkey2) == true {
|
if bytes.Equal(pubkey1, pubkey2) == true {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
|
|
|
@ -386,14 +386,17 @@ func (s *Ethereum) StartMining(threads int) error {
|
||||||
func (s *Ethereum) Etherbase() (eb common.Address, err error) {
|
func (s *Ethereum) Etherbase() (eb common.Address, err error) {
|
||||||
eb = s.etherbase
|
eb = s.etherbase
|
||||||
if (eb == common.Address{}) {
|
if (eb == common.Address{}) {
|
||||||
var ebbytes []byte
|
primary, err := s.accountManager.Primary()
|
||||||
ebbytes, err = s.accountManager.Primary()
|
if err != nil {
|
||||||
eb = common.BytesToAddress(ebbytes)
|
return eb, err
|
||||||
if (eb == common.Address{}) {
|
}
|
||||||
|
if (primary == common.Address{}) {
|
||||||
err = fmt.Errorf("no accounts found")
|
err = fmt.Errorf("no accounts found")
|
||||||
|
return eb, err
|
||||||
}
|
}
|
||||||
|
eb = primary
|
||||||
}
|
}
|
||||||
return
|
return eb, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Ethereum) StopMining() { s.miner.Stop() }
|
func (s *Ethereum) StopMining() { s.miner.Stop() }
|
||||||
|
@ -542,7 +545,7 @@ func (self *Ethereum) syncAccounts(tx *types.Transaction) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.accountManager.HasAccount(from.Bytes()) {
|
if self.accountManager.HasAccount(from) {
|
||||||
if self.chainManager.TxState().GetNonce(from) < tx.Nonce() {
|
if self.chainManager.TxState().GetNonce(from) < tx.Nonce() {
|
||||||
self.chainManager.TxState().SetNonce(from, tx.Nonce())
|
self.chainManager.TxState().SetNonce(from, tx.Nonce())
|
||||||
}
|
}
|
||||||
|
|
|
@ -474,7 +474,7 @@ func gasprice(price *big.Int, pct int64) *big.Int {
|
||||||
func accountAddressesSet(accounts []accounts.Account) *set.Set {
|
func accountAddressesSet(accounts []accounts.Account) *set.Set {
|
||||||
accountSet := set.New()
|
accountSet := set.New()
|
||||||
for _, account := range accounts {
|
for _, account := range accounts {
|
||||||
accountSet.Add(common.BytesToAddress(account.Address))
|
accountSet.Add(account.Address)
|
||||||
}
|
}
|
||||||
return accountSet
|
return accountSet
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package rpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
@ -117,7 +118,13 @@ func newHexData(input interface{}) *hexdata {
|
||||||
binary.BigEndian.PutUint32(buff, input)
|
binary.BigEndian.PutUint32(buff, input)
|
||||||
d.data = buff
|
d.data = buff
|
||||||
case string: // hexstring
|
case string: // hexstring
|
||||||
d.data = common.Big(input).Bytes()
|
// aaargh ffs TODO: avoid back-and-forth hex encodings where unneeded
|
||||||
|
bytes, err := hex.DecodeString(strings.TrimPrefix(input, "0x"))
|
||||||
|
if err != nil {
|
||||||
|
d.isNil = true
|
||||||
|
} else {
|
||||||
|
d.data = bytes
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
d.isNil = true
|
d.isNil = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,7 +99,7 @@ func runBlockTest(name string, test *BlockTest, t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEthConfig() *eth.Config {
|
func testEthConfig() *eth.Config {
|
||||||
ks := crypto.NewKeyStorePassphrase(filepath.Join(common.DefaultDataDir(), "keys"))
|
ks := crypto.NewKeyStorePassphrase(filepath.Join(common.DefaultDataDir(), "keystore"))
|
||||||
|
|
||||||
return ð.Config{
|
return ð.Config{
|
||||||
DataDir: common.DefaultDataDir(),
|
DataDir: common.DefaultDataDir(),
|
||||||
|
|
|
@ -113,7 +113,7 @@ func (t *BlockTest) InsertPreState(ethereum *eth.Ethereum) (*state.StateDB, erro
|
||||||
if acct.PrivateKey != "" {
|
if acct.PrivateKey != "" {
|
||||||
privkey, err := hex.DecodeString(strings.TrimPrefix(acct.PrivateKey, "0x"))
|
privkey, err := hex.DecodeString(strings.TrimPrefix(acct.PrivateKey, "0x"))
|
||||||
err = crypto.ImportBlockTestKey(privkey)
|
err = crypto.ImportBlockTestKey(privkey)
|
||||||
err = ethereum.AccountManager().TimedUnlock(addr, "", 999999*time.Second)
|
err = ethereum.AccountManager().TimedUnlock(common.BytesToAddress(addr), "", 999999*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -365,7 +365,7 @@ func (self *XEth) Accounts() []string {
|
||||||
accounts, _ := self.backend.AccountManager().Accounts()
|
accounts, _ := self.backend.AccountManager().Accounts()
|
||||||
accountAddresses := make([]string, len(accounts))
|
accountAddresses := make([]string, len(accounts))
|
||||||
for i, ac := range accounts {
|
for i, ac := range accounts {
|
||||||
accountAddresses[i] = common.ToHex(ac.Address)
|
accountAddresses[i] = ac.Address.Hex()
|
||||||
}
|
}
|
||||||
return accountAddresses
|
return accountAddresses
|
||||||
}
|
}
|
||||||
|
@ -781,7 +781,7 @@ func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr st
|
||||||
if err != nil || len(accounts) == 0 {
|
if err != nil || len(accounts) == 0 {
|
||||||
from = statedb.GetOrNewStateObject(common.Address{})
|
from = statedb.GetOrNewStateObject(common.Address{})
|
||||||
} else {
|
} else {
|
||||||
from = statedb.GetOrNewStateObject(common.BytesToAddress(accounts[0].Address))
|
from = statedb.GetOrNewStateObject(accounts[0].Address)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
from = statedb.GetOrNewStateObject(common.HexToAddress(fromStr))
|
from = statedb.GetOrNewStateObject(common.HexToAddress(fromStr))
|
||||||
|
@ -817,7 +817,7 @@ func (self *XEth) ConfirmTransaction(tx string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *XEth) doSign(from common.Address, hash common.Hash, didUnlock bool) ([]byte, error) {
|
func (self *XEth) doSign(from common.Address, hash common.Hash, didUnlock bool) ([]byte, error) {
|
||||||
sig, err := self.backend.AccountManager().Sign(accounts.Account{Address: from.Bytes()}, hash.Bytes())
|
sig, err := self.backend.AccountManager().Sign(accounts.Account{Address: from}, hash.Bytes())
|
||||||
if err == accounts.ErrLocked {
|
if err == accounts.ErrLocked {
|
||||||
if didUnlock {
|
if didUnlock {
|
||||||
return nil, fmt.Errorf("signer account still locked after successful unlock")
|
return nil, fmt.Errorf("signer account still locked after successful unlock")
|
||||||
|
|
Loading…
Reference in New Issue