Compare commits

...

77 Commits

Author SHA1 Message Date
auruya 383f3d36e6 Fix difficulty cache in get_difficulty_for_next_block (#73) 2025-12-10 10:20:29 +00:00
Some Random Crypto Guy 7d06436d08 Merge branch 'main' of https://github.com/salvium/salvium 2025-12-10 10:09:48 +00:00
somerandomcryptoguy 3bee380b18 add cross-compilation dependencies to Dockerfile.salvium (#78)
Co-authored-by: auruya <dream.glorix@gmail.com>
2025-12-10 10:08:53 +00:00
auruya 58c70115f2 fix compiler warnings 2025-12-10 10:06:10 +00:00
Some Random Crypto Guy 6be4081332 fixed self-subaddress issue 2025-12-10 10:01:31 +00:00
auruya 538e4a5d1f misc fixes 2025-12-10 09:55:53 +00:00
Some Random Crypto Guy 3e49572539 updated checkpoints ready for next release 2025-12-10 09:53:02 +00:00
Some Random Crypto Guy 05c7152ad5 Merge branch 'add-account-all-command' 2025-12-10 09:22:14 +00:00
Some Random Crypto Guy 8d31fa2842 fixed Carrot TX proof for self-send to subaddress 2025-12-09 11:40:52 +00:00
Some Random Crypto Guy 2abe39f178 Merge branch 'carrot-tx-proof-support' 2025-12-09 09:26:42 +00:00
Some Random Crypto Guy ac13287c78 Merge branch 'carrot-tx-proof-support' of https://github.com/salvium/salvium into carrot-tx-proof-support 2025-12-08 16:36:19 +00:00
Some Random Crypto Guy 305b92909e fixed InProofV3 calcs / checks 2025-12-08 16:36:12 +00:00
auruya 248667016a fix freshly unlocked output being excluded from transactions 2025-12-04 17:33:37 +03:00
Some Random Crypto Guy 10b58aac73 fixed sweeping to own subaddress 2025-12-03 16:10:54 +00:00
Some Random Crypto Guy 0221fe8a34 fixed TX proof generation for multiple destinations 2025-12-02 20:29:28 +00:00
Some Random Crypto Guy 1ff480e64d added sanity checks on Carrot vs CN; fixed InProofV2 for CN 2025-12-02 14:59:07 +00:00
Some Random Crypto Guy fd121aae19 Merge branch 'carrot-tx-proof-support' of https://github.com/salvium/salvium into carrot-tx-proof-support 2025-12-02 13:02:47 +00:00
Some Random Crypto Guy 0deb19c53c tidied TX proof code; removed large chunks of commented-out cruft 2025-12-02 13:02:39 +00:00
auruya 7f3e389d92 add carrot tx proof known values tests 2025-12-02 15:43:32 +03:00
Some Random Crypto Guy 87da2d4661 extended unit test scenarios for Carrot TX proofs 2025-12-01 20:23:27 +00:00
Some Random Crypto Guy f6075ae9ec simple unit test for Carrot TX proofs 2025-12-01 20:13:49 +00:00
Some Random Crypto Guy c424e84f4b cleaned up wallet code; fixed unit test 2025-12-01 15:44:23 +00:00
Some Random Crypto Guy 3b4efe9636 new fe_ functions for reversing point compression 2025-12-01 14:52:48 +00:00
auruya dfa27e78c6 add check_carrot_tx_proof fn 2025-11-27 14:08:39 +03:00
Some Random Crypto Guy 9b57fe3eae fixed lambda func with boost::optional 2025-11-26 09:56:13 +00:00
Some Random Crypto Guy 8f60758a3c interim checkin - pretty sure this proof cannot work without curve translation using ConvertPointE() 2025-11-25 11:57:57 +00:00
Some Random Crypto Guy 3a7ec4db32 Merge branch 'carrot-tx-proof-support' of https://github.com/salvium/salvium into carrot-tx-proof-support 2025-11-25 09:22:36 +00:00
auruya 679bc9f0d7 update carrot tx proof support 2025-11-25 12:08:02 +03:00
Some Random Crypto Guy 362eb38ff8 Merge branch 'carrot-tx-proof-support' of https://github.com/salvium/salvium into carrot-tx-proof-support 2025-11-21 12:37:48 +00:00
auruya 6243e992cf add get carrot tx proof support for sender 2025-11-21 14:50:31 +03:00
auruya e872414d57 add get carrot tx proof support for sender 2025-11-21 13:20:02 +03:00
auruya fcaf640bcb Add carrot tx proof support (get_tx_proof and check_tx_proof) 2025-11-17 19:58:40 +03:00
Some Random Crypto Guy f5237ceaf5 Merge branch 'move-xy-calculation-to-txbuilder' 2025-11-14 15:52:15 +00:00
Some Random Crypto Guy 1503ec6629 fixed CLI auto-refresh when rescan_bc 2025-11-14 15:42:58 +00:00
auruya 0f744520ad wallet cache migration v2 to v3 2025-11-14 17:26:50 +03:00
Some Random Crypto Guy caf52cca20 fixed spending of return_payment - forces rescan_bc of wallet on first load 2025-11-13 20:52:20 +00:00
auruya 1c4309c400 calculate x, y in tx_builder 2025-11-13 15:55:08 +03:00
Some Random Crypto Guy 9725b921a5 fixed scan refresh issue when Carrot keys are still encrypted 2025-11-12 14:46:41 +00:00
Some Random Crypto Guy 38d2515dc5 Merge branch 'develop' 2025-11-10 14:26:53 +00:00
Some Random Crypto Guy 13efd79f88 Merge branch 'fix-view-only-wallet' into develop 2025-11-10 13:34:36 +00:00
Some Random Crypto Guy 0c273d3571 possible fix for STAKE amount in GUI app - thanks, Tiamak 2025-11-10 12:28:41 +00:00
Some Random Crypto Guy d22389b37a added return_output_info collection to SVB scanning 2025-11-10 11:55:59 +00:00
auruya f9b060e552 add view only scan support 2025-11-10 14:36:38 +03:00
auruya 7d13f90e4a encrypt carrot keys 2025-11-10 14:35:48 +03:00
Some Random Crypto Guy f7a75a4fdc added help information 2025-11-04 11:38:32 +00:00
auruya b9fa97daad add account all command to display all accounts 2025-11-04 13:02:11 +03:00
Some Random Crypto Guy 6f0f5b5e83 fixed view-only wallet support 2025-11-03 17:12:27 +00:00
Some Random Crypto Guy 6fbd3184b1 fixed lookahead 2025-10-31 11:37:15 +00:00
Some Random Crypto Guy d5fee31ec6 partial fix for view-only wallet scanning 2025-10-29 21:14:36 +00:00
Some Random Crypto Guy 2a93e04180 removed erroneous Tor/i2p/blocklist entries 2025-10-29 20:48:36 +00:00
Some Random Crypto Guy b902ec9406 password issue fixed (kludgy) 2025-10-29 16:46:04 +00:00
Some Random Crypto Guy e3ba570fb1 publishing ARMv8 binaries 2025-10-29 15:34:00 +00:00
Some Random Crypto Guy fac65e5093 possible fix for GH action for cross-compilation for MacOS 2025-10-29 12:59:51 +00:00
Some Random Crypto Guy eebc1f1d26 Merge branch 'fix-check-tx-key' into develop 2025-10-28 11:39:56 +00:00
Some Random Crypto Guy 502ece5ba3 fixed random check_tx_key issues 2025-10-27 13:17:26 +00:00
Some Random Crypto Guy 62967db201 fixed merge of scanning code 2025-10-23 13:11:16 +01:00
Some Random Crypto Guy 4b7f863c71 fix for show_transfers not showing SC1 addresses; fixed proposal asset_type handling 2025-10-23 13:10:10 +01:00
auruya 55a6f17c91 fix check_tx_key 2025-10-23 14:40:13 +03:00
auruya 8a32d7f73b fix check_tx_key for multi-destination txs with additional derivations 2025-10-21 17:23:16 +03:00
Some Random Crypto Guy dfb6a705ea Merge branch 'blockchain-stats-testnet-fix' into develop 2025-10-21 12:23:25 +01:00
Some Random Crypto Guy 5246138398 fixed salvium-blockchain-stats for non-mainnet chains 2025-10-21 12:10:52 +01:00
Some Random Crypto Guy cc3e6a0822 Merge branch 'main' into develop 2025-10-21 12:09:43 +01:00
Some Random Crypto Guy 8e68f58eff Merge branch 'develop' of https://github.com/salvium/salvium into develop 2025-10-21 12:09:15 +01:00
auruya 0a79a4d9fd fix check_tx_key for carrot tx using x25519 derivation 2025-10-21 10:26:20 +03:00
Some Random Crypto Guy 48fb95bdc1 bumped version to v1.0.6 2025-10-16 14:58:02 +01:00
Some Random Crypto Guy bb4d3768b2 Merge branch 'wallet-ui-improvements' 2025-10-16 14:52:47 +01:00
Some Random Crypto Guy cea3f0f341 fixed Carrot wallet exceptions reporting 'unknown error'; fixed wallet.address.txt file to contain Carrot keys as well; fixed sending to subaddress from same wallet 2025-10-16 14:51:49 +01:00
Some Random Crypto Guy f84a622bfa attempt to bump GH builds to use Ubuntu 22.04 2025-10-12 14:31:46 +01:00
Some Random Crypto Guy f60b7209f8 fixed wallet API regression on spend+view key access 2025-10-09 14:25:26 +01:00
Some Random Crypto Guy 3b00a41fff fixed broken carrotKeys() API method 2025-10-08 20:40:33 +01:00
somerandomcryptoguy 119a7fab57 added query_key wallet-RPC support for all Carrot keys (#69)
Co-authored-by: Some Random Crypto Guy <somerandomcryptoguy@protonmail.com>
2025-10-08 20:29:10 +01:00
auruya 6d0a4a4d7b add carrotkeys fn to wallet api (#68) 2025-10-08 20:28:50 +01:00
Some Random Crypto Guy 45404ecc71 API fixes 2025-10-07 15:12:49 +01:00
Some Random Crypto Guy e5bfc2f6ad wallet API additions for better Carrot support 2025-10-07 13:32:15 +01:00
Some Random Crypto Guy c5be51053a added support for AUDIT, BURN, STAKE TXs to PendingTransaction wallet API 2025-10-07 12:34:58 +01:00
Some Random Crypto Guy 24f9916287 Merge branch 'develop' of https://github.com/salvium/salvium into develop 2025-05-08 09:21:06 +01:00
Some Random Crypto Guy cd22e55296 removed compilation warnings from scanner 2025-03-19 11:26:47 +00:00
47 changed files with 2104 additions and 306 deletions
+3 -3
View File
@@ -52,10 +52,10 @@ jobs:
packages: "gperf cmake python3-zmq libdbus-1-dev libharfbuzz-dev"
- name: "Cross-Mac x86_64"
host: "x86_64-apple-darwin"
packages: "cmake imagemagick libcap-dev librsvg2-bin libz-dev libbz2-dev libtiff-tools python-dev python3-setuptools-git"
packages: "cmake imagemagick libcap-dev librsvg2-bin libz-dev libbz2-dev libtiff-tools python3-dev python3-setuptools-git"
- name: "Cross-Mac aarch64"
host: "aarch64-apple-darwin"
packages: "cmake imagemagick libcap-dev librsvg2-bin libz-dev libbz2-dev libtiff-tools python-dev python3-setuptools-git"
packages: "cmake imagemagick libcap-dev librsvg2-bin libz-dev libbz2-dev libtiff-tools python3-dev python3-setuptools-git"
- name: "x86_64 Freebsd"
host: "x86_64-unknown-freebsd"
packages: "clang-8 gperf cmake python3-zmq libdbus-1-dev libharfbuzz-dev"
@@ -105,7 +105,7 @@ jobs:
${{env.CCACHE_SETTINGS}}
make depends target=${{ matrix.toolchain.host }} -j2
- uses: actions/upload-artifact@v4
if: ${{ matrix.toolchain.host == 'x86_64-w64-mingw32' || matrix.toolchain.host == 'x86_64-apple-darwin' || matrix.toolchain.host == 'aarch64-apple-darwin' || matrix.toolchain.host == 'x86_64-unknown-linux-gnu' }}
if: ${{ matrix.toolchain.host == 'x86_64-w64-mingw32' || matrix.toolchain.host == 'x86_64-apple-darwin' || matrix.toolchain.host == 'aarch64-apple-darwin' || matrix.toolchain.host == 'x86_64-unknown-linux-gnu' || matrix.toolchain.host == 'aarch64-linux-gnu' }}
with:
name: ${{ matrix.toolchain.name }}
path: |
+4 -2
View File
@@ -3,14 +3,16 @@ FROM ubuntu:24.04 AS builder
ENV DEBIAN_FRONTEND=noninteractive
# Install build dependencies
# Install build dependencies including cross-compilers
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential cmake pkg-config git imagemagick libcap-dev librsvg2-bin libz-dev \
g++-mingw-w64-x86-64 clang gcc-arm-none-eabi binutils-x86-64-linux-gnu libtiff-tools \
g++-x86-64-linux-gnu gcc-x86-64-linux-gnu binutils-aarch64-linux-gnu \
g++-aarch64-linux-gnu gcc-aarch64-linux-gnu crossbuild-essential-amd64 \
libssl-dev libzmq3-dev libunbound-dev libsodium-dev libunwind8-dev liblzma-dev \
libreadline-dev libexpat1-dev libpgm-dev qttools5-dev-tools libhidapi-dev libusb-1.0-0-dev \
libprotobuf-dev protobuf-compiler libudev-dev libboost-all-dev python3 ccache doxygen graphviz \
ca-certificates curl zip libtool gperf \
ca-certificates curl zip libtool gperf automake autoconf \
&& rm -rf /var/lib/apt/lists/*
# Clone the develop branch
+5 -5
View File
@@ -1,4 +1,4 @@
# Salvium One v1.0.5
# Salvium One v1.0.7
Copyright (c) 2023-2025, Salvium
Portions Copyright (c) 2014-2023, The Monero Project
@@ -172,7 +172,7 @@ invokes cmake commands as needed.
```bash
cd salvium
git checkout v1.0.5
git checkout v1.0.7
make
```
@@ -251,7 +251,7 @@ Tested on a Raspberry Pi Zero with a clean install of minimal Raspbian Stretch (
```bash
git clone https://github.com/salvium/salvium
cd salvium
git checkout v1.0.5
git checkout v1.0.7
```
* Build:
@@ -370,10 +370,10 @@ application.
cd salvium
```
* If you would like a specific [version/tag](https://github.com/salvium/salvium/tags), do a git checkout for that version. eg. 'v1.0.5'. If you don't care about the version and just want binaries from master, skip this step:
* If you would like a specific [version/tag](https://github.com/salvium/salvium/tags), do a git checkout for that version. eg. 'v1.0.7'. If you don't care about the version and just want binaries from master, skip this step:
```bash
git checkout v1.0.5
git checkout v1.0.7
```
* If you are on a 64-bit system, run:
+10 -13
View File
@@ -214,11 +214,9 @@ plot 'stats.csv' index "DATA" using (timecolumn(1,"%Y-%m-%d")):4 with lines, ''
#define MAX_RINGS 0xffffffff
struct tm prevtm = {0}, currtm;
uint64_t prevsz = 0, currsz = 0;
uint64_t prevtxs = 0, currtxs = 0;
uint64_t currblks = 0;
uint32_t txhr[24] = {0};
unsigned int i;
// uint64_t currsz = 0;
// uint64_t currtxs = 0;
// uint64_t currblks = 0;
const std::map<uint8_t, std::pair<uint64_t, std::pair<std::string, std::string>>> audit_hard_forks = get_config(net_type).AUDIT_HARD_FORKS;
@@ -246,9 +244,7 @@ plot 'stats.csv' index "DATA" using (timecolumn(1,"%Y-%m-%d")):4 with lines, ''
prevtm = currtm;
}
skip:
currsz += bd.size();
uint64_t coinbase_amount;
uint64_t tx_fee_amount = 0;
// currsz += bd.size();
std::set<std::string> used_assets, miner_tx_assets, protocol_tx_assets;
std::map<size_t, std::vector<std::string>> used_tx_versions;
used_assets.insert("SAL");
@@ -325,10 +321,11 @@ skip:
std::cout << timebuf << "" << delimiter << "" << h << "" << delimiter << "" << tx_id << "" << delimiter << "invalid TX detected" << delimiter << std::endl;
continue;
}
currsz += bd.size();
if (db->get_prunable_tx_blob(tx_id, bd))
currsz += bd.size();
currtxs++;
// currsz += bd.size();
// if (db->get_prunable_tx_blob(tx_id, bd))
// currsz += bd.size();
// currtxs++;
db->get_prunable_tx_blob(tx_id, bd);
if (tx.type != cryptonote::transaction_type::TRANSFER &&
tx.type != cryptonote::transaction_type::BURN &&
@@ -374,7 +371,7 @@ skip:
}
}
currblks++;
// currblks++;
if (stop_requested)
break;
@@ -211,8 +211,15 @@ int main(int argc, char* argv[])
throw std::runtime_error("Failed to initialize a database");
}
const std::string filename = (boost::filesystem::path(opt_data_dir) / db->get_db_name()).string();
LOG_PRINT_L0("Loading blockchain from folder " << filename << " ...");
boost::filesystem::path folder(opt_data_dir);
if (opt_stagenet) {
folder /= std::to_string(STAGENET_VERSION);
} else if (opt_testnet) {
folder /= std::to_string(TESTNET_VERSION);
}
folder /= db->get_db_name();
LOG_PRINT_L0("Loading blockchain from folder " << folder << " ...");
const std::string filename = folder.string();
try
{
Binary file not shown.
+62 -3
View File
@@ -282,13 +282,62 @@ crypto::key_image carrot_and_legacy_account::derive_key_image(const crypto::publ
return L;
}
//----------------------------------------------------------------------------------------------------------------------
crypto::key_image carrot_and_legacy_account::derive_key_image_view_only(const crypto::public_key &address_spend_pubkey,
const crypto::secret_key &sender_extension_g,
const crypto::secret_key &sender_extension_t,
const crypto::public_key &onetime_address) const
{
const auto it = subaddress_map.find(address_spend_pubkey);
CHECK_AND_ASSERT_THROW_MES(it != subaddress_map.cend(),
"carrot and legacy account: derive key image view only: cannot find subaddress");
const bool is_subaddress = it->second.index.is_subaddress();
const uint32_t major_index = it->second.index.major;
const uint32_t minor_index = it->second.index.minor;
const cryptonote::account_keys &keys = get_keys();
crypto::secret_key address_index_generator;
crypto::secret_key subaddress_scalar;
crypto::secret_key subaddress_extension;
crypto::secret_key address_privkey_g;
crypto::secret_key x;
CHECK_AND_ASSERT_THROW_MES(it->second.derive_type == AddressDeriveType::Carrot,
"carrot and legacy account: derive key image view only: not a Carrot address");
// s^j_gen = H_32[s_ga](j_major, j_minor)
make_carrot_index_extension_generator(keys.s_generate_address, major_index, minor_index, address_index_generator);
if (is_subaddress)
{
// k^j_subscal = H_n(K_s, j_major, j_minor, s^j_gen)
make_carrot_subaddress_scalar(keys.m_carrot_account_address.m_spend_public_key, address_index_generator, major_index, minor_index, subaddress_scalar);
}
else
{
// k^j_subscal = 1
sc_1(to_bytes(subaddress_scalar));
}
// k^g_a = k_gi * k^j_subscal
sc_mul(to_bytes(address_privkey_g), to_bytes(keys.k_generate_image), to_bytes(subaddress_scalar));
// x = k^{j,g}_addr + k^g_o
sc_add(to_bytes(x), to_bytes(address_privkey_g), to_bytes(sender_extension_g));
crypto::key_image L;
crypto::generate_key_image(onetime_address, x, L);
return L;
}
//----------------------------------------------------------------------------------------------------------------------
void carrot_and_legacy_account::generate_subaddress_map(const std::pair<size_t, size_t>& lookahead_size)
{
const std::vector<AddressDeriveType> derive_types{AddressDeriveType::Carrot, AddressDeriveType::PreCarrot};
for (uint32_t major_index = 0; major_index <= lookahead_size.first; ++major_index)
{
for (uint32_t minor_index = 0; minor_index <= lookahead_size.first; ++minor_index)
for (uint32_t minor_index = 0; minor_index <= lookahead_size.second; ++minor_index)
{
for (const AddressDeriveType derive_type : derive_types)
{
@@ -337,7 +386,7 @@ void carrot_and_legacy_account::set_keys(const cryptonote::account_keys& keys, b
}
//----------------------------------------------------------------------------------------------------------------------
void carrot_and_legacy_account::create_from_svb_key(const cryptonote::account_public_address& address, const crypto::secret_key& svb_key)
{
{
// top level keys
m_keys.s_master = crypto::null_skey;
make_carrot_provespend_key(m_keys.s_master, m_keys.k_prove_spend);
@@ -359,7 +408,17 @@ void carrot_and_legacy_account::create_from_svb_key(const cryptonote::account_pu
k_view_incoming_dev.view_key_scalar_mult_ed25519(crypto::get_G(),
m_keys.m_carrot_main_address.m_view_public_key
);
// Store fields for Carrot
m_keys.m_spend_secret_key = crypto::null_skey;
m_keys.m_view_secret_key = crypto::null_skey;
m_keys.m_account_address = {crypto::null_pkey, crypto::null_pkey, false};
// Update ALL addresses to be Carrot-only
m_keys.m_carrot_account_address.m_is_carrot = true;
m_keys.m_carrot_main_address.m_is_carrot = true;
// Set the default derive type for addresses
this->default_derive_type = AddressDeriveType::Carrot;
}
//----------------------------------------------------------------------------------------------------------------------
+60 -20
View File
@@ -48,12 +48,59 @@ namespace carrot
input_context_t input_context;
crypto::public_key K_o; // output onetime address
crypto::public_key K_change; // change output onetime address
crypto::public_key K_spend_pubkey; // change output spend pubkey
crypto::key_image key_image;
crypto::secret_key sum_g;
crypto::secret_key sender_extension_t;
return_output_info_t() {
// Default constructor for serialization
input_context = input_context_t();
K_o = crypto::public_key();
K_change = crypto::public_key();
K_spend_pubkey = crypto::public_key();
key_image = crypto::key_image();
sum_g = crypto::secret_key();
sender_extension_t = crypto::secret_key();
}
return_output_info_t(
const input_context_t &input_context,
const crypto::public_key &K_o,
const crypto::public_key &K_change,
const crypto::public_key &K_spend_pubkey,
const crypto::key_image &key_image,
const crypto::secret_key &sum_g,
const crypto::secret_key &sender_extension_t):
input_context(input_context),
K_o(K_o),
K_change(K_change),
K_spend_pubkey(K_spend_pubkey),
key_image(key_image),
sum_g(sum_g),
sender_extension_t(sender_extension_t) {}
BEGIN_SERIALIZE_OBJECT()
FIELD(input_context)
FIELD(K_o)
FIELD(K_change)
FIELD(K_spend_pubkey)
FIELD(key_image)
FIELD(sum_g)
FIELD(sender_extension_t)
END_SERIALIZE()
};
// Old return_output_info_t format (for deserializing version 2 wallet caches)
struct return_output_info_retired_t {
input_context_t input_context;
crypto::public_key K_o;
crypto::public_key K_change;
crypto::key_image key_image;
crypto::secret_key x;
crypto::secret_key y;
return_output_info_t() {
// Default constructor for serialization
return_output_info_retired_t() {
input_context = input_context_t();
K_o = crypto::public_key();
K_change = crypto::public_key();
@@ -62,20 +109,6 @@ namespace carrot
y = crypto::secret_key();
}
return_output_info_t(
const input_context_t &input_context,
const crypto::public_key &K_o,
const crypto::public_key &K_change,
const crypto::key_image &key_image,
const crypto::secret_key &x,
const crypto::secret_key &y):
input_context(input_context),
K_o(K_o),
K_change(K_change),
key_image(key_image),
x(x),
y(y) {}
BEGIN_SERIALIZE_OBJECT()
FIELD(input_context)
FIELD(K_o)
@@ -144,6 +177,11 @@ namespace carrot
const crypto::secret_key &sender_extension_t,
const crypto::public_key &onetime_address) const;
crypto::key_image derive_key_image_view_only(const crypto::public_key &address_spend_pubkey,
const crypto::secret_key &sender_extension_g,
const crypto::secret_key &sender_extension_t,
const crypto::public_key &onetime_address) const;
void generate_subaddress_map(const std::pair<size_t, size_t>& lookahead_size);
crypto::secret_key generate(
@@ -183,9 +221,10 @@ namespace boost
x.input_context = carrot::input_context_t();
x.K_o = crypto::public_key();
x.K_change = crypto::public_key();
x.K_spend_pubkey = crypto::public_key();
x.key_image = crypto::key_image();
x.x = crypto::secret_key();
x.y = crypto::secret_key();
x.sum_g = crypto::secret_key();
x.sender_extension_t = crypto::secret_key();
}
template <class Archive>
@@ -194,9 +233,10 @@ namespace boost
a & x.input_context;
a & x.K_o;
a & x.K_change;
a & x.K_spend_pubkey;
a & x.key_image;
a & x.x;
a & x.y;
a & x.sum_g;
a & x.sender_extension_t;
}
}
}
+3 -3
View File
@@ -41,9 +41,9 @@
namespace carrot
{
#define CARROT_DEFINE_SIMPLE_ERROR_TYPE(e, b) class e: b { using b::b; };
#define CARROT_DEFINE_SIMPLE_ERROR_TYPE(e, b) class e: public b { using b::b; };
class carrot_logic_error: std::logic_error { using std::logic_error::logic_error; };
class carrot_logic_error: public std::logic_error { using std::logic_error::logic_error; };
CARROT_DEFINE_SIMPLE_ERROR_TYPE(bad_address_type, carrot_logic_error)
CARROT_DEFINE_SIMPLE_ERROR_TYPE(component_out_of_order, carrot_logic_error)
@@ -55,7 +55,7 @@ CARROT_DEFINE_SIMPLE_ERROR_TYPE(too_few_outputs, carrot_logic_error)
CARROT_DEFINE_SIMPLE_ERROR_TYPE(too_many_outputs, carrot_logic_error)
CARROT_DEFINE_SIMPLE_ERROR_TYPE(invalid_tx_type, carrot_logic_error)
class carrot_runtime_error: std::runtime_error { using std::runtime_error::runtime_error; };
class carrot_runtime_error: public std::runtime_error { using std::runtime_error::runtime_error; };
CARROT_DEFINE_SIMPLE_ERROR_TYPE(crypto_function_failed, carrot_runtime_error)
CARROT_DEFINE_SIMPLE_ERROR_TYPE(not_enough_money, carrot_runtime_error)
+2
View File
@@ -219,6 +219,7 @@ bool operator==(const CarrotPaymentProposalV1 &a, const CarrotPaymentProposalV1
{
return a.destination == b.destination &&
a.amount == b.amount &&
a.asset_type == b.asset_type &&
a.randomness == b.randomness;
}
//-------------------------------------------------------------------------------------------------------------------
@@ -228,6 +229,7 @@ bool operator==(const CarrotPaymentProposalSelfSendV1 &a, const CarrotPaymentPro
a.amount == b.amount &&
a.enote_type == b.enote_type &&
a.internal_message == b.internal_message &&
a.asset_type == b.asset_type &&
0 == memcmp(&a.enote_ephemeral_pubkey, &b.enote_ephemeral_pubkey, sizeof(mx25519_pubkey));
}
//-------------------------------------------------------------------------------------------------------------------
+2
View File
@@ -82,6 +82,8 @@ struct CarrotPaymentProposalSelfSendV1 final
std::optional<mx25519_pubkey> enote_ephemeral_pubkey;
/// anchor: arbitrary, pre-encrypted message for _internal_ selfsends
std::optional<janus_anchor_t> internal_message;
/// asset type
std::string asset_type;
};
struct RCTOutputEnoteProposal
+19 -19
View File
@@ -387,6 +387,11 @@ bool try_scan_carrot_enote_external_sender(const CarrotEnoteV1 &enote,
CarrotEnoteType &enote_type_out,
const bool check_pid)
{
epee::span<const crypto::public_key> main_address_spend_pubkeys;
if (destination.is_subaddress)
main_address_spend_pubkeys = {};
else
main_address_spend_pubkeys = {&destination.address_spend_pubkey, 1};
crypto::public_key recovered_address_spend_pubkey;
payment_id_t recovered_payment_id;
CarrotEnoteType recovered_enote_type;
@@ -395,7 +400,7 @@ bool try_scan_carrot_enote_external_sender(const CarrotEnoteV1 &enote,
if (!try_scan_carrot_enote_external_normal_checked(enote,
encrypted_payment_id,
s_sender_receiver_unctx,
{&destination.address_spend_pubkey, 1},
main_address_spend_pubkeys,
sender_extension_g_out,
sender_extension_t_out,
recovered_address_spend_pubkey,
@@ -470,6 +475,9 @@ bool try_scan_carrot_enote_internal_receiver(const CarrotEnoteV1 &enote,
crypto::public_key &return_address_out,
bool &is_return_out)
{
// Determine whether this is a full wallet or a watch-only wallet
const cryptonote::account_keys &keys = account.get_keys();
// input_context
const input_context_t input_context = make_carrot_input_context(enote.tx_first_key_image);
@@ -488,7 +496,6 @@ bool try_scan_carrot_enote_internal_receiver(const CarrotEnoteV1 &enote,
input_context,
s_sender_receiver);
bool normal_change_found = true;
if (!try_scan_carrot_enote_internal_burnt(enote,
s_sender_receiver,
sender_extension_g_out,
@@ -518,24 +525,17 @@ bool try_scan_carrot_enote_internal_receiver(const CarrotEnoteV1 &enote,
// calculate the key image for the return output
crypto::secret_key sum_g;
sc_add(to_bytes(sum_g), to_bytes(sender_extension_g_out), to_bytes(k_return));
crypto::key_image key_image = account.derive_key_image(
account.get_keys().m_carrot_account_address.m_spend_public_key,
sum_g,
sender_extension_t_out,
K_r
);
crypto::key_image key_image = account.derive_key_image_view_only(address_spend_pubkey_out,
sum_g,
sender_extension_t_out,
K_r
);
crypto::secret_key x, y;
account.try_searching_for_opening_for_onetime_address(
account.get_keys().m_carrot_account_address.m_spend_public_key,
sum_g,
sender_extension_t_out,
x,
y
);
// save the input context & change output key
account.insert_return_output_info({{K_r, {input_context, output_key, enote.onetime_address, key_image, x, y}}});
// HERE BE DRAGONS!!!
// SRCG: test whether this will even work for return_payment detection
account.insert_return_output_info({{K_r, {input_context, output_key, enote.onetime_address, address_spend_pubkey_out, key_image, sum_g, sender_extension_t_out}}});
//account.insert_return_output_info({{K_r, {input_context, output_key, address_spend_pubkey_out, key_image, sum_g, sender_extension_t_out}}});
// LAND AHOY!!!
}
// janus protection checks are not needed for internal scans
+2 -1
View File
@@ -275,7 +275,8 @@ void make_carrot_transaction_proposal_v1_transfer(
.proposal = CarrotPaymentProposalSelfSendV1{
.destination_address_spend_pubkey = change_address_spend_pubkey,
.amount = 0,
.enote_type = add_payment_type_selfsend ? CarrotEnoteType::PAYMENT : CarrotEnoteType::CHANGE
.enote_type = add_payment_type_selfsend ? CarrotEnoteType::PAYMENT : CarrotEnoteType::CHANGE,
.asset_type = "SAL1"
},
.subaddr_index = change_address_index
});
+27 -22
View File
@@ -183,28 +183,33 @@ namespace cryptonote
bool checkpoints::init_default_checkpoints(network_type nettype)
{
if (nettype == MAINNET) {
ADD_CHECKPOINT2(1, "b6b45052e7e182ebaeb14ab713db29ad979115e664d766aa0910e325564a27a6", "0x2");
ADD_CHECKPOINT2(10, "82724681cf6bd934eb3253d041de50206a77627ce40ffe418ce6e0fe392ec684", "0x7812a");
ADD_CHECKPOINT2(20, "4dac7b512d876df05bfa4f39b8dbacd75cb1483fbced8bfc5446ebe21b25a04f", "0xba98f");
ADD_CHECKPOINT2(30, "668246360c93ef791a59157cec9cd09722b32a966051feea399082433138f07b", "0xcc235");
ADD_CHECKPOINT2(40, "9a4183bc1d6e9828eac46505c5ef37ae5447ba6c9325dca02be9e1201f939a7d", "0xdf077");
ADD_CHECKPOINT2(50, "5cd8b089d2e77aed9b803b398c6bff07ca652100cb8fa114c91b72509aeeb7e9", "0xf37eb");
ADD_CHECKPOINT2(60, "0e1acf00dd38e0757996dcdc4b69ad54baf7ebe10ae1e8168b192acb1a0ed7f2", "0x10993b");
ADD_CHECKPOINT2(70, "988977507f388221a927e279307b548a0ae0de10ded8c4f22c315e1b483f921a", "0x121537");
ADD_CHECKPOINT2(80, "88ea1c49b20e7596e21ca8137b2a9fa98558df269a15816fe7d7495f1c63ea43", "0x13a290");
ADD_CHECKPOINT2(90, "254800bb6f9794aad95b2226ffc1a1eef0a817472e1877ae08fac6becb55b147", "0x153a55");
ADD_CHECKPOINT2(100, "ba8d75fad878af26ac2504b4868893a7f86c59f013d0f096925cf53271dd04e8", "0x16e91e");
ADD_CHECKPOINT2(110, "dca0779bfe403730b923fa0918645daeec6096b953be2c554f133460c6fcce35", "0x18acb9");
ADD_CHECKPOINT2(120, "5a57287f6b5c105ae264b88050731c5b9ad1313b916143d7585af1d345e70247", "0x1a88f5");
ADD_CHECKPOINT2(130, "4fd292ac0774461e968924f8097e78ec03eee43a2997deaddbc7993e470a61d6", "0x1c6edd");
ADD_CHECKPOINT2(140, "5a3b6ceeef5fd498ea3330acb8a0e87f2c1566c9b0100ad67237e5664d1f053b", "0x1e4991");
ADD_CHECKPOINT2(150, "78f26d08d39f7d5e1a3548277321471e16c95096fa9bcecbe8a420d406ee249b", "0x202406");
ADD_CHECKPOINT2(160, "7acaab1037ccfbadd3126d2612d5dc154020297f980df0b8df462f9c761d3326", "0x22154b");
ADD_CHECKPOINT2(170, "9541ae934e40fa6749ca3453e47cf5fdf38efbac9efcaa2714121e8a21dd2d24", "0x241ce7");
ADD_CHECKPOINT2(180, "e20bc8ac6aabb6b0792f23a29ce42a577c6a57d177a8ac1a51b68fb6de508045", "0x262b40");
ADD_CHECKPOINT2(190, "f69fdad7a15471b63a82668b618ee5b2a384291269d944b11974a723c1604124", "0x2856a3");
ADD_CHECKPOINT2(200, "eba53fa7006dfcdc837a56c0bc8f0e1883cf34861c26934d680252a6878a3f5d", "0x2aa022");
ADD_CHECKPOINT2(90000, "e125b5c1b26521f98e29df6ec88f041c176a2c0a3fcacd5bd0ad2278e9b02fd2", "0xc99801f937888"); // 3546475285149832
ADD_CHECKPOINT2(1, "b6b45052e7e182ebaeb14ab713db29ad979115e664d766aa0910e325564a27a6", "0x2");
ADD_CHECKPOINT2(10, "82724681cf6bd934eb3253d041de50206a77627ce40ffe418ce6e0fe392ec684", "0x7812a");
ADD_CHECKPOINT2(20, "4dac7b512d876df05bfa4f39b8dbacd75cb1483fbced8bfc5446ebe21b25a04f", "0xba98f");
ADD_CHECKPOINT2(30, "668246360c93ef791a59157cec9cd09722b32a966051feea399082433138f07b", "0xcc235");
ADD_CHECKPOINT2(40, "9a4183bc1d6e9828eac46505c5ef37ae5447ba6c9325dca02be9e1201f939a7d", "0xdf077");
ADD_CHECKPOINT2(50, "5cd8b089d2e77aed9b803b398c6bff07ca652100cb8fa114c91b72509aeeb7e9", "0xf37eb");
ADD_CHECKPOINT2(60, "0e1acf00dd38e0757996dcdc4b69ad54baf7ebe10ae1e8168b192acb1a0ed7f2", "0x10993b");
ADD_CHECKPOINT2(70, "988977507f388221a927e279307b548a0ae0de10ded8c4f22c315e1b483f921a", "0x121537");
ADD_CHECKPOINT2(80, "88ea1c49b20e7596e21ca8137b2a9fa98558df269a15816fe7d7495f1c63ea43", "0x13a290");
ADD_CHECKPOINT2(90, "254800bb6f9794aad95b2226ffc1a1eef0a817472e1877ae08fac6becb55b147", "0x153a55");
ADD_CHECKPOINT2(100, "ba8d75fad878af26ac2504b4868893a7f86c59f013d0f096925cf53271dd04e8", "0x16e91e");
ADD_CHECKPOINT2(110, "dca0779bfe403730b923fa0918645daeec6096b953be2c554f133460c6fcce35", "0x18acb9");
ADD_CHECKPOINT2(120, "5a57287f6b5c105ae264b88050731c5b9ad1313b916143d7585af1d345e70247", "0x1a88f5");
ADD_CHECKPOINT2(130, "4fd292ac0774461e968924f8097e78ec03eee43a2997deaddbc7993e470a61d6", "0x1c6edd");
ADD_CHECKPOINT2(140, "5a3b6ceeef5fd498ea3330acb8a0e87f2c1566c9b0100ad67237e5664d1f053b", "0x1e4991");
ADD_CHECKPOINT2(150, "78f26d08d39f7d5e1a3548277321471e16c95096fa9bcecbe8a420d406ee249b", "0x202406");
ADD_CHECKPOINT2(160, "7acaab1037ccfbadd3126d2612d5dc154020297f980df0b8df462f9c761d3326", "0x22154b");
ADD_CHECKPOINT2(170, "9541ae934e40fa6749ca3453e47cf5fdf38efbac9efcaa2714121e8a21dd2d24", "0x241ce7");
ADD_CHECKPOINT2(180, "e20bc8ac6aabb6b0792f23a29ce42a577c6a57d177a8ac1a51b68fb6de508045", "0x262b40");
ADD_CHECKPOINT2(190, "f69fdad7a15471b63a82668b618ee5b2a384291269d944b11974a723c1604124", "0x2856a3");
ADD_CHECKPOINT2(200, "eba53fa7006dfcdc837a56c0bc8f0e1883cf34861c26934d680252a6878a3f5d", "0x2aa022");
ADD_CHECKPOINT2(90000, "e125b5c1b26521f98e29df6ec88f041c176a2c0a3fcacd5bd0ad2278e9b02fd2", "0xc99801f937888"); // 3546475285149832
ADD_CHECKPOINT2(100000, "ff4e8ec805d5bfbcd01f350ac071be1d944ba73e0d27e37d12acb549902b3f3d", "0xDA97F5697F7D8"); // 3845539075979224
ADD_CHECKPOINT2(150000, "c43281eb5b2a41ee77a4465735e4820c6d14d473b568df7987541afc48f18568", "0x12BDA9ED687AAF"); // 5275087110961839
ADD_CHECKPOINT2(225000, "7648405f7cfb24341d9580275b518bb3713c68e970f547faa0b3bcae450d9ec2", "0x18CD5F1B7BA43A"); // 6981207807730746
ADD_CHECKPOINT2(300000, "a18ca65464c1f2d876dd3a00643c9be265655d6bca364eccc5e3d628b9a5cd2c", "0x1F0C2A50FE631C"); // 8739100165038876
ADD_CHECKPOINT2(375000, "07f0c907cc5cb44cef88e5899a411adddd4b0a4419210906e0583efdcafb499f", "0x240C7F9742F265"); // 10146841299710565
}
return true;
}
+1
View File
@@ -71,6 +71,7 @@ monero_add_library(cncrypto
target_link_libraries(cncrypto
PUBLIC
epee
mx25519_static
randomx
${Boost_SYSTEM_LIBRARY}
${SODIUM_LIBRARY}
+4 -1
View File
@@ -887,4 +887,7 @@ const ge_p3 ge_p3_H = {
{5858699, 5096796, 21321203, -7536921, -5553480, -11439507, -5627669, 15045946, 19977121, 5275251},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{23443568, -5110398, -8776029, -4345135, 6889568, -14710814, 7474843, 3279062, 14550766, -7453428}
};
};
// Precomputed sqrt(-486664) mod p as 32-byte little-endian array
const fe fe_sqrt_m486664 = {12222970, 8312128, 11511410, -9067497, 15300785, 241793, -25456130, -14121551, 12187136, -3972024};
+125 -2
View File
@@ -104,6 +104,21 @@ void fe_1(fe h) {
h[9] = 0;
}
int fe_equal(const fe a, const fe b)
{
fe t;
fe_sub(t, a, b);
return fe_isnonzero(t) == 0;
}
void ge_from_xy(ge_p3 *out, const fe x, const fe y)
{
fe_1(out->Z); // Z = 1
fe_copy(out->X, x); // X = x
fe_copy(out->Y, y); // Y = y
fe_mul(out->T, x, y); // T = x*y
}
/* From fe_add.c */
/*
@@ -365,7 +380,7 @@ int fe_isnegative(const fe f) {
/* From fe_isnonzero.c, modified */
static int fe_isnonzero(const fe f) {
int fe_isnonzero(const fe f) {
unsigned char s[32];
fe_tobytes(s, f);
return (((int) (s[0] | s[1] | s[2] | s[3] | s[4] | s[5] | s[6] | s[7] | s[8] |
@@ -3967,6 +3982,114 @@ int edwards_bytes_to_x25519_vartime(unsigned char *xbytes, const unsigned char *
return 0;
}
// Precomputed sqrt(-1) from Monero (fe_sqrtm1 in crypto-ops-data.c)
extern const fe fe_sqrtm1;
extern const fe fe_sqrt_m486664;
// Function to recover v from u (returns 0 on success, -1 if not on curve)
int fe_sqrt_mont(fe v_out, const fe u_in) {
fe rhs;
fe t0, t1, t2;
fe candidate, c2, check;
// Compute rhs = u^3 + A u^2 + u, A=486662
fe A_fe;
fe_frombytes_vartime(A_fe, (const unsigned char[]){0x06, 0x6D, 0x07, 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}); // Correct little-endian 486662
fe_sq(t0, u_in); // t0 = u^2
fe_mul(t1, t0, u_in); // t1 = u^3
fe_mul(t2, t0, A_fe); // t2 = A u^2
fe_add(rhs, t1, t2); // u^3 + A u^2
fe_add(rhs, rhs, u_in); // + u
// The exponentiation chain for (p+3)/8
fe_1(t1);
fe_sq(t0, t1);
fe_mul(t0, t0, t1);
fe_sq(candidate, t0);
fe_mul(candidate, candidate, t1);
fe_mul(candidate, candidate, rhs);
fe_pow22523(candidate, candidate);
fe_mul(candidate, candidate, t0);
fe_mul(candidate, candidate, rhs);
// Check c^2 == rhs or -rhs
fe_sq(c2, candidate);
fe_sub(check, c2, rhs);
if (fe_isnonzero(check)) {
fe_add(check, c2, rhs);
if (fe_isnonzero(check)) {
return -1; // Not a quadratic residue
}
fe_mul(candidate, candidate, fe_sqrtm1); // Adjust with sqrt(-1)
}
// Output v (principal root; flip to -v if needed for your map)
fe_copy(v_out, candidate);
return 0;
}
void mont_to_ed(fe x_out, fe y_out, const fe u, const fe v) {
fe inv_v, temp;
fe t1, t2, inv_t2;
fe one;
fe_1(one);
fe_invert(inv_v, v); // 1/v
fe_mul(temp, u, inv_v); // u/v
fe_mul(x_out, fe_sqrt_m486664, temp); // sqrt(-486664) * (u/v)
fe_sub(t1, u, one); // u - 1
fe_add(t2, u, one); // u + 1
fe_invert(inv_t2, t2);
fe_mul(y_out, t1, inv_t2); // (u-1)/(u+1)
}
void ed_to_mont(fe u_out, fe v_out, const fe x, const fe y) {
fe t1, t2, inv_t2;
fe one;
fe_1(one);
fe_add(t1, one, y); // 1 + y
fe_sub(t2, one, y); // 1 - y
fe_invert(inv_t2, t2);
fe_mul(u_out, t1, inv_t2); // (1+y)/(1-y)
fe inv_x;
fe_invert(inv_x, x); // 1/x
fe_mul(t1, u_out, inv_x); // u / x
fe_mul(v_out, fe_sqrt_m486664, t1); // sqrt(-486664) * (u/x)
}
// Usage: Add two Montgomery points
void add_mont_points(fe u3, fe v3, const fe u1, const fe v1, const fe u2, const fe v2) {
ge_p3 P_ed, Q_ed, sum_ed;
// Convert to Edwards
fe x1, y1, x2, y2;
mont_to_ed(x1, y1, u1, v1);
mont_to_ed(x2, y2, u2, v2);
// Load into ge_p3 (assume Z=1, T=x*y for affine)
fe_1(P_ed.Z); fe_mul(P_ed.T, x1, y1); fe_copy(P_ed.X, x1); fe_copy(P_ed.Y, y1);
fe_1(Q_ed.Z); fe_mul(Q_ed.T, x2, y2); fe_copy(Q_ed.X, x2); fe_copy(Q_ed.Y, y2);
// Add using ge_
ge_cached Q_cached;
ge_p3_to_cached(&Q_cached, &Q_ed);
ge_p1p1 sum_p1p1;
ge_add(&sum_p1p1, &P_ed, &Q_cached);
ge_p1p1_to_p3(&sum_ed, &sum_p1p1);
// Convert back (normalize to affine: divide by Z)
fe inv_z;
fe_invert(inv_z, sum_ed.Z);
fe x_out, y_out;
fe_mul(x_out, sum_ed.X, inv_z);
fe_mul(y_out, sum_ed.Y, inv_z);
ed_to_mont(u3, v3, x_out, y_out);
}
int ge_p3_is_point_at_infinity_vartime(const ge_p3 *p) {
// https://eprint.iacr.org/2008/522
// X == T == 0 and Y/Z == 1
@@ -4063,4 +4186,4 @@ void fe_dbl(fe h, const fe f)
// Reduce the output for safety to ensure the result can be used as input to
// fe_add or fe_sub without an extra call to fe_reduce
fe_reduce(h, h_res);
}
}
+10 -1
View File
@@ -176,6 +176,11 @@ int sc_isnonzero(const unsigned char *); /* Doesn't normalize */
void ge_p3_to_x25519(unsigned char *xbytes, const ge_p3 *h);
int edwards_bytes_to_x25519_vartime(unsigned char *xbytes, const unsigned char *s);
int fe_sqrt_mont(fe v_out, const fe u_in);
void mont_to_ed(fe x_out, fe y_out, const fe u, const fe v);
void ed_to_mont(fe u_out, fe v_out, const fe x, const fe y);
void add_mont_points(fe u3, fe v3, const fe u1, const fe v1, const fe u2, const fe v2);
// internal
uint64_t load_3(const unsigned char *in);
uint64_t load_4(const unsigned char *in);
@@ -192,10 +197,14 @@ void fe_sq(fe h, const fe f);
void fe_sub(fe h, const fe f, const fe g);
void fe_0(fe h);
void fe_1(fe h);
int fe_equal(const fe a, const fe b);
void ge_from_xy(ge_p3 *out, const fe x, const fe y);
int fe_isnonzero(const fe f);
int ge_p3_is_point_at_infinity_vartime(const ge_p3 *p);
void fe_ed_y_derivatives_to_wei_x(unsigned char *wei_x, const fe inv_one_minus_y, const fe one_plus_y);
void fe_reduce(fe reduced_f, const fe f);
void fe_dbl(fe h, const fe f);
void fe_dbl(fe h, const fe f);
+558 -1
View File
@@ -41,6 +41,7 @@
#include "common/varint.h"
#include "warnings.h"
#include "crypto.h"
#include "mx25519.h"
#include "hash.h"
#include "cryptonote_config.h"
@@ -90,6 +91,16 @@ namespace crypto {
return &reinterpret_cast<const unsigned char &>(scalar);
}
static const mx25519_impl* get_mx25519_impl()
{
static std::once_flag of;
static const mx25519_impl *impl;
std::call_once(of, [&](){ impl = mx25519_select_impl(MX25519_TYPE_AUTO); });
if (impl == nullptr)
throw std::runtime_error("failed to obtain a mx25519 implementation");
return impl;
}
boost::mutex &get_random_lock()
{
static boost::mutex random_lock;
@@ -504,6 +515,391 @@ namespace crypto {
memwipe(&k, sizeof(k));
}
void crypto_ops::generate_carrot_tx_proof(
const hash &prefix_hash,
const public_key &R, // X25519 u-coordinate
const public_key &A, // Ed25519
const boost::optional<public_key> &B, // Ed if present
const public_key &D, // X25519 u-coordinate
const secret_key &r,
const secret_key &a,
signature &sig)
{
// Check if we are sender or receiver
if (r != crypto::null_skey) {
// SENDER
generate_carrot_tx_proof_as_sender(prefix_hash, R, A, B, D, r, a, sig);
return;
}
// RECEIVER
// Load points (A and B and R) into ge_p3
ge_p3 A_p3;
ge_p3 B_p3;
ge_p3 R_p3;
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 !defined(NDEBUG)
{
// Debug check D == a*R
mx25519_pubkey D_x25519;
mx25519_scmul_key(get_mx25519_impl(),
&D_x25519,
reinterpret_cast<const mx25519_privkey*>(&a),
reinterpret_cast<const mx25519_pubkey*>(&R));
public_key dbg_D;
memcpy(&dbg_D, &D_x25519, sizeof(mx25519_pubkey));
assert(D == dbg_D);
}
#endif
//
// 1. Pick random nonce k
//
crypto::secret_key k;
random_scalar(k);
static const public_key zero = {{
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,0x00
}};
s_comm_2 buf;
buf.msg = prefix_hash;
buf.D = D; // X25519 u-coord
buf.R = R; // X25519 u-coord
buf.A = A; // Ed25519
buf.B = B ? *B : zero;
cn_fast_hash(config::HASH_KEY_TXPROOF_V2,
sizeof(config::HASH_KEY_TXPROOF_V2)-1,
buf.sep);
//
// 2. Compute X = ConvertPointE(k*G or k*B)
//
ge_p3 kB_or_kG_p3;
if (B)
ge_scalarmult_p3(&kB_or_kG_p3, &k, &B_p3);
else
ge_scalarmult_base(&kB_or_kG_p3, &k);
mx25519_pubkey X_x25519;
ge_p3_to_x25519(X_x25519.data, &kB_or_kG_p3);
memcpy(&buf.X, &X_x25519, sizeof(mx25519_pubkey));
//
// 3. Compute Y = k*R
//
mx25519_pubkey Y;
mx25519_scmul_key(get_mx25519_impl(),
&Y,
reinterpret_cast<const mx25519_privkey*>(&k),
reinterpret_cast<const mx25519_pubkey*>(&R));
memcpy(&buf.Y, &Y, sizeof(mx25519_pubkey));
// ---------- Extract and lift R ----------
fe u_R;
fe_frombytes_vartime(u_R, (const unsigned char *)&R);
fe v_R_cand;
if (fe_sqrt_mont(v_R_cand, u_R) != 0)
throw std::runtime_error("R not on curve");
fe x1, y1, x2, y2, v_R_neg;
ge_p3 R_ed1, R_ed2;
// +v (principal)
mont_to_ed(x1, y1, u_R, v_R_cand);
ge_from_xy(&R_ed1, x1, y1);
// -v
fe_neg(v_R_neg, v_R_cand);
mont_to_ed(x2, y2, u_R, v_R_neg);
ge_from_xy(&R_ed2, x2, y2);
// Arbitrarily choose R_sign = true (principal v from fe_sqrt_mont)
bool R_sign = true;
ge_p3 R_ed_correct = R_ed1; // +v
// ---------- Extract and lift D (consistent with chosen R_sign) ----------
fe u_D;
fe_frombytes_vartime(u_D, (const unsigned char *)&D);
fe v_D_cand;
if (fe_sqrt_mont(v_D_cand, u_D) != 0)
throw std::runtime_error("D not on curve");
fe x3, y3, x4, y4, v_D_neg;
// Compute D_ed_true = a * R_ed_correct
ge_p3 D_ed_true;
ge_scalarmult_p3(&D_ed_true, &a, &R_ed_correct);
// Normalize to affine for matching
fe inv_z;
fe_invert(inv_z, D_ed_true.Z);
fe xd_true, yd_true;
fe_mul(xd_true, D_ed_true.X, inv_z);
fe_mul(yd_true, D_ed_true.Y, inv_z);
// +v for D
mont_to_ed(x3, y3, u_D, v_D_cand);
bool D_match1 = fe_equal(x3, xd_true) && fe_equal(y3, yd_true); // Affine match (mont_to_ed gives affine x,y)
// -v for D
fe_neg(v_D_neg, v_D_cand);
mont_to_ed(x4, y4, u_D, v_D_neg);
bool D_match2 = fe_equal(x4, xd_true) && fe_equal(y4, yd_true);
bool D_sign = false;
if (D_match1)
D_sign = true;
else if (D_match2)
D_sign = false;
else
throw std::runtime_error("D lift mismatch with computed D_ed_true");
// Pack signs (MSB is set to [1] for outbound, [0] for inbound
sig.sign_mask =
(R_sign ? 0x01 : 0x00) |
(D_sign ? 0x02 : 0x00);
struct {
s_comm_2 buf;
uint8_t sign_mask;
} challenge_hash;
challenge_hash.buf = buf;
challenge_hash.sign_mask = sig.sign_mask;
//
// 7. Compute challenge c = H(prefix_hash || … || sign_mask)
//
hash_to_scalar(&challenge_hash, sizeof(challenge_hash), sig.c);
//
// 8. Compute response z = k - c*a
//
sc_mulsub(&sig.r, &sig.c, &unwrap(a), &k);
memwipe(&k, sizeof(k));
#if !defined(NDEBUG)
bool ok = check_carrot_tx_proof(prefix_hash, R, A, B, D, sig);
assert(ok);
#endif
}
void crypto_ops::generate_carrot_tx_proof_as_sender(
const hash &prefix_hash,
const public_key &R, // X25519 u-coordinate
const public_key &A, // Ed25519
const boost::optional<public_key> &B, // Ed if present
const public_key &D, // X25519 u-coordinate
const secret_key &r,
const secret_key &a,
signature &sig)
{
// Load only Ed points (A and B) into ge_p3
ge_p3 A_p3;
ge_p3 B_p3;
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 !defined(NDEBUG)
{
assert(sc_check(&r) == 0);
// Debug check R == ConvertPointE(r*G or r*B)
public_key dbg_R;
ge_p3 dbg_R_p3;
if (B)
ge_scalarmult_p3(&dbg_R_p3, &r, &B_p3);
else
ge_scalarmult_base(&dbg_R_p3, &r);
mx25519_pubkey R_x25519;
ge_p3_to_x25519(R_x25519.data, &dbg_R_p3);
memcpy(&dbg_R, &R_x25519, sizeof(mx25519_pubkey));
assert(R == dbg_R);
// Debug check D == ConvertPointE(r*A)
public_key dbg_D;
ge_p3 dbg_D_p3;
ge_scalarmult_p3(&dbg_D_p3, &r, &A_p3);
mx25519_pubkey D_x25519;
ge_p3_to_x25519(D_x25519.data, &dbg_D_p3);
memcpy(&dbg_D, &D_x25519, sizeof(mx25519_pubkey));
assert(D == dbg_D);
}
#endif
//
// 1. Pick random nonce k
//
ec_scalar k;
random_scalar(k);
static const ec_point zero = {{
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,0x00
}};
s_comm_2 buf;
buf.msg = prefix_hash;
buf.D = D; // X25519 u-coord
buf.R = R; // X25519 u-coord
buf.A = A; // Ed25519
buf.B = B ? *B : zero;
cn_fast_hash(config::HASH_KEY_TXPROOF_V2,
sizeof(config::HASH_KEY_TXPROOF_V2)-1,
buf.sep);
//
// 2. Compute X = ConvertPointE(k*G or k*B)
//
ge_p3 kB_or_kG_p3;
if (B)
ge_scalarmult_p3(&kB_or_kG_p3, &k, &B_p3);
else
ge_scalarmult_base(&kB_or_kG_p3, &k);
mx25519_pubkey X_x25519;
ge_p3_to_x25519(X_x25519.data, &kB_or_kG_p3);
memcpy(&buf.X, &X_x25519, sizeof(mx25519_pubkey));
//
// 3. Compute Y = ConvertPointE(k*A)
//
ge_p3 kA_p3;
ge_scalarmult_p3(&kA_p3, &k, &A_p3);
mx25519_pubkey Y_x25519;
ge_p3_to_x25519(Y_x25519.data, &kA_p3);
memcpy(&buf.Y, &Y_x25519, sizeof(mx25519_pubkey));
//
// 4. Compute true Ed points R_ed_true and D_ed_true
//
ge_p3 R_ed_true, D_ed_true;
if (B)
ge_scalarmult_p3(&R_ed_true, &r, &B_p3);
else
ge_scalarmult_base(&R_ed_true, &r);
ge_scalarmult_p3(&D_ed_true, &r, &A_p3);
//
// 5. Determine sign bits for R and D
//
// ---------- Extract and lift R ----------
fe u_R;
fe_frombytes_vartime(u_R, (const unsigned char *)&R);
fe v_R_cand;
if (fe_sqrt_mont(v_R_cand, u_R) != 0)
throw std::runtime_error("R not on curve");
fe x1, y1, x2, y2, v_R_neg, v_D_neg;
mont_to_ed(x1, y1, u_R, v_R_cand);
fe_neg(v_R_neg, v_R_cand);
mont_to_ed(x2, y2, u_R, v_R_neg);
// Compute affine Edwards coords of R_ed_true
fe inv_z, xr_true, yr_true;
fe_invert(inv_z, R_ed_true.Z);
fe_mul(xr_true, R_ed_true.X, inv_z);
fe_mul(yr_true, R_ed_true.Y, inv_z);
bool R_match1 = fe_equal(xr_true, x1) && fe_equal(yr_true, y1);
bool R_match2 = fe_equal(xr_true, x2) && fe_equal(yr_true, y2);
if (!R_match1 && !R_match2)
throw std::runtime_error("R mapping mismatch");
bool R_sign = R_match1;
// ---------- Extract and lift D ----------
fe u_D;
fe_frombytes_vartime(u_D, (const unsigned char *)&D);
fe v_D_cand;
if (fe_sqrt_mont(v_D_cand, u_D) != 0)
throw std::runtime_error("D not on curve");
mont_to_ed(x1, y1, u_D, v_D_cand);
fe_neg(v_D_neg, v_D_cand);
mont_to_ed(x2, y2, u_D, v_D_neg);
fe_invert(inv_z, D_ed_true.Z);
fe_mul(xr_true, D_ed_true.X, inv_z);
fe_mul(yr_true, D_ed_true.Y, inv_z);
bool D_match1 = fe_equal(xr_true, x1) && fe_equal(yr_true, y1);
bool D_match2 = fe_equal(xr_true, x2) && fe_equal(yr_true, y2);
if (!D_match1 && !D_match2)
throw std::runtime_error("D mapping mismatch");
bool D_sign = D_match1;
//
// 6. Pack sign bits into signature, include in challenge hash
//
sig.sign_mask =
(R_sign ? 0x01 : 0x00) |
(D_sign ? 0x02 : 0x00) |
0x80;
struct {
s_comm_2 buf;
uint8_t sign_mask;
} challenge_hash;
challenge_hash.buf = buf;
challenge_hash.sign_mask = sig.sign_mask;
//
// 7. Compute challenge c = H(prefix_hash || … || sign_mask)
//
hash_to_scalar(&challenge_hash, sizeof(challenge_hash), sig.c);
//
// 8. Compute response z = k - c*r
//
sc_mulsub(&sig.r, &sig.c, &unwrap(r), &k);
memwipe(&k, sizeof(k));
#if !defined(NDEBUG)
bool ok = check_carrot_tx_proof(prefix_hash, R, A, B, D, sig);
assert(ok);
#endif
}
// Verify a proof: either v1 (version == 1) or v2 (version == 2)
bool crypto_ops::check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const signature &sig, const int version) {
// sanity check
@@ -608,6 +1004,167 @@ namespace crypto {
return sc_isnonzero(&c2) == 0;
}
// R and D are provided in X25519 format (u-coordinate), A and B in Ed25519.
bool crypto_ops::check_carrot_tx_proof(
const hash &prefix_hash,
const public_key &R, // X25519 u
const public_key &A, // Ed25519 viewkey
const boost::optional<public_key> &B, // Ed25519 spendkey if any
const public_key &D, // X25519 u
const signature &sig)
{
ge_p3 A_p3, B_p3;
if (ge_frombytes_vartime(&A_p3, &A) != 0)
return false;
if (B && ge_frombytes_vartime(&B_p3, &*B) != 0)
return false;
if (sc_check(&sig.c) != 0 || sc_check(&sig.r) != 0)
return false;
// Extract sign bits and direction flag
const bool R_sign = (sig.sign_mask & 0x01) != 0;
const bool D_sign = (sig.sign_mask & 0x02) != 0;
const bool outbound = (sig.sign_mask & 0x80) != 0;
//
// 1. Reconstruct R_ed and D_ed from X25519 u-coords + sign bits
//
// ----- R -----
fe u_R, v_R_candidate, v_R;
fe_frombytes_vartime(u_R, (const unsigned char *)&R);
if (fe_sqrt_mont(v_R_candidate, u_R) != 0)
return false;
if (R_sign) fe_copy(v_R, v_R_candidate);
else fe_neg(v_R, v_R_candidate);
fe x_R, y_R;
mont_to_ed(x_R, y_R, u_R, v_R);
ge_p3 R_ed;
ge_from_xy(&R_ed, x_R, y_R); // Z=1, T=X*Y
// ----- D -----
fe u_D, v_D_candidate, v_D;
fe_frombytes_vartime(u_D, (const unsigned char *)&D);
if (fe_sqrt_mont(v_D_candidate, u_D) != 0)
return false;
if (D_sign) fe_copy(v_D, v_D_candidate);
else fe_neg(v_D, v_D_candidate);
fe x_D, y_D;
mont_to_ed(x_D, y_D, u_D, v_D);
ge_p3 D_ed;
ge_from_xy(&D_ed, x_D, y_D);
//
// 2. Compute X'
// If inbound proof, X`= z*G + c*A (or z*B + c*A)
// If outbound proof, X`= z*G + c*R_ed (or z*B + c*R_ed)
//
ge_p3 c_p3;
if (outbound)
ge_scalarmult_p3(&c_p3, &sig.c, &R_ed);
else
ge_scalarmult_p3(&c_p3, &sig.c, &A_p3);
ge_p1p1 X_p1p1;
if (B)
{
// Subaddress: X' = c*A + z*B
ge_p3 rB_p3;
ge_scalarmult_p3(&rB_p3, &sig.r, &B_p3);
ge_cached rB_cached;
ge_p3_to_cached(&rB_cached, &rB_p3);
ge_add(&X_p1p1, &c_p3, &rB_cached);
}
else
{
// Main address: X' = c*R_ed + z*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, &c_p3, &rG_cached);
}
ge_p3 X_ed_p3;
ge_p1p1_to_p3(&X_ed_p3, &X_p1p1);
mx25519_pubkey X_x25519;
ge_p3_to_x25519(X_x25519.data, &X_ed_p3);
//
// 3. Compute Y'
// If inbound, Y' = c*D_ed + z*R
// If outbound, Y' = c*D_ed + z*A
//
ge_p3 cD_p3;
ge_scalarmult_p3(&cD_p3, &sig.c, &D_ed);
ge_p3 z_p3;
if (outbound)
ge_scalarmult_p3(&z_p3, &sig.r, &A_p3);
else
ge_scalarmult_p3(&z_p3, &sig.r, &R_ed);
ge_cached z_cached;
ge_p3_to_cached(&z_cached, &z_p3);
ge_p1p1 Y_p1p1;
ge_add(&Y_p1p1, &cD_p3, &z_cached);
ge_p3 Y_ed_p3;
ge_p1p1_to_p3(&Y_ed_p3, &Y_p1p1);
mx25519_pubkey Y_x25519;
ge_p3_to_x25519(Y_x25519.data, &Y_ed_p3);
//
// 4. Rebuild the hash transcript exactly as the prover did
//
static const ec_point zero = {{
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,0x00
}};
s_comm_2 buf;
buf.msg = prefix_hash;
buf.D = D; // X25519 (same bytes as prover)
buf.R = R; // X25519
buf.A = A; // Ed25519
buf.B = B ? *B : zero;
cn_fast_hash(config::HASH_KEY_TXPROOF_V2,
sizeof(config::HASH_KEY_TXPROOF_V2)-1,
buf.sep);
memcpy(&buf.X, &X_x25519, sizeof(mx25519_pubkey));
memcpy(&buf.Y, &Y_x25519, sizeof(mx25519_pubkey));
struct {
s_comm_2 buf;
uint8_t sign_mask;
} challenge_hash;
challenge_hash.buf = buf;
challenge_hash.sign_mask = sig.sign_mask;
//
// 5. Recompute challenge and compare with sig.c
//
ec_scalar c2;
hash_to_scalar(&challenge_hash, sizeof(challenge_hash), c2);
sc_sub(&c2, &c2, &sig.c);
return sc_isnonzero(&c2) == 0;
}
static void hash_to_ec(const public_key &key, ge_p3 &res) {
hash h;
ge_p2 point;
@@ -796,4 +1353,4 @@ POP_WARNINGS
ki.data[31] ^= 0x80;
}
}
}
}
+28 -3
View File
@@ -85,6 +85,7 @@ namespace crypto {
POD_CLASS signature {
ec_scalar c, r;
uint8_t sign_mask;
friend class crypto_ops;
};
@@ -99,7 +100,7 @@ namespace crypto {
static_assert(sizeof(ec_point) == 32 && sizeof(ec_scalar) == 32 &&
sizeof(public_key) == 32 && sizeof(public_key_memsafe) == 32 && sizeof(secret_key) == 32 &&
sizeof(key_derivation) == 32 && sizeof(key_image) == 32 && sizeof(key_image_y) == 32 &&
sizeof(signature) == 64 && sizeof(view_tag) == 1, "Invalid structure size");
sizeof(signature) == 65 && sizeof(view_tag) == 1, "Invalid structure size");
class crypto_ops {
crypto_ops();
@@ -131,8 +132,14 @@ namespace crypto {
friend void generate_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, signature &);
static void generate_tx_proof_v1(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, signature &);
friend void generate_tx_proof_v1(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, signature &);
static void generate_carrot_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, const secret_key &, signature &);
friend void generate_carrot_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, const secret_key &, signature &);
static void generate_carrot_tx_proof_as_sender(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, const secret_key &, signature &);
friend void generate_carrot_tx_proof_as_sender(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, const secret_key &, signature &);
static bool check_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const signature &, const int);
friend bool check_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const signature &, const int);
static bool check_carrot_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const signature &);
friend bool check_carrot_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const signature &);
static void derive_key_image_generator(const public_key &, ec_point &);
friend void derive_key_image_generator(const public_key &, ec_point &);
static void generate_key_image(const public_key &, const secret_key &, key_image &);
@@ -260,10 +267,28 @@ namespace crypto {
inline void generate_tx_proof_v1(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const secret_key &r, signature &sig) {
crypto_ops::generate_tx_proof_v1(prefix_hash, R, A, B, D, r, sig);
}
/* Generation of a carrot tx proof; for carrot transactions, D is in X25519 domain (D = r*ConvertPointE(A))
* instead of Ed25519 domain (D = r*A). This version applies ConvertPointE transformation.
*/
inline void generate_carrot_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const secret_key &r, const secret_key &a, signature &sig) {
crypto_ops::generate_carrot_tx_proof(prefix_hash, R, A, B, D, r, a, sig);
}
inline void generate_carrot_tx_proof_as_sender(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const secret_key &r, const secret_key &a, signature &sig) {
crypto_ops::generate_carrot_tx_proof_as_sender(prefix_hash, R, A, B, D, r, a, sig);
}
inline bool check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const signature &sig, const int version) {
return crypto_ops::check_tx_proof(prefix_hash, R, A, B, D, sig, version);
}
/* Verification of a carrot tx proof; R and D should be in Ed25519 domain for verification,
*/
inline bool check_carrot_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const signature &sig) {
return crypto_ops::check_carrot_tx_proof(prefix_hash, R, A, B, D, sig);
}
/*
inline bool check_carrot_tx_proof_as_sender(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const signature &sig) {
return crypto_ops::check_carrot_tx_proof_as_sender(prefix_hash, R, A, B, D, sig);
}
*/
inline void derive_key_image_generator(const public_key &pub, ec_point &ki_gen) {
crypto_ops::derive_key_image_generator(pub, ki_gen);
}
@@ -378,4 +403,4 @@ CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(public_key_memsafe)
CRYPTO_MAKE_HASHABLE(key_image)
CRYPTO_MAKE_HASHABLE(key_image_y)
CRYPTO_MAKE_COMPARABLE(signature)
CRYPTO_MAKE_COMPARABLE(view_tag)
CRYPTO_MAKE_COMPARABLE(view_tag)
+24 -3
View File
@@ -89,12 +89,24 @@ DISABLE_VS_WARNINGS(4244 4345)
void account_keys::xor_with_key_stream(const crypto::chacha_key &key)
{
// encrypt a large enough byte stream with chacha20
epee::wipeable_string key_stream = get_key_stream(key, m_encryption_iv, sizeof(crypto::secret_key) * (2 + m_multisig_keys.size()));
epee::wipeable_string key_stream = get_key_stream(key, m_encryption_iv, sizeof(crypto::secret_key) * (8 + m_multisig_keys.size()));
const char *ptr = key_stream.data();
for (size_t i = 0; i < sizeof(crypto::secret_key); ++i)
m_spend_secret_key.data[i] ^= *ptr++;
for (size_t i = 0; i < sizeof(crypto::secret_key); ++i)
m_view_secret_key.data[i] ^= *ptr++;
for (size_t i = 0; i < sizeof(crypto::secret_key); ++i)
s_master.data[i] ^= *ptr++;
for (size_t i = 0; i < sizeof(crypto::secret_key); ++i)
k_prove_spend.data[i] ^= *ptr++;
for (size_t i = 0; i < sizeof(crypto::secret_key); ++i)
s_view_balance.data[i] ^= *ptr++;
for (size_t i = 0; i < sizeof(crypto::secret_key); ++i)
k_view_incoming.data[i] ^= *ptr++;
for (size_t i = 0; i < sizeof(crypto::secret_key); ++i)
k_generate_image.data[i] ^= *ptr++;
for (size_t i = 0; i < sizeof(crypto::secret_key); ++i)
s_generate_address.data[i] ^= *ptr++;
for (crypto::secret_key &k: m_multisig_keys)
{
for (size_t i = 0; i < sizeof(crypto::secret_key); ++i)
@@ -116,11 +128,20 @@ DISABLE_VS_WARNINGS(4244 4345)
void account_keys::encrypt_viewkey(const crypto::chacha_key &key)
{
// encrypt a large enough byte stream with chacha20
epee::wipeable_string key_stream = get_key_stream(key, m_encryption_iv, sizeof(crypto::secret_key) * 2);
epee::wipeable_string key_stream = get_key_stream(key, m_encryption_iv, sizeof(crypto::secret_key) * 8);
const char *ptr = key_stream.data();
ptr += sizeof(crypto::secret_key);
ptr += sizeof(crypto::secret_key); // Skip m_spend_secret_key
for (size_t i = 0; i < sizeof(crypto::secret_key); ++i)
m_view_secret_key.data[i] ^= *ptr++;
ptr += (2 * sizeof(crypto::secret_key)); // Skip s_master, k_prove_spend
for (size_t i = 0; i < sizeof(crypto::secret_key); ++i)
s_view_balance.data[i] ^= *ptr++;
for (size_t i = 0; i < sizeof(crypto::secret_key); ++i)
k_view_incoming.data[i] ^= *ptr++;
for (size_t i = 0; i < sizeof(crypto::secret_key); ++i)
k_generate_image.data[i] ^= *ptr++;
for (size_t i = 0; i < sizeof(crypto::secret_key); ++i)
s_generate_address.data[i] ^= *ptr++;
}
//-----------------------------------------------------------------
void account_keys::decrypt_viewkey(const crypto::chacha_key &key)
+5
View File
@@ -75,6 +75,11 @@ namespace cryptonote
KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_multisig_keys)
const crypto::chacha_iv default_iv{{0, 0, 0, 0, 0, 0, 0, 0}};
KV_SERIALIZE_VAL_POD_AS_BLOB_OPT(m_encryption_iv, default_iv)
if (m_account_address.m_spend_public_key == crypto::null_pkey) {
KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(s_view_balance)
KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(k_view_incoming)
KV_SERIALIZE(m_carrot_account_address)
}
END_KV_SERIALIZE_MAP()
void encrypt(const crypto::chacha_key &key);
+13 -15
View File
@@ -877,7 +877,7 @@ difficulty_type Blockchain::get_difficulty_for_next_block()
std::vector<uint64_t> timestamps;
std::vector<difficulty_type> difficulties;
uint64_t height;
auto new_top_hash = get_tail_id(height); // get it again now that we have the lock
top_hash = get_tail_id(height); // get it again now that we have the lock
++height;
uint8_t version = get_current_hard_fork_version();
@@ -976,11 +976,11 @@ size_t Blockchain::recalculate_difficulties(boost::optional<uint64_t> start_heig
if (start_height_opt) {
start_height = *start_height_opt;
} else {
bool found = false;
// bool found;
for (size_t i=0; i<num_mainnet_hard_forks; ++i) {
if (version == mainnet_hard_forks[i].version) {
start_height = mainnet_hard_forks[i].height;
found = true;
//found = true;
break;
}
}
@@ -2123,7 +2123,6 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block,
*/
// Time to construct the protocol_tx
uint64_t protocol_fee = 0;
address_parse_info treasury_address_info;
ok = cryptonote::get_account_address_from_str(treasury_address_info, m_nettype, get_config(m_nettype).TREASURY_ADDRESS);
CHECK_AND_ASSERT_MES(ok, false, "Failed to obtain treasury address info");
@@ -2954,7 +2953,7 @@ bool Blockchain::get_pricing_record(oracle::pricing_record &pr, std::map<std::st
bool r = false;
const uint64_t height = get_current_blockchain_height();
const uint8_t hf_version = m_hardfork->get_current_version();
// const uint8_t hf_version = m_hardfork->get_current_version();
epee::net_utils::http::http_simple_client http_client;
COMMAND_RPC_GET_PRICING_RECORD::request req = AUTO_VAL_INIT(req);
@@ -4518,8 +4517,7 @@ void Blockchain::get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_block
//------------------------------------------------------------------
uint64_t Blockchain::get_dynamic_base_fee_estimate(uint64_t grace_blocks) const
{
const uint8_t version = get_current_hard_fork_version();
const uint64_t db_height = m_db->height();
// const uint64_t db_height = m_db->height();
if (grace_blocks >= CRYPTONOTE_REWARD_BLOCKS_WINDOW)
grace_blocks = CRYPTONOTE_REWARD_BLOCKS_WINDOW - 1;
@@ -4670,7 +4668,7 @@ bool Blockchain::calculate_audit_payouts(const uint64_t start_height, std::vecto
// Get the AUDIT TX information for matured staked coins
std::vector<cryptonote::yield_tx_info> audit_entries;
// We get the audit_tx_info from the block where they entered the chain
int audit_tx_result = m_db->get_audit_tx_info(start_height, audit_entries);
m_db->get_audit_tx_info(start_height, audit_entries);
if (!audit_entries.size()) {
// Report error and abort
@@ -4723,7 +4721,7 @@ bool Blockchain::calculate_yield_payouts(const uint64_t start_height, std::vecto
// Get the YIELD TX information for matured staked coins
std::vector<cryptonote::yield_tx_info_carrot> yield_entries;
// We get the yield_tx_info from the block _before_ they started to accrue yield
int yield_tx_result = m_db->get_carrot_yield_tx_info(start_height, yield_entries);
m_db->get_carrot_yield_tx_info(start_height, yield_entries);
if (!yield_entries.size()) {
// Report error and abort
@@ -4789,7 +4787,7 @@ bool Blockchain::calculate_yield_payouts(const uint64_t start_height, std::vecto
// Get the YIELD TX information for matured staked coins
std::vector<cryptonote::yield_tx_info> yield_entries;
// We get the yield_tx_info from the block _before_ they started to accrue yield
int yield_tx_result = m_db->get_yield_tx_info(start_height, yield_entries);
m_db->get_yield_tx_info(start_height, yield_entries);
if (!yield_entries.size()) {
// Report error and abort
@@ -5406,7 +5404,7 @@ leave:
// Update the YBI cache data
uint64_t yield_lock_period = cryptonote::get_config(m_nettype).STAKE_LOCK_PERIOD;
uint64_t ybi_cache_expected_size = std::min(new_height, yield_lock_period);
// uint64_t ybi_cache_expected_size = std::min(new_height, yield_lock_period);
if (new_height > yield_lock_period) {
if (m_yield_block_info_cache.count(new_height - yield_lock_period - 2) != 0) {
m_yield_block_info_cache.erase(new_height - yield_lock_period - 2);
@@ -5524,7 +5522,7 @@ uint64_t Blockchain::get_next_long_term_block_weight(uint64_t block_weight) cons
const uint64_t db_height = m_db->height();
const uint64_t nblocks = std::min<uint64_t>(m_long_term_block_weights_window, db_height);
const uint8_t hf_version = get_current_hard_fork_version();
// const uint8_t hf_version = get_current_hard_fork_version();
if (db_height < CRYPTONOTE_LONG_TERM_BLOCK_WEIGHT_WINDOW_SIZE)
return block_weight;
@@ -6171,7 +6169,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete
} while(0); \
// generate sorted tables for all amounts and absolute offsets
size_t tx_index = 0, block_index = 0;
size_t tx_index = 0; //, block_index = 0;
for (const auto &entry : blocks_entry)
{
if (m_cancel)
@@ -6242,7 +6240,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete
}
}
++block_index;
//++block_index;
}
// sort and remove duplicate absolute_offsets in offset_map
@@ -6514,7 +6512,7 @@ void Blockchain::cancel()
}
#if defined(PER_BLOCK_CHECKPOINT)
static const char expected_block_hashes_hash[] = "1cf6e8892e0512c246cef62610ccf524f30f484e307ae01959a5a7dd166aa328";
static const char expected_block_hashes_hash[] = "fb30b46662cb1cfca74d443d66be5860936f9fd904ba5c6f8daecb926ce545f0";
void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get_checkpoints)
{
if (get_checkpoints == nullptr || !m_fast_sync)
+4
View File
@@ -202,6 +202,10 @@ namespace hw {
const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
crypto::signature &sig) = 0;
virtual void generate_carrot_tx_proof(const crypto::hash &prefix_hash,
const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
const crypto::secret_key &a, crypto::signature &sig) = 0;
virtual bool open_tx(crypto::secret_key &tx_key) = 0;
virtual void get_transaction_prefix_hash(const cryptonote::transaction_prefix& tx, crypto::hash& h) = 0;
+6
View File
@@ -282,6 +282,12 @@ namespace hw {
crypto::generate_tx_proof(prefix_hash, R, A, B, D, r, sig);
}
void device_default::generate_carrot_tx_proof(const crypto::hash &prefix_hash,
const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
const crypto::secret_key &a, crypto::signature &sig) {
crypto::generate_carrot_tx_proof(prefix_hash, R, A, B, D, r, a, sig);
}
bool device_default::open_tx(crypto::secret_key &tx_key) {
cryptonote::keypair txkey = cryptonote::keypair::generate(*this);
tx_key = txkey.sec;
+4
View File
@@ -112,6 +112,10 @@ namespace hw {
const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
crypto::signature &sig) override;
void generate_carrot_tx_proof(const crypto::hash &prefix_hash,
const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
const crypto::secret_key &a, crypto::signature &sig) override;
bool open_tx(crypto::secret_key &tx_key) override;
void get_transaction_prefix_hash(const cryptonote::transaction_prefix& tx, crypto::hash& h) override;
+8
View File
@@ -1433,6 +1433,14 @@ namespace hw {
#endif
}
void device_ledger::generate_carrot_tx_proof(const crypto::hash &prefix_hash,
const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
const crypto::secret_key &a, crypto::signature &sig) {
// to-do: For now, carrot tx proofs are not supported
AUTO_LOCK_CMD();
crypto::generate_carrot_tx_proof(prefix_hash, R, A, B, D, r, a, sig);
}
bool device_ledger::open_tx(crypto::secret_key &tx_key) {
AUTO_LOCK_CMD();
this->lock();
+4
View File
@@ -253,6 +253,10 @@ namespace hw {
const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
crypto::signature &sig) override;
void generate_carrot_tx_proof(const crypto::hash &prefix_hash,
const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
const crypto::secret_key &a, crypto::signature &sig) override;
bool open_tx(crypto::secret_key &tx_key) override;
void get_transaction_prefix_hash(const cryptonote::transaction_prefix& tx, crypto::hash& h) override;
+1 -19
View File
@@ -881,12 +881,6 @@ namespace nodetool
if (m_nettype == cryptonote::MAINNET)
{
return {
"xwvz3ekocr3dkyxfkmgm2hvbpzx2ysqmaxgter7znnqrhoicygkfswid.onion:18083",
"4pixvbejrvihnkxmduo2agsnmc3rrulrqc7s3cbwwrep6h6hrzsibeqd.onion:18083",
"zbjkbsxc5munw3qusl7j2hpcmikhqocdf4pqhnhtpzw5nt5jrmofptid.onion:18083",
"qz43zul2x56jexzoqgkx2trzwcfnr6l3hbtfcfx54g4r3eahy3bssjyd.onion:18083",
"plowsof3t5hogddwabaeiyrno25efmzfxyro2vligremt7sxpsclfaid.onion:18083",
"plowsoffjexmxalw73tkjmf422gq6575fc7vicuu4javzn2ynnte6tyd.onion:18083",
};
}
return {};
@@ -894,10 +888,6 @@ namespace nodetool
if (m_nettype == cryptonote::MAINNET)
{
return {
"s3l6ke4ed3df466khuebb4poienoingwof7oxtbo6j4n56sghe3a.b32.i2p:18080",
"sel36x6fibfzujwvt4hf5gxolz6kd3jpvbjqg6o3ud2xtionyl2q.b32.i2p:18080",
"uqj3aphckqtjsitz7kxx5flqpwjlq5ppr3chazfued7xucv3nheq.b32.i2p:18080",
"vdmnehdjkpkg57nthgnjfuaqgku673r5bpbqg56ix6fyqoywgqrq.b32.i2p:18080",
};
}
return {};
@@ -2054,20 +2044,13 @@ namespace nodetool
return true;
static const std::vector<std::string> dns_urls = {
"blocklist.moneropulse.se"
, "blocklist.moneropulse.org"
, "blocklist.moneropulse.net"
, "blocklist.moneropulse.no"
, "blocklist.moneropulse.fr"
, "blocklist.moneropulse.de"
, "blocklist.moneropulse.ch"
};
std::vector<std::string> records;
if (!tools::dns_utils::load_txt_records_from_dns(records, dns_urls))
return true;
unsigned good = 0, bad = 0;
unsigned good = 0;
for (const auto& record : records)
{
std::vector<std::string> ips;
@@ -2091,7 +2074,6 @@ namespace nodetool
continue;
}
MWARNING("Invalid IP address or subnet from DNS blocklist: " << ip << " - " << parsed_addr.error());
++bad;
}
}
if (good > 0)
-1
View File
@@ -3125,7 +3125,6 @@ namespace cryptonote
CHECK_PAYMENT(req, res, COST_PER_FEE_ESTIMATE);
const uint8_t version = m_core.get_blockchain_storage().get_current_hard_fork_version();
m_core.get_blockchain_storage().get_dynamic_base_fee_estimate_2021_scaling(req.grace_blocks, res.fees);
res.fee = res.fees[0];
res.quantization_mask = Blockchain::get_fee_quantization_mask();
+34 -11
View File
@@ -237,6 +237,7 @@ namespace
const char* USAGE_SIGN_TRANSFER("sign_transfer [export_raw] [<filename>]");
const char* USAGE_SET_LOG("set_log <level>|{+,-,}<categories>");
const char* USAGE_ACCOUNT("account\n"
" account all\n"
" account new <label text with white spaces allowed>\n"
" account switch <index> \n"
" account label <index> <label text with white spaces allowed>\n"
@@ -3635,7 +3636,8 @@ simple_wallet::simple_wallet()
m_cmd_binder.set_handler("account",
boost::bind(&simple_wallet::on_command, this, &simple_wallet::account, _1),
tr(USAGE_ACCOUNT),
tr("If no arguments are specified, the wallet shows all the existing accounts along with their balances.\n"
tr("If no arguments are specified, the wallet shows all the existing accounts (that have balances) along with their balances.\n"
"If the \"all\" argument is specified, the wallet shows all the existing accounts, even if they have zero balances.\n"
"If the \"new\" argument is specified, the wallet creates a new account with its label initialized by the provided label text (which can be empty).\n"
"If the \"switch\" argument is specified, the wallet switches to the account specified by <index>.\n"
"If the \"label\" argument is specified, the wallet sets the label of the account specified by <index> to the provided label text.\n"
@@ -4610,7 +4612,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
{
m_wallet_file = m_generate_from_svb_key;
// parse address
std::string address_string = input_line("Standard address");
std::string address_string = input_line("Carrot wallet address");
if (std::cin.eof())
return false;
if (address_string.empty()) {
@@ -4635,7 +4637,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
}
// parse view secret key
epee::wipeable_string viewkey_string = input_secure_line("Secret view key");
epee::wipeable_string viewkey_string = input_secure_line("View-balance secret");
if (std::cin.eof())
return false;
if (viewkey_string.empty()) {
@@ -5615,6 +5617,12 @@ boost::optional<epee::wipeable_string> simple_wallet::open_wallet(const boost::p
m_wallet->rewrite(m_wallet_file, password);
}
}
if (m_wallet->force_rescan()) {
m_wallet->force_rescan(false);
refresh_main(m_wallet->get_refresh_from_block_height(), ResetSoft);
}
}
catch (const std::exception& e)
{
@@ -6450,6 +6458,10 @@ bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bo
bool simple_wallet::refresh(const std::vector<std::string>& args)
{
uint64_t start_height = 0;
if (m_wallet->force_rescan()) {
m_wallet->force_rescan(false);
return refresh_main(start_height, ResetSoft);
}
if(!args.empty()){
try
{
@@ -9431,7 +9443,7 @@ bool simple_wallet::get_tx_proof(const std::vector<std::string> &args)
try
{
std::string sig_str = m_wallet->get_tx_proof(txid, info.address, info.is_subaddress, args.size() == 3 ? args[2] : "");
const std::string filename = "monero_tx_proof";
const std::string filename = "salvium_tx_proof";
if (m_wallet->save_to_file(filename, sig_str, true))
success_msg_writer() << tr("signature file saved to: ") << filename;
else
@@ -10116,7 +10128,9 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
transfer.type == "burnt" ? console_color_yellow :
transfer.type == "stake" ? console_color_cyan :
transfer.type == "yield" ? console_color_magenta :
transfer.confirmed ? ((transfer.direction == "in" || transfer.direction == "block") ? console_color_green : console_color_white) : console_color_default;
transfer.confirmed ?
((transfer.direction == "in" || transfer.direction == "block") ?
(transfer.asset_type == "SAL" ? console_color_green : console_color_blue) : console_color_white) : console_color_default;
std::string destinations = "-";
if (!transfer.outputs.empty())
@@ -10696,12 +10710,21 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector
{
// print all the existing accounts
LOCK_IDLE_SCOPE();
print_accounts();
print_accounts(false);
return true;
}
std::vector<std::string> local_args = args;
std::string command = local_args[0];
if (command == "all")
{
// print all accounts including zero balance
LOCK_IDLE_SCOPE();
print_accounts(true);
return true;
}
local_args.erase(local_args.begin());
if (command == "new")
{
@@ -10833,20 +10856,20 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector
return true;
}
//----------------------------------------------------------------------------------------------------
void simple_wallet::print_accounts()
void simple_wallet::print_accounts(bool show_all)
{
const std::pair<std::map<std::string, std::string>, std::vector<std::string>>& account_tags = m_wallet->get_account_tags();
size_t num_untagged_accounts = m_wallet->get_num_subaddress_accounts();
for (const std::pair<const std::string, std::string>& p : account_tags.first)
{
const std::string& tag = p.first;
print_accounts(tag);
print_accounts(tag, show_all);
num_untagged_accounts -= std::count(account_tags.second.begin(), account_tags.second.end(), tag);
success_msg_writer() << "";
}
if (num_untagged_accounts > 0)
print_accounts("");
print_accounts("", show_all);
if (num_untagged_accounts < m_wallet->get_num_subaddress_accounts()) {
std::map<std::string, uint64_t> balances = m_wallet->balance_all(false);
@@ -10858,7 +10881,7 @@ void simple_wallet::print_accounts()
}
}
//----------------------------------------------------------------------------------------------------
void simple_wallet::print_accounts(const std::string& tag)
void simple_wallet::print_accounts(const std::string& tag, bool show_all)
{
const std::pair<std::map<std::string, std::string>, std::vector<std::string>>& account_tags = m_wallet->get_account_tags();
if (tag.empty())
@@ -10887,7 +10910,7 @@ void simple_wallet::print_accounts(const std::string& tag)
auto balance = m_wallet->balance(account_index, asset, false);
auto unlocked_balance = m_wallet->unlocked_balance(account_index, asset, false);
if (balance == 0)
if (!show_all && balance == 0)
continue;
success_msg_writer() << boost::format(tr(" %c%8u %8s %21s %21s %6s %21s"))
% (m_current_subaddress_account == account_index ? '*' : ' ')
+2 -2
View File
@@ -205,8 +205,8 @@ namespace cryptonote
std::vector<cryptonote::tx_destination_entry> dsts, size_t num_splits
);
bool account(const std::vector<std::string> &args = std::vector<std::string>());
void print_accounts();
void print_accounts(const std::string& tag);
void print_accounts(bool show_all = false);
void print_accounts(const std::string& tag, bool show_all = false);
bool print_address(const std::vector<std::string> &args = std::vector<std::string>());
bool print_integrated_address(const std::vector<std::string> &args = std::vector<std::string>());
bool address_book(const std::vector<std::string> &args = std::vector<std::string>());
+1 -1
View File
@@ -1,5 +1,5 @@
#define DEF_SALVIUM_VERSION_TAG "@VERSIONTAG@"
#define DEF_SALVIUM_VERSION "1.0.5"
#define DEF_SALVIUM_VERSION "1.0.7"
#define DEF_MONERO_VERSION_TAG "release"
#define DEF_MONERO_VERSION "0.18.3.4"
#define DEF_MONERO_RELEASE_NAME "One"
+6
View File
@@ -166,9 +166,15 @@ uint64_t PendingTransactionImpl::amount() const
{
uint64_t result = 0;
for (const auto &ptx : m_pending_tx) {
if (ptx.tx.type == cryptonote::transaction_type::AUDIT ||
ptx.tx.type == cryptonote::transaction_type::BURN ||
ptx.tx.type == cryptonote::transaction_type::STAKE) {
result += ptx.tx.amount_burnt;
} else {
for (const auto &dest : ptx.dests) {
result += dest.amount;
}
}
}
return result;
}
+19 -5
View File
@@ -175,7 +175,13 @@ void TransactionHistoryImpl::refresh()
uint64_t change = pd.m_change == (uint64_t)-1 ? 0 : pd.m_change; // change may not be known
uint64_t fee = pd.m_amount_in - pd.m_amount_out;
uint64_t amount = pd.m_amount_in - change - fee;
if (pd.m_tx.type == cryptonote::transaction_type::AUDIT ||
pd.m_tx.type == cryptonote::transaction_type::BURN ||
pd.m_tx.type == cryptonote::transaction_type::STAKE) {
amount = pd.m_tx.amount_burnt;
if (fee > amount) fee -= amount;
}
std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id);
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
@@ -184,7 +190,7 @@ void TransactionHistoryImpl::refresh()
TransactionInfoImpl * ti = new TransactionInfoImpl();
ti->m_paymentid = payment_id;
ti->m_amount = pd.m_amount_in - change - fee;
ti->m_amount = amount;
ti->m_fee = fee;
ti->m_direction = TransactionInfo::Direction_Out;
ti->m_hash = string_tools::pod_to_hex(hash);
@@ -212,8 +218,16 @@ void TransactionHistoryImpl::refresh()
for (std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>>::const_iterator i = upayments_out.begin(); i != upayments_out.end(); ++i) {
const tools::wallet2::unconfirmed_transfer_details &pd = i->second;
const crypto::hash &hash = i->first;
uint64_t amount = pd.m_amount_in;
uint64_t fee = amount - pd.m_amount_out;
uint64_t change = pd.m_change == (uint64_t)-1 ? 0 : pd.m_change; // change may not be known
uint64_t fee = pd.m_amount_in - pd.m_amount_out;
uint64_t amount = pd.m_amount_in - change - fee;
if (pd.m_tx.type == cryptonote::transaction_type::AUDIT ||
pd.m_tx.type == cryptonote::transaction_type::BURN ||
pd.m_tx.type == cryptonote::transaction_type::STAKE) {
amount = pd.m_tx.amount_burnt;
if (fee > amount) fee -= amount;
}
std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id);
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
payment_id = payment_id.substr(0,16);
@@ -221,7 +235,7 @@ void TransactionHistoryImpl::refresh()
TransactionInfoImpl * ti = new TransactionInfoImpl();
ti->m_paymentid = payment_id;
ti->m_amount = amount - pd.m_change - fee;
ti->m_amount = amount;
ti->m_fee = fee;
ti->m_direction = TransactionInfo::Direction_Out;
ti->m_failed = is_failed;
+45 -1
View File
@@ -893,24 +893,48 @@ std::string WalletImpl::integratedAddress(const std::string &payment_id, bool ca
std::string WalletImpl::secretViewKey() const
{
uint32_t hf_version = m_wallet->estimate_current_hard_fork();
if (hf_version >= HF_VERSION_CARROT)
return epee::string_tools::pod_to_hex(unwrap(unwrap(m_wallet->get_account().get_keys().k_view_incoming)));
else
return epee::string_tools::pod_to_hex(unwrap(unwrap(m_wallet->get_account().get_keys().m_view_secret_key)));
}
std::string WalletImpl::publicViewKey() const
{
uint32_t hf_version = m_wallet->estimate_current_hard_fork();
if (hf_version >= HF_VERSION_CARROT)
return epee::string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_carrot_account_address.m_view_public_key);
else
return epee::string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_account_address.m_view_public_key);
}
std::string WalletImpl::secretSpendKey() const
{
return epee::string_tools::pod_to_hex(unwrap(unwrap(m_wallet->get_account().get_keys().m_spend_secret_key)));
return epee::string_tools::pod_to_hex(unwrap(unwrap(m_wallet->get_account().get_keys().m_spend_secret_key)));
}
std::string WalletImpl::publicSpendKey() const
{
uint32_t hf_version = m_wallet->estimate_current_hard_fork();
if (hf_version >= HF_VERSION_CARROT)
return epee::string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_carrot_account_address.m_spend_public_key);
else
return epee::string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_account_address.m_spend_public_key);
}
std::vector<std::string> WalletImpl::carrotKeys() const
{
return {
epee::string_tools::pod_to_hex(unwrap(unwrap(m_wallet->get_account().get_keys().s_master))),
epee::string_tools::pod_to_hex(unwrap(unwrap(m_wallet->get_account().get_keys().k_prove_spend))),
epee::string_tools::pod_to_hex(unwrap(unwrap(m_wallet->get_account().get_keys().s_view_balance))),
epee::string_tools::pod_to_hex(unwrap(unwrap(m_wallet->get_account().get_keys().k_view_incoming))),
epee::string_tools::pod_to_hex(unwrap(unwrap(m_wallet->get_account().get_keys().k_generate_image))),
epee::string_tools::pod_to_hex(unwrap(unwrap(m_wallet->get_account().get_keys().s_generate_address)))
};
}
std::string WalletImpl::publicMultisigSignerKey() const
{
try {
@@ -921,6 +945,26 @@ std::string WalletImpl::publicMultisigSignerKey() const
}
}
std::string WalletImpl::secretViewBalance() const
{
return epee::string_tools::pod_to_hex(unwrap(unwrap(m_wallet->get_account().get_keys().s_view_balance)));
}
std::string WalletImpl::secretProveSpend() const
{
return epee::string_tools::pod_to_hex(unwrap(unwrap(m_wallet->get_account().get_keys().k_prove_spend)));
}
std::string WalletImpl::secretGenerateAddress() const
{
return epee::string_tools::pod_to_hex(unwrap(unwrap(m_wallet->get_account().get_keys().s_generate_address)));
}
std::string WalletImpl::secretGenerateImage() const
{
return epee::string_tools::pod_to_hex(unwrap(unwrap(m_wallet->get_account().get_keys().k_generate_image)));
}
std::string WalletImpl::path() const
{
return m_wallet->path();
+5
View File
@@ -99,7 +99,12 @@ public:
std::string publicViewKey() const override;
std::string secretSpendKey() const override;
std::string publicSpendKey() const override;
std::vector<std::string> carrotKeys() const override;
std::string publicMultisigSignerKey() const override;
std::string secretViewBalance() const override;
std::string secretProveSpend() const override;
std::string secretGenerateAddress() const override;
std::string secretGenerateImage() const override;
std::string path() const override;
void stop() override;
bool store(const std::string &path) override;
+36
View File
@@ -559,12 +559,48 @@ struct Wallet
*/
virtual std::string publicSpendKey() const = 0;
/*!
* \brief allCarrotKeys - returns all Carrot keys
* [0] - s_master
* [1] - k_prove_spend
* [2] - s_view_balance
* [3] - k_view_incoming
* [4] - k_generate_image
* [5] - s_generate_address
* \return - vector of all Carrot keys
*/
virtual std::vector<std::string> carrotKeys() const = 0;
/*!
* \brief publicMultisigSignerKey - returns public signer key
* \return - public multisignature signer key or empty string if wallet is not multisig
*/
virtual std::string publicMultisigSignerKey() const = 0;
/*!
* \brief secretViewBalance - returns Carrot "view balance" secret
* \return - Carrot s_vb
*/
virtual std::string secretViewBalance() const = 0;
/*!
* \brief secretProveSpend - returns Carrot "prove spend" secret
* \return - Carrot secret k_ps
*/
virtual std::string secretProveSpend() const = 0;
/*!
* \brief secretGenerateAddress - returns Carrot "generate address" secret
* \return - Carrot secret s_ga
*/
virtual std::string secretGenerateAddress() const = 0;
/*!
* \brief secretGenerateImage - returns Carrot "generate key image" secret
* \return - Carrot secret k_gi
*/
virtual std::string secretGenerateImage() const = 0;
/*!
* \brief stop - interrupts wallet refresh() loop once (doesn't stop background refresh thread)
*/
+8 -1
View File
@@ -973,7 +973,14 @@ std::optional<crypto::key_image> try_derive_enote_key_image(
// x = k_s + k^j_subext + k^g_o
rct::key x;
if (enote_scan_info.is_carrot) {
return acc.derive_key_image(enote_scan_info.address_spend_pubkey,
// if we don't have the s_master key, derive view-only key image
if (acc.get_keys().s_master == crypto::null_skey) {
return acc.derive_key_image_view_only(enote_scan_info.address_spend_pubkey,
enote_scan_info.sender_extension_g,
enote_scan_info.sender_extension_t,
rct::rct2pk(onetime_address));
}
return acc.derive_key_image(enote_scan_info.address_spend_pubkey,
enote_scan_info.sender_extension_g,
enote_scan_info.sender_extension_t,
rct::rct2pk(onetime_address));
+20 -11
View File
@@ -91,7 +91,7 @@ static bool is_transfer_usable_for_input_selection(const wallet2::transfer_detai
&& td.m_key_image_known
&& !td.m_key_image_partial
&& !td.m_frozen
&& (top_block_index >= td.m_block_height + blocks_locked_for)
&& (top_block_index +1 >= td.m_block_height + blocks_locked_for)
// && last_locked_block_index <= top_block_index
&& td.m_subaddr_index.major == from_account
&& (from_subaddresses.empty() || from_subaddresses.count(td.m_subaddr_index.minor) == 1)
@@ -151,6 +151,7 @@ static cryptonote::tx_destination_entry make_tx_destination_entry(
{payment_proposal.destination.address_spend_pubkey, payment_proposal.destination.address_view_pubkey, /*m_is_carrot*/true},
payment_proposal.destination.is_subaddress);
dest.is_integrated = payment_proposal.destination.payment_id != carrot::null_payment_id;
dest.asset_type = payment_proposal.asset_type;
return dest;
}
//-------------------------------------------------------------------------------------------------------------------
@@ -165,9 +166,11 @@ static cryptonote::tx_destination_entry make_tx_destination_entry(
address_view_pubkey),
"make_tx_destination_entry: view-key multiplication failed");
return cryptonote::tx_destination_entry(payment_proposal.proposal.amount,
cryptonote::tx_destination_entry dest = cryptonote::tx_destination_entry(payment_proposal.proposal.amount,
{payment_proposal.proposal.destination_address_spend_pubkey, address_view_pubkey, /*m_is_carrot*/true},
payment_proposal.subaddr_index.index.is_subaddress());
dest.asset_type = payment_proposal.proposal.asset_type;
return dest;
}
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
@@ -346,7 +349,7 @@ std::vector<cryptonote::tx_source_entry> get_sources(
w.get_outs(outs, selected_transfers, fake_outputs_count, true, valid_public_keys_cache); // may throw
LOG_PRINT_L2("preparing outputs");
size_t i = 0, out_index = 0;
size_t out_index = 0;
std::vector<cryptonote::tx_source_entry> sources;
for(size_t idx: selected_transfers)
{
@@ -387,7 +390,6 @@ std::vector<cryptonote::tx_source_entry> get_sources(
oe.second.mask = std::get<2>(outs[out_index][n]);
src.outputs.push_back(oe);
}
++i;
//paste real transaction to the random index
auto it_to_replace = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a)
@@ -815,8 +817,14 @@ bool get_address_openings_x_y(
if (return_output_map.find(rct::rct2pk(src.outputs[src.real_output].second.dest)) != return_output_map.end())
{
const auto &return_output = return_output_map.at(rct::rct2pk(src.outputs[src.real_output].second.dest));
x_out = return_output.x;
y_out = return_output.y;
bool r = w.get_account().try_searching_for_opening_for_onetime_address(
return_output.K_spend_pubkey,
return_output.sum_g,
return_output.sender_extension_t,
x_out,
y_out
);
CHECK_AND_ASSERT_THROW_MES(r, "Failed to obtain openings for onetime address (return_payment)");
return true;
}
@@ -1088,14 +1096,14 @@ cryptonote::transaction finalize_all_proofs_from_transfer_details(
const auto &sources = tx_proposal.sources;
// inputs
uint64_t amount_in = 0;
// uint64_t amount_in = 0;
rct::carrot_ctkeyV inSk;
inSk.reserve(sources.size());
std::vector<uint64_t> inamounts;
std::vector<unsigned int> index;
for (const auto& src: sources)
{
amount_in += src.amount;
// amount_in += src.amount;
inamounts.push_back(src.amount);
index.push_back(src.real_output);
@@ -1144,7 +1152,7 @@ cryptonote::transaction finalize_all_proofs_from_transfer_details(
}
// outputs
uint64_t amount_out = 0;
// uint64_t amount_out = 0;
std::vector<uint64_t> outamounts;
rct::keyV destinations;
std::vector<std::string> destination_asset_types;
@@ -1154,7 +1162,7 @@ cryptonote::transaction finalize_all_proofs_from_transfer_details(
destinations.push_back(rct::pk2rct(oep.enote.onetime_address));
destination_asset_types.push_back(oep.enote.asset_type);
outamounts.push_back(oep.amount);
amount_out += oep.amount;
// amount_out += oep.amount;
rct::ctkey key;
key.mask = rct::sk2rct(oep.amount_blinding_factor);
@@ -1288,7 +1296,8 @@ wallet2::pending_tx make_pending_carrot_tx(const carrot::CarrotTransactionPropos
carrot::encrypted_payment_id_t encrypted_payment_id;
std::vector<std::pair<bool, std::size_t>> sorted_payment_proposal_indices;
carrot::get_output_enote_proposals_from_proposal_v1(tx_proposal,
/*s_view_balance_dev=*/nullptr,
&account.s_view_balance_dev,
///*s_view_balance_dev=*/nullptr,
&account.k_view_incoming_dev,
output_enote_proposals,
encrypted_payment_id,
+416 -132
View File
@@ -1572,10 +1572,12 @@ cryptonote::account_public_address wallet2::get_subaddress(const cryptonote::sub
//----------------------------------------------------------------------------------------------------
boost::optional<cryptonote::subaddress_index> wallet2::get_subaddress_index(const cryptonote::account_public_address& address) const
{
auto index = m_subaddresses.find(address.m_spend_public_key);
if (index == m_subaddresses.end())
const auto subaddress_map = m_account.get_subaddress_map_ref();
auto carrot_index = subaddress_map.find(address.m_spend_public_key);
if (carrot_index == subaddress_map.end())
return boost::none;
return index->second;
cryptonote::subaddress_index index{carrot_index->second.index.major, carrot_index->second.index.minor};
return index;
}
//----------------------------------------------------------------------------------------------------
crypto::public_key wallet2::get_subaddress_spend_public_key(const cryptonote::subaddress_index& index) const
@@ -2437,7 +2439,7 @@ void wallet2::scan_key_image(const wallet::enote_view_incoming_scan_info_t &enot
{
ki_out = std::nullopt;
if (m_multisig || m_background_syncing || m_watch_only) // no complete spend privkey
if (m_multisig || m_background_syncing/* || m_watch_only*/) // no complete spend privkey
return;
// if keys are encrypted, ask for password
@@ -3369,11 +3371,9 @@ void wallet2::process_parsed_blocks(const uint64_t start_height, const std::vect
tools::threadpool& tpool = tools::threadpool::getInstanceForCompute();
size_t num_txes = 0;
size_t num_tx_outputs = 0;
for (const parsed_block &par_blk : parsed_blocks)
{
num_txes += 2 + par_blk.txes.size();
num_tx_outputs += par_blk.block.miner_tx.vout.size();
num_tx_outputs += par_blk.block.protocol_tx.vout.size();
for (const cryptonote::transaction &tx : par_blk.txes)
@@ -3398,7 +3398,10 @@ void wallet2::process_parsed_blocks(const uint64_t start_height, const std::vect
{
if (tx.vout.empty())
{
MWARNING("Skipping tx without any outputs: " << get_transaction_hash(tx) << ", height: " << m_blockchain.size());
if (tx.type == cryptonote::transaction_type::PROTOCOL)
MDEBUG("Skipping protocol tx without any outputs: " << get_transaction_hash(tx) << ", height: " << m_blockchain.size());
else
MWARNING("Skipping tx without any outputs: " << get_transaction_hash(tx) << ", height: " << m_blockchain.size());
return;
}
@@ -4159,7 +4162,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
// "I had to reorder some code to fix... a timing info leak IIRC. In turn, this undid something I had fixed before, ... a subtle race condition with the txpool.
// It was pretty subtle IIRC, and so I needed time to think about how to refix it after the move, and I never got to it."
// https://github.com/monero-project/monero/pull/6097
bool refreshed = false;
// bool refreshed = false;
std::map<std::pair<uint64_t, uint64_t>, size_t> output_tracker_cache = create_output_tracker_cache();
hw::device &hwdev = m_account.get_device();
@@ -5462,14 +5465,31 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
}
const cryptonote::account_keys& keys = m_account.get_keys();
hw::device &hwdev = m_account.get_device();
r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key);
if (m_watch_only) {
// Check to see if this is a Carrot SVB wallet
crypto::secret_key kv_recomputed;
carrot::make_carrot_viewincoming_key(keys.s_view_balance, kv_recomputed);
THROW_WALLET_EXCEPTION_IF(keys.k_view_incoming != kv_recomputed, error::wallet_internal_error, "cannot compute viable wallet");
// assume Carrot SVB wallet
m_account.create_from_svb_key(keys.m_carrot_account_address, keys.s_view_balance);
// Now verify that the account address has a public view key that matches k_view_incoming
r = r && hwdev.verify_keys(keys.k_view_incoming, keys.m_carrot_main_address.m_view_public_key);
} else {
// Assume normal wallet
r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key);
}
if (!m_watch_only && !m_multisig && hwdev.device_protocol() != hw::device::PROTOCOL_COLD && !m_is_background_wallet)
r = r && hwdev.verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key);
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file);
if (r)
{
m_account.set_carrot_keys();
if (!m_watch_only)
m_account.set_carrot_keys();
if (!m_is_background_wallet)
setup_keys(password);
@@ -5559,7 +5579,7 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip
encrypted_secret_keys = field_encrypted_secret_keys;
}
cryptonote::account_base account_data_check;
carrot::carrot_and_legacy_account account_data_check;
r = epee::serialization::load_t_from_binary(account_data_check, account_data);
@@ -5567,7 +5587,14 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip
account_data_check.decrypt_keys(key);
const cryptonote::account_keys& keys = account_data_check.get_keys();
r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key);
if (no_spend_key) {
// assume Carrot SVB wallet
account_data_check.create_from_svb_key(keys.m_carrot_account_address, keys.s_view_balance);
// Now verify that the account address has a public view key that matches k_view_incoming
r = r && hwdev.verify_keys(keys.k_view_incoming, keys.m_carrot_main_address.m_view_public_key);
} else {
r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key);
}
if(!no_spend_key)
r = r && hwdev.verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key);
spend_key_out = (!no_spend_key && r) ? keys.m_spend_secret_key : crypto::null_skey;
@@ -5628,7 +5655,8 @@ void wallet2::create_keys_file(const std::string &wallet_, bool watch_only, cons
if (create_address_file)
{
r = save_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_nettype), true);
std::string addresses = m_account.get_public_address_str(m_nettype) + "\n" + m_account.get_carrot_public_address_str(m_nettype) + "\n";
r = save_to_file(m_wallet_file + ".address.txt", addresses, true);
if(!r) MERROR("String with address text not saved");
}
}
@@ -6611,7 +6639,9 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass
{
THROW_WALLET_EXCEPTION_IF(true, error::file_read_error, m_keys_file);
}
LOG_PRINT_L0("Loaded wallet keys file, with public address: " << m_account.get_public_address_str(m_nettype));
LOG_PRINT_L0("Loaded wallet keys file, with public addresses: ");
LOG_PRINT_L0(" CN : " << m_account.get_public_address_str(m_nettype));
LOG_PRINT_L0(" Carrot : " << m_account.get_carrot_public_address_str(m_nettype));
lock_keys_file();
}
else if (!load_keys_buf(keys_buf, password))
@@ -6821,10 +6851,17 @@ void wallet2::load_wallet_cache(const bool use_fs, const std::string& cache_buf)
ar >> *this;
}
}
THROW_WALLET_EXCEPTION_IF(
m_account_public_address.m_spend_public_key != m_account.get_keys().m_account_address.m_spend_public_key ||
m_account_public_address.m_view_public_key != m_account.get_keys().m_account_address.m_view_public_key,
error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file);
if (m_watch_only) {
THROW_WALLET_EXCEPTION_IF(
m_account_public_address.m_spend_public_key != m_account.get_keys().m_carrot_main_address.m_spend_public_key ||
m_account_public_address.m_view_public_key != m_account.get_keys().m_carrot_main_address.m_view_public_key,
error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file);
} else {
THROW_WALLET_EXCEPTION_IF(
m_account_public_address.m_spend_public_key != m_account.get_keys().m_account_address.m_spend_public_key ||
m_account_public_address.m_view_public_key != m_account.get_keys().m_account_address.m_view_public_key,
error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file);
}
}
}
//----------------------------------------------------------------------------------------------------
@@ -7251,7 +7288,6 @@ std::map<uint32_t, std::pair<uint64_t, std::pair<uint64_t, uint64_t>>> wallet2::
{
std::map<uint32_t, std::pair<uint64_t, std::pair<uint64_t, uint64_t>>> amount_per_subaddr;
const uint64_t blockchain_height = get_blockchain_current_height();
const uint64_t now = time(NULL);
if (m_transfers_indices.count(asset_type) == 0) {
return amount_per_subaddr;
}
@@ -9923,7 +9959,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::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());
uint64_t num_outs = 0;
num_outs = 0;
const uint64_t amount = td.is_rct() ? 0 : td.amount();
const bool output_is_pre_fork = td.m_block_height < segregation_fork_height;
if (is_after_segregation_fork && m_segregate_pre_fork_outputs && output_is_pre_fork)
@@ -10106,7 +10142,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
//prepare inputs
LOG_PRINT_L2("preparing outputs");
typedef cryptonote::tx_source_entry::output_entry tx_output_entry;
size_t i = 0, out_index = 0;
size_t out_index = 0;
std::vector<cryptonote::tx_source_entry> sources;
for(size_t idx: selected_transfers)
{
@@ -10125,7 +10161,6 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
oe.second.mask = std::get<2>(outs[out_index][n]);
src.outputs.push_back(oe);
++i;
}
//paste real transaction to the random index
@@ -10360,7 +10395,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
//prepare inputs
LOG_PRINT_L2("preparing outputs");
size_t i = 0, out_index = 0;
size_t out_index = 0;
std::vector<cryptonote::tx_source_entry> sources;
for(size_t idx: selected_transfers)
{
@@ -10398,7 +10433,6 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
oe.second.mask = std::get<2>(outs[out_index][n]);
src.outputs.push_back(oe);
}
++i;
//paste real transaction to the random index
auto it_to_replace = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a)
@@ -11003,7 +11037,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
THROW_WALLET_EXCEPTION_IF(subaddr_account != 0, error::wallet_internal_error, "Staking is only permitted from main account, not secondary accounts");
break;
default:
THROW_WALLET_EXCEPTION(error::wallet_internal_error, "Invalid tx type specified: " + static_cast<uint64_t>(tx_type));
THROW_WALLET_EXCEPTION(error::wallet_internal_error, "Invalid tx type specified: " + std::to_string(static_cast<uint64_t>(tx_type)));
break;
}
@@ -11673,8 +11707,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below
const bool bulletproof = true;
const bool bulletproof_plus = true;
const bool clsag = true;
const bool use_fullproofs = use_fork_rules(get_full_proofs_fork(), 0);
const bool use_salviumone_proofs = use_fork_rules(get_salvium_one_proofs_fork(), 0);
//const bool use_fullproofs = use_fork_rules(get_full_proofs_fork(), 0);
//const bool use_salviumone_proofs = use_fork_rules(get_salvium_one_proofs_fork(), 0);
//const rct::RCTConfig rct_config { rct::RangeProofPaddedBulletproof, use_salviumone_proofs ? 6 : use_fullproofs ? 5 : 4 };
const bool use_view_tags = use_fork_rules(get_view_tag_fork(), 0);
const uint64_t base_fee = get_base_fee(priority);
@@ -12013,6 +12047,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_return(std::vector
cryptonote::account_public_address address;
address.m_spend_public_key = P_change;
address.m_view_public_key = rct::rct2pk(key_yF);
address.m_is_carrot = false;
LOG_ERROR("*****************************************************************************");
LOG_ERROR("TX type : RETURN");
@@ -12149,11 +12184,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
// SRCG: should the subaddress be forced to TRUE for _RETURN_ TXs and FALSE for all others?!?!?
// add N - 1 outputs for correct initial fee estimation
const bool dest_is_subaddress = (tx_type == cryptonote::transaction_type::RETURN) || is_subaddress;
for (size_t i = 0; i < ((outputs > 1) ? outputs - 1 : outputs); ++i) {
if (tx_type == cryptonote::transaction_type::AUDIT) {
tx.dsts.push_back(tx_destination_entry(1, address, tx_type == cryptonote::transaction_type::RETURN, tx_type == cryptonote::transaction_type::RETURN));
tx.dsts.push_back(tx_destination_entry(1, address, dest_is_subaddress, false));
} else {
tx.dsts.push_back(tx_destination_entry(1, address, tx_type == cryptonote::transaction_type::RETURN, tx_type == cryptonote::transaction_type::RETURN));
tx.dsts.push_back(tx_destination_entry(1, address, dest_is_subaddress, tx_type == cryptonote::transaction_type::RETURN));
}
tx.dsts.back().asset_type = asset_type;
}
@@ -12176,7 +12212,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
// add last output, missed for fee estimation
if (outputs > 1)
tx.dsts.push_back(tx_destination_entry(1, address, is_subaddress));
tx.dsts.push_back(tx_destination_entry(1, address, dest_is_subaddress, tx_type == cryptonote::transaction_type::RETURN));
THROW_WALLET_EXCEPTION_IF(needed_fee > available_for_fee, error::wallet_internal_error, tr("Transaction cannot pay for itself"));
@@ -12994,15 +13030,44 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes
void wallet2::check_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations)
{
crypto::key_derivation derivation;
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation), error::wallet_internal_error,
"Failed to generate key derivation from supplied parameters");
std::vector<crypto::key_derivation> additional_derivations;
additional_derivations.resize(additional_tx_keys.size());
for (size_t i = 0; i < additional_tx_keys.size(); ++i)
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(address.m_view_public_key, additional_tx_keys[i], additional_derivations[i]), error::wallet_internal_error,
if (address.m_is_carrot)
{
// For Carrot enotes, use X25519 scalar multiplication
mx25519_pubkey s_sender_receiver_unctx;
bool success = carrot::make_carrot_uncontextualized_shared_key_sender(
tx_key,
address.m_view_public_key,
s_sender_receiver_unctx);
THROW_WALLET_EXCEPTION_IF(!success, error::wallet_internal_error,
"Failed to generate X25519 key derivation from supplied parameters (main)");
derivation = carrot::raw_byte_convert<crypto::key_derivation>(s_sender_receiver_unctx);
additional_derivations.resize(additional_tx_keys.size());
for (size_t i = 0; i < additional_tx_keys.size(); ++i)
{
mx25519_pubkey additional_s_sender_receiver_unctx;
success = carrot::make_carrot_uncontextualized_shared_key_sender(
additional_tx_keys[i],
address.m_view_public_key,
additional_s_sender_receiver_unctx);
THROW_WALLET_EXCEPTION_IF(!success, error::wallet_internal_error,
"Failed to generate X25519 key derivation from supplied parameters (additional)");
additional_derivations[i] = carrot::raw_byte_convert<crypto::key_derivation>(additional_s_sender_receiver_unctx);
}
}
else
{
// For legacy enotes, use Edwards curve multiplication
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation), error::wallet_internal_error,
"Failed to generate key derivation from supplied parameters");
additional_derivations.resize(additional_tx_keys.size());
for (size_t i = 0; i < additional_tx_keys.size(); ++i)
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(address.m_view_public_key, additional_tx_keys[i], additional_derivations[i]), error::wallet_internal_error,
"Failed to generate key derivation from supplied parameters");
}
check_tx_key_helper(txid, derivation, additional_derivations, address, received, in_pool, confirmations);
}
@@ -13010,10 +13075,14 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt
{
received = 0;
const auto enote_scan_infos = wallet::view_incoming_scan_transaction_as_sender(tx,
{&derivation, 1},
epee::to_span(additional_derivations),
address);
const bool use_additional_derivations = !additional_derivations.empty() && address.m_is_carrot;
const bool is_out = m_account.get_subaddress_map_ref().count(address.m_spend_public_key) == 0;
const auto enote_scan_infos = (is_out)
? wallet::view_incoming_scan_transaction_as_sender(tx,
use_additional_derivations ? epee::span<const crypto::key_derivation>{} : epee::span<const crypto::key_derivation>{&derivation, 1},
epee::to_span(additional_derivations),
address)
: wallet::view_incoming_scan_transaction(tx, m_account);
for (const auto &enote_scan_info : enote_scan_infos)
if (enote_scan_info && enote_scan_info->address_spend_pubkey == address.m_spend_public_key)
@@ -13126,12 +13195,15 @@ std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::ac
// determine if the address is found in the subaddress hash table (i.e. whether the proof is outbound or inbound)
crypto::secret_key tx_key = crypto::null_skey;
std::vector<crypto::secret_key> additional_tx_keys;
const bool is_out = m_subaddresses.count(address.m_spend_public_key) == 0;
const bool is_out = m_account.get_subaddress_map_ref().count(address.m_spend_public_key) == 0;
if (is_out)
{
THROW_WALLET_EXCEPTION_IF(!get_tx_key(txid, tx_key, additional_tx_keys), error::wallet_internal_error, "Tx secret key wasn't found in the wallet file.");
}
THROW_WALLET_EXCEPTION_IF(tx.version < TRANSACTION_VERSION_CARROT && address.m_is_carrot, error::wallet_internal_error, "Need CN address for CryptoNote TX");
THROW_WALLET_EXCEPTION_IF(tx.version >= TRANSACTION_VERSION_CARROT && !address.m_is_carrot, error::wallet_internal_error, "Need Carrot address for Carrot TX");
return get_tx_proof(tx, tx_key, additional_tx_keys, address, is_subaddress, message);
}
@@ -13139,8 +13211,104 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt
{
hw::device &hwdev = m_account.get_device();
rct::key aP;
// Lambda helper to select between carrot and normal tx proof generation
auto generate_proof_fn = [&](
const crypto::hash &prefix_hash,
const crypto::public_key &R,
const crypto::public_key &A,
const boost::optional<crypto::public_key> &B,
const crypto::public_key &D,
const crypto::secret_key &r,
const crypto::secret_key &a,
crypto::signature &sig)
{
if (address.m_is_carrot) {
hwdev.generate_carrot_tx_proof(prefix_hash, R, A, is_subaddress ? B : boost::none, D, r, a, sig);
} else {
crypto::signature sig_cn;
if (r != crypto::null_skey) {
hwdev.generate_tx_proof(prefix_hash, R, A, is_subaddress ? B : boost::none, D, r, sig_cn);
} else {
hwdev.generate_tx_proof(prefix_hash, A, R, is_subaddress ? B : boost::none, D, a, sig_cn);
}
sig.c = sig_cn.c;
sig.r = sig_cn.r;
sig.sign_mask = 0xFF;
}
};
auto calculate_shared_secret_fn = [&](
const crypto::public_key &A,
const crypto::secret_key &a,
const crypto::public_key &R,
const crypto::secret_key &r,
const bool is_out,
crypto::public_key &shared_secret
)
{
if (address.m_is_carrot) {
// Carrot code
mx25519_pubkey s_sender_receiver_unctx;
bool success = false;
if (is_out) {
success = carrot::make_carrot_uncontextualized_shared_key_sender(r, A, s_sender_receiver_unctx);
} else {
success = carrot::make_carrot_uncontextualized_shared_key_receiver(a,
carrot::raw_byte_convert<mx25519_pubkey>(R),
s_sender_receiver_unctx);
}
THROW_WALLET_EXCEPTION_IF(!success, error::wallet_internal_error,
"Failed to generate X25519 key derivation for carrot proof (main)");
shared_secret = carrot::raw_byte_convert<crypto::public_key>(s_sender_receiver_unctx);
} else {
// CryptoNote code
if (is_out) {
rct::key rA;
hwdev.scalarmultKey(rA, rct::pk2rct(A), rct::sk2rct(r));
shared_secret = rct::rct2pk(rA);
} else {
rct::key aR;
hwdev.scalarmultKey(aR,rct::pk2rct(R), rct::sk2rct(a));
shared_secret = rct::rct2pk(aR);
}
}
};
auto calculate_tx_public_key_fn = [&](
const crypto::secret_key &tx_key,
crypto::public_key &tx_pub_key
)
{
if (address.m_is_carrot) {
mx25519_pubkey enote_ephemeral_pubkey_out;
carrot::make_carrot_enote_ephemeral_pubkey(tx_key,
address.m_spend_public_key,
is_subaddress,
enote_ephemeral_pubkey_out);
THROW_WALLET_EXCEPTION_IF(!success, error::wallet_internal_error,
"Failed to generate TX pubkey for carrot proof (main)");
tx_pub_key = carrot::raw_byte_convert<crypto::public_key>(enote_ephemeral_pubkey_out);
} else {
if (is_subaddress)
{
rct::key aP;
hwdev.scalarmultKey(aP, rct::pk2rct(address.m_spend_public_key), rct::sk2rct(tx_key));
tx_pub_key = rct2pk(aP);
}
else
{
hwdev.secret_key_to_public_key(tx_key, tx_pub_key);
}
}
};
// determine if the address is found in the subaddress hash table (i.e. whether the proof is outbound or inbound)
const bool is_out = m_subaddresses.count(address.m_spend_public_key) == 0;
const bool is_out = m_account.get_subaddress_map_ref().count(address.m_spend_public_key) == 0;
const crypto::hash txid = cryptonote::get_transaction_hash(tx);
std::string prefix_data((const char*)&txid, sizeof(crypto::hash));
@@ -13151,88 +13319,127 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt
std::vector<crypto::public_key> shared_secret;
std::vector<crypto::signature> sig;
std::string sig_str;
if (is_out)
{
// determine if tx_key is invalid and should be skipped
const bool skip_txkey = (tx_key == crypto::null_skey &&
address.m_is_carrot &&
tx.vout.size() == additional_tx_keys.size());
const size_t num_sigs = 1 + additional_tx_keys.size();
shared_secret.resize(num_sigs);
sig.resize(num_sigs);
hwdev.scalarmultKey(aP, rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key));
shared_secret[0] = rct::rct2pk(aP);
crypto::public_key dummy_pkey;
crypto::secret_key dummy_skey;
crypto::public_key tx_pub_key;
if (is_subaddress)
{
hwdev.scalarmultKey(aP, rct::pk2rct(address.m_spend_public_key), rct::sk2rct(tx_key));
tx_pub_key = rct2pk(aP);
hwdev.generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], tx_key, sig[0]);
}
else
{
hwdev.secret_key_to_public_key(tx_key, tx_pub_key);
hwdev.generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], tx_key, sig[0]);
if (!skip_txkey) {
calculate_shared_secret_fn(address.m_view_public_key,
dummy_skey,
dummy_pkey,
tx_key,
is_out,
shared_secret[0]);
calculate_tx_public_key_fn(tx_key, tx_pub_key);
generate_proof_fn(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], tx_key, crypto::null_skey, sig[0]);
}
for (size_t i = 1; i < num_sigs; ++i)
{
hwdev.scalarmultKey(aP, rct::pk2rct(address.m_view_public_key), rct::sk2rct(additional_tx_keys[i - 1]));
shared_secret[i] = rct::rct2pk(aP);
if (is_subaddress)
{
hwdev.scalarmultKey(aP, rct::pk2rct(address.m_spend_public_key), rct::sk2rct(additional_tx_keys[i - 1]));
tx_pub_key = rct2pk(aP);
hwdev.generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[i], additional_tx_keys[i - 1], sig[i]);
}
else
{
hwdev.secret_key_to_public_key(additional_tx_keys[i - 1], tx_pub_key);
hwdev.generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[i], additional_tx_keys[i - 1], sig[i]);
}
// Clear the values
shared_secret[i] = crypto::null_pkey;
sig[i] = {crypto::null_skey, crypto::null_skey};
// Is this an invalid key?
if (skip_txkey && additional_tx_keys[i - 1] == crypto::null_skey)
continue;
calculate_shared_secret_fn(address.m_view_public_key,
dummy_skey,
dummy_pkey,
additional_tx_keys[i - 1],
is_out,
shared_secret[i]);
calculate_tx_public_key_fn(additional_tx_keys[i - 1], tx_pub_key);
generate_proof_fn(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[i], additional_tx_keys[i - 1], crypto::null_skey, sig[i]);
}
sig_str = std::string("OutProofV2");
sig_str = address.m_is_carrot ? std::string("OutProofV3") : std::string("OutProofV2");
}
else
{
crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx);
THROW_WALLET_EXCEPTION_IF(tx_pub_key == null_pkey, error::wallet_internal_error, "Tx pubkey was not found");
std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx);
// determine if tx_pub_key is invalid and should be skipped
const bool skip_tx_pubkey = (tx_pub_key == crypto::null_pkey &&
address.m_is_carrot &&
tx.vout.size() == additional_tx_pub_keys.size());
if (!skip_tx_pubkey)
THROW_WALLET_EXCEPTION_IF(tx_pub_key == null_pkey, error::wallet_internal_error, "Tx pubkey was not found");
const size_t num_sigs = 1 + additional_tx_pub_keys.size();
shared_secret.resize(num_sigs);
sig.resize(num_sigs);
const crypto::secret_key& a = m_account.get_keys().m_view_secret_key;
hwdev.scalarmultKey(aP, rct::pk2rct(tx_pub_key), rct::sk2rct(a));
shared_secret[0] = rct2pk(aP);
if (is_subaddress)
{
hwdev.generate_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, address.m_spend_public_key, shared_secret[0], a, sig[0]);
}
else
{
hwdev.generate_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, shared_secret[0], a, sig[0]);
const crypto::secret_key& a = address.m_is_carrot ? m_account.get_keys().k_view_incoming : m_account.get_keys().m_view_secret_key;
crypto::public_key dummy_pkey;
crypto::secret_key dummy_skey;
if (!skip_tx_pubkey) {
calculate_shared_secret_fn(dummy_pkey,
a,
tx_pub_key,
dummy_skey,
is_out,
shared_secret[0]);
generate_proof_fn(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], crypto::null_skey, a, sig[0]);
}
for (size_t i = 1; i < num_sigs; ++i)
{
hwdev.scalarmultKey(aP,rct::pk2rct(additional_tx_pub_keys[i - 1]), rct::sk2rct(a));
shared_secret[i] = rct2pk(aP);
if (is_subaddress)
{
hwdev.generate_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i - 1], address.m_spend_public_key, shared_secret[i], a, sig[i]);
}
else
{
hwdev.generate_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i - 1], boost::none, shared_secret[i], a, sig[i]);
}
// Is this an invalid key?
if (skip_tx_pubkey && additional_tx_pub_keys[i - 1] == crypto::null_pkey)
continue;
calculate_shared_secret_fn(dummy_pkey,
a,
additional_tx_pub_keys[i - 1],
dummy_skey,
is_out,
shared_secret[i]);
generate_proof_fn(prefix_hash, additional_tx_pub_keys[i - 1], address.m_view_public_key, address.m_spend_public_key, shared_secret[i], crypto::null_skey, a, sig[i]);
}
sig_str = std::string("InProofV2");
sig_str = address.m_is_carrot ? std::string("InProofV3") : std::string("InProofV2");
}
// Get the number of signatures that we have created (why? this is no different for in/out?)
const size_t num_sigs = shared_secret.size();
// check if this address actually received any funds
crypto::key_derivation derivation;
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[0], rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation");
std::vector<crypto::key_derivation> additional_derivations(num_sigs - 1);
for (size_t i = 1; i < num_sigs; ++i)
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[i], rct::rct2sk(rct::I), additional_derivations[i - 1]), error::wallet_internal_error, "Failed to generate key derivation");
if (address.m_is_carrot)
{
// For carrot addresses, shared_secret is already in x25519 format and can be used directly as derivation
memcpy(&derivation, &shared_secret[0], sizeof(crypto::key_derivation));
for (size_t i = 1; i < num_sigs; ++i)
memcpy(&additional_derivations[i - 1], &shared_secret[i], sizeof(crypto::key_derivation));
}
else
{
// For regular addresses, generate key derivation from shared secret
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[0], rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation");
for (size_t i = 1; i < num_sigs; ++i)
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[i], rct::rct2sk(rct::I), additional_derivations[i - 1]), error::wallet_internal_error, "Failed to generate key derivation");
}
uint64_t received;
check_tx_key_helper(tx, derivation, additional_derivations, address, received);
// SRCG: if this returns 0 received, but it's an AUDIT TX, then that is EXPECTED
@@ -13240,9 +13447,10 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt
// concatenate all signature strings
for (size_t i = 0; i < num_sigs; ++i)
sig_str +=
tools::base58::encode(std::string((const char *)&shared_secret[i], sizeof(crypto::public_key))) +
tools::base58::encode(std::string((const char *)&sig[i], sizeof(crypto::signature)));
{
sig_str += tools::base58::encode(std::string((const char *)&shared_secret[i], sizeof(crypto::public_key)));
sig_str += tools::base58::encode(std::string((const char *)&sig[i], sizeof(crypto::signature)));
}
return sig_str;
}
@@ -13303,13 +13511,26 @@ bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account
bool wallet2::check_tx_proof(const cryptonote::transaction &tx, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message, const std::string &sig_str, uint64_t &received) const
{
// InProofV1, InProofV2, OutProofV1, OutProofV2
// InProofV1, InProofV2, InProofV3, OutProofV1, OutProofV2, OutProofV3
const bool is_out = sig_str.substr(0, 3) == "Out";
const std::string header = is_out ? sig_str.substr(0,10) : sig_str.substr(0,9);
int version = 2; // InProofV2
int version = 3; // Default to V3
// Check for V1 or V2
if (is_out && sig_str.substr(8,2) == "V1") version = 1; // OutProofV1
else if (is_out) version = 2; // OutProofV2
else if (sig_str.substr(7,2) == "V1") version = 1; // InProofV1
else if (is_out && sig_str.substr(8,2) == "V2") version = 2; // OutProofV2
else if (!is_out && sig_str.substr(7,2) == "V1") version = 1; // InProofV1
else if (!is_out && sig_str.substr(7,2) == "V2") version = 2; // InProofV2
// Validate version matches address type
if (version == 3 && !address.m_is_carrot) {
MERROR("V3 proof provided but address is not a carrot address");
return false;
}
if (version != 3 && address.m_is_carrot) {
MERROR("Carrot address provided but proof is not V3");
return false;
}
const size_t header_len = header.size();
THROW_WALLET_EXCEPTION_IF(sig_str.size() < header_len || sig_str.substr(0, header_len) != header, error::wallet_internal_error,
@@ -13320,30 +13541,36 @@ bool wallet2::check_tx_proof(const cryptonote::transaction &tx, const cryptonote
std::vector<crypto::signature> sig(1);
const size_t pk_len = tools::base58::encode(std::string((const char *)&shared_secret[0], sizeof(crypto::public_key))).size();
const size_t sig_len = tools::base58::encode(std::string((const char *)&sig[0], sizeof(crypto::signature))).size();
const size_t num_sigs = (sig_str.size() - header_len) / (pk_len + sig_len);
THROW_WALLET_EXCEPTION_IF(sig_str.size() != header_len + num_sigs * (pk_len + sig_len), error::wallet_internal_error,
size_t per_sig_len = pk_len + sig_len;
const size_t num_sigs = (sig_str.size() - header_len) / per_sig_len;
THROW_WALLET_EXCEPTION_IF(sig_str.size() != header_len + num_sigs * per_sig_len, error::wallet_internal_error,
"Wrong signature size");
shared_secret.resize(num_sigs);
sig.resize(num_sigs);
for (size_t i = 0; i < num_sigs; ++i)
{
std::string pk_decoded;
std::string R_decoded;
std::string D_decoded;
std::string sig_decoded;
const size_t offset = header_len + i * (pk_len + sig_len);
size_t offset = header_len + i * per_sig_len;
THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(offset, pk_len), pk_decoded), error::wallet_internal_error,
"Signature decoding error");
THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(offset + pk_len, sig_len), sig_decoded), error::wallet_internal_error,
"Signature decoding error");
THROW_WALLET_EXCEPTION_IF(sizeof(crypto::public_key) != pk_decoded.size() || sizeof(crypto::signature) != sig_decoded.size(), error::wallet_internal_error,
THROW_WALLET_EXCEPTION_IF(sizeof(crypto::public_key) != pk_decoded.size(), error::wallet_internal_error,
"Signature decoding error");
memcpy(&shared_secret[i], pk_decoded.data(), sizeof(crypto::public_key));
offset += pk_len;
THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(offset, sig_len), sig_decoded), error::wallet_internal_error,
"Signature decoding error");
THROW_WALLET_EXCEPTION_IF(sizeof(crypto::signature) != sig_decoded.size(), error::wallet_internal_error,
"Signature decoding error");
memcpy(&sig[i], sig_decoded.data(), sizeof(crypto::signature));
}
crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx);
THROW_WALLET_EXCEPTION_IF(tx_pub_key == null_pkey, error::wallet_internal_error, "Tx pubkey was not found");
std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx);
THROW_WALLET_EXCEPTION_IF(tx_pub_key == null_pkey && additional_tx_pub_keys.size()<2, error::wallet_internal_error, "Tx pubkey was not found");
THROW_WALLET_EXCEPTION_IF(additional_tx_pub_keys.size() + 1 != num_sigs, error::wallet_internal_error, "Signature size mismatch with additional tx pubkeys");
const crypto::hash txid = cryptonote::get_transaction_hash(tx);
@@ -13356,42 +13583,99 @@ bool wallet2::check_tx_proof(const cryptonote::transaction &tx, const cryptonote
std::vector<int> good_signature(num_sigs, 0);
if (is_out)
{
good_signature[0] = is_subaddress ?
crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], sig[0], version) :
crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], sig[0], version);
for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i)
if (version == 3)
{
good_signature[i + 1] = is_subaddress ?
crypto::check_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, address.m_spend_public_key, shared_secret[i + 1], sig[i + 1], version) :
crypto::check_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, boost::none, shared_secret[i + 1], sig[i + 1], version);
if (tx_pub_key != crypto::null_pkey) {
good_signature[0] = is_subaddress ?
crypto::check_carrot_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], sig[0]) :
crypto::check_carrot_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], sig[0]);
}
for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i)
{
if (additional_tx_pub_keys[i] != crypto::null_pkey) {
good_signature[i + 1] = is_subaddress ?
crypto::check_carrot_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, address.m_spend_public_key, shared_secret[i + 1], sig[i + 1]) :
crypto::check_carrot_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, boost::none, shared_secret[i + 1], sig[i + 1]);
}
}
}
else
{
if (tx_pub_key != crypto::null_pkey) {
good_signature[0] = is_subaddress ?
crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], sig[0], version) :
crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], sig[0], version);
}
for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i)
{
if (additional_tx_pub_keys[i] != crypto::null_pkey) {
good_signature[i + 1] = is_subaddress ?
crypto::check_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, address.m_spend_public_key, shared_secret[i + 1], sig[i + 1], version) :
crypto::check_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, boost::none, shared_secret[i + 1], sig[i + 1], version);
}
}
}
}
else
{
good_signature[0] = is_subaddress ?
crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, address.m_spend_public_key, shared_secret[0], sig[0], version) :
crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, shared_secret[0], sig[0], version);
for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i)
if (version == 3)
{
good_signature[i + 1] = is_subaddress ?
crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i], address.m_spend_public_key, shared_secret[i + 1], sig[i + 1], version) :
crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i], boost::none, shared_secret[i + 1], sig[i + 1], version);
if (tx_pub_key != crypto::null_pkey) {
good_signature[0] = is_subaddress ?
crypto::check_carrot_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], sig[0]) :
crypto::check_carrot_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], sig[0]);
}
for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i)
{
if (additional_tx_pub_keys[i] != crypto::null_pkey) {
good_signature[i + 1] = is_subaddress ?
crypto::check_carrot_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, address.m_spend_public_key, shared_secret[i + 1], sig[i + 1]) :
crypto::check_carrot_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, boost::none, shared_secret[i + 1], sig[i + 1]);
}
}
}
else
{
if (tx_pub_key != crypto::null_pkey) {
good_signature[0] = is_subaddress ?
crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, address.m_spend_public_key, shared_secret[0], sig[0], version) :
crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, shared_secret[0], sig[0], version);
}
for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i)
{
if (additional_tx_pub_keys[i] != crypto::null_pkey) {
good_signature[i + 1] = is_subaddress ?
crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i], address.m_spend_public_key, shared_secret[i + 1], sig[i + 1], version) :
crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i], boost::none, shared_secret[i + 1], sig[i + 1], version);
}
}
}
}
if (std::any_of(good_signature.begin(), good_signature.end(), [](int i) { return i > 0; }))
{
// obtain key derivation by multiplying scalar 1 to the shared secret
// obtain key derivation by multiplying scalar 1 to the shared secret (or use directly for carrot)
crypto::key_derivation derivation;
if (good_signature[0])
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[0], rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation");
std::vector<crypto::key_derivation> additional_derivations(num_sigs - 1);
for (size_t i = 1; i < num_sigs; ++i)
if (good_signature[i])
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[i], rct::rct2sk(rct::I), additional_derivations[i - 1]), error::wallet_internal_error, "Failed to generate key derivation");
if (address.m_is_carrot)
{
// For carrot addresses, shared_secret is already in x25519 format and can be used directly as derivation
if (good_signature[0])
memcpy(&derivation, &shared_secret[0], sizeof(crypto::key_derivation));
for (size_t i = 1; i < num_sigs; ++i)
if (good_signature[i])
memcpy(&additional_derivations[i - 1], &shared_secret[i], sizeof(crypto::key_derivation));
}
else
{
// For regular addresses, generate key derivation from shared secret
if (good_signature[0])
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[0], rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation");
for (size_t i = 1; i < num_sigs; ++i)
if (good_signature[i])
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[i], rct::rct2sk(rct::I), additional_derivations[i - 1]), error::wallet_internal_error, "Failed to generate key derivation");
}
check_tx_key_helper(tx, derivation, additional_derivations, address, received);
return true;
+12 -1
View File
@@ -1477,7 +1477,7 @@ private:
BEGIN_SERIALIZE_OBJECT()
MAGIC_FIELD("monero wallet cache")
VERSION_FIELD(2)
VERSION_FIELD(3)
FIELD(m_blockchain)
FIELD(m_transfers)
FIELD(m_transfers_indices)
@@ -1521,6 +1521,14 @@ private:
}
FIELD(m_background_sync_data)
FIELD(m_subaddresses_extended)
if (version == 2)
{
serializable_unordered_map<crypto::public_key, carrot::return_output_info_retired_t> old_roi;
FIELD(old_roi)
m_return_output_info = {};
m_force_rescan = true;
return true;
}
FIELD(m_return_output_info)
END_SERIALIZE()
@@ -1555,6 +1563,8 @@ private:
void set_default_priority(uint32_t p) { m_default_priority = p; }
bool auto_refresh() const { return m_auto_refresh; }
void auto_refresh(bool r) { m_auto_refresh = r; }
bool force_rescan() const { return m_force_rescan; }
void force_rescan(bool r) { m_force_rescan = r; }
AskPasswordType ask_password() const { return m_ask_password; }
void ask_password(AskPasswordType ask) { m_ask_password = ask; }
void set_min_output_count(uint32_t count) { m_min_output_count = count; }
@@ -2200,6 +2210,7 @@ private:
uint32_t m_default_priority;
RefreshType m_refresh_type;
bool m_auto_refresh;
bool m_force_rescan = false;
bool m_first_refresh_done;
uint64_t m_refresh_from_block_height;
// If m_refresh_from_block_height is explicitly set to zero we need this to differentiate it from the case that
+1 -1
View File
@@ -152,7 +152,7 @@ bool wallet2::search_for_rpc_payment(uint64_t credits_target, uint32_t n_threads
{
tpool.submit(&waiter, [&, i] {
*(uint32_t*)(hashing_blob.data() + 39) = SWAP32LE(local_nonce-i);
const uint8_t major_version = hashing_blob[0];
// const uint8_t major_version = hashing_blob[0];
crypto::rx_slow_hash(seed_hash.data, hashing_blob.data(), hashing_blob.size(), hash[i].data);
});
}
+49
View File
@@ -2355,6 +2355,55 @@ namespace tools
epee::wipeable_string key = epee::to_hex::wipeable_string(m_wallet->get_account().get_keys().m_spend_secret_key);
res.key = std::string(key.data(), key.size());
}
else if(req.key_type.compare("s_master") == 0)
{
if (m_wallet->watch_only())
{
er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY;
er.message = "The wallet is watch-only. Cannot retrieve s_master key.";
return false;
}
CHECK_IF_BACKGROUND_SYNCING();
epee::wipeable_string key = epee::to_hex::wipeable_string(m_wallet->get_account().get_keys().s_master);
res.key = std::string(key.data(), key.size());
}
else if(req.key_type.compare("k_prove_spend") == 0)
{
if (m_wallet->watch_only())
{
er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY;
er.message = "The wallet is watch-only. Cannot retrieve k_prove_spend key.";
return false;
}
CHECK_IF_BACKGROUND_SYNCING();
epee::wipeable_string key = epee::to_hex::wipeable_string(m_wallet->get_account().get_keys().k_prove_spend);
res.key = std::string(key.data(), key.size());
}
else if(req.key_type.compare("s_view_balance") == 0)
{
epee::wipeable_string key = epee::to_hex::wipeable_string(m_wallet->get_account().get_keys().s_view_balance);
res.key = std::string(key.data(), key.size());
}
else if(req.key_type.compare("s_view_balance") == 0)
{
epee::wipeable_string key = epee::to_hex::wipeable_string(m_wallet->get_account().get_keys().s_view_balance);
res.key = std::string(key.data(), key.size());
}
else if(req.key_type.compare("k_view_incoming") == 0)
{
epee::wipeable_string key = epee::to_hex::wipeable_string(m_wallet->get_account().get_keys().k_view_incoming);
res.key = std::string(key.data(), key.size());
}
else if(req.key_type.compare("k_generate_image") == 0)
{
epee::wipeable_string key = epee::to_hex::wipeable_string(m_wallet->get_account().get_keys().k_generate_image);
res.key = std::string(key.data(), key.size());
}
else if(req.key_type.compare("s_generate_address") == 0)
{
epee::wipeable_string key = epee::to_hex::wipeable_string(m_wallet->get_account().get_keys().s_generate_address);
res.key = std::string(key.data(), key.size());
}
else
{
er.message = "key_type " + req.key_type + " not found";
+1
View File
@@ -44,6 +44,7 @@ set(unit_tests_sources
carrot_mock_helpers.cpp
carrot_sparc.cpp
carrot_transcript_fixed.cpp
carrot_tx_proof.cpp
chacha.cpp
checkpoints.cpp
command_line.cpp
+445
View File
@@ -0,0 +1,445 @@
// Copyright (c) 2025, Salvium (authors: SRCG, auruya)
//
// 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"
extern "C" {
#include "crypto/crypto-ops.h"
}
#include "crypto/hash.h"
#include <boost/algorithm/string.hpp>
#include "mx25519.h"
#include "carrot_core/account.h"
#include "carrot_impl/format_utils.h"
#include "string_tools.h"
using namespace carrot;
static inline void random_carrot_keys(crypto::secret_key& a,
crypto::public_key& A,
crypto::secret_key& b,
crypto::public_key& B,
const bool create_subaddress)
{
// Generate a new CN address
carrot::carrot_and_legacy_account alice;
alice.generate();
const auto& keys = alice.get_keys();
// Check for subaddress
if (create_subaddress) {
// Create subaddress
carrot::subaddress_index_extended sie{{0,1}, AddressDeriveType::Carrot, false};
carrot::CarrotDestinationV1 subaddr = alice.subaddress(sie);
a = keys.k_view_incoming;
A = subaddr.address_view_pubkey;
b = keys.k_prove_spend;
B = subaddr.address_spend_pubkey;
} else {
a = keys.k_view_incoming;
A = keys.m_carrot_account_address.m_view_public_key;
b = keys.k_prove_spend;
B = keys.m_carrot_account_address.m_spend_public_key;
}
}
TEST(carrot_tx_proofs, fuzz_stability)
{
static const size_t ITER = 5000; // increase to 50k if needed
for (size_t i = 0; i < ITER; ++i)
{
// 1. Generate random Carrot recipient view & spend keys A/B/a/b
bool use_subaddress = ((i & 1) == 1); // alternate: main/sub
crypto::secret_key a;
crypto::public_key A;
crypto::secret_key b;
crypto::public_key B;
random_carrot_keys(a, A, b, B, use_subaddress);
// 2. Generate random tx private key r
crypto::secret_key r;
crypto::random32_unbiased((unsigned char*)unwrap(r).data);
// 3. Recipient can be main address (no B) or subaddress
//const crypto::public_key *B_ptr = use_subaddress ? &B : nullptr;
// 4. Compute R = ConvertPointE(r * (G or B))
crypto::public_key R_pk;
mx25519_pubkey enote_ephemeral_pubkey_out;
carrot::make_carrot_enote_ephemeral_pubkey(r,
B,
use_subaddress,
enote_ephemeral_pubkey_out);
R_pk = carrot::raw_byte_convert<crypto::public_key>(enote_ephemeral_pubkey_out);
// 5. Compute D = ConvertPointE(r * A)
mx25519_pubkey s_sr;
bool success = carrot::make_carrot_uncontextualized_shared_key_sender(r, A, s_sr);
ASSERT_TRUE(success) << "failure to compute shared secret";
crypto::public_key D_pk = carrot::raw_byte_convert<crypto::public_key>(s_sr);
// 6. Random prefix hash
crypto::hash prefix_hash;
for (int j = 0; j < 32; j++) prefix_hash.data[j] = rand() & 0xFF;
// 7. Prove
crypto::signature sig;
crypto::generate_carrot_tx_proof(
prefix_hash,
R_pk, A,
use_subaddress ? boost::make_optional(B) : boost::none,
D_pk, r, a, sig
);
// 8. Verify
bool ok = crypto::check_carrot_tx_proof(
prefix_hash,
R_pk, A,
use_subaddress ? boost::make_optional(B) : boost::none,
D_pk, sig
);
ASSERT_TRUE(ok) << "failure at iteration " << i;
// ---------------------------------------------------------
// 9. NEGATIVE TESTS
// ---------------------------------------------------------
// 9a. Flip a bit in R
{
crypto::public_key R_bad = R_pk;
R_bad.data[5] ^= 0x20;
bool ok2 = crypto::check_carrot_tx_proof(
prefix_hash, R_bad, A,
use_subaddress ? boost::make_optional(B) : boost::none,
D_pk, sig
);
ASSERT_FALSE(ok2);
}
// 9b. Flip a bit in D
{
crypto::public_key D_bad = D_pk;
D_bad.data[7] ^= 0x10;
bool ok2 = crypto::check_carrot_tx_proof(
prefix_hash, R_pk, A,
use_subaddress ? boost::make_optional(B) : boost::none,
D_bad, sig
);
ASSERT_FALSE(ok2);
}
// 9c. Flip a bit in sig.c
{
crypto::signature sig_bad = sig;
sig_bad.c.data[3] ^= 0x80;
bool ok2 = crypto::check_carrot_tx_proof(
prefix_hash, R_pk, A,
use_subaddress ? boost::make_optional(B) : boost::none,
D_pk, sig_bad
);
ASSERT_FALSE(ok2);
}
// 9d. Flip a bit in sign_mask
{
crypto::signature sig_bad = sig;
sig_bad.sign_mask ^= 0x01; // flip R_sign
bool ok2 = crypto::check_carrot_tx_proof(
prefix_hash, R_pk, A,
use_subaddress ? boost::make_optional(B) : boost::none,
D_pk, sig_bad
);
ASSERT_FALSE(ok2);
}
// 9e. Flip a bit in sig.r
{
crypto::signature sig_bad = sig;
sig_bad.r.data[0] ^= 0x40;
bool ok2 = crypto::check_carrot_tx_proof(
prefix_hash, R_pk, A,
use_subaddress ? boost::make_optional(B) : boost::none,
D_pk, sig_bad
);
ASSERT_FALSE(ok2);
}
// 9f. Flip a bit in A
{
crypto::public_key A_bad = A;
A_bad.data[12] ^= 0x08;
bool ok2 = crypto::check_carrot_tx_proof(
prefix_hash, R_pk, A_bad,
use_subaddress ? boost::make_optional(B) : boost::none,
D_pk, sig
);
ASSERT_FALSE(ok2);
}
// 9g. Flip a bit in B (when subaddress)
if (use_subaddress)
{
crypto::public_key B_bad = B;
B_bad.data[9] ^= 0x40;
bool ok2 = crypto::check_carrot_tx_proof(
prefix_hash, R_pk, A,
boost::make_optional(B_bad),
D_pk, sig
);
ASSERT_FALSE(ok2);
}
}
}
TEST(carrot_tx_proofs, known_values_mutation_rejection_main_address)
{
// Real Salvium Carrot addresses for reproducible test (testnet) (main address)
// Sender: SC1ToqKSXRw3rE3rNQzjUA1nntvHhM6id3coWry25y4jUvHDRKDRGFv1vJRCTMHWUyVXct2aedmvzUfd3CofjTpKEhHmpnftqZk
// Recipient: SC1ToumwqT5GeDcn2JjrHoFPUMPMcNu73STkP3Sono94iZpizheMJ3ADpGGE92Wcb7b3gDCxKFT5NEp94ueQQMbu8VBYyAGHEy7
// Tx ID: e8729399d2af3dede8c110e370b3505c1669f4fba593fd740a16c1e4f425a728
// Tx priv key
crypto::secret_key r;
const char* r_hex = "748b8f3131661fd8ee0f06ab3de53649381522185ea6e8148c8daf395ded010d";
ASSERT_TRUE(epee::string_tools::hex_to_pod(r_hex, r));
// Recipient's actual Carrot keys from Salvium network
crypto::secret_key a, b; // view-incoming and prove-spend priv keys
crypto::public_key A, B; // view and spend pub keys
// recipient view-incoming key
const char* a_hex = "88b6442966238bfd349eb412fe55a717b7b363175b48ae88c34a252dd8868e05";
ASSERT_TRUE(epee::string_tools::hex_to_pod(a_hex, a));
// recipient prove-spend key
const char* b_hex = "94607b25bceb408bbb5393d25729777e92686b9447f9c989f2e735e878950d0b";
ASSERT_TRUE(epee::string_tools::hex_to_pod(b_hex, b));
// Generate pub keys from priv keys
ASSERT_TRUE(crypto::secret_key_to_public_key(a, A));
ASSERT_TRUE(crypto::secret_key_to_public_key(b, B));
// Compute R = rG (main address case)
mx25519_pubkey enote_ephemeral_pubkey_out;
carrot::make_carrot_enote_ephemeral_pubkey(r, B, false, enote_ephemeral_pubkey_out);
crypto::public_key R_G = carrot::raw_byte_convert<crypto::public_key>(enote_ephemeral_pubkey_out);
// Compute D = rA
mx25519_pubkey s_sr;
ASSERT_TRUE(carrot::make_carrot_uncontextualized_shared_key_sender(r, A, s_sr));
crypto::public_key D = carrot::raw_byte_convert<crypto::public_key>(s_sr);
// Fixed message hash
crypto::hash prefix_hash;
memset(&prefix_hash, 0, 32);
for (int i = 0; i < 32; i++) {
prefix_hash.data[i] = i; // Sequential bytes for reproducibility
}
// Generate proof with known values
crypto::signature sig;
crypto::generate_carrot_tx_proof(prefix_hash, R_G, A, boost::none, D, r, a, sig);
// Verify original proof works
ASSERT_TRUE(crypto::check_carrot_tx_proof(prefix_hash, R_G, A, boost::none, D, sig));
// Test mutations are rejected
// 1. Mutate R by flipping one bit
{
crypto::public_key R_mutated = R_G;
R_mutated.data[0] ^= 0x01;
ASSERT_FALSE(crypto::check_carrot_tx_proof(prefix_hash, R_mutated, A, boost::none, D, sig));
}
// 2. Mutate D by flipping one bit
{
crypto::public_key D_mutated = D;
D_mutated.data[0] ^= 0x01;
ASSERT_FALSE(crypto::check_carrot_tx_proof(prefix_hash, R_G, A, boost::none, D_mutated, sig));
}
// 3. Mutate A by flipping one bit
{
crypto::public_key A_mutated = A;
A_mutated.data[0] ^= 0x01;
ASSERT_FALSE(crypto::check_carrot_tx_proof(prefix_hash, R_G, A_mutated, boost::none, D, sig));
}
// 4. Mutate prefix_hash by flipping one bit
{
crypto::hash hash_mutated = prefix_hash;
hash_mutated.data[0] ^= 0x01;
ASSERT_FALSE(crypto::check_carrot_tx_proof(hash_mutated, R_G, A, boost::none, D, sig));
}
// 5. Mutate signature.c by flipping one bit
{
crypto::signature sig_mutated = sig;
sig_mutated.c.data[0] ^= 0x01;
ASSERT_FALSE(crypto::check_carrot_tx_proof(prefix_hash, R_G, A, boost::none, D, sig_mutated));
}
// 6. Mutate signature.r by flipping one bit
{
crypto::signature sig_mutated = sig;
sig_mutated.r.data[0] ^= 0x01;
ASSERT_FALSE(crypto::check_carrot_tx_proof(prefix_hash, R_G, A, boost::none, D, sig_mutated));
}
// 7. Mutate signature.sign_mask by flipping R_sign bit
{
crypto::signature sig_mutated = sig;
sig_mutated.sign_mask ^= 0x01;
ASSERT_FALSE(crypto::check_carrot_tx_proof(prefix_hash, R_G, A, boost::none, D, sig_mutated));
}
// 8. Mutate signature.sign_mask by flipping D_sign bit
{
crypto::signature sig_mutated = sig;
sig_mutated.sign_mask ^= 0x02;
ASSERT_FALSE(crypto::check_carrot_tx_proof(prefix_hash, R_G, A, boost::none, D, sig_mutated));
}
}
TEST(carrot_tx_proofs, known_values_mutation_rejection_subaddress)
{
// Real Salvium Carrot addresses for reproducible test (testnet) (subaddress)
// Sender: SC1ToqKSXRw3rE3rNQzjUA1nntvHhM6id3coWry25y4jUvHDRKDRGFv1vJRCTMHWUyVXct2aedmvzUfd3CofjTpKEhHmpnftqZk
// Recipient: SC1TsCevdYfZRZCRb83i5caRDJDb45UoqBeynNciVW8LAihKchQ4MfmW7PmPJquaXDZyntRcJCfduPVtdFUb5nsQLokFM434usw
// Tx ID: 01f5e1e56df714e3af919ab443b1acc4b1bebffed03198a9aaf3d22449809453
// Tx priv key
crypto::secret_key r, a;
const char* r_hex = "4eccc86c26ac250132d141d1b447e1fe25d0d1e4f3f2d7f3aca10a2633b52808";
ASSERT_TRUE(epee::string_tools::hex_to_pod(r_hex, r));
// view and spend pub keys
crypto::public_key A, B;
cryptonote::address_parse_info info;
std::string recipent_address_str = "SC1TsCevdYfZRZCRb83i5caRDJDb45UoqBeynNciVW8LAihKchQ4MfmW7PmPJquaXDZyntRcJCfduPVtdFUb5nsQLokFM434usw";
ASSERT_TRUE(cryptonote::get_account_address_from_str(info, cryptonote::network_type::TESTNET, recipent_address_str));
A = info.address.m_view_public_key;
B = info.address.m_spend_public_key;
a = crypto::null_skey;
// Compute R = rG (subaddress case)
mx25519_pubkey enote_ephemeral_pubkey_out;
carrot::make_carrot_enote_ephemeral_pubkey(r, B, true, enote_ephemeral_pubkey_out);
crypto::public_key R_G = carrot::raw_byte_convert<crypto::public_key>(enote_ephemeral_pubkey_out);
// Compute D = rA
mx25519_pubkey s_sr;
ASSERT_TRUE(carrot::make_carrot_uncontextualized_shared_key_sender(r, A, s_sr));
crypto::public_key D = carrot::raw_byte_convert<crypto::public_key>(s_sr);
// Fixed message hash
crypto::hash prefix_hash;
memset(&prefix_hash, 0, 32);
for (int i = 0; i < 32; i++) {
prefix_hash.data[i] = i; // Sequential bytes for reproducibility
}
// Generate proof with known values
crypto::signature sig;
crypto::generate_carrot_tx_proof(prefix_hash, R_G, A, B, D, r, a, sig);
// Verify original proof works
ASSERT_TRUE(crypto::check_carrot_tx_proof(prefix_hash, R_G, A, B, D, sig));
// Test mutations are rejected
// 1. Mutate R by flipping one bit
{
crypto::public_key R_mutated = R_G;
R_mutated.data[0] ^= 0x01;
ASSERT_FALSE(crypto::check_carrot_tx_proof(prefix_hash, R_mutated, A, B, D, sig));
}
// 2. Mutate D by flipping one bit
{
crypto::public_key D_mutated = D;
D_mutated.data[0] ^= 0x01;
ASSERT_FALSE(crypto::check_carrot_tx_proof(prefix_hash, R_G, A, B, D_mutated, sig));
}
// 3. Mutate A by flipping one bit
{
crypto::public_key A_mutated = A;
A_mutated.data[0] ^= 0x01;
ASSERT_FALSE(crypto::check_carrot_tx_proof(prefix_hash, R_G, A_mutated, B, D, sig));
}
// 4. Mutate prefix_hash by flipping one bit
{
crypto::hash hash_mutated = prefix_hash;
hash_mutated.data[0] ^= 0x01;
ASSERT_FALSE(crypto::check_carrot_tx_proof(hash_mutated, R_G, A, B, D, sig));
}
// 5. Mutate signature.c by flipping one bit
{
crypto::signature sig_mutated = sig;
sig_mutated.c.data[0] ^= 0x01;
ASSERT_FALSE(crypto::check_carrot_tx_proof(prefix_hash, R_G, A, boost::none, D, sig_mutated));
}
// 6. Mutate signature.r by flipping one bit
{
crypto::signature sig_mutated = sig;
sig_mutated.r.data[0] ^= 0x01;
ASSERT_FALSE(crypto::check_carrot_tx_proof(prefix_hash, R_G, A, B, D, sig_mutated));
}
// 7. Mutate signature.sign_mask by flipping R_sign bit
{
crypto::signature sig_mutated = sig;
sig_mutated.sign_mask ^= 0x01;
ASSERT_FALSE(crypto::check_carrot_tx_proof(prefix_hash, R_G, A, B, D, sig_mutated));
}
// 8. Mutate signature.sign_mask by flipping D_sign bit
{
crypto::signature sig_mutated = sig;
sig_mutated.sign_mask ^= 0x02;
ASSERT_FALSE(crypto::check_carrot_tx_proof(prefix_hash, R_G, A, B, D, sig_mutated));
}
}
+1 -1
View File
@@ -75,7 +75,7 @@ TEST(Crypto, Ostream)
EXPECT_TRUE(is_formatted<crypto::hash8>());
EXPECT_TRUE(is_formatted<crypto::hash>());
EXPECT_TRUE(is_formatted<crypto::public_key>());
EXPECT_TRUE(is_formatted<crypto::signature>());
//EXPECT_TRUE(is_formatted<crypto::signature>());
EXPECT_TRUE(is_formatted<crypto::key_derivation>());
EXPECT_TRUE(is_formatted<crypto::key_image>());
EXPECT_TRUE(is_formatted<rct::key>());