github.com/ZuluSpl0it/Sia@v1.3.7/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, error) { 278 if err := w.tg.Add(); err != nil { 279 return false, err 280 } 281 defer w.tg.Done() 282 w.mu.Lock() 283 defer w.mu.Unlock() 284 if build.DEBUG && w.unlocked && !w.encrypted { 285 panic("wallet is both unlocked and unencrypted") 286 } 287 return w.encrypted, nil 288 } 289 290 // Encrypt will create a primary seed for the wallet and encrypt it using 291 // masterKey. If masterKey is blank, then the hash of the primary seed will be 292 // used instead. The wallet will still be locked after Encrypt is called. 293 // 294 // Encrypt can only be called once throughout the life of the wallet, and will 295 // return an error on subsequent calls (even after restarting the wallet). To 296 // reset the wallet, the wallet files must be moved to a different directory 297 // or deleted. 298 func (w *Wallet) Encrypt(masterKey crypto.TwofishKey) (modules.Seed, error) { 299 if err := w.tg.Add(); err != nil { 300 return modules.Seed{}, err 301 } 302 defer w.tg.Done() 303 w.mu.Lock() 304 defer w.mu.Unlock() 305 306 // Create a random seed. 307 var seed modules.Seed 308 fastrand.Read(seed[:]) 309 310 // If masterKey is blank, use the hash of the seed. 311 if masterKey == (crypto.TwofishKey{}) { 312 masterKey = crypto.TwofishKey(crypto.HashObject(seed)) 313 } 314 // Initial seed progress is 0. 315 return w.initEncryption(masterKey, seed, 0) 316 } 317 318 // Reset will reset the wallet, clearing the database and returning it to 319 // the unencrypted state. Reset can only be called on a wallet that has 320 // already been encrypted. 321 func (w *Wallet) Reset() error { 322 if err := w.tg.Add(); err != nil { 323 return err 324 } 325 defer w.tg.Done() 326 w.mu.Lock() 327 defer w.mu.Unlock() 328 329 wb := w.dbTx.Bucket(bucketWallet) 330 if wb.Get(keyEncryptionVerification) == nil { 331 return errUnencryptedWallet 332 } 333 334 w.cs.Unsubscribe(w) 335 w.tpool.Unsubscribe(w) 336 337 err := dbReset(w.dbTx) 338 if err != nil { 339 return err 340 } 341 w.wipeSecrets() 342 w.keys = make(map[types.UnlockHash]spendableKey) 343 w.lookahead = make(map[types.UnlockHash]uint64) 344 w.seeds = []modules.Seed{} 345 w.unconfirmedProcessedTransactions = []modules.ProcessedTransaction{} 346 w.unlocked = false 347 w.encrypted = false 348 w.subscribed = false 349 350 return nil 351 } 352 353 // InitFromSeed functions like Init, but using a specified seed. Unlike Init, 354 // the blockchain will be scanned to determine the seed's progress. For this 355 // reason, InitFromSeed should not be called until the blockchain is fully 356 // synced. 357 func (w *Wallet) InitFromSeed(masterKey crypto.TwofishKey, seed modules.Seed) error { 358 if err := w.tg.Add(); err != nil { 359 return err 360 } 361 defer w.tg.Done() 362 363 if !w.cs.Synced() { 364 return errors.New("cannot init from seed until blockchain is synced") 365 } 366 367 // If masterKey is blank, use the hash of the seed. 368 if masterKey == (crypto.TwofishKey{}) { 369 masterKey = crypto.TwofishKey(crypto.HashObject(seed)) 370 } 371 372 if !w.scanLock.TryLock() { 373 return errScanInProgress 374 } 375 defer w.scanLock.Unlock() 376 377 // estimate the primarySeedProgress by scanning the blockchain 378 s := newSeedScanner(seed, w.log) 379 if err := s.scan(w.cs, w.tg.StopChan()); err != nil { 380 return err 381 } 382 // NOTE: each time the wallet generates a key for index n, it sets its 383 // progress to n+1, so the progress should be the largest index seen + 1. 384 // We also add 10% as a buffer because the seed may have addresses in the 385 // wild that have not appeared in the blockchain yet. 386 progress := s.largestIndexSeen + 1 387 progress += progress / 10 388 w.log.Printf("INFO: found key index %v in blockchain. Setting primary seed progress to %v", s.largestIndexSeen, progress) 389 390 // initialize the wallet with the appropriate seed progress 391 w.mu.Lock() 392 defer w.mu.Unlock() 393 _, err := w.initEncryption(masterKey, seed, progress) 394 return err 395 } 396 397 // Unlocked indicates whether the wallet is locked or unlocked. 398 func (w *Wallet) Unlocked() (bool, error) { 399 if err := w.tg.Add(); err != nil { 400 return false, err 401 } 402 defer w.tg.Done() 403 w.mu.RLock() 404 defer w.mu.RUnlock() 405 return w.unlocked, nil 406 } 407 408 // Lock will erase all keys from memory and prevent the wallet from spending 409 // coins until it is unlocked. 410 func (w *Wallet) Lock() error { 411 if err := w.tg.Add(); err != nil { 412 return err 413 } 414 defer w.tg.Done() 415 return w.managedLock() 416 } 417 418 // ChangeKey changes the wallet's encryption key from masterKey to newKey. 419 func (w *Wallet) ChangeKey(masterKey crypto.TwofishKey, newKey crypto.TwofishKey) error { 420 if err := w.tg.Add(); err != nil { 421 return err 422 } 423 defer w.tg.Done() 424 425 return w.managedChangeKey(masterKey, newKey) 426 } 427 428 // Unlock will decrypt the wallet seed and load all of the addresses into 429 // memory. 430 func (w *Wallet) Unlock(masterKey crypto.TwofishKey) error { 431 // By having the wallet's ThreadGroup track the Unlock method, we ensure 432 // that Unlock will never unlock the wallet once the ThreadGroup has been 433 // stopped. Without this precaution, the wallet's Close method would be 434 // unsafe because it would theoretically be possible for another function 435 // to Unlock the wallet in the short interval after Close calls w.Lock 436 // and before Close calls w.mu.Lock. 437 if err := w.tg.Add(); err != nil { 438 return err 439 } 440 defer w.tg.Done() 441 442 if !w.scanLock.TryLock() { 443 return errScanInProgress 444 } 445 defer w.scanLock.Unlock() 446 447 w.log.Println("INFO: Unlocking wallet.") 448 449 // Initialize all of the keys in the wallet under a lock. While holding the 450 // lock, also grab the subscriber status. 451 return w.managedUnlock(masterKey) 452 } 453 454 // managedChangeKey safely performs the database operations required to change 455 // the wallet's encryption key. 456 func (w *Wallet) managedChangeKey(masterKey crypto.TwofishKey, newKey crypto.TwofishKey) error { 457 w.mu.Lock() 458 encrypted := w.encrypted 459 w.mu.Unlock() 460 if !encrypted { 461 return errUnencryptedWallet 462 } 463 464 // grab the current seed files 465 var primarySeedFile seedFile 466 var auxiliarySeedFiles []seedFile 467 var unseededKeyFiles []spendableKeyFile 468 469 err := func() error { 470 w.mu.Lock() 471 defer w.mu.Unlock() 472 473 // verify masterKey 474 err := checkMasterKey(w.dbTx, masterKey) 475 if err != nil { 476 return err 477 } 478 479 wb := w.dbTx.Bucket(bucketWallet) 480 481 // primarySeedFile 482 err = encoding.Unmarshal(wb.Get(keyPrimarySeedFile), &primarySeedFile) 483 if err != nil { 484 return err 485 } 486 487 // auxiliarySeedFiles 488 err = encoding.Unmarshal(wb.Get(keyAuxiliarySeedFiles), &auxiliarySeedFiles) 489 if err != nil { 490 return err 491 } 492 493 // unseededKeyFiles 494 err = encoding.Unmarshal(wb.Get(keySpendableKeyFiles), &unseededKeyFiles) 495 if err != nil { 496 return err 497 } 498 499 return nil 500 }() 501 if err != nil { 502 return err 503 } 504 505 // decrypt key files 506 var primarySeed modules.Seed 507 var auxiliarySeeds []modules.Seed 508 var spendableKeys []spendableKey 509 510 primarySeed, err = decryptSeedFile(masterKey, primarySeedFile) 511 if err != nil { 512 return err 513 } 514 for _, sf := range auxiliarySeedFiles { 515 auxSeed, err := decryptSeedFile(masterKey, sf) 516 if err != nil { 517 return err 518 } 519 auxiliarySeeds = append(auxiliarySeeds, auxSeed) 520 } 521 for _, uk := range unseededKeyFiles { 522 sk, err := decryptSpendableKeyFile(masterKey, uk) 523 if err != nil { 524 return err 525 } 526 spendableKeys = append(spendableKeys, sk) 527 } 528 529 // encrypt new keyfiles using newKey 530 var newPrimarySeedFile seedFile 531 var newAuxiliarySeedFiles []seedFile 532 var newUnseededKeyFiles []spendableKeyFile 533 534 newPrimarySeedFile = createSeedFile(newKey, primarySeed) 535 for _, seed := range auxiliarySeeds { 536 newAuxiliarySeedFiles = append(newAuxiliarySeedFiles, createSeedFile(newKey, seed)) 537 } 538 for _, sk := range spendableKeys { 539 var skf spendableKeyFile 540 fastrand.Read(skf.UID[:]) 541 encryptionKey := uidEncryptionKey(newKey, skf.UID) 542 skf.EncryptionVerification = encryptionKey.EncryptBytes(verificationPlaintext) 543 544 // Encrypt and save the key. 545 skf.SpendableKey = encryptionKey.EncryptBytes(encoding.Marshal(sk)) 546 newUnseededKeyFiles = append(newUnseededKeyFiles, skf) 547 } 548 549 // put the newly encrypted keys in the database 550 err = func() error { 551 w.mu.Lock() 552 defer w.mu.Unlock() 553 554 wb := w.dbTx.Bucket(bucketWallet) 555 556 err = wb.Put(keyPrimarySeedFile, encoding.Marshal(newPrimarySeedFile)) 557 if err != nil { 558 return err 559 } 560 err = wb.Put(keyAuxiliarySeedFiles, encoding.Marshal(newAuxiliarySeedFiles)) 561 if err != nil { 562 return err 563 } 564 err = wb.Put(keySpendableKeyFiles, encoding.Marshal(newUnseededKeyFiles)) 565 if err != nil { 566 return err 567 } 568 569 uk := uidEncryptionKey(newKey, dbGetWalletUID(w.dbTx)) 570 err = wb.Put(keyEncryptionVerification, uk.EncryptBytes(verificationPlaintext)) 571 if err != nil { 572 return err 573 } 574 575 return nil 576 }() 577 if err != nil { 578 return err 579 } 580 581 return nil 582 } 583 584 // managedLock will erase all keys from memory and prevent the wallet from 585 // spending coins until it is unlocked. 586 func (w *Wallet) managedLock() error { 587 w.mu.Lock() 588 defer w.mu.Unlock() 589 if !w.unlocked { 590 return modules.ErrLockedWallet 591 } 592 w.log.Println("INFO: Locking wallet.") 593 594 // Wipe all of the seeds and secret keys. They will be replaced upon 595 // calling 'Unlock' again. Note that since the public keys are not wiped, 596 // we can continue processing blocks. 597 w.wipeSecrets() 598 w.unlocked = false 599 return nil 600 } 601 602 // managedUnlocked indicates whether the wallet is locked or unlocked. 603 func (w *Wallet) managedUnlocked() bool { 604 w.mu.RLock() 605 defer w.mu.RUnlock() 606 return w.unlocked 607 }