github.com/deroproject/derosuite@v2.1.6-1.0.20200307070847-0f2e589c7a2b+incompatible/walletapi/tx_creation_test.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 "os"
    20  import "fmt"
    21  import "testing"
    22  import "bytes"
    23  import "crypto/rand"
    24  import "path/filepath"
    25  import "encoding/hex"
    26  import "encoding/binary"
    27  import "runtime/pprof"
    28  
    29  import "github.com/deroproject/derosuite/globals"
    30  import "github.com/deroproject/derosuite/crypto"
    31  import "github.com/deroproject/derosuite/crypto/ringct"
    32  import "github.com/deroproject/derosuite/transaction"
    33  
    34  func init() {
    35  	globals.Init_rlog()
    36  }
    37  
    38  // this will test that the keys are placed properly and thus can be decoded by recievers
    39  func Test_Creation_TX(t *testing.T) {
    40  
    41  	temp_db := filepath.Join(os.TempDir(), "dero_temporary_test_wallet.db")
    42  
    43  	os.Remove(temp_db)
    44  
    45  	w, err := Create_Encrypted_Wallet(temp_db, "QWER", *crypto.RandomScalar())
    46  	if err != nil {
    47  		t.Fatalf("Cannot create encrypted wallet, err %s", err)
    48  	}
    49  
    50  	defer os.Remove(temp_db) // cleanup after test
    51  	//sender,_ := Generate_Keys_From_Random() // we have a single sender
    52  
    53  	var receivers []*Account
    54  
    55  	randomBytes := make([]byte, 4)
    56  
    57  	for loop := 0; loop < 10; loop++ { // run the test randomly 200 times
    58  		rand.Read(randomBytes)
    59  		random_inputs := 1 + (binary.LittleEndian.Uint32(randomBytes) % 20) //minimum 1 input is necessary
    60  		rand.Read(randomBytes)
    61  		random_outputs := 1 + (binary.LittleEndian.Uint32(randomBytes) % 20) //minimum 1 output is necessary
    62  
    63  		if loop == 0 {
    64  			random_inputs = 100
    65  		}
    66  
    67  		if loop == 1 {
    68  			random_outputs = 200 // randomly place 200 outputs in single tx
    69  		}
    70  
    71  		if loop == 2 {
    72  			random_inputs = 500
    73  			random_outputs = 2
    74  		}
    75  
    76  		txw := TX_Wallet_Data{WAmount: 4000000000000}
    77  		txw.TXdata.Index_Global = 739
    78  		txw.TXdata.Height = 730
    79  		txw.WKey.Destination = crypto.HexToKey("dbdfd2a3e9da6911b0a3e37e8e448f2de2477f81760585c2f197736bac127e0f")
    80  		txw.WKey.Mask = crypto.HexToKey("01e4e85ab0b5e30dd86b5356f0f6b4177738b9e6b32041c4e4781a2f26083101")
    81  		txw.WKimage = crypto.HexToKey("d8fb3b4260aea6582400a5f48244ff3f7c4dc36420698e3decc5d28ba04733c2")
    82  		txw.TXdata.InKey.Destination = crypto.HexToKey("ed0da9e74d240088a07909ea354b8d140b753642e25495e0931b4623b25ff523")
    83  		txw.TXdata.InKey.Mask = crypto.HexToKey("dbddab6c6b3063074e7cfd1a7f83f184ad78e92c8ff25118c0ed4edc77015948")
    84  
    85  		var outs []ringct.Output_info
    86  		for i := uint32(0); i < random_outputs; i++ {
    87  			r, _ := Generate_Keys_From_Random()
    88  			receivers = append(receivers, r)
    89  
    90  			out := ringct.Output_info{Public_Spend_Key: receivers[i].GetAddress().SpendKey,
    91  				Public_View_Key: receivers[i].GetAddress().ViewKey}
    92  
    93  			if i == 0 { // balance the outputs
    94  				out.Amount = uint64(random_inputs) * txw.WAmount
    95  			}
    96  			outs = append(outs, out) // fill outs with random address
    97  		}
    98  
    99  		var ins []ringct.Input_info
   100  		for i := uint32(0); i < random_inputs; i++ {
   101  
   102  			ins = append(ins, ringct.Input_info{Amount: txw.WAmount, Key_image: crypto.Hash(txw.WKimage), Sk: txw.WKey, Index_Global: txw.TXdata.Index_Global})
   103  
   104  			//now we must the ring members
   105  			ins[i].Ring_Members = append(ins[i].Ring_Members, txw.TXdata.Index_Global)
   106  			ins[i].Pubs = append(ins[i].Pubs, txw.TXdata.InKey)
   107  
   108  			// lets add 5 ring members randomly
   109  			for j := uint64(0); j < 5; j++ {
   110  				ins[i].Ring_Members = append(ins[i].Ring_Members, txw.TXdata.Index_Global+j+1)
   111  				ins[i].Pubs = append(ins[i].Pubs, ringct.CtKey{Destination: *crypto.RandomScalar(), Mask: *crypto.RandomScalar()})
   112  			}
   113  		}
   114  
   115  		// 739th main net consumed output
   116  		var payment_id []byte
   117  
   118  		if loop%2 == 0 {
   119  			payment_id = make([]byte, 32, 32) // test 32 byte payment id
   120  		} else {
   121  			payment_id = make([]byte, 8, 8) // make encrypted payment ID
   122  		}
   123  
   124  		if loop%3 == 0 {
   125  			payment_id = make([]byte, 0, 0) // test with out payment id
   126  		}
   127  
   128  		bulletproof := false
   129  		if loop%2 == 1 {
   130  			bulletproof = true
   131  		}
   132  
   133  		tx := w.Create_TX_v2(ins, outs, 0, 0, payment_id, bulletproof)
   134  
   135  		if !tx.RctSignature.Verify() {
   136  			t.Fatalf("TX ring signature verification failed")
   137  		}
   138  
   139  		// now check whether the outputs can be verified successfuly after serdes
   140  		var tx2 transaction.Transaction
   141  		tx2.DeserializeHeader(tx.Serialize())
   142  		tx2.Parse_Extra()
   143  
   144  		public_key := tx2.Extra_map[transaction.TX_PUBLIC_KEY].(crypto.Key)
   145  
   146  		if len(payment_id) == 8 { // test whether payment ID was encrypted and decrypted successfully
   147  			epayid := tx.PaymentID_map[transaction.TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID].([]byte)
   148  
   149  			derivation := crypto.KeyDerivation(&public_key, &receivers[0].Keys.Viewkey_Secret)
   150  			payid := EncryptDecryptPaymentID(derivation, public_key, epayid)
   151  
   152  			t.Logf("epay id %x  decrypted %x", epayid, payid)
   153  
   154  			if !bytes.Equal(payment_id, payid) {
   155  				t.Fatalf("8 byte encrypted payment ID missing failed")
   156  			}
   157  
   158  		}
   159  
   160  		if len(payment_id) == 32 { // test full 32 byte payment id
   161  			if !bytes.Equal(payment_id, tx.PaymentID_map[transaction.TX_EXTRA_NONCE_PAYMENT_ID].([]byte)) {
   162  				t.Fatalf("32 byte payment ID missing, failed")
   163  			}
   164  		}
   165  		for output_index := range outs {
   166  
   167  			tx_out_to_key := tx2.Vout[output_index].Target.(transaction.Txout_to_key)
   168  			if !receivers[output_index].Is_Output_Ours(public_key, uint64(output_index), tx_out_to_key.Key) {
   169  				t.Fatalf("Output mismatch index %d", output_index)
   170  			}
   171  
   172  		}
   173  		t.Logf("inputs %5d\toutputs %5d\t tx size %4d KB %+v", random_inputs, random_outputs, len(tx.Serialize())/1024, bulletproof)
   174  	}
   175  
   176  }
   177  
   178  // this will test that the keys are placed properly and thus can be decoded by recievers
   179  // this also forces the ring size from 2 to 10
   180  func Test_Creation_TX_Size(t *testing.T) {
   181  
   182  	temp_db := filepath.Join(os.TempDir(), "dero_temporary_test_wallet.db")
   183  
   184  	os.Remove(temp_db)
   185  
   186  	w, err := Create_Encrypted_Wallet(temp_db, "QWER", *crypto.RandomScalar())
   187  	if err != nil {
   188  		t.Fatalf("Cannot create encrypted wallet, err %s", err)
   189  	}
   190  
   191  	defer os.Remove(temp_db) // cleanup after test
   192  	//sender,_ := Generate_Keys_From_Random() // we have a single sender
   193  
   194  	var receivers []*Account
   195  
   196  	randomBytes := make([]byte, 4)
   197  
   198  	for loop := 0; loop < 1; loop++ { // run the test randomly 200 times
   199  		rand.Read(randomBytes)
   200  		random_inputs := 1 + (binary.LittleEndian.Uint32(randomBytes) % 20) //minimum 1 input is necessary
   201  		rand.Read(randomBytes)
   202  		random_outputs := 1 + (binary.LittleEndian.Uint32(randomBytes) % 20) //minimum 1 output is necessary
   203  
   204  		random_inputs = 20 * uint32(loop+1)
   205  		random_outputs = 2
   206  
   207  		txw := TX_Wallet_Data{WAmount: 4000000000000}
   208  		txw.TXdata.Index_Global = 739
   209  		txw.TXdata.Height = 730
   210  		txw.WKey.Destination = crypto.HexToKey("dbdfd2a3e9da6911b0a3e37e8e448f2de2477f81760585c2f197736bac127e0f")
   211  		txw.WKey.Mask = crypto.HexToKey("01e4e85ab0b5e30dd86b5356f0f6b4177738b9e6b32041c4e4781a2f26083101")
   212  		txw.WKimage = crypto.HexToKey("d8fb3b4260aea6582400a5f48244ff3f7c4dc36420698e3decc5d28ba04733c2")
   213  		txw.TXdata.InKey.Destination = crypto.HexToKey("ed0da9e74d240088a07909ea354b8d140b753642e25495e0931b4623b25ff523")
   214  		txw.TXdata.InKey.Mask = crypto.HexToKey("dbddab6c6b3063074e7cfd1a7f83f184ad78e92c8ff25118c0ed4edc77015948")
   215  
   216  		var outs []ringct.Output_info
   217  		for i := uint32(0); i < random_outputs; i++ {
   218  			r, _ := Generate_Keys_From_Random()
   219  			receivers = append(receivers, r)
   220  
   221  			out := ringct.Output_info{Public_Spend_Key: receivers[i].GetAddress().SpendKey,
   222  				Public_View_Key: receivers[i].GetAddress().ViewKey}
   223  
   224  			if i == 0 { // balance the outputs
   225  				out.Amount = uint64(random_inputs) * txw.WAmount
   226  			}
   227  			outs = append(outs, out) // fill outs with random address
   228  		}
   229  
   230  		ring_sizes := []uint64{1, 2, 3, 4, 5, 6, 7, 8, 10}
   231  
   232  		for x := range ring_sizes {
   233  
   234  			var ins []ringct.Input_info
   235  			for i := uint32(0); i < random_inputs; i++ {
   236  
   237  				ins = append(ins, ringct.Input_info{Amount: txw.WAmount, Key_image: crypto.Hash(txw.WKimage), Sk: txw.WKey, Index_Global: txw.TXdata.Index_Global})
   238  
   239  				//now we must the ring members
   240  				ins[i].Ring_Members = append(ins[i].Ring_Members, txw.TXdata.Index_Global)
   241  				ins[i].Pubs = append(ins[i].Pubs, txw.TXdata.InKey)
   242  
   243  				// lets add 5 ring members randomly
   244  				for j := uint64(0); j < ring_sizes[x]; j++ {
   245  					ins[i].Ring_Members = append(ins[i].Ring_Members, txw.TXdata.Index_Global+j+1)
   246  					ins[i].Pubs = append(ins[i].Pubs, ringct.CtKey{Destination: *crypto.RandomScalar(), Mask: *crypto.RandomScalar()})
   247  				}
   248  			}
   249  
   250  			// 739th main net consumed output
   251  			var payment_id []byte
   252  
   253  			if loop%2 == 0 {
   254  				payment_id = make([]byte, 32, 32) // test 32 byte payment id
   255  			} else {
   256  				payment_id = make([]byte, 8, 8) // make encrypted payment ID
   257  			}
   258  
   259  			if loop%3 == 0 {
   260  				payment_id = make([]byte, 0, 0) // test with out payment id
   261  			}
   262  
   263  			bulletproof := true
   264  
   265  			tx := w.Create_TX_v2(ins, outs, 0, 0, payment_id, bulletproof)
   266  
   267  			if !tx.RctSignature.Verify() {
   268  				t.Fatalf("TX ring signature verification failed")
   269  			}
   270  
   271  			// now check whether the outputs can be verified successfuly after serdes
   272  			var tx2 transaction.Transaction
   273  			tx2.DeserializeHeader(tx.Serialize())
   274  			tx2.Parse_Extra()
   275  
   276  			public_key := tx2.Extra_map[transaction.TX_PUBLIC_KEY].(crypto.Key)
   277  
   278  			if len(payment_id) == 8 { // test whether payment ID was encrypted and decrypted successfully
   279  				epayid := tx.PaymentID_map[transaction.TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID].([]byte)
   280  
   281  				derivation := crypto.KeyDerivation(&public_key, &receivers[0].Keys.Viewkey_Secret)
   282  				payid := EncryptDecryptPaymentID(derivation, public_key, epayid)
   283  
   284  				//t.Logf("epay id %x  decrypted %x", epayid, payid)
   285  
   286  				if !bytes.Equal(payment_id, payid) {
   287  					t.Fatalf("8 byte encrypted payment ID missing failed")
   288  				}
   289  
   290  			}
   291  
   292  			if len(payment_id) == 32 { // test full 32 byte payment id
   293  				if !bytes.Equal(payment_id, tx.PaymentID_map[transaction.TX_EXTRA_NONCE_PAYMENT_ID].([]byte)) {
   294  					t.Fatalf("32 byte payment ID missing, failed")
   295  				}
   296  			}
   297  			for output_index := range outs {
   298  
   299  				tx_out_to_key := tx2.Vout[output_index].Target.(transaction.Txout_to_key)
   300  				if !receivers[output_index].Is_Output_Ours(public_key, uint64(output_index), tx_out_to_key.Key) {
   301  					t.Fatalf("Output mismatch index %d", output_index)
   302  				}
   303  
   304  			}
   305  			t.Logf("inputs %5d\toutputs %5d\t  ring size %5d tx size %4d KB %+v", random_inputs, random_outputs, ring_sizes[x], len(tx.Serialize())/1024, bulletproof)
   306  		}
   307  	}
   308  
   309  }
   310  
   311  /* Related logs here for improvig the test here
   312   *
   313  [wallet dERoiV]: integrated_address
   314  Random payment ID: <0cbd6e050cf3b73c>
   315  Matching integrated address: dERijfr9y7XhWkdEPp17RJLXVoHkr2ucMdEbgGgpskhLb33732LBifWMCZhPga3EcjXoYqfM9jRv3W3bnWUSpdmKL24FBjG6ctTAEg1jrhDHh
   316  [wallet dERoiV]: address
   317  dERoiVavtPjhWkdEPp17RJLXVoHkr2ucMdEbgGgpskhLb33732LBifWMCZhPga3EcjXoYqfM9jRv3W3bnWUSpdmK5Jur1PhN6P
   318  [wallet dERoiV]: help
   319  
   320  [wallet dERoiV]: transfer dERijfr9y7XhWkdEPp17RJLXVoHkr2ucMdEbgGgpskhLb33732LBifWMCZhPga3EcjXoYqfM9jRv3W3bnWUSpdmKL24FBjG6ctTAEg1jrhDHh 1
   321  Wallet password:
   322  Sending 1.000000000000.  The transaction fee is 0.049197120000
   323  Is this okay?  (Y/Yes/N/No): y
   324  Transaction successfully submitted, transaction <ea551b02b9f1e8aebe4d7b1b7f6bf173d76ae614cb9a066800773fee9e226fd7>
   325  You can check its status by using the `show_transfers` command.
   326  [wallet dERoiV]: get_tx_key ea551b02b9f1e8aebe4d7b1b7f6bf173d76ae614cb9a066800773fee9e226fd7
   327  unknown command: ge_tx_key ea551b02b9f1e8aebe4d7b1b7f6bf173d76ae614cb9a066800773fee9e226fd7
   328  [wallet dERoiV]: get_tx_key ea551b02b9f1e8aebe4d7b1b7f6bf173d76ae614cb9a066800773fee9e226fd7
   329  Wallet password:
   330  Tx key: af7b5a4e75410d585e7faeb254811ea1274f6d20205205213c79655fe3958c07
   331  Height 2175, transaction <31b019dba022b29f8342f71c4ede91fea8b4236469786ae55f131af3432d3989>, received 31.661910734197
   332  Height 2175, transaction <ea551b02b9f1e8aebe4d7b1b7f6bf173d76ae614cb9a066800773fee9e226fd7>, received 7.862704628648
   333  *  Height 2175, transaction <ea551b02b9f1e8aebe4d7b1b7f6bf173d76ae614cb9a066800773fee9e226fd7>, received 1.000000000000
   334  Height 2175, transaction <ea551b02b9f1e8aebe4d7b1b7f6bf173d76ae614cb9a066800773fee9e226fd7>, spent 8.903560733970
   335  Height 2175, transaction <ea551b02b9f1e8aebe4d7b1b7f6bf173d76ae614cb9a066800773fee9e226fd7>, spent 0.008341014678
   336  
   337  */
   338  // this will test that the keys are placed properly and thus can be decoded by recievers
   339  func Test_Creation_TX_Encrypted_PaymentID_check(t *testing.T) {
   340  	temp_db := filepath.Join(os.TempDir(), "dero_temporary_test_wallet.db")
   341  
   342  	os.Remove(temp_db)
   343  
   344  	w, err := Create_Encrypted_Wallet(temp_db, "QWER", crypto.HexToKey("aebcbe93d8c386f954bbc2122d9d6423b7e5b77a6ad31c6a43855e15b9760c0d"))
   345  	if err != nil {
   346  		t.Fatalf("Cannot create encrypted wallet, err %s", err)
   347  	}
   348  
   349  	defer os.Remove(temp_db) // cleanup after test
   350  
   351  	tx_created_by_oldwallet_hex := "020002020005e1e401c90cc556b33ac30ec7b09288b613edc262773a1b426e244288bd7083703f64f9ba70932c005aab8a020005a441a597019b06db9001c90fb8b71bdf1694dc432a501201d9072f03b57e8ffc4b438525874af2b19fbefd7402000251ae0c92c8ef002020afe3ba8bbc7893a3421dd766aac5e4838c96e44b1aa9390002d0d5b3be45fada433dcb2787e289ac5656ec24f8c595ad5b761629b9d5204d172c0100e7e904ef8ceffbb04e14bb8bccfcbf219e33e878cc8087120b751b5c5423a502090112ac08ed7db374000280f481a3b701b59b6e44f8868a02e0b83eac1bd1101cd10f2ff62e2aaadc6d6d38ad6d4dd90a5d81bfb3011eef1b918ba1229e701c5baa418c0ce329f2aaba840da4ca46cfc40e957969a0890f12e82bb6e4cceabfd47f6d1d67069b52c05fc474c117a6510d3ba22a6b1d7313ed8bdda62a193e8638e1ff94cf3fc51bbb4421d1749c259b0c0f2b1aa6e3389e50d25f178240c7ca0ab147218971ba8555767debff4ad6ae001e8d5d500c9c7d49d8b00c0101e4ec13d6eae6c91df46fb082d4165016b2f00c2529d45b561bbecd12e84ec45df0cfc19572da72af45826d07cf9ff1c227cbcc325877b202c7b4cee9c31e8a39210aa2286e52b8d16bf958615b466c267168de6c7995854d27f455c0277adbb041961c201a0d0860e856f39fd311da5f748c0c382133de4fd17c7ab44d559b03f00fe10345c0daae4c9f1fd8c08f1f6e117d02830e8698e536b3d5bd1df197a320f0811456ba0d28e071b2eb75f5523c8635074ae15c24dd59e20912fede1af9297d7035524b43cde58f825bc8ed2659ca530594a064a249582416f4c93692cfd44e9668a1e928a406e54d8f891070e25d5f09e33fc80eec42a5599c1814f5b9361d698be0be4fefebc1a9a3b814297823a10fb885e33117ad8f48ffc06e2ddb071d14df5917cb4520e69a962bc6927eea8b0803e52985f168c655f79335687ead4e82fcd720c8546e42a76212c8c7a14eda016c4f6edcb3dda0aacb66e6e1dd777d689bead64d8d48af1d74a48d4213aa460e38255a3b73914d64a12d1e2044989f8862fed2be7a0a625ca0ac26a371affa0830850aaaadce63edb3db84f96aff87bdbd74e75c9aac4f5a83450595d47cd609acee9d3c3c3db7c774543e49d498e63932fb61a71a4b2753806b0f8558f40401a1f0454b568a923f59e5f85e6f33eb71fffa6300667fee110cf86276d80d510705a491bd7c86c317adf3b94353751602ad493f1ad4d627936b990bb74579230c5644c9085346a8513a21fa73b0c06b145ae02fe2c2ad619e8843a7e32d81210e3e4c6d05756b10b5aa0b380a18adb30b64a28f58e5c27892922866beb2ae200e9c9dbe6e62bbd5a21119ecbb1571aa918e8c7647b963062dfb6e38bab61dfd04b5deab8a7b24223541d3d05a3dd5e9f15e134036befc0778ee89ca65cb025b0e02e8d92c10725152b50f5d242238f15fe81eafdbf79f3144442648109dee850c167b7915fdd9f996f4890b2ffc30b8c0fec3a1d30f7bfc3e5a21f93f15d7fa066375326b30fa277de3a1d6b55b0bc5435044d99b8f0b3860ca0378e6d747420eea963f3f34ab1a6283b0338fbc0be94427a3f3ef8121282b067c82f416595c087dec5aad206c426d758016f240c1fffe5b3fb053d51a4fe795b09a80d409fc02fccdc92c51711c67a6b79f3c4a4bc87f006af05125de1eb8cd0f99929d205b03e8828db099b7c177f033abb297840dcd7d8723a61c3f96c6e3c348b973992b0f0bf5fbb3dda2eac764462c289b16dc01578b46e642a9678b4edf6f5625d28e0338b8c5a331d703fdf0595e40dea4d6e845110dfeb7a649712871a08dbe99f7054251821ac6634ca4af736b479eedb2fd87a566cf3990fb1591137f71834daa0b350c7041ade602a375a530b129209a9af357e3a0485124d049d1b9e1c906c804ff2d7566a2f3c659995dd65b1f42d943b65afb60210182749060e2709ed82902bb772e105f6730da8db97dc4203dd1833e9b24e64511eefc7a123616ff28130c5d48343f2defee057f65ef059df97ba01f4820275b90ad2458a7cc7fc568ff0cf28aa734aeaeedde3b5574c3819d020d725408860b174210ba7b67b2043d470e2ebe6f75631a1e2fad92968edb26a1adfdf06667f09d5e25ce6c48bb8368870185222b356126ee4de0c6728ace83226bc46b22a0f5ce0b81a8bf4264117830089ffaea98bf4326515d5f8cd0a44bb4af9a29c3756db5eb3453b282d00a6a1d06654623a5db5693cd50fd087e9422f235414aaf8ce6d26a064090969f5f49950cff06838c3947843384371769d4d51326447b5da564b36f5c256e7348f08c140d91231c88ef4ec28ab90789b8054ee972b48bbe06bb7556dbe0ac59ffc7d02b0c90d2ca7476134d2504e98eb548ce35488e976de0c565b3035aefefbf11b9bb034f8df46a67e2cab724f268fd3c5b6797fed72bc21fc373cba12e3ba1e9a54d0690481fbd521765fb7bc392680af376a366276606140f2e47903b9905ea0aa806662d23ce1154a2f0c35e5139874e9c58caff19a6d5eaa9a7aac5f30378f59f09df16f5102944fd5f9fabd39f6fd2b166dea8bca27c9e6df7c163f00b01cdab08ebf5f9559a0343af81edb0f7e8b0aa14314a7ad47a9a314421457e3b765d1d07ad5868082a6bdabdbc12c4eca16243c2fdc25c3b3f4566d6169b6c9cbe50320c16fd4e319fbe81115d9d041f6c660f8dcb418e07e58e6954484e35886f1c900c8dfa96b696cda674d0dc359f45e9502b9e13b26bab8100e8e59f75f13973460e2eb4ae27ca2359bdca01146a827caf3b6456774b663772ba928026035097cd0ef6a36c29a8ddf0aae7ab73cb5c7a54e3d5ac29c567f7f0e4f62c1ccc573619097af06f54b11d61789cb3a201402897a1fd7f1b1a7036bf81accf855275a3750012787ffd6765fb2aa233da254a48157a9bfd98b94d94a27a9e0b15409cc54603984306c538ec059976493f6f02c78f9b2a4874c521cc37e1b82ff68d32a60206f4ddadd7f07ecffceb55d355df73e979b561143627e681135d82ae741927ff0f3681a911039cb17a83ca7f54a11612df310da4b243244bf1af9f6ea5027ac60f65d8ac2989bf1d168bf0c3692a56c50b979f5176cf6fcefb5649cbd44373ff0c5e48c06e3eefa529b8e18aecaf9176da55983d9d01f33b7edb12c14ded2aa30e2840d3761e89049fb4fa4868da15f4e2d2b259a306f3d1feafe95dc90f523e00f80e6f768657a5286c07488be27b37469da220ce25361731220de79a43d90804214a15bc3df72be15d51304192b7c976dea4c83022be3687e1b3dd0963fca60707ee92a8146f62aeab50295abab5cd8e8975c31cc24ea6dbcf8eeed13f91fa092718ca814e0db51c4ae062d89acfa6371878aea03200fcdf1bb8f961ce8db205f68186b764292c179405612756a6d947f1c54d328f637baa18b00e61e18866070ee4b035592239213af9af8807063cdbdcb3e8b75453a1d6232a72b8a29d5e0e3c0faabf6d7593b59224de0d11404d0c80c2a1f4fb90a710e875998876b3fc06cb7343dea2bb44f6cc9c4b7749e3510af93cc6d40a06dcd02c53c04eb4bdb600f6bf4f8a4daffa86df3552f0f49fd16eaf871300c147da6a807c66cf85ac1a0fcba6552ed0288a09f80afcf345091dce0668608269d0281dfda5891fe3673e0b13ad587a28e40b0d324b3cb32571c409f1973767ec8fc1a85a979e4ef6fa710fd9bc9eda5415a7cc222660ef3b2de88d4dfc2be0299c6a5a161014c5fd839009dbf49ea12acccb02c364996a180499758419aada52c6905bd22588b0d584f402cab44b38b9ad7a076195845be09e954c797032b2075ba654b6e4162fad099106cc19eaa15ca75be4a1cf05a8ede3476370fcada6f9a02b0123effc3b717ad90d8cd0d66ccfd81131871ee1a31438d4f157096b96bc0872226440378b513bd5061fd447a2675fdee9c40b61730fdcef11f2e226609be1c95867de217c030c20000d00dcb15d851540ca156aa97c2638ea4d4741792b78aecca062d2b827fccb062db89590d82210f83e4ce320677483c9436672539562ab735fc8fd8dc72f62022c61968b773b08d9b3db1d84bce2d4c122d48b49cd34a7dbaf61d6b1e11e760ab8419c20f3cb5731fa53354a786689b759c3a4d8afea84a6ac041a4b8ba1f00097c1a0165f165f878786c0960fc13bb39f0cce7b44ca71dc9e177a1671858d0b66e609a9bba60a7c986190e3e2a8773876d1738ce81d90cb66c21de125df140aa80b44964ae09ce00e68fd4ffd096a48e4308e40cbe91aed2a6243dd6a93df0aa0f402652bcd90d29f99c1ec898e54c5287b0812c57ca0857f8971b2510b070f07e3678efcb2720b5afc337d18c4811d2539cea86c2ca55159675eab633e83043ab3d55a2d5e4d5961c3032693c0f78e7c4a68e4c9471bc18fdcd139cf70410967969d1fa0f83c605435dd1ea248e7f2cc57b2f09d76bbb68d45a9a85815320e6e3197453359380be2de9f73a100338c3ed46f829ec37bd55d6377e5b949590e68613db566b3c865786b8c39bda9149f02908340aacc19cd955685fe4e5ecb047f2360136a951d395253949907ca53b48dcc6ead43c7856090c957b834e24200d0271a3fcdaa129c7e1cc34923b0f391c542ad4566b0dcc26da11b4b29218504b5c3c1098e6341d59f236b16a6822fa94a518a17d6e2351ff6e6586bb40a4a06fe8d91842ebd4436ea68468619591a9dadd561b0c74bfc81946118671fb9260237a3edf343dde6586d15841fccfcf8c7d5db52edcfb5ccb3ddab117ca4bc530cdc44706da87c67a3e4b204d57d5c7699fbf99fb1bd733bf94a587ef82260de03d388703f7bb57614964a7dcc7688bb189108ed5f95f30b2aaae6f217dd3a9904bc718bc6b2a90584154a840bd299e510a60b93be2141209c15772bb1cc9c4807ddba42618c949264d68122c1889e1e96c602c5b39c190a886cc256364aa6c407c5d3d64ee3d90166ab5fcf9e43a140abb306932e9c32d5ccc7da2f8d8b59420d0f42a9d76d490f451027718d1957e18cf1834cd842f8d3c8d28bcf016e92db0b71d33d0e536a975244843f106c29cd1ec11b4a255c0ef97497a89e6635bf23077a9f0306a3414e8cc8e413a1e75ab63b5e4fd08cb3623dbb902d5731138d6908364ebc3aab512e3ad2418629429d850b3184b9944b798bfcc8e290a89d249c0eae116845a58cdd1e57d94513c1ff9601a58f6a29ddc9dec840830055b781c102c8db3c274405a8ee09e90c2b80953a216e888049358f0b0a8734d541542631082ef80a71f724ee8ccccbe6d8e2797ca1421b83309bf11679661720beeaaf650022c25a0c85b61a3490da381902283d90da00ef3dc4dec533ae9aa4ebd549ca057530cd8ba9e94153cead886e2bd96549364118da7c4f5e3a7167c9b6b4f809013e484edd1026e1f4b8f3385c89ff09682a5330c1361501839ce77a74cd57e20f49241791a253298e617c247ab775ddd18a8c885f8b0aed54dbe83b965beeee0a6d9221b4783162c6f78ecce329fb34395d21ff4351e05cfcf66ada52a25893037318678eb369a195d8c930a6694e627bfb6fbac4cdc9f6edf32c1a3d5b0bd0007cc86b4eed1fd1eb1d4460abcd1571fe7b70fcb7136c10083a22c15a73908d0885849e64dc3cbdf6897c506ada60aef3dd7f6f49336539a3534a2634b83b5f07f7ee7b9a3fc561f921a8ff125aa5066d772b43c099ce7f360fcb493124421700ccdb2403dd6f5d54408060694bec24e6e5e2655591f3bb8e1d6d6c056520ce032bbc0d556861c0653628b704b412fff0ac8378e9e14fc7f9559f7849addc0d08e8e979d5d3bf843bd4d1653799dca9196f78785eef10fa0e5512a6523917840a8fbeb567d5b5921080c8f51b77f0b870a9f906d603f1d95c9ea98b5e6549df0ce0d1c5f8604645514332a67afc858ce12363d6628c5161ac79332cf9cd252704b9122ff819ddae716b3be84233bc318d5b759e2929097d13d2fdb5bf83e8150eb41ea13c7536d9448c7e410a8790684f504aa096d26dadee07ae3691b1365c0df88042c0b86ddd14083dee24bd0e85c410c060d2175dc376728e705221634a0aa3684c4c865f897aca1d7e67a5ad5c29289f225820ba71bfe9568d6378998e0ba0fe7ff5af2f0146ffe148aa92d70ec1f95a114ef174cb68963466628d45e80c6de541461a9c36878d70c1d2db40e0aeae5067ccad9b74489a68940da9b23007c7fe69ab0f14c808a926f87fc00edbbdb2e75408be55111bccd7009ee2082a000e56a69361181a3b9b54b2d9efc1af426a1eea5af2a265f7b40615a5aba10802344f7f27b7cfde36144d2194599fd92926bfbec41d315439f8bedf8783dd3f000a417624f374d6f9d2c0e2fe582bdfeee542440cfd1f28501194c5cdf2f46b03c2b9a77490ef03d3fd7de695c604e249c6d7f76d60b753ce485a3431caaecfb037673603b148927ec4e82b32a04e10d81d67c876d484cb8e3bc3025875c57b1aa8b882d37ef88750acd5b31d2a399a857ce650f2493e2a28f90455a91b8cbc0bf3e39bcd3ef40972f1f7ad3a7d3c14354a78d233a02a865dd279fe1cdca02b4d114b421b98923bf142f91f84c572d5a4f03687907f4a94fc0ac81116fc88adb797802897c5c5852738a01b0e879d94d467faabd200c801bb501b82b9e36f2047311a8492a56f768e96666b5163c41aa0caa6a2ea9f4923db8d538dfa76f313361f7034fdbca17e8d9f4daa9efc0443c6e2be8007337a230ac1f3d152d4ff1f2bc44f523880d409bc02ee558bf55c7c45b3f7ff0a4651e4949022574f72c3d9ce5d5f037e28283d363bc8d15e802dae79e94b14917d9eca092b1749bd5d089b74699c78de561c49320457937f269019945903ae650f353fe5693031fa8f137a8c01ad038d30629b84f5a7f7d9d2d11cd163ee68908fe402cdfeeec495e13fddbc0d875647456be2e97c771acc6647441f4836de1c7639a269e3a843e1885541b69b79047b4cf9b83caebf81a4aa039e7230d4f1d9c0fcf37b49c86a99e3bedc07caf6c4eed50f789cebe8c8654a8463e409ac88b5425da8596cc84f3a2973235d6cb8e4a51609ec52ccbce5efbecac3e440c68662ea358293b33798aeb2ec052af99b3f4ad0d4e6772d00e1c8bf45b160f0d2b621c8287225f7597647cf4725094f0bb80b225a41fb1387a1d1eb4ceb2ac96653a8e2b1d0136ea6c37a726deca40d4c6a23bf5b15a62e9c63539dda2f22c02a7f1841a3adf4b715ddd76cab9e6cd51bc81cc8570e166315b655e10ac67e57e69002cef6786132b8b8d10922d24ed2823b2cb86053ab67eeaff86ad05db14379e1cf174f876c175ab9e0850a13cb19b7b8416938789c508363d25910658705c4984cb2612d84f7d5cfc16c94c34f70d6150a6bb3de645bb308419d9854b8e8f768d23b7475e0198f8a894e0b6700d21a879485990680290b124712fa3b673ff79e0e5388e1132d66cd814e38c08678086c277e4ecf38bc16032a5ef644d3e0744ed56cc2a7e443be94398ad7d21bd305203053166a18e5621b785a2f3cf7271df5c1807255ed26396d95c72a46cb1dabc54ab08aa8f29d210ed8a2be60fa701faac646fb630ea032f524fa50ad6224dff2922a1bfe7f6c5ed0e259b822add3c6245d3800f8408190979d462b007d3efe1208e4408b96e8ce41853ebb0c5aeddebbfffbe824a8401a04327c9aad78c19b63573b3a1b478b8304eade4207baa53e1b53df94a8b289887b2930ab56f781a53bc11a32771e629255bfc9692aec628ea82b879efd9926e62a462326b8aab1f320dbcd07b0230ad7ef3d860aa887df73bc7252ccb6681caf8909385ad6910514018e9b31fa1886d75a7eb8e1af3d54c4b514eb7cbc988eccab78fcdd174147927159786a951014151d9499dfeed70bb7af725a496e3f5d53176bfb03f00846c6282ec844033cb430c853d9afa3bf4301b8502c942e635479595900130cb5983ba46bb5b7440064dde0f0a7bcf4e0b446be43eb32bfeb3ca35108888682e82517e484c50504a9885ba6f2c35a0d1037909f79626d268c3badd147c88566eabbab23f387597c2e7be55625d948f459bdef60e6ce1fc308dc1e710b6d4fa526b301dc81f3584713b0462ff5eec895079558fc8d4bff1565f636188037a1a285b2bcb96f2fcf60f418861812f71e4a73121198f44dd43713280ccb1c5c3cba886d63b2d26c5d66c02c796decb9db5f6c798e9a7a320f1e966a24287075629fff94a6b5d770aac8d8f0120d120cbf93c79e394b33a5f488ffa74bba43729152367f3e2fd42c04c0291c19ed2369951783853bf9d2145a3a666f8aa9a3f1ec22ae7fbc1409220ca8dab721eec732765be8a1af553586bcf765229279fb01b2d9f0cbeb66bee4b7174af9aa1726864526f0246297480e0517439b976606f2e640bce589582cb8e1c67023988506a1a7658dbcd4778e760d3eb844bf537801f7b40a7753b34b1be0623cf2e06c48eabacd99bb9015f1ea531f76a0c760d7916ef474cd9163c94e8b47b987abff2e4d5e5842f1b4915a0ce78b8e7a492a77ad7a72f68c2f2d60f6db88568bf6203aa16f6847499f830236cdd735d4400dff5a596f249616fbfe000a72c935f4d4d8329756cf47ca8ebb40d011004580dcee43ba4d95a8de2b84e8183cc0bb885155f7bc2160cf53fac00b89746ac3f1b20ca5532c0a984c77f48c6d809dcf285600ae59650fcadde1b6a3e65478539ca5aefd5fb99a841dc4faead2bc1df55affff321fdb3be2665bc8b33b0bc0dca76984e5c4b23ef387d027f76dd8918a42eea685f992a0bd2ccbaebe948bd0dd528fd0663057d84f4ef2b5f332e054300cebd4bb319a68b3309176f0072eee797f6fdd628639a652629fee51dcf8d0a5c0cce7a094be9395b1d5367b0931da666ca49b954715e9a778a7de10faa3c1a14158bb27c384d3ca7346ef7904265887810b99cc765eda9e12b8d94821e30b25b952d4a359a95747840f72d9fc99e6a1f1a827785ba33317e32c9601c447f01afd7fc15aa169c3309f388280290840e80c7011f056771f7c41a0013d34a79040cc01b2d38564d0ce590a27834dd7987ab0910446f159f0669cbbe21597f6854c65d9e209f87b270ba9e80adf8a783d2e937a71004231919f51b7132c305df44472541cdba5b1dfc9d027d0534b15d34002d38bbe884a653efbd9b561a95c8a135259698ff58e7f61573b75249483929273eba508d6012156671b6b36d8ca337b89ceab7991187423549ccc47f3a17225a8e245e2e757b7aca85236b3e60028ef059c5ae5733d76ff94bfd632880e799ec1e4c416a9d70b49be502ecafa98a0503b260f20f25c5cb944a0bf392690cbd179c29a225347097fbb2068b6a4ffc0e4f6d8ec73594477dc39a09db2c61e667f5911d5974eed063396a99a6b53f997a2850dd75d0e2591b95a3a4df95d293810e8e15bd47c6f0a39d39a8d3125e8c0a76239f4da1d9d8d26ed9e099613476e5ecc9aff7bcf4e091965d6d5f83f0be398f6000db698c12e55e54e4efb1bfd1179aa389b9e686e09b0b946a3c0544b85c0d78ab29d97829b9d915b463fc62e02bb7542844a455d02dc0892698ca33ab73c8bc63cafa3d0f403cb9e56e53bb9ed1ab06f03b9bfa102481d5253b676537363a738fa52dd261d69a578eea5386bdb42c4d3b5d39ab900e2d7f61528be9c21a8344290929deba0dd5a9d0fc6cafd7af6a03f232bf60b070e58bcad85e5c703f510d23a7419e1cf51851d70897c0c5630687018ea61d50d3c43c93f3ff79512b7a8f9f8e19975673c84cae98332f28fb7ef0003a5b8bb0d37541b590aff9729579e5d41c4ce2ff85ab934f949a17dd05fa5d9b2bb15260be16e17f7224d25e664783dff790c6d8d128b1f4699d15482733c30e7243dc60127395cd884deaa8969be08024ad4d198e04378e4d67c95404f0486d6e5407f02a3f27e4ff0270d0928e2952dda25470480aa8bc13ee8483182c0358ee9ebeb057535449a6138d6d61763e836d4e885630c3737952ea3dd0a8679959113065a0be98b561016b955e920d98f2bde91616e1378bd3633ba09d6803f5e195d9fb0013e55af6e7c13bfa72a2d902ed84485727ab1bdee97adcf70daa92d1d464f4408254571c910dfa6784498343de84fa9c86f8f8aa1ad6c0d7f564307378d8d700a1b6a0bb2775fde72cb8c87cd9cc4fff96ce748a958f61e7c0b74632a9ce69203801d41e2f3bd178ca72a219cc39bee461b96b3973d89010704586d50e66ea805ecee56b233d7bc4183039bf669ebcb33f68bc89e4ca150b3adbb61d8f3a1e5072adc15f9794166bdbddf3c81591bf1c6a4319fcb0adef3c4714247ed8682e10faf05dc4fb304c516cfb993b4a371f7a6936b30020ba44ac2a3ed32a14580c70424d08cba2ac6177543922f06d2a9649b7c20a455e00df2e5f57fae24dfd18008828c90b0ae93187f03a63626a9c69b8222a2bc2eeba6ed5f1409dfd7dc8c330c26c530d279d560bdfcc0b367311bf4c4667c927e70dd881e4b9e46711644c80d24715eeaaecef3c8eae0000c01f9aab65d4201ad001d5c50a36bf91da1da81025492e6d3ea1911e2d574c04da6fa59ec47dba9f6447fa6d46785b32f074b26005ceb368b195c894f0442e38f8df547039a68977542d110e0914bb00f7e0ef3066c28752203cd79db2a39c30c681acf128721df5447921876e0c5e8e6ca6bf4037401621e738a258ad59e78e009d67069d628e2c176f2db95b103cc6992511302526a2070056abe08b1c82bcf75c035128e804b4bb17e47141d626dbe5fa93e0b4760a1dc30784fa63d3cce1490dbb803e0ce9d0a57e3821248bda37b3e02ac0f66fd92a920b3cf5f3d1d8e268437c8c0d919e670b369b89e7c469334b6618d00ce048dc0e826ccff3f265dc35c38e52f88b5326aa428799d96710ae7fa42040a73c8bd479d85402260afedd7032a01a2de2778ffcadd3a0c73f63b871cdd4e0a2d54a2153e3a68af5dded4de8e3a2e3f5db23db3e7a9362cb5513d0a589d2f06872c1113b84bf0875742a73b920d65604b1c92ab206eb1644d212932e2ba4a08f555a1305c0966db76d89c32b47e7fdede68284034343e3bae098203adb1010f5e8844269ff8e2c4fa28076d1d28e7b17c3e3e5d8314f6aedfc851da5953b209982031042b92a60727496112108e8752c0037166a75e28bff7be5e4d42b96f0df348f27df6109109c2def5c188de8b1eb1f52310b30b023223cfeb511642dc02d06db015a1d71057db2f7d589f793c53a87259bb596f6366872d1df6ec8bed033e54ebafa600bb2024e86d6958787935d128baa15a138995e6c7024fc731e10daf6c1d188c3846fc28ec9ee8f1cbe22dcb78cc6cd7378985b94523dbff6cc008704e81dab9d30b0b9ca77113440e605e6cf557a211e37fca6696380bf758d80340f7151702ec8e353abcb123742207eb6849abcbfad7e255ee1e3772156cc9080e241db4b4542a99e48953f353c6f3f15483e22cc457629fba034af2ca29ae0bf87ccffd8793d08eafeb9d0aadb778078d14a55941e99da14ae4adea526c610a8e7cbf7457ca55af9b20a8787c8ccdba16b60073c1e5d393595adecb68b6d206138158e51311a5e5828134705c4942e3ec73491cc0b0238e58915f0c409bf80a594ba445d498fbf1bd38b88cd6999d05d3cedcc3ae5f810fc8a37a8fa1908f00a08d7d3228f47eb7712bb1a59f14f2691e0656a706ffbfaee6d716aa706dd109c86da739da9241af66091c7004a9451ba95fb6164c66f36c21223e92239ece0b612e7a6d130f84eebd5657564d0927ae2c3053593db77a827fe8d98481292b09072ddaa5ce188cc88c21fd4a00694542281b0fe5e11c304d3a1c47ad85e5140223f5fd90069d2aa1bebcdd37a8676c4873dd24b2a494638b036f05ded5cc8003d8c6d180e33f8add66e8f77f353490c000392801f762ee9a1759a5ea482a5f0a7b5b7b884feb02e69cc2f4bd19929cd130ff9f6e1bcc3be4f4a15058c12d3d068910f3ebb59179557ed2bd2bcadefe7b4e1a8435537824257e5eb99299b3b600a251f4465dd9b562bcb62cef125ac936b0302404652d4714aa9fe4ed8888e7050501f17433b99c1411f9a8ccd2960bbd303bac894486a81f00dfec9a8a477b04e9f859268809d3c302c6d0c04d8cd490376cf9de794f9b44dd514aa25a597e07251703c24dee127ca297db3b137c489b88fde607696bf8b610f596a220e3730a822ed02ebdeb3f3a730f74a48f29e0bb8d3823b9a758f923a4e8a98e2b95990d0fe71210a373e8546c086052214fa0a509c900bca640eeeadb8d9f4c8ee73c09e58b54e01ff4c3b6c2af284ec0b6b64b18c953fca19cecde100b1c0402a06605556605bed2c9dc6d61d810095d404cb8fef83e1bd137ba8c97e85d7a2bff4e07e2ca35ed8cbc27170c87023a186f2d309e5e9b85d829f66321ac8e29957bf90a50bddfc0eaff9ac88acf9f53568faf285b01988411e1c4a5a0e79f12e561dc047cc35c7bd2893b09d0434c2cdb28aab8d981a5eb00daed7f01a2a8504110e30c4a80f35455d3dc5bf9176174af8eee4535b8246f71f59581782d972e0e989501878c348d9f91993668c1226d4b21a5513a3e04814c2a3bec8a0761be317f21053688f68daaf8fecd01ff951f2e70f633c37e3861d35a7d787d196f93fddc520d146eb29427271e3de85cd97a5fd74424262d329ee292491688e6a589956d000f95cbb672942151b82c4f59c6c6e96087712e43d7e304bc5ae62c0cf9da2f150f61375c7755917212881ec934b1511088502471d5845cac1b2b7b3f6a1512d30e5ecb6c3a32a1638d002391390eaf96298c791ade33151ef041639d96cb976c047dc8a39527fe9c21497108446867c7d4a353ea42a80f306e702f30bde32dc10f5db99f03793ce72cfff0779099e9b9adb8078147e09d95f0f9af85ffeefec009f101e4092ce6e8f446525c5d4cf970e8006f5ec9544751f1e8f285b82a00fb018db34b726e8edda4ecac89fd6b02505ddf9f31814610eaf460b2d77844a6fb090f1619b093f049a766a9469042b5a4a32497531af614cc03ab3830247db52005110447e2ad4f9569707e7467a25f0f141e90ef90c2c949afdc56eb3bf52c5808b25c16e5e5046190484037ea48aee9c92081f8320764431c95685ae01a90d4036b7298f9f3b5fca717ba77cf5a1a06d22f1194501ebdbb971df7b291ad41f10ddda1cf78f2aab6759066dba3fd9c79c772c288c754a5614c6b484dc4b6ce4100be89385e46a8b2eaca52be969befd4b0510c85dce397d33983972c3804b4a6051fe312f26d59153a068b82c140335672c79f367711932d8619a5564c8ba5db0f13bd511de60a26a061c1a439d60e1bc08fb381cf5ba567486c5e95cec1581a0a49198c5144a5442773701e1ebf8b3e83ad43e1b9782de488161dffc868cf750d6bd43435c477ce5799513fe32dac537cea4f39db075bc8bd09db0943a62ac703ec466ee76c21bbefbf69693d5ec79cf4fd06559864206f61c28ce0398dbe3d074fac57a0eebec7afb35ddc4f4c6331c06e38183e4b249cd27f8b18bd86442d0f3dec5d4237baf7f61bc6ba397500e84ad9edf591e84f1c969dcfff847581e5063c916e4096a3d5c143622ec632e7592710756fa2d88073e0a661b14c38deae0101475c15a3dce77876363a48617cdd1e50a1b19b6ec8c764818fa5055f80ca07d7aba9fd0fa7309446573ef949d64d8e027dd01e0cb86fb12a691e32a464360e5e62a8721ba16e8da76de0e91cfe6b2e61e1167598b89d228c30e1d5fe02b802edf3f787c17427e45ef6d0223e5ed21ac8334f94d11e5a98a10f2afdb24f9f0cba9184efccd9a7434d598dfb52beda83bc9f99b0a957e4f3f93e5b2484b83b0cae245dc65f3a123431082a456e2d315ae1f2cc866c03c1cd8c028a3f0907ba0f05f69b59382f7ee88309227581eeed19b718c0f9255adcf35b327149b1fc0f0f9617c12a5bae1ea5c44f883eb2180abd37cda59fc50e5bf1c304550ac3f4a00fc81260edfa79bf69f21e5ac5cf514da8d29264c07f7de6fffaa5b6fe1435910dd9576f82b5e869bdfe1f062e8d42740a4ec7b76774585edc3d8feb3be8b32b08e86175788fe1686c1dcfb1d9b5dc71800124144c727638b6b17ac80d9a75e2026393edf362b66b200718566f49fc5f98ef55a4ab29d9c0a5744e3097ee952206db41539af7a41a88276803b59c98cbf1950438bee77b5879d5222535b1143a0fdf78c3bc834df85564e3ed991dd830accf0d5c6c763fd72a16b35949fc59c1019b0ecf2a7d75617d882a6602bc433fdbd86471bd9eab39c82874c99d9268a6079e690970aa1a5db2135b11466bb437cca3f5660274cd059b6977ad74ba2e1f008de8da1df548a3eee679a8c9f4227fe2a1964657d5216ce81b68c0a1ee33a10de8b71eb2f672df415930ade7f61333863ca6d8028a6816b4e7f8d9e82de21c06c57483895fc3054565faa35aedf7b4c75d8b38a8549737e72118af6175e9ca01815b3b5f2403de32d343b4ceb5c7b24d4309f93488859aa8b4cefd1d221ee40c6f36f58662b2b67896076cb32562b9b6cd0759a793a28da4b7c6015a419feb058de354f41af135d0b4116769d44b0d134587c7356efca96d50141ec46243eb0bd6989df79b17ce832f0e0fa7daf80ad6032c23f3a59849b53fae61745b7f5d08cd1c57da34a0e99f02d7d48332a83510359ab78c2a06e6b87fca8af94ac3440612287437a4c15e9e325e2dfa2fd4220708d9699b97b3c9759a4aac4f5996840ba6f4791e301c0a16a4d047784ea775004b9ad119a6faa690e42548bcd32b500081b4f0c55b16eab85b1270bc75fe8d4a55258bead13587e43d29aa9984633c08cc8c3829baf59dd3de458110eff6395cc96fef3c928c2a9048df66f7217c870518bc5d2f50aa1143e2c8d05dfba335ebb32bfc83c46764eeb50371a3e0b37c05915bf576ba8e078effa80fb77b47278420ed207c961ccbc4f7834ac2cdb4c90cdd2c3b30b6501d9b409a61bb4af806ea4f4b003d664dae40d07e8236927b7407e95501451617274a24e9681b49b3aee4facc8a9f43e1285ac787c2b73ecd869b33aaedcf4cf80b7d6c541d1064999f572b8a83c5ea46db76e66f290575e0c47803dfaf2abcebcfa2eefc402979912cc06a97f072a74c4bf006d9992006f5978f488360fd7829a396a991952be8e84fd102b013c49ade2cb21bbbf009391642c0c3ac84240077ab0a0838f5053e991971bb06f3157e512dcd2a2ab9fa7aed631f8c662640ecc7a753ed8e29ce32e19148a8ab93ee55fd370cc72d7bac70bd2b1b263bef9039526f6bdf9e66caf69d882a167ee9deef66a3f5ffd38fafd54006547fef1d57cc95ce5a075bd5463c4f531271a589244c8c0cf12f5f0b1e639bb500633458bedf156b73494b41e47905e06a5bfb26d020a71b4dc61f14762232943929c7bcb2f5f0d638bd84892c6ac632a6a49004c1d7a82bde740e626f52e59a1cf8b8150330ee5ec65e6072994b6926d991e5e3c4cd415ff8876c6492a25bb51eb634e7da6db25ba1846ae037143549e836f595437173983ae6af12206bd5eddb9b869daf8d0d069fd1dedd590babf98a4a0423d74806c8946992c814ff1964fb887c0f86cb2fff1bb72851b0b4ef370c3957f8bf4fc053b08a5ead9918fa15f64db0cad9b5dae7e19079e13e7107b7f15394817ff53934f0683c2433bc4e2fece8bb22a29c4f5808e995eb46ad1f0c3cf6fca2fc5cf61982793778aebe07059d7131f050054f965a17113766b72773822f560f681ce8e5719669be7e1cd8d4f638c57aacf59ad78f04af8b6aaf7578247560067cc876db49fbed0dd59d5385bcfa652ed0484e284f92eda2d35b7216b06bf4f76332e25aeb05ed27e4f6f4eecc2cfd061cdca4e9343cd41495e9156891c892c1f48136ec3aad1f1fc144fe9194d5ad0b5b9b3378a27f8fb73bd60d425848064536c8bbe14e34de1944665d1c34c789296f8262996f32ab5eed1b64498c728b7f3d8bce98de1da86f5565b03538d1f82d6b667423218c651ab605ce7c19112e310acf27920a430cff8bfb2d5606df75a5454e83367db33a9363266d32008fcc5517be1568a1ecda27eaf93622fd28eaabceedef5503bd3363d4e31c2f82141f49acc0699d1d2168683afddbbc8c99f0a4ed37d8df1eb935ae392b8d4f103017b8c7430073a8fa88f8e8b746ad049e019c1310c591eb6d7e88470270713e8e6262562e46f79c78390e81a80b3f60847056d24308533b4d2ffc28ee240806a112bb0a371bb5eb0d923d6f377e0b8d2ab3ebd28ea84ca2e4f97443d470801e516a8f0fd00b15ad8e2bc027b2de0424be9faf0ca5cebc1ad6e299a806b07f6d66311dbb47333312770f290dda4922860fecd351d7b5822159e75206661dcf9933977ab3b28c119259b675b443c0a062554cf240843c877512b70e6e47b9082d701bbf59514c0f96bd2aff0902825d55690dcc5fecf4bace51b732eecf18358d1a72c59c3b5247b1ca75485509df4ec04d092f304adbffb71e2415eb653f807af39828d61d014a12233d60ef3d00d27862b981bc5e252bb4c481aba2bf67a4d7d469ba9f76fc737c5457b02418e5f701930313723dceb3e20ee2a1edd49cc6b36c45f23dd2c93f15f635e7a5f74771b75ebcf98461fdde037cdf47d01a96585378fc4ec3da3bf0cdc90979abbaeaf8a24ef1d0e54c6f0e21fbea9b2e6c133420c7965b2a350c1f48d85f0d40b03a91e66c8a3cf221bd6de8fddd74ae802f9d12b601a953eba33607f94d42c06d9ccc23c70d5a16397a18dc7582372d651cbfc41630681103f0cb46a2c738ca4cc9e74f1206cd2b0dbeaaf5bc2714c5a256ec2728bb10d1be87e38a35d616fa39e1417743f851c4d7a16ebf95d97e1e02b6b93dd8edd27981b2a590891b9ae6b2d4151df0de9e03724c8388b27c320364ae485b98fc79117f0a82818d754dd4f5f960d8fde43752c701c1e325910f29508c2d9c4eefaf7faa9e7abb87fb82d5827b35d6455523743e41b6bde1e74c6be81ff9497fc3b29d25e5322713d21ff815747f4f756157727f1b11789cdb4f2b865ed4447cbb18bc5328bd010c5387b5e11464b3e0928f0a254faaca660ec95d375907df860b866b08d4072f0de8a61f568c3b9b067d2b271cabc676d6262fae32e8d3c05687dd0fc6347f438ab34a334bac70caba91228bedd6e44e4dbbda2f88e98b3524caf7f16a9264382f1fff450829d93e8fe2599e5f7991b0d15885b1f2767648d3ce360d35c2f48e48b761c7879a61d27dcaa9aff470c2feb69ba544983ea3e510e84c956fe277523f3eb4fd516a1a300a667dacf42b4777aa0d64a850f0c2eb19616c9e0afa6d1330f8443329f738cd10f29ee286c57ef3cee1b7400654336da976367c835af9f422d6228437ad8422fef313967f2c719d0fc68aef8f930301c17e9ac6c44c3e0748ab4c4d926505234405d8449717db2464730142a97ea6d398dbd5acdf2b86bf2da0850c90a44ad770324cbfd4dc9b4670a29f80041e746f26d295c50b809bf0a8cf1f51b00a685b83d22cf24601f23c35958e9688784aa27f2a9f6f50d8787d72a6d823df6419f1c47344f362821220359ad16fa0f8a90311aca79c699f1be89da341355fd492494da431ae07c7d3aaa1509c28d3ebe368ac6dd68845101efe8656c6bd5c6eb9b87592c02a68924640c102c73005d40a1b7119bcb5b395eb0a55e07dd928d1e6285432cd0cbc15aec82450714e3dd0eef84fc16b1a2f405107588612a24ee2377eed68fbbd363990a05d270071c6cd5c703f2ffd7297f940d91701dc6591cff59b9f9f8dfed90da03f3c9e661c64b0e69d29afc63d4f9e0b5f3121dfadfc4a0c984328be69f8b06df45070f0939d4186c222f01c34ab6006dc080a33b18d694891ab30d4bb4dba9d6ccb0980e308e4736ca1184f7d188cebf9c7bd625b08f8da0b168f5bddeb21519393164547daacd924e85f3890b7ebe4eb500580cbc3792f0b9330c352517282946705433516d588fd33fe27035f3ab78ec5ca6d4a34295700e77297aa3ee036e945872255f5e2386ca5f2c6aa793441b9500ff24e5d06c30cb4bb3fdacaa0920a72557810b760df1a3e1abcb41911f18ae3ad8f16a4c20e060339a795d0b90265ed8367c784d364499bcb68925bd82d2b746c5ec06d17ff09cd15af9f6ec654c0d2ae47ed7a0e9936dc24dd32e06b3d143fea63e0705dc100ca9cc9fa24a690066a68ee231aa7985c7c0759c47f4421ebc83dc8c2ff30ef0567b68de1fe158f944cb964b323c943ebd57dcd70ac0f7e8655dc512fd694ab0ce17e9c57a22ac7391c0acf7230d0ede5f7c299748c8a5222410339380058470734c773355157653e85cfd8c7ee7c594b15153a38ea134c11c835e51743f6570980f02609f4361e84527a9c6d6eb9ada9d8049a4ef825cfc72373b676eefbf7097860aa9b787c32580f5717882fea4764b1e7a0fea1c02941464b87e2233edd0c32e23e4c1a47b80e28d40717672c2d06c2ea11bb7e21bc14e04dbabcbd4cb00016ef367236190f4f7ac70f47ac6ba4047527df2d2312dd368deb90b07a57680077e60f1e2ddd7a6637ec5aa969055aa01c6427308da322f41bae11df4710b300d64690c37be2ed7e49fc67179745505128266ef6c531cc7e3698e173056d270817565b4b373008328218ab20be3232d44c36a7fff2ad00423308438278da8003333d3a696662668d97721ad48a55af3a4afc3049f8576485d1b782cf05622307b6d862229804ff56d83ab854799d3467e3526e6a69a03fb8d31d0a59f12dde0e2d0650d7d3ef2a0cb720a96b6f5f2ab0acb70b196d962ab7a061dd7511671a0267e9b51440beb1f9e9e70d424151ea292b4131d94c832cd1f7c5288737830e07"
   352  	tx_created_by_oldwallet, _ := hex.DecodeString(tx_created_by_oldwallet_hex)
   353  
   354  	var tx transaction.Transaction
   355  
   356  	err = tx.DeserializeHeader(tx_created_by_oldwallet)
   357  	if err != nil {
   358  		t.Fatalf("TX could not be deserialized. This needs to be fixed urgently as encrypted payment ID functionalyi is broken")
   359  	}
   360  
   361  	// the tx is sending payment to itself with encrypted payment
   362  	tx.Parse_Extra()
   363  
   364  	public_key := tx.Extra_map[transaction.TX_PUBLIC_KEY].(crypto.Key)
   365  
   366  	payment_id := "0cbd6e050cf3b73c"
   367  
   368  	// lets locate and decode payment id, see whether it matches with expected value
   369  	epayid := tx.PaymentID_map[transaction.TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID].([]byte)
   370  
   371  	derivation := crypto.KeyDerivation(&public_key, &w.account.Keys.Viewkey_Secret)
   372  	payid := EncryptDecryptPaymentID(derivation, public_key, epayid)
   373  
   374  	t.Logf("epay id %x  decrypted %x  original payment ID %s ", epayid, payid, payment_id)
   375  
   376  	if payment_id != fmt.Sprintf("%x", payid) {
   377  		t.Fatalf("8 byte encrypted payment ID missing failed, Critical, encrypted payment Id failed")
   378  	}
   379  }
   380  
   381  // this will  create the transaction and benchmark the verification times
   382  // this also forces the ring size from 2 to 10
   383  func benchmark_TX_Verification(b *testing.B, num_inputs uint32, num_outputs uint32, num_ring_size int) (tx *transaction.Transaction) {
   384  
   385  	temp_db := filepath.Join(os.TempDir(), "dero_temporary_test_wallet.db")
   386  
   387  	os.Remove(temp_db)
   388  
   389  	w, err := Create_Encrypted_Wallet(temp_db, "QWER", *crypto.RandomScalar())
   390  	if err != nil {
   391  		b.Fatalf("Cannot create encrypted wallet, err %s", err)
   392  	}
   393  
   394  	defer os.Remove(temp_db) // cleanup after test
   395  	//sender,_ := Generate_Keys_From_Random() // we have a single sender
   396  
   397  	var receivers []*Account
   398  
   399  	randomBytes := make([]byte, 4)
   400  
   401  	rand.Read(randomBytes)
   402  	random_inputs := 1 + (binary.LittleEndian.Uint32(randomBytes) % 20) //minimum 1 input is necessary
   403  	rand.Read(randomBytes)
   404  	random_outputs := 1 + (binary.LittleEndian.Uint32(randomBytes) % 20) //minimum 1 output is necessary
   405  
   406  	random_inputs = num_inputs
   407  	random_outputs = num_outputs
   408  
   409  	txw := TX_Wallet_Data{WAmount: 4000000000000}
   410  	txw.TXdata.Index_Global = 739
   411  	txw.TXdata.Height = 730
   412  	txw.WKey.Destination = crypto.HexToKey("dbdfd2a3e9da6911b0a3e37e8e448f2de2477f81760585c2f197736bac127e0f")
   413  	txw.WKey.Mask = crypto.HexToKey("01e4e85ab0b5e30dd86b5356f0f6b4177738b9e6b32041c4e4781a2f26083101")
   414  	txw.WKimage = crypto.HexToKey("d8fb3b4260aea6582400a5f48244ff3f7c4dc36420698e3decc5d28ba04733c2")
   415  	txw.TXdata.InKey.Destination = crypto.HexToKey("ed0da9e74d240088a07909ea354b8d140b753642e25495e0931b4623b25ff523")
   416  	txw.TXdata.InKey.Mask = crypto.HexToKey("dbddab6c6b3063074e7cfd1a7f83f184ad78e92c8ff25118c0ed4edc77015948")
   417  
   418  	var outs []ringct.Output_info
   419  	for i := uint32(0); i < random_outputs; i++ {
   420  		r, _ := Generate_Keys_From_Random()
   421  		receivers = append(receivers, r)
   422  
   423  		out := ringct.Output_info{Public_Spend_Key: receivers[i].GetAddress().SpendKey,
   424  			Public_View_Key: receivers[i].GetAddress().ViewKey}
   425  
   426  		if i == 0 { // balance the outputs
   427  			out.Amount = uint64(random_inputs) * txw.WAmount
   428  		}
   429  		outs = append(outs, out) // fill outs with random address
   430  	}
   431  
   432  	ring_size := uint64(num_ring_size)
   433  
   434  	var ins []ringct.Input_info
   435  	for i := uint32(0); i < random_inputs; i++ {
   436  
   437  		ins = append(ins, ringct.Input_info{Amount: txw.WAmount, Key_image: crypto.Hash(txw.WKimage), Sk: txw.WKey, Index_Global: txw.TXdata.Index_Global})
   438  
   439  		//now we must the ring members
   440  		ins[i].Ring_Members = append(ins[i].Ring_Members, txw.TXdata.Index_Global)
   441  		ins[i].Pubs = append(ins[i].Pubs, txw.TXdata.InKey)
   442  
   443  		// lets add 5 ring members randomly
   444  		for j := uint64(0); j < ring_size; j++ {
   445  			ins[i].Ring_Members = append(ins[i].Ring_Members, txw.TXdata.Index_Global+j+1)
   446  			ins[i].Pubs = append(ins[i].Pubs, ringct.CtKey{Destination: *crypto.RandomScalar(), Mask: *crypto.RandomScalar()})
   447  		}
   448  	}
   449  
   450  	// 739th main net consumed output
   451  	var payment_id []byte
   452  
   453  	bulletproof := true
   454  
   455  	tx = w.Create_TX_v2(ins, outs, 0, 0, payment_id, bulletproof)
   456  
   457  	return tx
   458  
   459  }
   460  
   461  /*
   462  func Benchmark_TX_Verification_inputs_10_outputs_2_mixin_7(b *testing.B){
   463  	benchmark_TX_Verification(b,10,2,7)
   464  }
   465  func Benchmark_TX_Verification_inputs_10_outputs_4_mixin_7(b *testing.B){
   466  	benchmark_TX_Verification(b,10,4,7)
   467  }
   468  func Benchmark_TX_Verification_inputs_10_outputs_7_mixin_7(b *testing.B){
   469  	benchmark_TX_Verification(b,10,7,7)
   470  }
   471  func Benchmark_TX_Verification_inputs_10_outputs_9_mixin_7(b *testing.B){
   472  	benchmark_TX_Verification(b,10,9,7)
   473  }
   474  func Benchmark_TX_Verification_inputs_10_outputs_11_mixin_7(b *testing.B){
   475  	benchmark_TX_Verification(b,10,11,7)
   476  }
   477  */
   478  
   479  /*
   480  func Benchmark_TX_Verification(b *testing.B){
   481  
   482  	cpufile,err := os.Create("/tmp/cpuprofile.prof")
   483  			if err != nil{
   484  
   485  			}
   486  			if err := pprof.StartCPUProfile(cpufile); err != nil {
   487              }
   488          	defer pprof.StopCPUProfile()
   489  
   490          	input := uint32(20)
   491          	output := uint32(2)
   492          	mixin := 7
   493  
   494  				b.Run(fmt.Sprintf("in %d/ out %d/mixin %d", input, output,mixin), func(b *testing.B) {
   495  					tx := benchmark_TX_Verification(b,input,output,mixin)
   496  					b.Logf("tx size %d bytes    %d KB", len(tx.Serialize()), len(tx.Serialize())/1024)
   497  	b.ResetTimer()
   498  	for n := 0; n < b.N; n++ {
   499  		if !tx.RctSignature.Verify() {
   500  			b.Fatalf("TX ring signature verification failed")
   501  		}
   502  	}
   503  		})
   504  
   505  
   506  
   507  }
   508  */
   509  
   510  // verify and confirm few parameters for the network
   511  func Benchmark_TX_Verification(b *testing.B) {
   512  
   513  	cpufile, err := os.Create("/tmp/cpuprofile.prof")
   514  	if err != nil {
   515  
   516  	}
   517  	if err := pprof.StartCPUProfile(cpufile); err != nil {
   518  	}
   519  	defer pprof.StopCPUProfile()
   520  
   521  	for input := uint32(10); input < 400; input += 50 {
   522  		for output := uint32(1); output < 9; output += 2 {
   523  			for mixin := 5; mixin < 15; mixin += 3 {
   524  				b.Run(fmt.Sprintf("in %d/ out %d/mixin %d", input, output, mixin), func(b *testing.B) {
   525  					benchmark_TX_Verification(b, input, output, mixin)
   526  				})
   527  			}
   528  		}
   529  	}
   530  }
   531  
   532  /*  specific testing if required
   533  // inputs
   534  func Benchmark_TX_Verification_inputs_10_outputs_2_mixin_8(b *testing.B){
   535  	benchmark_TX_Verification(b,10,2,8)
   536  }
   537  */