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

     1  package modules
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  
     7  	"github.com/NebulousLabs/entropy-mnemonics"
     8  
     9  	"github.com/NebulousLabs/Sia/crypto"
    10  	"github.com/NebulousLabs/Sia/types"
    11  )
    12  
    13  const (
    14  	// WalletDir is the directory that contains the wallet persistence.
    15  	WalletDir = "wallet"
    16  
    17  	// SeedChecksumSize is the number of bytes that are used to checksum
    18  	// addresses to prevent accidental spending.
    19  	SeedChecksumSize = 6
    20  
    21  	// PublicKeysPerSeed define the number of public keys that get pregenerated
    22  	// for a seed at startup when searching for balances in the blockchain.
    23  	PublicKeysPerSeed = 2500
    24  
    25  	// WalletSeedPreloadDepth is the number of addresses that get automatically
    26  	// loaded by the wallet at startup.
    27  	WalletSeedPreloadDepth = 25
    28  )
    29  
    30  var (
    31  	// ErrBadEncryptionKey is returned if the incorrect encryption key to a
    32  	// file is provided.
    33  	ErrBadEncryptionKey = errors.New("provided encryption key is incorrect")
    34  
    35  	// ErrLowBalance is returned if the wallet does not have enough funds to
    36  	// complete the desired action.
    37  	ErrLowBalance = errors.New("insufficient balance")
    38  
    39  	// ErrPotentialDoubleSpend is returned when the wallet is uncertain whether
    40  	// a spend is going to be legal or not.
    41  	ErrPotentialDoubleSpend = errors.New("wallet has coins spent in unconfirmed transactions - not enough remaining coins to complete transaction")
    42  
    43  	// ErrLockedWallet is returned when an action cannot be performed due to
    44  	// the wallet being locked.
    45  	ErrLockedWallet = errors.New("wallet must be unlocked before it can be used")
    46  )
    47  
    48  type (
    49  	// Seed is cryptographic entropy that is used to derive spendable wallet
    50  	// addresses.
    51  	Seed [crypto.EntropySize]byte
    52  
    53  	// WalletTransactionID is a unique identifier for a wallet transaction.
    54  	WalletTransactionID crypto.Hash
    55  
    56  	// A ProcessedInput represents funding to a transaction. The input is
    57  	// coming from an address and going to the outputs. The fund types are
    58  	// 'SiacoinInput', 'SiafundInput'.
    59  	ProcessedInput struct {
    60  		FundType       types.Specifier  `json:"fundtype"`
    61  		WalletAddress  bool             `json:"walletaddress"`
    62  		RelatedAddress types.UnlockHash `json:"relatedaddress"`
    63  		Value          types.Currency   `json:"value"`
    64  	}
    65  
    66  	// A ProcessedOutput is a siacoin output that appears in a transaction.
    67  	// Some outputs mature immediately, some are delayed, and some may never
    68  	// mature at all (in the event of storage proofs).
    69  	//
    70  	// Fund type can either be 'SiacoinOutput', 'SiafundOutput', 'ClaimOutput',
    71  	// 'MinerPayout', or 'MinerFee'. All outputs except the miner fee create
    72  	// outputs accessible to an address. Miner fees are not spendable, and
    73  	// instead contribute to the block subsidy.
    74  	//
    75  	// MaturityHeight indicates at what block height the output becomes
    76  	// available. SiacoinInputs and SiafundInputs become available immediately.
    77  	// ClaimInputs and MinerPayouts become available after 144 confirmations.
    78  	ProcessedOutput struct {
    79  		FundType       types.Specifier   `json:"fundtype"`
    80  		MaturityHeight types.BlockHeight `json:"maturityheight"`
    81  		WalletAddress  bool              `json:"walletaddress"`
    82  		RelatedAddress types.UnlockHash  `json:"relatedaddress"`
    83  		Value          types.Currency    `json:"value"`
    84  	}
    85  
    86  	// A ProcessedTransaction is a transaction that has been processed into
    87  	// explicit inputs and outputs and tagged with some header data such as
    88  	// confirmation height + timestamp.
    89  	//
    90  	// Because of the block subsidy, a block is considered as a transaction.
    91  	// Since there is technically no transaction id for the block subsidy, the
    92  	// block id is used instead.
    93  	ProcessedTransaction struct {
    94  		Transaction           types.Transaction   `json:"transaction"`
    95  		TransactionID         types.TransactionID `json:"transactionid"`
    96  		ConfirmationHeight    types.BlockHeight   `json:"confirmationheight"`
    97  		ConfirmationTimestamp types.Timestamp     `json:"confirmationtimestamp"`
    98  
    99  		Inputs  []ProcessedInput  `json:"inputs"`
   100  		Outputs []ProcessedOutput `json:"outputs"`
   101  	}
   102  
   103  	// TransactionBuilder is used to construct custom transactions. A transaction
   104  	// builder is initialized via 'RegisterTransaction' and then can be modified by
   105  	// adding funds or other fields. The transaction is completed by calling
   106  	// 'Sign', which will sign all inputs added via the 'FundSiacoins' or
   107  	// 'FundSiafunds' call. All modifications are additive.
   108  	//
   109  	// Parents of the transaction are kept in the transaction builder. A parent is
   110  	// any unconfirmed transaction that is required for the child to be valid.
   111  	//
   112  	// Transaction builders are not thread safe.
   113  	TransactionBuilder interface {
   114  		// FundSiacoins will add a siacoin input of exaclty 'amount' to the
   115  		// transaction. A parent transaction may be needed to achieve an input
   116  		// with the correct value. The siacoin input will not be signed until
   117  		// 'Sign' is called on the transaction builder. The expectation is that
   118  		// the transaction will be completed and broadcast within a few hours.
   119  		// Longer risks double-spends, as the wallet will assume that the
   120  		// transaction failed.
   121  		FundSiacoins(amount types.Currency) error
   122  
   123  		// FundSiafunds will add a siafund input of exaclty 'amount' to the
   124  		// transaction. A parent transaction may be needed to achieve an input
   125  		// with the correct value. The siafund input will not be signed until
   126  		// 'Sign' is called on the transaction builder. Any siacoins that are
   127  		// released by spending the siafund outputs will be sent to another
   128  		// address owned by the wallet. The expectation is that the transaction
   129  		// will be completed and broadcast within a few hours. Longer risks
   130  		// double-spends, because the wallet will assume the transcation
   131  		// failed.
   132  		FundSiafunds(amount types.Currency) error
   133  
   134  		// AddParents adds a set of parents to the transaction.
   135  		AddParents([]types.Transaction)
   136  
   137  		// AddMinerFee adds a miner fee to the transaction, returning the index
   138  		// of the miner fee within the transaction.
   139  		AddMinerFee(fee types.Currency) uint64
   140  
   141  		// AddSiacoinInput adds a siacoin input to the transaction, returning
   142  		// the index of the siacoin input within the transaction. When 'Sign'
   143  		// gets called, this input will be left unsigned.
   144  		AddSiacoinInput(types.SiacoinInput) uint64
   145  
   146  		// AddSiacoinOutput adds a siacoin output to the transaction, returning
   147  		// the index of the siacoin output within the transaction.
   148  		AddSiacoinOutput(types.SiacoinOutput) uint64
   149  
   150  		// AddFileContract adds a file contract to the transaction, returning
   151  		// the index of the file contract within the transaction.
   152  		AddFileContract(types.FileContract) uint64
   153  
   154  		// AddFileContractRevision adds a file contract revision to the
   155  		// transaction, returning the index of the file contract revision
   156  		// within the transaction. When 'Sign' gets called, this revision will
   157  		// be left unsigned.
   158  		AddFileContractRevision(types.FileContractRevision) uint64
   159  
   160  		// AddStorageProof adds a storage proof to the transaction, returning
   161  		// the index of the storage proof within the transaction.
   162  		AddStorageProof(types.StorageProof) uint64
   163  
   164  		// AddSiafundInput adds a siafund input to the transaction, returning
   165  		// the index of the siafund input within the transaction. When 'Sign'
   166  		// is called, this input will be left unsigned.
   167  		AddSiafundInput(types.SiafundInput) uint64
   168  
   169  		// AddSiafundOutput adds a siafund output to the transaction, returning
   170  		// the index of the siafund output within the transaction.
   171  		AddSiafundOutput(types.SiafundOutput) uint64
   172  
   173  		// AddArbitraryData adds arbitrary data to the transaction, returning
   174  		// the index of the data within the transaction.
   175  		AddArbitraryData(arb []byte) uint64
   176  
   177  		// AddTransactionSignature adds a transaction signature to the
   178  		// transaction, returning the index of the signature within the
   179  		// transaction. The signature should already be valid, and shouldn't
   180  		// sign any of the inputs that were added by calling 'FundSiacoins' or
   181  		// 'FundSiafunds'.
   182  		AddTransactionSignature(types.TransactionSignature) uint64
   183  
   184  		// Sign will sign any inputs added by 'FundSiacoins' or 'FundSiafunds'
   185  		// and return a transaction set that contains all parents prepended to
   186  		// the transaction. If more fields need to be added, a new transaction
   187  		// builder will need to be created.
   188  		//
   189  		// If the whole transaction flag is set to true, then the whole
   190  		// transaction flag will be set in the covered fields object. If the
   191  		// whole transaction flag is set to false, then the covered fields
   192  		// object will cover all fields that have already been added to the
   193  		// transaction, but will also leave room for more fields to be added.
   194  		//
   195  		// An error will be returned if there are multiple calls to 'Sign',
   196  		// sometimes even if the first call to Sign has failed. Sign should
   197  		// only ever be called once, and if the first signing fails, the
   198  		// transaction should be dropped.
   199  		Sign(wholeTransaction bool) ([]types.Transaction, error)
   200  
   201  		// View returns the incomplete transaction along with all of its
   202  		// parents.
   203  		View() (txn types.Transaction, parents []types.Transaction)
   204  
   205  		// ViewAdded returns all of the siacoin inputs, siafund inputs, and
   206  		// parent transactions that have been automatically added by the
   207  		// builder. Items are returned by index.
   208  		ViewAdded() (newParents, siacoinInputs, siafundInputs, transactionSignatures []int)
   209  
   210  		// Drop indicates that a transaction is no longer useful, will not be
   211  		// broadcast, and that all of the outputs can be reclaimed. 'Drop'
   212  		// should only be used before signatures are added.
   213  		Drop()
   214  	}
   215  
   216  	// EncryptionManager can encrypt, lock, unlock, and indicate the current
   217  	// status of the EncryptionManager.
   218  	EncryptionManager interface {
   219  		// Encrypt will encrypt the wallet using the input key. Upon
   220  		// encryption, a primary seed will be created for the wallet (no seed
   221  		// exists prior to this point). If the key is blank, then the hash of
   222  		// the seed that is generated will be used as the key.
   223  		//
   224  		// Encrypt can only be called once throughout the life of the wallet,
   225  		// and will return an error on subsequent calls (even after restarting
   226  		// the wallet). To reset the wallet, the wallet files must be moved to
   227  		// a different directory or deleted.
   228  		Encrypt(masterKey crypto.TwofishKey) (Seed, error)
   229  
   230  		// Encrypted returns whether or not the wallet has been encrypted yet.
   231  		// After being encrypted for the first time, the wallet can only be
   232  		// unlocked using the encryption password.
   233  		Encrypted() bool
   234  
   235  		// Lock deletes all keys in memory and prevents the wallet from being
   236  		// used to spend coins or extract keys until 'Unlock' is called.
   237  		Lock() error
   238  
   239  		// Unlock must be called before the wallet is usable. All wallets and
   240  		// wallet seeds are encrypted by default, and the wallet will not know
   241  		// which addresses to watch for on the blockchain until unlock has been
   242  		// called.
   243  		//
   244  		// All items in the wallet are encrypted using different keys which are
   245  		// derived from the master key.
   246  		Unlock(masterKey crypto.TwofishKey) error
   247  
   248  		// Unlocked returns true if the wallet is currently unlocked, false
   249  		// otherwise.
   250  		Unlocked() bool
   251  	}
   252  
   253  	// KeyManager manages wallet keys, including the use of seeds, creating and
   254  	// loading backups, and providing a layer of compatibility for older wallet
   255  	// files.
   256  	KeyManager interface {
   257  		// AllAddresses returns all addresses that the wallet is able to spend
   258  		// from, including unseeded addresses. Addresses are returned sorted in
   259  		// byte-order.
   260  		AllAddresses() []types.UnlockHash
   261  
   262  		// AllSeeds returns all of the seeds that are being tracked by the
   263  		// wallet, including the primary seed. Only the primary seed is used to
   264  		// generate new addresses, but the wallet can spend funds sent to
   265  		// public keys generated by any of the seeds returned.
   266  		AllSeeds() ([]Seed, error)
   267  
   268  		// PrimarySeed returns the current primary seed of the wallet,
   269  		// unencrypted, with an int indicating how many addresses have been
   270  		// consumed.
   271  		PrimarySeed() (Seed, uint64, error)
   272  
   273  		// NextAddress returns a new coin addresses generated from the
   274  		// primary seed.
   275  		NextAddress() (types.UnlockConditions, error)
   276  
   277  		// CreateBackup will create a backup of the wallet at the provided
   278  		// filepath. The backup will have all seeds and keys.
   279  		CreateBackup(string) error
   280  
   281  		// LoadBackup will load a backup of the wallet from the provided
   282  		// address. The backup wallet will be added as an auxiliary seed, not
   283  		// as a primary seed.
   284  		// LoadBackup(masterKey, backupMasterKey crypto.TwofishKey, string) error
   285  
   286  		// Load033xWallet will load a version 0.3.3.x wallet from disk and add all of
   287  		// the keys in the wallet as unseeded keys.
   288  		Load033xWallet(crypto.TwofishKey, string) error
   289  
   290  		// LoadSeed will recreate a wallet file using the recovery phrase.
   291  		// LoadSeed only needs to be called if the original seed file or
   292  		// encryption password was lost. The master key is used encrypt the
   293  		// recovery seed before saving it to disk.
   294  		LoadSeed(crypto.TwofishKey, Seed) error
   295  
   296  		// LoadSiagKeys will take a set of filepaths that point to a siag key
   297  		// and will have the siag keys loaded into the wallet so that they will
   298  		// become spendable.
   299  		LoadSiagKeys(crypto.TwofishKey, []string) error
   300  	}
   301  
   302  	// Wallet stores and manages siacoins and siafunds. The wallet file is
   303  	// encrypted using a user-specified password. Common addresses are all
   304  	// dervied from a single address seed.
   305  	Wallet interface {
   306  		EncryptionManager
   307  		KeyManager
   308  
   309  		// ConfirmedBalance returns the confirmed balance of the wallet, minus
   310  		// any outgoing transactions. ConfirmedBalance will include unconfirmed
   311  		// refund transacitons.
   312  		ConfirmedBalance() (siacoinBalance types.Currency, siafundBalance types.Currency, siacoinClaimBalance types.Currency)
   313  
   314  		// UnconfirmedBalance returns the unconfirmed balance of the wallet.
   315  		// Outgoing funds and incoming funds are reported separately. Refund
   316  		// outputs are included, meaning that a sending a single coin to
   317  		// someone could result in 'outgoing: 12, incoming: 11'. Siafunds are
   318  		// not considered in the unconfirmed balance.
   319  		UnconfirmedBalance() (outgoingSiacoins types.Currency, incomingSiacoins types.Currency)
   320  
   321  		// AddressTransactions returns all of the transactions that are related
   322  		// to a given address.
   323  		AddressTransactions(types.UnlockHash) []ProcessedTransaction
   324  
   325  		// AddressUnconfirmedHistory returns all of the unconfirmed
   326  		// transactions related to a given address.
   327  		AddressUnconfirmedTransactions(types.UnlockHash) []ProcessedTransaction
   328  
   329  		// Transaction returns the transaction with the given id. The bool
   330  		// indicates whether the transaction is in the wallet database. The
   331  		// wallet only stores transactions that are related to the wallet.
   332  		Transaction(types.TransactionID) (ProcessedTransaction, bool)
   333  
   334  		// Transactions returns all of the transactions that were confirmed at
   335  		// heights [startHeight, endHeight]. Unconfirmed transactions are not
   336  		// included.
   337  		Transactions(startHeight types.BlockHeight, endHeight types.BlockHeight) ([]ProcessedTransaction, error)
   338  
   339  		// UnconfirmedTransactions returns all unconfirmed transactions
   340  		// relative to the wallet.
   341  		UnconfirmedTransactions() []ProcessedTransaction
   342  
   343  		// RegisterTransaction takes a transaction and its parents and returns
   344  		// a TransactionBuilder which can be used to expand the transaction.
   345  		RegisterTransaction(t types.Transaction, parents []types.Transaction) TransactionBuilder
   346  
   347  		// StartTransaction is a convenience method that calls
   348  		// RegisterTransaction(types.Transaction{}, nil)
   349  		StartTransaction() TransactionBuilder
   350  
   351  		// SendSiacoins is a tool for sending siacoins from the wallet to an
   352  		// address. Sending money usually results in multiple transactions. The
   353  		// transactions are automatically given to the transaction pool, and
   354  		// are also returned to the caller.
   355  		SendSiacoins(amount types.Currency, dest types.UnlockHash) ([]types.Transaction, error)
   356  
   357  		// SendSiafunds is a tool for sending siafunds from the wallet to an
   358  		// address. Sending money usually results in multiple transactions. The
   359  		// transactions are automatically given to the transaction pool, and
   360  		// are also returned to the caller.
   361  		SendSiafunds(amount types.Currency, dest types.UnlockHash) ([]types.Transaction, error)
   362  	}
   363  )
   364  
   365  // CalculateWalletTransactionID is a helper function for determining the id of
   366  // a wallet transaction.
   367  func CalculateWalletTransactionID(tid types.TransactionID, oid types.OutputID) WalletTransactionID {
   368  	return WalletTransactionID(crypto.HashAll(tid, oid))
   369  }
   370  
   371  // SeedToString converts a wallet seed to a human friendly string.
   372  func SeedToString(seed Seed, did mnemonics.DictionaryID) (string, error) {
   373  	fullChecksum := crypto.HashObject(seed)
   374  	checksumSeed := append(seed[:], fullChecksum[:SeedChecksumSize]...)
   375  	phrase, err := mnemonics.ToPhrase(checksumSeed, did)
   376  	if err != nil {
   377  		return "", err
   378  	}
   379  	return phrase.String(), nil
   380  }
   381  
   382  // StringToSeed converts a string to a wallet seed.
   383  func StringToSeed(str string, did mnemonics.DictionaryID) (Seed, error) {
   384  	// Decode the string into the checksummed byte slice.
   385  	checksumSeedBytes, err := mnemonics.FromString(str, did)
   386  	if err != nil {
   387  		return Seed{}, err
   388  	}
   389  
   390  	// Copy the seed from the checksummed slice.
   391  	var seed Seed
   392  	copy(seed[:], checksumSeedBytes)
   393  	fullChecksum := crypto.HashObject(seed)
   394  	if len(checksumSeedBytes) != crypto.EntropySize+SeedChecksumSize || !bytes.Equal(fullChecksum[:SeedChecksumSize], checksumSeedBytes[crypto.EntropySize:]) {
   395  		return Seed{}, errors.New("seed failed checksum verification")
   396  	}
   397  	return seed, nil
   398  }