github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/wallet/encrypt.go (about)

     1  package wallet
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/rand"
     6  	"errors"
     7  	"time"
     8  
     9  	"github.com/NebulousLabs/Sia/build"
    10  	"github.com/NebulousLabs/Sia/crypto"
    11  	"github.com/NebulousLabs/Sia/modules"
    12  	"github.com/NebulousLabs/Sia/types"
    13  )
    14  
    15  var (
    16  	errAlreadyUnlocked   = errors.New("wallet has already been unlocked")
    17  	errReencrypt         = errors.New("wallet is already encrypted, cannot encrypt again")
    18  	errUnencryptedWallet = errors.New("wallet has not been encrypted yet")
    19  
    20  	unlockModifier = types.Specifier{'u', 'n', 'l', 'o', 'c', 'k'}
    21  )
    22  
    23  // uidEncryptionKey creates an encryption key that is used to decrypt a
    24  // specific key file.
    25  func uidEncryptionKey(masterKey crypto.TwofishKey, uid UniqueID) crypto.TwofishKey {
    26  	return crypto.TwofishKey(crypto.HashAll(masterKey, uid))
    27  }
    28  
    29  // checkMasterKey verifies that the master key is correct.
    30  func (w *Wallet) checkMasterKey(masterKey crypto.TwofishKey) error {
    31  	uk := uidEncryptionKey(masterKey, w.persist.UID)
    32  	verification, err := uk.DecryptBytes(w.persist.EncryptionVerification)
    33  	if err != nil {
    34  		// Most of the time, the failure is an authentication failure.
    35  		return modules.ErrBadEncryptionKey
    36  	}
    37  	expected := make([]byte, encryptionVerificationLen)
    38  	if !bytes.Equal(expected, verification) {
    39  		return modules.ErrBadEncryptionKey
    40  	}
    41  	return nil
    42  }
    43  
    44  // initEncryption checks that the provided encryption key is the valid
    45  // encryption key for the wallet. If encryption has not yet been established
    46  // for the wallet, an encryption key is created.
    47  func (w *Wallet) initEncryption(masterKey crypto.TwofishKey) (modules.Seed, error) {
    48  	// Check if the wallet encryption key has already been set.
    49  	if len(w.persist.EncryptionVerification) != 0 {
    50  		return modules.Seed{}, errReencrypt
    51  	}
    52  
    53  	// Create a random seed and use it to generate the seed file for the
    54  	// wallet.
    55  	var seed modules.Seed
    56  	_, err := rand.Read(seed[:])
    57  	if err != nil {
    58  		return modules.Seed{}, err
    59  	}
    60  
    61  	// If the input key is blank, use the seed to create the master key.
    62  	// Otherwise, use the input key.
    63  	if masterKey == (crypto.TwofishKey{}) {
    64  		masterKey = crypto.TwofishKey(crypto.HashObject(seed))
    65  	}
    66  	err = w.createSeed(masterKey, seed)
    67  	if err != nil {
    68  		return modules.Seed{}, err
    69  	}
    70  
    71  	// Establish the encryption verification using the masterKey. After this
    72  	// point, the wallet is encrypted.
    73  	uk := uidEncryptionKey(masterKey, w.persist.UID)
    74  	encryptionBase := make([]byte, encryptionVerificationLen)
    75  	w.persist.EncryptionVerification, err = uk.EncryptBytes(encryptionBase)
    76  	if err != nil {
    77  		return modules.Seed{}, err
    78  	}
    79  	err = w.saveSettings()
    80  	if err != nil {
    81  		return modules.Seed{}, err
    82  	}
    83  	return seed, nil
    84  }
    85  
    86  // unlock loads all of the encrypted file structures into wallet memory. Even
    87  // after loading, the structures are kept encrypted, but some data such as
    88  // addresses are decrypted so that the wallet knows what to track.
    89  func (w *Wallet) unlock(masterKey crypto.TwofishKey) error {
    90  	// Wallet should only be unlocked once.
    91  	if w.unlocked {
    92  		return errAlreadyUnlocked
    93  	}
    94  
    95  	// Check if the wallet encryption key has already been set.
    96  	if len(w.persist.EncryptionVerification) == 0 {
    97  		return errUnencryptedWallet
    98  	}
    99  
   100  	// Initialize the encryption of the wallet.
   101  	err := w.checkMasterKey(masterKey)
   102  	if err != nil {
   103  		return err
   104  	}
   105  
   106  	// Load the wallet seed that is used to generate new addresses.
   107  	err = w.initPrimarySeed(masterKey)
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	// Load all wallet seeds that are not used to generate new addresses.
   113  	err = w.initAuxiliarySeeds(masterKey)
   114  	if err != nil {
   115  		return err
   116  	}
   117  
   118  	// Load all keys that were not generated by a seed.
   119  	err = w.initUnseededKeys(masterKey)
   120  	if err != nil {
   121  		return err
   122  	}
   123  	w.unlocked = true
   124  	return nil
   125  }
   126  
   127  // wipeSecrets erases all of the seeds and secret keys in the wallet.
   128  func (w *Wallet) wipeSecrets() {
   129  	// 'for i := range' must be used to prevent copies of secret data from
   130  	// being made.
   131  	for i := range w.keys {
   132  		for j := range w.keys[i].SecretKeys {
   133  			crypto.SecureWipe(w.keys[i].SecretKeys[j][:])
   134  		}
   135  	}
   136  	for i := range w.seeds {
   137  		crypto.SecureWipe(w.seeds[i][:])
   138  	}
   139  	crypto.SecureWipe(w.primarySeed[:])
   140  	w.seeds = w.seeds[:0]
   141  }
   142  
   143  // Encrypted returns whether or not the wallet has been encrypted.
   144  func (w *Wallet) Encrypted() bool {
   145  	w.mu.Lock()
   146  	defer w.mu.Unlock()
   147  	if build.DEBUG && w.unlocked && len(w.persist.EncryptionVerification) == 0 {
   148  		panic("wallet is both unlocked and unencrypted")
   149  	}
   150  	return len(w.persist.EncryptionVerification) != 0
   151  }
   152  
   153  // Encrypt will encrypt the wallet using the input key. Upon encryption, a
   154  // primary seed will be created for the wallet (no seed exists prior to this
   155  // point). If the key is blank, then the hash of the seed that is generated
   156  // will be used as the key. The wallet will still be locked after encryption.
   157  //
   158  // Encrypt can only be called once throughout the life of the wallet, and will
   159  // return an error on subsequent calls (even after restarting the wallet). To
   160  // reset the wallet, the wallet files must be moved to a different directory or
   161  // deleted.
   162  func (w *Wallet) Encrypt(masterKey crypto.TwofishKey) (modules.Seed, error) {
   163  	w.mu.Lock()
   164  	defer w.mu.Unlock()
   165  	return w.initEncryption(masterKey)
   166  }
   167  
   168  // Unlocked indicates whether the wallet is locked or unlocked.
   169  func (w *Wallet) Unlocked() bool {
   170  	w.mu.RLock()
   171  	defer w.mu.RUnlock()
   172  	return w.unlocked
   173  }
   174  
   175  // Lock will erase all keys from memory and prevent the wallet from spending
   176  // coins until it is unlocked.
   177  func (w *Wallet) Lock() error {
   178  	w.mu.Lock()
   179  	defer w.mu.Unlock()
   180  	if !w.unlocked {
   181  		return modules.ErrLockedWallet
   182  	}
   183  	w.log.Println("INFO: Locking wallet.")
   184  
   185  	// Wipe all of the seeds and secret keys, they will be replaced upon
   186  	// calling 'Unlock' again.
   187  	w.wipeSecrets()
   188  	w.unlocked = false
   189  	return nil
   190  }
   191  
   192  // Unlock will decrypt the wallet seed and load all of the addresses into
   193  // memory.
   194  func (w *Wallet) Unlock(masterKey crypto.TwofishKey) error {
   195  	w.log.Println("INFO: Unlocking wallet.")
   196  
   197  	// Initialize all of the keys in the wallet under a lock. While holding the
   198  	// lock, also grab the subscriber status.
   199  	w.mu.Lock()
   200  	subscribed := w.subscribed
   201  	err := w.unlock(masterKey)
   202  	w.mu.Unlock()
   203  	if err != nil {
   204  		return err
   205  	}
   206  
   207  	// Subscribe to the consensus set if this is the first unlock for the
   208  	// wallet object.
   209  	if !subscribed {
   210  		// During rescan, print height every 3 seconds.
   211  		if build.Release != "testing" {
   212  			go func() {
   213  				println("Rescanning consensus set...")
   214  				for range time.Tick(time.Second * 3) {
   215  					w.mu.RLock()
   216  					height := w.consensusSetHeight
   217  					done := w.subscribed
   218  					w.mu.RUnlock()
   219  					if done {
   220  						println("\nDone!")
   221  						break
   222  					}
   223  					print("\rScanned to height ", height, "...")
   224  				}
   225  			}()
   226  		}
   227  		err = w.cs.ConsensusSetSubscribe(w, modules.ConsensusChangeBeginning)
   228  		if err != nil {
   229  			return errors.New("wallet subscription failed: " + err.Error())
   230  		}
   231  		w.tpool.TransactionPoolSubscribe(w)
   232  		w.mu.Lock()
   233  		w.subscribed = true
   234  		w.mu.Unlock()
   235  	}
   236  	return nil
   237  }