github.com/johnathanhowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/wallet/encrypt.go (about) 1 package wallet 2 3 import ( 4 "bytes" 5 "crypto/rand" 6 "errors" 7 "time" 8 9 "github.com/NebulousLabs/Sia/build" 10 "github.com/NebulousLabs/Sia/crypto" 11 "github.com/NebulousLabs/Sia/modules" 12 "github.com/NebulousLabs/Sia/types" 13 ) 14 15 var ( 16 errAlreadyUnlocked = errors.New("wallet has already been unlocked") 17 errReencrypt = errors.New("wallet is already encrypted, cannot encrypt again") 18 errUnencryptedWallet = errors.New("wallet has not been encrypted yet") 19 20 unlockModifier = types.Specifier{'u', 'n', 'l', 'o', 'c', 'k'} 21 ) 22 23 // uidEncryptionKey creates an encryption key that is used to decrypt a 24 // specific key file. 25 func uidEncryptionKey(masterKey crypto.TwofishKey, uid UniqueID) crypto.TwofishKey { 26 return crypto.TwofishKey(crypto.HashAll(masterKey, uid)) 27 } 28 29 // checkMasterKey verifies that the master key is correct. 30 func (w *Wallet) checkMasterKey(masterKey crypto.TwofishKey) error { 31 uk := uidEncryptionKey(masterKey, w.persist.UID) 32 verification, err := uk.DecryptBytes(w.persist.EncryptionVerification) 33 if err != nil { 34 // Most of the time, the failure is an authentication failure. 35 return modules.ErrBadEncryptionKey 36 } 37 expected := make([]byte, encryptionVerificationLen) 38 if !bytes.Equal(expected, verification) { 39 return modules.ErrBadEncryptionKey 40 } 41 return nil 42 } 43 44 // initEncryption checks that the provided encryption key is the valid 45 // encryption key for the wallet. If encryption has not yet been established 46 // for the wallet, an encryption key is created. 47 func (w *Wallet) initEncryption(masterKey crypto.TwofishKey) (modules.Seed, error) { 48 // Check if the wallet encryption key has already been set. 49 if len(w.persist.EncryptionVerification) != 0 { 50 return modules.Seed{}, errReencrypt 51 } 52 53 // Create a random seed and use it to generate the seed file for the 54 // wallet. 55 var seed modules.Seed 56 _, err := rand.Read(seed[:]) 57 if err != nil { 58 return modules.Seed{}, err 59 } 60 61 // If the input key is blank, use the seed to create the master key. 62 // Otherwise, use the input key. 63 if masterKey == (crypto.TwofishKey{}) { 64 masterKey = crypto.TwofishKey(crypto.HashObject(seed)) 65 } 66 err = w.createSeed(masterKey, seed) 67 if err != nil { 68 return modules.Seed{}, err 69 } 70 71 // Establish the encryption verification using the masterKey. After this 72 // point, the wallet is encrypted. 73 uk := uidEncryptionKey(masterKey, w.persist.UID) 74 encryptionBase := make([]byte, encryptionVerificationLen) 75 w.persist.EncryptionVerification, err = uk.EncryptBytes(encryptionBase) 76 if err != nil { 77 return modules.Seed{}, err 78 } 79 err = w.saveSettings() 80 if err != nil { 81 return modules.Seed{}, err 82 } 83 return seed, nil 84 } 85 86 // unlock loads all of the encrypted file structures into wallet memory. Even 87 // after loading, the structures are kept encrypted, but some data such as 88 // addresses are decrypted so that the wallet knows what to track. 89 func (w *Wallet) unlock(masterKey crypto.TwofishKey) error { 90 // Wallet should only be unlocked once. 91 if w.unlocked { 92 return errAlreadyUnlocked 93 } 94 95 // Check if the wallet encryption key has already been set. 96 if len(w.persist.EncryptionVerification) == 0 { 97 return errUnencryptedWallet 98 } 99 100 // Initialize the encryption of the wallet. 101 err := w.checkMasterKey(masterKey) 102 if err != nil { 103 return err 104 } 105 106 // Load the wallet seed that is used to generate new addresses. 107 err = w.initPrimarySeed(masterKey) 108 if err != nil { 109 return err 110 } 111 112 // Load all wallet seeds that are not used to generate new addresses. 113 err = w.initAuxiliarySeeds(masterKey) 114 if err != nil { 115 return err 116 } 117 118 // Load all keys that were not generated by a seed. 119 err = w.initUnseededKeys(masterKey) 120 if err != nil { 121 return err 122 } 123 w.unlocked = true 124 return nil 125 } 126 127 // wipeSecrets erases all of the seeds and secret keys in the wallet. 128 func (w *Wallet) wipeSecrets() { 129 // 'for i := range' must be used to prevent copies of secret data from 130 // being made. 131 for i := range w.keys { 132 for j := range w.keys[i].SecretKeys { 133 crypto.SecureWipe(w.keys[i].SecretKeys[j][:]) 134 } 135 } 136 for i := range w.seeds { 137 crypto.SecureWipe(w.seeds[i][:]) 138 } 139 crypto.SecureWipe(w.primarySeed[:]) 140 w.seeds = w.seeds[:0] 141 } 142 143 // Encrypted returns whether or not the wallet has been encrypted. 144 func (w *Wallet) Encrypted() bool { 145 w.mu.Lock() 146 defer w.mu.Unlock() 147 if build.DEBUG && w.unlocked && len(w.persist.EncryptionVerification) == 0 { 148 panic("wallet is both unlocked and unencrypted") 149 } 150 return len(w.persist.EncryptionVerification) != 0 151 } 152 153 // Encrypt will encrypt the wallet using the input key. Upon encryption, a 154 // primary seed will be created for the wallet (no seed exists prior to this 155 // point). If the key is blank, then the hash of the seed that is generated 156 // will be used as the key. The wallet will still be locked after encryption. 157 // 158 // Encrypt can only be called once throughout the life of the wallet, and will 159 // return an error on subsequent calls (even after restarting the wallet). To 160 // reset the wallet, the wallet files must be moved to a different directory or 161 // deleted. 162 func (w *Wallet) Encrypt(masterKey crypto.TwofishKey) (modules.Seed, error) { 163 w.mu.Lock() 164 defer w.mu.Unlock() 165 return w.initEncryption(masterKey) 166 } 167 168 // Unlocked indicates whether the wallet is locked or unlocked. 169 func (w *Wallet) Unlocked() bool { 170 w.mu.RLock() 171 defer w.mu.RUnlock() 172 return w.unlocked 173 } 174 175 // Lock will erase all keys from memory and prevent the wallet from spending 176 // coins until it is unlocked. 177 func (w *Wallet) Lock() error { 178 w.mu.Lock() 179 defer w.mu.Unlock() 180 if !w.unlocked { 181 return modules.ErrLockedWallet 182 } 183 w.log.Println("INFO: Locking wallet.") 184 185 // Wipe all of the seeds and secret keys, they will be replaced upon 186 // calling 'Unlock' again. 187 w.wipeSecrets() 188 w.unlocked = false 189 return nil 190 } 191 192 // Unlock will decrypt the wallet seed and load all of the addresses into 193 // memory. 194 func (w *Wallet) Unlock(masterKey crypto.TwofishKey) error { 195 w.log.Println("INFO: Unlocking wallet.") 196 197 // Initialize all of the keys in the wallet under a lock. While holding the 198 // lock, also grab the subscriber status. 199 w.mu.Lock() 200 subscribed := w.subscribed 201 err := w.unlock(masterKey) 202 w.mu.Unlock() 203 if err != nil { 204 return err 205 } 206 207 // Subscribe to the consensus set if this is the first unlock for the 208 // wallet object. 209 if !subscribed { 210 // During rescan, print height every 3 seconds. 211 if build.Release != "testing" { 212 go func() { 213 println("Rescanning consensus set...") 214 for range time.Tick(time.Second * 3) { 215 w.mu.RLock() 216 height := w.consensusSetHeight 217 done := w.subscribed 218 w.mu.RUnlock() 219 if done { 220 println("\nDone!") 221 break 222 } 223 print("\rScanned to height ", height, "...") 224 } 225 }() 226 } 227 err = w.cs.ConsensusSetSubscribe(w, modules.ConsensusChangeBeginning) 228 if err != nil { 229 return errors.New("wallet subscription failed: " + err.Error()) 230 } 231 w.tpool.TransactionPoolSubscribe(w) 232 w.mu.Lock() 233 w.subscribed = true 234 w.mu.Unlock() 235 } 236 return nil 237 }