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  }