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

     1  package wallet
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/rand"
     6  	"errors"
     7  	"path/filepath"
     8  
     9  	"github.com/NebulousLabs/Sia/crypto"
    10  	"github.com/NebulousLabs/Sia/encoding"
    11  	"github.com/NebulousLabs/Sia/modules"
    12  	"github.com/NebulousLabs/Sia/persist"
    13  	"github.com/NebulousLabs/Sia/types"
    14  )
    15  
    16  const (
    17  	// The header for all siag files. Do not change. Because siag was created
    18  	// early in development, compatibility with siag requires manually handling
    19  	// the headers and version instead of using the persist package.
    20  	SiagFileHeader    = "siag"
    21  	SiagFileExtension = ".siakey"
    22  	SiagFileVersion   = "1.0"
    23  )
    24  
    25  var (
    26  	ErrInconsistentKeys = errors.New("keyfiles provided that are for different addresses")
    27  	ErrInsufficientKeys = errors.New("not enough keys provided to spend the siafunds")
    28  	ErrNoKeyfile        = errors.New("no keyfile has been presented")
    29  	ErrUnknownHeader    = errors.New("file contains the wrong header")
    30  	ErrUnknownVersion   = errors.New("file has an unknown version number")
    31  
    32  	errAllDuplicates         = errors.New("old wallet has no new seeds")
    33  	errDuplicateSpendableKey = errors.New("key has already been loaded into the wallet")
    34  )
    35  
    36  // A SiagKeyPair is the struct representation of the bytes that get saved to
    37  // disk by siag when a new keyfile is created.
    38  type SiagKeyPair struct {
    39  	Header           string
    40  	Version          string
    41  	Index            int // should be uint64 - too late now
    42  	SecretKey        crypto.SecretKey
    43  	UnlockConditions types.UnlockConditions
    44  }
    45  
    46  // SavedKey033x is the persist structure that was used to save and load private
    47  // keys in versions v0.3.3.x for siad.
    48  type SavedKey033x struct {
    49  	SecretKey        crypto.SecretKey
    50  	UnlockConditions types.UnlockConditions
    51  	Visible          bool
    52  }
    53  
    54  // initUnseededKeys loads all of the unseeded keys into the wallet after the
    55  // wallet gets unlocked.
    56  func (w *Wallet) initUnseededKeys(masterKey crypto.TwofishKey) error {
    57  	for _, uk := range w.persist.UnseededKeys {
    58  		// Verify that the decryption key is correct.
    59  		encKey := uidEncryptionKey(masterKey, uk.UID)
    60  		expectedDecryptedVerification := make([]byte, crypto.EntropySize)
    61  		decryptedVerification, err := encKey.DecryptBytes(uk.EncryptionVerification)
    62  		if err != nil {
    63  			return err
    64  		}
    65  		if !bytes.Equal(expectedDecryptedVerification, decryptedVerification) {
    66  			return modules.ErrBadEncryptionKey
    67  		}
    68  
    69  		// Decrypt the spendable key and add it to the wallet.
    70  		encodedKey, err := encKey.DecryptBytes(uk.SpendableKey)
    71  		if err != nil {
    72  			return err
    73  		}
    74  		var sk spendableKey
    75  		err = encoding.Unmarshal(encodedKey, &sk)
    76  		if err != nil {
    77  			return err
    78  		}
    79  		w.keys[sk.UnlockConditions.UnlockHash()] = sk
    80  	}
    81  	return nil
    82  }
    83  
    84  // loadSpendableKey loads a spendable key into the wallet's persist structure.
    85  func (w *Wallet) loadSpendableKey(masterKey crypto.TwofishKey, sk spendableKey) error {
    86  	// Duplication is detected by looking at the set of unlock conditions. If
    87  	// the wallet is locked, correct deduplication is uncertain.
    88  	if !w.unlocked {
    89  		return modules.ErrLockedWallet
    90  	}
    91  
    92  	// Check for duplicates.
    93  	_, exists := w.keys[sk.UnlockConditions.UnlockHash()]
    94  	if exists {
    95  		return errDuplicateSpendableKey
    96  	}
    97  
    98  	// TODO: Check that the key is actually spendable.
    99  
   100  	// Create a UID and encryption verification.
   101  	var skf SpendableKeyFile
   102  	_, err := rand.Read(skf.UID[:])
   103  	if err != nil {
   104  		return err
   105  	}
   106  	encryptionKey := uidEncryptionKey(masterKey, skf.UID)
   107  	plaintextVerification := make([]byte, encryptionVerificationLen)
   108  	skf.EncryptionVerification, err = encryptionKey.EncryptBytes(plaintextVerification)
   109  	if err != nil {
   110  		return err
   111  	}
   112  
   113  	// Encrypt and save the key.
   114  	skf.SpendableKey, err = encryptionKey.EncryptBytes(encoding.Marshal(sk))
   115  	if err != nil {
   116  		return err
   117  	}
   118  	w.persist.UnseededKeys = append(w.persist.UnseededKeys, skf)
   119  	// w.keys[sk.UnlockConditions.UnlockHash()] = sk -> aids with duplicate
   120  	// detection, but causes db inconsistency. Rescanning is probably the
   121  	// solution.
   122  	return nil
   123  }
   124  
   125  // loadSiagKeys loads a set of siag keyfiles into the wallet, so that the
   126  // wallet may spend the siafunds.
   127  func (w *Wallet) loadSiagKeys(masterKey crypto.TwofishKey, keyfiles []string) error {
   128  	// Load the keyfiles from disk.
   129  	if len(keyfiles) < 1 {
   130  		return ErrNoKeyfile
   131  	}
   132  	skps := make([]SiagKeyPair, len(keyfiles))
   133  	for i, keyfile := range keyfiles {
   134  		err := encoding.ReadFile(keyfile, &skps[i])
   135  		if err != nil {
   136  			return err
   137  		}
   138  
   139  		if skps[i].Header != SiagFileHeader {
   140  			return ErrUnknownHeader
   141  		}
   142  		if skps[i].Version != SiagFileVersion {
   143  			return ErrUnknownVersion
   144  		}
   145  	}
   146  
   147  	// Check that all of the loaded files have the same address, and that there
   148  	// are enough to create the transaction.
   149  	baseUnlockHash := skps[0].UnlockConditions.UnlockHash()
   150  	for _, skp := range skps {
   151  		if skp.UnlockConditions.UnlockHash() != baseUnlockHash {
   152  			return ErrInconsistentKeys
   153  		}
   154  	}
   155  	if uint64(len(skps)) < skps[0].UnlockConditions.SignaturesRequired {
   156  		return ErrInsufficientKeys
   157  	}
   158  	// Drop all unneeded keys.
   159  	skps = skps[0:skps[0].UnlockConditions.SignaturesRequired]
   160  
   161  	// Merge the keys into a single spendableKey and save it to the wallet.
   162  	var sk spendableKey
   163  	sk.UnlockConditions = skps[0].UnlockConditions
   164  	for _, skp := range skps {
   165  		sk.SecretKeys = append(sk.SecretKeys, skp.SecretKey)
   166  	}
   167  	err := w.loadSpendableKey(masterKey, sk)
   168  	if err != nil {
   169  		return err
   170  	}
   171  	err = w.saveSettingsSync()
   172  	if err != nil {
   173  		return err
   174  	}
   175  	return w.createBackup(filepath.Join(w.persistDir, "Sia Wallet Encrypted Backup - "+persist.RandomSuffix()+settingsFileSuffix))
   176  }
   177  
   178  // LoadSiagKeys loads a set of siag-generated keys into the wallet.
   179  func (w *Wallet) LoadSiagKeys(masterKey crypto.TwofishKey, keyfiles []string) error {
   180  	w.mu.Lock()
   181  	defer w.mu.Unlock()
   182  	err := w.checkMasterKey(masterKey)
   183  	if err != nil {
   184  		return err
   185  	}
   186  	return w.loadSiagKeys(masterKey, keyfiles)
   187  }
   188  
   189  // Load033xWallet loads a v0.3.3.x wallet as an unseeded key, such that the
   190  // funds become spendable to the current wallet.
   191  func (w *Wallet) Load033xWallet(masterKey crypto.TwofishKey, filepath033x string) error {
   192  	w.mu.Lock()
   193  	defer w.mu.Unlock()
   194  	err := w.checkMasterKey(masterKey)
   195  	if err != nil {
   196  		return err
   197  	}
   198  
   199  	var savedKeys []SavedKey033x
   200  	err = encoding.ReadFile(filepath033x, &savedKeys)
   201  	if err != nil {
   202  		return err
   203  	}
   204  	var seedsLoaded int
   205  	for _, savedKey := range savedKeys {
   206  		spendKey := spendableKey{
   207  			UnlockConditions: savedKey.UnlockConditions,
   208  			SecretKeys:       []crypto.SecretKey{savedKey.SecretKey},
   209  		}
   210  		err = w.loadSpendableKey(masterKey, spendKey)
   211  		if err != nil && err != errDuplicateSpendableKey {
   212  			return err
   213  		}
   214  		if err == nil {
   215  			seedsLoaded++
   216  		}
   217  	}
   218  	err = w.saveSettingsSync()
   219  	if err != nil {
   220  		return err
   221  	}
   222  	if seedsLoaded == 0 {
   223  		return errAllDuplicates
   224  	}
   225  	return w.createBackup(filepath.Join(w.persistDir, "Sia Wallet Encrypted Backup - "+persist.RandomSuffix()+settingsFileSuffix))
   226  }