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