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