github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/wallet/offline.go (about) 1 package wallet 2 3 import ( 4 "bytes" 5 "errors" 6 "math" 7 8 "SiaPrime/crypto" 9 "SiaPrime/modules" 10 "SiaPrime/types" 11 ) 12 13 // UnspentOutputs returns the unspent outputs tracked by the wallet. 14 func (w *Wallet) UnspentOutputs() ([]modules.UnspentOutput, error) { 15 if err := w.tg.Add(); err != nil { 16 return nil, err 17 } 18 defer w.tg.Done() 19 w.mu.Lock() 20 defer w.mu.Unlock() 21 22 // ensure durability of reported outputs 23 if err := w.syncDB(); err != nil { 24 return nil, err 25 } 26 27 // build initial list of confirmed outputs 28 var outputs []modules.UnspentOutput 29 dbForEachSiacoinOutput(w.dbTx, func(scoid types.SiacoinOutputID, sco types.SiacoinOutput) { 30 outputs = append(outputs, modules.UnspentOutput{ 31 FundType: types.SpecifierSiacoinOutput, 32 ID: types.OutputID(scoid), 33 UnlockHash: sco.UnlockHash, 34 Value: sco.Value, 35 }) 36 }) 37 dbForEachSiafundOutput(w.dbTx, func(sfoid types.SiafundOutputID, sfo types.SiafundOutput) { 38 outputs = append(outputs, modules.UnspentOutput{ 39 FundType: types.SpecifierSiafundOutput, 40 ID: types.OutputID(sfoid), 41 UnlockHash: sfo.UnlockHash, 42 Value: sfo.Value, 43 }) 44 }) 45 46 // don't include outputs marked as spent in pending transactions 47 pending := make(map[types.OutputID]struct{}) 48 for _, pt := range w.unconfirmedProcessedTransactions { 49 for _, input := range pt.Inputs { 50 if input.WalletAddress { 51 pending[input.ParentID] = struct{}{} 52 } 53 } 54 } 55 filtered := outputs[:0] 56 for _, o := range outputs { 57 if _, ok := pending[o.ID]; !ok { 58 filtered = append(filtered, o) 59 } 60 } 61 outputs = filtered 62 63 // set the confirmation height for each output 64 outer: 65 for i, o := range outputs { 66 txnIndices, err := dbGetAddrTransactions(w.dbTx, o.UnlockHash) 67 if err != nil { 68 return nil, err 69 } 70 for _, j := range txnIndices { 71 pt, err := dbGetProcessedTransaction(w.dbTx, j) 72 if err != nil { 73 return nil, err 74 } 75 for _, sco := range pt.Outputs { 76 if sco.ID == o.ID { 77 outputs[i].ConfirmationHeight = pt.ConfirmationHeight 78 continue outer 79 } 80 } 81 } 82 } 83 84 // add unconfirmed outputs, except those that are spent in pending 85 // transactions 86 for _, pt := range w.unconfirmedProcessedTransactions { 87 for _, o := range pt.Outputs { 88 if _, ok := pending[o.ID]; !ok && o.WalletAddress { 89 outputs = append(outputs, modules.UnspentOutput{ 90 FundType: types.SpecifierSiacoinOutput, 91 ID: o.ID, 92 UnlockHash: o.RelatedAddress, 93 Value: o.Value, 94 ConfirmationHeight: types.BlockHeight(math.MaxUint64), // unconfirmed 95 }) 96 } 97 } 98 } 99 100 // mark the watch-only outputs 101 for i, o := range outputs { 102 _, ok := w.watchedAddrs[o.UnlockHash] 103 outputs[i].IsWatchOnly = ok 104 } 105 106 return outputs, nil 107 } 108 109 // UnlockConditions returns the UnlockConditions for the specified address, if 110 // they are known to the wallet. 111 func (w *Wallet) UnlockConditions(addr types.UnlockHash) (uc types.UnlockConditions, err error) { 112 if err := w.tg.Add(); err != nil { 113 return types.UnlockConditions{}, err 114 } 115 defer w.tg.Done() 116 w.mu.RLock() 117 defer w.mu.RUnlock() 118 if !w.unlocked { 119 return types.UnlockConditions{}, modules.ErrLockedWallet 120 } 121 if sk, ok := w.keys[addr]; ok { 122 uc = sk.UnlockConditions 123 } else { 124 // not in memory; try database 125 uc, err = dbGetUnlockConditions(w.dbTx, addr) 126 if err != nil { 127 return types.UnlockConditions{}, errors.New("no record of UnlockConditions for that UnlockHash") 128 } 129 } 130 // make a copy of the public key slice; otherwise the caller can modify it 131 uc.PublicKeys = append([]types.SiaPublicKey(nil), uc.PublicKeys...) 132 return uc, nil 133 } 134 135 // AddUnlockConditions adds a set of UnlockConditions to the wallet database. 136 func (w *Wallet) AddUnlockConditions(uc types.UnlockConditions) error { 137 if err := w.tg.Add(); err != nil { 138 return err 139 } 140 defer w.tg.Done() 141 w.mu.RLock() 142 defer w.mu.RUnlock() 143 if !w.unlocked { 144 return modules.ErrLockedWallet 145 } 146 return dbPutUnlockConditions(w.dbTx, uc) 147 } 148 149 // SignTransaction signs txn using secret keys known to the wallet. The 150 // transaction should be complete with the exception of the Signature fields 151 // of each TransactionSignature referenced by toSign. For convenience, if 152 // toSign is empty, SignTransaction signs everything that it can. 153 func (w *Wallet) SignTransaction(txn *types.Transaction, toSign []crypto.Hash) error { 154 if err := w.tg.Add(); err != nil { 155 return err 156 } 157 defer w.tg.Done() 158 159 w.mu.Lock() 160 defer w.mu.Unlock() 161 if !w.unlocked { 162 return modules.ErrLockedWallet 163 } 164 consensusHeight, err := dbGetConsensusHeight(w.dbTx) 165 if err != nil { 166 return err 167 } 168 169 // if toSign is empty, sign all inputs that we have keys for 170 if len(toSign) == 0 { 171 for _, sci := range txn.SiacoinInputs { 172 if _, ok := w.keys[sci.UnlockConditions.UnlockHash()]; ok { 173 toSign = append(toSign, crypto.Hash(sci.ParentID)) 174 } 175 } 176 for _, sfi := range txn.SiafundInputs { 177 if _, ok := w.keys[sfi.UnlockConditions.UnlockHash()]; ok { 178 toSign = append(toSign, crypto.Hash(sfi.ParentID)) 179 } 180 } 181 } 182 return signTransaction(txn, w.keys, toSign, consensusHeight) 183 } 184 185 // SignTransaction signs txn using secret keys derived from seed. The 186 // transaction should be complete with the exception of the Signature fields 187 // of each TransactionSignature referenced by toSign, which must not be empty. 188 // 189 // SignTransaction must derive all of the keys from scratch, so it is 190 // appreciably slower than calling the Wallet.SignTransaction method. Only the 191 // first 1 million keys are derived. 192 func SignTransaction(txn *types.Transaction, seed modules.Seed, toSign []crypto.Hash, height types.BlockHeight) error { 193 if len(toSign) == 0 { 194 // unlike the wallet method, we can't simply "sign all inputs we have 195 // keys for," because without generating all of the keys up front, we 196 // don't know how many inputs we actually have keys for. 197 return errors.New("toSign cannot be empty") 198 } 199 // generate keys in batches up to 1e6 before giving up 200 keys := make(map[types.UnlockHash]spendableKey, 1e6) 201 var keyIndex uint64 202 const keysPerBatch = 1000 203 for len(keys) < 1e6 { 204 for _, sk := range generateKeys(seed, keyIndex, keyIndex+keysPerBatch) { 205 keys[sk.UnlockConditions.UnlockHash()] = sk 206 } 207 keyIndex += keysPerBatch 208 if err := signTransaction(txn, keys, toSign, height); err == nil { 209 return nil 210 } 211 } 212 return signTransaction(txn, keys, toSign, height) 213 } 214 215 // signTransaction signs the specified inputs of txn using the specified keys. 216 // It returns an error if any of the specified inputs cannot be signed. 217 func signTransaction(txn *types.Transaction, keys map[types.UnlockHash]spendableKey, toSign []crypto.Hash, height types.BlockHeight) error { 218 // helper function to lookup unlock conditions in the txn associated with 219 // a transaction signature's ParentID 220 findUnlockConditions := func(id crypto.Hash) (types.UnlockConditions, bool) { 221 for _, sci := range txn.SiacoinInputs { 222 if crypto.Hash(sci.ParentID) == id { 223 return sci.UnlockConditions, true 224 } 225 } 226 for _, sfi := range txn.SiafundInputs { 227 if crypto.Hash(sfi.ParentID) == id { 228 return sfi.UnlockConditions, true 229 } 230 } 231 return types.UnlockConditions{}, false 232 } 233 // helper function to lookup the secret key that can sign 234 findSigningKey := func(uc types.UnlockConditions, pubkeyIndex uint64) (crypto.SecretKey, bool) { 235 if pubkeyIndex >= uint64(len(uc.PublicKeys)) { 236 return crypto.SecretKey{}, false 237 } 238 pk := uc.PublicKeys[pubkeyIndex] 239 sk, ok := keys[uc.UnlockHash()] 240 if !ok { 241 return crypto.SecretKey{}, false 242 } 243 for _, key := range sk.SecretKeys { 244 pubKey := key.PublicKey() 245 if bytes.Equal(pk.Key, pubKey[:]) { 246 return key, true 247 } 248 } 249 return crypto.SecretKey{}, false 250 } 251 252 for _, id := range toSign { 253 // find associated txn signature 254 sigIndex := -1 255 for i, sig := range txn.TransactionSignatures { 256 if sig.ParentID == id { 257 sigIndex = i 258 break 259 } 260 } 261 if sigIndex == -1 { 262 return errors.New("toSign references signatures not present in transaction") 263 } 264 // find associated input 265 uc, ok := findUnlockConditions(id) 266 if !ok { 267 return errors.New("toSign references IDs not present in transaction") 268 } 269 // lookup the signing key 270 sk, ok := findSigningKey(uc, txn.TransactionSignatures[sigIndex].PublicKeyIndex) 271 if !ok { 272 return errors.New("could not locate signing key for " + id.String()) 273 } 274 // add signature 275 // 276 // NOTE: it's possible that the Signature field will already be filled 277 // out. Although we could save a bit of work by not signing it, in 278 // practice it's probably best to overwrite any existing signatures, 279 // since we know that ours will be valid. 280 sigHash := txn.SigHash(sigIndex, height) 281 encodedSig := crypto.SignHash(sigHash, sk) 282 txn.TransactionSignatures[sigIndex].Signature = encodedSig[:] 283 } 284 285 return nil 286 } 287 288 // AddWatchAddresses instructs the wallet to begin tracking a set of 289 // addresses, in addition to the addresses it was previously tracking. If none 290 // of the addresses have appeared in the blockchain, the unused flag may be 291 // set to true. Otherwise, the wallet must rescan the blockchain to search for 292 // transactions containing the addresses. 293 func (w *Wallet) AddWatchAddresses(addrs []types.UnlockHash, unused bool) error { 294 if err := w.tg.Add(); err != nil { 295 return modules.ErrWalletShutdown 296 } 297 defer w.tg.Done() 298 299 err := func() error { 300 w.mu.Lock() 301 defer w.mu.Unlock() 302 if !w.unlocked { 303 return modules.ErrLockedWallet 304 } 305 306 // update in-memory map 307 for _, addr := range addrs { 308 w.watchedAddrs[addr] = struct{}{} 309 } 310 // update db 311 alladdrs := make([]types.UnlockHash, 0, len(w.watchedAddrs)) 312 for addr := range w.watchedAddrs { 313 alladdrs = append(alladdrs, addr) 314 } 315 if err := dbPutWatchedAddresses(w.dbTx, alladdrs); err != nil { 316 return err 317 } 318 319 if !unused { 320 // prepare to rescan 321 if err := w.dbTx.DeleteBucket(bucketProcessedTransactions); err != nil { 322 return err 323 } 324 if _, err := w.dbTx.CreateBucket(bucketProcessedTransactions); err != nil { 325 return err 326 } 327 w.unconfirmedProcessedTransactions = nil 328 if err := dbPutConsensusChangeID(w.dbTx, modules.ConsensusChangeBeginning); err != nil { 329 return err 330 } 331 if err := dbPutConsensusHeight(w.dbTx, 0); err != nil { 332 return err 333 } 334 } 335 return w.syncDB() 336 }() 337 if err != nil { 338 return err 339 } 340 341 if !unused { 342 // rescan the blockchain 343 w.cs.Unsubscribe(w) 344 w.tpool.Unsubscribe(w) 345 346 done := make(chan struct{}) 347 go w.rescanMessage(done) 348 defer close(done) 349 if err := w.cs.ConsensusSetSubscribe(w, modules.ConsensusChangeBeginning, w.tg.StopChan()); err != nil { 350 return err 351 } 352 w.tpool.TransactionPoolSubscribe(w) 353 } 354 355 return nil 356 } 357 358 // RemoveWatchAddresses instructs the wallet to stop tracking a set of 359 // addresses and delete their associated transactions. If none of the 360 // addresses have appeared in the blockchain, the unused flag may be set to 361 // true. Otherwise, the wallet must rescan the blockchain to rebuild its 362 // transaction history. 363 func (w *Wallet) RemoveWatchAddresses(addrs []types.UnlockHash, unused bool) error { 364 if err := w.tg.Add(); err != nil { 365 return modules.ErrWalletShutdown 366 } 367 defer w.tg.Done() 368 369 err := func() error { 370 w.mu.Lock() 371 defer w.mu.Unlock() 372 if !w.unlocked { 373 return modules.ErrLockedWallet 374 } 375 376 // update in-memory map 377 for _, addr := range addrs { 378 delete(w.watchedAddrs, addr) 379 } 380 // update db 381 alladdrs := make([]types.UnlockHash, 0, len(w.watchedAddrs)) 382 for addr := range w.watchedAddrs { 383 alladdrs = append(alladdrs, addr) 384 } 385 if err := dbPutWatchedAddresses(w.dbTx, alladdrs); err != nil { 386 return err 387 } 388 if !unused { 389 // outputs associated with the addresses may be present in the 390 // SiacoinOutputs bucket. Iterate through the bucket and remove 391 // any outputs that we are no longer watching. 392 var outputIDs []types.SiacoinOutputID 393 dbForEachSiacoinOutput(w.dbTx, func(scoid types.SiacoinOutputID, sco types.SiacoinOutput) { 394 if !w.isWalletAddress(sco.UnlockHash) { 395 outputIDs = append(outputIDs, scoid) 396 } 397 }) 398 for _, scoid := range outputIDs { 399 if err := dbDeleteSiacoinOutput(w.dbTx, scoid); err != nil { 400 return err 401 } 402 } 403 404 // prepare to rescan 405 if err := w.dbTx.DeleteBucket(bucketProcessedTransactions); err != nil { 406 return err 407 } 408 if _, err := w.dbTx.CreateBucket(bucketProcessedTransactions); err != nil { 409 return err 410 } 411 w.unconfirmedProcessedTransactions = nil 412 if err := dbPutConsensusChangeID(w.dbTx, modules.ConsensusChangeBeginning); err != nil { 413 return err 414 } 415 if err := dbPutConsensusHeight(w.dbTx, 0); err != nil { 416 return err 417 } 418 } 419 return w.syncDB() 420 }() 421 if err != nil { 422 return err 423 } 424 425 if !unused { 426 // rescan the blockchain 427 w.cs.Unsubscribe(w) 428 w.tpool.Unsubscribe(w) 429 430 done := make(chan struct{}) 431 go w.rescanMessage(done) 432 defer close(done) 433 if err := w.cs.ConsensusSetSubscribe(w, modules.ConsensusChangeBeginning, w.tg.StopChan()); err != nil { 434 return err 435 } 436 w.tpool.TransactionPoolSubscribe(w) 437 } 438 439 return nil 440 } 441 442 // WatchAddresses returns the set of addresses that the wallet is currently 443 // watching. 444 func (w *Wallet) WatchAddresses() ([]types.UnlockHash, error) { 445 if err := w.tg.Add(); err != nil { 446 return nil, modules.ErrWalletShutdown 447 } 448 defer w.tg.Done() 449 450 w.mu.RLock() 451 defer w.mu.RUnlock() 452 453 addrs := make([]types.UnlockHash, 0, len(w.watchedAddrs)) 454 for addr := range w.watchedAddrs { 455 addrs = append(addrs, addr) 456 } 457 return addrs, nil 458 }