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