github.com/deroproject/derosuite@v2.1.6-1.0.20200307070847-0f2e589c7a2b+incompatible/walletapi/wallet.go (about) 1 // Copyright 2017-2018 DERO Project. All rights reserved. 2 // Use of this source code in any form is governed by RESEARCH license. 3 // license can be found in the LICENSE file. 4 // GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 5 // 6 // 7 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 8 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 9 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 10 // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 11 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 12 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 13 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 14 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 15 // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 16 17 package walletapi 18 19 import "fmt" 20 import "net" 21 import "sort" 22 import "sync" 23 import "time" 24 import "bytes" 25 import "strings" 26 import "crypto/rand" 27 import "encoding/json" 28 import "encoding/binary" 29 30 import "github.com/romana/rlog" 31 import "github.com/vmihailenco/msgpack" 32 33 import "github.com/deroproject/derosuite/config" 34 import "github.com/deroproject/derosuite/structures" 35 import "github.com/deroproject/derosuite/crypto" 36 import "github.com/deroproject/derosuite/crypto/ringct" 37 import "github.com/deroproject/derosuite/globals" 38 import "github.com/deroproject/derosuite/walletapi/mnemonics" 39 import "github.com/deroproject/derosuite/address" 40 import "github.com/deroproject/derosuite/blockchain/inputmaturity" 41 42 // used to encrypt payment id 43 const ENCRYPTED_PAYMENT_ID_TAIL = 0x8d 44 45 type _Keys struct { 46 Spendkey_Secret crypto.Key `json:"spendkey_secret"` 47 Spendkey_Public crypto.Key `json:"spendkey_public"` 48 Viewkey_Secret crypto.Key `json:"viewkey_secret"` 49 Viewkey_Public crypto.Key `json:"viewkey_public"` 50 } 51 52 // all random outputs are stored within wallet in this form 53 // to be used as ring members 54 type Ring_Member struct { // structure size is around 74 bytes 55 InKey ringct.CtKey `msgpack:"K"` 56 Index_Global uint64 `msgpack:"I"` 57 Height uint64 `msgpack:"H"` 58 Unlock_Height uint64 `msgpack:"U,omitempty"` // this is mostly empty 59 Sigtype uint64 `msgpack:"S,omitempty"` // this is empty for miner tx 60 } 61 62 type Account struct { 63 Keys _Keys `json:"keys"` 64 SeedLanguage string `json:"seedlanguage"` 65 FeesMultiplier float32 `json:"feesmultiplier"` // fees multiplier accurate to 2 decimals 66 Mixin int `json:"mixin"` // default mixn to use for txs 67 68 ViewOnly bool `json:"viewonly"` // is this viewonly wallet 69 70 Index_Global uint64 `json:"index_global"` // till where the indexes have been processed, it must only increase and never decrease 71 Height uint64 `json:"height"` // block height till where blockchain has been scanned 72 TopoHeight int64 `json:"topoheight"` // block height till where blockchain has been scanned 73 74 //Wallet_Height uint64 `json:"wallet_height"`// used to track height till which we have scanned the inputs 75 76 balance_stale bool // whether the balance is stale 77 Balance_Mature uint64 `json:"balance_mature"` // total balance of account 78 Balance_Locked uint64 `json:"balance_locked"` // balance locked 79 80 random_percent uint64 // number of outputs to store within the db, for mixing, default is 10% 81 82 key_image_checklist map[crypto.Key]bool // key images which need to be monitored, this is updated when new funds arrive 83 84 //Outputs_Array []TX_Wallet_Data // all outputs found in the chain belonging to us, as found in chain 85 86 // uint64 si the Index_Global which is the unique number 87 //Outputs_Index map[uint64]bool // all outputs which are ours for deduplication 88 //Outputs_Ready map[uint64]TX_Wallet_Data // these outputs are ready for consumption ( maturity needs to be checked) 89 //Keyimages_Ready map[crypto.Key]bool // keyimages which are ready to get consumed, // we monitor them to find which 90 //Outputs_Consumed map[crypto.Key]TX_Wallet_Data // the key is the keyimage 91 92 //Random_Outputs map[uint64]TX_Wallet_Data // random ring members 93 //Random_Outputs_Recent map[uint64]TX_Wallet_Data // random ring members from recent blocks 94 //Ring_Members map[uint64]bool // ring members 95 96 sync.Mutex // syncronise modifications to this structure 97 } 98 99 // this structure is kept by wallet 100 type TX_Wallet_Data struct { 101 TXdata globals.TX_Output_Data `msgpack:"txdata"` // all the fields of output data 102 103 WAmount uint64 `msgpack:"wamount"` // actual amount, in case of miner it is verbatim, for other cases it decrypted 104 WKey ringct.CtKey `msgpack:"wkey"` // key which is used to later send this specific output 105 WKimage crypto.Key `msgpack:"wkimage"` // key image which gets consumed when this output is spent 106 WSpent bool `msgpack:"wspent"` // whether this output has been spent 107 WSpentPool bool //`msgpack:""`// we built and send out a tx , but it has not been mined 108 WPaymentID []byte `msgpack:"wpaymentid"` // payment if if present and decrypted if required 109 WSecretTXkey crypto.Key `msgpack:"wsecrettxkey"` // tx secret which can be be used to prove that the funds have been spent 110 } 111 112 // generate keys from using random numbers 113 func Generate_Keys_From_Random() (user *Account, err error) { 114 user = &Account{Mixin: 5, FeesMultiplier: 1.5} 115 seed := crypto.RandomScalar() 116 user.Keys = Generate_Keys_From_Seed(*seed) 117 118 return 119 } 120 121 // generate keys from seed which is from the recovery words 122 // or we feed in direct 123 func Generate_Keys_From_Seed(Seed crypto.Key) (keys _Keys) { 124 125 // setup main keys 126 keys.Spendkey_Secret = Seed 127 keys.Spendkey_Public = *(Seed.PublicKey()) 128 129 // view keys are generated from secret ( so as single recovery seed is enough ) 130 hash := crypto.Key(crypto.Keccak256(Seed[:])) 131 crypto.ScReduce32(&hash) 132 keys.Viewkey_Secret = hash 133 keys.Viewkey_Public = *(keys.Viewkey_Secret.PublicKey()) 134 135 return 136 } 137 138 // generate user account using recovery seeds 139 func Generate_Account_From_Recovery_Words(words string) (user *Account, err error) { 140 user = &Account{Mixin: 5, FeesMultiplier: 1.5} 141 language, seed, err := mnemonics.Words_To_Key(words) 142 if err != nil { 143 return 144 } 145 146 user.SeedLanguage = language 147 user.Keys = Generate_Keys_From_Seed(seed) 148 149 return 150 } 151 152 func Generate_Account_From_Seed(Seed crypto.Key) (user *Account, err error) { 153 user = &Account{Mixin: 5, FeesMultiplier: 1.5} 154 155 // TODO check whether the seed is invalid 156 user.Keys = Generate_Keys_From_Seed(Seed) 157 158 return 159 } 160 161 // generate keys for view only wallet 162 func Generate_Account_View_Only(Publicspend crypto.Key, ViewSecret crypto.Key) (user *Account, err error) { 163 164 user = &Account{Mixin: 5, FeesMultiplier: 1.5} 165 166 // TODO check whether seed is valid secret 167 user.Keys.Spendkey_Public = Publicspend 168 user.Keys.Viewkey_Secret = ViewSecret 169 user.Keys.Viewkey_Public = *(ViewSecret.PublicKey()) 170 user.ViewOnly = true 171 172 return 173 } 174 175 // generate keys for view only wallet 176 func Generate_Account_NONDeterministic_Only(Secretspend crypto.Key, ViewSecret crypto.Key) (user *Account, err error) { 177 178 user = &Account{Mixin: 5, FeesMultiplier: 1.5} 179 180 // TODO check whether seed is valid secret 181 user.Keys.Spendkey_Secret = Secretspend 182 user.Keys.Spendkey_Public = *(Secretspend.PublicKey()) 183 user.Keys.Viewkey_Secret = ViewSecret 184 user.Keys.Viewkey_Public = *(ViewSecret.PublicKey()) 185 user.ViewOnly = true 186 187 return 188 } 189 190 // convert key to seed using language 191 func (w *Wallet) GetSeed() (str string) { 192 return mnemonics.Key_To_Words(w.account.Keys.Spendkey_Secret, w.account.SeedLanguage) 193 } 194 195 // convert key to seed using language 196 func (w *Wallet) GetSeedinLanguage(lang string) (str string) { 197 return mnemonics.Key_To_Words(w.account.Keys.Spendkey_Secret, lang) 198 } 199 200 // view wallet key consists of public spendkey and private view key 201 func (w *Wallet) GetViewWalletKey() (str string) { 202 return fmt.Sprintf("%s%s", w.account.Keys.Spendkey_Public, w.account.Keys.Viewkey_Secret) 203 } 204 205 func (account *Account) GetAddress() (addr address.Address) { 206 switch globals.Config.Name { 207 case "testnet": 208 addr.Network = config.Testnet.Public_Address_Prefix //choose dETo 209 210 default: 211 fallthrough // assume mainnet 212 case "mainnet": 213 addr.Network = config.Mainnet.Public_Address_Prefix //choose dERo 214 215 //panic(fmt.Sprintf("Unknown Network \"%s\"", globals.Config.Name)) 216 } 217 218 addr.SpendKey = account.Keys.Spendkey_Public 219 addr.ViewKey = account.Keys.Viewkey_Public 220 221 return 222 } 223 224 // convert a user account to address 225 func (w *Wallet) GetAddress() (addr address.Address) { 226 return w.account.GetAddress() 227 } 228 229 // get a random integrated address 230 func (w *Wallet) GetRandomIAddress8() (addr address.Address) { 231 addr = w.account.GetAddress() 232 233 if addr.Network == config.Mainnet.Public_Address_Prefix { 234 addr.Network = config.Mainnet.Public_Address_Prefix_Integrated 235 } else { // it's a testnet address 236 addr.Network = config.Testnet.Public_Address_Prefix_Integrated 237 } 238 239 // setup random 8 bytes of payment ID, it must be from non-deterministic RNG namely crypto random 240 addr.PaymentID = make([]byte, 8, 8) 241 rand.Read(addr.PaymentID[:]) 242 243 return 244 } 245 246 // get a random integrated address 247 func (w *Wallet) GetRandomIAddress32() (addr address.Address) { 248 addr = w.account.GetAddress() 249 250 if addr.Network == config.Mainnet.Public_Address_Prefix { 251 addr.Network = config.Mainnet.Public_Address_Prefix_Integrated 252 } else { // it's a testnet address 253 addr.Network = config.Testnet.Public_Address_Prefix_Integrated 254 } 255 256 // setup random 32 bytes of payment ID, it must be from non-deterministic RNG namely crypto random 257 addr.PaymentID = make([]byte, 32, 32) 258 rand.Read(addr.PaymentID[:]) 259 260 return 261 } 262 263 // this function is used to encrypt/decrypt payment id 264 // as the operation is symmetric XOR, is the same in both direction 265 func EncryptDecryptPaymentID(derivation crypto.Key, tx_public crypto.Key, input []byte) (output []byte) { 266 // input must be exactly 8 bytes long 267 if len(input) != 8 { 268 panic("Encrypted payment ID must be exactly 8 bytes long") 269 } 270 271 var tmp_buf [33]byte 272 copy(tmp_buf[:], derivation[:]) // copy derivation key to buffer 273 tmp_buf[32] = ENCRYPTED_PAYMENT_ID_TAIL 274 275 // take hash 276 hash := crypto.Keccak256(tmp_buf[:]) // take hash of entire 33 bytes, 32 bytes derivation key, 1 byte tail 277 278 output = make([]byte, 8, 8) 279 for i := range input { 280 output[i] = input[i] ^ hash[i] // xor the bytes with the hash 281 } 282 283 return 284 } 285 286 // one simple function which does all the crypto to find out whether output belongs to this account 287 // NOTE: this function only uses view key secret and Spendkey_Public 288 // output index is the position of vout within the tx list itself 289 func (w *Wallet) Is_Output_Ours(tx_public crypto.Key, output_index uint64, vout_key crypto.Key) bool { 290 derivation := crypto.KeyDerivation(&tx_public, &w.account.Keys.Viewkey_Secret) 291 derivation_public_key := derivation.KeyDerivation_To_PublicKey(output_index, w.account.Keys.Spendkey_Public) 292 293 return derivation_public_key == vout_key 294 } 295 296 // only for testing purposes 297 func (acc *Account) Is_Output_Ours(tx_public crypto.Key, output_index uint64, vout_key crypto.Key) bool { 298 derivation := crypto.KeyDerivation(&tx_public, &acc.Keys.Viewkey_Secret) 299 derivation_public_key := derivation.KeyDerivation_To_PublicKey(output_index, acc.Keys.Spendkey_Public) 300 return derivation_public_key == vout_key 301 } 302 303 // this function does all the keyderivation required for decrypting ringct outputs, generate keyimage etc 304 // also used when we build up a transaction for mining or sending amount 305 func (w *Wallet) Generate_Helper_Key_Image(tx_public crypto.Key, output_index uint64) (ephermal_secret, ephermal_public, keyimage crypto.Key) { 306 derivation := crypto.KeyDerivation(&tx_public, &w.account.Keys.Viewkey_Secret) 307 ephermal_secret = derivation.KeyDerivation_To_PrivateKey(output_index, w.account.Keys.Spendkey_Secret) 308 ephermal_public = derivation.KeyDerivation_To_PublicKey(output_index, w.account.Keys.Spendkey_Public) 309 keyimage = crypto.GenerateKeyImage(ephermal_public, ephermal_secret) 310 311 return 312 } 313 314 // this function decodes ringCT encoded output amounts 315 // this is only possible if signature is full or simple 316 func (w *Wallet) Decode_RingCT_Output(tx_public crypto.Key, output_index uint64, pkkey crypto.Key, tuple ringct.ECdhTuple, sigtype uint64) (amount uint64, mask crypto.Key, result bool) { 317 318 derivation := crypto.KeyDerivation(&tx_public, &w.account.Keys.Viewkey_Secret) 319 scalar_key := derivation.KeyDerivationToScalar(output_index) 320 321 switch sigtype { 322 case 0: // NOT possible , miner tx outputs are not hidden 323 return 324 case 1: // ringct MG // Both ringct outputs can be decoded using the same methods 325 // however, original implementation has different methods, maybe need to evaluate more 326 fallthrough 327 case 2, 4: // ringct sample 328 329 amount, mask, result = ringct.Decode_Amount(tuple, *scalar_key, pkkey) 330 331 default: 332 return 333 } 334 335 return 336 } 337 338 // add the transaction to our wallet record, so as funds can be later on tracked 339 // due to enhanced features, we have to wait and watch for all funds 340 // this will extract secret keys from newly arrived funds to consume them later on 341 func (w *Wallet) Add_Transaction_Record_Funds(txdata *globals.TX_Output_Data) (amount uint64, result bool) { 342 343 // check for input maturity at every height change 344 w.Lock() 345 if w.account.Height < txdata.Height { 346 w.account.Height = txdata.Height 347 w.account.TopoHeight = txdata.TopoHeight 348 w.account.balance_stale = true // balance needs recalculation 349 } 350 w.Unlock() 351 352 w.Store_Height_Mapping(txdata) // update height to block mapping, also sets wallet height 353 w.Add_Possible_Ring_Member(txdata) // add as ringmember for future 354 355 // if our funds have been consumed, remove it from our available list 356 for i := range txdata.Key_Images { 357 w.Is_Our_Fund_Consumed(txdata.Key_Images[i], txdata) 358 } 359 360 // confirm that that data belongs to this user 361 if !w.Is_Output_Ours(txdata.Tx_Public_Key, txdata.Index_within_tx, crypto.Key(txdata.InKey.Destination)) { 362 return // output is not ours 363 } 364 365 // since input is ours, take a lock for processing 366 defer w.Save_Wallet() 367 w.Lock() 368 defer w.Unlock() 369 370 var tx_wallet TX_Wallet_Data 371 372 /* 373 // check whether we are deduplicating, is the transaction already in our records, skip it 374 // transaction is already in our wallet, skip it for being duplicate 375 if w.check_key_exists(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_BUCKET), itob(txdata.Index_Global)) { 376 return 377 } 378 */ 379 380 // setup Amount 381 switch txdata.SigType { 382 case 0: // miner tx 383 tx_wallet.WAmount = txdata.Amount 384 tx_wallet.WKey.Mask = ringct.Identity // secret mask for miner tx is Identity 385 386 case 1, 2, 4: // ringct full/simple, simplebulletproof 387 tx_wallet.WAmount, tx_wallet.WKey.Mask, result = w.Decode_RingCT_Output(txdata.Tx_Public_Key, txdata.Index_within_tx, crypto.Key(txdata.InKey.Mask), txdata.ECDHTuple, 388 txdata.SigType) 389 390 if result == false { // It's an internal error most probably, log more details 391 return 392 } 393 case 3: //fullbulletproof are not supported 394 395 } 396 397 amount = tx_wallet.WAmount // setup amount so it can be properly returned 398 tx_wallet.TXdata = *txdata 399 400 // use payment ID if availble 401 // due to design we never know for which output the payment id was 402 // C daemon places relates it with any outputs 403 // so we attach the payment with all outputs 404 // tracking it would make wallet slow 405 // we cannot send to ourselves with a payment ID 406 // if the TX was sent by this wallet, do NOT process payment IDs,to avoid triggerring a critical bug 407 // we need a better FIX for this 408 // NOTE: this fix needs reqork before adding support for SHADOW addresses 409 if w.GetTXKey(txdata.TXID) == "" { // make sure TX was not sent by this wallet 410 if !w.check_key_exists(BLOCKCHAIN_UNIVERSE, []byte(OUR_TX_BUCKET), txdata.TXID[:]) { // if wallet is recreated, we track our TX via key-images 411 if txdata.Index_within_tx >= 0 { 412 switch len(txdata.PaymentID) { 413 case 8: // this needs to be decoded 414 derivation := crypto.KeyDerivation(&txdata.Tx_Public_Key, &w.account.Keys.Viewkey_Secret) 415 tx_wallet.WPaymentID = EncryptDecryptPaymentID(derivation, txdata.Tx_Public_Key, txdata.PaymentID) 416 case 32: 417 tx_wallet.WPaymentID = txdata.PaymentID 418 } 419 } 420 } 421 } 422 423 // if wallet is viewonly, we cannot track when the funds were spent 424 // so lets skip the part, since we do not have the keys 425 if !w.account.ViewOnly { // it's a full wallet, track spendable and get ready to spend 426 secret_key, _, kimage := w.Generate_Helper_Key_Image(txdata.Tx_Public_Key, txdata.Index_within_tx) 427 tx_wallet.WKimage = kimage 428 tx_wallet.WKey.Destination = secret_key 429 430 if w.check_key_exists(BLOCKCHAIN_UNIVERSE, []byte(KEYIMAGE_BUCKET), kimage[:]) { 431 // find the output index to which this key image belong 432 value_bytes, err := w.load_key_value(BLOCKCHAIN_UNIVERSE, []byte(KEYIMAGE_BUCKET), kimage[:]) 433 if err == nil && len(value_bytes) == 8 { 434 index := binary.BigEndian.Uint64(value_bytes) 435 436 // now lets load the suitable index data 437 value_bytes, err = w.load_key_value(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_BUCKET), itob(index)) 438 if err == nil { 439 var tx_wallet_temp TX_Wallet_Data 440 err = msgpack.Unmarshal(value_bytes, &tx_wallet_temp) 441 if err == nil { 442 if tx_wallet_temp.TXdata.TXID != txdata.TXID { // transaction mismatch 443 rlog.Warnf("KICD %s,%s, \n%+v \n%+v",txdata.TXID, tx_wallet_temp.TXdata.TXID, txdata,tx_wallet_temp); 444 return 0, false 445 } 446 447 if tx_wallet_temp.TXdata.Index_within_tx != txdata.Index_within_tx { // index within tx mismatch 448 rlog.Warnf("KICD2 %s,%s, \n%+v \n%+v",txdata.TXID, tx_wallet_temp.TXdata.TXID, txdata,tx_wallet_temp); 449 return 0, false 450 } 451 } 452 } 453 } 454 455 } 456 457 // store the key image so as later on we can find when it is spent 458 w.store_key_value(BLOCKCHAIN_UNIVERSE, []byte(KEYIMAGE_BUCKET), kimage[:], itob(txdata.Index_Global)) 459 } 460 461 // serialize and store the tx, make it available for funds 462 serialized, err := msgpack.Marshal(&tx_wallet) 463 if err != nil { 464 panic(err) 465 } 466 // store all data about the transfer 467 w.store_key_value(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_BUCKET), itob(txdata.Index_Global), serialized) 468 469 // store TX to global output index link 470 w.store_key_value(BLOCKCHAIN_UNIVERSE, append([]byte(TXID), txdata.TXID[:]...), itob(txdata.Index_Global), itob(txdata.Index_Global)) 471 472 // store payment ID to global output index link, but only if we have payment ID 473 if len(tx_wallet.WPaymentID) == 8 || len(tx_wallet.WPaymentID) == 32 { 474 w.store_key_value(BLOCKCHAIN_UNIVERSE, append([]byte(PAYID), tx_wallet.WPaymentID[:]...), itob(txdata.Index_Global), itob(txdata.Index_Global)) 475 } 476 477 // mark the funds as available 478 w.store_key_value(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_AVAILABLE), itob(txdata.Index_Global), itob(txdata.Index_Global)) 479 480 // TODO we must also sort transactions by payment id, so as they are searchable by payment id 481 w.account.balance_stale = true // balance needs recalculation, as new funds have arrived 482 483 result = true 484 return 485 } 486 487 // check whether our fund is consumed 488 // this is done by finding the keyimages floating in blockchain, to what keyimages belong to this account 489 // if match is found, we have consumed our funds 490 // NOTE: Funds spent check should always be earlier than TX output check, so as we can handle payment IDs properly 491 492 func (w *Wallet) Is_Our_Fund_Consumed(key_image crypto.Key, txdata *globals.TX_Output_Data) (amount uint64, result bool) { 493 494 // if not ours, go back 495 if !w.check_key_exists(BLOCKCHAIN_UNIVERSE, []byte(KEYIMAGE_BUCKET), key_image[:]) { 496 return 0, false 497 } 498 defer w.Save_Wallet() 499 w.Lock() 500 defer w.Unlock() 501 502 spent_index := txdata.Index_Global 503 504 // these are stored in a different bucket so we never collide with incoming funds 505 { 506 var tx_wallet TX_Wallet_Data 507 tx_wallet.TXdata = *txdata 508 509 // yes it is our fund, store relevant info to FUNDS bucket 510 serialized, err := msgpack.Marshal(&tx_wallet) 511 if err != nil { 512 panic(err) 513 } 514 // store all data 515 w.store_key_value(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_BUCKET_OUTGOING), itob(txdata.Index_Global), serialized) 516 } 517 518 // mark that this TX was sent by us 519 w.store_key_value(BLOCKCHAIN_UNIVERSE, []byte(OUR_TX_BUCKET), txdata.TXID[:], txdata.TXID[:]) 520 521 // find the output index to which this key image belong 522 value_bytes, err := w.load_key_value(BLOCKCHAIN_UNIVERSE, []byte(KEYIMAGE_BUCKET), key_image[:]) 523 if err != nil { 524 panic(fmt.Sprintf("Error while reading spent keyimage data key_image %s, err %s", key_image, err)) 525 } 526 index := binary.BigEndian.Uint64(value_bytes) 527 528 // now lets load the suitable index data 529 value_bytes, err = w.load_key_value(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_BUCKET), itob(index)) 530 if err != nil { 531 panic(fmt.Sprintf("Error while reading spent funds data key_image %s, index %d err %s", key_image, index, err)) 532 } 533 534 var tx_wallet TX_Wallet_Data 535 err = msgpack.Unmarshal(value_bytes, &tx_wallet) 536 if err != nil { 537 panic(fmt.Sprintf("Error while decoding spent funds data key_image %s, index %d err %s", key_image, index, err)) 538 } 539 540 // this case should never be possible, until logical or db corruption has already occured 541 if key_image != tx_wallet.WKimage { 542 fmt.Printf("%+v\n", tx_wallet) 543 panic(fmt.Sprintf("Stored key_image %s and loaded key image mismatch %s index %d err %s", key_image, tx_wallet.WKimage, index, err)) 544 } 545 546 // move the funds from availble to spent bucket 547 w.delete_key(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_AVAILABLE), itob(index)) 548 w.store_key_value(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_SPENT), itob(index), itob(index)) 549 550 w.store_key_value(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_SPENT_WHERE), itob(index), itob(spent_index)) 551 552 w.account.balance_stale = true // balance needs recalculation 553 554 return tx_wallet.WAmount, true 555 } 556 557 // add the transaction to record, 558 // this will mark the funds as consumed on the basis of keyimages 559 // locate the transaction and get the amount , this is O(n), so we can tell how much funds were spent 560 // cryptnote only allows to spend complete funds, change comes back 561 func (w *Wallet) Consume_Transaction_Record_Funds(txdata *globals.TX_Output_Data, key_image crypto.Key) bool { 562 return false 563 564 } 565 566 // get the unlocked balance ( amounts which are mature and can be spent at this time ) 567 // offline wallets may get this wrong, since they may not have latest data 568 // TODO: for offline wallets, we must make all balance as mature 569 // full resync costly 570 // TODO URGENT we are still not cleaning up, spent funds,do that asap to recover funds which were spent on alt-xhain 571 func (w *Wallet) Get_Balance_Rescan() (mature_balance uint64, locked_balance uint64) { 572 w.RLock() 573 defer w.RUnlock() 574 575 index_list := w.load_all_values_from_bucket(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_AVAILABLE)) 576 577 //fmt.Printf("found %d elements in bucket \n", len(index_list)) 578 for i := range index_list { // load index 579 index := binary.BigEndian.Uint64(index_list[i]) 580 value_bytes, err := w.load_key_value(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_BUCKET), index_list[i]) 581 if err != nil { 582 rlog.Debugf("Error while reading available funds index index %d err %s", index, err) 583 continue 584 } 585 586 var tx_wallet TX_Wallet_Data 587 err = msgpack.Unmarshal(value_bytes, &tx_wallet) 588 if err != nil { 589 rlog.Debugf("Error while decoding availble funds data index %d err %s", index, err) 590 continue 591 } 592 593 // check whether the height and block matches with what is stored at this point in time 594 local_hash, err := w.load_key_value(BLOCKCHAIN_UNIVERSE, []byte(HEIGHT_TO_BLOCK_BUCKET), itob(uint64(tx_wallet.TXdata.TopoHeight))) 595 if err != nil { 596 continue 597 } 598 599 // input disappeared due to chain soft fork 600 if len(local_hash) == 32 && !bytes.Equal(tx_wallet.TXdata.BLID[:], local_hash) { 601 // stop tracking the funds everywhere 602 w.delete_key(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_BUCKET), index_list[i]) // delete index 603 w.delete_key(BLOCKCHAIN_UNIVERSE, []byte(KEYIMAGE_BUCKET), tx_wallet.WKimage[:]) // delete key_image for this 604 w.delete_key(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_AVAILABLE), itob(index)) 605 w.delete_key(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_SPENT), index_list[i]) 606 607 w.delete_key(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_SPENT), index_list[i]) 608 609 // delete TXID to index mapping 610 w.delete_key(BLOCKCHAIN_UNIVERSE, append([]byte(TXID), tx_wallet.TXdata.TXID[:]...), index_list[i]) 611 // delete payment ID to index mapping 612 w.delete_key(BLOCKCHAIN_UNIVERSE, append([]byte(PAYID), tx_wallet.WPaymentID[:]...), index_list[i]) 613 614 continue // skip this input 615 } 616 617 if inputmaturity.Is_Input_Mature(w.account.Height, 618 tx_wallet.TXdata.Height, 619 tx_wallet.TXdata.Unlock_Height, 620 tx_wallet.TXdata.SigType) { 621 mature_balance += tx_wallet.WAmount 622 } else { 623 locked_balance += tx_wallet.WAmount 624 } 625 626 } 627 628 w.account.Balance_Mature = mature_balance 629 w.account.Balance_Locked = locked_balance 630 w.account.balance_stale = false // balance is updated 631 632 return 633 } 634 635 // get the unlocked balance ( amounts which are mature and can be spent at this time ) 636 // offline wallets may get this wrong, since they may not have latest data 637 638 // 639 func (w *Wallet) Get_Balance() (mature_balance uint64, locked_balance uint64) { 640 w.RLock() 641 if !w.Is_Balance_Modified() { 642 w.RUnlock() 643 return w.account.Balance_Mature, w.account.Balance_Locked 644 } 645 646 w.RUnlock() 647 return w.Get_Balance_Rescan() // call heavy function 648 } 649 650 var old_block_cache crypto.Hash // used as a cache to skip decryptions is possible 651 652 func (w *Wallet) Store_Height_Mapping(txdata *globals.TX_Output_Data) { 653 654 w.Lock() 655 defer w.Unlock() 656 657 // store height to block hash mapping 658 // store all data 659 // save block height only if required 660 661 w.account.Height = txdata.Height // increase wallet height 662 w.account.TopoHeight = txdata.TopoHeight // increase wallet topo height 663 if old_block_cache != txdata.BLID { // make wallet scanning as fast as possible 664 //fmt.Printf("gStoring height to block %d %s t %s\n", txdata.Height, txdata.BLID, txdata.TXID) 665 old_block, err := w.load_key_value(BLOCKCHAIN_UNIVERSE, []byte(HEIGHT_TO_BLOCK_BUCKET), itob(uint64(txdata.TopoHeight))) 666 if err != nil || !bytes.Equal(old_block, txdata.BLID[:]) { 667 w.store_key_value(BLOCKCHAIN_UNIVERSE, []byte(HEIGHT_TO_BLOCK_BUCKET), itob(uint64(txdata.TopoHeight)), txdata.BLID[:]) 668 old_block_cache = txdata.BLID 669 670 // fmt.Printf("Storing height to block %d %s\n", txdata.Height, txdata.BLID) 671 } 672 } 673 674 } 675 676 // add all random outputs which will be used while creating transactions 677 // currently we store all ringmembers 678 func (w *Wallet) Add_Possible_Ring_Member(txdata *globals.TX_Output_Data) { 679 680 if txdata == nil { 681 return 682 } 683 684 if w.Is_View_Only() { // view only wallets can never construct a transaction 685 return 686 } 687 w.Lock() 688 defer w.Unlock() 689 690 w.account.Index_Global = txdata.Index_Global // increment out pointer 691 692 var r Ring_Member 693 r.InKey = txdata.InKey 694 r.Height = txdata.Height 695 r.Index_Global = txdata.Index_Global 696 r.Unlock_Height = txdata.Unlock_Height // required for maturity checking 697 r.Sigtype = txdata.SigType // required for maturity checking 698 699 // lets serialize and store the data encrypted, so as no one track any info 700 serialized, err := msgpack.Marshal(&r) 701 if err != nil { 702 panic(err) 703 } 704 // store all data 705 w.store_key_value(BLOCKCHAIN_UNIVERSE, []byte(RING_BUCKET), itob(txdata.Index_Global), serialized) 706 707 return 708 } 709 710 type Entry struct { 711 Index_Global uint64 `json:"index_global"` 712 Height uint64 `json:"height"` 713 TopoHeight int64 `json:"topoheight"` 714 TXID crypto.Hash `json:"txid"` 715 Amount uint64 `json:"amount"` 716 PaymentID []byte `json:"payment_id"` 717 Status byte `json:"status"` 718 Unlock_Time uint64 `json:"unlock_time"` 719 Time time.Time `json:"time"` 720 Secret_TX_Key string `json:"secret_tx_key"` // can be used to prove if available 721 Details structures.Outgoing_Transfer_Details `json:"details"` // actual details if available 722 } 723 724 725 // finds all inputs which have been received/spent etc 726 // TODO this code can be easily parallelised and need to be parallelised 727 // if only the availble is requested, then the wallet is very fast 728 // the spent tracking may make it slow ( in case of large probably million txs ) 729 //TODO currently we do not track POOL at all any where ( except while building tx) 730 // if payment_id is true, only entries with payment ids are returned 731 func (w *Wallet) Show_Transfers(available bool, in bool, out bool, pool bool, failed bool, payment_id bool, min_height, max_height uint64) (entries []Entry) { 732 733 dero_first_block_time := time.Unix(1512432000, 0) //Tuesday, December 5, 2017 12:00:00 AM 734 735 if max_height == 0 { 736 max_height = 5000000000 737 } 738 if available || in { 739 index_list := w.load_all_values_from_bucket(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_AVAILABLE)) 740 741 for i := range index_list { // load index 742 current_index := binary.BigEndian.Uint64(index_list[i]) 743 744 tx, err := w.load_funds_data(current_index, FUNDS_BUCKET) 745 if err != nil { 746 rlog.Warnf("Error while reading available funds index index %d err %s", current_index, err) 747 continue 748 } 749 750 if tx.TXdata.Height >= min_height && tx.TXdata.Height <= max_height { // height filter 751 var entry Entry 752 entry.Index_Global = current_index 753 entry.Height = tx.TXdata.Height 754 entry.TopoHeight = tx.TXdata.TopoHeight 755 entry.TXID = tx.TXdata.TXID 756 entry.Amount = tx.WAmount 757 entry.PaymentID = tx.WPaymentID 758 entry.Status = 0 759 entry.Time = time.Unix(int64(tx.TXdata.Block_Time), 0) 760 761 if entry.Height < 95600 { // make up time for pre-atlantis blocks 762 duration, _ := time.ParseDuration(fmt.Sprintf("%ds", int64(180*entry.Height))) 763 entry.Time = dero_first_block_time.Add(duration) 764 } 765 766 if payment_id { 767 768 if len(entry.PaymentID) >= 8 { 769 entries = append(entries, entry) 770 } 771 } else { 772 entries = append(entries, entry) 773 } 774 775 } 776 777 } 778 } 779 780 if in || out { 781 // all spent funds will have 2 entries, one for receive/other for spent 782 index_list := w.load_all_values_from_bucket(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_SPENT)) 783 784 for i := range index_list { // load index 785 current_index := binary.BigEndian.Uint64(index_list[i]) 786 787 tx, err := w.load_funds_data(current_index, FUNDS_BUCKET) 788 if err != nil { 789 rlog.Warnf("Error while reading available funds index index %d err %s", current_index, err) 790 continue 791 } 792 793 /// receipt entry 794 var entry Entry 795 entry.Index_Global = current_index 796 entry.Height = tx.TXdata.Height 797 entry.TopoHeight = tx.TXdata.TopoHeight 798 entry.TXID = tx.TXdata.TXID 799 entry.Amount = tx.WAmount 800 entry.PaymentID = tx.WPaymentID 801 entry.Status = 0 802 entry.Time = time.Unix(int64(tx.TXdata.Block_Time), 0) 803 804 if entry.Height < 95600 { // make up time for pre-atlantis blocks 805 duration, _ := time.ParseDuration(fmt.Sprintf("%ds", int64(180*entry.Height))) 806 entry.Time = dero_first_block_time.Add(duration) 807 } 808 809 if in { 810 if tx.TXdata.Height >= min_height && tx.TXdata.Height <= max_height { // height filter 811 if payment_id { 812 813 if len(entry.PaymentID) >= 8 { 814 entries = append(entries, entry) 815 } 816 } else { 817 entries = append(entries, entry) 818 } 819 } 820 821 } 822 823 if out { 824 // spendy entry 825 value_bytes, err := w.load_key_value(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_SPENT_WHERE), index_list[i]) 826 if err != nil { 827 fmt.Printf("Error while reading FUNDS_SPENT_WHERE index %d err %s", current_index, err) 828 continue 829 } 830 spent_index := binary.BigEndian.Uint64(value_bytes) 831 tx, err = w.load_funds_data(spent_index, FUNDS_BUCKET_OUTGOING) 832 if err != nil { 833 rlog.Warnf("Error while reading available funds index index %d err %s", current_index, err) 834 continue 835 } 836 if tx.TXdata.Height >= min_height && tx.TXdata.Height <= max_height { // height filter 837 entry.Index_Global = tx.TXdata.Index_Global 838 entry.Height = tx.TXdata.Height 839 entry.TXID = tx.TXdata.TXID 840 entry.TopoHeight = tx.TXdata.TopoHeight 841 entry.PaymentID = entry.PaymentID[:0] // payment id needs to be zero or tracked from some where else 842 entry.Time = time.Unix(int64(tx.TXdata.Block_Time), 0) 843 844 if entry.Height < 95600 { // make up time for pre-atlantis blocks 845 duration, _ := time.ParseDuration(fmt.Sprintf("%ds", int64(180*entry.Height))) 846 entry.Time = dero_first_block_time.Add(duration) 847 } 848 849 // fill tx secret_key 850 entry.Secret_TX_Key = w.GetTXKey(tx.TXdata.TXID) 851 entry.Details = w.GetTXOutDetails(tx.TXdata.TXID) 852 853 entry.Status = 1 854 entries = append(entries, entry) // spend entry 855 } 856 } 857 858 } 859 } 860 861 sort.SliceStable(entries, func(i, j int) bool { return entries[i].Index_Global > entries[j].Index_Global }) 862 863 return 864 865 } 866 867 // w.delete_key(BLOCKCHAIN_UNIVERSE, append([]byte(TXID),tx_wallet.TXdata.TXID[:]...) , index_list[i]) 868 869 /* gets all the payments done to specific payment ID and filtered by specific block height */ 870 func (w *Wallet) Get_Payments_Payment_ID(payid []byte, min_height uint64) (entries []Entry) { 871 index_list := w.load_all_values_from_bucket(BLOCKCHAIN_UNIVERSE, append([]byte(PAYID), payid[:]...)) 872 873 for i := range index_list { 874 current_index := binary.BigEndian.Uint64(index_list[i]) 875 876 tx, err := w.load_funds_data(current_index, FUNDS_BUCKET) 877 if err != nil { 878 rlog.Warnf("Error while reading available funds index index %d err %s", current_index, err) 879 continue 880 } 881 882 if tx.TXdata.Height > min_height { // height filter 883 var entry Entry 884 entry.Index_Global = current_index 885 entry.TopoHeight = tx.TXdata.TopoHeight 886 entry.Height = tx.TXdata.Height 887 entry.TXID = tx.TXdata.TXID 888 entry.Amount = tx.WAmount 889 entry.PaymentID = tx.WPaymentID 890 entry.Status = 0 891 entry.Unlock_Time = tx.TXdata.Unlock_Height 892 893 // fill tx secret_key 894 entry.Secret_TX_Key = w.GetTXKey(tx.TXdata.TXID) 895 entry.Details = w.GetTXOutDetails(tx.TXdata.TXID) 896 entries = append(entries, entry) 897 } 898 } 899 900 sort.SliceStable(entries, func(i, j int) bool { return entries[i].Index_Global > entries[j].Index_Global }) 901 902 return 903 904 } 905 906 // return all payments within a tx there can be more than 1 entry, if yes then they will be merged 907 // NOTE: 908 func (w *Wallet) Get_Payments_TXID(txid []byte) (entry Entry) { 909 index_list := w.load_all_values_from_bucket(BLOCKCHAIN_UNIVERSE, append([]byte(TXID), txid[:]...)) 910 911 for i := range index_list { 912 current_index := binary.BigEndian.Uint64(index_list[i]) 913 914 tx, err := w.load_funds_data(current_index, FUNDS_BUCKET) 915 if err != nil { 916 rlog.Warnf("Error while reading available funds index index %d err %s", current_index, err) 917 continue 918 } 919 if bytes.Compare(txid, tx.TXdata.TXID[:]) == 0 { 920 921 entry.Index_Global = current_index 922 entry.Height = tx.TXdata.Height 923 entry.TopoHeight = tx.TXdata.TopoHeight 924 entry.TXID = tx.TXdata.TXID 925 entry.Amount += tx.WAmount // merge all amounts ( if it was provided in different outputs) 926 entry.PaymentID = tx.WPaymentID 927 entry.Status = 0 928 entry.Unlock_Time = tx.TXdata.Unlock_Height 929 930 // fill tx secret_key 931 entry.Secret_TX_Key = w.GetTXKey(tx.TXdata.TXID) 932 entry.Details = w.GetTXOutDetails(tx.TXdata.TXID) 933 } 934 } 935 936 return 937 938 } 939 940 // get the unlocked balance ( amounts which are mature and can be spent at this time ) 941 // offline wallets may get this wrong, since they may not have latest data 942 // TODO: for offline wallets, we must make all balance as mature 943 // 944 func (w *Wallet) Start_RPC_Server(address string) (err error) { 945 w.Lock() 946 defer w.Unlock() 947 948 tcpAddr, err := net.ResolveTCPAddr("tcp", address) 949 if err != nil { 950 return 951 } 952 953 w.rpcserver, err = RPCServer_Start(w, tcpAddr.String()) 954 if err != nil { 955 w.rpcserver = nil 956 } 957 958 return 959 } 960 961 func (w *Wallet) Stop_RPC_Server() { 962 w.Lock() 963 defer w.Unlock() 964 965 if w.rpcserver != nil { 966 w.rpcserver.RPCServer_Stop() 967 w.rpcserver = nil // remover reference 968 } 969 970 return 971 } 972 973 // delete most of the data and prepare for rescan 974 func (w *Wallet) Clean() { 975 w.Lock() 976 defer w.Get_Balance_Rescan() 977 defer w.Unlock() 978 979 w.delete_bucket(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_BUCKET)) 980 w.delete_bucket(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_AVAILABLE)) 981 w.delete_bucket(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_SPENT)) 982 w.delete_bucket(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_SPENT_WHERE)) 983 w.delete_bucket(BLOCKCHAIN_UNIVERSE, []byte(FUNDS_BUCKET_OUTGOING)) 984 985 w.delete_bucket(BLOCKCHAIN_UNIVERSE, []byte(RING_BUCKET)) //Improves wallet rescan performance. 986 w.delete_bucket(BLOCKCHAIN_UNIVERSE, []byte(KEYIMAGE_BUCKET)) 987 w.delete_bucket(BLOCKCHAIN_UNIVERSE, []byte(HEIGHT_TO_BLOCK_BUCKET)) 988 989 } 990 991 // whether account is view only 992 func (w *Wallet) Is_View_Only() bool { 993 // w.RLock() 994 //defer w.RUnlock() 995 return w.account.ViewOnly 996 } 997 998 // informs thats balance information may be stale and needs recalculation 999 func (w *Wallet) Is_Balance_Modified() bool { 1000 // w.RLock() 1001 // defer w.RUnlock() 1002 return w.account.balance_stale 1003 } 1004 1005 // return height of wallet 1006 func (w *Wallet) Get_Height() uint64 { 1007 w.RLock() 1008 defer w.RUnlock() 1009 return w.account.Height 1010 } 1011 1012 // return topoheight of wallet 1013 func (w *Wallet) Get_TopoHeight() int64 { 1014 w.RLock() 1015 defer w.RUnlock() 1016 return w.account.TopoHeight 1017 } 1018 1019 func (w *Wallet) Get_Daemon_Height() uint64 { 1020 w.Lock() 1021 defer w.Unlock() 1022 1023 return w.Daemon_Height 1024 } 1025 1026 // return index position 1027 func (w *Wallet) Get_Index_Global() uint64 { 1028 w.RLock() 1029 defer w.RUnlock() 1030 return w.account.Index_Global 1031 } 1032 1033 func (w *Wallet) Get_Keys() _Keys { 1034 return w.account.Keys 1035 } 1036 1037 // by default a wallet opens in Offline Mode 1038 // however, if the wallet is in online mode, it can be made offline instantly using this 1039 func (w *Wallet) SetOfflineMode() bool { 1040 w.Lock() 1041 defer w.Unlock() 1042 1043 current_mode := w.wallet_online_mode 1044 w.wallet_online_mode = false 1045 return current_mode 1046 } 1047 1048 // return current mode 1049 func (w *Wallet) GetMode() bool { 1050 w.RLock() 1051 defer w.RUnlock() 1052 1053 return w.wallet_online_mode 1054 } 1055 1056 // use the endpoint set by the program 1057 func (w *Wallet) SetDaemonAddress(endpoint string) string { 1058 w.Lock() 1059 defer w.Unlock() 1060 1061 w.Daemon_Endpoint = endpoint 1062 return w.Daemon_Endpoint 1063 } 1064 1065 // by default a wallet opens in Offline Mode 1066 // however, It can be made online by calling this 1067 func (w *Wallet) SetOnlineMode() bool { 1068 w.Lock() 1069 defer w.Unlock() 1070 1071 current_mode := w.wallet_online_mode 1072 w.wallet_online_mode = true 1073 1074 if current_mode != true { // trigger subroutine if previous mode was offline 1075 go w.sync_loop() // start sync subroutine 1076 } 1077 return current_mode 1078 } 1079 1080 // by default a wallet opens in Offline Mode 1081 // however, It can be made online by calling this 1082 func (w *Wallet) SetMixin(Mixin int) int { 1083 defer w.Save_Wallet() // save wallet 1084 w.Lock() 1085 defer w.Unlock() 1086 1087 if Mixin >= 5 && Mixin < 14 { //reasonable limits for mixin, atleastt for now, network should bump it to 13 on next HF 1088 w.account.Mixin = Mixin 1089 } 1090 return w.account.Mixin 1091 } 1092 1093 // by default a wallet opens in Offline Mode 1094 // however, It can be made online by calling this 1095 func (w *Wallet) GetMixin() int { 1096 w.Lock() 1097 defer w.Unlock() 1098 if w.account.Mixin < 5 { 1099 return 5 1100 } 1101 return w.account.Mixin 1102 } 1103 1104 // sets a fee multiplier 1105 func (w *Wallet) SetFeeMultiplier(x float32) float32 { 1106 defer w.Save_Wallet() // save wallet 1107 w.Lock() 1108 defer w.Unlock() 1109 if x < 1.0 { // fee cannot be less than 1.0, base fees 1110 w.account.FeesMultiplier = 2.0 1111 } else { 1112 w.account.FeesMultiplier = x 1113 } 1114 return w.account.FeesMultiplier 1115 } 1116 1117 // gets current fee multiplier 1118 func (w *Wallet) GetFeeMultiplier() float32 { 1119 w.Lock() 1120 defer w.Unlock() 1121 if w.account.FeesMultiplier < 1.0 { 1122 return 1.0 1123 } 1124 return w.account.FeesMultiplier 1125 } 1126 1127 // get fees multiplied by multiplier 1128 func (w *Wallet) getfees(txfee uint64) uint64 { 1129 multiplier := w.account.FeesMultiplier 1130 if multiplier < 1.0 { 1131 multiplier = 2.0 1132 } 1133 return txfee * uint64(multiplier*100.0) / 100 1134 } 1135 1136 // Ability to change seed lanaguage 1137 func (w *Wallet) SetSeedLanguage(language string) string { 1138 defer w.Save_Wallet() // save wallet 1139 w.Lock() 1140 defer w.Unlock() 1141 1142 language_list := mnemonics.Language_List() 1143 for i := range language_list { 1144 if strings.ToLower(language) == strings.ToLower(language_list[i]) { 1145 w.account.SeedLanguage = language_list[i] 1146 } 1147 } 1148 return w.account.SeedLanguage 1149 } 1150 1151 // retrieve current seed language 1152 func (w *Wallet) GetSeedLanguage() string { 1153 w.Lock() 1154 defer w.Unlock() 1155 if w.account.SeedLanguage == "" { // default is English 1156 return "English" 1157 } 1158 return w.account.SeedLanguage 1159 } 1160 1161 // retrieve secret key for any tx we may have created 1162 func (w *Wallet) GetTXKey(txhash crypto.Hash) string { 1163 1164 key, err := w.load_key_value(BLOCKCHAIN_UNIVERSE, []byte(SECRET_KEY_BUCKET), txhash[:]) 1165 if err != nil { 1166 return "" 1167 } 1168 1169 return fmt.Sprintf("%x", key) 1170 } 1171 1172 // we need better names for functions 1173 func (w *Wallet) GetTXOutDetails(txhash crypto.Hash) (details structures.Outgoing_Transfer_Details) { 1174 1175 data_bytes, err := w.load_key_value(BLOCKCHAIN_UNIVERSE, []byte(TX_OUT_DETAILS_BUCKET), txhash[:]) 1176 if err != nil { 1177 return 1178 } 1179 1180 if len(data_bytes) > 10 { 1181 json.Unmarshal(data_bytes, &details) 1182 } 1183 1184 return 1185 }