// Copyright 2017-2021 DERO Project. All rights reserved. // Use of this source code in any form is governed by RESEARCH license. // license can be found in the LICENSE file. // GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 // // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package walletapi // this file needs serious improvements but have extremely limited time /* this file handles communication with the daemon * this includes receiving output information * * * */ //import "io" //import "os" import "fmt" import "time" import "sync" import "bytes" import "math/big" //import "bufio" import "strings" import "context" //import "runtime" //import "compress/gzip" import "encoding/hex" import "encoding/binary" import "runtime/debug" import "github.com/romana/rlog" //import "github.com/vmihailenco/msgpack" //import "github.com/gorilla/websocket" //import "github.com/mafredri/cdp/rpcc" import "github.com/deroproject/derohe/block" import "github.com/deroproject/derohe/address" import "github.com/deroproject/derohe/crypto" import "github.com/deroproject/derohe/errormsg" import "github.com/deroproject/derohe/structures" import "github.com/deroproject/derohe/transaction" import "github.com/deroproject/derohe/crypto/bn256" import "github.com/creachadair/jrpc2" // this global variable should be within wallet structure var Connected bool = false // there should be no global variables, so multiple wallets can run at the same time with different assset var endpoint string var output_lock sync.Mutex var NotifyNewBlock *sync.Cond = sync.NewCond(&sync.Mutex{}) var NotifyHeightChange *sync.Cond = sync.NewCond(&sync.Mutex{}) // this function will wait n goroutines to wait for new block func WaitNewBlock() { NotifyNewBlock.L.Lock() NotifyNewBlock.Wait() NotifyNewBlock.L.Unlock() } // this function will wait n goroutines to wait till height changes func WaitNewHeightBlock() { NotifyHeightChange.L.Lock() NotifyHeightChange.Wait() NotifyHeightChange.L.Unlock() } func Notify_broadcaster(req *jrpc2.Request) { timer.Reset(timeout) // connection is alive switch req.Method() { case "Repoll": NotifyNewBlock.L.Lock() NotifyNewBlock.Broadcast() NotifyNewBlock.L.Unlock() case "HRepoll": NotifyHeightChange.L.Lock() NotifyHeightChange.Broadcast() NotifyHeightChange.L.Unlock() default: rlog.Debugf("Notification received %s\n", req.Method()) } } // triggers syncing with wallet every 5 seconds func (w *Wallet_Memory) sync_loop() { w.Sync_Wallet_Memory_With_Daemon() // sync with the daemon } func (cli *Client) Call(method string, params interface{}, result interface{}) error { return cli.RPC.CallResult(context.Background(), method, params, result) } // returns whether wallet was online some time ago func (w *Wallet_Memory) IsDaemonOnlineCached() bool { return Connected } // currently process url with compatibility for older ip address func buildurl(endpoint string) string { if strings.IndexAny(endpoint, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") >= 0 { // url is already complete return strings.TrimSuffix(endpoint, "/") } else { return "http://" + endpoint } } // this is as simple as it gets // single threaded communication to get the daemon status and height // this will tell whether the wallet can connection successfully to daemon or not func IsDaemonOnline() bool { if rpc_client.WS == nil || rpc_client.RPC == nil { return false } return true } // sync the wallet with daemon, this is instantaneous and can be done with a single call // we have now the apis to avoid polling func (w *Wallet_Memory) Sync_Wallet_Memory_With_Daemon() { first_time := true for { select { case <-w.quit: break default: } if (first_time && IsDaemonOnline()) || (!first_time && IsDaemonOnline()) { first_time = false w.random_ring_members() rlog.Debugf("wallet topo height %d daemon online topo height %d\n", w.account.TopoHeight, w.Daemon_TopoHeight) previous := w.account.Balance_Result.Data if _, err := w.GetEncryptedBalance("", w.GetAddress().String()); err == nil { if w.account.Balance_Result.Data != previous || (len(w.account.Entries) >= 1 && strings.ToLower(w.account.Balance_Result.Data) != strings.ToLower(w.account.Entries[len(w.account.Entries)-1].EWData)) { w.DecodeEncryptedBalance() // try to decode balance w.SyncHistory() // also update statement } } else { rlog.Infof("getbalance err %s", err) } } time.Sleep(timeout) // wait 5 seconds } return } // this is as simple as it gets // single threaded communication to relay TX to daemon // if this is successful, then daemon is in control func (w *Wallet_Memory) SendTransaction(tx *transaction.Transaction) (err error) { if tx == nil { return fmt.Errorf("Can not send nil transaction") } if !IsDaemonOnline() { return fmt.Errorf("offline or not connected. cannot send transaction.") } params := structures.SendRawTransaction_Params{Tx_as_hex: hex.EncodeToString(tx.Serialize())} var result structures.SendRawTransaction_Result // Issue a call with a response. if err := rpc_client.Call("DERO.SendRawTransaction", params, &result); err != nil { return err } //fmt.Printf("raw transaction result %+v\n", result) if result.Status == "OK" { return nil } else { err = fmt.Errorf("Err %s", result.Status) } //fmt.Printf("err in response %+v", result) return } // this is as simple as it gets // single threaded communication gets whether the the key image is spent in pool or in blockchain // this can leak informtion which keyimage belongs to us // TODO in order to stop privacy leaks we must guess this information somehow on client side itself // maybe the server can broadcast a bloomfilter or something else from the mempool keyimages // func (w *Wallet_Memory) GetEncryptedBalance(treehash string, accountaddr string) (e *crypto.ElGamal, err error) { defer func() { if r := recover(); r != nil { rlog.Warnf("Stack trace \n%s", debug.Stack()) } }() if !w.GetMode() { // if wallet is in offline mode , we cannot do anything err = fmt.Errorf("wallet is in offline mode") return } if !IsDaemonOnline() { err = fmt.Errorf("offline or not connected") return } //var params structures.GetEncryptedBalance_Params var result structures.GetEncryptedBalance_Result // Issue a call with a response. if err = rpc_client.Call("DERO.GetEncryptedBalance", structures.GetEncryptedBalance_Params{Address: accountaddr, TopoHeight: -1}, &result); err != nil { rlog.Warnf("GetEncryptedBalance err %s", err) if strings.Contains(strings.ToLower(err.Error()), strings.ToLower(errormsg.ErrAccountUnregistered.Error())) && accountaddr == w.GetAddress().String() { w.Error = errormsg.ErrAccountUnregistered } return } //fmt.Printf("result %+v\n", result) w.Daemon_Height = uint64(result.DHeight) w.Daemon_TopoHeight = result.DTopoheight w.Merkle_Balance_TreeHash = result.DMerkle_Balance_TreeHash if accountaddr == w.GetAddress().String() { w.account.Balance_Result = result w.account.TopoHeight = result.Topoheight } //fmt.Printf("status '%s' err '%s' %+v %+v \n", result.Status , w.Error , result.Status == errormsg.ErrAccountUnregistered.Error() , accountaddr == w.account.GetAddress().String()) if result.Status != "OK" { err = fmt.Errorf("%s", result.Status) return } hexdecoded, err := hex.DecodeString(result.Data) if err != nil { return } if accountaddr == w.GetAddress().String() { w.Error = nil } el := new(crypto.ElGamal).Deserialize(hexdecoded) return el, nil } func (w *Wallet_Memory) DecodeEncryptedBalance() (err error) { var el crypto.ElGamal var balance_point bn256.G1 hexdecoded, err := hex.DecodeString(w.account.Balance_Result.Data) if err != nil { return } el = *el.Deserialize(hexdecoded) if err != nil { panic(err) return } balance_point.Add(el.Left, new(bn256.G1).Neg(new(bn256.G1).ScalarMult(el.Right, w.account.Keys.Secret.BigInt()))) w.account.Balance_Mature = Balance_lookup_table.Lookup(&balance_point, w.account.Balance_Mature) return nil } // this is as simple as it gets // single threaded communication gets whether the the key image is spent in pool or in blockchain // this can leak informtion which keyimage belongs to us // TODO in order to stop privacy leaks we must guess this information somehow on client side itself // maybe the server can broadcast a bloomfilter or something else from the mempool keyimages // func (w *Wallet_Memory) GetEncryptedBalanceAtTopoHeight(topoheight int64, accountaddr string) (e *crypto.ElGamal, err error) { defer func() { if r := recover(); r != nil { rlog.Warnf("Stack trace \n%s", debug.Stack()) } }() if !w.GetMode() { // if wallet is in offline mode , we cannot do anything err = fmt.Errorf("wallet is in offline mode") return } if !IsDaemonOnline() { err = fmt.Errorf("offline or not connected") return } //var params structures.GetEncryptedBalance_Params var result structures.GetEncryptedBalance_Result // Issue a call with a response. if err = rpc_client.Call("DERO.GetEncryptedBalance", structures.GetEncryptedBalance_Params{Address: accountaddr, TopoHeight: topoheight}, &result); err != nil { rlog.Errorf("Call failed: %v", err) return } // fmt.Printf("encrypted_balance %+v\n", result) /* response, err := rpcClient.CallNamed("getencryptedbalance", map[string]interface{}{"address": accountaddr, "treehash":treehash,}) if err != nil { rlog.Errorf("getencryptedbalance call Failed err %s", err) return } // parse response if response.Error != nil { rlog.Errorf("getencryptedbalance Failed err %s", response.Error) return } err = response.GetObject(&result) if err != nil { return // err } */ if result.Status == errormsg.ErrAccountUnregistered.Error() && accountaddr == w.GetAddress().String() { w.Error = errormsg.ErrAccountUnregistered } // fmt.Printf("status '%s' err '%s' %+v %+v \n", result.Status , w.Error , result.Status == errormsg.ErrAccountUnregistered.Error() , accountaddr == w.account.GetAddress().String()) if result.Status == errormsg.ErrAccountUnregistered.Error() { err = fmt.Errorf("%s", result.Status) return } if result.Status != "OK" { err = fmt.Errorf("%s", result.Status) return } hexdecoded, err := hex.DecodeString(result.Data) if err != nil { return } if accountaddr == w.GetAddress().String() { w.Error = nil } el := new(crypto.ElGamal).Deserialize(hexdecoded) return el, nil } func (w *Wallet_Memory) DecodeEncryptedBalance_Memory(el *crypto.ElGamal, hint uint64) (balance uint64) { var balance_point bn256.G1 balance_point.Add(el.Left, new(bn256.G1).Neg(new(bn256.G1).ScalarMult(el.Right, w.account.Keys.Secret.BigInt()))) return Balance_lookup_table.Lookup(&balance_point, hint) } func (w *Wallet_Memory) GetDecryptedBalanceAtTopoHeight(topoheight int64, accountaddr string) (balance uint64, err error) { encrypted_balance, err := w.GetEncryptedBalanceAtTopoHeight(topoheight, accountaddr) if err != nil { return 0, err } return w.DecodeEncryptedBalance_Memory(encrypted_balance, 0), nil } // sync history of wallet from blockchain func (w *Wallet_Memory) random_ring_members() { //fmt.Printf("getting random_ring_members\n") if len(w.account.RingMembers) > 300 { // unregistered so skip return } var result structures.GetRandomAddress_Result // Issue a call with a response. if err := rpc_client.Call("DERO.GetRandomAddress", nil, &result); err != nil { rlog.Errorf("GetRandomAddress Call failed: %v", err) return } //fmt.Printf("ring members %+v\n", result) // we have found a matching block hash, start syncing from here if w.account.RingMembers == nil { w.account.RingMembers = map[string]int64{} } for _, k := range result.Address { if k != w.GetAddress().String() { w.account.RingMembers[k] = 1 } } return } // sync history of wallet from blockchain func (w *Wallet_Memory) SyncHistory() (balance uint64) { if w.account.Balance_Result.Registration < 0 { // unregistered so skip return } last_topo_height := int64(-1) //fmt.Printf("finding sync point ( Registration point %d)\n", w.account.Balance_Result.Registration) // we need to find a sync point, to minimize traffic for i := len(w.account.Entries) - 1; i >= 0; { // below condition will trigger if chain got pruned on server if w.account.Balance_Result.Registration >= w.account.Entries[i].TopoHeight { // keep old history if chain got pruned break } if last_topo_height == w.account.Entries[i].TopoHeight { i-- } else { last_topo_height = w.account.Entries[i].TopoHeight var result structures.GetBlockHeaderByHeight_Result // Issue a call with a response. if err := rpc_client.Call("DERO.GetBlockHeaderByTopoHeight", structures.GetBlockHeaderByTopoHeight_Params{TopoHeight: uint64(w.account.Entries[i].TopoHeight)}, &result); err != nil { rlog.Errorf("GetBlockHeaderByTopoHeight Call failed: %v", err) return 0 } if i >= 1 && last_topo_height == w.account.Entries[i-1].TopoHeight { // skipping any entries withing same block for ; i >= 1; i-- { if last_topo_height != w.account.Entries[i-1].TopoHeight { w.account.Entries = w.account.Entries[:i] } } } if i == 0 { w.account.Entries = w.account.Entries[:0] // discard all entries break } // we have found a matching block hash, start syncing from here if result.Status == "OK" && result.Block_Header.Hash == w.account.Entries[i].BlockHash { w.synchistory_internal(w.account.Entries[i].TopoHeight+1, w.account.Balance_Result.Topoheight) return } } } //fmt.Printf("syncing loop using Registration %d\n", w.account.Balance_Result.Registration) // if we reached here, means we should sync from scratch w.synchistory_internal(w.account.Balance_Result.Registration, w.account.Balance_Result.Topoheight) //if w.account.Registration >= 0 { // err := // err = w.synchistory_internal(w.account.Registration,6) // } // fmt.Printf("syncing err %s\n",err) // fmt.Printf("entries %+v\n", w.account.Entries) return 0 } // sync history func (w *Wallet_Memory) synchistory_internal(start_topo, end_topo int64) error { var err error var start_balance_e *crypto.ElGamal if start_topo == w.account.Balance_Result.Registration { start_balance_e = crypto.ConstructElGamal(w.account.Keys.Public.G1(), crypto.ElGamal_BASE_G) } else { start_balance_e, err = w.GetEncryptedBalanceAtTopoHeight(start_topo, w.GetAddress().String()) if err != nil { return err } } end_balance_e, err := w.GetEncryptedBalanceAtTopoHeight(end_topo, w.GetAddress().String()) if err != nil { return err } return w.synchistory_internal_binary_search(start_topo, start_balance_e, end_topo, end_balance_e) } func (w *Wallet_Memory) synchistory_internal_binary_search(start_topo int64, start_balance_e *crypto.ElGamal, end_topo int64, end_balance_e *crypto.ElGamal) error { //fmt.Printf("end %d start %d\n", end_topo, start_topo) if end_topo < 0 { return fmt.Errorf("done") } /* if bytes.Compare(start_balance_e.Serialize(), end_balance_e.Serialize()) == 0 { return nil } */ //for start_topo <= end_topo{ { median := (start_topo + end_topo) / 2 //fmt.Printf("low %d high %d median %d\n", start_topo,end_topo,median) if start_topo == median { //fmt.Printf("syncing block %d\n", start_topo) err := w.synchistory_block(start_topo) if err != nil { return err } } if end_topo-start_topo <= 1 { return w.synchistory_block(end_topo) } median_balance_e, err := w.GetEncryptedBalanceAtTopoHeight(median, w.GetAddress().String()) if err != nil { return err } // check if there is a change in lower section, if yes process more if start_topo == w.account.Balance_Result.Registration || bytes.Compare(start_balance_e.Serialize(), median_balance_e.Serialize()) != 0 { //fmt.Printf("lower\n") err = w.synchistory_internal_binary_search(start_topo, start_balance_e, median, median_balance_e) if err != nil { return err } } // check if there is a change in higher section, if yes process more if bytes.Compare(median_balance_e.Serialize(), end_balance_e.Serialize()) != 0 { //fmt.Printf("higher\n") err = w.synchistory_internal_binary_search(median, median_balance_e, end_topo, end_balance_e) if err != nil { return err } } /*if IsRegisteredAtTopoHeight (addr,median) { high = median - 1 }else{ low = median + 1 }*/ } /* if end_topo - start_topo <= 1 { err := w.synchistory_block(start_topo) if err != nil { return err } return w.synchistory_block(end_topo) } // this means the address is either a ring member or a sender or a receiver in atleast one of the blocks middle := start_topo + (end_topo-start_topo)/2 middle_balance_e, err := w.GetEncryptedBalanceAtTopoHeight( middle ,w.account.GetAddress().String()) if err != nil { return err } // check if there is a change in lower section, if yes process more if bytes.Compare(start_balance_e.Serialize(), middle_balance_e.Serialize()) != 0 { fmt.Printf("lower\n") err = w.synchistory_internal_binary_search(start_topo,start_balance_e, middle, middle_balance_e ) if err != nil { return err } } // check if there is a change in lower section, if yes process more if bytes.Compare(middle_balance_e.Serialize(), end_balance_e.Serialize()) != 0 { fmt.Printf("higher\n") err = w.synchistory_internal_binary_search(middle, middle_balance_e, end_topo,end_balance_e ) if err != nil { return err } } */ return nil } // extract history from a single block // first get a block, then get all the txs // Todo we should expose an API to get all txs which have the specific address as ring member // for a particular block // for the entire chain func (w *Wallet_Memory) synchistory_block(topo int64) (err error) { var local_entries []Entry compressed_address := w.account.Keys.Public.EncodeCompressed() var previous_balance_e, current_balance_e *crypto.ElGamal var previous_balance, current_balance, total_sent, total_received uint64 if topo <= 0 || w.account.Balance_Result.Registration == topo { previous_balance_e = crypto.ConstructElGamal(w.account.Keys.Public.G1(), crypto.ElGamal_BASE_G) } else { previous_balance_e, err = w.GetEncryptedBalanceAtTopoHeight(topo-1, w.GetAddress().String()) if err != nil { return err } } current_balance_e, err = w.GetEncryptedBalanceAtTopoHeight(topo, w.GetAddress().String()) if err != nil { return err } EWData := fmt.Sprintf("%x", current_balance_e.Serialize()) previous_balance = w.DecodeEncryptedBalance_Memory(previous_balance_e, 0) current_balance = w.DecodeEncryptedBalance_Memory(current_balance_e, 0) // we can skip some check if both balances are equal ( means we are ring members in this block) // this check will also fail if we total spend == total receivein the block // currently it is not implmented, and we bruteforce everything _ = current_balance var bl block.Block var bresult structures.GetBlock_Result if err = rpc_client.Call("DERO.GetBlock", structures.GetBlock_Params{Height: uint64(topo)}, &bresult); err != nil { return fmt.Errorf("getblock rpc failed") } block_bin, _ := hex.DecodeString(bresult.Blob) bl.Deserialize(block_bin) if len(bl.Tx_hashes) >= 1 { //fmt.Printf("Requesting tx data %s", txhash); for i := range bl.Tx_hashes { var tx transaction.Transaction var tx_params structures.GetTransaction_Params var tx_result structures.GetTransaction_Result tx_params.Tx_Hashes = append(tx_params.Tx_Hashes, bl.Tx_hashes[i].String()) if err = rpc_client.Call("DERO.GetTransaction", tx_params, &tx_result); err != nil { return fmt.Errorf("gettransa rpc failed %s", err) } tx_bin, err := hex.DecodeString(tx_result.Txs_as_hex[0]) if err != nil { return err } tx.DeserializeHeader(tx_bin) for j := range tx.Statement.Publickeylist_compressed { // check whether statement has public key // check whether our address is a ring member if yes, process it as ours if bytes.Compare(compressed_address, tx.Statement.Publickeylist_compressed[j][:]) == 0 { // this tx contains us either as a ring member, or sender or receiver, so add all members as ring members for future // keep collecting ring members to make things exponentially complex for k := range tx.Statement.Publickeylist_compressed { if j != k { ringmember := address.NewAddressFromKeys((*crypto.Point)(tx.Statement.Publickeylist[k])) ringmember.Mainnet = w.GetNetwork() w.account.RingMembers[ringmember.String()] = 1 } } changes := crypto.ConstructElGamal(tx.Statement.C[j], tx.Statement.D) changed_balance_e := previous_balance_e.Add(changes) changed_balance := w.DecodeEncryptedBalance_Memory(changed_balance_e, previous_balance) entry := Entry{Height: bl.Height, TopoHeight: topo, BlockHash: bl.GetHash().String(), TransactionPos: i, TXID: tx.GetHash(), Time: time.Unix(int64(bl.Timestamp), 0)} entry.EWData = EWData ring_member := false switch { case previous_balance == changed_balance: //ring member/* handle 0 value tx but fees is deducted */ //fmt.Printf("Anon Ring Member in TX %s\n", bl.Tx_hashes[i].String()) ring_member = true case previous_balance > changed_balance: // we generated this tx entry.Amount = previous_balance - changed_balance - tx.Statement.Fees entry.Fees = tx.Statement.Fees entry.Status = 1 // mark it as spend total_sent += (previous_balance - changed_balance) + tx.Statement.Fees rinputs := append([]byte{}, tx.Statement.Roothash[:]...) for l := range tx.Statement.Publickeylist_compressed { rinputs = append(rinputs, tx.Statement.Publickeylist_compressed[l][:]...) } rencrypted := new(bn256.G1).ScalarMult(crypto.HashToPoint(crypto.HashtoNumber(append([]byte(crypto.PROTOCOL_CONSTANT), rinputs...))), w.account.Keys.Secret.BigInt()) r := crypto.ReducedHash(rencrypted.EncodeCompressed()) // fmt.Printf("r calculated %s\n", r.Text(16)) // lets separate ring members for k := range tx.Statement.C { // skip self address, this can be optimized way more if tx.Statement.Publickeylist[k].String() != w.account.Keys.Public.G1().String() { var x bn256.G1 x.ScalarMult(crypto.G, new(big.Int).SetInt64(int64(entry.Amount))) x.Add(new(bn256.G1).Set(&x), new(bn256.G1).ScalarMult(tx.Statement.Publickeylist[k], r)) if x.String() == tx.Statement.C[k].String() { // lets encrypt the payment id, it's simple, we XOR the paymentID blinder := new(bn256.G1).ScalarMult(tx.Statement.Publickeylist[k], r) // proof is blinder + amount transferred, it will recover the encrypted payment id also proof := address.NewAddressFromKeys((*crypto.Point)(blinder)) proof.PaymentID = make([]byte, 8, 8) proof.Proof = true binary.LittleEndian.PutUint64(proof.PaymentID, entry.Amount) entry.Proof = proof.String() entry.PaymentID = crypto.EncryptDecryptPaymentID(blinder, tx.PaymentID[:]) //paymentID := binary.BigEndian.Uint64(payment_id_encrypted_bytes[:]) // get decrypted payment id addr := address.NewAddressFromKeys((*crypto.Point)(tx.Statement.Publickeylist[k])) addr.Mainnet = w.GetNetwork() //fmt.Printf("%d Sent funds to %s paymentid %x \n", tx.Height, addr.String(), entry.PaymentID) entry.Details.TXID = fmt.Sprintf("%x", entry.TXID) entry.Details.PaymentID = fmt.Sprintf("%x", entry.PaymentID) entry.Details.Fees = tx.Statement.Fees entry.Details.Amount = append(entry.Details.Amount, entry.Amount) entry.Details.Daddress = append(entry.Details.Daddress, addr.String()) break } } } case previous_balance < changed_balance: // someone sentus this amount entry.Amount = changed_balance - previous_balance entry.Incoming = true // we should decode the payment id var x bn256.G1 x.ScalarMult(crypto.G, new(big.Int).SetInt64(0-int64(entry.Amount))) // increase receiver's balance x.Add(new(bn256.G1).Set(&x), tx.Statement.C[j]) // get the blinder entry.PaymentID = crypto.EncryptDecryptPaymentID(&x, tx.PaymentID[:]) //fmt.Printf("Received %s amount in TX(%d) %s payment id %x\n", globals.FormatMoney(changed_balance-previous_balance), tx.Height, bl.Tx_hashes[i].String(), entry.PaymentID) total_received += (changed_balance - previous_balance) } if !ring_member { // do not book keep ring members local_entries = append(local_entries, entry) } //break // this tx has been processed so skip it } } } //fmt.Printf("block %d %+v\n", topo, tx_result) } if bytes.Compare(compressed_address, bl.Miner_TX.MinerAddress[:]) == 0 { // wallet user has minted a block entry := Entry{Height: bl.Height, TopoHeight: topo, BlockHash: bl.GetHash().String(), TransactionPos: -1, Time: time.Unix(int64(bl.Timestamp), 0)} entry.EWData = EWData entry.Amount = current_balance - (previous_balance - total_sent + total_received) entry.Coinbase = true local_entries = append([]Entry{entry}, local_entries...) //fmt.Printf("Coinbase Reward %s for block %d\n", globals.FormatMoney(current_balance-(previous_balance-total_sent+total_received)), topo) } for _, e := range local_entries { w.InsertReplace(e) } if len(local_entries) >= 1 { w.Save_Wallet() // w.db.Sync() } return nil }