derohe-miniblock-mod/walletapi/daemon_communication.go

829 lines
26 KiB
Go

// 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
}