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  }