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 }