From e895c3def1aa6c037c3d9c2daca8dacbd62e74dd Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Thu, 18 Jan 2018 12:01:45 +0000 Subject: [PATCH] make straus cached mode thread safe, and add tests for it --- src/ringct/bulletproofs.cc | 11 ++- src/ringct/multiexp.cc | 86 +++++++++++------ src/ringct/multiexp.h | 7 +- tests/performance_tests/main.cpp | 7 ++ tests/performance_tests/multiexp.h | 7 +- tests/unit_tests/CMakeLists.txt | 1 + tests/unit_tests/multiexp.cpp | 149 +++++++++++++++++++++++++++++ 7 files changed, 233 insertions(+), 35 deletions(-) create mode 100644 tests/unit_tests/multiexp.cpp diff --git a/src/ringct/bulletproofs.cc b/src/ringct/bulletproofs.cc index 1c29b1b99..6ba984b03 100644 --- a/src/ringct/bulletproofs.cc +++ b/src/ringct/bulletproofs.cc @@ -61,6 +61,7 @@ static constexpr size_t maxM = 16; static rct::key Hi[maxN*maxM], Gi[maxN*maxM]; static ge_p3 Hi_p3[maxN*maxM], Gi_p3[maxN*maxM]; static ge_dsmp Gprecomp[maxN*maxM], Hprecomp[maxN*maxM]; +static std::shared_ptr HiGi_cache; static const rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 } }; static const rct::keyV oneN = vector_dup(rct::identity(), maxN); static const rct::keyV twoN = vector_powers(TWO, maxN); @@ -70,7 +71,7 @@ static boost::mutex init_mutex; static inline rct::key multiexp(const std::vector &data, bool HiGi) { if (HiGi || data.size() < 1000) - return straus(data, HiGi); + return straus(data, HiGi ? HiGi_cache: NULL); else return bos_coster_heap_conv_robust(data); } @@ -116,6 +117,7 @@ static void init_exponents() static bool init_done = false; if (init_done) return; + std::vector data; for (size_t i = 0; i < maxN*maxM; ++i) { Hi[i] = get_exponent(rct::H, i * 2); @@ -124,8 +126,13 @@ static void init_exponents() Gi[i] = get_exponent(rct::H, i * 2 + 1); rct::precomp(Gprecomp[i], Gi[i]); CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Gi_p3[i], Gi[i].bytes) == 0, "ge_frombytes_vartime failed"); + + data.push_back({rct::zero(), Gi[i]}); + data.push_back({rct::zero(), Hi[i]}); } - MINFO("cache size: " << (sizeof(Hi)+sizeof(Hprecomp)+sizeof(Hi_p3))*2/1024 << " kB"); + HiGi_cache = straus_init_cache(data); + size_t cache_size = (sizeof(Hi)+sizeof(Hprecomp)+sizeof(Hi_p3))*2 + straus_get_cache_size(HiGi_cache); + MINFO("cache size: " << cache_size/1024 << " kB"); init_done = true; } diff --git a/src/ringct/multiexp.cc b/src/ringct/multiexp.cc index 7ed9672f2..4f16bd588 100644 --- a/src/ringct/multiexp.cc +++ b/src/ringct/multiexp.cc @@ -259,15 +259,58 @@ rct::key bos_coster_heap_conv_robust(std::vector data) return res; } -rct::key straus(const std::vector &data, bool HiGi) +struct straus_cached_data +{ + std::vector> multiples; +}; + +static constexpr unsigned int STRAUS_C = 4; + +std::shared_ptr straus_init_cache(const std::vector &data) +{ + MULTIEXP_PERF(PERF_TIMER_START_UNIT(multiples, 1000000)); + ge_cached cached; + ge_p1p1 p1; + ge_p3 p3; + std::shared_ptr cache(new straus_cached_data()); + + cache->multiples.resize(1<multiples[1].size(); + cache->multiples[1].resize(std::max(offset, data.size())); + for (size_t i = offset; i < data.size(); ++i) + ge_p3_to_cached(&cache->multiples[1][i], &data[i].point); + for (size_t i=2;i<1<multiples[i].resize(std::max(offset, data.size())); + for (size_t j=offset;jmultiples[i-1][j]); + ge_p1p1_to_p3(&p3, &p1); + ge_p3_to_cached(&cache->multiples[i][j], &p3); + } + } + MULTIEXP_PERF(PERF_TIMER_STOP(multiples)); + + return cache; +} + +size_t straus_get_cache_size(const std::shared_ptr &cache) +{ + size_t sz = 0; + for (const auto &e0: cache->multiples) + sz += e0.size() * sizeof(ge_p3); + return sz; +} + +rct::key straus(const std::vector &data, const std::shared_ptr &cache) { MULTIEXP_PERF(PERF_TIMER_UNIT(straus, 1000000)); + bool HiGi = cache != NULL; MULTIEXP_PERF(PERF_TIMER_START_UNIT(setup, 1000000)); - static constexpr unsigned int c = 4; - static constexpr unsigned int mask = (1<> HiGi_multiples; - std::vector> local_multiples, &multiples = HiGi ? HiGi_multiples : local_multiples; + static constexpr unsigned int mask = (1< local_cache = cache == NULL ? straus_init_cache(data) : cache; ge_cached cached; ge_p1p1 p1; ge_p3 p3; @@ -276,25 +319,6 @@ rct::key straus(const std::vector &data, bool HiGi) for (size_t i = 0; i < data.size(); ++i) skip[i] = data[i].scalar == rct::zero() || !memcmp(&data[i].point, &ge_p3_identity, sizeof(ge_p3)); - MULTIEXP_PERF(PERF_TIMER_START_UNIT(multiples, 1000000)); - multiples.resize(1<> digits; digits.resize(data.size()); @@ -305,7 +329,7 @@ rct::key straus(const std::vector &data, bool HiGi) memcpy(bytes33, data[j].scalar.bytes, 32); bytes33[32] = 0; #if 1 - static_assert(c == 4, "optimized version needs c == 4"); + static_assert(STRAUS_C == 4, "optimized version needs STRAUS_C == 4"); const unsigned char *bytes = bytes33; unsigned int i; for (i = 0; i < 256; i += 8, bytes++) @@ -339,22 +363,22 @@ rct::key straus(const std::vector &data, bool HiGi) maxscalar = data[i].scalar; size_t i = 0; while (i < 256 && !(maxscalar < pow2(i))) - i += c; + i += STRAUS_C; MULTIEXP_PERF(PERF_TIMER_STOP(setup)); ge_p3 res_p3 = ge_p3_identity; - if (!(i < c)) + if (!(i < STRAUS_C)) goto skipfirst; - while (!(i < c)) + while (!(i < STRAUS_C)) { - for (size_t j = 0; j < c; ++j) + for (size_t j = 0; j < STRAUS_C; ++j) { ge_p3_to_cached(&cached, &res_p3); ge_add(&p1, &res_p3, &cached); ge_p1p1_to_p3(&res_p3, &p1); } skipfirst: - i -= c; + i -= STRAUS_C; for (size_t j = 0; j < data.size(); ++j) { if (skip[j]) @@ -362,7 +386,7 @@ skipfirst: int digit = digits[j][i]; if (digit) { - ge_add(&p1, &res_p3, &multiples[digit][j]); + ge_add(&p1, &res_p3, &local_cache->multiples[digit][j]); ge_p1p1_to_p3(&res_p3, &p1); } } diff --git a/src/ringct/multiexp.h b/src/ringct/multiexp.h index cc53e633e..44998e2e0 100644 --- a/src/ringct/multiexp.h +++ b/src/ringct/multiexp.h @@ -36,6 +36,7 @@ #include #include "crypto/crypto.h" #include "rctTypes.h" +#include "misc_log_ex.h" namespace rct { @@ -52,9 +53,13 @@ struct MultiexpData { } }; +struct straus_cached_data; + rct::key bos_coster_heap_conv(std::vector data); rct::key bos_coster_heap_conv_robust(std::vector data); -rct::key straus(const std::vector &data, bool HiGi = false); +std::shared_ptr straus_init_cache(const std::vector &data); +size_t straus_get_cache_size(const std::shared_ptr &cache); +rct::key straus(const std::vector &data, const std::shared_ptr &cache = NULL); } diff --git a/tests/performance_tests/main.cpp b/tests/performance_tests/main.cpp index c18a653c8..a00f05ce7 100644 --- a/tests/performance_tests/main.cpp +++ b/tests/performance_tests/main.cpp @@ -221,6 +221,13 @@ int main(int argc, char** argv) TEST_PERFORMANCE2(filter, verbose, test_multiexp, multiexp_straus, 1024); TEST_PERFORMANCE2(filter, verbose, test_multiexp, multiexp_straus, 4096); + TEST_PERFORMANCE2(filter, verbose, test_multiexp, multiexp_straus_cached, 2); + TEST_PERFORMANCE2(filter, verbose, test_multiexp, multiexp_straus_cached, 8); + TEST_PERFORMANCE2(filter, verbose, test_multiexp, multiexp_straus_cached, 16); + TEST_PERFORMANCE2(filter, verbose, test_multiexp, multiexp_straus_cached, 256); + TEST_PERFORMANCE2(filter, verbose, test_multiexp, multiexp_straus_cached, 1024); + TEST_PERFORMANCE2(filter, verbose, test_multiexp, multiexp_straus_cached, 4096); + std::cout << "Tests finished. Elapsed time: " << timer.elapsed_ms() / 1000 << " sec" << std::endl; return 0; diff --git a/tests/performance_tests/multiexp.h b/tests/performance_tests/multiexp.h index ac5f60fdf..ab5af166b 100644 --- a/tests/performance_tests/multiexp.h +++ b/tests/performance_tests/multiexp.h @@ -38,6 +38,7 @@ enum test_multiexp_algorithm { multiexp_bos_coster, multiexp_straus, + multiexp_straus_cached, }; template @@ -59,6 +60,7 @@ public: rct::key kn = rct::scalarmultKey(point, data[n].scalar); res = rct::addKeys(res, kn); } + cache = rct::straus_init_cache(data); return true; } @@ -69,7 +71,9 @@ public: case multiexp_bos_coster: return res == bos_coster_heap_conv_robust(data); case multiexp_straus: - return res == straus(data, false); + return res == straus(data); + case multiexp_straus_cached: + return res == straus(data, cache); default: return false; } @@ -77,5 +81,6 @@ public: private: std::vector data; + std::shared_ptr cache; rct::key res; }; diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 7366990ad..cdb741699 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -58,6 +58,7 @@ set(unit_tests_sources mlocker.cpp mnemonics.cpp mul_div.cpp + multiexp.cpp multisig.cpp parse_amount.cpp random.cpp diff --git a/tests/unit_tests/multiexp.cpp b/tests/unit_tests/multiexp.cpp new file mode 100644 index 000000000..2dce5bb80 --- /dev/null +++ b/tests/unit_tests/multiexp.cpp @@ -0,0 +1,149 @@ +// Copyright (c) 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 "gtest/gtest.h" + +#include "crypto/crypto.h" +#include "ringct/rctOps.h" +#include "ringct/multiexp.h" + +static const rct::key TESTSCALAR = rct::H; +static const rct::key TESTPOINT = rct::scalarmultBase(rct::H); + +static rct::key basic(const std::vector &data) +{ + ge_p3 res_p3 = ge_p3_identity; + for (const auto &d: data) + { + ge_cached cached; + ge_p3 p3; + ge_p1p1 p1; + ge_scalarmult_p3(&p3, d.scalar.bytes, &d.point); + ge_p3_to_cached(&cached, &p3); + ge_add(&p1, &res_p3, &cached); + ge_p1p1_to_p3(&res_p3, &p1); + } + rct::key res; + ge_p3_tobytes(res.bytes, &res_p3); + return res; +} + +static ge_p3 get_p3(const rct::key &point) +{ + ge_p3 p3; + EXPECT_TRUE(ge_frombytes_vartime(&p3, point.bytes) == 0); + return p3; +} + +TEST(multiexp, bos_coster_empty) +{ + std::vector data; + data.push_back({rct::zero(), get_p3(rct::identity())}); + ASSERT_TRUE(basic(data) == bos_coster_heap_conv_robust(data)); +} + +TEST(multiexp, straus_empty) +{ + std::vector data; + data.push_back({rct::zero(), get_p3(rct::identity())}); + ASSERT_TRUE(basic(data) == straus(data)); +} + +TEST(multiexp, bos_coster_only_zeroes) +{ + std::vector data; + for (int n = 0; n < 16; ++n) + data.push_back({rct::zero(), get_p3(TESTPOINT)}); + ASSERT_TRUE(basic(data) == bos_coster_heap_conv_robust(data)); +} + +TEST(multiexp, straus_only_zeroes) +{ + std::vector data; + for (int n = 0; n < 16; ++n) + data.push_back({rct::zero(), get_p3(TESTPOINT)}); + ASSERT_TRUE(basic(data) == straus(data)); +} + +TEST(multiexp, bos_coster_only_identities) +{ + std::vector data; + for (int n = 0; n < 16; ++n) + data.push_back({TESTSCALAR, get_p3(rct::identity())}); + ASSERT_TRUE(basic(data) == bos_coster_heap_conv_robust(data)); +} + +TEST(multiexp, straus_only_identities) +{ + std::vector data; + for (int n = 0; n < 16; ++n) + data.push_back({TESTSCALAR, get_p3(rct::identity())}); + ASSERT_TRUE(basic(data) == straus(data)); +} + +TEST(multiexp, bos_coster_random) +{ + std::vector data; + for (int n = 0; n < 32; ++n) + { + data.push_back({rct::skGen(), get_p3(rct::scalarmultBase(rct::skGen()))}); + ASSERT_TRUE(basic(data) == bos_coster_heap_conv_robust(data)); + } +} + +TEST(multiexp, straus_random) +{ + std::vector data; + for (int n = 0; n < 32; ++n) + { + data.push_back({rct::skGen(), get_p3(rct::scalarmultBase(rct::skGen()))}); + ASSERT_TRUE(basic(data) == straus(data)); + } +} + +TEST(multiexp, straus_cached) +{ + static constexpr size_t N = 256; + std::vector P(N); + for (size_t n = 0; n < N; ++n) + { + P[n].scalar = rct::zero(); + ASSERT_TRUE(ge_frombytes_vartime(&P[n].point, rct::scalarmultBase(rct::skGen()).bytes) == 0); + } + std::shared_ptr cache = rct::straus_init_cache(P); + for (size_t n = 0; n < N/16; ++n) + { + std::vector data; + size_t sz = 1 + crypto::rand() % (N-1); + for (size_t s = 0; s < sz; ++s) + { + data.push_back({rct::skGen(), P[s].point}); + } + ASSERT_TRUE(basic(data) == straus(data, cache)); + } +}