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