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  }