gitlab.com/jokerrs1/Sia@v1.3.2/modules/wallet/encrypt.go (about) 1 package wallet 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "time" 8 9 "github.com/NebulousLabs/Sia/build" 10 "github.com/NebulousLabs/Sia/crypto" 11 "github.com/NebulousLabs/Sia/encoding" 12 "github.com/NebulousLabs/Sia/modules" 13 "github.com/NebulousLabs/Sia/types" 14 "github.com/NebulousLabs/fastrand" 15 "github.com/coreos/bbolt" 16 ) 17 18 var ( 19 errAlreadyUnlocked = errors.New("wallet has already been unlocked") 20 errReencrypt = errors.New("wallet is already encrypted, cannot encrypt again") 21 errScanInProgress = errors.New("another wallet rescan is already underway") 22 errUnencryptedWallet = errors.New("wallet has not been encrypted yet") 23 24 // verificationPlaintext is the plaintext used to verify encryption keys. 25 // By storing the corresponding ciphertext for a given key, we can later 26 // verify that a key is correct by using it to decrypt the ciphertext and 27 // comparing the result to verificationPlaintext. 28 verificationPlaintext = make([]byte, 32) 29 ) 30 31 // uidEncryptionKey creates an encryption key that is used to decrypt a 32 // specific key file. 33 func uidEncryptionKey(masterKey crypto.TwofishKey, uid uniqueID) crypto.TwofishKey { 34 return crypto.TwofishKey(crypto.HashAll(masterKey, uid)) 35 } 36 37 // verifyEncryption verifies that key properly decrypts the ciphertext to a 38 // preset plaintext. 39 func verifyEncryption(key crypto.TwofishKey, encrypted crypto.Ciphertext) error { 40 verification, err := key.DecryptBytes(encrypted) 41 if err != nil { 42 return modules.ErrBadEncryptionKey 43 } 44 if !bytes.Equal(verificationPlaintext, verification) { 45 return modules.ErrBadEncryptionKey 46 } 47 return nil 48 } 49 50 // checkMasterKey verifies that the masterKey is the key used to encrypt the wallet. 51 func checkMasterKey(tx *bolt.Tx, masterKey crypto.TwofishKey) error { 52 uk := uidEncryptionKey(masterKey, dbGetWalletUID(tx)) 53 encryptedVerification := tx.Bucket(bucketWallet).Get(keyEncryptionVerification) 54 return verifyEncryption(uk, encryptedVerification) 55 } 56 57 // initEncryption initializes and encrypts the primary SeedFile. 58 func (w *Wallet) initEncryption(masterKey crypto.TwofishKey, seed modules.Seed, progress uint64) (modules.Seed, error) { 59 wb := w.dbTx.Bucket(bucketWallet) 60 // Check if the wallet encryption key has already been set. 61 if wb.Get(keyEncryptionVerification) != nil { 62 return modules.Seed{}, errReencrypt 63 } 64 65 // create a seedFile for the seed 66 sf := createSeedFile(masterKey, seed) 67 68 // set this as the primary seedFile 69 err := wb.Put(keyPrimarySeedFile, encoding.Marshal(sf)) 70 if err != nil { 71 return modules.Seed{}, err 72 } 73 err = wb.Put(keyPrimarySeedProgress, encoding.Marshal(progress)) 74 if err != nil { 75 return modules.Seed{}, err 76 } 77 78 // Establish the encryption verification using the masterKey. After this 79 // point, the wallet is encrypted. 80 uk := uidEncryptionKey(masterKey, dbGetWalletUID(w.dbTx)) 81 err = wb.Put(keyEncryptionVerification, uk.EncryptBytes(verificationPlaintext)) 82 if err != nil { 83 return modules.Seed{}, err 84 } 85 86 // on future startups, this field will be set by w.initPersist 87 w.encrypted = true 88 89 return seed, nil 90 } 91 92 // managedUnlock loads all of the encrypted file structures into wallet memory. Even 93 // after loading, the structures are kept encrypted, but some data such as 94 // addresses are decrypted so that the wallet knows what to track. 95 func (w *Wallet) managedUnlock(masterKey crypto.TwofishKey) error { 96 w.mu.RLock() 97 unlocked := w.unlocked 98 encrypted := w.encrypted 99 w.mu.RUnlock() 100 if unlocked { 101 return errAlreadyUnlocked 102 } else if !encrypted { 103 return errUnencryptedWallet 104 } 105 106 // Load db objects into memory. 107 var lastChange modules.ConsensusChangeID 108 var primarySeedFile seedFile 109 var primarySeedProgress uint64 110 var auxiliarySeedFiles []seedFile 111 var unseededKeyFiles []spendableKeyFile 112 err := func() error { 113 w.mu.Lock() 114 defer w.mu.Unlock() 115 116 // verify masterKey 117 err := checkMasterKey(w.dbTx, masterKey) 118 if err != nil { 119 return err 120 } 121 122 // lastChange 123 lastChange = dbGetConsensusChangeID(w.dbTx) 124 125 // primarySeedFile + primarySeedProgress 126 wb := w.dbTx.Bucket(bucketWallet) 127 err = encoding.Unmarshal(wb.Get(keyPrimarySeedFile), &primarySeedFile) 128 if err != nil { 129 return err 130 } 131 err = encoding.Unmarshal(wb.Get(keyPrimarySeedProgress), &primarySeedProgress) 132 if err != nil { 133 return err 134 } 135 136 // auxiliarySeedFiles 137 err = encoding.Unmarshal(wb.Get(keyAuxiliarySeedFiles), &auxiliarySeedFiles) 138 if err != nil { 139 return err 140 } 141 142 // unseededKeyFiles 143 err = encoding.Unmarshal(wb.Get(keySpendableKeyFiles), &unseededKeyFiles) 144 if err != nil { 145 return err 146 } 147 148 return nil 149 }() 150 if err != nil { 151 return err 152 } 153 154 // Decrypt + load keys. 155 err = func() error { 156 w.mu.Lock() 157 defer w.mu.Unlock() 158 159 // primarySeedFile 160 primarySeed, err := decryptSeedFile(masterKey, primarySeedFile) 161 if err != nil { 162 return err 163 } 164 w.integrateSeed(primarySeed, primarySeedProgress) 165 w.primarySeed = primarySeed 166 w.regenerateLookahead(primarySeedProgress) 167 168 // auxiliarySeedFiles 169 for _, sf := range auxiliarySeedFiles { 170 auxSeed, err := decryptSeedFile(masterKey, sf) 171 if err != nil { 172 return err 173 } 174 w.integrateSeed(auxSeed, modules.PublicKeysPerSeed) 175 w.seeds = append(w.seeds, auxSeed) 176 } 177 178 // unseededKeyFiles 179 for _, uk := range unseededKeyFiles { 180 sk, err := decryptSpendableKeyFile(masterKey, uk) 181 if err != nil { 182 return err 183 } 184 w.integrateSpendableKey(masterKey, sk) 185 } 186 return nil 187 }() 188 if err != nil { 189 return err 190 } 191 192 // Subscribe to the consensus set if this is the first unlock for the 193 // wallet object. 194 w.mu.RLock() 195 subscribed := w.subscribed 196 w.mu.RUnlock() 197 if !subscribed { 198 // Subscription can take a while, so spawn a goroutine to print the 199 // wallet height every few seconds. (If subscription completes 200 // quickly, nothing will be printed.) 201 done := make(chan struct{}) 202 go w.rescanMessage(done) 203 defer close(done) 204 205 err = w.cs.ConsensusSetSubscribe(w, lastChange, w.tg.StopChan()) 206 if err == modules.ErrInvalidConsensusChangeID { 207 // something went wrong; resubscribe from the beginning 208 err = dbPutConsensusChangeID(w.dbTx, modules.ConsensusChangeBeginning) 209 if err != nil { 210 return fmt.Errorf("failed to reset db during rescan: %v", err) 211 } 212 err = dbPutConsensusHeight(w.dbTx, 0) 213 if err != nil { 214 return fmt.Errorf("failed to reset db during rescan: %v", err) 215 } 216 err = w.cs.ConsensusSetSubscribe(w, modules.ConsensusChangeBeginning, w.tg.StopChan()) 217 } 218 if err != nil { 219 return fmt.Errorf("wallet subscription failed: %v", err) 220 } 221 w.tpool.TransactionPoolSubscribe(w) 222 } 223 224 w.mu.Lock() 225 w.unlocked = true 226 w.subscribed = true 227 w.mu.Unlock() 228 return nil 229 } 230 231 // rescanMessage prints the blockheight every 3 seconds until done is closed. 232 func (w *Wallet) rescanMessage(done chan struct{}) { 233 if build.Release == "testing" { 234 return 235 } 236 237 // sleep first because we may not need to print a message at all if 238 // done is closed quickly. 239 select { 240 case <-done: 241 return 242 case <-time.After(3 * time.Second): 243 } 244 245 for { 246 w.mu.Lock() 247 height, _ := dbGetConsensusHeight(w.dbTx) 248 w.mu.Unlock() 249 print("\rWallet: scanned to height ", height, "...") 250 251 select { 252 case <-done: 253 println("\nDone!") 254 return 255 case <-time.After(3 * time.Second): 256 } 257 } 258 } 259 260 // wipeSecrets erases all of the seeds and secret keys in the wallet. 261 func (w *Wallet) wipeSecrets() { 262 // 'for i := range' must be used to prevent copies of secret data from 263 // being made. 264 for i := range w.keys { 265 for j := range w.keys[i].SecretKeys { 266 crypto.SecureWipe(w.keys[i].SecretKeys[j][:]) 267 } 268 } 269 for i := range w.seeds { 270 crypto.SecureWipe(w.seeds[i][:]) 271 } 272 crypto.SecureWipe(w.primarySeed[:]) 273 w.seeds = w.seeds[:0] 274 } 275 276 // Encrypted returns whether or not the wallet has been encrypted. 277 func (w *Wallet) Encrypted() bool { 278 w.mu.Lock() 279 defer w.mu.Unlock() 280 if build.DEBUG && w.unlocked && !w.encrypted { 281 panic("wallet is both unlocked and unencrypted") 282 } 283 return w.encrypted 284 } 285 286 // Encrypt will create a primary seed for the wallet and encrypt it using 287 // masterKey. If masterKey is blank, then the hash of the primary seed will be 288 // used instead. The wallet will still be locked after Encrypt is called. 289 // 290 // Encrypt can only be called once throughout the life of the wallet, and will 291 // return an error on subsequent calls (even after restarting the wallet). To 292 // reset the wallet, the wallet files must be moved to a different directory 293 // or deleted. 294 func (w *Wallet) Encrypt(masterKey crypto.TwofishKey) (modules.Seed, error) { 295 if err := w.tg.Add(); err != nil { 296 return modules.Seed{}, err 297 } 298 defer w.tg.Done() 299 w.mu.Lock() 300 defer w.mu.Unlock() 301 302 // Create a random seed. 303 var seed modules.Seed 304 fastrand.Read(seed[:]) 305 306 // If masterKey is blank, use the hash of the seed. 307 if masterKey == (crypto.TwofishKey{}) { 308 masterKey = crypto.TwofishKey(crypto.HashObject(seed)) 309 } 310 // Initial seed progress is 0. 311 return w.initEncryption(masterKey, seed, 0) 312 } 313 314 // Reset will reset the wallet, clearing the database and returning it to 315 // the unencrypted state. Reset can only be called on a wallet that has 316 // already been encrypted. 317 func (w *Wallet) Reset() error { 318 if err := w.tg.Add(); err != nil { 319 return err 320 } 321 defer w.tg.Done() 322 w.mu.Lock() 323 defer w.mu.Unlock() 324 325 wb := w.dbTx.Bucket(bucketWallet) 326 if wb.Get(keyEncryptionVerification) == nil { 327 return errUnencryptedWallet 328 } 329 330 w.cs.Unsubscribe(w) 331 w.tpool.Unsubscribe(w) 332 333 err := dbReset(w.dbTx) 334 if err != nil { 335 return err 336 } 337 w.wipeSecrets() 338 w.keys = make(map[types.UnlockHash]spendableKey) 339 w.lookahead = make(map[types.UnlockHash]uint64) 340 w.seeds = []modules.Seed{} 341 w.unconfirmedProcessedTransactions = []modules.ProcessedTransaction{} 342 w.unlocked = false 343 w.encrypted = false 344 w.subscribed = false 345 346 return nil 347 } 348 349 // InitFromSeed functions like Init, but using a specified seed. Unlike Init, 350 // the blockchain will be scanned to determine the seed's progress. For this 351 // reason, InitFromSeed should not be called until the blockchain is fully 352 // synced. 353 func (w *Wallet) InitFromSeed(masterKey crypto.TwofishKey, seed modules.Seed) error { 354 if err := w.tg.Add(); err != nil { 355 return err 356 } 357 defer w.tg.Done() 358 359 if !w.cs.Synced() { 360 return errors.New("cannot init from seed until blockchain is synced") 361 } 362 363 // If masterKey is blank, use the hash of the seed. 364 if masterKey == (crypto.TwofishKey{}) { 365 masterKey = crypto.TwofishKey(crypto.HashObject(seed)) 366 } 367 368 if !w.scanLock.TryLock() { 369 return errScanInProgress 370 } 371 defer w.scanLock.Unlock() 372 373 // estimate the primarySeedProgress by scanning the blockchain 374 s := newSeedScanner(seed, w.log) 375 if err := s.scan(w.cs, w.tg.StopChan()); err != nil { 376 return err 377 } 378 // NOTE: each time the wallet generates a key for index n, it sets its 379 // progress to n+1, so the progress should be the largest index seen + 1. 380 // We also add 10% as a buffer because the seed may have addresses in the 381 // wild that have not appeared in the blockchain yet. 382 progress := s.largestIndexSeen + 1 383 progress += progress / 10 384 w.log.Printf("INFO: found key index %v in blockchain. Setting primary seed progress to %v", s.largestIndexSeen, progress) 385 386 // initialize the wallet with the appropriate seed progress 387 w.mu.Lock() 388 defer w.mu.Unlock() 389 _, err := w.initEncryption(masterKey, seed, progress) 390 return err 391 } 392 393 // Unlocked indicates whether the wallet is locked or unlocked. 394 func (w *Wallet) Unlocked() bool { 395 w.mu.RLock() 396 defer w.mu.RUnlock() 397 return w.unlocked 398 } 399 400 // Lock will erase all keys from memory and prevent the wallet from spending 401 // coins until it is unlocked. 402 func (w *Wallet) Lock() error { 403 w.mu.Lock() 404 defer w.mu.Unlock() 405 if !w.unlocked { 406 return modules.ErrLockedWallet 407 } 408 w.log.Println("INFO: Locking wallet.") 409 410 // Wipe all of the seeds and secret keys. They will be replaced upon 411 // calling 'Unlock' again. Note that since the public keys are not wiped, 412 // we can continue processing blocks. 413 w.wipeSecrets() 414 w.unlocked = false 415 return nil 416 } 417 418 // managedChangeKey safely performs the database operations required to change 419 // the wallet's encryption key. 420 func (w *Wallet) managedChangeKey(masterKey crypto.TwofishKey, newKey crypto.TwofishKey) error { 421 w.mu.Lock() 422 encrypted := w.encrypted 423 w.mu.Unlock() 424 if !encrypted { 425 return errUnencryptedWallet 426 } 427 428 // grab the current seed files 429 var primarySeedFile seedFile 430 var auxiliarySeedFiles []seedFile 431 var unseededKeyFiles []spendableKeyFile 432 433 err := func() error { 434 w.mu.Lock() 435 defer w.mu.Unlock() 436 437 // verify masterKey 438 err := checkMasterKey(w.dbTx, masterKey) 439 if err != nil { 440 return err 441 } 442 443 wb := w.dbTx.Bucket(bucketWallet) 444 445 // primarySeedFile 446 err = encoding.Unmarshal(wb.Get(keyPrimarySeedFile), &primarySeedFile) 447 if err != nil { 448 return err 449 } 450 451 // auxiliarySeedFiles 452 err = encoding.Unmarshal(wb.Get(keyAuxiliarySeedFiles), &auxiliarySeedFiles) 453 if err != nil { 454 return err 455 } 456 457 // unseededKeyFiles 458 err = encoding.Unmarshal(wb.Get(keySpendableKeyFiles), &unseededKeyFiles) 459 if err != nil { 460 return err 461 } 462 463 return nil 464 }() 465 if err != nil { 466 return err 467 } 468 469 // decrypt key files 470 var primarySeed modules.Seed 471 var auxiliarySeeds []modules.Seed 472 var spendableKeys []spendableKey 473 474 primarySeed, err = decryptSeedFile(masterKey, primarySeedFile) 475 if err != nil { 476 return err 477 } 478 for _, sf := range auxiliarySeedFiles { 479 auxSeed, err := decryptSeedFile(masterKey, sf) 480 if err != nil { 481 return err 482 } 483 auxiliarySeeds = append(auxiliarySeeds, auxSeed) 484 } 485 for _, uk := range unseededKeyFiles { 486 sk, err := decryptSpendableKeyFile(masterKey, uk) 487 if err != nil { 488 return err 489 } 490 spendableKeys = append(spendableKeys, sk) 491 } 492 493 // encrypt new keyfiles using newKey 494 var newPrimarySeedFile seedFile 495 var newAuxiliarySeedFiles []seedFile 496 var newUnseededKeyFiles []spendableKeyFile 497 498 newPrimarySeedFile = createSeedFile(newKey, primarySeed) 499 for _, seed := range auxiliarySeeds { 500 newAuxiliarySeedFiles = append(newAuxiliarySeedFiles, createSeedFile(newKey, seed)) 501 } 502 for _, sk := range spendableKeys { 503 var skf spendableKeyFile 504 fastrand.Read(skf.UID[:]) 505 encryptionKey := uidEncryptionKey(newKey, skf.UID) 506 skf.EncryptionVerification = encryptionKey.EncryptBytes(verificationPlaintext) 507 508 // Encrypt and save the key. 509 skf.SpendableKey = encryptionKey.EncryptBytes(encoding.Marshal(sk)) 510 newUnseededKeyFiles = append(newUnseededKeyFiles, skf) 511 } 512 513 // put the newly encrypted keys in the database 514 err = func() error { 515 w.mu.Lock() 516 defer w.mu.Unlock() 517 518 wb := w.dbTx.Bucket(bucketWallet) 519 520 err = wb.Put(keyPrimarySeedFile, encoding.Marshal(newPrimarySeedFile)) 521 if err != nil { 522 return err 523 } 524 err = wb.Put(keyAuxiliarySeedFiles, encoding.Marshal(newAuxiliarySeedFiles)) 525 if err != nil { 526 return err 527 } 528 err = wb.Put(keySpendableKeyFiles, encoding.Marshal(newUnseededKeyFiles)) 529 if err != nil { 530 return err 531 } 532 533 uk := uidEncryptionKey(newKey, dbGetWalletUID(w.dbTx)) 534 err = wb.Put(keyEncryptionVerification, uk.EncryptBytes(verificationPlaintext)) 535 if err != nil { 536 return err 537 } 538 539 return nil 540 }() 541 if err != nil { 542 return err 543 } 544 545 return nil 546 } 547 548 // ChangeKey changes the wallet's encryption key from masterKey to newKey. 549 func (w *Wallet) ChangeKey(masterKey crypto.TwofishKey, newKey crypto.TwofishKey) error { 550 if err := w.tg.Add(); err != nil { 551 return err 552 } 553 defer w.tg.Done() 554 555 return w.managedChangeKey(masterKey, newKey) 556 } 557 558 // Unlock will decrypt the wallet seed and load all of the addresses into 559 // memory. 560 func (w *Wallet) Unlock(masterKey crypto.TwofishKey) error { 561 // By having the wallet's ThreadGroup track the Unlock method, we ensure 562 // that Unlock will never unlock the wallet once the ThreadGroup has been 563 // stopped. Without this precaution, the wallet's Close method would be 564 // unsafe because it would theoretically be possible for another function 565 // to Unlock the wallet in the short interval after Close calls w.Lock 566 // and before Close calls w.mu.Lock. 567 if err := w.tg.Add(); err != nil { 568 return err 569 } 570 defer w.tg.Done() 571 572 if !w.scanLock.TryLock() { 573 return errScanInProgress 574 } 575 defer w.scanLock.Unlock() 576 577 w.log.Println("INFO: Unlocking wallet.") 578 579 // Initialize all of the keys in the wallet under a lock. While holding the 580 // lock, also grab the subscriber status. 581 return w.managedUnlock(masterKey) 582 }