Merge pull request #10108

ede4d7f RingCT crypto: 6x faster zero commit (j-berman)

ACKs: UkoeHB, jeffro256
This commit is contained in:
tobtoht
2026-07-03 11:57:32 +00:00
16 changed files with 146 additions and 33 deletions
+1 -1
View File
@@ -226,7 +226,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);
+2 -2
View File
@@ -3487,7 +3487,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;
@@ -4276,7 +4276,7 @@ void BlockchainLMDB::get_output_key(const epee::span<const uint64_t> &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);
}
}
+1 -2
View File
@@ -43,7 +43,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 */
@@ -1580,7 +1579,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);
+4
View File
@@ -103,6 +103,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;
+1 -1
View File
@@ -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)
+43 -4
View File
@@ -29,6 +29,7 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <boost/lexical_cast.hpp>
#include "crypto/generators.h"
#include "misc_log_ex.h"
extern "C" {
#include "rctCryptoOps.h"
@@ -220,6 +221,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<ge_cached>& H_TABLE()
{
struct static_h_table
{
std::vector<ge_cached> 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
@@ -322,7 +350,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()};
@@ -331,9 +359,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) {
+1 -1
View File
@@ -98,7 +98,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);
+2 -2
View File
@@ -9496,7 +9496,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
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<get_outs_entry>());
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();
@@ -9605,7 +9605,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
{
const transfer_details &td = m_transfers[idx];
std::vector<get_outs_entry> 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);
}
+1 -1
View File
@@ -594,7 +594,7 @@ bool fill_tx_sources(std::vector<tx_source_entry>& sources, const std::vector<te
ts.rct = false;
ts.mask = rct::identity(); // non-rct has identity mask by definition
rct::key comm = rct::zeroCommit(ts.amount);
rct::key comm = rct::zeroCommitVartime(ts.amount);
for(auto & ot : ts.outputs)
ot.second.mask = comm;
+5 -5
View File
@@ -292,7 +292,7 @@ bool gen_rct_tx_pre_rct_bad_real_mask::generate(std::vector<test_event_entry>& 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<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations) {sources[0].outputs[0].second.mask = rct::zeroCommit(99999);},
[](std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations) {sources[0].outputs[0].second.mask = rct::zeroCommitVartime(99999);},
NULL);
}
@@ -312,7 +312,7 @@ bool gen_rct_tx_pre_rct_bad_fake_mask::generate(std::vector<test_event_entry>& 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<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations) {sources[0].outputs[1].second.mask = rct::zeroCommit(99999);},
[](std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations) {sources[0].outputs[1].second.mask = rct::zeroCommitVartime(99999);},
NULL);
}
@@ -335,7 +335,7 @@ bool gen_rct_tx_rct_bad_real_mask::generate(std::vector<test_event_entry>& 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<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations) {sources[0].outputs[0].second.mask = rct::zeroCommit(99999);},
[](std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations) {sources[0].outputs[0].second.mask = rct::zeroCommitVartime(99999);},
NULL);
}
@@ -355,7 +355,7 @@ bool gen_rct_tx_rct_bad_fake_mask::generate(std::vector<test_event_entry>& 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<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations) {sources[0].outputs[1].second.mask = rct::zeroCommit(99999);},
[](std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations) {sources[0].outputs[1].second.mask = rct::zeroCommitVartime(99999);},
NULL);
}
@@ -365,7 +365,7 @@ bool gen_rct_tx_rct_spend_with_zero_commit::generate(std::vector<test_event_entr
const int out_idx[] = {1, -1};
const uint64_t amount_paid = 10000;
return generate_with(events, out_idx, mixin, amount_paid, false,
[](std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations) {sources[0].outputs[0].second.mask = rct::zeroCommit(sources[0].amount); sources[0].mask = rct::identity();},
[](std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations) {sources[0].outputs[0].second.mask = rct::zeroCommitVartime(sources[0].amount); sources[0].mask = rct::identity();},
[](transaction &tx){boost::get<txin_to_key>(tx.vin[0]).amount = 0;});
}
+2 -1
View File
@@ -56,7 +56,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}
+2 -2
View File
@@ -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;
+4
View File
@@ -67,6 +67,7 @@
#include "multiexp.h"
#include "sig_mlsag.h"
#include "sig_clsag.h"
#include "zero_commit.h"
namespace po = boost::program_options;
@@ -597,6 +598,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;
+1 -1
View File
@@ -59,7 +59,7 @@ public:
return false;
txout_to_key tx_out = boost::get<txout_to_key>(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];
}
+66
View File
@@ -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<bool fast>
class test_zero_commit
{
public:
static const size_t loop_count = 1000;
bool init()
{
m_amount = crypto::rand<uint64_t>();
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;
};
+10 -10
View File
@@ -1206,14 +1206,14 @@ TEST(ringct, key_ostream)
TEST(ringct, zeroCommmit)
{
static const uint64_t amount = crypto::rand<uint64_t>();
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);
@@ -1222,14 +1222,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)