mirror of
https://git.wownero.com/wownero/wownero.git
synced 2024-12-25 02:48:52 +00:00
126196b017
- use a raw memory block to store cache - use aligned memory - use doubling API where appropriate - calculate straus in bands
493 lines
16 KiB
C++
493 lines
16 KiB
C++
// Copyright (c) 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.
|
|
//
|
|
// Adapted from Python code by Sarang Noether
|
|
|
|
#include "misc_log_ex.h"
|
|
#include "common/perf_timer.h"
|
|
extern "C"
|
|
{
|
|
#include "crypto/crypto-ops.h"
|
|
}
|
|
#include "common/aligned.h"
|
|
#include "rctOps.h"
|
|
#include "multiexp.h"
|
|
|
|
#undef MONERO_DEFAULT_LOG_CATEGORY
|
|
#define MONERO_DEFAULT_LOG_CATEGORY "multiexp"
|
|
|
|
//#define MULTIEXP_PERF(x) x
|
|
#define MULTIEXP_PERF(x)
|
|
|
|
#define RAW_MEMORY_BLOCK
|
|
//#define ALTERNATE_LAYOUT
|
|
//#define TRACK_STRAUS_ZERO_IDENTITY
|
|
|
|
// per points us for N/B points (B point bands)
|
|
// raw alt 128/192 4096/192 4096/4096
|
|
// 0 0 52.6 71 71.2
|
|
// 0 1 53.2 72.2 72.4
|
|
// 1 0 52.7 67 67.1
|
|
// 1 1 52.8 70.4 70.2
|
|
|
|
namespace rct
|
|
{
|
|
|
|
static inline bool operator<(const rct::key &k0, const rct::key&k1)
|
|
{
|
|
for (int n = 31; n >= 0; --n)
|
|
{
|
|
if (k0.bytes[n] < k1.bytes[n])
|
|
return true;
|
|
if (k0.bytes[n] > k1.bytes[n])
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static inline rct::key div2(const rct::key &k)
|
|
{
|
|
rct::key res;
|
|
int carry = 0;
|
|
for (int n = 31; n >= 0; --n)
|
|
{
|
|
int new_carry = (k.bytes[n] & 1) << 7;
|
|
res.bytes[n] = k.bytes[n] / 2 + carry;
|
|
carry = new_carry;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static inline rct::key pow2(size_t n)
|
|
{
|
|
CHECK_AND_ASSERT_THROW_MES(n < 256, "Invalid pow2 argument");
|
|
rct::key res = rct::zero();
|
|
res[n >> 3] |= 1<<(n&7);
|
|
return res;
|
|
}
|
|
|
|
rct::key bos_coster_heap_conv(std::vector<MultiexpData> data)
|
|
{
|
|
MULTIEXP_PERF(PERF_TIMER_START_UNIT(bos_coster, 1000000));
|
|
MULTIEXP_PERF(PERF_TIMER_START_UNIT(setup, 1000000));
|
|
size_t points = data.size();
|
|
CHECK_AND_ASSERT_THROW_MES(points > 1, "Not enough points");
|
|
std::vector<size_t> heap(points);
|
|
for (size_t n = 0; n < points; ++n)
|
|
heap[n] = n;
|
|
|
|
auto Comp = [&](size_t e0, size_t e1) { return data[e0].scalar < data[e1].scalar; };
|
|
std::make_heap(heap.begin(), heap.end(), Comp);
|
|
MULTIEXP_PERF(PERF_TIMER_STOP(setup));
|
|
|
|
MULTIEXP_PERF(PERF_TIMER_START_UNIT(loop, 1000000));
|
|
MULTIEXP_PERF(PERF_TIMER_START_UNIT(pop, 1000000)); MULTIEXP_PERF(PERF_TIMER_PAUSE(pop));
|
|
MULTIEXP_PERF(PERF_TIMER_START_UNIT(add, 1000000)); MULTIEXP_PERF(PERF_TIMER_PAUSE(add));
|
|
MULTIEXP_PERF(PERF_TIMER_START_UNIT(sub, 1000000)); MULTIEXP_PERF(PERF_TIMER_PAUSE(sub));
|
|
MULTIEXP_PERF(PERF_TIMER_START_UNIT(push, 1000000)); MULTIEXP_PERF(PERF_TIMER_PAUSE(push));
|
|
while (heap.size() > 1)
|
|
{
|
|
MULTIEXP_PERF(PERF_TIMER_RESUME(pop));
|
|
std::pop_heap(heap.begin(), heap.end(), Comp);
|
|
size_t index1 = heap.back();
|
|
heap.pop_back();
|
|
std::pop_heap(heap.begin(), heap.end(), Comp);
|
|
size_t index2 = heap.back();
|
|
heap.pop_back();
|
|
MULTIEXP_PERF(PERF_TIMER_PAUSE(pop));
|
|
|
|
MULTIEXP_PERF(PERF_TIMER_RESUME(add));
|
|
ge_cached cached;
|
|
ge_p3_to_cached(&cached, &data[index1].point);
|
|
ge_p1p1 p1;
|
|
ge_add(&p1, &data[index2].point, &cached);
|
|
ge_p1p1_to_p3(&data[index2].point, &p1);
|
|
MULTIEXP_PERF(PERF_TIMER_PAUSE(add));
|
|
|
|
MULTIEXP_PERF(PERF_TIMER_RESUME(sub));
|
|
sc_sub(data[index1].scalar.bytes, data[index1].scalar.bytes, data[index2].scalar.bytes);
|
|
MULTIEXP_PERF(PERF_TIMER_PAUSE(sub));
|
|
|
|
MULTIEXP_PERF(PERF_TIMER_RESUME(push));
|
|
if (!(data[index1].scalar == rct::zero()))
|
|
{
|
|
heap.push_back(index1);
|
|
std::push_heap(heap.begin(), heap.end(), Comp);
|
|
}
|
|
|
|
heap.push_back(index2);
|
|
std::push_heap(heap.begin(), heap.end(), Comp);
|
|
MULTIEXP_PERF(PERF_TIMER_PAUSE(push));
|
|
}
|
|
MULTIEXP_PERF(PERF_TIMER_STOP(push));
|
|
MULTIEXP_PERF(PERF_TIMER_STOP(sub));
|
|
MULTIEXP_PERF(PERF_TIMER_STOP(add));
|
|
MULTIEXP_PERF(PERF_TIMER_STOP(pop));
|
|
MULTIEXP_PERF(PERF_TIMER_STOP(loop));
|
|
|
|
MULTIEXP_PERF(PERF_TIMER_START_UNIT(end, 1000000));
|
|
//return rct::scalarmultKey(data[index1].point, data[index1].scalar);
|
|
std::pop_heap(heap.begin(), heap.end(), Comp);
|
|
size_t index1 = heap.back();
|
|
heap.pop_back();
|
|
ge_p2 p2;
|
|
ge_scalarmult(&p2, data[index1].scalar.bytes, &data[index1].point);
|
|
rct::key res;
|
|
ge_tobytes(res.bytes, &p2);
|
|
return res;
|
|
}
|
|
|
|
rct::key bos_coster_heap_conv_robust(std::vector<MultiexpData> data)
|
|
{
|
|
MULTIEXP_PERF(PERF_TIMER_START_UNIT(bos_coster, 1000000));
|
|
MULTIEXP_PERF(PERF_TIMER_START_UNIT(setup, 1000000));
|
|
size_t points = data.size();
|
|
CHECK_AND_ASSERT_THROW_MES(points > 0, "Not enough points");
|
|
std::vector<size_t> heap;
|
|
heap.reserve(points);
|
|
for (size_t n = 0; n < points; ++n)
|
|
{
|
|
if (!(data[n].scalar == rct::zero()) && memcmp(&data[n].point, &ge_p3_identity, sizeof(ge_p3)))
|
|
heap.push_back(n);
|
|
}
|
|
points = heap.size();
|
|
if (points == 0)
|
|
return rct::identity();
|
|
if (points < 2)
|
|
{
|
|
ge_p2 p2;
|
|
ge_scalarmult(&p2, data[0].scalar.bytes, &data[0].point);
|
|
rct::key res;
|
|
ge_tobytes(res.bytes, &p2);
|
|
return res;
|
|
}
|
|
|
|
auto Comp = [&](size_t e0, size_t e1) { return data[e0].scalar < data[e1].scalar; };
|
|
std::make_heap(heap.begin(), heap.end(), Comp);
|
|
MULTIEXP_PERF(PERF_TIMER_STOP(setup));
|
|
|
|
MULTIEXP_PERF(PERF_TIMER_START_UNIT(loop, 1000000));
|
|
MULTIEXP_PERF(PERF_TIMER_START_UNIT(pop, 1000000)); MULTIEXP_PERF(PERF_TIMER_PAUSE(pop));
|
|
MULTIEXP_PERF(PERF_TIMER_START_UNIT(div, 1000000)); MULTIEXP_PERF(PERF_TIMER_PAUSE(div));
|
|
MULTIEXP_PERF(PERF_TIMER_START_UNIT(add, 1000000)); MULTIEXP_PERF(PERF_TIMER_PAUSE(add));
|
|
MULTIEXP_PERF(PERF_TIMER_START_UNIT(sub, 1000000)); MULTIEXP_PERF(PERF_TIMER_PAUSE(sub));
|
|
MULTIEXP_PERF(PERF_TIMER_START_UNIT(push, 1000000)); MULTIEXP_PERF(PERF_TIMER_PAUSE(push));
|
|
while (heap.size() > 1)
|
|
{
|
|
MULTIEXP_PERF(PERF_TIMER_RESUME(pop));
|
|
std::pop_heap(heap.begin(), heap.end(), Comp);
|
|
size_t index1 = heap.back();
|
|
heap.pop_back();
|
|
std::pop_heap(heap.begin(), heap.end(), Comp);
|
|
size_t index2 = heap.back();
|
|
heap.pop_back();
|
|
MULTIEXP_PERF(PERF_TIMER_PAUSE(pop));
|
|
|
|
ge_cached cached;
|
|
ge_p1p1 p1;
|
|
ge_p2 p2;
|
|
|
|
MULTIEXP_PERF(PERF_TIMER_RESUME(div));
|
|
while (1)
|
|
{
|
|
rct::key s1_2 = div2(data[index1].scalar);
|
|
if (!(data[index2].scalar < s1_2))
|
|
break;
|
|
if (data[index1].scalar.bytes[0] & 1)
|
|
{
|
|
data.resize(data.size()+1);
|
|
data.back().scalar = rct::identity();
|
|
data.back().point = data[index1].point;
|
|
heap.push_back(data.size() - 1);
|
|
std::push_heap(heap.begin(), heap.end(), Comp);
|
|
}
|
|
data[index1].scalar = div2(data[index1].scalar);
|
|
ge_p3_to_p2(&p2, &data[index1].point);
|
|
ge_p2_dbl(&p1, &p2);
|
|
ge_p1p1_to_p3(&data[index1].point, &p1);
|
|
}
|
|
MULTIEXP_PERF(PERF_TIMER_PAUSE(div));
|
|
|
|
MULTIEXP_PERF(PERF_TIMER_RESUME(add));
|
|
ge_p3_to_cached(&cached, &data[index1].point);
|
|
ge_add(&p1, &data[index2].point, &cached);
|
|
ge_p1p1_to_p3(&data[index2].point, &p1);
|
|
MULTIEXP_PERF(PERF_TIMER_PAUSE(add));
|
|
|
|
MULTIEXP_PERF(PERF_TIMER_RESUME(sub));
|
|
sc_sub(data[index1].scalar.bytes, data[index1].scalar.bytes, data[index2].scalar.bytes);
|
|
MULTIEXP_PERF(PERF_TIMER_PAUSE(sub));
|
|
|
|
MULTIEXP_PERF(PERF_TIMER_RESUME(push));
|
|
if (!(data[index1].scalar == rct::zero()))
|
|
{
|
|
heap.push_back(index1);
|
|
std::push_heap(heap.begin(), heap.end(), Comp);
|
|
}
|
|
|
|
heap.push_back(index2);
|
|
std::push_heap(heap.begin(), heap.end(), Comp);
|
|
MULTIEXP_PERF(PERF_TIMER_PAUSE(push));
|
|
}
|
|
MULTIEXP_PERF(PERF_TIMER_STOP(push));
|
|
MULTIEXP_PERF(PERF_TIMER_STOP(sub));
|
|
MULTIEXP_PERF(PERF_TIMER_STOP(add));
|
|
MULTIEXP_PERF(PERF_TIMER_STOP(pop));
|
|
MULTIEXP_PERF(PERF_TIMER_STOP(loop));
|
|
|
|
MULTIEXP_PERF(PERF_TIMER_START_UNIT(end, 1000000));
|
|
//return rct::scalarmultKey(data[index1].point, data[index1].scalar);
|
|
std::pop_heap(heap.begin(), heap.end(), Comp);
|
|
size_t index1 = heap.back();
|
|
heap.pop_back();
|
|
ge_p2 p2;
|
|
ge_scalarmult(&p2, data[index1].scalar.bytes, &data[index1].point);
|
|
rct::key res;
|
|
ge_tobytes(res.bytes, &p2);
|
|
return res;
|
|
}
|
|
|
|
static constexpr unsigned int STRAUS_C = 4;
|
|
|
|
struct straus_cached_data
|
|
{
|
|
#ifdef RAW_MEMORY_BLOCK
|
|
size_t size;
|
|
ge_cached *multiples;
|
|
straus_cached_data(): size(0), multiples(NULL) {}
|
|
~straus_cached_data() { aligned_free(multiples); }
|
|
#else
|
|
std::vector<std::vector<ge_cached>> multiples;
|
|
#endif
|
|
};
|
|
#ifdef RAW_MEMORY_BLOCK
|
|
#ifdef ALTERNATE_LAYOUT
|
|
#define CACHE_OFFSET(cache,point,digit) cache->multiples[(point)*((1<<STRAUS_C)-1)+((digit)-1)]
|
|
#else
|
|
#define CACHE_OFFSET(cache,point,digit) cache->multiples[(point)+cache->size*((digit)-1)]
|
|
#endif
|
|
#else
|
|
#ifdef ALTERNATE_LAYOUT
|
|
#define CACHE_OFFSET(cache,point,digit) local_cache->multiples[j][digit-1]
|
|
#else
|
|
#define CACHE_OFFSET(cache,point,digit) local_cache->multiples[digit][j]
|
|
#endif
|
|
#endif
|
|
|
|
std::shared_ptr<straus_cached_data> straus_init_cache(const std::vector<MultiexpData> &data)
|
|
{
|
|
MULTIEXP_PERF(PERF_TIMER_START_UNIT(multiples, 1000000));
|
|
ge_cached cached;
|
|
ge_p1p1 p1;
|
|
ge_p3 p3;
|
|
std::shared_ptr<straus_cached_data> cache(new straus_cached_data());
|
|
|
|
#ifdef RAW_MEMORY_BLOCK
|
|
const size_t offset = cache->size;
|
|
cache->multiples = (ge_cached*)aligned_realloc(cache->multiples, sizeof(ge_cached) * ((1<<STRAUS_C)-1) * std::max(offset, data.size()), 4096);
|
|
cache->size = data.size();
|
|
for (size_t j=offset;j<data.size();++j)
|
|
{
|
|
ge_p3_to_cached(&CACHE_OFFSET(cache, j, 1), &data[j].point);
|
|
for (size_t i=2;i<1<<STRAUS_C;++i)
|
|
{
|
|
ge_add(&p1, &data[j].point, &CACHE_OFFSET(cache, j, i-1));
|
|
ge_p1p1_to_p3(&p3, &p1);
|
|
ge_p3_to_cached(&CACHE_OFFSET(cache, j, i), &p3);
|
|
}
|
|
}
|
|
#else
|
|
#ifdef ALTERNATE_LAYOUT
|
|
const size_t offset = cache->multiples.size();
|
|
cache->multiples.resize(std::max(offset, data.size()));
|
|
for (size_t i = offset; i < data.size(); ++i)
|
|
{
|
|
cache->multiples[i].resize((1<<STRAUS_C)-1);
|
|
ge_p3_to_cached(&cache->multiples[i][0], &data[i].point);
|
|
for (size_t j=2;j<1<<STRAUS_C;++j)
|
|
{
|
|
ge_add(&p1, &data[i].point, &cache->multiples[i][j-2]);
|
|
ge_p1p1_to_p3(&p3, &p1);
|
|
ge_p3_to_cached(&cache->multiples[i][j-1], &p3);
|
|
}
|
|
}
|
|
#else
|
|
cache->multiples.resize(1<<STRAUS_C);
|
|
size_t offset = cache->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<<STRAUS_C;++i)
|
|
cache->multiples[i].resize(std::max(offset, data.size()));
|
|
for (size_t j=offset;j<data.size();++j)
|
|
{
|
|
for (size_t i=2;i<1<<STRAUS_C;++i)
|
|
{
|
|
ge_add(&p1, &data[j].point, &cache->multiples[i-1][j]);
|
|
ge_p1p1_to_p3(&p3, &p1);
|
|
ge_p3_to_cached(&cache->multiples[i][j], &p3);
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
MULTIEXP_PERF(PERF_TIMER_STOP(multiples));
|
|
|
|
return cache;
|
|
}
|
|
|
|
size_t straus_get_cache_size(const std::shared_ptr<straus_cached_data> &cache)
|
|
{
|
|
size_t sz = 0;
|
|
#ifdef RAW_MEMORY_BLOCK
|
|
sz += cache->size * sizeof(ge_cached) * ((1<<STRAUS_C)-1);
|
|
#else
|
|
for (const auto &e0: cache->multiples)
|
|
sz += e0.size() * sizeof(ge_cached);
|
|
#endif
|
|
return sz;
|
|
}
|
|
|
|
rct::key straus(const std::vector<MultiexpData> &data, const std::shared_ptr<straus_cached_data> &cache, size_t STEP)
|
|
{
|
|
MULTIEXP_PERF(PERF_TIMER_UNIT(straus, 1000000));
|
|
bool HiGi = cache != NULL;
|
|
STEP = STEP ? STEP : 192;
|
|
|
|
MULTIEXP_PERF(PERF_TIMER_START_UNIT(setup, 1000000));
|
|
static constexpr unsigned int mask = (1<<STRAUS_C)-1;
|
|
std::shared_ptr<straus_cached_data> local_cache = cache == NULL ? straus_init_cache(data) : cache;
|
|
ge_cached cached;
|
|
ge_p1p1 p1;
|
|
ge_p3 p3;
|
|
|
|
#ifdef TRACK_STRAUS_ZERO_IDENTITY
|
|
MULTIEXP_PERF(PERF_TIMER_START_UNIT(skip, 1000000));
|
|
std::vector<uint8_t> skip(data.size());
|
|
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_STOP(skip));
|
|
#endif
|
|
|
|
MULTIEXP_PERF(PERF_TIMER_START_UNIT(digits, 1000000));
|
|
std::vector<std::vector<uint8_t>> digits;
|
|
digits.resize(data.size());
|
|
for (size_t j = 0; j < data.size(); ++j)
|
|
{
|
|
digits[j].resize(256);
|
|
unsigned char bytes33[33];
|
|
memcpy(bytes33, data[j].scalar.bytes, 32);
|
|
bytes33[32] = 0;
|
|
#if 1
|
|
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++)
|
|
{
|
|
digits[j][i] = bytes[0] & 0xf;
|
|
digits[j][i+1] = (bytes[0] >> 1) & 0xf;
|
|
digits[j][i+2] = (bytes[0] >> 2) & 0xf;
|
|
digits[j][i+3] = (bytes[0] >> 3) & 0xf;
|
|
digits[j][i+4] = ((bytes[0] >> 4) | (bytes[1]<<4)) & 0xf;
|
|
digits[j][i+5] = ((bytes[0] >> 5) | (bytes[1]<<3)) & 0xf;
|
|
digits[j][i+6] = ((bytes[0] >> 6) | (bytes[1]<<2)) & 0xf;
|
|
digits[j][i+7] = ((bytes[0] >> 7) | (bytes[1]<<1)) & 0xf;
|
|
}
|
|
#elif 1
|
|
for (size_t i = 0; i < 256; ++i)
|
|
digits[j][i] = ((bytes[i>>3] | (bytes[(i>>3)+1]<<8)) >> (i&7)) & mask;
|
|
#else
|
|
rct::key shifted = data[j].scalar;
|
|
for (size_t i = 0; i < 256; ++i)
|
|
{
|
|
digits[j][i] = shifted.bytes[0] & 0xf;
|
|
shifted = div2(shifted, (256-i)>>3);
|
|
}
|
|
#endif
|
|
}
|
|
MULTIEXP_PERF(PERF_TIMER_STOP(digits));
|
|
|
|
rct::key maxscalar = rct::zero();
|
|
for (size_t i = 0; i < data.size(); ++i)
|
|
if (maxscalar < data[i].scalar)
|
|
maxscalar = data[i].scalar;
|
|
size_t start_i = 0;
|
|
while (start_i < 256 && !(maxscalar < pow2(start_i)))
|
|
start_i += STRAUS_C;
|
|
MULTIEXP_PERF(PERF_TIMER_STOP(setup));
|
|
|
|
ge_p3 res_p3 = ge_p3_identity;
|
|
|
|
for (size_t start_offset = 0; start_offset < data.size(); start_offset += STEP)
|
|
{
|
|
const size_t num_points = std::min(data.size() - start_offset, STEP);
|
|
|
|
ge_p3 band_p3 = ge_p3_identity;
|
|
size_t i = start_i;
|
|
if (!(i < STRAUS_C))
|
|
goto skipfirst;
|
|
while (!(i < STRAUS_C))
|
|
{
|
|
ge_p2 p2;
|
|
ge_p3_to_p2(&p2, &band_p3);
|
|
for (size_t j = 0; j < STRAUS_C; ++j)
|
|
{
|
|
ge_p2_dbl(&p1, &p2);
|
|
if (j == STRAUS_C - 1)
|
|
ge_p1p1_to_p3(&band_p3, &p1);
|
|
else
|
|
ge_p1p1_to_p2(&p2, &p1);
|
|
}
|
|
skipfirst:
|
|
i -= STRAUS_C;
|
|
for (size_t j = start_offset; j < start_offset + num_points; ++j)
|
|
{
|
|
#ifdef TRACK_STRAUS_ZERO_IDENTITY
|
|
if (skip[j])
|
|
continue;
|
|
#endif
|
|
const uint8_t digit = digits[j][i];
|
|
if (digit)
|
|
{
|
|
ge_add(&p1, &band_p3, &CACHE_OFFSET(local_cache, j, digit));
|
|
ge_p1p1_to_p3(&band_p3, &p1);
|
|
}
|
|
}
|
|
}
|
|
|
|
ge_p3_to_cached(&cached, &band_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;
|
|
}
|
|
|
|
}
|