diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index 72492f9ae..294a1b341 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -233,7 +233,7 @@ void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const transacti if (miner_tx && tx.version == 2) { cryptonote::tx_out vout = tx.vout[i]; - rct::key commitment = rct::zeroCommit(vout.amount); + rct::key commitment = rct::zeroCommitVartime(vout.amount); vout.amount = 0; amount_output_indices[i] = add_output(tx_hash, vout, i, tx.unlock_time, &commitment); diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 82de0a8cf..b5b18251c 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -3457,7 +3457,7 @@ output_data_t BlockchainLMDB::get_output_key(const uint64_t& amount, const uint6 const pre_rct_outkey *okp = (const pre_rct_outkey *)v.mv_data; memcpy(&ret, &okp->data, sizeof(pre_rct_output_data_t)); if (include_commitmemt) - ret.commitment = rct::zeroCommit(amount); + ret.commitment = rct::zeroCommitVartime(amount); } TXN_POSTFIX_RDONLY(); return ret; @@ -4203,7 +4203,7 @@ void BlockchainLMDB::get_output_key(const epee::span &amounts, c outputs.resize(outputs.size() + 1); output_data_t &data = outputs.back(); memcpy(&data, &okp->data, sizeof(pre_rct_output_data_t)); - data.commitment = rct::zeroCommit(amount); + data.commitment = rct::zeroCommitVartime(amount); } } diff --git a/src/crypto/crypto-ops.c b/src/crypto/crypto-ops.c index 314fe448a..ec7309822 100644 --- a/src/crypto/crypto-ops.c +++ b/src/crypto/crypto-ops.c @@ -42,7 +42,6 @@ static void fe_sq(fe, const fe); static void ge_madd(ge_p1p1 *, const ge_p3 *, const ge_precomp *); static void ge_msub(ge_p1p1 *, const ge_p3 *, const ge_precomp *); static void ge_p2_0(ge_p2 *); -static void ge_p3_dbl(ge_p1p1 *, const ge_p3 *); static void fe_divpowm1(fe, const fe, const fe); /* Common functions */ @@ -1529,7 +1528,7 @@ static void ge_p3_0(ge_p3 *h) { r = 2 * p */ -static void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p) { +void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p) { ge_p2 q; ge_p3_to_p2(&q, p); ge_p2_dbl(r, &q); diff --git a/src/crypto/crypto-ops.h b/src/crypto/crypto-ops.h index c103f1f78..2bf82c9de 100644 --- a/src/crypto/crypto-ops.h +++ b/src/crypto/crypto-ops.h @@ -102,6 +102,10 @@ void ge_p1p1_to_p3(ge_p3 *, const ge_p1p1 *); void ge_p2_dbl(ge_p1p1 *, const ge_p2 *); +/* From ge_p3_dbl.c */ + +void ge_p3_dbl(ge_p1p1 *r, const ge_p3 *p); + /* From ge_p3_to_cached.c */ extern const fe fe_d2; diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index d3b8bfe2f..deffee98e 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -53,7 +53,7 @@ namespace cryptonote rct::key mask; //ringct amount mask rct::multisig_kLRki multisig_kLRki; //multisig info - void push_output(uint64_t idx, const crypto::public_key &k, uint64_t amount) { outputs.push_back(std::make_pair(idx, rct::ctkey({rct::pk2rct(k), rct::zeroCommit(amount)}))); } + void push_output(uint64_t idx, const crypto::public_key &k, uint64_t amount) { outputs.push_back(std::make_pair(idx, rct::ctkey({rct::pk2rct(k), rct::zeroCommitVartime(amount)}))); } BEGIN_SERIALIZE_OBJECT() FIELD(outputs) diff --git a/src/ringct/rctOps.cpp b/src/ringct/rctOps.cpp index 3860a7e1f..3c11e6b94 100644 --- a/src/ringct/rctOps.cpp +++ b/src/ringct/rctOps.cpp @@ -29,6 +29,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +#include "crypto/generators.h" #include "misc_log_ex.h" #include "rctOps.h" using namespace crypto; @@ -216,6 +217,33 @@ static const zero_commitment zero_commitments[] = { { (uint64_t)10000000000000000000ull, {{0x65, 0x8d, 0x1, 0x37, 0x6d, 0x18, 0x63, 0xe7, 0x7b, 0x9, 0x6f, 0x98, 0xe6, 0xe5, 0x13, 0xc2, 0x4, 0x10, 0xf5, 0xc7, 0xfb, 0x18, 0xa6, 0xe5, 0x9a, 0x52, 0x66, 0x84, 0x5c, 0xd9, 0xb1, 0xe3}} }, }; +static constexpr std::size_t H_TABLE_SIZE = 64; +const std::vector& H_TABLE() +{ + struct static_h_table + { + std::vector h_table; + static_h_table() + : h_table() + { + h_table.resize(H_TABLE_SIZE); + + ge_p3_to_cached(&h_table.at(0), &ge_p3_H); + ge_p3 H_bit_p3 = ge_p3_H; + + for (std::size_t i = 1; i < H_TABLE_SIZE; ++i) + { + ge_p1p1 H_bit_p1p1; + ge_p3_dbl(&H_bit_p1p1, &H_bit_p3); + ge_p1p1_to_p3(&H_bit_p3, &H_bit_p1p1); + ge_p3_to_cached(&h_table.at(i), &H_bit_p3); + } + } + }; + static const static_h_table out; + return out.h_table; +} + namespace rct { //Various key initialization functions @@ -318,7 +346,7 @@ namespace rct { return make_tuple(sk, pk); } - key zeroCommit(xmr_amount amount) { + key zeroCommitVartime(xmr_amount amount) { const zero_commitment *begin = zero_commitments; const zero_commitment *end = zero_commitments + sizeof(zero_commitments) / sizeof(zero_commitments[0]); const zero_commitment value{amount, rct::zero()}; @@ -327,9 +355,20 @@ namespace rct { { return it->commitment; } - key am = d2h(amount); - key bH = scalarmultH(am); - return addKeys(G, bH); + ge_p3 res_ge_p3 = get_G_p3(); + static_assert(sizeof(xmr_amount) * 8 == H_TABLE_SIZE, "unexpected size of h table"); + for (size_t i = 0; i < H_TABLE_SIZE; ++i) + { + if (amount & (xmr_amount(1) << i)) + { + ge_p1p1 p1p1; + ge_add(&p1p1, &res_ge_p3, &H_TABLE()[i]); + ge_p1p1_to_p3(&res_ge_p3, &p1p1); + } + } + rct::key res; + ge_p3_tobytes(res.bytes, &res_ge_p3); + return res; } key commit(xmr_amount amount, const key &mask) { diff --git a/src/ringct/rctOps.h b/src/ringct/rctOps.h index 0edd0308c..e4110c626 100644 --- a/src/ringct/rctOps.h +++ b/src/ringct/rctOps.h @@ -108,7 +108,7 @@ namespace rct { // make a pedersen commitment with given key key commit(xmr_amount amount, const key &mask); // make a pedersen commitment with zero key - key zeroCommit(xmr_amount amount); + key zeroCommitVartime(xmr_amount amount); //generates a random uint long long xmr_amount randXmrAmount(xmr_amount upperlimit); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index b7ec6d28c..5d7e5300f 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -9550,7 +9550,7 @@ void wallet2::get_outs(std::vector> size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); outs.push_back(std::vector()); outs.back().reserve(fake_outputs_count + 1); - const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommit(td.amount()); + const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommitVartime(td.amount()); uint64_t num_outs = 0; const uint64_t amount = td.is_rct() ? 0 : td.amount(); @@ -9659,7 +9659,7 @@ void wallet2::get_outs(std::vector> { const transfer_details &td = m_transfers[idx]; std::vector v; - const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommit(td.amount()); + const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommitVartime(td.amount()); v.push_back(std::make_tuple(td.m_global_output_index, td.get_public_key(), mask)); outs.push_back(v); } diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 05a6ce1f9..a5ebb30e3 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -594,7 +594,7 @@ bool fill_tx_sources(std::vector& sources, const std::vector& e const int out_idx[] = {0, -1}; const uint64_t amount_paid = 10000; return generate_with(events, out_idx, mixin, amount_paid, false, - [](std::vector &sources, std::vector &destinations) {sources[0].outputs[0].second.mask = rct::zeroCommit(99999);}, + [](std::vector &sources, std::vector &destinations) {sources[0].outputs[0].second.mask = rct::zeroCommitVartime(99999);}, NULL); } @@ -316,7 +316,7 @@ bool gen_rct_tx_pre_rct_bad_fake_mask::generate(std::vector& e const int out_idx[] = {0, -1}; const uint64_t amount_paid = 10000; return generate_with(events, out_idx, mixin, amount_paid, false, - [](std::vector &sources, std::vector &destinations) {sources[0].outputs[1].second.mask = rct::zeroCommit(99999);}, + [](std::vector &sources, std::vector &destinations) {sources[0].outputs[1].second.mask = rct::zeroCommitVartime(99999);}, NULL); } @@ -339,7 +339,7 @@ bool gen_rct_tx_rct_bad_real_mask::generate(std::vector& event const int out_idx[] = {1, -1}; const uint64_t amount_paid = 10000; return generate_with(events, out_idx, mixin, amount_paid, false, - [](std::vector &sources, std::vector &destinations) {sources[0].outputs[0].second.mask = rct::zeroCommit(99999);}, + [](std::vector &sources, std::vector &destinations) {sources[0].outputs[0].second.mask = rct::zeroCommitVartime(99999);}, NULL); } @@ -359,7 +359,7 @@ bool gen_rct_tx_rct_bad_fake_mask::generate(std::vector& event const int out_idx[] = {1, -1}; const uint64_t amount_paid = 10000; return generate_with(events, out_idx, mixin, amount_paid, false, - [](std::vector &sources, std::vector &destinations) {sources[0].outputs[1].second.mask = rct::zeroCommit(99999);}, + [](std::vector &sources, std::vector &destinations) {sources[0].outputs[1].second.mask = rct::zeroCommitVartime(99999);}, NULL); } @@ -369,7 +369,7 @@ bool gen_rct_tx_rct_spend_with_zero_commit::generate(std::vector &sources, std::vector &destinations) {sources[0].outputs[0].second.mask = rct::zeroCommit(sources[0].amount); sources[0].mask = rct::identity();}, + [](std::vector &sources, std::vector &destinations) {sources[0].outputs[0].second.mask = rct::zeroCommitVartime(sources[0].amount); sources[0].mask = rct::identity();}, [](transaction &tx){boost::get(tx.vin[0]).amount = 0;}); } diff --git a/tests/performance_tests/CMakeLists.txt b/tests/performance_tests/CMakeLists.txt index a1158fcec..3c18d6776 100644 --- a/tests/performance_tests/CMakeLists.txt +++ b/tests/performance_tests/CMakeLists.txt @@ -55,7 +55,8 @@ set(performance_tests_headers multi_tx_test_base.h performance_tests.h performance_utils.h - single_tx_test_base.h) + single_tx_test_base.h + zero_commit.h) monero_add_minimal_executable(performance_tests ${performance_tests_sources} diff --git a/tests/performance_tests/crypto_ops.h b/tests/performance_tests/crypto_ops.h index b8ebc85dc..334beca8a 100644 --- a/tests/performance_tests/crypto_ops.h +++ b/tests/performance_tests/crypto_ops.h @@ -128,8 +128,8 @@ public: case op_addKeys_aGbBcC: rct::addKeys_aGbBcC(key, scalar0, scalar1, precomp1, scalar2, precomp2); break; case op_addKeys_aAbBcC: rct::addKeys_aAbBcC(key, scalar0, precomp0, scalar1, precomp1, scalar2, precomp2); break; case op_isInMainSubgroup: rct::isInMainSubgroup(point0); break; - case op_zeroCommitUncached: rct::zeroCommit(9001); break; - case op_zeroCommitCached: rct::zeroCommit(9000); break; + case op_zeroCommitUncached: rct::zeroCommitVartime(9001); break; + case op_zeroCommitCached: rct::zeroCommitVartime(9000); break; default: return false; } return true; diff --git a/tests/performance_tests/main.cpp b/tests/performance_tests/main.cpp index c40656e3d..da151d286 100644 --- a/tests/performance_tests/main.cpp +++ b/tests/performance_tests/main.cpp @@ -66,6 +66,7 @@ #include "multiexp.h" #include "sig_mlsag.h" #include "sig_clsag.h" +#include "zero_commit.h" namespace po = boost::program_options; @@ -594,6 +595,9 @@ int main(int argc, char** argv) TEST_PERFORMANCE3(filter, p, test_multiexp, multiexp_pippenger, 4096, 9); #endif + TEST_PERFORMANCE1(filter, p, test_zero_commit, true); // fast + TEST_PERFORMANCE1(filter, p, test_zero_commit, false); + std::cout << "Tests finished. Elapsed time: " << timer.elapsed_ms() / 1000 << " sec" << std::endl; return 0; diff --git a/tests/performance_tests/multi_tx_test_base.h b/tests/performance_tests/multi_tx_test_base.h index 75e0120cf..64413fbef 100644 --- a/tests/performance_tests/multi_tx_test_base.h +++ b/tests/performance_tests/multi_tx_test_base.h @@ -59,7 +59,7 @@ public: return false; txout_to_key tx_out = boost::get(m_miner_txs[i].vout[0].target); - output_entries.push_back(std::make_pair(i, rct::ctkey({rct::pk2rct(tx_out.key), rct::zeroCommit(m_miner_txs[i].vout[0].amount)}))); + output_entries.push_back(std::make_pair(i, rct::ctkey({rct::pk2rct(tx_out.key), rct::zeroCommitVartime(m_miner_txs[i].vout[0].amount)}))); m_public_keys[i] = tx_out.key; m_public_key_ptrs[i] = &m_public_keys[i]; } diff --git a/tests/performance_tests/zero_commit.h b/tests/performance_tests/zero_commit.h new file mode 100644 index 000000000..188d31ee5 --- /dev/null +++ b/tests/performance_tests/zero_commit.h @@ -0,0 +1,66 @@ +// Copyright (c) 2025, 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 "crypto/random.h" +#include "ringct/rctOps.h" + +template +class test_zero_commit +{ +public: + static const size_t loop_count = 1000; + + bool init() + { + m_amount = crypto::rand(); + return true; + } + + bool test() + { + if constexpr (fast) + { + rct::zeroCommitVartime(m_amount); + } + else + { + // Old way of doing it + rct::key am = rct::d2h(m_amount); + rct::key bH = rct::scalarmultH(am); + rct::addKeys(rct::G, bH); + } + return true; + } + +private: + uint64_t m_amount; +}; diff --git a/tests/unit_tests/ringct.cpp b/tests/unit_tests/ringct.cpp index 9979fad9a..090b021e1 100644 --- a/tests/unit_tests/ringct.cpp +++ b/tests/unit_tests/ringct.cpp @@ -1204,14 +1204,14 @@ TEST(ringct, key_ostream) TEST(ringct, zeroCommmit) { static const uint64_t amount = crypto::rand(); - const rct::key z = rct::zeroCommit(amount); + const rct::key z = rct::zeroCommitVartime(amount); const rct::key a = rct::scalarmultBase(rct::identity()); const rct::key b = rct::scalarmultH(rct::d2h(amount)); const rct::key manual = rct::addKeys(a, b); ASSERT_EQ(z, manual); } -static rct::key uncachedZeroCommit(uint64_t amount) +static rct::key uncachedZeroCommitVartime(uint64_t amount) { const rct::key am = rct::d2h(amount); const rct::key bH = rct::scalarmultH(am); @@ -1220,14 +1220,14 @@ static rct::key uncachedZeroCommit(uint64_t amount) TEST(ringct, zeroCommitCache) { - ASSERT_EQ(rct::zeroCommit(0), uncachedZeroCommit(0)); - ASSERT_EQ(rct::zeroCommit(1), uncachedZeroCommit(1)); - ASSERT_EQ(rct::zeroCommit(2), uncachedZeroCommit(2)); - ASSERT_EQ(rct::zeroCommit(10), uncachedZeroCommit(10)); - ASSERT_EQ(rct::zeroCommit(200), uncachedZeroCommit(200)); - ASSERT_EQ(rct::zeroCommit(1000000000), uncachedZeroCommit(1000000000)); - ASSERT_EQ(rct::zeroCommit(3000000000000), uncachedZeroCommit(3000000000000)); - ASSERT_EQ(rct::zeroCommit(900000000000000), uncachedZeroCommit(900000000000000)); + ASSERT_EQ(rct::zeroCommitVartime(0), uncachedZeroCommitVartime(0)); + ASSERT_EQ(rct::zeroCommitVartime(1), uncachedZeroCommitVartime(1)); + ASSERT_EQ(rct::zeroCommitVartime(2), uncachedZeroCommitVartime(2)); + ASSERT_EQ(rct::zeroCommitVartime(10), uncachedZeroCommitVartime(10)); + ASSERT_EQ(rct::zeroCommitVartime(200), uncachedZeroCommitVartime(200)); + ASSERT_EQ(rct::zeroCommitVartime(1000000000), uncachedZeroCommitVartime(1000000000)); + ASSERT_EQ(rct::zeroCommitVartime(3000000000000), uncachedZeroCommitVartime(3000000000000)); + ASSERT_EQ(rct::zeroCommitVartime(900000000000000), uncachedZeroCommitVartime(900000000000000)); } TEST(ringct, H)