gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/wallet/unseeded.go (about)

     1  package wallet
     2  
     3  import (
     4  	"errors"
     5  
     6  	"gitlab.com/NebulousLabs/fastrand"
     7  	"gitlab.com/SiaPrime/SiaPrime/crypto"
     8  	"gitlab.com/SiaPrime/SiaPrime/encoding"
     9  	"gitlab.com/SiaPrime/SiaPrime/modules"
    10  	"gitlab.com/SiaPrime/SiaPrime/types"
    11  )
    12  
    13  const (
    14  	// SiagFileExtension is the file extension to be used for siag files
    15  	SiagFileExtension = ".siakey"
    16  
    17  	// SiagFileHeader is 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  
    22  	// SiagFileVersion is the version number to be used for siag files
    23  	SiagFileVersion = "1.0"
    24  )
    25  
    26  var (
    27  	errAllDuplicates         = errors.New("old wallet has no new seeds")
    28  	errDuplicateSpendableKey = errors.New("key has already been loaded into the wallet")
    29  
    30  	// ErrInconsistentKeys is the error when keyfiles provided are for different addresses
    31  	ErrInconsistentKeys = errors.New("keyfiles provided that are for different addresses")
    32  	// ErrInsufficientKeys is the error when there's not enough keys provided to spend the siafunds
    33  	ErrInsufficientKeys = errors.New("not enough keys provided to spend the siafunds")
    34  	// ErrNoKeyfile is the error when no keyfile has been presented
    35  	ErrNoKeyfile = errors.New("no keyfile has been presented")
    36  	// ErrUnknownHeader is the error when file contains wrong header
    37  	ErrUnknownHeader = errors.New("file contains the wrong header")
    38  	// ErrUnknownVersion is the error when the file has an unknown version number
    39  	ErrUnknownVersion = errors.New("file has an unknown version number")
    40  )
    41  
    42  // A siagKeyPair is the struct representation of the bytes that get saved to
    43  // disk by siag when a new keyfile is created.
    44  type siagKeyPair struct {
    45  	Header           string
    46  	Version          string
    47  	Index            int // should be uint64 - too late now
    48  	SecretKey        crypto.SecretKey
    49  	UnlockConditions types.UnlockConditions
    50  }
    51  
    52  // savedKey033x is the persist structure that was used to save and load private
    53  // keys in versions v0.3.3.x for siad.
    54  type savedKey033x struct {
    55  	SecretKey        crypto.SecretKey
    56  	UnlockConditions types.UnlockConditions
    57  	Visible          bool
    58  }
    59  
    60  // decryptSpendableKeyFile decrypts a spendableKeyFile, returning a
    61  // spendableKey.
    62  func decryptSpendableKeyFile(masterKey crypto.CipherKey, uk spendableKeyFile) (sk spendableKey, err error) {
    63  	// Verify that the decryption key is correct.
    64  	decryptionKey := uidEncryptionKey(masterKey, uk.UID)
    65  	err = verifyEncryption(decryptionKey, uk.EncryptionVerification)
    66  	if err != nil {
    67  		return
    68  	}
    69  
    70  	// Decrypt the spendable key and add it to the wallet.
    71  	encodedKey, err := decryptionKey.DecryptBytes(uk.SpendableKey)
    72  	if err != nil {
    73  		return
    74  	}
    75  	err = encoding.Unmarshal(encodedKey, &sk)
    76  	return
    77  }
    78  
    79  // integrateSpendableKey loads a spendableKey into the wallet.
    80  func (w *Wallet) integrateSpendableKey(masterKey crypto.CipherKey, sk spendableKey) {
    81  	w.keys[sk.UnlockConditions.UnlockHash()] = sk
    82  }
    83  
    84  // loadSpendableKey loads a spendable key into the wallet database.
    85  func (w *Wallet) loadSpendableKey(masterKey crypto.CipherKey, 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  	fastrand.Read(skf.UID[:])
   103  	encryptionKey := uidEncryptionKey(masterKey, skf.UID)
   104  	skf.EncryptionVerification = encryptionKey.EncryptBytes(verificationPlaintext)
   105  
   106  	// Encrypt and save the key.
   107  	skf.SpendableKey = encryptionKey.EncryptBytes(encoding.Marshal(sk))
   108  
   109  	err := checkMasterKey(w.dbTx, masterKey)
   110  	var current []spendableKeyFile
   111  	err = encoding.Unmarshal(w.dbTx.Bucket(bucketWallet).Get(keySpendableKeyFiles), &current)
   112  	if err != nil {
   113  		return err
   114  	}
   115  	return w.dbTx.Bucket(bucketWallet).Put(keySpendableKeyFiles, encoding.Marshal(append(current, skf)))
   116  
   117  	// w.keys[sk.UnlockConditions.UnlockHash()] = sk -> aids with duplicate
   118  	// detection, but causes db inconsistency. Rescanning is probably the
   119  	// solution.
   120  }
   121  
   122  // loadSiagKeys loads a set of siag keyfiles into the wallet, so that the
   123  // wallet may spend the siafunds.
   124  func (w *Wallet) loadSiagKeys(masterKey crypto.CipherKey, keyfiles []string) error {
   125  	// Load the keyfiles from disk.
   126  	if len(keyfiles) < 1 {
   127  		return ErrNoKeyfile
   128  	}
   129  	skps := make([]siagKeyPair, len(keyfiles))
   130  	for i, keyfile := range keyfiles {
   131  		err := encoding.ReadFile(keyfile, &skps[i])
   132  		if err != nil {
   133  			return err
   134  		}
   135  
   136  		if skps[i].Header != SiagFileHeader {
   137  			return ErrUnknownHeader
   138  		}
   139  		if skps[i].Version != SiagFileVersion {
   140  			return ErrUnknownVersion
   141  		}
   142  	}
   143  
   144  	// Check that all of the loaded files have the same address, and that there
   145  	// are enough to create the transaction.
   146  	baseUnlockHash := skps[0].UnlockConditions.UnlockHash()
   147  	for _, skp := range skps {
   148  		if skp.UnlockConditions.UnlockHash() != baseUnlockHash {
   149  			return ErrInconsistentKeys
   150  		}
   151  	}
   152  	if uint64(len(skps)) < skps[0].UnlockConditions.SignaturesRequired {
   153  		return ErrInsufficientKeys
   154  	}
   155  	// Drop all unneeded keys.
   156  	skps = skps[0:skps[0].UnlockConditions.SignaturesRequired]
   157  
   158  	// Merge the keys into a single spendableKey and save it to the wallet.
   159  	var sk spendableKey
   160  	sk.UnlockConditions = skps[0].UnlockConditions
   161  	for _, skp := range skps {
   162  		sk.SecretKeys = append(sk.SecretKeys, skp.SecretKey)
   163  	}
   164  	err := w.loadSpendableKey(masterKey, sk)
   165  	if err != nil {
   166  		return err
   167  	}
   168  	w.integrateSpendableKey(masterKey, sk)
   169  	return nil
   170  }
   171  
   172  // LoadSiagKeys loads a set of siag-generated keys into the wallet.
   173  func (w *Wallet) LoadSiagKeys(masterKey crypto.CipherKey, keyfiles []string) error {
   174  	if err := w.tg.Add(); err != nil {
   175  		return err
   176  	}
   177  	defer w.tg.Done()
   178  
   179  	// load the keys and reset the consensus change ID and height in preparation for rescan
   180  	err := func() error {
   181  		w.mu.Lock()
   182  		defer w.mu.Unlock()
   183  		err := w.loadSiagKeys(masterKey, keyfiles)
   184  		if err != nil {
   185  			return err
   186  		}
   187  
   188  		if err = w.dbTx.DeleteBucket(bucketProcessedTransactions); err != nil {
   189  			return err
   190  		}
   191  		if _, err = w.dbTx.CreateBucket(bucketProcessedTransactions); err != nil {
   192  			return err
   193  		}
   194  		w.unconfirmedProcessedTransactions = nil
   195  		err = dbPutConsensusChangeID(w.dbTx, modules.ConsensusChangeBeginning)
   196  		if err != nil {
   197  			return err
   198  		}
   199  		return dbPutConsensusHeight(w.dbTx, 0)
   200  	}()
   201  	if err != nil {
   202  		return err
   203  	}
   204  
   205  	// rescan the blockchain
   206  	w.cs.Unsubscribe(w)
   207  	w.tpool.Unsubscribe(w)
   208  
   209  	done := make(chan struct{})
   210  	go w.rescanMessage(done)
   211  	defer close(done)
   212  
   213  	err = w.cs.ConsensusSetSubscribe(w, modules.ConsensusChangeBeginning, w.tg.StopChan())
   214  	if err != nil {
   215  		return err
   216  	}
   217  	w.tpool.TransactionPoolSubscribe(w)
   218  	return nil
   219  }
   220  
   221  // Load033xWallet loads a v0.3.3.x wallet as an unseeded key, such that the
   222  // funds become spendable to the current wallet.
   223  func (w *Wallet) Load033xWallet(masterKey crypto.CipherKey, filepath033x string) error {
   224  	if err := w.tg.Add(); err != nil {
   225  		return err
   226  	}
   227  	defer w.tg.Done()
   228  
   229  	// load the keys and reset the consensus change ID and height in preparation for rescan
   230  	err := func() error {
   231  		w.mu.Lock()
   232  		defer w.mu.Unlock()
   233  
   234  		var savedKeys []savedKey033x
   235  		err := encoding.ReadFile(filepath033x, &savedKeys)
   236  		if err != nil {
   237  			return err
   238  		}
   239  		var seedsLoaded int
   240  		for _, savedKey := range savedKeys {
   241  			spendKey := spendableKey{
   242  				UnlockConditions: savedKey.UnlockConditions,
   243  				SecretKeys:       []crypto.SecretKey{savedKey.SecretKey},
   244  			}
   245  			err = w.loadSpendableKey(masterKey, spendKey)
   246  			if err != nil && err != errDuplicateSpendableKey {
   247  				return err
   248  			}
   249  			if err == nil {
   250  				seedsLoaded++
   251  			}
   252  			w.integrateSpendableKey(masterKey, spendKey)
   253  		}
   254  		if seedsLoaded == 0 {
   255  			return errAllDuplicates
   256  		}
   257  
   258  		if err = w.dbTx.DeleteBucket(bucketProcessedTransactions); err != nil {
   259  			return err
   260  		}
   261  		if _, err = w.dbTx.CreateBucket(bucketProcessedTransactions); err != nil {
   262  			return err
   263  		}
   264  		w.unconfirmedProcessedTransactions = nil
   265  		err = dbPutConsensusChangeID(w.dbTx, modules.ConsensusChangeBeginning)
   266  		if err != nil {
   267  			return err
   268  		}
   269  		return dbPutConsensusHeight(w.dbTx, 0)
   270  	}()
   271  	if err != nil {
   272  		return err
   273  	}
   274  
   275  	// rescan the blockchain
   276  	w.cs.Unsubscribe(w)
   277  	w.tpool.Unsubscribe(w)
   278  
   279  	done := make(chan struct{})
   280  	go w.rescanMessage(done)
   281  	defer close(done)
   282  
   283  	err = w.cs.ConsensusSetSubscribe(w, modules.ConsensusChangeBeginning, w.tg.StopChan())
   284  	if err != nil {
   285  		return err
   286  	}
   287  	w.tpool.TransactionPoolSubscribe(w)
   288  
   289  	return nil
   290  }