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