From f511dd67361d52f88b42d62256dc3450a24e74f8 Mon Sep 17 00:00:00 2001 From: Oliver Weichhold Date: Fri, 16 Mar 2018 17:37:49 +0100 Subject: [PATCH] Dev (#271) * Fix ethereum explorer links * Refactored-out chain specific share DTOs * Moved ShareRecorder and ShareRelay to more fitting namespace * Greatly reduce the number of threads for external stratum monitoring * Add payload-type-flag-frame to share publisher/subscriber * ProtoBuf Share Relay & Recorder * Do not require monero wallet daemon config if payment processing is disabled * Reconnect to share relay on receive timeout * Cryptonote Tests * Monero v7 --- .gitignore | 1 + .../Blockchain/Bitcoin/BitcoinJobTests.cs | 10 +- .../Blockchain/Monero/MoneroJobTests.cs | 6 +- src/MiningCore.Tests/Crypto/CrytonoteTests.cs | 89 ++ src/MiningCore.Tests/Crypto/HashingTests.cs | 36 +- .../Native/LibCryptonoteTests.cs | 71 -- src/MiningCore/AutoMapperProfile.cs | 8 +- src/MiningCore/Blockchain/Abstractions.cs | 80 -- .../Blockchain/Bitcoin/BitcoinJob.cs | 27 +- .../Blockchain/Bitcoin/BitcoinJobManager.cs | 42 +- .../Blockchain/Bitcoin/BitcoinPoolBase.cs | 2 +- .../Blockchain/Bitcoin/BitcoinProperties.cs | 19 +- .../Blockchain/Bitcoin/BitcoinShare.cs | 27 - src/MiningCore/Blockchain/CoinMetaData.cs | 304 +++---- .../Blockchain/Ethereum/EthereumJob.cs | 19 +- .../Blockchain/Ethereum/EthereumJobManager.cs | 29 +- .../Blockchain/Ethereum/EthereumPool.cs | 2 +- .../Blockchain/Ethereum/EthereumShare.cs | 29 - src/MiningCore/Blockchain/Monero/MoneroJob.cs | 33 +- .../Blockchain/Monero/MoneroJobManager.cs | 98 ++- .../Blockchain/Monero/MoneroPool.cs | 2 +- .../Blockchain/Monero/MoneroShare.cs | 28 - src/MiningCore/Blockchain/Share.cs | 121 +++ src/MiningCore/Blockchain/ShareBase.cs | 43 - src/MiningCore/Blockchain/ZCash/ZCashJob.cs | 24 +- .../Blockchain/ZCash/ZCashJobManager.cs | 6 +- src/MiningCore/Mining/Abstractions.cs | 4 +- src/MiningCore/Mining/PoolBase.cs | 109 +-- .../{Payments => Mining}/ShareRecorder.cs | 210 ++++- .../{Payments => Mining}/ShareRelay.cs | 31 +- src/MiningCore/MiningCore.csproj | 21 +- src/MiningCore/Native/LibCryptonote.cs | 6 +- .../Postgres/Repositories/StatsRepository.cs | 4 +- src/MiningCore/Program.cs | 2 +- src/MiningCore/Properties/launchSettings.json | 14 +- .../runtimes/win-x64/native/libcryptonote.dll | Bin 412672 -> 439296 bytes .../runtimes/win-x86/native/libcryptonote.dll | Bin 366592 -> 394752 bytes src/Native/libcryptonote/Makefile | 3 +- src/Native/libcryptonote/Makefile.MSys2 | 3 +- src/Native/libcryptonote/common/base58.cpp | 18 +- src/Native/libcryptonote/common/base58.h | 2 +- src/Native/libcryptonote/common/int-util.h | 19 +- src/Native/libcryptonote/common/pod-class.h | 2 +- src/Native/libcryptonote/common/util.h | 215 +++++ src/Native/libcryptonote/common/varint.h | 6 +- .../contrib/epee/include/console_handler.h | 27 +- .../contrib/epee/include/copyable_atomic.h | 2 + .../contrib/epee/include/file_io_utils.h | 108 ++- .../libcryptonote/contrib/epee/include/hex.h | 2 +- .../contrib/epee/include/math_helper.h | 1 + .../contrib/epee/include/memwipe.h | 80 ++ .../contrib/epee/include/misc_os_dependent.h | 11 +- .../epee/include/net/abstract_tcp_server.h | 2 +- .../epee/include/net/abstract_tcp_server2.h | 16 +- .../epee/include/net/abstract_tcp_server2.inl | 67 +- .../epee/include/net/connection_basic.hpp | 141 +++ .../contrib/epee/include/net/http_auth.h | 15 +- .../contrib/epee/include/net/http_base.h | 6 +- .../contrib/epee/include/net/http_client.h | 199 +++-- .../epee/include/net/http_client_base.h | 6 +- .../epee/include/net/http_protocol_handler.h | 5 +- .../include/net/http_protocol_handler.inl | 65 +- .../epee/include/net/http_server_impl_base.h | 13 +- .../contrib/epee/include/net/levin_base.h | 1 + .../epee/include/net/levin_client_async.h | 12 +- .../epee/include/net/levin_protocol_handler.h | 2 + .../net/levin_protocol_handler_async.h | 118 ++- .../contrib/epee/include/net/net_helper.h | 155 +++- .../epee/include/net/net_parse_helpers.h | 4 +- .../contrib/epee/include/net/net_utils_base.h | 248 +++--- .../include/net/network_throttle-detail.hpp | 125 +++ .../epee/include/net/network_throttle.hpp | 171 ++++ .../contrib/epee/include/profile_tools.h | 2 + .../contrib/epee/include/readline_buffer.h | 2 - .../contrib/epee/include/reg_exp_definer.h | 2 +- .../serialization/keyvalue_serialization.h | 1 - .../keyvalue_serialization_overloads.h | 93 +- .../libcryptonote/contrib/epee/include/span.h | 4 +- .../include/storages/http_abstract_invoke.h | 7 +- .../include/storages/levin_abstract_invoke2.h | 28 +- .../storages/portable_storage_from_json.h | 2 + .../storages/portable_storage_to_bin.h | 4 + .../portable_storage_val_converters.h | 33 + .../contrib/epee/include/string_tools.h | 16 +- .../contrib/epee/include/wipeable_string.h | 67 ++ .../libcryptonote/contrib/epee/src/hex.cpp | 2 +- .../libcryptonote/contrib/epee/src/memwipe.c | 116 +++ .../contrib/epee/src/wipeable_string.cpp | 146 ++++ .../libcryptonote/crypto/CMakeLists.txt | 103 +++ src/Native/libcryptonote/crypto/blake256.c | 4 +- src/Native/libcryptonote/crypto/blake256.h | 2 +- src/Native/libcryptonote/crypto/chacha.c | 182 ++++ src/Native/libcryptonote/crypto/chacha.h | 91 ++ .../libcryptonote/crypto/crypto-ops-data.c | 3 +- src/Native/libcryptonote/crypto/crypto-ops.c | 817 +++++++++++++++++- src/Native/libcryptonote/crypto/crypto-ops.h | 9 +- src/Native/libcryptonote/crypto/crypto.cpp | 147 +++- src/Native/libcryptonote/crypto/crypto.h | 58 +- src/Native/libcryptonote/crypto/generic-ops.h | 2 +- src/Native/libcryptonote/crypto/groestl.h | 2 +- .../libcryptonote/crypto/groestl_tables.h | 2 +- .../libcryptonote/crypto/hash-extra-blake.c | 2 +- .../libcryptonote/crypto/hash-extra-groestl.c | 2 +- .../libcryptonote/crypto/hash-extra-jh.c | 2 +- .../libcryptonote/crypto/hash-extra-skein.c | 2 +- src/Native/libcryptonote/crypto/hash-ops.h | 19 +- src/Native/libcryptonote/crypto/hash.c | 2 +- src/Native/libcryptonote/crypto/hash.h | 31 +- src/Native/libcryptonote/crypto/initializer.h | 7 +- src/Native/libcryptonote/crypto/keccak.c | 31 +- src/Native/libcryptonote/crypto/keccak.h | 2 +- src/Native/libcryptonote/crypto/oaes_lib.c | 20 +- src/Native/libcryptonote/crypto/random.c | 7 +- src/Native/libcryptonote/crypto/random.h | 2 +- src/Native/libcryptonote/crypto/skein_port.h | 2 +- src/Native/libcryptonote/crypto/slow-hash.c | 120 ++- src/Native/libcryptonote/crypto/tree-hash.c | 10 +- .../cryptonote_basic/CMakeLists.txt | 79 ++ .../cryptonote_basic/account.cpp | 62 +- .../libcryptonote/cryptonote_basic/account.h | 21 +- .../account_boost_serialization.h | 2 +- .../cryptonote_basic/blobdatatype.h | 36 + .../cryptonote_basic/connection_context.h | 4 +- .../cryptonote_basic/cryptonote_basic.h | 72 +- .../cryptonote_basic_impl.cpp | 102 +-- .../cryptonote_basic/cryptonote_basic_impl.h | 65 +- .../cryptonote_boost_serialization.h | 53 +- .../cryptonote_format_utils.cpp | 238 +++-- .../cryptonote_format_utils.h | 35 +- .../cryptonote_basic/cryptonote_stat_info.h | 2 +- .../cryptonote_basic/difficulty.cpp | 2 +- .../cryptonote_basic/difficulty.h | 2 +- .../cryptonote_basic/hardfork.cpp | 2 +- .../libcryptonote/cryptonote_basic/hardfork.h | 3 +- .../libcryptonote/cryptonote_basic/miner.cpp | 162 +++- .../libcryptonote/cryptonote_basic/miner.h | 4 +- .../cryptonote_basic/subaddress_index.h | 102 +++ .../libcryptonote/cryptonote_basic/tx_extra.h | 16 +- .../cryptonote_basic/verification_context.h | 2 +- src/Native/libcryptonote/cryptonote_config.h | 46 +- src/Native/libcryptonote/device/device.hpp | 188 ++++ src/Native/libcryptonote/exports.cpp | 10 +- .../libcryptonote/libcryptonote.vcxproj | 14 +- .../libcryptonote.vcxproj.filters | 18 +- .../serialization/binary_archive.h | 4 +- .../serialization/binary_utils.h | 2 +- .../libcryptonote/serialization/container.h | 113 +++ .../libcryptonote/serialization/crypto.h | 6 +- .../serialization/debug_archive.h | 2 +- .../serialization/json_archive.h | 2 +- .../libcryptonote/serialization/json_utils.h | 2 +- src/Native/libcryptonote/serialization/list.h | 72 +- src/Native/libcryptonote/serialization/pair.h | 2 +- .../serialization/serialization.h | 36 +- .../libcryptonote/serialization/string.h | 2 +- .../libcryptonote/serialization/variant.h | 4 +- .../libcryptonote/serialization/vector.h | 73 +- src/Native/libcryptonote/stdafx.h | 30 +- 158 files changed, 5612 insertions(+), 1715 deletions(-) create mode 100644 src/MiningCore.Tests/Crypto/CrytonoteTests.cs delete mode 100644 src/MiningCore.Tests/Native/LibCryptonoteTests.cs delete mode 100644 src/MiningCore/Blockchain/Bitcoin/BitcoinShare.cs delete mode 100644 src/MiningCore/Blockchain/Ethereum/EthereumShare.cs delete mode 100644 src/MiningCore/Blockchain/Monero/MoneroShare.cs create mode 100644 src/MiningCore/Blockchain/Share.cs delete mode 100644 src/MiningCore/Blockchain/ShareBase.cs rename src/MiningCore/{Payments => Mining}/ShareRecorder.cs (59%) rename src/MiningCore/{Payments => Mining}/ShareRelay.cs (80%) create mode 100644 src/Native/libcryptonote/common/util.h create mode 100644 src/Native/libcryptonote/contrib/epee/include/memwipe.h create mode 100644 src/Native/libcryptonote/contrib/epee/include/net/connection_basic.hpp create mode 100644 src/Native/libcryptonote/contrib/epee/include/net/network_throttle-detail.hpp create mode 100644 src/Native/libcryptonote/contrib/epee/include/net/network_throttle.hpp create mode 100644 src/Native/libcryptonote/contrib/epee/include/wipeable_string.h create mode 100644 src/Native/libcryptonote/contrib/epee/src/memwipe.c create mode 100644 src/Native/libcryptonote/contrib/epee/src/wipeable_string.cpp create mode 100644 src/Native/libcryptonote/crypto/CMakeLists.txt create mode 100644 src/Native/libcryptonote/crypto/chacha.c create mode 100644 src/Native/libcryptonote/crypto/chacha.h create mode 100644 src/Native/libcryptonote/cryptonote_basic/CMakeLists.txt create mode 100644 src/Native/libcryptonote/cryptonote_basic/blobdatatype.h create mode 100644 src/Native/libcryptonote/cryptonote_basic/subaddress_index.h create mode 100644 src/Native/libcryptonote/device/device.hpp create mode 100644 src/Native/libcryptonote/serialization/container.h diff --git a/.gitignore b/.gitignore index 370ac6467..5bcc43ed6 100644 --- a/.gitignore +++ b/.gitignore @@ -117,3 +117,4 @@ recovered-shares.json src/MiningCore/config\.json +/src/MiningCore/config2.json diff --git a/src/MiningCore.Tests/Blockchain/Bitcoin/BitcoinJobTests.cs b/src/MiningCore.Tests/Blockchain/Bitcoin/BitcoinJobTests.cs index 2282c2ead..faafe0fa8 100644 --- a/src/MiningCore.Tests/Blockchain/Bitcoin/BitcoinJobTests.cs +++ b/src/MiningCore.Tests/Blockchain/Bitcoin/BitcoinJobTests.cs @@ -47,12 +47,12 @@ public void BitcoinJob_Should_Accept_Valid_Share() // set clock to submission time clock.CurrentTime = DateTimeOffset.FromUnixTimeSeconds(1508869907).UtcDateTime; - var share = job.ProcessShare(worker, "01000000", "59ef86f2", "8d84ae6a"); + var (share, blockHex) = job.ProcessShare(worker, "01000000", "59ef86f2", "8d84ae6a"); Assert.NotNull(share); Assert.True(share.IsBlockCandidate); - Assert.Equal(share.BlockHash, "000000000fccf11cd0b7d9057441e430c320384b95b034bd28092c4553594b4a"); - Assert.Equal(share.BlockHex, "00000020bb76da6422b707a90831c421798123293bc5fd377bbeb51985570909000000008677145722cbe6f1ebec19fecc724cab5487f3292a69f6908bd512f645bb0635f286ef59ffff7f206aae848d0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff295e0c0b2f454231362f414431322f04f286ef590801000058010000000c2f4d696e696e67436f72652f000000000100f2052a010000001976a9142ebb5cccf9a6bb927661d2953655c43c04accc3788ac00000000"); + Assert.Equal(share.BlockHash, "601ed85039804bcecbbdb53e0ca358aeb8dabef2366fb64c216aac3aba02b716"); + Assert.Equal(blockHex, "00000020bb76da6422b707a90831c421798123293bc5fd377bbeb5198557090900000000fd5418fe788ef961678e4bacdd1fe3903185b9ec63865bb3d2d279bb0eb48c0bf286ef59ffff7f206aae848d0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff295e0c0b2f454231362f414431322f04f286ef590001000058010000000c2f4d696e696e67436f72652f000000000100f2052a010000001976a9142ebb5cccf9a6bb927661d2953655c43c04accc3788ac00000000"); Assert.Equal(share.BlockHeight, 14); Assert.Equal(share.BlockReward, 50); Assert.Equal(share.Difficulty, 0.5); @@ -87,14 +87,14 @@ public void BitcoinJob_Should_Not_Accept_Invalid_Share() Assert.Throws(() => job.ProcessShare(worker, "02000000", "59ef86f2", "8d84ae6a")); // make sure we don't accept case-sensitive duplicate shares as basically 0xdeadbeaf = 0xDEADBEAF. - var share = job.ProcessShare(worker, "01000000", "59ef86f2", "8d84ae6a"); + var (share, blockHex) = job.ProcessShare(worker, "01000000", "59ef86f2", "8d84ae6a"); Assert.Throws(() => job.ProcessShare(worker, "01000000", "59ef86f2", "8D84AE6A")); // invalid time Assert.Throws(() => job.ProcessShare(worker, "01000000", "69ef86f2", "8d84ae6a")); // invalid nonce - Assert.Throws(() => job.ProcessShare(worker, "01000000", "59ef86f2", "ad84be6a")); + Assert.Throws(() => job.ProcessShare(worker, "01000000", "59ef86f2", "4a84be6a")); // valid share data but invalid submission time clock.CurrentTime = DateTimeOffset.FromUnixTimeSeconds(1408869907).UtcDateTime; diff --git a/src/MiningCore.Tests/Blockchain/Monero/MoneroJobTests.cs b/src/MiningCore.Tests/Blockchain/Monero/MoneroJobTests.cs index 39f2ac5e8..b2e757de6 100644 --- a/src/MiningCore.Tests/Blockchain/Monero/MoneroJobTests.cs +++ b/src/MiningCore.Tests/Blockchain/Monero/MoneroJobTests.cs @@ -26,12 +26,12 @@ public void MoneroJob_Should_Accept_Valid_Share() "{\"blocktemplate_blob\":\"0106e7eabdcf058234351e2e6ea901a56b33bb531587424321873072d80a9e97295b6c43152b9d00000000019c0201ffe00106e3a1a0cc010275d92c0a057aa5f073079694a153d426f837f49fdb9654da10a5364e79a2086280a0d9e61d028b46dca0d04998500b40b046fd6f8bb33229e6380fd465dbb1327aa6f813d8bd80c0fc82aa0202372f076459e769116d604d30aabff7160782acc0d20e0c5cdc8963ed4e16372f8090cad2c60e02f009504ce65538bbb684b466b21be3a90e3740f185d7089d37b75f0cf62b6e7680e08d84ddcb0102cf01b85c0b592bb6e508e20b5d317052b75de121908390363201abff3476ef0180c0caf384a302024b81076c8ad0cfe84cc32fe0813d63cdd0f7d8d0e56d82aa3f58cbbe49d4c61e2b017aaf3074be7ecb30a769595758e4da7c7c87ead864baf89b679b73153dfa352c0208000000000000000000\",\"Difficulty\":2,\"Height\":224,\"prev_hash\":\"8234351e2e6ea901a56b33bb531587424321873072d80a9e97295b6c43152b9d\",\"reserved_offset\":322,\"Status\":\"OK\"}"); var job = new MoneroJob(bt, "d150da".HexToByteArray(), "1", poolConfig, clusterConfig); - var share = job.ProcessShare("040100a4", 1, "f29c7fbf57d97eeedb61555857d7a34314250da20742b8157f96e0be89530a00", worker); + var (share, blobHex, blobHash) = job.ProcessShare("040100a4", 1, "f29c7fbf57d97eeedb61555857d7a34314250da20742b8157f96e0be89530a00", worker); Assert.NotNull(share); Assert.True(share.IsBlockCandidate); - Assert.Equal(share.BlobHash, "9258faf2dff5daf026681b5fa5d94a34dbb5bade1d9e2070865ba8c68f8f0454"); - Assert.Equal(share.BlobHex, "0106e7eabdcf058234351e2e6ea901a56b33bb531587424321873072d80a9e97295b6c43152b9d040100a4019c0201ffe00106e3a1a0cc010275d92c0a057aa5f073079694a153d426f837f49fdb9654da10a5364e79a2086280a0d9e61d028b46dca0d04998500b40b046fd6f8bb33229e6380fd465dbb1327aa6f813d8bd80c0fc82aa0202372f076459e769116d604d30aabff7160782acc0d20e0c5cdc8963ed4e16372f8090cad2c60e02f009504ce65538bbb684b466b21be3a90e3740f185d7089d37b75f0cf62b6e7680e08d84ddcb0102cf01b85c0b592bb6e508e20b5d317052b75de121908390363201abff3476ef0180c0caf384a302024b81076c8ad0cfe84cc32fe0813d63cdd0f7d8d0e56d82aa3f58cbbe49d4c61e2b017aaf3074be7ecb30a769595758e4da7c7c87ead864baf89b679b73153dfa352c02080000000001d150da00"); + Assert.Equal(blobHash, "9258faf2dff5daf026681b5fa5d94a34dbb5bade1d9e2070865ba8c68f8f0454"); + Assert.Equal(blobHex, "0106e7eabdcf058234351e2e6ea901a56b33bb531587424321873072d80a9e97295b6c43152b9d040100a4019c0201ffe00106e3a1a0cc010275d92c0a057aa5f073079694a153d426f837f49fdb9654da10a5364e79a2086280a0d9e61d028b46dca0d04998500b40b046fd6f8bb33229e6380fd465dbb1327aa6f813d8bd80c0fc82aa0202372f076459e769116d604d30aabff7160782acc0d20e0c5cdc8963ed4e16372f8090cad2c60e02f009504ce65538bbb684b466b21be3a90e3740f185d7089d37b75f0cf62b6e7680e08d84ddcb0102cf01b85c0b592bb6e508e20b5d317052b75de121908390363201abff3476ef0180c0caf384a302024b81076c8ad0cfe84cc32fe0813d63cdd0f7d8d0e56d82aa3f58cbbe49d4c61e2b017aaf3074be7ecb30a769595758e4da7c7c87ead864baf89b679b73153dfa352c02080000000001d150da00"); Assert.Equal(share.BlockHeight, 224); Assert.Equal(share.Difficulty, 1000); } diff --git a/src/MiningCore.Tests/Crypto/CrytonoteTests.cs b/src/MiningCore.Tests/Crypto/CrytonoteTests.cs new file mode 100644 index 000000000..b8e32bacf --- /dev/null +++ b/src/MiningCore.Tests/Crypto/CrytonoteTests.cs @@ -0,0 +1,89 @@ +using System; +using MiningCore.Extensions; +using MiningCore.Native; +using Xunit; + +namespace MiningCore.Tests.Crypto +{ + public class CrytonoteTests : TestBase + { + [Fact] + public void Crytonote_Hash_Slow() + { + var blobConverted = "0106a2aaafd505583cf50bcc743d04d831d2b119dc94ad88679e359076ee3f18d258ee138b3b42580100a4b1e2f4baf6ab7109071ab59bc52dba740d1de99fa0ae0c4afd6ea9f40c5d87ec01".HexToByteArray(); + var result = LibCryptonote.CryptonightHashSlow(blobConverted, 0).ToHexString(); + + Assert.Equal("a845ffbdf83ae9a8ffa504a1011efbd5ed2294bb9da591d3b583740568402c00", result); + } + + [Fact] + public void Cryptonote_SlowHash_Should_Throw_On_Null_Argument() + { + Assert.Throws(() => LibCryptonote.CryptonightHashSlow(null, 0)); + } + + [Fact] + public void Crytonote_Hash_Fast() + { + var blobConverted = "0106a2aaafd505583cf50bcc743d04d831d2b119dc94ad88679e359076ee3f18d258ee138b3b42580100a4b1e2f4baf6ab7109071ab59bc52dba740d1de99fa0ae0c4afd6ea9f40c5d87ec01".HexToByteArray(); + var result = LibCryptonote.CryptonightHashFast(blobConverted).ToHexString(); + + Assert.Equal("ddc0e3a33b605ce39fa2d16a98d7634e33399ab1e4b56b3bdd3414b655fe9a98", result); + } + + [Fact] + public void Cryptonote_FastHash_Should_Throw_On_Null_Argument() + { + Assert.Throws(() => LibCryptonote.CryptonightHashFast(null)); + } + + [Fact] + public void Crytonote_Hash_Slow_Lite() + { + var blobConverted = "0106f1adafd505583cf50bcc743d04d831d2b119dc94ad88679e359076ee3f18d258ee138b3b42597710c48c6d885e2622f40f82ecd9b9fd538f28df9b0557e07cd3237a31c76569ada98001".HexToByteArray(); + var result = LibCryptonote.CryptonightHashSlowLite(blobConverted).ToHexString(); + + Assert.Equal("0769caee428a232cffb76fa200f174ff962734f24e7b3bf8d1b0d4e8ba6ceebf", result); + } + + [Fact] + public void Crytonote_ConvertBlob() + { + var blob = "0106e5b3afd505583cf50bcc743d04d831d2b119dc94ad88679e359076ee3f18d258ee138b3b421c0300a401d90101ff9d0106d6d6a88702023c62e43372a58cb588147e20be53a27083f5c522f33c722b082ab7518c48cda280b4c4c32102609ec96e2499ee267d70efefc49f26e330526d3ef455314b7b5ba268a6045f8c80c0fc82aa0202fe5cc0fa56c4277d1a47827edce4725571529d57f33c73ada481ef84c323f30a8090cad2c60e02d88bf5e72a611c8b8464ce29e3b1adbfe1ae163886d9150fe511171cada98fcb80e08d84ddcb0102441915aaf9fbaf70ff454c701a6ae2bd59bb94dc0b888bf7e5d06274ee9238ca80c0caf384a302024078526e2132def44bde2806242652f5944e632f7d94290dd6ee5dda1929f5ee2b016e29f25f07ec2a8df59f0e118a6c9a4b769b745dc0c729071f6e0399d2585745020800000000012e7f76000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".HexToByteArray(); + var result = LibCryptonote.ConvertBlob(blob, 330).ToHexString(); + + Assert.Equal("0106e5b3afd505583cf50bcc743d04d831d2b119dc94ad88679e359076ee3f18d258ee138b3b421c0300a4487286e262e95b8d2163a0c8b73527e8c9425adbdc4e532cf0ef4241f9ffbe9e01", result); + } + + [Fact] + public void Cryptonote_ConvertBlob_Should_Throw_On_Null_Argument() + { + Assert.Throws(() => LibCryptonote.ConvertBlob(null, 0)); + } + + [Fact] + public void Crytonote_DecodeAddress() + { + var address = "48nhyWcSey31ngSEhV8j8NPm6B8PistCQJBjjDjmTvRSTWYg6iocAw131vE2JPh3ps33vgQDKLrUx3fcErusYWcMJBxpm1d"; + var result = LibCryptonote.DecodeAddress(address); + + Assert.Equal(18ul, result); + } + + [Fact] + public void Cryptonote_DecodeAddress_Should_Throw_On_Null_Or_Empty_Argument() + { + Assert.Throws(() => LibCryptonote.DecodeAddress(null)); + Assert.Throws(() => LibCryptonote.DecodeAddress("")); + } + + [Fact] + public void Crytonote_DecodeIntegratedAddress() + { + var address = "4BrL51JCc9NGQ71kWhnYoDRffsDZy7m1HUU7MRU4nUMXAHNFBEJhkTZV9HdaL4gfuNBxLPc3BeMkLGaPbF5vWtANQsGwTGg55Kq4p3ENE7"; + var result = LibCryptonote.DecodeIntegratedAddress(address); + + Assert.Equal(19ul, result); + } + } +} diff --git a/src/MiningCore.Tests/Crypto/HashingTests.cs b/src/MiningCore.Tests/Crypto/HashingTests.cs index a0be06bd4..ae8292e44 100644 --- a/src/MiningCore.Tests/Crypto/HashingTests.cs +++ b/src/MiningCore.Tests/Crypto/HashingTests.cs @@ -18,7 +18,7 @@ public class HashingTests : TestBase private static readonly byte[] testValue2 = Enumerable.Repeat((byte)0x80, 80).ToArray(); [Fact] - public void Blake_Hash_Should_Match() + public void Blake_Hash() { var hasher = new Blake(); var result = hasher.Digest(testValue).ToHexString(); @@ -34,7 +34,7 @@ public void Blake_Hash_Should_Throw_On_Null_Input() } [Fact] - public void Blake2s_Hash_Should_Match() + public void Blake2s_Hash() { var hasher = new Blake2s(); var result = hasher.Digest(testValue2).ToHexString(); @@ -50,7 +50,7 @@ public void Blake2s_Hash_Should_Throw_On_Null_Input() } [Fact] - public void Groestl_Hash_Should_Match() + public void Groestl_Hash() { var hasher = new Groestl(); var result = hasher.Digest(testValue).ToHexString(); @@ -66,7 +66,7 @@ public void Groestl_Hash_Should_Throw_On_Null_Input() } [Fact] - public void Kezzak_Hash_Should_Match() + public void Kezzak_Hash() { var hasher = new Kezzak(); var result = hasher.Digest(testValue, 0ul).ToHexString(); @@ -82,7 +82,7 @@ public void Kezzak_Hash_Should_Throw_On_Null_Input() } [Fact] - public void Scrypt_Hash_Should_Match() + public void Scrypt_Hash() { var hasher = new Scrypt(1024, 1); var result = hasher.Digest(testValue).ToHexString(); @@ -98,7 +98,7 @@ public void Scrypt_Hash_Should_Throw_On_Null_Input() } [Fact] - public void NeoScrypt_Hash_Should_Match() + public void NeoScrypt_Hash() { var hasher = new NeoScrypt(0); var result = hasher.Digest(testValue2).ToHexString(); @@ -114,7 +114,7 @@ public void NeoScrypt_Hash_Should_Throw_On_Null_Input() } [Fact] - public void ScryptN_Hash_Should_Match() + public void ScryptN_Hash() { var clock = new MockMasterClock { CurrentTime = new DateTime(2017, 10, 16) }; var hasher = new ScryptN(clock, new []{ Tuple.Create(2048L, 1389306217L) }); @@ -132,7 +132,7 @@ public void ScryptN_Hash_Should_Throw_On_Null_Input() } [Fact] - public void Lyra2Rev2_Hash_Should_Match() + public void Lyra2Rev2_Hash() { var hasher = new Lyra2Rev2(); var result = hasher.Digest(Enumerable.Repeat((byte) 5, 80).ToArray()).ToHexString(); @@ -155,7 +155,7 @@ public void Lyra2Rev2_Hash_Should_Throw_On_Short_Input() } [Fact] - public void Sha256D_Hash_Should_Match() + public void Sha256D_Hash() { var hasher = new Sha256D(); var result = hasher.Digest(testValue).ToHexString(); @@ -171,7 +171,7 @@ public void Sha256D_Hash_Should_Throw_On_Null_Input() } [Fact] - public void Sha256S_Hash_Should_Match() + public void Sha256S_Hash() { var hasher = new Sha256S(); var result = hasher.Digest(testValue).ToHexString(); @@ -187,7 +187,7 @@ public void Sha256S_Hash_Should_Throw_On_Null_Input() } [Fact] - public void X11_Hash_Should_Match() + public void X11_Hash() { var hasher = new X11(); var result = hasher.Digest(testValue).ToHexString(); @@ -203,7 +203,7 @@ public void X11_Hash_Should_Throw_On_Null_Input() } [Fact] - public void X17_Hash_Should_Match() + public void X17_Hash() { var hasher = new X17(); var result = hasher.Digest(testValue).ToHexString(); @@ -219,7 +219,7 @@ public void X17_Hash_Should_Throw_On_Null_Input() } [Fact] - public void Skein_Hash_Should_Match() + public void Skein_Hash() { var hasher = new Skein(); var result = hasher.Digest(testValue).ToHexString(); @@ -235,7 +235,7 @@ public void Skein_Hash_Should_Throw_On_Null_Input() } [Fact] - public void Qubit_Hash_Should_Match() + public void Qubit_Hash() { var hasher = new Qubit(); var result = hasher.Digest(testValue).ToHexString(); @@ -251,7 +251,7 @@ public void Qubit_Hash_Should_Throw_On_Null_Input() } [Fact] - public void GroestlMyriad_Hash_Should_Match() + public void GroestlMyriad_Hash() { var hasher = new GroestlMyriad(); var result = hasher.Digest(testValue).ToHexString(); @@ -267,7 +267,7 @@ public void GroestlMyriad_Hash_Should_Throw_On_Null_Input() } [Fact] - public void DigestReverser_Hash_Should_Match() + public void DigestReverser_Hash() { var hasher = new DigestReverser(new Sha256S()); var result = hasher.Digest(testValue).ToHexString(); @@ -317,7 +317,7 @@ public void EquihashVerifier_Should_Throw_On_Wrong_Argument_Length() } [Fact] - public void Sha3_256_Hash_Should_Match() + public void Sha3_256_Hash() { var hasher = new Sha3_256(); var result = hasher.Digest(testValue).ToHexString(); @@ -333,7 +333,7 @@ public void Sha3_256_Hash_Should_Throw_On_Null_Input() } [Fact] - public void Sha3_512_Hash_Should_Match() + public void Sha3_512_Hash() { var hasher = new Sha3_512(); var result = hasher.Digest(testValue).ToHexString(); diff --git a/src/MiningCore.Tests/Native/LibCryptonoteTests.cs b/src/MiningCore.Tests/Native/LibCryptonoteTests.cs deleted file mode 100644 index a699012e7..000000000 --- a/src/MiningCore.Tests/Native/LibCryptonoteTests.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Linq; -using System.Text; -using MiningCore.Extensions; -using MiningCore.Native; -using Xunit; - -namespace MiningCore.Tests.Native -{ - public class HashinLibCryptonoteTestsgTests : TestBase - { - private static readonly byte[] testValue = Enumerable.Repeat((byte)0x80, 128).ToArray(); - - [Fact] - public void Cryptonote_SlowHash_Should_Match() - { - var result = LibCryptonote.CryptonightHashSlow(testValue).ToHexString(); - - Assert.Equal("9a267e32aefcc40ab12a906fd3f2de45a24a5ccde9e8b84528e656577f14e0fe", result); - } - - [Fact] - public void Cryptonote_SlowHash_Should_Throw_On_Null_Argument() - { - Assert.Throws(() => LibCryptonote.CryptonightHashSlow(null)); - } - - [Fact] - public void Cryptonote_FastHash_Should_Match() - { - var result = LibCryptonote.CryptonightHashFast(testValue).ToHexString(); - - Assert.Equal("80ad002b1c333a29913d9edb5340412b121c0e9045e59fa9b2aabb53f9dcc92d", result); - } - - [Fact] - public void Cryptonote_FastHash_Should_Throw_On_Null_Argument() - { - Assert.Throws(() => LibCryptonote.CryptonightHashFast(null)); - } - - [Fact] - public void Cryptonote_DecodeAddress_Should_Match() - { - var result = LibCryptonote.DecodeAddress("9wviCeWe2D8XS82k2ovp5EUYLzBt9pYNW2LXUFsZiv8S3Mt21FZ5qQaAroko1enzw3eGr9qC7X1D7Geoo2RrAotYPwq9Gm8"); - Assert.Equal(53u, result); - } - - [Fact] - public void Cryptonote_DecodeAddress_Should_Throw_On_Null_Or_Empty_Argument() - { - Assert.Throws(() => LibCryptonote.DecodeAddress(null)); - Assert.Throws(() => LibCryptonote.DecodeAddress("")); - } - - [Fact] - public void Cryptonote_ConvertBlob_Should_Match() - { - var blob = "0105bfdfcecd05583cf50bcc743d04d831d2b119dc94ad88679e359076ee3f18d258ee138b3b420000000001d90101ff9d0106d6d6a88702020a79e36c5f5ac69abb68daa616b70e4dc911ed2edf50133fc121447cc403cd6780b4c4c32102b3adc5521c68a35e2dd1934e30b5fada872b384dbbf8c4e8130e43bd0097b8b680c0fc82aa0202b186f6745517ec23a87df7811849d71914a222c937da3e3a39c7bde6f27d2dc98090cad2c60e02df3a6eed49d05b0163986888ebe7da3fae808a72f3beec97346e0a18a960a7b180e08d84ddcb0102f37220a0c601e2dfe78cfab584cabeecf59079b3b2ee045561fb83ebf67941ba80c0caf384a30202b5e50c62333f3237d497eac37b26bd1217b6996eeb7d45e099b71b0f0b5399162b011c2515730ca7e8bb9b79e177557a1fa8b41e9aee544b25d69dc46f12f66b13f102080000000001ff0d7500".HexToByteArray(); - var result = LibCryptonote.ConvertBlob(blob, blob.Length).ToHexString(); - var expected = "0105bfdfcecd05583cf50bcc743d04d831d2b119dc94ad88679e359076ee3f18d258ee138b3b4200000000f262fa431f692fa1d8a6e89fb809487a2133dd6fd999d95c664b964df354ac4701"; - Assert.Equal(expected, result); - } - - [Fact] - public void Cryptonote_ConvertBlob_Should_Throw_On_Null_Argument() - { - Assert.Throws(() => LibCryptonote.ConvertBlob(null, 0)); - } - } -} diff --git a/src/MiningCore/AutoMapperProfile.cs b/src/MiningCore/AutoMapperProfile.cs index e76232781..5dd268ac1 100644 --- a/src/MiningCore/AutoMapperProfile.cs +++ b/src/MiningCore/AutoMapperProfile.cs @@ -34,9 +34,9 @@ public AutoMapperProfile() ////////////////////// // outgoing mappings - CreateMap(); + CreateMap(); - CreateMap() + CreateMap() .ForMember(dest => dest.Reward, opt => opt.MapFrom(src => src.BlockReward)) .ForMember(dest => dest.Hash, opt => opt.MapFrom(src => src.BlockHash)) .ForMember(dest => dest.Status, opt => opt.Ignore()); @@ -69,7 +69,7 @@ public AutoMapperProfile() CreateMap(); // PostgreSQL - CreateMap(); + CreateMap(); CreateMap(); CreateMap(); CreateMap(); @@ -82,7 +82,7 @@ public AutoMapperProfile() // incoming mappings // PostgreSQL - CreateMap(); + CreateMap(); CreateMap(); CreateMap(); CreateMap(); diff --git a/src/MiningCore/Blockchain/Abstractions.cs b/src/MiningCore/Blockchain/Abstractions.cs index 9b2c1ff45..a583f83b0 100644 --- a/src/MiningCore/Blockchain/Abstractions.cs +++ b/src/MiningCore/Blockchain/Abstractions.cs @@ -37,84 +37,4 @@ public interface IExtraNonceProvider { string Next(); } - - public interface IShare - { - /// - /// The pool originating this share from - /// - string PoolId { get; set; } - - /// - /// Who mined it (wallet address) - /// - string Miner { get; } - - /// - /// Who mined it - /// - string Worker { get; } - - /// - /// Extra information for payout processing - /// - string PayoutInfo { get; set; } - - /// - /// Mining Software - /// - string UserAgent { get; } - - /// - /// From where was it submitted - /// - string IpAddress { get; } - - /// - /// Submission source (pool, external stratum etc) - /// - string Source { get; set; } - - /// - /// Stratum difficulty assigned to the miner at the time the share was submitted/accepted (used for payout - /// calculations) - /// - double Difficulty { get; set; } - - /// - /// Block this share refers to - /// - long BlockHeight { get; set; } - - /// - /// Block reward after deducting pool fee and donations - /// - decimal BlockReward { get; set; } - - /// - /// Block hash - /// - string BlockHash { get; set; } - - /// - /// If this share presumably resulted in a block - /// - bool IsBlockCandidate { get; set; } - - /// - /// Arbitrary data to be interpreted by the payment processor specialized - /// in this coin to verify this block candidate was accepted by the network - /// - string TransactionConfirmationData { get; set; } - - /// - /// Network difficulty at the time the share was submitted (used for some payout schemes like PPLNS) - /// - double NetworkDifficulty { get; set; } - - /// - /// When the share was found - /// - DateTime Created { get; set; } - } } diff --git a/src/MiningCore/Blockchain/Bitcoin/BitcoinJob.cs b/src/MiningCore/Blockchain/Bitcoin/BitcoinJob.cs index de532815b..2fd135fef 100644 --- a/src/MiningCore/Blockchain/Bitcoin/BitcoinJob.cs +++ b/src/MiningCore/Blockchain/Bitcoin/BitcoinJob.cs @@ -99,15 +99,12 @@ protected virtual void BuildMerkleBranches() protected virtual void BuildCoinbase() { - var extraNoncePlaceHolderLengthByte = (byte) extraNoncePlaceHolderLength; - // generate script parts var sigScriptInitial = GenerateScriptSigInitial(); var sigScriptInitialBytes = sigScriptInitial.ToBytes(); var sigScriptLength = (uint) ( sigScriptInitial.Length + - 1 + // for extranonce-placeholder length after sigScriptInitial extraNoncePlaceHolderLength + scriptSigFinalBytes.Length); @@ -138,9 +135,6 @@ protected virtual void BuildCoinbase() bs.ReadWriteAsVarInt(ref sigScriptLength); bs.ReadWrite(ref sigScriptInitialBytes); - // emit a simulated OP_PUSH(n) just without the payload (which is filled in by the miner: extranonce1 and extranonce2) - bs.ReadWrite(ref extraNoncePlaceHolderLengthByte); - // done coinbaseInitial = stream.ToArray(); coinbaseInitialHex = coinbaseInitial.ToHexString(); @@ -235,6 +229,9 @@ protected virtual Script GenerateScriptSigInitial() // push timestamp ops.Add(Op.GetPushOp(now)); + // push placeholder + ops.Add(Op.GetPushOp((uint) 0)); + return new Script(ops); } @@ -289,7 +286,7 @@ protected virtual byte[] SerializeHeader(byte[] coinbaseHash, uint nTime, uint n return blockHeader.ToBytes(); } - protected virtual BitcoinShare ProcessShareInternal(StratumClient worker, string extraNonce2, uint nTime, uint nonce) + protected virtual (Share Share, string BlockHex) ProcessShareInternal(StratumClient worker, string extraNonce2, uint nTime, uint nonce) { var context = worker.GetContextAs(); var extraNonce1 = context.ExtraNonce1; @@ -330,24 +327,26 @@ protected virtual BitcoinShare ProcessShareInternal(StratumClient worker, string throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})"); } - var result = new BitcoinShare + var result = new Share { BlockHeight = BlockTemplate.Height, - BlockReward = rewardToPool.ToDecimal(MoneyUnit.BTC), NetworkDifficulty = Difficulty * shareMultiplier, Difficulty = stratumDifficulty, }; - var blockBytes = SerializeBlock(headerBytes, coinbase); - if (isBlockCandidate) { result.IsBlockCandidate = true; - result.BlockHex = blockBytes.ToHexString(); + result.BlockReward = rewardToPool.ToDecimal(MoneyUnit.BTC); result.BlockHash = blockHasher.Digest(headerBytes, nTime).ToHexString(); + + var blockBytes = SerializeBlock(headerBytes, coinbase); + var blockHex = blockBytes.ToHexString(); + + return (result, blockHex); } - return result; + return (result, null); } protected virtual byte[] SerializeCoinbase(string extraNonce1, string extraNonce2) @@ -479,7 +478,7 @@ public virtual object GetJobParams(bool isNew) return jobParams; } - public virtual BitcoinShare ProcessShare(StratumClient worker, + public virtual (Share Share, string BlockHex) ProcessShare(StratumClient worker, string extraNonce2, string nTime, string nonce) { Contract.RequiresNonNull(worker, nameof(worker)); diff --git a/src/MiningCore/Blockchain/Bitcoin/BitcoinJobManager.cs b/src/MiningCore/Blockchain/Bitcoin/BitcoinJobManager.cs index 71313b26b..398b72e16 100644 --- a/src/MiningCore/Blockchain/Bitcoin/BitcoinJobManager.cs +++ b/src/MiningCore/Blockchain/Bitcoin/BitcoinJobManager.cs @@ -20,6 +20,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Net; using System.Reactive.Linq; @@ -269,23 +270,25 @@ private async Task UpdateNetworkStatsAsync() BlockchainStats.ConnectedPeers = networkInfoResponse.Connections; } - protected virtual async Task<(bool Accepted, string CoinbaseTransaction)> SubmitBlockAsync(BitcoinShare share) + protected virtual async Task<(bool Accepted, string CoinbaseTransaction)> SubmitBlockAsync(Share share, string blockHex) { // execute command batch var results = await daemon.ExecuteBatchAnyAsync( hasSubmitBlockMethod - ? new DaemonCmd(BitcoinCommands.SubmitBlock, new[] { share.BlockHex }) - : new DaemonCmd(BitcoinCommands.GetBlockTemplate, new { mode = "submit", data = share.BlockHex }), + ? new DaemonCmd(BitcoinCommands.SubmitBlock, new[] { blockHex }) + : new DaemonCmd(BitcoinCommands.GetBlockTemplate, new { mode = "submit", data = blockHex }), new DaemonCmd(BitcoinCommands.GetBlock, new[] { share.BlockHash })); // did submission succeed? var submitResult = results[0]; - var submitError = submitResult.Error?.Message ?? submitResult.Response?.ToString(); + var submitError = submitResult.Error?.Message ?? + submitResult.Error?.Code.ToString(CultureInfo.InvariantCulture) ?? + submitResult.Response?.ToString(); if (!string.IsNullOrEmpty(submitError)) { logger.Warn(() => $"[{LogCat}] Block {share.BlockHeight} submission failed with: {submitError}"); - notificationService.NotifyAdmin("Block submission failed", $"Block {share.BlockHeight} submission failed with: {submitError}"); + notificationService.NotifyAdmin($"[{share.PoolId.ToUpper()}]-[{share.Source}] Block submission failed", $"[{share.PoolId.ToUpper()}]-[{share.Source}] Block {share.BlockHeight} submission failed with: {submitError}"); return (false, null); } @@ -298,7 +301,7 @@ private async Task UpdateNetworkStatsAsync() if (!accepted) { logger.Warn(() => $"[{LogCat}] Block {share.BlockHeight} submission failed for pool {poolConfig.Id} because block was not found after submission"); - notificationService.NotifyAdmin("Block submission failed", $"Block {share.BlockHeight} submission failed for pool {poolConfig.Id} because block was not found after submission"); + notificationService.NotifyAdmin($"[{share.PoolId.ToUpper()}]-[{share.Source}] Block submission failed", $"[{share.PoolId.ToUpper()}]-[{share.Source}] Block {share.BlockHeight} submission failed for pool {poolConfig.Id} because block was not found after submission"); } return (accepted, block?.Transactions.FirstOrDefault()); @@ -445,7 +448,7 @@ public string[] GetTransactions(StratumClient worker, object requestParams) return job.BlockTemplate.Transactions.Select(x => x.Data).ToArray(); } - public virtual async Task SubmitShareAsync(StratumClient worker, object submission, + public virtual async Task SubmitShareAsync(StratumClient worker, object submission, double stratumDifficultyBase) { Contract.RequiresNonNull(worker, nameof(worker)); @@ -484,14 +487,23 @@ public virtual async Task SubmitShareAsync(StratumClient worker, o var workerName = split.Length > 1 ? split[1] : null; // validate & process - var share = job.ProcessShare(worker, extraNonce2, nTime, nonce); + var (share, blockHex) = job.ProcessShare(worker, extraNonce2, nTime, nonce); + + // enrich share with common data + share.PoolId = poolConfig.Id; + share.IpAddress = worker.RemoteEndpoint.Address.ToString(); + share.Miner = minerName; + share.Worker = workerName; + share.UserAgent = context.UserAgent; + share.Source = clusterConfig.ClusterName; + share.Created = clock.Now; // if block candidate, submit & check if accepted by network if (share.IsBlockCandidate) { logger.Info(() => $"[{LogCat}] Submitting block {share.BlockHeight} [{share.BlockHash}]"); - var acceptResponse = await SubmitBlockAsync(share); + var acceptResponse = await SubmitBlockAsync(share, blockHex); // is it still a block candidate? share.IsBlockCandidate = acceptResponse.Accepted; @@ -512,15 +524,6 @@ public virtual async Task SubmitShareAsync(StratumClient worker, o } } - // enrich share with common data - share.PoolId = poolConfig.Id; - share.IpAddress = worker.RemoteEndpoint.Address.ToString(); - share.Miner = minerName; - share.Worker = workerName; - share.UserAgent = context.UserAgent; - share.Source = clusterConfig.ClusterName; - share.Created = clock.Now; - return share; } @@ -666,7 +669,8 @@ protected override async Task PostStartInitAsync() else networkType = daemonInfoResponse.Testnet ? BitcoinNetworkType.Test : BitcoinNetworkType.Main; - ConfigureRewards(); + if(clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true) + ConfigureRewards(); // update stats BlockchainStats.NetworkType = networkType.ToString(); diff --git a/src/MiningCore/Blockchain/Bitcoin/BitcoinPoolBase.cs b/src/MiningCore/Blockchain/Bitcoin/BitcoinPoolBase.cs index fa368b2c8..a7be6c34d 100644 --- a/src/MiningCore/Blockchain/Bitcoin/BitcoinPoolBase.cs +++ b/src/MiningCore/Blockchain/Bitcoin/BitcoinPoolBase.cs @@ -40,7 +40,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace MiningCore.Blockchain.Bitcoin { - public class BitcoinPoolBase : PoolBase + public class BitcoinPoolBase : PoolBase where TBlockTemplate : BlockTemplate where TJob : BitcoinJob, new() { diff --git a/src/MiningCore/Blockchain/Bitcoin/BitcoinProperties.cs b/src/MiningCore/Blockchain/Bitcoin/BitcoinProperties.cs index 40e4a661d..c888b92ef 100644 --- a/src/MiningCore/Blockchain/Bitcoin/BitcoinProperties.cs +++ b/src/MiningCore/Blockchain/Bitcoin/BitcoinProperties.cs @@ -44,6 +44,7 @@ public class BitcoinProperties private static readonly IHashAlgorithm qubit = new Qubit(); private static readonly IHashAlgorithm groestlMyriad = new GroestlMyriad(); private static readonly IHashAlgorithm neoScryptProfile1 = new NeoScrypt(0x80000620); + private static readonly IHashAlgorithm vergeBlockHasher = new DigestReverser(scrypt_1024_1); private static readonly BitcoinCoinProperties sha256Coin = new BitcoinCoinProperties(1, sha256D, sha256D, sha256DReverse, "Sha256"); @@ -79,16 +80,19 @@ public class BitcoinProperties new BitcoinCoinProperties(Math.Pow(2, 16), sha256D, neoScryptProfile1, new DigestReverser(neoScryptProfile1), "Neoscrypt"); private static readonly BitcoinCoinProperties vergeLyraCoin = - new BitcoinCoinProperties(Math.Pow(2, 8), sha256D, lyra2Rev2, new DigestReverser(scrypt_1024_1), "Lyra2re2"); + new BitcoinCoinProperties(Math.Pow(2, 8), sha256D, lyra2Rev2, vergeBlockHasher, "Lyra2re2"); private static readonly BitcoinCoinProperties vergeBlake2sCoin = - new BitcoinCoinProperties(1, sha256D, blake2s, new DigestReverser(scrypt_1024_1), "Blake2s"); + new BitcoinCoinProperties(1, sha256D, blake2s, vergeBlockHasher, "Blake2s"); private static readonly BitcoinCoinProperties vergeX17Coin = - new BitcoinCoinProperties(1, x17, blake2s, new DigestReverser(scrypt_1024_1), "X17"); + new BitcoinCoinProperties(1, sha256D, x17, vergeBlockHasher, "X17"); + + private static readonly BitcoinCoinProperties vergeScryptCoin = + new BitcoinCoinProperties(Math.Pow(2, 16), sha256D, scrypt_1024_1, vergeBlockHasher, "Scrypt"); private static readonly BitcoinCoinProperties vergeGroestlCoin = - new BitcoinCoinProperties(1, groestlMyriad, blake2s, new DigestReverser(scrypt_1024_1), "Groestl-Myriad"); + new BitcoinCoinProperties(1, sha256D, groestlMyriad, vergeBlockHasher, "Groestl-Myriad"); private static readonly Dictionary coinProperties = new Dictionary { @@ -97,7 +101,7 @@ public class BitcoinProperties { CoinType.BCH, sha256Coin }, { CoinType.NMC, sha256Coin }, { CoinType.PPC, sha256Coin }, - { CoinType.GLT, sha256Coin }, + { CoinType.GLT, sha256Coin }, // Scrypt { CoinType.LTC, scryptCoin }, @@ -145,9 +149,10 @@ private static BitcoinCoinProperties GetDigiByteProperties(string algorithm) { Contract.Requires(!string.IsNullOrEmpty(algorithm), $"{nameof(algorithm)} must not be empty"); - switch(algorithm.ToLower()) + switch (algorithm.ToLower()) { case "sha256d": + case "sha256": return sha256Coin; case "skein": @@ -184,7 +189,7 @@ private static BitcoinCoinProperties GetVergeProperties(string algorithm) return vergeBlake2sCoin; default: // scrypt - return scryptCoin; + return vergeScryptCoin; } } diff --git a/src/MiningCore/Blockchain/Bitcoin/BitcoinShare.cs b/src/MiningCore/Blockchain/Bitcoin/BitcoinShare.cs deleted file mode 100644 index d3bd3d317..000000000 --- a/src/MiningCore/Blockchain/Bitcoin/BitcoinShare.cs +++ /dev/null @@ -1,27 +0,0 @@ -/* -Copyright 2017 Coin Foundry (coinfoundry.org) -Authors: Oliver Weichhold (oliver@weichhold.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT -LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -namespace MiningCore.Blockchain.Bitcoin -{ - public class BitcoinShare : ShareBase - { - public string BlockHex { get; set; } - } -} diff --git a/src/MiningCore/Blockchain/CoinMetaData.cs b/src/MiningCore/Blockchain/CoinMetaData.cs index f97494bbd..76b24bd92 100644 --- a/src/MiningCore/Blockchain/CoinMetaData.cs +++ b/src/MiningCore/Blockchain/CoinMetaData.cs @@ -1,150 +1,154 @@ -using System; -using System.Collections.Generic; -using MiningCore.Blockchain.Bitcoin; -using MiningCore.Blockchain.Ethereum; -using MiningCore.Configuration; - -namespace MiningCore.Blockchain -{ - public static class CoinMetaData - { - public const string BlockHeightPH = "$height$"; - public const string BlockHashPH = "$hash$"; - - public static readonly Dictionary> BlockInfoLinks = new Dictionary> - { - { CoinType.ETH, new Dictionary - { - { string.Empty, $"https://etherscan.io/block/{BlockHeightPH}" }, - { EthereumConstants.BlockTypeUncle, $"https://etherscan.io/uncle/{BlockHeightPH}" }, - }}, - - { CoinType.ETC, new Dictionary - { - { string.Empty, $"https://gastracker.io/block/{BlockHeightPH}" }, - { EthereumConstants.BlockTypeUncle, $"https://gastracker.io/uncle/{BlockHeightPH}" } - }}, - - { CoinType.XMR, new Dictionary { { string.Empty, $"https://chainradar.com/xmr/block/{BlockHeightPH}" }}}, - { CoinType.ETN, new Dictionary { { string.Empty, $"https://blockexplorer.electroneum.com/block/{BlockHeightPH}" } }}, - { CoinType.LTC, new Dictionary { { string.Empty, $"https://chainz.cryptoid.info/ltc/block.dws?{BlockHeightPH}.htm" } }}, - { CoinType.BCH, new Dictionary { { string.Empty, $"https://www.blocktrail.com/BCC/block/{BlockHeightPH}" }}}, - { CoinType.DASH, new Dictionary { { string.Empty, $"https://chainz.cryptoid.info/dash/block.dws?{BlockHeightPH}.htm" }}}, - { CoinType.BTC, new Dictionary { { string.Empty, $"https://blockchain.info/block/{BlockHeightPH}" }}}, - { CoinType.DOGE, new Dictionary { { string.Empty, $"https://dogechain.info/block/{BlockHeightPH}" }}}, - { CoinType.ZEC, new Dictionary { { string.Empty, $"https://explorer.zcha.in/blocks/{BlockHashPH}" } }}, - { CoinType.BTCP, new Dictionary { { string.Empty, $"http://explorer.btcprivate.org/block/{BlockHashPH}" } }}, - { CoinType.ZCL, new Dictionary { { string.Empty, $"http://explorer.zclmine.pro/block/{BlockHeightPH}" }}}, - { CoinType.ZEN, new Dictionary { { string.Empty, $"http://explorer.zensystem.io/block/{BlockHashPH}" } }}, - { CoinType.DGB, new Dictionary { { string.Empty, $"https://digiexplorer.info/block/{BlockHeightPH}" }}}, - { CoinType.NMC, new Dictionary { { string.Empty, $"https://explorer.namecoin.info/b/{BlockHeightPH}" }}}, - { CoinType.GRS, new Dictionary { { string.Empty, $"https://groestlsight.groestlcoin.org/block/{BlockHeightPH}" }}}, - { CoinType.MONA, new Dictionary { { string.Empty, $"https://bchain.info/MONA/block/{BlockHeightPH}" }}}, - { CoinType.GLT, new Dictionary { { string.Empty, $"https://bchain.info/GLT/block/{BlockHeightPH}" }}}, - { CoinType.VTC, new Dictionary { { string.Empty, $"https://bchain.info/VTC/block/{BlockHeightPH}" }}}, - { CoinType.BTG, new Dictionary { { string.Empty, $"https://btg-bitcore2.trezor.io/block/{BlockHashPH}" } }}, - { CoinType.ELLA, new Dictionary { { string.Empty, $"https://explorer.ellaism.org/block/{BlockHeightPH}" }}}, - { CoinType.EXP, new Dictionary { { string.Empty, $"http://www.gander.tech/blocks/{BlockHeightPH}" }}}, - { CoinType.AEON, new Dictionary { { string.Empty, $"https://chainradar.com/aeon/block/{BlockHeightPH}" }}}, - { CoinType.STAK, new Dictionary { { string.Empty, $"https://straks.info/block/{BlockHeightPH}" }}}, - { CoinType.MOON, new Dictionary { { string.Empty, $"https://chainz.cryptoid.info/moon/block.dws?{BlockHeightPH}.htm" }}}, - { CoinType.XVG, new Dictionary { { string.Empty, $"https://verge-blockchain.info/block/{BlockHashPH}" } }}, - }; - - public static readonly Dictionary TxInfoLinks = new Dictionary - { - { CoinType.XMR, "https://chainradar.com/xmr/transaction/{0}" }, - { CoinType.ETN, "https://blockexplorer.electroneum.com/tx/{0}" }, - { CoinType.ETH, "https://etherscan.io/tx/{0}" }, - { CoinType.ETC, "https://gastracker.io/tx/{0}" }, - { CoinType.LTC, "https://chainz.cryptoid.info/ltc/tx.dws?{0}.htm" }, - { CoinType.BCH, "https://www.blocktrail.com/BCC/tx/{0}" }, - { CoinType.DASH, "https://chainz.cryptoid.info/dash/tx.dws?{0}.htm" }, - { CoinType.BTC, "https://blockchain.info/tx/{0}" }, - { CoinType.DOGE, "https://dogechain.info/tx/{0}" }, - { CoinType.ZEC, "https://explorer.zcha.in/transactions/{0}" }, - { CoinType.ZCL, "http://explorer.zclmine.pro/transactions/{0}" }, - { CoinType.ZEN, "http://explorer.zensystem.io/transactions/{0}" }, - { CoinType.BTCP, "https://explorer.btcprivate.org/transactions/{0}" }, - { CoinType.DGB, "https://digiexplorer.info/tx/{0}" }, - { CoinType.NMC, "https://explorer.namecoin.info/tx/{0}" }, - { CoinType.GRS, "https://groestlsight.groestlcoin.org/tx/{0}" }, - { CoinType.MONA, "https://bchain.info/MONA/tx/{0}" }, - { CoinType.STAK, "https://straks.info/transaction/{0}" }, - { CoinType.GLT, "https://bchain.info/GLT/tx/{0}" }, - { CoinType.VTC, "https://bchain.info/VTC/tx/{0}" }, - { CoinType.BTG, "https://btgexp.com/tx/{0}" }, - { CoinType.ELLA, "https://explorer.ellaism.org/tx/{0}" }, - { CoinType.EXP, "http://www.gander.tech/tx/{0}" }, - { CoinType.AEON, "https://chainradar.com/aeon/transaction/{0}" }, - { CoinType.MOON, "https://chainz.cryptoid.info/moon/tx.dws?{0}.htm" }, - { CoinType.XVG, "https://verge-blockchain.info/tx/{0}" }, - { CoinType.GBX, "http://gobyte.ezmine.io/tx/{0}" }, - { CoinType.CRC, "http://explorer.cryptopros.us/tx/{0}" }, - }; - - public static readonly Dictionary AddressInfoLinks = new Dictionary - { - { CoinType.ETH, "https://etherscan.io/address/{0}" }, - { CoinType.ETC, "https://gastracker.io/addr/{0}" }, - { CoinType.LTC, "https://chainz.cryptoid.info/ltc/address.dws?{0}.htm" }, - { CoinType.BCH, "https://www.blocktrail.com/BCC/address/{0}" }, - { CoinType.DASH, "https://chainz.cryptoid.info/dash/address.dws?{0}.htm" }, - { CoinType.BTC, "https://blockchain.info/address/{0}" }, - { CoinType.DOGE, "https://dogechain.info/address/{0}" }, - { CoinType.ZEC, "https://explorer.zcha.in/accounts/{0}" }, - { CoinType.ZCL, "http://explorer.zclmine.pro/accounts/{0}" }, - { CoinType.ZEN, "http://explorer.zensystem.io/accounts/{0}" }, - { CoinType.DGB, "https://digiexplorer.info/address/{0}" }, - { CoinType.NMC, "https://explorer.namecoin.info/a/{0}" }, - { CoinType.GRS, "https://groestlsight.groestlcoin.org/address/{0}" }, - { CoinType.MONA, "https://bchain.info/MONA/addr/{0}" }, - { CoinType.STAK, "https://straks.info/address/{0}" }, - { CoinType.GLT, "https://bchain.info/GLT/addr/{0}" }, - { CoinType.VTC, "https://bchain.info/VTC/addr/{0}" }, - { CoinType.BTG, "https://btgexp.com/address/{0}" }, - { CoinType.ELLA, "https://explorer.ellaism.org/addr/{0}" }, - { CoinType.EXP, "http://www.gander.tech/address/{0}" }, - { CoinType.MOON, "https://chainz.cryptoid.info/moon/address.dws?{0}.htm" }, - { CoinType.XVG, "https://verge-blockchain.info/address/{0}" }, - { CoinType.GBX, "http://gobyte.ezmine.io/address/{0}" }, - { CoinType.CRC, "http://explorer.cryptopros.us/address/{0}" }, - }; - - private const string Ethash = "Dagger-Hashimoto"; - private const string Cryptonight = "Cryptonight"; - private const string CryptonightLight = "Cryptonight-Light"; - - public static readonly Dictionary> CoinAlgorithm = new Dictionary> - { - { CoinType.ETH, (coin, alg)=> Ethash }, - { CoinType.ETC, (coin, alg)=> Ethash }, - { CoinType.LTC, BitcoinProperties.GetAlgorithm }, - { CoinType.BCH, BitcoinProperties.GetAlgorithm }, - { CoinType.DASH, BitcoinProperties.GetAlgorithm }, - { CoinType.BTC, BitcoinProperties.GetAlgorithm }, - { CoinType.DOGE, BitcoinProperties.GetAlgorithm }, - { CoinType.ZEC, BitcoinProperties.GetAlgorithm }, - { CoinType.ZCL, BitcoinProperties.GetAlgorithm }, - { CoinType.BTCP, BitcoinProperties.GetAlgorithm }, - { CoinType.ZEN, BitcoinProperties.GetAlgorithm }, - { CoinType.DGB, BitcoinProperties.GetAlgorithm }, - { CoinType.NMC, BitcoinProperties.GetAlgorithm }, - { CoinType.GRS, BitcoinProperties.GetAlgorithm }, - { CoinType.MONA, BitcoinProperties.GetAlgorithm }, - { CoinType.STAK, BitcoinProperties.GetAlgorithm }, - { CoinType.GLT, BitcoinProperties.GetAlgorithm }, - { CoinType.VTC, BitcoinProperties.GetAlgorithm }, - { CoinType.BTG, BitcoinProperties.GetAlgorithm }, - { CoinType.ELLA, (coin, alg)=> Ethash }, - { CoinType.EXP, (coin, alg)=> Ethash }, - { CoinType.MOON, BitcoinProperties.GetAlgorithm }, - { CoinType.XVG, BitcoinProperties.GetAlgorithm }, - { CoinType.XMR, (coin, alg)=> Cryptonight }, - { CoinType.ETN, (coin, alg)=> Cryptonight }, - { CoinType.AEON, (coin, alg)=> CryptonightLight }, - { CoinType.GBX, BitcoinProperties.GetAlgorithm }, - { CoinType.CRC, BitcoinProperties.GetAlgorithm }, - }; - } -} +using System; +using System.Collections.Generic; +using MiningCore.Blockchain.Bitcoin; +using MiningCore.Blockchain.Ethereum; +using MiningCore.Configuration; + +namespace MiningCore.Blockchain +{ + public static class CoinMetaData + { + public const string BlockHeightPH = "$height$"; + public const string BlockHashPH = "$hash$"; + + public static readonly Dictionary> BlockInfoLinks = new Dictionary> + { + { CoinType.ETH, new Dictionary + { + { string.Empty, $"https://etherscan.io/block/{BlockHeightPH}" }, + { EthereumConstants.BlockTypeUncle, $"https://etherscan.io/uncle/{BlockHeightPH}" }, + }}, + + { CoinType.ETC, new Dictionary + { + { string.Empty, $"https://gastracker.io/block/{BlockHeightPH}" }, + { EthereumConstants.BlockTypeUncle, $"https://gastracker.io/uncle/{BlockHeightPH}" } + }}, + + { CoinType.XMR, new Dictionary { { string.Empty, $"https://chainradar.com/xmr/block/{BlockHeightPH}" }}}, + { CoinType.ETN, new Dictionary { { string.Empty, $"https://blockexplorer.electroneum.com/block/{BlockHeightPH}" } }}, + { CoinType.LTC, new Dictionary { { string.Empty, $"https://chainz.cryptoid.info/ltc/block.dws?{BlockHeightPH}.htm" } }}, + { CoinType.PPC, new Dictionary { { string.Empty, $"https://chainz.cryptoid.info/ppc/block.dws?{BlockHeightPH}.htm" } }}, + { CoinType.BCH, new Dictionary { { string.Empty, $"https://www.blocktrail.com/BCC/block/{BlockHeightPH}" }}}, + { CoinType.DASH, new Dictionary { { string.Empty, $"https://chainz.cryptoid.info/dash/block.dws?{BlockHeightPH}.htm" }}}, + { CoinType.BTC, new Dictionary { { string.Empty, $"https://blockchain.info/block/{BlockHeightPH}" }}}, + { CoinType.DOGE, new Dictionary { { string.Empty, $"https://dogechain.info/block/{BlockHeightPH}" }}}, + { CoinType.ZEC, new Dictionary { { string.Empty, $"https://explorer.zcha.in/blocks/{BlockHashPH}" } }}, + { CoinType.BTCP, new Dictionary { { string.Empty, $"http://explorer.btcprivate.org/block/{BlockHashPH}" } }}, + { CoinType.ZCL, new Dictionary { { string.Empty, $"http://explorer.zclmine.pro/block/{BlockHeightPH}" }}}, + { CoinType.ZEN, new Dictionary { { string.Empty, $"http://explorer.zensystem.io/block/{BlockHashPH}" } }}, + { CoinType.DGB, new Dictionary { { string.Empty, $"https://digiexplorer.info/block/{BlockHeightPH}" }}}, + { CoinType.NMC, new Dictionary { { string.Empty, $"https://explorer.namecoin.info/b/{BlockHeightPH}" }}}, + { CoinType.GRS, new Dictionary { { string.Empty, $"https://groestlsight.groestlcoin.org/block/{BlockHeightPH}" }}}, + { CoinType.MONA, new Dictionary { { string.Empty, $"https://bchain.info/MONA/block/{BlockHeightPH}" }}}, + { CoinType.GLT, new Dictionary { { string.Empty, $"https://bchain.info/GLT/block/{BlockHeightPH}" }}}, + { CoinType.VTC, new Dictionary { { string.Empty, $"https://bchain.info/VTC/block/{BlockHeightPH}" }}}, + { CoinType.BTG, new Dictionary { { string.Empty, $"https://btg-bitcore2.trezor.io/block/{BlockHashPH}" } }}, + { CoinType.ELLA, new Dictionary { { string.Empty, $"https://explorer.ellaism.org/block/{BlockHeightPH}" }}}, + { CoinType.EXP, new Dictionary { { string.Empty, $"http://www.gander.tech/blocks/{BlockHeightPH}" }}}, + { CoinType.AEON, new Dictionary { { string.Empty, $"https://chainradar.com/aeon/block/{BlockHeightPH}" }}}, + { CoinType.STAK, new Dictionary { { string.Empty, $"https://straks.info/block/{BlockHeightPH}" }}}, + { CoinType.MOON, new Dictionary { { string.Empty, $"https://chainz.cryptoid.info/moon/block.dws?{BlockHeightPH}.htm" }}}, + { CoinType.XVG, new Dictionary { { string.Empty, $"https://verge-blockchain.info/block/{BlockHashPH}" } }}, + }; + + public static readonly Dictionary TxInfoLinks = new Dictionary + { + { CoinType.XMR, "https://chainradar.com/xmr/transaction/{0}" }, + { CoinType.ETN, "https://blockexplorer.electroneum.com/tx/{0}" }, + { CoinType.ETH, "https://etherscan.io/tx/{0}" }, + { CoinType.ETC, "https://gastracker.io/tx/{0}" }, + { CoinType.LTC, "https://chainz.cryptoid.info/ltc/tx.dws?{0}.htm" }, + { CoinType.PPC, "https://chainz.cryptoid.info/ppc/tx.dws?{0}.htm" }, + { CoinType.BCH, "https://www.blocktrail.com/BCC/tx/{0}" }, + { CoinType.DASH, "https://chainz.cryptoid.info/dash/tx.dws?{0}.htm" }, + { CoinType.BTC, "https://blockchain.info/tx/{0}" }, + { CoinType.DOGE, "https://dogechain.info/tx/{0}" }, + { CoinType.ZEC, "https://explorer.zcha.in/transactions/{0}" }, + { CoinType.ZCL, "http://explorer.zclmine.pro/transactions/{0}" }, + { CoinType.ZEN, "http://explorer.zensystem.io/transactions/{0}" }, + { CoinType.BTCP, "https://explorer.btcprivate.org/transactions/{0}" }, + { CoinType.DGB, "https://digiexplorer.info/tx/{0}" }, + { CoinType.NMC, "https://explorer.namecoin.info/tx/{0}" }, + { CoinType.GRS, "https://groestlsight.groestlcoin.org/tx/{0}" }, + { CoinType.MONA, "https://bchain.info/MONA/tx/{0}" }, + { CoinType.STAK, "https://straks.info/transaction/{0}" }, + { CoinType.GLT, "https://bchain.info/GLT/tx/{0}" }, + { CoinType.VTC, "https://bchain.info/VTC/tx/{0}" }, + { CoinType.BTG, "https://btgexp.com/tx/{0}" }, + { CoinType.ELLA, "https://explorer.ellaism.org/tx/{0}" }, + { CoinType.EXP, "http://www.gander.tech/tx/{0}" }, + { CoinType.AEON, "https://chainradar.com/aeon/transaction/{0}" }, + { CoinType.MOON, "https://chainz.cryptoid.info/moon/tx.dws?{0}.htm" }, + { CoinType.XVG, "https://verge-blockchain.info/tx/{0}" }, + { CoinType.GBX, "http://gobyte.ezmine.io/tx/{0}" }, + { CoinType.CRC, "http://explorer.cryptopros.us/tx/{0}" }, + }; + + public static readonly Dictionary AddressInfoLinks = new Dictionary + { + { CoinType.ETH, "https://etherscan.io/address/{0}" }, + { CoinType.ETC, "https://gastracker.io/addr/{0}" }, + { CoinType.LTC, "https://chainz.cryptoid.info/ltc/address.dws?{0}.htm" }, + { CoinType.PPC, "https://chainz.cryptoid.info/ppc/address.dws?{0}.htm" }, + { CoinType.BCH, "https://www.blocktrail.com/BCC/address/{0}" }, + { CoinType.DASH, "https://chainz.cryptoid.info/dash/address.dws?{0}.htm" }, + { CoinType.BTC, "https://blockchain.info/address/{0}" }, + { CoinType.DOGE, "https://dogechain.info/address/{0}" }, + { CoinType.ZEC, "https://explorer.zcha.in/accounts/{0}" }, + { CoinType.ZCL, "http://explorer.zclmine.pro/accounts/{0}" }, + { CoinType.ZEN, "http://explorer.zensystem.io/accounts/{0}" }, + { CoinType.DGB, "https://digiexplorer.info/address/{0}" }, + { CoinType.NMC, "https://explorer.namecoin.info/a/{0}" }, + { CoinType.GRS, "https://groestlsight.groestlcoin.org/address/{0}" }, + { CoinType.MONA, "https://bchain.info/MONA/addr/{0}" }, + { CoinType.STAK, "https://straks.info/address/{0}" }, + { CoinType.GLT, "https://bchain.info/GLT/addr/{0}" }, + { CoinType.VTC, "https://bchain.info/VTC/addr/{0}" }, + { CoinType.BTG, "https://btgexp.com/address/{0}" }, + { CoinType.ELLA, "https://explorer.ellaism.org/addr/{0}" }, + { CoinType.EXP, "http://www.gander.tech/address/{0}" }, + { CoinType.MOON, "https://chainz.cryptoid.info/moon/address.dws?{0}.htm" }, + { CoinType.XVG, "https://verge-blockchain.info/address/{0}" }, + { CoinType.GBX, "http://gobyte.ezmine.io/address/{0}" }, + { CoinType.CRC, "http://explorer.cryptopros.us/address/{0}" }, + }; + + private const string Ethash = "Dagger-Hashimoto"; + private const string Cryptonight = "Cryptonight"; + private const string CryptonightLight = "Cryptonight-Light"; + + public static readonly Dictionary> CoinAlgorithm = new Dictionary> + { + { CoinType.ETH, (coin, alg)=> Ethash }, + { CoinType.ETC, (coin, alg)=> Ethash }, + { CoinType.LTC, BitcoinProperties.GetAlgorithm }, + { CoinType.PPC, BitcoinProperties.GetAlgorithm }, + { CoinType.BCH, BitcoinProperties.GetAlgorithm }, + { CoinType.DASH, BitcoinProperties.GetAlgorithm }, + { CoinType.BTC, BitcoinProperties.GetAlgorithm }, + { CoinType.DOGE, BitcoinProperties.GetAlgorithm }, + { CoinType.ZEC, BitcoinProperties.GetAlgorithm }, + { CoinType.ZCL, BitcoinProperties.GetAlgorithm }, + { CoinType.BTCP, BitcoinProperties.GetAlgorithm }, + { CoinType.ZEN, BitcoinProperties.GetAlgorithm }, + { CoinType.DGB, BitcoinProperties.GetAlgorithm }, + { CoinType.NMC, BitcoinProperties.GetAlgorithm }, + { CoinType.GRS, BitcoinProperties.GetAlgorithm }, + { CoinType.MONA, BitcoinProperties.GetAlgorithm }, + { CoinType.STAK, BitcoinProperties.GetAlgorithm }, + { CoinType.GLT, BitcoinProperties.GetAlgorithm }, + { CoinType.VTC, BitcoinProperties.GetAlgorithm }, + { CoinType.BTG, BitcoinProperties.GetAlgorithm }, + { CoinType.ELLA, (coin, alg)=> Ethash }, + { CoinType.EXP, (coin, alg)=> Ethash }, + { CoinType.MOON, BitcoinProperties.GetAlgorithm }, + { CoinType.XVG, BitcoinProperties.GetAlgorithm }, + { CoinType.XMR, (coin, alg)=> Cryptonight }, + { CoinType.ETN, (coin, alg)=> Cryptonight }, + { CoinType.AEON, (coin, alg)=> CryptonightLight }, + { CoinType.GBX, BitcoinProperties.GetAlgorithm }, + { CoinType.CRC, BitcoinProperties.GetAlgorithm }, + }; + } +} diff --git a/src/MiningCore/Blockchain/Ethereum/EthereumJob.cs b/src/MiningCore/Blockchain/Ethereum/EthereumJob.cs index e6df3ac6b..5d263a308 100644 --- a/src/MiningCore/Blockchain/Ethereum/EthereumJob.cs +++ b/src/MiningCore/Blockchain/Ethereum/EthereumJob.cs @@ -49,7 +49,7 @@ private void RegisterNonce(StratumClient worker, string nonce) } } - public async Task ProcessShareAsync(StratumClient worker, string nonce, EthashFull ethash) + public async Task<(Share Share, string FullNonceHex, string HeaderHash, string MixHash)> ProcessShareAsync(StratumClient worker, string nonce, EthashFull ethash) { // duplicate nonce? lock(workerNonces) @@ -98,25 +98,30 @@ public async Task ProcessShareAsync(StratumClient worker, string } // create share - var share = new EthereumShare + var share = new Share { BlockHeight = (long) BlockTemplate.Height, IpAddress = worker.RemoteEndpoint?.Address?.ToString(), Miner = context.MinerName, Worker = context.WorkerName, UserAgent = context.UserAgent, - FullNonceHex = "0x" + fullNonceHex, - HeaderHash = BlockTemplate.Header, - MixHash = mixDigest.ToHexString(true), IsBlockCandidate = isBlockCandidate, Difficulty = stratumDifficulty * EthereumConstants.Pow2x32, BlockHash = mixDigest.ToHexString(true) // OW: is this correct? }; if (share.IsBlockCandidate) - share.TransactionConfirmationData = $"{mixDigest.ToHexString(true)}:{share.FullNonceHex}"; + { + fullNonceHex = "0x" + fullNonceHex; + var headerHash = BlockTemplate.Header; + var mixHash = mixDigest.ToHexString(true); + + share.TransactionConfirmationData = $"{mixDigest.ToHexString(true)}:{fullNonceHex}"; + + return (share, fullNonceHex, headerHash, mixHash); + } - return share; + return (share, null, null, null); } } } diff --git a/src/MiningCore/Blockchain/Ethereum/EthereumJobManager.cs b/src/MiningCore/Blockchain/Ethereum/EthereumJobManager.cs index e74ec4f40..a30e86c8f 100644 --- a/src/MiningCore/Blockchain/Ethereum/EthereumJobManager.cs +++ b/src/MiningCore/Blockchain/Ethereum/EthereumJobManager.cs @@ -291,14 +291,14 @@ private async Task UpdateNetworkStatsAsync() BlockchainStats.ConnectedPeers = peerCount; } - private async Task SubmitBlockAsync(EthereumShare share) + private async Task SubmitBlockAsync(Share share, string fullNonceHex, string headerHash, string mixHash) { // submit work var response = await daemon.ExecuteCmdAnyAsync(EC.SubmitWork, new[] { - share.FullNonceHex, - share.HeaderHash, - share.MixHash + fullNonceHex, + headerHash, + mixHash }); if (response.Error != null || (bool?) response.Response == false) @@ -393,7 +393,7 @@ public void PrepareWorker(StratumClient client) context.ExtraNonce1 = extraNonceProvider.Next(); } - public async Task SubmitShareAsync(StratumClient worker, + public async Task SubmitShareAsync(StratumClient worker, string[] request, double stratumDifficulty, double stratumDifficultyBase) { Contract.RequiresNonNull(worker, nameof(worker)); @@ -415,14 +415,20 @@ public async Task SubmitShareAsync(StratumClient worker, } // validate & process - var share = await job.ProcessShareAsync(worker, nonce, ethash); + var (share, fullNonceHex, headerHash, mixHash) = await job.ProcessShareAsync(worker, nonce, ethash); + + // enrich share with common data + share.PoolId = poolConfig.Id; + share.NetworkDifficulty = BlockchainStats.NetworkDifficulty; + share.Source = clusterConfig.ClusterName; + share.Created = clock.Now; // if block candidate, submit & check if accepted by network if (share.IsBlockCandidate) { logger.Info(() => $"[{LogCat}] Submitting block {share.BlockHeight}"); - share.IsBlockCandidate = await SubmitBlockAsync(share); + share.IsBlockCandidate = await SubmitBlockAsync(share, fullNonceHex, headerHash, mixHash); if (share.IsBlockCandidate) { @@ -430,12 +436,6 @@ public async Task SubmitShareAsync(StratumClient worker, } } - // enrich share with common data - share.PoolId = poolConfig.Id; - share.NetworkDifficulty = BlockchainStats.NetworkDifficulty; - share.Source = clusterConfig.ClusterName; - share.Created = clock.Now; - return share; } @@ -542,7 +542,8 @@ protected override async Task PostStartInitAsync() EthereumUtils.DetectNetworkAndChain(netVersion, parityChain, out networkType, out chainType); - ConfigureRewards(); + if (clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true) + ConfigureRewards(); // update stats BlockchainStats.RewardType = "POW"; diff --git a/src/MiningCore/Blockchain/Ethereum/EthereumPool.cs b/src/MiningCore/Blockchain/Ethereum/EthereumPool.cs index 57a11c1e4..d2bcd422f 100644 --- a/src/MiningCore/Blockchain/Ethereum/EthereumPool.cs +++ b/src/MiningCore/Blockchain/Ethereum/EthereumPool.cs @@ -42,7 +42,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace MiningCore.Blockchain.Ethereum { [CoinMetadata(CoinType.ETH, CoinType.ETC, CoinType.EXP, CoinType.ELLA)] - public class EthereumPool : PoolBase + public class EthereumPool : PoolBase { public EthereumPool(IComponentContext ctx, JsonSerializerSettings serializerSettings, diff --git a/src/MiningCore/Blockchain/Ethereum/EthereumShare.cs b/src/MiningCore/Blockchain/Ethereum/EthereumShare.cs deleted file mode 100644 index 187a4f0e4..000000000 --- a/src/MiningCore/Blockchain/Ethereum/EthereumShare.cs +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright 2017 Coin Foundry (coinfoundry.org) -Authors: Oliver Weichhold (oliver@weichhold.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT -LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -namespace MiningCore.Blockchain.Ethereum -{ - public class EthereumShare : ShareBase - { - public string FullNonceHex { get; set; } - public string HeaderHash { get; set; } - public string MixHash { get; set; } - } -} diff --git a/src/MiningCore/Blockchain/Monero/MoneroJob.cs b/src/MiningCore/Blockchain/Monero/MoneroJob.cs index 6ea927106..7afb65d85 100644 --- a/src/MiningCore/Blockchain/Monero/MoneroJob.cs +++ b/src/MiningCore/Blockchain/Monero/MoneroJob.cs @@ -19,10 +19,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ using System; -using System.Buffers; -using System.Globalization; using System.Linq; -using Microsoft.AspNetCore.Server.Kestrel.Internal.System; using MiningCore.Blockchain.Monero.DaemonResponses; using MiningCore.Buffers; using MiningCore.Configuration; @@ -49,11 +46,15 @@ public MoneroJob(GetBlockTemplateResponse blockTemplate, byte[] instanceId, stri switch (poolConfig.Coin.Type) { case CoinType.AEON: - hashSlow = LibCryptonote.CryptonightHashSlowLite; + hashSlow = (buf, variant)=> LibCryptonote.CryptonightHashSlowLite(buf); break; - default: - hashSlow = LibCryptonote.CryptonightHashSlow; + case CoinType.XMR: + hashSlow = LibCryptonote.CryptonightHashSlow; + break; + + default: + hashSlow = (buf, variant) => LibCryptonote.CryptonightHashSlow(buf, 0); break; } @@ -61,7 +62,7 @@ public MoneroJob(GetBlockTemplateResponse blockTemplate, byte[] instanceId, stri PrepareBlobTemplate(instanceId); } - private readonly Func> hashSlow; + private readonly Func> hashSlow; private byte[] blobTemplate; private uint extraNonce; @@ -135,7 +136,7 @@ public void PrepareWorkerJob(MoneroWorkerJob workerJob, out string blob, out str target = EncodeTarget(workerJob.Difficulty); } - public MoneroShare ProcessShare(string nonce, uint workerExtraNonce, string workerHash, StratumClient worker) + public (Share Share, string BlobHex, string BlobHash) ProcessShare(string nonce, uint workerExtraNonce, string workerHash, StratumClient worker) { Contract.Requires(!string.IsNullOrEmpty(nonce), $"{nameof(nonce)} must not be empty"); Contract.Requires(!string.IsNullOrEmpty(workerHash), $"{nameof(workerHash)} must not be empty"); @@ -165,8 +166,11 @@ public MoneroShare ProcessShare(string nonce, uint workerExtraNonce, string work if (blobConverted == null) throw new StratumException(StratumError.MinusOne, "malformed blob"); - // hash it - using (var hashSeg = hashSlow(blobConverted)) + // PoW variant + var hashVariant = blobConverted[0] >= 7 ? blobConverted[0] - 6 : 0; + + // hash it + using (var hashSeg = hashSlow(blobConverted, hashVariant)) { var hash = hashSeg.ToHexString(); if (hash != workerHash) @@ -200,17 +204,18 @@ public MoneroShare ProcessShare(string nonce, uint workerExtraNonce, string work using (var blockHash = ComputeBlockHash(blobConverted)) { - var result = new MoneroShare + var result = new Share { BlockHeight = BlockTemplate.Height, IsBlockCandidate = isBlockCandidate, - BlobHex = blob.ToHexString(), - BlobHash = blockHash.ToHexString(), BlockHash = blockHash.ToHexString(), Difficulty = stratumDifficulty, }; - return result; + var blobHex = blob.ToHexString(); + var blobHash = blockHash.ToHexString(); + + return (result, blobHex, blobHash); } } } diff --git a/src/MiningCore/Blockchain/Monero/MoneroJobManager.cs b/src/MiningCore/Blockchain/Monero/MoneroJobManager.cs index 69e7ec02a..044eb62ad 100644 --- a/src/MiningCore/Blockchain/Monero/MoneroJobManager.cs +++ b/src/MiningCore/Blockchain/Monero/MoneroJobManager.cs @@ -169,15 +169,15 @@ private async Task UpdateNetworkStatsAsync() BlockchainStats.ConnectedPeers = info.OutgoingConnectionsCount + info.IncomingConnectionsCount; } - private async Task SubmitBlockAsync(MoneroShare share) + private async Task SubmitBlockAsync(Share share, string blobHex, string blobHash) { - var response = await daemon.ExecuteCmdAnyAsync(MC.SubmitBlock, new[] { share.BlobHex }); + var response = await daemon.ExecuteCmdAnyAsync(MC.SubmitBlock, new[] { blobHex }); if (response.Error != null || response?.Response?.Status != "OK") { var error = response.Error?.Message ?? response.Response?.Status; - logger.Warn(() => $"[{LogCat}] Block {share.BlockHeight} [{share.BlobHash.Substring(0, 6)}] submission failed with: {error}"); + logger.Warn(() => $"[{LogCat}] Block {share.BlockHeight} [{blobHash.Substring(0, 6)}] submission failed with: {error}"); notificationService.NotifyAdmin("Block submission failed", $"Block {share.BlockHeight} submission failed with: {error}"); return false; @@ -204,13 +204,16 @@ public override void Configure(PoolConfig poolConfig, ClusterConfig clusterConfi .Where(x => string.IsNullOrEmpty(x.Category)) .ToArray(); - // extract wallet daemon endpoints - walletDaemonEndpoints = poolConfig.Daemons - .Where(x => x.Category?.ToLower() == MoneroConstants.WalletDaemonCategory) - .ToArray(); + if (clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true) + { + // extract wallet daemon endpoints + walletDaemonEndpoints = poolConfig.Daemons + .Where(x => x.Category?.ToLower() == MoneroConstants.WalletDaemonCategory) + .ToArray(); - if (walletDaemonEndpoints.Length == 0) - logger.ThrowLogPoolStartupException("Wallet-RPC daemon is not configured (Daemon configuration for monero-pools require an additional entry of category \'wallet' pointing to the wallet daemon)", LogCat); + if (walletDaemonEndpoints.Length == 0) + logger.ThrowLogPoolStartupException("Wallet-RPC daemon is not configured (Daemon configuration for monero-pools require an additional entry of category \'wallet' pointing to the wallet daemon)", LogCat); + } ConfigureDaemons(); } @@ -258,7 +261,7 @@ public void PrepareWorkerJob(MoneroWorkerJob workerJob, out string blob, out str } } - public async Task SubmitShareAsync(StratumClient worker, + public async Task SubmitShareAsync(StratumClient worker, MoneroSubmitShareRequest request, MoneroWorkerJob workerJob, double stratumDifficultyBase) { Contract.RequiresNonNull(worker, nameof(worker)); @@ -272,20 +275,31 @@ public async Task SubmitShareAsync(StratumClient worker, throw new StratumException(StratumError.MinusOne, "block expired"); // validate & process - var share = job?.ProcessShare(request.Nonce, workerJob.ExtraNonce, request.Hash, worker); + var (share, blobHex, blobHash) = job.ProcessShare(request.Nonce, workerJob.ExtraNonce, request.Hash, worker); + + // enrich share with common data + share.PoolId = poolConfig.Id; + share.IpAddress = worker.RemoteEndpoint.Address.ToString(); + share.Miner = context.MinerName; + share.Worker = context.WorkerName; + share.PayoutInfo = context.PaymentId; + share.UserAgent = context.UserAgent; + share.Source = clusterConfig.ClusterName; + share.NetworkDifficulty = job.BlockTemplate.Difficulty; + share.Created = clock.Now; // if block candidate, submit & check if accepted by network if (share.IsBlockCandidate) { - logger.Info(() => $"[{LogCat}] Submitting block {share.BlockHeight} [{share.BlobHash.Substring(0, 6)}]"); + logger.Info(() => $"[{LogCat}] Submitting block {share.BlockHeight} [{blobHash.Substring(0, 6)}]"); - share.IsBlockCandidate = await SubmitBlockAsync(share); + share.IsBlockCandidate = await SubmitBlockAsync(share, blobHex, blobHash); if (share.IsBlockCandidate) { - logger.Info(() => $"[{LogCat}] Daemon accepted block {share.BlockHeight} [{share.BlobHash.Substring(0, 6)}] submitted by {context.MinerName}"); + logger.Info(() => $"[{LogCat}] Daemon accepted block {share.BlockHeight} [{blobHash.Substring(0, 6)}] submitted by {context.MinerName}"); - share.TransactionConfirmationData = share.BlobHash; + share.TransactionConfirmationData = blobHash; } else @@ -295,17 +309,6 @@ public async Task SubmitShareAsync(StratumClient worker, } } - // enrich share with common data - share.PoolId = poolConfig.Id; - share.IpAddress = worker.RemoteEndpoint.Address.ToString(); - share.Miner = context.MinerName; - share.Worker = context.WorkerName; - share.PayoutInfo = context.PaymentId; - share.UserAgent = context.UserAgent; - share.Source = clusterConfig.ClusterName; - share.NetworkDifficulty = job.BlockTemplate.Difficulty; - share.Created = clock.Now; - return share; } @@ -322,9 +325,12 @@ protected override void ConfigureDaemons() daemon = new DaemonClient(jsonSerializerSettings); daemon.Configure(daemonEndpoints, MoneroConstants.DaemonRpcLocation); - // also setup wallet daemon - walletDaemon = new DaemonClient(jsonSerializerSettings); - walletDaemon.Configure(walletDaemonEndpoints, MoneroConstants.DaemonRpcLocation); + if (clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true) + { + // also setup wallet daemon + walletDaemon = new DaemonClient(jsonSerializerSettings); + walletDaemon.Configure(walletDaemonEndpoints, MoneroConstants.DaemonRpcLocation); + } } protected override async Task AreDaemonsHealthyAsync() @@ -340,15 +346,20 @@ protected override async Task AreDaemonsHealthyAsync() if (!responses.All(x => x.Error == null)) return false; - // test wallet daemons - var responses2 = await walletDaemon.ExecuteCmdAllAsync(MWC.GetAddress); + if (clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true) + { + // test wallet daemons + var responses2 = await walletDaemon.ExecuteCmdAllAsync(MWC.GetAddress); - if (responses2.Where(x => x.Error?.InnerException?.GetType() == typeof(DaemonClientException)) - .Select(x => (DaemonClientException) x.Error.InnerException) - .Any(x => x.Code == HttpStatusCode.Unauthorized)) - logger.ThrowLogPoolStartupException($"Wallet-Daemon reports invalid credentials", LogCat); + if (responses2.Where(x => x.Error?.InnerException?.GetType() == typeof(DaemonClientException)) + .Select(x => (DaemonClientException) x.Error.InnerException) + .Any(x => x.Code == HttpStatusCode.Unauthorized)) + logger.ThrowLogPoolStartupException($"Wallet-Daemon reports invalid credentials", LogCat); + + return responses2.All(x => x.Error == null); + } - return responses2.All(x => x.Error == null); + return true; } protected override async Task AreDaemonsConnectedAsync() @@ -398,14 +409,18 @@ protected override async Task EnsureDaemonsSynchedAsync() protected override async Task PostStartInitAsync() { var infoResponse = await daemon.ExecuteCmdAnyAsync(MC.GetInfo); - var addressResponse = await walletDaemon.ExecuteCmdAnyAsync(MWC.GetAddress); if (infoResponse.Error != null) logger.ThrowLogPoolStartupException($"Init RPC failed: {infoResponse.Error.Message} (Code {infoResponse.Error.Code})", LogCat); - // ensure pool owns wallet - if (clusterConfig.PaymentProcessing?.Enabled == true && addressResponse.Response?.Address != poolConfig.Address) - logger.ThrowLogPoolStartupException($"Wallet-Daemon does not own pool-address '{poolConfig.Address}'", LogCat); + if (clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true) + { + var addressResponse = await walletDaemon.ExecuteCmdAnyAsync(MWC.GetAddress); + + // ensure pool owns wallet + if (clusterConfig.PaymentProcessing?.Enabled == true && addressResponse.Response?.Address != poolConfig.Address) + logger.ThrowLogPoolStartupException($"Wallet-Daemon does not own pool-address '{poolConfig.Address}'", LogCat); + } var info = infoResponse.Response.ToObject(); @@ -430,7 +445,8 @@ protected override async Task PostStartInitAsync() break; } - ConfigureRewards(); + if (clusterConfig.PaymentProcessing?.Enabled == true && poolConfig.PaymentProcessing?.Enabled == true) + ConfigureRewards(); // update stats BlockchainStats.RewardType = "POW"; diff --git a/src/MiningCore/Blockchain/Monero/MoneroPool.cs b/src/MiningCore/Blockchain/Monero/MoneroPool.cs index 4be9ade41..b640c5050 100644 --- a/src/MiningCore/Blockchain/Monero/MoneroPool.cs +++ b/src/MiningCore/Blockchain/Monero/MoneroPool.cs @@ -43,7 +43,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace MiningCore.Blockchain.Monero { [CoinMetadata(CoinType.XMR, CoinType.AEON, CoinType.ETN)] - public class MoneroPool : PoolBase + public class MoneroPool : PoolBase { public MoneroPool(IComponentContext ctx, JsonSerializerSettings serializerSettings, diff --git a/src/MiningCore/Blockchain/Monero/MoneroShare.cs b/src/MiningCore/Blockchain/Monero/MoneroShare.cs deleted file mode 100644 index 7c35f14e1..000000000 --- a/src/MiningCore/Blockchain/Monero/MoneroShare.cs +++ /dev/null @@ -1,28 +0,0 @@ -/* -Copyright 2017 Coin Foundry (coinfoundry.org) -Authors: Oliver Weichhold (oliver@weichhold.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT -LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -namespace MiningCore.Blockchain.Monero -{ - public class MoneroShare : ShareBase - { - public string BlobHex { get; set; } - public string BlobHash { get; set; } - } -} diff --git a/src/MiningCore/Blockchain/Share.cs b/src/MiningCore/Blockchain/Share.cs new file mode 100644 index 000000000..d29d97a4b --- /dev/null +++ b/src/MiningCore/Blockchain/Share.cs @@ -0,0 +1,121 @@ +/* +Copyright 2017 Coin Foundry (coinfoundry.org) +Authors: Oliver Weichhold (oliver@weichhold.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +using System; +using ProtoBuf; + +namespace MiningCore.Blockchain +{ + [ProtoContract] + public class Share + { + /// + /// The pool originating this share from + /// + [ProtoMember(1)] + public string PoolId { get; set; } + + /// + /// Who mined it (wallet address) + /// + [ProtoMember(2)] + public string Miner { get; set; } + + /// + /// Who mined it + /// + [ProtoMember(3)] + public string Worker { get; set; } + + /// + /// Extra information for payout processing + /// + [ProtoMember(4)] + public string PayoutInfo { get; set; } + + /// + /// Mining Software + /// + [ProtoMember(5)] + public string UserAgent { get; set; } + + /// + /// From where was it submitted + /// + [ProtoMember(6)] + public string IpAddress { get; set; } + + /// + /// Submission source (pool, external stratum etc) + /// + [ProtoMember(7)] + public string Source { get; set; } + + /// + /// Stratum difficulty assigned to the miner at the time the share was submitted/accepted (used for payout + /// calculations) + /// + [ProtoMember(8)] + public double Difficulty { get; set; } + + /// + /// Block this share refers to + /// + [ProtoMember(9)] + public long BlockHeight { get; set; } + + /// + /// Block reward after deducting pool fee and donations + /// + [ProtoMember(10)] + public decimal BlockReward { get; set; } + + /// + /// Block hash + /// + [ProtoMember(11)] + public string BlockHash { get; set; } + + /// + /// If this share presumably resulted in a block + /// + [ProtoMember(12)] + public bool IsBlockCandidate { get; set; } + + /// + /// Arbitrary data to be interpreted by the payment processor specialized + /// in this coin to verify this block candidate was accepted by the network + /// + [ProtoMember(13)] + public string TransactionConfirmationData { get; set; } + + /// + /// Network difficulty at the time the share was submitted (used for some payout schemes like PPLNS) + /// + [ProtoMember(14)] + public double NetworkDifficulty { get; set; } + + /// + /// When the share was found + /// + [ProtoMember(15)] + public DateTime Created { get; set; } + } +} diff --git a/src/MiningCore/Blockchain/ShareBase.cs b/src/MiningCore/Blockchain/ShareBase.cs deleted file mode 100644 index fd00a6582..000000000 --- a/src/MiningCore/Blockchain/ShareBase.cs +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2017 Coin Foundry (coinfoundry.org) -Authors: Oliver Weichhold (oliver@weichhold.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT -LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -using System; - -namespace MiningCore.Blockchain -{ - public class ShareBase : IShare - { - public string PoolId { get; set; } - public string Miner { get; set; } - public string Worker { get; set; } - public string PayoutInfo { get; set; } - public string UserAgent { get; set; } - public string IpAddress { get; set; } - public string Source { get; set; } - public double Difficulty { get; set; } - public double NetworkDifficulty { get; set; } - public long BlockHeight { get; set; } - public decimal BlockReward { get; set; } - public string BlockHash { get; set; } - public bool IsBlockCandidate { get; set; } - public string TransactionConfirmationData { get; set; } - public DateTime Created { get; set; } - } -} diff --git a/src/MiningCore/Blockchain/ZCash/ZCashJob.cs b/src/MiningCore/Blockchain/ZCash/ZCashJob.cs index ad4bb386d..2049d9b05 100644 --- a/src/MiningCore/Blockchain/ZCash/ZCashJob.cs +++ b/src/MiningCore/Blockchain/ZCash/ZCashJob.cs @@ -20,11 +20,9 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; -using System.Numerics; using MiningCore.Blockchain.Bitcoin; using MiningCore.Blockchain.ZCash.DaemonResponses; using MiningCore.Configuration; @@ -227,7 +225,7 @@ public override void Init(ZCashBlockTemplate blockTemplate, string jobId, #endregion - public override BitcoinShare ProcessShare(StratumClient worker, string extraNonce2, string nTime, string solution) + public override (Share Share, string BlockHex) ProcessShare(StratumClient worker, string extraNonce2, string nTime, string solution) { Contract.RequiresNonNull(worker, nameof(worker)); Contract.Requires(!string.IsNullOrEmpty(extraNonce2), $"{nameof(extraNonce2)} must not be empty"); @@ -295,7 +293,7 @@ protected byte[] SerializeBlock(byte[] header, byte[] coinbase, byte[] solution) } } - protected virtual BitcoinShare ProcessShareInternal(StratumClient worker, string nonce, + protected virtual (Share Share, string BlockHex) ProcessShareInternal(StratumClient worker, string nonce, uint nTime, string solution) { var context = worker.GetContextAs(); @@ -341,22 +339,26 @@ protected virtual BitcoinShare ProcessShareInternal(StratumClient worker, string throw new StratumException(StratumError.LowDifficultyShare, $"low difficulty share ({shareDiff})"); } - var result = new BitcoinShare + var result = new Share { BlockHeight = BlockTemplate.Height, - IsBlockCandidate = isBlockCandidate, - Difficulty = stratumDifficulty + NetworkDifficulty = Difficulty, + Difficulty = stratumDifficulty, }; if (isBlockCandidate) { - var blockBytes = SerializeBlock(headerBytes, coinbaseInitial, solutionBytes); - result.BlockHex = blockBytes.ToHexString(); - result.BlockHash = headerHashReversed.ToHexString(); + result.IsBlockCandidate = true; result.BlockReward = rewardToPool.ToDecimal(MoneyUnit.BTC); + result.BlockHash = headerHashReversed.ToHexString(); + + var blockBytes = SerializeBlock(headerBytes, coinbaseInitial, solutionBytes); + var blockHex = blockBytes.ToHexString(); + + return (result, blockHex); } - return result; + return (result, null); } protected bool RegisterSubmit(string nonce, string solution) diff --git a/src/MiningCore/Blockchain/ZCash/ZCashJobManager.cs b/src/MiningCore/Blockchain/ZCash/ZCashJobManager.cs index b0c7ca7f4..e091c411e 100644 --- a/src/MiningCore/Blockchain/ZCash/ZCashJobManager.cs +++ b/src/MiningCore/Blockchain/ZCash/ZCashJobManager.cs @@ -106,7 +106,7 @@ protected override IDestination AddressToDestination(string address) return result; } - public override async Task SubmitShareAsync(StratumClient worker, object submission, + public override async Task SubmitShareAsync(StratumClient worker, object submission, double stratumDifficultyBase) { Contract.RequiresNonNull(worker, nameof(worker)); @@ -148,14 +148,14 @@ public override async Task SubmitShareAsync(StratumClient worker, var workerName = split.Length > 1 ? split[1] : null; // validate & process - var share = job.ProcessShare(worker, extraNonce2, nTime, solution); + var (share, blockHex) = job.ProcessShare(worker, extraNonce2, nTime, solution); // if block candidate, submit & check if accepted by network if (share.IsBlockCandidate) { logger.Info(() => $"[{LogCat}] Submitting block {share.BlockHeight} [{share.BlockHash}]"); - var acceptResponse = await SubmitBlockAsync(share); + var acceptResponse = await SubmitBlockAsync(share, blockHex); // is it still a block candidate? share.IsBlockCandidate = acceptResponse.Accepted; diff --git a/src/MiningCore/Mining/Abstractions.cs b/src/MiningCore/Mining/Abstractions.cs index 686dde3a6..2264af804 100644 --- a/src/MiningCore/Mining/Abstractions.cs +++ b/src/MiningCore/Mining/Abstractions.cs @@ -28,14 +28,14 @@ namespace MiningCore.Mining { public struct ClientShare { - public ClientShare(StratumClient client, IShare share) + public ClientShare(StratumClient client, Share share) { Client = client; Share = share; } public StratumClient Client; - public IShare Share; + public Share Share; } public interface IMiningPool diff --git a/src/MiningCore/Mining/PoolBase.cs b/src/MiningCore/Mining/PoolBase.cs index 2342b47e4..b5125028c 100644 --- a/src/MiningCore/Mining/PoolBase.cs +++ b/src/MiningCore/Mining/PoolBase.cs @@ -52,9 +52,8 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. namespace MiningCore.Mining { - public abstract class PoolBase : StratumServer, + public abstract class PoolBase : StratumServer, IMiningPool - where TShare: IShare { protected PoolBase(IComponentContext ctx, JsonSerializerSettings serializerSettings, @@ -139,107 +138,6 @@ private void EnsureNoZombieClient(StratumClient client) }); } - private void StartExternalStratumPublisherListeners() - { - foreach (var externalStratum in poolConfig.ExternalStratums) - { - var thread = new Thread(arg => - { - var serializer = new JsonSerializer - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }; - - var currentHeight = 0L; - var lastBlockTime = clock.Now; - var config = (ZmqPubSubEndpointConfig) arg; - - while (true) - { - try - { - using (var subSocket = new SubscriberSocket()) - { - //subSocket.Options.ReceiveHighWatermark = 1000; - subSocket.Connect(config.Url); - subSocket.Subscribe(config.Topic); - - logger.Info($"{LogCat}] Monitoring external stratum {config.Url}/{config.Topic}"); - - while (true) - { - var msg = subSocket.ReceiveMultipartMessage(2); - var topic = msg.Pop().ConvertToString(Encoding.UTF8); - var data = msg.Pop().ConvertToString(Encoding.UTF8); - - // validate - if (topic != config.Topic) - { - logger.Warn(() => $"{LogCat}] Received non-matching topic {topic} on ZeroMQ subscriber socket"); - continue; - } - - if (string.IsNullOrEmpty(data)) - { - logger.Warn(() => $"{LogCat}] Received empty data on ZeroMQ subscriber socket"); - continue; - } - - // deserialize - TShare share; - - using (var reader = new StringReader(data)) - { - using (var jreader = new JsonTextReader(reader)) - { - share = serializer.Deserialize(jreader); - } - } - - if (share == null) - { - logger.Error(() => $"{LogCat}] Unable to deserialize share received from ZeroMQ subscriber socket"); - continue; - } - - // update network stats - blockchainStats.BlockHeight = share.BlockHeight; - blockchainStats.NetworkDifficulty = share.NetworkDifficulty; - - if (currentHeight != share.BlockHeight) - { - blockchainStats.LastNetworkBlockTime = clock.Now; - currentHeight = share.BlockHeight; - lastBlockTime = clock.Now; - } - - else - blockchainStats.LastNetworkBlockTime = lastBlockTime; - - // fill in the blacks - share.PoolId = poolConfig.Id; - share.Created = clock.Now; - - // re-publish - shareSubject.OnNext(new ClientShare(null, share)); - - var source = !string.IsNullOrEmpty(share.Source) ? $"[{share.Source.ToUpper()}]" : string.Empty; - logger.Info(() => $"[{LogCat}] External {source} share accepted: D={Math.Round(share.Difficulty, 3)}"); - } - } - } - - catch (Exception ex) - { - logger.Error(ex); - } - } - }) {Name = $"{poolConfig.Id} external stratum listener"}; - - thread.Start(externalStratum); - } - } - #region VarDiff protected void UpdateVarDiff(StratumClient client, bool isIdleUpdate = false) @@ -414,7 +312,7 @@ public virtual void Configure(PoolConfig poolConfig, ClusterConfig clusterConfig Contract.RequiresNonNull(poolConfig, nameof(poolConfig)); Contract.RequiresNonNull(clusterConfig, nameof(clusterConfig)); - logger = LogUtil.GetPoolScopedLogger(typeof(PoolBase), poolConfig); + logger = LogUtil.GetPoolScopedLogger(typeof(PoolBase), poolConfig); this.poolConfig = poolConfig; this.clusterConfig = clusterConfig; } @@ -442,9 +340,6 @@ public virtual async Task StartAsync() StartListeners(poolConfig.Id, ipEndpoints); } - if(poolConfig.ExternalStratums?.Length > 0) - StartExternalStratumPublisherListeners(); - logger.Info(() => $"[{LogCat}] Online"); OutputPoolInfo(); } diff --git a/src/MiningCore/Payments/ShareRecorder.cs b/src/MiningCore/Mining/ShareRecorder.cs similarity index 59% rename from src/MiningCore/Payments/ShareRecorder.cs rename to src/MiningCore/Mining/ShareRecorder.cs index 26257ce2c..b0a5d433d 100644 --- a/src/MiningCore/Payments/ShareRecorder.cs +++ b/src/MiningCore/Mining/ShareRecorder.cs @@ -28,23 +28,29 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Text; -using Autofac.Features.Metadata; +using System.Threading; using AutoMapper; -using MiningCore.Blockchain; using MiningCore.Configuration; using MiningCore.Extensions; -using MiningCore.Mining; using MiningCore.Notifications; using MiningCore.Persistence; using MiningCore.Persistence.Model; using MiningCore.Persistence.Repositories; +using MiningCore.Time; +using MiningCore.Util; +using NetMQ; +using NetMQ.Sockets; using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; using NLog; +using Org.BouncyCastle.Utilities.Collections; using Polly; using Polly.CircuitBreaker; +using ProtoBuf; using Contract = MiningCore.Contracts.Contract; +using Share = MiningCore.Blockchain.Share; -namespace MiningCore.Payments +namespace MiningCore.Mining { /// /// Asynchronously persist shares produced by all pools for processing by coin-specific payment processor(s) @@ -54,6 +60,7 @@ public class ShareRecorder public ShareRecorder(IConnectionFactory cf, IMapper mapper, JsonSerializerSettings jsonSerializerSettings, IShareRepository shareRepo, IBlockRepository blockRepo, + IMasterClock clock, NotificationService notificationService) { Contract.RequiresNonNull(cf, nameof(cf)); @@ -61,11 +68,13 @@ public ShareRecorder(IConnectionFactory cf, IMapper mapper, Contract.RequiresNonNull(shareRepo, nameof(shareRepo)); Contract.RequiresNonNull(blockRepo, nameof(blockRepo)); Contract.RequiresNonNull(jsonSerializerSettings, nameof(jsonSerializerSettings)); + Contract.RequiresNonNull(clock, nameof(clock)); Contract.RequiresNonNull(notificationService, nameof(notificationService)); this.cf = cf; this.mapper = mapper; this.jsonSerializerSettings = jsonSerializerSettings; + this.clock = clock; this.notificationService = notificationService; this.shareRepo = shareRepo; @@ -74,16 +83,33 @@ public ShareRecorder(IConnectionFactory cf, IMapper mapper, BuildFaultHandlingPolicy(); } + private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); private readonly IBlockRepository blockRepo; - private readonly IConnectionFactory cf; private readonly JsonSerializerSettings jsonSerializerSettings; + private readonly IMasterClock clock; private readonly NotificationService notificationService; private ClusterConfig clusterConfig; private readonly IMapper mapper; - private readonly BlockingCollection queue = new BlockingCollection(); + private readonly ConcurrentDictionary pools = new ConcurrentDictionary(); + private readonly BlockingCollection queue = new BlockingCollection(); + + class PoolContext + { + public PoolContext(IMiningPool pool, ILogger logger) + { + Pool = pool; + Logger = logger; + } + + public IMiningPool Pool; + public ILogger Logger; + public DateTime? LastBlock; + public long BlockHeight; + } private readonly int QueueSizeWarningThreshold = 1024; + private readonly TimeSpan relayReceiveTimeout = TimeSpan.FromSeconds(60); private readonly IShareRepository shareRepo; private Policy faultPolicy; private bool hasLoggedPolicyFallbackFailure; @@ -94,22 +120,20 @@ public ShareRecorder(IConnectionFactory cf, IMapper mapper, private const string PolicyContextKeyShares = "share"; private bool notifiedAdminOnPolicyFallback = false; - private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); - - private void PersistSharesFaulTolerant(IList shares) + private void PersistSharesFaulTolerant(IList shares) { var context = new Dictionary { { PolicyContextKeyShares, shares } }; faultPolicy.Execute(() => { PersistShares(shares); }, context); } - private void PersistShares(IList shares) + private void PersistShares(IList shares) { cf.RunTx((con, tx) => { foreach(var share in shares) { - var shareEntity = mapper.Map(share); + var shareEntity = mapper.Map(share); shareRepo.Insert(con, tx, shareEntity); if (share.IsBlockCandidate) @@ -136,7 +160,7 @@ private void OnPolicyFallback(Exception ex, Context context) private void OnExecutePolicyFallback(Context context) { - var shares = (IList) context[PolicyContextKeyShares]; + var shares = (IList) context[PolicyContextKeyShares]; try { @@ -189,7 +213,7 @@ public void RecoverShares(ClusterConfig clusterConfig, string recoveryFilename) { using(var reader = new StreamReader(stream, new UTF8Encoding(false))) { - var shares = new List(); + var shares = new List(); var lastProgressUpdate = DateTime.UtcNow; while(!reader.EndOfStream) @@ -207,7 +231,7 @@ public void RecoverShares(ClusterConfig clusterConfig, string recoveryFilename) // parse try { - var share = JsonConvert.DeserializeObject(line, jsonSerializerSettings); + var share = JsonConvert.DeserializeObject(line, jsonSerializerSettings); shares.Add(share); } @@ -289,10 +313,167 @@ private void NotifyAdminOnPolicyFallback() } } + private void StartExternalStratumPublisherListeners() + { + var stratumsByUrl = clusterConfig.Pools.Where(x => x.ExternalStratums?.Any() == true) + .SelectMany(x => x.ExternalStratums) + .Where(x => x.Url != null && x.Topic != null) + .GroupBy(x => + { + var tmp = x.Url.Trim(); + return !tmp.EndsWith("/") ? tmp : tmp.Substring(0, tmp.Length - 1); + }, x=> x.Topic.Trim()); + + var serializer = new JsonSerializer + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + }; + + foreach (var item in stratumsByUrl) + { + var thread = new Thread(arg => + { + var urlAndTopic = (IGrouping) arg; + var url = urlAndTopic.Key; + var topics = new HashSet(urlAndTopic.Distinct()); + var receivedOnce = false; + + while (true) + { + try + { + using (var subSocket = new SubscriberSocket()) + { + subSocket.Connect(url); + + // subscribe to all topics + foreach (var topic in topics) + subSocket.Subscribe(topic); + + logger.Info($"Monitoring external stratum {url}/[{string.Join(", ", topics)}]"); + + while (true) + { + // receive + var msg = (NetMQMessage)null; + + if (!subSocket.TryReceiveMultipartMessage(relayReceiveTimeout, ref msg, 3)) + { + if (receivedOnce) + { + logger.Warn(() => $"Timeout receiving message from {url}. Reconnecting ..."); + break; + } + + // retry + continue; + } + + // extract frames + var topic = msg.Pop().ConvertToString(Encoding.UTF8); + var flags = msg.Pop().ConvertToInt32(); + var data = msg.Pop().ToByteArray(); + receivedOnce = true; + + // validate + if (!topics.Contains(topic)) + { + logger.Warn(() => $"Received non-matching topic {topic} on ZeroMQ subscriber socket"); + continue; + } + + if (data?.Length == 0) + { + logger.Warn(() => $"Received empty data from {url}/{topic}"); + continue; + } + + // deserialize + var wireFormat = (ShareRelay.WireFormat)(flags & ShareRelay.WireFormatMask); + Share share = null; + + switch (wireFormat) + { + case ShareRelay.WireFormat.Json: + using (var stream = new MemoryStream(data)) + { + using (var reader = new StreamReader(stream, Encoding.UTF8)) + { + using (var jreader = new JsonTextReader(reader)) + { + share = serializer.Deserialize(jreader); + } + } + } + break; + + case ShareRelay.WireFormat.ProtocolBuffers: + using (var stream = new MemoryStream(data)) + { + share = Serializer.Deserialize(stream); + } + break; + + default: + logger.Error(() => $"Unsupported wire format {wireFormat} of share received from {url}/{topic} "); + break; + } + + if (share == null) + { + logger.Error(() => $"Unable to deserialize share received from {url}/{topic}"); + continue; + } + + // store + share.PoolId = topic; + share.Created = clock.Now; + queue.Add(share); + + // misc + if (pools.TryGetValue(topic, out var poolContext)) + { + var pool = poolContext.Pool; + poolContext.Logger.Info(() => $"External {(!string.IsNullOrEmpty(share.Source) ? $"[{share.Source.ToUpper()}] " : string.Empty)}share accepted: D={Math.Round(share.Difficulty, 3)}"); + + // update pool stats + if (pool.NetworkStats != null) + { + pool.NetworkStats.BlockHeight = share.BlockHeight; + pool.NetworkStats.NetworkDifficulty = share.NetworkDifficulty; + + if (poolContext.BlockHeight != share.BlockHeight) + { + pool.NetworkStats.LastNetworkBlockTime = clock.Now; + poolContext.BlockHeight = share.BlockHeight; + poolContext.LastBlock = clock.Now; + } + + else + pool.NetworkStats.LastNetworkBlockTime = poolContext.LastBlock; + } + } + } + } + } + + catch (Exception ex) + { + logger.Error(ex); + } + } + }); + + thread.Start(item); + } + } + #region API-Surface public void AttachPool(IMiningPool pool) { + pools[pool.Config.Id] = new PoolContext(pool, LogUtil.GetPoolScopedLogger(typeof(ShareRecorder), pool.Config)); + pool.Shares.Subscribe(x => { queue.Add(x.Share); }); } @@ -302,6 +483,7 @@ public void Start(ClusterConfig clusterConfig) ConfigureRecovery(); InitializeQueue(); + StartExternalStratumPublisherListeners(); logger.Info(() => "Online"); } diff --git a/src/MiningCore/Payments/ShareRelay.cs b/src/MiningCore/Mining/ShareRelay.cs similarity index 80% rename from src/MiningCore/Payments/ShareRelay.cs rename to src/MiningCore/Mining/ShareRelay.cs index 10e82644c..3bd7669cc 100644 --- a/src/MiningCore/Payments/ShareRelay.cs +++ b/src/MiningCore/Mining/ShareRelay.cs @@ -1,19 +1,17 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; +using System.IO; using System.Reactive.Concurrency; using System.Reactive.Linq; -using System.Text; using MiningCore.Blockchain; using MiningCore.Configuration; -using MiningCore.Mining; using NetMQ; using NetMQ.Sockets; using Newtonsoft.Json; using NLog; +using ProtoBuf; -namespace MiningCore.Payments +namespace MiningCore.Mining { public class ShareRelay { @@ -23,7 +21,7 @@ public ShareRelay(JsonSerializerSettings serializerSettings) } private ClusterConfig clusterConfig; - private readonly BlockingCollection queue = new BlockingCollection(); + private readonly BlockingCollection queue = new BlockingCollection(); private IDisposable queueSub; private readonly int QueueSizeWarningThreshold = 1024; private bool hasWarnedAboutBacklogSize; @@ -32,6 +30,15 @@ public ShareRelay(JsonSerializerSettings serializerSettings) private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); + [Flags] + public enum WireFormat + { + Json = 1, + ProtocolBuffers = 2 + } + + public const int WireFormatMask = 0xF; + #region API-Surface public void AttachPool(IMiningPool pool) @@ -87,10 +94,16 @@ private void InitializeQueue() try { - var json = JsonConvert.SerializeObject(share, serializerSettings); - + var flags = (int) WireFormat.ProtocolBuffers; var msg = new NetMQMessage(2); - msg.Push(json); + + using (var stream = new MemoryStream()) + { + Serializer.Serialize(stream, share); + msg.Push(stream.ToArray()); + } + + msg.Push(flags); msg.Push(share.PoolId); pubSocket.SendMultipartMessage(msg); } diff --git a/src/MiningCore/MiningCore.csproj b/src/MiningCore/MiningCore.csproj index 74b9172f3..797efb61d 100644 --- a/src/MiningCore/MiningCore.csproj +++ b/src/MiningCore/MiningCore.csproj @@ -46,21 +46,22 @@ - + - - - + + + - - - + + + - + - - + + + diff --git a/src/MiningCore/Native/LibCryptonote.cs b/src/MiningCore/Native/LibCryptonote.cs index 165fcd11e..fbb36d543 100644 --- a/src/MiningCore/Native/LibCryptonote.cs +++ b/src/MiningCore/Native/LibCryptonote.cs @@ -39,7 +39,7 @@ public static unsafe class LibCryptonote private static extern UInt64 decode_integrated_address(byte* input, int inputSize); [DllImport("libcryptonote", EntryPoint = "cn_slow_hash_export", CallingConvention = CallingConvention.Cdecl)] - private static extern int cn_slow_hash(byte* input, byte* output, uint inputLength); + private static extern int cn_slow_hash(byte* input, byte* output, uint inputLength, int variant); [DllImport("libcryptonote", EntryPoint = "cn_slow_hash_lite_export", CallingConvention = CallingConvention.Cdecl)] private static extern int cn_slow_hash_lite(byte* input, byte* output, uint inputLength); @@ -124,7 +124,7 @@ public static UInt64 DecodeIntegratedAddress(string address) } } - public static PooledArraySegment CryptonightHashSlow(byte[] data) + public static PooledArraySegment CryptonightHashSlow(byte[] data, int variant) { Contract.RequiresNonNull(data, nameof(data)); @@ -134,7 +134,7 @@ public static PooledArraySegment CryptonightHashSlow(byte[] data) { fixed(byte* output = result.Array) { - cn_slow_hash(input, output, (uint) data.Length); + cn_slow_hash(input, output, (uint) data.Length, variant); } } diff --git a/src/MiningCore/Persistence/Postgres/Repositories/StatsRepository.cs b/src/MiningCore/Persistence/Postgres/Repositories/StatsRepository.cs index 0974ed0df..4b6718031 100644 --- a/src/MiningCore/Persistence/Postgres/Repositories/StatsRepository.cs +++ b/src/MiningCore/Persistence/Postgres/Repositories/StatsRepository.cs @@ -46,7 +46,7 @@ public StatsRepository(IMapper mapper, IMasterClock clock) private readonly IMapper mapper; private readonly IMasterClock clock; private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); - private static readonly TimeSpan MinerStatsMaxAge = TimeSpan.FromMinutes(15); + private static readonly TimeSpan MinerStatsMaxAge = TimeSpan.FromMinutes(20); public void InsertPoolStats(IDbConnection con, IDbTransaction tx, PoolStats stats) { @@ -146,7 +146,7 @@ public MinerStats GetMinerStats(IDbConnection con, IDbTransaction tx, string poo var lastUpdate = con.QuerySingleOrDefault(query, new { poolId, miner }, tx); // ignore stale minerstats - if (lastUpdate.HasValue && (clock.Now - lastUpdate) > MinerStatsMaxAge) + if (lastUpdate.HasValue && (clock.Now - DateTime.SpecifyKind(lastUpdate.Value, DateTimeKind.Utc) > MinerStatsMaxAge)) lastUpdate = null; if (lastUpdate.HasValue) diff --git a/src/MiningCore/Program.cs b/src/MiningCore/Program.cs index 0d2bc8677..cb991e461 100644 --- a/src/MiningCore/Program.cs +++ b/src/MiningCore/Program.cs @@ -647,7 +647,7 @@ private static void OnAppDomainUnhandledException(object sender, UnhandledExcept private static void TouchNativeLibs() { - Console.WriteLine(LibCryptonote.CryptonightHashSlow(Encoding.UTF8.GetBytes("test")).ToHexString()); + Console.WriteLine(LibCryptonote.CryptonightHashSlow(Encoding.UTF8.GetBytes("test"), 0).ToHexString()); Console.WriteLine(LibCryptonote.CryptonightHashFast(Encoding.UTF8.GetBytes("test")).ToHexString()); Console.WriteLine(new Blake().Digest(Encoding.UTF8.GetBytes("test"), 0).ToHexString()); } diff --git a/src/MiningCore/Properties/launchSettings.json b/src/MiningCore/Properties/launchSettings.json index 72807c195..9218cd2b3 100644 --- a/src/MiningCore/Properties/launchSettings.json +++ b/src/MiningCore/Properties/launchSettings.json @@ -1,8 +1,8 @@ -{ - "profiles": { - "MiningCore": { - "commandName": "Project", - "commandLineArgs": "-c config.json" - } - } +{ + "profiles": { + "MiningCore": { + "commandName": "Project", + "commandLineArgs": "-c ..\\..\\..\\config.json" + } + } } \ No newline at end of file diff --git a/src/MiningCore/runtimes/win-x64/native/libcryptonote.dll b/src/MiningCore/runtimes/win-x64/native/libcryptonote.dll index 15a282c4992bd2ed2ed2c5d69885516ca1c028ce..8d1a1fa94ba9206ace015b4774e98fc3f6163f47 100644 GIT binary patch delta 231579 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π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 diff --git a/src/MiningCore/runtimes/win-x86/native/libcryptonote.dll b/src/MiningCore/runtimes/win-x86/native/libcryptonote.dll index 1f4803a9d8fac460b99cfcbe58a468038c6eac97..bc0245ed5fbc61d00ec47ce92826c45b53d9d3f1 100644 GIT binary patch delta 189443 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 delta 160780 zcmeFae_T{m{y#ogb>XxrN$0E5dreO_}T$UTLJ} zdYpIeUR`wiv0Cn~`(E2+H*;-QT?ULHHN|oZ%WY{hyCz$hyakk;&+~Qe3=H_I-Oujh z`^V>NkC=1MkJovWC;pB9zB=(i{GNXArxTas_kYf9nYfJq{&Zq4|9y4heEjB}e`(?q_+6>Ibo(&; z?rBjbKEi*0GI0g}eeibecQb#U_s9dq=u^1;hr?qPi(s0y_LF;lpbPwK(&>js3jHH> zKQZWZef2!|LhF;q-)m!CzfdnDT%ya!^A-n9eBZbjJ(Bbfc)khA>@| z@WbKJ=B;76T~}K!%Xl{Re(S z{$*0D6%Ri8Uk~Uwjym9~8;=T0-`uaiQ&^nOnoIn}`9CsPmTseFj=XJ;E(x<(-*I?s ztxI30ERXHH&(_Hci(AXXEG0ARZ0G&zycC_zS}pG1pnKG4Vg0DYozwSdrmOwV6=#a8 zUN>0O3GvE{#+xPQ^`Wf%I65FEfF-5-3QafeoZbMRcdfn2=VN!~y37>CD;5dfnmfe`h}F^?WF+ zM|p7r5p0gr>lW5ht24jqRsW@4&#Y(rlFZ`jgfPq74aH#=b#8*B(-pT6#6QLcAQo>0 zcEzTlsI!Oaj7h-blI^InnOD)utEhY{m7A%&fy$rcM0@j_9aL0##W~RouKv2uD=nNM}6C zo2a~)mxofkS>wo#0Ksod>PoaY;|DQwcc8^Y4dOydr)ud3D4nRKAER_MPrt2u0xf?- zEmu+QcY}f=K`e%@J^xylu3e#OOSg@|D6@Xe3!e(J1k{3=-P>k79cIZWCxKo)v6vpz z83Ty>M%NU2@;iqYZ$M|hu+7ljU8m6P3+Ps>`PV2uPQI`d)ZDy8G4>fDeJ>n(qL34c z0zIfZIAEYaPlUo&j>1Nu&_F1BIS?rDnp1-)B%vnCm9Rd;{}~*vWP>S)!!_72op9g` z`>7?sh8W-gHjTG1d+SoUA9$);*rf%ex`z+H9g8|=a?^tHNRrZQ#%L)|^-Y?Q^E-pZ z@sRkTpzeSUGkYhrfYn*wTcm!|AJ1PD)fmZgpiu!k2Zi(G8XdlHYK2BRW;3~}jSltE zEakJl(UPOYXsLKtw0WUNS_@RyYCF_d03U;V!DbRlBTLK$YDo+yg4HdePI!H%$@R5y z#XvDJQ^$l1z0>0I8eKI;WUlOUy;2S4mm0#~!8;0i@x=VWI~iB9d)DJbdY>B-I^#Y@2{ zkNuR|aoO)LHZBOWcxWmqBoufeYUlx=#=2+!?Wc186-LS6)xIn#hSxunH9HRh6_tnN2Fp|@D9ze^KS;;ZtV(|>C>c_fiJ z{qNRq&?ztWzeAs|Qx5jG>XQWi^o35jQ=G0pWK&$?ZTG%p!{FG=t^?+x8jCI%67s*)HLFEw;?XTGcXo`7JB)3UtG$Cu3lXaJK;y02#>ugj~(?p zOm((S!DFj(l^q~3erL5f%~s`*)CW+L*$%kcEi3L~_NvS&6yB{EeZiY3R-7ZevpO@p zqU$u0sr9Z7qxjq>NV+m(E68XD>qT;y|`$f6yy+0AYj*nF`dyk<@9h4{mYckxf4e zF-8gV3kpfEX2wi>z`Rb#T3hYxNVS{u=6URm>cRdL4mSn^*7=3(hT5iL(TOa7?Fq$|OHs(-3p z=b4>iRNuYi_dANWlR(Z+iBS)d8C*|hFek<0P#aNA{W-6)H(2EtJ*&J$Rb0Hv@nDs; zJ*)g^s@|qWy#e0>VJl=Ep6T#l5kXT${!Be|yI$vuAPFFT=9=Z{W>^z>x`p|C5g_ZE zfho84Xz6%oa zwkh7*c#y=(WRzm)U13RNkc{TlP{I%wiK|BS$m0YzB&s*K#8uPL{})=cFOsU z>YY5p?>q(j!oEemI;uOT3QgqvJ?cOT9e_}i@_xd~$P*ZrRi=!3@wualV`zNRE<9IM zixoTZ>nOR??>xXx$Ym zc+5VSy&V?R`d*c=c+tJ;939uuS|uTB=)gC-nLEF{c?YzNRN%<4n-(%9P1f2bv)$SLw>s@6k;)Ua| z(Z}-)H6D)x6HJHt7tH>sX$5A7qNRc|wW2zn)`?NJ?J&F*OHe?zcbAVRi5I?#rN|`a zsMFD!*-m+8bY|4AQks*7=^r&I=HapW6;CTS4WBqE4!wY922cKWSJ@tTi@nTtSe!;r z4Hj{leQ$jra?;>{VA&A=vW-ewkA}8-YJm=DKWIF^SoadvXQMyN%%9jS*IwcUyxb zjNal7Xnk!i_~i_WLiQ%o_scy7)UauSzPh)#t@uZfx}s6 zb5;KKfDkQtFCm=!q6prmR{h1J!I-@(0M*2@;@#5wwzZHhd%f-YDIk98s@dKqy+#0c zeN(jlZrA!7OZ|w_$imLe!x7w;|uc@$cOYtg3f5jc*;Bj-p*F+`f{gToyFf#bf-)f~g} zP4iii8ExeJ=0drsmCcD^Ubfnj>XYp)8I7wZ%Fa%YF+I+za4Iqd-prBq)=t@vCk!VV~U#vdrlRWHiuxoy`WHL%jnT%+~6X4#XO4?RCaUcxvgAq1r^}18iDn zM(xV`QX5^Dj4Se8my%aGSmS(k4Cdzm1D4C-tgefw0*CtLcmmlzKhQ%XJ(A&!^%x_0 z2X%|({y`?FaWFG#^CJs1siTp}jUidSl@01bqbAXr&a%lYjZSTFT{Nypc3n(fHH?I6xtZ&k7z=`ZU<5V{LANX8MT2cKhdH7a2!!WdLgfh;b0a80bm~DjvS^8x?`07*S9E{cO*!!-hiJs zVrLWVb)nzcPONO44>D*7|Cmk~v~pf1UYC~=%`#SWGxH6YOUPV+nE_tD2a}<0eJIGw zp!HanZzh%&fTwa%i#y-X=92YTWl61*?ai2|%O=?_^D-{LxBx-O_ZngyvN3-UEsgo%8`0(udtVz!5 z)M`4^hqQcFPYu&Qfog)|pEh~qldR^_t6|h{% zt3>8?sGbhq2~9%O|6ZzB?#>uL^iK%D7GrJ(2Sf9Df?C{+d4&2Hx=~8*N{R{zB3;cC zW-Bk(W_46$&9-#NC>V^`H@P~#3>U)f@fXlkj;TNC3OrADR z{ndF5!zYtnUpC3pJI_Ws)JiRn<`A9iL%*{#wZUV4Ai zHU$QRXF~Q7tmO8Jj>H!#(S28hG>nIMYpH+DNSA3w`Ux`9kCTyJ7Z6P2@8v1Pq}Qo0 zkMXDXE`FW3wTLW@S(nlB#0WyJg)n0uvZHDtu!9-n)fWe9m6|-p-}T5*9k9Ua$Wz&n z>!efobIbiu9J2XrAfNd3qbR9sNci*(LlPJaU2rhq-fmS!W+qAyKBM)Mvv-a|q)qEd z<$k_SljxbAoZ%CnZwKuTHT7GK&-Wy|K5vqzfzO5NU@ea|5}%Kx!r|F1;{HA`cZNK* z2I{Ycf7=KH;`Ds=jS+g?Ho}n@U0r$?1F8-b){lE%tk{H($~$=9Lw8__r~=QLQm)pN zauq4%3R23b|JATD4wLz;^mXb2VHX(OS*{3KG17jWqfku)4&bN9co(k_%F;nSa?}se zm-;J6yZUqb^P9z?rU>gI*Pn6=p2v$>rqP#^wpqoLo8@j+!sGg<-UCm9bf9FbR>Ec& zJy}h1PWnccyF)A5VN6h_#>Gi3AvIL0(Q77OrfFlyVm--7?cri0&GY{b)1cY6;#hkA zOV=|*10?)F30c}PYfC#l-fk*i-aKGTJLaKS+ynsMwBpV5quDFZGqKEo*O8D99bs)W zv@BCbj=E{7Fo4g7Svb!PH{#75(nMrCNa$n+tv09L5F|dX6JQbG>Vn0bZB&e@yeDh3 zoD50_oAy0LwqZbvh#}n9~iuW;TtoJXfEo4uPVG)L!jE0rr;%@!oMYR{Q zCL5h&SXL6#K~$6Bg2V2}oAgBd9a)oB8sw}=ENfC~INVrP0^~|3?$*^V=8*UpD09X< z9B1M|@Z=r((!NweT5r@*5TP6!vt9BS@9@mYM2O%}|9T$lW>`sVWPN0NCl&z)6hc|v zPKE~Y83#0U3YL^GUyv6~l4DtJ68%Y&A!+6dxs#3blu+8w2ZoZwjge!|mQvmrA8EVp z?8B@!)hpyqLPKUj+SZwROc{IIf=AII5q4BZNk7nrqHvVe!3w6PuH?h(&p_ z1zCWTbn7+zH4zK07hYd4&JlcH_zs9sX^|2&cC_^tQtaOmmgH?k*DsLd$}K+kGLlem z@dbs7q2$Y(xsogQjh&_+vrO4Gc8uY>vx3qvcHqFj7XM-z&EBrqSOO$>R8PM;vfKuK7c1q**N zmKh%sqjOSXEBxa6!-SyIESV}Uh-?2u4+~Nf7z*VDDP|dKkm}Q<`Hidw53qC3&XJ52 zpOntXXqU{-8Z*wX3}>Ip&!)sOK*i+B1R3+Esizb5I-$q}dnMeLVgWcQHYz8@oO+(B z4QXdtX}83prVxAfR?2}Gu_$r3!Qr20#==hNXv&=CLhphMD8NMLs`I-(@&n@+Y4PJ( zLbu{SkOI19z_$dVHFFpKT^X7T|2VS0bgCUQ=WFocU??B z;jj=sNW_RBJ~NB|#FIUfYSBY_w&mf1~iAxqC%}dO?BW6~SbYyJ6fosA z>(Z3#3;E;YeFDUD^!Qs0?|w<*8KabpH{Z6V+uR0LS-W4l1Z--aPv9%G#t#K$1H`g= zeBYSgee3u07Xkf?tH99$H48959Um7v@?xMMvLvTK9n2#*TD@1ki5z_Q*6z;ul6?=m zuA`}VlV`Q2DQVCYxTpx`FnY4#hsK>`xh-5n(prGSe@%!Rulb=Su0!O9PWJLc-yP?Q z8F*h&T&O;9T2S^*7+rLIMrE~o9&gvQwLRb_2nV={zOB7D@D0Naqlz?CW#pTrUO-JrJ8wpgw#`BOi1+VqA1s0DTDZVBkMupeensoG5t%csA z!Q;085GE?Ed`-6rQwUlb!3@&+o?8DGOar(hyWlm76>E5x32YLa)+&}ghM8El*>IF$ z$ukcV5WGYTz*3Ih*;i^h{ZHah*aL^DT{r+Iti=I)>w6)zbraUrJtyUUPUB)u`m!|!~#=0iyO5xbkm~IQ+{%`5p*TXBo_5{kuTv4v9&lq}J>1tO~_~h`?RfY4( ztLCVFKezJjc-z4@0+~JLMDDidOmd2fIC)^di92H1NAU2X?HD_%jGcV@IEv>Hv{heU zu{%mkzKAi9a@F>!vTgD}{rrK8=6&`%6sf9e+{Ym=df1 z%^}4%<#yv(NQNTZHA|oNXJyV^BNEUvUXS~9j{>eQ(@0_39ttC9&Z0az>wl9{@9sQP z+3xq#?|-#vzY!hU@1O9j4DUZsO4=9_ZpL0E+|};>sTv>qZ*oG;3GCJ0eh>2rSd(DevW5Zw>G~MDz2m_I^$MF*Q`i z6wJJDMmLdpS2MgFa#;&BRG!Gg<9~>Bup{T;!oB&s=jzEi@_J+Koz{Hqy$ZdqzqVF65-e=u2-Sy1!q)ZybfK z>E4T^#Ur(Qk{vpWriADZn`D@P$?SZ#(1;)&KG3UL@TN!3ZviFjp!ZBHtpm$WshycU zOP(}ZtmcX(rSJr`Ue&}awo=Y=TFpQ?cYe^k9wQ7|;aS-mc+kMG>$OcY6sY9;jsi(L(fD z%Zgfp7~47n5QF%(2Juz?xNxNM^2WXr@uG!gVaGDnY#GU5`+tsMXb_sMzj+n0!$+qz`DBu@c>v!oC!6Q1Rd77M&6jS+1|_t!~2zm zfj`6yFca08y@h!-)Vya*#5>LoFp_hXz^np*8W!^kul_0aKXo&WQhew*D`F&#f3?;^ zGeGhVY1n!Wysfzx8$nsfu+Oz{aslv~OL7bh|=`oN@ z7G$tdBiqQyreU(RgLsW};#fD`8vC&3$`Uxy*154-HN2U!CTQYo0}{kodIkrGL-{Qd zJ#pEyE>0j096}8m{9YWmrXXQFYOQmVI0qmF^}UMZj$gaSCw zZk?WjDQ%b3^x^;<3yBu^7Z?D}fg0~u95lfI_hL5DuxsR7=Z?badSLXsL@OWzq$^Sp zFQulCfiVR9;MF-0R3`o1ZLG9NBV0?%G&^TPl zfq(#j30Ic^Ad11Q?vjK_#DSm;pm7fwkRSxSG#~?$i39(f3}kW+{DU$ekq}%V135R4 z0nn}y*~Nhn8L-Zs0Wkp40X}H*a19y2JAN3SYOUUTg zm~P3y19;NYD%woes*M34gerxHT8lt{sIeem<3ItW0=;k8Oby}u&%&wm;2=;L@>psd z4f=yVAt<1sI*p#)w6e-B75U0!bJxb!z(bAJx4V&)rTUH!nSr&E& z%S%ECS^llNWGeUNS%_&6VaAd)XC)xcWLYT_J}|vrA(u6=r;M`g0LvOR;kI>g(Q;WM zdkT^0NtQKcLe9$N2D#{z>#51QKQ^TQ^cLCHut$D$PBT{c<^1D5Tm6VheYX2-)tjY@ zw_)$$VdrSJFpYh{vc{KxOGR>ieffE6$QsJO-HbXDc@ZvV7SUvVVNIFQg55BN2EJpl$(mp9t+5zt(!ou8I}kn2jBl-xP|3P> z3bjB?W2j?gO@?;n19et7*W=!Llc7PjVW(QN^;_?e2#7ZDN0+bhp7*@Ydu|N&Y22rE z+CrVSVAG@Z16Fi^y8YN%>#awpEz~LRb06v*QD?7CYpKtZa{ei6el;i53K%DQcIQ20Hq^>!T6D^Q7k4M+BI%U%W0v3P zJ!CS}T0!Yabm~1AfjvnqUqzQc*Sh=#bs1o6kjPf*kmeoMW6a(_T^_W0IgzbBdu7g6 zqEXdoRIN2cU_Ew_qA$sMj#Jnw+Z)i`ueI)eL*3cc-aWP(>U%*xX8k}$J^KMxgosSl zkT}7~G_+7mir(b6axl8+Q?^yh70ZzZ7_jz)Y(LtGN^|dwAoG2sgv8MZSG7fci}q#8Wu5Xq z+KmcrpV9=oB|ax&_r-p!`{?9X=)H!_H+y>a5lCj=<5{Ht-6QeBxTQ3BK5-|sAI8&X z;~$~8nx?w2?PJCCRmKeOiY&-(o~t@@o}td6{^+ujRWexEqAV;KHmqOyCG0}ft(YVx z=HR7tZW=u~4gTEmSS%`1Hk8Z~zEe(?#7PNVi?N-w0S%f^SZ8Y%>TE6LZ1p5okj8Sy z8$QA-kV6?CtrVtZxr^E|K62+@!dD732aa{an$ld@ZTnwxFnGoWAO75d-!mq7Hn;tP z+jg^E{{PszU^Z)UludWD|@(+>b^jx?+NYE-!pXJ>o+I>PDh1 z$0kpHGYUTC1tvle|5tnRTTzyYf_G2=O$M6c?p%Xgz+b&POJLzdT$Wzk9q!IDATz?9 zWyBAj!dR}67vfcHuF;(pfuBfs7Par|&g#Q*IbX8+V)m5_5Ru3i-C1V*^mk|V!%I=# zxIdoF?ko{M(e5lbiQED1tSGFq^3nl#wz#vR@iW++6@#Ba?yP~>v&c&a;W@^gH5flb z+*t@CbK~7vaV!^6H9CyPbF4dSh&Y6 zt6ut(wqT|tKK6!?_U3z!#R?)B9Jbto1@n8bR<%KC{m(mv9m?4M8YpdDRlXdX~SJ9v*|TBR+yfjLOH3 zv_5c_ZI4w6wq{Cuo&^Bpx1B)-e&OJ#%{O>rb(H_uoJLC5@^k|wZ993oo~L*5bTv<( zxZkn>FexQ|sSmgNZ}HVU@RD9Sv*>+HZI2Wx73480+}OPRBLaN<_f zX?qO7k+XrP0UYUe-nLm;yF6v#%W}S()wMmgojKit?PcbR{+0uInPCp_GCdrJU-MF; zfgIdUN*$(D6{YGawTn^*D0PxjdnvU;5gv~bURDM@o^mg~RcJ*I-b?WUE!tE?(IXH= z8uTN5oTouQ(ua9^2Ty}Xr~@*Pe}Jbq@-#?lK>}1Mn;uV*UZyp5!S%jQ+}RN4y%?vN zUm`5PpeB4|A?I%n@)U!oM$sc5t26jc2^rC_;oJGxalcBB+VJmyleMjhJ_wX4wUbf} zl-h;RiykDPsS@o-12ZJIQWCsY8FvKH2YGHwwB5}ymF=6cD(gB1eCy-99dWX~6>d&* zSP&{m96oAbL3~KoPIaTRKJcUsJtX0jM1`8PKoU;TcpPM9rvmtLv^c&Tt9rtG14oro zYdNNr0x5=lZvh>?+FBct)wihIU|RJ4r%=(jAmnO(~AyUP^7Gygifx>4tsOt-OzPE!w_WL(RTZ z`z*nAOnGbN(DYq|0XgO9izKp0?VwZxrM6P)Fr_w9s-99C6hrBRoSo78=+lP4sO$=e zkhuMPJQpMGDD!uEPsgX$;oXXT`;ZTyK+vA@I+8eymFpv&@@(l;X``W$Mu7_yKG|*z zh(jZ#I8|JEIN!O-aK2ZgRI@;wSgn!aQ+8g9Yod>`vd(hag@Pcw$(3)?@ox1P3=k{s zJQB}7#wf`4DmI$v1>3d2Or!bZ03S9+Bh^r%<5ZVKE6be@p-S;OqorxJu%rBQ4YrS7 z;fvcl;=NzSr}|h~^N?d;{!tjBaxWo(y)w)T_Kd_sXDUXT?5()-82A*=^4;JE{>gzk4eaR{x$FRY zI#w>LXHO@{WmW9yB)M!eD{GLs2Vfv$UW%N*-ea69+ctV)ZeM1rx@lQieY6bCj2R;5 zzbxCU4e!F3FSD(`Nw#laX5S2_v;lLbWwryzfTuVh+x9TYhZiqxk3p~ZV2&O|>AjTp zA{OIMT*{9l39xNnVNl^_TH3y*(S@6$Iv(smvB4wJ%sd}J;c||9%bGN*IMdB{wt2z2q4u*rwk3f$=`XssAMvrj_B?*0GZ|CW)Jbf~{MsPN={GHKW zJ{#M?)jh;r*wz3|U=0k~BoNWWiGW4HC?E}HBaKl&dIL|_v$9?M@i5sF*nC6{Ly$bn zike8{8>258iq^3FlhI{6HQQ3u#EPmyyWF5r!UvJ8J+Vz~du+1=3m%0RPXW*MGB@ZT z5@m2Rs0YfB29-!-ERhD4NUPD9=xz}5Peo(?K~AWNde*~0i90b^Jn}&=UVpq?whJ64 zV!y(k-j6{$*~E4r1WSSOr{rNW8e1v#GNrh6J{A43L7QUdtI6}4G|??V6OH$Ppm$`@M3cKD6AgEUs0e0yC(Jb2NV3sXsM+YU27`SEQe>~kBvRRa$j z-PmoT`P39P+TN*EZr4CV*~vJMsOD`v#Ba@c*fyrt$upDW?3BPeZRO#zUrR|W8!Hkj z%f<>@C(Di{0nFwrY}rY)PL!PjW4Nq3nwAy%l9A`a zHI!LL#Xd@5GL9rPh|+LENVo8GGtz)+kZ2ByLX9r0bE8TF5rUnIl+S%q173wApA*=^ z2ryPu@X|vo#VRkY0+ykZU>&>$#cB_)*;NOn_&Nq(ZUO7#esbr+>64Q*Im#DOE8kR-QdpcOYkGzxse1T+}jZi z=xC*P=k}xDzTS%i+;hxsOf3sUTITj5v;cbU+^7Iz1J>2(tKD-f8e(V?;m(brrlLD{ zpnFa<90YZ5@Ln9;w}ILKvBC5!0kc?l!65e0I(#rOu)4{pb!~L###4KAI>bFE4q{5} z!@U<{wDv%4D7EkF?HI7wU6A0$>~l^c|MaHERwcgH+YGMGN61yczAePkC8nl^Eo4i? z?I$FfQ0u*)o3=zYMp6bn3tJ)&A}g{H^K!}xKH{}EO2fCXdaPq16~-=#+mD*Ict0~m zUW}|qjwg&I#SW3w3FBes_kGH^(pxXnHAA3)mBDEUlO-Q3gzb%n1 zQY3r{TAkO+MTe0W4aRs+>yZbe$G#P}e@Zn6aD2awgl$FLPqn@?qp0t1dt_OHS#r@{ zv>UXAeJpOTHF=w$l~lo6yfNswWX|`q-MR z)#wg2nvkc*XEta%rPJ>h#ENwg-U@sg@*IB4%UWAt%$-l&k*_*S%gdUZ{QgAevWicz z4(@!oysTvnz&jV+k>BWC=qlUmch0Z4;GA1tb{wD#&fGhkhn-pFW!3F|e}oeo%$(!P zrLqPNBlQmFN#{-FW%bqg0J4px4TAWwUeB5`?E8kj>3Bpdlw*tg^4bl_-sOGAD&rc?9BBd8H~)}i_&%)~hSm$lmP z7jdaA5MbsrIQQ~4^2*7n^`4auM6%qO&&mY9c$#C3eyUwtMbMYz*j8jHGz0Iwnz zM^n5G5WMw89N19kne`-4eohX6GJmR_MPfwbm76g2ghcd=gH#$e1=lC;~b^eiR^{p430^ z`a2z-{Nt=%-P=_}P+wK1x(7?oqlW8i!FkSO3s!UWsP-T#?C(R;02l|=f{^;iq4Aj? ztbg2NU#m`_`q8}pS`^;YT_0%YuLT;(TK)ZfQAyIE3f;AWSX{;@nj{(;3!1(L)buVa zj^QzgbpVSa!TKDFDysiL7Z%&P>j$xTmDg7m$OD!=(OnwsePs|Daj^j9oV)65O7Zm- zJQMA+0`)3r=mR4#vAgY|LiHByDPUGBvlm=7gL9Q z!sgEnfeoOv1sibcTFrzrzRW<;agyucv?h;e=48$LHEwL@-|H1wF4 zPz|7mP1m#{e+=)0@Oe+R$y%2_&edYUd``~Bv&CBHYQbc{!m?v2nI>n)V&gs~Gi7Yx z&mKidhnzi%Wv5YciJYCrvL{h;xtu*o+@0M=+&!R>ncp#9%7XHqV3Xg`T|&YKkBW@~ zk&Ot;V4bZAf2M5jH?ff&@VW!6lmBd_YU9xkKSx*$HB**sNt!=I+?|&KFudR>YH)aU z-p}ZjM`XSC8*CQkz|!pqp<*L12VtR?zhb{Fz~2gRy!k<`xnkKk)N*_8&bG1P<{rHt zVYg=TjH^v0@zRBWL$*= zrDcn;C&t^@P)kH_x{j~}CB-v9x-KarZcGX`B74tJ2o+gxJ>fl#1;kElp^g03dRg3^ zl?oEa%Qy$)%7F?{IsOJ@PPrDDmhj#b5t(A|Od_-YmcBEFi@OVAgJMT=NYckn1m&-r zP7L5^nj~~wW8{AO-=k*n)*(Xo{YEV8nxypajdk9Oy@YP8k}xa*(^z>%ED*c}#3XTT z0u=!4N>aBV^~8PP4RD__tQVT065T+XwqMsCJFC?^J1<-Q@0rd0^Mc+Gy;pEU3TDVWRK!RA`{a_RKXZpT2Ta3|@Q}OuDX+^}Qxxqh?=AMZV#7zRRv@ z1dR+OC}7IFY;&1OiQ1TSrA3?A8|kr1ytG(eK6#NmV-lSSqaA`uC+)zg)wN;g2V7hs zoLYLek!72$b!`JCz~g_@HqRtyo3W~eoOu??`fJ;~SUEd}WhYQp5@j82n>R|%PQV%$ zWu;NppW5b4!U`G79%HSe=ikWLkBnj2e~r& zA6@#EWgst-XTGWj!PfEb=^l!HCz5+pK|LB?DEy)m@6DS`IId$M#kI$nIeOnc3lbwT+<*YNm{gdrT4t9 zr_{{R7zA=~}+`!ue!|L8l zf+1YUKInX)N|O6>GJ_)`D;^>quBG5uFDCZvBuAnT868A z(&{NVNwjRp2BqP(iG5uAsoJEnc*XMierZ1zYCv~rpTDB4An6)+HdB_NN39CV3Nk>e zwSuz3demA>S;1k`YR#ssh(N7Wjm$eJFUSe4+D(+zr&|UFomQUQKC^FBf6;;u%Psc9 z%Hi!B6GuF+ZAIiO71~+~?!qW%d8UfQFw^$Xi5w&PeD!+JBm6>@c&ZRml0^b6UA7XHSohyE_zi1)k>p3nZBHq z`=CBLc~{B~0s^dD9huKc>Fk{M>u{W)%P~z--@$r6js#gEIE{XS>&v7ivcEB{84d@r zuxEjmcZ=(9Qw=q)rs@oDSt5=Gv8*#s%5={jUmU2J8|Wyb=GiaMijD~zxXw69UeA$q z=Zr7*uLUMEK`#!20h5x{SvbNBOxg?dIETvD_Fz410ys}cf7#67jXl^4l+*1 zl6j>l=o9sKx|Er}93dT-ovj)qTQv@NXJysc>4KfwY-7h7^yFDQfUWJIZ+|=qCooMY zYHxdA){0~;pGC82Mr1~#behqV@OzfivDC9FoGs{B;u(Jqxz6Tf<6n#h9HRVwa?D?J zNTlaC3%cqhF45#+xsljh;j>6Y(R?`syHYeFNUA(Na9;EXg5?@XL8Q5 zOskQ@VWr1X*oVmrI&!Qrya#$!<#Sc{mGe*HTS|bY*KWFGJ$B%u34x{$G4ik>tT{Q) zh(;3i!v2bPD{}q;mUCih^4b$bEn9G6iM1|y;|XGdHNOF0Bgi=?Qm}$9FF1jdmy!n& zw!p2dIeoe@eK6{0qInKTL`&cgQ0W?|bPWhT1;@Mf0Jo9{p-L{=NjNK_j8D42E)bGL z+)D;~z@i-RCllMJ*&y_iJl&W)IDMKiJ(|81!4`v!UP6tn^%A!&k<*Nk(V!B& z5}{t;iAFhI;CT)wo1No$e3-uW7-t-I6bwp^2HMHfjMPz4dwMkRW(&@NwsR@Twv*{- zmYm;|zTjLYlLj*Y25X#$lRw1HpkyBi1OiPo4@H0Xd6#f{(dfidR-~p|*?D{;n!XyG z?*y+h(+&8WLmlLFEJ@cB`>5$4D|l(u1Qobg!BJ~KUW+>ALHJ}s1}}0t7A0X&(j$nW zIo=c^QjeanY>PK1oCo3f%LjpX3(Of|Y+Z0}i8bfkQfstzno-7sj0a{&U#l?#!?gAs zaYOmh&M88R(y(&~{{DJrY&1x)w#ZK#<#9MJt%codWS5i^J5xsmGcgUxOIV^?`pzuchy!*#AbomcEZ-|I73m915y- zK(D3iD|RDS?4Ekv5Y+3&e}P_0-$$|k3B4wzCf&Y~Ueo7C-P#-V+gRmSza0_=jm~dT z?*8r2TU)FxFO2$yC`48 z08H$S=3RsUhgGq{#e|rKCY`enw+q{^VHZN|fz;c}{{sf0hds!I;=$P%|B{2mb?kv7 zSZ@K<8zl8~^dSa;0QN8l1h|JmxWXO~>|XYOdbyrGkk(*St%w2sO^*IX_CWeKJNg^h z0|{}$_jB}D8iap^J&^v*j{ZjWVER=Y{r5EpJ?z1Tw_=25<@8&_0v6$gw*nSH#J}>> z^p`8Gkt_m2pDW`5Ku|`!6+iA;7QqOMU_@K*H&A%82plF^1f}Jz;nF{%_5UiRUjpso zO25>AizBY1^~-}5OOd!v6hHubDE(4oTwm!4G?ZQn4ezO!>uLSJLFpw5w}avRjkNyX zp!8B`cz+|U|2HVTq=onY_9)<5L4Xt*-rq>;|3yjEqXaGl+5Q2PIAaAZ~Rd6z*+`_mv8Q+#`<&CS_| zSVE)vf%UfSNYXmnN2HNC#~fIfOJfU;EkN;jtgP)qF&Yp+_E)GRc16>oT3|^nWJ->p z+gc}Mxkkp~0cIq0LJwdp$GYQ2oV~5XYFMdq_)mY8w4%rb9S$nu;@q(SYh-DNH^$TI z1X^KTim#txU5c+?rUw>%01u@Ol$HYC0+doWDCI}_XzOm;+EfSDrr^8)36N2LJYSl^ z!q^}O)?~^slVwGXSQJ{2e%~>yhn!S0y<>+iIJX?ZB3zQT{*!aA6thUIIBAPO>%CJ4 zZahaF@`!TnIqHJD?3-L-6_d z)d!F2g9|wrodZZ-XYh`r7=uRu|DPE=Xhi6!1;$e89z51Pa5Roe^*gDFp@X*xgZBrl zy5qRzllY7802-CPb*ZBoT{U{7UD&3N8wxI(R=i8=b#*v>2%Gqi(;WTsXz{tIzbQp^ z4-20u@6_EZ&1WaE@6>e)H~1Ly*)pU2Xo`_ed!ifSyN@Zs^-oysD=2(B3BBQ6Kt&Q> zp6##hgBJxA79{D$0MEWOq~uLT{5}6Owh~8;(4DXqCd&1g`QKRjyDtHM2}t{8zl4+K zozdl&=?%onNVW-C{xi>`6IMZQ615DuLPza35sVG2f;#Ycs9Vuu)Z>o9D$$ao$A#>+ zYDy#_(LjkbB%0JmIT&i{s>#$~xHb_y`iSweglszPe$_8mm;zY*lzBLs9A#i<#up(b z_7+im*1_u75m!@$t6s-^&OY>-$@SUPvj~xRA`5SuH2iQ$B2I3va;W=X#rqo$5HDzC z4n^LV0$3VLB<3B7tJHJ==p0y*skMpRF5rTlsx@b-I3tK%uG0~&BRb~@>c~@8g-+3x z2Ex$sb|&hja(vGYW5@8lgaI$6P$AoOF>~c8)aNw}7gGWUp>ZxOpV7<9`a?IHV^S|{$FJ+MmrmDi0QZ3t8m2ep)Bf>wGFHScu+ms(U1 z|LA?xCQ7h_Zre8+Xkbr=uxJl%``7HCge)AbVsYF5&OE$#FZ#vqVZ5vNx@~9i#Tw2| zv3-SKemJ4qu4?_d^PAnaGwP2z(E%61DQmz+>EY7cTbqDtX_8A#5-a$70xp3#>3f41 zg|}R`tzuNB=X*Pj`OdP)S+Qs5uwb(_S~IlH#2ZoGJn8J% zDg1DsQNddLU=^(*>hp@!ASuuQAFp&;s~6K9=WR>)3q87R5pPoMfmf>ZUaxuSe0Bm? zJ7)I9MLf7_P~9H}N<8ynKARZZinMjslh#=d>#R}MNaos38PlG%PFrA|Hiljf*taT= zzdKmiuE_8Hd`Pnq#r#X6W_**6)d(zzY0glsWnLMJ#`;Sa~xq2L*v%#a(w7qY&wI;)}pKSXG z??&VqE%do0KJLzHO~=9iIF6z*`2;>;mH@Q9ts%XjGkqA|^duj`r#x*RBo_e$UeRD8 zoZpf@$CRFHPG4k6FNkr~jdDHKEKoF3kw-$9e~NPx6yv>%0dHMgH72~vDZi)#kvRMe zY!=rSLY4!c7x2v&k9HeDCGGG{`awTc?m4_R+LBc*My-FR3YNuHZ4xcB@2FC~IlMsM zr&F=LcgJ10x~+mf^>wIAH`7N6=DM{*=~(gJ={ETaPo^0iKRQ&!kJiQIy|Zq_iJN)h8ZQMjjm@J-P%A`W<{~ zj63XL4F`C#^ovQF-%~%sO~16klZp5+4*3y*{C9)-f>+hcSusp35m;7?t2#o)Nr58E zLOf}dvrH_)zuctWij%^bhz$X9mKhgJ%rI-fOaWk7&jz3s;G&P^#ys_=A8Yl^fWsb- z!FqK@i|9(lNX^HA*0@D?A*4%XIaPG`B{rO%S0WmcB&2bI;yQOjzc8HXWb0VBhEi@@ z8y+qOA1>hGQt;t_@tNE>_n%PfEH17Ke_|4Do^)bk5WZ~sN1&V?xDGHSdpi#6T4%fH zwq00f>p;Kw^qSSB){480&yDlCjlY9q5mUjdJRC@s^PDiw*5=OnozOE^KhAamx%!^D zhH+)6C*za9=F4ZIF!pu`mu+0cr2EZ=LS~DsBQbL5!kE&A1dTxJSdxV^baKxI+Zb$kX9CFkk$=odOfql2;&HVHT*J zplZ)$%#(cN%ybV8pKb(f>xB)f7bWhTiQ&0)Gw->FTyu?jYy+@&Vh08e;(D`C+4g?? zINSrf&{9C(2t&g1U&ct`EX?)zZ1=1IXJ8m?X3=tYEKVlC`6i+zZxmu)<*WC{4}C~n zdBRLb)#3K})z#vt8vGnW=EpF%$KSl`17WaGtz>=`m$Mg#j>wOXvht7S%0Gv}_>*U2mu|kom>pXk@GBzMP8-#kjVW2k=Usmhykg$(^q5-XSH3O~@_l~`GwD@^W-tFm=2RS?&XuMD3q zjGYjTYur4!_ZYln?(R(C661fu=vjRaI|9q_k(&ED4D_&)Onf0*8&T*zYfxiSDwj;EKk)fEDPwRQ{gZhP_0`H3X9~CL+n%rV%>?N! zj>^RjVUWJjQF(c$kf8rtd}YEcVUGT}$jV1&32*6N>Qk9KTX7RzjVUu=Jxg?`6082#ncp9sE%XtD1(hf4!ZQ61Q)TWP zAx%2{G(=)IaKkR8CsA{ZtI5DUR2~lJbqor#_#0Sh$|!g-*IjfE$`LhY4L;#iH_n8r zu1{$JiBL9eOVcM{G+jZb{o`Ajh%ZSxwE+hYXtNHDFG)eZM0PRewzA3%GldDmUw95| zZ^tID1nkgT;+jgG9#tQg0;X-vY>i>XvpK_VS}}-^G^*@C6<8{&JrqgL+7uP!W5bh0 zmsy!POBk&m5J60xCYUO#W(kg)%0Gke#^J_B?`KBTcA&Q596k^gE#YF++LRnTtE0k* z{~4A*U0#u{y{lX{TNpm=&M&SQm7951&ryl)%IiHU9j7WWDxV7}m2cXGkt04`P4P9Z z4kVmm?{=k}L0`1)5)9rdTkOID{U{?Jyayh7;yQ!3R%J95CN_R!$&7{${KU5 zvNb;ZdjT~AEnpQBirrRR1o{tScQrssp@0ZGU!#T{uCltHJ|*%N!W?53G6}2g%Z;vD z0cVyv+i{cu-1djq3)Uj~`a||I+A*bYp2+yt`Dbin!oHplamo9K-DjSh*)=VA{Uw*E zJ#L6dtuiPodBBqH5t^@E##@RS#GsG$+K^&5AgCse+6 zk1*>Wfw$mLW&A=RO&{A(IeVcHAM^8W)XSS20hHA9>4m~gmQD*FAdko4$A|j;`&Cvg z6yhZ7B${W?`i8^fSwJPj&t1mH*^`jOJ-9fW{xC(mNMByc12Xnq4Ott;^ha7EX5Kn<7RN*zY=xo?riO}OFVa7iR4*(=IdjQ-+4YE2jyrSz34IsgLEeLN*0NzhDcsP&`07!4whEeKxVpzH* zkV)!WC9myA6b2Yk@+I`VR}*7GRv+`~x9&`lutCg;JxYhDemA>^;oO z)vU`B&5mrnC!zBGdtqJnoF?nCHIS=athbe07i}2O!u|g(};gkxRvZ}uyhKEg05=U%n z{cp8@S9$w=!Z4}$ZW`)mk*y82_}uT&jdsdn*9G9XkUp7@%*q7quH%7p*Rh30S9zb~ ze5M?&RAq{wv*PGqH!hvbsaMxyNrzu!=&7I?HU*7u_Y24^WvDNG8h1H(QI)5n8d+>^ zLh9UDxuWyC%1`eT($hLYmqUFdj$h|Bz~kY~e(;ye{&Cf3PD20#^K*}@X zb7R%F>GJ9s=F}M`Z-?RTvwAEt?4It(^S?zvJTKA`V*wTvAQ?O( zCF21VC`RpIdz|mxKqR^x|S6 z#gsP$p+b0-z%L1pG#>=EUlh^mSmm*YglLJMop5noZsywR(pjSAd0G?^qau(Z%O*xm zx8N87M1aHA{O8C1jVSrak=v3{lZX*V4*Xe`iDM2Baa;Q<&XIQXf3GRvx$1Z z5(!=T_UZ&=*cxEri>dNo&!DocA)~!C9?@~u(aUuCV*}VtYokr*db;IoE)MY_TYMLe z=Ls>ycClg#zy~bxI3&5wi_hJRB)9Cip_N{X&7#7_#qcUnK?953m_JX32RoUOjZ33;vc?vKC zw;>`#QBQqy7n;#D@>X&qjKT{WEIBY`Tr!q3*+J zH@LNzwRpe8$OvMTaiVC+lF&=eL<|4wA+^RauRtwmCydSP5H70rJKGD?foQlWh=Upd z4h5zWXILLy*OUTzaNBuIz-uwq(DpuW-H8VrEpvB++Axlw#)iyawfdDy1^rEu!AC)y z&Nq!OuKK3YQkkyeLg}P(44gl4RYrmOr+%J zR9ClQ%*=NIb$J#h%&a!RHFd&=0gQnk_=|IDuq2z&F0MU{PVoK2TvO%3hlL@LPuyP) zONLh)FZ`m?^{_BXY@Imx5%*NE zRmRlD`B-%R#f8d*Vj(`2p%nOd%){ilMugA4#o9os*?%UKh+i0fy7$`je|UQzxTvc1 ze|+wp0Y({}K|xVbP%*X9{DXf`)KD4F6dgoBQq(qek<3gP&C&)MP{-?(rFN}#SIum# zbvN7XkChdO%U}}zU9+^bV(So8RHT^X{NB&Gcm9BA?YGbC=gZ;Vd+xdCJm)#*Jm>j$ zo+CdXh+Sk;h}hYbKa#iFzG`$Pd$sDiN3IjZsPQ_K)oSzJKI7{U>KBD~y6C5-IKq*K z+L1>s7NXijkuaBH-F}}ucd^iI3KzkkJ=UdV@-yT|HIXh^r#;SUz)WE#fcR*TM2FCa zfa#;NjB#$f4d=!ma3&#l71K~g96K%_Su70gI7s%9aUfc$xJ@1^j-`r|=cD2~@`P?U z5nP^En?>%TPj^dKHuI;CD`ay%4nk|vp!i;^Z! zOtYeg=2gq@qSshrZ@8`Ozgpw~%`cAs~OHlwm49wdMgS$G}f*UnKvqROqW6`JPtZb)V2DWb+$< zF*%nbP@jzl4qn(SAwnLwM2L|~9>7rgtW^Hw0ik=x!{7K$^WKM1o5@}&mD?>7y0kCw zAJ9u8-LPfCn0r7kB~ss~Ntt@{2`pBHFdcb9R!uDZ9Jd-1&LiovFWNRtn$(m~v0|XP z)X}J0ew(AQ_o|*!sTDi3L9M_(a^mhzR>)Q|CtGvN*cF&nw%pS(Vj{0%Y>O&Vn^!ST zsbZv3#bBz!OjT?v;f2flmSOFC7$x$#jVS;QRUda$Dy1c1EC)IsnxU~A=4jWX-dF$z zq_GNYg`}l@Qg6p+mz?7fN|o|k*#RY8s-Q;}{tqmAM*QI&O-Sifd@A26CF{+IiK?aI zqk$94C9P6^*UN_qDHEC!4g)DCVA}ywekP=xn8A*|cPpeEyj||GT$pI@mc`D2syKsb ze^;>Eb-KqlcCli=Rf{QzlZzyR9sNG4LrZc~vHY1DOR}0DtlOmZBCb!p ze63Yhzh;9cBO@bXRf0>G2y0-=@e}kG4xDLi0DbeOQoAyM(x{fH_|xj>L{ZISVSu`P<=d$(-@k+k>hP!43b1=eOBEErG}^KP%S|d6=&OLrf0D}I)N1*5Wqw7k z!u(Rlzkhx)C{S8M&j}@6onOPK^g+CI3}@a2h=i?I=G72iv7CkMrLd6Q6c#evan;^? zm63SJ@89NC$w0P*s(@WUy;75V4P^1 zjPu6K$%CZ~1Hz0$&?@eT7jYgepo8MhOUZ+ABv*{K%*qCSFF*OP(9!x8z)W$J#U!jX zEI%zxOH|ZV)5b_SCel;sR6P}zl42~?kkfx2Hb6WCZ;CqyY$e%~$ z%?}IVb_cRzBREy&D2cJM=OJa&w+lYD0iefJBSV}V*%P`7Lu!Jw)U9i?5;;=6?f12sw77!X^(pr;Qu39a0jva#U z+?IO0)IdF2)D77rOJpMWQhdKQk{>lE>mC+*Ce=Z}gOeA}dDK|+EN-Y^ZZbp97?aLT zj63Db2#1dm<-vhxk|9J&3e%Z&Vbt&Ftrz8m4-3)lL@6U&!Wo!uD_i?5?{Z$1YJXQpmunWvQYF> z7K)yvYlxzCBJWjBd*Fb9<6z-3X9HhH0ymo*?OimK$+$XjG8!^>!#6?R_lPiSOH5do zh`;$?LJ@ZkkPy>+{jkv4t}1ECz(GpdbKfeoj$13(Tkny^jPxz)j3jrWD(;dpZv^M| zJ0S#gdGSc(5HOx5V>$^2n4gs-jf;CxLZxO!26h30uaW|-8e)~OgjcWs5$Y?RNztqo z4?spmxi&^B9*hS<))2)XG7w*>3civReBDX-8cp~*^3Oo{yS;nevynr`RjT$^_jBx# zN|im^oe;v_g7K!nKvr=4)BhA!F!EOA~lX&KCNzJBEWFNOxNp{G#CEb0?Npxzx!W^AdP3mXa_ zk*u;42kW5S@Ro+OUn#Kif?6Qi%~9Z%??OA<>-|&5Q5U1g;^YMwU^`k^kCS|)t5QhZ zd7R~e(;bLL=f}s4niC(pCJaLG<@`|4=7jG_tp%&YWoc@BymRdtEXYBU;IZn|$;EXt zjy3UEufvdq7VI{O&>#k}r%q!)k(S1>W)xPdttA^Q=kO8HNhl`TzD=@f%_uxVm4av- zqr&@?-~l0+^5;HXAj~vZ*Y=xM`vOFro>B;$Tj0Z;sPotu!9j~)tmj06MQ_V4K4r_0 z3krl4!lf^2K`JVzI+<-JMwsmkfFQj`IqXDR zPGoeJ*A@yLd#jzmFA9xX@QjaPfrl?h(3U?Y_q1zbxcTUeGiNhr2u_f-pSS@AZ8vvR zuvC-fU&WH!a-b6u;=xS(m(Pr3XQ*jjXR?=*-*iZ~xRm(-bzR$<-j}P|41?_ex z1sxeix0WM7sB8R|kd*fKy`d#Fwtm6x0VqLgSGSrf*ngl0T@K9Rmsbnum0`o~%EBO1 zLLoKZ_;?qfIhPWc0h}K=UdLE^g2$ML-e;8nI9=WVv?k~r}S({(bn%EMEVoD`PUH}L=n*r%=o=v;^ z0_C1a9<921PofTJV1I=U10>pQ;fS=toIo>&{Q+bj03w;%D|xAssHbVkj3D2ac3a4L0+(nGW8*55;Ln{BOw68S)A*+G|p zlV%-632Y5o%R7jkTI-+_lq>56cvqx^i~11WLkWF(552HIu!kt4bNCshhhnIQE}-*s zc`d`9_w^9ZrrmvpayRdxKf)HF^bjG^*F&e^+S9Cu>`y$`yobPMg91rcRo+JWcoEPj zXwqGXU^CK%bBambDkTk$F9vg2Z`^8WV8QKC1bm8hfjlyVS6CjP#V^{0J4d6aE^XkI z`IjiqHBs-jD=ZWySG`gZFEJy|U!s@m%Yn~07WA$iI8!hkN_-!0Z5!`OkHWig2;UF& zU|h3zeO&4N@a}Lpf7iL$yEX@+$v#OVbl%*dCCzk~3T=@$7YgCxLwfmzLSe2rYp5Lj zn9xuBM>l!!W5NK@`h`6AF(_L95h-teOz0CcU+TKwqY+LnROPFDHsAD6#`ZylwR(r_e@fo<#pRAeAhug)wgO!vgN%i!>b zO2#;B&k?;$x7*6*eJ)RT2v_XC;-w@d$GC$z!AcJI5v;*6oM0u#aDvqc?RnHq-J)_& zDualk>w708^ix<%I#8c%`KC*I9C6%_XeT7APFt1@3+^!B*#Nq%CqZsYd}{BpoXjd& zeKVUJ`S=1-#SY{4=HPDGo_fb`YAm1o$>F}aKRAZZ{TmufQ>CSsRpg2PzZwhYSf#P8 zg1d!H8>_vtTeGF#^nO|*uj$jxw-eR162sz3iL|omRRpRYEg*J9acPsv@rb2u%f9IY zwJEp!d?$1*%ICzKxPu8B{BzLVEwiix4F@xVuo#}PvfBS>wmfN#usWJ|Y8HC6Io~3E zpEp1ir`X9IXde90bvRE??Q2K*Id@779|I{gT((@c#`Nc{PvM=F2~v~gPKsB$E5$c9 zM@6v{m#=TRGi`)nnHQ#OXB3!D|1U6o_NQNm>C3d3{(QEi@Y&*niO-j2FkSlT`j$a6 z`X&A;V5qC6mF8%1F098fm;k#-iU}x@Rnb0P!lBfStc;G1X&*?m zBf#A#L^~9BoM?-<@vJa-bsQ^TN>g+k5s(EW*iEsqM6xI9;O)fuN@MX!8pl(@xtoo7 z3Un0M+Ci>0@6(s1HPKR1e7yF3I`SS;E4DK=vIGDU=NYxOe&vI4=OOAV^o}_ z+Lek{;iNEF-RiKn35pqQ8U9FM)b&<7in6fcqgPH@jgwEZ0jV^SBa5*3Sm^6!<;5Wd znh0yc+WO^B6Vo}4GsO@)Rrot__+u0`%akaBpOHJz&Ea2Thh)VYU|dJ59ke0Msw_ns zwnRxudgzHRlw%hu#WS1{g+rz?>O9MJo}ui0*!=h&kie#sZx}Tic2k^!1i{`?hbB|2 zC!yH}E48|jTAj+9opra`?EIN({iu=DI2N>bFxqQ;Y#~H5YI6X1w!XALR5JrPtY4mH()G>`(zxI>m#Ab>&-q8#xv-{S<_8FA<1JlImw=){@3 z4Qvk7LKwanCB@ZLhNUS6mx4!i8ryInq07rcTpOwV#<@XQ*y3bQ5v+$L$A!+FgUeyK zt8J^;@o+DAER7C#h-`gah`4JqP8cmZPuqcGY$?!r8ecDST(Y0O1mUcWP;`FvozPG^ zz79eCneeq>k979JP;?vbnnte+LYchg0Q@}g?3>#P|J*N-yjX7}xtLUESQ^?UolwcF zD|H{+g@oLGoLKI&G7m_-s+cekc0QJ_rn3*==pv2e{GDUXVA5D_hAl_qnkY!eVF0AJ zfjrkLP8DcGH+qOFG?I>uttPz$?Jx6>V_PaI8C?_MEI0ckhF@SIL!}jixoD{<5{#K_ z)|=NorQhluwF3NwjCNkks5yEAB+?r>H6nFCcU(2d6T2;Di(oovg-c#e*})|%_~p=p z{F2WT(>%6t%q2FNdc97MCpH&B8jP=Ntdu+y$%{5$d`d8ViQhN)HQ;y6PJe#KkJI9K z&QOy`jQ=0fwW>TNo>&}qA)5N%K$Pkz24CR3G#10Sut1aPnx4oOft#m1kj+};*-9xy zS2A!k9C==Wjun@vYc8k*nCb?B903^^>pHfUHt=Gr8L{eK#(JhaBsr_Lp7P<|OtloNnUDyzapJ~cUVs0Pkr<-tYWq9E=G94S&zl>QyKBn6snZ_M0 zK5s0dc>=AN-O_{|c&QC(@Z)`kBr7B6waSEHT-0F$jv#f=o~2_`5_L0NP`Y7q6_>bh z(xBxMMo2Y#j(oJ6x7S#F4-s#CbKG%Q&ol|jY$eZjmD(u_v9Z`biqcXMC1P;GG2>%P z@#OD^tA7dbV&Ig++adJ;1WN{>Xbx-hKDnfh;%YXpk!o%}inAmTRJp?*EGp=PYn90H zKXyXO`oK5Z4J_utnYoi4wlScTPAiArNX?&{m%15CUX~;6; zqQ_1ckVZ&tSn!X4BBRmK2s&JW4SdGePZ=#IH-wLq_}Of}8D?_=_!O9+UiM?_tv?4~ zOHr~Xg6aJTTlp`PPfXN6*X~N!OUKJ6j@PhXn8!l|^a~=FkpU|Z*i|Gs9?|0@Q@#AB zC*j(AiMxGUnWGo-21BsLQR?736H4<3nkzU91D0?~l*->mf|Kz(jr%5Rxou(GQJw{( z4Y|!zA_FJ0v50)}<1WMEWkbY(dTC-LRv~RE)`VG{rKMHkWbsk!l~ZZN(QYsTmQ)6& z7FZpXT5an{niVLMHGyOo2f~MHtFWDMac_-A8z;?$r;Pbr>9ycdIgPfnd;;Ec#L7N? zKcMf+N-qT`R2UzF5eU*J`jnImd@7+^wZ`>lu%=MgF6;_;scXmKl<3S9sVa;8pdct! z4?jlG>_n*zHd0n{3zi*@>K=!sF%fHuniQFNk5gdkCorwip>>M ztSW)EX0aXM2xyor=e77Lys>ybQVJfiY7|N$O^M7Uwt`(N12`3ICcF%xcVZnw8G7NRvGr3K7e_X z4ioDI-Wxmpz=}z;bmmBKai@Lj8*ybjG5q;jF)XlfnCuZtLbFj9Y~O%Q7q%wWkJQ8g zv%}UtIpN3UmE>o2gOZE`z?2mVKxST>1RRGS#5>+bQ*gH zsz5-SqBECbhXpM4bKXKiwBtxPhG_<^qI_cqqz%87d@QeT{WGYWiyW#aMh}P_#$pH9 zT?`fyJJu*-hmFLJWh8d&-$a}m0?aqMbSvRF2ges9@d1`5r5rq`;FJYI6Is`OTp10J z%|E6|({Le|{Q1`ArGm8`qU`L@Cqp zYl4uQ2eqnsP$yll)w{;DO}K8Ha6OBa6Z-~J?g>;63mgP(EK6t28BJ;IPht2rRGR3+ zS6jF=Nm{~JN&a|ZTPjMu<~!s1V$?LpZ^I+E#+8ZIH*~R!6P+m@yFn)TedhzVNrc8#x6$9?AZl^YZ)mYe z-u#TvXMp}`w79l`3(v%wa#L!!YD%qQt1A?d`HJm_d2_eKwC{BHZ_(!_azAN9BsoEo zd1G+QV7d)dak_&qkS!zGFki_FC8E6fS>ZOm*VW1ClGo7?i%MHJ z3Xf5%4xSn%-@QZV-eoUHG#z$}Gj0cDvXu24GO`xSiT%aSL;U*#Tk&7Q@wlz`N>|=q z_VvH`KvRp4$z*S=<9l=E!G9J^VVh91D%C?Rd;V>%0=56kk6ADkt)VtREnE5+dg|6{ zSreH*Az(lxz$&a^FQ8W78w`xbm1U4pV%#CTn?+^xg<$x_WBAE+e-^rFU%Nnx+Mcrc zFGA?hUm3|a!$ms<7k4X;7v!xaANUJYRBuA;RLnH^t1O5TP6lZrCob4A%*q0Ar6x0r z^mjj(eixmt{h6(Bdpv1UJuRD{-dH!%C|r_0lXVRTDk$iWDIMMtOVB)@MB8flc2q!e zno>c~uUBwavkLa#SOIxuw#QJ-f$Wr?#y%zySQU1p9$0-ybLQWfXeZrS(5fuH0I7bg zTz6(s*06EP8a9&Fu%WbumBZ-^rOc+&Y*O{k_R5Fk!bz^2{wLL;^LaSx_|U6hpc#Pn zG`3NNkkAkSp_dIF%f(dy=y$ZeC4jfx03dzn2hjVZ0BB~q8zF?#;)!PCU!g5q@@Dkb zj15t&$FT&C7B10m@F^8uf3qUxM|qJ`3vO9t*MK6s zVF?Pvs^T+3u0aLx9ER%AC||4+`h=wf)dnj{&hiwUAlVFZ*vmq~0EFJKg;LOu(0M~k z=H`^q?N9R#2)LzEX%;{u)(0A~>KEm2{(^li+fVq_TuU&4Uw%!{4D|O(xnb@7XEDw((It`NU44Q^#RD0*odK&62O~6r#ubtwUZ@BhewE zJuDk$1JEI&Nv+YLKr$reo|RYb63n5sFL=BpL%#4lLcYAzLk{07#CDz|g_7yEQ5pf~ z!+P#|n8}`aTb{mGh)!DiHYTE?@ljRk1@N;=ftDjkaV~63SR20lZ$8gL@+gEngVnu? z?-QzH1ES!4S3&?QsZj!=vS^dxe?8d-Ajwge38< zx42X#|Mdl7tl0Rb{3Wh-@)ZK1i$kDDiWTNuTb+)5<+P{!iHnLD^ENOOsHlR=AyQ}q zy0JKwYEDU#rcZ+91`=p<@pif@-cG-%c>4#tPrhxG)V!U6Ra7;=(vx)?+n97$A--lKIQK#^yAg0|!N+YVJ3 z>GsPFkVtbHcyO^_3P&Vo@=fBZoax_J4oS~nCXsKiz917tx~BW& z&vfQQ3@8!l>c|S|u{B`LPoqWuDSF{s^s8}*qEAADi)1epXTFvO%>x2w5jEyn z_0I7Da$I)=es&GBmwu_!5-(B3o&bX(rP{$r=$sGwGqQ6uauEvG&V(Sx}? zjlO0W^2RDqFhmQjalMKmdN^rs6-5?MG9QXKh6+>+&8F6o7idciiOn#y;8qyw9Dt#b z{+45dGNI@_KZ@QfCluX)p@R;1_iacY)^Q(Lc(BtZ>?>YHt1*ry-Li8)r{UxsNxn%8 zLqWrRlc2JNZxSPy$eXw)eId8~yU-&BW)TcNShLixRhJ`t&s~o8%9D`mkKSDR)^lAU zs@X!ZY9o6EH*fX-7g`mlKDm2i&b7G9Fu@SGP%o3Q5{85aKa@ZCyU^9xpsnCxGgBLE z|AC2HzWjG#Ajmp)*l>4YtVUT@Od#m-%soo41+lqA)H0f|A8dp)LuWoaBQuM&-Gd@| zq)K#adKP>CRo@8KX^0*0?G=_*=*)2l2B?cGskLx-V~iNmluK^t0F^x>u`WC91T?Pu zGt$%8mk)AQNZo9e+Qc^m)*7tr1LXyHZ8+Xf;ak(Jtl&A61$6>#O&i->ovL#4k%)S0z9RR{94l|A*+$zc04nrqE*WwpcxaB>^6N zFb4+mC#skbXcdn0%*b@cJ0F5yN+Wyshj8u$2#Q!-%loV;5f{#SwPp;q-(U1!J>2Z&5Z(aPXfNECe5!1l_rl2has9 zFK^=Ou=3!>HCdliW54~M*ak2be%szAAN|4C)%J{n5>X~6EsSUx0L+~Me)5H5sg!0W???+u{llc?h+3_K` zmKg>_T|r7^WxDh+t=Z@E=VZE8#Vp#Khy6uaY!QS{QgEysYz#|2gemKA4~8FEa-}=@ zkb>b7<#I&1R4A89<#Ix~9Ha{w>i;$uLy`wrt{hzn_Mm5AY&`dPtRLtzP)_77wK~2Q zYzQ>%o0fbf)II#1IKPDjEZKR5V)I>;IsY zu}M0HP#4fKf2ge@9kb~t%s1GM=`d7fQ^hp+u{rEV!f97UBlHADd>6wPNtkJ}OnX|96rBH{Cu1hZ>cAvm{1;En*Ub5%g!3|$R% zZN$0e%ZzM%S7o1}Q1+Krnu+-A> zdj)MzW17VTvd(4g?fCo>K66fJs%>v&HMu;Q7T*&RVjZI^xTc@f{Eca>BPbgGrm%ff7|+iU1g@mX1TR|vmjnX3Fniy$(PO1wRDz(8$$JwsM( z!(P^nu=;TOGH^-l53lfN$&2KUav zGQ!;gZ3(nX?zq#k&t=(Vbz63!!5TIfE2W(FoDh}sE))yMNRk~!8Dej*SOAQ`N!(-G z0nwt0r6NB%276NA>GvzH3*+a(rk2_SS(Y}}^FTf4clc!2^PatFQHGJU0(JxhOa<{XJTv&so=9PY^k^fpIjOh^uBQr;* zvG_6ijtAv;^3t8fb7bKNqKnSniSSCrxv0?1{{mhO#axMlLQL95>5dAI`Ec;p_aXkcc1F|?!>mgz$IZ%2eK^S`-=cO~u5BV}f0EQSr|Mh<(q5rnE3u55z8L)ir1 zmo~xoqD}DI{&)*+q>LfI$v^4MYF}PumRC??;c6tg5 zM5L}mY#`u7P<}yS za~3YYt`xAGtO%Z}c3iPf8(Z5De|%t1-w1+D1tZ7`DDjQ6n7}2ggUgkVJA_5sPVu<; z2y+$lq%b8jZ=nm=Gg zzq5EIQut{syPn~FcPIeWc8?ZiQBM&SLhLq^r4kR+rR;MsioX^6Be14$>>psqFhxKV z`u-|fPR+XR5r>v2`*dH{zM5f?j=ev z25q5*z@3~(ckbjQy11sr&aG*K8#bm&VHA^(Uvuy|pK6=ren>>nBdG3|L%`Ao zn6-OJu{=OgH)VYfr}f=P>w5ueMRnxDzzRQ*e|lR;6vE{Zhu|jz22p#Kv`}8{rS>G( zh$=xc;nLA)Tt~yW&_l61jO!o4y_f5x6qW1r?qv@1?`2+!bqzcW6oenTbU#r8p^r9~ zo6ouorD^OEd|#z>ogHA2$xh)r>!nb(orX&s@tv2AMNbo2Ch8D(u)B0_krHrZ7X}yt zjzHB!`+k44eNEck)CRuiU+N}J){{9+X~p{>Qrh>Gup`Fx~?l^=?-JJp05J_7{Iq) zUkMSS&U`%$iWP_upJVQWZR3ZJ~eHY@>0b>A^Q3=~PqCpJuOGb#eC zt=}Vu5=wt=;}aX`UX5|8c;tKz7z?jcAQaJF}RQr9pI2dASgymZ`EU!>SRk4=bX;3K9jD5`yR8 z9bq@Mad9~}!WhW~N4FSm{DA7#?l}Jj5#q9dT=*UWHih^-a4_z>-3OFjKk8-pIab5r zP0aPykjbFUUICvTGZbHXbLf??;if>c4L0_xQ=x(94A|p9G8twE5qqx7hD`QG!0fOG z2xS+Qt^N?oNTm(0W$dU9iMx!#YbSH>R(xBCZz1paqAz)chNVbPWB=y6KyG@zRx40Ub;*52kT0G-BR!a&1xq2-EQae>J2JM zk^G>X@Xo8d|Rtu}$M_K9co+qcAPO3&*9=BeIy8HA0g3h*-B)A;U_a!h#8Ti3Vv?+n1 z55btxrUa;ABb1*gaURVMp}j-|I>iq+{t;%o0{dzGJC+mpy}m1Q6t^ zgE)Ee(-vs*3rEAy3Q0?z;VxahmAR_{DWXSUcm{ag8;e&Ew}TM*SC;>|h0)PioJH>} z^~C%;O2Vz|^fo+)5{;*d>`M5cttILfhfWKGX%%64b+2`OcVGx>~LH-uif zQ?}CZIc?_isK-j@ z50OE8n4pYm`zBW#{4GFju)pY(u{au3jNHg9)x6R$%Ney(u|X2Iya8-1LWF?99@Wgj z_>R^#ylkCH95#Ij;5}@30~l?>sT3z0&tZ?)M8v+>&v|Oweubgl4>!p!^4N1ikJxd+ zGz%`N;z#nhwh`*V2oYr??UaWl{9&!K*YnI{FDud)qLZ?k~s zBume!OBnU~RyG0Nf65CImvG#FiP_nYlq(e&b+CKUs8RnjgX9d%@>Mt0d|K1;Ap0NINN5yN`ql?|P z`J5bfQdlA8hRW+s3QO$d(xyW69V8epL}f?@L|XvGZv3v{*Y+GtGQacZ8w1d!gQh1m zjxiQNB-4ooxqcs4hMZ^ z!+YYsa_Y6^zPQixy&px=M-eknQaYQ0M0i-BB<+vTM?+ybceZPiA+3{Zx`7>EhhgFi z3;q2ZL>`?dh!gEf;Oh_yd~MteNr3`iqY!PJ6X(N{vOXa2^*`1E)YbxDFGO*C5^?O^ z#2Bq*k8c4Cuqp%?qkFKsQ7GmDpoW|VYjhrvtqLnTjl45o7#hZ`{rGW*Q>7N6iaYrb<<%8H>%sMwg6 z#YVZZV!?#QgFAw2Py4_s^B zYl#Ar-Y+wYXz-4Ua{5J_9Qcn-^5%;;#cpmp`O-xky!Ho4He3>t?3KrSH2Je<{eZrP zEf6mHRKND>Be;oC}Ubfv2yNqP4Cqb74(|G)j z;-^O)Pyf!J3OyDu@@<1Ez|Q0OWnz~T?X)8ir2+#JN58;Y2OIQqucYy38ot~x7=5d# zey>Pq?QyBze5~{{ZJNvQX&wanH4c266BySo&&GKrSfHoDy)KhI(%9St-OGHf^eZi5 z`^{j9^zlt%`$09}mx9@s=~zV`J}+FAzxW(y%WV&qJADBm&=^8OU_={;v;9FQ_f^a9 zRKw~?4qWOweg@)-cX01R@&hC4f|3s4dntqoyk{(i7K9LRQi#jafP?$e z^v)1zJZ+D6xb+D?t8(cCozxb3NQ7S{cB?Oxg>~jqFkhJ%<#79~$80IRu7z(Nn60Ut z*&@+gp7W89*&2mKikPiZ2c`$~55#QYgpuc#ab~Ob&V(OU-r*YdAIve36Yw2u1KyB{ zH>WOu&02v(5~g%F9di=Pn~JB{0|L7W zbw4$j{S4KDx5Ji}`8v$;l)uBWkPHI!7W&BhJBItSK;ZxITW~{WUtjT{5>Q2c51owa zP!9bBVmGV zYgv>X#6i9t#NeCBkArp)KZo^%?;wtkJFjdY)`07W?+!K)m*vs=Y6XWd@KL0-aNOR> zqaQaef4_z5#J0|ri4$koVz}TCBa%;yNZ`_3Od@1}4>KE-ss!mr{9{@pHvlvY>-k@U z`Lhr|g-{oO!Tbks28g-Vo`u2q0FgA6pD;*V!p&n~ln*CN%+1az1EYloC`Dbm2SxFS z7K&tcU^Nk}x!HDP6Kl_TM^W2S-zbU(WG1&uK7@-7}zAZW5}-0gdTFpw?aGdnPYOsw{Vob>pdUD^B(s@9EW7!%vLLyUR8fT z(MWPQ_k2D4MO$^u0z zg158pc-IH`M5unesP2qp0Pd+1OdU`N{W|^TpZVsQy(9ww#sR?UZ{)G{*jiltjt`1w zH~FDhisVI`ccS7w_|f&f|AQaF;QdES&CeY}I51nkRe<@=As;Ybg9q}W4w{2ur}_;~@LD_{4*?X-)&9#rZhNMh*ahY;noUg~I$$GKVX6!gbU`;erK{!eph z!;>G6Jwv3SN>oC;+vbAPB045%XbZ|yAV-1J$aW_-pTtn?mC9zx{jgU7ZigZs{g z^Prk#G3@g6Y2iJ}r`dMApuFF!-@N#c@_bUc(zokS4Q2UO$x2DRQ4A%8zUntTD=5V# zybl7Ew#uPIErQ?y!xp{kK|M}iw%h8kCCD|+bmd|4JS)TDF$bae{pd>RM~)(R?#E6q<7)@9kp9q7l7y%si0a&cZO^W0NsG9Z zo{fXIu+oU7~aZWB9vao9%8L5yf7)g^@aJYQpMGKbx(;UlUI~nl+rthd@QmsoOs1=@Ck~--cL?O5 zsM}pn5YE_7RL&Ed$kxC<=E((g`v4U?J)fn}>*-1CB4K$tltnc`SlD?LnR)SuG6@uY zv7FAJj3+d?lV{;;G;@w}nXg(hmCWsHODJy{?2V_~%}CM7hCTh3~G?cXz^fcZScX!Em zM`MEixz{x)H@Iu`-8K2{$R&&Zx!38ucgd6J&Yf)K>B%$cB4y5zrp;mtib+|MJj%fdXkm+r@RDN)MZ3oeE9 zgblQbMqr2mAiN~ZEhLS;OhFqx<)PfUlBo$IE!K%wD>p!j2UfK&UDUhZ(2sv&)}LGi||#yV0|(r#aeMVJvA5g#1Odw z)le!&B?FK^c@|I_ZBV6$e0nh84UYFLiX|Mv!7HNG{uw#J^o)$?lXC8}ZK)>w)IgsgKY>z(L&`m!i? z5*~q-eh5A=8?`0j4wz zBb*Y@uXe}#I&k5Wagw0oy^Btt!9MGE-Ghx`#^S31=tgUE3GJwkfWYHodSngbV42$* z%Lv@5vGHu?0u)~lP!Av_p&VN&D%j>>bku4vA(B$*q*sbH4*Zwk(Vc8ULAZqTVH6B+ z%h;)OM#P;G3Jf@e)mVHtvh$5AM#psA^35y8$C7Z14J^j>3G^C%F-=1`rcq9$v?-SA zaOxAL#>T9g0DK?8qe+F&o@Sg_6#FVtte*VX0qmJvKJLnsVmk}9-PESCj%Xs41)l@_ zqnCt*Na~qM(8c1uAC0L^7*kRaFJC!9b`x){+JM{m`QXo@0JQ;6;MT6TfCeXR!Lbwc zi7NC(d}v_8Py^^6ZV7mGvJoZ`GS;+wDB4JaC-5wd5z6+SLCW@?epGgED!UqHW{f0! z(FE`}#-lCs=EhYr^4sO2Hbo8gttTK9;4JF>oJC>XFjNGRA>VyX7<%U=^s9HKr{eSi z#-X&-aR@EvaJmsPIO-x@mJ$i_ZWx|i8RuNC6HwbStyw;HP3Suo>jI5!3(_O8$XMB^ z#lUGXzjwJxDC=Km0kiMWt*iJP{wlKTarVUh@}MRms?&q_<2{{zDb0yDz=6k%Vcva- zd{2|mMSQN0yrxO$F5>j@KT%!_wDb}$9r*UFe`HRo=UsyLLu&)OX{gu8pB7?uL?-`=@bp>$1()-DeTt{CoZrRt) zMFD+%kg{IMpuVPC7e}YJuP^4w7q1IZVricI3$C5^QbCobZNzCNc0@;>J`?^NRPf9~^_2mIgk`5)DCZqn!X-lq2X zRJi}qEYFYzBEqqOR{c~x+^hI96W|cS#kK0j!n!jUu$cDSz7wOw@7;1cLF_43_mKw+ zV)wBZEjN5ta||vQJ~E<=E>Eu2Ke*`RFXhzPyG1d64uj9y&q{z_1dVBaP^b3ieC(bj zm%WuiY{_uGIRKtSH^ z1@ex$08&{e4zvc;$3bHJLZ&^bLd1bn&SB*pt)~?OJv>G6Vg^`Cu}EaGMh=$C@C&^M z3eCq`>F0_-436L!Od#M+>C}Es&IuTWHTMDL=3|n!Y9AyQS*~cJvz;UC({&UE7G9y` zt^0#=;C1P@(aq1i*8Y&6pg8WX8kfno5U^zWR~%zu_}AizVlb}MNeRla*E_-aw`%>* zjPtse)g%DGs8%4PsCunKsQVQN`4NbKaH1DN+I`3UBW{H7yY!nt_yP#K0#!{YAnel| zLW=*>TF2A8w$r>mtad$XIj;}rFXvkm(divfg=CULpd2p0~J`Vm-h#YJvz|vxDL*T zlYzr<(XEu5+K9=jBL~hX&1BzUVNo49f?IXu7&qf4i27-;iYU?&P*|yDN?561mScrz z8CI&9mu_mrs+7RJnLKFIaK#b(4#g3>FF9iOB9Gj>SdI(@t<7oWY{X-q^@KbwRP1CY z03K8T_@*@g{Bf<6uvQ8Hfnlv~2Ea$XZwY|=ahhlH#85FT5Qx94K-@BP6@X|Fy6R>? zOujV`BRLTEfU(NYQR5NwjYYI+K#iK;iH6#_LVv=&y(GzC1ou?07%*+&u^h&PkK`0yq|cLJc1{$(tFM& zkKnl~nGa7jdgm>x+}`t6Rj%LEj0jWzfVA0fJjq}cy2$I{u0|I9jVBp2>w$m{&;T&P zLI|xX%?~oL^OFp|!LZ;1hUPDwv4@b_{M7Mae(JcEP94WBCs4OKb$olwEivG&#XmMP zqgx3}37Lc1?+Z&AF6{L2=8if#Mu6n7XWNVYM~;As6N*k~HwC^Y#Nj4Qrv~8|Q9|vf z@KnbBf;!e=DQDC;?dzR^(RMe^ln)ycSZoxP+1Drzfwd*uD2@(UjI|V4kRLUPo#X>X zv3=)^Y7};ky#XE!*yM#x!r~_HLJr+mk=T*BQ`b4{!PZNCK%Q(82Z>vC^1~*vueYq& zfU*{18RBIP(4j1nN&R?I_>4CLi}tdSKbyqOA7 zITX5?)>q0O;8bsA4+CkaSB)SuzKK@KMM~OlTPgpk^{s`Q>nNw2&%)9WI*fA!J)pT#lo1{mrW1?(vKa6HPPm zlksc7Z)}I(_fs14y}vuqmf!$7WDlf6pO9xz>vQ9uF8|#4Vrr|Cuq>_whK$5|V`-2E zXI$C3WWHOELq;-LTa0o?d1Q9s^#l1qrZwMEBotC;!?cxYNBA?4ZCeN{+@jIcVwa_U zt?|B+$RPUE%D%$5%2q8p@bdPSz1mmyZe(ayb{WNsF&2$SX)Vfnk;F7-yjs*8x zJsU99K*Jv)knkc6j{R|`u#)5!A!06~O1Y**VgW`MV?o}5S@vxw@iwz2AXsfHA$eEp zpw?Q3^Gn#{-Q+tvixJ|gZgOU4F?unzp22g$lZTjU*I>cP&X$H#V)13$r(DGk#=PK3 zAGv8Q6mBL51jk4MD959bn(X{Go|TcT44%;04iF7@@Fu|dX=kyo`1qr8NTk?3>~aCb zF>N;1#!zeqVO^)l!z0Ds_C5*#inWXypQmTfz~lz&F6A{z?Fcld9$E(HBpuF6E!jf} zMBR5E!cAG8{$TiSt%+H>(#{|lgEJY$+LgxATObdSJd!e5G60ntv)H}3W@qQ_!>Mbo z>5=XfyXj7#m9+;toMR7C!N8ghSi8^#9~}3b-A##m&f!nNTD=DFsP7M{?@=MMQGIUq zGLi_Kkp#{gc))SLSUWwS=kSJcENs$9j~Hs>rHT6KlI|e9sL(!nSr@U5eF+toTJJfl zBzAzqupkWq6zSddj&S9p(hJ4HX`DlD0k9`0OMJ5DkLRpv&j)!73`Qg$egnzPWU6cCZ++vo%zf#6$ zUJUyQdPTL1XaGY~zZU>e_fXHEn1ZpN0B0Iohun8Ni>C4*dJ@?8n1k7abjl=Jyn*?-^VHgaMa0Fw2D1o##a<@%rVrf`CYsnMc%xPdmohkgF!swY z9(QmBMxe2q*lGM~?;{=Ki=2mb8UU!~-Opbh!gx^UxBu0X{M8w?n7ey z@=i*KnWE+hNs-^{29D&zPV%L0Vr+lCEy%R7fc_Sh*rPE_po7#%D+eR|6zzp<@chE( z_LwkvU=%*<7A8-N61&;2Lp+wsF+?ugzI8y~haYAOJTX^;Ddg0$7oWgoqK-yfeh);qyNqY)5#5`Z<{GKq zJ&~N6H9zj1H@DWnKE^x8xO?^V0b4^Q;S{=w6nw6&l~v)FUBp5mWIb)#oxiWO^d3c!~?6(-m_YC}mG1x~W-m6Hkt6nXG@8a>ukx4yR=Mp4gV@ zgL(FTEt8XY@_?4fGkNmhmdUw1d05NjH9R@qpKMnew3Vli@uw@v`*?C<%j624oYXS; z5>KAgGP#i_Pi>iOcu#BEW3?l>Ic{T+p1Egc%j{!#@~oE0b9nNcmdOwC|15e)3GTBf{?cCKe zIhH3s-!eI#C-1Z6wrKt&p1!|j_E|jnK#Sx(uOcC?gcG|=Y4`wo8cscz-x(V|q$)ob zw%sU4^%nbThhf?$_ZFk=sYbBX8K6^$#EAgO>qk1S3;E~r+Ib@S(KIiqtpZ6vnKTX6 zGQ&|EmC4yU+H~nTTWeGvJrM)=SBc6(`)gcQEe!EG*FhAGbFkX!0k9`x7N<3!^KC%q z55PUjb5_DD*$^v6-Gv;)b3UGrWSf4{9_5pn^a=mmsg$M?Sp(#Ca`VC}PMgNVL2n-o z$D(Lr-Jg_~$BMUW=Ku{mW5w7mg{Vln;9S^<*o3ieZDl1`v8T5FeXYFkHt{xX18yG2P2`C(qQSI5UqL$WR&=CXd7IcntO%9s=(`1|$JAF` zYENiK?fbzd{!sQq%}|&^YK}t%&7jpHJI>G06|ikbNQr| z8kHxh9(WpwjqGvwY>=bRR#c;rj^%Eq>Q=W!7oN^ysafzS+q0dLCg3(Zd(RHs*LI34 zv0C#4t0!VGYS}}d0*|yogc|TdC>oG!C%E|vX4d_eePPt!6DRxd`w&fzwv z=1Kge7ho`P=KTWT4zI^M`JGe0VD|u?Ubh>2*~4CV7oHkR_)^!8eP z-U5b27Xks|-uHBj^W=Yf^HOTHj`m1 zbQMPyCc;#3mEacAlNz(6-y1r-V3{)$xwTGGCIxIh54MJ?GiPoNsqfu>`Qmj{*N1A+BGdst@$NF6y{ zvlsr%p=^uq#vwhsw&s8U?|b^P&QLJ61fOa0LlC}e17Qe*3{xivB-v(XHsBNJxDAuC zmjcj7*oVkD#oSj3zW0^K{A6sXpTbF>%hr(PxD@>!fu9$X4gGh8=X8a_yTNxDn~*^o z_m14V_dO~=Nx?UixOeZo@*o07p#Ab7W_dyE6JV(_PoFy3RgZ*6eKBHidi~Y6%b$ z#2Bo|aJmA~6QH$-gC|C9sM7Ekai4wVTz0pTEAs3+!S~m6TI^+MSv|%oIuE6hm;xOG z&i$bP-dtQcM=t}!6%6)>H8i1!7k&sgxovcs5>G?{ZZX%2aN$K2*2R)xtTTIVjzFyp z$`i9c=)b3870}l1h@CDGCG@xZA^JP~ApITtD*i6o6mf}P+MVH-?g#iKXg|O7*~c#f zpT}j#6G7*2kt~`$mHevPa{^ZvWXTKkTzmoYr#mPT_W~r8-Sl$L86e7?7F2-?LGBK! z#2v{c?x1t(U7l{%eI*)q!38{vc%wW^eDQEj4Qj-5=_L@vWkm4soSTGWF-j7lxtOiA z74nn6t*G?>ho)Aesk=w}TlgCqNa~Xf)J6{fj`8B{`r(k9YlE0kt{M*s$Ah+K=NvX@ z@=Un}G=$SIo-q;cMJ_Cqh-_3+Q-M4z1HNh-EMNRf*pTxT=`#q0$C^Az&^*eXyiuCs{l&xR_wV{*^X>#6kmm+kbL zN|dDu7|C!D^soykN2;eqr3Vb?3V8V)Nw*6;8 z%=yY=`-0Wd7iD>d++EblEWFuG*@{c-6m30w(jIzZv#(MstNjfW(ZDVtOoJ9O10ZF6 zsX33~kE0L3yK^EO07yrXogH@+8=SV4N~MgrjxzQGit^a1*#d9HB~7)Q*&*DqvZa%&OI}+sZXl;{}V%PFwd8zrsm88+g zCDqqrvt6AeqV@G+fG?IcgvP@dYC=^yTJZzv@@<`>ZFB(jA2MhT_ zpf~^~2whXlR~j$BY)0u=$Y~fNCtrzz*bIEA)_@A$C2iIb17)f7^qGWF;xog@|4kBO zDGYU}MI1`kJ1t^Q9qH5MITrDP5k)GLDjlS1LZ&=5S?n$TVVL}2vbaG!7bjm!7Q1OT zfq(3hB6hbE;iFSp70QRj*?=e^`aCT|r)si!3;{7y0o@SzbUTyI` zi1l6SxCcQ?#_Q%F-7U~tLWHUe`WWcRy?`OXl|e%XVilp6_CEDgwxcdKJ?{L(Hr)$A zfAZS0(wRhcaO@>UxMN|xCa%QMj$VmzM>DgW-RT?G+6(E%=cFHJBW?qB?@!m`faE_&$+F%J+`Pw9Lw4Fa|vEOsv+!x^@tI1!YK&5jB5ky2+k*?9_ z&!mefKQ->CQ8+=T*&r|&oE}r_9hZXRjyf8HZ9}1Rykj>t$x)$KKF5^P?4gv)WigNz zDq@sm3=1VWE}?{X2cU;|Fe>PrBgjM?5Vxe{78Ho41)kx^Oy0pJ=aN{H3qSKE)%p9e(DX!X<(cZCgN(D( z#Q}Sz#0v0LIy39NXW6BSQD$)+Je|H zB-6~zl=5Q@;NA?5>so9Z?2cqCMVG-*?#?ITKx}hVoPhNlVff>JiidYh?_#2K5P>LXSbvDeD zvwAzexa6GJR6Ewna`93+2V}v=_zw0lUPHP95p`PhiOz-fw8u$g5+*fb>K?p4N;cmv z-VvF88<#o}h?LwyLldPct7ioJAV_}jc5#mQ0KZ0b6X_hRJWYA9hTYW%)Fa@Z?5Wbe z^0Zm(rh5jP8|7%T*tzSYxFa}9Z0_2&?DGp)%4)N*lzo{kTg~FkF8ZH-@ywllSdZWv zv7lm7mHAwHCi|h`7x^7CTsX^`P0lE{v~y&463#O(Nj(1Y)OkfLco3 z1?m52zTR=wldnzloR%^+J8iq-B{g8^~ou{8Z)1O@Wb1(h`j3#9oTn_AE)e(X8NLEnK1$&6h(jnP7FmxE$=*Bm~PiyZM z#7`ULWoNXb#UC2w{b#h@x)uX7wHXDgA@4+#k2eU?`JK^h6Ssm3K8&3 z>PW(Ej4}d~2P-3Rku!OiGVGE^xROWEb`BGp`6C}{NEqiEYp*D2a{~!o?wVr3Yrb z#Oab_EIoN_J#%;MBV=v`dQafQMmDV)d=k(3!bOj#E#Et+qqlP1BSP2SyPovnh_Aqb z1JX0Z6Y(k<`b@w7kFtM(i@Ny!$8nfhE-JXI0)nEVqT(F|R9rPeK{U-pbP*LV!4}eeubXxqdzYnIy?b}l%Dhx6ir4a1S!rrgSq&{2UP4TCf6p_oUBI{S{eS-Y zU|+A-nai2WnVB=^oH@ffHeZX(a9@?qEF}(Yi9>PV6_mTT?+c7uDFJAlwa&bb5F%q%nS0Jb#CApnXCy;+NhZg&Jnm}N`RtPdO9 zJ!GtEpc=H3YKs7~+^0>bv^IMi=mE3bBo_36Iat0}--m_eTIxq}J@xx3ba(Y2{fcaG zhx)CBuV|UZ;Gr@NHOm3@Qz+mpv+Q^WaJE_E!p*)ctmie*)9sBn*X(o3T(4+N%<`x( zAwj|RI}*$@%Z+yc=bPozcK{zW%g62jHaE*zcK{cd<+wY53(aycE)o5o@;+>qUG4xb zGE2i9z&zM)Xlp^en%T_qw>yA~&GM@|fRCEx=Um(u&sxR(=Z?T-xNGl!ftEsXG@iAN zdJ;p{EbqG`c!ju$-~uI?ll|`q{+CMD<_`EO1!wi)G7kd_=l6mg{cw4oa}SZ!nrB8b zLu>{!=<(;m-?CB6@H(FN;_owrEy45t4A#L=iI-&j{R7}z_&b5Wfr$Joo(Jy{PxfO! z6a?U#GN8BzG0goq_viKgF*aw-@7_2jaOQ-FkZwZU$MO9&!Up2qi1#<}SAh3W{LRJR zIQ;EI_;dJs8-F_x{tNz&;jaeYT}Pvk)%aMAzfOoO@Ye$GRM6RYqLBaeXK&H@a?G*+ zuW9XnrRfZs-uzY-BwN-z3R*lt{PNd%DTRaKdh{feLU^3OuipW& zDT&3zuLDc%8e8%i*mpm7Pb-uJ7Q*AAgN^C{p7(s?0Xo=NQg%~#l2{^3t`bQDSRA|S zFEM=p8-uwi^V*15=mtY-*S z7lyrH<2w8y#tmdC>|D8cW+40bz)R!7d~LB*Si!juIe@GWB9`+w&QY3V7i{UUBgb|` zM|+OY6jTB%^C>yxPby{W0!+W~+(gT%;SLK}RoLxc-BwsHbGCg6u+?Lnwl9QzNxkp? zE49yXhWff(u@F9TSB$}?uf621u@J2l3n8mP2pZ#HQsKediEF6W&P49Oj}wU0iUaO} zHu1(pVZMiTV&nUY$M1nq>DUCZ{vH+)^lvjWP^6vru*of*kkvR-V0BUh*He%ge8@%j zF>DKKJxrV*!$yaln8*yIFM=upoV zT?f&M5aYGP8XH1N4o7o&1A;M_W8tuhti-EhVJs$nRcNz3PhqxTO5q}q7`eq`#?;d@8lfx7^!^@-HSP=45Wt13%jl-mJhmr(Sv2SMfN2I zZhKI4EMjd6#yc~ju}`}P-uf|}bR$9Uc+bK}r833LLxNIwpM;V@X(1%Of(TXGl3-8f zZ^(;urbIgFs!Uv2nyHy}9>X{T{?~w#s22i$SSrwMAo!5(&P1BAC`fYzz_cp_7FS~1 zM3yzsjv)JtNWbzUiI^K~^tB|?Z&|Rk;?`u1ipWbslh&!!HuA7f@Dn^%#>=`6roKU3?-D! z5ZcU#@*#yX#F!f5LmAQt%1|T=Z7@?p*&LzG8w@3sdW7nM(%;6S9>lumOg9))4b+*b z1|&8#g0cm|TQry{p=^oJmJNmy%2o(%<*Vf^g)_{U8s@8ISR*LIku1EyObKNKLL(Xs zC6uiZ+S-TmJW*_Vq%k$phcdDelu<|))nKNCvJFDpG#E-K+ak2B59MTqGTN9L?L!&e z2+DRy)~>-!2_-U4ZQo!hq3nRr4qlW6-*Tunq47V>FY&L8r}g%OH*hE9752NFy2(qi6HUKhOudk2(?-&T26LzJ`zvz)wZTv-*>4E_tzKw&-QcI9{f^+@y+wPd zJCHlmr7>0du*!NV>k<;yawIHoFqE+VfzUr13?-y)gt~o5U&j{QnO+;$VoC8~ z{cD)*(=fG)H5S(7p%7JwYGZ1(ujbVa=0jz;j(n~+7)oXM2ciEo7)oWhfzTVi<}Su! z?o6*Srq=k7)-;%qklsYXn+=8%(pw0<)nF(g#p$S2kA^g&zy`IXbIjTU|IT%MeaL@r6uAWQh6fxW zZ+yTJa&5qA%LRO=1E<@V>h|GuH;S_YI2#^sgtPJSMmV+crmg)C-LH`TWla6chxD&T zkyZj}!{d#RHa^}6sW#rcNY9duK>8J9>J=Z-D~%$>a7%4?xDnFEhZ`Z)hMR`8V4^~L z&6s*k4Py+pW!DZi8y{eVRU2Tn1a;dmJ_=ERGyR4!^@gwJHySNN4a(5) zc%w2jKHjJdw~+RhZvsmPQhej@UW_ta0E+M1rWj9oXH~ABjj2ET%Jp-j<@yDAH9Qol zTz3w|Rz9wk3Z{)kFCDysCDfVzn=$n_AJ+Oy-t7yQw&FFk!VuQR#~>lq#-JBzxI+4e zG4&50(uP+&npd*9M`t z<_`nu9B2BiImXmm`l6ZmDkrAVnf{Y8^(WOBKLOoOjgBkaEtJ~us3Qt*eAH1vFzR$z z0lgjn9kh1N^k0pszxt}*Fr!J;|6fM4ZqyOh#z!4t)kd8c={SY7+?ZPKL)tK(Nk|*# zGYM(qqmGbjqt1);P!-kmPh;wzKBNuvnS``)K9i6(KI#amHtM_`eYZk-$(VXc^(w~P zvW6K=!rC~aNmv^nc7#-4{5`ECL#R?(*C^{mXpu?Uz&zE;xzj7vw{Hq7iVf$P<8eOQyP zROTf&5QR+U5k-@+G?>Zzh#|gwj71lele!8%F>pLlzZLR|z(lFqpcQ zPRsRlA%h&{P}*{VQmOQGderJKdm$PUIxCTk+6qZ0tEBEg&E5oAp!Lm`3QDPx(*aW} zlb2EE|4A8Bn-caR24Bjnl(Hg(e(4Y@OkB5%6H8fm+%KD{jD!egBDK+LD56S3XH%^@ z`Gg3~XOYPpwb;;h(AhWbup3aQ_WE=o8rzIEOQ=%xv7H+Kn_~%w>C~K$JOe!8=q>}41+ZOQev9JU%8;F$V1Q_vEGXq%y!j5ekY<^z)aBCvY6o0~LBODi zuh9d4V$Y3p6x3Ae2u4fU7AfQm#73yi<7w_u$1G$3z=$U{=d-rqbKjteIT+?T{CGmh zAl!UbKH&{fmJdCcI--0a%>z~7RpjmGoEGd#Hzc$pYB1pfxe7;aweqsy%r|7qc2F8( zOn_3WQgn0l#bn2gi``vm7vO)#xeT7uYwR9y8ixUQjbtv}Naq+ky#Lvqkq-C?IGi`z zWj4q?DD8*-E62jIWWka-%O)a6ubN??eP}iGeSUd;EcBEXoW52@etB)2Nd$2ov-+b~ zZB;$Dm4ASs!=-x^?S)bR^?2R~F zi&$c7nQ$h{3+t<3Zk_YTgetS_N!6JK%yGax9mn5qD-tdQt6!u_xgDFG95bCCMEofR z*GT_sr`ncuK1im*zb%J3ZY&~fr1Iy`p&&Z;K>W3w#dcbIoaQ5hs8bc?YfuukN2z9ibu){b;=@;8_t z%Xa|-H$gU`j*MveFiZkuIRcb5etryYAI=ZS!)b*WoP$$mF`7ULf%(;*-;1s?3byfV zH}}|j$*)jPkYp^TFk2)}7-!3Mno=f3xlZud;v6GUrD)GW%#(;h9=(%r_-;o=jB`)z`B!_z2=D=FRgFwIvoiP8%3RO#0$m{*wIEQx zD;DLQYCyg*yrlxW=3EKlRrV=FT|CPmA2 zC~^*+KpKJb3&|byPxnAFXQ?B%%sEbH30{cdIwn{rKe&DqXN~m5EhsdtY`DDy3Zex#mGhGHuj?Mw-A2D@;-X_|D5J>i&m#4+(gfn)61;={{&dKO1} z`g?4Z^01;ZFgS2cjUyZrFJ##F>Ktij5f`h#DWtWhRT?s2HeOt^tee z9P9AdVzEMD-UrPd^28%MkQ&Z$UWID@OBw?EdZ9kfv1O=3s(nwGV`2sHmr?DWB;{km znZo>NKb#HpH_KN~(pW0Y_xS0H?Nt7tE{M@#C=tpdHhTdEQ%Kdw7+u~2b_^EV{q$~I z;CU2AjR$FjQxD!NE5NeL-|6RP{5U`~Kj3N4k3|PvH9IoG3T!jLKe%xTu>rlXxZR-K zXj^yG19dP_;c+n!EE-`xPi7MGRU`)S*n+&Pj69DSo=STO7Be~U4)eLp+K&qYj$qny zrG0LasnC`qH~Gz%Fu?F=tq~$Og2XVAQCK-(DTtVcd}bywtjq60P@%r)1`wjr`U0Ob zH`2ZYo`BNGDs}>9ZkzyaEq-6Yda!Q}i&iV)@@T~uV#rF?JtXKV4q>!{BTeU2CLUhN z2Knz2@2te!Y8OXWvdQc>7>qdBT>-yiemElDbFi*KlFSHpzAa8W*xhX6*CP5~EV96; zCR!6bEM-eL>hgGP1?1CjV~V6xFJvl^M4905-e#Jj`yeFYj5E#P%+NvqP}CG%FeoP& z7hvMWX$mg>+7_G~sdrtbo}}$e@!=%_@c73s)AgVEVMT`@i>(SWpKQD=l3-V3^ ze!}x;7>pgvlB4ax2-yq5bhhcl@lB@qIM^_eCifgipnFvE^hjG*Ez6gRs|xa7Kw6cl z?62aO%yQQkDi?SAd{cXMw;mDAQRp+sik zg0)h#S1`4gAKkW=W>~&NS4b_A7EF`qLYhPu20LPKcyA>(oSu|OS}u&Xu)L|*vMd`9 zA~im&%O5R%aI&5cSg@g{v)>i+=@Y03bqYmy+RAVa&i#gb1Z%Ia=U_2F`PlvdD#yZL z`TAS5*a6QCvN6}}F)Hy2dkkV(G0$V2kV|PG$y4H?RcxC7esOFS%Vj^U7rkAqy?+le z#>F-S;ym0?LmzR)#d`SOJ3q}jt4)e;bCyuz+na49GnY~vRyu_zSaZ$gLAKm`Q8h05 zJlqpd`M9WF&D!^>L>%;E%)A4U*{6fqMAC4VLUF{(Krf9nH^)_N!UE|12?XHB0R_7r_Z731_f7 z_&UMk`OP(oY+BF;xFyrl$hbTBB?zHMYF77UD(8D z#p*RIp?M}kyr;paNGH~?#XSjTGdMFtoZ~~1GlStji^^9Dv{{*-Z3aK(Zti;*4?hF9 zAp6if@Dhf`AU7OKUqL^8N4HpD{DoM8O`xZLU&GS21ExXFg>=lRy&{BT%0|q99Tkpx zszK``hY@I$BTf%ZJ191w$O}|hBU^Vsj}!l_0+(0BVLI9{nAmRRxPyENWW)6u82kKJ zL337HT1Rq#u zTG5$Q%2~^J@j7Iz?pB;G-&>7}`$_}S-FOup?=4)W+UTibndS6BV&-%3R0VRxZi8-_ z4x@|T@Aak!-&5y(AuJ*3>f_>r=h!`bCAzETIXFEiL{U(pB0S)1HZz&n_Pk(gg!~w< zIaL8AXaSbj$=cczjx=PF6krX)nRBbZB^=jTU<<49Qz3XWckwlDZuzh%v@5l9Zh1Oc z>d@=*iW0PTz0x~v!?4x1#Yk5qR+ETnj(8b`E+*SdT&6iq_IO%r1zegve-yRv7=$LA zF*(-Ji)^1J=D!CEsD5{gXWnD2+Xwv{D_wC4f{X#xXs^66u3{6uP`?wX?}7JNQv5#D zXCnoILkXo15V4_~pI>&?9LK#O@`abE11%E0Ki(u;U-c6`-)C*R(&b<;O?Lz)lsL?~ zDdleP+gbEacEQ_`^7rv8UrH47-)FHc7A2y(REf8;bpSePw!*xws6+7G% zgFj%=A@gb>*J{LlAFw|D{lqgLumRn+U&N*@J$gqW%;>I_@0fH8o+-g)RJYwrTme{3 zTfmOkTkuZ!0er{Iz33;}ZenetMs?gT@gK1eP_X8^ADF`?2a25x4FV!>M0VVI== z2W4$7H<<3n5yg?Z4HMKe*bTRT({O2t3m2LOjdwj7?93SBXzI+!Fu|hofpjus9Vnkp z7hkSrapFVd_Tq=EbLitBi-Ue>13ww?kBI+}#jq>4wvGRg<@<-?m?IV_y5L8awh%ja zuy_&jF-r{{2Y-d$G(TLUH0^a4fY2pH>K3Mp>}wM z@Fr}2Np$;^b?BO12l~0vseRg{PWFE8Q66!P&lMzQs?C5B#&748H+P~kCjb9m3$yZK|2%-RgId5>o6(L zCCHO}et>{Bti2DXYq#k#>}AY5^6j@rwl^3VE-aiLtTe$-+_uuB_E$qY(i1RgDb|n! zk@ka;%Ws*)`p;QRUy6%S5{(hAQah6vI;>$D1fdC6Yy;DXUJt zKxM)nP=brqPXEWzH2t7U4A{x~u|k)4U?&^GN@B%-c4Fvm&J{;?f&qIlSLk=abtd|& z>n>*O{3iOq8JL4h1550)4#xV~4})u{t}3KknV2oPDq!mUqG%VEo7|@2-Cb;8ln%!} zy%RgRV6=JLb9<_|v5Q5q{W&6HH;W(j91dV&io`S)Vy}+K>uj&?1P^!7>V*EdRN|hU zxJ38gU~Y5Zv{ZQ<$)maw4(f5Ob&S)GIB zK{o}y89^AB0gY%9C4jZ*oCn>%lV>9QlcNyVTXFe$$J_HeF#z9i@0o)gYVB&0DPQ=X zX#e#^3s{c=I)4rIkz0j{mhPJ+Y*axT)KoQ`zW6kjI;#%Cm>Dz;;zX(32#(mwb{Ar0 z&X%J!=mqH_sDHLh)&OO4sTTnE$dvA^oTmU5VwGhp0G-uYj{HM%0>JitLy~h(>5JwA zS#s_Lv40O6z!zdNy|sreWXGqAg?n+F?B(g=jlI|hjGQWt?q%2dq@tOfPd$%X>@LJl z&x0n}SAo`gzvxhtXDcZeWJG|tT*^}7PQomXyha>R&oa4BJEs7G zF?k=0O)0FzSviHl?+U$9#)Vh$rn!y9!iBqS>JTgc4)O{=&bx*tL)K z=0$k9xQ}%k_-cD1AY0(}K{LT0+DpPprPBvW)IbJ}TI_Jv2tpt)s0D5@WAa^E~L z?g+-*>3PC>gmu36p?PSc>#*%SU1lG;6e9y0+v|Y2Ld&YS;nQ|#!SY1pQyX~p_CR^e z>gPAP)HNYCSX$4U;tkR3I5V;qlf(nZ*#cHnDNY|}y+qOpum?jSn$Z}B z!(}4(1nU_dNRUqMg9%mcFSQwDL*--Jc7MhG;}7Xp@Q$gNv*rAz5F>gg7#h8kPqMc6 zDDIGYTzLW^-?18l_3?umm4chjP}6iimbBRyzzE1if1wtNoA$Z`BkTO6sY^x4Nfr_M zI3m(E3K5_DOZ;+@^-_L(yi6&J*cSaQi}N2%c)!naObd1!JP+#}Il*u@OUE|=#dMy7 zOJm>)YB~Zk5-$}FwV)9|V6UTh!Tax+fzK8G31?`x9q6R7Hpr>=XwL8LKPwg57ucg6gda9U zG#<)pm2e$=o%4%tj@K2{vN9k1VpDr{fxoSp<#~L_=FKpId|TeY7CC?mmF*1appk|$tGAumpo7WRc zmgfg6F`}6N4ko{!=7=BPVbNXcvF^k=j6>oaym-P-fiRDd&}0ne%MS0dvs-9bR{5v`6(Izx{;5sh?>)=QI|i&upd~juxsUL4NaF352*u+ITCj``~a0#Bofg0 z;MIk9`Pu&KZjmLsqs@@jt;@(NC*cfUuh1)4lv1xt7L6U)`K^y^G-XX*2WMcm^Oifr zB=;hLs{r20F%j6{y!#_91DY(dokN$R$Y3}EFd#+P864S-q&_|c--%QbYOw29qAh)~xFvNFqG;$`v(?MNw2iv=(JD-k2J&A-5*}E&flUFTO z!aG{FYnjg70t+RMyr~JI$Hd?qa;9?4=B7oJwuAfcSF}b>NoYF>YIFJ-1B&v+S7%rh z9KLC~tMiz-!9k0VldRvIauaqEmY_X29!eb}NhD?IM4wb&IRKxYB-LBEBoMEZiF|}| z$SEyPL0Zg=hiH2F_7QDn)aIj8I3ll2jyS>fxxdKV$s+nlM4`#ac0rq;+!TrYU%gEk z6y27=iVI={_}_5xKWY9=X~aKgu^GbL9es{vG`oOtf{htu6urem=ise+Uo9Y1tUkv& zMo~LD^K0r$ot($Xy`DIHjwN*u)T2)vLGtk(XujNP+@syZ;qMx$gRc~VG&AG-Zk?R6 zLv;HQ+bAR(`6Fvb&pAJ`$sw9#!B(yG_eXwYqsJAKR0i67=)atSzF9QkYp35gUnD57 z!$5Pb0|IyT2%ZiSP}9W}*=C07Lv4Y={DQev%s$WhLtwl1JnI-nxz?|Us>adtY)HX6 z3`m-3EVM7$F&|q|Nv8>*MpN@MwZ7+|E>=y?WClPAHhD&iL25Y4*{sY75~|3c0RAl% zNZtnxkXhcu9?&dH!1P zX)a@n;7OW=S2*O-K<5>RW+R6RkjE6@6$K!$+z9{}7)R18%SCw^>o9Ky_SDJ4@|r?S zyAokJj$u%SgXAso)mTopW0ICHV7>u;%5ZRf-eY5Hz}Hes)J3|YMN;LZ<$D#nyLiT zr+|Z^lE)#Yr5HaZ%Twd<$d;2Hq;(RzI9q@-J;nWQPRj?Ol*x}WAe|5SAJm~yR8PK zmW_Gg9MKgb$7LdNpW4Vt=fs6ySj^o-5O6jlr(*{8u^bXcJANu|Z8nL%Y%(aDqnMpF zoF~tUv{_luef>`C_L@1pQmtX&iXvdmSMcd0#irKr!TOeA)NK!aF52p%{=N;x{&-ARjvjXZn~N zV*t$)d0|6o&d3Y3FM(tr$RwYA0Am4KT}S#Dr3N-hP7s*Tq|DSz9Xgs#@*8l{N=nv> zHc1pBBf$2V<;z$freP-V84kb@!j~@6BwuL?&mwRW1P?(F(AXBbm#I)IZp}S~9I;S7 zXqkngV*{;6pJAU*8~t#T7sVFD$(DD6B6Hj$vc169R6?8H7il#`a-X(P?Xu;=H;Ijr zp-7_!P+ma@R?#?GzsY97`!3mGj1N^7Gg)(>s8=xCsTu}B%Yu?Ft}SU_t)#PkC7lC; z(Rh`*k~Tvevt0Wtm2?PCD@`f7KB>NiE}HEQ3-33*9gS%I(EAD%(sAkv`* zhG~c@p&5~~WbruRkahwr)uZiP~3$<0WjwQr%f0PaG|pI^Wp#2>?P zqn8B897OyoF%o<<5emBxM6ibvfYvLp1S@IWmCr_o*ll5$fcdY0^M*S&TyKLR`Q})i ze@C@PfRo27Nb6-b#u_~RWEG{>Rwrbafg`e_GMcyTLg{l$x4)r1-qRlI)k7@=^Fj-+ zaS`8+o6-m>H(tV2hP@;ukS}^aC===Meo!8ADL!E4vkDD^t1L>by(|{0(WKAB{&qZ* zJ$h3_w&$JFOMk?gtjT!q-4CVRDMapnc;(n%AT_ejKH>*Er)E$b^n|iF=U-!gvCls1 zcOdO&_*;ZFFYOdU!xYz1*q#q+8cmT9`RE^FZ+o7cRD`84g!6f!E7i*(N7_E)p1dyh z8Nq(`k`Q;LTJY~+CE`2q_J&K?NP89rXFJE9RoteS)`54>XM=UlDK*yuS0QS_3yA6* zd&q&q`X%!H9kf{^-ZSBFZuv&c!NlEB#oxKFepkK^;iuM>IOb0qJ4hY*(?nT1!-@NBq{*g#NJ>huEbu)43;{{=Mld)-Bdv=ZG`U66oJ^H@(7;MHZ-bMQ@Q zu&*bE9-aQpvAW%!V%Mh0f+pipe|3|Kj}7)e(25GO1ANZ>?7dk^h0mfa7h=gs0u0I7698@=&A#2nEVYr zsDjEU{weywo4EA{>$G4qMnoefwhM`AYtY2@P7B-4+9nheSe=Efz82Q7g>C!RiX743 z%F@;k{aI&eJD{MxW$nrTfL8u5mbDs#sOU+P=UA$#x>xCk-mK9w8{@}kX4?Xk1Ea@( zd~yPJ!1gZ8Pf3^}uxT4T&PPDC`HC$SEP=s^M|lP77&C(gf+hg^?p`e}j8tflLDEiK zYL7qR7X-0QcosOp(0gOg_r``de~MjSEozCR!W!sD83uFN32U~m#F}AnZFymJJE*P< z5#C(Ao6OsCjk(zjIjX#2G%RlY>1BwOWcHy zU#z{iIAK&A&Bj?G6+{>qh2EO9^ro#>liD_3OC?%EKgw{rr94Db<24Hpha}c_Z%8c- z0EvdDmnvPPl1Fa7N2@gU^tFw3ZzXG+s5MqATPBe!K*|(G@a&^5O1ZLdDAt}%<{YIVs{0+7-1yDU1A;E&#FTwMr#H2jvKXGn|q11 zkJ8X7vxO}{q*)hCljBBsWljHcA0f6p!uzFgK>%vgK=`5 zZz)^|9rP*yWDPhxtN)HQ^j?W~OC>ml9naI}PxypWD!d*7Lj)1pc8XQHodQd@Q|i(U z6zb(>#umUGi!EFhepAa!G{aWh8Zim{I;C{@lP49`MUWqi%a=85I3$a z?#H!psT97rqIV=uVXrI`4@L63*aP2*&5=AJ>@qfbR=eQuObF^W!GSmh{*-3BsaUy zWXsrZ#NTaspE+02A4K1p`fb|?H6mzReTew8hbKUqI{|eA>fY8s`N#n;hvox@$V{D=3LLdJWpA)}M+d)vOnLG(`-ohB|L5T5oPOOK64{!q8lP zdsMtxjSEd7gbjgX8f_cenzZ^;v7IkoPFrXgiI0a$u1-_ri%hbmygMVe8eKm!mV96e zmwh4i@UqySevE!N1il;=;usr~3Ias$=dlsN3r>%iejz# zeBh-|iKM z0{KWb>3-27hz}?rbCDIn&OlE*^hv;3GR7oJval=yB6p9+3zTYDQ2czx6645N0tzQv zx@X}SB{7g|5Pt)eBqwK@bLC17=q$Mq9@y25Eq;ByIJY|qF*!2 zmJ)elEjP=+BVt-JzKM0ZB=jLXv59jLEQJDOx>bw`;kJU%zi@F8RMR)9wV10o7ITfs zqBHo#z`g9B6b6rR3W^Gbc)}||#18eX-&W$_uIc_+W{Vw(J)e;Ag#2lyui2iVJ0&7L zpF|V*3m;ftycirhbTkt^pBQd=bRM~RrWg^*d$6LJVqqvB!9F-FwubUy?QhNS=A)IdTl2N?z(^u#8QSR-k^EY#7I4#(N3l6SDRuAQo!g1@rNFPk=#B#?;KSE z;X{ryJr?b3z@5T_#@TR-Zmj(8{ud3r9sAdQ(ZRr@q8@5E)};L+)4^ zu`U+d30Ztb*js^4w@eHlX8|(F;5-o+!PAhFyI|y_)EL<*nRY@{`S=3OdvCJFEk;U>$%zJSNup9@gX_P0TU z&jna0u6l0!q7$FRm5acBUHP}e9{7+(W+!wTCbg;RqoexJ)>63GtB4_oLdjwKx9fD^ zDO2p#?Ur702RTFbf36cFy76@XKejo$ap~W+5LnLS*1Sxo6AUnwIn$dU=W?&UjE|~4 zPzIdoy(&<=)SXX{dGv$Y8G*fkF>uIJ-VdV$$4y*grS!ZcB75+ATWDOAz8KOrSSUbH zA|CC*yZHx**Lv`HACJ{iiuc72J@^nGAXJ*(7ae=@$$kI57Q|VaoE!Nlccw)S1*H`% zZZCg)k2V7CNjPMp7C!G;t?<$4cUAb4@?jy~>ct)E`o;ded9r$0(3^MWRDtJv^8pHSv^QVC65kO+ z`|!~|#d*_YTcs_)wZ!8F(2J$Y?#MTN2Y(N;2)5~#|&v|23a%X^sfz+0ATPUeeC zHZH*zxt+9kp*PYXDgv-82dzGmr6UobSSjy*9>{P3izk~7Nx-HMdsv#wdwnG?_T~N9 zvF@T{Jdb3bzAcRL*ywzELd=Ng?V4e8fs>wQnGx*~E8}@g#1rk1kr$Wy}KExb=9zyP|zR-l^^2-o6E@7)Nbk(^FnqOBPf5@p$&c2I1<*le@3NN(7@# z_oMb|W*b6948t9X;1nIjc_0Cd+{spEl2IGPU;V%)G(Rpv`eV2D$1xG#pLghYvaMDo zZRz`VRoz?^U2m%Ow#5Z0o)Lxpc`wEKZ|ctzdDCtlG4lYneN}G?Pk-LAVCYoEv)~XI zR%p7c6lN5jaE-R`p2Cz_&rMI(LpiiXLTu~LcG68IohydM&<$t>$4^oi)3BX5ZAiQaNgO61Y(onvBg zA})+ra7=uZ$Ol9RbO8<=q05_fFi71$8fE+nhhO)YT9qZ|(~I+fYj#C#MK(oh710iPx=XU#7~5-M5}EiqeQyoC%Sk>BE`+=rCunXNr}Y^2T|VUE+t5DSwE=~sgEGPZ;;k5#J^Ap6o=R;DhaV*vv8FjbZ$OY^u2P^74y?XyrNj z|7&9qj-2CY`LE{a7k+UOEsIv1^cDV20(lp-(yY5s=E}frXr)AdmJuY*rSP7u2Pq25 z2}r@Sd^Q*%Dr!oQ=x{gpkm+emD(}mzK_W91!Xtb?m&%jq`9&&k+i?aG9VlK1;^w7I z58F18?J2=ZVRHJ@ncmBSMBs3K4|_I9WDVy}v5P_C%y2%No~a}F$Lz--acu-PF(pA_ zUK(%P@+6Wr{WA{+woP%_&AEWzg2dW1K7sWeDK4g=f&GHT*mUkG=oPFpoIZT|=DE{^ z=WM%Ze3Nl2-Ok{*=-}DVApp2{p9_fnpNL_;h#*_P#jl9D#@3qJSL`LZ1GEXH2dZ>!dC3v^9kFuFfBTr3=oo#6Dx#J15qPMpu= z0is81-X_dJ@|64Gi8=ZHm?mB?92^weku3R zT8W)j4Q6Vb<2`_;aN25e+|8WBb&h!FHT!U#E?aH_n64q6GM(y13p@VF#~!A|FO0Tvnj^q8=tPs)9 z#5?wQ9&0fQIR&N+ir{j~vmwrhFlQz4@-3R9W=fb7S*VPJ^?d=U6`v#(ivO5+ySO6o zH!>79P|m;|q~^?)QxG>wILvCb2| z5IpCC@%%y7x=frKk2D>B5x2(kuKw3Wmn{A+`~4sBbrzv z+)AQKq6;Dmby;qJeCRW3Xs~@5*w97ZV+&Wucwzs8?Oqel!VH8;Lr^)YOdt?oemM287 ziM&%&&HM>sk`-bSz6)kNfwGmt0%8PmD=i$lhML-Tw|N&yO)V4~Cq-9db8#?$I(6k7 zh;sv|_X}sE2zP_KG@GEfxhY$KbTO6=1W*qC&|j3o{1dP8f%UWj)}!Dsvh((CjE@Ht z*C%33ubC@)nt5WcV00(Rb>2bey3(WxqW~?hKt%wllP#|FNVu7>BnzjRKgL!q7lGNl zQ_I7H)CiiFb^*Sb%3Ou(=ZeAEP!g=1D`sY6v3q{5crTlG)%{v^%Ol!+34aNO>_UEM3wcBj7EDg*uQG+8WdP<2I*59Bpn|~urHMRVNEx_85PeZA zxre^g)?lob9fy{L1Yo$t47J+09!-JG7rnlL*UjQmHXp$wF@^_C;xQKD>9yG<(Iht@ zFD2f~izcQJQ&V=98BgB^C(wC5!Q*BL5Z8Z;QJ&*w&hyx?1i_3Z8VG8Qn0-Tu$QiWI zO71HVC6mCl9-1zGn8dqAkPWZxiR9dnyy)b1WWk#kCck(_MC9;JV}5!J60eQQ%C{4c zKn9xJj=uCoOEC7d=(f)4;L=tVRapREd(s<~zVt<75fxV7q9_Lp#+E0=$2s6rRy--n zaX_z>DdFw?{FqwBri&2z8nt*&D_!8?-4-kY| z{ujNNgMHpEfVH&=f!Xp|0+VMNwa94ez_eMSd@`06&!ZxG3h!0$)uZS!KZnjJTOl?H z?}r%u@_Km&(ybSXfWD}p5Yx3I%j+Q9)+v~fen;QX6t8A@`RDz05~cU?engqLlMyu< z7B=D#bb4}BBWjcneU4KZyF0J7u;1mE*8{SqEb_Mk2SczUf*$k`#rP~LE;8&D9%cF$u z9^N@*4%iWGv|gSe67JzMTU`gy;q=Zk-ZcygDvvmst4a3TO#^wInjx&yc*hYfK^mT| zB$bz2V1@?!wWkX4frUKMJ_rX?(R^rFZ;UZG@SBt4S-e!9sRN!^DvnR%F?~Lu4!Hd| z+{+3?r`G6joJ*xyMW~)!D%wrwouifbuAMUo=)wq{-Xup;tOPlHshBz)R|&UK?5w!Fb~)^3nLlD!dIhc1>$ZL*OHt!E=0~ zcBQg~C!Fg@rklZMk)p#ogAYu8o0g1cp(G>|IXRYO57o|4r9NA&mW?3iqhruV!*SSj z=>lX)S{hH%`~vaA4Bk0>OD+=NfRaxBivnrISWqKc&g8?`+WW-#nc(`wB%o5v@6#Q6X4oPw3Lps>j(&;j?)OK>c(sm z#qO0neC$5xftpp)3|A6nvPSrV->(BlnyjsT;1>b5?=yK5z^hiJ$qK(MT9%LP7abiu zJnFu>w6P{@rZ4!O{bC$~qZ4n-qr1r(rD9;EY_ne!BF1Q5mqAaHb)ql00$Nuu=3XYN z*$4g>;JPyPHd(WMA)oCN=aFB*8+8f$nyfG$M0tVERRetYInv z#Hmy35~TRr3gEOl@CaX90o=z29&aznFj-B$Tw33j{}^8jA;ry7s!3S019{z@q@+oy zAyaG@#*^yJBK30BB%gRl?Ee=eEm%Z@ocyt-yD;f{7A=)q7l?m3dHi^yJ{o3frJ#Fz z0?v9NR$3nDTAhG0FJzJii8lrWducdbgCrUQLcEZ~1tMe>?;ozH+6=A{A_A+)?66&x z8)k{DRWN?GVt#`mJEYn0y!iQpz(O}T!CbONet<7r@SRegj+BmJ$)C|0sV|y=05O9c0 zw4g1w1-W;LO{;ky_Sr6Rel^kdF3Q67)EG3U$r|U&!Q};$M8jn5?gK9dm|6%4)>F3D z4k`q#JJp-u4F!(zfz!O;w+U>4FHk>UNKX|a7Oml7V_Vc^OWjJ@(qO891=CKgc#{-Z z%N7+Z^Ma=mxGr0S9M~yNuHpAb24N-hY^Bo38ccMatyBVe$s)!*!^h{YQy|JzKC50S zBe_Do&Y;(&c(w0~^D$n^Pq2MAH6=`ub`iJc9&4F}n3#HX(}{K!!Q#MEszuy-hDUYj zh8%J*MZ3FL{SGR=4zc~Dfxw<^uh4sTg&6WI?;k=N4ULa^Y_xd%S#Vt&?-!pw%SVPx zt_}HZln8x}!!N*nqUbsP2`So)&x1U2oFeCWKDx#H?={X|lS)09;SpP&=ktS}y_Xwg zo0X#53*6dtX(sle<`DVT3i0v_+|;60CZeh*H|C3)7qF-Jb*1R|BD6j~o)E)c#GdMf zmEz$Sc@$f7LacfbI=*Lnit-ov<1rgnkZ!})%rR{eEk#MFpR1VIUx-yq@}=Y3?1K06 z??2@mRWk4x+E#7y2p`u4%Mmn#Pg;;xqZMPvKq6ZL*McNq`9}qe*_GqAEr)6wy&ZSQeAaN%L^x<&Ef4#@g)=!7ef216s4J(27jSq>%X~?bw47m{2Q$A+$Cbr%h<$KcNOzr zhW<%*78_pX3pgYuqU9_6zBy~K@dm4Dv8K3QD@2oJ;RYEr_KFxZxk)~vg^spP&ys;g z+Tx(l&8>a1Wc6U}D=*xdl4T9@CAeEl5M^y@HMmxggwJG_^Sg*2Ug2$fbWdrldq{Bx zTf-ILS)He3;bPHY9q-r1qGUL>fQ~^a?gU>iEt()dIxMW~c;B$a$FyGZn)iYmzibol zuj5@q-k%6^Or52UcZl=rup4^ICa$mJGxUeEiBP9{4}R!HuNQb!E9U5+Y-v;vV6QEk zso0&w)>nCCNYv!IB;toxd4@XatJx7~<(>!4Cdxf4PZ(e0{W!`k9(oOAzUH7<1%R^P zMk8;ktphf52gTQ~L6rMMrx(}re3hhdJ=B)=1LCFid_-+I5&f4x5{;#9^&m0y5UgvOduE;o%X+#_^A~y1{ zY^}Mmz=M*LGUXRoe{$TtZl`1%*U=2py&IlJ=@^W9LdhKC%9uE@d?VCbw;mE3HbM(e zZF6uVB%;6V6$9SjL;BPbI|d8gKd4$1-aTbY@D%nElPkXx&ZQGd-hKC7=~*DwzX26v zNSw})onf+os=s=JXM|kss)MSs0ua!5mpA!%E`jFpH(`GE!~yZ*n|#P*O+=WtiM&Ez z)r`c^BkP^yMl_E>6rWa+ZJnqMRK$=UL2v+p$ut06b~P9X`U;5?QnD=m7$Yhls!p%B zc=RBx@$^MWm`W7!JBj!+&GMJFN>@PBSX+c?o72p!Y-_wVMdpnbZ$F47bK+y-)PoRp z&CC)(3wZYeWo&sc9w&h>n`QSbbc8yFi(O;}ZM8l+dCLQ56n-%&g0OjFLY{{HDsEj zCyqRMPdvdLnVHds%^g*(ivKBWN@YYQ!F9;f8^(k&F;8CNz-I`IBB-rcWjaC$GVK-`0AXR%eI;PnCG{0Ct0-Utx4ki9y0@O@7C-nvNcrCPMR zH}T=gM<|EW=zhRX()&^Ey@KBNXzy>(`zPxCEEX}sg4}m*;+@rrbt=5AnOuMa6Ph8@ zxwNb2U;(9hifD#RoWF_=9l7$O&|@=Hj5*JXWt;h^v>Pv=1@}cdmFx?g3kHi!c^R-_ z!8tjQCyzevfq&z?W@h;y-UtnRmk20whVrdR9x>)aG;72&;^`0hK(?s0 zIQSu%e^TctQDu{lpa#a8H~1syV~ci)H6QV*#OPf72h}2PE5qA0b&1{9VDdL8bhuuSay<#_x)GyaY)P z!{_t!dz^c`k`&Dooh=YjrWT@j8}H7|i2mU=-oDT3;k8KHAmd$_dg=3OwCAI9U9(lSf8*9a}{sr7N^#hR*OQ=3l?^sLdi~ z7mv-Z^(^J?BrV9x-S)q@HpS1j-@Y_9*w40$EC8%f<8LQgul0nr88PeSCU<<`@l1yM z8Rd;jU(_l>yu6F|?fEkr$nAGUzYN#x!o(xI4CwJZguiN7q?~{i$1>QhObGCsDgNFC zN$1cjB4ao2TW|sqVd;Q~-;IgYokMciUl?XWls!kkw_PPd1Cgv9MMIRS-YAN??G;1n zrXDPzxV{-v>h-`Z%&BIl&P5Y3DX1LzCmmytI7dXneD{PiEsP(;*4b|6+gTJD303@PUU?MO)S7V+5}9-s9^D=?6AjHR|Xd%BL< zZ#1=qTSKR0$&Y++19I@B2ip%aV}8veU)E)Yg+tSbYQvit2D2P;408Nob9rQeD#6nD zIKTW3vi%ec=aez_?X*RM%S`VYQn)m}yPr+hzr@`}vFX~{W>gt!s1Ms;(tk~fNP=kocgX+~Ih*HL%W|Iv>WL#K^jE#qUQ z|A&ml#}>X+<=g)O-gTtPH;CF5XYa|=o#Vs$`9@NK%l7_1@&$1~JStv$fUi(p+uh20 zs_e6^yw9LJiam9+mOWSy3$Y*(CHS@l+i%^nMam_EG4x~nqRgaTLVPTe}{A8mnsz$~A4V?MVZho^XTNrE>R9@huB+%DZ=3eMB@ytBlEqswT!7`OVP4`V=#L;<> zjG!Sd&g0SH9lcR{)kWFXL3EhUBiK2A@&B>+F3?dG+5hiJx*G!qX&_*LpbZ3w^043U zN05M#hen7P6g5D=fDtkf5RtKiAR>=33eu>kK~PZ{jfxNuHN2yuMrAZ3sBuK>44E-1 zDr$uL*{4s3hTr^tzxm&F?^<`QI}JPEdhESxSJkdMb?Q`~c9n3<3D$>8bko~23h)yS zu&XCyjkmFCGw?83v*a5d_}*teZp(}AAT<8?rP@&ZDJMBAyR2=wue*91*dzJ@*$0#i znkxDVL0XYz#c|z$VbRw2u}$K=wr3u^U863L$3^!v!u|f~@xB9dI!2d^mdFdQaR|vB zv#UdF=<&`2SLE<(9I(Xh)350+zD9PH7VV9HH%F9l8R`<{VIp>@jk2t#+KR`oSw1}V zI*N!rg7A!s_TJA^dwwhAUBT!|wn6fuCsHG!$+~oJg}A`f42ApDUwMC;`r1z3y<#k0 zD&Eqb11glnV-B^j@-u~<-!t;SWL@VAszvBHFA(P;(^b9(3Zh*mD_v86j$A zGmd8se9vF>ttKaSgh4%Wa>F+a{-h#ZkIT+DSFh`$ZKBAqD)q;jq-S)StdE=HlT!3G zM`UD?uIHFSw8?V{dF_t8#6gF;)<+fU4T-pU`)C>B+A{~X5a$ik7P0{rvLDN=`)ixn zH?3&u9Ywlu!|0b|Yckum+Ss7MDbnuw>8>=M zv(a_#Nb(+zI6pK&{YjbFsERE+#?8s)D<%pD!QQg88kV1aI+C|X*Dd|YGiig;<-X*2 zcYaRj(zcO>dpM`t!U@vqJ-Uo5U1!*KU&sadDo)_H zlOQ=VU_W=#1F9m0`*nQ36oEVU>n<4?6DaG+Z?CXXCO_uEZ&Jwj3{KBE6y1?e1DxdT z`Zpfsy){psVn&eg*y#Ndmea+xt%q_y+z|P8zpjh!&)rT(&O4xUM(S#Hp57)_1G1(t z$FOHb4Etus(2+MWGT<#;MmzIVm9N?~DRRwQx_qCGnrxhpw-HRmzA2=n(HZmJ{PzJ~A)x>PyNa;Q^#z7TcrZQa$$ z>7Pe>zQgw}j^7!%{T)_Z-y1ec^65JM7h^$oarSgJak?nz-cVjF zQ&D>XdD?rvyxRYCEN-h=^vh}auEcsZm*?QTLHZPrlvJ}K6$f-(x=0Q`;l`}59)cef zyF~9epv&lyn8h-ZV^)2aQle^9m&3Z(B2}O1_C&_k>&AAA((T3N<@|iYdhAp37B!`? zHOOjW+ne>eyOSXvS+ zJFM%SbW!siY!8&V)O~}LMMvcT?)M!A>NDTsj-vUFi19OBWeSvkq}h&aCB zfCO)u-SUNQNYVu+$>Ox!)a7GS7YvU$zvK-${tm=DeEZha4s#gbb9PVda2)((7oP5z z&SA%?zTN!r;I8Bj+mfb|*39mlry~uS+h^DDOi3iaJ2_$dM`g@qZKC5%5@Shhj0}M*W(&j_zXh} z3|(3o?ux5fIl8tn<&hRH8dZ%7*s5?X{9ivvRYr*F!-T4rit3!WWXy_{qw5=MA5M@v z1oxkXdneqw#;*G!Fa1rI5xz%~qN3oR+%TxJjlM{NoRm^1OlcmN-h!#|`8DU{XG?yz zd{S$cBMDA z-HgE!G&SDE4~Z=w9c^@y-P)!{vET;Laa45d{{;D6 z;`!mmyZ$A^j}<7$_Dc2Q`TT@cL+O5??ygwI#R!f^YM*g01}0)L-8p*L#>ybyOsCc8C9Ut4piC zDs1Wix+q>it3vV5ha_KZ)Kl!?sqG}=CzlEd)_nB4if%1lMZ8tM0{Lsjps+fiMvAI| zek|G&R~2jbl`Hdb)y7x1qiZF1Zl@bBO&A8tGGcz5Dw@`OK} zDzRy$BEd@u#?4sP{Cij(2nYWp3ZzoI303bD1p`zC%kLS56u;}xVKFD-ltoWT^!Mc} zGep-D58>^YDB+KuK(EG}l*oKaW1gilk3P~k?JcQ{B9(dk39~d-UE?@1TdivNT{X(x zYSdscs#%PR)I|5@UwSK_s-P^?0hDP(&gmT3DuN8rVBJf6BKs+%;% z{~+G+VQm8Ms~Q$W;7?}+jx^Szn6KM5=CumIFEi+mE5stHq3Cjf=DkvB@D$O7%qr2$aGhKS3=FC1^*~~TZHe}8}X)KX>$DU9A+(8 zC2bjRZyAw(;cgJ_rxLiO0avc%w*PeF6kd!QmsrOAj?BU5Arv#}_vMJEW+OiJ9PxuP zqi=duTIWz>ssCez@ zjg8O1Aoym}@BAtfw|3Fh7{eX(z~#f%=0yvbRr&c*SSuS#8qZihVg+Sh6QgQDe8Rf{HWNE8pRiHo;?^&IiP-G_L}<%o||#l_jzz7lhK<9i9r;`FZy zbB-`y6t`HqKiZaR$8FjS-%8=zB78rwM}c?tZPIn-2C~OVFw*R%rHBGv% zDSeqMrFqp#7jOMqtMrYD1&Cy_NTv=&@>aEg*3xCN_ldgL>Uf7Y8gxW}93x)rr@c*4((8ckq8<+Nj=bj>lOWyK(*kNEt>8S@=RMX={ zEh~r(rlCm329rnh49r2#lcFd7Svh)bTWQz!-Ux7eRObhtCpe$9b<1MJ-MNTQIZIp&&}!T#E+6+3sqtQs zs}#9Gn%r4k^weU;U$t^2`4`0X%1(+xn>6P|KUf(b56zADyqREXO&9rSr?a#yMay@0$KCH6 zLe-|xNaw|+4<3v*FwW1ED}M5(@NJe-vd-{{m66RSbSdGJ4^i5tv!tY9?-i*9qP>}k zK&xD|{473MtKBSohlH>844=k+Fq(H3zqG<-!oNxQ*Q)&CIIoIP(^&#i!0CkuRPr0M zjm7`9fZ}X^Pc8Z*I@#?XwsTPfYx~$@YUinyqiY%uF6FWreTQosPn*=gZ_3|Rhp)DU zYom`-LTpN0EoGe)<9>f7R%uz0Wo@8^mWrHgAk0T)biS&qO#M6KERD68Ji1lkP*Ip8 z#&1SpYq4@E&62DYSF?-P_<&ZJG(qEG?s^m2r16Hc)1Mrc3swTdi_yCi%uG6S(dk##O#2=nWtIZf7*5NoT1BywD@+-_7A8HFZxA4%dzN} zKDV2f*IpC7gZY&o&N*OcKteXgRDA~wqKjA5DfCDUDArp~bHJr*x4*LUaxB^=X8{T7i=K?-ck8<5?}xD0-zp zkvw1!U9kcst~5HOz`y=O3XIZc(SA~33n8sMNtE)!|JCi!_QLz(1)h|ymjXrffI)P{ z3Y56gxSz4uIE@0gM`tr`hu%u)NEsnd386z0`k}Pt^!B8=gevBdrbsB<1IC0gEkWMf z${FFcM|IlhJz!XURy^i(Tc14RblSm%Gd{N{;Ai%ecWjEuv-&T0WD(yke~RT#bRL^A z>*2A)60UVl+Y7P$5(fRYFxz4O9m;Lq-ZPLt==0A{oiXBbmuY>)~YM$uEWm${4K^HX%*zJ`XD)DP*JAAXzEHSkiPGka5`b* zMX~gZep)#4>?vK3f}%_Cwr+~?z8jNOGWRkVXLU(7nn}xshN6GMz%!~AlmEx`WVML8 zgs`dinRKyfTl#6v28K6nWAG5I1*d@Z;2*$dkoQ+8`f8m~PHJX=eECYk+ zc}2+wO<*B79V`KF0n0(&qNh}XCEylNb{}iO+rT3r4H#Bh2wXx!+IF2W1C)fbWL6;NY0x;z@ zosn04C>bD!z7#je16U;&)KR`tM1XTlN*P!VE(ZSyt_JS{H-fTW+X5~GYr$z?Jy;4h zft;IF^gDINyTJ@_1!xEF0Y`xxJ5dV2C143Chn5zAVfkIk)dVuZ%^*KHqZ|Z#gH6H! zDz9_64M+!~(|4iy;8JigxCvYeR)Z6Zie^ojJ=-*O;%(DP z?l5ipmKU|O9o&4W!PwlLk*7Iz9atq{(o9e#Q~}jNHQgicwbyUn_V@+*Wl5@eQhDQt zu~tn7c_6QtKyiaCP`cd>N-t!C;@7#LxLY15{#*b`FBgK+2F0K>dMPN4HxCs5F9+p< zuoP7Nw}OE9>snA8yb_dd-ULdoR)OMg)u8xe4JeIO3yQzif!)D+Q2f0C6!(dO;Ds6m){~Ko?jJy1}KON7}!FKoDFDhQLZN8{7mA0jofnrK-Ukum&6s)`C}n zb>Nj?Jvahv07rsR@G7twyc#?S=7Gu~UabqHfZ^*17ztbtrh`*JlVpHv`7wg0Bi#mf#-pxU<$YZYzr<0&j(k7sbD489^8CL7dBo%pqhjZ;9gL? zt`6)79sxUnQLr<30!#yyzu*&KD%cfF2h+h!up4LwyMunP2bc?Hfcaogun@cmEcr_q zrywwo1QWOz>;+bUy}@;0A8-?x32p)Vf;C`2@F3V9tOqXvkAjzi&ENpA1sn*be2h6UI}J^Bfuhi@-5pDL4*X z0A2?!1+NF!f>Xdv;H_XaSOV693-ye@Lj>d(nHoSn*aV&jo&?*1`g$f7&SJT5y+K{fmkFNU?JEJERl3G!&B11#gYzINV=WjDe2%QNe8z` zx|@TX)GuJ1g5AMfP-ex; zmDqfajY=MAvf?vxfdP~W#OuLg@Kdl1dgeVJxA68H;P1%3$b1fKO*l$OT8PbSKDI4(6foJW&66?21-EJcYQC_y#Zod>70D zH-mQYEifB=5F7>W01Lo-{RHz-u!7$?} za4GqN!Eg-;vk8>Ka6WjD_-e2odtykgMtmtKixC%S zA}-E7551}2K;p6(sUTlgMQ-9{a{p2f19Om)L&A$-9w>{Ea?;y_6Nt;|r2xS!u$cIC z&`A7Zu#EUba4Gy3faSy)xk?4`4&Vyn4}ojJYXnJ`ak`QC7+L?7qi7HbRV2&;D~Vgc zoy6yXvW)VAwZ!k1d=zv94-p>=%IYZuHW0rL+yuWBY$h)EJK4nhf-S^v0o}wi!IUp` z#;MXoX$1ZRmXa|5G!efI90)D~6$%IdZsJS8X83$y4)Mj{TH9C2+(>*SSc_t(q!YgjtR_DV+(~>KSVO!ESWEmm z@DR8bYyek)&EOND@;9Av6Hp6(SI|hj0Id6v@qZzK3=+nJvO@KPS;X%F?cg(DHaHg? z1+D`dP}B`9Abtawgz9~ zK$+*i0*?^i4QB7w8D9qrz^B1d@L{kSxgOwB;*WrJc;5A@DTBvBp;jsD)99Lnuy;F))04qCy6fr(}|lv{T`ih z5|~2#BG5>@5UeI{2Q!F24&GoVa3_I0GWvs~h))9xz?onXr~}Kuzk!Rv3a|oP53U1W z1~-Avfm^`gV9g%gw#gUi_ZcJeF3@L2a<1l2)n)q3o~7)gym4u=v6fBC^xI>5g4OZ8 zmB@FO>3fK{Ql)JPjT(GrYbJwj8rjBKJRpV=E?vmJ6Yu{6o(hJUk`R!sgu!aqYDnv;wNk;}U; z$+!_+UuZlP@b4wO5$X+3-DT_szmjA$Lt;wUM1Y@#H*SP6F(aOy(ucP-4v%e3NqzFJ zV54p{TVGct8#iisrm@M!!mH2DJE7&7$unO=euCV!Gj5$R^Y$60DYIssH**$^x$(xx z1!jHk)U2-=A5bL}sklsUicB!;x5yl%tp0{%XcM#rs(}td^-vT#0kuH-DAOmD0X1E0 zFlK>f$PM|SY$ylHg+@XASg$c3Dufni7>q?=F|=+M7b>tc${bKeU>>vpDu)(BOQ97| z1+*Gk3$24Hp^eZcXfsp=ZGozxolp(57pjF0LUqs~r~zt%PC!Z{*BmGlayK#u6cCsP zt%a(gdZ-0T|2wsUCP0g!N~j9j0_}tjLe0=gNPnE!5i&!0&^)LTs)bHK#wLayQ~)i8 zs-T0=k?#DFrfdfFd||-^*`fS@=(fFL)qiH#X7=by+jc0ODYLqK8bbVuyg5q$SVZs9 z4-DV3FSb|uxP~>KCL3FzM~Mp+KAmi=hWu@~h(n^|FzMI6muy@J{q@IW<7Gc38>bFt z#Yp<90WluolepkfP|XAH(8A-U&7L!{#1y%Al-`q9bCL@*j-~uUqeA6S12pj0n9~)5 zo1mkR={I^Fs)dX#cn2g-Sq7FvwUFwd+j6hcXB#3X#_LN)h!ypxVwTB}iV~LhA4*s` z9SeWEfp%SPCp?Tn9r~;~lwm^HK zdZ-CH0kuGE6B*r*?EHw)vTCU06#;VTl2--DwRTgOfLuJ~%>@^OTR>TU>;!v(dqG*z z$@>RnB`EJ6kQLn#P_!Ne4WPV-K(28oKw00M1WjNIC`(U;H_gk^HU*UB3VT)RwRKE7 z0a-?vKv~gcf$5+bly!<5l;ue_D9ejnun#y2loebaC@Z^supc;~%`?Mq&?jvRPSB?& zT`)17g=!D3qR<1~A_X_<*M?<7OEx%ULrZqg*d8-xKz1k(Dufn570_m=208>qp%%#0 z4qhl1it<=5A1s1Op>n7Ks)VYbz0gtUBqY2U=j*eL1JBnRbD(^v1X=(sh1NpCv{}Pi z@CeilrSi&56GZ;DCvMUYQa9Zd@kb-8lw>d+ltqFG)Pq@|^o$#917?Hgfw^D`mgyV%Z5sn+mNfR|J24!jCQG|1~ zuozURg~iatT38HPr-etzgQ=Zb5Vhbv>$ zR#~To#fY6+Sd44X!lJ*0a86hmrbrDkE+Rr$1ah>n2o!5!5m-!EYA-dYBz&ckuQusU z!dEF*Q#7rW5I&)WuOdDDV%by6SB=XdJPb+E87|f$(nL$OuoS#e3tvsRRtt-9&008z zaF!{iNS-9*YT;poOSQ1H)@m&*K2fcOM-#4(g~QZ``+qG%YL?L}rc_$RuZ5*+3bnA* za)B0>F4(Ar#}nSGg~t(&5+19JQ^dHG-m%t|8q1>_X+<$Et#>%qq@pMTrnqxS;hDs* zrI93TCwv8sCxzz_7N-@%^VIP5bR64K2|OhU;iZJdfu+UP63$gds)cWs^ssV0Sy-+z z#li;(#|vmk2%jLFhY8Y3seNL#8%a2W@b#D}HFXmnsrf=)LV8I;`eMTQ_=0q8KWUD5 z%{Gw{57!co*FLTHw%vc!clAW_?l+`tYu>6iwY3dJT%7J&6v-xC>%)+Flc$uHO`AD` zv`G_-OeHfXPb`^YnlbUVDUn-u>F)?nn=yA{$+RNV?6O(YX53;bojH4&%073>H@55j4~ zrdugqi4=aUm!n^tfv);kpT1&S)?|YziJ#Xo6mH8cGNdN!BZ8bI zPa3*J%sULmRSn7gJG8hAMm;a3X}SX+dzly4&NFnen5P+xIUI!-ZAp;yeLnGGsGvVc z-z???zT^w9F4FNnLz+XAkW~5Ple0D7rt2O&O~-mae}N29{NgHKGJ*xiMyeE zE&ar_$mjD6T?4{rnjYho&cX#xi%d3Ds_{x(YEVvmsg|b7L>B(hkmgU|63#rdRVSoL z1!{@cLx~m$pVkA3?4qKAtQ1DGgykbD(urq5i7JFIrsA47yRf!}c4nOsmwGLhnK0o~ zHiIU`Ay=TX65aybD&DkdCE}P1%MED*FO5mbs1z;fNYz{tOOqCnA+`=Ji*FMN@us%< zVk6Py$OCs8(#D@tY|gD5Wnk_^uJx0T(zOYEqIf3-7h~3_gme**ycF_sZdJSQ8qsyG z`8zRRc+c(5T&mch<;ABN;mL+JqqA#sQQENxRTU+wv2D%hX%PcrE#)Vxv^5$YhCnIOI?WE%r^+baNEJMsiS8)NcLFA=v_YyCT>F?X1^|s_mo4ToDVpYY3 zlOqemhO`nm>nQKsp4{?3G^l9JBY!8o^6$#3P99l!mmw{j;1eiPim7SbURf51z2e%V zwE78;e8ly{=MqQ0_>XjF{3Eh?P~4~D>EgxWzI9mL^UyhpMJSaFal91u`X-Z85vpTT zM90N=fQdE|Z6{m%<+h#YIO!IRMcAZ??1)KNI!7Lbo*?em(j;9zQuaLb$Z!;y_(#&S zA#oJ7l4_q1POO)(Pas<^(FB!q{jQ-T^3Qt=U8ltTM0n&QHHe!o4^E{hnGaSaz~k^1O~ z7sDbF!XDE*QKZ&t$(k}1M3bmEcut zo~>Dy$HKF4kiocsbzq_?5-vbh8khWdy0l^~vL<9oNSB%|1^*lQuu?&RnK0O)wG<#z z-j;-d3Q;KCRXGc#3F%VML5u=a&l8@8gt|(8lg2OWP5BuAfDVj1V_1inWs(`YnDn~a z&^a8xm}HSGdQVbzj;1S~7PnhO>A`rpmod5Y3WEwmV8 zqX>!7hS>Bcwu@dFU2+hil=`NWsu!-FDd&`%Lb;MA?G~Fapbckxg9cCGy9sENSwQ6-fOcLmQ*Cqd}c*vb5HWs~e?RAFo*zHH%X5*lg4$ zo$RKWu`9HAr+A%~CC*11Ty!^=8>z2RWxH@7$!g*J5m!jl3XG>2XEMpaBdx48k2JPq z$EzK8?TxUVI})WzD>VN2{hin=hKi1OJt}D?iN`;7T3~@D7xyI1D>*thBl6F3L)W%4 z^o21g@&^_=yOKhgp?KkOT#fKfcvsJk`Hh5Ch3Z%eC#w1F+GORCamflVw^i~UNLH?S zFj=|&0i99~RYG-8%7Z$^3|$M&fnI@rhOS=)A5;nP0t+PynO5tRTxcFt3DrT$L!?86 z(9;ib=;#=M`D=8_7tm!9GN1~m25N?kYjuhnx&wM1YKDp))+tA!fsg2vMbJl(@~BS9 zf(oElpgO1-vai!AGocgEkjHe&yHGP^dR(XEJs#F6r35OVDyR-hdP1jIpg%y{q3>Bfc8Q^L4%*vDdV6e5HDa*F5I9~ilLRzn6Wuy`=-3y`@Z+z z&$<7prMBlLJ~1IlHX|m7yU~F{a(Jl)x?*VlDA^gRV0;ZOn=-E~vuK+1XGIr9DZi6q z)U?}V(@QZ*=X8qm%~Pr<>i$-_JBoxysiGs$KX_Wv#IlK*4oPC0K%HL)o6X_LQ`!CE zM|5HdL&xN277QICOnIb<7Y~l54VAPBq=_eLX`>{qh_uo(X|lhm96DQ{x+7A5R@#+n z8Ao0U>l9gy#1vks7INZjMpfa-v(r?CEoY^TQWdhpkx-FQsy_YMX{u~WJT0u&E>9Is zCGkw{@?>XDNsBYa_zHwE{p>VVVa8c$`KmtC*=cG;GD(wjX|W3DU#~tD$tJOWMv^jk zmh=$?uwkUOiW<+%7ejcQTk(idRGAbv}N{jKaxfD}aRwA2S+;N(6hO3Epj& zGwl#Fsc)9I!H~FKA_C#gFzkW0c9nX z1Io3N9}PA3j;yaX^oZ2m&lPxcg0l1)GT~8bg^AKcVM79gQF-u8Mk=pb7BiYQ7*dRX zxhc6-!mVY9ea5^5iK4`eQY}M_tI#rPHW=FWCtYlK6qNEefKs97HGT;n()i5}8roi{ z@kh0MX`vMFnHI`6t%@lmU0tun(NDUxQ2cWRacQA3V7&5035r_ls9KzmF<2@ui-R~e zpdlU?O|oJ?~B|OFwR?CPgMbXz1)0X;CdGl@t>DM2g@3HfDqI&v7Q} zxQ+bicB?xWv(YCRA~-K0qhG03plBD3g2E`JY=KeI_WC0KTxGDD>dz_7kryjXtV`w9 z!ExuyszE7fO09;X^xTT)B?!2)ABt5wSHrkKo)#}iNRL-zqozQd<6`oqKz{V~tb(K# z4G9IM*J%ugK~W@Kkj86;Ar$x3=s7lg zJ~d{8=*-}a#-cL^j8(8fQyOhmY8NBURa$^RHiGd_20s)h6%(Z^$cy)Dr+7_-G3JVq z5f2->>+2JkjOMkmHWyR)T8Jv0{ivaBhlF&qmY(+zbBvZQUNnJpky`<3RUlW2ih}<} zA5|ilje_`R&VpD~WY?x%E41N3Lu!HX;h4vygHobJi+ez6>>;4IK zH9>5sO96R9VzpHZs6c=;Wi2QLyrjjqgHpgQP!zqZ;SunEDnNAX)e2B2f8!&ub{1vL z2^pq5GDuTy21V-51jd1v$4r!l@2~N_sPVm{@kzHA!zZ#8V5Ib6{F6+n?@3`+=hT1c zN*m$NMlSw&Z$+&AMOVF6wraiU(F89UddA0$d0K{4%X}uIZC}zw>;0f;dtT!cXD)$H z(iRL==h%J~#IuRVKfz5gD};Bi#(OBnYl@!3`*{LyM$Q?-(v9n41`GscbcnLNM20+# zp+IAh-dwCPNPD}q{7s-(yHmphpz2DXc*JL*6d)~7pHP6w^jOS-EKn49Kw*@pF6qP0 z_};iujqy%UG%o?g+y_9>x(<|*o&lw|U)IuJ2jvpK4~$oFF^Z+I)x%=DKJnPxR&tkLfKqhxfgtGrTJ_-gO#p)j7Odlh5!T)p$>6 zyeU_lZG+bkLC*98A1U~% z@K>Cp^V;@jY}u>nJd$8bgJw(fIdb1?a-uu)%9t(oE7g88(J^tz5Ay$U)E+8*{ z@klpU#=LhZD9$C0SCzovEG0u~vIrFG*J${-mi{al9~Ui}6=@^Rv}8l2+5=fxBhI#{ zQ|H*i>9X}PdC~2LCvKTBH6f!&%U}*qk?*~$b513RQlHIWQ9iqIgk?(^Xx+Gy`)c$2 z*m;NiY5z5^AFrzu{Yg{GrRrJXw1-4f%xA7#k!&2jLiL$k$Dd+3zpliO2=i)q)zda<7>|FsIL7A$ zvxP*R_E~yQvT@C7gQ;u5)v;a>V~Yt(t>;}GnftV%U#OG<${>-6d*Kmygf|MxIFvkj zA|NO&Bq$zOK0hgP_-RA8HZQMAHf~*|dU^8rXH?YPxGg!BnY<&}*l9<^`wT;ZH$Ktx5-b#+#`K+O5^4t$%0>|Eq;ng3;7;k8Z#oHZ?#c%y8 zh8w<$rT6(sMO}D+F5-K}&{OwZKYnigX#&@}wSeBIiyVH6l5euZ4?}@pE+L_^Q%lN`IByAOM*K(~b<=B9v$J9Y4WM5vi*04qb(@bFnvrYOpvldqUkmN#qWWL~c-9E$RT z?&{JhGgMtel*oWr3>QZVwiw1mKHOsH-abba9WzB$6#4TOL*GcJR}EwOzmSBQAw%;N zadik8GQ>VMnan~M())~K$Kr|a|ocM5BtL+~qi{`XW* zet9!*KH?=NGPdO@37^!~!t=)Fa~L%cNq^0d-6nS;2OcIyZhFnoea+(reWYPK%Y~iQ zhIQ>1ATMK2bR|W4?J#tX_@5+GPOIj~n?F>w+MoYBaTZtUVr;9PA9(%930~i#)3fkO zZ@+dD&-*E7C=90}A8a>V%puv}wohL(e5{Yu)EFvt*pjHsxks{WEh`_Uy7E%8`#~)$KKmzBrM2 z+RWKEPntM;N}Nxau%p*mOrgxlVsv(n#wAQx6RYUyy#|kJ$nt$!%JcgSm#8V9?9*E1 zw|$0gYRWtNHRj0&G@knpXi_g8(6WvnFgW@o+BkLMdZ!#=HxF5GXhBN7DeM+%sva=c zoMX}3S`$9=wkAe9b%{jZHe{>X2E1eVgBp0^9nFh=hzAb5W4I*J<)EQod){G@>~5b{}}%r z{_uVNTK}lP6M?S+J%U4mMZuqfDWNAriiwpD?$OQM-|RP!FyCyxjdpq3{JQxoTBNv?pLo9X zC}xhgqw^1zhb_+{cf|6&<(IJKv_)_2WWC+`nDq^78(XTao9zwT9@|H@FKn0C2ix8D z8|^dgYiN&W?b(iDj(o>N$CHj{94|ROaWp!*I{P?7&PmQcI@da1aemWM&IZO_( zW3FSd<55Q&XJ_YS&Rd-O-G|(NcQ5foJhk37{&c@FuqLoE&?eX=xF+~y@L2H2;MUOo zkdjAhB(X$wnoG^Mo9{EPF~4kn!~Bl99;f@xthaQuq+7BqZp$#sI?G1OR-CVaraoag zWy!GivAV6ptYfSm3Y5UpM(VkAr`s~B(1@>^UeU5#FeU*I!&i1bTko`;h342FJx?`}z=NRcI za1=Y{I951TIW{<|9Pc^~IlgrK?C9uBcMf*?oYy!DoW;&L&K1s8&JE5g=ey2BbivQg zj;?gqV3*G|%vInjcFl3!=UV03;Hq-H>pJB6()F{eqdVO_*zI!T)()+UaP45@pM(! zx)=@5SjXCKw=K0PrLo)J#{(rnrJSu*=8;L31(v5RuUa-+KeisVIvM#Dw%xV>1N%n% zZT2$zAMKm$FWL9e%HP0Io5)%k|=W9Q$Tjn40#Jzf1=Ue_qs1lJUr^+DHG*PE_pnzWs}r#sU<$X(!`NPFJv zj<_q`d)@E4zjOcU9^kQiM$(cu)0V3|>uAUQo)0~TJ*Pb>-ta)L!#mYG)4RmG%KHdI z<2~;u-s9dRUwdCypVv3kcZ;vox8C=hZ3cHJdD# zSOS*OmVC>NmZg>lEKgasSq@n07#F`=dRZ^EUS}<|-f3NGz2Caly2pCZ+Q4L-Y&+j} znaypRV4GrFV|#+3yoU++Bikw4dG>G*yTvX&G~GVi9${2}VE@$qo&A*E;JCoyVX__X zC~?eVs(p&?`OtCLk?QQ?9O(2ovz=Eu%b0jpJDcZ=p7yS;OutvVN?fyD^IeNv zFSxe3YVr7gFdcVy_iWK9@>mr8M^Uf{KkDN!HZCu@4KX_ZbJ$#vTOTKTe?>^t;P)TTcXm#k7(B9Co&@UmS zirrRP@FUw1+iy0#y^q~)A7`ItAL<_Ep5mV2UgKWxu5ll5_wf((5A$E;pX(1V^jG?y z@hdgla3c6e^Ez{rZ5B0t*tx@LVZwaTyWP9r`w>IyNAGEGs;`^x3O|gdBz5y=x||}( zPEvog$ym5vrM$!XMNZjv<#|8wmSDX zzi=kIdb-Bb+&w(~JTG`Me1m)s`!@Rqjdi^)~Btz@rU!c zEDy9@X3j)gm4`4t|;2oxz-vY_Oj=|nR zb1*k}H2C*muh4*yj}dTl=$6pjaA<32UnnYlSDvI)Q=Y+`YVL0C#bPASe1mzhdAa#P z^LnlgHC!3WEb}cdSzfce!!qPs%g-!A|hRFGAfuC%nud>Cj<*wkrf9^f~CQ-;Jl#1`L(*3mppBK z)q0)%urqS%l%Z>|fa^h_tB8wjiL2CA=9=fa-&NsW?Oy9%_rDtVye~5Fu%Vy3Ft{+d zj!WLg&?e^Os?e5Db!ca(hMA={bTCvGIuxo89SJprj^eFY-;ku#bID6GM>38WG7Wlb ziuKmWH@_J!Njg9B#RYPR(Mx?*Lv4^E4>>TRhzx9G2ie4e_y&U!)NlX`P7gx@DDzdKhvMZ z{AKpr{cbKq+5Q|ZMx*?B{(OIde}cczU*s?Lm-v74M`nCx$c&W#WY799Knd{6u zty68E*&U9b+$o-+o-4g~;xAA8s##TE7`QS}7I-Y!FXX0Wl2w_m<}7m?R&d8GCoRKS zXU(O8ZG2sPKlu!-L!$mQflYyaboo!xma;|5e}lQFd6Rjwxyrnyzq#7HlOey?Tx&i^ z;fEOfN6Zc8qvoi&$=qx{f$|Gkg-8598%*I!>qhG)>t^d#YppfcmS-!pEn~s=yzMpH z$6VsN+lSh(v2V0*x4&io*nXT_iDw-<9D5w^u$KGX(aG7L>)Ckc=UiYWxu)YjVb*E& zu3uf<+)np3?o#(s_jdPR-M_f6^jz(!^&Dd1aFc(L{{jEQ{wMrT`~U2JCG4;Ezv18O zf7}1Q|1bW-{xAK<{7wGv{U`mu`*ne~fewMRK=*)&mDhlPCEyAK0+$DJ16Kv|1LFfX z2c`sW4U`7vGHKq;l3`_FRp8;k<1Cn;WBKrE;I+W6z=6QQz=whQz~|hgGzPv6{22H( zaGC{mD%0wP!JffzANp=EBgh*J1&0Mk2CoT@W9&>~1n9e>HYzlrK{5kl0P#0<&x*(Jm>K-zM`Z8BrLavaX z`8qdrRcLHzJoEOH(DYDgXl`ghXkqBy(8_RVRcLMKaYoK_p%+=rycXKU0{>v>Lq^PJ zp|3)Xp>IPcLcdB+o08>v!nL-I`Ft*e7t-Us@sz=4yV+yDf)&qlZan{DK5YJ-o2>4Z z%eWc_EMqJ)E#*v|Ct&#``Kv8CEOaa9c0+W%?4z|q6e%Q4Vlbp#wa zjw>Bw9M?Gt9c7LMj{8|$KIeGRvDfjo<9)}cj$~)5v%9mm^JeZF;+3# zoo_n#Ip5(n>ag=`XS4HH=V|A~TtI4Ei(BT>4xyCceGtYCsXQStN&r6;? zo_E8Z0baNFX74QwlXOUuuF|MdOryOdGwWK3Vqh`!oC&VM6g`Bwid?o$`~@5ANR`PZ{p zdCC8p|2_XlekZ;&hnuVLqk%14ZTAN{Gk(3ypV!mp+vwXxTwE1%vU*un%og($^APn; zeTj9Qb&GX}^*wHvzO}Zqb+J9mJ^2pXPqudUj`l0;dG_)4TkNIWY?nCxOWmA9@<^IT-pm)WS_@ zvLc%kQ_b_tW7$5q%{JE-=EmbG+b-_pKEhbNeF&?&TkOM^*w=Dib^NFGwAEI)* z`#1UT4SX8^Ujx@uQP5wbk;k6V7lqfT`ZJ$(JUU}oqNv8E6&YPUKI~O?DIoCT6IDd2YcKKbi*g)wR_Kf!|^X%{#ywjO#d-{I$C2=o4)PKEy zA|Cn_ZuqwU6E3``xEJrw1$J^^HuvGr23`)l&y=HMrIQtO1h3}mSQxxJxGMN~aA)v9 zFoV0bOS#GgLl1Bfd!A+A`yr(+S^Z|j{pM6l7t0_^$Wmf?#1j6@a-Q`9Ynru(b&%D{ zUFz-D#nuY$v^QJ#THj(i{hTFx)OynTJNMk3Y!})xxX-fKoQ&_0>at`CqkKN2d@)1s zVcX*@>0hx`+iIE5K4f326D>559lufbN%p1ot?YgN#MFC{dP%$7afPEO?6}YII;)n` zj!dpyv(!e4&`7U4YuS7H(D@Jco;tfOa`ka#xrX9`(^v}M;kwJU&h@lwx9c0$>gn8t zXR%F^>mI}H@Jja+?p>_YKX8A{rqg+zKAw#6K!JjhzV z&ia}4c-Z=jRnMp%Y;)0!uiD<@GH}E;o~zV8dmZ~0jqF_fVsCT|r*(dCo^U#qxZ8Nhr_4#~GqI+-)gPP2gNL_YG&7=pTp%0 zI)^#0b{0A_TsO!r75h+s@qFXy?VamA;XUrVn2XHVzzu;FfrkQ>?8bfnRx_HZV(T0p}zgb^zlVM^m2FkpcJe$Lq?D5CMN{zcZk@B6&Z^FGga7d|5FtvkdErOPN?NqQ4& zw2h)g;A^|M>K3QGA0M7amot*+jsJkpN9`K@N&O!f zpBb+0ea6+s&Bo7-Nv`etjVFxHo0po`m^X3!y@f71(Z1Ne)&3=OGmZB7J?iIK`$hX* zTZL}E=j@DsDZUTgyDeR_iHG5ZPb52%qsiNokGuTn)#Mvq1;i#H$48TRtoRJWb0!0m zkS~Bt&rz;bCZW$)l-H4mpH|OQW%YdZa%94{wW>CzzpI~)B~Ufq;BM{UZhghvXFb3s z+!K2+_L%)LGA!Vn;+)Qb6@vj^cBHr!-hS@$vIZOEz$;Tt= z@8FOh>Oaw&jT7-JZZZxUpW*xvtzFoYm)j-#1=l{(IhE+*yUwoob({+e@n_;^CVr3@ zOROZ`N!*`2=vAh#Nf_tjIH2_fpVM;U_r?8wcJZ`$EZgkstmtV>l*gn69E+3Wv*j!0 z@5nA+<+t*RydhiE@FHceGQ^U4LAg(RS)0($VFB+q+%4ma=5^-rEQvGiH2&EYsDNWw zwJMh92$xHlRr@wFw1hIchXdmoPKx7_=WsWp=Z5%uM!uLX?iB~nbce*#P(hdSbqdk} z={Pwie~k#~eYu?<{!NCq4P}2dXWPS!?q#&@O8se9e?f1D2<|eTHLf#XM`GGmmo;TQ zVwrXWIh;X877z;Cvk?(@W2{|EZuKLK(%Ui99>O9$OWwnrT#Ol3lm9NiChvqZcPlY` zm74mxdKGm3H|;cC)W4!fv6^=qvT?T2ZCuVCbq)7s<4zwow-M^xWj$g21*1r^FSKRI z=FPb5mA}Yeb@GuF2c=usXAS8TzrZ)jPst}>cHDx{eoA>;k=08`x$agQ>hbLNoc1UT zc)R|pzK22|FrG4Yn4dGxH*Yo{H+NWPTX}v(A(n7sx5i$I9gi=)&wdop>~Ow}AUh$x zH~yFSPHw*|V6_8@M-ngMGGCDVG18N7Qf?CF`Do(M+6zrLk>s5xWhr@G3Xxk4KwtCR z0SctFpM6?aUd1{+QZ>{RPE$9?xr)5!H>4x?s%{N?_<5G(NyI*B&6~6qcv_^H;alQq) zSq{U>(bDd>f+z1BArLCcWBgtdGR{Ehr_TjXF15TGoCpau{_dC8o~3Uc3buT53GP@` z8eR&?DbcJ()K;}ky>Dqfc=r(}uE#cGf*nF!w_v@r+ak=Dy6+dS1^GX1EH(+Fr9ZtM zyzxAt-Xs*bQ5+?ubSj?p&_m{wRq_;bgxslkSYnr1NflK6v@!$VZ(zA@DS{eML;Tn< zKe(mzz&pW{Pwk@KJ(wjKwNLF=2h>4suwgZ;=G1)Y;5$L(s7Krx|B2XW&ulN)ci00+77c=dg%G!XTATa0uRa3_&VEF$zaR$ zlgAB~u38D6aYl_iab8(a7768+=|r7gtkI2izdRo;jq*QAqZUw0qiPX6jgym>*%=k0 z*y+++>p^?qPEv@wiHW++9>Spv2Wmf&+u;BE>-<+QbKTr9H_a_mumV=dYPL#Sn?bQR z5O2opX@%B1Sc(p5P!~2&hV?jzMV5;fp!zY6p~?6ZH+wZc#|~dYo37%`ZNRnxOq~dR zTpM1TiXYm^q11zyQ<{A<7<0QgkTc1CLXTm*mja|XmYl$LnZm5BawIHZQm(*|4eS;c zE>Gktls|x>9uZp+DIMqqN9+{4#2zst_Cs33#7hMNrZIAlNvyXST(>zKwLnS&B%lQkyGvNGis4r_@FMl#%)o#lu8?1)P~NKc$_LW>9c*5X%zr_A2u(vaZs=rx)l90Gq6uXDW(JM44%r=8CSV^N7S{ z*kq0HaZ?pE{^pjTH>$O>$xJP!rFmD-i)l2V4G{q4wGpkTjiYWVM8C6IO`F#iwPmfY zt!eAprY7hiJ*>C5dQ@+RFH91#wBC)k&_^abq-XWKKB5;1=F578glv`@dtP7U)T`@j zxJ#S5V1zgoTZ|}bT{KLtwKNN}mxVci3z-G9BLH^XDEnG%8?K8$wT}C~j`J>S730%B+XH)-4eM} zQ&!sQCMWGf5DsB+<*g9{k#VbRRmeu@EJYyQ zjX%+cyFG-nEkpyp_p}D41y)@P`&CpO8r4Ia25C}(7EREg8QQZzb5?21mKxF`w55Zl zbkdRx4H>2#qcmfZR#a)k5^ZSEgn*t2b9cAHJt@75;`dYf9EBgF>{Aqdj*_oX@J&5H zv0Ev%N};#SY1YxbNyZ!g%3_L9A9uh?~am4_w`d);$Wc*^Yg72SFdUo691^fMWQy5Izy z5Vv*MiSRn&e;N57h7Rj4kQSmrVGBTe#`wJ00wkkS8+c4f-QckoMD|GoIHg6Yr#V4P(;NweR3OA za**4IQue9rgAKmS*7oQOB7;LcK&F6V--ql)pGR5CGr(}3KCc2n0q{j|72D~u1NeG? z-VlAxB8Cdu1hAW>(@Owu9jFCdJ%T^bj#V!Lvo61sdCc1p65=A7V}?%81FjXl0k{G{ ztC@aBfL90ZLCWYn46Y$R9olBZaSr1$S4xHc&-hHZ<_B64o@a=rYC2GcM;3Wdbg~yF zl|L@sD&4UXJjsoMpEP1L1A2yd_>;AARspkR3gpayoduAyhPUHoaADwL0+rra#*cu! z;HMIsja6fFvH93SY$?{jK^A;tuMO?d;j0hFPH}s8;UN#$dAn$P13B#v;DWvCGId)# zZwfF`UO42%LcM+rG~^6BStsY@xq-(qkf)q!XNJI{>ePrVmU+8ScUB2x8ouutfw@#z zD-CaT!(6?XcUh$UD4bO$keGq7=HaYG;)s=a-A|RgG|3G>T3&pVJ{;R+6C<43MNX6P z#6)5m7hsMj5DNqitBDOB9EAxN#H5<+N_Hm)Ahbeq#LwQUkl2c!vO!>CP{^XLn#C{# z)&hMwVv1C_59%7`g2+Kz<6c@MF1TbwYko2mkV3>x9yClJ8l6%acnqL;b3mgYjYCxB z!|<2}AoDV}-`b%_f7;SbZ>GZ6> z8yi+L$*PKZl4cDK6X{J5=S|Vy8tH1?*Od|8Fo<@yoxwjT*rRl^%mP_uZG>rHloqO_ zc^TaQLE4vdMlda>Y1$mEYLF|nU{AMWO`E>IIvCHnD2pQfsnH!zPk36w(-9(xY8N)b zFlK9kc8t@C*~FsX1R)us3GK-gs-mAfr$_^;6yJ|GQ~YxlteOyoS1Ed$q7R69aRi+( zMKJcoB=KWaL4^`89o7M1c8|&S+1@@#i!B3SY{HiYn1eNy%4bd)IBqtQjuf-NG@u+?ex0CtZg$1Hco zs#%sxuz4ox3Wg2FEI;jKdb$f${ z1_D&k#!U19=}{i6RDkdjzzrcay*Ws`pml&4G0PlGG6##yfPmTF&IFkMHUpzTaw1W| zF{mfD5`sSg9b|jm{uGS(igJ#~dl9e23%eV)xK+_8a5*wO-i z_f?*WcnWQhmrI^Dn`91Z-oNkC7xbPZ3Enzc{z%Tei2@FxfWv6u4=A@CAuRfvqv&7H z%d+daz9D{Bk1JK5Dn-V_TMjc!#Rg_-H(P!&7NyGret$RZ2pt`Aimc)~&&N!TgaQEG zjJF{9hXHGqCWV0|Eg0hOOZb8zveI6BrZ5Yw%KfX#-6-g?ym^#7$f;RXctqdzPtoW= AYXATM diff --git a/src/Native/libcryptonote/Makefile b/src/Native/libcryptonote/Makefile index 17df8a373..0cd26e4c2 100644 --- a/src/Native/libcryptonote/Makefile +++ b/src/Native/libcryptonote/Makefile @@ -7,7 +7,8 @@ LDLIBS = -lboost_system -lboost_date_time TARGET = libcryptonote.so OBJECTS = contrib/epee/src/hex.o \ - common/base58.o crypto/aesb.o crypto/blake256.o crypto/chacha8.o \ + common/base58.o crypto/aesb.o crypto/blake256.o crypto/chacha.o \ + contrib/epee/src/memwipe.o \ crypto/crypto-ops-data.o crypto/crypto-ops.o crypto/crypto.o crypto/groestl.o crypto/hash-extra-blake.o \ crypto/hash-extra-groestl.o crypto/hash-extra-jh.o crypto/hash-extra-skein.o crypto/hash.o crypto/jh.o \ crypto/keccak.o crypto/oaes_lib.o crypto/random.o crypto/skein.o crypto/slow-hash.o crypto/tree-hash.o \ diff --git a/src/Native/libcryptonote/Makefile.MSys2 b/src/Native/libcryptonote/Makefile.MSys2 index 5b0eb9e5f..a04d38435 100644 --- a/src/Native/libcryptonote/Makefile.MSys2 +++ b/src/Native/libcryptonote/Makefile.MSys2 @@ -7,7 +7,8 @@ LDLIBS = -lboost_system-mt -lboost_date_time-mt TARGET = libcryptonote.dll OBJECTS = contrib/epee/src/hex.o \ - common/base58.o crypto/aesb.o crypto/blake256.o crypto/chacha8.o \ + common/base58.o crypto/aesb.o crypto/blake256.o crypto/chacha.o \ + contrib/epee/src/memwipe.o \ crypto/crypto-ops-data.o crypto/crypto-ops.o crypto/crypto.o crypto/groestl.o crypto/hash-extra-blake.o \ crypto/hash-extra-groestl.o crypto/hash-extra-jh.o crypto/hash-extra-skein.o crypto/hash.o crypto/jh.o \ crypto/keccak.o crypto/oaes_lib.o crypto/random.o crypto/skein.o crypto/slow-hash.o crypto/tree-hash.o \ diff --git a/src/Native/libcryptonote/common/base58.cpp b/src/Native/libcryptonote/common/base58.cpp index 43208c1bf..75556cad9 100644 --- a/src/Native/libcryptonote/common/base58.cpp +++ b/src/Native/libcryptonote/common/base58.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -36,7 +36,7 @@ #include "crypto/hash.h" #include "int-util.h" -// OW: unused #include "util.h" +#include "util.h" #include "varint.h" namespace tools @@ -111,13 +111,13 @@ namespace tools uint64_t res = 0; switch (9 - size) { - case 1: res |= *data++; - case 2: res <<= 8; res |= *data++; - case 3: res <<= 8; res |= *data++; - case 4: res <<= 8; res |= *data++; - case 5: res <<= 8; res |= *data++; - case 6: res <<= 8; res |= *data++; - case 7: res <<= 8; res |= *data++; + case 1: res |= *data++; /* FALLTHRU */ + case 2: res <<= 8; res |= *data++; /* FALLTHRU */ + case 3: res <<= 8; res |= *data++; /* FALLTHRU */ + case 4: res <<= 8; res |= *data++; /* FALLTHRU */ + case 5: res <<= 8; res |= *data++; /* FALLTHRU */ + case 6: res <<= 8; res |= *data++; /* FALLTHRU */ + case 7: res <<= 8; res |= *data++; /* FALLTHRU */ case 8: res <<= 8; res |= *data; break; default: assert(false); } diff --git a/src/Native/libcryptonote/common/base58.h b/src/Native/libcryptonote/common/base58.h index 6dd850c03..02ca96956 100644 --- a/src/Native/libcryptonote/common/base58.h +++ b/src/Native/libcryptonote/common/base58.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/common/int-util.h b/src/Native/libcryptonote/common/int-util.h index ff1340dc2..3bcc085e2 100644 --- a/src/Native/libcryptonote/common/int-util.h +++ b/src/Native/libcryptonote/common/int-util.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -35,7 +35,7 @@ #include #include -#if !defined(_MSC_VER) +#ifndef _MSC_VER #include #endif @@ -43,6 +43,10 @@ #include #endif +#if defined(__sun) && defined(__SVR4) +#include +#endif + #if defined(_MSC_VER) #include @@ -205,13 +209,14 @@ static inline void memcpy_swap64(void *dst, const void *src, size_t n) { } } +#ifdef _MSC_VER +# define LITTLE_ENDIAN 1234 +# define BIG_ENDIAN 4321 +# define BYTE_ORDER LITTLE_ENDIAN +#endif + #if !defined(BYTE_ORDER) || !defined(LITTLE_ENDIAN) || !defined(BIG_ENDIAN) -#if !defined(_MSC_VER) static_assert(false, "BYTE_ORDER is undefined. Perhaps, GNU extensions are not enabled"); -#else -#define LITTLE_ENDIAN 1234 -#define BYTE_ORDER LITTLE_ENDIAN -#endif #endif #if BYTE_ORDER == LITTLE_ENDIAN diff --git a/src/Native/libcryptonote/common/pod-class.h b/src/Native/libcryptonote/common/pod-class.h index 3896d5c29..5f6709eef 100644 --- a/src/Native/libcryptonote/common/pod-class.h +++ b/src/Native/libcryptonote/common/pod-class.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/common/util.h b/src/Native/libcryptonote/common/util.h new file mode 100644 index 000000000..d3ba47a4f --- /dev/null +++ b/src/Native/libcryptonote/common/util.h @@ -0,0 +1,215 @@ +// Copyright (c) 2014-2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// 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. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include "windows.h" +#include "misc_log_ex.h" +#endif + +#include "crypto/hash.h" + +/*! \brief Various Tools + * + * + * + */ +namespace tools +{ + //! Functional class for closing C file handles. + struct close_file + { + void operator()(std::FILE* handle) const noexcept + { + if (handle) + { + std::fclose(handle); + } + } + }; + + //! A file restricted to process owner AND process. Deletes file on destruction. + class private_file { + std::unique_ptr m_handle; + std::string m_filename; + + private_file(std::FILE* handle, std::string&& filename) noexcept; + public: + + //! `handle() == nullptr && filename.empty()`. + private_file() noexcept; + + /*! \return File only readable by owner and only used by this process + OR `private_file{}` on error. */ + static private_file create(std::string filename); + + private_file(private_file&&) = default; + private_file& operator=(private_file&&) = default; + + //! Deletes `filename()` and closes `handle()`. + ~private_file() noexcept; + + std::FILE* handle() const noexcept { return m_handle.get(); } + const std::string& filename() const noexcept { return m_filename; } + }; + + /*! \brief Returns the default data directory. + * + * \details Windows < Vista: C:\\Documents and Settings\\Username\\Application Data\\CRYPTONOTE_NAME + * + * Windows >= Vista: C:\\Users\\Username\\AppData\\Roaming\\CRYPTONOTE_NAME + * + * Mac: ~/Library/Application Support/CRYPTONOTE_NAME + * + * Unix: ~/.CRYPTONOTE_NAME + */ + std::string get_default_data_dir(); + +#ifdef WIN32 + /** + * @brief + * + * @param nfolder + * @param iscreate + * + * @return + */ + std::string get_special_folder_path(int nfolder, bool iscreate); +#endif + + /*! \brief Returns the OS version string + * + * \details This is a wrapper around the primitives + * get_windows_version_display_string() and + * get_nix_version_display_string() + */ + std::string get_os_version_string(); + + /*! \brief creates directories for a path + * + * wrapper around boost::filesyste::create_directories. + * (ensure-directory-exists): greenspun's tenth rule in action! + */ + bool create_directories_if_necessary(const std::string& path); + /*! \brief std::rename wrapper for nix and something strange for windows. + */ + std::error_code replace_file(const std::string& replacement_name, const std::string& replaced_name); + + bool sanitize_locale(); + + bool on_startup(); + + /*! \brief Defines a signal handler for win32 and *nix + */ + class signal_handler + { + public: + /*! \brief installs a signal handler */ + template + static bool install(T t) + { +#if defined(WIN32) + bool r = TRUE == ::SetConsoleCtrlHandler(&win_handler, TRUE); + if (r) + { + m_handler = t; + } + return r; +#else + static struct sigaction sa; + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = posix_handler; + sa.sa_flags = 0; + /* Only blocks SIGINT, SIGTERM and SIGPIPE */ + sigaction(SIGINT, &sa, NULL); + signal(SIGTERM, posix_handler); + signal(SIGPIPE, SIG_IGN); + m_handler = t; + return true; +#endif + } + + private: +#if defined(WIN32) + /*! \brief Handler for win */ + static BOOL WINAPI win_handler(DWORD type) + { + if (CTRL_C_EVENT == type || CTRL_BREAK_EVENT == type) + { + handle_signal(type); + } + else + { + MGINFO_RED("Got control signal " << type << ". Exiting without saving..."); + return FALSE; + } + return TRUE; + } +#else + /*! \brief handler for NIX */ + static void posix_handler(int type) + { + handle_signal(type); + } +#endif + + /*! \brief calles m_handler */ + static void handle_signal(int type) + { + static boost::mutex m_mutex; + boost::unique_lock lock(m_mutex); + m_handler(type); + } + + /*! \brief where the installed handler is stored */ + static std::function m_handler; + }; + + void set_strict_default_file_permissions(bool strict); + + void set_max_concurrency(unsigned n); + unsigned get_max_concurrency(); + + bool is_local_address(const std::string &address); + int vercmp(const char *v0, const char *v1); // returns < 0, 0, > 0, similar to strcmp, but more human friendly than lexical - does not attempt to validate + + bool sha256sum(const uint8_t *data, size_t len, crypto::hash &hash); + bool sha256sum(const std::string &filename, crypto::hash &hash); +} diff --git a/src/Native/libcryptonote/common/varint.h b/src/Native/libcryptonote/common/varint.h index cb785e61a..151d13dbf 100644 --- a/src/Native/libcryptonote/common/varint.h +++ b/src/Native/libcryptonote/common/varint.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -45,7 +45,7 @@ * is as follows: Strip the msb of each byte, then from left to right, * read in what remains, placing it in reverse, into the buffer. Thus, * the following bit stream: 0xff02 would return 0x027f. 0xff turns - * into 0x7f, is placed on the beggining of the buffer, then 0x02 is + * into 0x7f, is placed on the beginning of the buffer, then 0x02 is * unchanged, since its msb is not set, and placed at the end of the * buffer. */ @@ -108,7 +108,7 @@ namespace tools { return EVARINT_REPRESENT; } - write |= static_cast(byte & 0x7f) << shift; /* Does the actualy placing into write, stripping the first bit */ + write |= static_cast(byte & 0x7f) << shift; /* Does the actually placing into write, stripping the first bit */ /* If there is no next */ if ((byte & 0x80) == 0) { diff --git a/src/Native/libcryptonote/contrib/epee/include/console_handler.h b/src/Native/libcryptonote/contrib/epee/include/console_handler.h index b8336b270..4ea3fa54b 100644 --- a/src/Native/libcryptonote/contrib/epee/include/console_handler.h +++ b/src/Native/libcryptonote/contrib/epee/include/console_handler.h @@ -27,6 +27,7 @@ #pragma once #include "misc_log_ex.h" +#include "string_tools.h" #include #include #include @@ -37,6 +38,8 @@ #include #endif #include +#include +#include #ifdef HAVE_READLINE #include "readline_buffer.h" @@ -456,29 +459,35 @@ namespace epee class command_handler { public: typedef boost::function &)> callback; - typedef std::map > lookup; + typedef std::map>> lookup; std::string get_usage() { std::stringstream ss; - size_t max_command_len = 0; - for(auto& x:m_command_handlers) - if(x.first.size() > max_command_len) - max_command_len = x.first.size(); for(auto& x:m_command_handlers) { - ss.width(max_command_len + 3); - ss << std::left << x.first << x.second.second << ENDL; + ss << x.second.second.first << ENDL; } return ss.str(); } - void set_handler(const std::string& cmd, const callback& hndlr, const std::string& usage = "") + std::pair get_documentation(const std::vector& cmd) + { + if(cmd.empty()) + return std::make_pair("", ""); + auto it = m_command_handlers.find(cmd.front()); + if(it == m_command_handlers.end()) + return std::make_pair("", ""); + return it->second.second; + } + + void set_handler(const std::string& cmd, const callback& hndlr, const std::string& usage = "", const std::string& description = "") { lookup::mapped_type & vt = m_command_handlers[cmd]; vt.first = hndlr; - vt.second = usage; + vt.second.first = description.empty() ? cmd : usage; + vt.second.second = description.empty() ? usage : description; #ifdef HAVE_READLINE rdln::readline_buffer::add_completion(cmd); #endif diff --git a/src/Native/libcryptonote/contrib/epee/include/copyable_atomic.h b/src/Native/libcryptonote/contrib/epee/include/copyable_atomic.h index 410b4b4ff..00a5f484b 100644 --- a/src/Native/libcryptonote/contrib/epee/include/copyable_atomic.h +++ b/src/Native/libcryptonote/contrib/epee/include/copyable_atomic.h @@ -35,6 +35,8 @@ namespace epee public: copyable_atomic() {}; + copyable_atomic(uint32_t value) + { store(value); } copyable_atomic(const copyable_atomic& a):std::atomic(a.load()) {} copyable_atomic& operator= (const copyable_atomic& a) diff --git a/src/Native/libcryptonote/contrib/epee/include/file_io_utils.h b/src/Native/libcryptonote/contrib/epee/include/file_io_utils.h index c387743a6..196610674 100644 --- a/src/Native/libcryptonote/contrib/epee/include/file_io_utils.h +++ b/src/Native/libcryptonote/contrib/epee/include/file_io_utils.h @@ -29,7 +29,33 @@ #define _FILE_IO_UTILS_H_ #include -#include +#include +#include +#ifdef WIN32 +#include +#endif + +// On Windows there is a problem with non-ASCII characters in path and file names +// as far as support by the standard components used is concerned: + +// The various file stream classes, e.g. std::ifstream and std::ofstream, are +// part of the GNU C++ Library / libstdc++. On the most basic level they use the +// fopen() call as defined / made accessible to programs compiled within MSYS2 +// by the stdio.h header file maintained by the MinGW project. + +// The critical point: The implementation of fopen() is part of MSVCRT, the +// Microsoft Visual C/C++ Runtime Library, and this method does NOT offer any +// Unicode support. + +// Monero code that would want to continue to use the normal file stream classes +// but WITH Unicode support could therefore not solve this problem on its own, +// but 2 different projects from 2 different maintaining groups would need changes +// in this particular direction - something probably difficult to achieve and +// with a long time to wait until all new versions / releases arrive. + +// Implemented solution approach: Circumvent the problem by stopping to use std +// file stream classes on Windows and directly use Unicode-capable WIN32 API +// calls. Most of the code doing so is concentrated in this header file here. namespace epee { @@ -45,7 +71,22 @@ namespace file_io_utils inline bool save_string_to_file(const std::string& path_to_file, const std::string& str) { - +#ifdef WIN32 + WCHAR wide_path[1000]; + int chars = MultiByteToWideChar(CP_UTF8, 0, path_to_file.c_str(), path_to_file.size() + 1, wide_path, 1000); + if (chars == 0) + return false; + HANDLE file_handle = CreateFileW(wide_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (file_handle == INVALID_HANDLE_VALUE) + return false; + DWORD bytes_written; + DWORD bytes_to_write = (DWORD)str.size(); + BOOL result = WriteFile(file_handle, str.data(), bytes_to_write, &bytes_written, NULL); + CloseHandle(file_handle); + if (bytes_written != bytes_to_write) + result = FALSE; + return result; +#else try { std::ofstream fstream; @@ -60,10 +101,11 @@ namespace file_io_utils { return false; } +#endif } inline - bool get_file_time(const std::string& path_to_file, OUT time_t& ft) + bool get_file_time(const std::string& path_to_file, time_t& ft) { boost::system::error_code ec; ft = boost::filesystem::last_write_time(boost::filesystem::path(path_to_file), ec); @@ -88,6 +130,27 @@ namespace file_io_utils inline bool load_file_to_string(const std::string& path_to_file, std::string& target_str) { +#ifdef WIN32 + WCHAR wide_path[1000]; + int chars = MultiByteToWideChar(CP_UTF8, 0, path_to_file.c_str(), path_to_file.size() + 1, wide_path, 1000); + if (chars == 0) + return false; + HANDLE file_handle = CreateFileW(wide_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (file_handle == INVALID_HANDLE_VALUE) + return false; + DWORD file_size = GetFileSize(file_handle, NULL); + if ((file_size == INVALID_FILE_SIZE) || (file_size > 1000000000)) { + CloseHandle(file_handle); + return false; + } + target_str.resize(file_size); + DWORD bytes_read; + BOOL result = ReadFile(file_handle, &target_str[0], file_size, &bytes_read, NULL); + CloseHandle(file_handle); + if (bytes_read != file_size) + result = FALSE; + return result; +#else try { std::ifstream fstream; @@ -112,11 +175,13 @@ namespace file_io_utils { return false; } +#endif } inline bool append_string_to_file(const std::string& path_to_file, const std::string& str) { + // No special Windows implementation because so far not used in Monero code try { std::ofstream fstream; @@ -132,6 +197,43 @@ namespace file_io_utils return false; } } + + inline + bool get_file_size(const std::string& path_to_file, uint64_t &size) + { +#ifdef WIN32 + WCHAR wide_path[1000]; + int chars = MultiByteToWideChar(CP_UTF8, 0, path_to_file.c_str(), path_to_file.size() + 1, wide_path, 1000); + if (chars == 0) + return false; + HANDLE file_handle = CreateFileW(wide_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (file_handle == INVALID_HANDLE_VALUE) + return false; + LARGE_INTEGER file_size; + BOOL result = GetFileSizeEx(file_handle, &file_size); + CloseHandle(file_handle); + if (result) { + size = file_size.QuadPart; + } + return size; +#else + try + { + std::ifstream fstream; + fstream.exceptions(std::ifstream::failbit | std::ifstream::badbit); + fstream.open(path_to_file, std::ios_base::binary | std::ios_base::in | std::ios::ate); + size = fstream.tellg(); + fstream.close(); + return true; + } + + catch(...) + { + return false; + } +#endif + } + } } diff --git a/src/Native/libcryptonote/contrib/epee/include/hex.h b/src/Native/libcryptonote/contrib/epee/include/hex.h index f8d6be048..e960da1d2 100644 --- a/src/Native/libcryptonote/contrib/epee/include/hex.h +++ b/src/Native/libcryptonote/contrib/epee/include/hex.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017, The Monero Project +// Copyright (c) 2017-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/contrib/epee/include/math_helper.h b/src/Native/libcryptonote/contrib/epee/include/math_helper.h index 90398acbb..ef839f609 100644 --- a/src/Native/libcryptonote/contrib/epee/include/math_helper.h +++ b/src/Native/libcryptonote/contrib/epee/include/math_helper.h @@ -37,6 +37,7 @@ #include #include "misc_os_dependent.h" +#include "syncobj.h" namespace epee { diff --git a/src/Native/libcryptonote/contrib/epee/include/memwipe.h b/src/Native/libcryptonote/contrib/epee/include/memwipe.h new file mode 100644 index 000000000..0d8f491b7 --- /dev/null +++ b/src/Native/libcryptonote/contrib/epee/include/memwipe.h @@ -0,0 +1,80 @@ +// Copyright (c) 2017-2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// 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. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#ifdef __cplusplus +#include + +extern "C" { +#endif + +void *memwipe(void *src, size_t n); + +#ifdef __cplusplus +} +#endif + +#ifdef __cplusplus +namespace tools { + + /// Scrubs data in the contained type upon destruction. + /// + /// Primarily useful for making sure that private keys don't stick around in + /// memory after the objects that held them have gone out of scope. + template + struct scrubbed : public T { + using type = T; + + ~scrubbed() { + scrub(); + } + + /// Destroy the contents of the contained type. + void scrub() { + static_assert(std::is_pod::value, + "T cannot be auto-scrubbed. T must be POD."); + static_assert(std::is_trivially_destructible::value, + "T cannot be auto-scrubbed. T must be trivially destructable."); + memwipe(this, sizeof(T)); + } + }; + + template + T& unwrap(scrubbed& src) { return src; } + + template + const T& unwrap(scrubbed const& src) { return src; } + + template + using scrubbed_arr = scrubbed>; +} // namespace tools + +#endif // __cplusplus diff --git a/src/Native/libcryptonote/contrib/epee/include/misc_os_dependent.h b/src/Native/libcryptonote/contrib/epee/include/misc_os_dependent.h index 69ded09e5..ffe575501 100644 --- a/src/Native/libcryptonote/contrib/epee/include/misc_os_dependent.h +++ b/src/Native/libcryptonote/contrib/epee/include/misc_os_dependent.h @@ -23,6 +23,10 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // +#ifdef _WIN32 +#include +#endif + #ifdef WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN @@ -42,6 +46,9 @@ #include #endif +#include +#include + #pragma once namespace epee { @@ -68,13 +75,13 @@ namespace misc_utils clock_get_time(cclock, &mts); mach_port_deallocate(mach_task_self(), cclock); - return (mts.tv_sec * 1000000000) + (mts.tv_nsec); + return ((uint64_t)mts.tv_sec * 1000000000) + (mts.tv_nsec); #else struct timespec ts; if(clock_gettime(CLOCK_MONOTONIC, &ts) != 0) { return 0; } - return (ts.tv_sec * 1000000000) + (ts.tv_nsec); + return ((uint64_t)ts.tv_sec * 1000000000) + (ts.tv_nsec); #endif } diff --git a/src/Native/libcryptonote/contrib/epee/include/net/abstract_tcp_server.h b/src/Native/libcryptonote/contrib/epee/include/net/abstract_tcp_server.h index 000305cfa..cbad1717c 100644 --- a/src/Native/libcryptonote/contrib/epee/include/net/abstract_tcp_server.h +++ b/src/Native/libcryptonote/contrib/epee/include/net/abstract_tcp_server.h @@ -305,7 +305,7 @@ namespace net_utils m_connections.back().powner = this; m_connections.back().m_self_it = --m_connections.end(); m_connections.back().m_context.m_remote_address = remote_address; - m_connections.back().m_htread = threads_helper::create_thread(ConnectionHandlerProc, &m_connections.back()); + m_connections.back().m_htread = threads_helper::create_thread(ConnectionHandlerProc, &m_connections.back()); // ugh, seems very risky return true; } diff --git a/src/Native/libcryptonote/contrib/epee/include/net/abstract_tcp_server2.h b/src/Native/libcryptonote/contrib/epee/include/net/abstract_tcp_server2.h index ca58d5467..ccde928ba 100644 --- a/src/Native/libcryptonote/contrib/epee/include/net/abstract_tcp_server2.h +++ b/src/Native/libcryptonote/contrib/epee/include/net/abstract_tcp_server2.h @@ -54,8 +54,8 @@ #include #include "net_utils_base.h" #include "syncobj.h" -#include "../../../../src/p2p/connection_basic.hpp" -#include "../../../../src/p2p/network_throttle-detail.hpp" +#include "connection_basic.hpp" +#include "network_throttle-detail.hpp" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "net" @@ -207,12 +207,18 @@ namespace net_utils bool connect(const std::string& adr, const std::string& port, uint32_t conn_timeot, t_connection_context& cn, const std::string& bind_ip = "0.0.0.0"); template - bool connect_async(const std::string& adr, const std::string& port, uint32_t conn_timeot, t_callback cb, const std::string& bind_ip = "0.0.0.0"); + bool connect_async(const std::string& adr, const std::string& port, uint32_t conn_timeot, const t_callback &cb, const std::string& bind_ip = "0.0.0.0"); typename t_protocol_handler::config_type& get_config_object(){return m_config;} int get_binded_port(){return m_port;} + long get_connections_count() const + { + auto connections_count = (m_sock_count > 0) ? (m_sock_count - 1) : 0; // Socket count minus listening socket + return connections_count; + } + boost::asio::io_service& get_io_service(){return io_service_;} struct idle_callback_conext_base @@ -281,8 +287,6 @@ namespace net_utils bool is_thread_worker(); - bool cleanup_connections(); - /// The io_service used to perform asynchronous operations. std::unique_ptr m_io_service_local_instance; boost::asio::io_service& io_service_; @@ -309,7 +313,7 @@ namespace net_utils connection_ptr new_connection_; boost::mutex connections_mutex; - std::deque> connections_; + std::set connections_; }; // class <>boosted_tcp_server diff --git a/src/Native/libcryptonote/contrib/epee/include/net/abstract_tcp_server2.inl b/src/Native/libcryptonote/contrib/epee/include/net/abstract_tcp_server2.inl index 61276e761..195ee2f0c 100644 --- a/src/Native/libcryptonote/contrib/epee/include/net/abstract_tcp_server2.inl +++ b/src/Native/libcryptonote/contrib/epee/include/net/abstract_tcp_server2.inl @@ -54,8 +54,6 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "net" -#define CONNECTION_CLEANUP_TIME 30 // seconds - PRAGMA_WARNING_PUSH namespace epee { @@ -82,7 +80,7 @@ PRAGMA_WARNING_DISABLE_VS(4355) m_throttle_speed_in("speed_in", "throttle_speed_in"), m_throttle_speed_out("speed_out", "throttle_speed_out") { - MINFO("test, connection constructor set m_connection_type="<(); - long ip_ = boost::asio::detail::socket_ops::host_to_network_long(remote_ep.address().to_v4().to_ulong()); + const unsigned long ip_{boost::asio::detail::socket_ops::host_to_network_long(remote_ep.address().to_v4().to_ulong())}; // create a random uuid boost::uuids::uuid random_uuid; // that stuff turns out to be included, even though it's from src... Taking advantage random_uuid = crypto::rand(); - context.set_details(random_uuid, new epee::net_utils::ipv4_network_address(ip_, remote_ep.port()), is_income); + context.set_details(random_uuid, epee::net_utils::ipv4_network_address(ip_, remote_ep.port()), is_income); _dbg3("[sock " << socket_.native_handle() << "] new connection from " << print_connection_context_short(context) << " to " << local_ep.address().to_string() << ':' << local_ep.port() << ", total sockets objects " << m_ref_sock_count); @@ -266,7 +264,7 @@ PRAGMA_WARNING_DISABLE_VS(4355) address = endpoint.address().to_string(); port = boost::lexical_cast(endpoint.port()); } - MINFO(" connection type " << to_string( m_connection_type ) << " " + MDEBUG(" connection type " << to_string( m_connection_type ) << " " << socket_.local_endpoint().address().to_string() << ":" << socket_.local_endpoint().port() << " <--> " << address << ":" << port); } @@ -288,7 +286,7 @@ PRAGMA_WARNING_DISABLE_VS(4355) { CRITICAL_REGION_LOCAL( epee::net_utils::network_throttle_manager::network_throttle_manager::m_lock_get_global_throttle_in ); - epee::net_utils::network_throttle_manager::network_throttle_manager::get_global_throttle_in().handle_trafic_exact(bytes_transferred * 1024); + epee::net_utils::network_throttle_manager::network_throttle_manager::get_global_throttle_in().handle_trafic_exact(bytes_transferred); } double delay=0; // will be calculated - how much we should sleep to obey speed limit etc @@ -299,7 +297,7 @@ PRAGMA_WARNING_DISABLE_VS(4355) { { //_scope_dbg1("CRITICAL_REGION_LOCAL"); CRITICAL_REGION_LOCAL( epee::net_utils::network_throttle_manager::m_lock_get_global_throttle_in ); - delay = epee::net_utils::network_throttle_manager::get_global_throttle_in().get_sleep_time_after_tick( bytes_transferred ); // decission from global throttle + delay = epee::net_utils::network_throttle_manager::get_global_throttle_in().get_sleep_time_after_tick( bytes_transferred ); } delay *= 0.5; @@ -484,9 +482,7 @@ PRAGMA_WARNING_DISABLE_VS(4355) //some data should be wrote to stream //request complete - if (speed_limit_is_enabled()) { - sleep_before_packet(cb, 1, 1); - } + // No sleeping here; sleeping is done once and for all in "handle_write" m_send_que_lock.lock(); // *** critical *** epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){m_send_que_lock.unlock();}); @@ -609,6 +605,7 @@ PRAGMA_WARNING_DISABLE_VS(4355) } logger_handle_net_write(cb); + // The single sleeping that is needed for correctly handling "out" speed throttling if (speed_limit_is_enabled()) { sleep_before_packet(cb, 1, 1); } @@ -738,7 +735,17 @@ PRAGMA_WARNING_DISABLE_VS(4355) boost::asio::placeholders::error)); return true; - CATCH_ENTRY_L0("boosted_tcp_server::init_server", false); + } + catch (const std::exception &e) + { + MFATAL("Error starting server: " << e.what()); + return false; + } + catch (...) + { + MFATAL("Error starting server"); + return false; + } } //----------------------------------------------------------------------------- PUSH_WARNINGS @@ -808,7 +815,6 @@ POP_WARNINGS m_threads_count = threads_count; m_main_thread_id = boost::this_thread::get_id(); MLOG_SET_THREAD_NAME("[SRV_MAIN]"); - add_idle_handler(boost::bind(&boosted_tcp_server::cleanup_connections, this), 5000); while(!m_stop_signal_sent) { @@ -898,7 +904,7 @@ POP_WARNINGS connections_mutex.lock(); for (auto &c: connections_) { - c.second->cancel(); + c->cancel(); } connections_.clear(); connections_mutex.unlock(); @@ -907,19 +913,6 @@ POP_WARNINGS } //--------------------------------------------------------------------------------- template - bool boosted_tcp_server::cleanup_connections() - { - connections_mutex.lock(); - boost::system_time cutoff = boost::get_system_time() - boost::posix_time::seconds(CONNECTION_CLEANUP_TIME); - while (!connections_.empty() && connections_.front().first < cutoff) - { - connections_.pop_front(); - } - connections_mutex.unlock(); - return true; - } - //--------------------------------------------------------------------------------- - template bool boosted_tcp_server::is_stop_signal_sent() { return m_stop_signal_sent; @@ -942,6 +935,9 @@ POP_WARNINGS boost::bind(&boosted_tcp_server::handle_accept, this, boost::asio::placeholders::error)); + boost::asio::socket_base::keep_alive opt(true); + conn->socket().set_option(opt); + conn->start(true, 1 < m_threads_count); conn->save_dbg_log(); }else @@ -958,9 +954,10 @@ POP_WARNINGS connection_ptr new_connection_l(new connection(io_service_, m_config, m_sock_count, m_sock_number, m_pfilter, m_connection_type) ); connections_mutex.lock(); - connections_.push_back(std::make_pair(boost::get_system_time(), new_connection_l)); + connections_.insert(new_connection_l); MDEBUG("connections_ size now " << connections_.size()); connections_mutex.unlock(); + epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ CRITICAL_REGION_LOCAL(connections_mutex); connections_.erase(new_connection_l); }); boost::asio::ip::tcp::socket& sock_ = new_connection_l->socket(); ////////////////////////////////////////////////////////////////////////// @@ -1038,6 +1035,10 @@ POP_WARNINGS _dbg3("Connected success to " << adr << ':' << port); + // start adds the connection to the config object's list, so we don't need to have it locally anymore + connections_mutex.lock(); + connections_.erase(new_connection_l); + connections_mutex.unlock(); bool r = new_connection_l->start(false, 1 < m_threads_count); if (r) { @@ -1057,14 +1058,15 @@ POP_WARNINGS } //--------------------------------------------------------------------------------- template template - bool boosted_tcp_server::connect_async(const std::string& adr, const std::string& port, uint32_t conn_timeout, t_callback cb, const std::string& bind_ip) + bool boosted_tcp_server::connect_async(const std::string& adr, const std::string& port, uint32_t conn_timeout, const t_callback &cb, const std::string& bind_ip) { TRY_ENTRY(); connection_ptr new_connection_l(new connection(io_service_, m_config, m_sock_count, m_sock_number, m_pfilter, m_connection_type) ); connections_mutex.lock(); - connections_.push_back(std::make_pair(boost::get_system_time(), new_connection_l)); + connections_.insert(new_connection_l); MDEBUG("connections_ size now " << connections_.size()); connections_mutex.unlock(); + epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ CRITICAL_REGION_LOCAL(connections_mutex); connections_.erase(new_connection_l); }); boost::asio::ip::tcp::socket& sock_ = new_connection_l->socket(); ////////////////////////////////////////////////////////////////////////// @@ -1113,6 +1115,11 @@ POP_WARNINGS { _dbg3("[sock " << new_connection_l->socket().native_handle() << "] Connected success to " << adr << ':' << port << " from " << lep.address().to_string() << ':' << lep.port()); + + // start adds the connection to the config object's list, so we don't need to have it locally anymore + connections_mutex.lock(); + connections_.erase(new_connection_l); + connections_mutex.unlock(); bool r = new_connection_l->start(false, 1 < m_threads_count); if (r) { diff --git a/src/Native/libcryptonote/contrib/epee/include/net/connection_basic.hpp b/src/Native/libcryptonote/contrib/epee/include/net/connection_basic.hpp new file mode 100644 index 000000000..095e747a5 --- /dev/null +++ b/src/Native/libcryptonote/contrib/epee/include/net/connection_basic.hpp @@ -0,0 +1,141 @@ +/// @file +/// @author rfree (current maintainer in monero.cc project) +/// @brief base for connection, contains e.g. the ratelimit hooks + +// ! This file might contain variable names same as in template class connection<> +// ! from files contrib/epee/include/net/abstract_tcp_server2.* +// ! I am not a lawyer; afaik APIs, var names etc are not copyrightable ;) +// ! (how ever if in some wonderful juristdictions that is not the case, then why not make another sub-class withat that members and licence it as epee part) +// ! Working on above premise, IF this is valid in your juristdictions, then consider this code as released as: + +// Copyright (c) 2014-2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// 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. + +/* rfree: place for hanlers for the non-template base, can be used by connection<> template class in abstract_tcp_server2 file */ + +#ifndef INCLUDED_p2p_connection_basic_hpp +#define INCLUDED_p2p_connection_basic_hpp + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "net/net_utils_base.h" +#include "syncobj.h" + +namespace epee +{ +namespace net_utils +{ + + /************************************************************************/ + /* */ + /************************************************************************/ + /// Represents a single connection from a client. + +class connection_basic_pimpl; // PIMPL for this class + + enum t_connection_type { // type of the connection (of this server), e.g. so that we will know how to limit it + e_connection_type_NET = 0, // default (not used?) + e_connection_type_RPC = 1, // the rpc commands (probably not rate limited, not chunked, etc) + e_connection_type_P2P = 2 // to other p2p node (probably limited) + }; + + std::string to_string(t_connection_type type); + +class connection_basic { // not-templated base class for rapid developmet of some code parts + public: + std::unique_ptr< connection_basic_pimpl > mI; // my Implementation + + // moved here from orginal connecton<> - common member variables that do not depend on template in connection<> + volatile uint32_t m_want_close_connection; + std::atomic m_was_shutdown; + critical_section m_send_que_lock; + std::list m_send_que; + volatile bool m_is_multithreaded; + double m_start_time; + /// Strand to ensure the connection's handlers are not called concurrently. + boost::asio::io_service::strand strand_; + /// Socket for the connection. + boost::asio::ip::tcp::socket socket_; + + std::atomic &m_ref_sock_count; // reference to external counter of existing sockets that we will ++/-- + public: + // first counter is the ++/-- count of current sockets, the other socket_number is only-increasing ++ number generator + connection_basic(boost::asio::io_service& io_service, std::atomic &ref_sock_count, std::atomic &sock_number); + + virtual ~connection_basic() noexcept(false); + + // various handlers to be called from connection class: + void do_send_handler_write(const void * ptr , size_t cb); + void do_send_handler_write_from_queue(const boost::system::error_code& e, size_t cb , int q_len); // from handle_write, sending next part + + void logger_handle_net_write(size_t size); // network data written + void logger_handle_net_read(size_t size); // network data read + + void set_start_time(); + + // config for rate limit + + static void set_rate_up_limit(uint64_t limit); + static void set_rate_down_limit(uint64_t limit); + static uint64_t get_rate_up_limit(); + static uint64_t get_rate_down_limit(); + + // config misc + static void set_tos_flag(int tos); // ToS / QoS flag + static int get_tos_flag(); + + // handlers and sleep + void sleep_before_packet(size_t packet_size, int phase, int q_len); // execute a sleep ; phase is not really used now(?) + static void save_limit_to_file(int limit); ///< for dr-monero + static double get_sleep_time(size_t cb); + + static void set_save_graph(bool save_graph); +}; + +} // nameserver +} // nameserver + +#endif + + diff --git a/src/Native/libcryptonote/contrib/epee/include/net/http_auth.h b/src/Native/libcryptonote/contrib/epee/include/net/http_auth.h index bf368e6f4..4324c41fd 100644 --- a/src/Native/libcryptonote/contrib/epee/include/net/http_auth.h +++ b/src/Native/libcryptonote/contrib/epee/include/net/http_auth.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -33,7 +33,7 @@ #include #include #include - +#include "wipeable_string.h" #include "http_base.h" #undef MONERO_DEFAULT_LOG_CATEGORY @@ -48,12 +48,12 @@ namespace net_utils struct login { login() : username(), password() {} - login(std::string username_, std::string password_) + login(std::string username_, wipeable_string password_) : username(std::move(username_)), password(std::move(password_)) {} std::string username; - std::string password; + wipeable_string password; }; //! Implements RFC 2617 digest auth. Digests from RFC 7616 can be added. @@ -71,8 +71,8 @@ namespace net_utils std::uint32_t counter; }; - http_server_auth() : user() {} - http_server_auth(login credentials); + http_server_auth() : user(), rng() {} + http_server_auth(login credentials, std::function r); //! \return Auth response, or `boost::none` iff `request` had valid auth. boost::optional get_response(const http_request_info& request) @@ -81,10 +81,13 @@ namespace net_utils return do_get_response(request); return boost::none; } + private: boost::optional do_get_response(const http_request_info& request); boost::optional user; + + std::function rng; }; //! Implements RFC 2617 digest auth. Digests from RFC 7616 can be added. diff --git a/src/Native/libcryptonote/contrib/epee/include/net/http_base.h b/src/Native/libcryptonote/contrib/epee/include/net/http_base.h index e5aa06cb4..a66fb7c23 100644 --- a/src/Native/libcryptonote/contrib/epee/include/net/http_base.h +++ b/src/Native/libcryptonote/contrib/epee/include/net/http_base.h @@ -46,6 +46,7 @@ namespace net_utils { enum http_method{ + http_method_options, http_method_get, http_method_post, http_method_put, @@ -115,6 +116,7 @@ namespace net_utils std::string m_host; //"Host:" std::string m_cookie; //"Cookie:" std::string m_user_agent; //"User-Agent:" + std::string m_origin; //"Origin:" fields_list m_etc_fields; void clear() @@ -128,6 +130,7 @@ namespace net_utils m_host.clear(); m_cookie.clear(); m_user_agent.clear(); + m_origin.clear(); m_etc_fields.clear(); } }; @@ -155,7 +158,8 @@ namespace net_utils http_request_info():m_http_method(http_method_unknown), m_http_ver_hi(0), m_http_ver_lo(0), - m_have_to_block(false) + m_have_to_block(false), + m_full_request_buf_size(0) {} http_method m_http_method; diff --git a/src/Native/libcryptonote/contrib/epee/include/net/http_client.h b/src/Native/libcryptonote/contrib/epee/include/net/http_client.h index 8e099e2bc..1edf65928 100644 --- a/src/Native/libcryptonote/contrib/epee/include/net/http_client.h +++ b/src/Native/libcryptonote/contrib/epee/include/net/http_client.h @@ -27,6 +27,7 @@ #pragma once +#include #include #include #include @@ -50,6 +51,7 @@ #include "http_auth.h" #include "to_nonconst_iterator.h" #include "net_parse_helpers.h" +#include "syncobj.h" //#include "shlwapi.h" @@ -66,8 +68,6 @@ namespace epee namespace net_utils { -using namespace std; - /*struct url { public: @@ -237,7 +237,8 @@ using namespace std; namespace http { - class http_simple_client: public i_target_handler + template + class http_simple_client_template: public i_target_handler { private: enum reciev_machine_state @@ -260,7 +261,7 @@ using namespace std; }; - blocked_mode_client m_net_client; + net_client_type m_net_client; std::string m_host_buff; std::string m_port; http_client_auth m_auth; @@ -274,9 +275,10 @@ using namespace std; chunked_state m_chunked_state; std::string m_chunked_cache; critical_section m_lock; + bool m_ssl; public: - explicit http_simple_client() + explicit http_simple_client_template() : i_target_handler() , m_net_client() , m_host_buff() @@ -291,33 +293,35 @@ using namespace std; , m_chunked_state() , m_chunked_cache() , m_lock() + , m_ssl(false) {} const std::string &get_host() const { return m_host_buff; }; const std::string &get_port() const { return m_port; }; - bool set_server(const std::string& address, boost::optional user) + bool set_server(const std::string& address, boost::optional user, bool ssl = false) { http::url_content parsed{}; const bool r = parse_url(address, parsed); CHECK_AND_ASSERT_MES(r, false, "failed to parse url: " << address); - set_server(std::move(parsed.host), std::to_string(parsed.port), std::move(user)); + set_server(std::move(parsed.host), std::to_string(parsed.port), std::move(user), ssl); return true; } - void set_server(std::string host, std::string port, boost::optional user) + void set_server(std::string host, std::string port, boost::optional user, bool ssl = false) { CRITICAL_REGION_LOCAL(m_lock); disconnect(); m_host_buff = std::move(host); m_port = std::move(port); m_auth = user ? http_client_auth{std::move(*user)} : http_client_auth{}; + m_ssl = ssl; } bool connect(std::chrono::milliseconds timeout) { CRITICAL_REGION_LOCAL(m_lock); - return m_net_client.connect(m_host_buff, m_port, timeout); + return m_net_client.connect(m_host_buff, m_port, timeout, m_ssl); } //--------------------------------------------------------------------------- bool disconnect() @@ -392,7 +396,6 @@ using namespace std; res = m_net_client.send(body, timeout); CHECK_AND_ASSERT_MES(res, false, "HTTP_CLIENT: Failed to SEND"); - m_response_info.clear(); m_state = reciev_machine_state_header; if (!handle_reciev(timeout)) @@ -427,6 +430,15 @@ using namespace std; CRITICAL_REGION_LOCAL(m_lock); return invoke(uri, "POST", body, timeout, ppresponse_info, additional_params); } + //--------------------------------------------------------------------------- + bool test(const std::string &s, std::chrono::milliseconds timeout) // TEST FUNC ONLY + { + CRITICAL_REGION_LOCAL(m_lock); + m_net_client.set_test_data(s); + m_state = reciev_machine_state_header; + return handle_reciev(timeout); + } + //--------------------------------------------------------------------------- private: //--------------------------------------------------------------------------- inline bool handle_reciev(std::chrono::milliseconds timeout) @@ -741,85 +753,107 @@ using namespace std; return true; } //--------------------------------------------------------------------------- - inline - bool parse_header(http_header_info& body_info, const std::string& m_cache_to_process) - { + inline bool parse_header(http_header_info& body_info, const std::string& m_cache_to_process) + { MTRACE("http_stream_filter::parse_cached_header(*)"); - - STATIC_REGEXP_EXPR_1(rexp_mach_field, - "\n?((Connection)|(Referer)|(Content-Length)|(Content-Type)|(Transfer-Encoding)|(Content-Encoding)|(Host)|(Cookie)|(User-Agent)" - // 12 3 4 5 6 7 8 9 10 - "|([\\w-]+?)) ?: ?((.*?)(\r?\n))[^\t ]", - //11 1213 14 - boost::regex::icase | boost::regex::normal); - - boost::smatch result; - std::string::const_iterator it_current_bound = m_cache_to_process.begin(); - std::string::const_iterator it_end_bound = m_cache_to_process.end(); - - - //lookup all fields and fill well-known fields - while( boost::regex_search( it_current_bound, it_end_bound, result, rexp_mach_field, boost::match_default) && result[0].matched) + const char *ptr = m_cache_to_process.c_str(); + while (ptr[0] != '\r' || ptr[1] != '\n') { - const size_t field_val = 13; - //const size_t field_etc_name = 11; - - int i = 2; //start position = 2 - if(result[i++].matched)//"Connection" - body_info.m_connection = result[field_val]; - else if(result[i++].matched)//"Referrer" - body_info.m_referer = result[field_val]; - else if(result[i++].matched)//"Content-Length" - body_info.m_content_length = result[field_val]; - else if(result[i++].matched)//"Content-Type" - body_info.m_content_type = result[field_val]; - else if(result[i++].matched)//"Transfer-Encoding" - body_info.m_transfer_encoding = result[field_val]; - else if(result[i++].matched)//"Content-Encoding" - body_info.m_content_encoding = result[field_val]; - else if(result[i++].matched)//"Host" - { body_info.m_host = result[field_val]; - string_tools::trim(body_info.m_host); + // optional \n + if (*ptr == '\n') + ++ptr; + // an identifier composed of letters or - + const char *key_pos = ptr; + while (isalnum(*ptr) || *ptr == '_' || *ptr == '-') + ++ptr; + const char *key_end = ptr; + // optional space (not in RFC, but in previous code) + if (*ptr == ' ') + ++ptr; + CHECK_AND_ASSERT_MES(*ptr == ':', true, "http_stream_filter::parse_cached_header() invalid header in: " << m_cache_to_process); + ++ptr; + // optional whitespace, but not newlines - line folding is obsolete, let's ignore it + while (isblank(*ptr)) + ++ptr; + const char *value_pos = ptr; + while (*ptr != '\r' && *ptr != '\n') + ++ptr; + const char *value_end = ptr; + // optional trailing whitespace + while (value_end > value_pos && isblank(*(value_end-1))) + --value_end; + if (*ptr == '\r') + ++ptr; + CHECK_AND_ASSERT_MES(*ptr == '\n', true, "http_stream_filter::parse_cached_header() invalid header in: " << m_cache_to_process); + ++ptr; + + const std::string key = std::string(key_pos, key_end - key_pos); + const std::string value = std::string(value_pos, value_end - value_pos); + if (!key.empty()) + { + if (!string_tools::compare_no_case(key, "Connection")) + body_info.m_connection = value; + else if(!string_tools::compare_no_case(key, "Referrer")) + body_info.m_referer = value; + else if(!string_tools::compare_no_case(key, "Content-Length")) + body_info.m_content_length = value; + else if(!string_tools::compare_no_case(key, "Content-Type")) + body_info.m_content_type = value; + else if(!string_tools::compare_no_case(key, "Transfer-Encoding")) + body_info.m_transfer_encoding = value; + else if(!string_tools::compare_no_case(key, "Content-Encoding")) + body_info.m_content_encoding = value; + else if(!string_tools::compare_no_case(key, "Host")) + body_info.m_host = value; + else if(!string_tools::compare_no_case(key, "Cookie")) + body_info.m_cookie = value; + else if(!string_tools::compare_no_case(key, "User-Agent")) + body_info.m_user_agent = value; + else if(!string_tools::compare_no_case(key, "Origin")) + body_info.m_origin = value; + else + body_info.m_etc_fields.emplace_back(key, value); } - else if(result[i++].matched)//"Cookie" - body_info.m_cookie = result[field_val]; - else if(result[i++].matched)//"User-Agent" - body_info.m_user_agent = result[field_val]; - else if(result[i++].matched)//e.t.c (HAVE TO BE MATCHED!) - body_info.m_etc_fields.emplace_back(result[11], result[field_val]); - else - {CHECK_AND_ASSERT_MES(false, false, "http_stream_filter::parse_cached_header() not matched last entry in:"<(result[1]); - m_response_info.m_http_ver_lo = boost::lexical_cast(result[2]); - m_response_info.m_response_code = boost::lexical_cast(result[3]); - - m_header_cache.erase(to_nonsonst_iterator(m_header_cache, result[0].first), to_nonsonst_iterator(m_header_cache, result[0].second)); - return true; - }else - { - LOG_ERROR("http_stream_filter::handle_invoke_reply_line(): Failed to match first response line:" << m_header_cache); - return false; - } - + //First line response, look like this: "HTTP/1.1 200 OK" + const char *ptr = m_header_cache.c_str(); + CHECK_AND_ASSERT_MES(!memcmp(ptr, "HTTP/", 5), false, "Invalid first response line: " + m_header_cache); + ptr += 5; + CHECK_AND_ASSERT_MES(isdigit(*ptr), false, "Invalid first response line: " + m_header_cache); + unsigned long ul; + char *end; + ul = strtoul(ptr, &end, 10); + CHECK_AND_ASSERT_MES(ul <= INT_MAX && *end =='.', false, "Invalid first response line: " + m_header_cache); + m_response_info.m_http_ver_hi = ul; + ptr = end + 1; + CHECK_AND_ASSERT_MES(isdigit(*ptr), false, "Invalid first response line: " + m_header_cache + ", ptr: " << ptr); + ul = strtoul(ptr, &end, 10); + CHECK_AND_ASSERT_MES(ul <= INT_MAX && isblank(*end), false, "Invalid first response line: " + m_header_cache + ", ptr: " << ptr); + m_response_info.m_http_ver_lo = ul; + ptr = end + 1; + while (isblank(*ptr)) + ++ptr; + CHECK_AND_ASSERT_MES(isdigit(*ptr), false, "Invalid first response line: " + m_header_cache); + ul = strtoul(ptr, &end, 10); + CHECK_AND_ASSERT_MES(ul >= 100 && ul <= 999 && isspace(*end), false, "Invalid first response line: " + m_header_cache); + m_response_info.m_response_code = ul; + ptr = end; + // ignore the optional text, till the end + while (*ptr != '\r' && *ptr != '\n') + ++ptr; + if (*ptr == '\r') + ++ptr; + CHECK_AND_ASSERT_MES(*ptr == '\n', false, "Invalid first response line: " << m_header_cache); + ++ptr; + + m_header_cache.erase(0, ptr - m_header_cache.c_str()); + return true; } inline bool set_reply_content_encoder() @@ -954,6 +988,7 @@ using namespace std; return true; } }; + typedef http_simple_client_template http_simple_client; } } } diff --git a/src/Native/libcryptonote/contrib/epee/include/net/http_client_base.h b/src/Native/libcryptonote/contrib/epee/include/net/http_client_base.h index f5fb57d03..c3da28718 100644 --- a/src/Native/libcryptonote/contrib/epee/include/net/http_client_base.h +++ b/src/Native/libcryptonote/contrib/epee/include/net/http_client_base.h @@ -38,8 +38,8 @@ namespace epee virtual ~i_sub_handler(){} virtual bool update_in( std::string& piece_of_transfer)=0; - virtual void stop(std::string& OUT collect_remains)=0; - virtual bool update_and_stop(std::string& OUT collect_remains, bool& is_changed) + virtual void stop(std::string& collect_remains)=0; + virtual bool update_and_stop(std::string& collect_remains, bool& is_changed) { is_changed = true; bool res = this->update_in(collect_remains); @@ -66,7 +66,7 @@ namespace epee { return m_powner_filter->handle_target_data(piece_of_transfer); } - virtual void stop(std::string& OUT collect_remains) + virtual void stop(std::string& collect_remains) { } diff --git a/src/Native/libcryptonote/contrib/epee/include/net/http_protocol_handler.h b/src/Native/libcryptonote/contrib/epee/include/net/http_protocol_handler.h index babe49ad7..b4485d1cd 100644 --- a/src/Native/libcryptonote/contrib/epee/include/net/http_protocol_handler.h +++ b/src/Native/libcryptonote/contrib/epee/include/net/http_protocol_handler.h @@ -54,6 +54,7 @@ namespace net_utils struct http_server_config { std::string m_folder; + std::vector m_access_control_origins; boost::optional m_user; critical_section m_lock; }; @@ -159,6 +160,7 @@ namespace net_utils struct custum_handler_config: public http_server_config { i_http_server_handler* m_phandler; + std::function rng; }; /************************************************************************/ @@ -175,7 +177,7 @@ namespace net_utils : simple_http_connection_handler(psnd_hndlr, config), m_config(config), m_conn_context(conn_context), - m_auth(m_config.m_user ? http_server_auth{*m_config.m_user} : http_server_auth{}) + m_auth(m_config.m_user ? http_server_auth{*m_config.m_user, config.rng} : http_server_auth{}) {} inline bool handle_request(const http_request_info& query_info, http_response_info& response) { @@ -193,6 +195,7 @@ namespace net_utils response.m_response_code = 200; response.m_response_comment = "OK"; response.m_body.clear(); + return m_config.m_phandler->handle_http_request(query_info, response, m_conn_context); } diff --git a/src/Native/libcryptonote/contrib/epee/include/net/http_protocol_handler.inl b/src/Native/libcryptonote/contrib/epee/include/net/http_protocol_handler.inl index c92a13bcc..c18f7f706 100644 --- a/src/Native/libcryptonote/contrib/epee/include/net/http_protocol_handler.inl +++ b/src/Native/libcryptonote/contrib/epee/include/net/http_protocol_handler.inl @@ -316,7 +316,10 @@ namespace net_utils CHECK_AND_ASSERT_MES(result[0].matched, false, "simple_http_connection_handler::analize_http_method() assert failed..."); http_ver_major = boost::lexical_cast(result[11]); http_ver_minor = boost::lexical_cast(result[12]); - if(result[4].matched) + + if(result[3].matched) + method = http::http_method_options; + else if(result[4].matched) method = http::http_method_get; else if(result[5].matched) method = http::http_method_head; @@ -342,7 +345,12 @@ namespace net_utils { analize_http_method(result, m_query_info.m_http_method, m_query_info.m_http_ver_hi, m_query_info.m_http_ver_hi); m_query_info.m_URI = result[10]; - parse_uri(m_query_info.m_URI, m_query_info.m_uri_content); + if (!parse_uri(m_query_info.m_URI, m_query_info.m_uri_content)) + { + m_state = http_state_error; + MERROR("Failed to parse URI: m_query_info.m_URI"); + return false; + } m_query_info.m_http_method_str = result[2]; m_query_info.m_full_request_str = result[0]; @@ -472,8 +480,8 @@ namespace net_utils bool simple_http_connection_handler::parse_cached_header(http_header_info& body_info, const std::string& m_cache_to_process, size_t pos) { STATIC_REGEXP_EXPR_1(rexp_mach_field, - "\n?((Connection)|(Referer)|(Content-Length)|(Content-Type)|(Transfer-Encoding)|(Content-Encoding)|(Host)|(Cookie)|(User-Agent)" - // 12 3 4 5 6 7 8 9 10 + "\n?((Connection)|(Referer)|(Content-Length)|(Content-Type)|(Transfer-Encoding)|(Content-Encoding)|(Host)|(Cookie)|(User-Agent)|(Origin)" + // 12 3 4 5 6 7 8 9 10 11 "|([\\w-]+?)) ?: ?((.*?)(\r?\n))[^\t ]", //11 1213 14 boost::regex::icase | boost::regex::normal); @@ -487,8 +495,8 @@ namespace net_utils //lookup all fields and fill well-known fields while( boost::regex_search( it_current_bound, it_end_bound, result, rexp_mach_field, boost::match_default) && result[0].matched) { - const size_t field_val = 13; - const size_t field_etc_name = 11; + const size_t field_val = 14; + const size_t field_etc_name = 12; int i = 2; //start position = 2 if(result[i++].matched)//"Connection" @@ -509,6 +517,8 @@ namespace net_utils body_info.m_cookie = result[field_val]; else if(result[i++].matched)//"User-Agent" body_info.m_user_agent = result[field_val]; + else if(result[i++].matched)//"Origin" + body_info.m_origin = result[field_val]; else if(result[i++].matched)//e.t.c (HAVE TO BE MATCHED!) body_info.m_etc_fields.push_back(std::pair(result[field_etc_name], result[field_val])); else @@ -537,17 +547,27 @@ namespace net_utils template bool simple_http_connection_handler::handle_request_and_send_response(const http::http_request_info& query_info) { - http_response_info response; - bool res = handle_request(query_info, response); + http_response_info response{}; //CHECK_AND_ASSERT_MES(res, res, "handle_request(query_info, response) returned false" ); + bool res = true; + + if (query_info.m_http_method != http::http_method_options) + { + res = handle_request(query_info, response); + } + else + { + response.m_response_code = 200; + response.m_response_comment = "OK"; + } std::string response_data = get_response_header(response); - //LOG_PRINT_L0("HTTP_SEND: << \r\n" << response_data + response.m_body); + LOG_PRINT_L3("HTTP_RESPONSE_HEAD: << \r\n" << response_data); m_psnd_hndlr->do_send((void*)response_data.data(), response_data.size()); - if(response.m_body.size() && (query_info.m_http_method != http::http_method_head)) + if ((response.m_body.size() && (query_info.m_http_method != http::http_method_head)) || (query_info.m_http_method == http::http_method_options)) m_psnd_hndlr->do_send((void*)response.m_body.data(), response.m_body.size()); return res; } @@ -579,7 +599,6 @@ namespace net_utils response.m_response_comment = "OK"; response.m_mime_tipe = get_file_mime_tipe(uri_to_path); - return true; } //----------------------------------------------------------------------------------- @@ -591,8 +610,12 @@ namespace net_utils "Server: Epee-based\r\n" "Content-Length: "; buf += boost::lexical_cast(response.m_body.size()) + "\r\n"; - buf += "Content-Type: "; - buf += response.m_mime_tipe + "\r\n"; + + if(!response.m_mime_tipe.empty()) + { + buf += "Content-Type: "; + buf += response.m_mime_tipe + "\r\n"; + } buf += "Last-Modified: "; time_t tm; @@ -612,6 +635,22 @@ namespace net_utils m_want_close = true; } } + + // Cross-origin resource sharing + if(m_query_info.m_header_info.m_origin.size()) + { + if (std::binary_search(m_config.m_access_control_origins.begin(), m_config.m_access_control_origins.end(), m_query_info.m_header_info.m_origin)) + { + buf += "Access-Control-Allow-Origin: "; + buf += m_query_info.m_header_info.m_origin; + buf += "\r\n"; + buf += "Access-Control-Expose-Headers: www-authenticate\r\n"; + if (m_query_info.m_http_method == http::http_method_options) + buf += "Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With\r\n"; + buf += "Access-Control-Allow-Methods: POST, PUT, GET, OPTIONS\r\n"; + } + } + //add additional fields, if it is for(fields_list::const_iterator it = response.m_additional_fields.begin(); it!=response.m_additional_fields.end(); it++) buf += it->first + ":" + it->second + "\r\n"; diff --git a/src/Native/libcryptonote/contrib/epee/include/net/http_server_impl_base.h b/src/Native/libcryptonote/contrib/epee/include/net/http_server_impl_base.h index acecbb2d4..1a97e610a 100644 --- a/src/Native/libcryptonote/contrib/epee/include/net/http_server_impl_base.h +++ b/src/Native/libcryptonote/contrib/epee/include/net/http_server_impl_base.h @@ -55,16 +55,22 @@ namespace epee : m_net_server(external_io_service) {} - bool init(const std::string& bind_port = "0", const std::string& bind_ip = "0.0.0.0", + bool init(std::function rng, const std::string& bind_port = "0", const std::string& bind_ip = "0.0.0.0", + std::vector access_control_origins = std::vector(), boost::optional user = boost::none) { //set self as callback handler m_net_server.get_config_object().m_phandler = static_cast(this); + m_net_server.get_config_object().rng = std::move(rng); //here set folder for hosting reqests m_net_server.get_config_object().m_folder = ""; + //set access control allow origins if configured + std::sort(access_control_origins.begin(), access_control_origins.end()); + m_net_server.get_config_object().m_access_control_origins = std::move(access_control_origins); + m_net_server.get_config_object().m_user = std::move(user); MGINFO("Binding on " << bind_ip << ":" << bind_port); @@ -112,6 +118,11 @@ namespace epee return m_net_server.get_binded_port(); } + long get_connections_count() const + { + return m_net_server.get_connections_count(); + } + protected: net_utils::boosted_tcp_server > m_net_server; }; diff --git a/src/Native/libcryptonote/contrib/epee/include/net/levin_base.h b/src/Native/libcryptonote/contrib/epee/include/net/levin_base.h index d630bff19..7d060f5ef 100644 --- a/src/Native/libcryptonote/contrib/epee/include/net/levin_base.h +++ b/src/Native/libcryptonote/contrib/epee/include/net/levin_base.h @@ -87,6 +87,7 @@ namespace levin virtual void on_connection_new(t_connection_context& context){}; virtual void on_connection_close(t_connection_context& context){}; + virtual ~levin_commands_handler(){} }; #define LEVIN_OK 0 diff --git a/src/Native/libcryptonote/contrib/epee/include/net/levin_client_async.h b/src/Native/libcryptonote/contrib/epee/include/net/levin_client_async.h index 337d345c4..6c8f9bcb3 100644 --- a/src/Native/libcryptonote/contrib/epee/include/net/levin_client_async.h +++ b/src/Native/libcryptonote/contrib/epee/include/net/levin_client_async.h @@ -52,6 +52,7 @@ namespace levin class levin_client_async { levin_commands_handler* m_pcommands_handler; + void (*commands_handler_destroy)(levin_commands_handler*); volatile uint32_t m_is_stop; volatile uint32_t m_threads_count; ::critical_section m_send_lock; @@ -85,9 +86,9 @@ namespace levin ::critical_section m_connection_lock; net_utils::blocked_mode_client m_transport; public: - levin_client_async():m_pcommands_handler(NULL), m_is_stop(0), m_threads_count(0), m_invoke_data_ready(0), m_invoke_is_active(0) + levin_client_async():m_pcommands_handler(NULL), commands_handler_destroy(NULL), m_is_stop(0), m_threads_count(0), m_invoke_data_ready(0), m_invoke_is_active(0) {} - levin_client_async(const levin_client_async& /*v*/):m_pcommands_handler(NULL), m_is_stop(0), m_threads_count(0), m_invoke_data_ready(0), m_invoke_is_active(0) + levin_client_async(const levin_client_async& /*v*/):m_pcommands_handler(NULL), commands_handler_destroy(NULL), m_is_stop(0), m_threads_count(0), m_invoke_data_ready(0), m_invoke_is_active(0) {} ~levin_client_async() { @@ -97,11 +98,16 @@ namespace levin while(boost::interprocess::ipcdetail::atomic_read32(&m_threads_count)) ::Sleep(100); + + set_handler(NULL); } - void set_handler(levin_commands_handler* phandler) + void set_handler(levin_commands_handler* phandler, void (*destroy)(levin_commands_handler*) = NULL) { + if (commands_handler_destroy && m_pcommands_handler) + (*commands_handler_destroy)(m_pcommands_handler); m_pcommands_handler = phandler; + m_pcommands_handler_destroy = destroy; } bool connect(uint32_t ip, uint32_t port, uint32_t timeout) diff --git a/src/Native/libcryptonote/contrib/epee/include/net/levin_protocol_handler.h b/src/Native/libcryptonote/contrib/epee/include/net/levin_protocol_handler.h index fbc9727e2..b3a75bedc 100644 --- a/src/Native/libcryptonote/contrib/epee/include/net/levin_protocol_handler.h +++ b/src/Native/libcryptonote/contrib/epee/include/net/levin_protocol_handler.h @@ -43,6 +43,8 @@ namespace levin struct protocl_handler_config { levin_commands_handler* m_pcommands_handler; + void (*m_pcommands_handler_destroy)(levin_commands_handler*); + ~protocl_handler_config() { if (m_pcommands_handler && m_pcommands_handler_destroy) (*m_pcommands_handler_destroy)(m_pcommands_handler); } }; template diff --git a/src/Native/libcryptonote/contrib/epee/include/net/levin_protocol_handler_async.h b/src/Native/libcryptonote/contrib/epee/include/net/levin_protocol_handler_async.h index 60a667690..0b1fe05fa 100644 --- a/src/Native/libcryptonote/contrib/epee/include/net/levin_protocol_handler_async.h +++ b/src/Native/libcryptonote/contrib/epee/include/net/levin_protocol_handler_async.h @@ -35,6 +35,8 @@ #include "levin_base.h" #include "misc_language.h" +#include "syncobj.h" +#include "misc_os_dependent.h" #include #include @@ -72,29 +74,36 @@ class async_protocol_handler_config friend class async_protocol_handler; + levin_commands_handler* m_pcommands_handler; + void (*m_pcommands_handler_destroy)(levin_commands_handler*); + + void delete_connections (size_t count, bool incoming); + public: typedef t_connection_context connection_context; - levin_commands_handler* m_pcommands_handler; uint64_t m_max_packet_size; uint64_t m_invoke_timeout; int invoke(int command, const std::string& in_buff, std::string& buff_out, boost::uuids::uuid connection_id); template - int invoke_async(int command, const std::string& in_buff, boost::uuids::uuid connection_id, callback_t cb, size_t timeout = LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED); + int invoke_async(int command, const std::string& in_buff, boost::uuids::uuid connection_id, const callback_t &cb, size_t timeout = LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED); int notify(int command, const std::string& in_buff, boost::uuids::uuid connection_id); bool close(boost::uuids::uuid connection_id); bool update_connection_context(const t_connection_context& contxt); bool request_callback(boost::uuids::uuid connection_id); template - bool foreach_connection(callback_t cb); + bool foreach_connection(const callback_t &cb); template - bool for_connection(const boost::uuids::uuid &connection_id, callback_t cb); + bool for_connection(const boost::uuids::uuid &connection_id, const callback_t &cb); size_t get_connections_count(); + void set_handler(levin_commands_handler* handler, void (*destroy)(levin_commands_handler*) = NULL); - async_protocol_handler_config():m_pcommands_handler(NULL), m_max_packet_size(LEVIN_DEFAULT_MAX_PACKET_SIZE) + async_protocol_handler_config():m_pcommands_handler(NULL), m_pcommands_handler_destroy(NULL), m_max_packet_size(LEVIN_DEFAULT_MAX_PACKET_SIZE) {} + ~async_protocol_handler_config() { set_handler(NULL, NULL); } void del_out_connections(size_t count); + void del_in_connections(size_t count); }; @@ -239,7 +248,7 @@ class async_protocol_handler std::list > m_invoke_response_handlers; template - bool add_invoke_response_handler(callback_t cb, uint64_t timeout, async_protocol_handler& con, int command) + bool add_invoke_response_handler(const callback_t &cb, uint64_t timeout, async_protocol_handler& con, int command) { CRITICAL_REGION_LOCAL(m_invoke_response_handlers_lock); boost::shared_ptr handler(boost::make_shared>(cb, timeout, con, command)); @@ -373,12 +382,16 @@ class async_protocol_handler if(m_cache_in_buffer.size() < m_current_head.m_cb) { is_continue = false; - if(cb >= MIN_BYTES_WANTED && !m_invoke_response_handlers.empty()) + if(cb >= MIN_BYTES_WANTED) { - //async call scenario - boost::shared_ptr response_handler = m_invoke_response_handlers.front(); - response_handler->reset_timer(); - MDEBUG(m_connection_context << "LEVIN_PACKET partial msg received. len=" << cb); + CRITICAL_REGION_LOCAL(m_invoke_response_handlers_lock); + if (!m_invoke_response_handlers.empty()) + { + //async call scenario + boost::shared_ptr response_handler = m_invoke_response_handlers.front(); + response_handler->reset_timer(); + MDEBUG(m_connection_context << "LEVIN_PACKET partial msg received. len=" << cb); + } } break; } @@ -519,7 +532,7 @@ class async_protocol_handler } template - bool async_invoke(int command, const std::string& in_buff, callback_t cb, size_t timeout = LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED) + bool async_invoke(int command, const std::string& in_buff, const callback_t &cb, size_t timeout = LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED) { misc_utils::auto_scope_leave_caller scope_exit_handler = misc_utils::create_scope_leave_handler( boost::bind(&async_protocol_handler::finish_outer_call, this)); @@ -721,32 +734,50 @@ void async_protocol_handler_config::del_connection(async_p } //------------------------------------------------------------------------------------------ template +void async_protocol_handler_config::delete_connections(size_t count, bool incoming) +{ + std::vector connections; + CRITICAL_REGION_BEGIN(m_connects_lock); + for (auto& c: m_connects) + { + if (c.second->m_connection_context.m_is_income == incoming) + connections.push_back(c.first); + } + + // close random connections from the provided set + // TODO or better just keep removing random elements (performance) + unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); + shuffle(connections.begin(), connections.end(), std::default_random_engine(seed)); + while (count > 0 && connections.size() > 0) + { + try + { + auto i = connections.end() - 1; + async_protocol_handler *conn = m_connects.at(*i); + del_connection(conn); + close(*i); + connections.erase(i); + } + catch (const std::out_of_range &e) + { + MWARNING("Connection not found in m_connects, continuing"); + } + --count; + } + + CRITICAL_REGION_END(); +} +//------------------------------------------------------------------------------------------ +template void async_protocol_handler_config::del_out_connections(size_t count) { - std::vector out_connections; - CRITICAL_REGION_BEGIN(m_connects_lock); - for (auto& c: m_connects) - { - if (!c.second->m_connection_context.m_is_income) - out_connections.push_back(c.first); - } - - if (out_connections.size() == 0) - return; - - // close random out connections - // TODO or better just keep removing random elements (performance) - unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); - shuffle(out_connections.begin(), out_connections.end(), std::default_random_engine(seed)); - while (count > 0 && out_connections.size() > 0) - { - close(*out_connections.begin()); - del_connection(m_connects.at(*out_connections.begin())); - out_connections.erase(out_connections.begin()); - --count; - } - - CRITICAL_REGION_END(); + delete_connections(count, false); +} +//------------------------------------------------------------------------------------------ +template +void async_protocol_handler_config::del_in_connections(size_t count) +{ + delete_connections(count, true); } //------------------------------------------------------------------------------------------ template @@ -786,7 +817,7 @@ int async_protocol_handler_config::invoke(int command, con } //------------------------------------------------------------------------------------------ template template -int async_protocol_handler_config::invoke_async(int command, const std::string& in_buff, boost::uuids::uuid connection_id, callback_t cb, size_t timeout) +int async_protocol_handler_config::invoke_async(int command, const std::string& in_buff, boost::uuids::uuid connection_id, const callback_t &cb, size_t timeout) { async_protocol_handler* aph; int r = find_and_lock_connection(connection_id, aph); @@ -794,7 +825,7 @@ int async_protocol_handler_config::invoke_async(int comman } //------------------------------------------------------------------------------------------ template template -bool async_protocol_handler_config::foreach_connection(callback_t cb) +bool async_protocol_handler_config::foreach_connection(const callback_t &cb) { CRITICAL_REGION_LOCAL(m_connects_lock); for(auto& c: m_connects) @@ -807,7 +838,7 @@ bool async_protocol_handler_config::foreach_connection(cal } //------------------------------------------------------------------------------------------ template template -bool async_protocol_handler_config::for_connection(const boost::uuids::uuid &connection_id, callback_t cb) +bool async_protocol_handler_config::for_connection(const boost::uuids::uuid &connection_id, const callback_t &cb) { CRITICAL_REGION_LOCAL(m_connects_lock); async_protocol_handler* aph = find_connection(connection_id); @@ -826,6 +857,15 @@ size_t async_protocol_handler_config::get_connections_coun } //------------------------------------------------------------------------------------------ template +void async_protocol_handler_config::set_handler(levin_commands_handler* handler, void (*destroy)(levin_commands_handler*)) +{ + if (m_pcommands_handler && m_pcommands_handler_destroy) + (*m_pcommands_handler_destroy)(m_pcommands_handler); + m_pcommands_handler = handler; + m_pcommands_handler_destroy = destroy; +} +//------------------------------------------------------------------------------------------ +template int async_protocol_handler_config::notify(int command, const std::string& in_buff, boost::uuids::uuid connection_id) { async_protocol_handler* aph; diff --git a/src/Native/libcryptonote/contrib/epee/include/net/net_helper.h b/src/Native/libcryptonote/contrib/epee/include/net/net_helper.h index 1d808cc4c..2c2efcd82 100644 --- a/src/Native/libcryptonote/contrib/epee/include/net/net_helper.h +++ b/src/Native/libcryptonote/contrib/epee/include/net/net_helper.h @@ -31,21 +31,16 @@ //#include //#include -#include -#include -#include -#include #include +#include #include +#include #include -#include #include #include #include #include "net/net_utils_base.h" #include "misc_language.h" -//#include "profile_tools.h" -#include "../string_tools.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "net" @@ -85,11 +80,13 @@ namespace net_utils public: inline - blocked_mode_client():m_socket(m_io_service), - m_initialized(false), + blocked_mode_client():m_initialized(false), m_connected(false), m_deadline(m_io_service), - m_shutdowned(0) + m_shutdowned(0), + m_ssl(false), + m_ctx(boost::asio::ssl::context::sslv23), + m_ssl_socket(m_io_service,m_ctx) { @@ -113,18 +110,25 @@ namespace net_utils } inline - bool connect(const std::string& addr, int port, std::chrono::milliseconds timeout, const std::string& bind_ip = "0.0.0.0") + bool connect(const std::string& addr, int port, std::chrono::milliseconds timeout, bool ssl = false, const std::string& bind_ip = "0.0.0.0") { - return connect(addr, std::to_string(port), timeout, bind_ip); + return connect(addr, std::to_string(port), timeout, ssl, bind_ip); } inline - bool connect(const std::string& addr, const std::string& port, std::chrono::milliseconds timeout, const std::string& bind_ip = "0.0.0.0") + bool connect(const std::string& addr, const std::string& port, std::chrono::milliseconds timeout, bool ssl = false, const std::string& bind_ip = "0.0.0.0") { m_connected = false; + m_ssl = ssl; try { - m_socket.close(); + m_ssl_socket.next_layer().close(); + + // Set SSL options + // disable sslv2 + m_ctx.set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2); + m_ctx.set_default_verify_paths(); + // Get a list of endpoints corresponding to the server name. @@ -147,11 +151,11 @@ namespace net_utils boost::asio::ip::tcp::endpoint remote_endpoint(*iterator); - m_socket.open(remote_endpoint.protocol()); + m_ssl_socket.next_layer().open(remote_endpoint.protocol()); if(bind_ip != "0.0.0.0" && bind_ip != "0" && bind_ip != "" ) { boost::asio::ip::tcp::endpoint local_endpoint(boost::asio::ip::address::from_string(addr.c_str()), 0); - m_socket.bind(local_endpoint); + m_ssl_socket.next_layer().bind(local_endpoint); } @@ -160,17 +164,24 @@ namespace net_utils boost::system::error_code ec = boost::asio::error::would_block; - //m_socket.connect(remote_endpoint); - m_socket.async_connect(remote_endpoint, boost::lambda::var(ec) = boost::lambda::_1); + m_ssl_socket.next_layer().async_connect(remote_endpoint, boost::lambda::var(ec) = boost::lambda::_1); while (ec == boost::asio::error::would_block) { m_io_service.run_one(); } - if (!ec && m_socket.is_open()) + if (!ec && m_ssl_socket.next_layer().is_open()) { m_connected = true; m_deadline.expires_at(std::chrono::steady_clock::time_point::max()); + // SSL Options + if(m_ssl) { + // Disable verification of host certificate + m_ssl_socket.set_verify_mode(boost::asio::ssl::verify_peer); + // Handshake + m_ssl_socket.next_layer().set_option(boost::asio::ip::tcp::no_delay(true)); + m_ssl_socket.handshake(boost::asio::ssl::stream_base::client); + } return true; }else { @@ -193,7 +204,6 @@ namespace net_utils return true; } - inline bool disconnect() { @@ -202,8 +212,9 @@ namespace net_utils if(m_connected) { m_connected = false; - m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both); - + if(m_ssl) + shutdown_ssl(); + m_ssl_socket.next_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both); } } @@ -240,7 +251,7 @@ namespace net_utils // object is used as a callback and will update the ec variable when the // operation completes. The blocking_udp_client.cpp example shows how you // can use boost::bind rather than boost::lambda. - boost::asio::async_write(m_socket, boost::asio::buffer(buff), boost::lambda::var(ec) = boost::lambda::_1); + async_write(buff.c_str(), buff.size(), ec); // Block until the asynchronous operation has completed. while (ec == boost::asio::error::would_block) @@ -302,9 +313,7 @@ namespace net_utils */ boost::system::error_code ec; - size_t writen = m_socket.write_some(boost::asio::buffer(data, sz), ec); - - + size_t writen = write(data, sz, ec); if (!writen || ec) { @@ -334,10 +343,7 @@ namespace net_utils bool is_connected() { - return m_connected && m_socket.is_open(); - //TRY_ENTRY() - //return m_socket.is_open(); - //CATCH_ENTRY_L0("is_connected", false) + return m_connected && m_ssl_socket.next_layer().is_open(); } inline @@ -369,8 +375,8 @@ namespace net_utils handler_obj hndlr(ec, bytes_transfered); char local_buff[10000] = {0}; - //m_socket.async_read_some(boost::asio::buffer(local_buff, sizeof(local_buff)), hndlr); - boost::asio::async_read(m_socket, boost::asio::buffer(local_buff, sizeof(local_buff)), boost::asio::transfer_at_least(1), hndlr); + + async_read(local_buff, sizeof(local_buff), boost::asio::transfer_at_least(1), hndlr); // Block until the asynchronous operation has completed. while (ec == boost::asio::error::would_block && !boost::interprocess::ipcdetail::atomic_read32(&m_shutdowned)) @@ -451,10 +457,8 @@ namespace net_utils handler_obj hndlr(ec, bytes_transfered); - - //char local_buff[10000] = {0}; - boost::asio::async_read(m_socket, boost::asio::buffer((char*)buff.data(), buff.size()), boost::asio::transfer_at_least(buff.size()), hndlr); - + async_read((char*)buff.data(), buff.size(), boost::asio::transfer_at_least(buff.size()), hndlr); + // Block until the asynchronous operation has completed. while (ec == boost::asio::error::would_block && !boost::interprocess::ipcdetail::atomic_read32(&m_shutdowned)) { @@ -500,10 +504,18 @@ namespace net_utils bool shutdown() { m_deadline.cancel(); - boost::system::error_code ignored_ec; - m_socket.cancel(ignored_ec); - m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); - m_socket.close(ignored_ec); + boost::system::error_code ec; + if(m_ssl) + shutdown_ssl(); + m_ssl_socket.next_layer().cancel(ec); + if(ec) + MDEBUG("Problems at cancel: " << ec.message()); + m_ssl_socket.next_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); + if(ec) + MDEBUG("Problems at shutdown: " << ec.message()); + m_ssl_socket.next_layer().close(ec); + if(ec) + MDEBUG("Problems at close: " << ec.message()); boost::interprocess::ipcdetail::atomic_write32(&m_shutdowned, 1); m_connected = false; return true; @@ -520,7 +532,7 @@ namespace net_utils boost::asio::ip::tcp::socket& get_socket() { - return m_socket; + return m_ssl_socket.next_layer(); } private: @@ -537,7 +549,7 @@ namespace net_utils // connect(), read_line() or write_line() functions to return. LOG_PRINT_L3("Timed out socket"); m_connected = false; - m_socket.close(); + m_ssl_socket.next_layer().close(); // There is no longer an active deadline. The expiry is set to positive // infinity so that the actor takes no action until a new deadline is set. @@ -547,12 +559,61 @@ namespace net_utils // Put the actor back to sleep. m_deadline.async_wait(boost::bind(&blocked_mode_client::check_deadline, this)); } - + void shutdown_ssl() { + // ssl socket shutdown blocks if server doesn't respond. We close after 2 secs + boost::system::error_code ec = boost::asio::error::would_block; + m_deadline.expires_from_now(std::chrono::milliseconds(2000)); + m_ssl_socket.async_shutdown(boost::lambda::var(ec) = boost::lambda::_1); + while (ec == boost::asio::error::would_block) + { + m_io_service.run_one(); + } + // Ignore "short read" error + if (ec.category() == boost::asio::error::get_ssl_category() && + ec.value() != +#if BOOST_VERSION >= 106200 + boost::asio::ssl::error::stream_truncated +#else // older Boost supports only OpenSSL 1.0, so 1.0-only macros are appropriate + ERR_PACK(ERR_LIB_SSL, 0, SSL_R_SHORT_READ) +#endif + ) + MDEBUG("Problems at ssl shutdown: " << ec.message()); + } + + protected: + bool write(const void* data, size_t sz, boost::system::error_code& ec) + { + bool success; + if(m_ssl) + success = boost::asio::write(m_ssl_socket, boost::asio::buffer(data, sz), ec); + else + success = boost::asio::write(m_ssl_socket.next_layer(), boost::asio::buffer(data, sz), ec); + return success; + } + + void async_write(const void* data, size_t sz, boost::system::error_code& ec) + { + if(m_ssl) + boost::asio::async_write(m_ssl_socket, boost::asio::buffer(data, sz), boost::lambda::var(ec) = boost::lambda::_1); + else + boost::asio::async_write(m_ssl_socket.next_layer(), boost::asio::buffer(data, sz), boost::lambda::var(ec) = boost::lambda::_1); + } + + void async_read(char* buff, size_t sz, boost::asio::detail::transfer_at_least_t transfer_at_least, handler_obj& hndlr) + { + if(!m_ssl) + boost::asio::async_read(m_ssl_socket.next_layer(), boost::asio::buffer(buff, sz), transfer_at_least, hndlr); + else + boost::asio::async_read(m_ssl_socket, boost::asio::buffer(buff, sz), transfer_at_least, hndlr); + + } protected: boost::asio::io_service m_io_service; - boost::asio::ip::tcp::socket m_socket; + boost::asio::ssl::context m_ctx; + boost::asio::ssl::stream m_ssl_socket; + bool m_ssl; bool m_initialized; bool m_connected; boost::asio::steady_timer m_deadline; @@ -618,8 +679,8 @@ namespace net_utils boost::system::error_code ec; - size_t writen = m_socket.write_some(boost::asio::buffer(data, sz), ec); - + size_t writen = write(data, sz, ec); + if (!writen || ec) { LOG_PRINT_L3("Problems at write: " << ec.message()); @@ -660,7 +721,7 @@ namespace net_utils // asynchronous operations are cancelled. This allows the blocked // connect(), read_line() or write_line() functions to return. LOG_PRINT_L3("Timed out socket"); - m_socket.close(); + m_ssl_socket.next_layer().close(); // There is no longer an active deadline. The expiry is set to positive // infinity so that the actor takes no action until a new deadline is set. diff --git a/src/Native/libcryptonote/contrib/epee/include/net/net_parse_helpers.h b/src/Native/libcryptonote/contrib/epee/include/net/net_parse_helpers.h index 08d2a2000..708cce0ff 100644 --- a/src/Native/libcryptonote/contrib/epee/include/net/net_parse_helpers.h +++ b/src/Native/libcryptonote/contrib/epee/include/net/net_parse_helpers.h @@ -103,7 +103,7 @@ namespace net_utils STATIC_REGEXP_EXPR_1(rexp_match_uri, "^([^?#]*)(\\?([^#]*))?(#(.*))?", boost::regex::icase | boost::regex::normal); boost::smatch result; - if(!boost::regex_search(uri, result, rexp_match_uri, boost::match_default) && result[0].matched) + if(!(boost::regex_search(uri, result, rexp_match_uri, boost::match_default) && result[0].matched)) { LOG_PRINT_L1("[PARSE URI] regex not matched for uri: " << uri); content.m_path = uri; @@ -139,7 +139,7 @@ namespace net_utils // 12 34 5 6 7 content.port = 0; boost::smatch result; - if(!boost::regex_search(url_str, result, rexp_match_uri, boost::match_default) && result[0].matched) + if(!(boost::regex_search(url_str, result, rexp_match_uri, boost::match_default) && result[0].matched)) { LOG_PRINT_L1("[PARSE URI] regex not matched for uri: " << rexp_match_uri); //content.m_path = uri; diff --git a/src/Native/libcryptonote/contrib/epee/include/net/net_utils_base.h b/src/Native/libcryptonote/contrib/epee/include/net/net_utils_base.h index ef3a1d146..7615786be 100644 --- a/src/Native/libcryptonote/contrib/epee/include/net/net_utils_base.h +++ b/src/Native/libcryptonote/contrib/epee/include/net/net_utils_base.h @@ -29,12 +29,11 @@ #ifndef _NET_UTILS_BASE_H_ #define _NET_UTILS_BASE_H_ -#include -#include #include +#include +#include +#include #include "serialization/keyvalue_serialization.h" -#include "net/local_ip.h" -#include "string_tools.h" #include "misc_log_ex.h" #undef MONERO_DEFAULT_LOG_CATEGORY @@ -44,108 +43,176 @@ #define MAKE_IP( a1, a2, a3, a4 ) (a1|(a2<<8)|(a3<<16)|(a4<<24)) #endif - namespace epee { namespace net_utils { - struct network_address_base + class ipv4_network_address { - public: - bool operator==(const network_address_base &other) const { return m_full_id == other.m_full_id; } - bool operator!=(const network_address_base &other) const { return !operator==(other); } - bool operator<(const network_address_base &other) const { return m_full_id < other.m_full_id; } - bool is_same_host(const network_address_base &other) const { return m_host_id == other.m_host_id; } - virtual std::string str() const = 0; - virtual std::string host_str() const = 0; - virtual bool is_loopback() const = 0; - virtual bool is_local() const = 0; - virtual uint8_t get_type_id() const = 0; - protected: - // A very simple non cryptographic hash function by Fowler, Noll, Vo - uint64_t fnv1a(const uint8_t *data, size_t len) const { - uint64_t h = 0xcbf29ce484222325; - while (len--) - h = (h ^ *data++) * 0x100000001b3; - return h; - } - uint64_t m_host_id; - uint64_t m_full_id; - }; - struct ipv4_network_address: public network_address_base - { - void init_ids() - { - m_host_id = fnv1a((const uint8_t*)&m_ip, sizeof(m_ip)); - m_full_id = fnv1a((const uint8_t*)&m_ip, sizeof(m_ip) + sizeof(m_port)); - } - public: - ipv4_network_address(uint32_t ip, uint16_t port): network_address_base(), m_ip(ip), m_port(port) { init_ids(); } - uint32_t ip() const { return m_ip; } - uint16_t port() const { return m_port; } - virtual std::string str() const { return epee::string_tools::get_ip_string_from_int32(m_ip) + ":" + std::to_string(m_port); } - virtual std::string host_str() const { return epee::string_tools::get_ip_string_from_int32(m_ip); } - virtual bool is_loopback() const { return epee::net_utils::is_ip_loopback(m_ip); } - virtual bool is_local() const { return epee::net_utils::is_ip_local(m_ip); } - virtual uint8_t get_type_id() const { return ID; } - public: // serialization - static const uint8_t ID = 1; -#pragma pack(push) -#pragma pack(1) uint32_t m_ip; uint16_t m_port; -#pragma pack(pop) + + public: + constexpr ipv4_network_address(uint32_t ip, uint16_t port) noexcept + : m_ip(ip), m_port(port) {} + + bool equal(const ipv4_network_address& other) const noexcept; + bool less(const ipv4_network_address& other) const noexcept; + constexpr bool is_same_host(const ipv4_network_address& other) const noexcept + { return ip() == other.ip(); } + + constexpr uint32_t ip() const noexcept { return m_ip; } + constexpr uint16_t port() const noexcept { return m_port; } + std::string str() const; + std::string host_str() const; + bool is_loopback() const; + bool is_local() const; + static constexpr uint8_t get_type_id() noexcept { return ID; } + + static const uint8_t ID = 1; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(m_ip) KV_SERIALIZE(m_port) - if (!is_store) - const_cast(this_ref).init_ids(); END_KV_SERIALIZE_MAP() }; - class network_address: public boost::shared_ptr + + inline bool operator==(const ipv4_network_address& lhs, const ipv4_network_address& rhs) noexcept + { return lhs.equal(rhs); } + inline bool operator!=(const ipv4_network_address& lhs, const ipv4_network_address& rhs) noexcept + { return !lhs.equal(rhs); } + inline bool operator<(const ipv4_network_address& lhs, const ipv4_network_address& rhs) noexcept + { return lhs.less(rhs); } + inline bool operator<=(const ipv4_network_address& lhs, const ipv4_network_address& rhs) noexcept + { return !rhs.less(lhs); } + inline bool operator>(const ipv4_network_address& lhs, const ipv4_network_address& rhs) noexcept + { return rhs.less(lhs); } + inline bool operator>=(const ipv4_network_address& lhs, const ipv4_network_address& rhs) noexcept + { return !lhs.less(rhs); } + + class network_address { + struct interface + { + virtual ~interface() {}; + + virtual bool equal(const interface&) const = 0; + virtual bool less(const interface&) const = 0; + virtual bool is_same_host(const interface&) const = 0; + + virtual std::string str() const = 0; + virtual std::string host_str() const = 0; + virtual bool is_loopback() const = 0; + virtual bool is_local() const = 0; + virtual uint8_t get_type_id() const = 0; + }; + + template + struct implementation final : interface + { + T value; + + implementation(const T& src) : value(src) {} + ~implementation() = default; + + // Type-checks for cast are done in cpp + static const T& cast(const interface& src) noexcept + { return static_cast&>(src).value; } + + virtual bool equal(const interface& other) const override + { return value.equal(cast(other)); } + + virtual bool less(const interface& other) const override + { return value.less(cast(other)); } + + virtual bool is_same_host(const interface& other) const override + { return value.is_same_host(cast(other)); } + + virtual std::string str() const override { return value.str(); } + virtual std::string host_str() const override { return value.host_str(); } + virtual bool is_loopback() const override { return value.is_loopback(); } + virtual bool is_local() const override { return value.is_local(); } + virtual uint8_t get_type_id() const override { return value.get_type_id(); } + }; + + std::shared_ptr self; + + template + Type& as_mutable() const + { + // types `implmentation` and `implementation` are unique + using Type_ = typename std::remove_const::type; + network_address::interface* const self_ = self.get(); // avoid clang warning in typeid + if (!self_ || typeid(implementation) != typeid(*self_)) + throw std::bad_cast{}; + return static_cast*>(self_)->value; + } public: - network_address() {} - network_address(ipv4_network_address *address): boost::shared_ptr(address) {} - bool operator==(const network_address &other) const { return (*this)->operator==(*other); } - bool operator!=(const network_address &other) const { return (*this)->operator!=(*other); } - bool operator<(const network_address &other) const { return (*this)->operator<(*other); } - bool is_same_host(const network_address &other) const { return (*this)->is_same_host(*other); } - std::string str() const { return (*this) ? (*this)->str() : ""; } - std::string host_str() const { return (*this) ? (*this)->host_str() : ""; } - bool is_loopback() const { return (*this)->is_loopback(); } - bool is_local() const { return (*this)->is_local(); } - uint8_t get_type_id() const { return (*this)->get_type_id(); } - template Type &as() { if (get_type_id() != Type::ID) throw std::runtime_error("Bad type"); return *(Type*)get(); } - template const Type &as() const { if (get_type_id() != Type::ID) throw std::runtime_error("Bad type"); return *(const Type*)get(); } + network_address() : self(nullptr) {} + template + network_address(const T& src) + : self(std::make_shared>(src)) {} + bool equal(const network_address &other) const; + bool less(const network_address &other) const; + bool is_same_host(const network_address &other) const; + std::string str() const { return self ? self->str() : ""; } + std::string host_str() const { return self ? self->host_str() : ""; } + bool is_loopback() const { return self ? self->is_loopback() : false; } + bool is_local() const { return self ? self->is_local() : false; } + uint8_t get_type_id() const { return self ? self->get_type_id() : 0; } + template const Type &as() const { return as_mutable(); } BEGIN_KV_SERIALIZE_MAP() uint8_t type = is_store ? this_ref.get_type_id() : 0; - epee::serialization::selector::serialize(type, stg, hparent_section, "type"); + if (!epee::serialization::selector::serialize(type, stg, hparent_section, "type")) + return false; switch (type) { case ipv4_network_address::ID: + { if (!is_store) - const_cast(this_ref).reset(new ipv4_network_address(0, 0)); - KV_SERIALIZE(template as()); + { + const_cast(this_ref) = ipv4_network_address{0, 0}; + auto &addr = this_ref.template as_mutable(); + if (epee::serialization::selector::serialize(addr, stg, hparent_section, "addr")) + MDEBUG("Found as addr: " << this_ref.str()); + else if (epee::serialization::selector::serialize(addr, stg, hparent_section, "template as()")) + MDEBUG("Found as template as(): " << this_ref.str()); + else if (epee::serialization::selector::serialize(addr, stg, hparent_section, "template as_mutable()")) + MDEBUG("Found as template as_mutable(): " << this_ref.str()); + else + { + MWARNING("Address not found"); + return false; + } + } + else + { + auto &addr = this_ref.template as_mutable(); + if (!epee::serialization::selector::serialize(addr, stg, hparent_section, "addr")) + return false; + } break; - default: MERROR("Unsupported network address type: " << type); return false; + } + default: MERROR("Unsupported network address type: " << (unsigned)type); return false; } END_KV_SERIALIZE_MAP() }; - inline bool create_network_address(network_address &address, const std::string &string, uint16_t default_port = 0) - { - uint32_t ip; - uint16_t port; - if (epee::string_tools::parse_peer_from_string(ip, port, string)) - { - if (default_port && !port) - port = default_port; - address.reset(new ipv4_network_address(ip, port)); - return true; - } - return false; - } + + inline bool operator==(const network_address& lhs, const network_address& rhs) + { return lhs.equal(rhs); } + inline bool operator!=(const network_address& lhs, const network_address& rhs) + { return !lhs.equal(rhs); } + inline bool operator<(const network_address& lhs, const network_address& rhs) + { return lhs.less(rhs); } + inline bool operator<=(const network_address& lhs, const network_address& rhs) + { return !rhs.less(lhs); } + inline bool operator>(const network_address& lhs, const network_address& rhs) + { return rhs.less(lhs); } + inline bool operator>=(const network_address& lhs, const network_address& rhs) + { return !lhs.less(rhs); } + + bool create_network_address(network_address &address, const std::string &string, uint16_t default_port = 0); + /************************************************************************/ /* */ /************************************************************************/ @@ -179,7 +246,7 @@ namespace net_utils {} connection_context_base(): m_connection_id(), - m_remote_address(new ipv4_network_address(0,0)), + m_remote_address(ipv4_network_address{0,0}), m_is_income(false), m_started(time(NULL)), m_last_recv(0), @@ -228,21 +295,8 @@ namespace net_utils //some helpers - inline - std::string print_connection_context(const connection_context_base& ctx) - { - std::stringstream ss; - ss << ctx.m_remote_address->str() << " " << epee::string_tools::get_str_from_guid_a(ctx.m_connection_id) << (ctx.m_is_income ? " INC":" OUT"); - return ss.str(); - } - - inline - std::string print_connection_context_short(const connection_context_base& ctx) - { - std::stringstream ss; - ss << ctx.m_remote_address->str() << (ctx.m_is_income ? " INC":" OUT"); - return ss.str(); - } + std::string print_connection_context(const connection_context_base& ctx); + std::string print_connection_context_short(const connection_context_base& ctx); inline MAKE_LOGGABLE(connection_context_base, ct, os) { diff --git a/src/Native/libcryptonote/contrib/epee/include/net/network_throttle-detail.hpp b/src/Native/libcryptonote/contrib/epee/include/net/network_throttle-detail.hpp new file mode 100644 index 000000000..955668d62 --- /dev/null +++ b/src/Native/libcryptonote/contrib/epee/include/net/network_throttle-detail.hpp @@ -0,0 +1,125 @@ +/// @file +/// @author rfree (current maintainer in monero.cc project) +/// @brief implementaion for throttling of connection (count and rate-limit speed etc) + +// Copyright (c) 2014-2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// 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. + +/* rfree: throttle details, implementing rate limiting */ + + +#ifndef INCLUDED_throttle_detail_hpp +#define INCLUDED_throttle_detail_hpp + +#include "network_throttle.hpp" + +namespace epee +{ +namespace net_utils +{ + + +class network_throttle : public i_network_throttle { + private: + struct packet_info { + size_t m_size; // octets sent. Summary for given small-window (e.g. for all packaged in 1 second) + packet_info(); + }; + + + network_speed_bps m_target_speed; + size_t m_network_add_cost; // estimated add cost of headers + size_t m_network_minimal_segment; // estimated minimal cost of sending 1 byte to round up to + size_t m_network_max_segment; // recommended max size of 1 TCP transmission + + const size_t m_window_size; // the number of samples to average over + network_time_seconds m_slot_size; // the size of one slot. TODO: now hardcoded for 1 second e.g. in time_to_slot() + // TODO for big window size, for performance better the substract on change of m_last_sample_time instead of recalculating average of eg >100 elements + + std::vector< packet_info > m_history; // the history of bw usage + network_time_seconds m_last_sample_time; // time of last history[0] - so we know when to rotate the buffer + network_time_seconds m_start_time; // when we were created + bool m_any_packet_yet; // did we yet got any packet to count + + std::string m_name; // my name for debug and logs + std::string m_nameshort; // my name for debug and logs (used in log file name) + + // each sample is now 1 second + public: + network_throttle(const std::string &nameshort, const std::string &name, int window_size=-1); + virtual ~network_throttle(); + virtual void set_name(const std::string &name); + virtual void set_target_speed( network_speed_kbps target ); + virtual network_speed_kbps get_target_speed(); + + // add information about events: + virtual void handle_trafic_exact(size_t packet_size); ///< count the new traffic/packet; the size is exact considering all network costs + virtual void handle_trafic_tcp(size_t packet_size); ///< count the new traffic/packet; the size is as TCP, we will consider MTU etc + + virtual void tick(); ///< poke and update timers/history (recalculates, moves the history if needed, checks the real clock etc) + + virtual double get_time_seconds() const ; ///< timer that we use, time in seconds, monotionic + + // time calculations: + virtual void calculate_times(size_t packet_size, calculate_times_struct &cts, bool dbg, double force_window) const; ///< MAIN LOGIC (see base class for info) + + virtual network_time_seconds get_sleep_time_after_tick(size_t packet_size); ///< increase the timer if needed, and get the package size + virtual network_time_seconds get_sleep_time(size_t packet_size) const; ///< gets the Delay (recommended Delay time) from calc. (not safe: only if time didnt change?) TODO + + virtual size_t get_recommended_size_of_planned_transport() const; ///< what should be the size (bytes) of next data block to be transported + virtual size_t get_recommended_size_of_planned_transport_window(double force_window) const; ///< ditto, but for given windows time frame + virtual double get_current_speed() const; + + private: + virtual network_time_seconds time_to_slot(network_time_seconds t) const { return std::floor( t ); } // convert exact time eg 13.7 to rounded time for slot number in history 13 + virtual void _handle_trafic_exact(size_t packet_size, size_t orginal_size); + virtual void logger_handle_net(const std::string &filename, double time, size_t size); +}; + +/*** + * The complete set of traffic throttle for one typical connection +*/ +struct network_throttle_bw { + public: + network_throttle m_in; ///< for incomming traffic (this we can not controll directly as it depends of what others send to us - usually) + network_throttle m_inreq; ///< for requesting incomming traffic (this is exact usually) + network_throttle m_out; ///< for outgoing traffic that we just sent (this is exact usually) + + public: + network_throttle_bw(const std::string &name1); +}; + + + +} // namespace net_utils +} // namespace epee + + +#endif + + diff --git a/src/Native/libcryptonote/contrib/epee/include/net/network_throttle.hpp b/src/Native/libcryptonote/contrib/epee/include/net/network_throttle.hpp new file mode 100644 index 000000000..7df79a908 --- /dev/null +++ b/src/Native/libcryptonote/contrib/epee/include/net/network_throttle.hpp @@ -0,0 +1,171 @@ +/// @file +/// @author rfree (current maintainer in monero.cc project) +/// @brief interface for throttling of connection (count and rate-limit speed etc) + +// Copyright (c) 2014-2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// 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. + +/* rfree: throttle basic interface */ +/* rfree: also includes the manager for singeton/global such objects */ + + +#ifndef INCLUDED_network_throttle_hpp +#define INCLUDED_network_throttle_hpp + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "syncobj.h" + +#include "net/net_utils_base.h" +#include "misc_log_ex.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "misc_language.h" +#include "pragma_comp_defs.h" +#include +#include +#include + +#include +#include +#include + +namespace epee +{ +namespace net_utils +{ + +// just typedefs to in code define the units used. TODO later it will be enforced that casts to other numericals are only explicit to avoid mistakes? use boost::chrono? +typedef double network_speed_kbps; // externally, for parameters and return values, all defined in kilobytes per second +typedef double network_speed_bps; // throttle-internally, bytes per second +typedef double network_time_seconds; +typedef double network_MB; + +class i_network_throttle; + +/*** +@brief All information about given throttle - speed calculations +*/ +struct calculate_times_struct { + double average; + double window; + double delay; + double recomendetDataSize; +}; +typedef calculate_times_struct calculate_times_struct; + + +/*** +@brief Access to simple throttles, with singlton to access global network limits +*/ +class network_throttle_manager { + // provides global (singleton) in/inreq/out throttle access + + // [[note1]] see also http://www.nuonsoft.com/blog/2012/10/21/implementing-a-thread-safe-singleton-with-c11/ + // [[note2]] _inreq is the requested in traffic - we anticipate we will get in-bound traffic soon as result of what we do (e.g. that we sent network downloads requests) + + //protected: + public: // XXX + + static boost::mutex m_lock_get_global_throttle_in; + static boost::mutex m_lock_get_global_throttle_inreq; + static boost::mutex m_lock_get_global_throttle_out; + + friend class connection_basic; // FRIEND - to directly access global throttle-s. !! REMEMBER TO USE LOCKS! + friend class connection_basic_pimpl; // ditto + + public: + static i_network_throttle & get_global_throttle_in(); ///< singleton ; for friend class ; caller MUST use proper locks! like m_lock_get_global_throttle_in + static i_network_throttle & get_global_throttle_inreq(); ///< ditto ; use lock ... use m_lock_get_global_throttle_inreq obviously + static i_network_throttle & get_global_throttle_out(); ///< ditto ; use lock ... use m_lock_get_global_throttle_out obviously +}; + + + +/*** +@brief interface for the throttle, see the derivated class +*/ +class i_network_throttle { + public: + virtual void set_name(const std::string &name)=0; + virtual void set_target_speed( network_speed_kbps target )=0; + virtual network_speed_kbps get_target_speed()=0; + + virtual void handle_trafic_exact(size_t packet_size) =0; // count the new traffic/packet; the size is exact considering all network costs + virtual void handle_trafic_tcp(size_t packet_size) =0; // count the new traffic/packet; the size is as TCP, we will consider MTU etc + virtual void tick() =0; // poke and update timers/history + + // time calculations: + + virtual void calculate_times(size_t packet_size, calculate_times_struct &cts, bool dbg, double force_window) const =0; // assuming sending new package (or 0), calculate: + // Average, Window, Delay, Recommended data size ; also gets dbg=debug flag, and forced widnow size if >0 or -1 for not forcing window size + + // Average speed, Window size, recommended Delay to sleep now, Recommended size of data to send now + + virtual network_time_seconds get_sleep_time(size_t packet_size) const =0; // gets the D (recommended Delay time) from calc + virtual network_time_seconds get_sleep_time_after_tick(size_t packet_size) =0; // ditto, but first tick the timer + + virtual size_t get_recommended_size_of_planned_transport() const =0; // what should be the recommended limit of data size that we can transport over current network_throttle in near future + + virtual double get_time_seconds() const =0; // a timer + virtual void logger_handle_net(const std::string &filename, double time, size_t size)=0; + + +}; + + +// ... more in the -advanced.h file + + +} // namespace net_utils +} // namespace epee + + +#endif + + + diff --git a/src/Native/libcryptonote/contrib/epee/include/profile_tools.h b/src/Native/libcryptonote/contrib/epee/include/profile_tools.h index f285fe48b..a0b5f77f4 100644 --- a/src/Native/libcryptonote/contrib/epee/include/profile_tools.h +++ b/src/Native/libcryptonote/contrib/epee/include/profile_tools.h @@ -28,6 +28,8 @@ #ifndef _PROFILE_TOOLS_H_ #define _PROFILE_TOOLS_H_ +#include "misc_os_dependent.h" + namespace epee { diff --git a/src/Native/libcryptonote/contrib/epee/include/readline_buffer.h b/src/Native/libcryptonote/contrib/epee/include/readline_buffer.h index cda7e34f9..87c8826cb 100644 --- a/src/Native/libcryptonote/contrib/epee/include/readline_buffer.h +++ b/src/Native/libcryptonote/contrib/epee/include/readline_buffer.h @@ -2,9 +2,7 @@ #include #include -#include #include -#include namespace rdln { diff --git a/src/Native/libcryptonote/contrib/epee/include/reg_exp_definer.h b/src/Native/libcryptonote/contrib/epee/include/reg_exp_definer.h index e2bed5c3f..eb11c9e10 100644 --- a/src/Native/libcryptonote/contrib/epee/include/reg_exp_definer.h +++ b/src/Native/libcryptonote/contrib/epee/include/reg_exp_definer.h @@ -29,7 +29,7 @@ #define _REG_EXP_DEFINER_H_ #include - +#include "syncobj.h" namespace epee { diff --git a/src/Native/libcryptonote/contrib/epee/include/serialization/keyvalue_serialization.h b/src/Native/libcryptonote/contrib/epee/include/serialization/keyvalue_serialization.h index d4413a71b..5791e1998 100644 --- a/src/Native/libcryptonote/contrib/epee/include/serialization/keyvalue_serialization.h +++ b/src/Native/libcryptonote/contrib/epee/include/serialization/keyvalue_serialization.h @@ -31,7 +31,6 @@ #include "misc_log_ex.h" #include "enableable.h" #include "keyvalue_serialization_overloads.h" -#include "serialization/serialization.h" namespace epee { diff --git a/src/Native/libcryptonote/contrib/epee/include/serialization/keyvalue_serialization_overloads.h b/src/Native/libcryptonote/contrib/epee/include/serialization/keyvalue_serialization_overloads.h index 1a58cab99..7087136cc 100644 --- a/src/Native/libcryptonote/contrib/epee/include/serialization/keyvalue_serialization_overloads.h +++ b/src/Native/libcryptonote/contrib/epee/include/serialization/keyvalue_serialization_overloads.h @@ -26,6 +26,13 @@ #pragma once +#include +#include +#include +#include +#include +#include + namespace epee { namespace serialization @@ -73,7 +80,7 @@ namespace epee template static bool unserialize_t_obj(serializible_type& obj, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) { - typename t_storage::hsection hchild_section = stg.open_section(pname, hparent_section, true); + typename t_storage::hsection hchild_section = stg.open_section(pname, hparent_section, false); if(!hchild_section) return false; return obj._load(stg, hchild_section); } @@ -90,7 +97,7 @@ namespace epee static bool unserialize_t_obj(enableable& obj, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) { obj.enabled = false; - typename t_storage::hsection hchild_section = stg.open_section(pname, hparent_section, true); + typename t_storage::hsection hchild_section = stg.open_section(pname, hparent_section, false); if(!hchild_section) return false; obj.enabled = true; return obj.v._load(stg, hchild_section); @@ -117,9 +124,9 @@ namespace epee typename stl_container::value_type exchange_val; typename t_storage::harray hval_array = stg.get_first_value(pname, exchange_val, hparent_section); if(!hval_array) return false; - container.push_back(std::move(exchange_val)); + container.insert(container.end(), std::move(exchange_val)); while(stg.get_next_value(hval_array, exchange_val)) - container.push_back(std::move(exchange_val)); + container.insert(container.end(), std::move(exchange_val)); return true; }//-------------------------------------------------------------------------------------------------------------------- template @@ -152,7 +159,7 @@ namespace epee "size in blob " << loaded_size << " not have not zero modulo for sizeof(value_type) = " << sizeof(typename stl_container::value_type)); size_t count = (loaded_size/sizeof(typename stl_container::value_type)); for(size_t i = 0; i < count; i++) - container.push_back(*(pelem++)); + container.insert(container.end(), *(pelem++)); } return res; } @@ -186,12 +193,12 @@ namespace epee typename t_storage::harray hsec_array = stg.get_first_section(pname, hchild_section, hparent_section); if(!hsec_array || !hchild_section) return false; res = val._load(stg, hchild_section); - container.push_back(val); + container.insert(container.end(), val); while(stg.get_next_section(hsec_array, hchild_section)) { typename stl_container::value_type val_l = typename stl_container::value_type(); res |= val_l._load(stg, hchild_section); - container.push_back(std::move(val_l)); + container.insert(container.end(), std::move(val_l)); } return res; } @@ -227,6 +234,18 @@ namespace epee } //------------------------------------------------------------------------------------------------------------------- template + static bool kv_serialize(const std::deque& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return serialize_stl_container_t_val(d, stg, hparent_section, pname); + } + //------------------------------------------------------------------------------------------------------------------- + template + static bool kv_unserialize(std::deque& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return unserialize_stl_container_t_val(d, stg, hparent_section, pname); + } + //------------------------------------------------------------------------------------------------------------------- + template static bool kv_serialize(const std::list& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) { return serialize_stl_container_t_val(d, stg, hparent_section, pname); @@ -238,6 +257,18 @@ namespace epee return unserialize_stl_container_t_val(d, stg, hparent_section, pname); } //------------------------------------------------------------------------------------------------------------------- + template + static bool kv_serialize(const std::set& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return serialize_stl_container_t_val(d, stg, hparent_section, pname); + } + //------------------------------------------------------------------------------------------------------------------- + template + static bool kv_unserialize(std::set& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return unserialize_stl_container_t_val(d, stg, hparent_section, pname); + } + //------------------------------------------------------------------------------------------------------------------- }; template<> struct kv_serialization_overloads_impl_is_base_serializable_types @@ -268,6 +299,18 @@ namespace epee } //------------------------------------------------------------------------------------------------------------------- template + static bool kv_serialize(const std::deque& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return serialize_stl_container_t_obj(d, stg, hparent_section, pname); + } + //------------------------------------------------------------------------------------------------------------------- + template + static bool kv_unserialize(std::deque& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return unserialize_stl_container_t_obj(d, stg, hparent_section, pname); + } + //------------------------------------------------------------------------------------------------------------------- + template static bool kv_serialize(const std::list& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) { return serialize_stl_container_t_obj(d, stg, hparent_section, pname); @@ -278,6 +321,18 @@ namespace epee { return unserialize_stl_container_t_obj(d, stg, hparent_section, pname); } + //------------------------------------------------------------------------------------------------------------------- + template + static bool kv_serialize(const std::set& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return serialize_stl_container_t_obj(d, stg, hparent_section, pname); + } + //------------------------------------------------------------------------------------------------------------------- + template + static bool kv_unserialize(std::set& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return unserialize_stl_container_t_obj(d, stg, hparent_section, pname); + } }; template struct base_serializable_types: public boost::mpl::vector::type @@ -353,6 +408,18 @@ namespace epee } //------------------------------------------------------------------------------------------------------------------- template + bool kv_serialize(const std::deque& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return kv_serialization_overloads_impl_is_base_serializable_types, typename std::remove_const::type>::value>::kv_serialize(d, stg, hparent_section, pname); + } + //------------------------------------------------------------------------------------------------------------------- + template + bool kv_unserialize(std::deque& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return kv_serialization_overloads_impl_is_base_serializable_types, typename std::remove_const::type>::value>::kv_unserialize(d, stg, hparent_section, pname); + } + //------------------------------------------------------------------------------------------------------------------- + template bool kv_serialize(const std::list& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) { return kv_serialization_overloads_impl_is_base_serializable_types, typename std::remove_const::type>::value>::kv_serialize(d, stg, hparent_section, pname); @@ -363,5 +430,17 @@ namespace epee { return kv_serialization_overloads_impl_is_base_serializable_types, typename std::remove_const::type>::value>::kv_unserialize(d, stg, hparent_section, pname); } + //------------------------------------------------------------------------------------------------------------------- + template + bool kv_serialize(const std::set& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return kv_serialization_overloads_impl_is_base_serializable_types, typename std::remove_const::type>::value>::kv_serialize(d, stg, hparent_section, pname); + } + //------------------------------------------------------------------------------------------------------------------- + template + bool kv_unserialize(std::set& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname) + { + return kv_serialization_overloads_impl_is_base_serializable_types, typename std::remove_const::type>::value>::kv_unserialize(d, stg, hparent_section, pname); + } } } diff --git a/src/Native/libcryptonote/contrib/epee/include/span.h b/src/Native/libcryptonote/contrib/epee/include/span.h index ea4ba63dd..452cc088f 100644 --- a/src/Native/libcryptonote/contrib/epee/include/span.h +++ b/src/Native/libcryptonote/contrib/epee/include/span.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017, The Monero Project +// Copyright (c) 2017-2018, The Monero Project // // All rights reserved. // @@ -108,7 +108,7 @@ namespace epee template constexpr bool has_padding() noexcept { - return !std::is_pod() || alignof(T) != 1; + return !std::is_standard_layout() || alignof(T) != 1; } //! \return Cast data from `src` as `span`. diff --git a/src/Native/libcryptonote/contrib/epee/include/storages/http_abstract_invoke.h b/src/Native/libcryptonote/contrib/epee/include/storages/http_abstract_invoke.h index 823ce6731..d93084ab0 100644 --- a/src/Native/libcryptonote/contrib/epee/include/storages/http_abstract_invoke.h +++ b/src/Native/libcryptonote/contrib/epee/include/storages/http_abstract_invoke.h @@ -44,8 +44,11 @@ namespace epee if(!serialization::store_t_to_json(out_struct, req_param)) return false; + http::fields_list additional_params; + additional_params.push_back(std::make_pair("Content-Type","application/json; charset=utf-8")); + const http::http_response_info* pri = NULL; - if(!transport.invoke(uri, method, req_param, timeout, std::addressof(pri))) + if(!transport.invoke(uri, method, req_param, timeout, std::addressof(pri), std::move(additional_params))) { LOG_PRINT_L1("Failed to invoke http request to " << uri); return false; @@ -112,7 +115,7 @@ namespace epee } if(resp_t.error.code || resp_t.error.message.size()) { - LOG_ERROR("RPC call of \"" << method_name << "\" returned error: " << resp_t.error.code << ", message: " << resp_t.error.message); + LOG_ERROR("RPC call of \"" << req_t.method << "\" returned error: " << resp_t.error.code << ", message: " << resp_t.error.message); return false; } result_struct = resp_t.result; diff --git a/src/Native/libcryptonote/contrib/epee/include/storages/levin_abstract_invoke2.h b/src/Native/libcryptonote/contrib/epee/include/storages/levin_abstract_invoke2.h index 8ced9d689..d77e7a1f8 100644 --- a/src/Native/libcryptonote/contrib/epee/include/storages/levin_abstract_invoke2.h +++ b/src/Native/libcryptonote/contrib/epee/include/storages/levin_abstract_invoke2.h @@ -60,8 +60,7 @@ namespace epee LOG_ERROR("Failed to load_from_binary on command " << command); return false; } - result_struct.load(stg_ret); - return true; + return result_struct.load(stg_ret); } template @@ -105,13 +104,11 @@ namespace epee LOG_ERROR("Failed to load_from_binary on command " << command); return false; } - result_struct.load(stg_ret); - - return true; + return result_struct.load(stg_ret); } template - bool async_invoke_remote_command2(boost::uuids::uuid conn_id, int command, const t_arg& out_struct, t_transport& transport, callback_t cb, size_t inv_timeout = LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED) + bool async_invoke_remote_command2(boost::uuids::uuid conn_id, int command, const t_arg& out_struct, t_transport& transport, const callback_t &cb, size_t inv_timeout = LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED) { typename serialization::portable_storage stg; const_cast(out_struct).store(stg);//TODO: add true const support to searilzation @@ -133,7 +130,12 @@ namespace epee cb(LEVIN_ERROR_FORMAT, result_struct, context); return false; } - result_struct.load(stg_ret); + if (!result_struct.load(stg_ret)) + { + LOG_ERROR("Failed to load result struct on command " << command); + cb(LEVIN_ERROR_FORMAT, result_struct, context); + return false; + } cb(code, result_struct, context); return true; }, inv_timeout); @@ -176,7 +178,11 @@ namespace epee boost::value_initialized in_struct; boost::value_initialized out_struct; - static_cast(in_struct).load(strg); + if (!static_cast(in_struct).load(strg)) + { + LOG_ERROR("Failed to load in_struct in command " << command); + return -1; + } int res = cb(command, static_cast(in_struct), static_cast(out_struct), context); serialization::portable_storage strg_out; static_cast(out_struct).store(strg_out); @@ -200,7 +206,11 @@ namespace epee return -1; } boost::value_initialized in_struct; - static_cast(in_struct).load(strg); + if (!static_cast(in_struct).load(strg)) + { + LOG_ERROR("Failed to load in_struct in notify " << command); + return -1; + } return cb(command, in_struct, context); } diff --git a/src/Native/libcryptonote/contrib/epee/include/storages/portable_storage_from_json.h b/src/Native/libcryptonote/contrib/epee/include/storages/portable_storage_from_json.h index 04b57376c..727f36552 100644 --- a/src/Native/libcryptonote/contrib/epee/include/storages/portable_storage_from_json.h +++ b/src/Native/libcryptonote/contrib/epee/include/storages/portable_storage_from_json.h @@ -25,6 +25,8 @@ // #pragma once +#include +#include #include "parserse_base_utils.h" #include "file_io_utils.h" diff --git a/src/Native/libcryptonote/contrib/epee/include/storages/portable_storage_to_bin.h b/src/Native/libcryptonote/contrib/epee/include/storages/portable_storage_to_bin.h index 5695143b0..9501bbc2a 100644 --- a/src/Native/libcryptonote/contrib/epee/include/storages/portable_storage_to_bin.h +++ b/src/Native/libcryptonote/contrib/epee/include/storages/portable_storage_to_bin.h @@ -28,6 +28,7 @@ #pragma once +#include "pragma_comp_defs.h" #include "misc_language.h" #include "portable_storage_base.h" @@ -47,6 +48,9 @@ namespace epee PRAGMA_WARNING_PUSH PRAGMA_GCC("GCC diagnostic ignored \"-Wstrict-aliasing\"") +#ifdef __clang__ + PRAGMA_GCC("GCC diagnostic ignored \"-Wtautological-constant-out-of-range-compare\"") +#endif template size_t pack_varint(t_stream& strm, size_t val) { //the first two bits always reserved for size information diff --git a/src/Native/libcryptonote/contrib/epee/include/storages/portable_storage_val_converters.h b/src/Native/libcryptonote/contrib/epee/include/storages/portable_storage_val_converters.h index e9b91c82b..36bb28627 100644 --- a/src/Native/libcryptonote/contrib/epee/include/storages/portable_storage_val_converters.h +++ b/src/Native/libcryptonote/contrib/epee/include/storages/portable_storage_val_converters.h @@ -28,6 +28,9 @@ #pragma once +#include +#include + #include "misc_language.h" #include "portable_storage_base.h" #include "warnings.h" @@ -131,6 +134,36 @@ POP_WARNINGS } }; + // For MyMonero/OpenMonero backend compatibility + // MyMonero backend sends amount, fees and timestamp values as strings. + // Until MM backend is updated, this is needed for compatibility between OpenMonero and MyMonero. + template<> + struct convert_to_integral + { + static void convert(const std::string& from, uint64_t& to) + { + MTRACE("Converting std::string to uint64_t. Source: " << from); + // String only contains digits + if(std::all_of(from.begin(), from.end(), ::isdigit)) + to = boost::lexical_cast(from); + // MyMonero ISO 8061 timestamp (2017-05-06T16:27:06Z) + else if (boost::regex_match (from, boost::regex("\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\dZ"))) + { + // Convert to unix timestamp +#ifdef HAVE_STRPTIME + struct tm tm; + if (strptime(from.c_str(), "%Y-%m-%dT%H:%M:%S", &tm)) +#else + std::tm tm = {}; + std::istringstream ss(from); + if (ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S")) +#endif + to = std::mktime(&tm); + } else + ASSERT_AND_THROW_WRONG_CONVERSION(); + } + }; + template struct is_convertable: std::integral_constant::value && diff --git a/src/Native/libcryptonote/contrib/epee/include/string_tools.h b/src/Native/libcryptonote/contrib/epee/include/string_tools.h index ce7b2fb87..63705e401 100644 --- a/src/Native/libcryptonote/contrib/epee/include/string_tools.h +++ b/src/Native/libcryptonote/contrib/epee/include/string_tools.h @@ -35,6 +35,7 @@ # include #endif +#include #include #include #include @@ -42,8 +43,9 @@ #include #include #include -#include +#include #include "hex.h" +#include "memwipe.h" #include "span.h" #include "warnings.h" @@ -160,7 +162,7 @@ DISABLE_GCC_WARNING(maybe-uninitialized) val = boost::lexical_cast(str_id); return true; } - catch(std::exception& /*e*/) + catch(const std::exception& /*e*/) { //const char* pmsg = e.what(); return false; @@ -329,7 +331,7 @@ POP_WARNINGS template std::string pod_to_hex(const t_pod_type& s) { - static_assert(std::is_pod::value, "expected pod type"); + static_assert(std::is_standard_layout(), "expected standard layout type"); return to_hex::string(as_byte_span(s)); } //---------------------------------------------------------------------------- @@ -349,6 +351,14 @@ POP_WARNINGS s = *(t_pod_type*)bin_buff.data(); return true; } + //---------------------------------------------------------------------------- + template + bool hex_to_pod(const std::string& hex_str, tools::scrubbed& s) + { + return hex_to_pod(hex_str, unwrap(s)); + } + //---------------------------------------------------------------------------- + bool validate_hex(uint64_t length, const std::string& str); //---------------------------------------------------------------------------- inline std::string get_extension(const std::string& str) { diff --git a/src/Native/libcryptonote/contrib/epee/include/wipeable_string.h b/src/Native/libcryptonote/contrib/epee/include/wipeable_string.h new file mode 100644 index 000000000..70d1a9586 --- /dev/null +++ b/src/Native/libcryptonote/contrib/epee/include/wipeable_string.h @@ -0,0 +1,67 @@ +// Copyright (c) 2017-2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// 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. + +#pragma once + +#include +#include +#include + +namespace epee +{ + class wipeable_string + { + public: + wipeable_string() {} + wipeable_string(const wipeable_string &other); + wipeable_string(wipeable_string &&other); + wipeable_string(const std::string &other); + wipeable_string(std::string &&other); + wipeable_string(const char *s); + ~wipeable_string(); + void wipe(); + void push_back(char c); + void pop_back(); + const char *data() const noexcept { return buffer.data(); } + size_t size() const noexcept { return buffer.size(); } + bool empty() const noexcept { return buffer.empty(); } + void resize(size_t sz); + void reserve(size_t sz); + void clear(); + bool operator==(const wipeable_string &other) const noexcept { return buffer == other.buffer; } + bool operator!=(const wipeable_string &other) const noexcept { return buffer != other.buffer; } + wipeable_string &operator=(wipeable_string &&other); + wipeable_string &operator=(const wipeable_string &other); + + private: + void grow(size_t sz, size_t reserved = 0); + + private: + std::vector buffer; + }; +} diff --git a/src/Native/libcryptonote/contrib/epee/src/hex.cpp b/src/Native/libcryptonote/contrib/epee/src/hex.cpp index cfbd3cf87..c143b2dc2 100644 --- a/src/Native/libcryptonote/contrib/epee/src/hex.cpp +++ b/src/Native/libcryptonote/contrib/epee/src/hex.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017, The Monero Project +// Copyright (c) 2017-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/contrib/epee/src/memwipe.c b/src/Native/libcryptonote/contrib/epee/src/memwipe.c new file mode 100644 index 000000000..026ef7277 --- /dev/null +++ b/src/Native/libcryptonote/contrib/epee/src/memwipe.c @@ -0,0 +1,116 @@ +// Copyright (c) 2017-2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// 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. +// +// Parts of this file Copyright (c) 2009-2015 The Bitcoin Core developers + +#define __STDC_WANT_LIB_EXT1__ 1 +#include +#include +#include + +#ifndef _WIN32 +#include +#endif + +#ifdef HAVE_EXPLICIT_BZERO +#include +#endif +#include "memwipe.h" + +#if defined(_MSC_VER) +#define SCARECROW +#else +#define SCARECROW \ + __asm__ __volatile__("" : : "r"(ptr) : "memory"); +#endif + +#ifdef HAVE_MEMSET_S + +void *memwipe(void *ptr, size_t n) +{ + if (memset_s(ptr, n, 0, n)) + { +#ifdef NDEBUG + fprintf(stderr, "Error: memset_s failed\n"); + _exit(1); +#else + abort(); +#endif + } + SCARECROW // might as well... + return ptr; +} + +#elif defined HAVE_EXPLICIT_BZERO + +void *memwipe(void *ptr, size_t n) +{ + explicit_bzero(ptr, n); + SCARECROW + return ptr; +} + +#else + +/* The memory_cleanse implementation is taken from Bitcoin */ + +/* Compilers have a bad habit of removing "superfluous" memset calls that + * are trying to zero memory. For example, when memset()ing a buffer and + * then free()ing it, the compiler might decide that the memset is + * unobservable and thus can be removed. + * + * Previously we used OpenSSL which tried to stop this by a) implementing + * memset in assembly on x86 and b) putting the function in its own file + * for other platforms. + * + * This change removes those tricks in favour of using asm directives to + * scare the compiler away. As best as our compiler folks can tell, this is + * sufficient and will continue to be so. + * + * Adam Langley + * Commit: ad1907fe73334d6c696c8539646c21b11178f20f + * BoringSSL (LICENSE: ISC) + */ +static void memory_cleanse(void *ptr, size_t len) +{ + memset(ptr, 0, len); + + /* As best as we can tell, this is sufficient to break any optimisations that + might try to eliminate "superfluous" memsets. If there's an easy way to + detect memset_s, it would be better to use that. */ + SCARECROW +} + +void *memwipe(void *ptr, size_t n) +{ + memory_cleanse(ptr, n); + SCARECROW + return ptr; +} + +#endif diff --git a/src/Native/libcryptonote/contrib/epee/src/wipeable_string.cpp b/src/Native/libcryptonote/contrib/epee/src/wipeable_string.cpp new file mode 100644 index 000000000..6ed4ee8a2 --- /dev/null +++ b/src/Native/libcryptonote/contrib/epee/src/wipeable_string.cpp @@ -0,0 +1,146 @@ +// Copyright (c) 2017-2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// 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. + +#include +#include "memwipe.h" +#include "misc_log_ex.h" +#include "wipeable_string.h" + +namespace epee +{ + +wipeable_string::wipeable_string(const wipeable_string &other): + buffer(other.buffer) +{ +} + +wipeable_string::wipeable_string(wipeable_string &&other) +{ + if (&other == this) + return; + buffer = std::move(other.buffer); +} + +wipeable_string::wipeable_string(const std::string &other) +{ + grow(other.size()); + memcpy(buffer.data(), other.c_str(), size()); +} + +wipeable_string::wipeable_string(std::string &&other) +{ + grow(other.size()); + memcpy(buffer.data(), other.c_str(), size()); + if (!other.empty()) + { + memwipe(&other[0], other.size()); // we're kinda left with this again aren't we + other = std::string(); + } +} + +wipeable_string::wipeable_string(const char *s) +{ + grow(strlen(s)); + memcpy(buffer.data(), s, size()); +} + +wipeable_string::~wipeable_string() +{ + wipe(); +} + +void wipeable_string::wipe() +{ + if (!buffer.empty()) + memwipe(buffer.data(), buffer.size() * sizeof(char)); +} + +void wipeable_string::grow(size_t sz, size_t reserved) +{ + if (reserved < sz) + reserved = sz; + if (reserved <= buffer.capacity()) + { + if (sz < buffer.size()) + memwipe(buffer.data() + sz, buffer.size() - sz); + buffer.resize(sz); + return; + } + size_t old_sz = buffer.size(); + std::unique_ptr tmp{new char[old_sz]}; + memcpy(tmp.get(), buffer.data(), old_sz * sizeof(char)); + if (old_sz > 0) + memwipe(buffer.data(), old_sz * sizeof(char)); + buffer.reserve(reserved); + buffer.resize(sz); + memcpy(buffer.data(), tmp.get(), old_sz * sizeof(char)); + if (old_sz > 0) + memwipe(tmp.get(), old_sz * sizeof(char)); +} + +void wipeable_string::push_back(char c) +{ + grow(size() + 1); + buffer.back() = c; +} + +void wipeable_string::pop_back() +{ + resize(size() - 1); +} + +void wipeable_string::resize(size_t sz) +{ + grow(sz); +} + +void wipeable_string::reserve(size_t sz) +{ + grow(size(), sz); +} + +void wipeable_string::clear() +{ + resize(0); +} + +wipeable_string &wipeable_string::operator=(wipeable_string &&other) +{ + if (&other != this) + buffer = std::move(other.buffer); + return *this; +} + +wipeable_string &wipeable_string::operator=(const wipeable_string &other) +{ + if (&other != this) + buffer = other.buffer; + return *this; +} + +} diff --git a/src/Native/libcryptonote/crypto/CMakeLists.txt b/src/Native/libcryptonote/crypto/CMakeLists.txt new file mode 100644 index 000000000..71dcedcab --- /dev/null +++ b/src/Native/libcryptonote/crypto/CMakeLists.txt @@ -0,0 +1,103 @@ +# Copyright (c) 2014-2018, The Monero Project +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are +# permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of +# conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list +# of conditions and the following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be +# used to endorse or promote products derived from this software without specific +# prior written permission. +# +# 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. + +set(crypto_sources + aesb.c + blake256.c + chacha.c + crypto-ops-data.c + crypto-ops.c + crypto.cpp + groestl.c + hash-extra-blake.c + hash-extra-groestl.c + hash-extra-jh.c + hash-extra-skein.c + hash.c + jh.c + keccak.c + oaes_lib.c + random.c + skein.c + slow-hash.c + tree-hash.c) + +set(crypto_headers) + +set(crypto_private_headers + blake256.h + chacha.h + crypto-ops.h + crypto.h + generic-ops.h + groestl.h + groestl_tables.h + hash-ops.h + hash.h + initializer.h + jh.h + keccak.h + oaes_config.h + oaes_lib.h + random.h + skein.h + skein_port.h) + +monero_private_headers(cncrypto + ${crypto_private_headers}) +monero_add_library(cncrypto + ${crypto_sources} + ${crypto_headers} + ${crypto_private_headers}) +target_link_libraries(cncrypto + PUBLIC + epee + ${Boost_SYSTEM_LIBRARY} + PRIVATE + ${EXTRA_LIBRARIES}) + +if (ARM) + option(NO_OPTIMIZED_MULTIPLY_ON_ARM + "Compute multiply using generic C implementation instead of ARM ASM" OFF) + if(NO_OPTIMIZED_MULTIPLY_ON_ARM) + message(STATUS "Using generic C implementation for multiply") + set_property(SOURCE slow-hash.c + PROPERTY COMPILE_DEFINITIONS "NO_OPTIMIZED_MULTIPLY_ON_ARM") + endif() +endif() + +# Because of the way Qt works on android with JNI, the code does not live in the main android thread +# So this code runs with a 1 MB default stack size. +# This will force the use of the heap for the allocation of the scratchpad +if (ANDROID OR IOS) + if( BUILD_GUI_DEPS ) + add_definitions(-DFORCE_USE_HEAP=1) + endif() +endif() + + diff --git a/src/Native/libcryptonote/crypto/blake256.c b/src/Native/libcryptonote/crypto/blake256.c index 1e43f9c4d..d503c47e0 100644 --- a/src/Native/libcryptonote/crypto/blake256.c +++ b/src/Native/libcryptonote/crypto/blake256.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -157,7 +157,7 @@ void blake256_update(state *S, const uint8_t *data, uint64_t datalen) { int left = S->buflen >> 3; int fill = 64 - left; - if (left && (((datalen >> 3) & 0x3F) >= (unsigned) fill)) { + if (left && (((datalen >> 3)) >= (unsigned) fill)) { memcpy((void *) (S->buf + left), (void *) data, fill); S->t[0] += 512; if (S->t[0] == 0) S->t[1]++; diff --git a/src/Native/libcryptonote/crypto/blake256.h b/src/Native/libcryptonote/crypto/blake256.h index 921fcd2fd..073772289 100644 --- a/src/Native/libcryptonote/crypto/blake256.h +++ b/src/Native/libcryptonote/crypto/blake256.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/crypto/chacha.c b/src/Native/libcryptonote/crypto/chacha.c new file mode 100644 index 000000000..5d3edb98d --- /dev/null +++ b/src/Native/libcryptonote/crypto/chacha.c @@ -0,0 +1,182 @@ +/* +chacha-merged.c version 20080118 +D. J. Bernstein +Public domain. +*/ + +#include +#include +#ifndef _MSC_VER +#include +#endif + +#include "chacha.h" +#include "common/int-util.h" +#include "warnings.h" + +/* + * The following macros are used to obtain exact-width results. + */ +#define U8V(v) ((uint8_t)(v) & UINT8_C(0xFF)) +#define U32V(v) ((uint32_t)(v) & UINT32_C(0xFFFFFFFF)) + +/* + * The following macros load words from an array of bytes with + * different types of endianness, and vice versa. + */ +#define U8TO32_LITTLE(p) SWAP32LE(((uint32_t*)(p))[0]) +#define U32TO8_LITTLE(p, v) (((uint32_t*)(p))[0] = SWAP32LE(v)) + +#define ROTATE(v,c) (rol32(v,c)) +#define XOR(v,w) ((v) ^ (w)) +#define PLUS(v,w) (U32V((v) + (w))) +#define PLUSONE(v) (PLUS((v),1)) + +#define QUARTERROUND(a,b,c,d) \ + a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \ + a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c), 7); + +static const char sigma[] = "expand 32-byte k"; + +DISABLE_GCC_AND_CLANG_WARNING(strict-aliasing) + +static void chacha(unsigned rounds, const void* data, size_t length, const uint8_t* key, const uint8_t* iv, char* cipher) { + uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; + uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; + char* ctarget = 0; + char tmp[64]; + int i; + + if (!length) return; + + j0 = U8TO32_LITTLE(sigma + 0); + j1 = U8TO32_LITTLE(sigma + 4); + j2 = U8TO32_LITTLE(sigma + 8); + j3 = U8TO32_LITTLE(sigma + 12); + j4 = U8TO32_LITTLE(key + 0); + j5 = U8TO32_LITTLE(key + 4); + j6 = U8TO32_LITTLE(key + 8); + j7 = U8TO32_LITTLE(key + 12); + j8 = U8TO32_LITTLE(key + 16); + j9 = U8TO32_LITTLE(key + 20); + j10 = U8TO32_LITTLE(key + 24); + j11 = U8TO32_LITTLE(key + 28); + j12 = 0; + j13 = 0; + j14 = U8TO32_LITTLE(iv + 0); + j15 = U8TO32_LITTLE(iv + 4); + + for (;;) { + if (length < 64) { + memcpy(tmp, data, length); + data = tmp; + ctarget = cipher; + cipher = tmp; + } + x0 = j0; + x1 = j1; + x2 = j2; + x3 = j3; + x4 = j4; + x5 = j5; + x6 = j6; + x7 = j7; + x8 = j8; + x9 = j9; + x10 = j10; + x11 = j11; + x12 = j12; + x13 = j13; + x14 = j14; + x15 = j15; + for (i = rounds;i > 0;i -= 2) { + QUARTERROUND( x0, x4, x8,x12) + QUARTERROUND( x1, x5, x9,x13) + QUARTERROUND( x2, x6,x10,x14) + QUARTERROUND( x3, x7,x11,x15) + QUARTERROUND( x0, x5,x10,x15) + QUARTERROUND( x1, x6,x11,x12) + QUARTERROUND( x2, x7, x8,x13) + QUARTERROUND( x3, x4, x9,x14) + } + x0 = PLUS( x0, j0); + x1 = PLUS( x1, j1); + x2 = PLUS( x2, j2); + x3 = PLUS( x3, j3); + x4 = PLUS( x4, j4); + x5 = PLUS( x5, j5); + x6 = PLUS( x6, j6); + x7 = PLUS( x7, j7); + x8 = PLUS( x8, j8); + x9 = PLUS( x9, j9); + x10 = PLUS(x10,j10); + x11 = PLUS(x11,j11); + x12 = PLUS(x12,j12); + x13 = PLUS(x13,j13); + x14 = PLUS(x14,j14); + x15 = PLUS(x15,j15); + + x0 = XOR( x0,U8TO32_LITTLE((uint8_t*)data + 0)); + x1 = XOR( x1,U8TO32_LITTLE((uint8_t*)data + 4)); + x2 = XOR( x2,U8TO32_LITTLE((uint8_t*)data + 8)); + x3 = XOR( x3,U8TO32_LITTLE((uint8_t*)data + 12)); + x4 = XOR( x4,U8TO32_LITTLE((uint8_t*)data + 16)); + x5 = XOR( x5,U8TO32_LITTLE((uint8_t*)data + 20)); + x6 = XOR( x6,U8TO32_LITTLE((uint8_t*)data + 24)); + x7 = XOR( x7,U8TO32_LITTLE((uint8_t*)data + 28)); + x8 = XOR( x8,U8TO32_LITTLE((uint8_t*)data + 32)); + x9 = XOR( x9,U8TO32_LITTLE((uint8_t*)data + 36)); + x10 = XOR(x10,U8TO32_LITTLE((uint8_t*)data + 40)); + x11 = XOR(x11,U8TO32_LITTLE((uint8_t*)data + 44)); + x12 = XOR(x12,U8TO32_LITTLE((uint8_t*)data + 48)); + x13 = XOR(x13,U8TO32_LITTLE((uint8_t*)data + 52)); + x14 = XOR(x14,U8TO32_LITTLE((uint8_t*)data + 56)); + x15 = XOR(x15,U8TO32_LITTLE((uint8_t*)data + 60)); + + j12 = PLUSONE(j12); + if (!j12) + { + j13 = PLUSONE(j13); + /* stopping at 2^70 bytes per iv is user's responsibility */ + } + + U32TO8_LITTLE(cipher + 0,x0); + U32TO8_LITTLE(cipher + 4,x1); + U32TO8_LITTLE(cipher + 8,x2); + U32TO8_LITTLE(cipher + 12,x3); + U32TO8_LITTLE(cipher + 16,x4); + U32TO8_LITTLE(cipher + 20,x5); + U32TO8_LITTLE(cipher + 24,x6); + U32TO8_LITTLE(cipher + 28,x7); + U32TO8_LITTLE(cipher + 32,x8); + U32TO8_LITTLE(cipher + 36,x9); + U32TO8_LITTLE(cipher + 40,x10); + U32TO8_LITTLE(cipher + 44,x11); + U32TO8_LITTLE(cipher + 48,x12); + U32TO8_LITTLE(cipher + 52,x13); + U32TO8_LITTLE(cipher + 56,x14); + U32TO8_LITTLE(cipher + 60,x15); + + if (length <= 64) { + if (length < 64) { + memcpy(ctarget, cipher, length); + } + return; + } + length -= 64; + cipher += 64; + data = (uint8_t*)data + 64; + } +} + +void chacha8(const void* data, size_t length, const uint8_t* key, const uint8_t* iv, char* cipher) +{ + chacha(8, data, length, key, iv, cipher); +} + +void chacha20(const void* data, size_t length, const uint8_t* key, const uint8_t* iv, char* cipher) +{ + chacha(20, data, length, key, iv, cipher); +} diff --git a/src/Native/libcryptonote/crypto/chacha.h b/src/Native/libcryptonote/crypto/chacha.h new file mode 100644 index 000000000..7a120931a --- /dev/null +++ b/src/Native/libcryptonote/crypto/chacha.h @@ -0,0 +1,91 @@ +// Copyright (c) 2014-2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// 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. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include +#include + +#define CHACHA_KEY_SIZE 32 +#define CHACHA_IV_SIZE 8 + +#if defined(__cplusplus) +#include + +#include "memwipe.h" +#include "hash.h" + +namespace crypto { + extern "C" { +#endif + void chacha8(const void* data, size_t length, const uint8_t* key, const uint8_t* iv, char* cipher); + void chacha20(const void* data, size_t length, const uint8_t* key, const uint8_t* iv, char* cipher); +#if defined(__cplusplus) + } + + using chacha_key = tools::scrubbed_arr; + +#pragma pack(push, 1) + // MS VC 2012 doesn't interpret `class chacha_iv` as POD in spite of [9.0.10], so it is a struct + struct chacha_iv { + uint8_t data[CHACHA_IV_SIZE]; + }; +#pragma pack(pop) + + static_assert(sizeof(chacha_key) == CHACHA_KEY_SIZE && sizeof(chacha_iv) == CHACHA_IV_SIZE, "Invalid structure size"); + + inline void chacha8(const void* data, std::size_t length, const chacha_key& key, const chacha_iv& iv, char* cipher) { + chacha8(data, length, key.data(), reinterpret_cast(&iv), cipher); + } + + inline void chacha20(const void* data, std::size_t length, const chacha_key& key, const chacha_iv& iv, char* cipher) { + chacha20(data, length, key.data(), reinterpret_cast(&iv), cipher); + } + + inline void generate_chacha_key(const void *data, size_t size, chacha_key& key) { + static_assert(sizeof(chacha_key) <= sizeof(hash), "Size of hash must be at least that of chacha_key"); + tools::scrubbed_arr pwd_hash; + crypto::cn_slow_hash(data, size, pwd_hash.data(), 0/*variant*/, 0/*prehashed*/); + memcpy(&key, pwd_hash.data(), sizeof(key)); + } + + inline void generate_chacha_key_prehashed(const void *data, size_t size, chacha_key& key) { + static_assert(sizeof(chacha_key) <= sizeof(hash), "Size of hash must be at least that of chacha_key"); + tools::scrubbed_arr pwd_hash; + crypto::cn_slow_hash(data, size, pwd_hash.data(), 0/*variant*/, 1/*prehashed*/); + memcpy(&key, pwd_hash.data(), sizeof(key)); + } + + inline void generate_chacha_key(std::string password, chacha_key& key) { + return generate_chacha_key(password.data(), password.size(), key); + } +} + +#endif diff --git a/src/Native/libcryptonote/crypto/crypto-ops-data.c b/src/Native/libcryptonote/crypto/crypto-ops-data.c index 4bd75b77c..4ff4310de 100644 --- a/src/Native/libcryptonote/crypto/crypto-ops-data.c +++ b/src/Native/libcryptonote/crypto/crypto-ops-data.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -870,3 +870,4 @@ const fe fe_fffb1 = {-31702527, -2466483, -26106795, -12203692, -12169197, -3210 const fe fe_fffb2 = {8166131, -6741800, -17040804, 3154616, 21461005, 1466302, -30876704, -6368709, 10503587, -13363080}; /* sqrt(2 * A * (A + 2)) */ const fe fe_fffb3 = {-13620103, 14639558, 4532995, 7679154, 16815101, -15883539, -22863840, -14813421, 13716513, -6477756}; /* sqrt(-sqrt(-1) * A * (A + 2)) */ const fe fe_fffb4 = {-21786234, -12173074, 21573800, 4524538, -4645904, 16204591, 8012863, -8444712, 3212926, 6885324}; /* sqrt(sqrt(-1) * A * (A + 2)) */ +const ge_p3 ge_p3_identity = { {0}, {1, 0}, {1, 0}, {0} }; diff --git a/src/Native/libcryptonote/crypto/crypto-ops.c b/src/Native/libcryptonote/crypto/crypto-ops.c index 4edfee0ce..45d412ac6 100644 --- a/src/Native/libcryptonote/crypto/crypto-ops.c +++ b/src/Native/libcryptonote/crypto/crypto-ops.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -1234,6 +1234,51 @@ void ge_double_scalarmult_base_vartime(ge_p2 *r, const unsigned char *a, const g } } +void ge_double_scalarmult_base_vartime_p3(ge_p3 *r3, const unsigned char *a, const ge_p3 *A, const unsigned char *b) { + signed char aslide[256]; + signed char bslide[256]; + ge_dsmp Ai; /* A, 3A, 5A, 7A, 9A, 11A, 13A, 15A */ + ge_p1p1 t; + ge_p3 u; + ge_p2 r; + int i; + + slide(aslide, a); + slide(bslide, b); + ge_dsm_precomp(Ai, A); + + ge_p2_0(&r); + + for (i = 255; i >= 0; --i) { + if (aslide[i] || bslide[i]) break; + } + + for (; i >= 0; --i) { + ge_p2_dbl(&t, &r); + + if (aslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_add(&t, &u, &Ai[aslide[i]/2]); + } else if (aslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_sub(&t, &u, &Ai[(-aslide[i])/2]); + } + + if (bslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_madd(&t, &u, &ge_Bi[bslide[i]/2]); + } else if (bslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_msub(&t, &u, &ge_Bi[(-bslide[i])/2]); + } + + if (i == 0) + ge_p1p1_to_p3(r3, &t); + else + ge_p1p1_to_p2(&r, &t); + } +} + /* From ge_frombytes.c, modified */ int ge_frombytes_vartime(ge_p3 *h, const unsigned char *s) { @@ -2000,17 +2045,79 @@ void ge_scalarmult(ge_p2 *r, const unsigned char *a, const ge_p3 *A) { } } -void ge_double_scalarmult_precomp_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b, const ge_dsmp Bi) { +void ge_scalarmult_p3(ge_p3 *r3, const unsigned char *a, const ge_p3 *A) { + signed char e[64]; + int carry, carry2, i; + ge_cached Ai[8]; /* 1 * A, 2 * A, ..., 8 * A */ + ge_p1p1 t; + ge_p3 u; + ge_p2 r; + + carry = 0; /* 0..1 */ + for (i = 0; i < 31; i++) { + carry += a[i]; /* 0..256 */ + carry2 = (carry + 8) >> 4; /* 0..16 */ + e[2 * i] = carry - (carry2 << 4); /* -8..7 */ + carry = (carry2 + 8) >> 4; /* 0..1 */ + e[2 * i + 1] = carry2 - (carry << 4); /* -8..7 */ + } + carry += a[31]; /* 0..128 */ + carry2 = (carry + 8) >> 4; /* 0..8 */ + e[62] = carry - (carry2 << 4); /* -8..7 */ + e[63] = carry2; /* 0..8 */ + + ge_p3_to_cached(&Ai[0], A); + for (i = 0; i < 7; i++) { + ge_add(&t, A, &Ai[i]); + ge_p1p1_to_p3(&u, &t); + ge_p3_to_cached(&Ai[i + 1], &u); + } + + ge_p2_0(&r); + for (i = 63; i >= 0; i--) { + signed char b = e[i]; + unsigned char bnegative = negative(b); + unsigned char babs = b - (((-bnegative) & b) << 1); + ge_cached cur, minuscur; + ge_p2_dbl(&t, &r); + ge_p1p1_to_p2(&r, &t); + ge_p2_dbl(&t, &r); + ge_p1p1_to_p2(&r, &t); + ge_p2_dbl(&t, &r); + ge_p1p1_to_p2(&r, &t); + ge_p2_dbl(&t, &r); + ge_p1p1_to_p3(&u, &t); + ge_cached_0(&cur); + ge_cached_cmov(&cur, &Ai[0], equal(babs, 1)); + ge_cached_cmov(&cur, &Ai[1], equal(babs, 2)); + ge_cached_cmov(&cur, &Ai[2], equal(babs, 3)); + ge_cached_cmov(&cur, &Ai[3], equal(babs, 4)); + ge_cached_cmov(&cur, &Ai[4], equal(babs, 5)); + ge_cached_cmov(&cur, &Ai[5], equal(babs, 6)); + ge_cached_cmov(&cur, &Ai[6], equal(babs, 7)); + ge_cached_cmov(&cur, &Ai[7], equal(babs, 8)); + fe_copy(minuscur.YplusX, cur.YminusX); + fe_copy(minuscur.YminusX, cur.YplusX); + fe_copy(minuscur.Z, cur.Z); + fe_neg(minuscur.T2d, cur.T2d); + ge_cached_cmov(&cur, &minuscur, bnegative); + ge_add(&t, &u, &cur); + if (i == 0) + ge_p1p1_to_p3(r3, &t); + else + ge_p1p1_to_p2(&r, &t); + } +} + +void ge_double_scalarmult_precomp_vartime2(ge_p2 *r, const unsigned char *a, const ge_dsmp Ai, const unsigned char *b, const ge_dsmp Bi) { signed char aslide[256]; signed char bslide[256]; - ge_dsmp Ai; /* A, 3A, 5A, 7A, 9A, 11A, 13A, 15A */ ge_p1p1 t; ge_p3 u; int i; slide(aslide, a); slide(bslide, b); - ge_dsm_precomp(Ai, A); ge_p2_0(r); @@ -2041,6 +2148,56 @@ void ge_double_scalarmult_precomp_vartime(ge_p2 *r, const unsigned char *a, cons } } +void ge_double_scalarmult_precomp_vartime2_p3(ge_p3 *r3, const unsigned char *a, const ge_dsmp Ai, const unsigned char *b, const ge_dsmp Bi) { + signed char aslide[256]; + signed char bslide[256]; + ge_p1p1 t; + ge_p3 u; + ge_p2 r; + int i; + + slide(aslide, a); + slide(bslide, b); + + ge_p2_0(&r); + + for (i = 255; i >= 0; --i) { + if (aslide[i] || bslide[i]) break; + } + + for (; i >= 0; --i) { + ge_p2_dbl(&t, &r); + + if (aslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_add(&t, &u, &Ai[aslide[i]/2]); + } else if (aslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_sub(&t, &u, &Ai[(-aslide[i])/2]); + } + + if (bslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_add(&t, &u, &Bi[bslide[i]/2]); + } else if (bslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_sub(&t, &u, &Bi[(-bslide[i])/2]); + } + + if (i == 0) + ge_p1p1_to_p3(r3, &t); + else + ge_p1p1_to_p2(&r, &t); + } +} + +void ge_double_scalarmult_precomp_vartime(ge_p2 *r, const unsigned char *a, const ge_p3 *A, const unsigned char *b, const ge_dsmp Bi) { + ge_dsmp Ai; /* A, 3A, 5A, 7A, 9A, 11A, 13A, 15A */ + + ge_dsm_precomp(Ai, A); + ge_double_scalarmult_precomp_vartime2(r, a, Ai, b, Bi); +} + void ge_mul8(ge_p1p1 *r, const ge_p2 *t) { ge_p2 u; ge_p2_dbl(r, t); @@ -2898,6 +3055,658 @@ void sc_mulsub(unsigned char *s, const unsigned char *a, const unsigned char *b, s[31] = s11 >> 17; } +//copied from above and modified +/* +Input: + a[0]+256*a[1]+...+256^31*a[31] = a + b[0]+256*b[1]+...+256^31*b[31] = b + +Output: + s[0]+256*s[1]+...+256^31*s[31] = (ab) mod l + where l = 2^252 + 27742317777372353535851937790883648493. +*/ +void sc_mul(unsigned char *s, const unsigned char *a, const unsigned char *b) { + int64_t a0 = 2097151 & load_3(a); + int64_t a1 = 2097151 & (load_4(a + 2) >> 5); + int64_t a2 = 2097151 & (load_3(a + 5) >> 2); + int64_t a3 = 2097151 & (load_4(a + 7) >> 7); + int64_t a4 = 2097151 & (load_4(a + 10) >> 4); + int64_t a5 = 2097151 & (load_3(a + 13) >> 1); + int64_t a6 = 2097151 & (load_4(a + 15) >> 6); + int64_t a7 = 2097151 & (load_3(a + 18) >> 3); + int64_t a8 = 2097151 & load_3(a + 21); + int64_t a9 = 2097151 & (load_4(a + 23) >> 5); + int64_t a10 = 2097151 & (load_3(a + 26) >> 2); + int64_t a11 = (load_4(a + 28) >> 7); + int64_t b0 = 2097151 & load_3(b); + int64_t b1 = 2097151 & (load_4(b + 2) >> 5); + int64_t b2 = 2097151 & (load_3(b + 5) >> 2); + int64_t b3 = 2097151 & (load_4(b + 7) >> 7); + int64_t b4 = 2097151 & (load_4(b + 10) >> 4); + int64_t b5 = 2097151 & (load_3(b + 13) >> 1); + int64_t b6 = 2097151 & (load_4(b + 15) >> 6); + int64_t b7 = 2097151 & (load_3(b + 18) >> 3); + int64_t b8 = 2097151 & load_3(b + 21); + int64_t b9 = 2097151 & (load_4(b + 23) >> 5); + int64_t b10 = 2097151 & (load_3(b + 26) >> 2); + int64_t b11 = (load_4(b + 28) >> 7); + int64_t s0; + int64_t s1; + int64_t s2; + int64_t s3; + int64_t s4; + int64_t s5; + int64_t s6; + int64_t s7; + int64_t s8; + int64_t s9; + int64_t s10; + int64_t s11; + int64_t s12; + int64_t s13; + int64_t s14; + int64_t s15; + int64_t s16; + int64_t s17; + int64_t s18; + int64_t s19; + int64_t s20; + int64_t s21; + int64_t s22; + int64_t s23; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + int64_t carry10; + int64_t carry11; + int64_t carry12; + int64_t carry13; + int64_t carry14; + int64_t carry15; + int64_t carry16; + int64_t carry17; + int64_t carry18; + int64_t carry19; + int64_t carry20; + int64_t carry21; + int64_t carry22; + + s0 = a0*b0; + s1 = (a0*b1 + a1*b0); + s2 = (a0*b2 + a1*b1 + a2*b0); + s3 = (a0*b3 + a1*b2 + a2*b1 + a3*b0); + s4 = (a0*b4 + a1*b3 + a2*b2 + a3*b1 + a4*b0); + s5 = (a0*b5 + a1*b4 + a2*b3 + a3*b2 + a4*b1 + a5*b0); + s6 = (a0*b6 + a1*b5 + a2*b4 + a3*b3 + a4*b2 + a5*b1 + a6*b0); + s7 = (a0*b7 + a1*b6 + a2*b5 + a3*b4 + a4*b3 + a5*b2 + a6*b1 + a7*b0); + s8 = (a0*b8 + a1*b7 + a2*b6 + a3*b5 + a4*b4 + a5*b3 + a6*b2 + a7*b1 + a8*b0); + s9 = (a0*b9 + a1*b8 + a2*b7 + a3*b6 + a4*b5 + a5*b4 + a6*b3 + a7*b2 + a8*b1 + a9*b0); + s10 = (a0*b10 + a1*b9 + a2*b8 + a3*b7 + a4*b6 + a5*b5 + a6*b4 + a7*b3 + a8*b2 + a9*b1 + a10*b0); + s11 = (a0*b11 + a1*b10 + a2*b9 + a3*b8 + a4*b7 + a5*b6 + a6*b5 + a7*b4 + a8*b3 + a9*b2 + a10*b1 + a11*b0); + s12 = (a1*b11 + a2*b10 + a3*b9 + a4*b8 + a5*b7 + a6*b6 + a7*b5 + a8*b4 + a9*b3 + a10*b2 + a11*b1); + s13 = (a2*b11 + a3*b10 + a4*b9 + a5*b8 + a6*b7 + a7*b6 + a8*b5 + a9*b4 + a10*b3 + a11*b2); + s14 = (a3*b11 + a4*b10 + a5*b9 + a6*b8 + a7*b7 + a8*b6 + a9*b5 + a10*b4 + a11*b3); + s15 = (a4*b11 + a5*b10 + a6*b9 + a7*b8 + a8*b7 + a9*b6 + a10*b5 + a11*b4); + s16 = (a5*b11 + a6*b10 + a7*b9 + a8*b8 + a9*b7 + a10*b6 + a11*b5); + s17 = (a6*b11 + a7*b10 + a8*b9 + a9*b8 + a10*b7 + a11*b6); + s18 = (a7*b11 + a8*b10 + a9*b9 + a10*b8 + a11*b7); + s19 = (a8*b11 + a9*b10 + a10*b9 + a11*b8); + s20 = (a9*b11 + a10*b10 + a11*b9); + s21 = (a10*b11 + a11*b10); + s22 = a11*b11; + s23 = 0; + + carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + carry12 = (s12 + (1<<20)) >> 21; s13 += carry12; s12 -= carry12 << 21; + carry14 = (s14 + (1<<20)) >> 21; s15 += carry14; s14 -= carry14 << 21; + carry16 = (s16 + (1<<20)) >> 21; s17 += carry16; s16 -= carry16 << 21; + carry18 = (s18 + (1<<20)) >> 21; s19 += carry18; s18 -= carry18 << 21; + carry20 = (s20 + (1<<20)) >> 21; s21 += carry20; s20 -= carry20 << 21; + carry22 = (s22 + (1<<20)) >> 21; s23 += carry22; s22 -= carry22 << 21; + + carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + carry13 = (s13 + (1<<20)) >> 21; s14 += carry13; s13 -= carry13 << 21; + carry15 = (s15 + (1<<20)) >> 21; s16 += carry15; s15 -= carry15 << 21; + carry17 = (s17 + (1<<20)) >> 21; s18 += carry17; s17 -= carry17 << 21; + carry19 = (s19 + (1<<20)) >> 21; s20 += carry19; s19 -= carry19 << 21; + carry21 = (s21 + (1<<20)) >> 21; s22 += carry21; s21 -= carry21 << 21; + + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + carry12 = (s12 + (1<<20)) >> 21; s13 += carry12; s12 -= carry12 << 21; + carry14 = (s14 + (1<<20)) >> 21; s15 += carry14; s14 -= carry14 << 21; + carry16 = (s16 + (1<<20)) >> 21; s17 += carry16; s16 -= carry16 << 21; + + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + carry13 = (s13 + (1<<20)) >> 21; s14 += carry13; s13 -= carry13 << 21; + carry15 = (s15 + (1<<20)) >> 21; s16 += carry15; s15 -= carry15 << 21; + + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + + carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + + s[0] = s0 >> 0; + s[1] = s0 >> 8; + s[2] = (s0 >> 16) | (s1 << 5); + s[3] = s1 >> 3; + s[4] = s1 >> 11; + s[5] = (s1 >> 19) | (s2 << 2); + s[6] = s2 >> 6; + s[7] = (s2 >> 14) | (s3 << 7); + s[8] = s3 >> 1; + s[9] = s3 >> 9; + s[10] = (s3 >> 17) | (s4 << 4); + s[11] = s4 >> 4; + s[12] = s4 >> 12; + s[13] = (s4 >> 20) | (s5 << 1); + s[14] = s5 >> 7; + s[15] = (s5 >> 15) | (s6 << 6); + s[16] = s6 >> 2; + s[17] = s6 >> 10; + s[18] = (s6 >> 18) | (s7 << 3); + s[19] = s7 >> 5; + s[20] = s7 >> 13; + s[21] = s8 >> 0; + s[22] = s8 >> 8; + s[23] = (s8 >> 16) | (s9 << 5); + s[24] = s9 >> 3; + s[25] = s9 >> 11; + s[26] = (s9 >> 19) | (s10 << 2); + s[27] = s10 >> 6; + s[28] = (s10 >> 14) | (s11 << 7); + s[29] = s11 >> 1; + s[30] = s11 >> 9; + s[31] = s11 >> 17; +} + +//copied from above and modified +/* +Input: + a[0]+256*a[1]+...+256^31*a[31] = a + b[0]+256*b[1]+...+256^31*b[31] = b + c[0]+256*c[1]+...+256^31*c[31] = c + +Output: + s[0]+256*s[1]+...+256^31*s[31] = (c+ab) mod l + where l = 2^252 + 27742317777372353535851937790883648493. +*/ + +void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c) { + int64_t a0 = 2097151 & load_3(a); + int64_t a1 = 2097151 & (load_4(a + 2) >> 5); + int64_t a2 = 2097151 & (load_3(a + 5) >> 2); + int64_t a3 = 2097151 & (load_4(a + 7) >> 7); + int64_t a4 = 2097151 & (load_4(a + 10) >> 4); + int64_t a5 = 2097151 & (load_3(a + 13) >> 1); + int64_t a6 = 2097151 & (load_4(a + 15) >> 6); + int64_t a7 = 2097151 & (load_3(a + 18) >> 3); + int64_t a8 = 2097151 & load_3(a + 21); + int64_t a9 = 2097151 & (load_4(a + 23) >> 5); + int64_t a10 = 2097151 & (load_3(a + 26) >> 2); + int64_t a11 = (load_4(a + 28) >> 7); + int64_t b0 = 2097151 & load_3(b); + int64_t b1 = 2097151 & (load_4(b + 2) >> 5); + int64_t b2 = 2097151 & (load_3(b + 5) >> 2); + int64_t b3 = 2097151 & (load_4(b + 7) >> 7); + int64_t b4 = 2097151 & (load_4(b + 10) >> 4); + int64_t b5 = 2097151 & (load_3(b + 13) >> 1); + int64_t b6 = 2097151 & (load_4(b + 15) >> 6); + int64_t b7 = 2097151 & (load_3(b + 18) >> 3); + int64_t b8 = 2097151 & load_3(b + 21); + int64_t b9 = 2097151 & (load_4(b + 23) >> 5); + int64_t b10 = 2097151 & (load_3(b + 26) >> 2); + int64_t b11 = (load_4(b + 28) >> 7); + int64_t c0 = 2097151 & load_3(c); + int64_t c1 = 2097151 & (load_4(c + 2) >> 5); + int64_t c2 = 2097151 & (load_3(c + 5) >> 2); + int64_t c3 = 2097151 & (load_4(c + 7) >> 7); + int64_t c4 = 2097151 & (load_4(c + 10) >> 4); + int64_t c5 = 2097151 & (load_3(c + 13) >> 1); + int64_t c6 = 2097151 & (load_4(c + 15) >> 6); + int64_t c7 = 2097151 & (load_3(c + 18) >> 3); + int64_t c8 = 2097151 & load_3(c + 21); + int64_t c9 = 2097151 & (load_4(c + 23) >> 5); + int64_t c10 = 2097151 & (load_3(c + 26) >> 2); + int64_t c11 = (load_4(c + 28) >> 7); + int64_t s0; + int64_t s1; + int64_t s2; + int64_t s3; + int64_t s4; + int64_t s5; + int64_t s6; + int64_t s7; + int64_t s8; + int64_t s9; + int64_t s10; + int64_t s11; + int64_t s12; + int64_t s13; + int64_t s14; + int64_t s15; + int64_t s16; + int64_t s17; + int64_t s18; + int64_t s19; + int64_t s20; + int64_t s21; + int64_t s22; + int64_t s23; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + int64_t carry10; + int64_t carry11; + int64_t carry12; + int64_t carry13; + int64_t carry14; + int64_t carry15; + int64_t carry16; + int64_t carry17; + int64_t carry18; + int64_t carry19; + int64_t carry20; + int64_t carry21; + int64_t carry22; + + s0 = c0 + a0*b0; + s1 = c1 + (a0*b1 + a1*b0); + s2 = c2 + (a0*b2 + a1*b1 + a2*b0); + s3 = c3 + (a0*b3 + a1*b2 + a2*b1 + a3*b0); + s4 = c4 + (a0*b4 + a1*b3 + a2*b2 + a3*b1 + a4*b0); + s5 = c5 + (a0*b5 + a1*b4 + a2*b3 + a3*b2 + a4*b1 + a5*b0); + s6 = c6 + (a0*b6 + a1*b5 + a2*b4 + a3*b3 + a4*b2 + a5*b1 + a6*b0); + s7 = c7 + (a0*b7 + a1*b6 + a2*b5 + a3*b4 + a4*b3 + a5*b2 + a6*b1 + a7*b0); + s8 = c8 + (a0*b8 + a1*b7 + a2*b6 + a3*b5 + a4*b4 + a5*b3 + a6*b2 + a7*b1 + a8*b0); + s9 = c9 + (a0*b9 + a1*b8 + a2*b7 + a3*b6 + a4*b5 + a5*b4 + a6*b3 + a7*b2 + a8*b1 + a9*b0); + s10 = c10 + (a0*b10 + a1*b9 + a2*b8 + a3*b7 + a4*b6 + a5*b5 + a6*b4 + a7*b3 + a8*b2 + a9*b1 + a10*b0); + s11 = c11 + (a0*b11 + a1*b10 + a2*b9 + a3*b8 + a4*b7 + a5*b6 + a6*b5 + a7*b4 + a8*b3 + a9*b2 + a10*b1 + a11*b0); + s12 = (a1*b11 + a2*b10 + a3*b9 + a4*b8 + a5*b7 + a6*b6 + a7*b5 + a8*b4 + a9*b3 + a10*b2 + a11*b1); + s13 = (a2*b11 + a3*b10 + a4*b9 + a5*b8 + a6*b7 + a7*b6 + a8*b5 + a9*b4 + a10*b3 + a11*b2); + s14 = (a3*b11 + a4*b10 + a5*b9 + a6*b8 + a7*b7 + a8*b6 + a9*b5 + a10*b4 + a11*b3); + s15 = (a4*b11 + a5*b10 + a6*b9 + a7*b8 + a8*b7 + a9*b6 + a10*b5 + a11*b4); + s16 = (a5*b11 + a6*b10 + a7*b9 + a8*b8 + a9*b7 + a10*b6 + a11*b5); + s17 = (a6*b11 + a7*b10 + a8*b9 + a9*b8 + a10*b7 + a11*b6); + s18 = (a7*b11 + a8*b10 + a9*b9 + a10*b8 + a11*b7); + s19 = (a8*b11 + a9*b10 + a10*b9 + a11*b8); + s20 = (a9*b11 + a10*b10 + a11*b9); + s21 = (a10*b11 + a11*b10); + s22 = a11*b11; + s23 = 0; + + carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + carry12 = (s12 + (1<<20)) >> 21; s13 += carry12; s12 -= carry12 << 21; + carry14 = (s14 + (1<<20)) >> 21; s15 += carry14; s14 -= carry14 << 21; + carry16 = (s16 + (1<<20)) >> 21; s17 += carry16; s16 -= carry16 << 21; + carry18 = (s18 + (1<<20)) >> 21; s19 += carry18; s18 -= carry18 << 21; + carry20 = (s20 + (1<<20)) >> 21; s21 += carry20; s20 -= carry20 << 21; + carry22 = (s22 + (1<<20)) >> 21; s23 += carry22; s22 -= carry22 << 21; + + carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + carry13 = (s13 + (1<<20)) >> 21; s14 += carry13; s13 -= carry13 << 21; + carry15 = (s15 + (1<<20)) >> 21; s16 += carry15; s15 -= carry15 << 21; + carry17 = (s17 + (1<<20)) >> 21; s18 += carry17; s17 -= carry17 << 21; + carry19 = (s19 + (1<<20)) >> 21; s20 += carry19; s19 -= carry19 << 21; + carry21 = (s21 + (1<<20)) >> 21; s22 += carry21; s21 -= carry21 << 21; + + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + carry12 = (s12 + (1<<20)) >> 21; s13 += carry12; s12 -= carry12 << 21; + carry14 = (s14 + (1<<20)) >> 21; s15 += carry14; s14 -= carry14 << 21; + carry16 = (s16 + (1<<20)) >> 21; s17 += carry16; s16 -= carry16 << 21; + + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + carry13 = (s13 + (1<<20)) >> 21; s14 += carry13; s13 -= carry13 << 21; + carry15 = (s15 + (1<<20)) >> 21; s16 += carry15; s15 -= carry15 << 21; + + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + + carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + + s[0] = s0 >> 0; + s[1] = s0 >> 8; + s[2] = (s0 >> 16) | (s1 << 5); + s[3] = s1 >> 3; + s[4] = s1 >> 11; + s[5] = (s1 >> 19) | (s2 << 2); + s[6] = s2 >> 6; + s[7] = (s2 >> 14) | (s3 << 7); + s[8] = s3 >> 1; + s[9] = s3 >> 9; + s[10] = (s3 >> 17) | (s4 << 4); + s[11] = s4 >> 4; + s[12] = s4 >> 12; + s[13] = (s4 >> 20) | (s5 << 1); + s[14] = s5 >> 7; + s[15] = (s5 >> 15) | (s6 << 6); + s[16] = s6 >> 2; + s[17] = s6 >> 10; + s[18] = (s6 >> 18) | (s7 << 3); + s[19] = s7 >> 5; + s[20] = s7 >> 13; + s[21] = s8 >> 0; + s[22] = s8 >> 8; + s[23] = (s8 >> 16) | (s9 << 5); + s[24] = s9 >> 3; + s[25] = s9 >> 11; + s[26] = (s9 >> 19) | (s10 << 2); + s[27] = s10 >> 6; + s[28] = (s10 >> 14) | (s11 << 7); + s[29] = s11 >> 1; + s[30] = s11 >> 9; + s[31] = s11 >> 17; +} + /* Assumes that a != INT64_MIN */ static int64_t signum(int64_t a) { return (a >> 63) - ((-a) >> 63); diff --git a/src/Native/libcryptonote/crypto/crypto-ops.h b/src/Native/libcryptonote/crypto/crypto-ops.h index 37edf5b6d..dc3c60794 100644 --- a/src/Native/libcryptonote/crypto/crypto-ops.h +++ b/src/Native/libcryptonote/crypto/crypto-ops.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -79,6 +79,7 @@ typedef ge_cached ge_dsmp[8]; extern const ge_precomp ge_Bi[8]; void ge_dsm_precomp(ge_dsmp r, const ge_p3 *s); void ge_double_scalarmult_base_vartime(ge_p2 *, const unsigned char *, const ge_p3 *, const unsigned char *); +void ge_double_scalarmult_base_vartime_p3(ge_p3 *, const unsigned char *, const ge_p3 *, const unsigned char *); /* From ge_frombytes.c, modified */ @@ -127,7 +128,10 @@ void sc_reduce(unsigned char *); /* New code */ void ge_scalarmult(ge_p2 *, const unsigned char *, const ge_p3 *); +void ge_scalarmult_p3(ge_p3 *, const unsigned char *, const ge_p3 *); void ge_double_scalarmult_precomp_vartime(ge_p2 *, const unsigned char *, const ge_p3 *, const unsigned char *, const ge_dsmp); +void ge_double_scalarmult_precomp_vartime2(ge_p2 *, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp); +void ge_double_scalarmult_precomp_vartime2_p3(ge_p3 *, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp); void ge_mul8(ge_p1p1 *, const ge_p2 *); extern const fe fe_ma2; extern const fe fe_ma; @@ -135,12 +139,15 @@ extern const fe fe_fffb1; extern const fe fe_fffb2; extern const fe fe_fffb3; extern const fe fe_fffb4; +extern const ge_p3 ge_p3_identity; void ge_fromfe_frombytes_vartime(ge_p2 *, const unsigned char *); void sc_0(unsigned char *); void sc_reduce32(unsigned char *); void sc_add(unsigned char *, const unsigned char *, const unsigned char *); void sc_sub(unsigned char *, const unsigned char *, const unsigned char *); void sc_mulsub(unsigned char *, const unsigned char *, const unsigned char *, const unsigned char *); +void sc_mul(unsigned char *, const unsigned char *, const unsigned char *); +void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, const unsigned char *c); int sc_check(const unsigned char *); int sc_isnonzero(const unsigned char *); /* Doesn't normalize */ diff --git a/src/Native/libcryptonote/crypto/crypto.cpp b/src/Native/libcryptonote/crypto/crypto.cpp index 5fb670f87..105b00c18 100644 --- a/src/Native/libcryptonote/crypto/crypto.cpp +++ b/src/Native/libcryptonote/crypto/crypto.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -43,6 +43,18 @@ #include "crypto.h" #include "hash.h" +namespace { + static void local_abort(const char *msg) + { + fprintf(stderr, "%s\n", msg); +#ifdef NDEBUG + _exit(1); +#else + abort(); +#endif + } +} + namespace crypto { using std::abort; @@ -87,14 +99,14 @@ namespace crypto { random_scalar_not_thread_safe(res); } - static inline void hash_to_scalar(const void *data, size_t length, ec_scalar &res) { + void hash_to_scalar(const void *data, size_t length, ec_scalar &res) { cn_fast_hash(data, length, reinterpret_cast(res)); sc_reduce32(&res); } /* * generate public and secret keys from a random 256-bit integer - * TODO: allow specifiying random value (for wallet recovery) + * TODO: allow specifying random value (for wallet recovery) * */ secret_key crypto_ops::generate_keys(public_key &pub, secret_key &sec, const secret_key& recovery_key, bool recover) { @@ -189,6 +201,25 @@ namespace crypto { sc_add(&derived_key, &base, &scalar); } + bool crypto_ops::derive_subaddress_public_key(const public_key &out_key, const key_derivation &derivation, std::size_t output_index, public_key &derived_key) { + ec_scalar scalar; + ge_p3 point1; + ge_p3 point2; + ge_cached point3; + ge_p1p1 point4; + ge_p2 point5; + if (ge_frombytes_vartime(&point1, &out_key) != 0) { + return false; + } + derivation_to_scalar(derivation, output_index, scalar); + ge_scalarmult_base(&point2, &scalar); + ge_p3_to_cached(&point3, &point2); + ge_sub(&point4, &point1, &point3); + ge_p1p1_to_p2(&point5, &point4); + ge_tobytes(&derived_key, &point5); + return true; + } + struct s_comm { hash h; ec_point key; @@ -246,22 +277,33 @@ namespace crypto { return sc_isnonzero(&c) == 0; } - void crypto_ops::generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const public_key &D, const secret_key &r, signature &sig) { + void crypto_ops::generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional &B, const public_key &D, const secret_key &r, signature &sig) { // sanity check ge_p3 R_p3; ge_p3 A_p3; + ge_p3 B_p3; ge_p3 D_p3; if (ge_frombytes_vartime(&R_p3, &R) != 0) throw std::runtime_error("tx pubkey is invalid"); if (ge_frombytes_vartime(&A_p3, &A) != 0) throw std::runtime_error("recipient view pubkey is invalid"); + if (B && ge_frombytes_vartime(&B_p3, &*B) != 0) throw std::runtime_error("recipient spend pubkey is invalid"); if (ge_frombytes_vartime(&D_p3, &D) != 0) throw std::runtime_error("key derivation is invalid"); #if !defined(NDEBUG) { assert(sc_check(&r) == 0); - // check R == r*G - ge_p3 dbg_R_p3; - ge_scalarmult_base(&dbg_R_p3, &r); + // check R == r*G or R == r*B public_key dbg_R; - ge_p3_tobytes(&dbg_R, &dbg_R_p3); + if (B) + { + ge_p2 dbg_R_p2; + ge_scalarmult(&dbg_R_p2, &r, &B_p3); + ge_tobytes(&dbg_R, &dbg_R_p2); + } + else + { + ge_p3 dbg_R_p3; + ge_scalarmult_base(&dbg_R_p3, &r); + ge_p3_tobytes(&dbg_R, &dbg_R_p3); + } assert(R == dbg_R); // check D == r*A ge_p2 dbg_D_p2; @@ -276,43 +318,84 @@ namespace crypto { ec_scalar k; random_scalar(k); - // compute X = k*G - ge_p3 X_p3; - ge_scalarmult_base(&X_p3, &k); + s_comm_2 buf; + buf.msg = prefix_hash; + buf.D = D; + + if (B) + { + // compute X = k*B + ge_p2 X_p2; + ge_scalarmult(&X_p2, &k, &B_p3); + ge_tobytes(&buf.X, &X_p2); + } + else + { + // compute X = k*G + ge_p3 X_p3; + ge_scalarmult_base(&X_p3, &k); + ge_p3_tobytes(&buf.X, &X_p3); + } // compute Y = k*A ge_p2 Y_p2; ge_scalarmult(&Y_p2, &k, &A_p3); + ge_tobytes(&buf.Y, &Y_p2); // sig.c = Hs(Msg || D || X || Y) - s_comm_2 buf; - buf.msg = prefix_hash; - buf.D = D; - ge_p3_tobytes(&buf.X, &X_p3); - ge_tobytes(&buf.Y, &Y_p2); - hash_to_scalar(&buf, sizeof(s_comm_2), sig.c); + hash_to_scalar(&buf, sizeof(buf), sig.c); // sig.r = k - sig.c*r sc_mulsub(&sig.r, &sig.c, &r, &k); } - bool crypto_ops::check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const public_key &D, const signature &sig) { + bool crypto_ops::check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional &B, const public_key &D, const signature &sig) { // sanity check ge_p3 R_p3; ge_p3 A_p3; + ge_p3 B_p3; ge_p3 D_p3; if (ge_frombytes_vartime(&R_p3, &R) != 0) return false; if (ge_frombytes_vartime(&A_p3, &A) != 0) return false; + if (B && ge_frombytes_vartime(&B_p3, &*B) != 0) return false; if (ge_frombytes_vartime(&D_p3, &D) != 0) return false; if (sc_check(&sig.c) != 0 || sc_check(&sig.r) != 0) return false; // compute sig.c*R - ge_p2 cR_p2; - ge_scalarmult(&cR_p2, &sig.c, &R_p3); + ge_p3 cR_p3; + { + ge_p2 cR_p2; + ge_scalarmult(&cR_p2, &sig.c, &R_p3); + public_key cR; + ge_tobytes(&cR, &cR_p2); + if (ge_frombytes_vartime(&cR_p3, &cR) != 0) return false; + } - // compute sig.r*G - ge_p3 rG_p3; - ge_scalarmult_base(&rG_p3, &sig.r); + ge_p1p1 X_p1p1; + if (B) + { + // compute X = sig.c*R + sig.r*B + ge_p2 rB_p2; + ge_scalarmult(&rB_p2, &sig.r, &B_p3); + public_key rB; + ge_tobytes(&rB, &rB_p2); + ge_p3 rB_p3; + if (ge_frombytes_vartime(&rB_p3, &rB) != 0) return false; + ge_cached rB_cached; + ge_p3_to_cached(&rB_cached, &rB_p3); + ge_add(&X_p1p1, &cR_p3, &rB_cached); + } + else + { + // compute X = sig.c*R + sig.r*G + ge_p3 rG_p3; + ge_scalarmult_base(&rG_p3, &sig.r); + ge_cached rG_cached; + ge_p3_to_cached(&rG_cached, &rG_p3); + ge_add(&X_p1p1, &cR_p3, &rG_cached); + } + ge_p2 X_p2; + ge_p1p1_to_p2(&X_p2, &X_p1p1); // compute sig.c*D ge_p2 cD_p2; @@ -322,18 +405,6 @@ namespace crypto { ge_p2 rA_p2; ge_scalarmult(&rA_p2, &sig.r, &A_p3); - // compute X = sig.c*R + sig.r*G - public_key cR; - ge_tobytes(&cR, &cR_p2); - ge_p3 cR_p3; - if (ge_frombytes_vartime(&cR_p3, &cR) != 0) return false; - ge_cached rG_cached; - ge_p3_to_cached(&rG_cached, &rG_p3); - ge_p1p1 X_p1p1; - ge_add(&X_p1p1, &cR_p3, &rG_cached); - ge_p2 X_p2; - ge_p1p1_to_p2(&X_p2, &X_p1p1); - // compute Y = sig.c*D + sig.r*A public_key cD; public_key rA; @@ -408,7 +479,7 @@ POP_WARNINGS ec_scalar sum, k, h; boost::shared_ptr buf(reinterpret_cast(malloc(rs_comm_size(pubs_count))), free); if (!buf) - abort(); + local_abort("malloc failure"); assert(sec_index < pubs_count); #if !defined(NDEBUG) { @@ -427,7 +498,7 @@ POP_WARNINGS } #endif if (ge_frombytes_vartime(&image_unp, &image) != 0) { - abort(); + local_abort("invalid key image"); } ge_dsm_precomp(image_pre, &image_unp); sc_0(&sum); @@ -446,7 +517,7 @@ POP_WARNINGS random_scalar(sig[i].c); random_scalar(sig[i].r); if (ge_frombytes_vartime(&tmp3, &*pubs[i]) != 0) { - abort(); + local_abort("invalid pubkey"); } ge_double_scalarmult_base_vartime(&tmp2, &sig[i].c, &tmp3, &sig[i].r); ge_tobytes(&buf->ab[i].a, &tmp2); diff --git a/src/Native/libcryptonote/crypto/crypto.h b/src/Native/libcryptonote/crypto/crypto.h index e99b6651f..81ebfb9e2 100644 --- a/src/Native/libcryptonote/crypto/crypto.h +++ b/src/Native/libcryptonote/crypto/crypto.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -31,12 +31,20 @@ #pragma once #include +#include #include #include +#include +#include +#include #include #include "common/pod-class.h" +#include "common/util.h" +#include "memwipe.h" #include "generic-ops.h" +#include "hex.h" +#include "span.h" #include "hash.h" namespace crypto { @@ -60,9 +68,7 @@ namespace crypto { friend class crypto_ops; }; - POD_CLASS secret_key: ec_scalar { - friend class crypto_ops; - }; + using secret_key = tools::scrubbed; POD_CLASS public_keyV { std::vector keys; @@ -94,6 +100,8 @@ namespace crypto { }; #pragma pack(pop) + void hash_to_scalar(const void *data, size_t length, ec_scalar &res); + static_assert(sizeof(ec_point) == 32 && sizeof(ec_scalar) == 32 && sizeof(public_key) == 32 && sizeof(secret_key) == 32 && sizeof(key_derivation) == 32 && sizeof(key_image) == 32 && @@ -119,14 +127,16 @@ namespace crypto { friend bool derive_public_key(const key_derivation &, std::size_t, const public_key &, public_key &); static void derive_secret_key(const key_derivation &, std::size_t, const secret_key &, secret_key &); friend void derive_secret_key(const key_derivation &, std::size_t, const secret_key &, secret_key &); + static bool derive_subaddress_public_key(const public_key &, const key_derivation &, std::size_t, public_key &); + friend bool derive_subaddress_public_key(const public_key &, const key_derivation &, std::size_t, public_key &); static void generate_signature(const hash &, const public_key &, const secret_key &, signature &); friend void generate_signature(const hash &, const public_key &, const secret_key &, signature &); static bool check_signature(const hash &, const public_key &, const signature &); friend bool check_signature(const hash &, const public_key &, const signature &); - static void generate_tx_proof(const hash &, const public_key &, const public_key &, const public_key &, const secret_key &, signature &); - friend void generate_tx_proof(const hash &, const public_key &, const public_key &, const public_key &, const secret_key &, signature &); - static bool check_tx_proof(const hash &, const public_key &, const public_key &, const public_key &, const signature &); - friend bool check_tx_proof(const hash &, const public_key &, const public_key &, const public_key &, const signature &); + static void generate_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const secret_key &, signature &); + friend void generate_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const secret_key &, signature &); + static bool check_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const signature &); + friend bool check_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const signature &); static void generate_key_image(const public_key &, const secret_key &, key_image &); friend void generate_key_image(const public_key &, const secret_key &, key_image &); static void generate_ring_signature(const hash &, const key_image &, @@ -194,6 +204,9 @@ namespace crypto { const secret_key &base, secret_key &derived_key) { crypto_ops::derive_secret_key(derivation, output_index, base, derived_key); } + inline bool derive_subaddress_public_key(const public_key &out_key, const key_derivation &derivation, std::size_t output_index, public_key &result) { + return crypto_ops::derive_subaddress_public_key(out_key, derivation, output_index, result); + } /* Generation and checking of a standard signature. */ @@ -206,12 +219,13 @@ namespace crypto { /* Generation and checking of a tx proof; given a tx pubkey R, the recipient's view pubkey A, and the key * derivation D, the signature proves the knowledge of the tx secret key r such that R=r*G and D=r*A + * When the recipient's address is a subaddress, the tx pubkey R is defined as R=r*B where B is the recipient's spend pubkey */ - inline void generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const public_key &D, const secret_key &r, signature &sig) { - crypto_ops::generate_tx_proof(prefix_hash, R, A, D, r, sig); + inline void generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional &B, const public_key &D, const secret_key &r, signature &sig) { + crypto_ops::generate_tx_proof(prefix_hash, R, A, B, D, r, sig); } - inline bool check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const public_key &D, const signature &sig) { - return crypto_ops::check_tx_proof(prefix_hash, R, A, D, sig); + inline bool check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional &B, const public_key &D, const signature &sig) { + return crypto_ops::check_tx_proof(prefix_hash, R, A, B, D, sig); } /* To send money to a key: @@ -248,8 +262,28 @@ namespace crypto { const signature *sig) { return check_ring_signature(prefix_hash, image, pubs.data(), pubs.size(), sig); } + + inline std::ostream &operator <<(std::ostream &o, const crypto::public_key &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::secret_key &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::key_derivation &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::key_image &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::signature &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + + const static crypto::public_key null_pkey = boost::value_initialized(); + const static crypto::secret_key null_skey = boost::value_initialized(); } CRYPTO_MAKE_HASHABLE(public_key) +CRYPTO_MAKE_HASHABLE(secret_key) CRYPTO_MAKE_HASHABLE(key_image) CRYPTO_MAKE_COMPARABLE(signature) diff --git a/src/Native/libcryptonote/crypto/generic-ops.h b/src/Native/libcryptonote/crypto/generic-ops.h index 1a135ffcf..62bc758c9 100644 --- a/src/Native/libcryptonote/crypto/generic-ops.h +++ b/src/Native/libcryptonote/crypto/generic-ops.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/crypto/groestl.h b/src/Native/libcryptonote/crypto/groestl.h index 89a073a4c..19837f309 100644 --- a/src/Native/libcryptonote/crypto/groestl.h +++ b/src/Native/libcryptonote/crypto/groestl.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/crypto/groestl_tables.h b/src/Native/libcryptonote/crypto/groestl_tables.h index 8fa6d7a83..c4b368584 100644 --- a/src/Native/libcryptonote/crypto/groestl_tables.h +++ b/src/Native/libcryptonote/crypto/groestl_tables.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/crypto/hash-extra-blake.c b/src/Native/libcryptonote/crypto/hash-extra-blake.c index 236479880..d33103c97 100644 --- a/src/Native/libcryptonote/crypto/hash-extra-blake.c +++ b/src/Native/libcryptonote/crypto/hash-extra-blake.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/crypto/hash-extra-groestl.c b/src/Native/libcryptonote/crypto/hash-extra-groestl.c index b15075306..228853a44 100644 --- a/src/Native/libcryptonote/crypto/hash-extra-groestl.c +++ b/src/Native/libcryptonote/crypto/hash-extra-groestl.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/crypto/hash-extra-jh.c b/src/Native/libcryptonote/crypto/hash-extra-jh.c index 8950687d3..e765a18f3 100644 --- a/src/Native/libcryptonote/crypto/hash-extra-jh.c +++ b/src/Native/libcryptonote/crypto/hash-extra-jh.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/crypto/hash-extra-skein.c b/src/Native/libcryptonote/crypto/hash-extra-skein.c index e63e7da20..06d8f87cc 100644 --- a/src/Native/libcryptonote/crypto/hash-extra-skein.c +++ b/src/Native/libcryptonote/crypto/hash-extra-skein.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/crypto/hash-ops.h b/src/Native/libcryptonote/crypto/hash-ops.h index 6998da5a4..d77d55cf3 100644 --- a/src/Native/libcryptonote/crypto/hash-ops.h +++ b/src/Native/libcryptonote/crypto/hash-ops.h @@ -1,21 +1,21 @@ -// Copyright (c) 2014-2017, The Monero Project -// +// Copyright (c) 2014-2018, The Monero Project +// // All rights reserved. -// +// // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. -// +// // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. -// +// // 3. Neither the name of the copyright holder nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. -// +// // 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 @@ -25,7 +25,7 @@ // 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. -// +// // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #pragma once @@ -79,8 +79,7 @@ enum { }; void cn_fast_hash(const void *data, size_t length, char *hash); -void cn_slow_hash(const void *data, size_t length, char *hash); -void cn_slow_hash_lite(const void *data, size_t length, char *hash); +void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed); void hash_extra_blake(const void *data, size_t length, char *hash); void hash_extra_groestl(const void *data, size_t length, char *hash); diff --git a/src/Native/libcryptonote/crypto/hash.c b/src/Native/libcryptonote/crypto/hash.c index ed95391d8..42f272e34 100644 --- a/src/Native/libcryptonote/crypto/hash.c +++ b/src/Native/libcryptonote/crypto/hash.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/crypto/hash.h b/src/Native/libcryptonote/crypto/hash.h index 22991e513..8519af1f7 100644 --- a/src/Native/libcryptonote/crypto/hash.h +++ b/src/Native/libcryptonote/crypto/hash.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -31,9 +31,13 @@ #pragma once #include +#include +#include #include "common/pod-class.h" #include "generic-ops.h" +#include "hex.h" +#include "span.h" namespace crypto { @@ -61,20 +65,41 @@ namespace crypto { cn_fast_hash(data, length, reinterpret_cast(&hash)); } + inline void cn_fast_hash_old_sig(const void *data, std::size_t length, char *hash) { + cn_fast_hash(data, length, hash); + } + inline hash cn_fast_hash(const void *data, std::size_t length) { hash h; cn_fast_hash(data, length, reinterpret_cast(&h)); return h; } - inline void cn_slow_hash(const void *data, std::size_t length, hash &hash) { - cn_slow_hash(data, length, reinterpret_cast(&hash)); + inline void cn_slow_hash(const void *data, std::size_t length, hash &hash, int variant = 0) { + cn_slow_hash(data, length, reinterpret_cast(&hash), variant, 0/*prehashed*/); + } + + inline void cn_slow_hash_old_sig(const void *data, std::size_t length, char *hash, int variant = 0) { + cn_slow_hash(data, length, hash, variant, 0/*prehashed*/); + } + + inline void cn_slow_hash_prehashed(const void *data, std::size_t length, hash &hash, int variant = 0) { + cn_slow_hash(data, length, reinterpret_cast(&hash), variant, 1/*prehashed*/); } inline void tree_hash(const hash *hashes, std::size_t count, hash &root_hash) { tree_hash(reinterpret_cast(hashes), count, reinterpret_cast(&root_hash)); } + inline std::ostream &operator <<(std::ostream &o, const crypto::hash &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + inline std::ostream &operator <<(std::ostream &o, const crypto::hash8 &v) { + epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; + } + + const static crypto::hash null_hash = boost::value_initialized(); + const static crypto::hash8 null_hash8 = boost::value_initialized(); } CRYPTO_MAKE_HASHABLE(hash) diff --git a/src/Native/libcryptonote/crypto/initializer.h b/src/Native/libcryptonote/crypto/initializer.h index 619038ae6..afbace726 100644 --- a/src/Native/libcryptonote/crypto/initializer.h +++ b/src/Native/libcryptonote/crypto/initializer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -31,8 +31,13 @@ #pragma once #if defined(__GNUC__) +#if defined(__sun) && defined(__SVR4) +#define INITIALIZER(name) __attribute__((constructor)) static void name(void) +#define FINALIZER(name) __attribute__((destructor)) static void name(void) +#else #define INITIALIZER(name) __attribute__((constructor(101))) static void name(void) #define FINALIZER(name) __attribute__((destructor(101))) static void name(void) +#endif #define REGISTER_FINALIZER(name) ((void) 0) #elif defined(_MSC_VER) diff --git a/src/Native/libcryptonote/crypto/keccak.c b/src/Native/libcryptonote/crypto/keccak.c index 090d563a2..95fb3d33d 100644 --- a/src/Native/libcryptonote/crypto/keccak.c +++ b/src/Native/libcryptonote/crypto/keccak.c @@ -2,9 +2,25 @@ // 19-Nov-11 Markku-Juhani O. Saarinen // A baseline Keccak (3rd round) implementation. +#include +#include #include "hash-ops.h" #include "keccak.h" +#ifndef _WIN32 +#include +#endif + +static void local_abort(const char *msg) +{ + fprintf(stderr, "%s\n", msg); +#ifdef NDEBUG + _exit(1); +#else + abort(); +#endif +} + const uint64_t keccakf_rndc[24] = { 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, @@ -73,12 +89,18 @@ void keccakf(uint64_t st[25], int rounds) // compute a keccak hash (md) of given byte length from "in" typedef uint64_t state_t[25]; -int keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen) +void keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen) { state_t st; uint8_t temp[144]; size_t i, rsiz, rsizw; + static_assert(HASH_DATA_AREA <= sizeof(temp), "Bad keccak preconditions"); + if (mdlen <= 0 || (mdlen > 100 && sizeof(st) != (size_t)mdlen)) + { + local_abort("Bad keccak use"); + } + rsiz = sizeof(state_t) == mdlen ? HASH_DATA_AREA : 200 - 2 * mdlen; rsizw = rsiz / 8; @@ -91,6 +113,11 @@ int keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen) } // last block and padding + if (inlen + 1 >= sizeof(temp) || inlen > rsiz || rsiz - inlen + inlen + 1 >= sizeof(temp) || rsiz == 0 || rsiz - 1 >= sizeof(temp) || rsizw * 8 > sizeof(temp)) + { + local_abort("Bad keccak use"); + } + memcpy(temp, in, inlen); temp[inlen++] = 1; memset(temp + inlen, 0, rsiz - inlen); @@ -102,8 +129,6 @@ int keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen) keccakf(st, KECCAK_ROUNDS); memcpy(md, st, mdlen); - - return 0; } void keccak1600(const uint8_t *in, size_t inlen, uint8_t *md) diff --git a/src/Native/libcryptonote/crypto/keccak.h b/src/Native/libcryptonote/crypto/keccak.h index fbd8e1904..fb9d8bd04 100644 --- a/src/Native/libcryptonote/crypto/keccak.h +++ b/src/Native/libcryptonote/crypto/keccak.h @@ -16,7 +16,7 @@ #endif // compute a keccak hash (md) of given byte length from "in" -int keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen); +void keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen); // update the state void keccakf(uint64_t st[25], int norounds); diff --git a/src/Native/libcryptonote/crypto/oaes_lib.c b/src/Native/libcryptonote/crypto/oaes_lib.c index f83a44b08..aa7c3ee4b 100644 --- a/src/Native/libcryptonote/crypto/oaes_lib.c +++ b/src/Native/libcryptonote/crypto/oaes_lib.c @@ -53,6 +53,12 @@ #include #endif +#ifdef _MSC_VER +#define GETPID() _getpid() +#else +#define GETPID() getpid() +#endif + #include "oaes_config.h" #include "oaes_lib.h" @@ -478,7 +484,7 @@ static void oaes_get_seed( char buf[RANDSIZ + 1] ) sprintf( buf, "%04d%02d%02d%02d%02d%02d%03d%p%d", gmTimer->tm_year + 1900, gmTimer->tm_mon + 1, gmTimer->tm_mday, gmTimer->tm_hour, gmTimer->tm_min, gmTimer->tm_sec, timer.millitm, - _test + timer.millitm, getpid() ); + _test + timer.millitm, GETPID() ); #else struct timeval timer; struct tm *gmTimer; @@ -490,7 +496,7 @@ static void oaes_get_seed( char buf[RANDSIZ + 1] ) sprintf( buf, "%04d%02d%02d%02d%02d%02d%03d%p%d", gmTimer->tm_year + 1900, gmTimer->tm_mon + 1, gmTimer->tm_mday, gmTimer->tm_hour, gmTimer->tm_min, gmTimer->tm_sec, timer.tv_usec/1000, - _test + timer.tv_usec/1000, getpid() ); + _test + timer.tv_usec/1000, GETPID() ); #endif if( _test ) @@ -510,13 +516,7 @@ static uint32_t oaes_get_seed(void) _test = (char *) calloc( sizeof( char ), timer.millitm ); _ret = gmTimer->tm_year + 1900 + gmTimer->tm_mon + 1 + gmTimer->tm_mday + gmTimer->tm_hour + gmTimer->tm_min + gmTimer->tm_sec + timer.millitm + - (uintptr_t)(_test + timer.millitm) + -#if !defined(_MSC_VER) - getpid(); -#else - _getpid(); -#endif - + (uintptr_t) ( _test + timer.millitm ) + GETPID(); #else struct timeval timer; struct tm *gmTimer; @@ -528,7 +528,7 @@ static uint32_t oaes_get_seed(void) _test = (char *) calloc( sizeof( char ), timer.tv_usec/1000 ); _ret = gmTimer->tm_year + 1900 + gmTimer->tm_mon + 1 + gmTimer->tm_mday + gmTimer->tm_hour + gmTimer->tm_min + gmTimer->tm_sec + timer.tv_usec/1000 + - (uintptr_t) ( _test + timer.tv_usec/1000 ) + getpid(); + (uintptr_t) ( _test + timer.tv_usec/1000 ) + GETPID(); #endif if( _test ) diff --git a/src/Native/libcryptonote/crypto/random.c b/src/Native/libcryptonote/crypto/random.c index 691c31f62..9e1a70a2d 100644 --- a/src/Native/libcryptonote/crypto/random.c +++ b/src/Native/libcryptonote/crypto/random.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -42,10 +42,15 @@ static void generate_system_random_bytes(size_t n, void *result); #include #include +#include static void generate_system_random_bytes(size_t n, void *result) { HCRYPTPROV prov; +#ifdef NDEBUG +#define must_succeed(x) do if (!(x)) { fprintf(stderr, "Failed: " #x); _exit(1); } while (0) +#else #define must_succeed(x) do if (!(x)) abort(); while (0) +#endif must_succeed(CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)); must_succeed(CryptGenRandom(prov, (DWORD)n, result)); must_succeed(CryptReleaseContext(prov, 0)); diff --git a/src/Native/libcryptonote/crypto/random.h b/src/Native/libcryptonote/crypto/random.h index 75d23fd04..6468136cc 100644 --- a/src/Native/libcryptonote/crypto/random.h +++ b/src/Native/libcryptonote/crypto/random.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/crypto/skein_port.h b/src/Native/libcryptonote/crypto/skein_port.h index a06ef30a2..a50a28e6b 100644 --- a/src/Native/libcryptonote/crypto/skein_port.h +++ b/src/Native/libcryptonote/crypto/skein_port.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/crypto/slow-hash.c b/src/Native/libcryptonote/crypto/slow-hash.c index b92b6e6c3..4a8ebbf9d 100644 --- a/src/Native/libcryptonote/crypto/slow-hash.c +++ b/src/Native/libcryptonote/crypto/slow-hash.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -32,6 +32,11 @@ #include #include #include +#include + +#ifndef _WIN32 +#include +#endif #include "common/int-util.h" #include "hash-ops.h" @@ -47,7 +52,47 @@ extern int aesb_single_round(const uint8_t *in, uint8_t*out, const uint8_t *expandedKey); extern int aesb_pseudo_round(const uint8_t *in, uint8_t *out, const uint8_t *expandedKey); -#if defined(__x86_64__) || (defined(_MSC_VER) && defined(_WIN64)) +#define VARIANT1_1(p) \ + do if (variant > 0) \ + { \ + const uint8_t tmp = ((const uint8_t*)(p))[11]; \ + static const uint32_t table = 0x75310; \ + const uint8_t index = (((tmp >> 3) & 6) | (tmp & 1)) << 1; \ + ((uint8_t*)(p))[11] = tmp ^ ((table >> index) & 0x30); \ + } while(0) + +#define VARIANT1_2(p) \ + do if (variant > 0) \ + { \ + xor64(p, tweak1_2); \ + } while(0) + +#define VARIANT1_CHECK() \ + do if (length < 43) \ + { \ + fprintf(stderr, "Cryptonight variants need at least 43 bytes of data"); \ + _exit(1); \ + } while(0) + +#define NONCE_POINTER (((const uint8_t*)data)+35) + +#define VARIANT1_PORTABLE_INIT() \ + uint8_t tweak1_2[8]; \ + do if (variant > 0) \ + { \ + VARIANT1_CHECK(); \ + memcpy(&tweak1_2, &state.hs.b[192], sizeof(tweak1_2)); \ + xor64(tweak1_2, NONCE_POINTER); \ + } while(0) + +#define VARIANT1_INIT64() \ + if (variant > 0) \ + { \ + VARIANT1_CHECK(); \ + } \ + const uint64_t tweak1_2 = variant > 0 ? (state.hs.w[24] ^ (*((const uint64_t*)NONCE_POINTER))) : 0 + +#if !defined NO_AES && (defined(__x86_64__) || (defined(_MSC_VER) && defined(_WIN64))) // Optimised code below, uses x86-specific intrinsics, SSE2, AES-NI // Fall back to more portable code is down at the bottom @@ -125,6 +170,7 @@ extern int aesb_pseudo_round(const uint8_t *in, uint8_t *out, const uint8_t *exp _mm_store_si128(R128(c), _c); \ _b = _mm_xor_si128(_b, _c); \ _mm_store_si128(R128(&hp_state[j]), _b); \ + VARIANT1_1(&hp_state[j]); \ j = state_index(c); \ p = U64(&hp_state[j]); \ b[0] = p[0]; b[1] = p[1]; \ @@ -133,6 +179,7 @@ extern int aesb_pseudo_round(const uint8_t *in, uint8_t *out, const uint8_t *exp p = U64(&hp_state[j]); \ p[0] = a[0]; p[1] = a[1]; \ a[0] ^= b[0]; a[1] ^= b[1]; \ + VARIANT1_2(p + 1); \ _b = _c; \ #if defined(_MSC_VER) @@ -183,6 +230,11 @@ STATIC INLINE void xor_blocks(uint8_t *a, const uint8_t *b) U64(a)[1] ^= U64(b)[1]; } +STATIC INLINE void xor64(uint64_t *a, const uint64_t b) +{ + *a ^= b; +} + /** * @brief uses cpuid to determine if the CPU supports the AES instructions * @return true if the CPU supports AES, false otherwise @@ -515,8 +567,7 @@ void slow_hash_free_state(void) * @param length the length in bytes of the data * @param hash a pointer to a buffer in which the final 256 bit hash will be stored */ - -void cn_slow_hash(const void *data, size_t length, char *hash) +void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed) { RDATA_ALIGN16 uint8_t expandedKey[240]; /* These buffers are aligned to use later with SSE functions */ @@ -543,10 +594,15 @@ void cn_slow_hash(const void *data, size_t length, char *hash) slow_hash_allocate_state(); /* CryptoNight Step 1: Use Keccak1600 to initialize the 'state' (and 'text') buffers from the data. */ - - hash_process(&state.hs, data, length); + if (prehashed) { + memcpy(&state.hs, data, length); + } else { + hash_process(&state.hs, data, length); + } memcpy(text, state.init, INIT_SIZE_BYTE); + VARIANT1_INIT64(); + /* CryptoNight Step 2: Iteratively encrypt the results from Keccak to fill * the 2MB large random access buffer. */ @@ -645,7 +701,7 @@ void cn_slow_hash(const void *data, size_t length, char *hash) extra_hashes[state.hs.b[0] & 3](&state, 200, hash); } -#elif defined(__arm__) || defined(__aarch64__) +#elif !defined NO_AES && (defined(__arm__) || defined(__aarch64__)) void slow_hash_allocate_state(void) { // Do nothing, this is just to maintain compatibility with the upgraded slow-hash.c @@ -670,6 +726,11 @@ void slow_hash_free_state(void) #define U64(x) ((uint64_t *) (x)) +STATIC INLINE void xor64(uint64_t *a, const uint64_t b) +{ + *a ^= b; +} + #pragma pack(push, 1) union cn_slow_hash_state { @@ -706,6 +767,7 @@ union cn_slow_hash_state vst1q_u8((uint8_t *)c, _c); \ _b = veorq_u8(_b, _c); \ vst1q_u8(&hp_state[j], _b); \ + VARIANT1_1(&hp_state[j]); \ j = state_index(c); \ p = U64(&hp_state[j]); \ b[0] = p[0]; b[1] = p[1]; \ @@ -714,6 +776,7 @@ union cn_slow_hash_state p = U64(&hp_state[j]); \ p[0] = a[0]; p[1] = a[1]; \ a[0] ^= b[0]; a[1] ^= b[1]; \ + VARIANT1_2(p + 1); \ _b = _c; \ @@ -845,7 +908,7 @@ STATIC INLINE void aes_pseudo_round_xor(const uint8_t *in, uint8_t *out, const u } } -void cn_slow_hash(const void *data, size_t length, char *hash) +void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed) { RDATA_ALIGN16 uint8_t expandedKey[240]; RDATA_ALIGN16 uint8_t hp_state[MEMORY]; @@ -868,9 +931,15 @@ void cn_slow_hash(const void *data, size_t length, char *hash) /* CryptoNight Step 1: Use Keccak1600 to initialize the 'state' (and 'text') buffers from the data. */ - hash_process(&state.hs, data, length); + if (prehashed) { + memcpy(&state.hs, data, length); + } else { + hash_process(&state.hs, data, length); + } memcpy(text, state.init, INIT_SIZE_BYTE); + VARIANT1_INIT64(); + /* CryptoNight Step 2: Iteratively encrypt the results from Keccak to fill * the 2MB large random access buffer. */ @@ -1039,7 +1108,7 @@ STATIC INLINE void xor_blocks(uint8_t* a, const uint8_t* b) U64(a)[1] ^= U64(b)[1]; } -void cn_slow_hash(const void *data, size_t length, char *hash) +void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed) { uint8_t text[INIT_SIZE_BYTE]; uint8_t a[AES_BLOCK_SIZE]; @@ -1065,9 +1134,15 @@ void cn_slow_hash(const void *data, size_t length, char *hash) long_state = (uint8_t *)malloc(MEMORY); #endif - hash_process(&state.hs, data, length); + if (prehashed) { + memcpy(&state.hs, data, length); + } else { + hash_process(&state.hs, data, length); + } memcpy(text, state.init, INIT_SIZE_BYTE); + VARIANT1_INIT64(); + aes_ctx = (oaes_ctx *) oaes_alloc(); oaes_key_import_data(aes_ctx, state.hs.b, AES_KEY_SIZE); @@ -1097,6 +1172,7 @@ void cn_slow_hash(const void *data, size_t length, char *hash) xor_blocks(b, p); swap_blocks(b, p); swap_blocks(a, b); + VARIANT1_1(p); // Iteration 2 p = &long_state[state_index(a)]; @@ -1106,6 +1182,7 @@ void cn_slow_hash(const void *data, size_t length, char *hash) swap_blocks(b, p); xor_blocks(b, p); swap_blocks(a, b); + VARIANT1_2(U64(p) + 1); } memcpy(text, state.init, INIT_SIZE_BYTE); @@ -1200,6 +1277,15 @@ static void xor_blocks(uint8_t* a, const uint8_t* b) { } } +static void xor64(uint8_t* left, const uint8_t* right) +{ + size_t i; + for (i = 0; i < 8; ++i) + { + left[i] ^= right[i]; + } +} + #pragma pack(push, 1) union cn_slow_hash_state { union hash_state hs; @@ -1210,7 +1296,7 @@ union cn_slow_hash_state { }; #pragma pack(pop) -void cn_slow_hash(const void *data, size_t length, char *hash) { +void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed) { uint8_t long_state[MEMORY]; union cn_slow_hash_state state; uint8_t text[INIT_SIZE_BYTE]; @@ -1222,11 +1308,17 @@ void cn_slow_hash(const void *data, size_t length, char *hash) { uint8_t aes_key[AES_KEY_SIZE]; oaes_ctx *aes_ctx; - hash_process(&state.hs, data, length); + if (prehashed) { + memcpy(&state.hs, data, length); + } else { + hash_process(&state.hs, data, length); + } memcpy(text, state.init, INIT_SIZE_BYTE); memcpy(aes_key, state.hs.b, AES_KEY_SIZE); aes_ctx = (oaes_ctx *) oaes_alloc(); + VARIANT1_PORTABLE_INIT(); + oaes_key_import_data(aes_ctx, aes_key, AES_KEY_SIZE); for (i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) { for (j = 0; j < INIT_SIZE_BLK; j++) { @@ -1254,6 +1346,7 @@ void cn_slow_hash(const void *data, size_t length, char *hash) { copy_block(&long_state[j * AES_BLOCK_SIZE], c); assert(j == e2i(a, MEMORY / AES_BLOCK_SIZE)); swap_blocks(a, b); + VARIANT1_1(&long_state[j * AES_BLOCK_SIZE]); /* Iteration 2 */ j = e2i(a, MEMORY / AES_BLOCK_SIZE); copy_block(c, &long_state[j * AES_BLOCK_SIZE]); @@ -1261,6 +1354,7 @@ void cn_slow_hash(const void *data, size_t length, char *hash) { sum_half_blocks(b, d); swap_blocks(b, c); xor_blocks(b, c); + VARIANT1_2(c + 8); copy_block(&long_state[j * AES_BLOCK_SIZE], c); assert(j == e2i(a, MEMORY / AES_BLOCK_SIZE)); swap_blocks(a, b); diff --git a/src/Native/libcryptonote/crypto/tree-hash.c b/src/Native/libcryptonote/crypto/tree-hash.c index 88561308f..e6d6a267c 100644 --- a/src/Native/libcryptonote/crypto/tree-hash.c +++ b/src/Native/libcryptonote/crypto/tree-hash.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -34,15 +34,15 @@ #include "hash-ops.h" -#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__DragonFly__) && !defined(__MINGW32__) && !defined(_MSC_VER) +#ifdef _MSC_VER +#include +#elif !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__DragonFly__) #include #else #include #endif -#if defined(_MSC_VER) -#define alloca(x) _alloca(x) -#endif/*** +/*** * Round to power of two, for count>=3 and for count being not too large (as reasonable for tree hash calculations) */ size_t tree_hash_cnt(size_t count) { diff --git a/src/Native/libcryptonote/cryptonote_basic/CMakeLists.txt b/src/Native/libcryptonote/cryptonote_basic/CMakeLists.txt new file mode 100644 index 000000000..d50a9df67 --- /dev/null +++ b/src/Native/libcryptonote/cryptonote_basic/CMakeLists.txt @@ -0,0 +1,79 @@ +# Copyright (c) 2014-2018, The Monero Project +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are +# permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of +# conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list +# of conditions and the following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be +# used to endorse or promote products derived from this software without specific +# prior written permission. +# +# 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. + +if(APPLE) + find_library(IOKIT_LIBRARY IOKit) + mark_as_advanced(IOKIT_LIBRARY) + list(APPEND EXTRA_LIBRARIES ${IOKIT_LIBRARY}) +endif() + +set(cryptonote_basic_sources + account.cpp + cryptonote_basic_impl.cpp + cryptonote_format_utils.cpp + difficulty.cpp + hardfork.cpp + miner.cpp) + +set(cryptonote_basic_headers) + +set(cryptonote_basic_private_headers + account.h + account_boost_serialization.h + connection_context.h + cryptonote_basic.h + cryptonote_basic_impl.h + cryptonote_boost_serialization.h + cryptonote_format_utils.h + cryptonote_stat_info.h + difficulty.h + hardfork.h + miner.h + tx_extra.h + verification_context.h) + +monero_private_headers(cryptonote_basic + ${cryptonote_basic_private_headers}) +monero_add_library(cryptonote_basic + ${cryptonote_basic_sources} + ${cryptonote_basic_headers} + ${cryptonote_basic_private_headers}) +target_link_libraries(cryptonote_basic + PUBLIC + common + cncrypto + checkpoints + device + ${Boost_DATE_TIME_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_SERIALIZATION_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + PRIVATE + ${EXTRA_LIBRARIES}) diff --git a/src/Native/libcryptonote/cryptonote_basic/account.cpp b/src/Native/libcryptonote/cryptonote_basic/account.cpp index dd875402f..bab991d19 100644 --- a/src/Native/libcryptonote/cryptonote_basic/account.cpp +++ b/src/Native/libcryptonote/cryptonote_basic/account.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -50,6 +50,17 @@ DISABLE_VS_WARNINGS(4244 4345) namespace cryptonote { + + //----------------------------------------------------------------- + hw::device& account_keys::get_device() const { + return *m_device; + } + //----------------------------------------------------------------- + void account_keys::set_device( hw::device &hwdev) { + m_device = &hwdev; + MCDEBUG("device", "account_keys::set_device device type: "< &multisig_keys) + { + m_keys.m_account_address.m_spend_public_key = spend_public_key; + m_keys.m_view_secret_key = view_secret_key; + m_keys.m_spend_secret_key = spend_secret_key; + m_keys.m_multisig_keys = multisig_keys; + return crypto::secret_key_to_public_key(view_secret_key, m_keys.m_account_address.m_view_public_key); + } + //----------------------------------------------------------------- + void account_base::finalize_multisig(const crypto::public_key &spend_public_key) + { + m_keys.m_account_address.m_spend_public_key = spend_public_key; + } + //----------------------------------------------------------------- const account_keys& account_base::get_keys() const { return m_keys; } //----------------------------------------------------------------- - std::string account_base::get_public_address_str(bool testnet) const + std::string account_base::get_public_address_str(network_type nettype) const { //TODO: change this code into base 58 - return get_account_address_as_str(testnet, m_keys.m_account_address); + return get_account_address_as_str(nettype, false, m_keys.m_account_address); } //----------------------------------------------------------------- - std::string account_base::get_public_integrated_address_str(const crypto::hash8 &payment_id, bool testnet) const + std::string account_base::get_public_integrated_address_str(const crypto::hash8 &payment_id, network_type nettype) const { //TODO: change this code into base 58 - return get_account_integrated_address_as_str(testnet, m_keys.m_account_address, payment_id); + return get_account_integrated_address_as_str(nettype, m_keys.m_account_address, payment_id); } //----------------------------------------------------------------- } diff --git a/src/Native/libcryptonote/cryptonote_basic/account.h b/src/Native/libcryptonote/cryptonote_basic/account.h index e0d5447a2..df4344730 100644 --- a/src/Native/libcryptonote/cryptonote_basic/account.h +++ b/src/Native/libcryptonote/cryptonote_basic/account.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -42,12 +42,20 @@ namespace cryptonote account_public_address m_account_address; crypto::secret_key m_spend_secret_key; crypto::secret_key m_view_secret_key; + std::vector m_multisig_keys; + hw::device *m_device = &hw::get_device("default"); BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(m_account_address) KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_spend_secret_key) KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_view_secret_key) + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_multisig_keys) END_KV_SERIALIZE_MAP() + + account_keys& operator=(account_keys const&) = default; + + hw::device& get_device() const ; + void set_device( hw::device &hwdev) ; }; /************************************************************************/ @@ -58,11 +66,17 @@ namespace cryptonote public: account_base(); crypto::secret_key generate(const crypto::secret_key& recovery_key = crypto::secret_key(), bool recover = false, bool two_random = false); + void create_from_device(const std::string &device_name) ; void create_from_keys(const cryptonote::account_public_address& address, const crypto::secret_key& spendkey, const crypto::secret_key& viewkey); void create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey); + bool make_multisig(const crypto::secret_key &view_secret_key, const crypto::secret_key &spend_secret_key, const crypto::public_key &spend_public_key, const std::vector &multisig_keys); + void finalize_multisig(const crypto::public_key &spend_public_key); const account_keys& get_keys() const; - std::string get_public_address_str(bool testnet) const; - std::string get_public_integrated_address_str(const crypto::hash8 &payment_id, bool testnet) const; + std::string get_public_address_str(uint8_t nettype) const; + std::string get_public_integrated_address_str(const crypto::hash8 &payment_id, uint8_t nettype) const; + + hw::device& get_device() const {return m_keys.get_device();} + void set_device( hw::device &hwdev) {m_keys.set_device(hwdev);} uint64_t get_createtime() const { return m_creation_timestamp; } void set_createtime(uint64_t val) { m_creation_timestamp = val; } @@ -71,6 +85,7 @@ namespace cryptonote bool store(const std::string& file_path); void forget_spend_key(); + const std::vector &get_multisig_keys() const { return m_keys.m_multisig_keys; } template inline void serialize(t_archive &a, const unsigned int /*ver*/) diff --git a/src/Native/libcryptonote/cryptonote_basic/account_boost_serialization.h b/src/Native/libcryptonote/cryptonote_basic/account_boost_serialization.h index d2f541638..7379d787f 100644 --- a/src/Native/libcryptonote/cryptonote_basic/account_boost_serialization.h +++ b/src/Native/libcryptonote/cryptonote_basic/account_boost_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/cryptonote_basic/blobdatatype.h b/src/Native/libcryptonote/cryptonote_basic/blobdatatype.h new file mode 100644 index 000000000..7d6ff0187 --- /dev/null +++ b/src/Native/libcryptonote/cryptonote_basic/blobdatatype.h @@ -0,0 +1,36 @@ +// Copyright (c) 2014-2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// 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. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +namespace cryptonote +{ + typedef std::string blobdata; +} diff --git a/src/Native/libcryptonote/cryptonote_basic/connection_context.h b/src/Native/libcryptonote/cryptonote_basic/connection_context.h index 3283543e2..5cd1709ab 100644 --- a/src/Native/libcryptonote/cryptonote_basic/connection_context.h +++ b/src/Native/libcryptonote/cryptonote_basic/connection_context.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -40,7 +40,7 @@ namespace cryptonote struct cryptonote_connection_context: public epee::net_utils::connection_context_base { cryptonote_connection_context(): m_state(state_before_handshake), m_remote_blockchain_height(0), m_last_response_height(0), - m_last_known_hash(cryptonote::null_hash) {} + m_last_request_time(boost::posix_time::microsec_clock::universal_time()), m_callback_request_count(0), m_last_known_hash(crypto::null_hash) {} enum state { diff --git a/src/Native/libcryptonote/cryptonote_basic/cryptonote_basic.h b/src/Native/libcryptonote/cryptonote_basic/cryptonote_basic.h index 873bf5374..c5a2ea6f1 100644 --- a/src/Native/libcryptonote/cryptonote_basic/cryptonote_basic.h +++ b/src/Native/libcryptonote/cryptonote_basic/cryptonote_basic.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -36,7 +36,6 @@ #include // memcmp #include #include -#include "serialization/serialization.h" #include "serialization/variant.h" #include "serialization/vector.h" #include "serialization/binary_archive.h" @@ -50,14 +49,10 @@ #include "misc_language.h" #include "tx_extra.h" #include "ringct/rctTypes.h" +#include "device/device.hpp" namespace cryptonote { - - const static crypto::hash null_hash = AUTO_VAL_INIT(null_hash); - const static crypto::hash8 null_hash8 = AUTO_VAL_INIT(null_hash8); - const static crypto::public_key null_pkey = AUTO_VAL_INIT(null_pkey); - typedef std::vector ring_signature; @@ -264,7 +259,7 @@ namespace cryptonote ar.tag("rctsig_prunable"); ar.begin_object(); r = rct_signatures.p.serialize_rctsig_prunable(ar, rct_signatures.type, vin.size(), vout.size(), - vin[0].type() == typeid(txin_to_key) ? boost::get(vin[0]).key_offsets.size() - 1 : 0); + vin.size() > 0 && vin[0].type() == typeid(txin_to_key) ? boost::get(vin[0]).key_offsets.size() - 1 : 0); if (!r || !ar.stream().good()) return false; ar.end_object(); } @@ -416,33 +411,45 @@ namespace cryptonote KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_spend_public_key) KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_view_public_key) END_KV_SERIALIZE_MAP() - }; - struct integrated_address - { - account_public_address adr; - crypto::hash8 payment_id; - - BEGIN_SERIALIZE_OBJECT() - FIELD(adr) - FIELD(payment_id) - END_SERIALIZE() - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(adr) - KV_SERIALIZE(payment_id) - END_KV_SERIALIZE_MAP() + bool operator==(const account_public_address& rhs) const + { + return m_spend_public_key == rhs.m_spend_public_key && + m_view_public_key == rhs.m_view_public_key; + } + + bool operator!=(const account_public_address& rhs) const + { + return !(*this == rhs); + } }; + struct integrated_address + { + account_public_address adr; + crypto::hash8 payment_id; + + BEGIN_SERIALIZE_OBJECT() + FIELD(adr) + FIELD(payment_id) + END_SERIALIZE() + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(adr) + KV_SERIALIZE(payment_id) + END_KV_SERIALIZE_MAP() + }; + + struct keypair { crypto::public_key pub; crypto::secret_key sec; - static inline keypair generate() + static inline keypair generate(hw::device &hwdev) { keypair k; - generate_keys(k.pub, k.sec); + hwdev.generate_keys(k.pub, k.sec); return k; } }; @@ -450,6 +457,21 @@ namespace cryptonote } +namespace std { + template <> + struct hash + { + std::size_t operator()(const cryptonote::account_public_address& addr) const + { + // https://stackoverflow.com/a/17017281 + size_t res = 17; + res = res * 31 + hash()(addr.m_spend_public_key); + res = res * 31 + hash()(addr.m_view_public_key); + return res; + } + }; +} + BLOB_SERIALIZER(cryptonote::txout_to_key); BLOB_SERIALIZER(cryptonote::txout_to_scripthash); diff --git a/src/Native/libcryptonote/cryptonote_basic/cryptonote_basic_impl.cpp b/src/Native/libcryptonote/cryptonote_basic/cryptonote_basic_impl.cpp index a59f96956..9a9362466 100644 --- a/src/Native/libcryptonote/cryptonote_basic/cryptonote_basic_impl.cpp +++ b/src/Native/libcryptonote/cryptonote_basic/cryptonote_basic_impl.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -34,7 +34,7 @@ using namespace epee; #include "cryptonote_basic_impl.h" #include "string_tools.h" #include "serialization/binary_utils.h" -#include "serialization/vector.h" +#include "serialization/container.h" #include "cryptonote_format_utils.h" #include "cryptonote_config.h" #include "misc_language.h" @@ -157,24 +157,26 @@ namespace cryptonote { } //----------------------------------------------------------------------- std::string get_account_address_as_str( - bool testnet + uint8_t nettype + , bool subaddress , account_public_address const & adr ) { - uint64_t address_prefix = testnet ? - config::testnet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX; + uint64_t address_prefix = nettype == TESTNET ? + (subaddress ? config::testnet::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX : config::testnet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX) : nettype == STAGENET ? + (subaddress ? config::stagenet::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX : config::stagenet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX) : + (subaddress ? config::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX); return tools::base58::encode_addr(address_prefix, t_serializable_object_to_blob(adr)); } //----------------------------------------------------------------------- std::string get_account_integrated_address_as_str( - bool testnet + uint8_t nettype , account_public_address const & adr , crypto::hash8 const & payment_id ) { - uint64_t integrated_address_prefix = testnet ? - config::testnet::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX; + uint64_t integrated_address_prefix = nettype == TESTNET ? config::testnet::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX : nettype == STAGENET ? config::stagenet::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX; integrated_address iadr = { adr, payment_id @@ -193,18 +195,21 @@ namespace cryptonote { return true; } //----------------------------------------------------------------------- - bool get_account_integrated_address_from_str( - account_public_address& adr - , bool& has_payment_id - , crypto::hash8& payment_id - , bool testnet + bool get_account_address_from_str( + address_parse_info& info + , uint8_t nettype , std::string const & str ) { - uint64_t address_prefix = testnet ? - config::testnet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX; - uint64_t integrated_address_prefix = testnet ? - config::testnet::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX; + uint64_t address_prefix = nettype == TESTNET ? + config::testnet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX : nettype == STAGENET ? + config::stagenet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX; + uint64_t integrated_address_prefix = nettype == TESTNET ? + config::testnet::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX : nettype == STAGENET ? + config::stagenet::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX; + uint64_t subaddress_prefix = nettype == TESTNET ? + config::testnet::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX : nettype == STAGENET ? + config::stagenet::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX; if (2 * sizeof(public_address_outer_blob) != str.size()) { @@ -218,18 +223,27 @@ namespace cryptonote { if (integrated_address_prefix == prefix) { - has_payment_id = true; + info.is_subaddress = false; + info.has_payment_id = true; } else if (address_prefix == prefix) { - has_payment_id = false; + info.is_subaddress = false; + info.has_payment_id = false; + } + else if (subaddress_prefix == prefix) + { + info.is_subaddress = true; + info.has_payment_id = false; } else { - LOG_PRINT_L1("Wrong address prefix: " << prefix << ", expected " << address_prefix << " or " << integrated_address_prefix); + LOG_PRINT_L1("Wrong address prefix: " << prefix << ", expected " << address_prefix + << " or " << integrated_address_prefix + << " or " << subaddress_prefix); return false; } - if (has_payment_id) + if (info.has_payment_id) { integrated_address iadr; if (!::serialization::parse_binary(data, iadr)) @@ -237,19 +251,19 @@ namespace cryptonote { LOG_PRINT_L1("Account public address keys can't be parsed"); return false; } - adr = iadr.adr; - payment_id = iadr.payment_id; + info.address = iadr.adr; + info.payment_id = iadr.payment_id; } else { - if (!::serialization::parse_binary(data, adr)) + if (!::serialization::parse_binary(data, info.address)) { LOG_PRINT_L1("Account public address keys can't be parsed"); return false; } } - if (!crypto::check_key(adr.m_spend_public_key) || !crypto::check_key(adr.m_view_public_key)) + if (!crypto::check_key(info.address.m_spend_public_key) || !crypto::check_key(info.address.m_view_public_key)) { LOG_PRINT_L1("Failed to validate address keys"); return false; @@ -284,51 +298,27 @@ namespace cryptonote { } //we success - adr = blob.m_address; - has_payment_id = false; + info.address = blob.m_address; + info.is_subaddress = false; + info.has_payment_id = false; } return true; } - //----------------------------------------------------------------------- - bool get_account_address_from_str( - account_public_address& adr - , bool testnet - , std::string const & str - ) - { - bool has_payment_id; - crypto::hash8 payment_id; - return get_account_integrated_address_from_str(adr, has_payment_id, payment_id, testnet, str); - } //-------------------------------------------------------------------------------- bool get_account_address_from_str_or_url( - cryptonote::account_public_address& address - , bool& has_payment_id - , crypto::hash8& payment_id - , bool testnet + address_parse_info& info + , uint8_t nettype , const std::string& str_or_url , std::function&, bool)> dns_confirm ) { - if (get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, str_or_url)) + if (get_account_address_from_str(info, nettype, str_or_url)) return true; bool dnssec_valid; std::string address_str = tools::dns_utils::get_account_address_as_str_from_url(str_or_url, dnssec_valid, dns_confirm); return !address_str.empty() && - get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, address_str); - } - //-------------------------------------------------------------------------------- - bool get_account_address_from_str_or_url( - cryptonote::account_public_address& address - , bool testnet - , const std::string& str_or_url - , std::function&, bool)> dns_confirm - ) - { - bool has_payment_id; - crypto::hash8 payment_id; - return get_account_address_from_str_or_url(address, has_payment_id, payment_id, testnet, str_or_url, dns_confirm); + get_account_address_from_str(info, nettype, address_str); } //-------------------------------------------------------------------------------- bool operator ==(const cryptonote::transaction& a, const cryptonote::transaction& b) { diff --git a/src/Native/libcryptonote/cryptonote_basic/cryptonote_basic_impl.h b/src/Native/libcryptonote/cryptonote_basic/cryptonote_basic_impl.h index 7a2259b32..4ec2b403d 100644 --- a/src/Native/libcryptonote/cryptonote_basic/cryptonote_basic_impl.h +++ b/src/Native/libcryptonote/cryptonote_basic/cryptonote_basic_impl.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -33,8 +33,6 @@ #include "cryptonote_basic.h" #include "crypto/crypto.h" #include "crypto/hash.h" -#include "hex.h" -#include "span.h" namespace cryptonote { @@ -77,6 +75,14 @@ namespace cryptonote { } } + struct address_parse_info + { + account_public_address address; + bool is_subaddress; + bool has_payment_id; + crypto::hash8 payment_id; + }; + /************************************************************************/ /* Cryptonote helper functions */ /************************************************************************/ @@ -88,42 +94,26 @@ namespace cryptonote { uint8_t get_account_integrated_address_checksum(const public_integrated_address_outer_blob& bl); std::string get_account_address_as_str( - bool testnet + uint8_t nettype + , bool subaddress , const account_public_address& adr ); std::string get_account_integrated_address_as_str( - bool testnet + uint8_t nettype , const account_public_address& adr , const crypto::hash8& payment_id ); - bool get_account_integrated_address_from_str( - account_public_address& adr - , bool& has_payment_id - , crypto::hash8& payment_id - , bool testnet - , const std::string& str - ); - bool get_account_address_from_str( - account_public_address& adr - , bool testnet + address_parse_info& info + , uint8_t nettype , const std::string& str ); bool get_account_address_from_str_or_url( - cryptonote::account_public_address& address - , bool& has_payment_id - , crypto::hash8& payment_id - , bool testnet - , const std::string& str_or_url - , std::function&, bool)> dns_confirm = return_first_address - ); - - bool get_account_address_from_str_or_url( - cryptonote::account_public_address& address - , bool testnet + address_parse_info& info + , uint8_t nettype , const std::string& str_or_url , std::function&, bool)> dns_confirm = return_first_address ); @@ -136,26 +126,3 @@ namespace cryptonote { bool parse_hash256(const std::string str_hash, crypto::hash& hash); -namespace crypto { - inline std::ostream &operator <<(std::ostream &o, const crypto::public_key &v) { - epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; - } - inline std::ostream &operator <<(std::ostream &o, const crypto::secret_key &v) { - epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; - } - inline std::ostream &operator <<(std::ostream &o, const crypto::key_derivation &v) { - epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; - } - inline std::ostream &operator <<(std::ostream &o, const crypto::key_image &v) { - epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; - } - inline std::ostream &operator <<(std::ostream &o, const crypto::signature &v) { - epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; - } - inline std::ostream &operator <<(std::ostream &o, const crypto::hash &v) { - epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; - } - inline std::ostream &operator <<(std::ostream &o, const crypto::hash8 &v) { - epee::to_hex::formatted(o, epee::as_byte_span(v)); return o; - } -} diff --git a/src/Native/libcryptonote/cryptonote_basic/cryptonote_boost_serialization.h b/src/Native/libcryptonote/cryptonote_basic/cryptonote_boost_serialization.h index 6e4ac9b72..143133163 100644 --- a/src/Native/libcryptonote/cryptonote_basic/cryptonote_boost_serialization.h +++ b/src/Native/libcryptonote/cryptonote_basic/cryptonote_boost_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -83,6 +83,11 @@ namespace boost { a & reinterpret_cast(x); } + template + inline void serialize(Archive &a, crypto::hash8 &x, const boost::serialization::version_type ver) + { + a & reinterpret_cast(x); + } template inline void serialize(Archive &a, cryptonote::txout_to_script &x, const boost::serialization::version_type ver) @@ -206,6 +211,23 @@ namespace boost a & x.Ci; } + template + inline void serialize(Archive &a, rct::Bulletproof &x, const boost::serialization::version_type ver) + { + a & x.V; + a & x.A; + a & x.S; + a & x.T1; + a & x.T2; + a & x.taux; + a & x.mu; + a & x.L; + a & x.R; + a & x.a; + a & x.b; + a & x.t; + } + template inline void serialize(Archive &a, rct::boroSig &x, const boost::serialization::version_type ver) { @@ -230,6 +252,21 @@ namespace boost // a & x.senderPk; // not serialized, as we do not use it in monero currently } + template + inline void serialize(Archive &a, rct::multisig_kLRki &x, const boost::serialization::version_type ver) + { + a & x.k; + a & x.L; + a & x.R; + a & x.ki; + } + + template + inline void serialize(Archive &a, rct::multisig_out &x, const boost::serialization::version_type ver) + { + a & x.c; + } + template inline typename std::enable_if::type serializeOutPk(Archive &a, rct::ctkeyV &outPk_, const boost::serialization::version_type ver) { @@ -258,11 +295,11 @@ namespace boost a & x.type; if (x.type == rct::RCTTypeNull) return; - if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple) + if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeFullBulletproof && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeSimpleBulletproof) throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type"); // a & x.message; message is not serialized, as it can be reconstructed from the tx data // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets - if (x.type == rct::RCTTypeSimple) + if (x.type == rct::RCTTypeSimple) // moved to prunable with bulletproofs a & x.pseudoOuts; a & x.ecdhInfo; serializeOutPk(a, x.outPk, ver); @@ -273,7 +310,11 @@ namespace boost inline void serialize(Archive &a, rct::rctSigPrunable &x, const boost::serialization::version_type ver) { a & x.rangeSigs; + if (x.rangeSigs.empty()) + a & x.bulletproofs; a & x.MGs; + if (x.rangeSigs.empty()) + a & x.pseudoOuts; } template @@ -282,7 +323,7 @@ namespace boost a & x.type; if (x.type == rct::RCTTypeNull) return; - if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple) + if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeFullBulletproof && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeSimpleBulletproof) throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type"); // a & x.message; message is not serialized, as it can be reconstructed from the tx data // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets @@ -293,7 +334,11 @@ namespace boost a & x.txnFee; //-------------- a & x.p.rangeSigs; + if (x.p.rangeSigs.empty()) + a & x.p.bulletproofs; a & x.p.MGs; + if (x.type == rct::RCTTypeSimpleBulletproof) + a & x.p.pseudoOuts; } } } diff --git a/src/Native/libcryptonote/cryptonote_basic/cryptonote_format_utils.cpp b/src/Native/libcryptonote/cryptonote_basic/cryptonote_format_utils.cpp index 745dfb72e..934767960 100644 --- a/src/Native/libcryptonote/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/Native/libcryptonote/cryptonote_basic/cryptonote_format_utils.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -32,8 +32,11 @@ using namespace epee; #include +#include +#include "wipeable_string.h" +#include "string_tools.h" +#include "serialization/string.h" #include "cryptonote_format_utils.h" -#include "cryptonote_config.h" #include "crypto/crypto.h" #include "crypto/hash.h" #include "ringct/rctSigs.h" @@ -45,6 +48,8 @@ using namespace epee; // #define ENABLE_HASH_CASH_INTEGRITY_CHECK +using namespace crypto; + static const uint64_t valid_decomposed_outputs[] = { (uint64_t)1, (uint64_t)2, (uint64_t)3, (uint64_t)4, (uint64_t)5, (uint64_t)6, (uint64_t)7, (uint64_t)8, (uint64_t)9, // 1 piconero (uint64_t)10, (uint64_t)20, (uint64_t)30, (uint64_t)40, (uint64_t)50, (uint64_t)60, (uint64_t)70, (uint64_t)80, (uint64_t)90, @@ -75,6 +80,31 @@ static std::atomic tx_hashes_cached_count(0); static std::atomic block_hashes_calculated_count(0); static std::atomic block_hashes_cached_count(0); +#define CHECK_AND_ASSERT_THROW_MES_L1(expr, message) {if(!(expr)) {MWARNING(message); throw std::runtime_error(message);}} + +namespace cryptonote +{ + static inline unsigned char *operator &(ec_point &point) { + return &reinterpret_cast(point); + } + static inline const unsigned char *operator &(const ec_point &point) { + return &reinterpret_cast(point); + } + + // a copy of rct::addKeys, since we can't link to libringct to avoid circular dependencies + static void add_public_key(crypto::public_key &AB, const crypto::public_key &A, const crypto::public_key &B) { + ge_p3 B2, A2; + CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&B2, &B) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast(__LINE__)); + CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&A2, &A) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast(__LINE__)); + ge_cached tmp2; + ge_p3_to_cached(&tmp2, &B2); + ge_p1p1 tmp3; + ge_add(&tmp3, &A2, &tmp2); + ge_p1p1_to_p3(&A2, &tmp3); + ge_p3_tobytes(&AB, &A2); + } +} + namespace cryptonote { //--------------------------------------------------------------- @@ -129,18 +159,58 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - bool generate_key_image_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki) + bool generate_key_image_helper_precomp(const account_keys& ack, const crypto::public_key& out_key, const crypto::key_derivation& recv_derivation, size_t real_output_index, const subaddress_index& received_index, keypair& in_ephemeral, crypto::key_image& ki, hw::device &hwdev) { - crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation); - bool r = crypto::generate_key_derivation(tx_public_key, ack.m_view_secret_key, recv_derivation); - CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")"); + if (ack.m_spend_secret_key == crypto::null_skey) + { + // for watch-only wallet, simply copy the known output pubkey + in_ephemeral.pub = out_key; + in_ephemeral.sec = crypto::null_skey; + } + else + { + // derive secret key with subaddress - step 1: original CN derivation + crypto::secret_key scalar_step1; + hwdev.derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, scalar_step1); // computes Hs(a*R || idx) + b + + // step 2: add Hs(a || index_major || index_minor) + crypto::secret_key subaddr_sk; + crypto::secret_key scalar_step2; + if (received_index.is_zero()) + { + scalar_step2 = scalar_step1; // treat index=(0,0) as a special case representing the main address + } + else + { + subaddr_sk = hwdev.get_subaddress_secret_key(ack.m_view_secret_key, received_index); + hwdev.sc_secret_add(scalar_step2, scalar_step1,subaddr_sk); + } - r = crypto::derive_public_key(recv_derivation, real_output_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub); - CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to derive_public_key(" << recv_derivation << ", " << real_output_index << ", " << ack.m_account_address.m_spend_public_key << ")"); + in_ephemeral.sec = scalar_step2; + + if (ack.m_multisig_keys.empty()) + { + // when not in multisig, we know the full spend secret key, so the output pubkey can be obtained by scalarmultBase + CHECK_AND_ASSERT_MES(hwdev.secret_key_to_public_key(in_ephemeral.sec, in_ephemeral.pub), false, "Failed to derive public key"); + } + else + { + // when in multisig, we only know the partial spend secret key. but we do know the full spend public key, so the output pubkey can be obtained by using the standard CN key derivation + CHECK_AND_ASSERT_MES(hwdev.derive_public_key(recv_derivation, real_output_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub), false, "Failed to derive public key"); + // and don't forget to add the contribution from the subaddress part + if (!received_index.is_zero()) + { + crypto::public_key subaddr_pk; + CHECK_AND_ASSERT_MES(hwdev.secret_key_to_public_key(subaddr_sk, subaddr_pk), false, "Failed to derive public key"); + add_public_key(in_ephemeral.pub, in_ephemeral.pub, subaddr_pk); + } + } - crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, in_ephemeral.sec); + CHECK_AND_ASSERT_MES(in_ephemeral.pub == out_key, + false, "key image helper precomp: given output pubkey doesn't match the derived one"); + } - crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, ki); + hwdev.generate_key_image(in_ephemeral.pub, in_ephemeral.sec, ki); return true; } //--------------------------------------------------------------- @@ -271,9 +341,53 @@ namespace cryptonote //--------------------------------------------------------------- bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key) { - tx.extra.resize(tx.extra.size() + 1 + sizeof(crypto::public_key)); - tx.extra[tx.extra.size() - 1 - sizeof(crypto::public_key)] = TX_EXTRA_TAG_PUBKEY; - *reinterpret_cast(&tx.extra[tx.extra.size() - sizeof(crypto::public_key)]) = tx_pub_key; + return add_tx_pub_key_to_extra(tx.extra, tx_pub_key); + } + //--------------------------------------------------------------- + bool add_tx_pub_key_to_extra(transaction_prefix& tx, const crypto::public_key& tx_pub_key) + { + return add_tx_pub_key_to_extra(tx.extra, tx_pub_key); + } + //--------------------------------------------------------------- + bool add_tx_pub_key_to_extra(std::vector& tx_extra, const crypto::public_key& tx_pub_key) + { + tx_extra.resize(tx_extra.size() + 1 + sizeof(crypto::public_key)); + tx_extra[tx_extra.size() - 1 - sizeof(crypto::public_key)] = TX_EXTRA_TAG_PUBKEY; + *reinterpret_cast(&tx_extra[tx_extra.size() - sizeof(crypto::public_key)]) = tx_pub_key; + return true; + } + //--------------------------------------------------------------- + std::vector get_additional_tx_pub_keys_from_extra(const std::vector& tx_extra) + { + // parse + std::vector tx_extra_fields; + parse_tx_extra(tx_extra, tx_extra_fields); + // find corresponding field + tx_extra_additional_pub_keys additional_pub_keys; + if(!find_tx_extra_field_by_type(tx_extra_fields, additional_pub_keys)) + return {}; + return additional_pub_keys.data; + } + //--------------------------------------------------------------- + std::vector get_additional_tx_pub_keys_from_extra(const transaction_prefix& tx) + { + return get_additional_tx_pub_keys_from_extra(tx.extra); + } + //--------------------------------------------------------------- + bool add_additional_tx_pub_keys_to_extra(std::vector& tx_extra, const std::vector& additional_pub_keys) + { + // convert to variant + tx_extra_field field = tx_extra_additional_pub_keys{ additional_pub_keys }; + // serialize + std::ostringstream oss; + binary_archive ar(oss); + bool r = ::do_serialize(ar, field); + CHECK_AND_NO_ASSERT_MES_L1(r, false, "failed to serialize tx extra additional tx pub keys"); + // append + std::string tx_extra_str = oss.str(); + size_t pos = tx_extra.size(); + tx_extra.resize(tx_extra.size() + tx_extra_str.size()); + memcpy(&tx_extra[pos], tx_extra_str.data(), tx_extra_str.size()); return true; } //--------------------------------------------------------------- @@ -360,30 +474,6 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - bool encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) - { - crypto::key_derivation derivation; - crypto::hash hash; - char data[33]; /* A hash, and an extra byte */ - - if (!generate_key_derivation(public_key, secret_key, derivation)) - return false; - - memcpy(data, &derivation, 32); - data[32] = ENCRYPTED_PAYMENT_ID_TAIL; - cn_fast_hash(data, 33, hash); - - for (size_t b = 0; b < 8; ++b) - payment_id.data[b] ^= hash.data[b]; - - return true; - } - bool decrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) - { - // Encryption and decryption are the same operation (xor with a key) - return encrypt_payment_id(payment_id, public_key, secret_key); - } - //--------------------------------------------------------------- bool get_inputs_money_amount(const transaction& tx, uint64_t& money) { money = 0; @@ -479,48 +569,27 @@ namespace cryptonote res.insert(8, "...."); return res; } - //--------------------------------------------------------------- - bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, size_t output_index) - { - crypto::key_derivation derivation; - generate_key_derivation(tx_pub_key, acc.m_view_secret_key, derivation); - crypto::public_key pk; - derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk); - return pk == out_key.key; - } - //--------------------------------------------------------------- - bool is_out_to_acc_precomp(const crypto::public_key& spend_public_key, const txout_to_key& out_key, const crypto::key_derivation& derivation, size_t output_index) - { - crypto::public_key pk; - derive_public_key(derivation, output_index, spend_public_key, pk); - return pk == out_key.key; - } - //--------------------------------------------------------------- - bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector& outs, uint64_t& money_transfered) - { - crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); - if(null_pkey == tx_pub_key) - return false; - return lookup_acc_outs(acc, tx, tx_pub_key, outs, money_transfered); - } - //--------------------------------------------------------------- - bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector& outs, uint64_t& money_transfered) + boost::optional is_out_to_acc_precomp(const std::unordered_map& subaddresses, const crypto::public_key& out_key, const crypto::key_derivation& derivation, const std::vector& additional_derivations, size_t output_index, hw::device &hwdev) { - money_transfered = 0; - size_t i = 0; - for(const tx_out& o: tx.vout) + // try the shared tx pubkey + crypto::public_key subaddress_spendkey; + hwdev.derive_subaddress_public_key(out_key, derivation, output_index, subaddress_spendkey); + auto found = subaddresses.find(subaddress_spendkey); + if (found != subaddresses.end()) + return subaddress_receive_info{ found->second, derivation }; + // try additional tx pubkeys if available + if (!additional_derivations.empty()) { - CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_key), false, "wrong type id in transaction out" ); - if(is_out_to_acc(acc, boost::get(o.target), tx_pub_key, i)) - { - outs.push_back(i); - money_transfered += o.amount; - } - i++; + CHECK_AND_ASSERT_MES(output_index < additional_derivations.size(), boost::none, "wrong number of additional derivations"); + hwdev.derive_subaddress_public_key(out_key, additional_derivations[output_index], output_index, subaddress_spendkey); + found = subaddresses.find(subaddress_spendkey); + if (found != subaddresses.end()) + return subaddress_receive_info{ found->second, additional_derivations[output_index] }; } - return true; + return boost::none; } //--------------------------------------------------------------- + //--------------------------------------------------------------- void get_blob_hash(const blobdata& blob, crypto::hash& res) { cn_fast_hash(blob.data(), blob.size(), res); @@ -632,7 +701,7 @@ namespace cryptonote // prunable rct if (t.rct_signatures.type == rct::RCTTypeNull) { - hashes[2] = cryptonote::null_hash; + hashes[2] = crypto::null_hash; } else { @@ -768,7 +837,8 @@ namespace cryptonote return true; } blobdata bd = get_block_hashing_blob(b); - crypto::cn_slow_hash(bd.data(), bd.size(), res); + const int cn_variant = b.major_version >= 7 ? b.major_version - 6 : 0; + crypto::cn_slow_hash(bd.data(), bd.size(), res, cn_variant); return true; } //--------------------------------------------------------------- @@ -869,4 +939,20 @@ namespace cryptonote block_hashes_calculated = block_hashes_calculated_count; block_hashes_cached = block_hashes_cached_count; } + //--------------------------------------------------------------- + crypto::secret_key encrypt_key(crypto::secret_key key, const epee::wipeable_string &passphrase) + { + crypto::hash hash; + crypto::cn_slow_hash(passphrase.data(), passphrase.size(), hash); + sc_add((unsigned char*)key.data, (const unsigned char*)key.data, (const unsigned char*)hash.data); + return key; + } + //--------------------------------------------------------------- + crypto::secret_key decrypt_key(crypto::secret_key key, const epee::wipeable_string &passphrase) + { + crypto::hash hash; + crypto::cn_slow_hash(passphrase.data(), passphrase.size(), hash); + sc_sub((unsigned char*)key.data, (const unsigned char*)key.data, (const unsigned char*)hash.data); + return key; + } } diff --git a/src/Native/libcryptonote/cryptonote_basic/cryptonote_format_utils.h b/src/Native/libcryptonote/cryptonote_basic/cryptonote_format_utils.h index d8ccf8eec..79466e9c4 100644 --- a/src/Native/libcryptonote/cryptonote_basic/cryptonote_format_utils.h +++ b/src/Native/libcryptonote/cryptonote_basic/cryptonote_format_utils.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -29,12 +29,19 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #pragma once -#include "cryptonote_protocol/cryptonote_protocol_defs.h" +#include "blobdatatype.h" #include "cryptonote_basic_impl.h" #include "account.h" +#include "subaddress_index.h" #include "include_base_utils.h" #include "crypto/crypto.h" #include "crypto/hash.h" +#include + +namespace epee +{ + class wipeable_string; +} namespace cryptonote { @@ -44,8 +51,6 @@ namespace cryptonote bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash); bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx); bool parse_and_validate_tx_base_from_blob(const blobdata& tx_blob, transaction& tx); - bool encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key); - bool decrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key); template bool find_tx_extra_field_by_type(const std::vector& tx_extra_fields, T& field, size_t index = 0) @@ -63,19 +68,30 @@ namespace cryptonote crypto::public_key get_tx_pub_key_from_extra(const transaction_prefix& tx, size_t pk_index = 0); crypto::public_key get_tx_pub_key_from_extra(const transaction& tx, size_t pk_index = 0); bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key); + bool add_tx_pub_key_to_extra(transaction_prefix& tx, const crypto::public_key& tx_pub_key); + bool add_tx_pub_key_to_extra(std::vector& tx_extra, const crypto::public_key& tx_pub_key); + std::vector get_additional_tx_pub_keys_from_extra(const std::vector& tx_extra); + std::vector get_additional_tx_pub_keys_from_extra(const transaction_prefix& tx); + bool add_additional_tx_pub_keys_to_extra(std::vector& tx_extra, const std::vector& additional_pub_keys); bool add_extra_nonce_to_tx_extra(std::vector& tx_extra, const blobdata& extra_nonce); bool remove_field_from_tx_extra(std::vector& tx_extra, const std::type_info &type); void set_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash& payment_id); void set_encrypted_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash8& payment_id); bool get_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash& payment_id); bool get_encrypted_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash8& payment_id); - bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, size_t output_index); - bool is_out_to_acc_precomp(const crypto::public_key& spend_public_key, const txout_to_key& out_key, const crypto::key_derivation& derivation, size_t output_index); - bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector& outs, uint64_t& money_transfered); + bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, const std::vector& additional_tx_public_keys, size_t output_index); + struct subaddress_receive_info + { + subaddress_index index; + crypto::key_derivation derivation; + }; + boost::optional is_out_to_acc_precomp(const std::unordered_map& subaddresses, const crypto::public_key& out_key, const crypto::key_derivation& derivation, const std::vector& additional_derivations, size_t output_index, hw::device &hwdev); + bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, const std::vector& additional_tx_public_keys, std::vector& outs, uint64_t& money_transfered); bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector& outs, uint64_t& money_transfered); bool get_tx_fee(const transaction& tx, uint64_t & fee); uint64_t get_tx_fee(const transaction& tx); - bool generate_key_image_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki); + bool generate_key_image_helper(const account_keys& ack, const std::unordered_map& subaddresses, const crypto::public_key& out_key, const crypto::public_key& tx_public_key, const std::vector& additional_tx_public_keys, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki, hw::device &hwdev); + bool generate_key_image_helper_precomp(const account_keys& ack, const crypto::public_key& out_key, const crypto::key_derivation& recv_derivation, size_t real_output_index, const subaddress_index& received_index, keypair& in_ephemeral, crypto::key_image& ki, hw::device &hwdev); void get_blob_hash(const blobdata& blob, crypto::hash& res); crypto::hash get_blob_hash(const blobdata& blob); std::string short_hash_str(const crypto::hash& h); @@ -212,8 +228,9 @@ namespace cryptonote bool is_valid_decomposed_amount(uint64_t amount); void get_hash_stats(uint64_t &tx_hashes_calculated, uint64_t &tx_hashes_cached, uint64_t &block_hashes_calculated, uint64_t & block_hashes_cached); + crypto::secret_key encrypt_key(crypto::secret_key key, const epee::wipeable_string &passphrase); + crypto::secret_key decrypt_key(crypto::secret_key key, const epee::wipeable_string &passphrase); #define CHECKED_GET_SPECIFIC_VARIANT(variant_var, specific_type, variable_name, fail_return_val) \ CHECK_AND_ASSERT_MES(variant_var.type() == typeid(specific_type), fail_return_val, "wrong variant type: " << variant_var.type().name() << ", expected " << typeid(specific_type).name()); \ specific_type& variable_name = boost::get(variant_var); - } diff --git a/src/Native/libcryptonote/cryptonote_basic/cryptonote_stat_info.h b/src/Native/libcryptonote/cryptonote_basic/cryptonote_stat_info.h index 7ebf86878..c0be2144e 100644 --- a/src/Native/libcryptonote/cryptonote_basic/cryptonote_stat_info.h +++ b/src/Native/libcryptonote/cryptonote_basic/cryptonote_stat_info.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/cryptonote_basic/difficulty.cpp b/src/Native/libcryptonote/cryptonote_basic/difficulty.cpp index 863aa4359..cb2a00a12 100644 --- a/src/Native/libcryptonote/cryptonote_basic/difficulty.cpp +++ b/src/Native/libcryptonote/cryptonote_basic/difficulty.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/cryptonote_basic/difficulty.h b/src/Native/libcryptonote/cryptonote_basic/difficulty.h index aeb1c030d..b06538467 100644 --- a/src/Native/libcryptonote/cryptonote_basic/difficulty.h +++ b/src/Native/libcryptonote/cryptonote_basic/difficulty.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/cryptonote_basic/hardfork.cpp b/src/Native/libcryptonote/cryptonote_basic/hardfork.cpp index 546af2076..95f1ecab9 100644 --- a/src/Native/libcryptonote/cryptonote_basic/hardfork.cpp +++ b/src/Native/libcryptonote/cryptonote_basic/hardfork.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/cryptonote_basic/hardfork.h b/src/Native/libcryptonote/cryptonote_basic/hardfork.h index 6c6fbcb84..ee5ec0596 100644 --- a/src/Native/libcryptonote/cryptonote_basic/hardfork.h +++ b/src/Native/libcryptonote/cryptonote_basic/hardfork.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -79,7 +79,6 @@ namespace cryptonote * returns true if no error, false otherwise * * @param version the major block version for the fork - * @param voting_version the minor block version for the fork, used for voting * @param height The height the hardfork takes effect * @param time Approximate time of the hardfork (seconds since epoch) */ diff --git a/src/Native/libcryptonote/cryptonote_basic/miner.cpp b/src/Native/libcryptonote/cryptonote_basic/miner.cpp index 3c5811d61..f949bbd2b 100644 --- a/src/Native/libcryptonote/cryptonote_basic/miner.cpp +++ b/src/Native/libcryptonote/cryptonote_basic/miner.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -32,14 +32,17 @@ #include #include #include +#include #include -#include "misc_language.h" #include "include_base_utils.h" +#include "misc_language.h" +#include "syncobj.h" #include "cryptonote_basic_impl.h" #include "cryptonote_format_utils.h" #include "file_io_utils.h" #include "common/command_line.h" #include "string_coding.h" +#include "string_tools.h" #include "storages/portable_storage_template_helper.h" #include "boost/logic/tribool.hpp" @@ -53,6 +56,19 @@ #include #endif +#ifdef __FreeBSD__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "miner" @@ -182,7 +198,8 @@ namespace cryptonote { uint64_t total_hr = std::accumulate(m_last_hash_rates.begin(), m_last_hash_rates.end(), 0); float hr = static_cast(total_hr)/static_cast(m_last_hash_rates.size()); - std::cout << "hashrate: " << std::setprecision(4) << std::fixed << hr << ENDL; + const auto precision = std::cout.precision(); + std::cout << "hashrate: " << std::setprecision(4) << std::fixed << hr << precision << ENDL; } } m_last_hr_merge_time = misc_utils::get_tick_count(); @@ -201,7 +218,7 @@ namespace cryptonote command_line::add_arg(desc, arg_bg_mining_miner_target_percentage); } //----------------------------------------------------------------------------------------------------- - bool miner::init(const boost::program_options::variables_map& vm, bool testnet) + bool miner::init(const boost::program_options::variables_map& vm, network_type nettype) { if(command_line::has_arg(vm, arg_extra_messages)) { @@ -228,11 +245,13 @@ namespace cryptonote if(command_line::has_arg(vm, arg_start_mining)) { - if(!cryptonote::get_account_address_from_str(m_mine_address, testnet, command_line::get_arg(vm, arg_start_mining))) + address_parse_info info; + if(!cryptonote::get_account_address_from_str(info, nettype, command_line::get_arg(vm, arg_start_mining)) || info.is_subaddress) { LOG_ERROR("Target account address " << command_line::get_arg(vm, arg_start_mining) << " has wrong format, starting daemon canceled"); return false; } + m_mine_address = info.address; m_threads_total = 1; m_do_mining = true; if(command_line::has_arg(vm, arg_mining_threads)) @@ -289,8 +308,7 @@ namespace cryptonote return false; } - if(!m_template_no) - request_block_template();//lets update block template + request_block_template();//lets update block template boost::interprocess::ipcdetail::atomic_write32(&m_stop, 0); boost::interprocess::ipcdetail::atomic_write32(&m_thread_index, 0); @@ -584,7 +602,7 @@ namespace cryptonote // this should take care of the case where mining is started with bg-enabled, // and then the user decides to un-check background mining, and just do // regular full-speed mining. I might just be over-doing it and thinking up - // non-existant use-cases, so if the concensus is to simplify, we can remove all this fluff. + // non-existant use-cases, so if the consensus is to simplify, we can remove all this fluff. /* while( !m_is_background_mining_enabled ) { @@ -606,21 +624,15 @@ namespace cryptonote continue; // if interrupted because stop called, loop should end .. } - boost::tribool battery_powered(on_battery_power()); - bool on_ac_power = false; - if(indeterminate( battery_powered )) + bool on_ac_power = m_ignore_battery; + if(!m_ignore_battery) { - // name could be better, only ignores battery requirement if we failed - // to get the status of the system - if( m_ignore_battery ) + boost::tribool battery_powered(on_battery_power()); + if(!indeterminate( battery_powered )) { - on_ac_power = true; + on_ac_power = !battery_powered; } } - else - { - on_ac_power = !battery_powered; - } if( m_is_background_mining_started ) { @@ -734,8 +746,6 @@ namespace cryptonote #elif defined(__linux__) - const std::string STR_CPU("cpu"); - const std::size_t STR_CPU_LEN = STR_CPU.size(); const std::string STAT_FILE_PATH = "/proc/stat"; if( !epee::file_io_utils::is_file_exist(STAT_FILE_PATH) ) @@ -784,9 +794,36 @@ namespace cryptonote return true; + #elif defined(__FreeBSD__) + + struct statinfo s; + size_t n = sizeof(s.cp_time); + if( sysctlbyname("kern.cp_time", s.cp_time, &n, NULL, 0) == -1 ) + { + LOG_ERROR("sysctlbyname(\"kern.cp_time\"): " << strerror(errno)); + return false; + } + if( n != sizeof(s.cp_time) ) + { + LOG_ERROR("sysctlbyname(\"kern.cp_time\") output is unexpectedly " + << n << " bytes instead of the expected " << sizeof(s.cp_time) + << " bytes."); + return false; + } + + idle_time = s.cp_time[CP_IDLE]; + total_time = + s.cp_time[CP_USER] + + s.cp_time[CP_NICE] + + s.cp_time[CP_SYS] + + s.cp_time[CP_INTR] + + s.cp_time[CP_IDLE]; + + return true; + #endif - return false; // unsupported systemm.. + return false; // unsupported system } //----------------------------------------------------------------------------------------------------- bool miner::get_process_time(uint64_t& total_time) @@ -806,7 +843,7 @@ namespace cryptonote return true; } - #elif (defined(__linux__) && defined(_SC_CLK_TCK)) || defined(__APPLE__) + #elif (defined(__linux__) && defined(_SC_CLK_TCK)) || defined(__APPLE__) || defined(__FreeBSD__) struct tms tms; if ( times(&tms) != (clock_t)-1 ) @@ -817,7 +854,7 @@ namespace cryptonote #endif - return false; // unsupported system.. + return false; // unsupported system } //----------------------------------------------------------------------------------------------------- uint8_t miner::get_percent_of_total(uint64_t other, uint64_t total) @@ -858,19 +895,6 @@ namespace cryptonote const boost::filesystem::path& power_supply_path = iter->path(); if (boost::filesystem::is_directory(power_supply_path)) { - std::ifstream power_supply_present_stream((power_supply_path / "present").string()); - if (power_supply_present_stream.fail()) - { - LOG_PRINT_L0("Unable to read from " << power_supply_path << " to check if power supply present"); - continue; - } - - if (power_supply_present_stream.get() != '1') - { - LOG_PRINT_L4("Power supply not present at " << power_supply_path); - continue; - } - boost::filesystem::path power_supply_type_path = power_supply_path / "type"; if (boost::filesystem::is_regular_file(power_supply_type_path)) { @@ -941,6 +965,70 @@ namespace cryptonote } return on_battery; + #elif defined(__FreeBSD__) + int ac; + size_t n = sizeof(ac); + if( sysctlbyname("hw.acpi.acline", &ac, &n, NULL, 0) == -1 ) + { + if( errno != ENOENT ) + { + LOG_ERROR("Cannot query battery status: " + << "sysctlbyname(\"hw.acpi.acline\"): " << strerror(errno)); + return boost::logic::tribool(boost::logic::indeterminate); + } + + // If sysctl fails with ENOENT, then try querying /dev/apm. + + static const char* dev_apm = "/dev/apm"; + const int fd = open(dev_apm, O_RDONLY); + if( fd == -1 ) { + LOG_ERROR("Cannot query battery status: " + << "open(): " << dev_apm << ": " << strerror(errno)); + return boost::logic::tribool(boost::logic::indeterminate); + } + + apm_info info; + if( ioctl(fd, APMIO_GETINFO, &info) == -1 ) { + close(fd); + LOG_ERROR("Cannot query battery status: " + << "ioctl(" << dev_apm << ", APMIO_GETINFO): " << strerror(errno)); + return boost::logic::tribool(boost::logic::indeterminate); + } + + close(fd); + + // See apm(8). + switch( info.ai_acline ) + { + case 0: // off-line + case 2: // backup power + return boost::logic::tribool(true); + case 1: // on-line + return boost::logic::tribool(false); + } + switch( info.ai_batt_stat ) + { + case 0: // high + case 1: // low + case 2: // critical + return boost::logic::tribool(true); + case 3: // charging + return boost::logic::tribool(false); + } + + LOG_ERROR("Cannot query battery status: " + << "sysctl hw.acpi.acline is not available and /dev/apm returns " + << "unexpected ac-line status (" << info.ai_acline << ") and " + << "battery status (" << info.ai_batt_stat << ")."); + return boost::logic::tribool(boost::logic::indeterminate); + } + if( n != sizeof(ac) ) + { + LOG_ERROR("sysctlbyname(\"hw.acpi.acline\") output is unexpectedly " + << n << " bytes instead of the expected " << sizeof(ac) << " bytes."); + return boost::logic::tribool(boost::logic::indeterminate); + } + return boost::logic::tribool(ac == 0); #endif LOG_ERROR("couldn't query power status"); diff --git a/src/Native/libcryptonote/cryptonote_basic/miner.h b/src/Native/libcryptonote/cryptonote_basic/miner.h index 964ee6a36..2bff784c7 100644 --- a/src/Native/libcryptonote/cryptonote_basic/miner.h +++ b/src/Native/libcryptonote/cryptonote_basic/miner.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -64,7 +64,7 @@ namespace cryptonote public: miner(i_miner_handler* phandler); ~miner(); - bool init(const boost::program_options::variables_map& vm, bool testnet); + bool init(const boost::program_options::variables_map& vm, network_type nettype); static void init_options(boost::program_options::options_description& desc); bool set_block_template(const block& bl, const difficulty_type& diffic, uint64_t height); bool on_block_chain_update(); diff --git a/src/Native/libcryptonote/cryptonote_basic/subaddress_index.h b/src/Native/libcryptonote/cryptonote_basic/subaddress_index.h new file mode 100644 index 000000000..9b71448f9 --- /dev/null +++ b/src/Native/libcryptonote/cryptonote_basic/subaddress_index.h @@ -0,0 +1,102 @@ +// Copyright (c) 2017-2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// 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. + +#pragma once + +#include "serialization/keyvalue_serialization.h" +#include +#include +#include + +namespace cryptonote +{ + struct subaddress_index + { + uint32_t major; + uint32_t minor; + bool operator==(const subaddress_index& rhs) const { return !memcmp(this, &rhs, sizeof(subaddress_index)); } + bool operator!=(const subaddress_index& rhs) const { return !(*this == rhs); } + bool is_zero() const { return major == 0 && minor == 0; } + + BEGIN_SERIALIZE_OBJECT() + FIELD(major) + FIELD(minor) + END_SERIALIZE() + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(major) + KV_SERIALIZE(minor) + END_KV_SERIALIZE_MAP() + }; +} + +namespace cryptonote { + inline std::ostream& operator<<(std::ostream& out, const cryptonote::subaddress_index& subaddr_index) + { + return out << subaddr_index.major << '/' << subaddr_index.minor; + } +} + +namespace std +{ + template <> + struct hash + { + size_t operator()(const cryptonote::subaddress_index& index ) const + { + size_t res; + if (sizeof(size_t) == 8) + { + res = ((uint64_t)index.major << 32) | index.minor; + } + else + { + // https://stackoverflow.com/a/17017281 + res = 17; + res = res * 31 + hash()(index.major); + res = res * 31 + hash()(index.minor); + } + return res; + } + }; +} + +BOOST_CLASS_VERSION(cryptonote::subaddress_index, 0) + +namespace boost +{ + namespace serialization + { + template + inline void serialize(Archive &a, cryptonote::subaddress_index &x, const boost::serialization::version_type ver) + { + a & x.major; + a & x.minor; + } + } +} diff --git a/src/Native/libcryptonote/cryptonote_basic/tx_extra.h b/src/Native/libcryptonote/cryptonote_basic/tx_extra.h index 5a6c3176d..009e35ebe 100644 --- a/src/Native/libcryptonote/cryptonote_basic/tx_extra.h +++ b/src/Native/libcryptonote/cryptonote_basic/tx_extra.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -38,6 +38,7 @@ #define TX_EXTRA_TAG_PUBKEY 0x01 #define TX_EXTRA_NONCE 0x02 #define TX_EXTRA_MERGE_MINING_TAG 0x03 +#define TX_EXTRA_TAG_ADDITIONAL_PUBKEYS 0x04 #define TX_EXTRA_MYSTERIOUS_MINERGATE_TAG 0xDE #define TX_EXTRA_NONCE_PAYMENT_ID 0x00 @@ -159,6 +160,16 @@ namespace cryptonote } }; + // per-output additional tx pubkey for multi-destination transfers involving at least one subaddress + struct tx_extra_additional_pub_keys + { + std::vector data; + + BEGIN_SERIALIZE() + FIELD(data) + END_SERIALIZE() + }; + struct tx_extra_mysterious_minergate { std::string data; @@ -172,11 +183,12 @@ namespace cryptonote // varint tag; // varint size; // varint data[]; - typedef boost::variant tx_extra_field; + typedef boost::variant tx_extra_field; } VARIANT_TAG(binary_archive, cryptonote::tx_extra_padding, TX_EXTRA_TAG_PADDING); VARIANT_TAG(binary_archive, cryptonote::tx_extra_pub_key, TX_EXTRA_TAG_PUBKEY); VARIANT_TAG(binary_archive, cryptonote::tx_extra_nonce, TX_EXTRA_NONCE); VARIANT_TAG(binary_archive, cryptonote::tx_extra_merge_mining_tag, TX_EXTRA_MERGE_MINING_TAG); +VARIANT_TAG(binary_archive, cryptonote::tx_extra_additional_pub_keys, TX_EXTRA_TAG_ADDITIONAL_PUBKEYS); VARIANT_TAG(binary_archive, cryptonote::tx_extra_mysterious_minergate, TX_EXTRA_MYSTERIOUS_MINERGATE_TAG); diff --git a/src/Native/libcryptonote/cryptonote_basic/verification_context.h b/src/Native/libcryptonote/cryptonote_basic/verification_context.h index ce885ec1d..8d2b633a2 100644 --- a/src/Native/libcryptonote/cryptonote_basic/verification_context.h +++ b/src/Native/libcryptonote/cryptonote_basic/verification_context.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/cryptonote_config.h b/src/Native/libcryptonote/cryptonote_config.h index abfa665ff..0ad8a6005 100644 --- a/src/Native/libcryptonote/cryptonote_config.h +++ b/src/Native/libcryptonote/cryptonote_config.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -89,10 +89,10 @@ #define BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT 10000 //by default, blocks ids count in synchronizing -#define BLOCKS_SYNCHRONIZING_DEFAULT_COUNT 200 //by default, blocks count in blocks downloading -#define CRYPTONOTE_PROTOCOL_HOP_RELAX_COUNT 3 //value of hop, after which we use only announce of new block +#define BLOCKS_SYNCHRONIZING_DEFAULT_COUNT_PRE_V4 100 //by default, blocks count in blocks downloading +#define BLOCKS_SYNCHRONIZING_DEFAULT_COUNT 20 //by default, blocks count in blocks downloading -#define CRYPTONOTE_MEMPOOL_TX_LIVETIME 86400 //seconds, one day +#define CRYPTONOTE_MEMPOOL_TX_LIVETIME (86400*3) //seconds, three days #define CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME 604800 //seconds, one week #define COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT 1000 @@ -109,6 +109,7 @@ #define P2P_DEFAULT_INVOKE_TIMEOUT 60*2*1000 //2 minutes #define P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT 5000 //5 seconds #define P2P_DEFAULT_WHITELIST_CONNECTIONS_PERCENT 70 +#define P2P_DEFAULT_ANCHOR_CONNECTIONS_COUNT 2 #define P2P_FAILED_ADDR_FORGET_SECONDS (60*60) //1 hour #define P2P_IP_BLOCKTIME (60*60*24) //24 hour @@ -131,10 +132,15 @@ #define HF_VERSION_DYNAMIC_FEE 4 #define HF_VERSION_MIN_MIXIN_4 6 +#define HF_VERSION_MIN_MIXIN_6 7 #define HF_VERSION_ENFORCE_RCT 6 #define PER_KB_FEE_QUANTIZATION_DECIMALS 8 +#define HASH_OF_HASHES_STEP 256 + +#define DEFAULT_TXPOOL_MAX_SIZE 648000000ull // 3 days at 300000, in bytes + // New constants are intended to go here namespace config { @@ -146,8 +152,10 @@ namespace config uint64_t const CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 18; uint64_t const CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 19; + uint64_t const CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX = 42; uint16_t const P2P_DEFAULT_PORT = 18080; uint16_t const RPC_DEFAULT_PORT = 18081; + uint16_t const ZMQ_RPC_DEFAULT_PORT = 18082; boost::uuids::uuid const NETWORK_ID = { { 0x12 ,0x30, 0xF1, 0x71 , 0x61, 0x04 , 0x41, 0x61, 0x17, 0x31, 0x00, 0x82, 0x16, 0xA1, 0xA1, 0x10 } }; // Bender's nightmare @@ -158,12 +166,40 @@ namespace config { uint64_t const CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 53; uint64_t const CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 54; + uint64_t const CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX = 63; uint16_t const P2P_DEFAULT_PORT = 28080; uint16_t const RPC_DEFAULT_PORT = 28081; + uint16_t const ZMQ_RPC_DEFAULT_PORT = 28082; boost::uuids::uuid const NETWORK_ID = { { 0x12 ,0x30, 0xF1, 0x71 , 0x61, 0x04 , 0x41, 0x61, 0x17, 0x31, 0x00, 0x82, 0x16, 0xA1, 0xA1, 0x11 } }; // Bender's daydream - std::string const GENESIS_TX = "013c01ff0001ffffffffffff0f029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd0880712101168d0c4ca86fb55a4cf6a36d31431be1c53a3bd7411bb24e8832410289fa6f3b"; + std::string const GENESIS_TX = "013c01ff0001ffffffffffff03029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121017767aafcde9be00dcfd098715ebcf7f410daebc582fda69d24a28e9d0bc890d1"; uint32_t const GENESIS_NONCE = 10001; } + + namespace stagenet + { + uint64_t const CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 24; + uint64_t const CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 25; + uint64_t const CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX = 36; + uint16_t const P2P_DEFAULT_PORT = 38080; + uint16_t const RPC_DEFAULT_PORT = 38081; + uint16_t const ZMQ_RPC_DEFAULT_PORT = 38082; + boost::uuids::uuid const NETWORK_ID = { { + 0x12 ,0x30, 0xF1, 0x71 , 0x61, 0x04 , 0x41, 0x61, 0x17, 0x31, 0x00, 0x82, 0x16, 0xA1, 0xA1, 0x12 + } }; // Bender's daydream + std::string const GENESIS_TX = "013c01ff0001ffffffffffff0302df5d56da0c7d643ddd1ce61901c7bdc5fb1738bfe39fbe69c28a3a7032729c0f2101168d0c4ca86fb55a4cf6a36d31431be1c53a3bd7411bb24e8832410289fa6f3b"; + uint32_t const GENESIS_NONCE = 10002; + } +} + +namespace cryptonote +{ + enum network_type : uint8_t + { + MAINNET = 0, + TESTNET, + STAGENET, + FAKECHAIN + }; } diff --git a/src/Native/libcryptonote/device/device.hpp b/src/Native/libcryptonote/device/device.hpp new file mode 100644 index 000000000..b47460472 --- /dev/null +++ b/src/Native/libcryptonote/device/device.hpp @@ -0,0 +1,188 @@ +// Copyright (c) 2017-2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// 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. +// + + +/* Note about debug: + * To debug Device you can def the following : + * #define DEBUG_HWDEVICE + * Activate debug mechanism: + * - Add more trace + * - All computation done by device are checked by default device. + * Required IODUMMYCRYPT_HWDEVICE or IONOCRYPT_HWDEVICE for fully working + * #define IODUMMYCRYPT_HWDEVICE 1 + * - It assumes sensitive data encryption is is off on device side. a XOR with 0x55. This allow Ledger Class to make check on clear value + * #define IONOCRYPT_HWDEVICE 1 + * - It assumes sensitive data encryption is off on device side. + */ + + +#pragma once + +#include "crypto/crypto.h" +#include "crypto/chacha.h" +#include "ringct/rctTypes.h" + +#ifndef USE_DEVICE_LEDGER +#define USE_DEVICE_LEDGER 1 +#endif + +#if !defined(HAVE_PCSC) +#undef USE_DEVICE_LEDGER +#define USE_DEVICE_LEDGER 0 +#endif + +#if USE_DEVICE_LEDGER +#define WITH_DEVICE_LEDGER +#endif + +// forward declaration needed because this header is included by headers in libcryptonote_basic which depends on libdevice +namespace cryptonote +{ + struct account_public_address; + struct account_keys; + struct subaddress_index; +} + +namespace hw { + namespace { + //device funcion not supported + #define dfns() \ + throw std::runtime_error(std::string("device function not supported: ")+ std::string(__FUNCTION__) + \ + std::string(" (device.hpp line ")+std::to_string(__LINE__)+std::string(").")); \ + return false; + } + + + class device { + public: + + device() {} + device(const device &hwdev) {} + virtual ~device() {} + + explicit virtual operator bool() const = 0; + + static const int SIGNATURE_REAL = 0; + static const int SIGNATURE_FAKE = 1; + + + std::string name; + + /* ======================================================================= */ + /* SETUP/TEARDOWN */ + /* ======================================================================= */ + virtual bool set_name(const std::string &name) = 0; + virtual const std::string get_name() const = 0; + + virtual bool init(void) = 0; + virtual bool release() = 0; + + virtual bool connect(void) = 0; + virtual bool disconnect() = 0; + + /* ======================================================================= */ + /* WALLET & ADDRESS */ + /* ======================================================================= */ + virtual bool get_public_address(cryptonote::account_public_address &pubkey) = 0; + virtual bool get_secret_keys(crypto::secret_key &viewkey , crypto::secret_key &spendkey) = 0; + virtual bool generate_chacha_key(const cryptonote::account_keys &keys, crypto::chacha_key &key) = 0; + + /* ======================================================================= */ + /* SUB ADDRESS */ + /* ======================================================================= */ + virtual bool derive_subaddress_public_key(const crypto::public_key &pub, const crypto::key_derivation &derivation, const std::size_t output_index, crypto::public_key &derived_pub) = 0; + virtual crypto::public_key get_subaddress_spend_public_key(const cryptonote::account_keys& keys, const cryptonote::subaddress_index& index) = 0; + virtual std::vector get_subaddress_spend_public_keys(const cryptonote::account_keys &keys, uint32_t account, uint32_t begin, uint32_t end) = 0; + virtual cryptonote::account_public_address get_subaddress(const cryptonote::account_keys& keys, const cryptonote::subaddress_index &index) = 0; + virtual crypto::secret_key get_subaddress_secret_key(const crypto::secret_key &sec, const cryptonote::subaddress_index &index) = 0; + + /* ======================================================================= */ + /* DERIVATION & KEY */ + /* ======================================================================= */ + virtual bool verify_keys(const crypto::secret_key &secret_key, const crypto::public_key &public_key) = 0; + virtual bool scalarmultKey(rct::key & aP, const rct::key &P, const rct::key &a) = 0; + virtual bool scalarmultBase(rct::key &aG, const rct::key &a) = 0; + virtual bool sc_secret_add( crypto::secret_key &r, const crypto::secret_key &a, const crypto::secret_key &b) = 0; + virtual crypto::secret_key generate_keys(crypto::public_key &pub, crypto::secret_key &sec, const crypto::secret_key& recovery_key = crypto::secret_key(), bool recover = false) = 0; + virtual bool generate_key_derivation(const crypto::public_key &pub, const crypto::secret_key &sec, crypto::key_derivation &derivation) = 0; + virtual bool derivation_to_scalar(const crypto::key_derivation &derivation, const size_t output_index, crypto::ec_scalar &res) = 0; + virtual bool derive_secret_key(const crypto::key_derivation &derivation, const std::size_t output_index, const crypto::secret_key &sec, crypto::secret_key &derived_sec) = 0; + virtual bool derive_public_key(const crypto::key_derivation &derivation, const std::size_t output_index, const crypto::public_key &pub, crypto::public_key &derived_pub) = 0; + virtual bool secret_key_to_public_key(const crypto::secret_key &sec, crypto::public_key &pub) = 0; + virtual bool generate_key_image(const crypto::public_key &pub, const crypto::secret_key &sec, crypto::key_image &image) = 0; + + // alternative prototypes available in libringct + rct::key scalarmultKey(const rct::key &P, const rct::key &a) + { + rct::key aP; + scalarmultKey(aP, P, a); + return aP; + } + + rct::key scalarmultBase(const rct::key &a) + { + rct::key aG; + scalarmultBase(aG, a); + return aG; + } + + /* ======================================================================= */ + /* TRANSACTION */ + /* ======================================================================= */ + + virtual bool open_tx(crypto::secret_key &tx_key) = 0; + + virtual bool set_signature_mode(unsigned int sig_mode) = 0; + + virtual bool encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) = 0; + bool decrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) + { + // Encryption and decryption are the same operation (xor with a key) + return encrypt_payment_id(payment_id, public_key, secret_key); + } + + virtual bool ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & sharedSec) = 0; + virtual bool ecdhDecode(rct::ecdhTuple & masked, const rct::key & sharedSec) = 0; + + virtual bool add_output_key_mapping(const crypto::public_key &Aout, const crypto::public_key &Bout, const bool is_subaddress, const size_t real_output_index, + const rct::key &amount_key, const crypto::public_key &out_eph_public_key) = 0; + + + virtual bool mlsag_prehash(const std::string &blob, size_t inputs_size, size_t outputs_size, const rct::keyV &hashes, const rct::ctkeyV &outPk, rct::key &prehash) = 0; + virtual bool mlsag_prepare(const rct::key &H, const rct::key &xx, rct::key &a, rct::key &aG, rct::key &aHP, rct::key &rvII) = 0; + virtual bool mlsag_prepare(rct::key &a, rct::key &aG) = 0; + virtual bool mlsag_hash(const rct::keyV &long_message, rct::key &c) = 0; + virtual bool mlsag_sign(const rct::key &c, const rct::keyV &xx, const rct::keyV &alpha, const size_t rows, const size_t dsRows, rct::keyV &ss) = 0; + + virtual bool close_tx(void) = 0; + } ; + + device& get_device(const std::string device_descriptor) ; +} + diff --git a/src/Native/libcryptonote/exports.cpp b/src/Native/libcryptonote/exports.cpp index 5081474df..2ea47f905 100644 --- a/src/Native/libcryptonote/exports.cpp +++ b/src/Native/libcryptonote/exports.cpp @@ -28,6 +28,8 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using namespace cryptonote; +extern "C" void cn_slow_hash_lite(const void *data, size_t length, char *hash); + #ifdef _WIN32 #define MODULE_API __declspec(dllexport) #else @@ -65,7 +67,7 @@ extern "C" MODULE_API bool convert_blob_export(const char* input, unsigned int i // now hash it result = get_block_hashing_blob(block); - *outputSize = result.length(); + *outputSize = (int) result.length(); // output buffer big enough? if (result.length() > originalOutputSize) @@ -115,14 +117,14 @@ extern "C" MODULE_API uint64_t decode_integrated_address_export(const char* inpu return prefix; } -extern "C" MODULE_API void cn_slow_hash_export(const char* input, unsigned char *output, uint32_t inputSize) +extern "C" MODULE_API void cn_slow_hash_export(const char* input, unsigned char *output, uint32_t inputSize, uint32_t variant) { - cn_slow_hash((const void *) input, (const size_t) inputSize, (char *) output); + cn_slow_hash_old_sig((const void *) input, (const size_t) inputSize, (char *) output, variant); } extern "C" MODULE_API void cn_fast_hash_export(const char* input, unsigned char *output, uint32_t inputSize) { - cn_fast_hash((const void *)input, (const size_t) inputSize, (char *) output); + cn_fast_hash_old_sig((const void *)input, (const size_t) inputSize, (char *) output); } extern "C" MODULE_API void cn_slow_hash_lite_export(const char* input, unsigned char *output, uint32_t inputSize) diff --git a/src/Native/libcryptonote/libcryptonote.vcxproj b/src/Native/libcryptonote/libcryptonote.vcxproj index f04d72c5c..263a574b9 100644 --- a/src/Native/libcryptonote/libcryptonote.vcxproj +++ b/src/Native/libcryptonote/libcryptonote.vcxproj @@ -97,7 +97,7 @@ Level3 Disabled - WIN32;_DEBUG;netmultihashnative_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);NEOSCRYPT_SHA256;NEOSCRYPT_BLAKE256;_SCL_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS + WIN32;_DEBUG;netmultihashnative_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);NEOSCRYPT_SHA256;NEOSCRYPT_BLAKE256;_SCL_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGSWIN32_LEAN_AND_MEAN;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_WARNINGS true MultiThreadedDebug $(ProjectDir);$(ProjectDir)contrib\epee\include;;$(ProjectDir)contrib\easylogging;%(AdditionalIncludeDirectories) @@ -114,7 +114,7 @@ Level3 Disabled - _DEBUG;netmultihashnative_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);NEOSCRYPT_SHA256;NEOSCRYPT_BLAKE256;_SCL_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS + _DEBUG;netmultihashnative_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);NEOSCRYPT_SHA256;NEOSCRYPT_BLAKE256;_SCL_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGSWIN32_LEAN_AND_MEAN;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_WARNINGS true MultiThreadedDebug $(ProjectDir);$(ProjectDir)contrib\epee\include;;$(ProjectDir)contrib\easylogging;%(AdditionalIncludeDirectories) @@ -133,7 +133,7 @@ MaxSpeed true true - WIN32;NDEBUG;netmultihashnative_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);NEOSCRYPT_SHA256;NEOSCRYPT_BLAKE256;_SCL_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS + WIN32;NDEBUG;netmultihashnative_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);NEOSCRYPT_SHA256;NEOSCRYPT_BLAKE256;_SCL_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGSWIN32_LEAN_AND_MEAN;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_WARNINGS true MultiThreaded $(ProjectDir);$(ProjectDir)contrib\epee\include;;$(ProjectDir)contrib\easylogging;%(AdditionalIncludeDirectories) @@ -154,7 +154,7 @@ MaxSpeed true true - NDEBUG;netmultihashnative_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);NEOSCRYPT_SHA256;NEOSCRYPT_BLAKE256;_SCL_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGS + NDEBUG;netmultihashnative_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);NEOSCRYPT_SHA256;NEOSCRYPT_BLAKE256;_SCL_SECURE_NO_WARNINGS;_CRT_SECURE_NO_WARNINGSWIN32_LEAN_AND_MEAN;WIN32_LEAN_AND_MEAN;_CRT_SECURE_NO_WARNINGS true MultiThreaded $(ProjectDir);$(ProjectDir)contrib\epee\include;;$(ProjectDir)contrib\easylogging;%(AdditionalIncludeDirectories) @@ -172,10 +172,11 @@ + - + @@ -206,9 +207,10 @@ + - + diff --git a/src/Native/libcryptonote/libcryptonote.vcxproj.filters b/src/Native/libcryptonote/libcryptonote.vcxproj.filters index 02e8bd0bc..228959932 100644 --- a/src/Native/libcryptonote/libcryptonote.vcxproj.filters +++ b/src/Native/libcryptonote/libcryptonote.vcxproj.filters @@ -86,9 +86,6 @@ Header Files - - Header Files - Header Files @@ -113,6 +110,12 @@ Header Files + + Header Files + + + Header Files + @@ -181,9 +184,6 @@ Source Files - - Source Files - Source Files @@ -193,6 +193,12 @@ Source Files + + Source Files + + + Source Files + diff --git a/src/Native/libcryptonote/serialization/binary_archive.h b/src/Native/libcryptonote/serialization/binary_archive.h index 0a267b081..f47a4494d 100644 --- a/src/Native/libcryptonote/serialization/binary_archive.h +++ b/src/Native/libcryptonote/serialization/binary_archive.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // @@ -82,7 +82,7 @@ struct binary_archive_base /* \struct binary_archive * - * \brief the actualy binary archive type + * \brief the actually binary archive type * * \detailed The boolean template argument /a W is the is_saving * parameter for binary_archive_base. diff --git a/src/Native/libcryptonote/serialization/binary_utils.h b/src/Native/libcryptonote/serialization/binary_utils.h index 08eba41da..79b30b337 100644 --- a/src/Native/libcryptonote/serialization/binary_utils.h +++ b/src/Native/libcryptonote/serialization/binary_utils.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2017, The Monero Project +// Copyright (c) 2014-2018, The Monero Project // // All rights reserved. // diff --git a/src/Native/libcryptonote/serialization/container.h b/src/Native/libcryptonote/serialization/container.h new file mode 100644 index 000000000..978a59d2a --- /dev/null +++ b/src/Native/libcryptonote/serialization/container.h @@ -0,0 +1,113 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// 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. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include "serialization.h" + +namespace serialization +{ + namespace detail + { + template + bool serialize_container_element(Archive& ar, T& e) + { + return ::do_serialize(ar, e); + } + + template + bool serialize_container_element(Archive& ar, uint32_t& e) + { + ar.serialize_varint(e); + return true; + } + + template + bool serialize_container_element(Archive& ar, uint64_t& e) + { + ar.serialize_varint(e); + return true; + } + + template + void do_reserve(C &c, size_t N) {} + } +} + +template