538 lines
45 KiB
Plaintext
538 lines
45 KiB
Plaintext
// Copyright 2017-2018 DERO Project. All rights reserved.
|
|
// Use of this source code in any form is governed by RESEARCH license.
|
|
// license can be found in the LICENSE file.
|
|
// GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8
|
|
//
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
package walletapi
|
|
|
|
import "os"
|
|
import "fmt"
|
|
import "testing"
|
|
import "bytes"
|
|
import "crypto/rand"
|
|
import "path/filepath"
|
|
import "encoding/hex"
|
|
import "encoding/binary"
|
|
import "runtime/pprof"
|
|
|
|
import "github.com/deroproject/derosuite/globals"
|
|
import "github.com/deroproject/derosuite/crypto"
|
|
import "github.com/deroproject/derosuite/crypto/ringct"
|
|
import "github.com/deroproject/derosuite/transaction"
|
|
|
|
func init() {
|
|
globals.Init_rlog()
|
|
}
|
|
|
|
// this will test that the keys are placed properly and thus can be decoded by recievers
|
|
func Test_Creation_TX(t *testing.T) {
|
|
|
|
temp_db := filepath.Join(os.TempDir(), "dero_temporary_test_wallet.db")
|
|
|
|
os.Remove(temp_db)
|
|
|
|
w, err := Create_Encrypted_Wallet(temp_db, "QWER", *crypto.RandomScalar())
|
|
if err != nil {
|
|
t.Fatalf("Cannot create encrypted wallet, err %s", err)
|
|
}
|
|
|
|
defer os.Remove(temp_db) // cleanup after test
|
|
//sender,_ := Generate_Keys_From_Random() // we have a single sender
|
|
|
|
var receivers []*Account
|
|
|
|
randomBytes := make([]byte, 4)
|
|
|
|
for loop := 0; loop < 10; loop++ { // run the test randomly 200 times
|
|
rand.Read(randomBytes)
|
|
random_inputs := 1 + (binary.LittleEndian.Uint32(randomBytes) % 20) //minimum 1 input is necessary
|
|
rand.Read(randomBytes)
|
|
random_outputs := 1 + (binary.LittleEndian.Uint32(randomBytes) % 20) //minimum 1 output is necessary
|
|
|
|
if loop == 0 {
|
|
random_inputs = 100
|
|
}
|
|
|
|
if loop == 1 {
|
|
random_outputs = 200 // randomly place 200 outputs in single tx
|
|
}
|
|
|
|
if loop == 2 {
|
|
random_inputs = 500
|
|
random_outputs = 2
|
|
}
|
|
|
|
txw := TX_Wallet_Data{WAmount: 4000000000000}
|
|
txw.TXdata.Index_Global = 739
|
|
txw.TXdata.Height = 730
|
|
txw.WKey.Destination = crypto.HexToKey("dbdfd2a3e9da6911b0a3e37e8e448f2de2477f81760585c2f197736bac127e0f")
|
|
txw.WKey.Mask = crypto.HexToKey("01e4e85ab0b5e30dd86b5356f0f6b4177738b9e6b32041c4e4781a2f26083101")
|
|
txw.WKimage = crypto.HexToKey("d8fb3b4260aea6582400a5f48244ff3f7c4dc36420698e3decc5d28ba04733c2")
|
|
txw.TXdata.InKey.Destination = crypto.HexToKey("ed0da9e74d240088a07909ea354b8d140b753642e25495e0931b4623b25ff523")
|
|
txw.TXdata.InKey.Mask = crypto.HexToKey("dbddab6c6b3063074e7cfd1a7f83f184ad78e92c8ff25118c0ed4edc77015948")
|
|
|
|
var outs []ringct.Output_info
|
|
for i := uint32(0); i < random_outputs; i++ {
|
|
r, _ := Generate_Keys_From_Random()
|
|
receivers = append(receivers, r)
|
|
|
|
out := ringct.Output_info{Public_Spend_Key: receivers[i].GetAddress().SpendKey,
|
|
Public_View_Key: receivers[i].GetAddress().ViewKey}
|
|
|
|
if i == 0 { // balance the outputs
|
|
out.Amount = uint64(random_inputs) * txw.WAmount
|
|
}
|
|
outs = append(outs, out) // fill outs with random address
|
|
}
|
|
|
|
var ins []ringct.Input_info
|
|
for i := uint32(0); i < random_inputs; i++ {
|
|
|
|
ins = append(ins, ringct.Input_info{Amount: txw.WAmount, Key_image: crypto.Hash(txw.WKimage), Sk: txw.WKey, Index_Global: txw.TXdata.Index_Global})
|
|
|
|
//now we must the ring members
|
|
ins[i].Ring_Members = append(ins[i].Ring_Members, txw.TXdata.Index_Global)
|
|
ins[i].Pubs = append(ins[i].Pubs, txw.TXdata.InKey)
|
|
|
|
// lets add 5 ring members randomly
|
|
for j := uint64(0); j < 5; j++ {
|
|
ins[i].Ring_Members = append(ins[i].Ring_Members, txw.TXdata.Index_Global+j+1)
|
|
ins[i].Pubs = append(ins[i].Pubs, ringct.CtKey{Destination: *crypto.RandomScalar(), Mask: *crypto.RandomScalar()})
|
|
}
|
|
}
|
|
|
|
// 739th main net consumed output
|
|
var payment_id []byte
|
|
|
|
if loop%2 == 0 {
|
|
payment_id = make([]byte, 32, 32) // test 32 byte payment id
|
|
} else {
|
|
payment_id = make([]byte, 8, 8) // make encrypted payment ID
|
|
}
|
|
|
|
if loop%3 == 0 {
|
|
payment_id = make([]byte, 0, 0) // test with out payment id
|
|
}
|
|
|
|
bulletproof := false
|
|
if loop%2 == 1 {
|
|
bulletproof = true
|
|
}
|
|
|
|
tx := w.Create_TX_v2(ins, outs, 0, 0, payment_id, bulletproof)
|
|
|
|
if !tx.RctSignature.Verify() {
|
|
t.Fatalf("TX ring signature verification failed")
|
|
}
|
|
|
|
// now check whether the outputs can be verified successfuly after serdes
|
|
var tx2 transaction.Transaction
|
|
tx2.DeserializeHeader(tx.Serialize())
|
|
tx2.Parse_Extra()
|
|
|
|
public_key := tx2.Extra_map[transaction.TX_PUBLIC_KEY].(crypto.Key)
|
|
|
|
if len(payment_id) == 8 { // test whether payment ID was encrypted and decrypted successfully
|
|
epayid := tx.PaymentID_map[transaction.TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID].([]byte)
|
|
|
|
derivation := crypto.KeyDerivation(&public_key, &receivers[0].Keys.Viewkey_Secret)
|
|
payid := EncryptDecryptPaymentID(derivation, public_key, epayid)
|
|
|
|
t.Logf("epay id %x decrypted %x", epayid, payid)
|
|
|
|
if !bytes.Equal(payment_id, payid) {
|
|
t.Fatalf("8 byte encrypted payment ID missing failed")
|
|
}
|
|
|
|
}
|
|
|
|
if len(payment_id) == 32 { // test full 32 byte payment id
|
|
if !bytes.Equal(payment_id, tx.PaymentID_map[transaction.TX_EXTRA_NONCE_PAYMENT_ID].([]byte)) {
|
|
t.Fatalf("32 byte payment ID missing, failed")
|
|
}
|
|
}
|
|
for output_index := range outs {
|
|
|
|
tx_out_to_key := tx2.Vout[output_index].Target.(transaction.Txout_to_key)
|
|
if !receivers[output_index].Is_Output_Ours(public_key, uint64(output_index), tx_out_to_key.Key) {
|
|
t.Fatalf("Output mismatch index %d", output_index)
|
|
}
|
|
|
|
}
|
|
t.Logf("inputs %5d\toutputs %5d\t tx size %4d KB %+v", random_inputs, random_outputs, len(tx.Serialize())/1024, bulletproof)
|
|
}
|
|
|
|
}
|
|
|
|
// this will test that the keys are placed properly and thus can be decoded by recievers
|
|
// this also forces the ring size from 2 to 10
|
|
func Test_Creation_TX_Size(t *testing.T) {
|
|
|
|
temp_db := filepath.Join(os.TempDir(), "dero_temporary_test_wallet.db")
|
|
|
|
os.Remove(temp_db)
|
|
|
|
w, err := Create_Encrypted_Wallet(temp_db, "QWER", *crypto.RandomScalar())
|
|
if err != nil {
|
|
t.Fatalf("Cannot create encrypted wallet, err %s", err)
|
|
}
|
|
|
|
defer os.Remove(temp_db) // cleanup after test
|
|
//sender,_ := Generate_Keys_From_Random() // we have a single sender
|
|
|
|
var receivers []*Account
|
|
|
|
randomBytes := make([]byte, 4)
|
|
|
|
for loop := 0; loop < 1; loop++ { // run the test randomly 200 times
|
|
rand.Read(randomBytes)
|
|
random_inputs := 1 + (binary.LittleEndian.Uint32(randomBytes) % 20) //minimum 1 input is necessary
|
|
rand.Read(randomBytes)
|
|
random_outputs := 1 + (binary.LittleEndian.Uint32(randomBytes) % 20) //minimum 1 output is necessary
|
|
|
|
random_inputs = 20 * uint32(loop+1)
|
|
random_outputs = 2
|
|
|
|
txw := TX_Wallet_Data{WAmount: 4000000000000}
|
|
txw.TXdata.Index_Global = 739
|
|
txw.TXdata.Height = 730
|
|
txw.WKey.Destination = crypto.HexToKey("dbdfd2a3e9da6911b0a3e37e8e448f2de2477f81760585c2f197736bac127e0f")
|
|
txw.WKey.Mask = crypto.HexToKey("01e4e85ab0b5e30dd86b5356f0f6b4177738b9e6b32041c4e4781a2f26083101")
|
|
txw.WKimage = crypto.HexToKey("d8fb3b4260aea6582400a5f48244ff3f7c4dc36420698e3decc5d28ba04733c2")
|
|
txw.TXdata.InKey.Destination = crypto.HexToKey("ed0da9e74d240088a07909ea354b8d140b753642e25495e0931b4623b25ff523")
|
|
txw.TXdata.InKey.Mask = crypto.HexToKey("dbddab6c6b3063074e7cfd1a7f83f184ad78e92c8ff25118c0ed4edc77015948")
|
|
|
|
var outs []ringct.Output_info
|
|
for i := uint32(0); i < random_outputs; i++ {
|
|
r, _ := Generate_Keys_From_Random()
|
|
receivers = append(receivers, r)
|
|
|
|
out := ringct.Output_info{Public_Spend_Key: receivers[i].GetAddress().SpendKey,
|
|
Public_View_Key: receivers[i].GetAddress().ViewKey}
|
|
|
|
if i == 0 { // balance the outputs
|
|
out.Amount = uint64(random_inputs) * txw.WAmount
|
|
}
|
|
outs = append(outs, out) // fill outs with random address
|
|
}
|
|
|
|
ring_sizes := []uint64{1, 2, 3, 4, 5, 6, 7, 8, 10}
|
|
|
|
for x := range ring_sizes {
|
|
|
|
var ins []ringct.Input_info
|
|
for i := uint32(0); i < random_inputs; i++ {
|
|
|
|
ins = append(ins, ringct.Input_info{Amount: txw.WAmount, Key_image: crypto.Hash(txw.WKimage), Sk: txw.WKey, Index_Global: txw.TXdata.Index_Global})
|
|
|
|
//now we must the ring members
|
|
ins[i].Ring_Members = append(ins[i].Ring_Members, txw.TXdata.Index_Global)
|
|
ins[i].Pubs = append(ins[i].Pubs, txw.TXdata.InKey)
|
|
|
|
// lets add 5 ring members randomly
|
|
for j := uint64(0); j < ring_sizes[x]; j++ {
|
|
ins[i].Ring_Members = append(ins[i].Ring_Members, txw.TXdata.Index_Global+j+1)
|
|
ins[i].Pubs = append(ins[i].Pubs, ringct.CtKey{Destination: *crypto.RandomScalar(), Mask: *crypto.RandomScalar()})
|
|
}
|
|
}
|
|
|
|
// 739th main net consumed output
|
|
var payment_id []byte
|
|
|
|
if loop%2 == 0 {
|
|
payment_id = make([]byte, 32, 32) // test 32 byte payment id
|
|
} else {
|
|
payment_id = make([]byte, 8, 8) // make encrypted payment ID
|
|
}
|
|
|
|
if loop%3 == 0 {
|
|
payment_id = make([]byte, 0, 0) // test with out payment id
|
|
}
|
|
|
|
bulletproof := true
|
|
|
|
tx := w.Create_TX_v2(ins, outs, 0, 0, payment_id, bulletproof)
|
|
|
|
if !tx.RctSignature.Verify() {
|
|
t.Fatalf("TX ring signature verification failed")
|
|
}
|
|
|
|
// now check whether the outputs can be verified successfuly after serdes
|
|
var tx2 transaction.Transaction
|
|
tx2.DeserializeHeader(tx.Serialize())
|
|
tx2.Parse_Extra()
|
|
|
|
public_key := tx2.Extra_map[transaction.TX_PUBLIC_KEY].(crypto.Key)
|
|
|
|
if len(payment_id) == 8 { // test whether payment ID was encrypted and decrypted successfully
|
|
epayid := tx.PaymentID_map[transaction.TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID].([]byte)
|
|
|
|
derivation := crypto.KeyDerivation(&public_key, &receivers[0].Keys.Viewkey_Secret)
|
|
payid := EncryptDecryptPaymentID(derivation, public_key, epayid)
|
|
|
|
//t.Logf("epay id %x decrypted %x", epayid, payid)
|
|
|
|
if !bytes.Equal(payment_id, payid) {
|
|
t.Fatalf("8 byte encrypted payment ID missing failed")
|
|
}
|
|
|
|
}
|
|
|
|
if len(payment_id) == 32 { // test full 32 byte payment id
|
|
if !bytes.Equal(payment_id, tx.PaymentID_map[transaction.TX_EXTRA_NONCE_PAYMENT_ID].([]byte)) {
|
|
t.Fatalf("32 byte payment ID missing, failed")
|
|
}
|
|
}
|
|
for output_index := range outs {
|
|
|
|
tx_out_to_key := tx2.Vout[output_index].Target.(transaction.Txout_to_key)
|
|
if !receivers[output_index].Is_Output_Ours(public_key, uint64(output_index), tx_out_to_key.Key) {
|
|
t.Fatalf("Output mismatch index %d", output_index)
|
|
}
|
|
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* Related logs here for improvig the test here
|
|
*
|
|
[wallet dERoiV]: integrated_address
|
|
Random payment ID: <0cbd6e050cf3b73c>
|
|
Matching integrated address: dERijfr9y7XhWkdEPp17RJLXVoHkr2ucMdEbgGgpskhLb33732LBifWMCZhPga3EcjXoYqfM9jRv3W3bnWUSpdmKL24FBjG6ctTAEg1jrhDHh
|
|
[wallet dERoiV]: address
|
|
dERoiVavtPjhWkdEPp17RJLXVoHkr2ucMdEbgGgpskhLb33732LBifWMCZhPga3EcjXoYqfM9jRv3W3bnWUSpdmK5Jur1PhN6P
|
|
[wallet dERoiV]: help
|
|
|
|
[wallet dERoiV]: transfer dERijfr9y7XhWkdEPp17RJLXVoHkr2ucMdEbgGgpskhLb33732LBifWMCZhPga3EcjXoYqfM9jRv3W3bnWUSpdmKL24FBjG6ctTAEg1jrhDHh 1
|
|
Wallet password:
|
|
Sending 1.000000000000. The transaction fee is 0.049197120000
|
|
Is this okay? (Y/Yes/N/No): y
|
|
Transaction successfully submitted, transaction <ea551b02b9f1e8aebe4d7b1b7f6bf173d76ae614cb9a066800773fee9e226fd7>
|
|
You can check its status by using the `show_transfers` command.
|
|
[wallet dERoiV]: get_tx_key ea551b02b9f1e8aebe4d7b1b7f6bf173d76ae614cb9a066800773fee9e226fd7
|
|
unknown command: ge_tx_key ea551b02b9f1e8aebe4d7b1b7f6bf173d76ae614cb9a066800773fee9e226fd7
|
|
[wallet dERoiV]: get_tx_key ea551b02b9f1e8aebe4d7b1b7f6bf173d76ae614cb9a066800773fee9e226fd7
|
|
Wallet password:
|
|
Tx key: af7b5a4e75410d585e7faeb254811ea1274f6d20205205213c79655fe3958c07
|
|
Height 2175, transaction <31b019dba022b29f8342f71c4ede91fea8b4236469786ae55f131af3432d3989>, received 31.661910734197
|
|
Height 2175, transaction <ea551b02b9f1e8aebe4d7b1b7f6bf173d76ae614cb9a066800773fee9e226fd7>, received 7.862704628648
|
|
* Height 2175, transaction <ea551b02b9f1e8aebe4d7b1b7f6bf173d76ae614cb9a066800773fee9e226fd7>, received 1.000000000000
|
|
Height 2175, transaction <ea551b02b9f1e8aebe4d7b1b7f6bf173d76ae614cb9a066800773fee9e226fd7>, spent 8.903560733970
|
|
Height 2175, transaction <ea551b02b9f1e8aebe4d7b1b7f6bf173d76ae614cb9a066800773fee9e226fd7>, spent 0.008341014678
|
|
|
|
*/
|
|
// this will test that the keys are placed properly and thus can be decoded by recievers
|
|
func Test_Creation_TX_Encrypted_PaymentID_check(t *testing.T) {
|
|
temp_db := filepath.Join(os.TempDir(), "dero_temporary_test_wallet.db")
|
|
|
|
os.Remove(temp_db)
|
|
|
|
w, err := Create_Encrypted_Wallet(temp_db, "QWER", crypto.HexToKey("aebcbe93d8c386f954bbc2122d9d6423b7e5b77a6ad31c6a43855e15b9760c0d"))
|
|
if err != nil {
|
|
t.Fatalf("Cannot create encrypted wallet, err %s", err)
|
|
}
|
|
|
|
defer os.Remove(temp_db) // cleanup after test
|
|
|
|
tx_created_by_oldwallet_hex := "020002020005e1e401c90cc556b33ac30ec7b09288b613edc262773a1b426e244288bd7083703f64f9ba70932c005aab8a020005a441a597019b06db9001c90fb8b71bdf1694dc432a501201d9072f03b57e8ffc4b438525874af2b19fbefd7402000251ae0c92c8ef002020afe3ba8bbc7893a3421dd766aac5e4838c96e44b1aa9390002d0d5b3be45fada433dcb2787e289ac5656ec24f8c595ad5b761629b9d5204d172c0100e7e904ef8ceffbb04e14bb8bccfcbf219e33e878cc8087120b751b5c5423a502090112ac08ed7db374000280f481a3b701b59b6e44f8868a02e0b83eac1bd1101cd10f2ff62e2aaadc6d6d38ad6d4dd90a5d81bfb3011eef1b918ba1229e701c5baa418c0ce329f2aaba840da4ca46cfc40e957969a0890f12e82bb6e4cceabfd47f6d1d67069b52c05fc474c117a6510d3ba22a6b1d7313ed8bdda62a193e8638e1ff94cf3fc51bbb4421d1749c259b0c0f2b1aa6e3389e50d25f178240c7ca0ab147218971ba8555767debff4ad6ae001e8d5d500c9c7d49d8b00c0101e4ec13d6eae6c91df46fb082d4165016b2f00c2529d45b561bbecd12e84ec45df0cfc19572da72af45826d07cf9ff1c227cbcc325877b202c7b4cee9c31e8a39210aa2286e52b8d16bf958615b466c267168de6c7995854d27f455c0277adbb041961c201a0d0860e856f39fd311da5f748c0c382133de4fd17c7ab44d559b03f00fe10345c0daae4c9f1fd8c08f1f6e117d02830e8698e536b3d5bd1df197a320f0811456ba0d28e071b2eb75f5523c8635074ae15c24dd59e20912fede1af9297d7035524b43cde58f825bc8ed2659ca530594a064a249582416f4c93692cfd44e9668a1e928a406e54d8f891070e25d5f09e33fc80eec42a5599c1814f5b9361d698be0be4fefebc1a9a3b814297823a10fb885e33117ad8f48ffc06e2ddb071d14df5917cb4520e69a962bc6927eea8b0803e52985f168c655f79335687ead4e82fcd720c8546e42a76212c8c7a14eda016c4f6edcb3dda0aacb66e6e1dd777d689bead64d8d48af1d74a48d4213aa460e38255a3b73914d64a12d1e2044989f8862fed2be7a0a625ca0ac26a371affa0830850aaaadce63edb3db84f96aff87bdbd74e75c9aac4f5a83450595d47cd609acee9d3c3c3db7c774543e49d498e63932fb61a71a4b2753806b0f8558f40401a1f0454b568a923f59e5f85e6f33eb71fffa6300667fee110cf86276d80d510705a491bd7c86c317adf3b94353751602ad493f1ad4d627936b990bb74579230c5644c9085346a8513a21fa73b0c06b145ae02fe2c2ad619e8843a7e32d81210e3e4c6d05756b10b5aa0b380a18adb30b64a28f58e5c27892922866beb2ae200e9c9dbe6e62bbd5a21119ecbb1571aa918e8c7647b963062dfb6e38bab61dfd04b5deab8a7b24223541d3d05a3dd5e9f15e134036befc0778ee89ca65cb025b0e02e8d92c10725152b50f5d242238f15fe81eafdbf79f3144442648109dee850c167b7915fdd9f996f4890b2ffc30b8c0fec3a1d30f7bfc3e5a21f93f15d7fa066375326b30fa277de3a1d6b55b0bc5435044d99b8f0b3860ca0378e6d747420eea963f3f34ab1a6283b0338fbc0be94427a3f3ef8121282b067c82f416595c087dec5aad206c426d758016f240c1fffe5b3fb053d51a4fe795b09a80d409fc02fccdc92c51711c67a6b79f3c4a4bc87f006af05125de1eb8cd0f99929d205b03e8828db099b7c177f033abb297840dcd7d8723a61c3f96c6e3c348b973992b0f0bf5fbb3dda2eac764462c289b16dc01578b46e642a9678b4edf6f5625d28e0338b8c5a331d703fdf0595e40dea4d6e845110dfeb7a649712871a08dbe99f7054251821ac6634ca4af736b479eedb2fd87a566cf3990fb1591137f71834daa0b350c7041ade602a375a530b129209a9af357e3a0485124d049d1b9e1c906c804ff2d7566a2f3c659995dd65b1f42d943b65afb60210182749060e2709ed82902bb772e105f6730da8db97dc4203dd1833e9b24e64511eefc7a123616ff28130c5d48343f2defee057f65ef059df97ba01f4820275b90ad2458a7cc7fc568ff0cf28aa734aeaeedde3b5574c3819d020d725408860b174210ba7b67b2043d470e2ebe6f75631a1e2fad92968edb26a1adfdf06667f09d5e25ce6c48bb8368870185222b356126ee4de0c6728ace83226bc46b22a0f5ce0b81a8bf4264117830089ffaea98bf4326515d5f8cd0a44bb4af9a29c3756db5eb3453b282d00a6a1d06654623a5db5693cd50fd087e9422f235414aaf8ce6d26a064090969f5f49950cff06838c3947843384371769d4d51326447b5da564b36f5c256e7348f08c140d91231c88ef4ec28ab90789b8054ee972b48bbe06bb7556dbe0ac59ffc7d02b0c90d2ca7476134d2504e98eb548ce35488e976de0c565b3035aefefbf11b9bb034f8df46a67e2cab724f268fd3c5b6797fed72bc21fc373cba12e3ba1e9a54d0690481fbd521765fb7bc392680af376a366276606140f2e47903b9905ea0aa806662d23ce1154a2f0c35e5139874e9c58caff19a6d5eaa9a7aac5f30378f59f09df16f5102944fd5f9fabd39f6fd2b166dea8bca27c9e6df7c163f00b01cdab08ebf5f9559a0343af81edb0f7e8b0aa14314a7ad47a9a314421457e3b765d1d07ad5868082a6bdabdbc12c4eca16243c2fdc25c3b3f4566d6169b6c9cbe50320c16fd4e319fbe81115d9d041f6c660f8dcb418e07e58e6954484e35886f1c900c8dfa96b696cda674d0dc359f45e9502b9e13b26bab8100e8e59f75f13973460e2eb4ae27ca2359bdca01146a827caf3b6456774b663772ba928026035097cd0ef6a36c29a8ddf0aae7ab73cb5c7a54e3d5ac29c567f7f0e4f62c1ccc573619097af06f54b11d61789cb3a201402897a1fd7f1b1a7036bf81accf855275a3750012787ffd6765fb2aa233da254a48157a9bfd98b94d94a27a9e0b15409cc54603984306c538ec059976493f6f02c78f9b2a4874c521cc37e1b82ff68d32a60206f4ddadd7f07ecffceb55d355df73e979b561143627e681135d82ae741927ff0f3681a911039cb17a83ca7f54a11612df310da4b243244bf1af9f6ea5027ac60f65d8ac2989bf1d168bf0c3692a56c50b979f5176cf6fcefb5649cbd44373ff0c5e48c06e3eefa529b8e18aecaf9176da55983d9d01f33b7edb12c14ded2aa30e2840d3761e89049fb4fa4868da15f4e2d2b259a306f3d1feafe95dc90f523e00f80e6f768657a5286c07488be27b37469da220ce25361731220de79a43d90804214a15bc3df72be15d51304192b7c976dea4c83022be3687e1b3dd0963fca60707ee92a8146f62aeab50295abab5cd8e8975c31cc24ea6dbcf8eeed13f91fa092718ca814e0db51c4ae062d89acfa6371878aea03200fcdf1bb8f961ce8db205f68186b764292c179405612756a6d947f1c54d328f637baa18b00e61e18866070ee4b035592239213af9af8807063cdbdcb3e8b75453a1d6232a72b8a29d5e0e3c0faabf6d7593b59224de0d11404d0c80c2a1f4fb90a710e875998876b3fc06cb7343dea2bb44f6cc9c4b7749e3510af93cc6d40a06dcd02c53c04eb4bdb600f6bf4f8a4daffa86df3552f0f49fd16eaf871300c147da6a807c66cf85ac1a0fcba6552ed0288a09f80afcf345091dce0668608269d0281dfda5891fe3673e0b13ad587a28e40b0d324b3cb32571c409f1973767ec8fc1a85a979e4ef6fa710fd9bc9eda5415a7cc222660ef3b2de88d4dfc2be0299c6a5a161014c5fd839009dbf49ea12acccb02c364996a180499758419aada52c6905bd22588b0d584f402cab44b38b9ad7a076195845be09e954c797032b2075ba654b6e4162fad099106cc19eaa15ca75be4a1cf05a8ede3476370fcada6f9a02b0123effc3b717ad90d8cd0d66ccfd81131871ee1a31438d4f157096b96bc0872226440378b513bd5061fd447a2675fdee9c40b61730fdcef11f2e226609be1c95867de217c030c20000d00dcb15d851540ca156aa97c2638ea4d4741792b78aecca062d2b827fccb062db89590d82210f83e4ce320677483c9436672539562ab735fc8fd8dc72f62022c61968b773b08d9b3db1d84bce2d4c122d48b49cd34a7dbaf61d6b1e11e760ab8419c20f3cb5731fa53354a786689b759c3a4d8afea84a6ac041a4b8ba1f00097c1a0165f165f878786c0960fc13bb39f0cce7b44ca71dc9e177a1671858d0b66e609a9bba60a7c986190e3e2a8773876d1738ce81d90cb66c21de125df140aa80b44964ae09ce00e68fd4ffd096a48e4308e40cbe91aed2a6243dd6a93df0aa0f402652bcd90d29f99c1ec898e54c5287b0812c57ca0857f8971b2510b070f07e3678efcb2720b5afc337d18c4811d2539cea86c2ca55159675eab633e83043ab3d55a2d5e4d5961c3032693c0f78e7c4a68e4c9471bc18fdcd139cf70410967969d1fa0f83c605435dd1ea248e7f2cc57b2f09d76bbb68d45a9a85815320e6e3197453359380be2de9f73a100338c3ed46f829ec37bd55d6377e5b949590e68613db566b3c865786b8c39bda9149f02908340aacc19cd955685fe4e5ecb047f2360136a951d395253949907ca53b48dcc6ead43c7856090c957b834e24200d0271a3fcdaa129c7e1cc34923b0f391c542ad4566b0dcc26da11b4b29218504b5c3c1098e6341d59f236b16a6822fa94a518a17d6e2351ff6e6586bb40a4a06fe8d91842ebd4436ea68468619591a9dadd561b0c74bfc81946118671fb9260237a3edf343dde6586d15841fccfcf8c7d5db52edcfb5ccb3ddab117ca4bc530cdc44706da87c67a3e4b204d57d5c7699fbf99fb1bd733bf94a587ef82260de03d388703f7bb57614964a7dcc7688bb189108ed5f95f30b2aaae6f217dd3a9904bc718bc6b2a90584154a840bd299e510a60b93be2141209c15772bb1cc9c4807ddba42618c949264d68122c1889e1e96c602c5b39c190a886cc256364aa6c407c5d3d64ee3d90166ab5fcf9e43a140abb306932e9c32d5ccc7da2f8d8b59420d0f42a9d76d490f451027718d1957e18cf1834cd842f8d3c8d28bcf016e92db0b71d33d0e536a975244843f106c29cd1ec11b4a255c0ef97497a89e6635bf23077a9f0306a3414e8cc8e413a1e75ab63b5e4fd08cb3623dbb902d5731138d6908364ebc3aab512e3ad2418629429d850b3184b9944b798bfcc8e290a89d249c0eae116845a58cdd1e57d94513c1ff9601a58f6a29ddc9dec840830055b781c102c8db3c274405a8ee09e90c2b80953a216e888049358f0b0a8734d541542631082ef80a71f724ee8ccccbe6d8e2797ca1421b83309bf11679661720beeaaf650022c25a0c85b61a3490da381902283d90da00ef3dc4dec533ae9aa4ebd549ca057530cd8ba9e94153cead886e2bd96549364118da7c4f5e3a7167c9b6b4f809013e484edd1026e1f4b8f3385c89ff09682a5330c1361501839ce77a74cd57e20f49241791a253298e617c247ab775ddd18a8c885f8b0aed54dbe83b965beeee0a6d9221b4783162c6f78ecce329fb34395d21ff4351e05cfcf66ada52a25893037318678eb369a195d8c930a6694e627bfb6fbac4cdc9f6edf32c1a3d5b0bd0007cc86b4eed1fd1eb1d4460abcd1571fe7b70fcb7136c10083a22c15a73908d0885849e64dc3cbdf6897c506ada60aef3dd7f6f49336539a3534a2634b83b5f07f7ee7b9a3fc561f921a8ff125aa5066d772b43c099ce7f360fcb493124421700ccdb2403dd6f5d54408060694bec24e6e5e2655591f3bb8e1d6d6c056520ce032bbc0d556861c0653628b704b412fff0ac8378e9e14fc7f9559f7849addc0d08e8e979d5d3bf843bd4d1653799dca9196f78785eef10fa0e5512a6523917840a8fbeb567d5b5921080c8f51b77f0b870a9f906d603f1d95c9ea98b5e6549df0ce0d1c5f8604645514332a67afc858ce12363d6628c5161ac79332cf9cd252704b9122ff819ddae716b3be84233bc318d5b759e2929097d13d2fdb5bf83e8150eb41ea13c7536d9448c7e410a8790684f504aa096d26dadee07ae3691b1365c0df88042c0b86ddd14083dee24bd0e85c410c060d2175dc376728e705221634a0aa3684c4c865f897aca1d7e67a5ad5c29289f225820ba71bfe9568d6378998e0ba0fe7ff5af2f0146ffe148aa92d70ec1f95a114ef174cb68963466628d45e80c6de541461a9c36878d70c1d2db40e0aeae5067ccad9b74489a68940da9b23007c7fe69ab0f14c808a926f87fc00edbbdb2e75408be55111bccd7009ee2082a000e56a69361181a3b9b54b2d9efc1af426a1eea5af2a265f7b40615a5aba10802344f7f27b7cfde36144d2194599fd92926bfbec41d315439f8bedf8783dd3f000a417624f374d6f9d2c0e2fe582bdfeee542440cfd1f28501194c5cdf2f46b03c2b9a77490ef03d3fd7de695c604e249c6d7f76d60b753ce485a3431caaecfb037673603b148927ec4e82b32a04e10d81d67c876d484cb8e3bc3025875c57b1aa8b882d37ef88750acd5b31d2a399a857ce650f2493e2a28f90455a91b8cbc0bf3e39bcd3ef40972f1f7ad3a7d3c14354a78d233a02a865dd279fe1cdca02b4d114b421b98923bf142f91f84c572d5a4f03687907f4a94fc0ac81116fc88adb797802897c5c5852738a01b0e879d94d467faabd200c801bb501b82b9e36f2047311a8492a56f768e96666b5163c41aa0caa6a2ea9f4923db8d538dfa76f313361f7034fdbca17e8d9f4daa9efc0443c6e2be8007337a230ac1f3d152d4ff1f2bc44f523880d409bc02ee558bf55c7c45b3f7ff0a4651e4949022574f72c3d9ce5d5f037e28283d363bc8d15e802dae79e94b14917d9eca092b1749bd5d089b74699c78de561c49320457937f269019945903ae650f353fe5693031fa8f137a8c01ad038d30629b84f5a7f7d9d2d11cd163ee68908fe402cdfeeec495e13fddbc0d875647456be2e97c771acc6647441f4836de1c7639a269e3a843e1885541b69b79047b4cf9b83caebf81a4aa039e7230d4f1d9c0fcf37b49c86a99e3bedc07caf6c4eed50f789cebe8c8654a8463e409ac88b5425da8596cc84f3a2973235d6cb8e4a51609ec52ccbce5efbecac3e440c68662ea358293b33798aeb2ec052af99b3f4ad0d4e6772d00e1c8bf45b160f0d2b621c8287225f7597647cf4725094f0bb80b225a41fb1387a1d1eb4ceb2ac96653a8e2b1d0136ea6c37a726deca40d4c6a23bf5b15a62e9c63539dda2f22c02a7f1841a3adf4b715ddd76cab9e6cd51bc81cc8570e166315b655e10ac67e57e69002cef6786132b8b8d10922d24ed2823b2cb86053ab67eeaff86ad05db14379e1cf174f876c175ab9e0850a13cb19b7b8416938789c508363d25910658705c4984cb2612d84f7d5cfc16c94c34f70d6150a6bb3de645bb308419d9854b8e8f768d23b7475e0198f8a894e0b6700d21a879485990680290b124712fa3b673ff79e0e5388e1132d66cd814e38c08678086c277e4ecf38bc16032a5ef644d3e0744ed56cc2a7e443be94398ad7d21bd305203053166a18e5621b785a2f3cf7271df5c1807255ed26396d95c72a46cb1dabc54ab08aa8f29d210ed8a2be60fa701faac646fb630ea032f524fa50ad6224dff2922a1bfe7f6c5ed0e259b822add3c6245d3800f8408190979d462b007d3efe1208e4408b96e8ce41853ebb0c5aeddebbfffbe824a8401a04327c9aad78c19b63573b3a1b478b8304eade4207baa53e1b53df94a8b289887b2930ab56f781a53bc11a32771e629255bfc9692aec628ea82b879efd9926e62a462326b8aab1f320dbcd07b0230ad7ef3d860aa887df73bc7252ccb6681caf8909385ad6910514018e9b31fa1886d75a7eb8e1af3d54c4b514eb7cbc988eccab78fcdd174147927159786a951014151d9499dfeed70bb7af725a496e3f5d53176bfb03f00846c6282ec844033cb430c853d9afa3bf4301b8502c942e635479595900130cb5983ba46bb5b7440064dde0f0a7bcf4e0b446be43eb32bfeb3ca35108888682e82517e484c50504a9885ba6f2c35a0d1037909f79626d268c3badd147c88566eabbab23f387597c2e7be55625d948f459bdef60e6ce1fc308dc1e710b6d4fa526b301dc81f3584713b0462ff5eec895079558fc8d4bff1565f636188037a1a285b2bcb96f2fcf60f418861812f71e4a73121198f44dd43713280ccb1c5c3cba886d63b2d26c5d66c02c796decb9db5f6c798e9a7a320f1e966a24287075629fff94a6b5d770aac8d8f0120d120cbf93c79e394b33a5f488ffa74bba43729152367f3e2fd42c04c0291c19ed2369951783853bf9d2145a3a666f8aa9a3f1ec22ae7fbc1409220ca8dab721eec732765be8a1af553586bcf765229279fb01b2d9f0cbeb66bee4b7174af9aa1726864526f0246297480e0517439b976606f2e640bce589582cb8e1c67023988506a1a7658dbcd4778e760d3eb844bf537801f7b40a7753b34b1be0623cf2e06c48eabacd99bb9015f1ea531f76a0c760d7916ef474cd9163c94e8b47b987abff2e4d5e5842f1b4915a0ce78b8e7a492a77ad7a72f68c2f2d60f6db88568bf6203aa16f6847499f830236cdd735d4400dff5a596f249616fbfe000a72c935f4d4d8329756cf47ca8ebb40d011004580dcee43ba4d95a8de2b84e8183cc0bb885155f7bc2160cf53fac00b89746ac3f1b20ca5532c0a984c77f48c6d809dcf285600ae59650fcadde1b6a3e65478539ca5aefd5fb99a841dc4faead2bc1df55affff321fdb3be2665bc8b33b0bc0dca76984e5c4b23ef387d027f76dd8918a42eea685f992a0bd2ccbaebe948bd0dd528fd0663057d84f4ef2b5f332e054300cebd4bb319a68b3309176f0072eee797f6fdd628639a652629fee51dcf8d0a5c0cce7a094be9395b1d5367b0931da666ca49b954715e9a778a7de10faa3c1a14158bb27c384d3ca7346ef7904265887810b99cc765eda9e12b8d94821e30b25b952d4a359a95747840f72d9fc99e6a1f1a827785ba33317e32c9601c447f01afd7fc15aa169c3309f388280290840e80c7011f056771f7c41a0013d34a79040cc01b2d38564d0ce590a27834dd7987ab0910446f159f0669cbbe21597f6854c65d9e209f87b270ba9e80adf8a783d2e937a71004231919f51b7132c305df44472541cdba5b1dfc9d027d0534b15d34002d38bbe884a653efbd9b561a95c8a135259698ff58e7f61573b75249483929273eba508d6012156671b6b36d8ca337b89ceab7991187423549ccc47f3a17225a8e245e2e757b7aca85236b3e60028ef059c5ae5733d76ff94bfd632880e799ec1e4c416a9d70b49be502ecafa98a0503b260f20f25c5cb944a0bf392690cbd179c29a225347097fbb2068b6a4ffc0e4f6d8ec73594477dc39a09db2c61e667f5911d5974eed063396a99a6b53f997a2850dd75d0e2591b95a3a4df95d293810e8e15bd47c6f0a39d39a8d3125e8c0a76239f4da1d9d8d26ed9e099613476e5ecc9aff7bcf4e091965d6d5f83f0be398f6000db698c12e55e54e4efb1bfd1179aa389b9e686e09b0b946a3c0544b85c0d78ab29d97829b9d915b463fc62e02bb7542844a455d02dc0892698ca33ab73c8bc63cafa3d0f403cb9e56e53bb9ed1ab06f03b9bfa102481d5253b676537363a738fa52dd261d69a578eea5386bdb42c4d3b5d39ab900e2d7f61528be9c21a8344290929deba0dd5a9d0fc6cafd7af6a03f232bf60b070e58bcad85e5c703f510d23a7419e1cf51851d70897c0c5630687018ea61d50d3c43c93f3ff79512b7a8f9f8e19975673c84cae98332f28fb7ef0003a5b8bb0d37541b590aff9729579e5d41c4ce2ff85ab934f949a17dd05fa5d9b2bb15260be16e17f7224d25e664783dff790c6d8d128b1f4699d15482733c30e7243dc60127395cd884deaa8969be08024ad4d198e04378e4d67c95404f0486d6e5407f02a3f27e4ff0270d0928e2952dda25470480aa8bc13ee8483182c0358ee9ebeb057535449a6138d6d61763e836d4e885630c3737952ea3dd0a8679959113065a0be98b561016b955e920d98f2bde91616e1378bd3633ba09d6803f5e195d9fb0013e55af6e7c13bfa72a2d902ed84485727ab1bdee97adcf70daa92d1d464f4408254571c910dfa6784498343de84fa9c86f8f8aa1ad6c0d7f564307378d8d700a1b6a0bb2775fde72cb8c87cd9cc4fff96ce748a958f61e7c0b74632a9ce69203801d41e2f3bd178ca72a219cc39bee461b96b3973d89010704586d50e66ea805ecee56b233d7bc4183039bf669ebcb33f68bc89e4ca150b3adbb61d8f3a1e5072adc15f9794166bdbddf3c81591bf1c6a4319fcb0adef3c4714247ed8682e10faf05dc4fb304c516cfb993b4a371f7a6936b30020ba44ac2a3ed32a14580c70424d08cba2ac6177543922f06d2a9649b7c20a455e00df2e5f57fae24dfd18008828c90b0ae93187f03a63626a9c69b8222a2bc2eeba6ed5f1409dfd7dc8c330c26c530d279d560bdfcc0b367311bf4c4667c927e70dd881e4b9e46711644c80d24715eeaaecef3c8eae0000c01f9aab65d4201ad001d5c50a36bf91da1da81025492e6d3ea1911e2d574c04da6fa59ec47dba9f6447fa6d46785b32f074b26005ceb368b195c894f0442e38f8df547039a68977542d110e0914bb00f7e0ef3066c28752203cd79db2a39c30c681acf128721df5447921876e0c5e8e6ca6bf4037401621e738a258ad59e78e009d67069d628e2c176f2db95b103cc6992511302526a2070056abe08b1c82bcf75c035128e804b4bb17e47141d626dbe5fa93e0b4760a1dc30784fa63d3cce1490dbb803e0ce9d0a57e3821248bda37b3e02ac0f66fd92a920b3cf5f3d1d8e268437c8c0d919e670b369b89e7c469334b6618d00ce048dc0e826ccff3f265dc35c38e52f88b5326aa428799d96710ae7fa42040a73c8bd479d85402260afedd7032a01a2de2778ffcadd3a0c73f63b871cdd4e0a2d54a2153e3a68af5dded4de8e3a2e3f5db23db3e7a9362cb5513d0a589d2f06872c1113b84bf0875742a73b920d65604b1c92ab206eb1644d212932e2ba4a08f555a1305c0966db76d89c32b47e7fdede68284034343e3bae098203adb1010f5e8844269ff8e2c4fa28076d1d28e7b17c3e3e5d8314f6aedfc851da5953b209982031042b92a60727496112108e8752c0037166a75e28bff7be5e4d42b96f0df348f27df6109109c2def5c188de8b1eb1f52310b30b023223cfeb511642dc02d06db015a1d71057db2f7d589f793c53a87259bb596f6366872d1df6ec8bed033e54ebafa600bb2024e86d6958787935d128baa15a138995e6c7024fc731e10daf6c1d188c3846fc28ec9ee8f1cbe22dcb78cc6cd7378985b94523dbff6cc008704e81dab9d30b0b9ca77113440e605e6cf557a211e37fca6696380bf758d80340f7151702ec8e353abcb123742207eb6849abcbfad7e255ee1e3772156cc9080e241db4b4542a99e48953f353c6f3f15483e22cc457629fba034af2ca29ae0bf87ccffd8793d08eafeb9d0aadb778078d14a55941e99da14ae4adea526c610a8e7cbf7457ca55af9b20a8787c8ccdba16b60073c1e5d393595adecb68b6d206138158e51311a5e5828134705c4942e3ec73491cc0b0238e58915f0c409bf80a594ba445d498fbf1bd38b88cd6999d05d3cedcc3ae5f810fc8a37a8fa1908f00a08d7d3228f47eb7712bb1a59f14f2691e0656a706ffbfaee6d716aa706dd109c86da739da9241af66091c7004a9451ba95fb6164c66f36c21223e92239ece0b612e7a6d130f84eebd5657564d0927ae2c3053593db77a827fe8d98481292b09072ddaa5ce188cc88c21fd4a00694542281b0fe5e11c304d3a1c47ad85e5140223f5fd90069d2aa1bebcdd37a8676c4873dd24b2a494638b036f05ded5cc8003d8c6d180e33f8add66e8f77f353490c000392801f762ee9a1759a5ea482a5f0a7b5b7b884feb02e69cc2f4bd19929cd130ff9f6e1bcc3be4f4a15058c12d3d068910f3ebb59179557ed2bd2bcadefe7b4e1a8435537824257e5eb99299b3b600a251f4465dd9b562bcb62cef125ac936b0302404652d4714aa9fe4ed8888e7050501f17433b99c1411f9a8ccd2960bbd303bac894486a81f00dfec9a8a477b04e9f859268809d3c302c6d0c04d8cd490376cf9de794f9b44dd514aa25a597e07251703c24dee127ca297db3b137c489b88fde607696bf8b610f596a220e3730a822ed02ebdeb3f3a730f74a48f29e0bb8d3823b9a758f923a4e8a98e2b95990d0fe71210a373e8546c086052214fa0a509c900bca640eeeadb8d9f4c8ee73c09e58b54e01ff4c3b6c2af284ec0b6b64b18c953fca19cecde100b1c0402a06605556605bed2c9dc6d61d810095d404cb8fef83e1bd137ba8c97e85d7a2bff4e07e2ca35ed8cbc27170c87023a186f2d309e5e9b85d829f66321ac8e29957bf90a50bddfc0eaff9ac88acf9f53568faf285b01988411e1c4a5a0e79f12e561dc047cc35c7bd2893b09d0434c2cdb28aab8d981a5eb00daed7f01a2a8504110e30c4a80f35455d3dc5bf9176174af8eee4535b8246f71f59581782d972e0e989501878c348d9f91993668c1226d4b21a5513a3e04814c2a3bec8a0761be317f21053688f68daaf8fecd01ff951f2e70f633c37e3861d35a7d787d196f93fddc520d146eb29427271e3de85cd97a5fd74424262d329ee292491688e6a589956d000f95cbb672942151b82c4f59c6c6e96087712e43d7e304bc5ae62c0cf9da2f150f61375c7755917212881ec934b1511088502471d5845cac1b2b7b3f6a1512d30e5ecb6c3a32a1638d002391390eaf96298c791ade33151ef041639d96cb976c047dc8a39527fe9c21497108446867c7d4a353ea42a80f306e702f30bde32dc10f5db99f03793ce72cfff0779099e9b9adb8078147e09d95f0f9af85ffeefec009f101e4092ce6e8f446525c5d4cf970e8006f5ec9544751f1e8f285b82a00fb018db34b726e8edda4ecac89fd6b02505ddf9f31814610eaf460b2d77844a6fb090f1619b093f049a766a9469042b5a4a32497531af614cc03ab3830247db52005110447e2ad4f9569707e7467a25f0f141e90ef90c2c949afdc56eb3bf52c5808b25c16e5e5046190484037ea48aee9c92081f8320764431c95685ae01a90d4036b7298f9f3b5fca717ba77cf5a1a06d22f1194501ebdbb971df7b291ad41f10ddda1cf78f2aab6759066dba3fd9c79c772c288c754a5614c6b484dc4b6ce4100be89385e46a8b2eaca52be969befd4b0510c85dce397d33983972c3804b4a6051fe312f26d59153a068b82c140335672c79f367711932d8619a5564c8ba5db0f13bd511de60a26a061c1a439d60e1bc08fb381cf5ba567486c5e95cec1581a0a49198c5144a5442773701e1ebf8b3e83ad43e1b9782de488161dffc868cf750d6bd43435c477ce5799513fe32dac537cea4f39db075bc8bd09db0943a62ac703ec466ee76c21bbefbf69693d5ec79cf4fd06559864206f61c28ce0398dbe3d074fac57a0eebec7afb35ddc4f4c6331c06e38183e4b249cd27f8b18bd86442d0f3dec5d4237baf7f61bc6ba397500e84ad9edf591e84f1c969dcfff847581e5063c916e4096a3d5c143622ec632e7592710756fa2d88073e0a661b14c38deae0101475c15a3dce77876363a48617cdd1e50a1b19b6ec8c764818fa5055f80ca07d7aba9fd0fa7309446573ef949d64d8e027dd01e0cb86fb12a691e32a464360e5e62a8721ba16e8da76de0e91cfe6b2e61e1167598b89d228c30e1d5fe02b802edf3f787c17427e45ef6d0223e5ed21ac8334f94d11e5a98a10f2afdb24f9f0cba9184efccd9a7434d598dfb52beda83bc9f99b0a957e4f3f93e5b2484b83b0cae245dc65f3a123431082a456e2d315ae1f2cc866c03c1cd8c028a3f0907ba0f05f69b59382f7ee88309227581eeed19b718c0f9255adcf35b327149b1fc0f0f9617c12a5bae1ea5c44f883eb2180abd37cda59fc50e5bf1c304550ac3f4a00fc81260edfa79bf69f21e5ac5cf514da8d29264c07f7de6fffaa5b6fe1435910dd9576f82b5e869bdfe1f062e8d42740a4ec7b76774585edc3d8feb3be8b32b08e86175788fe1686c1dcfb1d9b5dc71800124144c727638b6b17ac80d9a75e2026393edf362b66b200718566f49fc5f98ef55a4ab29d9c0a5744e3097ee952206db41539af7a41a88276803b59c98cbf1950438bee77b5879d5222535b1143a0fdf78c3bc834df85564e3ed991dd830accf0d5c6c763fd72a16b35949fc59c1019b0ecf2a7d75617d882a6602bc433fdbd86471bd9eab39c82874c99d9268a6079e690970aa1a5db2135b11466bb437cca3f5660274cd059b6977ad74ba2e1f008de8da1df548a3eee679a8c9f4227fe2a1964657d5216ce81b68c0a1ee33a10de8b71eb2f672df415930ade7f61333863ca6d8028a6816b4e7f8d9e82de21c06c57483895fc3054565faa35aedf7b4c75d8b38a8549737e72118af6175e9ca01815b3b5f2403de32d343b4ceb5c7b24d4309f93488859aa8b4cefd1d221ee40c6f36f58662b2b67896076cb32562b9b6cd0759a793a28da4b7c6015a419feb058de354f41af135d0b4116769d44b0d134587c7356efca96d50141ec46243eb0bd6989df79b17ce832f0e0fa7daf80ad6032c23f3a59849b53fae61745b7f5d08cd1c57da34a0e99f02d7d48332a83510359ab78c2a06e6b87fca8af94ac3440612287437a4c15e9e325e2dfa2fd4220708d9699b97b3c9759a4aac4f5996840ba6f4791e301c0a16a4d047784ea775004b9ad119a6faa690e42548bcd32b500081b4f0c55b16eab85b1270bc75fe8d4a55258bead13587e43d29aa9984633c08cc8c3829baf59dd3de458110eff6395cc96fef3c928c2a9048df66f7217c870518bc5d2f50aa1143e2c8d05dfba335ebb32bfc83c46764eeb50371a3e0b37c05915bf576ba8e078effa80fb77b47278420ed207c961ccbc4f7834ac2cdb4c90cdd2c3b30b6501d9b409a61bb4af806ea4f4b003d664dae40d07e8236927b7407e95501451617274a24e9681b49b3aee4facc8a9f43e1285ac787c2b73ecd869b33aaedcf4cf80b7d6c541d1064999f572b8a83c5ea46db76e66f290575e0c47803dfaf2abcebcfa2eefc402979912cc06a97f072a74c4bf006d9992006f5978f488360fd7829a396a991952be8e84fd102b013c49ade2cb21bbbf009391642c0c3ac84240077ab0a0838f5053e991971bb06f3157e512dcd2a2ab9fa7aed631f8c662640ecc7a753ed8e29ce32e19148a8ab93ee55fd370cc72d7bac70bd2b1b263bef9039526f6bdf9e66caf69d882a167ee9deef66a3f5ffd38fafd54006547fef1d57cc95ce5a075bd5463c4f531271a589244c8c0cf12f5f0b1e639bb500633458bedf156b73494b41e47905e06a5bfb26d020a71b4dc61f14762232943929c7bcb2f5f0d638bd84892c6ac632a6a49004c1d7a82bde740e626f52e59a1cf8b8150330ee5ec65e6072994b6926d991e5e3c4cd415ff8876c6492a25bb51eb634e7da6db25ba1846ae037143549e836f595437173983ae6af12206bd5eddb9b869daf8d0d069fd1dedd590babf98a4a0423d74806c8946992c814ff1964fb887c0f86cb2fff1bb72851b0b4ef370c3957f8bf4fc053b08a5ead9918fa15f64db0cad9b5dae7e19079e13e7107b7f15394817ff53934f0683c2433bc4e2fece8bb22a29c4f5808e995eb46ad1f0c3cf6fca2fc5cf61982793778aebe07059d7131f050054f965a17113766b72773822f560f681ce8e5719669be7e1cd8d4f638c57aacf59ad78f04af8b6aaf7578247560067cc876db49fbed0dd59d5385bcfa652ed0484e284f92eda2d35b7216b06bf4f76332e25aeb05ed27e4f6f4eecc2cfd061cdca4e9343cd41495e9156891c892c1f48136ec3aad1f1fc144fe9194d5ad0b5b9b3378a27f8fb73bd60d425848064536c8bbe14e34de1944665d1c34c789296f8262996f32ab5eed1b64498c728b7f3d8bce98de1da86f5565b03538d1f82d6b667423218c651ab605ce7c19112e310acf27920a430cff8bfb2d5606df75a5454e83367db33a9363266d32008fcc5517be1568a1ecda27eaf93622fd28eaabceedef5503bd3363d4e31c2f82141f49acc0699d1d2168683afddbbc8c99f0a4ed37d8df1eb935ae392b8d4f103017b8c7430073a8fa88f8e8b746ad049e019c1310c591eb6d7e88470270713e8e6262562e46f79c78390e81a80b3f60847056d24308533b4d2ffc28ee240806a112bb0a371bb5eb0d923d6f377e0b8d2ab3ebd28ea84ca2e4f97443d470801e516a8f0fd00b15ad8e2bc027b2de0424be9faf0ca5cebc1ad6e299a806b07f6d66311dbb47333312770f290dda4922860fecd351d7b5822159e75206661dcf9933977ab3b28c119259b675b443c0a062554cf240843c877512b70e6e47b9082d701bbf59514c0f96bd2aff0902825d55690dcc5fecf4bace51b732eecf18358d1a72c59c3b5247b1ca75485509df4ec04d092f304adbffb71e2415eb653f807af39828d61d014a12233d60ef3d00d27862b981bc5e252bb4c481aba2bf67a4d7d469ba9f76fc737c5457b02418e5f701930313723dceb3e20ee2a1edd49cc6b36c45f23dd2c93f15f635e7a5f74771b75ebcf98461fdde037cdf47d01a96585378fc4ec3da3bf0cdc90979abbaeaf8a24ef1d0e54c6f0e21fbea9b2e6c133420c7965b2a350c1f48d85f0d40b03a91e66c8a3cf221bd6de8fddd74ae802f9d12b601a953eba33607f94d42c06d9ccc23c70d5a16397a18dc7582372d651cbfc41630681103f0cb46a2c738ca4cc9e74f1206cd2b0dbeaaf5bc2714c5a256ec2728bb10d1be87e38a35d616fa39e1417743f851c4d7a16ebf95d97e1e02b6b93dd8edd27981b2a590891b9ae6b2d4151df0de9e03724c8388b27c320364ae485b98fc79117f0a82818d754dd4f5f960d8fde43752c701c1e325910f29508c2d9c4eefaf7faa9e7abb87fb82d5827b35d6455523743e41b6bde1e74c6be81ff9497fc3b29d25e5322713d21ff815747f4f756157727f1b11789cdb4f2b865ed4447cbb18bc5328bd010c5387b5e11464b3e0928f0a254faaca660ec95d375907df860b866b08d4072f0de8a61f568c3b9b067d2b271cabc676d6262fae32e8d3c05687dd0fc6347f438ab34a334bac70caba91228bedd6e44e4dbbda2f88e98b3524caf7f16a9264382f1fff450829d93e8fe2599e5f7991b0d15885b1f2767648d3ce360d35c2f48e48b761c7879a61d27dcaa9aff470c2feb69ba544983ea3e510e84c956fe277523f3eb4fd516a1a300a667dacf42b4777aa0d64a850f0c2eb19616c9e0afa6d1330f8443329f738cd10f29ee286c57ef3cee1b7400654336da976367c835af9f422d6228437ad8422fef313967f2c719d0fc68aef8f930301c17e9ac6c44c3e0748ab4c4d926505234405d8449717db2464730142a97ea6d398dbd5acdf2b86bf2da0850c90a44ad770324cbfd4dc9b4670a29f80041e746f26d295c50b809bf0a8cf1f51b00a685b83d22cf24601f23c35958e9688784aa27f2a9f6f50d8787d72a6d823df6419f1c47344f362821220359ad16fa0f8a90311aca79c699f1be89da341355fd492494da431ae07c7d3aaa1509c28d3ebe368ac6dd68845101efe8656c6bd5c6eb9b87592c02a68924640c102c73005d40a1b7119bcb5b395eb0a55e07dd928d1e6285432cd0cbc15aec82450714e3dd0eef84fc16b1a2f405107588612a24ee2377eed68fbbd363990a05d270071c6cd5c703f2ffd7297f940d91701dc6591cff59b9f9f8dfed90da03f3c9e661c64b0e69d29afc63d4f9e0b5f3121dfadfc4a0c984328be69f8b06df45070f0939d4186c222f01c34ab6006dc080a33b18d694891ab30d4bb4dba9d6ccb0980e308e4736ca1184f7d188cebf9c7bd625b08f8da0b168f5bddeb21519393164547daacd924e85f3890b7ebe4eb500580cbc3792f0b9330c352517282946705433516d588fd33fe27035f3ab78ec5ca6d4a34295700e77297aa3ee036e945872255f5e2386ca5f2c6aa793441b9500ff24e5d06c30cb4bb3fdacaa0920a72557810b760df1a3e1abcb41911f18ae3ad8f16a4c20e060339a795d0b90265ed8367c784d364499bcb68925bd82d2b746c5ec06d17ff09cd15af9f6ec654c0d2ae47ed7a0e9936dc24dd32e06b3d143fea63e0705dc100ca9cc9fa24a690066a68ee231aa7985c7c0759c47f4421ebc83dc8c2ff30ef0567b68de1fe158f944cb964b323c943ebd57dcd70ac0f7e8655dc512fd694ab0ce17e9c57a22ac7391c0acf7230d0ede5f7c299748c8a5222410339380058470734c773355157653e85cfd8c7ee7c594b15153a38ea134c11c835e51743f6570980f02609f4361e84527a9c6d6eb9ada9d8049a4ef825cfc72373b676eefbf7097860aa9b787c32580f5717882fea4764b1e7a0fea1c02941464b87e2233edd0c32e23e4c1a47b80e28d40717672c2d06c2ea11bb7e21bc14e04dbabcbd4cb00016ef367236190f4f7ac70f47ac6ba4047527df2d2312dd368deb90b07a57680077e60f1e2ddd7a6637ec5aa969055aa01c6427308da322f41bae11df4710b300d64690c37be2ed7e49fc67179745505128266ef6c531cc7e3698e173056d270817565b4b373008328218ab20be3232d44c36a7fff2ad00423308438278da8003333d3a696662668d97721ad48a55af3a4afc3049f8576485d1b782cf05622307b6d862229804ff56d83ab854799d3467e3526e6a69a03fb8d31d0a59f12dde0e2d0650d7d3ef2a0cb720a96b6f5f2ab0acb70b196d962ab7a061dd7511671a0267e9b51440beb1f9e9e70d424151ea292b4131d94c832cd1f7c5288737830e07"
|
|
tx_created_by_oldwallet, _ := hex.DecodeString(tx_created_by_oldwallet_hex)
|
|
|
|
var tx transaction.Transaction
|
|
|
|
err = tx.DeserializeHeader(tx_created_by_oldwallet)
|
|
if err != nil {
|
|
t.Fatalf("TX could not be deserialized. This needs to be fixed urgently as encrypted payment ID functionalyi is broken")
|
|
}
|
|
|
|
// the tx is sending payment to itself with encrypted payment
|
|
tx.Parse_Extra()
|
|
|
|
public_key := tx.Extra_map[transaction.TX_PUBLIC_KEY].(crypto.Key)
|
|
|
|
payment_id := "0cbd6e050cf3b73c"
|
|
|
|
// lets locate and decode payment id, see whether it matches with expected value
|
|
epayid := tx.PaymentID_map[transaction.TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID].([]byte)
|
|
|
|
derivation := crypto.KeyDerivation(&public_key, &w.account.Keys.Viewkey_Secret)
|
|
payid := EncryptDecryptPaymentID(derivation, public_key, epayid)
|
|
|
|
t.Logf("epay id %x decrypted %x original payment ID %s ", epayid, payid, payment_id)
|
|
|
|
if payment_id != fmt.Sprintf("%x", payid) {
|
|
t.Fatalf("8 byte encrypted payment ID missing failed, Critical, encrypted payment Id failed")
|
|
}
|
|
}
|
|
|
|
// this will create the transaction and benchmark the verification times
|
|
// this also forces the ring size from 2 to 10
|
|
func benchmark_TX_Verification(b *testing.B, num_inputs uint32, num_outputs uint32, num_ring_size int) (tx *transaction.Transaction) {
|
|
|
|
temp_db := filepath.Join(os.TempDir(), "dero_temporary_test_wallet.db")
|
|
|
|
os.Remove(temp_db)
|
|
|
|
w, err := Create_Encrypted_Wallet(temp_db, "QWER", *crypto.RandomScalar())
|
|
if err != nil {
|
|
b.Fatalf("Cannot create encrypted wallet, err %s", err)
|
|
}
|
|
|
|
defer os.Remove(temp_db) // cleanup after test
|
|
//sender,_ := Generate_Keys_From_Random() // we have a single sender
|
|
|
|
var receivers []*Account
|
|
|
|
randomBytes := make([]byte, 4)
|
|
|
|
rand.Read(randomBytes)
|
|
random_inputs := 1 + (binary.LittleEndian.Uint32(randomBytes) % 20) //minimum 1 input is necessary
|
|
rand.Read(randomBytes)
|
|
random_outputs := 1 + (binary.LittleEndian.Uint32(randomBytes) % 20) //minimum 1 output is necessary
|
|
|
|
random_inputs = num_inputs
|
|
random_outputs = num_outputs
|
|
|
|
txw := TX_Wallet_Data{WAmount: 4000000000000}
|
|
txw.TXdata.Index_Global = 739
|
|
txw.TXdata.Height = 730
|
|
txw.WKey.Destination = crypto.HexToKey("dbdfd2a3e9da6911b0a3e37e8e448f2de2477f81760585c2f197736bac127e0f")
|
|
txw.WKey.Mask = crypto.HexToKey("01e4e85ab0b5e30dd86b5356f0f6b4177738b9e6b32041c4e4781a2f26083101")
|
|
txw.WKimage = crypto.HexToKey("d8fb3b4260aea6582400a5f48244ff3f7c4dc36420698e3decc5d28ba04733c2")
|
|
txw.TXdata.InKey.Destination = crypto.HexToKey("ed0da9e74d240088a07909ea354b8d140b753642e25495e0931b4623b25ff523")
|
|
txw.TXdata.InKey.Mask = crypto.HexToKey("dbddab6c6b3063074e7cfd1a7f83f184ad78e92c8ff25118c0ed4edc77015948")
|
|
|
|
var outs []ringct.Output_info
|
|
for i := uint32(0); i < random_outputs; i++ {
|
|
r, _ := Generate_Keys_From_Random()
|
|
receivers = append(receivers, r)
|
|
|
|
out := ringct.Output_info{Public_Spend_Key: receivers[i].GetAddress().SpendKey,
|
|
Public_View_Key: receivers[i].GetAddress().ViewKey}
|
|
|
|
if i == 0 { // balance the outputs
|
|
out.Amount = uint64(random_inputs) * txw.WAmount
|
|
}
|
|
outs = append(outs, out) // fill outs with random address
|
|
}
|
|
|
|
ring_size := uint64(num_ring_size)
|
|
|
|
var ins []ringct.Input_info
|
|
for i := uint32(0); i < random_inputs; i++ {
|
|
|
|
ins = append(ins, ringct.Input_info{Amount: txw.WAmount, Key_image: crypto.Hash(txw.WKimage), Sk: txw.WKey, Index_Global: txw.TXdata.Index_Global})
|
|
|
|
//now we must the ring members
|
|
ins[i].Ring_Members = append(ins[i].Ring_Members, txw.TXdata.Index_Global)
|
|
ins[i].Pubs = append(ins[i].Pubs, txw.TXdata.InKey)
|
|
|
|
// lets add 5 ring members randomly
|
|
for j := uint64(0); j < ring_size; j++ {
|
|
ins[i].Ring_Members = append(ins[i].Ring_Members, txw.TXdata.Index_Global+j+1)
|
|
ins[i].Pubs = append(ins[i].Pubs, ringct.CtKey{Destination: *crypto.RandomScalar(), Mask: *crypto.RandomScalar()})
|
|
}
|
|
}
|
|
|
|
// 739th main net consumed output
|
|
var payment_id []byte
|
|
|
|
bulletproof := true
|
|
|
|
tx = w.Create_TX_v2(ins, outs, 0, 0, payment_id, bulletproof)
|
|
|
|
return tx
|
|
|
|
}
|
|
|
|
/*
|
|
func Benchmark_TX_Verification_inputs_10_outputs_2_mixin_7(b *testing.B){
|
|
benchmark_TX_Verification(b,10,2,7)
|
|
}
|
|
func Benchmark_TX_Verification_inputs_10_outputs_4_mixin_7(b *testing.B){
|
|
benchmark_TX_Verification(b,10,4,7)
|
|
}
|
|
func Benchmark_TX_Verification_inputs_10_outputs_7_mixin_7(b *testing.B){
|
|
benchmark_TX_Verification(b,10,7,7)
|
|
}
|
|
func Benchmark_TX_Verification_inputs_10_outputs_9_mixin_7(b *testing.B){
|
|
benchmark_TX_Verification(b,10,9,7)
|
|
}
|
|
func Benchmark_TX_Verification_inputs_10_outputs_11_mixin_7(b *testing.B){
|
|
benchmark_TX_Verification(b,10,11,7)
|
|
}
|
|
*/
|
|
|
|
/*
|
|
func Benchmark_TX_Verification(b *testing.B){
|
|
|
|
cpufile,err := os.Create("/tmp/cpuprofile.prof")
|
|
if err != nil{
|
|
|
|
}
|
|
if err := pprof.StartCPUProfile(cpufile); err != nil {
|
|
}
|
|
defer pprof.StopCPUProfile()
|
|
|
|
input := uint32(20)
|
|
output := uint32(2)
|
|
mixin := 7
|
|
|
|
b.Run(fmt.Sprintf("in %d/ out %d/mixin %d", input, output,mixin), func(b *testing.B) {
|
|
tx := benchmark_TX_Verification(b,input,output,mixin)
|
|
b.Logf("tx size %d bytes %d KB", len(tx.Serialize()), len(tx.Serialize())/1024)
|
|
b.ResetTimer()
|
|
for n := 0; n < b.N; n++ {
|
|
if !tx.RctSignature.Verify() {
|
|
b.Fatalf("TX ring signature verification failed")
|
|
}
|
|
}
|
|
})
|
|
|
|
|
|
|
|
}
|
|
*/
|
|
|
|
// verify and confirm few parameters for the network
|
|
func Benchmark_TX_Verification(b *testing.B) {
|
|
|
|
cpufile, err := os.Create("/tmp/cpuprofile.prof")
|
|
if err != nil {
|
|
|
|
}
|
|
if err := pprof.StartCPUProfile(cpufile); err != nil {
|
|
}
|
|
defer pprof.StopCPUProfile()
|
|
|
|
for input := uint32(10); input < 400; input += 50 {
|
|
for output := uint32(1); output < 9; output += 2 {
|
|
for mixin := 5; mixin < 15; mixin += 3 {
|
|
b.Run(fmt.Sprintf("in %d/ out %d/mixin %d", input, output, mixin), func(b *testing.B) {
|
|
benchmark_TX_Verification(b, input, output, mixin)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* specific testing if required
|
|
// inputs
|
|
func Benchmark_TX_Verification_inputs_10_outputs_2_mixin_8(b *testing.B){
|
|
benchmark_TX_Verification(b,10,2,8)
|
|
}
|
|
*/
|