github.com/ava-labs/subnet-evm@v0.6.4/accounts/keystore/presale.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. 2 // 3 // This file is a derived work, based on the go-ethereum library whose original 4 // notices appear below. 5 // 6 // It is distributed under a license compatible with the licensing terms of the 7 // original code from which it is derived. 8 // 9 // Much love to the original authors for their work. 10 // ********** 11 // Copyright 2016 The go-ethereum Authors 12 // This file is part of the go-ethereum library. 13 // 14 // The go-ethereum library is free software: you can redistribute it and/or modify 15 // it under the terms of the GNU Lesser General Public License as published by 16 // the Free Software Foundation, either version 3 of the License, or 17 // (at your option) any later version. 18 // 19 // The go-ethereum library is distributed in the hope that it will be useful, 20 // but WITHOUT ANY WARRANTY; without even the implied warranty of 21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 // GNU Lesser General Public License for more details. 23 // 24 // You should have received a copy of the GNU Lesser General Public License 25 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 26 27 package keystore 28 29 import ( 30 "crypto/aes" 31 "crypto/cipher" 32 "crypto/sha256" 33 "encoding/hex" 34 "encoding/json" 35 "errors" 36 "fmt" 37 38 "github.com/ava-labs/subnet-evm/accounts" 39 "github.com/ethereum/go-ethereum/crypto" 40 "github.com/google/uuid" 41 "golang.org/x/crypto/pbkdf2" 42 ) 43 44 // creates a Key and stores that in the given KeyStore by decrypting a presale key JSON 45 func importPreSaleKey(keyStore keyStore, keyJSON []byte, password string) (accounts.Account, *Key, error) { 46 key, err := decryptPreSaleKey(keyJSON, password) 47 if err != nil { 48 return accounts.Account{}, nil, err 49 } 50 key.Id, err = uuid.NewRandom() 51 if err != nil { 52 return accounts.Account{}, nil, err 53 } 54 a := accounts.Account{ 55 Address: key.Address, 56 URL: accounts.URL{ 57 Scheme: KeyStoreScheme, 58 Path: keyStore.JoinPath(keyFileName(key.Address)), 59 }, 60 } 61 err = keyStore.StoreKey(a.URL.Path, key, password) 62 return a, key, err 63 } 64 65 func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error) { 66 preSaleKeyStruct := struct { 67 EncSeed string 68 EthAddr string 69 Email string 70 BtcAddr string 71 }{} 72 err = json.Unmarshal(fileContent, &preSaleKeyStruct) 73 if err != nil { 74 return nil, err 75 } 76 encSeedBytes, err := hex.DecodeString(preSaleKeyStruct.EncSeed) 77 if err != nil { 78 return nil, errors.New("invalid hex in encSeed") 79 } 80 if len(encSeedBytes) < 16 { 81 return nil, errors.New("invalid encSeed, too short") 82 } 83 iv := encSeedBytes[:16] 84 cipherText := encSeedBytes[16:] 85 /* 86 See https://github.com/ethereum/pyethsaletool 87 88 pyethsaletool generates the encryption key from password by 89 2000 rounds of PBKDF2 with HMAC-SHA-256 using password as salt (:(). 90 16 byte key length within PBKDF2 and resulting key is used as AES key 91 */ 92 passBytes := []byte(password) 93 derivedKey := pbkdf2.Key(passBytes, passBytes, 2000, 16, sha256.New) 94 plainText, err := aesCBCDecrypt(derivedKey, cipherText, iv) 95 if err != nil { 96 return nil, err 97 } 98 ethPriv := crypto.Keccak256(plainText) 99 ecKey := crypto.ToECDSAUnsafe(ethPriv) 100 101 key = &Key{ 102 Id: uuid.UUID{}, 103 Address: crypto.PubkeyToAddress(ecKey.PublicKey), 104 PrivateKey: ecKey, 105 } 106 derivedAddr := hex.EncodeToString(key.Address.Bytes()) // needed because .Hex() gives leading "0x" 107 expectedAddr := preSaleKeyStruct.EthAddr 108 if derivedAddr != expectedAddr { 109 err = fmt.Errorf("decrypted addr '%s' not equal to expected addr '%s'", derivedAddr, expectedAddr) 110 } 111 return key, err 112 } 113 114 func aesCTRXOR(key, inText, iv []byte) ([]byte, error) { 115 // AES-128 is selected due to size of encryptKey. 116 aesBlock, err := aes.NewCipher(key) 117 if err != nil { 118 return nil, err 119 } 120 stream := cipher.NewCTR(aesBlock, iv) 121 outText := make([]byte, len(inText)) 122 stream.XORKeyStream(outText, inText) 123 return outText, err 124 } 125 126 func aesCBCDecrypt(key, cipherText, iv []byte) ([]byte, error) { 127 aesBlock, err := aes.NewCipher(key) 128 if err != nil { 129 return nil, err 130 } 131 decrypter := cipher.NewCBCDecrypter(aesBlock, iv) 132 paddedPlaintext := make([]byte, len(cipherText)) 133 decrypter.CryptBlocks(paddedPlaintext, cipherText) 134 plaintext := pkcs7Unpad(paddedPlaintext) 135 if plaintext == nil { 136 return nil, ErrDecrypt 137 } 138 return plaintext, err 139 } 140 141 // From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes 142 func pkcs7Unpad(in []byte) []byte { 143 if len(in) == 0 { 144 return nil 145 } 146 147 padding := in[len(in)-1] 148 if int(padding) > len(in) || padding > aes.BlockSize { 149 return nil 150 } else if padding == 0 { 151 return nil 152 } 153 154 for i := len(in) - 1; i > len(in)-int(padding)-1; i-- { 155 if in[i] != padding { 156 return nil 157 } 158 } 159 return in[:len(in)-int(padding)] 160 }