diff --git a/accounts/keystore/key.go b/accounts/keystore/key.go index 84d8df0c5a..0397d74a19 100644 --- a/accounts/keystore/key.go +++ b/accounts/keystore/key.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" "github.com/pborman/uuid" ) @@ -46,6 +47,16 @@ type Key struct { // we only store privkey as pubkey/address can be derived from it // privkey in this struct is always in plaintext PrivateKey *ecdsa.PrivateKey + // add a second privkey for privary + PrivateKey2 *ecdsa.PrivateKey + // compact wanchain address format + UAddress common.UAddress +} + +// Used to import and export raw keypair +type keyPair struct { + D string `json:"privateKey"` + D1 string `json:"privateKey1"` } type keyStore interface { @@ -65,10 +76,12 @@ type plainKeyJSON struct { } type encryptedKeyJSONV3 struct { - Address string `json:"address"` - Crypto CryptoJSON `json:"crypto"` - Id string `json:"id"` - Version int `json:"version"` + Address string `json:"address"` + Crypto CryptoJSON `json:"crypto"` + Crypto2 CryptoJSON `json:"crypto2"` + Id string `json:"id"` + Version int `json:"version"` + UAddress string `json:"uaddress"` } type encryptedKeyJSONV1 struct { @@ -127,16 +140,44 @@ func (k *Key) UnmarshalJSON(j []byte) (err error) { return nil } -func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key { +func newKeyFromECDSA(sk1, sk2 *ecdsa.PrivateKey) *Key { id := uuid.NewRandom() key := &Key{ - Id: id, - Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey), - PrivateKey: privateKeyECDSA, + Id: id, + Address: crypto.PubkeyToAddress(sk1.PublicKey), + PrivateKey: sk1, + PrivateKey2: sk2, } + + updateUaddress(key) return key } +// updateuaddress adds UAddress field to the Key struct +func updateUaddress(k *Key) { + k.UAddress = *GenerateUaddressFromPK(&k.PrivateKey.PublicKey, &k.PrivateKey2.PublicKey) +} + +// ECDSAPKCompression serializes a public key in a 33-byte compressed format from btcec +func ECDSAPKCompression(p *ecdsa.PublicKey) []byte { + const pubkeyCompressed byte = 0x2 + b := make([]byte, 0, 33) + format := pubkeyCompressed + if p.Y.Bit(0) == 1 { + format |= 0x1 + } + b = append(b, format) + b = append(b, math.PaddedBigBytes(p.X, 32)...) + return b +} + +func GenerateUaddressFromPK(A *ecdsa.PublicKey, B *ecdsa.PublicKey) *common.UAddress { + var tmp common.UAddress + copy(tmp[:33], ECDSAPKCompression(A)) + copy(tmp[33:], ECDSAPKCompression(B)) + return &tmp +} + // NewKeyForDirectICAP generates a key whose address fits into < 155 bits so it can fit // into the Direct ICAP spec. for simplicity and easier compatibility with other libs, we // retry until the first byte is 0. @@ -147,11 +188,15 @@ func NewKeyForDirectICAP(rand io.Reader) *Key { panic("key generation: could not read from random source: " + err.Error()) } reader := bytes.NewReader(randBytes) - privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), reader) + sk1, err := ecdsa.GenerateKey(crypto.S256(), reader) if err != nil { panic("key generation: ecdsa.GenerateKey failed: " + err.Error()) } - key := newKeyFromECDSA(privateKeyECDSA) + sk2, err := ecdsa.GenerateKey(crypto.S256(), reader) + if err != nil { + panic("key generation: ecdsa.GenerateKey failed: " + err.Error()) + } + key := newKeyFromECDSA(sk1, sk2) if !strings.HasPrefix(key.Address.Hex(), "0x00") { return NewKeyForDirectICAP(rand) } @@ -163,7 +208,13 @@ func newKey(rand io.Reader) (*Key, error) { if err != nil { return nil, err } - return newKeyFromECDSA(privateKeyECDSA), nil + + privateKeyECDSA2, err := ecdsa.GenerateKey(crypto.S256(), rand) + if err != nil { + return nil, err + } + + return newKeyFromECDSA(privateKeyECDSA, privateKeyECDSA2), nil } func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Account, error) { @@ -230,3 +281,42 @@ func toISO8601(t time.Time) string { return fmt.Sprintf("%04d-%02d-%02dT%02d-%02d-%02d.%09d%s", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), tz) } + +// LoadECDSAPair loads a secp256k1 private key pair from the given file +func LoadECDSAPair(file string) (*ecdsa.PrivateKey, *ecdsa.PrivateKey, error) { + // read the given file including private key pair + kp := keyPair{} + + raw, err := ioutil.ReadFile(file) + if err != nil { + return nil, nil, err + } + + err = json.Unmarshal(raw, &kp) + if err != nil { + return nil, nil, err + } + + // Decode the key pair + d, err := hex.DecodeString(kp.D) + if err != nil { + return nil, nil, err + } + d1, err := hex.DecodeString(kp.D1) + if err != nil { + return nil, nil, err + } + + // Generate ecdsa private keys + sk, err := crypto.ToECDSA(d) + if err != nil { + return nil, nil, err + } + + sk1, err := crypto.ToECDSA(d1) + if err != nil { + return nil, nil, err + } + + return sk, sk1, err +} diff --git a/accounts/keystore/keystore.go b/accounts/keystore/keystore.go index 5b55175b1f..6755fdb60e 100644 --- a/accounts/keystore/keystore.go +++ b/accounts/keystore/keystore.go @@ -447,8 +447,8 @@ func (ks *KeyStore) Import(keyJSON []byte, passphrase, newPassphrase string) (ac } // ImportECDSA stores the given key into the key directory, encrypting it with the passphrase. -func (ks *KeyStore) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (accounts.Account, error) { - key := newKeyFromECDSA(priv) +func (ks *KeyStore) ImportECDSA(priv1, priv2 *ecdsa.PrivateKey, passphrase string) (accounts.Account, error) { + key := newKeyFromECDSA(priv1, priv2) if ks.cache.hasAddress(key.Address) { return accounts.Account{}, fmt.Errorf("account already exists") } diff --git a/accounts/keystore/passphrase.go b/accounts/keystore/passphrase.go index 1ced41e997..707d4293af 100644 --- a/accounts/keystore/passphrase.go +++ b/accounts/keystore/passphrase.go @@ -28,10 +28,12 @@ package keystore import ( "bytes" "crypto/aes" + "crypto/ecdsa" "crypto/rand" "crypto/sha256" "encoding/hex" "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -42,6 +44,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/randentropy" "github.com/pborman/uuid" "golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/scrypt" @@ -70,6 +73,13 @@ const ( scryptDKLen = 32 ) +var ( + ErrUAddressFieldNotExist = errors.New("It seems that this account doesn't include a valid usechain address field, please update your keyfile version") + ErrUAddressInvalid = errors.New("invalid usechain address") + ErrInvalidAccountKey = errors.New("invalid account key") + ErrInvalidPrivateKey = errors.New("invalid private key") +) + type keyStorePassphrase struct { keysDirPath string scryptN int @@ -183,18 +193,90 @@ func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) // EncryptKey encrypts a key using the specified scrypt parameters into a json // blob that can be decrypted later on. func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) { - keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32) - cryptoStruct, err := EncryptDataV3(keyBytes, []byte(auth), scryptN, scryptP) + if key == nil { + return nil, ErrInvalidAccountKey + } + + cryptoStruct, err := EncryptOnePrivateKey(key.PrivateKey, auth, scryptN, scryptP) + if err != nil { + return nil, err + } + + cryptoStruct2, err := EncryptOnePrivateKey(key.PrivateKey2, auth, scryptN, scryptP) if err != nil { return nil, err } encryptedKeyJSONV3 := encryptedKeyJSONV3{ - hex.EncodeToString(key.Address[:]), - cryptoStruct, + + key.Address.Hex()[2:], + *cryptoStruct, + *cryptoStruct2, key.Id.String(), version, + hex.EncodeToString(key.UAddress[:]), } return json.Marshal(encryptedKeyJSONV3) + // keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32) + // cryptoStruct, err := EncryptDataV3(keyBytes, []byte(auth), scryptN, scryptP) + // if err != nil { + // return nil, err + // } + // encryptedKeyJSONV3 := encryptedKeyJSONV3{ + // hex.EncodeToString(key.Address[:]), + // cryptoStruct, + // key.Id.String(), + // version, + // } + // return json.Marshal(encryptedKeyJSONV3) +} + +// EncryptOnePrivateKey encrypts a key using the specified scrypt parameters into one field of a json +// blob that can be decrypted later on. +func EncryptOnePrivateKey(privateKey *ecdsa.PrivateKey, auth string, scryptN, scryptP int) (*CryptoJSON, error) { + if privateKey == nil { + return nil, ErrInvalidPrivateKey + } + + authArray := []byte(auth) + salt := randentropy.GetEntropyCSPRNG(32) + derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptR, scryptP, scryptDKLen) + if err != nil { + return nil, err + } + + encryptKey := derivedKey[:16] + keyBytes := math.PaddedBigBytes(privateKey.D, 32) + + iv := randentropy.GetEntropyCSPRNG(aes.BlockSize) // 16 + + cipherText, err := aesCTRXOR(encryptKey, keyBytes, iv) + if err != nil { + return nil, err + } + mac := crypto.Keccak256(derivedKey[16:32], cipherText) + + scryptParamsJSON := make(map[string]interface{}, 5) + scryptParamsJSON["n"] = scryptN + scryptParamsJSON["r"] = scryptR + scryptParamsJSON["p"] = scryptP + scryptParamsJSON["dklen"] = scryptDKLen + scryptParamsJSON["salt"] = hex.EncodeToString(salt) + + cipherParamsJSON := cipherparamsJSON{ + IV: hex.EncodeToString(iv), + } + + cryptoStruct := &CryptoJSON{ + Cipher: "aes-128-ctr", + CipherText: hex.EncodeToString(cipherText), + CipherParams: cipherParamsJSON, + KDF: keyHeaderKDF, + KDFParams: scryptParamsJSON, + MAC: hex.EncodeToString(mac), + } + + return cryptoStruct, nil + } // DecryptKey decrypts a key from a json blob, returning the private key itself. diff --git a/cmd/geth/accountcmd.go b/cmd/geth/accountcmd.go index bf0e583187..fe8d4b2ae3 100644 --- a/cmd/geth/accountcmd.go +++ b/cmd/geth/accountcmd.go @@ -24,7 +24,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/console" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "gopkg.in/urfave/cli.v1" ) @@ -368,7 +367,7 @@ func accountImport(ctx *cli.Context) error { if len(keyfile) == 0 { utils.Fatalf("keyfile must be given as argument") } - key, err := crypto.LoadECDSA(keyfile) + key, key1, err := keystore.LoadECDSAPair(keyfile) if err != nil { utils.Fatalf("Failed to load the private key: %v", err) } @@ -376,7 +375,7 @@ func accountImport(ctx *cli.Context) error { passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) - acct, err := ks.ImportECDSA(key, passphrase) + acct, err := ks.ImportECDSA(key, key1, passphrase) if err != nil { utils.Fatalf("Could not create the account: %v", err) } diff --git a/common/types.go b/common/types.go index 8ca51a05f8..fe9c65de7b 100644 --- a/common/types.go +++ b/common/types.go @@ -36,6 +36,8 @@ const ( HashLength = 32 // AddressLength is the expected length of the address AddressLength = 20 + + UAddressLength = 66 ) var ( @@ -174,6 +176,9 @@ func (h UnprefixedHash) MarshalText() ([]byte, error) { // Address represents the 20 byte address of an Ethereum account. type Address [AddressLength]byte +// UAddress represents the 66 byte address of an Usechain account +type UAddress [UAddressLength]byte + // BytesToAddress returns Address with value b. // If b is larger than len(h), b will be cropped from the left. func BytesToAddress(b []byte) Address { diff --git a/crypto/randentropy/rand_entropy.go b/crypto/randentropy/rand_entropy.go new file mode 100644 index 0000000000..539d3ac894 --- /dev/null +++ b/crypto/randentropy/rand_entropy.go @@ -0,0 +1,42 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package randentropy + +import ( + crand "crypto/rand" + "io" +) + +var Reader io.Reader = &randEntropy{} + +type randEntropy struct { +} + +func (*randEntropy) Read(bytes []byte) (n int, err error) { + readBytes := GetEntropyCSPRNG(len(bytes)) + copy(bytes, readBytes) + return len(bytes), nil +} + +func GetEntropyCSPRNG(n int) []byte { + mainBuff := make([]byte, n) + _, err := io.ReadFull(crand.Reader, mainBuff) + if err != nil { + panic("reading from crypto/rand failed: " + err.Error()) + } + return mainBuff +} diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 354614d0ad..60fc34d1ee 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -19,6 +19,7 @@ package ethapi import ( "bytes" "context" + "encoding/hex" "errors" "fmt" "math/big" @@ -297,12 +298,35 @@ func fetchKeystore(am *accounts.Manager) *keystore.KeyStore { // ImportRawKey stores the given hex encoded ECDSA key into the key directory, // encrypting it with the passphrase. -func (s *PrivateAccountAPI) ImportRawKey(privkey string, password string) (common.Address, error) { - key, err := crypto.HexToECDSA(privkey) +func (s *PrivateAccountAPI) ImportRawKey(privkey0, privkey1 string, password string) (common.Address, error) { + if strings.HasPrefix(privkey0, "0x") { + privkey0 = privkey0[2:] + } + if strings.HasPrefix(privkey1, "0x") { + privkey1 = privkey1[2:] + } + + r0, err := hex.DecodeString(privkey0) if err != nil { return common.Address{}, err } - acc, err := fetchKeystore(s.am).ImportECDSA(key, password) + + r1, err := hex.DecodeString(privkey1) + if err != nil { + return common.Address{}, err + } + + sk0, err := crypto.ToECDSA(r0) + if err != nil { + return common.Address{}, nil + } + + sk1, err := crypto.ToECDSA(r1) + if err != nil { + return common.Address{}, nil + } + + acc, err := fetchKeystore(s.am).ImportECDSA(sk0, sk1, password) return acc.Address, err } diff --git a/miner/stress_clique.go b/miner/stress_clique.go index 7f5db2e520..915198cb28 100644 --- a/miner/stress_clique.go +++ b/miner/stress_clique.go @@ -84,7 +84,7 @@ func main() { // Inject the signer key and start sealing with it store := node.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) - signer, err := store.ImportECDSA(sealer, "") + signer, err := store.ImportECDSA(sealer, nil, "") if err != nil { panic(err) } diff --git a/mobile/accounts.go b/mobile/accounts.go index 4d979bffff..a209b36f10 100644 --- a/mobile/accounts.go +++ b/mobile/accounts.go @@ -203,7 +203,7 @@ func (ks *KeyStore) ImportECDSAKey(key []byte, passphrase string) (account *Acco if err != nil { return nil, err } - acc, err := ks.keystore.ImportECDSA(privkey, passphrase) + acc, err := ks.keystore.ImportECDSA(privkey, nil, passphrase) if err != nil { return nil, err } diff --git a/signer/core/uiapi.go b/signer/core/uiapi.go index 7c2d233f89..38394815b3 100644 --- a/signer/core/uiapi.go +++ b/signer/core/uiapi.go @@ -118,8 +118,12 @@ func fetchKeystore(am *accounts.Manager) *keystore.KeyStore { // encrypting it with the passphrase. // Example call (should fail on password too short) // {"jsonrpc":"2.0","method":"clef_importRawKey","params":["1111111111111111111111111111111111111111111111111111111111111111","test"], "id":6} -func (s *UIServerAPI) ImportRawKey(privkey string, password string) (accounts.Account, error) { - key, err := crypto.HexToECDSA(privkey) +func (s *UIServerAPI) ImportRawKey(privkey1, privkey2 string, password string) (accounts.Account, error) { + key1, err := crypto.HexToECDSA(privkey1) + if err != nil { + return accounts.Account{}, err + } + key2, err := crypto.HexToECDSA(privkey2) if err != nil { return accounts.Account{}, err } @@ -127,7 +131,7 @@ func (s *UIServerAPI) ImportRawKey(privkey string, password string) (accounts.Ac return accounts.Account{}, fmt.Errorf("password requirements not met: %v", err) } // No error - return fetchKeystore(s.am).ImportECDSA(key, password) + return fetchKeystore(s.am).ImportECDSA(key1, key2, password) } // OpenWallet initiates a hardware wallet opening procedure, establishing a USB