diff --git a/CMakeLists.txt b/CMakeLists.txt index d7069803..46f8e727 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,7 +86,7 @@ file(GLOB SRC_COMMON src/common/*.cpp src/common/*.hpp) file(GLOB SRC_SERIA src/seria/*.cpp src/seria/*.hpp) file(GLOB SRC_LOGGING src/logging/*.cpp src/logging/*.hpp) file(GLOB SRC_P2P src/p2p/*.cpp src/p2p/*.hpp) -file(GLOB SRC_CORE src/Core/*.cpp src/Core/*.hpp src/CryptoNote.hpp src/CryptoNote.cpp src/rpc_api.hpp src/rpc_api.cpp) +file(GLOB SRC_CORE src/Core/*.cpp src/Core/*.hpp src/Core/hw/*.cpp src/Core/hw/*.hpp src/CryptoNote.hpp src/CryptoNote.cpp src/rpc_api.hpp src/rpc_api.cpp) file(GLOB SRC_HTTP src/http/*.cpp src/http/*.hpp) file(GLOB SRC_PLATFORM src/platform/ExclusiveLock.cpp src/platform/ExclusiveLock.hpp @@ -147,7 +147,7 @@ add_executable(tests src/main_tests.cpp tests/io.hpp tests/Random.hpp tests/hash/test_hash.cpp tests/hash/test_hash.hpp tests/json/test_json.cpp tests/json/test_json.hpp tests/wallet_state/test_wallet_state.cpp tests/wallet_state/test_wallet_state.hpp - tests/wallet_file/test_wallet_file.cpp tests/wallet_file/test_wallet_file.hpp) + tests/wallet_file/test_wallet_file.cpp tests/wallet_file/test_wallet_file.hpp tests/crypto/benchmarks.cpp tests/crypto/benchmarks.hpp) set(Boost_USE_STATIC_LIBS ON) add_definitions(-DBOOST_BIND_NO_PLACEHOLDERS=1 -DBOOST_CONFIG_SUPPRESS_OUTDATED_MESSAGE=1) # boost::_1 conflicts with std::_1 add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY=1 -DBOOST_SYSTEM_NO_DEPRECATED=1) # required for header-only compilation @@ -179,3 +179,4 @@ if(NOT WIN32) target_link_libraries(${CRYPTONOTE_NAME}d ${LINK_OPENSSL} dl pthread) target_link_libraries(tests ${LINK_OPENSSL} dl pthread) endif() + diff --git a/README.md b/README.md index cd0d69bc..6aea9e70 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Contents ## Building on Linux 64-bit -All commands below are adapted for Ubuntu, other distributions may need an other command set. +All commands below work on Ubuntu 18.*, other distributions may need different command set. ### Building with standard options @@ -29,24 +29,27 @@ To go futher you have to have a number of packages and utilities. You need at le $bcndev> sudo apt-get install build-essential ``` -* CMake (3.5 or newer): +* CMake (3.0 or newer): ``` $bcndev> sudo apt-get install cmake $bcndev> cmake --version ``` If version is too old, follow instructions on [the official site](https://cmake.org/download/). -* Boost (1.62 or newer): - You need boost in `bcndev` folder. We do not configure to use boost installed by `apt-get`, because it is sometimes updated without your control by installing some unrelated packages. Also some users reported crashes after `find_package` finds headers from one version of boost and libraries from different version, or if installed boost uses dynamic linking. +* Boost (1.65 or newer): + We use boost as a header-only library via find_boost package. So, if your system has boost installed and set up, it will be used automatically. + + Note - there is a bug in `boost::asio` 1.66 that affects `bytecoind`. Please use either version 1.65 or 1.67+. ``` - $bcndev> wget -c 'http://sourceforge.net/projects/boost/files/boost/1.67.0/boost_1_67_0.tar.bz2/download' - $bcndev> tar xf download - $bcndev> rm download - $bcndev> mv boost_1_67_0 boost - $bcndev> cd boost - $bcndev/boost> ./bootstrap.sh - $bcndev/boost> ./b2 link=static -j 8 --build-dir=build64 --stagedir=stage - cd .. + $bcndev> sudo apt-get install libboost-dev + ``` + If the latest boost installed is too old (e.g. for Ubuntu 16.*), then you need to download and unpack boost into the `bcndev/boost` folder. + + ``` + $bcndev> wget -c 'https://dl.bintray.com/boostorg/release/1.69.0/source/boost_1_69_0.tar.gz' + $bcndev> tar -xzf ./boost_1_69_0.tar.gz + $bcndev> rm ./boost_1_69_0.tar.gz + $bcndev> mv ./boost_1_69_0/ ./boost/ ``` * OpenSSL (1.1.1 or newer): @@ -55,26 +58,38 @@ To go futher you have to have a number of packages and utilities. You need at le $bcndev> git clone https://github.com/openssl/openssl.git $bcndev> cd openssl $bcndev/openssl> ./Configure linux-x86_64 no-shared - $bcndev/openssl> time make -j4 + $bcndev/openssl> make -j4 $bcndev/openssl> cd .. ``` +* SQLite (3.1 or newer) + Download amalgamated [SQLite 3](https://www.sqlite.org/download.html) and unpack it into `bcndev/sqlite` folder (source files are referenced via relative paths, so you do not need to separately build it). + Please, note the direct download link is periodically updated with old versions removed, so you might need to tweak instructions below + ``` + $bcndev> wget -c https://www.sqlite.org/2018/sqlite-amalgamation-3260000.zip + $bcndev> unzip ./sqlite-amalgamation-3260000.zip + $bcndev> rm ./sqlite-amalgamation-3260000.zip + $bcndev> mv ./sqlite-amalgamation-3260000/ ./sqlite/ + ``` + +* LMDB + Source files are referenced via relative paths, so you do not need to separately build it: + Please note, we use LMDB only when building 64-bit daemons. For 32-bit daemons SQLite is used instead. + ``` + $bcndev> git clone https://github.com/bcndev/lmdb.git + ``` + Git-clone (or git-pull) Bytecoin source code in that folder: ``` $bcndev> git clone https://github.com/bcndev/bytecoin.git ``` -Put LMDB source code in `bcndev` folder (source files are referenced via relative paths, so you do not need to separately build it): -``` -$bcndev> git clone https://github.com/LMDB/lmdb.git -``` - Create build directory inside bytecoin, go there and run CMake and Make: ``` -$bcndev> mkdir bytecoin/build +$bcndev> mkdir -p bytecoin/build $bcndev> cd bytecoin/build $bcndev/bytecoin/build> cmake .. -$bcndev/bytecoin/build> time make -j4 +$bcndev/bytecoin/build> make -j4 ``` Check built binaries by running them from `../bin` folder @@ -82,19 +97,6 @@ Check built binaries by running them from `../bin` folder $bcndev/bytecoin/build> ../bin/bytecoind -v ``` -### Building with specific options - -Download amalgamated [SQLite 3](https://www.sqlite.org/download.html) and unpack it into `bcndev/sqlite` folder (source files are referenced via relative paths, so you do not need to separately build it). - -Below are the commands which remove OpenSSL support and switch from LMDB to SQLite by providing options to CMake: - -``` -$bcndev> mkdir bytecoin/build -$bcndev> cd bytecoin/build -$bcndev/bytecoin/build> cmake -DUSE_SSL=0 -DUSE_SQLITE=1 .. -$bcndev/bytecoin/build> time make -j4 -``` - ## Building on Mac OSX ### Building with standard options (10.11 El Capitan or newer) @@ -119,37 +121,7 @@ $bcndev> git clone https://github.com/bcndev/bytecoin.git Put LMDB source code in `bcndev` folder (source files are referenced via relative paths, so you do not need to separately build it): ``` -$bcndev> git clone https://github.com/LMDB/lmdb.git -``` - -Create build directory inside bytecoin, go there and run CMake and Make: -``` -$bcndev> mkdir bytecoin/build -$bcndev> cd bytecoin/build -$bcndev/bytecoin/build> cmake -DUSE_SSL=0 .. -$bcndev/bytecoin/build> time make -j4 -``` - -Check built binaries by running them from `../bin` folder: -``` -$bcndev/bytecoin/build> ../bin/bytecoind -v -``` - -### Building with specific options - -Binaries linked with Boost installed by Homebrew will work only on your computer's OS X version or newer, but not on older versions like El Capitan. - -If you need binaries to run on all versions of OS X starting from El Capitan, you need to build boost yourself targeting El Capitan SDK. - -Download [Mac OSX 10.11 SDK](https://github.com/phracker/MacOSX-SDKs/releases) and unpack to it into `Downloads` folder - -Download and unpack [Boost](https://boost.org) to `Downloads` folder. - -Then build and install Boost: -``` -$~> cd ~/Downloads/boost_1_67_0/ -$~/Downloads/boost_1_67_0> ./bootstrap.sh -$~/Downloads/boost_1_67_0> ./b2 -a -j 4 cxxflags="-stdlib=libc++ -std=c++14 -mmacosx-version-min=10.11 -isysroot/Users/user/Downloads/MacOSX10.11.sdk" install` +$~/Downloads/bcndev> git clone https://github.com/bcndev/lmdb.git ``` Install OpenSSL to `bcndev/openssl` folder: @@ -160,27 +132,38 @@ $~/Downloads/bcndev> cd openssl If you need binaries to run on all versions of OS X starting from El Capitan, you need to build OpenSSL targeting El Capitan SDK. ``` -$bcndev/openssl> ./Configure darwin64-x86_64-cc no-shared -mmacosx-version-min=10.11 -isysroot/Users/user/Downloads/MacOSX10.11.sdk +$~/Downloads/bcndev/openssl> ./Configure darwin64-x86_64-cc no-shared -mmacosx-version-min=10.11 -isysroot/Users/user/Downloads/MacOSX10.11.sdk ``` Otherwise just use ``` -$bcndev/openssl> ./Configure darwin64-x86_64-cc no-shared +$~/Downloads/bcndev/openssl> ./Configure darwin64-x86_64-cc no-shared ``` ``` -$bcndev/openssl> time make -j4 -$bcndev/openssl> cd .. +$~/Downloads/bcndev/openssl> make -j4 +$~/Downloads/bcndev/openssl> cd .. ``` Download amalgamated [SQLite 3](https://www.sqlite.org/download.html) and unpack it into `bcndev/sqlite` folder (source files are referenced via relative paths, so you do not need to separately build it). +Please, note the direct download link is periodically updated with old versions removed, so you might need to tweak instructions below +``` +$~/Downloads/bcndev> wget -c https://www.sqlite.org/2018/sqlite-amalgamation-3260000.zip +$~/Downloads/bcndev> unzip sqlite-amalgamation-3260000.zip +$~/Downloads/bcndev> rm sqlite-amalgamation-3260000.zip +$~/Downloads/bcndev> mv sqlite-amalgamation-3260000 sqlite +``` -You add OpenSSL support or switch from LMDB to SQLite by providing options to CMake: +Create build directory inside bytecoin, go there and run CMake and Make: +``` +$~/Downloads/bcndev> mkdir bytecoin/build +$~/Downloads/bcndev> cd bytecoin/build +$~/Downloads/bcndev/bytecoin/build> cmake .. +$~/Downloads/bcndev/bytecoin/build> make -j4 +``` +Check built binaries by running them from `../bin` folder: ``` -$bcndev> mkdir bytecoin/build -$bcndev> cd bytecoin/build -$bcndev/bytecoin/build> cmake -DUSE_SSL=1 -DUSE_SQLITE=1 .. -$bcndev/bytecoin/build> time make -j4 +$bcndev/bytecoin/build> ../bin/bytecoind -v ``` ## Building on Windows @@ -194,16 +177,8 @@ $C:\> mkdir bcndev $C:\> cd bcndev ``` -Get [Boost](https://boost.org) and unpack it into a folder inside `bcndev` and rename it from `boost_1_66_0` or similar to just `boost`. - -Build boost (build 32-bit boost version only if you need 32-bit bytecoin binaries). -``` -$> cd boost -$C:\bcndev\boost> bootstrap.bat -$C:\bcndev\boost> b2.exe address-model=64 link=static -j 8 --build-dir=build64 --stagedir=stage -$C:\bcndev\boost> b2.exe address-model=32 link=static -j 8 --build-dir=build32 --stagedir=stage32 -cd .. -``` +Boost (1.65 or newer): + We use boost as a header-only library via find_boost package. So, if your system has boost installed and set up, it will be used automatically. If not, you need to download and unpack boost into bcndev/boost folder. Git-clone (or git-pull) Bytecoin source code in that folder: ``` @@ -212,9 +187,11 @@ $C:\bcndev> git clone https://github.com/bcndev/bytecoin.git Put LMDB in the same folder (source files are referenced via relative paths, so you do not need to separately build it): ``` -$C:\bcndev> git clone https://github.com/LMDB/lmdb.git +$C:\bcndev> git clone https://github.com/bcndev/lmdb.git ``` +Download amalgamated [SQLite 3](https://www.sqlite.org/download.html) and unpack it into the same folder (source files are referenced via relative paths, so you do not need to separately build it). + You need to build openssl, first install ActivePerl (select "add to PATH" option, then restart console): ``` $C:\bcndev> git clone https://github.com/openssl/openssl.git @@ -236,21 +213,27 @@ Now launch Visual Studio, in File menu select `Open Folder`, select `C:\bcndev\b Wait until CMake finishes running and `Build` appears in main menu. Select `x64-Debug` or `x64-Release` from standard toolbar, and then `Build/Build Solution` from the main menu. -You cannot add options to CMake running inside Visual Studio so just edit `CMakeLists.txt` and set `USE_SSL` or `USE_SQLITE` to `ON` if you wish to build with them. +## Building with options + +You can build daemons that use SQLite istead of LMDB on any platform by providing options to CMake. +You may need to clean 'build' folder, if you built with default options before, due to cmake aggressive caching. + +``` +$bytecoin/build> cmake -DUSE_SQLITE=1 .. +$bytecoin/build> time make -j8 +``` ## Building on 32-bit x86 platforms, iOS, Android and other ARM platforms Bytecoin works on 32-bit systems if SQLite is used instead of LMDB (we've experienced lots of problems building and running with lmdb in 32-bit compatibility mode, especially on iOS). -Therefore SQLite option is automatically selected by CMake on 32-bit platforms and you must have SQLite downloaded as explained in appropriate sections above. - We build official x86 32-bit version for Windows only, because there is zero demand for 32-bit version for Linux or Mac. Building source code for iOS, Android, Raspberry PI, etc is possible (we have experimental `bytecoind` and `walletd` running on ARM64 iPhone) but requires major skills on your part. __TBD__ ## Building on Big-Endian platforms -Currently it is impossible to run Bytecoin on any Big-Endian platform, due to lots of endianess-dependent code. This may be fixed in the future. If you wish to run on Big-Endian platform, please contact us. +Currently bytecoin does not work out of the box on any Big-Endian platform, due to some endianess-dependent code. This may be fixed in the future. If you wish to run on Big-Endian platform, please contact us. ## Building with parameters diff --git a/ReleaseNotes.md b/ReleaseNotes.md index e60a3b88..eae714b5 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,5 +1,37 @@ ## Release Notes +### v3.4.0 (Amethyst) + +- Sendproofs are now in base58 format, which eases copying and sharing. +- New addresses now start from `bcnZ` prefix. + +*Command line changes/additions* + +- New walletd command-line parameter `--wallet-type` to create legacy wallets (`--create-wallet` by default creates new HD wallet). + +*API removal* + +- Removed `amethyst_only` flag in the `get_random_outputs` bytecoind method. + + +### v3.4.0-beta-20190123 + +*Strong support for audit-compatible wallets* + +- All new unlinkable addresses are now auditable, so separate auditable address type removed from system. +- View-only HD wallet is now guaranteed to have the same balance as original wallet. So owner of HD wallet cannot spend any funds in a way that view-only version of the same wallet does not see the fact. +- If view-only HD wallet was exported with --view-outgoing-addresses, it can also see all destination addresses in transactions that spend funds. If spender is sending to some address, he cannot make auditor see different destination address for this transaction. If spender is using sophisticated "out-of-blockchain shared secret" fraud, auditor will see random address, and spender will not be able to provide valid sendproof for this transaction. + +*Consensus update (hard fork)* +- New crypto for legacy addresses (unlinkable-inspired), which prevents "burning bug" attacks on crypto level. This is important because such attacks cannot be reliably fixed on operational level. + +*API additions* +- `amethyst_only` flag in `get_random_outputs` bytecoind method. + +*Incompatible API changes (likely to affect only developers of block explorers)* + +- In all raw block objects `output_indexes` renamed to `stack_indexes`. + ### v3.4.0-beta-20181218 - Signatures are now fully prunable (but not yet pruned) via modification to transaction hash calculations. @@ -19,7 +51,7 @@ - New auditable addresses, guaranteed to always have balance exactly equal to what view-only version of the same wallet shows (useful for public deposits). - Signatures are now half size. - The requirement that coinbase transactions are locked for 10 blocks is removed from consensus. -- Creating dust (or other not round output amounts) are now prohibited by consensus rules. +- Creating 6-digit dust (or other not round output amounts) are now prohibited by consensus rules. - Minimum anonymity is now 3 (4 output references per input). This does not apply to dust or not round outputs. - `bytecoind` now calculates output obfuscation value, and does not use less-than-ideal outputs for mix-in diff --git a/src/Core/BlockChain.cpp b/src/Core/BlockChain.cpp index 6c4e85e3..695ae6dd 100644 --- a/src/Core/BlockChain.cpp +++ b/src/Core/BlockChain.cpp @@ -19,7 +19,7 @@ using namespace cn; using namespace platform; -const std::string BlockChain::version_current = "6"; +const std::string BlockChain::version_current = "8"; // We increment when making incompatible changes to indices. // We use suffixes so all keys related to the same block are close to each other in DB diff --git a/src/Core/BlockChainFileFormat.cpp b/src/Core/BlockChainFileFormat.cpp index 95749969..ee2ea0fc 100644 --- a/src/Core/BlockChainFileFormat.cpp +++ b/src/Core/BlockChainFileFormat.cpp @@ -244,12 +244,14 @@ void LegacyBlockChainWriter::write_block(const RawBlock &raw_block) { m_indexes_file.write(&si, sizeof si); } -bool LegacyBlockChainWriter::export_blockchain2( - const std::string &index_file_name, const std::string &item_file_name, const BlockChainState &block_chain) { +bool LegacyBlockChainWriter::export_blockchain2(const std::string &index_file_name, const std::string &item_file_name, + const BlockChainState &block_chain, Height max_height) { auto idea_start = std::chrono::high_resolution_clock::now(); std::cout << "Start exporting blocks" << std::endl; LegacyBlockChainWriter writer(index_file_name, item_file_name, block_chain.get_tip_height() + 1); for (Height ha = 0; ha != block_chain.get_tip_height() + 1; ++ha) { + if (ha >= max_height) + break; Hash bid{}; BinaryArray block_data; RawBlock raw_block; diff --git a/src/Core/BlockChainFileFormat.hpp b/src/Core/BlockChainFileFormat.hpp index 86cdb6cb..999885a1 100644 --- a/src/Core/BlockChainFileFormat.hpp +++ b/src/Core/BlockChainFileFormat.hpp @@ -58,8 +58,8 @@ class LegacyBlockChainWriter { LegacyBlockChainWriter(const std::string &index_file_name, const std::string &item_file_name, uint64_t count); void write_block(const RawBlock &raw_block); - static bool export_blockchain2( - const std::string &index_file_name, const std::string &item_file_name, const BlockChainState &block_chain); + static bool export_blockchain2(const std::string &index_file_name, const std::string &item_file_name, + const BlockChainState &block_chain, Height max_height = std::numeric_limits::max()); }; } // namespace cn diff --git a/src/Core/BlockChainState.cpp b/src/Core/BlockChainState.cpp index c61e4376..3bf81f30 100644 --- a/src/Core/BlockChainState.cpp +++ b/src/Core/BlockChainState.cpp @@ -18,7 +18,8 @@ #include "seria/BinaryOutputStream.hpp" static const std::string KEYIMAGE_PREFIX = "i"; -static const std::string AMOUNT_OUTPUT_PREFIX = "a"; +static const std::string AMOUNT_OUTPUT_PREFIX = "a"; // amount:si -> g_index +static const std::string OUTPUT_PREFIX = "o"; // global_index -> OutputIndexData static const std::string BLOCK_GLOBAL_INDICES_PREFIX = "b"; static const std::string BLOCK_GLOBAL_INDICES_SUFFIX = "g"; @@ -32,12 +33,13 @@ using namespace platform; typedef std::pair, std::vector> InputDesc; namespace seria { -void ser_members(IBlockChainState::UnlockTimePublickKeyHeightSpent &v, ISeria &s) { +void ser_members(IBlockChainState::OutputIndexData &v, ISeria &s) { + seria_kv("amount", v.amount, s); seria_kv("unlock_block_or_timestamp", v.unlock_block_or_timestamp, s); seria_kv("public_key", v.public_key, s); seria_kv("height", v.height, s); - seria_kv("auditable", v.auditable, s); seria_kv("spent", v.spent, s); + seria_kv("is_amethyst", v.is_amethyst, s); seria_kv("dins", v.dins, s); } } // namespace seria @@ -68,56 +70,55 @@ bool BlockChainState::DeltaState::read_keyimage(const KeyImage &key_image, Heigh } size_t BlockChainState::DeltaState::push_amount_output( - Amount amount, BlockOrTimestamp unlock_time, Height block_height, const PublicKey &pk, bool is_auditable) { - auto pg = m_parent_state->next_global_index_for_amount(amount); + Amount amount, BlockOrTimestamp unlock_time, Height block_height, const PublicKey &pk, bool is_amethyst) { + m_ordered_global_amounts.push_back(OutputIndexData{amount, unlock_time, pk, 0, false, is_amethyst, {}}); + auto pg = m_parent_state->next_stack_index_for_amount(amount); auto &ga = m_global_amounts[amount]; - ga.push_back(std::make_tuple(unlock_time, pk, is_auditable)); + ga.push_back(std::make_tuple(unlock_time, pk, is_amethyst)); return pg + ga.size() - 1; } -void BlockChainState::DeltaState::pop_amount_output( - Amount amount, BlockOrTimestamp unlock_time, const PublicKey &pk, bool is_auditable) { - std::vector> &el = m_global_amounts[amount]; +void BlockChainState::DeltaState::pop_amount_output(Amount amount, BlockOrTimestamp unlock_time, const PublicKey &pk) { + std::vector> &el = m_global_amounts[amount]; invariant(!el.empty(), "DeltaState::pop_amount_output underflow"); - invariant( - std::get<0>(el.back()) == unlock_time && std::get<1>(el.back()) == pk && std::get<2>(el.back()) == is_auditable, + invariant(!m_ordered_global_amounts.empty(), "DeltaState::pop_amount_output hidden underflow"); + invariant(std::get<0>(el.back()) == unlock_time && std::get<1>(el.back()) == pk, "DeltaState::pop_amount_output wrong element"); el.pop_back(); + m_ordered_global_amounts.pop_back(); } -size_t BlockChainState::DeltaState::next_global_index_for_amount(Amount amount) const { - auto pg = m_parent_state->next_global_index_for_amount(amount); +size_t BlockChainState::DeltaState::next_stack_index_for_amount(Amount amount) const { + auto pg = m_parent_state->next_stack_index_for_amount(amount); auto git = m_global_amounts.find(amount); return (git == m_global_amounts.end()) ? pg : git->second.size() + pg; } -bool BlockChainState::DeltaState::read_amount_output( - Amount amount, size_t global_index, UnlockTimePublickKeyHeightSpent *unp) const { - // uint32_t pg = m_parent_state->next_global_index_for_amount(amount); - // if (global_index < pg) - return m_parent_state->read_amount_output(amount, global_index, unp); - // global_index -= pg; +bool BlockChainState::DeltaState::read_amount_output(Amount amount, size_t stack_index, OutputIndexData *unp) const { + // uint32_t pg = m_parent_state->next_stack_index_for_amount(amount); + // if (stack_index < pg) + return m_parent_state->read_amount_output(amount, stack_index, unp); + // stack_index -= pg; // auto git = m_global_amounts.find(amount); - // if (git == m_global_amounts.end() || global_index >= git->second.size()) + // if (git == m_global_amounts.end() || stack_index >= git->second.size()) // return false; - // unp->unlock_block_or_timestamp = std::get<0>(git->second[global_index]); - // unp->public_key = std::get<1>(git->second[global_index]); - // *is_white = std::get<2>(git->second[global_index]); + // unp->unlock_block_or_timestamp = std::get<0>(git->second[stack_index]); + // unp->public_key = std::get<1>(git->second[stack_index]); + // *is_white = std::get<2>(git->second[stack_index]); // unp->height = m_block_height; // unp->spent = false; // Spending just created outputs inside mempool or block is prohibited, simplifying logic // return true; } -// void BlockChainState::DeltaState::spend_output(Amount amount, size_t global_index) { -// m_spent_outputs.push_back(std::make_pair(amount, global_index)); +// void BlockChainState::DeltaState::spend_output(Amount amount, size_t stack_index) { +// m_spent_outputs.push_back(std::make_pair(amount, stack_index)); //} void BlockChainState::DeltaState::apply(IBlockChainState *parent_state) const { for (auto &&ki : m_keyimages) parent_state->store_keyimage(ki.first, ki.second); - for (auto && : m_global_amounts) - for (auto &&el : amp.second) - parent_state->push_amount_output( - amp.first, std::get<0>(el), m_block_height, std::get<1>(el), std::get<2>(el)); + for (auto && : m_ordered_global_amounts) + parent_state->push_amount_output( + amp.amount, amp.unlock_block_or_timestamp, m_block_height, amp.public_key, amp.is_amethyst); // for (auto &&mo : m_spent_outputs) // parent_state->spend_output(mo.first, mo.second); } @@ -142,19 +143,21 @@ api::BlockHeader BlockChainState::fill_genesis(Hash genesis_bid, const BlockTemp // returns reward for coinbase transaction or fee for non-coinbase one static Amount validate_semantic(const Currency ¤cy, uint8_t block_major_version, bool generating, - const Transaction &tx, bool check_output_key) { + const Transaction &tx, bool check_keys, bool key_image_subgroup_check) { if (tx.inputs.empty()) throw ConsensusError("Empty inputs"); // TODO - uncomment during next hard fork, finally prohibiting old signatures, outputs without secrets // We cannot do it at once, because mem pool will have v1 transactions during switch - // if(block_major_version >= currency.amethyst_block_version && tx.version < - // currency.amethyst_transaction_version && !generating) // for compatibility, we create v1 coinbase - // transaction if mining on legacy address - // return "WRONG_TRANSACTION_VERSION"; - if (block_major_version < currency.amethyst_block_version && tx.version >= currency.amethyst_transaction_version) + // for compatibility, we create v1 coinbase transaction if mining on legacy address + const bool is_tx_amethyst = tx.version >= currency.amethyst_transaction_version; + if (block_major_version < currency.amethyst_block_version && is_tx_amethyst) throw ConsensusError(common::to_string( "Wrong transaction version", int(tx.version), "in block version", int(block_major_version))); + // Subgroup check policy is as following: + // 1. output keys are checked here, encrypted output secrets not checked at all + // 2. keyimage and blinding coin commitment are checked here + // 3. hps will be checked as a part of signature Amount summary_output_amount = 0; for (const auto &output : tx.outputs) { Amount amount = 0; @@ -163,15 +166,13 @@ static Amount validate_semantic(const Currency ¤cy, uint8_t block_major_ve if (output.type() == typeid(OutputKey)) { const auto &key_output = boost::get(output); amount = key_output.amount; - if (check_output_key && !key_isvalid(key_output.public_key)) + if (check_keys && key_image_subgroup_check && !key_in_main_subgroup(key_output.public_key)) { throw ConsensusError(common::to_string("Output key not valid elliptic point", key_output.public_key)); - if (check_output_key && tx.version >= currency.amethyst_transaction_version && - !key_isvalid(key_output.encrypted_secret)) + } + if (check_keys && is_tx_amethyst && !key_isvalid(key_output.encrypted_secret)) throw ConsensusError( common::to_string("Output encrypted secret not valid elliptic point", key_output.encrypted_secret)); - if (tx.version < currency.amethyst_transaction_version && key_output.is_auditable) - throw ConsensusError( - common::to_string("Transaction version", tx.version, "insufficient for output with audit")); + // encrypted_secret can be outside main subgroup } else throw ConsensusError("Output type unknown"); if (amount == 0) @@ -195,23 +196,19 @@ static Amount validate_semantic(const Currency ¤cy, uint8_t block_major_ve amount = in.amount; if (!ki.insert(in.key_image).second) throw ConsensusError(common::to_string("Keyimage used twice in same transaction", in.key_image)); - std::vector global_indexes; - if (!relative_output_offsets_to_absolute(&global_indexes, in.output_indexes)) + std::vector absolute_indexes; + if (!relative_output_offsets_to_absolute(&absolute_indexes, in.output_indexes)) throw ConsensusError("Output indexes invalid in input"); + if (check_keys && key_image_subgroup_check && !key_in_main_subgroup(in.key_image)) + throw ConsensusError("Input key image not valid elliptic point"); } else throw ConsensusError("Input type unknown"); - // if (std::numeric_limits::max() - amount < summary_input_amount) - // throw ConsensusError("Inputs amounts overflow"); if (!add_amount(summary_input_amount, amount)) throw ConsensusError("Outputs amounts overflow"); } if (summary_output_amount > summary_input_amount && !generating) throw ConsensusError("Sum of outputs > sum of inputs in non-coinbase transaction"); - // Types/count of signatures will be checked as a part of signatures check - // if (tx.signatures.size() != tx.inputs.size() && !generating) - // return "INPUT_UNKNOWN_TYPE"; - // if (!tx.signatures.empty() && generating) - // return "INPUT_UNKNOWN_TYPE"; + // Types/count of signatures is derived from tx body during transaction serialization if (generating) return summary_output_amount; return summary_input_amount - summary_output_amount; @@ -223,7 +220,8 @@ BlockChainState::BlockChainState(logging::ILogger &log, const Config &config, co , m_log_redo_block_timestamp(std::chrono::steady_clock::now()) { std::string version; m_db.get("$version", version); - if (version == "B" || version == "1" || version == "2" || version == "3" || version == "4" || version == "5") { + if (version == "B" || version == "1" || version == "2" || version == "3" || version == "4" || version == "5" || + version == "6" || version == "7") { start_internal_import(); version = version_current; m_db.put("$version", version, false); @@ -251,6 +249,9 @@ BlockChainState::BlockChainState(logging::ILogger &log, const Config &config, co DB::Cursor cur2 = m_db.rbegin(DIN_PREFIX); m_next_nz_input_index = cur2.end() ? 0 : common::integer_cast(common::read_varint_sqlite4(cur2.get_suffix())) + 1; + DB::Cursor cur3 = m_db.rbegin(OUTPUT_PREFIX); + m_next_global_key_output_index = + cur3.end() ? 0 : common::integer_cast(common::read_varint_sqlite4(cur3.get_suffix())) + 1; } void BlockChainState::check_standalone_consensus( @@ -347,10 +348,12 @@ void BlockChainState::check_standalone_consensus( throw ConsensusError(common::to_string( "Wrong merge mining merkle root, tag", mm_tag.merkle_root, "actual", aux_blocks_merkle_root)); } +#if bytecoin_ALLOW_CM if (block.header.is_cm_mined()) { if (!crypto::cm_branch_valid(block.header.cm_merkle_branch)) throw ConsensusError("CM branch invalid"); } +#endif if (block.header.base_transaction.inputs.size() != 1) throw ConsensusError(common::to_string( "Coinbase transaction input count wrong,", block.header.base_transaction.inputs.size(), "should be 1")); @@ -368,9 +371,11 @@ void BlockChainState::check_standalone_consensus( block.header.base_transaction.unlock_block_or_timestamp, "should be", info->height + m_currency.mined_money_unlock_window)); } - const bool check_keys = m_config.paranoid_checks || !m_currency.is_in_hard_checkpoint_zone(info->height); - const Amount miner_reward = - validate_semantic(m_currency, block.header.major_version, true, block.header.base_transaction, check_keys); + const bool check_keys = m_config.paranoid_checks || !m_currency.is_in_hard_checkpoint_zone(info->height); + const bool subgroup_check = info->height >= m_currency.key_image_subgroup_checking_height; + const Amount miner_reward = validate_semantic( + m_currency, block.header.major_version, true, block.header.base_transaction, check_keys, subgroup_check); + size_t key_outputs_count = get_tx_key_outputs_count(block.header.base_transaction); { std::vector timestamps; std::vector difficulties; @@ -389,9 +394,12 @@ void BlockChainState::check_standalone_consensus( info->transactions_fee = 0; for (auto &&tx : pb.block.transactions) { - const Amount tx_fee = validate_semantic(m_currency, block.header.major_version, false, tx, check_keys); + const Amount tx_fee = + validate_semantic(m_currency, block.header.major_version, false, tx, check_keys, subgroup_check); info->transactions_fee += tx_fee; + key_outputs_count += get_tx_key_outputs_count(tx); } + info->already_generated_key_outputs = prev_info.already_generated_key_outputs + key_outputs_count; if (is_amethyst) { info->base_reward = m_currency.get_base_block_reward( @@ -501,13 +509,6 @@ void BlockChainState::create_mining_block_template(const Hash &parent_bid, const }); std::reverse(timestamps.begin(), timestamps.end()); std::reverse(difficulties.begin(), difficulties.end()); - // timestamps.reserve(blocks_count); - // difficulties.reserve(blocks_count); - // auto timestamps_window = get_tip_segment(parent_info, blocks_count, false); - // for (auto it = timestamps_window.begin(); it != timestamps_window.end(); ++it) { - // timestamps.push_back(it->timestamp); - // difficulties.push_back(it->cumulative_difficulty); - // } *difficulty = m_currency.next_effective_difficulty(b->major_version, timestamps, difficulties); } b->nonce.resize(4); @@ -800,22 +801,12 @@ bool BlockChainState::add_transaction(const Hash &tid, const Transaction &tx, co } } const Amount my_fee3 = - validate_semantic(m_currency, get_tip().major_version, false, tx, m_config.paranoid_checks || check_sigs); - // if (!validate_result.empty()) { - // m_log(logging::WARNING) << "add_transaction validation failed " << validate_result << " in transaction " << - // tid << std::endl; - // return AddTransactionResult::BAN; - // } + validate_semantic(m_currency, get_tip().major_version, false, tx, m_config.paranoid_checks || check_sigs, true); DeltaState memory_state(get_tip_height() + 1, get_tip().timestamp, get_tip().timestamp_median, this); BlockGlobalIndices global_indices; Hash newest_referenced_bid; redo_transaction( get_tip().major_version, false, tx, &memory_state, &global_indices, &newest_referenced_bid, check_sigs); - // if (!redo_result.empty()) { - // m_log(logging::TRACE) << "add_transaction redo failed " << redo_result << " in transaction " << tid - // << std::endl; - // return AddTransactionResult::FAILED_TO_REDO; // Not a ban because reorg can change indices - // } if (my_fee != my_fee3) m_log(logging::ERROR) << "Inconsistent fees " << my_fee << ", " << my_fee3 << " in transaction " << tid << std::endl; @@ -900,7 +891,7 @@ bool BlockChainState::get_largest_referenced_height(const TransactionPrefix &tra } Height max_height = 0; for (auto lit : largest_indices) { - UnlockTimePublickKeyHeightSpent unp; + OutputIndexData unp; if (!read_amount_output(lit.first, lit.second, &unp)) return false; max_height = std::max(max_height, unp.height); @@ -948,9 +939,24 @@ void BlockChainState::remove_from_pool(Hash tid) { // if output not found, conflict height is set to currency max_block_height // if no error, conflict_height is set to newest referenced height, (for coinbase transaction to 0) +std::vector BlockChainState::get_mixed_public_keys(const InputKey &in) const { + std::vector absolute_indexes; + if (!relative_output_offsets_to_absolute(&absolute_indexes, in.output_indexes)) + throw ConsensusError("Output indexes invalid in input"); + std::vector output_keys(absolute_indexes.size()); + for (size_t i = 0; i != absolute_indexes.size(); ++i) { + OutputIndexData unp; + if (!read_amount_output(in.amount, absolute_indexes[i], &unp)) + throw ConsensusErrorOutputDoesNotExist("Output does not exist", 0, absolute_indexes[i]); + output_keys[i] = unp.public_key; + } + return output_keys; +} + void BlockChainState::redo_transaction(uint8_t major_block_version, bool generating, const Transaction &transaction, DeltaState *delta_state, BlockGlobalIndices *global_indices, Hash *newest_referenced_bid, bool check_sigs) const { - const bool check_outputs = check_sigs; + const bool check_outputs = check_sigs; + const bool is_tx_amethyst = transaction.version >= m_currency.amethyst_transaction_version; Hash tx_prefix_hash; if (m_config.paranoid_checks || check_sigs) tx_prefix_hash = get_transaction_prefix_hash(transaction); @@ -961,8 +967,8 @@ void BlockChainState::redo_transaction(uint8_t major_block_version, bool generat my_indices.reserve(transaction.outputs.size()); Height newest_referenced_height = 0; - std::vector> all_output_keys; // For half-size sigs - std::vector all_keyimages; // For half-size sigs + std::vector> all_output_keys; // For auditable sigs + std::vector all_keyimages; // For auditable sigs for (size_t input_index = 0; input_index != transaction.inputs.size(); ++input_index) { const auto &input = transaction.inputs.at(input_index); if (input.type() == typeid(InputKey)) { @@ -978,16 +984,15 @@ void BlockChainState::redo_transaction(uint8_t major_block_version, bool generat // throw ConsensusError("Anonymity too low"); // In test/stage net we lack enough coins of each non-dust denomination // } - std::vector global_indexes; - if (!relative_output_offsets_to_absolute(&global_indexes, in.output_indexes)) + std::vector absolute_indexes; + if (!relative_output_offsets_to_absolute(&absolute_indexes, in.output_indexes)) throw ConsensusError("Output indexes invalid in input"); - std::vector output_keys(global_indexes.size()); - for (size_t i = 0; i != global_indexes.size(); ++i) { - UnlockTimePublickKeyHeightSpent unp; - if (!tx_delta.read_amount_output(in.amount, global_indexes[i], &unp)) - throw ConsensusErrorOutputDoesNotExist("Output does not exist", input_index, global_indexes[i]); - if (unp.auditable && global_indexes.size() != 1) - throw ConsensusErrorBadOutputOrSignature("Auditable output mixed", unp.height); + std::vector output_keys(absolute_indexes.size()); + for (size_t i = 0; i != absolute_indexes.size(); ++i) { + OutputIndexData unp; + if (!tx_delta.read_amount_output(in.amount, absolute_indexes[i], &unp)) + throw ConsensusErrorOutputDoesNotExist( + "Output does not exist", input_index, absolute_indexes[i]); if (!m_currency.is_transaction_unlocked(major_block_version, unp.unlock_block_or_timestamp, delta_state->get_block_height(), delta_state->get_block_timestamp(), delta_state->get_block_median_timestamp())) @@ -1004,12 +1009,11 @@ void BlockChainState::redo_transaction(uint8_t major_block_version, bool generat // [&output_key_pointers](const PublicKey &key) { // output_key_pointers.push_back(&key); }); if (!check_ring_signature(tx_prefix_hash, in.key_image, output_keys.data(), output_keys.size(), - signatures.signatures.at(input_index), - delta_state->get_block_height() >= m_currency.key_image_subgroup_checking_height)) { + signatures.signatures.at(input_index))) { throw ConsensusErrorBadOutputOrSignature{ "Bad signature or output reference changed", newest_referenced_height}; } - } else if (transaction.signatures.type() == typeid(RingSignature3)) { + } else if (transaction.signatures.type() == typeid(RingSignatureAmethyst)) { all_output_keys.push_back(std::move(output_keys)); all_keyimages.push_back(in.key_image); } else @@ -1020,9 +1024,9 @@ void BlockChainState::redo_transaction(uint8_t major_block_version, bool generat } } if (!all_output_keys.empty()) { - invariant(transaction.signatures.type() == typeid(RingSignature3), ""); - auto &signatures = boost::get(transaction.signatures); - if (!crypto::check_ring_signature3(tx_prefix_hash, all_keyimages, all_output_keys, signatures)) + invariant(transaction.signatures.type() == typeid(RingSignatureAmethyst), ""); + auto &signatures = boost::get(transaction.signatures); + if (!crypto::check_ring_signature_auditable(tx_prefix_hash, all_keyimages, all_output_keys, signatures)) throw ConsensusErrorBadOutputOrSignature{ "Bad signature or output reference changed", newest_referenced_height}; } @@ -1033,9 +1037,9 @@ void BlockChainState::redo_transaction(uint8_t major_block_version, bool generat for (const auto &output : transaction.outputs) { if (output.type() == typeid(OutputKey)) { const auto &key_output = boost::get(output); - auto global_index = tx_delta.push_amount_output(key_output.amount, transaction.unlock_block_or_timestamp, 0, - key_output.public_key, key_output.is_auditable); // DeltaState ignores unlock point - my_indices.push_back(global_index); + auto stack_index = tx_delta.push_amount_output(key_output.amount, transaction.unlock_block_or_timestamp, 0, + key_output.public_key, is_tx_amethyst); // DeltaState ignores unlock point + my_indices.push_back(stack_index); } } tx_delta.apply(delta_state); @@ -1046,8 +1050,7 @@ void BlockChainState::undo_transaction(IBlockChainState *delta_state, Height, co for (auto oit = tx.outputs.rbegin(); oit != tx.outputs.rend(); ++oit) { if (oit->type() == typeid(OutputKey)) { const auto &key_output = boost::get(*oit); - delta_state->pop_amount_output( - key_output.amount, tx.unlock_block_or_timestamp, key_output.public_key, key_output.is_auditable); + delta_state->pop_amount_output(key_output.amount, tx.unlock_block_or_timestamp, key_output.public_key); } } for (auto iit = tx.inputs.rbegin(); iit != tx.inputs.rend(); ++iit) { @@ -1076,8 +1079,7 @@ void BlockChainState::redo_block(const Hash &bhash, const Block &block, const ap global_indices.reserve(block.transactions.size() + 1); const bool check_sigs = m_config.paranoid_checks || !m_currency.is_in_hard_checkpoint_zone(info.height + 1); if (check_sigs) - m_ring_checker.start_work(this, m_currency, block, info.height, info.timestamp, info.timestamp_median, - info.height >= m_currency.key_image_subgroup_checking_height); + m_ring_checker.start_work(this, m_currency, block, info.height, info.timestamp, info.timestamp_median); redo_block(block, info, &delta, &global_indices); if (check_sigs) { auto errors = m_ring_checker.move_errors(); @@ -1145,69 +1147,72 @@ bool BlockChainState::read_block_output_global_indices(const Hash &bid, BlockGlo std::vector BlockChainState::get_random_outputs(uint8_t block_major_version, Amount amount, size_t output_count, Height confirmed_height, Timestamp block_timestamp, Timestamp block_median_timestamp) const { + // const bool is_amethyst = block_major_version >= m_currency.amethyst_block_version; std::vector result; std::vector spent_result; - size_t total_count = next_global_index_for_amount(amount); + size_t total_stack_count = next_stack_index_for_amount(amount); // We might need better algorithm if we have lots of locked amounts std::set tried_or_added; crypto::random_engine generator; std::lognormal_distribution distribution(1.9, 1.0); // Magic params here const uint32_t linear_part = 150; // Magic params here size_t attempts = 0; - for (; result.size() < output_count && attempts < output_count * 20; ++attempts) { // TODO - 20 - size_t num = 0; - if (result.size() % 2 == 0) { // Half of outputs linear - if (total_count <= linear_part) - num = crypto::rand() % total_count; // 0 handled above - else - num = total_count - 1 - crypto::rand() % linear_part; - } else { - double sample = distribution(generator); - int d_num = static_cast(std::floor(total_count * (1 - std::pow(10, -sample / 10)))); - if (d_num < 0 || d_num >= int(total_count)) + if (total_stack_count > output_count) + for (; result.size() < output_count && attempts < output_count * 20; ++attempts) { // TODO - 20 + size_t num = 0; + if (result.size() % 2 == 0) { // Half of outputs linear + if (total_stack_count <= linear_part) + num = crypto::rand() % total_stack_count; // 0 handled in if above + else + num = total_stack_count - 1 - crypto::rand() % linear_part; + } else { + double sample = distribution(generator); + int d_num = static_cast(std::floor(total_stack_count * (1 - std::pow(10, -sample / 10)))); + if (d_num < 0 || d_num >= int(total_stack_count)) + continue; + num = static_cast(d_num); + } + if (!tried_or_added.insert(num).second) continue; - num = static_cast(d_num); - } - if (!tried_or_added.insert(num).second) - continue; - UnlockTimePublickKeyHeightSpent unp; - invariant(read_amount_output(amount, num, &unp), "num < total_count not found"); - if (unp.height > confirmed_height) { - if (confirmed_height + 128 < get_tip_height()) - total_count = num; - // heuristic - if confirmed_height is deep, the area under ditribution curve - // with height < confirmed_height might be very small, so we adjust total_count - // to get descent results after small number of attempts - continue; + size_t global_index = 0; + invariant(read_hidden_amount_map(amount, num, &global_index), ""); + OutputIndexData unp; + invariant(read_hidden_amount_output(global_index, &unp), "num < total_count not found"); + if (unp.height > confirmed_height) { + if (confirmed_height + 128 < get_tip_height()) + total_stack_count = num; + // heuristic - if confirmed_height is deep, the area under ditribution curve + // with height < confirmed_height might be very small, so we adjust total_count + // to get descent results after small number of attempts + continue; + } + if (!m_currency.is_transaction_unlocked(block_major_version, unp.unlock_block_or_timestamp, + confirmed_height, block_timestamp, block_median_timestamp)) + continue; + if (unp.spent && spent_result.size() >= output_count) + continue; // We need only so much spent + api::Output item; + item.amount = amount; + item.stack_index = num; + item.global_index = global_index; + item.unlock_block_or_timestamp = unp.unlock_block_or_timestamp; + item.public_key = unp.public_key; + item.height = unp.height; + (unp.spent ? spent_result : result).push_back(item); } - if (unp.auditable) - continue; - if (!m_currency.is_transaction_unlocked(block_major_version, unp.unlock_block_or_timestamp, confirmed_height, - block_timestamp, block_median_timestamp)) - continue; - if (unp.spent && spent_result.size() >= output_count) - continue; // We need only so much spent - api::Output item; - item.amount = amount; - item.index = num; - item.unlock_block_or_timestamp = unp.unlock_block_or_timestamp; - item.public_key = unp.public_key; - item.height = unp.height; - (unp.spent ? spent_result : result).push_back(item); - } if (result.size() < output_count) { // Read the whole index. size_t attempts = 0; for (DB::Cursor cur = m_db.rbegin(AMOUNT_OUTPUT_PREFIX + common::write_varint_sqlite4(amount)); result.size() < output_count && attempts < 10000 && !cur.end(); cur.next(), ++attempts) { // TODO - 10000 - const char *be = cur.get_suffix().data(); - const char *en = be + cur.get_suffix().size(); - uint32_t global_index = common::integer_cast(common::read_varint_sqlite4(be, en)); - if (tried_or_added.count(global_index) != 0) + const size_t stack_index = common::integer_cast(common::read_varint_sqlite4(cur.get_suffix())); + if (tried_or_added.count(stack_index) != 0) continue; - UnlockTimePublickKeyHeightSpent unp; - seria::from_binary(unp, cur.get_value_array()); - if (unp.auditable || unp.height > confirmed_height) + size_t global_index = 0; + seria::from_binary(global_index, cur.get_value_array()); + OutputIndexData unp; + invariant(read_hidden_amount_output(global_index, &unp), ""); + if (unp.height > confirmed_height) continue; if (!m_currency.is_transaction_unlocked(block_major_version, unp.unlock_block_or_timestamp, confirmed_height, block_timestamp, block_median_timestamp)) @@ -1216,17 +1221,18 @@ std::vector BlockChainState::get_random_outputs(uint8_t block_major continue; // We need only so much spent api::Output item; item.amount = amount; - item.index = global_index; + item.stack_index = stack_index; + item.global_index = global_index; item.unlock_block_or_timestamp = unp.unlock_block_or_timestamp; item.public_key = unp.public_key; item.height = unp.height; (unp.spent ? spent_result : result).push_back(item); } // To satisfy minimum anonymity requirement for very rare coins, we add spent as a last resort - while (result.size() < output_count && !spent_result.empty()) { - result.push_back(spent_result.back()); - spent_result.pop_back(); - } + // while (result.size() < output_count && !spent_result.empty()) { + // result.push_back(spent_result.back()); + // spent_result.pop_back(); + // } } return result; } @@ -1255,46 +1261,73 @@ bool BlockChainState::read_keyimage(const KeyImage &key_image, Height *height) c } size_t BlockChainState::push_amount_output( - Amount amount, BlockOrTimestamp unlock_time, Height block_height, const PublicKey &pk, bool is_auditable) { - auto my_gi = next_global_index_for_amount(amount); - auto key = AMOUNT_OUTPUT_PREFIX + common::write_varint_sqlite4(amount) + common::write_varint_sqlite4(my_gi); - BinaryArray ba = - seria::to_binary(UnlockTimePublickKeyHeightSpent{unlock_time, pk, block_height, is_auditable, false, {}}); + Amount amount, BlockOrTimestamp unlock_time, Height block_height, const PublicKey &pk, bool is_amethyst) { + auto my_stack_index = next_stack_index_for_amount(amount); + if ((amount == 6299999999000000 && my_stack_index == 0) || (amount == 18899999999000000 && my_stack_index == 0)) + m_log(logging::WARNING) << "double-spend " << amount << ":" << my_stack_index << " -> " + << m_next_global_key_output_index << std::endl; + + auto key = + AMOUNT_OUTPUT_PREFIX + common::write_varint_sqlite4(amount) + common::write_varint_sqlite4(my_stack_index); + BinaryArray ba = seria::to_binary(m_next_global_key_output_index); + m_db.put(key, ba, true); + m_next_stack_index[amount] += 1; + + key = OUTPUT_PREFIX + common::write_varint_sqlite4(m_next_global_key_output_index); + ba = seria::to_binary(OutputIndexData{amount, unlock_time, pk, block_height, false, is_amethyst, {}}); m_db.put(key, ba, true); - m_next_gi_for_amount[amount] += 1; - return my_gi; + m_next_global_key_output_index += 1; + + return my_stack_index; } -void BlockChainState::pop_amount_output( - Amount amount, BlockOrTimestamp unlock_time, const PublicKey &pk, bool is_auditable) { - auto next_gi = next_global_index_for_amount(amount); - invariant(next_gi != 0, "BlockChainState::pop_amount_output underflow"); - next_gi -= 1; - m_next_gi_for_amount[amount] -= 1; - auto key = AMOUNT_OUTPUT_PREFIX + common::write_varint_sqlite4(amount) + common::write_varint_sqlite4(next_gi); - - UnlockTimePublickKeyHeightSpent unp; - invariant(read_amount_output(amount, next_gi, &unp), "BlockChainState::pop_amount_output element does not exist"); - invariant(!unp.spent && unp.unlock_block_or_timestamp == unlock_time && unp.public_key == pk && - unp.auditable == is_auditable, +void BlockChainState::pop_amount_output(Amount amount, BlockOrTimestamp unlock_time, const PublicKey &pk) { + auto my_stack_index = next_stack_index_for_amount(amount); + invariant(my_stack_index != 0, "BlockChainState::pop_amount_output underflow"); + my_stack_index -= 1; + m_next_stack_index[amount] -= 1; + + invariant(m_next_global_key_output_index != 0, "BlockChainState::pop_amount_output hidden underflow"); + m_next_global_key_output_index -= 1; + + size_t should_be_global_index = 0; + invariant(read_hidden_amount_map(amount, my_stack_index, &should_be_global_index), ""); + invariant(m_next_global_key_output_index == should_be_global_index, ""); + OutputIndexData unp; + invariant(read_hidden_amount_output(m_next_global_key_output_index, &unp), + "BlockChainState::pop_amount_output element does not exist"); + invariant( + !unp.spent && unp.amount == amount && unp.unlock_block_or_timestamp == unlock_time && unp.public_key == pk, "BlockChainState::pop_amount_output popping wrong element"); + auto key = + AMOUNT_OUTPUT_PREFIX + common::write_varint_sqlite4(amount) + common::write_varint_sqlite4(my_stack_index); + m_db.del(key, true); + key = OUTPUT_PREFIX + common::write_varint_sqlite4(m_next_global_key_output_index); m_db.del(key, true); } -size_t BlockChainState::next_global_index_for_amount(Amount amount) const { - auto it = m_next_gi_for_amount.find(amount); - if (it != m_next_gi_for_amount.end()) +size_t BlockChainState::next_stack_index_for_amount(Amount amount) const { + auto it = m_next_stack_index.find(amount); + if (it != m_next_stack_index.end()) return it->second; std::string prefix = AMOUNT_OUTPUT_PREFIX + common::write_varint_sqlite4(amount); DB::Cursor cur2 = m_db.rbegin(prefix); size_t alt_in = cur2.end() ? 0 : common::integer_cast(common::read_varint_sqlite4(cur2.get_suffix())) + 1; - m_next_gi_for_amount[amount] = alt_in; + m_next_stack_index[amount] = alt_in; return alt_in; } -bool BlockChainState::read_amount_output( - Amount amount, size_t global_index, UnlockTimePublickKeyHeightSpent *unp) const { - auto key = AMOUNT_OUTPUT_PREFIX + common::write_varint_sqlite4(amount) + common::write_varint_sqlite4(global_index); +bool BlockChainState::read_hidden_amount_map(Amount amount, size_t stack_index, size_t *hidden_index) const { + auto key = AMOUNT_OUTPUT_PREFIX + common::write_varint_sqlite4(amount) + common::write_varint_sqlite4(stack_index); + BinaryArray rb; + if (!m_db.get(key, rb)) + return false; + seria::from_binary(*hidden_index, rb); + return true; +} + +bool BlockChainState::read_hidden_amount_output(size_t hidden_index, OutputIndexData *unp) const { + auto key = OUTPUT_PREFIX + common::write_varint_sqlite4(hidden_index); BinaryArray rb; if (!m_db.get(key, rb)) return false; @@ -1302,14 +1335,20 @@ bool BlockChainState::read_amount_output( return true; } +bool BlockChainState::read_amount_output(Amount amount, size_t stack_index, OutputIndexData *unp) const { + size_t hidden_index = 0; + return read_hidden_amount_map(amount, stack_index, &hidden_index) && read_hidden_amount_output(hidden_index, unp); +} + void BlockChainState::process_input(const Hash &tid, size_t iid, const InputKey &input) { if (chain_reaction == 0) return; if (input.output_indexes.size() == 1) { - UnlockTimePublickKeyHeightSpent unp; - invariant(read_amount_output(input.amount, input.output_indexes.at(0), &unp), ""); - spend_output( - std::move(unp), input.amount, input.output_indexes.at(0), std::numeric_limits::max(), 0, true); + size_t hidden_index = 0; + invariant(read_hidden_amount_map(input.amount, input.output_indexes.at(0), &hidden_index), ""); + OutputIndexData unp; + invariant(read_hidden_amount_output(hidden_index, &unp), ""); + spend_output(std::move(unp), hidden_index, std::numeric_limits::max(), 0, true); return; } if (chain_reaction == 1) @@ -1317,29 +1356,29 @@ void BlockChainState::process_input(const Hash &tid, size_t iid, const InputKey const auto input_index = m_next_nz_input_index; auto din_key = DIN_PREFIX + common::write_varint_sqlite4(m_next_nz_input_index); m_next_nz_input_index += 1; - std::vector global_indexes; - invariant(relative_output_offsets_to_absolute(&global_indexes, input.output_indexes), ""); + std::vector absolute_indexes; + invariant(relative_output_offsets_to_absolute(&absolute_indexes, input.output_indexes), ""); InputDesc din; - std::vector unspents; - for (size_t i = 0; i != global_indexes.size(); ++i) { - const auto global_index = global_indexes.at(i); - UnlockTimePublickKeyHeightSpent unp; - invariant(read_amount_output(input.amount, global_index, &unp), ""); + std::vector unspents; + for (size_t i = 0; i != absolute_indexes.size(); ++i) { + size_t hidden_index = 0; + invariant(read_hidden_amount_map(input.amount, absolute_indexes.at(i), &hidden_index), ""); + OutputIndexData unp; + invariant(read_hidden_amount_output(hidden_index, &unp), ""); if (unp.spent) continue; - din.first.push_back(global_index); + din.first.push_back(hidden_index); unspents.push_back(std::move(unp)); } if (din.first.size() > 1) for (size_t i = 0; i != din.first.size(); ++i) { unspents[i].dins.push_back(input_index); - auto key = AMOUNT_OUTPUT_PREFIX + common::write_varint_sqlite4(input.amount) + - common::write_varint_sqlite4(din.first[i]); + auto key = OUTPUT_PREFIX + common::write_varint_sqlite4(din.first[i]); m_db.put(key, seria::to_binary(unspents[i]), false); } m_db.put(din_key, seria::to_binary(din), true); if (din.first.size() == 1) { - spend_output(std::move(unspents[0]), input.amount, din.first[0], input_index, 0, true); + spend_output(std::move(unspents[0]), din.first[0], input_index, 0, true); } } @@ -1347,10 +1386,11 @@ void BlockChainState::unprocess_input(const InputKey &input) { if (chain_reaction == 0) return; if (input.output_indexes.size() == 1) { - UnlockTimePublickKeyHeightSpent unp; - invariant(read_amount_output(input.amount, input.output_indexes.at(0), &unp), ""); - spend_output( - std::move(unp), input.amount, input.output_indexes.at(0), std::numeric_limits::max(), 0, false); + size_t hidden_index = 0; + invariant(read_hidden_amount_map(input.amount, input.output_indexes.at(0), &hidden_index), ""); + OutputIndexData unp; + invariant(read_hidden_amount_output(hidden_index, &unp), ""); + spend_output(std::move(unp), hidden_index, std::numeric_limits::max(), 0, false); return; } if (chain_reaction == 1) @@ -1364,30 +1404,28 @@ void BlockChainState::unprocess_input(const InputKey &input) { seria::from_binary(din, din_ba); invariant(din.second.empty(), ""); if (din.first.size() > 1) - for (auto global_index : din.first) { - UnlockTimePublickKeyHeightSpent unp; - invariant(read_amount_output(input.amount, global_index, &unp), ""); + for (auto hidden_index : din.first) { + OutputIndexData unp; + invariant(read_hidden_amount_output(hidden_index, &unp), ""); invariant(!unp.dins.empty() && unp.dins.back() == input_index, ""); unp.dins.pop_back(); - auto key = AMOUNT_OUTPUT_PREFIX + common::write_varint_sqlite4(input.amount) + - common::write_varint_sqlite4(global_index); + auto key = OUTPUT_PREFIX + common::write_varint_sqlite4(hidden_index); m_db.put(key, seria::to_binary(unp), false); } m_db.del(din_key, true); if (din.first.size() == 1) { - UnlockTimePublickKeyHeightSpent unp; - invariant(read_amount_output(input.amount, din.first[0], &unp), ""); - spend_output(std::move(unp), input.amount, din.first[0], input_index, 0, false); + OutputIndexData unp; + invariant(read_hidden_amount_output(din.first[0], &unp), ""); + spend_output(std::move(unp), din.first[0], input_index, 0, false); } } -void BlockChainState::spend_output(UnlockTimePublickKeyHeightSpent &&output, Amount amount, size_t global_index, - size_t trigger_input_index, size_t level, bool spent) { +void BlockChainState::spend_output( + OutputIndexData &&output, size_t hidden_index, size_t trigger_input_index, size_t level, bool spent) { if (level > 2) - std::cout << "Sure spent level=" << level << " am:gi=" << amount << ":" << global_index << std::endl; - auto key = AMOUNT_OUTPUT_PREFIX + common::write_varint_sqlite4(amount) + common::write_varint_sqlite4(global_index); - bool no_subgroup_check_aftermath = - (amount == 6299999999000000 && global_index == 0) || (amount == 18899999999000000 && global_index == 0); + std::cout << "Sure spent level=" << level << " hi=" << hidden_index << std::endl; + auto key = OUTPUT_PREFIX + common::write_varint_sqlite4(hidden_index); + bool no_subgroup_check_aftermath = hidden_index == 35654297 || hidden_index == 35655016; if (spent) { invariant(no_subgroup_check_aftermath || output.spent == 0, ""); output.spent += 1; @@ -1412,11 +1450,11 @@ void BlockChainState::spend_output(UnlockTimePublickKeyHeightSpent &&output, Amo seria::from_binary(din, din_ba); size_t only_index = std::numeric_limits::max(); if (spent) { - size_t found_index = std::lower_bound(din.first.begin(), din.first.end(), global_index) - din.first.begin(); - invariant(found_index != din.first.size() && din.first.at(found_index) == global_index, ""); + size_t found_index = std::lower_bound(din.first.begin(), din.first.end(), hidden_index) - din.first.begin(); + invariant(found_index != din.first.size() && din.first.at(found_index) == hidden_index, ""); din.first.erase(din.first.begin() + found_index); - din.second.push_back(global_index); - // std::cout << "Removing spent " << amount << ":" << global_index << " from " << din.tid << ":" << + din.second.push_back(hidden_index); + // std::cout << "Removing hidden_index " << hidden_index << " from " << din.tid << ":" << // din.index << std::endl; if (din.first.size() == 1) { only_index = din.first.back(); @@ -1425,63 +1463,46 @@ void BlockChainState::spend_output(UnlockTimePublickKeyHeightSpent &&output, Amo if (din.first.size() == 1) { only_index = din.first.back(); } - invariant(!din.second.empty() && din.second.back() == global_index, ""); + invariant(!din.second.empty() && din.second.back() == hidden_index, ""); din.second.pop_back(); size_t insert_index = - std::lower_bound(din.first.begin(), din.first.end(), global_index) - din.first.begin(); - din.first.insert(din.first.begin() + insert_index, global_index); + std::lower_bound(din.first.begin(), din.first.end(), hidden_index) - din.first.begin(); + din.first.insert(din.first.begin() + insert_index, hidden_index); } m_db.put(din_key, seria::to_binary(din), false); if (only_index != std::numeric_limits::max()) { - UnlockTimePublickKeyHeightSpent unp; - invariant(read_amount_output(amount, only_index, &unp), ""); - spend_output(std::move(unp), amount, only_index, input_index, level + 1, spent); + OutputIndexData unp; + invariant(read_hidden_amount_output(only_index, &unp), ""); + spend_output(std::move(unp), only_index, input_index, level + 1, spent); } } } -void BlockChainState::test_print_outputs() { - Amount previous_amount = (Amount)-1; - size_t next_global_index = 0; - int total_counter = 0; - std::map coins; - for (DB::Cursor cur = m_db.begin(AMOUNT_OUTPUT_PREFIX); !cur.end(); cur.next()) { - const char *be = cur.get_suffix().data(); - const char *en = be + cur.get_suffix().size(); - auto amount = common::read_varint_sqlite4(be, en); - size_t global_index = common::integer_cast(common::read_varint_sqlite4(be, en)); - if (be != en) - std::cout << "Excess value bytes for amount=" << amount << " index=" << global_index << std::endl; - if (amount != previous_amount) { - if (previous_amount != (Amount)-1) { - if (!coins.insert(std::make_pair(previous_amount, next_global_index)).second) { - std::cout << "Duplicate amount for previous_amount=" << previous_amount - << " next_global_index=" << next_global_index << std::endl; - } - } - previous_amount = amount; - next_global_index = 0; - } - if (global_index != next_global_index) { - std::cout << "Bad output index for amount=" << amount << " index=" << global_index << std::endl; - } - next_global_index += 1; - if (++total_counter % 2000000 == 0) - std::cout << "Working on amount=" << amount << " index=" << global_index << std::endl; +void BlockChainState::dump_outputs_quality(size_t max_count) const { + size_t total_spent = 0; + size_t total_not_spent = 0; + std::vector result(std::min(max_count, m_next_global_key_output_index) / 8); // must be round down + for (DB::Cursor cur = m_db.begin(OUTPUT_PREFIX); !cur.end(); cur.next()) { + const size_t global_index = common::integer_cast(common::read_varint_sqlite4(cur.get_suffix())); + OutputIndexData unp; + invariant(read_hidden_amount_output(global_index, &unp), ""); + if (global_index / 8 >= result.size()) + break; + if (unp.spent) { // beware, unp.spent is counter + result.at(global_index / 8) |= (1 << global_index % 8); + total_spent += 1; + } else + total_not_spent += 1; } - total_counter = 0; - std::cout << "Total coins=" << total_counter << " total stacks=" << coins.size() << std::endl; - for (auto &&co : coins) { - auto total_count = next_global_index_for_amount(co.first); - if (total_count != co.second) - std::cout << "Wrong next_global_index_for_amount amount=" << co.first << " total_count=" << total_count - << " should be " << co.second << std::endl; - for (size_t i = 0; i != total_count; ++i) { - UnlockTimePublickKeyHeightSpent unp; - if (!read_amount_output(co.first, i, &unp)) - std::cout << "Failed to read amount=" << co.first << " index=" << i << std::endl; - if (++total_counter % 1000000 == 0) - std::cout << "Working on amount=" << co.first << " index=" << i << std::endl; - } + std::cout << "spent=" << total_spent << " !spent=" << total_not_spent << std::endl; + std::cout << " constexpr size_t OUTPUT_QUALITY_SIZE=" << result.size() << ";" << std::endl; + std::cout << " const uint8_t output_quality[OUTPUT_QUALITY_SIZE]={" << std::endl; + for (size_t i = 0; i != result.size(); ++i) { + if (i != 0) + std::cout << ", "; + if (i % 16 == 0) + std::cout << std::endl; + std::cout << "0x" << common::to_hex(&result[i], 1); } + std::cout << "};" << std::endl; } diff --git a/src/Core/BlockChainState.hpp b/src/Core/BlockChainState.hpp index 3c0fc202..82919238 100644 --- a/src/Core/BlockChainState.hpp +++ b/src/Core/BlockChainState.hpp @@ -15,12 +15,13 @@ class Config; class IBlockChainState { public: - struct UnlockTimePublickKeyHeightSpent { + struct OutputIndexData { + Amount amount; // We will serialize encrypted amount if amount == 0 BlockOrTimestamp unlock_block_or_timestamp = 0; PublicKey public_key; - Height height = 0; - bool auditable = false; - uint8_t spent = 0; // Aftermath of "keyimage out of subgroup" attack + Height height = 0; + uint8_t spent = 0; // Aftermath of "keyimage out of subgroup" attack + bool is_amethyst = false; std::vector dins; }; virtual ~IBlockChainState() = default; @@ -28,10 +29,10 @@ class IBlockChainState { virtual void delete_keyimage(const KeyImage &) = 0; virtual bool read_keyimage(const KeyImage &, Height *) const = 0; - virtual size_t push_amount_output(Amount, BlockOrTimestamp, Height, const PublicKey &, bool is_auditable) = 0; - virtual void pop_amount_output(Amount, BlockOrTimestamp, const PublicKey &, bool is_auditable) = 0; - virtual size_t next_global_index_for_amount(Amount) const = 0; - virtual bool read_amount_output(Amount, size_t global_index, UnlockTimePublickKeyHeightSpent *) const = 0; + virtual size_t push_amount_output(Amount, BlockOrTimestamp, Height, const PublicKey &, bool is_amethyst) = 0; + virtual void pop_amount_output(Amount, BlockOrTimestamp, const PublicKey &) = 0; + virtual size_t next_stack_index_for_amount(Amount) const = 0; + virtual bool read_amount_output(Amount, size_t stack_index, OutputIndexData *) const = 0; }; class BlockChainState : public BlockChain, private IBlockChainState { @@ -72,9 +73,10 @@ class BlockChainState : public BlockChain, private IBlockChainState { static api::BlockHeader fill_genesis(Hash genesis_bid, const BlockTemplate &); - void test_print_outputs(); + void dump_outputs_quality(size_t max_count) const; void fill_statistics(api::cnd::GetStatistics::Response &res) const override; + std::vector get_mixed_public_keys(const InputKey &in) const; protected: void check_standalone_consensus(const PreparedBlock &pb, api::BlockHeader *info, const api::BlockHeader &prev_info, @@ -86,6 +88,7 @@ class BlockChainState : public BlockChain, private IBlockChainState { class DeltaState : public IBlockChainState { std::map m_keyimages; // sorted to speed up bulk saving to DB std::map>> m_global_amounts; + std::vector m_ordered_global_amounts; // std::vector> m_spent_outputs; Height m_block_height; // Every delta state corresponds to some height Timestamp m_block_timestamp; @@ -109,22 +112,23 @@ class BlockChainState : public BlockChain, private IBlockChainState { void delete_keyimage(const KeyImage &) override; bool read_keyimage(const KeyImage &, Height *) const override; - size_t push_amount_output(Amount, BlockOrTimestamp, Height, const PublicKey &, bool is_auditable) override; - void pop_amount_output(Amount, BlockOrTimestamp, const PublicKey &, bool is_auditable) override; - size_t next_global_index_for_amount(Amount) const override; - bool read_amount_output(Amount, size_t global_index, UnlockTimePublickKeyHeightSpent *) const override; + size_t push_amount_output(Amount, BlockOrTimestamp, Height, const PublicKey &, bool is_amethyst) override; + void pop_amount_output(Amount, BlockOrTimestamp, const PublicKey &) override; + size_t next_stack_index_for_amount(Amount) const override; + bool read_amount_output(Amount, size_t stack_index, OutputIndexData *) const override; }; void store_keyimage(const KeyImage &, Height) override; void delete_keyimage(const KeyImage &) override; bool read_keyimage(const KeyImage &, Height *) const override; - size_t push_amount_output(Amount, BlockOrTimestamp, Height, const PublicKey &, bool is_auditable) override; - void pop_amount_output(Amount, BlockOrTimestamp, const PublicKey &, bool is_auditable) override; - size_t next_global_index_for_amount(Amount) const override; - bool read_amount_output(Amount, size_t global_index, UnlockTimePublickKeyHeightSpent *) const override; - void spend_output(UnlockTimePublickKeyHeightSpent &&, Amount, size_t global_index, size_t trigger_input_index, - size_t level, bool spent); + size_t push_amount_output(Amount, BlockOrTimestamp, Height, const PublicKey &, bool is_amethyst) override; + void pop_amount_output(Amount, BlockOrTimestamp, const PublicKey &) override; + size_t next_stack_index_for_amount(Amount) const override; + bool read_amount_output(Amount, size_t stack_index, OutputIndexData *) const override; + bool read_hidden_amount_map(Amount, size_t stack_index, size_t *hidden_index) const; + bool read_hidden_amount_output(size_t hidden_index, OutputIndexData *) const; + void spend_output(OutputIndexData &&, size_t hidden_index, size_t trigger_input_index, size_t level, bool spent); void redo_transaction(uint8_t major_block_version, bool generating, const Transaction &, DeltaState *, BlockGlobalIndices *, Hash *newest_referenced_bid, @@ -136,7 +140,7 @@ class BlockChainState : public BlockChain, private IBlockChainState { const size_t m_max_pool_size; mutable crypto::CryptoNightContext m_hash_crypto_context; - mutable std::unordered_map m_next_gi_for_amount; + mutable std::unordered_map m_next_stack_index; // Read from db on first use, write on modification void remove_from_pool(Hash tid); @@ -151,7 +155,8 @@ class BlockChainState : public BlockChain, private IBlockChainState { mutable std::map> m_mining_transactions; // We remember them for several blocks void clear_mining_transactions() const; - size_t m_next_nz_input_index = 0; + size_t m_next_global_key_output_index = 0; + size_t m_next_nz_input_index = 0; void process_input(const Hash &tid, size_t iid, const InputKey &input); void unprocess_input(const InputKey &input); diff --git a/src/Core/Config.cpp b/src/Core/Config.cpp index e497daab..9c9af4bf 100644 --- a/src/Core/Config.cpp +++ b/src/Core/Config.cpp @@ -167,6 +167,8 @@ Config::Config(common::CommandLine &cmd) } } +bool Config::use_multicast() const { return multicast_period != 0 && p2p_bind_ip != "127.0.0.1"; } + std::string Config::prepare_usage(const std::string &usage) { std::string result = usage; boost::replace_all(result, "bytecoin", CRYPTONOTE_NAME); diff --git a/src/Core/Config.hpp b/src/Core/Config.hpp index fbd118b4..f7e7ecb6 100644 --- a/src/Core/Config.hpp +++ b/src/Core/Config.hpp @@ -38,6 +38,7 @@ class Config { // Consensus does not depend on those parameters uint16_t multicast_port; float multicast_period; bool secrets_via_api; + bool use_multicast() const; std::string bytecoind_authorization; std::string bytecoind_authorization_private; diff --git a/src/Core/CryptoNoteTools.cpp b/src/Core/CryptoNoteTools.cpp index 4f4e6d34..0c413bbe 100644 --- a/src/Core/CryptoNoteTools.cpp +++ b/src/Core/CryptoNoteTools.cpp @@ -4,6 +4,7 @@ #include "CryptoNoteTools.hpp" #include "Currency.hpp" #include "TransactionExtra.hpp" +#include "common/StringTools.hpp" #include "common/Varint.hpp" #include "seria/ISeria.hpp" @@ -130,6 +131,15 @@ Amount cn::get_tx_sum_inputs(const TransactionPrefix &tx) { return amount_in; } +size_t cn::get_tx_key_outputs_count(const TransactionPrefix &tx) { + size_t count = 0; + for (const auto &o : tx.outputs) { + if (o.type() == typeid(OutputKey)) + count += 1; + } + return count; +} + bool cn::get_tx_fee(const TransactionPrefix &tx, uint64_t *fee) { uint64_t amount_in = get_tx_sum_inputs(tx); uint64_t amount_out = get_tx_sum_outputs(tx); diff --git a/src/Core/CryptoNoteTools.hpp b/src/Core/CryptoNoteTools.hpp index 951200c9..ed47a19d 100644 --- a/src/Core/CryptoNoteTools.hpp +++ b/src/Core/CryptoNoteTools.hpp @@ -29,6 +29,8 @@ size_t get_maximum_tx_input_size(size_t anonymity); Amount get_tx_sum_outputs(const TransactionPrefix &tx); Amount get_tx_sum_inputs(const TransactionPrefix &tx); +size_t get_tx_key_outputs_count(const TransactionPrefix &tx); + inline bool add_amount(Amount &sum, Amount amount) { if (std::numeric_limits::max() - amount < sum) return false; @@ -43,4 +45,5 @@ std::vector absolute_output_offsets_to_relative(const std::vector *result, const std::vector &off); BlockBodyProxy get_body_proxy_from_template(const BlockTemplate &bt); + } // namespace cn diff --git a/src/Core/Currency.cpp b/src/Core/Currency.cpp index 952ac811..90988e1a 100644 --- a/src/Core/Currency.cpp +++ b/src/Core/Currency.cpp @@ -74,10 +74,11 @@ Currency::Currency(const std::string &net) , amethyst_block_version(BLOCK_VERSION_AMETHYST) , amethyst_transaction_version(TRANSACTION_VERSION_AMETHYST) , upgrade_from_major_version(3) - , upgrade_indicator_minor_version(5) + , upgrade_indicator_minor_version(6) , upgrade_desired_major_version(0) , upgrade_voting_window(UPGRADE_VOTING_WINDOW) - , upgrade_window(UPGRADE_WINDOW) { + , upgrade_window(UPGRADE_WINDOW) + , sendproof_base58_prefix(SENDPROOF_BASE58_PREFIX) { if (net == "test") { upgrade_heights = {1, 1}; // block 1 is already V3 upgrade_desired_major_version = 4; @@ -315,7 +316,7 @@ Transaction Currency::construct_miner_tx( for (size_t out_index = 0; out_index < out_amounts.size(); out_index++) { const KeyPair output_det_keys = crypto::random_keypair(); OutputKey tk = TransactionBuilder::create_output( - is_tx_amethyst, miner_address, txkey.secret_key, tx_inputs_hash, out_index, output_det_keys); + is_tx_amethyst, miner_address, txkey.secret_key, tx_inputs_hash, out_index, output_det_keys.public_key); tk.amount = out_amounts.at(out_index); summary_amounts += tk.amount; tx.outputs.push_back(tk); @@ -366,27 +367,24 @@ std::string Currency::account_address_as_string(const AccountAddress &v_addr) co if (v_addr.type() == typeid(AccountAddressUnlinkable)) { auto &addr = boost::get(v_addr); BinaryArray ba = seria::to_binary(addr); - if (addr.is_auditable) - return common::base58::encode_addr(ADDRESS_BASE58_PREFIX_AUDITABLE_UNLINKABLE, ba); - return common::base58::encode_addr(ADDRESS_BASE58_PREFIX_UNLINKABLE, ba); + return common::base58::encode_addr(ADDRESS_BASE58_PREFIX_AMETHYST, ba); } throw std::runtime_error("Unknown address type"); } bool Currency::parse_account_address_string(const std::string &str, AccountAddress *v_addr) const { - BinaryArray tag; + uint64_t tag = 0; BinaryArray data; - if (!common::base58::decode_addr(str, 2 * sizeof(PublicKey), &tag, &data)) + if (!common::base58::decode_addr(str, &tag, &data)) return false; - if (tag == ADDRESS_BASE58_PREFIX_UNLINKABLE || tag == ADDRESS_BASE58_PREFIX_AUDITABLE_UNLINKABLE) { + if (tag == ADDRESS_BASE58_PREFIX_AMETHYST) { AccountAddressUnlinkable addr; - addr.is_auditable = (tag == ADDRESS_BASE58_PREFIX_AUDITABLE_UNLINKABLE); try { seria::from_binary(addr, data); } catch (const std::exception &) { return false; } - if (!key_isvalid(addr.s) || !key_isvalid(addr.sv)) + if (!key_in_main_subgroup(addr.S) || !key_in_main_subgroup(addr.Sv)) return false; *v_addr = addr; return true; @@ -398,7 +396,7 @@ bool Currency::parse_account_address_string(const std::string &str, AccountAddre } catch (const std::exception &) { return false; } - if (!key_isvalid(addr.spend_public_key) || !key_isvalid(addr.view_public_key)) + if (!key_in_main_subgroup(addr.S) || !key_in_main_subgroup(addr.V)) return false; *v_addr = addr; return true; @@ -558,13 +556,15 @@ bool Currency::amount_allowed_in_output(uint8_t block_major_version, Amount amou }*/ Hash cn::get_transaction_inputs_hash(const TransactionPrefix &tx) { - const bool is_tx_amethyst = (tx.version >= parameters::TRANSACTION_VERSION_AMETHYST); - BinaryArray ba = seria::to_binary(tx.inputs, is_tx_amethyst); + // const bool is_tx_amethyst = (tx.version >= parameters::TRANSACTION_VERSION_AMETHYST); + BinaryArray ba = seria::to_binary(tx.inputs); + // std::cout << "get_transaction_inputs_hash body=" << common::to_hex(ba) << std::endl; return crypto::cn_fast_hash(ba.data(), ba.size()); } Hash cn::get_transaction_prefix_hash(const TransactionPrefix &tx) { BinaryArray ba = seria::to_binary(tx); + // std::cout << "get_transaction_prefix_hash body=" << common::to_hex(ba) << std::endl; return crypto::cn_fast_hash(ba.data(), ba.size()); } @@ -575,6 +575,9 @@ Hash cn::get_transaction_hash(const Transaction &tx) { BinaryArray binary_sigs = seria::to_binary(tx.signatures, static_cast(tx)); ha.second = crypto::cn_fast_hash(binary_sigs.data(), binary_sigs.size()); BinaryArray ba = seria::to_binary(ha); +// BinaryArray tx_body = seria::to_binary(static_cast(tx)); +// common::append(tx_body, binary_sigs); +// invariant(tx_body == seria::to_binary(tx), ""); return crypto::cn_fast_hash(ba.data(), ba.size()); } BinaryArray ba = seria::to_binary(tx); diff --git a/src/Core/Currency.hpp b/src/Core/Currency.hpp index d086bda7..258ce2f7 100644 --- a/src/Core/Currency.hpp +++ b/src/Core/Currency.hpp @@ -78,6 +78,8 @@ class Currency { // Consensus calculations depend on those parameters Height upgrade_votes_required() const; Height upgrade_window; + uint64_t sendproof_base58_prefix; + size_t hard_checkpoint_count() const { return checkpoints_end - checkpoints_begin; } bool is_in_hard_checkpoint_zone(Height height) const; bool check_hard_checkpoint(Height height, const Hash &h, bool &is_hard_checkpoint) const; diff --git a/src/Core/Multicore.cpp b/src/Core/Multicore.cpp index 9520a277..27438d9f 100644 --- a/src/Core/Multicore.cpp +++ b/src/Core/Multicore.cpp @@ -99,14 +99,14 @@ RingCheckerMulticore::~RingCheckerMulticore() { void RingCheckerMulticore::thread_run() { while (true) { RingSignatureArg arg; - RingSignatureArg3 arg3; + RingSignatureArgA arga; Height newest_referenced_height = 0; int local_work_counter = 0; { std::unique_lock lock(mu); if (quit) return; - if (args.empty() && args3.empty()) { + if (args.empty() && argsa.empty()) { have_work.wait(lock); continue; } @@ -116,18 +116,18 @@ void RingCheckerMulticore::thread_run() { newest_referenced_height = arg.newest_referenced_height; args.pop_front(); } else { - arg3 = std::move(args3.front()); - newest_referenced_height = arg3.newest_referenced_height; - args3.pop_front(); + arga = std::move(argsa.front()); + newest_referenced_height = arga.newest_referenced_height; + argsa.pop_front(); } } bool result = false; if (!arg.output_keys.empty()) { - result = crypto::check_ring_signature(arg.tx_prefix_hash, arg.key_image, arg.output_keys.data(), - arg.output_keys.size(), arg.input_signature, arg.key_image_subgroup_check); + result = crypto::check_ring_signature( + arg.tx_prefix_hash, arg.key_image, arg.output_keys.data(), arg.output_keys.size(), arg.input_signature); } else { - result = crypto::check_ring_signature3( - arg3.tx_prefix_hash, arg3.key_images, arg3.output_keys, arg3.input_signature); + result = crypto::check_ring_signature_auditable( + arga.tx_prefix_hash, arga.key_images, arga.output_keys, arga.input_signature); } { std::unique_lock lock(mu); @@ -144,16 +144,16 @@ void RingCheckerMulticore::thread_run() { void RingCheckerMulticore::cancel_work() { std::unique_lock lock(mu); args.clear(); - args3.clear(); + argsa.clear(); work_counter += 1; } void RingCheckerMulticore::start_work(IBlockChainState *state, const Currency ¤cy, const Block &block, - Height unlock_height, Timestamp block_timestamp, Timestamp block_median_timestamp, bool key_image_subgroup_check) { + Height unlock_height, Timestamp block_timestamp, Timestamp block_median_timestamp) { { std::unique_lock lock(mu); args.clear(); - args3.clear(); + argsa.clear(); errors.clear(); ready_counter = 0; work_counter += 1; @@ -161,7 +161,7 @@ void RingCheckerMulticore::start_work(IBlockChainState *state, const Currency &c total_counter = 0; for (auto &&transaction : block.transactions) { Hash tx_prefix_hash = get_transaction_prefix_hash(transaction); - RingSignatureArg3 arg3; + RingSignatureArgA arga; for (size_t input_index = 0; input_index != transaction.inputs.size(); ++input_index) { const auto &input = transaction.inputs.at(input_index); Height newest_referenced_height = 0; @@ -170,16 +170,15 @@ void RingCheckerMulticore::start_work(IBlockChainState *state, const Currency &c Height height = 0; if (state->read_keyimage(in.key_image, &height)) throw ConsensusErrorOutputSpent("Output already spent", in.key_image, height); - std::vector global_indexes; - if (!relative_output_offsets_to_absolute(&global_indexes, in.output_indexes)) + std::vector absolute_indexes; + if (!relative_output_offsets_to_absolute(&absolute_indexes, in.output_indexes)) throw ConsensusError("Output indexes invalid in input"); - std::vector output_keys(global_indexes.size()); - for (size_t i = 0; i != global_indexes.size(); ++i) { - IBlockChainState::UnlockTimePublickKeyHeightSpent unp; - if (!state->read_amount_output(in.amount, global_indexes[i], &unp)) - throw ConsensusErrorOutputDoesNotExist("Output does not exist", input_index, global_indexes[i]); - if (unp.auditable && global_indexes.size() != 1) - throw ConsensusErrorBadOutputOrSignature("Auditable output mixed", unp.height); + std::vector output_keys(absolute_indexes.size()); + for (size_t i = 0; i != absolute_indexes.size(); ++i) { + IBlockChainState::OutputIndexData unp; + if (!state->read_amount_output(in.amount, absolute_indexes[i], &unp)) + throw ConsensusErrorOutputDoesNotExist( + "Output does not exist", input_index, absolute_indexes[i]); if (!currency.is_transaction_unlocked(block.header.major_version, unp.unlock_block_or_timestamp, unlock_height, block_timestamp, block_median_timestamp)) throw ConsensusErrorBadOutputOrSignature("Output locked", unp.height); @@ -191,7 +190,6 @@ void RingCheckerMulticore::start_work(IBlockChainState *state, const Currency &c if (transaction.signatures.type() == typeid(RingSignatures)) { auto &signatures = boost::get(transaction.signatures); RingSignatureArg arg; - arg.key_image_subgroup_check = key_image_subgroup_check; arg.tx_prefix_hash = tx_prefix_hash; arg.newest_referenced_height = newest_referenced_height; arg.key_image = in.key_image; @@ -201,22 +199,22 @@ void RingCheckerMulticore::start_work(IBlockChainState *state, const Currency &c std::unique_lock lock(mu); args.push_back(std::move(arg)); have_work.notify_all(); - } else if (transaction.signatures.type() == typeid(RingSignature3)) { - auto &signatures = boost::get(transaction.signatures); - arg3.output_keys.push_back(std::move(output_keys)); - arg3.newest_referenced_height = std::max(arg3.newest_referenced_height, newest_referenced_height); - arg3.key_images.push_back(in.key_image); - if (arg3.input_signature.r.empty()) - arg3.input_signature = signatures; + } else if (transaction.signatures.type() == typeid(RingSignatureAmethyst)) { + auto &signatures = boost::get(transaction.signatures); + arga.output_keys.push_back(std::move(output_keys)); + arga.newest_referenced_height = std::max(arga.newest_referenced_height, newest_referenced_height); + arga.key_images.push_back(in.key_image); + if (arga.input_signature.ra.empty()) + arga.input_signature = signatures; } else throw ConsensusError("Unknown signatures type"); } } - if (!arg3.output_keys.empty()) { - arg3.tx_prefix_hash = tx_prefix_hash; + if (!arga.output_keys.empty()) { + arga.tx_prefix_hash = tx_prefix_hash; total_counter += 1; std::unique_lock lock(mu); - args3.push_back(std::move(arg3)); + argsa.push_back(std::move(arga)); have_work.notify_all(); } } @@ -261,15 +259,15 @@ PreparedWalletTransaction::PreparedWalletTransaction(TransactionPrefix &&ttx, co inputs_hash = get_transaction_inputs_hash(tx); KeyPair tx_keys; - spend_keys.resize(tx.outputs.size()); - output_secret_scalars.resize(tx.outputs.size()); + address_public_keys.resize(tx.outputs.size()); + output_spend_scalars.resize(tx.outputs.size()); for (size_t out_index = 0; out_index != tx.outputs.size(); ++out_index) { const auto &output = tx.outputs.at(out_index); if (output.type() != typeid(OutputKey)) continue; const auto &key_output = boost::get(output); - o_handler(tx_public_key, &derivation, inputs_hash, out_index, key_output, &spend_keys.at(out_index), - &output_secret_scalars.at(out_index)); + o_handler(tx.version, tx_public_key, &derivation, inputs_hash, out_index, key_output, + &address_public_keys.at(out_index), &output_spend_scalars.at(out_index)); } } diff --git a/src/Core/Multicore.hpp b/src/Core/Multicore.hpp index b1c22262..aa66c6b2 100644 --- a/src/Core/Multicore.hpp +++ b/src/Core/Multicore.hpp @@ -51,17 +51,17 @@ struct RingSignatureArg { Hash tx_prefix_hash; Height newest_referenced_height = 0; KeyImage key_image; - bool key_image_subgroup_check = false; std::vector output_keys; RingSignature input_signature; }; -struct RingSignatureArg3 { +struct RingSignatureArgA { Hash tx_prefix_hash; Height newest_referenced_height = 0; std::vector key_images; + std::vector ps; std::vector> output_keys; - RingSignature3 input_signature; + RingSignatureAmethyst input_signature; }; class RingCheckerMulticore { @@ -76,7 +76,7 @@ class RingCheckerMulticore { std::vector errors; std::deque args; - std::deque args3; + std::deque argsa; int work_counter = 0; void thread_run(); @@ -85,8 +85,7 @@ class RingCheckerMulticore { ~RingCheckerMulticore(); void cancel_work(); void start_work(IBlockChainState *state, const Currency ¤cy, const Block &block, Height unlock_height, - Timestamp block_timestamp, Timestamp block_median_timestamp, - bool key_image_subgroup_check); // can throw ConsensusError immediately + Timestamp block_timestamp, Timestamp block_median_timestamp); // can throw ConsensusError immediately std::vector move_errors(); }; @@ -95,8 +94,8 @@ struct PreparedWalletTransaction { Hash prefix_hash; Hash inputs_hash; boost::optional derivation; // Will be assigned on first actual use - std::vector spend_keys; - std::vector output_secret_scalars; + std::vector address_public_keys; + std::vector output_spend_scalars; PreparedWalletTransaction() = default; PreparedWalletTransaction(TransactionPrefix &&tx, const Wallet::OutputHandler &o_handler); diff --git a/src/Core/Node.cpp b/src/Core/Node.cpp index 031ac2fe..5b5eba5f 100644 --- a/src/Core/Node.cpp +++ b/src/Core/Node.cpp @@ -7,7 +7,9 @@ #include #include "Config.hpp" #include "CryptoNoteTools.hpp" +#include "TransactionBuilder.hpp" #include "TransactionExtra.hpp" +#include "common/Base58.hpp" #include "common/JsonValue.hpp" #include "http/Client.hpp" #include "http/Server.hpp" @@ -52,7 +54,7 @@ Node::Node(logging::ILogger &log, const Config &config, BlockChainState &block_c Node::~Node() {} // we have unique_ptr to incomplete type void Node::send_multicast() { - if (m_config.multicast_period == 0) + if (!m_config.use_multicast()) return; // std::cout << "sending multicast about node listening on port=" << m_config.p2p_external_port << std::endl; BinaryArray ha = P2PProtocolBasic::create_multicast_announce( @@ -62,6 +64,8 @@ void Node::send_multicast() { } void Node::on_multicast(const std::string &addr, const unsigned char *data, size_t size) { + if (!m_config.use_multicast()) + return; NetworkAddress na; na.port = P2PProtocolBasic::parse_multicast_announce( data, size, m_config.network_id, m_block_chain.get_currency().genesis_block_hash); @@ -398,7 +402,9 @@ bool Node::on_get_archive(http::Client *, http::RequestBody &&http_request, json return true; } -static void fill_transaction_info(const TransactionPrefix &tx, api::Transaction *api_tx) { +// mixed_public_keys can be null if keys not needed +void Node::fill_transaction_info( + const TransactionPrefix &tx, api::Transaction *api_tx, std::vector> *mixed_public_keys) { api_tx->unlock_block_or_timestamp = tx.unlock_block_or_timestamp; api_tx->extra = tx.extra; api_tx->anonymity = std::numeric_limits::max(); @@ -410,6 +416,8 @@ static void fill_transaction_info(const TransactionPrefix &tx, api::Transaction const InputKey &in = boost::get(input); api_tx->anonymity = std::min(api_tx->anonymity, in.output_indexes.size() - 1); input_amount += in.amount; + if (mixed_public_keys) + mixed_public_keys->push_back(m_block_chain.get_mixed_public_keys(in)); } } Amount output_amount = get_tx_sum_outputs(tx); @@ -471,7 +479,7 @@ bool Node::on_sync_blocks(http::Client *, http::RequestBody &&, json_rpc::Reques res_block.transactions.at(0).hash = get_transaction_hash(block.header.base_transaction); res_block.transactions.at(0).size = seria::binary_size(block.header.base_transaction); if (req.need_redundant_data) { - fill_transaction_info(block.header.base_transaction, &res_block.transactions.at(0)); + fill_transaction_info(block.header.base_transaction, &res_block.transactions.at(0), nullptr); res_block.transactions.at(0).block_height = start_height + static_cast(i); res_block.transactions.at(0).block_hash = bhash; res_block.transactions.at(0).coinbase = true; @@ -483,16 +491,15 @@ bool Node::on_sync_blocks(http::Client *, http::RequestBody &&, json_rpc::Reques res_block.transactions.at(tx_index + 1).hash = res_block.raw_header.transaction_hashes.at(tx_index); res_block.transactions.at(tx_index + 1).size = rb.transactions.at(tx_index).size(); if (req.need_redundant_data) { - fill_transaction_info(block.transactions.at(tx_index), &res_block.transactions.at(tx_index + 1)); + fill_transaction_info( + block.transactions.at(tx_index), &res_block.transactions.at(tx_index + 1), nullptr); res_block.transactions.at(tx_index + 1).block_height = start_height + static_cast(i); res_block.transactions.at(tx_index + 1).block_hash = bhash; res_block.transactions.at(tx_index + 1).timestamp = res_block.raw_header.timestamp; } - if (req.need_signatures) - res_block.signatures.push_back(std::move(block.transactions.at(tx_index).signatures)); res_block.raw_transactions.push_back(std::move(block.transactions.at(tx_index))); } - invariant(m_block_chain.read_block_output_global_indices(bhash, &res_block.output_indexes), + invariant(m_block_chain.read_block_output_global_indices(bhash, &res_block.output_stack_indexes), "Invariant dead - bid is in chain but blockchain has no block indices"); } total_size += res_block.header.transactions_size; @@ -514,11 +521,9 @@ bool Node::on_sync_mempool(http::Client *, http::RequestBody &&, json_rpc::Reque for (auto &&tx : pool) if (!std::binary_search(req.known_hashes.begin(), req.known_hashes.end(), tx.first)) { res.added_raw_transactions.push_back(tx.second.tx); - if (req.need_signatures) - res.added_signatures.push_back(tx.second.tx.signatures); res.added_transactions.push_back(api::Transaction{}); if (req.need_redundant_data) - fill_transaction_info(tx.second.tx, &res.added_transactions.back()); + fill_transaction_info(tx.second.tx, &res.added_transactions.back(), nullptr); res.added_transactions.back().hash = tx.first; res.added_transactions.back().timestamp = tx.second.timestamp; res.added_transactions.back().amount = tx.second.amount; @@ -572,7 +577,7 @@ bool Node::on_get_raw_block(http::Client *, http::RequestBody &&, json_rpc::Requ b.transactions.resize(block.transactions.size() + 1); b.transactions.at(0).hash = get_transaction_hash(block.header.base_transaction); b.transactions.at(0).size = seria::binary_size(block.header.base_transaction); - fill_transaction_info(block.header.base_transaction, &b.transactions.at(0)); + fill_transaction_info(block.header.base_transaction, &b.transactions.at(0), nullptr); b.transactions.at(0).block_height = b.header.height; b.transactions.at(0).block_hash = b.header.hash; b.transactions.at(0).coinbase = true; @@ -582,15 +587,13 @@ bool Node::on_get_raw_block(http::Client *, http::RequestBody &&, json_rpc::Requ for (size_t tx_index = 0; tx_index != block.transactions.size(); ++tx_index) { b.transactions.at(tx_index + 1).hash = b.raw_header.transaction_hashes.at(tx_index); b.transactions.at(tx_index + 1).size = rb.transactions.at(tx_index).size(); - fill_transaction_info(block.transactions.at(tx_index), &b.transactions.at(tx_index + 1)); + fill_transaction_info(block.transactions.at(tx_index), &b.transactions.at(tx_index + 1), nullptr); b.transactions.at(tx_index + 1).block_height = b.header.height; b.transactions.at(tx_index + 1).block_hash = b.header.hash; b.transactions.at(tx_index + 1).timestamp = b.raw_header.timestamp; - if (request.need_signatures) - b.signatures.push_back(std::move(block.transactions.at(tx_index).signatures)); b.raw_transactions.push_back(std::move(block.transactions.at(tx_index))); } - m_block_chain.read_block_output_global_indices(request.hash, &b.output_indexes); + m_block_chain.read_block_output_global_indices(request.hash, &b.output_stack_indexes); // If block not in main chain - global indices will be empty response.orphan_status = !m_block_chain.in_chain(b.header.height, b.header.hash); response.depth = api::HeightOrDepth(b.header.height) - api::HeightOrDepth(m_block_chain.get_tip_height()) - 1; @@ -603,9 +606,7 @@ bool Node::on_get_raw_transaction(http::Client *, http::RequestBody &&, json_rpc auto tit = pool.find(req.hash); if (tit != pool.end()) { res.raw_transaction = static_cast(tit->second.tx); - if (req.need_signatures) - res.signatures = tit->second.tx.signatures; - fill_transaction_info(tit->second.tx, &res.transaction); + fill_transaction_info(tit->second.tx, &res.transaction, &res.mixed_public_keys); res.transaction.fee = tit->second.fee; res.transaction.hash = req.hash; res.transaction.block_height = m_block_chain.get_tip_height() + 1; @@ -621,9 +622,7 @@ bool Node::on_get_raw_transaction(http::Client *, http::RequestBody &&, json_rpc res.transaction.size = binary_tx.size(); seria::from_binary(tx, binary_tx); res.raw_transaction = static_cast(tx); - if (req.need_signatures) - res.signatures = tx.signatures; - fill_transaction_info(tx, &res.transaction); + fill_transaction_info(tx, &res.transaction, &res.mixed_public_keys); res.transaction.hash = req.hash; res.transaction.fee = get_tx_fee(res.raw_transaction); // 0 for coinbase return true; @@ -677,20 +676,81 @@ bool Node::on_send_transaction(http::Client *, http::RequestBody &&, json_rpc::R return true; } -bool Node::on_check_sendproof(http::Client *, http::RequestBody &&, json_rpc::Request &&, - api::cnd::CheckSendproof::Request &&request, api::cnd::CheckSendproof::Response &response) { - Sendproof sp; - try { - seria::from_json_value(sp, common::JsonValue::from_string(request.sendproof), m_block_chain.get_currency()); - } catch (const std::exception &ex) { - std::throw_with_nested(api::cnd::CheckSendproof::Error( - api::cnd::CheckSendproof::FAILED_TO_PARSE, "Failed to parse proof object ex.what=" + common::what(ex))); +void Node::check_sendproof(const SendproofKey &sp, api::cnd::CheckSendproof::Response &response) const { + BinaryArray binary_tx; + Height height = 0; + Hash block_hash; + size_t index_in_block = 0; + if (!m_block_chain.get_transaction(sp.transaction_hash, &binary_tx, &height, &block_hash, &index_in_block)) { + throw api::cnd::CheckSendproof::Error( + api::cnd::CheckSendproof::NOT_IN_MAIN_CHAIN, "Transaction is not in main chain"); } + Transaction tx; + seria::from_binary(tx, binary_tx); + const Hash message_hash = crypto::cn_fast_hash(sp.message.data(), sp.message.size()); + if (tx.version >= m_block_chain.get_currency().amethyst_transaction_version) + throw api::cnd::CheckSendproof::Error(api::cnd::CheckSendproof::ADDRESS_NOT_IN_TRANSACTION, + "Legacy proof cannot be used for amethyst transactions"); + AccountAddress address; + if (!m_block_chain.get_currency().parse_account_address_string(sp.address, &address)) + throw api::ErrorAddress( + api::ErrorAddress::ADDRESS_FAILED_TO_PARSE, "Failed to parse sendproof address", sp.address); + if (address.type() != typeid(AccountAddressSimple)) + throw api::cnd::CheckSendproof::Error(api::cnd::CheckSendproof::ADDRESS_NOT_IN_TRANSACTION, + "Transaction version too low to contain address of type other than simple"); + auto &addr = boost::get(address); + PublicKey tx_public_key = extra_get_transaction_public_key(tx.extra); + if (!crypto::check_sendproof(tx_public_key, addr.V, sp.derivation, message_hash, sp.signature)) { + throw api::cnd::CheckSendproof::Error( + api::cnd::CheckSendproof::WRONG_SIGNATURE, "Proof object does not match transaction or was tampered with"); + } + Amount total_amount = 0; + size_t out_index = 0; + for (const auto &output : tx.outputs) { + if (output.type() == typeid(OutputKey)) { + const auto &key_output = boost::get(output); + const PublicKey spend_key = underive_address_S(sp.derivation, out_index, key_output.public_key); + if (spend_key == addr.S) { + total_amount += key_output.amount; + response.output_indexes.push_back(out_index); + } + } + ++out_index; + } + if (total_amount == 0) + throw api::cnd::CheckSendproof::Error( + api::cnd::CheckSendproof::ADDRESS_NOT_IN_TRANSACTION, "No transfers found to proof address"); response.transaction_hash = sp.transaction_hash; - response.address = m_block_chain.get_currency().account_address_as_string(sp.address); + response.address = sp.address; response.message = sp.message; - response.amount = sp.amount; + response.amount = total_amount; +} +void Node::check_sendproof(const BinaryArray &data_inside_base58, api::cnd::CheckSendproof::Response &response) const { + common::MemoryInputStream stream(data_inside_base58.data(), data_inside_base58.size()); + seria::BinaryInputStream ba(stream); + ba.begin_object(); + SendproofAmethyst sp; + try { + seria::ser_members(sp, ba); + } catch (const std::exception &) { + std::throw_with_nested( + api::cnd::CheckSendproof::Error(api::cnd::CheckSendproof::FAILED_TO_PARSE, "Failed to parse proof object")); + } + if (sp.version < m_block_chain.get_currency().amethyst_transaction_version) { + ba.end_object(); + if (!stream.empty()) + throw api::cnd::CheckSendproof::Error( + api::cnd::CheckSendproof::FAILED_TO_PARSE, "Failed to parse proof object - too many bytes"); + SendproofKey spk; + spk.transaction_hash = sp.transaction_hash; + spk.message = sp.message; + spk.address = m_block_chain.get_currency().account_address_as_string(sp.address_simple); + spk.derivation = sp.derivation; + spk.signature = sp.signature; + check_sendproof(spk, response); + return; + } BinaryArray binary_tx; Height height = 0; Hash block_hash; @@ -701,118 +761,105 @@ bool Node::on_check_sendproof(http::Client *, http::RequestBody &&, json_rpc::Re } Transaction tx; seria::from_binary(tx, binary_tx); + if (tx.inputs.empty() || tx.inputs.at(0).type() != typeid(InputKey)) + throw api::cnd::CheckSendproof::Error(api::cnd::CheckSendproof::FAILED_TO_PARSE, + "Proof object invalid, because references coinbase transactions"); + if (tx.version != sp.version) + throw api::cnd::CheckSendproof::Error( + api::cnd::CheckSendproof::ADDRESS_NOT_IN_TRANSACTION, "Proof version wrong for transaction version"); const Hash tx_inputs_hash = get_transaction_inputs_hash(tx); - const Hash message_hash = crypto::cn_fast_hash(sp.message.data(), sp.message.size()); - if (tx.version < m_block_chain.get_currency().amethyst_transaction_version) { - if (sp.address.type() != typeid(AccountAddressSimple)) - throw api::cnd::CheckSendproof::Error(api::cnd::CheckSendproof::ADDRESS_NOT_IN_TRANSACTION, - "Transaction version too low to contain address of type other than simple"); - auto &addr = boost::get(sp.address); - auto &var = boost::get(sp.proof); - PublicKey tx_public_key = extra_get_transaction_public_key(tx.extra); - if (!crypto::check_sendproof( - tx_public_key, addr.view_public_key, var.derivation, message_hash, var.signature)) { - throw api::cnd::CheckSendproof::Error(api::cnd::CheckSendproof::WRONG_SIGNATURE, - "Proof object does not match transaction or was tampered with"); - } - Amount total_amount = 0; - size_t out_index = 0; - for (const auto &output : tx.outputs) { - if (output.type() == typeid(OutputKey)) { - const auto &key_output = boost::get(output); - const PublicKey spend_key = underive_public_key(var.derivation, out_index, key_output.public_key); - if (spend_key == addr.spend_public_key) { - total_amount += key_output.amount; - response.output_indexes.push_back(out_index); - } - } - ++out_index; - } - if (total_amount == 0) - throw api::cnd::CheckSendproof::Error(api::cnd::CheckSendproof::ADDRESS_NOT_IN_TRANSACTION, - "No outputs found in transaction for the address being proofed"); - if (total_amount != sp.amount) - throw api::cnd::CheckSendproof::Error(api::cnd::CheckSendproof::WRONG_AMOUNT, - "Wrong amount in outputs, actual amount is " + common::to_string(total_amount)); - return true; + + const InputKey &in = boost::get(tx.inputs.at(0)); + TransactionPrefix fake_prefix; + fake_prefix.version = tx.version; + fake_prefix.inputs.push_back(in); + RingSignatureAmethyst rsa; + try { + seria::ser_members(rsa, ba, fake_prefix); + } catch (const std::exception &) { + std::throw_with_nested( + api::cnd::CheckSendproof::Error(api::cnd::CheckSendproof::FAILED_TO_PARSE, "Failed to parse proof object")); } - auto &var = boost::get(sp.proof); - for (size_t oi = 1; oi < var.elements.size(); ++oi) { - if (var.elements.at(oi).out_index <= var.elements.at(oi - 1).out_index) + ba.end_object(); + if (!stream.empty()) + throw api::cnd::CheckSendproof::Error( + api::cnd::CheckSendproof::FAILED_TO_PARSE, "Failed to parse proof object - too many bytes"); + + const auto proof_body = seria::to_binary(sp); +// std::cout << "Proof body: " << common::to_hex(proof_body) << std::endl; + const auto proof_prefix_hash = crypto::cn_fast_hash(proof_body); +// std::cout << "Proof hash: " << proof_prefix_hash << std::endl; + + std::vector all_keyimages{in.key_image}; + std::vector> all_output_keys{m_block_chain.get_mixed_public_keys(in)}; + + if (!crypto::check_ring_signature_auditable(proof_prefix_hash, all_keyimages, all_output_keys, rsa)) { + throw api::cnd::CheckSendproof::Error( + api::cnd::CheckSendproof::WRONG_SIGNATURE, "Proof object does not match transaction or was tampered with"); + } + for (size_t oi = 1; oi < sp.elements.size(); ++oi) { + if (sp.elements.at(oi).out_index <= sp.elements.at(oi - 1).out_index) throw api::cnd::CheckSendproof::Error( - api::cnd::CheckSendproof::WRONG_SIGNATURE, "Proof object elements are not in ascending order"); + api::cnd::CheckSendproof::WRONG_SIGNATURE, "Proof object elements are not in strict ascending order"); } - std::reverse(var.elements.begin(), var.elements.end()); // pop_back instead of erase(begin) + std::reverse(sp.elements.begin(), sp.elements.end()); // pop_back instead of erase(begin) Amount total_amount = 0; - for (size_t out_index = 0; out_index != tx.outputs.size() && !var.elements.empty(); ++out_index) { + boost::optional all_addresses; + for (size_t out_index = 0; out_index != tx.outputs.size() && !sp.elements.empty(); ++out_index) { const auto &output = tx.outputs.at(out_index); if (output.type() != typeid(OutputKey)) continue; const auto &key_output = boost::get(output); - if (var.elements.back().out_index != out_index) + if (sp.elements.back().out_index != out_index) continue; - const auto &el = var.elements.back(); - BinaryArray ba(std::begin(el.deterministic_public_key.data), std::end(el.deterministic_public_key.data)); - const SecretKey output_secret_scalar = crypto::hash_to_scalar(ba.data(), ba.size()); - const PublicKey output_secret_point = crypto::hash_to_point(ba.data(), ba.size()); - ba.push_back(0); // Or use cn_fast_hash64 - const Hash output_secret2 = crypto::cn_fast_hash(ba.data(), ba.size()); - - if (sp.address.type() == typeid(AccountAddressSimple)) { - const auto &addr = boost::get(sp.address); - AccountAddressSimple address; - crypto::linkable_underive_address(output_secret_scalar, tx_inputs_hash, out_index, key_output.public_key, - key_output.encrypted_secret, &address.spend_public_key, &address.view_public_key); - if (address != addr) - throw api::cnd::CheckSendproof::Error( - api::cnd::CheckSendproof::WRONG_SIGNATURE, "Proof linkable address derivation failed"); - const uint8_t should_be_encrypted_address_type = AccountAddressSimple::type_tag ^ output_secret2.data[0]; - if (key_output.encrypted_address_type != should_be_encrypted_address_type) - throw api::cnd::CheckSendproof::Error(api::cnd::CheckSendproof::WRONG_SIGNATURE, - "Proof invalid because encrypted address type wrong (protocol violated when sending)"); - if (!crypto::amethyst_check_sendproof(el.deterministic_public_key, sp.transaction_hash, message_hash, - address.spend_public_key, address.view_public_key, el.signature)) - throw api::cnd::CheckSendproof::Error( - api::cnd::CheckSendproof::WRONG_SIGNATURE, "Proof object element signature wrong"); - } else if (sp.address.type() == typeid(AccountAddressUnlinkable)) { - const auto &addr = boost::get(sp.address); - AccountAddressUnlinkable address; - crypto::unlinkable_underive_address(output_secret_point, tx_inputs_hash, out_index, key_output.public_key, - key_output.encrypted_secret, &address.s, &address.sv); - address.is_auditable = key_output.is_auditable; - if (addr.is_auditable != address.is_auditable) - throw api::cnd::CheckSendproof::Error(api::cnd::CheckSendproof::WRONG_SIGNATURE, - "Auditability of coin does not match auditability of address"); - if (address != addr) - throw api::cnd::CheckSendproof::Error( - api::cnd::CheckSendproof::WRONG_SIGNATURE, "Proof unlinkable address derivation failed"); - const uint8_t should_be_encrypted_address_type = - (addr.is_auditable ? AccountAddressUnlinkable::type_tag_auditable - : AccountAddressUnlinkable::type_tag) ^ - output_secret2.data[0]; - if (key_output.encrypted_address_type != should_be_encrypted_address_type) - throw api::cnd::CheckSendproof::Error(api::cnd::CheckSendproof::WRONG_SIGNATURE, - "Proof invalid because encrypted address type wrong (protocol violated when sending)"); - if (!crypto::amethyst_check_sendproof(el.deterministic_public_key, sp.transaction_hash, message_hash, - address.s, address.sv, el.signature)) - throw api::cnd::CheckSendproof::Error( - api::cnd::CheckSendproof::WRONG_SIGNATURE, "Proof object element signature wrong"); - } else + const auto &el = sp.elements.back(); + AccountAddress output_address; + if (!TransactionBuilder::detect_not_our_output_amethyst( + tx_inputs_hash, el.deterministic_public_key, out_index, key_output, &output_address)) { throw api::cnd::CheckSendproof::Error( - api::cnd::CheckSendproof::WRONG_SIGNATURE, "Unknown address type in proof"); + api::cnd::CheckSendproof::ADDRESS_NOT_IN_TRANSACTION, "Cannot underive address for proof output"); + } + if (all_addresses && all_addresses.get() != output_address) { + throw api::cnd::CheckSendproof::Error( + api::cnd::CheckSendproof::ADDRESS_NOT_IN_TRANSACTION, "Send proof address inconsistent"); + } + all_addresses = output_address; total_amount += key_output.amount; - response.output_indexes.push_back(out_index); - var.elements.pop_back(); + sp.elements.pop_back(); } - if (!var.elements.empty()) + if (!sp.elements.empty()) throw api::cnd::CheckSendproof::Error( api::cnd::CheckSendproof::WRONG_SIGNATURE, "Proof object contains excess elements"); - if (total_amount == 0) - throw api::cnd::CheckSendproof::Error(api::cnd::CheckSendproof::ADDRESS_NOT_IN_TRANSACTION, - "No outputs found in transaction for the address being proofed"); - if (total_amount != sp.amount) - throw api::cnd::CheckSendproof::Error(api::cnd::CheckSendproof::WRONG_AMOUNT, - "Wrong amount in outputs, actual amount is " + common::to_string(total_amount)); + if (total_amount == 0 || !all_addresses) + throw api::cnd::CheckSendproof::Error( + api::cnd::CheckSendproof::ADDRESS_NOT_IN_TRANSACTION, "No transfers found to proof address"); + response.transaction_hash = sp.transaction_hash; + response.address = m_block_chain.get_currency().account_address_as_string(all_addresses.get()); + ; + response.message = sp.message; + response.amount = total_amount; +} + +bool Node::on_check_sendproof(http::Client *, http::RequestBody &&, json_rpc::Request &&, + api::cnd::CheckSendproof::Request &&request, api::cnd::CheckSendproof::Response &response) { + uint64_t utag = 0; + BinaryArray data_inside_base58; + if (common::base58::decode_addr(request.sendproof, &utag, &data_inside_base58)) { + if (utag != m_block_chain.get_currency().sendproof_base58_prefix) + throw api::cnd::CheckSendproof::Error( + api::cnd::CheckSendproof::FAILED_TO_PARSE, "Failed to parse proof object, wrong prefix"); + check_sendproof(data_inside_base58, response); + return true; + } + SendproofKey sp; + try { + common::JsonValue jv = common::JsonValue::from_string(request.sendproof); + seria::from_json_value(sp, jv); + } catch (const std::exception &ex) { + std::throw_with_nested(api::cnd::CheckSendproof::Error( + api::cnd::CheckSendproof::FAILED_TO_PARSE, "Failed to parse proof object ex.what=" + common::what(ex))); + } + check_sendproof(sp, response); return true; } @@ -851,6 +898,7 @@ void Node::submit_block(const BinaryArray &blockblob, api::BlockHeader *info) { bool Node::on_submitblock(http::Client *, http::RequestBody &&, json_rpc::Request &&, api::cnd::SubmitBlock::Request &&req, api::cnd::SubmitBlock::Response &res) { if (!req.cm_nonce.empty()) { +#if bytecoin_ALLOW_CM // Experimental, a bit hacky BlockTemplate bt; seria::from_binary(bt, req.blocktemplate_blob); @@ -861,6 +909,10 @@ bool Node::on_submitblock(http::Client *, http::RequestBody &&, json_rpc::Reques // auto body_proxy = get_body_proxy_from_template(bt); // auto cm_prehash = get_auxiliary_block_header_hash(bt, body_proxy); // std::cout << "submit CM data " << body_proxy.transactions_merkle_root << " " << cm_prehash << std::endl; +#else + throw json_rpc::Error{ + api::cnd::SubmitBlock::BLOCK_NOT_ACCEPTED, "Block not accepted, CM mining is not supported"}; +#endif } submit_block(req.blocktemplate_blob, &res.block_header); res.orphan_status = !m_block_chain.in_chain(res.block_header.height, res.block_header.hash); diff --git a/src/Core/Node.hpp b/src/Core/Node.hpp index 6071e4a3..f1c95f48 100644 --- a/src/Core/Node.hpp +++ b/src/Core/Node.hpp @@ -206,6 +206,11 @@ class Node { static std::unordered_map m_jsonrpc_handlers; static const std::unordered_map m_binaryrpc_handlers; + + void fill_transaction_info( + const TransactionPrefix &tx, api::Transaction *api_tx, std::vector> *mixed_public_keys); + void check_sendproof(const BinaryArray &data_inside_base58, api::cnd::CheckSendproof::Response &resp) const; + void check_sendproof(const SendproofKey &sp, api::cnd::CheckSendproof::Response &resp) const; }; } // namespace cn diff --git a/src/Core/NodeLegacyAPI.cpp b/src/Core/NodeLegacyAPI.cpp index c3677879..a9795dcb 100644 --- a/src/Core/NodeLegacyAPI.cpp +++ b/src/Core/NodeLegacyAPI.cpp @@ -146,6 +146,7 @@ void Node::getblocktemplate(const api::cnd::GetBlockTemplate::Request &req, api: res.top_block_hash = m_block_chain.get_tip_bid(); res.transaction_pool_version = m_block_chain.get_tx_pool_version(); res.previous_block_hash = m_block_chain.get_tip().previous_block_hash; +#if bytecoin_ALLOW_CM // Experimental, a bit hacky if (block_template.major_version >= m_block_chain.get_currency().amethyst_block_version) { try { @@ -156,6 +157,7 @@ void Node::getblocktemplate(const api::cnd::GetBlockTemplate::Request &req, api: } catch (const std::exception &) { } } +#endif } bool Node::on_get_currency_id(http::Client *, http::RequestBody &&, json_rpc::Request &&, diff --git a/src/Core/TransactionBuilder.cpp b/src/Core/TransactionBuilder.cpp index e6adfd2c..10e783ea 100644 --- a/src/Core/TransactionBuilder.cpp +++ b/src/Core/TransactionBuilder.cpp @@ -12,39 +12,31 @@ #include "common/Varint.hpp" #include "common/string.hpp" #include "crypto/crypto.hpp" +#include "crypto/crypto_helpers.hpp" #include "http/JsonRpc.hpp" +#include "seria/BinaryInputStream.hpp" #include "seria/BinaryOutputStream.hpp" using namespace cn; - -std::array TransactionBuilder::encrypt_real_index( - const PublicKey &output_key, const SecretKey &view_secret_key) { - std::array result{}; - BinaryArray ba; - common::append(ba, std::begin(view_secret_key.data), std::end(view_secret_key.data)); - common::append(ba, std::begin(output_key.data), std::end(output_key.data)); - Hash ha = crypto::cn_fast_hash(ba.data(), ba.size()); - static_assert(result.size() < sizeof(ha), "Modify line below"); - std::copy(ha.data, ha.data + result.size(), result.begin()); - return result; -} +using namespace common; OutputKey TransactionBuilder::create_output(bool tx_amethyst, const AccountAddress &to, const SecretKey &tx_secret_key, - const Hash &tx_inputs_hash, size_t output_index, const KeyPair &output_det_keys) { + const Hash &tx_inputs_hash, size_t output_index, const PublicKey &output_det_key) { OutputKey out_key; - BinaryArray ba(std::begin(output_det_keys.public_key.data), std::end(output_det_keys.public_key.data)); - SecretKey output_secret_scalar = crypto::hash_to_scalar(ba.data(), ba.size()); - PublicKey output_secret_point = crypto::hash_to_point(ba.data(), ba.size()); - ba.push_back(0); // Or use cn_fast_hash64 - Hash output_secret2 = crypto::cn_fast_hash(ba.data(), ba.size()); + SecretKey output_secret_scalar; + PublicKey output_secret_point; + Hash output_secret_address_type; + TransactionBuilder::generate_output_secrets( + output_det_key, &output_secret_scalar, &output_secret_point, &output_secret_address_type); +// std::cout << "generate_output_secrets=" << output_secret_scalar << " " << output_secret_point << " " +// << output_secret_address_type << std::endl; if (!tx_amethyst) { if (to.type() == typeid(AccountAddressSimple)) { auto &addr = boost::get(to); - const KeyDerivation derivation = crypto::generate_key_derivation(addr.view_public_key, tx_secret_key); - out_key.public_key = crypto::derive_public_key(derivation, output_index, addr.spend_public_key); - out_key.encrypted_address_type = AccountAddressSimple::type_tag ^ output_secret2.data[0]; + const KeyDerivation derivation = crypto::generate_key_derivation(addr.V, tx_secret_key); + out_key.public_key = crypto::derive_output_public_key(derivation, output_index, addr.S); return out_key; } throw std::runtime_error( @@ -52,19 +44,20 @@ OutputKey TransactionBuilder::create_output(bool tx_amethyst, const AccountAddre } if (to.type() == typeid(AccountAddressSimple)) { auto &addr = boost::get(to); - out_key.public_key = crypto::linkable_derive_public_key(output_secret_scalar, tx_inputs_hash, output_index, - addr.spend_public_key, addr.view_public_key, &out_key.encrypted_secret); - out_key.encrypted_address_type = AccountAddressSimple::type_tag ^ output_secret2.data[0]; + out_key.public_key = crypto::linkable_derive_output_public_key( + output_secret_scalar, tx_inputs_hash, output_index, addr.S, addr.V, &out_key.encrypted_secret); + out_key.encrypted_address_type = AccountAddressSimple::type_tag ^ output_secret_address_type.data[0]; +// std::cout << "AccountAddressSimple=" << addr.S << " " << addr.V << " " << out_key.public_key << " " +// << out_key.encrypted_secret << std::endl; return out_key; } if (to.type() == typeid(AccountAddressUnlinkable)) { auto &addr = boost::get(to); - out_key.public_key = crypto::unlinkable_derive_public_key( - output_secret_point, tx_inputs_hash, output_index, addr.s, addr.sv, &out_key.encrypted_secret); - out_key.is_auditable = addr.is_auditable; - out_key.encrypted_address_type = - (addr.is_auditable ? AccountAddressUnlinkable::type_tag_auditable : AccountAddressUnlinkable::type_tag) ^ - output_secret2.data[0]; + out_key.public_key = crypto::unlinkable_derive_output_public_key( + output_secret_point, tx_inputs_hash, output_index, addr.S, addr.Sv, &out_key.encrypted_secret); + out_key.encrypted_address_type = AccountAddressUnlinkable::type_tag ^ output_secret_address_type.data[0]; +// std::cout << "AccountAddressUnlinkable=" << addr.S << " " << addr.Sv << " " << out_key.public_key << " " +// << out_key.encrypted_secret << std::endl; return out_key; } throw std::runtime_error("TransactionBuilder::create_output unknown address type"); @@ -72,7 +65,7 @@ OutputKey TransactionBuilder::create_output(bool tx_amethyst, const AccountAddre bool TransactionBuilder::detect_not_our_output(const Wallet *wallet, bool tx_amethyst, const Hash &tid, const Hash &tx_inputs_hash, boost::optional *history, KeyPair *tx_keys, size_t out_index, - const OutputKey &key_output, Amount *amount, AccountAddress *address) { + const OutputKey &key_output, AccountAddress *address) { if (!tx_amethyst) { if (!*history) *history = wallet->load_history(tid); @@ -80,46 +73,49 @@ bool TransactionBuilder::detect_not_our_output(const Wallet *wallet, bool tx_ame if (tx_keys->secret_key == SecretKey{}) *tx_keys = transaction_keys_from_seed(tx_inputs_hash, wallet->get_tx_derivation_seed()); for (const auto &addr : history->get()) { - const KeyDerivation derivation = generate_key_derivation(addr.view_public_key, tx_keys->secret_key); - const PublicKey guess_key = crypto::derive_public_key(derivation, out_index, addr.spend_public_key); + const KeyDerivation derivation = generate_key_derivation(addr.V, tx_keys->secret_key); + const PublicKey guess_key = crypto::derive_output_public_key(derivation, out_index, addr.S); if (guess_key == key_output.public_key) { *address = addr; - *amount = key_output.amount; return true; } } } return false; } - // In amethyst, we sholud always detect out outputs - const KeyPair output_det_keys = TransactionBuilder::deterministic_keys_from_seed( - tx_inputs_hash, wallet->get_tx_derivation_seed(), common::get_varint_data(out_index)); - BinaryArray ba(std::begin(output_det_keys.public_key.data), std::end(output_det_keys.public_key.data)); - const SecretKey output_secret_scalar = crypto::hash_to_scalar(ba.data(), ba.size()); - const PublicKey output_secret_point = crypto::hash_to_point(ba.data(), ba.size()); - ba.push_back(0); // Or use cn_fast_hash64 - const Hash output_secret2 = crypto::cn_fast_hash(ba.data(), ba.size()); - - const uint8_t address_type = key_output.encrypted_address_type ^ output_secret2.data[0]; + // In amethyst, we should always detect out outputs if we know tx_derivation_seed + PublicKey output_det_public_key; + if (wallet->get_hw()) { + wallet->get_hw()->generate_output_secret(tx_inputs_hash, out_index, &output_det_public_key); + } else { + output_det_public_key = TransactionBuilder::deterministic_keys_from_seed( + tx_inputs_hash, wallet->get_tx_derivation_seed(), common::get_varint_data(out_index)) + .public_key; + } + return detect_not_our_output_amethyst(tx_inputs_hash, output_det_public_key, out_index, key_output, address); +} + +bool TransactionBuilder::detect_not_our_output_amethyst(const Hash &tx_inputs_hash, + const PublicKey &output_det_public_key, size_t out_index, const OutputKey &key_output, AccountAddress *address) { + SecretKey output_secret_scalar; + PublicKey output_secret_point; + Hash output_secret_address_type; + TransactionBuilder::generate_output_secrets( + output_det_public_key, &output_secret_scalar, &output_secret_point, &output_secret_address_type); + + const uint8_t address_type = key_output.encrypted_address_type ^ output_secret_address_type.data[0]; if (address_type == AccountAddressSimple::type_tag) { AccountAddressSimple addr; crypto::linkable_underive_address(output_secret_scalar, tx_inputs_hash, out_index, key_output.public_key, - key_output.encrypted_secret, &addr.spend_public_key, &addr.view_public_key); + key_output.encrypted_secret, &addr.S, &addr.V); *address = addr; - *amount = key_output.amount; return true; } - if (address_type == AccountAddressUnlinkable::type_tag || - address_type == AccountAddressUnlinkable::type_tag_auditable) { - const bool address_auditable = (address_type == AccountAddressUnlinkable::type_tag_auditable); - if (address_auditable != key_output.is_auditable) - return false; + if (address_type == AccountAddressUnlinkable::type_tag) { AccountAddressUnlinkable u_address; - crypto::unlinkable_underive_address(output_secret_point, tx_inputs_hash, out_index, key_output.public_key, - key_output.encrypted_secret, &u_address.s, &u_address.sv); - u_address.is_auditable = key_output.is_auditable; - *amount = key_output.amount; - *address = u_address; + crypto::unlinkable_underive_address(&u_address.S, &u_address.Sv, output_secret_point, tx_inputs_hash, out_index, + key_output.public_key, key_output.encrypted_secret); + *address = u_address; return true; } return false; @@ -129,8 +125,12 @@ void TransactionBuilder::add_output(uint64_t amount, const AccountAddress &to) { m_output_descs.push_back(OutputDesc{amount, to}); } -static bool APIOutputLessGlobalIndex(const api::Output &a, const api::Output &b) { return a.index < b.index; } -static bool APIOutputEqualGlobalIndex(const api::Output &a, const api::Output &b) { return a.index == b.index; } +static bool APIOutputLessGlobalIndex(const api::Output &a, const api::Output &b) { + return a.stack_index < b.stack_index; +} +static bool APIOutputEqualGlobalIndex(const api::Output &a, const api::Output &b) { + return a.stack_index == b.stack_index; +} void TransactionBuilder::add_input(const std::vector &mix_outputs, size_t real_output_index) { m_input_descs.push_back(InputDesc{mix_outputs, real_output_index}); @@ -166,56 +166,80 @@ KeyPair TransactionBuilder::deterministic_keys_from_seed( return deterministic_keys_from_seed(tx_inputs_hash, tx_derivation_seed, add); } +void TransactionBuilder::generate_output_secrets(const PublicKey &output_det_key, SecretKey *output_secret_scalar, + PublicKey *output_secret_point, Hash *output_secret_address_type) { + BinaryArray ss = output_det_key.as_binary_array() | common::as_binary_array("output_secret_point"); + BinaryArray sp = output_det_key.as_binary_array() | common::as_binary_array("output_secret_scalar"); + BinaryArray sat = output_det_key.as_binary_array() | common::as_binary_array("address_type"); + *output_secret_scalar = crypto::hash_to_scalar(ss.data(), ss.size()); + *output_secret_point = crypto::hash_to_good_point(sp.data(), sp.size()); + *output_secret_address_type = crypto::cn_fast_hash(sat.data(), sat.size()); +} + Transaction TransactionBuilder::sign( const WalletStateBasic &wallet_state, Wallet *wallet, const std::set *only_records) { std::shuffle(m_output_descs.begin(), m_output_descs.end(), crypto::random_engine{}); std::shuffle(m_input_descs.begin(), m_input_descs.end(), crypto::random_engine{}); std::stable_sort(m_output_descs.begin(), m_output_descs.end(), OutputDesc::less_amount); std::stable_sort(m_input_descs.begin(), m_input_descs.end(), InputDesc::less_amount); + std::cout << "TransactionBuilder::sign" << std::endl << std::endl; + if (wallet->get_hw()) { + size_t change_record_index = 0; + bool change_record_index_set = false; + boost::optional destination_address; + for (size_t out_index = 0; out_index != m_output_descs.size(); ++out_index) { + size_t record_index = 0; + WalletRecord record; + if (wallet->get_record(m_output_descs.at(out_index).addr, &record_index, &record)) { + if (change_record_index_set && change_record_index != record_index) + throw std::runtime_error("You can have max 1 change address when sending via hardware wallet"); + change_record_index = record_index; + change_record_index_set = true; + } else { + if (destination_address && destination_address.get() != m_output_descs.at(out_index).addr) + throw std::runtime_error("You can send to max 1 address via hardware wallet"); + destination_address = m_output_descs.at(out_index).addr; + } + } + AccountAddress to = destination_address ? destination_address.get() : AccountAddress{}; + if (to.type() == typeid(AccountAddressSimple)) { + auto &addr = boost::get(to); + wallet->get_hw()->sign_start(m_transaction.version, m_transaction.unlock_block_or_timestamp, + m_input_descs.size(), m_output_descs.size(), m_transaction.extra.size(), change_record_index, + AccountAddressSimple::type_tag, addr.S, addr.V); + } else if (to.type() == typeid(AccountAddressUnlinkable)) { + auto &addr = boost::get(to); + wallet->get_hw()->sign_start(m_transaction.version, m_transaction.unlock_block_or_timestamp, + m_input_descs.size(), m_output_descs.size(), m_transaction.extra.size(), change_record_index, + AccountAddressUnlinkable::type_tag, addr.S, addr.Sv); + } else + throw std::runtime_error("TransactionBuilder::sign unknown address type"); + } const bool is_tx_amethyst = m_transaction.version >= wallet_state.get_currency().amethyst_transaction_version; // First we create inputs, because we need tx_inputs_hash m_transaction.inputs.reserve(m_input_descs.size()); - for (size_t i = 0; i != m_input_descs.size(); ++i) { - const InputDesc &desc = m_input_descs[i]; - const api::Output &our_output = desc.outputs.at(desc.real_output_index); - InputKey input_key; - input_key.key_image = our_output.key_image; - input_key.amount = our_output.amount; - for (const auto &o : desc.outputs) - input_key.output_indexes.push_back(o.index); - input_key.output_indexes = absolute_output_offsets_to_relative(input_key.output_indexes); - input_key.encrypted_real_index = encrypt_real_index(our_output.public_key, wallet->get_view_secret_key()); - m_transaction.inputs.push_back(input_key); - } - // Deterministic generation of tx private key. - const Hash tx_inputs_hash = get_transaction_inputs_hash(m_transaction); - const KeyPair tx_keys = transaction_keys_from_seed(tx_inputs_hash, wallet->get_tx_derivation_seed()); - if (!is_tx_amethyst) - extra_add_transaction_public_key(m_transaction.extra, tx_keys.public_key); - // Now when we set tx keys we can derive output keys - m_transaction.outputs.resize(m_output_descs.size()); - for (size_t out_index = 0; out_index != m_output_descs.size(); ++out_index) { - KeyPair output_det_keys = deterministic_keys_from_seed( - tx_inputs_hash, wallet->get_tx_derivation_seed(), common::get_varint_data(out_index)); - OutputKey out_key = TransactionBuilder::create_output(is_tx_amethyst, m_output_descs.at(out_index).addr, - tx_keys.secret_key, tx_inputs_hash, out_index, output_det_keys); - out_key.amount = m_output_descs.at(out_index).amount; - m_transaction.outputs.at(out_index) = out_key; - } - // Now we can sign - const Hash hash = get_transaction_prefix_hash(m_transaction); - std::vector all_secret_keys; + std::vector all_secret_keys_s; // empty if hw + std::vector all_secret_keys_a; // empty if hw std::vector all_sec_indexes; std::vector all_keyimages; std::vector> all_output_keys; + std::vector spend_scalars; // for hw + std::vector address_indexes; for (size_t i = 0; i != m_input_descs.size(); ++i) { const InputDesc &desc = m_input_descs[i]; const api::Output &our_output = desc.outputs.at(desc.real_output_index); std::vector output_keys; for (const auto &o : desc.outputs) output_keys.push_back(o.public_key); + InputKey input_key; + input_key.key_image = our_output.key_image; + input_key.amount = our_output.amount; + for (const auto &o : desc.outputs) + input_key.output_indexes.push_back(o.stack_index); + input_key.output_indexes = absolute_output_offsets_to_relative(input_key.output_indexes); + TransactionPrefix ptx; api::Transaction atx; invariant(wallet_state.get_transaction(our_output.transaction_hash, &ptx, &atx) && @@ -227,37 +251,135 @@ Transaction TransactionBuilder::sign( if (!wallet_state.get_currency().parse_account_address_string(our_output.address, &address)) throw json_rpc::Error(json_rpc::INTERNAL_ERROR, "Could not parse address " + our_output.address); invariant(!only_records || only_records->count(address) != 0, "Output with wrong address selected by selector"); + size_t record_index = 0; WalletRecord record; - if (!wallet->get_record(&record, address)) + if (!wallet->get_record(address, &record_index, &record)) throw json_rpc::Error(json_rpc::INTERNAL_ERROR, "No keys in wallet for address " + our_output.address); - KeyPair output_keypair; - boost::optional kd; - PublicKey spend_public_key; - SecretKey spend_secret; - wallet->get_output_handler()(atx.public_key, &kd, other_inputs_hash, our_output.index_in_transaction, - key_output, &spend_public_key, &spend_secret); - Amount other_amount = 0; - AccountAddress other_address; - if (!wallet->detect_our_output(atx.hash, other_inputs_hash, kd, our_output.index_in_transaction, - spend_public_key, spend_secret, key_output, &other_amount, &output_keypair, &other_address)) - throw json_rpc::Error(json_rpc::INTERNAL_ERROR, "No keys in wallet for address " + our_output.address); - const KeyImage other_key_image = generate_key_image(output_keypair.public_key, output_keypair.secret_key); - invariant(other_key_image == our_output.key_image, "generated key_image does not match input"); +// std::cout << "record.spend_public_key[" << i << "]=" << record.spend_public_key << std::endl; +// std::cout << "record.spend_secret_key[" << i << "]=" << record.spend_secret_key << std::endl; all_keyimages.push_back(our_output.key_image); - all_output_keys.push_back(std::move(output_keys)); - all_secret_keys.push_back(output_keypair.secret_key); + all_output_keys.push_back(output_keys); all_sec_indexes.push_back(desc.real_output_index); + address_indexes.push_back(record_index); + if (wallet->get_hw()) { + auto mulled_key = wallet->get_hw()->mul_by_view_secret_key({key_output.public_key}); + SecretKey spend_scalar; + crypto::unlinkable_underive_address_S_step2(mulled_key.at(0), other_inputs_hash, + our_output.index_in_transaction, key_output.public_key, key_output.encrypted_secret, &spend_scalar); + spend_scalars.push_back(spend_scalar); + wallet->get_hw()->add_input( + input_key.amount, input_key.output_indexes, crypto::sc_invert(spend_scalar), record_index); + } else { + SecretKey output_secret_key_s; + SecretKey output_secret_key_a; + boost::optional kd; + PublicKey address_S; + SecretKey spend_scalar; + wallet->get_output_handler()(ptx.version, atx.public_key, &kd, other_inputs_hash, + our_output.index_in_transaction, key_output, &address_S, &spend_scalar); + Amount other_amount = 0; + AccountAddress other_address; + size_t record_index = 0; + KeyImage other_key_image; + if (!wallet->detect_our_output(ptx.version, atx.hash, other_inputs_hash, kd, + our_output.index_in_transaction, address_S, spend_scalar, key_output, &other_amount, + &output_secret_key_s, &output_secret_key_a, &other_address, &record_index, &other_key_image)) + throw json_rpc::Error(json_rpc::INTERNAL_ERROR, "No keys in wallet for address " + our_output.address); + invariant(other_key_image == our_output.key_image, "generated key_image does not match input"); + spend_scalars.push_back(spend_scalar); + all_secret_keys_s.push_back(output_secret_key_s); + all_secret_keys_a.push_back(output_secret_key_a); + } + m_transaction.inputs.push_back(input_key); + } + // Deterministic generation of tx private key. + const Hash tx_inputs_hash = get_transaction_inputs_hash(m_transaction); +// std::cout << "tx_inputs_hash=" << tx_inputs_hash << std::endl; + const KeyPair tx_keys = transaction_keys_from_seed(tx_inputs_hash, wallet->get_tx_derivation_seed()); + + if (!is_tx_amethyst) // Never in case of hw, because we set extra size beforehand + extra_add_transaction_public_key(m_transaction.extra, tx_keys.public_key); + // Now when we set tx keys we can derive output keys + m_transaction.outputs.resize(m_output_descs.size()); + for (size_t out_index = 0; out_index != m_output_descs.size(); ++out_index) { + OutputKey out_key; + Amount amount = m_output_descs.at(out_index).amount; + if (wallet->get_hw()) { + bool is_change = wallet->is_our_address(m_output_descs.at(out_index).addr); + wallet->get_hw()->add_output( + is_change, amount, &out_key.public_key, &out_key.encrypted_secret, &out_key.encrypted_address_type); + } else { + KeyPair output_det_keys = deterministic_keys_from_seed( + tx_inputs_hash, wallet->get_tx_derivation_seed(), common::get_varint_data(out_index)); + out_key = TransactionBuilder::create_output(is_tx_amethyst, m_output_descs.at(out_index).addr, + tx_keys.secret_key, tx_inputs_hash, out_index, output_det_keys.public_key); + } + out_key.amount = amount; + m_transaction.outputs.at(out_index) = out_key; } - if (is_tx_amethyst) { - const RingSignature3 ring_signatures3 = generate_ring_signature3( - hash, all_keyimages, all_output_keys, all_secret_keys, all_sec_indexes, wallet->get_view_secret_key()); - m_transaction.signatures = std::move(ring_signatures3); + if (wallet->get_hw()) { + wallet->get_hw()->add_extra(m_transaction.extra); + } +/* for (size_t i = 0; i != m_input_descs.size(); ++i) { + if (i < all_secret_keys_s.size()) + std::cout << "all_secret_keys_s[" << i << "]=" << all_secret_keys_s.at(i) << std::endl; + if (i < all_secret_keys_a.size()) + std::cout << "all_secret_keys_a[" << i << "]=" << all_secret_keys_a.at(i) << std::endl; + std::cout << "all_keyimages[" << i << "]=" << all_keyimages.at(i) << std::endl; + std::cout << "spend_scalars[" << i << "]=" << spend_scalars.at(i) << std::endl; + std::cout << "address_indexes[" << i << "]=" << address_indexes.at(i) << std::endl; + }*/ + const Hash tx_prefix_hash = get_transaction_prefix_hash(m_transaction); +// std::cout << "tx_prefix_hash=" << tx_prefix_hash << std::endl; + if (wallet->get_hw()) { + RingSignatureAmethyst rsa; + rsa.p.resize(m_input_descs.size()); + rsa.ra.resize(m_input_descs.size()); + rsa.rb.resize(m_input_descs.size()); + rsa.rc.resize(m_input_descs.size()); + + for (size_t i = 0; i != m_input_descs.size(); ++i) { + PublicKey x; + PublicKey y; + wallet->get_hw()->add_sig_a(crypto::sc_invert(spend_scalars[i]), address_indexes[i], &rsa.p.at(i), &x, &y); + + const crypto::P3 b_coin_p3(hash_to_good_point_p3(all_keyimages[i])); + const crypto::P3 p_p3(rsa.p.at(i)); + const crypto::P3 G_plus_B_p3 = crypto::P3(crypto::G) + b_coin_p3; + const crypto::P3 image_p3(all_keyimages[i]); + + crypto::generate_ring_signature_auditable_loop1(i, tx_prefix_hash, image_p3, p_p3, G_plus_B_p3, + all_sec_indexes[i], all_output_keys[i], &rsa.ra[i], &x, &y); + BinaryArray ba = x.as_binary_array() | y.as_binary_array(); + for (const auto &pk : all_output_keys.at(i)) + ba |= pk.as_binary_array(); + wallet->get_hw()->add_sig_a_more_data(ba, &rsa.c0); + } + for (size_t i = 0; i != m_input_descs.size(); ++i) { + const crypto::P3 b_coin_p3(hash_to_good_point_p3(all_keyimages[i])); + const crypto::P3 p_p3(rsa.p.at(i)); + const crypto::P3 G_plus_B_p3 = crypto::P3(crypto::G) + b_coin_p3; + const crypto::P3 image_p3(all_keyimages[i]); + + crypto::EllipticCurveScalar next_c = rsa.c0; + crypto::generate_ring_signature_auditable_loop2(i, tx_prefix_hash, image_p3, p_p3, G_plus_B_p3, + all_sec_indexes[i], all_output_keys[i], &rsa.ra[i], &next_c); + wallet->get_hw()->add_sig_b(crypto::sc_invert(spend_scalars[i]), address_indexes[i], next_c, + &rsa.ra.at(i).at(all_sec_indexes.at(i)), &rsa.rb.at(i), &rsa.rc.at(i)); + } + invariant(crypto::check_ring_signature_auditable(tx_prefix_hash, all_keyimages, all_output_keys, rsa), ""); + m_transaction.signatures = std::move(rsa); + } else if (is_tx_amethyst) { + const RingSignatureAmethyst rsa = generate_ring_signature_auditable( + tx_prefix_hash, all_keyimages, all_output_keys, all_secret_keys_s, all_secret_keys_a, all_sec_indexes); + invariant(crypto::check_ring_signature_auditable(tx_prefix_hash, all_keyimages, all_output_keys, rsa), ""); + m_transaction.signatures = std::move(rsa); } else { RingSignatures ring_signatures; for (size_t i = 0; i != m_input_descs.size(); ++i) { const RingSignature signature = - generate_ring_signature(hash, all_keyimages.at(i), all_output_keys.at(i).data(), - all_output_keys.at(i).size(), all_secret_keys.at(i), all_sec_indexes.at(i)); + generate_ring_signature(tx_prefix_hash, all_keyimages.at(i), all_output_keys.at(i).data(), + all_output_keys.at(i).size(), all_secret_keys_a.at(i), all_sec_indexes.at(i)); ring_signatures.signatures.push_back(signature); } m_transaction.signatures = std::move(ring_signatures); @@ -295,7 +417,7 @@ size_t UnspentSelector::add_mixed_inputs( int best_distance = 0; size_t best_index = mix_outputs.size(); for (size_t i = 0; i != mix_outputs.size(); ++i) { - int distance = abs(int(uu.index) - int(mix_outputs[i].index)); + int distance = abs(int(uu.stack_index) - int(mix_outputs[i].stack_index)); if (best_index == mix_outputs.size() || distance < best_distance) { best_index = i; best_distance = distance; diff --git a/src/Core/TransactionBuilder.hpp b/src/Core/TransactionBuilder.hpp index a35c5109..5acaad05 100644 --- a/src/Core/TransactionBuilder.hpp +++ b/src/Core/TransactionBuilder.hpp @@ -45,12 +45,15 @@ class TransactionBuilder { const Hash &tx_inputs_hash, const Hash &tx_derivation_seed, const BinaryArray &add); static KeyPair deterministic_keys_from_seed( const TransactionPrefix &tx, const Hash &tx_derivation_seed, const BinaryArray &add); - static std::array encrypt_real_index(const PublicKey &output_key, const SecretKey &view_secret_key); + static void generate_output_secrets(const PublicKey &output_det_key, crypto::SecretKey *output_secret_scalar, + crypto::PublicKey *output_secret_point, crypto::Hash *output_secret_address_type); static OutputKey create_output(bool tx_amethyst, const AccountAddress &to, const SecretKey &tx_secret_key, - const Hash &tx_inputs_hash, size_t output_index, const KeyPair &output_det_keys); + const Hash &tx_inputs_hash, size_t output_index, const PublicKey &output_det_key); static bool detect_not_our_output(const Wallet *wallet, bool tx_amethyst, const Hash &tid, const Hash &tx_inputs_hash, boost::optional *, KeyPair *tx_keys, size_t out_index, - const OutputKey &, Amount *, AccountAddress *); + const OutputKey &, AccountAddress *); + static bool detect_not_our_output_amethyst(const Hash &tx_inputs_hash, const PublicKey &output_det_public_key, + size_t out_index, const OutputKey &, AccountAddress *); }; class UnspentSelector { diff --git a/src/Core/Wallet.cpp b/src/Core/Wallet.cpp index bc041338..326ae9a2 100644 --- a/src/Core/Wallet.cpp +++ b/src/Core/Wallet.cpp @@ -1,23 +1,23 @@ // Copyright (c) 2012-2018, The CryptoNote developers, The Bytecoin developers. // Licensed under the GNU Lesser General Public License. See LICENSE for details. -#include -#include -#include -#include +//#include +//#include +//#include +//#include #include #include "CryptoNoteTools.hpp" #include "TransactionBuilder.hpp" #include "WalletSerializationV1.hpp" #include "WalletState.hpp" #include "common/BIPs.hpp" -#include "common/CRC32.hpp" #include "common/Math.hpp" #include "common/MemoryStreams.hpp" #include "common/StringTools.hpp" #include "common/Varint.hpp" #include "common/Words.hpp" #include "crypto/crypto.hpp" +#include "crypto/crypto_helpers.hpp" #include "http/JsonRpc.hpp" #include "platform/Files.hpp" #include "platform/PathTools.hpp" @@ -26,47 +26,7 @@ #include "seria/BinaryOutputStream.hpp" using namespace cn; - -BinaryArray &operator|=(BinaryArray &a, const BinaryArray &b) { - common::append(a, b); - return a; -} - -BinaryArray operator|(const BinaryArray &a, const BinaryArray &b) { - BinaryArray tmp(a); - return tmp |= b; -} - -BinaryArray &operator|=(BinaryArray &a, const std::string &b) { return a |= common::as_binary_array(b); } - -BinaryArray operator|(const BinaryArray &a, const std::string &b) { - BinaryArray tmp(a); - return tmp |= b; -} - -BinaryArray &operator|=(BinaryArray &a, const Hash &b) { - common::append(a, std::begin(b.data), std::end(b.data)); - return a; -} - -BinaryArray operator|(const BinaryArray &a, const Hash &b) { - BinaryArray tmp(a); - return tmp |= b; -} - -BinaryArray &operator|=(const Hash &b, BinaryArray &a) { - common::append(a, std::begin(b.data), std::end(b.data)); - return a; -} - -BinaryArray operator|(const Hash &a, const BinaryArray &b) { - BinaryArray tmp(std::begin(a.data), std::end(a.data)); - return tmp |= b; -} -BinaryArray operator|(const Hash &a, const std::string &b) { - BinaryArray tmp(std::begin(a.data), std::end(a.data)); - return tmp |= b; -} +using namespace common; static std::string net_append(const std::string &net) { return net == "main" ? std::string() : "_" + net + "net"; } @@ -74,23 +34,16 @@ Wallet::Wallet(const Currency ¤cy, logging::ILogger &log, const std::strin : m_currency(currency), m_log(log, "Wallet"), m_path(path) {} static Hash derive_from_seed_legacy(const Hash &seed, const std::string &append) { - BinaryArray seed_data = common::as_binary_array(append); - common::append(seed_data, std::begin(seed.data), std::end(seed.data)); - return crypto::cn_fast_hash(seed_data.data(), seed_data.size()); -} - -static Hash derive_from_seed(const Hash &seed, const std::string &append) { - BinaryArray seed_data = seed | append; + BinaryArray seed_data = as_binary_array(append) | seed.as_binary_array(); return crypto::cn_fast_hash(seed_data.data(), seed_data.size()); } static Hash derive_from_key(const crypto::chacha_key &key, const std::string &append) { - BinaryArray seed_data = BinaryArray(key.data, key.data + sizeof(key.data)) | append; - common::append(seed_data, std::begin(key.data), std::end(key.data)); + BinaryArray seed_data = key.as_binary_array() | as_binary_array(append); return crypto::cn_fast_hash(seed_data.data(), seed_data.size()); } -AccountAddress Wallet::get_first_address() const { return record_to_address(m_wallet_records.at(0)); } +AccountAddress Wallet::get_first_address() const { return record_to_address(0); } std::string Wallet::get_cache_name() const { Hash h = crypto::cn_fast_hash(m_view_public_key.data, sizeof(m_view_public_key.data)); @@ -104,16 +57,28 @@ std::string Wallet::get_cache_name() const { return name; } -bool Wallet::get_look_ahead_record(WalletRecord &record, const PublicKey &spend_public_key) { - auto rit = m_records_map.find(spend_public_key); +bool Wallet::get_look_ahead_record( + const PublicKey &address_S, size_t *index, WalletRecord *record, AccountAddress *address) { + auto rit = m_records_map.find(address_S); if (rit == m_records_map.end()) return false; - invariant(m_wallet_records.at(rit->second).spend_public_key == spend_public_key, ""); - record = m_wallet_records.at(rit->second); + invariant(m_wallet_records.at(rit->second).spend_public_key == address_S, ""); + *index = rit->second; + *record = m_wallet_records.at(rit->second); + *address = record_to_address(*index); create_look_ahead_records(rit->second + 1); return true; } +bool Wallet::get_record(size_t index, WalletRecord *record, AccountAddress *address) const { + if (index >= get_actual_records_count()) + return false; + *record = m_wallet_records.at(index); + if (address) + *address = record_to_address(index); + return true; +} + static const uint8_t SERIALIZATION_VERSION_V2 = 6; static const size_t CHECK_KEYS_COUNT = 128; // >8 KB checked at start and end of file @@ -143,7 +108,7 @@ static void decrypt_key_pair( memcpy(pk.data, rec_data, sizeof(PublicKey)); memcpy(sk.data, rec_data + sizeof(PublicKey), sizeof(SecretKey)); ct = static_cast( - common::uint_le_from_bytes(rec_data + sizeof(PublicKey) + sizeof(SecretKey), sizeof(uint64_t))); + uint_le_from_bytes(rec_data + sizeof(PublicKey) + sizeof(SecretKey), sizeof(uint64_t))); } static void encrypt_key_pair( @@ -151,14 +116,15 @@ static void encrypt_key_pair( unsigned char rec_data[sizeof(r.data)]{}; memcpy(rec_data, pk.data, sizeof(PublicKey)); memcpy(rec_data + sizeof(PublicKey), sk.data, sizeof(SecretKey)); - common::uint_le_to_bytes(rec_data + sizeof(PublicKey) + sizeof(SecretKey), sizeof(uint64_t), ct); + uint_le_to_bytes(rec_data + sizeof(PublicKey) + sizeof(SecretKey), sizeof(uint64_t), ct); r.iv = crypto::rand(); chacha8(&rec_data, sizeof(r.data), key, r.iv, r.data); } bool Wallet::is_our_address(const AccountAddress &v_addr) const { + size_t index = 0; WalletRecord wr; - return get_record(&wr, v_addr); + return get_record(v_addr, &index, &wr); } size_t WalletContainerStorage::wallet_file_size(size_t records) { @@ -172,9 +138,8 @@ void WalletContainerStorage::load_container_storage() { m_file->read(&version, 1); m_file->read(&prefix, sizeof(prefix)); m_file->read(count_capacity_data, 2 * sizeof(uint64_t)); - uint64_t f_item_capacity = common::uint_le_from_bytes(count_capacity_data, sizeof(uint64_t)); - uint64_t f_item_count = - common::uint_le_from_bytes(count_capacity_data + sizeof(uint64_t), sizeof(uint64_t)); + uint64_t f_item_capacity = uint_le_from_bytes(count_capacity_data, sizeof(uint64_t)); + uint64_t f_item_count = uint_le_from_bytes(count_capacity_data + sizeof(uint64_t), sizeof(uint64_t)); if (version < SERIALIZATION_VERSION_V2) throw Exception(api::WALLET_FILE_DECRYPT_ERROR, "Wallet version too old"); @@ -186,7 +151,7 @@ void WalletContainerStorage::load_container_storage() { throw Exception(api::WALLET_FILE_DECRYPT_ERROR, "Restored view public key doesn't correspond to secret key"); const size_t item_count = - common::integer_cast(std::min(f_item_count, f_item_capacity)); // Protection against write shredding + integer_cast(std::min(f_item_count, f_item_capacity)); // Protection against write shredding if (item_count > std::numeric_limits::max() / sizeof(EncryptedWalletRecord)) throw Exception( api::WALLET_FILE_DECRYPT_ERROR, "Restored item count is too big " + common::to_string(item_count)); @@ -212,7 +177,7 @@ void WalletContainerStorage::load_container_storage() { throw Exception( api::WALLET_FILE_DECRYPT_ERROR, "Restored spend public key doesn't correspond to secret key"); } else { - if (!key_isvalid(wallet_record.spend_public_key)) { + if (!key_in_main_subgroup(wallet_record.spend_public_key)) { throw Exception(api::WALLET_FILE_DECRYPT_ERROR, "Public spend key is incorrect"); } } @@ -262,8 +227,8 @@ WalletContainerStorage::WalletContainerStorage(const Currency ¤cy, logging crypto::CryptoNightContext cn_ctx; m_wallet_key = generate_chacha8_key(cn_ctx, password.data(), password.size()); // try { - m_file.reset(new platform::FileStream(path, platform::O_CREATE_NEW)); - // } catch (const common::StreamError &) { + m_file = std::make_unique(path, platform::O_CREATE_NEW); + // } catch (const StreamError &) { // file does not exist // } // if (file.get()) // opened ok @@ -281,10 +246,10 @@ WalletContainerStorage::WalletContainerStorage(const Currency ¤cy, logging throw Exception(api::WALLET_FILE_DECRYPT_ERROR, "Imported keys should be exactly 128 hex bytes"); WalletRecord record{}; record.creation_timestamp = creation_timestamp; - if (!common::pod_from_hex(import_keys.substr(0, 64), &record.spend_public_key) || - !common::pod_from_hex(import_keys.substr(64, 64), &m_view_public_key) || - !common::pod_from_hex(import_keys.substr(128, 64), &record.spend_secret_key) || - !common::pod_from_hex(import_keys.substr(192, 64), &m_view_secret_key)) + if (!pod_from_hex(import_keys.substr(0, 64), &record.spend_public_key) || + !pod_from_hex(import_keys.substr(64, 64), &m_view_public_key) || + !pod_from_hex(import_keys.substr(128, 64), &record.spend_secret_key) || + !pod_from_hex(import_keys.substr(192, 64), &m_view_secret_key)) throw Exception(api::WALLET_FILE_DECRYPT_ERROR, "Imported keys should contain only hex bytes"); if (!keys_match(m_view_secret_key, m_view_public_key)) throw Exception( @@ -296,7 +261,9 @@ WalletContainerStorage::WalletContainerStorage(const Currency ¤cy, logging m_oldest_timestamp = 0; // Alas, will scan entire blockchain } m_records_map.insert(std::make_pair(m_wallet_records.at(0).spend_public_key, 0)); - save_and_check(); + save_and_check(); // TODO - better swap in save_and_check + m_wallet_records.clear(); + m_records_map.clear(); load(); } @@ -310,9 +277,9 @@ WalletContainerStorage::WalletContainerStorage( void WalletContainerStorage::load() { try { - m_file.reset(new platform::FileStream(m_path, platform::O_OPEN_EXISTING)); - } catch (const common::StreamError &) { // Read-only media? - m_file.reset(new platform::FileStream(m_path, platform::O_READ_EXISTING)); + m_file = std::make_unique(m_path, platform::O_OPEN_EXISTING); + } catch (const StreamError &) { // Read-only media? + m_file = std::make_unique(m_path, platform::O_READ_EXISTING); } uint8_t version = 0; m_file->read(&version, 1); @@ -322,7 +289,7 @@ void WalletContainerStorage::load() { if (version < SERIALIZATION_VERSION_V2) { try { load_legacy_wallet_file(); - } catch (const common::StreamError &ex) { + } catch (const StreamError &ex) { std::throw_with_nested( Exception(api::WALLET_FILE_READ_ERROR, std::string("Error reading wallet file ") + common::what(ex))); } catch (const std::exception &ex) { @@ -341,12 +308,11 @@ void WalletContainerStorage::load() { if (m_wallet_records.empty()) throw Exception(api::WALLET_FILE_DECRYPT_ERROR, "Error reading wallet file"); + m_inv_view_secret_key = sc_invert(m_view_secret_key); if (!is_view_only()) { - BinaryArray seed_data; - seed_data.assign(std::begin(m_view_secret_key.data), std::end(m_view_secret_key.data)); - common::append(seed_data, std::begin(m_wallet_records.at(0).spend_secret_key.data), - std::end(m_wallet_records.at(0).spend_secret_key.data)); - m_seed = crypto::cn_fast_hash(seed_data.data(), seed_data.size()); + BinaryArray seed_data = m_view_secret_key.as_binary_array(); + seed_data |= m_wallet_records.at(0).spend_secret_key.as_binary_array(); + m_seed = crypto::cn_fast_hash(seed_data); m_tx_derivation_seed = derive_from_seed_legacy(m_seed, "tx_derivation"); m_history_filename_seed = derive_from_seed_legacy(m_seed, "history_filename"); m_history_key = crypto::chacha_key{derive_from_seed_legacy(m_seed, "history")}; @@ -361,7 +327,7 @@ void WalletContainerStorage::save(const std::string &export_path, const crypto:: ContainerStoragePrefix prefix{}; encrypt_key_pair(prefix.encrypted_view_keys, m_view_public_key, m_view_secret_key, m_oldest_timestamp, wallet_key); unsigned char count_capacity_data[sizeof(uint64_t)]{}; - common::uint_le_to_bytes(count_capacity_data, sizeof(uint64_t), m_wallet_records.size()); + uint_le_to_bytes(count_capacity_data, sizeof(uint64_t), m_wallet_records.size()); f.write(&version, 1); f.write(&prefix, sizeof(prefix)); @@ -378,13 +344,10 @@ void WalletContainerStorage::save(const std::string &export_path, const crypto:: } std::string WalletContainerStorage::export_keys() const { - BinaryArray result; - common::append(result, std::begin(m_wallet_records.at(0).spend_public_key.data), - std::end(m_wallet_records.at(0).spend_public_key.data)); - common::append(result, std::begin(m_view_public_key.data), std::end(m_view_public_key.data)); - common::append(result, std::begin(m_wallet_records.at(0).spend_secret_key.data), - std::end(m_wallet_records.at(0).spend_secret_key.data)); - common::append(result, std::begin(m_view_secret_key.data), std::end(m_view_secret_key.data)); + BinaryArray result = m_wallet_records.at(0).spend_public_key.as_binary_array(); + result |= m_view_public_key.as_binary_array(); + result |= m_wallet_records.at(0).spend_secret_key.as_binary_array(); + result |= m_view_secret_key.as_binary_array(); return common::to_hex(result); } @@ -417,7 +380,7 @@ void WalletContainerStorage::export_wallet(const std::string &export_path, const throw Exception(api::WALLET_FILE_DECRYPT_ERROR, "Spend public key doesn't correspond to secret key (corrupted wallet?)"); } else { - if (!key_isvalid(rec.spend_public_key)) { + if (!key_in_main_subgroup(rec.spend_public_key)) { throw Exception(api::WALLET_FILE_DECRYPT_ERROR, "Public spend key is incorrect (corrupted wallet?)"); } } @@ -432,8 +395,9 @@ bool WalletContainerStorage::operator==(const WalletContainerStorage &other) con m_oldest_timestamp == other.m_oldest_timestamp && m_wallet_records == other.m_wallet_records; } -std::vector WalletContainerStorage::generate_new_addresses( - const std::vector &sks, Timestamp ct, Timestamp now, bool *rescan_from_ct) { +std::vector WalletContainerStorage::generate_new_addresses(const std::vector &sks, + Timestamp ct, Timestamp now, std::vector *addresses, bool *rescan_from_ct) { + std::vector result_addresses; std::vector result; if (is_view_only()) throw Exception(101, "Generate new addresses impossible for view-only wallet"); @@ -466,6 +430,7 @@ std::vector WalletContainerStorage::generate_new_addresses( *rescan_from_ct = true; } result.push_back(m_wallet_records.at(rit->second)); + result_addresses.push_back(record_to_address(rit->second)); continue; } m_records_map.insert(std::make_pair(record.spend_public_key, m_wallet_records.size())); @@ -475,12 +440,13 @@ std::vector WalletContainerStorage::generate_new_addresses( enc_record, record.spend_public_key, record.spend_secret_key, record.creation_timestamp, m_wallet_key); m_file->write(&enc_record, sizeof(enc_record)); result.push_back(record); + result_addresses.push_back(record_to_address(m_wallet_records.size() - 1)); } m_file->fsync(); m_file->seek(1 + sizeof(ContainerStoragePrefix), SEEK_SET); unsigned char count_capacity_data[sizeof(uint64_t)]{}; - common::uint_le_to_bytes(count_capacity_data, sizeof(uint64_t), m_wallet_records.size()); + uint_le_to_bytes(count_capacity_data, sizeof(uint64_t), m_wallet_records.size()); m_file->write(count_capacity_data, sizeof(uint64_t)); m_file->write(count_capacity_data, sizeof(uint64_t)); @@ -491,23 +457,26 @@ std::vector WalletContainerStorage::generate_new_addresses( << " in a wallet file (might take minutes for large wallets)..." << std::endl; save_and_check(); } + *addresses = result_addresses; return result; } -AccountAddress WalletContainerStorage::record_to_address(const WalletRecord &record) const { +AccountAddress WalletContainerStorage::record_to_address(size_t index) const { + const WalletRecord &record = m_wallet_records.at(index); return AccountAddressSimple{record.spend_public_key, m_view_public_key}; } -bool WalletContainerStorage::get_record(WalletRecord *record, const AccountAddress &v_addr) const { +bool WalletContainerStorage::get_record(const AccountAddress &v_addr, size_t *index, WalletRecord *record) const { if (v_addr.type() != typeid(AccountAddressSimple)) return false; auto &addr = boost::get(v_addr); - auto rit = m_records_map.find(addr.spend_public_key); - if (m_view_public_key != addr.view_public_key || rit == m_records_map.end()) + auto rit = m_records_map.find(addr.S); + if (m_view_public_key != addr.V || rit == m_records_map.end()) return false; if (rit->second >= get_actual_records_count()) return false; - invariant(m_wallet_records.at(rit->second).spend_public_key == addr.spend_public_key, ""); + invariant(m_wallet_records.at(rit->second).spend_public_key == addr.S, ""); + *index = rit->second; *record = m_wallet_records.at(rit->second); return true; } @@ -560,17 +529,16 @@ bool WalletContainerStorage::save_history(const Hash &tid, const History &used_a BinaryArray data; for (auto &&addr : used_addresses) { - common::append(data, std::begin(addr.view_public_key.data), std::end(addr.view_public_key.data)); - common::append(data, std::begin(addr.spend_public_key.data), std::end(addr.spend_public_key.data)); + data |= addr.V.as_binary_array(); + data |= addr.S.as_binary_array(); } BinaryArray encrypted_data; encrypted_data.resize(data.size(), 0); crypto::chacha8(data.data(), data.size(), m_history_key, iv, encrypted_data.data()); encrypted_data.insert(encrypted_data.begin(), std::begin(iv.data), std::end(iv.data)); - BinaryArray filename_data(std::begin(tid.data), std::end(tid.data)); - common::append(filename_data, std::begin(m_history_filename_seed.data), std::end(m_history_filename_seed.data)); - Hash filename_hash = crypto::cn_fast_hash(filename_data.data(), filename_data.size()); + const BinaryArray filename_data = tid.as_binary_array() | m_history_filename_seed.as_binary_array(); + const Hash filename_hash = crypto::cn_fast_hash(filename_data); const auto tmp_path = history_folder + "/_tmp.txh"; return platform::atomic_save_file(history_folder + "/" + common::pod_to_hex(filename_hash) + ".txh", @@ -579,10 +547,9 @@ bool WalletContainerStorage::save_history(const Hash &tid, const History &used_a Wallet::History WalletContainerStorage::load_history(const Hash &tid) const { Wallet::History used_addresses; - std::string history_folder = get_history_folder(); - BinaryArray filename_data(std::begin(tid.data), std::end(tid.data)); - common::append(filename_data, std::begin(m_history_filename_seed.data), std::end(m_history_filename_seed.data)); - Hash filename_hash = crypto::cn_fast_hash(filename_data.data(), filename_data.size()); + std::string history_folder = get_history_folder(); + const BinaryArray filename_data = tid.as_binary_array() | m_history_filename_seed.as_binary_array(); + const Hash filename_hash = crypto::cn_fast_hash(filename_data); BinaryArray hist; if (!platform::load_file(history_folder + "/" + common::pod_to_hex(filename_hash) + ".txh", hist) || @@ -595,8 +562,8 @@ Wallet::History WalletContainerStorage::load_history(const Hash &tid) const { *iv, dec.data()); for (size_t i = 0; i != dec.size() / (2 * sizeof(PublicKey)); ++i) { AccountAddressSimple ad; - memcpy(ad.view_public_key.data, dec.data() + i * 2 * sizeof(PublicKey), sizeof(PublicKey)); - memcpy(ad.spend_public_key.data, dec.data() + i * 2 * sizeof(PublicKey) + sizeof(PublicKey), sizeof(PublicKey)); + memcpy(ad.V.data, dec.data() + i * 2 * sizeof(PublicKey), sizeof(PublicKey)); + memcpy(ad.S.data, dec.data() + i * 2 * sizeof(PublicKey) + sizeof(PublicKey), sizeof(PublicKey)); used_addresses.insert(ad); } return used_addresses; @@ -606,7 +573,7 @@ std::vector WalletContainerStorage::payment_queue_get() const { std::vector result; platform::remove_file(get_payment_queue_folder() + "/tmp.tx"); for (const auto &file : platform::get_filenames_in_folder(get_payment_queue_folder())) { - common::BinaryArray body; + BinaryArray body; if (!platform::load_file(get_payment_queue_folder() + "/" + file, body)) continue; result.push_back(std::move(body)); @@ -638,119 +605,136 @@ void WalletContainerStorage::set_label(const std::string &address, const std::st } Wallet::OutputHandler WalletContainerStorage::get_output_handler() const { - SecretKey vsk_copy = m_view_secret_key; - return - [vsk_copy](const PublicKey &tx_public_key, boost::optional *kd, const Hash &tx_inputs_hash, - size_t output_index, const OutputKey &key_output, PublicKey *spend_public_key, SecretKey *secret_scalar) { - if (!*kd) { - try { - *kd = generate_key_derivation(tx_public_key, vsk_copy); - // tx_public_key is not checked by daemon, so can be invalid - } catch (const std::exception &) { - *kd = KeyDerivation{}; - } - } - *spend_public_key = underive_public_key(kd->get(), output_index, key_output.public_key); - }; -} - -bool WalletContainerStorage::detect_our_output(const Hash &tid, const Hash &tx_inputs_hash, - const boost::optional &kd, size_t out_index, const PublicKey &spend_public_key, - const SecretKey &secret_scalar, const OutputKey &key_output, Amount *amount, KeyPair *output_keypair, - AccountAddress *address) { + SecretKey vsk_copy = m_view_secret_key; + SecretKey inv_vsk_copy = m_inv_view_secret_key; + uint8_t amethyst_transaction_version = m_currency.amethyst_transaction_version; + return [vsk_copy, inv_vsk_copy, amethyst_transaction_version](uint8_t tx_version, const PublicKey &tx_public_key, + boost::optional *kd, const Hash &tx_inputs_hash, size_t output_index, + const OutputKey &key_output, PublicKey *address_S, SecretKey *secret_scalar) { + if (tx_version >= amethyst_transaction_version) { + *address_S = linkable_underive_address_S(inv_vsk_copy, tx_inputs_hash, output_index, key_output.public_key, + key_output.encrypted_secret, secret_scalar); + return; + } + if (!*kd) { + try { + *kd = generate_key_derivation(tx_public_key, vsk_copy); + // tx_public_key is not checked by daemon, so can be invalid + } catch (const std::exception &) { + *kd = KeyDerivation{}; + } + } + *address_S = underive_address_S(kd->get(), output_index, key_output.public_key); + }; +} + +bool WalletContainerStorage::detect_our_output(uint8_t tx_version, const Hash &tid, const Hash &tx_inputs_hash, + const boost::optional &kd, size_t out_index, const PublicKey &address_S, + const SecretKey &secret_scalar, const OutputKey &key_output, Amount *amount, SecretKey *output_secret_key_s, + SecretKey *output_secret_key_a, AccountAddress *address, size_t *record_index, KeyImage *keyimage) { WalletRecord record; - if (!get_look_ahead_record(record, spend_public_key)) + AccountAddress addr; + if (!get_look_ahead_record(address_S, record_index, &record, &addr)) return false; - AccountAddressSimple s_address{spend_public_key, get_view_public_key()}; if (record.spend_secret_key != SecretKey{}) { - if (!kd) // tx_public_key was invalid - return false; - // We do some calcs twice here, but only for our outputs (which are usually very small %) - output_keypair->public_key = derive_public_key(kd.get(), out_index, spend_public_key); - output_keypair->secret_key = derive_secret_key(kd.get(), out_index, record.spend_secret_key); - if (output_keypair->public_key != key_output.public_key) + const bool is_tx_amethyst = tx_version >= m_currency.amethyst_transaction_version; + if (is_tx_amethyst) { + *output_secret_key_a = linkable_derive_output_secret_key(record.spend_secret_key, secret_scalar); + } else { + if (!kd) // tx_public_key was invalid + return false; + // We do some calcs twice here, but only for our outputs (which are usually very small %) + PublicKey output_public_key2 = derive_output_public_key(kd.get(), out_index, address_S); + *output_secret_key_a = derive_output_secret_key(kd.get(), out_index, record.spend_secret_key); + if (output_public_key2 != key_output.public_key) + return false; + } + *output_secret_key_s = SecretKey{}; + PublicKey output_public_key = crypto::secret_keys_to_public_key(*output_secret_key_a, *output_secret_key_s); + if (output_public_key != key_output.public_key) return false; + *keyimage = generate_key_image(key_output.public_key, *output_secret_key_a); } - *address = s_address; + *address = addr; *amount = key_output.amount; return true; } -static const std::string current_version = "CryptoNoteWallet1"; +static const std::string current_version = "CryptoNoteWallet4"; static const size_t GENERATE_AHEAD = 20000; // TODO - move to better place -std::string WalletHD::generate_mnemonic(size_t bits, uint32_t version) { - using common::BITS_PER_WORD; - using common::crc32_reverse_step_zero; - using common::crc32_step_zero; - using common::word_crc32_adj; - using common::word_ptrs; - using common::words_bylen; - using common::WORDS_COUNT; - using common::WORDS_MAX_LEN; - using common::WORDS_MIN_LEN; - std::unordered_map last_word(WORDS_COUNT); - for (size_t i = 0; i != WORDS_COUNT; i++) { - uint32_t crc32_suffix = version ^ word_crc32_adj[i]; - for (auto p = word_ptrs[i]; p != word_ptrs[i + 1]; p++) { - crc32_suffix = crc32_reverse_step_zero(crc32_suffix); - } - last_word[crc32_suffix] = i; - } - size_t words_in_prefix = (bits - 1) / BITS_PER_WORD + 1; - size_t words_total = words_in_prefix + 3; - std::unique_ptr word_ids(new size_t[words_total]); - while (true) { - uint32_t crc32_prefix = 0; - for (size_t i = 0; i != words_in_prefix; i++) { - size_t j = crypto::rand() % WORDS_COUNT; - word_ids[i] = j; - for (auto p = word_ptrs[j]; p != word_ptrs[j + 1]; p++) { - crc32_prefix = crc32_step_zero(crc32_prefix); - } - crc32_prefix ^= word_crc32_adj[j]; - } - for (size_t i = 0; i < WORDS_MIN_LEN; i++) { - crc32_prefix = crc32_step_zero(crc32_prefix); - } - const uint32_t *adj1 = word_crc32_adj; - for (size_t l1 = 0;; l1++) { - for (; adj1 != words_bylen[l1]; adj1++) { - uint32_t crc32_prefix2 = crc32_prefix ^ *adj1; - for (size_t i = 0; i < WORDS_MIN_LEN; i++) { - crc32_prefix2 = crc32_step_zero(crc32_prefix2); - } - const uint32_t *adj2 = word_crc32_adj; - for (size_t l2 = 0;; l2++) { - for (; adj2 != words_bylen[l2]; adj2++) { - auto it = last_word.find(crc32_prefix2 ^ *adj2); - if (it != last_word.end()) { - word_ids[words_in_prefix] = adj1 - word_crc32_adj; - word_ids[words_in_prefix + 1] = adj2 - word_crc32_adj; - word_ids[words_in_prefix + 2] = it->second; - size_t word0 = word_ids[0]; - std::string result(word_ptrs[word0], word_ptrs[word0 + 1]); - for (size_t i = 1; i != words_total; i++) { - result.push_back(' '); - size_t word = word_ids[i]; - result.append(word_ptrs[word], word_ptrs[word + 1]); - } - return result; - } - } - if (l2 == WORDS_MAX_LEN - WORDS_MIN_LEN) { - break; - } - crc32_prefix2 = crc32_step_zero(crc32_prefix2); - } - } - if (l1 == WORDS_MAX_LEN - WORDS_MIN_LEN) { - break; - } - crc32_prefix = crc32_step_zero(crc32_prefix); - } - } -} +/*std::string WalletHD::generate_mnemonic(size_t bits, uint32_t version) { + // using common::BITS_PER_WORD; + // using common::crc32_reverse_step_zero; + // using common::crc32_step_zero; + // using common::word_crc32_adj; + // using common::word_ptrs; + // using common::words_bylen; + // using common::WORDS_COUNT; + // using common::WORDS_MAX_LEN; + // using common::WORDS_MIN_LEN; + std::unordered_map last_word(WORDS_COUNT); + for (size_t i = 0; i != WORDS_COUNT; i++) { + uint32_t crc32_suffix = version ^ word_crc32_adj[i]; + for (auto p = word_ptrs[i]; p != word_ptrs[i + 1]; p++) { + crc32_suffix = crc32_reverse_step_zero(crc32_suffix); + } + last_word[crc32_suffix] = i; + } + size_t words_in_prefix = (bits - 1) / BITS_PER_WORD + 1; + size_t words_total = words_in_prefix + 3; + std::unique_ptr word_ids(new size_t[words_total]); + while (true) { + uint32_t crc32_prefix = 0; + for (size_t i = 0; i != words_in_prefix; i++) { + size_t j = crypto::rand() % WORDS_COUNT; + word_ids[i] = j; + for (auto p = word_ptrs[j]; p != word_ptrs[j + 1]; p++) { + crc32_prefix = crc32_step_zero(crc32_prefix); + } + crc32_prefix ^= word_crc32_adj[j]; + } + for (size_t i = 0; i < WORDS_MIN_LEN; i++) { + crc32_prefix = crc32_step_zero(crc32_prefix); + } + const uint32_t *adj1 = word_crc32_adj; + for (size_t l1 = 0;; l1++) { + for (; adj1 != words_bylen[l1]; adj1++) { + uint32_t crc32_prefix2 = crc32_prefix ^ *adj1; + for (size_t i = 0; i < WORDS_MIN_LEN; i++) { + crc32_prefix2 = crc32_step_zero(crc32_prefix2); + } + const uint32_t *adj2 = word_crc32_adj; + for (size_t l2 = 0;; l2++) { + for (; adj2 != words_bylen[l2]; adj2++) { + auto it = last_word.find(crc32_prefix2 ^ *adj2); + if (it != last_word.end()) { + word_ids[words_in_prefix] = adj1 - word_crc32_adj; + word_ids[words_in_prefix + 1] = adj2 - word_crc32_adj; + word_ids[words_in_prefix + 2] = it->second; + size_t word0 = word_ids[0]; + std::string result(word_ptrs[word0], word_ptrs[word0 + 1]); + for (size_t i = 1; i != words_total; i++) { + result.push_back(' '); + size_t word = word_ids[i]; + result.append(word_ptrs[word], word_ptrs[word + 1]); + } + return result; + } + } + if (l2 == WORDS_MAX_LEN - WORDS_MIN_LEN) { + break; + } + crc32_prefix2 = crc32_step_zero(crc32_prefix2); + } + } + if (l1 == WORDS_MAX_LEN - WORDS_MIN_LEN) { + break; + } + crc32_prefix = crc32_step_zero(crc32_prefix); + } + } +}*/ static const std::string ADDRESS_COUNT_PREFIX = "total_address_count"; static const std::string CREATION_TIMESTAMP_PREFIX = "creation_timestamp"; @@ -768,15 +752,37 @@ bool WalletHD::is_sqlite(const std::string &full_path) { return false; } +bool WalletHD::can_view_outgoing_addresses() const { return m_hw || m_tx_derivation_seed != Hash{}; } + WalletHD::WalletHD(const Currency ¤cy, logging::ILogger &log, const std::string &path, const std::string &password, bool readonly) : Wallet(currency, log, path) { bool created = false; m_db_dbi.open_check_create(readonly ? platform::O_READ_EXISTING : platform::O_OPEN_EXISTING, path, &created); - BinaryArray salt = get_salt(); - common::append(salt, common::as_binary_array(password)); - crypto::CryptoNightContext cn_ctx; - m_wallet_key = generate_chacha8_key(cn_ctx, salt.data(), salt.size()); + + if (get_is_hardware()) { + auto connected = hw::HardwareWallet::get_connected(); + for (auto &&c : connected) { + try { + m_wallet_key = c->get_wallet_key(); + std::string version; + if (get("version", version)) { + m_hw = std::move(c); + break; + } + } catch (const std::exception &) { + // ignore, probably disconnected while we were trying + } + } + if (!m_hw) + throw Exception(api::WALLET_FILE_HARDWARE_DECRYPT_ERROR, + "Hardware-backed wallet file failed to decrypt using keys stored in " + + common::to_string(connected.size()) + " hardware wallet(s)"); + } else { + BinaryArray salt_data = get_salt() | as_binary_array(password); + crypto::CryptoNightContext cn_ctx; + m_wallet_key = generate_chacha8_key(cn_ctx, salt_data.data(), salt_data.size()); + } try { load(); } catch (const Bip32Key::Exception &) { @@ -787,8 +793,8 @@ WalletHD::WalletHD(const Currency ¤cy, logging::ILogger &log, const std::s } WalletHD::WalletHD(const Currency ¤cy, logging::ILogger &log, const std::string &path, - const std::string &password, const std::string &mnemonic, uint8_t address_type, Timestamp creation_timestamp, - const std::string &mnemonic_password) + const std::string &password, const std::string &mnemonic, Timestamp creation_timestamp, + const std::string &mnemonic_password, bool hardware_wallet) : Wallet(currency, log, path) { bool created = false; m_db_dbi.open_check_create(platform::O_CREATE_NEW, path, &created); @@ -803,17 +809,33 @@ WalletHD::WalletHD(const Currency ¤cy, logging::ILogger &log, const std::s BinaryArray salt(sizeof(Hash)); crypto::generate_random_bytes(salt.data(), salt.size()); put_salt(salt); // The only unencrypted field - common::append(salt, common::as_binary_array(password)); - crypto::CryptoNightContext cn_ctx; - m_wallet_key = generate_chacha8_key(cn_ctx, salt.data(), salt.size()); - - if (mnemonic.empty()) + if (hardware_wallet) { + if (!password.empty()) + throw Exception(api::WALLET_FILE_HARDWARE_DECRYPT_ERROR, + "Wallet password should be empty when backed by hardware, wallet file will be encrypted using key stored in hardware wallet"); + auto connected = hw::HardwareWallet::get_connected(); + if (connected.empty()) + throw Exception(api::WALLET_FILE_HARDWARE_DECRYPT_ERROR, + "No hardware wallets connected, please connect one and try again."); + if (connected.size() > 1) + throw Exception(api::WALLET_FILE_HARDWARE_DECRYPT_ERROR, + "More than 1 hardware wallet connected, please disconnect all but one you wish to use to create wallet file."); + m_hw = std::move(connected.back()); + m_wallet_key = m_hw->get_wallet_key(); + put_is_hardware(true); + } else { + salt |= as_binary_array(password); + crypto::CryptoNightContext cn_ctx; + m_wallet_key = generate_chacha8_key(cn_ctx, salt.data(), salt.size()); + } + if (mnemonic.empty() && !m_hw) return; put("version", current_version, true); put("coinname", CRYPTONOTE_NAME, true); - put("address-type", BinaryArray(1, address_type), true); - put("mnemonic", cn::Bip32Key::check_bip39_mnemonic(mnemonic), true); - put("mnemonic-password", mnemonic_password, true); // write always to keep row count the same + if (!m_hw) { + put("mnemonic", cn::Bip32Key::check_bip39_mnemonic(mnemonic), true); + put("mnemonic-password", mnemonic_password, true); // write always to keep row count the same + } put(ADDRESS_COUNT_PREFIX, seria::to_binary(m_used_address_count), true); on_first_output_found(creation_timestamp); @@ -830,48 +852,71 @@ WalletHD::WalletHD(const Currency ¤cy, logging::ILogger &log, const std::s void WalletHD::load() { std::string version; - if (!get("version", version) || version != current_version) + if (!get("version", version)) { + throw Exception(api::WALLET_FILE_DECRYPT_ERROR, "Wallet password incorrect"); + } + if (version != current_version) throw Exception(api::WALLET_FILE_DECRYPT_ERROR, "Wallet version unknown - " + version); std::string coinname; if (!get("coinname", coinname) || coinname != CRYPTONOTE_NAME) throw Exception(api::WALLET_FILE_DECRYPT_ERROR, "Wallet is for different coin - " + coinname); std::string mnemonic; - BinaryArray address_type; - if (!get("address-type", address_type) || address_type.size() != 1) - throw Exception(api::WALLET_FILE_DECRYPT_ERROR, "Wallet corrupted, no address type"); - m_address_type = address_type.at(0); - if (m_address_type != AccountAddressUnlinkable::type_tag && - m_address_type != AccountAddressUnlinkable::type_tag_auditable) - throw Exception(api::WALLET_FILE_DECRYPT_ERROR, "Wallet address type unknown"); - if (get("mnemonic", mnemonic)) { - std::string mnemonic_password; - invariant(get("mnemonic-password", mnemonic_password), ""); - mnemonic = cn::Bip32Key::check_bip39_mnemonic(mnemonic); - cn::Bip32Key master_key = cn::Bip32Key::create_master_key(mnemonic, mnemonic_password); - cn::Bip32Key k0 = master_key.derive_key(0x8000002c); - cn::Bip32Key k1 = k0.derive_key(0x80000300); - cn::Bip32Key k2 = k1.derive_key(0x80000000 + m_address_type); - cn::Bip32Key k3 = k2.derive_key(0); - cn::Bip32Key k4 = k3.derive_key(0); - m_seed = crypto::cn_fast_hash(k4.get_priv_key().data(), k4.get_priv_key().size()); - m_tx_derivation_seed = derive_from_seed(m_seed, "tx_derivation"); - BinaryArray sk_data = m_seed | "spend_key_base"; - m_spend_key_base.secret_key = crypto::hash_to_scalar(sk_data.data(), sk_data.size()); - invariant(crypto::secret_key_to_public_key(m_spend_key_base.secret_key, &m_spend_key_base.public_key), ""); - } else { // View only - BinaryArray ba; - invariant(get("spend_key_base_public_key", ba) && ba.size() == sizeof(m_spend_key_base.public_key), ""); - memcpy(m_spend_key_base.public_key.data, ba.data(), ba.size()); - invariant(crypto::key_isvalid(m_spend_key_base.public_key), "Wallet Corrupted - spend key base is invalid"); - if (get("tx_derivation_seed", ba) && ba.size() == sizeof(m_tx_derivation_seed)) - memcpy(m_tx_derivation_seed.data, ba.data(), ba.size()); - // only if we have output_secret_derivation_seed, view-only wallet will be able to see outgoing addresses + if (m_hw) { + m_A_plus_SH = m_hw->get_A_plus_SH(); + m_v_mul_A_plus_SH = m_hw->get_v_mul_A_plus_SH(); + m_view_public_key = m_hw->get_public_view_key(); + invariant(crypto::key_in_main_subgroup(m_A_plus_SH), "Hardware wallet error - spend key base is invalid"); + invariant(crypto::key_in_main_subgroup(m_v_mul_A_plus_SH), "Hardware wallet error - view key base is invalid"); + invariant( + crypto::key_in_main_subgroup(m_view_public_key), "Hardware wallet error - view public key is invalid"); + } else { + PublicKey sH; + if (get("mnemonic", mnemonic)) { + std::string mnemonic_password; + invariant(get("mnemonic-password", mnemonic_password), "Wallet corrupted - no mnemonic password"); + mnemonic = cn::Bip32Key::check_bip39_mnemonic(mnemonic); + cn::Bip32Key master_key = cn::Bip32Key::create_master_key(mnemonic, mnemonic_password); + cn::Bip32Key k0 = master_key.derive_key(0x8000002c); + cn::Bip32Key k1 = k0.derive_key(0x800000cc); + cn::Bip32Key k2 = k1.derive_key(0x80000001); + cn::Bip32Key k3 = k2.derive_key(0); + cn::Bip32Key k4 = k3.derive_key(0); + m_seed = crypto::cn_fast_hash(k4.get_priv_key().data(), k4.get_priv_key().size()); + const BinaryArray tx_data = m_seed.as_binary_array() | as_binary_array("tx_derivation"); + m_tx_derivation_seed = crypto::cn_fast_hash(tx_data.data(), tx_data.size()); + const BinaryArray vk_data = m_seed.as_binary_array() | as_binary_array("view_key"); + m_view_secret_key = crypto::hash_to_scalar(vk_data.data(), vk_data.size()); + const BinaryArray ak_data = m_seed.as_binary_array() | as_binary_array("audit_key_base"); + m_audit_key_base.secret_key = crypto::hash_to_scalar(ak_data.data(), ak_data.size()); + const BinaryArray sk_data = m_seed.as_binary_array() | as_binary_array("spend_key"); + m_spend_secret_key = crypto::hash_to_scalar(sk_data.data(), sk_data.size()); + sH = crypto::A_mul_b(crypto::get_H(), m_spend_secret_key); + } else { // View only + BinaryArray ba; + invariant(get("view_key", ba), "Wallet corrupted - no view key"); + seria::from_binary(m_view_secret_key, ba); + invariant(get("sH", ba), "Wallet corrupted - no audit key"); + seria::from_binary(sH, ba); + invariant(crypto::key_in_main_subgroup(sH), "Wallet Corrupted - s*H is invalid"); + invariant(get("audit_key_base", ba), "Wallet corrupted - no spend key base"); + seria::from_binary(m_audit_key_base.secret_key, ba); + + // only if we have output_secret_derivation_seed, view-only wallet will be able to see outgoing addresses + if (get("tx_derivation_seed", ba)) + seria::from_binary(m_tx_derivation_seed, ba); + // We check that sH is product of some known s0 by H, this is required by audit + invariant(get("view_secrets_signature", ba), "Wallet audit secrets are corrupted"); + Signature view_secrets_signature; + seria::from_binary(view_secrets_signature, ba); + + if (!check_view_signatures(m_audit_key_base.secret_key, sH, m_view_secret_key, view_secrets_signature)) + throw Exception(api::WALLET_FILE_DECRYPT_ERROR, "Wallet audit secrets are corrupted"); + } + invariant(crypto::secret_key_to_public_key(m_view_secret_key, &m_view_public_key), ""); + invariant(crypto::secret_key_to_public_key(m_audit_key_base.secret_key, &m_audit_key_base.public_key), ""); + m_A_plus_SH = crypto::A_plus_B(m_audit_key_base.public_key, sH); + m_v_mul_A_plus_SH = A_mul_b(m_A_plus_SH, m_view_secret_key); // for hw debug only } - BinaryArray vk_data = - BinaryArray{std::begin(m_spend_key_base.public_key.data), std::end(m_spend_key_base.public_key.data)} | - "view_key"; - m_view_secret_key = crypto::hash_to_scalar(vk_data.data(), vk_data.size()); - invariant(crypto::secret_key_to_public_key(m_view_secret_key, &m_view_public_key), ""); { BinaryArray ba; if (get(ADDRESS_COUNT_PREFIX, ba)) @@ -897,17 +942,46 @@ void WalletHD::load() { } } +Signature WalletHD::generate_view_secrets_signature(const PublicKey &sH) const { + BinaryArray view_secrets = m_audit_key_base.secret_key.as_binary_array() | m_view_secret_key.as_binary_array(); + Hash view_secrets_hash = crypto::cn_fast_hash(view_secrets); + return crypto::generate_signature_H(view_secrets_hash, sH, m_spend_secret_key); +} + +bool WalletHD::check_view_signatures(const SecretKey &audit_secret_key, const PublicKey &sH, + const SecretKey &view_secret_key, const Signature &view_secrets_signature) { + BinaryArray view_secrets = audit_secret_key.as_binary_array() | view_secret_key.as_binary_array(); + Hash view_secrets_hash = crypto::cn_fast_hash(view_secrets); + return crypto::check_signature_H(view_secrets_hash, sH, view_secrets_signature); +} + +// s(n) = s(0) + Hs(gen_seed | n) +// S(n) = S(0) + Hs(gen_seed | n)*G + a * H + +// V(n) = S(n) * v +// V(n) = S(0) * v + Hs(gen_seed | n)*G*v + a * H * v + +// So to generate addresses from hardware wallet + +// address_s0 = S(0) + a*H +// address_v0 = (S(0) + a*H)*v +// V = v * G + +// We always set gen_seed to be address_s0 + +// S(n) = address_s0 + Hs(address_s0 | n)*G +// V(n) = address_v0 + Hs(address_s0 | n)*V + void WalletHD::generate_ahead1(size_t counter, std::vector &result) const { std::vector key_result; key_result.resize(result.size()); - Hash view_seed; - memcpy(view_seed.data, m_spend_key_base.public_key.data, sizeof(m_spend_key_base.public_key.data)); - crypto::generate_hd_spendkeys(m_spend_key_base, view_seed, counter, &key_result); + crypto::generate_hd_spendkeys(m_audit_key_base.secret_key, m_A_plus_SH, counter, &key_result); for (size_t i = 0; i != result.size(); ++i) { - WalletRecord &record = result[i]; - record.spend_secret_key = key_result.at(i).secret_key; - record.spend_public_key = key_result.at(i).public_key; - record.creation_timestamp = std::numeric_limits::max(); // So adding an address will never rescan + WalletRecord &record = result[i]; + record.spend_secret_key = key_result.at(i).secret_key; + record.spend_public_key = key_result.at(i).public_key; + record.creation_timestamp = + std::numeric_limits::max(); // TODO - adding an address will never rescan, which is wrong } } @@ -940,13 +1014,6 @@ void WalletHD::generate_ahead() { } } -// std::string WalletHD::hash_for_key(const Hash & key)const{ -// BinaryArray seed_data(std::begin(key.data), std::end(key.data)); -// common::append(seed_data, std::begin(m_view_seed.data), std::end(m_view_seed.data)); -// Hash ha = crypto::cn_fast_hash(seed_data.data(), seed_data.size()); -// return std::string(ha.data, ha.data + sizeof(ha.data)); -//} - BinaryArray WalletHD::encrypt_data(const crypto::chacha_key &wallet_key, const BinaryArray &data) { const size_t MIN_SIZE = 256; const size_t EXTRA_SIZE = sizeof(Hash) + 4; // iv, actual size in le @@ -954,13 +1021,13 @@ BinaryArray WalletHD::encrypt_data(const crypto::chacha_key &wallet_key, const B while (actual_size < data.size() + EXTRA_SIZE || actual_size < MIN_SIZE) actual_size *= 2; BinaryArray large_data(actual_size - sizeof(Hash)); - common::uint_le_to_bytes(large_data.data(), 4, data.size()); + uint_le_to_bytes(large_data.data(), 4, data.size()); memcpy(large_data.data() + 4, data.data(), data.size()); BinaryArray enc_data(sizeof(Hash) + large_data.size()); Hash iv = crypto::rand(); memcpy(enc_data.data(), iv.data, sizeof(iv)); - BinaryArray key_data = BinaryArray{wallet_key.data, wallet_key.data + sizeof(wallet_key.data)} | iv; - crypto::chacha_key key{crypto::cn_fast_hash(key_data.data(), key_data.size())}; + const BinaryArray key_data = wallet_key.as_binary_array() | iv.as_binary_array(); + crypto::chacha_key key{crypto::cn_fast_hash(key_data)}; chacha(20, large_data.data(), large_data.size(), key, crypto::chacha_iv{}, enc_data.data() + sizeof(Hash)); return enc_data; } @@ -970,10 +1037,10 @@ BinaryArray WalletHD::decrypt_data(const crypto::chacha_key &wallet_key, const u invariant(value_size >= 4 + sizeof(Hash), ""); memcpy(iv.data, value_data, sizeof(Hash)); BinaryArray result(value_size - sizeof(Hash)); - BinaryArray key_data = BinaryArray{wallet_key.data, wallet_key.data + sizeof(wallet_key.data)} | iv; - crypto::chacha_key key{crypto::cn_fast_hash(key_data.data(), key_data.size())}; + BinaryArray key_data = wallet_key.as_binary_array() | iv.as_binary_array(); + crypto::chacha_key key{crypto::cn_fast_hash(key_data)}; chacha(20, value_data + sizeof(Hash), result.size(), key, crypto::chacha_iv{}, result.data()); - auto real_size = common::uint_le_from_bytes(result.data(), 4); + auto real_size = uint_le_from_bytes(result.data(), 4); invariant(real_size <= result.size() - 4, ""); return BinaryArray{result.data() + 4, result.data() + 4 + real_size}; } @@ -984,6 +1051,7 @@ void WalletHD::put_salt(const BinaryArray &salt) { stmt_update.bind_blob(1, salt.data(), salt.size()); invariant(!stmt_update.step(), ""); } + BinaryArray WalletHD::get_salt() const { sqlite::Stmt stmt_get; stmt_get.prepare(m_db_dbi, "SELECT value FROM unencrypted WHERE key = 'salt'"); @@ -993,9 +1061,24 @@ BinaryArray WalletHD::get_salt() const { return BinaryArray{salt_data, salt_data + salt_size}; } +void WalletHD::put_is_hardware(bool ha) { + sqlite::Stmt stmt_update; + if (ha) + stmt_update.prepare(m_db_dbi, "REPLACE INTO unencrypted (key, value) VALUES ('is_hardware', 1)"); + else + stmt_update.prepare(m_db_dbi, "DELETE FROM unencrypted WHERE key = 'is_hardware'"); + invariant(!stmt_update.step(), ""); +} + +bool WalletHD::get_is_hardware() const { + sqlite::Stmt stmt_get; + stmt_get.prepare(m_db_dbi, "SELECT value FROM unencrypted WHERE key = 'is_hardware'"); + return stmt_get.step(); +} + void WalletHD::put(const std::string &key, const BinaryArray &value, bool nooverwrite) { Hash key_hash = derive_from_key(m_wallet_key, "db_parameters" + key); - BinaryArray enc_key = encrypt_data(m_wallet_key, BinaryArray{key.data(), key.data() + key.size()}); + BinaryArray enc_key = encrypt_data(m_wallet_key, as_binary_array(key)); BinaryArray enc_value = encrypt_data(m_wallet_key, value); sqlite::Stmt stmt_update; stmt_update.prepare(m_db_dbi, @@ -1007,7 +1090,7 @@ void WalletHD::put(const std::string &key, const BinaryArray &value, bool noover invariant(!stmt_update.step(), ""); } -bool WalletHD::get(const std::string &key, common::BinaryArray &value) const { +bool WalletHD::get(const std::string &key, BinaryArray &value) const { Hash key_hash = derive_from_key(m_wallet_key, "db_parameters" + key); sqlite::Stmt stmt_get; @@ -1022,60 +1105,72 @@ bool WalletHD::get(const std::string &key, common::BinaryArray &value) const { } void WalletHD::put(const std::string &key, const std::string &value, bool nooverwrite) { - return put(key, common::as_binary_array(value), nooverwrite); + return put(key, as_binary_array(value), nooverwrite); } + bool WalletHD::get(const std::string &key, std::string &value) const { - common::BinaryArray ba; + BinaryArray ba; if (!get(key, ba)) return false; value = common::as_string(ba); return true; } -std::vector WalletHD::generate_new_addresses( - const std::vector &sks, Timestamp ct, Timestamp now, bool *rescan_from_ct) { +std::vector WalletHD::generate_new_addresses(const std::vector &sks, Timestamp ct, + Timestamp now, std::vector *addresses, bool *rescan_from_ct) { for (const auto &sk : sks) if (sk != SecretKey{}) throw std::runtime_error("Generating non-deterministic addreses not supported by HD wallet"); std::vector result; + addresses->clear(); if (sks.empty()) return result; auto was_used_address_count = m_used_address_count; m_used_address_count += sks.size(); generate_ahead(); - for (size_t i = 0; i != sks.size(); ++i) + for (size_t i = 0; i != sks.size(); ++i) { result.push_back(m_wallet_records.at(was_used_address_count + i)); + addresses->push_back(record_to_address(was_used_address_count + i)); + } put(ADDRESS_COUNT_PREFIX, seria::to_binary(m_used_address_count), false); commit(); return result; } -AccountAddress WalletHD::record_to_address(const WalletRecord &record) const { - PublicKey sv = generate_address_s_v(record.spend_public_key, m_view_secret_key); +AccountAddress WalletHD::record_to_address(size_t index) const { + const WalletRecord &record = m_wallet_records.at(index); + Hash view_seed; + memcpy(view_seed.data, m_audit_key_base.public_key.data, sizeof(m_audit_key_base.public_key.data)); + PublicKey sv2 = crypto::generate_hd_spendkey(m_v_mul_A_plus_SH, m_A_plus_SH, m_view_public_key, index); + if (m_view_secret_key != SecretKey{}) { + PublicKey sv = A_mul_b(record.spend_public_key, m_view_secret_key); + invariant(sv == sv2, ""); + } // TODO - do multiplication only once - return AccountAddressUnlinkable{ - record.spend_public_key, sv, m_address_type == AccountAddressUnlinkable::type_tag_auditable}; + return AccountAddressUnlinkable{record.spend_public_key, sv2}; } -bool WalletHD::get_record(WalletRecord *record, const AccountAddress &v_addr) const { +bool WalletHD::get_record(const AccountAddress &v_addr, size_t *index, WalletRecord *record) const { if (v_addr.type() != typeid(AccountAddressUnlinkable)) return false; auto &addr = boost::get(v_addr); - if (addr.is_auditable != is_auditable()) - return false; - auto rit = m_records_map.find(addr.s); + auto rit = m_records_map.find(addr.S); if (rit == m_records_map.end() || rit->second >= get_actual_records_count()) return false; // TODO - do not call record_to_address - auto addr2 = record_to_address(m_wallet_records.at(rit->second)); + auto addr2 = record_to_address(rit->second); if (v_addr != addr2) return false; // invariant (m_wallet_records.at(rit->second).spend_public_key == addr.spend_public_key, ""); + *index = rit->second; *record = m_wallet_records.at(rit->second); return true; } void WalletHD::set_password(const std::string &password) { + if (m_hw) + throw std::runtime_error( + "Cannot set password on this wallet created from hardware wallet. It is encrypted with keys stored in hardware wallet"); auto parameters = parameters_get(); auto pq2 = payment_queue_get2(); @@ -1086,7 +1181,7 @@ void WalletHD::set_password(const std::string &password) { BinaryArray salt(sizeof(Hash)); crypto::generate_random_bytes(salt.data(), salt.size()); put_salt(salt); - common::append(salt, BinaryArray{password.data(), password.data() + password.size()}); + salt |= as_binary_array(password); crypto::CryptoNightContext cn_ctx; m_wallet_key = generate_chacha8_key(cn_ctx, salt.data(), salt.size()); @@ -1101,15 +1196,44 @@ void WalletHD::set_password(const std::string &password) { void WalletHD::export_wallet(const std::string &export_path, const std::string &new_password, bool view_only, bool view_outgoing_addresses) const { - WalletHD other(m_currency, m_log.get_logger(), export_path, new_password, std::string(), 0, 0, std::string()); + if (m_hw && !view_only) + throw Exception(api::WALLET_FILE_DECRYPT_ERROR, "Exporting hardware-backed wallet is not possible"); + + WalletHD other(m_currency, m_log.get_logger(), export_path, new_password, std::string(), 0, std::string(), false); if (!is_view_only() && view_only) { - other.put("spend_key_base_public_key", - BinaryArray{std::begin(m_spend_key_base.public_key.data), std::end(m_spend_key_base.public_key.data)}, - true); - if (view_outgoing_addresses) - other.put("tx_derivation_seed", - BinaryArray{std::begin(m_tx_derivation_seed.data), std::end(m_tx_derivation_seed.data)}, true); + other.put("m_A_plus_SH", m_A_plus_SH.as_binary_array(), true); + if (m_hw) { + SecretKey audit_key_base_secret_key; + PublicKey A; + SecretKey view_secret_key; + Hash tx_derivation_seed; + Signature view_secrets_signature; + m_hw->export_view_only( + &audit_key_base_secret_key, &view_secret_key, &tx_derivation_seed, &view_secrets_signature); + invariant(crypto::secret_key_to_public_key(audit_key_base_secret_key, &A), ""); + PublicKey sH = crypto::A_minus_B(m_A_plus_SH, A); + other.put("view_key", view_secret_key.as_binary_array(), true); + other.put("sH", sH.as_binary_array(), true); + other.put("audit_key_base", audit_key_base_secret_key.as_binary_array(), true); + if (tx_derivation_seed != Hash{}) + other.put("tx_derivation_seed", tx_derivation_seed.as_binary_array(), true); + other.put("view_secrets_signature", seria::to_binary(view_secrets_signature), true); + invariant( + check_view_signatures(audit_key_base_secret_key, sH, view_secret_key, view_secrets_signature), ""); + } else { + if (view_outgoing_addresses) + other.put("tx_derivation_seed", m_tx_derivation_seed.as_binary_array(), true); + auto sH = crypto::A_mul_b(crypto::get_H(), m_spend_secret_key); + other.put("view_key", m_view_secret_key.as_binary_array(), true); + other.put("sH", sH.as_binary_array(), true); + other.put("audit_key_base", m_audit_key_base.secret_key.as_binary_array(), true); + + auto view_secrets_signature = generate_view_secrets_signature(sH); + other.put("view_secrets_signature", seria::to_binary(view_secrets_signature), true); + invariant( + check_view_signatures(m_audit_key_base.secret_key, sH, m_view_secret_key, view_secrets_signature), ""); + } for (const auto &p : parameters_get()) if (p.first != "mnemonic" && p.first != "mnemonic-password") other.put(p.first, p.second, true); @@ -1192,8 +1316,8 @@ void WalletHD::payment_queue_add(const Hash &tid, const std::string &net, const Hash tid_hash = derive_from_key(m_wallet_key, "db_payment_queue_tid" + std::string(std::begin(tid.data), std::end(tid.data))); Hash net_hash = derive_from_key(m_wallet_key, "db_payment_queue_net" + net); - BinaryArray enc_tid = encrypt_data(m_wallet_key, BinaryArray{tid.data, tid.data + sizeof(tid.data)}); - BinaryArray enc_net = encrypt_data(m_wallet_key, BinaryArray{net.data(), net.data() + net.size()}); + BinaryArray enc_tid = encrypt_data(m_wallet_key, tid.as_binary_array()); + BinaryArray enc_net = encrypt_data(m_wallet_key, as_binary_array(net)); BinaryArray enc_value = encrypt_data(m_wallet_key, binary_transaction); sqlite::Stmt stmt_update; stmt_update.prepare(m_db_dbi, @@ -1228,6 +1352,7 @@ std::vector> WalletHD::parameters_get() cons void WalletHD::payment_queue_add(const Hash &tid, const BinaryArray &binary_transaction) { payment_queue_add(tid, m_currency.net, binary_transaction); + commit(); } void WalletHD::commit() { @@ -1252,8 +1377,8 @@ void WalletHD::payment_queue_remove(const Hash &tid) { void WalletHD::set_label(const std::string &address, const std::string &label) { Hash address_hash = derive_from_key(m_wallet_key, "db_labels" + address); - BinaryArray enc_address = encrypt_data(m_wallet_key, BinaryArray{address.data(), address.data() + address.size()}); - BinaryArray enc_label = encrypt_data(m_wallet_key, BinaryArray{label.data(), label.data() + label.size()}); + BinaryArray enc_address = encrypt_data(m_wallet_key, as_binary_array(address)); + BinaryArray enc_label = encrypt_data(m_wallet_key, as_binary_array(label)); if (label.empty()) { m_labels.erase(address); @@ -1282,30 +1407,36 @@ std::string WalletHD::get_label(const std::string &address) const { } Wallet::OutputHandler WalletHD::get_output_handler() const { + if (m_hw) + throw std::logic_error("WalletHD::get_output_handler when using hw"); SecretKey vsk_copy = m_view_secret_key; - return - [vsk_copy](const PublicKey &tx_public_key, boost::optional *kd, const Hash &tx_inputs_hash, - size_t output_index, const OutputKey &key_output, PublicKey *spend_public_key, SecretKey *secret_scalar) { - *spend_public_key = crypto::unlinkable_underive_public_key(vsk_copy, tx_inputs_hash, output_index, - key_output.public_key, key_output.encrypted_secret, secret_scalar); - }; -} - -bool WalletHD::detect_our_output(const Hash &tid, const Hash &tx_inputs_hash, const boost::optional &kd, - size_t out_index, const PublicKey &spend_public_key, const SecretKey &secret_scalar, const OutputKey &key_output, - Amount *amount, KeyPair *output_keypair, AccountAddress *address) { + return [vsk_copy](uint8_t tx_version, const PublicKey &tx_public_key, boost::optional *kd, + const Hash &tx_inputs_hash, size_t output_index, const OutputKey &key_output, PublicKey *address_S, + SecretKey *secret_scalar) { + *address_S = crypto::unlinkable_underive_address_S( + vsk_copy, tx_inputs_hash, output_index, key_output.public_key, key_output.encrypted_secret, secret_scalar); + }; +} + +bool WalletHD::detect_our_output(uint8_t tx_version, const Hash &tid, const Hash &tx_inputs_hash, + const boost::optional &kd, size_t out_index, const PublicKey &address_S, + const SecretKey &secret_scalar, const OutputKey &key_output, Amount *amount, SecretKey *output_secret_key_s, + SecretKey *output_secret_key_a, AccountAddress *address, size_t *record_index, KeyImage *keyimage) { WalletRecord record; - if (!get_look_ahead_record(record, spend_public_key)) - return false; - AccountAddress addr = record_to_address(record); - const auto &u_address = boost::get(addr); // TODO - better logic - if (u_address.is_auditable != key_output.is_auditable) + AccountAddress addr; + if (!get_look_ahead_record(address_S, record_index, &record, &addr)) return false; - if (record.spend_secret_key != SecretKey{}) { - output_keypair->secret_key = crypto::unlinkable_derive_secret_key(record.spend_secret_key, secret_scalar); - if (!crypto::secret_key_to_public_key(output_keypair->secret_key, &output_keypair->public_key) || - output_keypair->public_key != key_output.public_key) - return false; + if (m_hw) { + *keyimage = m_hw->generate_keyimage(key_output.public_key, crypto::sc_invert(secret_scalar), *record_index); + } else { + *output_secret_key_a = crypto::unlinkable_derive_output_secret_key(record.spend_secret_key, secret_scalar); + if (m_spend_secret_key != SecretKey{}) { + *output_secret_key_s = crypto::unlinkable_derive_output_secret_key(m_spend_secret_key, secret_scalar); + PublicKey output_public_key = crypto::secret_keys_to_public_key(*output_secret_key_a, *output_secret_key_s); + if (output_public_key != key_output.public_key) + return false; + } + *keyimage = generate_key_image(key_output.public_key, *output_secret_key_a); } *address = addr; // std::cout << "My unlinkable output! out_index=" << out_index << diff --git a/src/Core/Wallet.hpp b/src/Core/Wallet.hpp index dfbc06b0..2a569fba 100644 --- a/src/Core/Wallet.hpp +++ b/src/Core/Wallet.hpp @@ -3,13 +3,13 @@ #pragma once +#include #include #include #include "CryptoNote.hpp" #include "Currency.hpp" -//#include "Multicore.hpp" -#include #include "crypto/chacha.hpp" +#include "hw/HardwareWallet.hpp" #include "logging/LoggerMessage.hpp" #include "platform/DBsqlite3.hpp" #include "platform/Files.hpp" @@ -47,6 +47,9 @@ class Wallet { Hash m_seed; // Main seed, never used directly Hash m_tx_derivation_seed; // Hashed from seed + + virtual AccountAddress record_to_address(size_t index) const = 0; + public: class Exception : public std::runtime_error { public: @@ -54,31 +57,30 @@ class Wallet { explicit Exception(int rc, const std::string &what) : std::runtime_error(what), return_code(rc) {} }; Wallet(const Currency ¤cy, logging::ILogger &log, const std::string &path); - virtual ~Wallet() = default; + virtual ~Wallet() = default; + virtual const hw::HardwareWallet *get_hw() const { return nullptr; } virtual void set_password(const std::string &password) = 0; virtual void export_wallet(const std::string &export_path, const std::string &new_password, bool view_only, bool view_outgoing_addresses) const = 0; virtual bool is_view_only() const { return m_wallet_records.at(0).spend_secret_key == SecretKey{}; } - bool can_view_outgoing_addresses() const { return m_tx_derivation_seed != Hash{}; } - virtual bool is_deterministic() const { return false; } - virtual bool is_unlinkable() const { return false; } - virtual bool is_auditable() const { return false; } - bool is_det_viewonly() const { return is_view_only() && is_deterministic(); } + virtual bool can_view_outgoing_addresses() const { return m_tx_derivation_seed != Hash{}; } + virtual bool is_amethyst() const { return false; } + virtual std::string get_hardware_type() const { return std::string(); } virtual std::string export_keys() const = 0; const PublicKey &get_view_public_key() const { return m_view_public_key; } const SecretKey &get_view_secret_key() const { return m_view_secret_key; } - const std::vector &get_records() const { return m_wallet_records; } + const std::vector &test_get_records() const { return m_wallet_records; } virtual size_t get_actual_records_count() const { return m_wallet_records.size(); } - virtual bool get_record(WalletRecord *record, const AccountAddress &) const = 0; + virtual bool get_record(const AccountAddress &, size_t *index, WalletRecord *record) const = 0; virtual void create_look_ahead_records(size_t count) {} - bool get_look_ahead_record(WalletRecord &record, const PublicKey &); + bool get_record(size_t index, WalletRecord *record, AccountAddress *) const; // address can be null if not needed + bool get_look_ahead_record(const PublicKey &, size_t *index, WalletRecord *record, AccountAddress *); bool is_our_address(const AccountAddress &) const; AccountAddress get_first_address() const; - virtual AccountAddress record_to_address(const WalletRecord &record) const = 0; virtual std::vector generate_new_addresses(const std::vector &sks, Timestamp ct, - Timestamp now, + Timestamp now, std::vector *addresses, bool *rescan_from_ct) = 0; // set secret_key to SecretKey{} to generate std::string get_cache_name() const; @@ -102,14 +104,15 @@ class Wallet { virtual void set_label(const std::string &address, const std::string &label) = 0; virtual std::string get_label(const std::string &address) const = 0; - typedef std::function *, + typedef std::function *, const Hash &tx_inputs_hash, size_t out_index, const OutputKey &, PublicKey *, SecretKey *)> OutputHandler; // Self-contain functor with all info copied to be called from other threads - virtual OutputHandler get_output_handler() const = 0; - virtual bool detect_our_output(const Hash &tid, const Hash &tx_inputs_hash, - const boost::optional &kd, size_t out_index, const PublicKey &spend_public_key, - const SecretKey &secret_scalar, const OutputKey &, Amount *, KeyPair *output_keypair, AccountAddress *) = 0; + virtual OutputHandler get_output_handler() const = 0; + virtual bool detect_our_output(uint8_t tx_version, const Hash &tid, const Hash &tx_inputs_hash, + const boost::optional &kd, size_t out_index, const PublicKey &address_S, + const SecretKey &secret_scalar, const OutputKey &, Amount *, SecretKey *output_secret_key_s, + SecretKey *output_secret_key_a, AccountAddress *, size_t *record_index, KeyImage *keyimage) = 0; }; // stores at most 1 view secret key. 1 or more spend secret keys @@ -117,6 +120,7 @@ class Wallet { // File formats are opened as is, and saved to V2 when changing something class WalletContainerStorage : public Wallet { std::unique_ptr m_file; + SecretKey m_inv_view_secret_key; // for new linkable crypto crypto::chacha_key m_history_key; // Hashed from seed Hash m_history_filename_seed; // Hashed from seed @@ -137,15 +141,17 @@ class WalletContainerStorage : public Wallet { WalletContainerStorage( const Currency ¤cy, logging::ILogger &log, const std::string &path, const crypto::chacha_key &wallet_key); +protected: + AccountAddress record_to_address(size_t index) const override; + public: WalletContainerStorage( const Currency ¤cy, logging::ILogger &log, const std::string &path, const std::string &password); WalletContainerStorage(const Currency ¤cy, logging::ILogger &log, const std::string &path, const std::string &password, const std::string &import_keys, Timestamp creation_timestamp); - std::vector generate_new_addresses( - const std::vector &sks, Timestamp ct, Timestamp now, bool *rescan_from_ct) override; - AccountAddress record_to_address(const WalletRecord &record) const override; - bool get_record(WalletRecord *record, const AccountAddress &) const override; + std::vector generate_new_addresses(const std::vector &sks, Timestamp ct, Timestamp now, + std::vector *addresses, bool *rescan_from_ct) override; + bool get_record(const AccountAddress &, size_t *index, WalletRecord *record) const override; void set_password(const std::string &password) override; void export_wallet(const std::string &export_path, const std::string &new_password, bool view_only, bool view_outgoing_addresses) const override; @@ -168,24 +174,30 @@ class WalletContainerStorage : public Wallet { std::string get_label(const std::string &address) const override { return std::string(); } OutputHandler get_output_handler() const override; - bool detect_our_output(const Hash &tid, const Hash &tx_inputs_hash, const boost::optional &kd, - size_t out_index, const PublicKey &spend_public_key, const SecretKey &secret_scalar, const OutputKey &, - Amount *, KeyPair *output_keypair, AccountAddress *) override; + bool detect_our_output(uint8_t tx_version, const Hash &tid, const Hash &tx_inputs_hash, + const boost::optional &kd, size_t out_index, const PublicKey &address_S, + const SecretKey &secret_scalar, const OutputKey &, Amount *, SecretKey *output_secret_key_s, + SecretKey *output_secret_key_a, AccountAddress *, size_t *record_index, KeyImage *keyimage) override; }; // stores either mnemonic or some seeds if view-only // stores number of used addresses, (per net) creation timestamp, (per net) payment queue class WalletHD : public Wallet { platform::sqlite::Dbi m_db_dbi; - uint8_t m_address_type = 0; - KeyPair m_spend_key_base; + SecretKey m_spend_secret_key; + KeyPair m_audit_key_base; + PublicKey m_A_plus_SH; + PublicKey m_v_mul_A_plus_SH; size_t m_used_address_count = 1; std::map m_labels; + std::unique_ptr m_hw; // quick prototyping, will refactor later static BinaryArray encrypt_data(const crypto::chacha_key &wallet_key, const BinaryArray &data); static BinaryArray decrypt_data(const crypto::chacha_key &wallet_key, const uint8_t *value_data, size_t value_size); void put_salt(const BinaryArray &salt); BinaryArray get_salt() const; + void put_is_hardware(bool ha); + bool get_is_hardware() const; void commit(); void put(const std::string &key, const common::BinaryArray &value, bool nooverwrite); @@ -200,26 +212,32 @@ class WalletHD : public Wallet { std::vector> parameters_get() const; std::vector> payment_queue_get2() const; void payment_queue_add(const Hash &tid, const std::string &net, const BinaryArray &binary_transaction); + Signature generate_view_secrets_signature(const PublicKey &sH) const; + static bool check_view_signatures(const SecretKey &audit_secret_key, const PublicKey &sH, + const SecretKey &view_secret_key, const Signature &view_secrets_signature); + +protected: + AccountAddress record_to_address(size_t index) const override; public: - static std::string generate_mnemonic(size_t bits, uint32_t version); + // static std::string generate_mnemonic(size_t bits, uint32_t version); static bool is_sqlite(const std::string &full_path); // In contrast with WalletContainerStorage, we must know read_only flag, because otherwise // inability to save address count will lead to wallet losing track of funds due to skipping outputs WalletHD(const Currency ¤cy, logging::ILogger &log, const std::string &path, const std::string &password, bool readonly); WalletHD(const Currency ¤cy, logging::ILogger &log, const std::string &path, const std::string &password, - const std::string &mnemonic, uint8_t address_type, Timestamp creation_timestamp, - const std::string &mnemonic_password); - bool is_view_only() const override { return m_spend_key_base.secret_key == SecretKey{}; } - bool is_deterministic() const override { return true; } - bool is_unlinkable() const override { return true; } - bool is_auditable() const override { return m_address_type == AccountAddressUnlinkable::type_tag_auditable; } + const std::string &mnemonic, Timestamp creation_timestamp, const std::string &mnemonic_password, + bool hardware_wallet); + const hw::HardwareWallet *get_hw() const override { return m_hw.get(); } + bool is_view_only() const override { return !m_hw && m_spend_secret_key == SecretKey{}; } + bool is_amethyst() const override { return true; } + bool can_view_outgoing_addresses() const override; + std::string get_hardware_type() const override { return m_hw ? m_hw->get_hardware_type() : std::string(); } size_t get_actual_records_count() const override { return m_used_address_count; } - std::vector generate_new_addresses( - const std::vector &sks, Timestamp ct, Timestamp now, bool *rescan_from_ct) override; - AccountAddress record_to_address(const WalletRecord &record) const override; - bool get_record(WalletRecord *record, const AccountAddress &) const override; + std::vector generate_new_addresses(const std::vector &sks, Timestamp ct, Timestamp now, + std::vector *addresses, bool *rescan_from_ct) override; + bool get_record(const AccountAddress &, size_t *index, WalletRecord *record) const override; void set_password(const std::string &password) override; void export_wallet(const std::string &export_path, const std::string &new_password, bool view_only, bool view_outgoing_addresses) const override; @@ -242,9 +260,10 @@ class WalletHD : public Wallet { std::string get_label(const std::string &address) const override; OutputHandler get_output_handler() const override; - bool detect_our_output(const Hash &tid, const Hash &tx_inputs_hash, const boost::optional &kd, - size_t out_index, const PublicKey &spend_public_key, const SecretKey &secret_scalar, const OutputKey &, - Amount *, KeyPair *output_keypair, AccountAddress *) override; + bool detect_our_output(uint8_t tx_version, const Hash &tid, const Hash &tx_inputs_hash, + const boost::optional &kd, size_t out_index, const PublicKey &address_S, + const SecretKey &secret_scalar, const OutputKey &, Amount *, SecretKey *output_secret_key_s, + SecretKey *output_secret_key_a, AccountAddress *, size_t *record_index, KeyImage *keyimage) override; }; } // namespace cn diff --git a/src/Core/WalletNode.cpp b/src/Core/WalletNode.cpp index cee27609..b3b48e43 100644 --- a/src/Core/WalletNode.cpp +++ b/src/Core/WalletNode.cpp @@ -37,9 +37,9 @@ WalletNode::WalletNode(Node *inproc_node, logging::ILogger &log, const Config &c : WalletSync(log, config, wallet_state, std::bind(&WalletNode::advance_long_poll, this)) , m_inproc_node(inproc_node) { if (!config.walletd_bind_ip.empty() && config.walletd_bind_port != 0) - m_api.reset(new http::Server(config.walletd_bind_ip, config.walletd_bind_port, + m_api = std::make_unique(config.walletd_bind_ip, config.walletd_bind_port, std::bind(&WalletNode::on_api_http_request, this, _1, _2, _3), - std::bind(&WalletNode::on_api_http_disconnect, this, _1))); + std::bind(&WalletNode::on_api_http_disconnect, this, _1)); } WalletNode::~WalletNode() {} // we have unique_ptr to incomplete type @@ -177,10 +177,12 @@ bool WalletNode::on_get_addresses(http::Client *, http::RequestBody &&, json_rpc for (size_t i = request.from_address; i < response.total_address_count; ++i) { if (response.addresses.size() >= request.max_count) break; - AccountAddress addr = wa.record_to_address(wa.get_records().at(i)); + WalletRecord rec; + AccountAddress addr; + wa.get_record(i, &rec, &addr); response.addresses.push_back(m_wallet_state.get_currency().account_address_as_string(addr)); if (request.need_secret_spend_keys) - response.secret_spend_keys.push_back(wa.get_records().at(i).spend_secret_key); + response.secret_spend_keys.push_back(rec.spend_secret_key); } return true; } @@ -189,9 +191,7 @@ bool WalletNode::on_get_wallet_info(http::Client *, http::RequestBody &&, json_r api::walletd::GetWalletInfo::Request &&request, api::walletd::GetWalletInfo::Response &response) { const Wallet &wa = m_wallet_state.get_wallet(); response.view_only = wa.is_view_only(); - response.deterministic = wa.is_deterministic(); - response.auditable = wa.is_auditable(); - response.unlinkable = wa.is_unlinkable(); + response.amethyst = wa.is_amethyst(); response.can_view_outgoing_addresses = wa.can_view_outgoing_addresses(); response.total_address_count = wa.get_actual_records_count(); response.wallet_creation_timestamp = wa.get_oldest_timestamp(); @@ -201,7 +201,7 @@ bool WalletNode::on_get_wallet_info(http::Client *, http::RequestBody &&, json_r if (!m_config.secrets_via_api) throw json_rpc::Error(json_rpc::INVALID_PARAMS, "To allow getting secrets via API, walletd must be launched with '--secrets-via-api' argument."); - if (wa.is_deterministic()) + if (wa.is_amethyst()) response.mnemonic = m_wallet_state.get_wallet().export_keys(); else response.import_keys = m_wallet_state.get_wallet().export_keys(); @@ -210,11 +210,12 @@ bool WalletNode::on_get_wallet_info(http::Client *, http::RequestBody &&, json_r } return true; } + bool WalletNode::on_get_wallet_records(http::Client *, http::RequestBody &&, json_rpc::Request &&, api::walletd::GetWalletRecords::Request &&request, api::walletd::GetWalletRecords::Response &response) { const Wallet &wa = m_wallet_state.get_wallet(); if (request.create) { - if (!wa.is_deterministic()) + if (!wa.is_amethyst()) throw json_rpc::Error( json_rpc::INVALID_PARAMS, "wallet is not deterministic, impossible to create addresses by index"); if (request.count == std::numeric_limits::max()) @@ -228,8 +229,9 @@ bool WalletNode::on_get_wallet_records(http::Client *, http::RequestBody &&, jso for (size_t i = request.index; i < response.total_count; ++i) { if (response.records.size() >= request.count) break; - const WalletRecord &record = wa.get_records().at(i); - AccountAddress addr = wa.record_to_address(record); + WalletRecord record; + AccountAddress addr; + wa.get_record(i, &record, &addr); api::walletd::GetWalletRecords::Record wr; wr.index = i; wr.address = m_wallet_state.get_currency().account_address_as_string(addr); @@ -267,14 +269,15 @@ bool WalletNode::on_create_addresses(http::Client *, http::RequestBody &&, json_ // throw json_rpc::Error(json_rpc::INVALID_PARAMS, "wallet is view-only, impossible to create addresses"); if (request.secret_spend_keys.empty()) return true; + std::vector addresses; auto records = m_wallet_state.generate_new_addresses( - request.secret_spend_keys, request.creation_timestamp, platform::now_unix_timestamp()); + request.secret_spend_keys, request.creation_timestamp, platform::now_unix_timestamp(), &addresses); response.addresses.reserve(records.size()); response.secret_spend_keys.reserve(records.size()); - for (auto &&rec : records) { - AccountAddress addr = m_wallet_state.get_wallet().record_to_address(rec); - response.addresses.push_back(m_wallet_state.get_currency().account_address_as_string(addr)); - response.secret_spend_keys.push_back(rec.spend_secret_key); + for (size_t i = 0; i != records.size(); ++i) { + // AccountAddress addr = m_wallet_state.get_wallet().record_to_address(rec); + response.addresses.push_back(m_wallet_state.get_currency().account_address_as_string(addresses.at(i))); + response.secret_spend_keys.push_back(records.at(i).spend_secret_key); } return true; } @@ -360,14 +363,11 @@ bool WalletNode::on_create_transaction(http::Client *who, http::RequestBody &&ra if (request.transaction.anonymity > 100) // Arbitrary value throw json_rpc::Error(api::walletd::CreateTransaction::TOO_MUCH_ANONYMITY, "Wallet will not create transactions with anonymity > 100 because large anonymity values actually reduce anonymity due to tiny number of similar transactions"); - if (request.transaction.anonymity != 0 && m_wallet_state.get_wallet().is_auditable()) - request.transaction.anonymity = 0; + // if (request.transaction.anonymity != 0 && m_wallet_state.get_wallet().is_auditable()) + // request.transaction.anonymity = 0; // throw json_rpc::Error(api::walletd::CreateTransaction::TOO_MUCH_ANONYMITY, // "Auditable wallet requires 0 anonymity when sending (checked and enforced by consensus)"); - const auto min_anonymity = - m_wallet_state.get_wallet().is_auditable() - ? 0 - : m_wallet_state.get_currency().minimum_anonymity(m_wallet_state.get_tip().major_version); + const auto min_anonymity = m_wallet_state.get_currency().minimum_anonymity(m_wallet_state.get_tip().major_version); const auto good_anonymity = std::max(min_anonymity, request.transaction.anonymity); Height confirmed_height = api::ErrorWrongHeight::fix_height_or_depth( request.confirmed_height_or_depth, m_wallet_state.get_tip_height(), true, false); @@ -620,9 +620,9 @@ bool WalletNode::on_create_transaction(http::Client *who, http::RequestBody &&ra return false; } -bool WalletNode::on_create_sendproof(http::Client *, http::RequestBody &&, json_rpc::Request &&, - api::walletd::CreateSendproof::Request &&request, api::walletd::CreateSendproof::Response &response) { - std::set addresses; +bool WalletNode::on_create_sendproof(http::Client *who, http::RequestBody &&raw_request, + json_rpc::Request &&raw_js_request, api::walletd::CreateSendproof::Request &&request, + api::walletd::CreateSendproof::Response &response) { TransactionPrefix tx; api::Transaction ptx; if (!m_wallet_state.api_get_transaction(request.transaction_hash, true, &tx, &ptx)) @@ -633,26 +633,52 @@ bool WalletNode::on_create_sendproof(http::Client *, http::RequestBody &&, json_ AccountAddress address; invariant(m_wallet_state.get_currency().parse_account_address_string(tr.address, &address), ""); if (!m_wallet_state.get_wallet().is_our_address(address)) - addresses.insert(address); + request.addresses.push_back(tr.address); } } - for (auto &&addr : request.addresses) { - AccountAddress address; - if (!m_wallet_state.get_currency().parse_account_address_string(addr, &address)) - throw api::ErrorAddress(api::ErrorAddress::ADDRESS_FAILED_TO_PARSE, "Failed to parse address", addr); - addresses.insert(address); - } - for (auto &&address : addresses) { - Sendproof sp; - sp.transaction_hash = request.transaction_hash; - sp.message = request.message; - sp.address = address; - if (m_wallet_state.api_create_proof(tx, sp)) { - response.sendproofs.push_back(seria::to_json_value(sp, m_wallet_state.get_currency()).to_string()); - } else - response.sendproofs.push_back(std::string()); // No proof can be created for address - } - return true; + api::cnd::GetRawTransaction::Request ra_request; + ra_request.hash = request.transaction_hash; + http::RequestBody new_request = + json_rpc::create_request(api::cnd::url(), api::cnd::GetRawTransaction::method(), ra_request); + new_request.r.basic_authorization = m_config.bytecoind_authorization; + m_log(logging::TRACE) << "sending get_raw_transaction, body=" << new_request.body << std::endl; + add_waiting_command(who, std::move(raw_request), raw_js_request.get_id().get(), std::move(new_request), + [=](const WaitingClient &wc, http::ResponseBody &&raw_transaction_response) mutable { + m_log(logging::TRACE) << "got response to get_raw_transaction, status=" << raw_transaction_response.r.status + << " body " << raw_transaction_response.body << std::endl; + if (raw_transaction_response.r.status != 200) { + throw json_rpc::Error( + json_rpc::INTERNAL_ERROR, "Transaction not in blockchain or " CRYPTONOTE_NAME "d out of sync"); + } + api::walletd::CreateSendproof::Response last_response; + json_rpc::Response json_resp(raw_transaction_response.body); + api::cnd::GetRawTransaction::Response ra_response; + json_resp.get_result(ra_response); + + if (seria::to_binary(static_cast(tx)) != seria::to_binary(ra_response.raw_transaction)) + throw json_rpc::Error( + json_rpc::INTERNAL_ERROR, "Wrong transaction body returned from " CRYPTONOTE_NAME "d"); + for (const auto &addr_str : request.addresses) { + std::string sp = m_wallet_state.api_create_proof( + tx, ra_response.mixed_public_keys, addr_str, request.transaction_hash, request.message); + last_response.sendproofs.push_back(sp); + } + http::ResponseBody last_http_response(wc.original_request.r); + last_http_response.r.headers.push_back({"Content-Type", "application/json; charset=utf-8"}); + last_http_response.r.status = 200; + last_http_response.set_body(json_rpc::create_response_body(last_response, wc.original_jsonrpc_id)); + wc.original_who->write(std::move(last_http_response)); + }, + [=](const WaitingClient &wc, std::string err) mutable { + m_log(logging::INFO) << "got error to get_raw_transaction from " CRYPTONOTE_NAME "d, " << err << std::endl; + http::ResponseBody last_http_response(wc.original_request.r); + last_http_response.r.headers.push_back({"Content-Type", "application/json; charset=utf-8"}); + last_http_response.r.status = 200; + last_http_response.set_body(json_rpc::create_error_response_body( + json_rpc::Error(json_rpc::INTERNAL_ERROR, err), wc.original_jsonrpc_id)); + wc.original_who->write(std::move(last_http_response)); + }); + return false; } bool WalletNode::on_send_transaction(http::Client *who, http::RequestBody &&raw_request, diff --git a/src/Core/WalletSerializationV1.cpp b/src/Core/WalletSerializationV1.cpp index 000c46a4..66762034 100644 --- a/src/Core/WalletSerializationV1.cpp +++ b/src/Core/WalletSerializationV1.cpp @@ -319,7 +319,7 @@ void WalletSerializerV1::load_wallets(common::IInputStream &source, CryptoContex if (!keys_match(dto.spend_secret_key, dto.spend_public_key)) throw std::runtime_error("Restored spend public key doesn't correspond to secret key"); } else { - if (!crypto::key_isvalid(dto.spend_public_key)) { + if (!crypto::key_in_main_subgroup(dto.spend_public_key)) { throw std::runtime_error("WRONG_PASSWORD - Public spend key is incorrect"); } } diff --git a/src/Core/WalletState.cpp b/src/Core/WalletState.cpp index ffc43872..b60bbe5e 100644 --- a/src/Core/WalletState.cpp +++ b/src/Core/WalletState.cpp @@ -6,6 +6,7 @@ #include "CryptoNoteTools.hpp" #include "TransactionBuilder.hpp" #include "TransactionExtra.hpp" +#include "common/Base58.hpp" #include "common/Varint.hpp" #include "common/string.hpp" #include "crypto/crypto.hpp" @@ -34,16 +35,6 @@ Amount WalletState::DeltaState::add_incoming_keyimage(Height height, const KeyIm return 0; // It does not know } -Amount WalletState::DeltaState::add_incoming_deterministic_input( - Height block_height, Amount am, size_t gi, const PublicKey &pk) { - auto tit = m_transactions.find(m_last_added_transaction); - if (tit == m_transactions.end()) - return 0; - if (tit->second.used_ki_or_pk.insert(pk).second) - m_used_kis_or_pks[pk] += 1; - return 0; // It does not know -} - void WalletState::DeltaState::add_transaction( Height, const Hash &tid, const TransactionPrefix &tx, const api::Transaction &ptx) { invariant(m_transactions.insert(std::make_pair(tid, DeltaStateTransaction{tx, ptx, {}})).second, @@ -89,8 +80,7 @@ void WalletState::DeltaState::clear() { } WalletState::WalletState(Wallet &wallet, logging::ILogger &log, const Config &config, const Currency ¤cy) - : WalletStateBasic( - log, config, currency, wallet.get_cache_name(), wallet.is_view_only() && wallet.is_deterministic()) + : WalletStateBasic(log, config, currency, wallet.get_cache_name()) , m_log_redo_block(std::chrono::steady_clock::now()) , m_wallet(wallet) { wallet_addresses_updated(); @@ -109,7 +99,9 @@ WalletState::WalletState(Wallet &wallet, logging::ILogger &log, const Config &co void WalletState::wallet_addresses_updated() { Timestamp undo_timestamp = std::numeric_limits::max(); try { - for (const auto &wa : m_wallet.get_records()) { + for (size_t i = 0; i != m_wallet.get_actual_records_count(); ++i) { + WalletRecord wa; + m_wallet.get_record(i, &wa, nullptr); auto keyuns = ADDRESSES_PREFIX + DB::to_binary_key(wa.spend_public_key.data, sizeof(wa.spend_public_key.data)); std::string st; @@ -139,9 +131,9 @@ void WalletState::wallet_addresses_updated() { } std::vector WalletState::generate_new_addresses( - const std::vector &sks, Timestamp ct, Timestamp now) { + const std::vector &sks, Timestamp ct, Timestamp now, std::vector *addresses) { bool rescan_from_ct = false; - auto result = m_wallet.generate_new_addresses(sks, ct, now, &rescan_from_ct); + auto result = m_wallet.generate_new_addresses(sks, ct, now, addresses, &rescan_from_ct); if (rescan_from_ct) wallet_addresses_updated(); return result; @@ -277,6 +269,16 @@ void WalletState::fix_payment_queue_after_undo_redo() { m_pq_version += 1; } +static void fill_tx_output_public_keys(std::vector *output_public_keys, const TransactionPrefix &tx) { + for (size_t out_index = 0; out_index != tx.outputs.size(); ++out_index) { + const auto &output = tx.outputs.at(out_index); + if (output.type() != typeid(OutputKey)) + continue; + const auto &key_output = boost::get(output); + output_public_keys->push_back(key_output.public_key); + } +} + void WalletState::add_transaction_to_mempool(Hash tid, Transaction &&tx, bool from_pq) { if (m_memory_state.get_transactions().count(tid) != 0) return; @@ -284,8 +286,25 @@ void WalletState::add_transaction_to_mempool(Hash tid, Transaction &&tx, bool fr m_log(logging::INFO) << "Now " << (from_pq ? "PQ" : "node") << " transaction " << tid << " is in MS size before adding=" << m_memory_state.get_transactions().size() << std::endl; std::vector global_indices(tx.outputs.size(), 0); - PreparedWalletTransaction pwtx(std::move(tx), m_wallet.get_output_handler()); - if (!redo_transaction(pwtx, global_indices, &m_memory_state, false, tid, get_tip_height() + 1, Hash{}, now)) { + PreparedWalletTransaction pwtx; + if (m_wallet.get_hw()) { + // TODO - very inefficient code for now + std::vector output_public_keys; + fill_tx_output_public_keys(&output_public_keys, tx); + auto P_v = m_wallet.get_hw()->mul_by_view_secret_key(output_public_keys); + size_t key_counter = 0; + pwtx = PreparedWalletTransaction(std::move(tx), + [&](bool is_tx_amethyst, const PublicKey &tx_public_key, boost::optional *kd, + const Hash &tx_inputs_hash, size_t output_index, const OutputKey &key_output, PublicKey *address_S, + SecretKey *secret_scalar) { + *address_S = crypto::unlinkable_underive_address_S_step2(P_v.at(key_counter++), tx_inputs_hash, + output_index, key_output.public_key, key_output.encrypted_secret, secret_scalar); + }); + + } else { + pwtx = PreparedWalletTransaction(std::move(tx), m_wallet.get_output_handler()); + } + if (!redo_transaction(pwtx, global_indices, 0, &m_memory_state, false, tid, get_tip_height() + 1, Hash{}, now)) { } // just ignore result } @@ -327,17 +346,42 @@ bool WalletState::sync_with_blockchain(api::cnd::SyncBlocks::Response &resp) { } if (empty_chain()) reset_chain(resp.start_height); - preparator.cancel_work(); - preparator.start_work(resp, m_wallet.get_output_handler()); + if (!m_wallet.get_hw()) { + preparator.cancel_work(); + preparator.start_work(resp, m_wallet.get_output_handler()); + } while (get_tip_height() + 1 < resp.start_height + resp.blocks.size()) { size_t bin = get_tip_height() + 1 - resp.start_height; const auto &header = resp.blocks.at(bin).header; if (!empty_chain() && header.previous_block_hash != get_tip_bid()) return false; if (header.timestamp + m_currency.block_future_time_limit >= m_wallet.get_oldest_timestamp()) { - const auto &block_gi = resp.blocks.at(bin).output_indexes; - PreparedWalletBlock pb = preparator.get_ready_work(get_tip_height() + 1); - // PreparedWalletBlock pb(std::move(resp.blocks.at(bin).block), m_wallet.get_view_secret_key()); + auto &sync_block = resp.blocks.at(bin); + const auto &block_gi = sync_block.output_stack_indexes; + PreparedWalletBlock pb; + if (m_wallet.get_hw()) { + // TODO - very inefficient code for now + // Also will repeat getting blocks for sync indefinetely when hw is disconnected + std::vector output_public_keys; + fill_tx_output_public_keys(&output_public_keys, sync_block.raw_header.base_transaction); + for (const auto &tx : sync_block.raw_transactions) + fill_tx_output_public_keys(&output_public_keys, tx); + auto P_v = m_wallet.get_hw()->mul_by_view_secret_key(output_public_keys); + size_t key_counter = 0; + pb = PreparedWalletBlock(std::move(sync_block.raw_header), std::move(sync_block.raw_transactions), + sync_block.transactions.at(0).hash, + [&](bool is_tx_amethyst, const PublicKey &tx_public_key, boost::optional *kd, + const Hash &tx_inputs_hash, size_t output_index, const OutputKey &key_output, + PublicKey *address_S, SecretKey *secret_scalar) { + *address_S = + crypto::unlinkable_underive_address_S_step2(P_v.at(key_counter++), tx_inputs_hash, + output_index, key_output.public_key, key_output.encrypted_secret, secret_scalar); + }); + } else + pb = preparator.get_ready_work(get_tip_height() + 1); + // pb = PreparedWalletBlock(std::move(sync_block.raw_header), + // std::move(sync_block.raw_transactions), sync_block.transactions.at(0).hash, + // m_wallet.get_output_handler()); redo_block(header, pb, block_gi, get_tip_height() + 1); // push_chain(header); // pop_chain(); @@ -378,9 +422,9 @@ bool WalletState::sync_with_blockchain(api::cnd::SyncMemPool::Response &resp) { } for (size_t i = 0; i != resp.added_raw_transactions.size(); ++i) { Transaction tx; - static_cast(tx) = std::move(resp.added_raw_transactions[i]); - if (i < resp.added_signatures.size()) - tx.signatures = std::move(resp.added_signatures[i]); + static_cast(tx) = std::move(resp.added_raw_transactions.at(i)); + // if (i < resp.added_signatures.size()) + // tx.signatures = std::move(resp.added_signatures[i]); // seria::from_binary(tx, resp.added_binary_transactions[i]); Hash tid = resp.added_transactions.at(i).hash; // get_transaction_hash(tx); m_pool_hashes.insert(tid); @@ -394,20 +438,28 @@ bool WalletState::redo_block(const api::BlockHeader &header, const PreparedWalle const BlockChainState::BlockGlobalIndices &global_indices, Height height) { invariant(height == get_tip_height() + 1, "Redo of incorrect block height"); if (global_indices.size() != pb.transactions.size() + 1) - return false; // Bad node - TODO - Hash base_hash = pb.base_transaction_hash; // get_transaction_hash(pb.base_transaction.tx); - if (!redo_transaction(pb.base_transaction, global_indices[0], this, true, base_hash, get_tip_height() + 1, - header.hash, pb.header.timestamp)) { + return false; // Bad node - TODO + Hash base_hash = pb.base_transaction_hash; // get_transaction_hash(pb.base_transaction.tx); + size_t key_outputs_count = get_tx_key_outputs_count(pb.base_transaction.tx); + for (const auto &tx : pb.transactions) + key_outputs_count += get_tx_key_outputs_count(tx.tx); + size_t start_global_key_output_index = header.already_generated_key_outputs - key_outputs_count; + if (!redo_transaction(pb.base_transaction, global_indices[0], start_global_key_output_index, this, true, base_hash, + get_tip_height() + 1, header.hash, pb.header.timestamp)) { } // Just ignore - TODO + start_global_key_output_index += get_tx_key_outputs_count(pb.base_transaction.tx); for (size_t tx_index = 0; tx_index != pb.transactions.size(); ++tx_index) { const Hash tid = pb.header.transaction_hashes.at(tx_index); if (m_pool_hashes.erase(tid) != 0) remove_transaction_from_mempool(tid, false); m_memory_state.undo_transaction(tid); - if (!redo_transaction(pb.transactions.at(tx_index), global_indices.at(tx_index + 1), this, false, tid, - get_tip_height() + 1, header.hash, pb.header.timestamp)) { + if (!redo_transaction(pb.transactions.at(tx_index), global_indices.at(tx_index + 1), + start_global_key_output_index, this, false, tid, get_tip_height() + 1, header.hash, + pb.header.timestamp)) { } // just ignore - TODO + start_global_key_output_index += get_tx_key_outputs_count(pb.transactions.at(tx_index).tx); } + invariant(header.already_generated_key_outputs == start_global_key_output_index, ""); unlock(height, header.timestamp_median); // If ex has lock_time in the past, it will be added to lock index in redo, then immediately unlocked here return true; @@ -417,7 +469,7 @@ bool WalletState::redo_block(const api::BlockHeader &header, const PreparedWalle bool WalletState::parse_raw_transaction(bool is_base, api::Transaction *ptx, std::vector *input_transfers, std::vector *output_transfers, Amount *unrecognized_inputs_amount, const PreparedWalletTransaction &pwtx, Hash tid, - const std::vector &global_indices, Height block_height) const { + const std::vector &global_indices, size_t start_global_key_output_index, Height block_height) const { if (global_indices.size() != pwtx.tx.outputs.size()) // Bad node return false; // Without global indices we cannot do anything with transaction const TransactionPrefix &tx = pwtx.tx; @@ -449,36 +501,14 @@ bool WalletState::parse_raw_transaction(bool is_base, api::Transaction *ptx, return false; ptx->anonymity = std::min(ptx->anonymity, in.output_indexes.size() - 1); api::Output existing_output; - if (m_wallet.is_det_viewonly()) { - std::vector global_indexes; - if (!relative_output_offsets_to_absolute(&global_indexes, in.output_indexes)) - return false; - size_t my_index = std::numeric_limits::max(); - for (auto index : global_indexes) - if (try_adding_deterministic_input(in.amount, index, &existing_output) && - (in.output_indexes.size() == 1 || - TransactionBuilder::encrypt_real_index(existing_output.public_key, - m_wallet.get_view_secret_key()) == in.encrypted_real_index)) { - my_index = index; - break; - } - if (my_index != std::numeric_limits::max()) { - api::Transfer &transfer = transfer_map_inputs[existing_output.address]; - transfer.amount -= static_cast(existing_output.amount); - transfer.ours = true; - transfer.outputs.push_back(existing_output); - our_inputs = true; - continue; - } - } else { - if (try_adding_incoming_keyimage(in.key_image, &existing_output)) { - api::Transfer &transfer = transfer_map_inputs[existing_output.address]; - transfer.amount -= static_cast(existing_output.amount); - transfer.ours = true; - transfer.outputs.push_back(existing_output); - our_inputs = true; - continue; - } + + if (try_adding_incoming_keyimage(in.key_image, &existing_output)) { + api::Transfer &transfer = transfer_map_inputs[existing_output.address]; + transfer.amount -= static_cast(existing_output.amount); + transfer.ours = true; + transfer.outputs.push_back(existing_output); + our_inputs = true; + continue; } *unrecognized_inputs_amount += in.amount; } @@ -495,14 +525,16 @@ bool WalletState::parse_raw_transaction(bool is_base, api::Transaction *ptx, const auto &output = tx.outputs.at(out_index); if (output.type() != typeid(OutputKey)) continue; - const auto &key_output = boost::get(output); - const auto &spend_public_key = pwtx.spend_keys.at(out_index); - const auto &spend_secret = pwtx.output_secret_scalars.at(out_index); + const auto &key_output = boost::get(output); + const auto &address_S = pwtx.address_public_keys.at(out_index); + const auto &spend_scalar = pwtx.output_spend_scalars.at(out_index); if (!add_amount(output_amount, key_output.amount)) return false; - bool our_key = false; api::Output out; - out.index = global_indices.at(out_index); + out.global_index = start_global_key_output_index; + start_global_key_output_index += 1; + out.amount = key_output.amount; + out.stack_index = global_indices.at(out_index); out.height = block_height; out.index_in_transaction = out_index; out.public_key = key_output.public_key; @@ -510,12 +542,13 @@ bool WalletState::parse_raw_transaction(bool is_base, api::Transaction *ptx, out.unlock_block_or_timestamp = tx.unlock_block_or_timestamp; AccountAddress address; - KeyPair output_keypair; - if (m_wallet.detect_our_output(tid, pwtx.inputs_hash, pwtx.derivation, out_index, spend_public_key, - spend_secret, key_output, &out.amount, &output_keypair, &address)) { + size_t record_index = 0; + SecretKey output_secret_key_s; + SecretKey output_secret_key_a; + if (m_wallet.detect_our_output(tx.version, tid, pwtx.inputs_hash, pwtx.derivation, out_index, address_S, + spend_scalar, key_output, &out.amount, &output_secret_key_s, &output_secret_key_a, &address, + &record_index, &out.key_image)) { // out.dust = m_currency.is_dust(key_output.amount); - if (output_keypair.secret_key != SecretKey{}) - out.key_image = generate_key_image(output_keypair.public_key, output_keypair.secret_key); api::Transfer &transfer = transfer_map_outputs[true][address]; if (transfer.address.empty()) transfer.address = m_currency.account_address_as_string(address); @@ -526,11 +559,12 @@ bool WalletState::parse_raw_transaction(bool is_base, api::Transaction *ptx, transfer.outputs.push_back(out); } our_outputs = true; - our_key = true; + continue; } - if (!our_key && our_inputs && - TransactionBuilder::detect_not_our_output(&m_wallet, is_tx_amethyst, tid, pwtx.inputs_hash, &history, - &tx_keys, out_index, key_output, &out.amount, &address)) { + if (!our_inputs) + continue; + if (TransactionBuilder::detect_not_our_output(&m_wallet, is_tx_amethyst, tid, pwtx.inputs_hash, &history, + &tx_keys, out_index, key_output, &address)) { // out.dust = m_currency.is_dust(key_output.amount); api::Transfer &transfer = transfer_map_outputs[false][address]; if (transfer.address.empty()) @@ -538,6 +572,10 @@ bool WalletState::parse_raw_transaction(bool is_base, api::Transaction *ptx, out.address = transfer.address; transfer.amount += key_output.amount; transfer.outputs.push_back(out); + } else { + if (is_tx_amethyst && m_wallet.can_view_outgoing_addresses()) + m_log(logging::WARNING) << "Auditor warning - failed to detect destination address for output #" + << out_index << " in tx " << tid << std::endl; } } for (bool ours : {false, true}) @@ -561,11 +599,28 @@ bool WalletState::parse_raw_transaction(bool is_base, api::Transaction *ptx, bool WalletState::parse_raw_transaction(bool is_base, api::Transaction &ptx, Transaction &&tx, Hash tid) const { std::vector global_indices(tx.outputs.size(), 0); Amount unrecognized_inputs_amount = 0; - PreparedWalletTransaction pwtx(std::move(tx), m_wallet.get_output_handler()); + PreparedWalletTransaction pwtx; + if (m_wallet.get_hw()) { + // TODO - very inefficient code for now + std::vector output_public_keys; + fill_tx_output_public_keys(&output_public_keys, tx); + auto P_v = m_wallet.get_hw()->mul_by_view_secret_key(output_public_keys); + size_t key_counter = 0; + pwtx = PreparedWalletTransaction(std::move(tx), + [&](bool is_tx_amethyst, const PublicKey &tx_public_key, boost::optional *kd, + const Hash &tx_inputs_hash, size_t output_index, const OutputKey &key_output, PublicKey *address_S, + SecretKey *secret_scalar) { + *address_S = crypto::unlinkable_underive_address_S_step2(P_v.at(key_counter++), tx_inputs_hash, + output_index, key_output.public_key, key_output.encrypted_secret, secret_scalar); + }); + + } else { + pwtx = PreparedWalletTransaction(std::move(tx), m_wallet.get_output_handler()); + } std::vector input_transfers; std::vector output_transfers; parse_raw_transaction(is_base, &ptx, &input_transfers, &output_transfers, &unrecognized_inputs_amount, pwtx, tid, - global_indices, get_tip_height()); + global_indices, 0, get_tip_height()); // We do not know "from" addresses, so leave address empty ptx.transfers.insert(ptx.transfers.end(), input_transfers.begin(), input_transfers.end()); ptx.transfers.insert(ptx.transfers.end(), output_transfers.begin(), output_transfers.end()); @@ -585,13 +640,14 @@ const std::map &WalletState::get_mempool_kis_or void WalletState::on_first_transaction_found(Timestamp ts) { m_wallet.on_first_output_found(ts); } bool WalletState::redo_transaction(const PreparedWalletTransaction &pwtx, const std::vector &global_indices, - IWalletState *delta_state, bool is_base, Hash tid, Height block_height, Hash bid, Timestamp tx_timestamp) { + size_t start_global_key_output_index, IWalletState *delta_state, bool is_base, Hash tid, Height block_height, + Hash bid, Timestamp tx_timestamp) { api::Transaction ptx; Amount unrecognized_inputs_amount = 0; std::vector input_transfers; std::vector output_transfers; if (!parse_raw_transaction(is_base, &ptx, &input_transfers, &output_transfers, &unrecognized_inputs_amount, pwtx, - tid, global_indices, block_height)) + tid, global_indices, start_global_key_output_index, block_height)) return false; // not ours ptx.block_hash = bid; ptx.timestamp = tx_timestamp; @@ -608,10 +664,7 @@ bool WalletState::redo_transaction(const PreparedWalletTransaction &pwtx, const } for (auto &&tr : input_transfers) { for (auto &&out : tr.outputs) { - if (m_wallet.is_det_viewonly()) - delta_state->add_incoming_deterministic_input(block_height, out.amount, out.index, out.public_key); - else - delta_state->add_incoming_keyimage(block_height, out.key_image); + delta_state->add_incoming_keyimage(block_height, out.key_image); } } // order of add_transaction is important - DeltaState associates subsequent add_ with last added transaction @@ -657,90 +710,148 @@ bool WalletState::api_get_transaction(Hash tid, bool check_pool, TransactionPref return get_transaction(tid, tx, atx); } -bool WalletState::api_create_proof(const TransactionPrefix &tx, Sendproof &sp) const { +std::string WalletState::api_create_proof(const TransactionPrefix &tx, + const std::vector> &mixed_public_keys, const std::string &addr_str, const Hash &tid, + const std::string &message) const { const Hash tx_inputs_hash = get_transaction_inputs_hash(tx); - const Hash message_hash = crypto::cn_fast_hash(sp.message.data(), sp.message.size()); + const Hash message_hash = crypto::cn_fast_hash(message.data(), message.size()); + AccountAddress address; + if (!m_currency.parse_account_address_string(addr_str, &address)) + throw api::ErrorAddress(api::ErrorAddress::ADDRESS_FAILED_TO_PARSE, "Failed to parse wallet address", addr_str); if (tx.version < m_currency.amethyst_transaction_version) { - if (sp.address.type() != typeid(AccountAddressSimple)) - return false; + if (address.type() != typeid(AccountAddressSimple)) + return std::string(); // TODO - throw? + SendproofAmethyst sp; + sp.version = tx.version; + sp.address_simple = boost::get(address); + sp.message = message; + sp.transaction_hash = tid; KeyPair tx_keys = TransactionBuilder::transaction_keys_from_seed(tx_inputs_hash, m_wallet.get_tx_derivation_seed()); - const auto &addr = boost::get(sp.address); - SendproofKey var; - var.derivation = crypto::generate_key_derivation(addr.view_public_key, tx_keys.secret_key); - var.signature = crypto::generate_sendproof( - tx_keys.public_key, tx_keys.secret_key, addr.view_public_key, var.derivation, message_hash); + const auto &addr = boost::get(address); + sp.derivation = crypto::generate_key_derivation(addr.V, tx_keys.secret_key); + sp.signature = + crypto::generate_sendproof(tx_keys.public_key, tx_keys.secret_key, addr.V, sp.derivation, message_hash); Amount total_amount = 0; for (size_t out_index = 0; out_index != tx.outputs.size(); ++out_index) { const auto &output = tx.outputs.at(out_index); if (output.type() != typeid(OutputKey)) continue; const auto &key_output = boost::get(output); - const PublicKey spend_key = underive_public_key(var.derivation, out_index, key_output.public_key); - if (spend_key != addr.spend_public_key) + const PublicKey address_S = underive_address_S(sp.derivation, out_index, key_output.public_key); + if (address_S != addr.S) continue; total_amount += key_output.amount; } - sp.amount = total_amount; - sp.proof = var; - return total_amount != 0; + if (total_amount == 0) + return std::string(); + const auto body = seria::to_binary(sp); + return common::base58::encode_addr(m_currency.sendproof_base58_prefix, body); } - SendproofAmethyst var; + SendproofAmethyst sp; + sp.version = m_currency.amethyst_transaction_version; + sp.message = message; + sp.transaction_hash = tid; Amount total_amount = 0; + std::vector all_output_det_keys; for (size_t out_index = 0; out_index != tx.outputs.size(); ++out_index) { const auto &output = tx.outputs.at(out_index); if (output.type() != typeid(OutputKey)) continue; - const auto &key_output = boost::get(output); - const KeyPair output_det_keys = TransactionBuilder::deterministic_keys_from_seed( - tx_inputs_hash, m_wallet.get_tx_derivation_seed(), common::get_varint_data(out_index)); - BinaryArray ba(std::begin(output_det_keys.public_key.data), std::end(output_det_keys.public_key.data)); - const SecretKey output_secret_scalar = crypto::hash_to_scalar(ba.data(), ba.size()); - const PublicKey output_secret_point = crypto::hash_to_point(ba.data(), ba.size()); - ba.push_back(0); // Or use cn_fast_hash64 - const Hash output_secret2 = crypto::cn_fast_hash(ba.data(), ba.size()); - if (sp.address.type() == typeid(AccountAddressSimple)) { - const uint8_t should_be_encrypted_address_type = AccountAddressSimple::type_tag ^ output_secret2.data[0]; - if (key_output.encrypted_address_type != should_be_encrypted_address_type) - continue; // Protocol violation to fool auditor - const auto &addr = boost::get(sp.address); - AccountAddressSimple address; - crypto::linkable_underive_address(output_secret_scalar, tx_inputs_hash, out_index, key_output.public_key, - key_output.encrypted_secret, &address.spend_public_key, &address.view_public_key); - if (address != addr) - continue; - SendproofAmethyst::Element el{out_index, output_det_keys.public_key, Signature{}}; - el.signature = crypto::amethyst_generate_sendproof( - output_det_keys, sp.transaction_hash, message_hash, address.spend_public_key, address.view_public_key); - var.elements.push_back(el); - total_amount += key_output.amount; - continue; - } - if (sp.address.type() == typeid(AccountAddressUnlinkable)) { - const auto &addr = boost::get(sp.address); - const uint8_t should_be_encrypted_address_type = - (addr.is_auditable ? AccountAddressUnlinkable::type_tag_auditable - : AccountAddressUnlinkable::type_tag) ^ - output_secret2.data[0]; - if (key_output.encrypted_address_type != should_be_encrypted_address_type) - continue; // Protocol violation to fool auditor - AccountAddressUnlinkable address; - crypto::unlinkable_underive_address(output_secret_point, tx_inputs_hash, out_index, key_output.public_key, - key_output.encrypted_secret, &address.s, &address.sv); - address.is_auditable = key_output.is_auditable; - if (address != addr) - continue; - SendproofAmethyst::Element el{out_index, output_det_keys.public_key, Signature{}}; - el.signature = crypto::amethyst_generate_sendproof( - output_det_keys, sp.transaction_hash, message_hash, address.s, address.sv); - var.elements.push_back(el); - total_amount += key_output.amount; - continue; + const auto &key_output = boost::get(output); + KeyPair output_det_keys; + if (m_wallet.get_hw()) { + m_wallet.get_hw()->generate_output_secret(tx_inputs_hash, out_index, &output_det_keys.public_key); + } else { + output_det_keys = TransactionBuilder::deterministic_keys_from_seed( + tx_inputs_hash, m_wallet.get_tx_derivation_seed(), common::get_varint_data(out_index)); } + OutputKey should_be_output = TransactionBuilder::create_output( + true, address, SecretKey{}, tx_inputs_hash, out_index, output_det_keys.public_key); + if (should_be_output.public_key != key_output.public_key || + should_be_output.encrypted_secret != key_output.encrypted_secret || + should_be_output.encrypted_address_type != key_output.encrypted_address_type) + continue; // output to different address or crypto protocol violated + sp.elements.push_back(SendproofAmethyst::Element{out_index, output_det_keys.public_key}); + all_output_det_keys.push_back(output_det_keys); + total_amount += key_output.amount; + } + const auto proof_body = seria::to_binary(sp); +// std::cout << "Proof body: " << common::to_hex(proof_body) << std::endl; + const auto proof_prefix_hash = crypto::cn_fast_hash(proof_body); +// std::cout << "Proof hash: " << proof_prefix_hash << std::endl; + if (tx.inputs.empty() || tx.inputs.at(0).type() != typeid(InputKey) || mixed_public_keys.empty()) + return std::string(); // TODO - throw? + const InputKey &in = boost::get(tx.inputs.at(0)); + if (in.output_indexes.size() != mixed_public_keys.at(0).size()) + return std::string(); // TODO - throw? + HeightGi heamgi; + if (!read_by_keyimage(in.key_image, &heamgi)) + return std::string(); // TODO - throw? + + TransactionPrefix other_tx; + api::Transaction other_atx; + if (!get_transaction(heamgi.transaction_hash, &other_tx, &other_atx) || + heamgi.index_in_transaction >= other_tx.outputs.size()) + return std::string(); + const auto &key_output = boost::get(other_tx.outputs.at(heamgi.index_in_transaction)); + Hash other_inputs_hash = get_transaction_inputs_hash(other_tx); + size_t sec_index = + std::find(mixed_public_keys.at(0).begin(), mixed_public_keys.at(0).end(), key_output.public_key) - + mixed_public_keys.at(0).begin(); + if (sec_index == mixed_public_keys.size()) + return std::string(); + + std::vector all_secret_keys_s; // empty if hw + std::vector all_secret_keys_a; // empty if hw + std::vector all_sec_indexes{sec_index}; + std::vector all_keyimages{in.key_image}; + std::vector> all_output_keys{mixed_public_keys.at(0)}; + std::vector spend_scalars; // for hw + std::vector address_indexes; + PublicKey address_S; + SecretKey spend_scalar; + boost::optional kd; + if (m_wallet.get_hw()) { + auto mulled_key = m_wallet.get_hw()->mul_by_view_secret_key({key_output.public_key}); + crypto::unlinkable_underive_address_S_step2(mulled_key.at(0), other_inputs_hash, heamgi.index_in_transaction, + key_output.public_key, key_output.encrypted_secret, &spend_scalar); + // TODO + } else { + m_wallet.get_output_handler()(other_tx.version, other_atx.public_key, &kd, other_inputs_hash, + heamgi.index_in_transaction, key_output, &address_S, &spend_scalar); } - sp.amount = total_amount; - sp.proof = var; - return total_amount != 0; + Amount other_amount = 0; + AccountAddress other_address; + size_t record_index = 0; + KeyImage other_key_image; + SecretKey output_secret_key_s; + SecretKey output_secret_key_a; + if (!m_wallet.detect_our_output(other_tx.version, other_atx.hash, other_inputs_hash, kd, + heamgi.index_in_transaction, address_S, spend_scalar, key_output, &other_amount, &output_secret_key_s, + &output_secret_key_a, &other_address, &record_index, &other_key_image)) + return std::string(); + if (other_key_image != in.key_image) + return std::string(); + spend_scalars.push_back(spend_scalar); + all_secret_keys_s.push_back(output_secret_key_s); + all_secret_keys_a.push_back(output_secret_key_a); + address_indexes.push_back(record_index); + + const RingSignatureAmethyst rsa = generate_ring_signature_auditable( + proof_prefix_hash, all_keyimages, all_output_keys, all_secret_keys_s, all_secret_keys_a, all_sec_indexes); + invariant(crypto::check_ring_signature_auditable(proof_prefix_hash, all_keyimages, all_output_keys, rsa), ""); + TransactionPrefix fake_prefix; + fake_prefix.version = tx.version; + fake_prefix.inputs.push_back(in); + BinaryArray sig_body = seria::to_binary(rsa, fake_prefix); +// std::cout << "Sig body: " << common::to_hex(sig_body) << std::endl; + + // TODO - refactor, common code with TransactionBuilder + BinaryArray total_body = proof_body; + common::append(total_body, sig_body); + + return common::base58::encode_addr(m_currency.sendproof_base58_prefix, total_body); } api::Block WalletState::api_get_pool_as_history(const std::string &address) const { diff --git a/src/Core/WalletState.hpp b/src/Core/WalletState.hpp index 2d1df332..73528b0f 100644 --- a/src/Core/WalletState.hpp +++ b/src/Core/WalletState.hpp @@ -41,8 +41,6 @@ class WalletState : public WalletStateBasic { Amount add_incoming_output(const api::Output &, const Hash &tid) override; // added amount may be lower Amount add_incoming_keyimage(Height, const KeyImage &) override; - Amount add_incoming_deterministic_input( - Height block_height, Amount am, size_t gi, const PublicKey &pk) override; void add_transaction( Height, const Hash &tid, const TransactionPrefix &tx, const api::Transaction &ptx) override; @@ -77,7 +75,9 @@ class WalletState : public WalletStateBasic { bool parse_raw_transaction(bool is_base, api::Transaction &ptx, Transaction &&tx, Hash tid) const; // Read state - bool api_create_proof(const TransactionPrefix &tx, Sendproof &sp) const; + std::string api_create_proof(const TransactionPrefix &tx, + const std::vector> &mixed_public_keys, const std::string &addr_str, const Hash &tid, + const std::string &message) const; api::Block api_get_pool_as_history(const std::string &address) const; size_t get_tx_pool_version() const { return m_tx_pool_version; } @@ -86,7 +86,8 @@ class WalletState : public WalletStateBasic { void wallet_addresses_updated(); // generating through state prevents undo of blocks within 2*block_future_time_limit from now - std::vector generate_new_addresses(const std::vector &sks, Timestamp ct, Timestamp now); + std::vector generate_new_addresses( + const std::vector &sks, Timestamp ct, Timestamp now, std::vector *addresses); void create_addresses(size_t count); protected: @@ -95,9 +96,11 @@ class WalletState : public WalletStateBasic { bool parse_raw_transaction(bool is_base, api::Transaction *ptx, std::vector *input_transfers, std::vector *output_transfers, Amount *output_amount, const PreparedWalletTransaction &pwtx, - Hash tid, const std::vector &global_indices, Height block_heights) const; + Hash tid, const std::vector &global_indices, size_t start_global_key_output_index, + Height block_heights) const; bool redo_transaction(const PreparedWalletTransaction &pwtx, const std::vector &global_indices, - IWalletState *delta_state, bool is_base, Hash tid, Height block_height, Hash bid, Timestamp tx_timestamp); + size_t start_global_key_output_index, IWalletState *delta_state, bool is_base, Hash tid, Height block_height, + Hash bid, Timestamp tx_timestamp); const std::map &get_mempool_kis_or_pks() const override; void on_first_transaction_found(Timestamp ts) override; diff --git a/src/Core/WalletStateBasic.cpp b/src/Core/WalletStateBasic.cpp index fba69c0e..686823f5 100644 --- a/src/Core/WalletStateBasic.cpp +++ b/src/Core/WalletStateBasic.cpp @@ -17,7 +17,7 @@ static const auto LEVEL = logging::TRACE; -static const std::string version_current = "9"; +static const std::string version_current = "11"; static const std::string INDEX_UID_to_STATE = "X"; // We do not store it for empty blocks @@ -34,52 +34,58 @@ static const std::string INDEX_ADDRESS_HEIGHT_TID = "th"; // for get_transfers // (addr) -> (balance) <- find balance by addr static const std::string INDEX_ADDRESS_to_BALANCE = "ba"; // for get_balance -// (ki) -> (he, am, gi) <- find largest coin from same ki group (can be spent or not) -static const std::string INDEX_KEYIMAGE_to_HE_AM_GI = "ki"; // ki->output_key, if !view_only +// (ki) -> (he, gi, tid, iit) <- find largest coin from same ki group (can be spent or not) +static const std::string INDEX_KEYIMAGE_to_HE_GI = "ki"; // ki->output_key, if !view_only -// (am, gi) -> (he, pk) <- find coin by am, gi (can be spent or not) -static const std::string INDEX_AM_GI_to_HE_PK = "g"; +// (gi) -> (he, pk) <- find coin by gi (can be spent or not) +// static const std::string INDEX_GI_to_HE_PK = "g"; +// (am, si) -> (gi, he, pk) <- find coin by am, si (can be spent or not) +// static const std::string INDEX_AM_SI_to_HE_PK = "s"; -// (he, am, gi) -> output <- find available unspents (never locked or already unlocked) -// (addr, he, am, gi) -> () <- find available unspents by addr (never locked or already unlocked) -static const std::string INDEX_HE_AM_GI_to_OUTPUT = "un"; -static const std::string INDEX_ADDRESS_HE_AM_GI = "uh"; +// (he, gi) -> output <- find available unspents (never locked or already unlocked) +// (addr, he, gi) -> () <- find available unspents by addr (never locked or already unlocked) +static const std::string INDEX_HE_GI_to_OUTPUT = "un"; +static const std::string INDEX_ADDRESS_HE_GI = "uh"; -// (real_he, am, gi) -> (output) <- find unlocked transfers by height (only those originally locked) Balance here is +// (real_he, gi) -> (output) <- find unlocked transfers by height (only those originally locked) Balance here is // adjusted, might become "crazy" -static const std::string UNLOCKED_INDEX_REALHE_AM_GI_to_OUTPUT = +static const std::string UNLOCKED_INDEX_REALHE_GI_to_OUTPUT = "ul"; // Amount here can be adjusted if not first in the ki group -// (ki, am, gi) -> (unl_mom) <- find in locked by key_image -static const std::string LOCKED_INDEX_KI_AM_GI = "li"; +// (ki, gi) -> (unl_mom) <- find in locked by key_image +static const std::string LOCKED_INDEX_KI_GI = "li"; -// (unl_he, am, gi) -> (output) <- find yet locked by height -// (unl_ti, am, gi) -> (output) <- find yet locked by timestamp -static const std::string LOCKED_INDEX_HEIGHT_AM_GI_to_OUTPUT = "lh"; // key contain unlock_block_or_timestamp -static const std::string LOCKED_INDEX_TIMESTAMP_AM_GI_to_OUTPUT = "lt"; // key contain unlock_block_or_timestamp +// (unl_he, gi) -> (output) <- find yet locked by height/timestamp +static const std::string LOCKED_INDEX_B_OR_T_GI_to_OUTPUT = "lh"; // key contain unlock_block_or_timestamp using namespace cn; using namespace platform; -void seria::ser_members(WalletStateBasic::HeightAmounGi &v, ISeria &s) { +void seria::ser_members(WalletStateBasic::HeightGi &v, ISeria &s) { seria_kv("height", v.height, s); - seria_kv("amount", v.amount, s); - seria_kv("index", v.global_index, s); + seria_kv("global_index", v.global_index, s); + seria_kv("transaction_hash", v.transaction_hash, s); + seria_kv("index_in_transaction", v.index_in_transaction, s); } +void seria::ser_members(WalletStateBasic::GiHeightPk &v, ISeria &s) { + seria_kv("global_index", v.global_index, s); + seria_kv("height", v.height, s); + seria_kv("public_key", v.public_key, s); +} void seria::ser_members(WalletStateBasic::UndoValue &v, seria::ISeria &s) { seria_kv("exists", v.exists, s); seria_kv("value", v.value, s); } -WalletStateBasic::WalletStateBasic(logging::ILogger &log, const Config &config, const Currency ¤cy, - const std::string &cache_name, bool is_det_viewonly) +WalletStateBasic::WalletStateBasic( + logging::ILogger &log, const Config &config, const Currency ¤cy, const std::string &cache_name) : m_genesis_bid(currency.genesis_block_hash) , m_config(config) , m_currency(currency) , m_log(log, "WalletState") , m_db(platform::O_OPEN_ALWAYS, config.get_data_folder("wallet_cache") + "/" + cache_name, 0x2000000000) // 128 gb - , is_det_viewonly(is_det_viewonly) { +{ std::string version; std::string other_genesis_bid; std::string other_cache_name; @@ -117,22 +123,38 @@ WalletStateBasic::WalletStateBasic(logging::ILogger &log, const Config &config, } } -void WalletStateBasic::put_am_gi_he(Amount am, size_t gi, Height he, const PublicKey &pk) { - std::string unkey = INDEX_AM_GI_to_HE_PK + common::write_varint_sqlite4(am) + common::write_varint_sqlite4(gi); - BinaryArray ba = seria::to_binary(std::make_pair(he, pk)); - put_with_undo(unkey, ba, true); -} -bool WalletStateBasic::get_am_gi_he(Amount am, size_t gi, Height *he, PublicKey *pk) const { - std::string unkey = INDEX_AM_GI_to_HE_PK + common::write_varint_sqlite4(am) + common::write_varint_sqlite4(gi); - BinaryArray ba; - if (!m_db.get(unkey, ba)) - return false; - std::pair pa; - seria::from_binary(pa, ba); - *he = pa.first; - *pk = pa.second; - return true; -} +/*void WalletStateBasic::put_am_gi_he(const api::Output &output) { + std::string unkey = INDEX_GI_to_HE_PK + common::write_varint_sqlite4(output.global_index); + BinaryArray ba = seria::to_binary(std::make_pair(output.height, output.public_key)); + put_with_undo(unkey, ba, true); + unkey = INDEX_AM_SI_to_HE_PK + common::write_varint_sqlite4(output.amount) + + common::write_varint_sqlite4(output.stack_index); + ba = seria::to_binary(GiHeightPk{output.global_index, output.height, output.public_key}); + put_with_undo(unkey, ba, true); +} +bool WalletStateBasic::get_gi_he(size_t gi, Height *he, PublicKey *pk) const { + std::string unkey = INDEX_GI_to_HE_PK + common::write_varint_sqlite4(gi); + BinaryArray ba; + if (!m_db.get(unkey, ba)) + return false; + std::pair pa; + seria::from_binary(pa, ba); + *he = pa.first; + *pk = pa.second; + return true; +} +bool WalletStateBasic::get_am_si_he(Amount am, size_t si, size_t *gi, Height *he, PublicKey *pk) const { + std::string unkey = INDEX_AM_SI_to_HE_PK + common::write_varint_sqlite4(am) + common::write_varint_sqlite4(si); + BinaryArray ba; + if (!m_db.get(unkey, ba)) + return false; + GiHeightPk pa; + seria::from_binary(pa, ba); + *gi = pa.global_index; + *he = pa.height; + *pk = pa.public_key; + return true; +}*/ void WalletStateBasic::combine_balance( api::Balance &balance, const api::Output &output, int locked_op, int spendable_op) { @@ -156,17 +178,10 @@ void WalletStateBasic::db_commit() { m_log(logging::TRACE) << "WalletState::db_commit finished..." << std::endl; } -static crypto::EllipticCurvePoint ki_or_pk(const api::Output &v, bool is_det_viewonly) { - crypto::EllipticCurvePoint p = v.key_image; - if (is_det_viewonly) - p = v.public_key; - return p; -} - std::string WalletStateBasic::format_output(const api::Output &v) { std::stringstream str; - str << " he=" << v.height << " am=" << m_currency.format_amount(v.amount) << " gi=" << v.index - << " ki=" << ki_or_pk(v, is_det_viewonly) << " addr=" << v.address + str << " he=" << v.height << " gi=" << v.global_index << " am=" << m_currency.format_amount(v.amount) + << " si=" << v.stack_index << " ki=" << v.key_image << " addr=" << v.address << (v.unlock_block_or_timestamp == 0 ? "" : " unl=" + common::to_string(v.unlock_block_or_timestamp)); return str.str(); } @@ -286,8 +301,8 @@ void WalletStateBasic::undo_db_state(Height state) { } bool WalletStateBasic::try_add_incoming_output(const api::Output &output, Amount *confirmed_balance_delta) const { - HeightAmounGi heamgi; - bool ki_exists = read_by_keyimage(ki_or_pk(output, is_det_viewonly), &heamgi); + HeightGi heamgi; + bool ki_exists = read_by_keyimage(output.key_image, &heamgi); api::Output existing_output; bool is_existing_unspent = ki_exists && read_from_unspent_index(heamgi, &existing_output); if (ki_exists && !is_existing_unspent) @@ -297,20 +312,12 @@ bool WalletStateBasic::try_add_incoming_output(const api::Output &output, Amount return true; } *confirmed_balance_delta = output.amount; - if (ki_exists) { // implies is_existing_unspent - // Replacement of coins in the same KI group cannot be done safely, see explanation in fun below.. - // if (output.amount <= heamgi.amount || - // output.address != existing_output.address) // We cannot replace coins if different addresses - // return false; - // *confirmed_balance_delta = output.amount - heamgi.amount; - return false; - } - return true; + return !ki_exists; // We fixed problem on crypto level, but retain code to keep indexes invariants } Amount WalletStateBasic::add_incoming_output(const api::Output &output, const Hash &tid, bool just_unlocked) { - HeightAmounGi heamgi; - bool ki_exists = read_by_keyimage(ki_or_pk(output, is_det_viewonly), &heamgi); + HeightGi heamgi; + bool ki_exists = read_by_keyimage(output.key_image, &heamgi); api::Output existing_output; bool is_existing_unspent = ki_exists && read_from_unspent_index(heamgi, &existing_output); if (ki_exists && !is_existing_unspent) { @@ -318,37 +325,27 @@ Amount WalletStateBasic::add_incoming_output(const api::Output &output, const Ha return 0; } if (output.unlock_block_or_timestamp != 0 && !just_unlocked) { // incoming - if (is_det_viewonly) - put_am_gi_he(output.amount, output.index, output.height, output.public_key); + // if (is_det_viewonly) + // put_am_gi_he(output); add_to_lock_index(output, tid); return output.amount; } Amount added_amount = output.amount; if (ki_exists) { - // implies is_existing_unspent - // Replacement of coins in the same KI group cannot be done safely - // Because replacement can occur while transaction to spend first coin is in mempool - // This will lead to crediting attacker's account first, then substracting from exchange account. - // if (output.amount <= heamgi.amount || output.address != existing_output.address) { + // We fixed problem on crypto level, but retain code to keep indexes invariants m_log(logging::WARNING) << " Duplicate key_output attack, ignoring output because have another one unspent with same or larger amount or different address, " << format_output(existing_output) << std::endl; return 0; - // } - // added_amount = output.amount - heamgi.amount; - // m_log(logging::WARNING) - // << " Duplicate key_output attack, reducing amount because have another one unspent with smaller - // amount, " - // << format_output(existing_output) << std::endl; - // remove_from_unspent_index(existing_output); } add_to_unspent_index(output); - heamgi.height = output.height; - heamgi.amount = output.amount; - heamgi.global_index = output.index; - update_keyimage(ki_or_pk(output, is_det_viewonly), heamgi, !ki_exists); - if (is_det_viewonly) - put_am_gi_he(output.amount, output.index, output.height, output.public_key); + heamgi.height = output.height; + heamgi.global_index = output.global_index; + heamgi.transaction_hash = output.transaction_hash; + heamgi.index_in_transaction = output.index_in_transaction; + update_keyimage(output.key_image, heamgi); + // if (is_det_viewonly) + // put_am_gi_he(output); return added_amount; } @@ -358,29 +355,22 @@ Amount WalletStateBasic::add_incoming_output(const api::Output &output, const Ha } Amount WalletStateBasic::add_incoming_keyimage(Height block_height, const KeyImage &key_image) { - if (is_det_viewonly) - return 0; + // if (is_det_viewonly) + // return 0; return add_incoming_ki_or_pk(block_height, key_image); } Amount WalletStateBasic::add_incoming_ki_or_pk(Height block_height, const crypto::EllipticCurvePoint &ki_or_pk) { m_log(LEVEL) << "Incoming ki_or_pk " << ki_or_pk << std::endl; - std::string prefix = LOCKED_INDEX_KI_AM_GI + DB::to_binary_key(ki_or_pk.data, sizeof(ki_or_pk.data)); + std::string prefix = LOCKED_INDEX_KI_GI + DB::to_binary_key(ki_or_pk.data, sizeof(ki_or_pk.data)); // find and remove in locked std::vector found_in_locked; for (DB::Cursor cur = m_db.begin(prefix); !cur.end(); cur.next()) { - const std::string &suf = cur.get_suffix(); - const char *be = suf.data(); - const char *en = be + suf.size(); - Amount am = common::read_varint_sqlite4(be, en); - size_t gi = common::integer_cast(common::read_varint_sqlite4(be, en)); - invariant(en - be == 0, ""); + size_t gi = common::integer_cast(common::read_varint_sqlite4(cur.get_suffix())); BlockOrTimestamp unl = 0; seria::from_binary(unl, cur.get_value_array()); - std::string unkey = m_currency.is_block_or_timestamp_block(unl) ? LOCKED_INDEX_HEIGHT_AM_GI_to_OUTPUT - : LOCKED_INDEX_TIMESTAMP_AM_GI_to_OUTPUT; - unkey += - common::write_varint_sqlite4(unl) + common::write_varint_sqlite4(am) + common::write_varint_sqlite4(gi); + std::string unkey = + LOCKED_INDEX_B_OR_T_GI_to_OUTPUT + common::write_varint_sqlite4(unl) + common::write_varint_sqlite4(gi); BinaryArray output_ba; invariant(m_db.get(unkey, output_ba), ""); api::Output output; @@ -391,7 +381,7 @@ Amount WalletStateBasic::add_incoming_ki_or_pk(Height block_height, const crypto unlock(block_height, std::move(lo)); } Amount removed_amount = 0; - HeightAmounGi heamgi; + HeightGi heamgi; bool ki_exists = read_by_keyimage(ki_or_pk, &heamgi); api::Output existing_output; if (ki_exists && read_from_unspent_index(heamgi, &existing_output)) { @@ -402,40 +392,33 @@ Amount WalletStateBasic::add_incoming_ki_or_pk(Height block_height, const crypto } bool WalletStateBasic::try_adding_incoming_keyimage(const KeyImage &key_image, api::Output *spending_output) const { - if (is_det_viewonly) - return false; + // if (is_det_viewonly) + // return false; return try_adding_incoming_ki_or_pk(key_image, spending_output); } bool WalletStateBasic::try_adding_incoming_ki_or_pk( const crypto::EllipticCurvePoint &ki_or_pk, api::Output *spending_output) const { bool candidate_found = false; - HeightAmounGi heamgi; + HeightGi heamgi; bool ki_exists = read_by_keyimage(ki_or_pk, &heamgi); if (ki_exists && read_from_unspent_index(heamgi, spending_output)) { candidate_found = true; } - std::string prefix = LOCKED_INDEX_KI_AM_GI + DB::to_binary_key(ki_or_pk.data, sizeof(ki_or_pk.data)); + std::string prefix = LOCKED_INDEX_KI_GI + DB::to_binary_key(ki_or_pk.data, sizeof(ki_or_pk.data)); for (DB::Cursor cur = m_db.begin(prefix); !cur.end(); cur.next()) { - const std::string &suf = cur.get_suffix(); - const char *be = suf.data(); - const char *en = be + suf.size(); - Amount am = common::read_varint_sqlite4(be, en); - size_t gi = common::integer_cast(common::read_varint_sqlite4(be, en)); - invariant(en - be == 0, ""); - if (candidate_found && am <= spending_output->amount) + size_t gi = common::integer_cast(common::read_varint_sqlite4(cur.get_suffix())); + if (candidate_found) continue; BlockOrTimestamp unl = 0; seria::from_binary(unl, cur.get_value_array()); - std::string unkey = m_currency.is_block_or_timestamp_block(unl) ? LOCKED_INDEX_HEIGHT_AM_GI_to_OUTPUT - : LOCKED_INDEX_TIMESTAMP_AM_GI_to_OUTPUT; - unkey += - common::write_varint_sqlite4(unl) + common::write_varint_sqlite4(am) + common::write_varint_sqlite4(gi); + std::string unkey = + LOCKED_INDEX_B_OR_T_GI_to_OUTPUT + common::write_varint_sqlite4(unl) + common::write_varint_sqlite4(gi); BinaryArray output_ba; invariant(m_db.get(unkey, output_ba), ""); api::Output output; seria::from_binary(output, output_ba); - invariant(output.amount == am && output.index == gi, ""); + invariant(output.global_index == gi, ""); if (candidate_found && output.address != spending_output->address) continue; *spending_output = output; @@ -444,28 +427,6 @@ bool WalletStateBasic::try_adding_incoming_ki_or_pk( return candidate_found; } -bool WalletStateBasic::try_adding_deterministic_input(Amount am, size_t gi, api::Output *spending_output) const { - if (!is_det_viewonly) - return false; - HeightAmounGi heamgi{0, am, gi}; - PublicKey pk; - if (!get_am_gi_he(am, gi, &heamgi.height, &pk)) - return false; - return try_adding_incoming_ki_or_pk(pk, spending_output); -} - -Amount WalletStateBasic::add_incoming_deterministic_input( - Height block_height, Amount am, size_t gi, const PublicKey &pk) { - if (!is_det_viewonly) - return 0; - HeightAmounGi heamgi{0, am, gi}; - PublicKey pk2; - if (!get_am_gi_he(am, gi, &heamgi.height, &pk2)) - return 0; - invariant(pk == pk2, ""); - return add_incoming_ki_or_pk(block_height, pk); -} - void WalletStateBasic::add_transaction( Height height, const Hash &tid, const TransactionPrefix &tx, const api::Transaction &ptx) { auto cur = m_db.begin(INDEX_TID_to_TRANSACTIONS); @@ -491,7 +452,7 @@ bool WalletStateBasic::api_add_unspent(std::vector *result, Amount auto recently_unlocked = get_unlocked_outputs(address, confirmed_height + 1, std::numeric_limits::max()); const size_t min_count = 10000; // We return up to 10k outputs after we find requested sum return for_each_in_unspent_index(address, 0, confirmed_height + 1, [&](api::Output &&output) -> bool { - if (!is_memory_spent(output) && recently_unlocked.count(std::make_pair(output.amount, output.index)) == 0) { + if (!is_memory_spent(output) && recently_unlocked.count(output.global_index) == 0) { // if (!output.dust) // We ensure total can be spent with non-zero anonymity // *total_amount += output.amount; result->push_back(std::move(output)); @@ -570,10 +531,7 @@ std::vector WalletStateBasic::api_get_locked_or_unconfirmed_unspent }); auto recently_unlocked = get_unlocked_outputs(address, confirmed_height + 1, std::numeric_limits::max()); for (auto &&lou : recently_unlocked) { - HeightAmounGi heamgi; - heamgi.height = lou.second.height; - heamgi.amount = lou.second.amount; - heamgi.global_index = lou.second.index; + HeightGi heamgi{lou.second.height, lou.second.global_index, {}, 0}; api::Output existing_output; bool is_existing_unspent = read_from_unspent_index(heamgi, &existing_output); if (!is_existing_unspent || is_memory_spent(lou.second)) @@ -581,11 +539,9 @@ std::vector WalletStateBasic::api_get_locked_or_unconfirmed_unspent if (lou.second.height <= confirmed_height) result.push_back(lou.second); } - std::map, api::Output> still_locked; - read_unlock_index(&still_locked, LOCKED_INDEX_TIMESTAMP_AM_GI_to_OUTPUT, address, 0, - std::numeric_limits::max()); + std::map still_locked; read_unlock_index( - &still_locked, LOCKED_INDEX_HEIGHT_AM_GI_to_OUTPUT, address, 0, std::numeric_limits::max()); + &still_locked, LOCKED_INDEX_B_OR_T_GI_to_OUTPUT, address, 0, std::numeric_limits::max()); for (auto &&lou : still_locked) if (!is_memory_spent(lou.second)) result.push_back(std::move(lou.second)); @@ -613,10 +569,7 @@ api::Balance WalletStateBasic::get_balance(const std::string &address, Height co auto recently_unlocked = get_unlocked_outputs(address, confirmed_height + 1, std::numeric_limits::max()); for (auto &&lou : recently_unlocked) { - HeightAmounGi heamgi; - heamgi.height = lou.second.height; - heamgi.amount = lou.first.first; - heamgi.global_index = lou.first.second; + HeightGi heamgi{lou.second.height, lou.first, {}, 0}; api::Output existing_output; bool is_existing_unspent = read_from_unspent_index(heamgi, &existing_output); if (!is_existing_unspent || is_memory_spent(lou.second)) @@ -625,7 +578,7 @@ api::Balance WalletStateBasic::get_balance(const std::string &address, Height co combine_balance(balance, existing_output, 1, -1); } for (auto &&kit : get_mempool_kis_or_pks()) { - HeightAmounGi heamgi; + HeightGi heamgi; bool ki_exists = read_by_keyimage(kit.first, &heamgi); api::Output existing_output; bool is_existing_unspent = ki_exists && read_from_unspent_index(heamgi, &existing_output); @@ -636,10 +589,8 @@ api::Balance WalletStateBasic::get_balance(const std::string &address, Height co // We commented code below because it requires either iterating all locked index ot all used keyimages // So, we do not account for memory spent locked outputs in unconfirmed balance - // std::map, api::Output> still_locked; - // read_unlock_index(&still_locked, LOCKED_INDEX_TIMESTAMP_AM_GI_to_OUTPUT, address, 0, - // std::numeric_limits::max()); - // read_unlock_index(&still_locked, LOCKED_INDEX_HEIGHT_AM_GI_to_OUTPUT, address, 0, + // std::map still_locked; + // read_unlock_index(&still_locked, LOCKED_INDEX_B_OR_T_GI_to_OUTPUT, address, 0, // std::numeric_limits::max()); // for(auto && lou : still_locked) // if (is_memory_spent(lou.second)) @@ -666,43 +617,41 @@ bool WalletStateBasic::get_transaction(Hash tid, TransactionPrefix *tx, api::Tra return true; } -static void parse_lock_key(const std::string &suffix, BlockOrTimestamp *unl, Amount *amount, size_t *global_index) { +static void parse_lock_key(const std::string &suffix, BlockOrTimestamp *unl, size_t *global_index) { const char *be = suffix.data(); const char *en = be + suffix.size(); *unl = common::integer_cast(common::read_varint_sqlite4(be, en)); - *amount = common::read_varint_sqlite4(be, en); *global_index = common::integer_cast(common::read_varint_sqlite4(be, en)); invariant(en - be == 0, ""); } -void WalletStateBasic::read_unlock_index(std::map, api::Output> *add, - const std::string &index_prefix, const std::string &address, BlockOrTimestamp begin, BlockOrTimestamp end) const { +void WalletStateBasic::read_unlock_index(std::map *add, const std::string &index_prefix, + const std::string &address, BlockOrTimestamp begin, BlockOrTimestamp end) const { if (begin >= end) // optimization return; auto middle = common::write_varint_sqlite4(begin); for (DB::Cursor cur = m_db.begin(index_prefix, middle); !cur.end(); cur.next()) { BlockOrTimestamp unl = 0; - Amount amount = 0; size_t global_index = 0; - parse_lock_key(cur.get_suffix(), &unl, &amount, &global_index); + parse_lock_key(cur.get_suffix(), &unl, &global_index); if (unl >= end) break; api::Output output; seria::from_binary(output, cur.get_value_array()); // amount can be different to output.amount, if added from te same ki group // original amount is in unlocked index key, use it as a key because it is unambigous - invariant(output.index == global_index, "Index corrupted"); + invariant(output.global_index == global_index, "Index corrupted"); if (address.empty() || output.address == address) - invariant(add->insert(std::make_pair(std::make_pair(amount, output.index), output)).second, + invariant(add->insert(std::make_pair(output.global_index, output)).second, "Invariant dead read_unlock_index adding output twice"); } } -std::map, api::Output> WalletStateBasic::get_unlocked_outputs(const std::string &address, +std::map WalletStateBasic::get_unlocked_outputs(const std::string &address, Height from_height, Height to_height) const { - std::map, api::Output> unlocked; - read_unlock_index(&unlocked, UNLOCKED_INDEX_REALHE_AM_GI_to_OUTPUT, address, from_height, to_height); + std::map unlocked; + read_unlock_index(&unlocked, UNLOCKED_INDEX_REALHE_GI_to_OUTPUT, address, from_height, to_height); return unlocked; } @@ -761,8 +710,8 @@ void WalletStateBasic::unlock(Height now_height, api::Output &&output) { // if( adjusted_amount == 0) // Unlocked and have coin with the same ki and amount // continue; // We decided to put in DB anyway, so that we know we did not miss unlock // We add into index with original amount as a key because otherwise there could be ambiguity in index - auto unkey = UNLOCKED_INDEX_REALHE_AM_GI_to_OUTPUT + common::write_varint_sqlite4(now_height) + - common::write_varint_sqlite4(output.amount) + common::write_varint_sqlite4(output.index); + auto unkey = UNLOCKED_INDEX_REALHE_GI_to_OUTPUT + common::write_varint_sqlite4(now_height) + + common::write_varint_sqlite4(output.global_index); output.amount = adjusted_amount; BinaryArray ba = seria::to_binary(output); put_with_undo(unkey, ba, true); @@ -773,15 +722,13 @@ void WalletStateBasic::add_to_lock_index(const api::Output &output, const Hash & // put_am_gi_tid(output.amount, output.index, tid); modify_balance(output, 1, 0); - std::string unkey = m_currency.is_block_or_timestamp_block(output.unlock_block_or_timestamp) - ? LOCKED_INDEX_HEIGHT_AM_GI_to_OUTPUT - : LOCKED_INDEX_TIMESTAMP_AM_GI_to_OUTPUT; - unkey += common::write_varint_sqlite4(output.unlock_block_or_timestamp) + - common::write_varint_sqlite4(output.amount) + common::write_varint_sqlite4(output.index); + std::string unkey = LOCKED_INDEX_B_OR_T_GI_to_OUTPUT + + common::write_varint_sqlite4(output.unlock_block_or_timestamp) + + common::write_varint_sqlite4(output.global_index); put_with_undo(unkey, seria::to_binary(output), true); if (output.key_image != KeyImage{}) { - unkey = LOCKED_INDEX_KI_AM_GI + DB::to_binary_key(output.key_image.data, sizeof(output.key_image.data)) + - common::write_varint_sqlite4(output.amount) + common::write_varint_sqlite4(output.index); + unkey = LOCKED_INDEX_KI_GI + DB::to_binary_key(output.key_image.data, sizeof(output.key_image.data)) + + common::write_varint_sqlite4(output.global_index); BinaryArray ba = seria::to_binary(output.unlock_block_or_timestamp); put_with_undo(unkey, ba, true); } @@ -789,24 +736,23 @@ void WalletStateBasic::add_to_lock_index(const api::Output &output, const Hash & void WalletStateBasic::remove_from_lock_index(const api::Output &output) { m_log(LEVEL) << " Removing output from lock index, " << format_output(output) << std::endl; - std::string unkey = m_currency.is_block_or_timestamp_block(output.unlock_block_or_timestamp) - ? LOCKED_INDEX_HEIGHT_AM_GI_to_OUTPUT - : LOCKED_INDEX_TIMESTAMP_AM_GI_to_OUTPUT; - unkey += common::write_varint_sqlite4(output.unlock_block_or_timestamp) + - common::write_varint_sqlite4(output.amount) + common::write_varint_sqlite4(output.index); + std::string unkey = LOCKED_INDEX_B_OR_T_GI_to_OUTPUT + + common::write_varint_sqlite4(output.unlock_block_or_timestamp) + + common::write_varint_sqlite4(output.global_index); modify_balance(output, -1, 0); del_with_undo(unkey, true); if (output.key_image != KeyImage{}) { - unkey = LOCKED_INDEX_KI_AM_GI + DB::to_binary_key(output.key_image.data, sizeof(output.key_image.data)) + - common::write_varint_sqlite4(output.amount) + common::write_varint_sqlite4(output.index); + unkey = LOCKED_INDEX_KI_GI + DB::to_binary_key(output.key_image.data, sizeof(output.key_image.data)) + + common::write_varint_sqlite4(output.global_index); del_with_undo(unkey, true); } } void WalletStateBasic::unlock(Height now_height, Timestamp now) { - std::map, api::Output> to_unlock; - read_unlock_index(&to_unlock, LOCKED_INDEX_HEIGHT_AM_GI_to_OUTPUT, std::string(), 0, now_height + 1); - read_unlock_index(&to_unlock, LOCKED_INDEX_TIMESTAMP_AM_GI_to_OUTPUT, std::string(), 0, now + 1); + std::map to_unlock; + read_unlock_index(&to_unlock, LOCKED_INDEX_B_OR_T_GI_to_OUTPUT, std::string(), 0, now_height + 1); + read_unlock_index( + &to_unlock, LOCKED_INDEX_B_OR_T_GI_to_OUTPUT, std::string(), m_currency.max_block_height, now + 1); if (!to_unlock.empty()) m_log(LEVEL) << "Unlocking for height=" << now_height << ", now=" << now << std::endl; for (auto &&unl : to_unlock) { @@ -814,9 +760,9 @@ void WalletStateBasic::unlock(Height now_height, Timestamp now) { } } -bool WalletStateBasic::read_from_unspent_index(const HeightAmounGi &value, api::Output *output) const { - auto keyun = INDEX_HE_AM_GI_to_OUTPUT + common::write_varint_sqlite4(value.height) + - common::write_varint_sqlite4(value.amount) + common::write_varint_sqlite4(value.global_index); +bool WalletStateBasic::read_from_unspent_index(const HeightGi &value, api::Output *output) const { + auto keyun = INDEX_HE_GI_to_OUTPUT + common::write_varint_sqlite4(value.height) + + common::write_varint_sqlite4(value.global_index); BinaryArray ba; if (!m_db.get(keyun, ba)) return false; @@ -825,21 +771,20 @@ bool WalletStateBasic::read_from_unspent_index(const HeightAmounGi &value, api:: } bool WalletStateBasic::for_each_in_unspent_index( const std::string &address, Height from, Height to, std::function fun) const { - auto prefix = address.empty() ? INDEX_HE_AM_GI_to_OUTPUT : INDEX_ADDRESS_HE_AM_GI + address + "/"; + auto prefix = address.empty() ? INDEX_HE_GI_to_OUTPUT : INDEX_ADDRESS_HE_GI + address + "/"; std::string middle = common::write_varint_sqlite4(from); for (DB::Cursor cur = m_db.begin(prefix, middle); !cur.end(); cur.next()) { const std::string &suf = cur.get_suffix(); const char *be = suf.data(); const char *en = be + suf.size(); Height he = common::integer_cast(common::read_varint_sqlite4(be, en)); - Amount am = common::read_varint_sqlite4(be, en); size_t gi = common::integer_cast(common::read_varint_sqlite4(be, en)); invariant(en - be == 0, ""); if (he >= to) break; api::Output output; if (!address.empty()) { - HeightAmounGi heamgi{he, am, gi}; + HeightGi heamgi{he, gi, {}, 0}; invariant(read_from_unspent_index(heamgi, &output), "unspent indexes do not match"); invariant(output.address == address, "output is in wrong index by address"); } else @@ -853,41 +798,40 @@ bool WalletStateBasic::for_each_in_unspent_index( void WalletStateBasic::add_to_unspent_index(const api::Output &output) { m_log(LEVEL) << " Adding to unspent, " << format_output(output) << std::endl; modify_balance(output, 0, 1); - auto keyun = INDEX_HE_AM_GI_to_OUTPUT + common::write_varint_sqlite4(output.height) + - common::write_varint_sqlite4(output.amount) + common::write_varint_sqlite4(output.index); + auto keyun = INDEX_HE_GI_to_OUTPUT + common::write_varint_sqlite4(output.height) + + common::write_varint_sqlite4(output.global_index); put_with_undo(keyun, seria::to_binary(output), true); - keyun = INDEX_ADDRESS_HE_AM_GI + output.address + "/" + common::write_varint_sqlite4(output.height) + - common::write_varint_sqlite4(output.amount) + common::write_varint_sqlite4(output.index); + keyun = INDEX_ADDRESS_HE_GI + output.address + "/" + common::write_varint_sqlite4(output.height) + + common::write_varint_sqlite4(output.global_index); put_with_undo(keyun, BinaryArray{}, true); } void WalletStateBasic::remove_from_unspent_index(const api::Output &output) { m_log(LEVEL) << " Removing from unspent, " << format_output(output) << std::endl; modify_balance(output, 0, -1); - auto keyun = INDEX_HE_AM_GI_to_OUTPUT + common::write_varint_sqlite4(output.height) + - common::write_varint_sqlite4(output.amount) + common::write_varint_sqlite4(output.index); + auto keyun = INDEX_HE_GI_to_OUTPUT + common::write_varint_sqlite4(output.height) + + common::write_varint_sqlite4(output.global_index); del_with_undo(keyun, true); - keyun = INDEX_ADDRESS_HE_AM_GI + output.address + "/" + common::write_varint_sqlite4(output.height) + - common::write_varint_sqlite4(output.amount) + common::write_varint_sqlite4(output.index); + keyun = INDEX_ADDRESS_HE_GI + output.address + "/" + common::write_varint_sqlite4(output.height) + + common::write_varint_sqlite4(output.global_index); del_with_undo(keyun, true); } -bool WalletStateBasic::read_by_keyimage(const crypto::EllipticCurvePoint &ki, HeightAmounGi *value) const { - auto keyun = INDEX_KEYIMAGE_to_HE_AM_GI + DB::to_binary_key(ki.data, sizeof(ki.data)); +bool WalletStateBasic::read_by_keyimage(const crypto::EllipticCurvePoint &ki, HeightGi *value) const { + auto keyun = INDEX_KEYIMAGE_to_HE_GI + DB::to_binary_key(ki.data, sizeof(ki.data)); BinaryArray ba; if (!m_db.get(keyun, ba)) return false; seria::from_binary(*value, ba); return true; } -void WalletStateBasic::update_keyimage( - const crypto::EllipticCurvePoint &ki, const HeightAmounGi &value, bool nooverwrite) { - // if (ki == KeyImage{}) - // return; - auto keyun = INDEX_KEYIMAGE_to_HE_AM_GI + DB::to_binary_key(ki.data, sizeof(ki.data)); - put_with_undo(keyun, seria::to_binary(value), nooverwrite); +void WalletStateBasic::update_keyimage(const crypto::EllipticCurvePoint &ki, const HeightGi &value) { + if (ki == KeyImage{}) // linkable view-only wallet cannot calculate keyimages + return; + auto keyun = INDEX_KEYIMAGE_to_HE_GI + DB::to_binary_key(ki.data, sizeof(ki.data)); + put_with_undo(keyun, seria::to_binary(value), true); } void WalletStateBasic::test_undo_blocks() { diff --git a/src/Core/WalletStateBasic.hpp b/src/Core/WalletStateBasic.hpp index 6b5c61ac..67dd60b1 100644 --- a/src/Core/WalletStateBasic.hpp +++ b/src/Core/WalletStateBasic.hpp @@ -23,11 +23,10 @@ class Config; class IWalletState { public: - virtual ~IWalletState() {} + virtual ~IWalletState() = default; virtual Amount add_incoming_output(const api::Output &, const Hash &tid) = 0; // added amount may be lower virtual Amount add_incoming_keyimage(Height block_height, const KeyImage &) = 0; - virtual Amount add_incoming_deterministic_input(Height block_height, Amount am, size_t gi, const PublicKey &pk) = 0; virtual void add_transaction( Height block_height, const Hash &tid, const TransactionPrefix &tx, const api::Transaction &ptx) = 0; }; @@ -36,8 +35,7 @@ class WalletStateBasic : protected IWalletState { public: typedef platform::DB DB; - explicit WalletStateBasic( - logging::ILogger &, const Config &, const Currency &, const std::string &cache_name, bool is_det_viewonly); + explicit WalletStateBasic(logging::ILogger &, const Config &, const Currency &, const std::string &cache_name); const Currency &get_currency() const { return m_currency; }; Hash get_tip_bid() const { return m_tip.hash; } @@ -64,10 +62,16 @@ class WalletStateBasic : protected IWalletState { bool exists = false; common::BinaryArray value; }; - struct HeightAmounGi { + struct HeightGi { Height height = 0; - Amount amount = 0; size_t global_index = 0; + Hash transaction_hash; // So we can look up spent coins by keyimage + size_t index_in_transaction = 0; + }; + struct GiHeightPk { + size_t global_index = 0; + Height height = 0; + PublicKey public_key; }; void db_commit(); @@ -100,18 +104,16 @@ class WalletStateBasic : protected IWalletState { virtual void on_first_transaction_found(Timestamp ts) {} void unlock(Height now_height, Timestamp now); - bool read_from_unspent_index(const HeightAmounGi &value, api::Output *) const; - bool read_by_keyimage(const crypto::EllipticCurvePoint &ki, HeightAmounGi *value) const; + bool read_from_unspent_index(const HeightGi &value, api::Output *) const; + bool read_by_keyimage(const crypto::EllipticCurvePoint &ki, HeightGi *value) const; bool try_add_incoming_output(const api::Output &, Amount *confirmed_balance_delta) const; bool try_adding_incoming_keyimage(const KeyImage &, api::Output *spending_output) const; - bool try_adding_deterministic_input(Amount am, size_t gi, api::Output *spending_output) const; // returns true if our keyimage // methods to add incoming tx Amount add_incoming_output(const api::Output &, const Hash &tid) override; // added amount may be lower Amount add_incoming_keyimage(Height block_height, const KeyImage &) override; - Amount add_incoming_deterministic_input(Height block_height, Amount am, size_t gi, const PublicKey &pk) override; void add_transaction(Height, const Hash &tid, const TransactionPrefix &tx, const api::Transaction &ptx) override; std::string format_output(const api::Output &output); @@ -124,9 +126,9 @@ class WalletStateBasic : protected IWalletState { Height m_tail_height = 0; api::BlockHeader m_tip; - const bool is_det_viewonly; - void put_am_gi_he(Amount am, size_t gi, Height he, const PublicKey &pk); - bool get_am_gi_he(Amount am, size_t gi, Height *he, PublicKey *pk) const; + // void put_am_gi_he(const api::Output &output); + // bool get_gi_he(size_t gi, Height *he, PublicKey *pk) const; + // bool get_am_si_he(Amount am, size_t si, size_t *gi, Height *he, PublicKey *pk) const; // DB generic undo machinery typedef std::map UndoMap; UndoMap current_undo_map; @@ -144,9 +146,9 @@ class WalletStateBasic : protected IWalletState { void remove_from_lock_index(const api::Output &); void unlock(Height now_height, api::Output &&output); - void read_unlock_index(std::map, api::Output> *add, const std::string &index_prefix, + void read_unlock_index(std::map *add, const std::string &index_prefix, const std::string &address, BlockOrTimestamp begin, BlockOrTimestamp end) const; - std::map, api::Output> get_unlocked_outputs( + std::map get_unlocked_outputs( const std::string &address, Height from_height, Height to_height) const; // add coin/spend coin @@ -154,12 +156,13 @@ class WalletStateBasic : protected IWalletState { void remove_from_unspent_index(const api::Output &); bool for_each_in_unspent_index( const std::string &address, Height from, Height to, std::function fun) const; - void update_keyimage(const crypto::EllipticCurvePoint &m, const HeightAmounGi &value, bool nooverwrite); + void update_keyimage(const crypto::EllipticCurvePoint &m, const HeightGi &value); }; } // namespace cn namespace seria { -void ser_members(cn::WalletStateBasic::HeightAmounGi &v, ISeria &s); +void ser_members(cn::WalletStateBasic::HeightGi &v, ISeria &s); +void ser_members(cn::WalletStateBasic::GiHeightPk &v, ISeria &s); void ser_members(cn::WalletStateBasic::UndoValue &v, seria::ISeria &s); } // namespace seria diff --git a/src/Core/hw/HardwareWallet.cpp b/src/Core/hw/HardwareWallet.cpp new file mode 100644 index 00000000..c48c4471 --- /dev/null +++ b/src/Core/hw/HardwareWallet.cpp @@ -0,0 +1,552 @@ +// Copyright (c) 2012-2018, The CryptoNote developers, The Bytecoin developers. +// Licensed under the GNU Lesser General Public License. See LICENSE for details. + +#include "HardwareWallet.hpp" +#include +#include +#include "Core/TransactionBuilder.hpp" +#include "CryptoNote.hpp" +#include "common/BIPs.hpp" +#include "common/Invariant.hpp" +#include "common/MemoryStreams.hpp" +#include "common/StringTools.hpp" +#include "common/Varint.hpp" +#include "crypto/bernstein/crypto-ops.h" +#include "crypto/crypto_helpers.hpp" + +static const bool debug_print = true; + +using namespace cn::hw; +using namespace crypto; +using namespace common; + +std::vector> HardwareWallet::get_connected() { + std::vector> result; + // TODO - add all real connected wallets here + + // Now we create emulator and if hardware wallet found with the same mnemonic, we connect it through emulator + std::unique_ptr em; + try { + em = std::make_unique(std::unique_ptr()); + } catch (const std::exception &) { + // no debug mnemonics set - no problem + } + for (auto &&r : result) { + if (em && r->get_public_view_key() == em->get_public_view_key()) { + em = std::unique_ptr(); + r = std::make_unique(std::move(r)); + } + } + if (em) + result.push_back(std::move(em)); + if (!result.empty()) + std::cout << "Connected hardware wallets" << std::endl; + for (auto &&r : result) { + std::cout << "\t" << r->get_hardware_type() << std::endl; + } + return result; +} + +void HardwareWallet::test_all_methods() { + const PublicKey pk = get_public_view_key(); + const PublicKey test_point1 = crypto::hash_to_good_point(pk.data, sizeof(pk.data)); + std::cout << "---- testing hashes for m_spend_key_base_public_key =" << pk << std::endl; + { + std::cout << "hash_to_bad_point = " << crypto::hash_to_bad_point(pk.data, sizeof(pk.data)) << std::endl; + std::cout << "hash_to_good_point = " << test_point1 << std::endl; + Hash h = cn_fast_hash(pk.data, sizeof(pk.data)); + Hash h2 = cn_fast_hash(h.data, sizeof(h.data)); + std::cout << "hash32 = " << h << std::endl; + std::cout << "hash64 = " << h << h2 << std::endl; + std::cout << "hash_to_scalar64 = " << crypto::hash_to_scalar64(pk.data, sizeof(pk.data)) << std::endl; + } + const SecretKey test_scalar1 = crypto::hash_to_scalar(test_point1.data, sizeof(test_point1.data)); + const Hash test_hash1 = crypto::cn_fast_hash(test_scalar1.data, sizeof(test_scalar1.data)); + const PublicKey test_address1_s = crypto::hash_to_good_point(test_hash1.data, sizeof(test_hash1.data)); + const PublicKey test_address1_v = crypto::hash_to_good_point(test_address1_s.data, sizeof(test_address1_s.data)); + const PublicKey test_point2 = crypto::hash_to_good_point(test_address1_v.data, sizeof(test_address1_v.data)); + const PublicKey test_point3 = crypto::hash_to_good_point(test_point2.data, sizeof(test_point2.data)); + const SecretKey test_scalar2 = crypto::hash_to_scalar(test_point3.data, sizeof(test_point3.data)); + + std::cout << "---- mul_by_view_secret_key" << std::endl; + std::cout << mul_by_view_secret_key({test_point1}).at(0) << std::endl; + std::cout << "---- generate_keyimage" << std::endl; + std::cout << generate_keyimage(test_point1, test_scalar1, 0) << std::endl; + std::cout << "---- generate_output_secret" << std::endl; + PublicKey result_point1, result_point2, result_point3; + generate_output_secret(test_hash1, 0, &result_point1); + std::cout << result_point1 << std::endl; + std::vector extra{1, 2, 3, 4, 5}; + const size_t my_address = 1; + std::cout << "---- sign_start" << std::endl; + sign_start( + 4, 5, 1, 2, extra.size(), my_address, cn::AccountAddressSimple::type_tag, test_address1_s, test_address1_v); + std::cout << "---- add_input" << std::endl; + uint8_t result_byte = 0; + add_input(1000, {0, 1, 2}, test_scalar1, my_address); + std::cout << result_point1 << std::endl; + std::cout << result_point2 << std::endl; + std::cout << "---- add_output" << std::endl; + add_output(false, 400, &result_point1, &result_point2, &result_byte); + std::cout << result_point1 << std::endl; + std::cout << result_point2 << std::endl; + std::cout << int(result_byte) << std::endl; + std::cout << "---- add_output" << std::endl; + add_output(true, 500, &result_point1, &result_point2, &result_byte); + std::cout << result_point1 << std::endl; + std::cout << result_point2 << std::endl; + std::cout << int(result_byte) << std::endl; + std::cout << "---- add_extra_chunk" << std::endl; + add_extra(extra); + std::cout << "---- add_sig_get_xy" << std::endl; + add_sig_a(test_scalar1, my_address, &result_point1, &result_point2, &result_point3); + std::cout << result_point1 << std::endl; + std::cout << result_point2 << std::endl; + std::cout << result_point3 << std::endl; + std::cout << "---- add_sig_a" << std::endl; + SecretKey result_scalar1, result_scalar2, result_scalar3; + add_sig_a_more_data(test_point1.as_binary_array() | test_point2.as_binary_array(), &result_scalar1); + std::cout << "---- add_sig_c0" << std::endl; + std::cout << result_scalar1 << std::endl; + std::cout << "---- add_sig_b" << std::endl; + add_sig_b(test_scalar1, my_address, test_scalar1, &result_scalar1, &result_scalar2, &result_scalar3); + std::cout << result_scalar1 << std::endl; + std::cout << result_scalar2 << std::endl; + std::cout << result_scalar3 << std::endl; + + // repeat first steps to check output generation to unlinkable address + std::cout << "---- sign_start" << std::endl; + sign_start( + 4, 0, 1, 2, extra.size(), my_address, cn::AccountAddressUnlinkable::type_tag, test_address1_s, test_address1_v); + std::cout << "---- add_input" << std::endl; + add_input(1000, {0, 1, 2}, test_scalar1, my_address); + std::cout << result_point1 << std::endl; + std::cout << result_point2 << std::endl; + std::cout << "---- add_output" << std::endl; + add_output(false, 400, &result_point1, &result_point2, &result_byte); + std::cout << result_point1 << std::endl; + std::cout << result_point2 << std::endl; + std::cout << int(result_byte) << std::endl; + + Signature result_sig0; + std::cout << "---- generate_sendproof" << std::endl; + generate_sendproof(test_hash1, 1, test_hash1, test_hash1, "mega address", 5, &result_sig0); + std::cout << result_sig0.c << result_sig0.r << std::endl; + std::cout << "---- export_view_only" << std::endl; + Hash result_hash1; + export_view_only(&result_scalar1, &result_scalar2, &result_hash1, &result_sig0); + std::cout << result_scalar1 << std::endl; + std::cout << result_scalar2 << std::endl; + std::cout << result_hash1 << std::endl; + std::cout << result_sig0.c << result_sig0.r << std::endl; + std::cout << "---- tests finished" << std::endl; +} + +static Hash derive_from_seed(const Hash &seed, const std::string &append) { + BinaryArray seed_data = seed.as_binary_array() | as_binary_array(append); + return crypto::cn_fast_hash(seed_data.data(), seed_data.size()); +} + +void Emulator::KeccakStream::append(const unsigned char *data, size_t size) { common::append(ba, data, data + size); } +void Emulator::KeccakStream::append(uint64_t a) { common::append(ba, common::get_varint_data(a)); } +void Emulator::KeccakStream::append_byte(uint8_t b) { ba.push_back(b); } +Hash Emulator::KeccakStream::cn_fast_hash() const { + Hash result = crypto::cn_fast_hash(ba.data(), ba.size()); + if (debug_print) + std::cout << "KeccakStream hash( " << common::to_hex(ba) << " )= " << result << std::endl; + return result; +} +SecretKey Emulator::KeccakStream::hash_to_scalar() const { return crypto::hash_to_scalar(ba.data(), ba.size()); } +SecretKey Emulator::KeccakStream::hash_to_scalar64() const { return crypto::hash_to_scalar(ba.data(), ba.size()); } +PublicKey Emulator::KeccakStream::hash_to_good_point() const { + return crypto::hash_to_good_point(ba.data(), ba.size()); +} + +inline bool add_amount(uint64_t &sum, uint64_t amount) { + if (std::numeric_limits::max() - amount < sum) + return false; + sum += amount; + return true; +} + +std::string debug_mnemonic; + +void Emulator::debug_set_mnemonic(const std::string &mnemonic) { debug_mnemonic = mnemonic; } + +Emulator::Emulator(std::unique_ptr &&proxy) : m_proxy(std::move(proxy)) { + // read m_wallet_key, m_spend_key_base_public_key from device + + m_mnemonics = cn::Bip32Key::check_bip39_mnemonic(debug_mnemonic); + const cn::Bip32Key master_key = cn::Bip32Key::create_master_key(m_mnemonics, std::string()); + + const cn::Bip32Key k0 = master_key.derive_key(0x8000002c); + const cn::Bip32Key k1 = k0.derive_key(0x800000cc); + const cn::Bip32Key k2 = k1.derive_key(0x80000000 + uint32_t(m_address_type)); + const cn::Bip32Key k3 = k2.derive_key(0); + const cn::Bip32Key k4 = k3.derive_key(0); + const Hash m_seed = crypto::cn_fast_hash(k4.get_priv_key().data(), k4.get_priv_key().size()); + + m_tx_derivation_seed = derive_from_seed(m_seed, "tx_derivation"); + BinaryArray vk_data = m_seed.as_binary_array() | as_binary_array("view_key"); + m_view_secret_key = crypto::hash_to_scalar(vk_data.data(), vk_data.size()); + BinaryArray ak_data = m_seed.as_binary_array() | as_binary_array("audit_key_base"); + m_audit_key_base_secret_key = crypto::hash_to_scalar(ak_data.data(), ak_data.size()); + BinaryArray sk_data = m_seed.as_binary_array() | as_binary_array("spend_key"); + m_spend_secret_key = crypto::hash_to_scalar(sk_data.data(), sk_data.size()); + + m_sH = crypto::A_mul_b(crypto::get_H(), m_spend_secret_key); + + invariant(crypto::secret_key_to_public_key(m_view_secret_key, &m_view_public_key), ""); + PublicKey A; + invariant(crypto::secret_key_to_public_key(m_audit_key_base_secret_key, &A), ""); + m_A_plus_SH = crypto::A_plus_B(A, m_sH); + m_v_mul_A_plus_SH = A_mul_b(m_A_plus_SH, m_view_secret_key); // for hw debug only + + const Hash wallet_key_hash = derive_from_seed(m_seed, "wallet_key"); + m_wallet_key = chacha_key{wallet_key_hash}; + + if (debug_print) { + std::cout << "bip44 child private key " << common::to_hex(k4.get_priv_key()) << std::endl; + std::cout << "m_seed " << m_seed << std::endl; + std::cout << "m_tx_derivation_seed " << m_tx_derivation_seed << std::endl; + std::cout << "m_audit_key_base_secret_key " << m_audit_key_base_secret_key << std::endl; + std::cout << "A " << A << std::endl; + std::cout << "m_view_secret_key " << m_view_secret_key << std::endl; + std::cout << "m_view_public_key " << m_view_public_key << std::endl; + std::cout << "m_spend_secret_key " << m_spend_secret_key << std::endl; + std::cout << "m_sH " << m_sH << std::endl; + std::cout << "m_wallet_key " << wallet_key_hash << std::endl; + } + if (m_proxy) { + invariant(get_A_plus_SH() == m_proxy->get_A_plus_SH(), ""); + invariant(get_v_mul_A_plus_SH() == m_proxy->get_v_mul_A_plus_SH(), ""); + invariant(get_public_view_key() == m_proxy->get_public_view_key(), ""); + // TODO - compare chacha keys + } + test_all_methods(); +} + +Emulator::~Emulator() {} + +std::string Emulator::get_hardware_type() const { + std::string result = "Emulator"; + if (m_proxy) + result += " connected to " + m_proxy->get_hardware_type(); + return result + ", mnemonic=" + m_mnemonics; +} + +// When we need only secrets +void Emulator::prepare_address(size_t address_index) const { + if (address_index != last_address_index) { + last_address_index = address_index; + last_address_audit_secret_key = + crypto::generate_hd_secretkey(m_audit_key_base_secret_key, m_A_plus_SH, address_index); + std::cout << "HW::prepare_address[" << address_index << "]=" << last_address_audit_secret_key << std::endl; + } +} + +// When we need also public address part +void Emulator::prepare_address(size_t address_index, PublicKey *address_S, PublicKey *address_Sv) const { + prepare_address(address_index); + PublicKey last_address_audit_public_key; + invariant(crypto::secret_key_to_public_key(last_address_audit_secret_key, &last_address_audit_public_key), ""); + *address_S = crypto::A_plus_B(last_address_audit_public_key, m_sH); + *address_Sv = crypto::A_mul_b(*address_S, m_view_secret_key); +} + +std::vector Emulator::mul_by_view_secret_key(const std::vector &output_public_keys) const { + // multiply by m_view_secret_key on device, throw if PublicKey detected to be invalid by device + std::vector result(output_public_keys.size()); + for (size_t i = 0; i != result.size(); ++i) + result.at(i) = crypto::unlinkable_underive_address_S_step1(m_view_secret_key, output_public_keys.at(i)); + if (m_proxy) + invariant(m_proxy->mul_by_view_secret_key(output_public_keys) == result, ""); + return result; +} + +KeyImage Emulator::generate_keyimage( + const PublicKey &output_public_key, const SecretKey &inv_spend_scalar, size_t address_index) const { + prepare_address(address_index); + SecretKey output_secret_key_a = crypto::sc_mul(last_address_audit_secret_key, inv_spend_scalar); + auto result = crypto::generate_key_image(output_public_key, output_secret_key_a); + if (m_proxy) + invariant(m_proxy->generate_keyimage(output_public_key, inv_spend_scalar, address_index) == result, ""); + return result; +} + +void Emulator::generate_output_secret(const Hash &tx_inputs_hash, size_t out_index, PublicKey *output_secret_Q) const { + *output_secret_Q = cn::TransactionBuilder::deterministic_keys_from_seed( + tx_inputs_hash, m_tx_derivation_seed, common::get_varint_data(out_index)) + .public_key; + if (m_proxy) { + PublicKey p_output_secret_Q; + m_proxy->generate_output_secret(tx_inputs_hash, out_index, &p_output_secret_Q); + invariant(*output_secret_Q == p_output_secret_Q, ""); + } +} + +// TODO - check sig methods for resuls with proxy + +void Emulator::sign_start(size_t version, size_t ut, size_t inputs_size, size_t outputs_size, size_t extra_size, + size_t change_address_index, uint8_t dst_address_tag, PublicKey dst_address_s, PublicKey dst_address_s_v) const { + invariant(inputs_size != 0, ""); // 0 inputs not allowed in consensus + invariant(outputs_size != 0, ""); // 0 outputs allowed in consensus, we prohibit it to simplify our state machine + sign = SigningState{}; + sign.version = version; + sign.ut = ut; + sign.inputs_size = inputs_size; + sign.outputs_size = outputs_size; + sign.extra_size = extra_size; + sign.dst_address_tag = dst_address_tag; + sign.dst_address_s = dst_address_s; + sign.dst_address_s_v = dst_address_s_v; + sign.state = SigningState::EXPECT_INPUT; + + prepare_address(change_address_index, &sign.change_address_s, &sign.change_address_s_v); + + sign.tx_prefix_stream.append(version); + sign.tx_prefix_stream.append(ut); + sign.tx_prefix_stream.append(inputs_size); + sign.tx_inputs_stream.append(inputs_size); + + sign.random_seed = Hash{}; // = crypto::rand(); - uncomment in final code for full security +} + +SecretKey Emulator::generate_sign_secret(size_t i, const char secret_name[2]) const { + KeccakStream ks{}; + ks.append(sign.random_seed.data, 32); + ks.append(m_spend_secret_key.data, 32); + ks.append_byte(secret_name[0]); + ks.append_byte(secret_name[1]); + ks.append(i); + SecretKey b = ks.hash_to_scalar64(); + if (debug_print) + std::cout << secret_name[0] << secret_name[1] << "[" << i << "]=" << b << std::endl; + return b; +} + +void Emulator::add_input(uint64_t amount, const std::vector &output_indexes, SecretKey inv_spend_scalar, + size_t address_index) const { + invariant(sign.state == SigningState::EXPECT_INPUT && sign.inputs_counter < sign.inputs_size, ""); + const uint8_t tag = cn::InputKey::type_tag; + sign.tx_prefix_stream.append_byte(tag); + sign.tx_inputs_stream.append_byte(tag); + sign.tx_prefix_stream.append(amount); + sign.tx_inputs_stream.append(amount); + invariant(add_amount(sign.inputs_amount, amount), ""); + sign.tx_prefix_stream.append(output_indexes.size()); + sign.tx_inputs_stream.append(output_indexes.size()); + for (size_t j = 0; j != output_indexes.size(); ++j) { + sign.tx_prefix_stream.append(output_indexes[j]); + sign.tx_inputs_stream.append(output_indexes[j]); + } + prepare_address(address_index); + SecretKey output_secret_key_a = crypto::sc_mul(last_address_audit_secret_key, inv_spend_scalar); + SecretKey output_secret_key_s = crypto::sc_mul(m_spend_secret_key, inv_spend_scalar); + PublicKey output_public_key = crypto::secret_keys_to_public_key(output_secret_key_a, output_secret_key_s); + KeyImage key_image = crypto::generate_key_image(output_public_key, output_secret_key_a); + + sign.tx_prefix_stream.append(key_image.data, 32); + sign.tx_inputs_stream.append(key_image.data, 32); + + if (++sign.inputs_counter < sign.inputs_size) + return; + sign.state = SigningState::EXPECT_OUTPUT; + sign.tx_inputs_hash = sign.tx_inputs_stream.cn_fast_hash(); + sign.tx_prefix_stream.append_byte(sign.outputs_size); +} + +void Emulator::add_output_or_change(uint64_t amount, uint8_t dst_address_tag, PublicKey dst_address_s, + PublicKey dst_address_s_v, PublicKey *public_key, PublicKey *encrypted_secret, + uint8_t *encrypted_address_type) const { + KeyPair output_det_keys = cn::TransactionBuilder::deterministic_keys_from_seed( + sign.tx_inputs_hash, m_tx_derivation_seed, common::get_varint_data(sign.outputs_counter)); + SecretKey output_secret_scalar; + PublicKey output_secret_point; + Hash output_secret_address_type; + cn::TransactionBuilder::generate_output_secrets( + output_det_keys.public_key, &output_secret_scalar, &output_secret_point, &output_secret_address_type); + + uint8_t output_tag = cn::OutputKey::type_tag; + + *encrypted_address_type = dst_address_tag ^ output_secret_address_type.data[0]; + if (dst_address_tag == cn::AccountAddressSimple::type_tag) { + *public_key = crypto::linkable_derive_output_public_key(output_secret_scalar, sign.tx_inputs_hash, + sign.outputs_counter, dst_address_s, dst_address_s_v, encrypted_secret); + } else { + *public_key = crypto::unlinkable_derive_output_public_key(output_secret_point, sign.tx_inputs_hash, + sign.outputs_counter, dst_address_s, dst_address_s_v, encrypted_secret); + } + + sign.tx_prefix_stream.append_byte(output_tag); + sign.tx_prefix_stream.append(amount); + sign.tx_prefix_stream.append(public_key->data, 32); + sign.tx_prefix_stream.append(encrypted_secret->data, 32); + sign.tx_prefix_stream.append_byte(*encrypted_address_type); +} + +void Emulator::add_output(bool change, uint64_t amount, PublicKey *public_key, PublicKey *encrypted_secret, + uint8_t *encrypted_address_type) const { + invariant(sign.state == SigningState::EXPECT_OUTPUT && sign.outputs_counter < sign.outputs_size, ""); + if (change) { + invariant(add_amount(sign.change_amount, amount), ""); + add_output_or_change(amount, cn::AccountAddressUnlinkable::type_tag, sign.change_address_s, + sign.change_address_s_v, public_key, encrypted_secret, encrypted_address_type); + } else { + invariant(add_amount(sign.dst_amount, amount), ""); + add_output_or_change(amount, sign.dst_address_tag, sign.dst_address_s, sign.dst_address_s_v, public_key, + encrypted_secret, encrypted_address_type); + } + if (++sign.outputs_counter < sign.outputs_size) + return; + uint64_t outputs_amount = sign.dst_amount; + invariant(add_amount(outputs_amount, sign.change_amount), ""); + invariant(sign.inputs_amount >= outputs_amount, ""); + uint64_t fee = sign.inputs_amount - outputs_amount; + std::cout << "fee=" << fee << std::endl; + // Here, show user 2 dialogs + // 1. Do you wish to send 'dst_amount' to 'dst_address'? + // 2. Fee will be 'fee' + // If both answered yes, continue to signing. Otherwise cancel + sign.state = SigningState::EXPECT_EXTRA_CHUNK; + sign.tx_prefix_stream.append(sign.extra_size); +} + +void Emulator::add_extra(const BinaryArray &chunk) const { + invariant(sign.state == SigningState::EXPECT_EXTRA_CHUNK, ""); + invariant(sign.extra_counter + chunk.size() <= sign.extra_size, ""); // <= because we call it also for empty extra + sign.tx_prefix_stream.append(chunk.data(), chunk.size()); + sign.extra_counter += chunk.size(); + if (sign.extra_counter < sign.extra_size) + return; + sign.state = SigningState::EXPECT_SIGN_A; + sign.tx_prefix_hash = sign.tx_prefix_stream.cn_fast_hash(); + sign.inputs_counter = 0; + sign.tx_inputs_stream = KeccakStream{}; + sign.tx_inputs_stream.append(sign.tx_prefix_hash.data, 32); +} + +void Emulator::add_sig_a(SecretKey inv_spend_scalar, size_t address_index, EllipticCurvePoint *sig_p, + EllipticCurvePoint *x, EllipticCurvePoint *y) const { + invariant(sign.state == SigningState::EXPECT_SIGN_A && sign.inputs_counter < sign.inputs_size, ""); + + prepare_address(address_index); + SecretKey output_secret_key_a = crypto::sc_mul(last_address_audit_secret_key, inv_spend_scalar); + SecretKey output_secret_key_s = crypto::sc_mul(m_spend_secret_key, inv_spend_scalar); + PublicKey output_public_key = crypto::secret_keys_to_public_key(output_secret_key_a, output_secret_key_s); + KeyImage key_image = crypto::generate_key_image(output_public_key, output_secret_key_a); + + const P3 b_coin_p3(hash_to_good_point_p3(key_image)); + const PublicKey b_coin = to_bytes(b_coin_p3); + const P3 hash_pubs_sec_p3(hash_to_good_point_p3(output_public_key)); + if (debug_print) + std::cout << "b_coin[" << sign.inputs_counter << "]=" << b_coin << std::endl; + const P3 p_p3 = H * output_secret_key_s - b_coin_p3 * output_secret_key_a; + *sig_p = to_bytes(p_p3); + if (debug_print) + std::cout << "p[" << sign.inputs_counter << "]=" << *sig_p << std::endl; + sign.tx_inputs_stream.append(sig_p->data, 32); + + const SecretKey ka = generate_sign_secret(sign.inputs_counter, "ka"); + const SecretKey kb = generate_sign_secret(sign.inputs_counter, "kb"); + const SecretKey kc = generate_sign_secret(sign.inputs_counter, "kc"); + + const PublicKey z = to_bytes(kb * H + kc * b_coin_p3); + sign.tx_inputs_stream.append(z.data, 32); + + const P3 G_plus_B_p3 = P3(G) + b_coin_p3; + if (debug_print) + std::cout << "pk[" << sign.inputs_counter << ", " + << "my" + << "]=" << output_public_key << std::endl; + *x = to_bytes(ka * G_plus_B_p3); + if (debug_print) + std::cout << "x[" << sign.inputs_counter << ", " + << "my" + << "]=" << *x << std::endl; + *y = to_bytes(ka * hash_pubs_sec_p3); + if (debug_print) + std::cout << "y[" << sign.inputs_counter << ", " + << "my" + << "]=" << *y << std::endl; + + sign.state = SigningState::EXPECT_SIGN_A_MORE_DATA; +} + +void Emulator::add_sig_a_more_data(const BinaryArray &data, EllipticCurveScalar *c0) const { + invariant(sign.state == SigningState::EXPECT_SIGN_A_MORE_DATA, ""); + + sign.tx_inputs_stream.append(data.data(), data.size()); + + if (++sign.inputs_counter < sign.inputs_size) { + *c0 = EllipticCurveScalar{}; + sign.state = SigningState::EXPECT_SIGN_A; + return; + } + sign.c0 = sign.tx_inputs_stream.hash_to_scalar(); + *c0 = sign.c0; + if (debug_print) + std::cout << "c0=" << sign.c0 << std::endl; + sign.state = SigningState::EXPECT_SIGN_B; + sign.inputs_counter = 0; +} + +void Emulator::add_sig_b(SecretKey inv_spend_scalar, size_t address_index, EllipticCurveScalar my_c, + EllipticCurveScalar *sig_my_ra, EllipticCurveScalar *sig_rb, EllipticCurveScalar *sig_rc) const { + invariant(sign.state == SigningState::EXPECT_SIGN_B, ""); + + prepare_address(address_index); + SecretKey output_secret_key_a = crypto::sc_mul(last_address_audit_secret_key, inv_spend_scalar); + SecretKey output_secret_key_s = crypto::sc_mul(m_spend_secret_key, inv_spend_scalar); + + const SecretKey ka = generate_sign_secret(sign.inputs_counter, "ka"); + const SecretKey kb = generate_sign_secret(sign.inputs_counter, "kb"); + const SecretKey kc = generate_sign_secret(sign.inputs_counter, "kc"); + + *sig_rb = kb - sign.c0 * output_secret_key_s; + *sig_rc = kc + sign.c0 * output_secret_key_a; + *sig_my_ra = ka - my_c * output_secret_key_a; + + if (debug_print) + std::cout << "ra[" << sign.inputs_counter << ", my]=" << *sig_my_ra << std::endl; + if (debug_print) + std::cout << "rb[" << sign.inputs_counter << "]=" << *sig_rb << std::endl; + if (debug_print) + std::cout << "rc[" << sign.inputs_counter << "]=" << *sig_rc << std::endl; + + if (++sign.inputs_counter < sign.inputs_size) + return; + sign.state = SigningState::FINISHED; +} + +void Emulator::generate_sendproof(const Hash &tx_inputs_hash, size_t out_index, const Hash &transaction_hash, + const Hash &message_hash, const std::string &address, size_t outputs_count, Signature *signature) const { + // KeyPair output_det_keys = cn::TransactionBuilder::deterministic_keys_from_seed( + // tx_inputs_hash, m_tx_derivation_seed, common::get_varint_data(out_index)); + // *signature = + // crypto::amethyst_generate_sendproof(output_det_keys, transaction_hash, message_hash, address, + // outputs_count); +} + +void Emulator::export_view_only(SecretKey *audit_key_base_secret_key, SecretKey *view_secret_key, + Hash *tx_derivation_seed, Signature *view_secrets_signature) const { + *view_secret_key = m_view_secret_key; + *audit_key_base_secret_key = m_audit_key_base_secret_key; + // Ask user if he wants view wallet to view outgoing addresses + bool view_outgoing_addresses = true; + if (view_outgoing_addresses) + *tx_derivation_seed = m_tx_derivation_seed; + KeccakStream ks; + ks.append(audit_key_base_secret_key->data, 32); + ks.append(m_view_secret_key.data, 32); + Hash view_secrets_hash = ks.cn_fast_hash(); + + *view_secrets_signature = crypto::generate_signature_H(view_secrets_hash, m_sH, m_spend_secret_key); + if (debug_print) { + std::cout << "audit_key_base_secret_key=" << *audit_key_base_secret_key << std::endl; + std::cout << "view_secret_key=" << view_secret_key << std::endl; + std::cout << "m_sH=" << m_sH << std::endl; + std::cout << "view_secrets_hash=" << view_secrets_hash << std::endl; + std::cout << "view_secrets_signature=" << view_secrets_signature->c << view_secrets_signature->r << std::endl; + } +} diff --git a/src/Core/hw/HardwareWallet.hpp b/src/Core/hw/HardwareWallet.hpp new file mode 100644 index 00000000..a50a0040 --- /dev/null +++ b/src/Core/hw/HardwareWallet.hpp @@ -0,0 +1,179 @@ +// Copyright (c) 2012-2018, The CryptoNote developers, The Bytecoin developers. +// Licensed under the GNU Lesser General Public License. See LICENSE for details. + +#pragma once + +#include +#include +#include "CryptoNote.hpp" +#include "crypto/chacha.hpp" + +namespace cn { namespace hw { + +// Prototype - max simplified synchronous calls + +// All funs including constructor throw std::runtime_error when connection to hardware wallet lost before end of fun. +// All funs must quickly try reestablishing connection at the start if it was lost during previous call +// Calls might be from different threads, but will be externally synchronized + +class HardwareWallet { +public: + virtual ~HardwareWallet() = default; + virtual std::string get_hardware_type() const = 0; + + // In constructor read those secrets from device at once + virtual crypto::chacha_key get_wallet_key() const = 0; + virtual PublicKey get_A_plus_SH() const = 0; + virtual PublicKey get_v_mul_A_plus_SH() const = 0; + virtual PublicKey get_public_view_key() const = 0; + + // We multiply in batches, because we have lots of them (for all outputs) + // Hardware is expected to divide into chunks of size that fit + virtual std::vector mul_by_view_secret_key(const std::vector &output_public_keys) const = 0; + // We generate key images by one because we have a few of them (only for 'our' outputs) + virtual KeyImage generate_keyimage( + const PublicKey &output_public_key, const SecretKey &inv_spend_scalar, size_t address_index) const = 0; + virtual void generate_output_secret( + const Hash &tx_inputs_hash, size_t out_index, PublicKey *output_secret_Q) const = 0; + virtual void sign_start(size_t version, size_t ut, size_t inputs_size, size_t outputs_size, size_t extra_size, + size_t change_address_index, uint8_t dst_address_tag, PublicKey dst_address_s, + PublicKey dst_address_s_v) const = 0; + virtual void add_input(uint64_t amount, const std::vector &output_indexes, SecretKey inv_spend_scalar, + size_t address_index) const = 0; + virtual void add_output(bool change, uint64_t amount, PublicKey *public_key, PublicKey *encrypted_secret, + uint8_t *encrypted_address_type) const = 0; + // add_extra called even when empty, to simplify state machine + virtual void add_extra(const common::BinaryArray &extra) const = 0; + // add_sig_a1, then add_sig_a2 is called for each input + virtual void add_sig_a(SecretKey inv_spend_scalar, size_t address_index, crypto::EllipticCurvePoint *sig_p, + crypto::EllipticCurvePoint *x, crypto::EllipticCurvePoint *y) const = 0; + virtual void add_sig_a_more_data(const common::BinaryArray &data, crypto::EllipticCurveScalar *c0) const = 0; + virtual void add_sig_b(SecretKey inv_spend_scalar, size_t address_index, crypto::EllipticCurveScalar my_c, + crypto::EllipticCurveScalar *sig_my_ra, crypto::EllipticCurveScalar *sig_rb, + crypto::EllipticCurveScalar *sig_rc) const = 0; + virtual void generate_sendproof(const Hash &tx_inputs_hash, size_t out_index, const Hash &transaction_hash, + const Hash &message_hash, const std::string &address, size_t outputs_count, Signature *signature) const = 0; + virtual void export_view_only(SecretKey *audit_key_base_secret_key, SecretKey *view_secret_key, + Hash *tx_derivation_seed, Signature *view_secrets_signature) const = 0; + + void test_all_methods(); + + static std::vector> get_connected(); +}; + +class Emulator : public HardwareWallet { + crypto::chacha_key m_wallet_key; // wallet encryption key, derived from secret + PublicKey m_A_plus_SH; + PublicKey m_v_mul_A_plus_SH; + PublicKey m_view_public_key; + + const size_t m_address_type = 1; + + // following vars reside inside hw wallet + std::string m_mnemonics; + SecretKey m_view_secret_key; + SecretKey m_spend_secret_key; + SecretKey m_audit_key_base_secret_key; + Hash m_tx_derivation_seed; + PublicKey m_sH; + + mutable size_t last_address_index = std::numeric_limits::max(); + mutable SecretKey last_address_audit_secret_key; + void prepare_address(size_t address_index) const; + void prepare_address(size_t address_index, PublicKey *address_S, PublicKey *address_Sv) const; + struct KeccakStream { // Naive implemenatation, real hardware will use fixed 200+4 bytes rep + common::BinaryArray ba; + + void append(const unsigned char *data, size_t size); + void append(uint64_t a); + void append_byte(uint8_t b); + Hash cn_fast_hash() const; + SecretKey hash_to_scalar() const; + SecretKey hash_to_scalar64() const; + PublicKey hash_to_good_point() const; + }; + struct SigningState { + // FINISHED is default state. + // sign_start can be called in any state to restart signing + // other sign methods require specific state, and + // move system into subsequent state until moved to FINISHED + enum State { + FINISHED, + EXPECT_INPUT, // inputs_counter as a substate + EXPECT_OUTPUT, // outputs_counter as a substate + EXPECT_EXTRA_CHUNK, // extra_counter as a substate + EXPECT_SIGN_A, // inputs_counter as a substate + EXPECT_SIGN_A_MORE_DATA, // inputs_counter as a substate + EXPECT_SIGN_B // inputs_counter as a substate + }; + State state = FINISHED; + size_t version = 0; + size_t ut = 0; + size_t inputs_size = 0; + size_t outputs_size = 0; + size_t extra_size = 0; + uint8_t dst_address_tag = 0; + PublicKey dst_address_s; + PublicKey dst_address_s_v; + PublicKey change_address_s; + PublicKey change_address_s_v; + + size_t inputs_counter = 0; + size_t outputs_counter = 0; + size_t extra_counter = 0; + Hash random_seed; // single seed for the whole transaction + KeccakStream tx_inputs_stream; // we reuse it during sig_a, sig_b for c0 = Hash(... + Hash tx_inputs_hash; + KeccakStream tx_prefix_stream; + Hash tx_prefix_hash; + uint64_t inputs_amount = 0; + uint64_t dst_amount = 0; + uint64_t change_amount = 0; + crypto::EllipticCurveScalar c0; + }; + mutable SigningState sign; + void add_output_or_change(uint64_t amount, uint8_t dst_address_tag, PublicKey dst_address_s, + PublicKey dst_address_s_v, PublicKey *public_key, PublicKey *encrypted_secret, + uint8_t *encrypted_address_type) const; + SecretKey generate_sign_secret(size_t i, const char secret_name[2]) const; + + std::unique_ptr m_proxy; + +public: + static void debug_set_mnemonic(const std::string &mnemonic); + + explicit Emulator(std::unique_ptr &&proxy); + ~Emulator() override; + std::string get_hardware_type() const override; + crypto::chacha_key get_wallet_key() const override { return m_wallet_key; } + PublicKey get_A_plus_SH() const override { return m_A_plus_SH; } + PublicKey get_v_mul_A_plus_SH() const override { return m_v_mul_A_plus_SH; } + PublicKey get_public_view_key() const override { return m_view_public_key; } + + std::vector mul_by_view_secret_key(const std::vector &output_public_keys) const override; + KeyImage generate_keyimage( + const PublicKey &output_public_key, const SecretKey &inv_spend_scalar, size_t address_index) const override; + void generate_output_secret( + const Hash &tx_inputs_hash, size_t out_index, PublicKey *output_secret_Q) const override; + void sign_start(size_t version, size_t ut, size_t inputs_size, size_t outputs_size, size_t extra_size, + size_t change_address_index, uint8_t dst_address_tag, PublicKey dst_address_s, + PublicKey dst_address_s_v) const override; + void add_input(uint64_t amount, const std::vector &output_indexes, SecretKey inv_spend_scalar, + size_t address_index) const override; + void add_output(bool change, uint64_t amount, PublicKey *public_key, PublicKey *encrypted_secret, + uint8_t *encrypted_address_type) const override; + void add_extra(const common::BinaryArray &extra) const override; + void add_sig_a(SecretKey inv_spend_scalar, size_t address_index, crypto::EllipticCurvePoint *sig_p, + crypto::EllipticCurvePoint *x, crypto::EllipticCurvePoint *y) const override; + void add_sig_a_more_data(const common::BinaryArray &data, crypto::EllipticCurveScalar *c0) const override; + void add_sig_b(SecretKey inv_spend_scalar, size_t address_index, crypto::EllipticCurveScalar my_c, + crypto::EllipticCurveScalar *sig_my_ra, crypto::EllipticCurveScalar *sig_rb, + crypto::EllipticCurveScalar *sig_rc) const override; + void generate_sendproof(const Hash &tx_inputs_hash, size_t out_index, const Hash &transaction_hash, + const Hash &message_hash, const std::string &address, size_t outputs_count, + Signature *signature) const override; + void export_view_only(SecretKey *audit_key_base_secret_key, SecretKey *view_secret_key, Hash *tx_derivation_seed, + Signature *view_secrets_signature) const override; +}; + +}} // namespace cn::hw diff --git a/src/CryptoNote.cpp b/src/CryptoNote.cpp index a3848407..dcd1ad52 100644 --- a/src/CryptoNote.cpp +++ b/src/CryptoNote.cpp @@ -27,12 +27,12 @@ void ser(Signature &v, ISeria &s) { s.binary(reinterpret_cast(&v), si void ser(crypto::EllipticCurveScalar &v, ISeria &s) { s.binary(v.data, sizeof(v.data)); } void ser_members(cn::AccountAddressSimple &v, ISeria &s) { - seria_kv("spend", v.spend_public_key, s); - seria_kv("view", v.view_public_key, s); + seria_kv("spend", v.S, s); + seria_kv("view", v.V, s); } void ser_members(cn::AccountAddressUnlinkable &v, ISeria &s) { - seria_kv("s", v.s, s); - seria_kv("sv", v.sv, s); + seria_kv("spend", v.S, s); + seria_kv("spend_view", v.Sv, s); } void ser_members(AccountAddress &v, ISeria &s) { if (s.is_input()) { @@ -45,8 +45,6 @@ void ser_members(AccountAddress &v, ISeria &s) { type = AccountAddressSimple::type_tag; else if (str_type_tag == AccountAddressUnlinkable::str_type_tag()) type = AccountAddressUnlinkable::type_tag; - else if (str_type_tag == AccountAddressUnlinkable::str_type_tag_auditable()) - type = AccountAddressUnlinkable::type_tag_auditable; else throw std::runtime_error("Deserialization error - unknown address type " + str_type_tag); } else @@ -58,10 +56,8 @@ void ser_members(AccountAddress &v, ISeria &s) { v = in; break; } - case AccountAddressUnlinkable::type_tag: - case AccountAddressUnlinkable::type_tag_auditable: { + case AccountAddressUnlinkable::type_tag: { AccountAddressUnlinkable in{}; - in.is_auditable = (type == AccountAddressUnlinkable::type_tag_auditable); ser_members(in, s); v = in; break; @@ -86,73 +82,94 @@ void ser_members(AccountAddress &v, ISeria &s) { auto &in = boost::get(v); s.object_key("type"); if (dynamic_cast(&s)) { - std::string str_type_tag = in.is_auditable ? AccountAddressUnlinkable::str_type_tag_auditable() - : AccountAddressUnlinkable::str_type_tag(); + std::string str_type_tag = AccountAddressUnlinkable::str_type_tag(); ser(str_type_tag, s); } else { - uint8_t type = - in.is_auditable ? AccountAddressUnlinkable::type_tag_auditable : AccountAddressUnlinkable::type_tag; + uint8_t type = AccountAddressUnlinkable::type_tag; s.binary(&type, 1); } ser_members(in, s); } } void ser_members(cn::SendproofKey &v, ISeria &s) { + Amount amount = 0; + seria_kv("transaction_hash", v.transaction_hash, s); + seria_kv("message", v.message, s); + seria_kv("address", v.address, s); seria_kv("derivation", v.derivation, s); seria_kv("signature", v.signature, s); + if (dynamic_cast(&s)) // skip amount + seria_kv("amount", amount, s); } void ser_members(cn::SendproofAmethyst::Element &v, ISeria &s) { seria_kv("out_index", v.out_index, s); seria_kv("deterministic_public_key", v.deterministic_public_key, s); - seria_kv("signature", v.signature, s); } -void ser_members(cn::SendproofAmethyst &v, ISeria &s) { seria_kv("elements", v.elements, s); } -void ser_members(Sendproof &v, ISeria &s, const Currency ¤cy) { - std::string addr; - if (!s.is_input()) - addr = currency.account_address_as_string(v.address); - seria_kv("address", addr, s); - if (s.is_input() && (!currency.parse_account_address_string(addr, &v.address))) - throw api::ErrorAddress(api::ErrorAddress::ADDRESS_FAILED_TO_PARSE, "Failed to parse wallet address", addr); +void ser_members(cn::SendproofAmethyst &v, ISeria &s) { + uint8_t guard_byte = 0; + seria_kv("guard_byte", guard_byte, s); + if (s.is_input() && guard_byte != 0) // forced binary incompatibility with transactions + throw std::runtime_error("Sendproof disambiguition fails"); + seria_kv("version", v.version, s); seria_kv("transaction_hash", v.transaction_hash, s); seria_kv("message", v.message, s); - seria_kv("amount", v.amount, s); - std::string proof; - BinaryArray binary_proof; - if (!s.is_input()) { - if (v.proof.type() == typeid(SendproofKey)) { - auto type = SendproofKey::str_type_tag(); - seria_kv("type", type, s); - auto &var = boost::get(v.proof); - binary_proof = seria::to_binary(var); - } else if (v.proof.type() == typeid(SendproofAmethyst)) { - auto type = SendproofAmethyst::str_type_tag(); - seria_kv("type", type, s); - auto &var = boost::get(v.proof); - binary_proof = seria::to_binary(var); - } else - throw api::ErrorAddress(api::ErrorAddress::ADDRESS_FAILED_TO_PARSE, "Unknown address type", addr); - proof = common::base58::encode(binary_proof); + if (v.version == 1) { + seria_kv("address", v.address_simple, s); + seria_kv("derivation", v.derivation, s); + seria_kv("signature", v.signature, s); + return; } - seria_kv("proof", proof, s); - if (s.is_input()) { - if (!common::base58::decode(proof, &binary_proof)) - throw std::runtime_error("Wrong proof format - " + proof); - std::string type = SendproofKey::str_type_tag(); - seria_kv("type", type, s); - if (type == SendproofKey::str_type_tag()) { - SendproofKey sp; - seria::from_binary(sp, binary_proof); - v.proof = sp; - } else if (type == SendproofAmethyst::str_type_tag()) { - SendproofAmethyst sp; - seria::from_binary(sp, binary_proof); - v.proof = sp; - } else - throw api::ErrorAddress(api::ErrorAddress::ADDRESS_FAILED_TO_PARSE, "Unknown address type", addr); + if (v.version == 4) { + seria_kv("elements", v.elements, s); + return; } } -void ser_members(TransactionInput &v, ISeria &s, bool is_tx_amethyst) { +/*void ser_members(Sendproof &v, ISeria &s, const Currency ¤cy) { + std::string addr; + if (!s.is_input()) + addr = currency.account_address_as_string(v.address); + seria_kv("address", addr, s); + if (s.is_input() && (!currency.parse_account_address_string(addr, &v.address))) + throw api::ErrorAddress(api::ErrorAddress::ADDRESS_FAILED_TO_PARSE, "Failed to parse wallet address", addr); + seria_kv("transaction_hash", v.transaction_hash, s); + seria_kv("message", v.message, s); + seria_kv("amount", v.amount, s); + std::string proof; + BinaryArray binary_proof; + if (!s.is_input()) { + if (v.proof.type() == typeid(SendproofKey)) { + auto type = SendproofKey::str_type_tag(); + seria_kv("type", type, s); + auto &var = boost::get(v.proof); + binary_proof = seria::to_binary(var); + } else if (v.proof.type() == typeid(SendproofAmethyst)) { + auto type = SendproofAmethyst::str_type_tag(); + seria_kv("type", type, s); + auto &var = boost::get(v.proof); + binary_proof = seria::to_binary(var); + } else + throw api::ErrorAddress(api::ErrorAddress::ADDRESS_FAILED_TO_PARSE, "Unknown address type", addr); + proof = common::base58::encode(binary_proof); + } + seria_kv("proof", proof, s); + if (s.is_input()) { + if (!common::base58::decode(proof, &binary_proof)) + throw std::runtime_error("Wrong proof format - " + proof); + std::string type = SendproofKey::str_type_tag(); + seria_kv("type", type, s); + if (type == SendproofKey::str_type_tag()) { + SendproofKey sp; + seria::from_binary(sp, binary_proof); + v.proof = sp; + } else if (type == SendproofAmethyst::str_type_tag()) { + SendproofAmethyst sp; + seria::from_binary(sp, binary_proof); + v.proof = sp; + } else + throw api::ErrorAddress(api::ErrorAddress::ADDRESS_FAILED_TO_PARSE, "Unknown address type", addr); + } +}*/ +void ser_members(TransactionInput &v, ISeria &s) { if (s.is_input()) { uint8_t type = 0; s.object_key("type"); @@ -176,7 +193,7 @@ void ser_members(TransactionInput &v, ISeria &s, bool is_tx_amethyst) { } case InputKey::type_tag: { InputKey in{}; - ser_members(in, s, is_tx_amethyst); + ser_members(in, s); v = in; break; } @@ -204,7 +221,7 @@ void ser_members(TransactionInput &v, ISeria &s, bool is_tx_amethyst) { ser(str_type_tag, s); } else s.binary(&type, 1); - ser_members(in, s, is_tx_amethyst); + ser_members(in, s); } } void ser_members(TransactionOutput &v, ISeria &s, bool is_tx_amethyst) { @@ -219,20 +236,16 @@ void ser_members(TransactionOutput &v, ISeria &s, bool is_tx_amethyst) { ser(str_type_tag, s); if (str_type_tag == OutputKey::str_type_tag()) type = OutputKey::type_tag; - else if (str_type_tag == OutputKey::str_type_tag_auditable()) - type = OutputKey::type_tag_auditable; else throw std::runtime_error("Deserialization error - unknown output type " + str_type_tag); } else s.binary(&type, 1); switch (type) { - case OutputKey::type_tag: - case OutputKey::type_tag_auditable: { - OutputKey in{}; - in.amount = amount; - in.is_auditable = (type == OutputKey::type_tag_auditable); - ser_members(in, s, is_tx_amethyst); - v = in; + case OutputKey::type_tag: { + OutputKey out{}; + out.amount = amount; + ser_members(out, s, is_tx_amethyst); + v = out; break; } default: @@ -241,160 +254,193 @@ void ser_members(TransactionOutput &v, ISeria &s, bool is_tx_amethyst) { return; } if (v.type() == typeid(OutputKey)) { - auto &in = boost::get(v); + auto &out = boost::get(v); if (!is_tx_amethyst) - seria_kv("amount", in.amount, s); + seria_kv("amount", out.amount, s); s.object_key("type"); if (dynamic_cast(&s)) { - std::string str_type_tag = - in.is_auditable ? OutputKey::str_type_tag_auditable() : OutputKey::str_type_tag(); + std::string str_type_tag = OutputKey::str_type_tag(); ser(str_type_tag, s); } else { - uint8_t type = in.is_auditable ? OutputKey::type_tag_auditable : OutputKey::type_tag; + uint8_t type = OutputKey::type_tag; s.binary(&type, 1); } - ser_members(in, s, is_tx_amethyst); + ser_members(out, s, is_tx_amethyst); } } void ser_members(InputCoinbase &v, ISeria &s) { seria_kv("height", v.height, s); } -void ser_members(InputKey &v, ISeria &s, bool is_tx_amethyst) { +void ser_members(InputKey &v, ISeria &s) { seria_kv("amount", v.amount, s); seria_kv("output_indexes", v.output_indexes, s); seria_kv("key_image", v.key_image, s); - if (is_tx_amethyst && v.output_indexes.size() >= 2) - seria_kv("encrypted_real_index", v.encrypted_real_index, s); } void ser_members(cn::RingSignatures &v, ISeria &s) { seria_kv("signatures", v.signatures, s); } -void ser_members(cn::RingSignature3 &v, ISeria &s) { +void ser_members(cn::RingSignatureAmethyst &v, ISeria &s) { + seria_kv("p", v.p, s); + seria_kv("c0", v.c0, s); + seria_kv("ra", v.ra, s); + seria_kv("rb", v.rb, s); + seria_kv("rc", v.rc, s); +} +void ser_members(cn::SendproofSignatureAmethyst &v, ISeria &s) { seria_kv("c0", v.c0, s); - seria_kv("r", v.r, s); + seria_kv("rb", v.rb, s); + seria_kv("rc", v.rc, s); } -enum { ring_signature_blank_tag = 0xff, ring_signature_normal_tag = 1, ring_signature_halfsize_tag = 2 }; -const char ring_signature_blank_tag_str[] = "blank"; -const char ring_signature_normal_tag_str[] = "normal"; -const char ring_signature_halfsize_tag_str[] = "halfsize"; +/* +enum { ring_signature_blank_tag = 0xff, ring_signature_normal_tag = 1, ring_signature_auditable_tag = 4 }; +const char ring_signature_blank_tag_str[] = "blank"; +const char ring_signature_normal_tag_str[] = "normal"; +const char ring_signature_auditable_tag_str[] = "auditable"; // Usually signatures are parsed in the context of transaction where type is known // but sometimes we need to send just signatures, so we need to seria them independently void ser_members(cn::TransactionSignatures &v, ISeria &s) { + if (s.is_input()) { + uint8_t type = 0; + s.object_key("type"); + if (dynamic_cast(&s)) { + std::string str_type_tag; + ser(str_type_tag, s); + if (str_type_tag == ring_signature_blank_tag_str) + type = ring_signature_blank_tag; + else if (str_type_tag == ring_signature_normal_tag_str) + type = ring_signature_normal_tag; + else if (str_type_tag == ring_signature_auditable_tag_str) + type = ring_signature_auditable_tag; + else + throw std::runtime_error("Deserialization error - unknown ring signature type " + str_type_tag); + } else + s.binary(&type, 1); + switch (type) { + case ring_signature_blank_tag: { + v = boost::blank{}; + break; + } + case ring_signature_normal_tag: { + RingSignatures ring_sig; + ser_members(ring_sig, s); + v = ring_sig; + break; + } + case ring_signature_auditable_tag: { + RingSignatureAmethyst ring_sig; + ser_members(ring_sig, s); + v = ring_sig; + break; + } + default: + throw std::runtime_error("Deserialization error - unknown ring signature type " + common::to_string(type)); + } + return; + } + if (v.type() == typeid(boost::blank)) { + s.object_key("type"); + if (dynamic_cast(&s)) { + std::string str_type_tag = ring_signature_blank_tag_str; + ser(str_type_tag, s); + } else { + uint8_t type = ring_signature_blank_tag; + s.binary(&type, 1); + } + } else if (v.type() == typeid(RingSignatures)) { + auto &in = boost::get(v); + s.object_key("type"); + if (dynamic_cast(&s)) { + std::string str_type_tag = ring_signature_normal_tag_str; + ser(str_type_tag, s); + } else { + uint8_t type = ring_signature_normal_tag; + s.binary(&type, 1); + } + ser_members(in, s); + } else if (v.type() == typeid(RingSignatureAmethyst)) { + auto &in = boost::get(v); + s.object_key("type"); + if (dynamic_cast(&s)) { + std::string str_type_tag = ring_signature_auditable_tag_str; + ser(str_type_tag, s); + } else { + uint8_t type = ring_signature_auditable_tag; + s.binary(&type, 1); + } + ser_members(in, s); + } else + throw std::runtime_error("Serialization error - unknown ring signature type"); +}*/ + +// Serializing in the context of transaction - sizes and types are known from transaction prefix +void ser_members(cn::RingSignatureAmethyst &sigs, ISeria &s, const cn::TransactionPrefix &prefix) { + size_t sig_size = prefix.inputs.size(); if (s.is_input()) { - uint8_t type = 0; - s.object_key("type"); - if (dynamic_cast(&s)) { - std::string str_type_tag; - ser(str_type_tag, s); - if (str_type_tag == ring_signature_blank_tag_str) - type = ring_signature_blank_tag; - else if (str_type_tag == ring_signature_normal_tag_str) - type = ring_signature_normal_tag; - else if (str_type_tag == ring_signature_halfsize_tag_str) - type = ring_signature_halfsize_tag; - else - throw std::runtime_error("Deserialization error - unknown ring signature type " + str_type_tag); - } else - s.binary(&type, 1); - switch (type) { - case ring_signature_blank_tag: { - v = boost::blank{}; - break; - } - case ring_signature_normal_tag: { - RingSignatures ring_sig; - ser_members(ring_sig, s); - v = ring_sig; - break; - } - case ring_signature_halfsize_tag: { - RingSignature3 ring_sig; - ser_members(ring_sig, s); - v = ring_sig; - break; - } - default: - throw std::runtime_error("Deserialization error - unknown ring signature type " + common::to_string(type)); - } - return; + sigs.p.resize(sig_size); + sigs.ra.resize(sig_size); + sigs.rb.resize(sig_size); + sigs.rc.resize(sig_size); } - if (v.type() == typeid(boost::blank)) { - s.object_key("type"); - if (dynamic_cast(&s)) { - std::string str_type_tag = ring_signature_blank_tag_str; - ser(str_type_tag, s); - } else { - uint8_t type = ring_signature_blank_tag; - s.binary(&type, 1); - } - } else if (v.type() == typeid(RingSignatures)) { - auto &in = boost::get(v); - s.object_key("type"); - if (dynamic_cast(&s)) { - std::string str_type_tag = ring_signature_normal_tag_str; - ser(str_type_tag, s); - } else { - uint8_t type = ring_signature_normal_tag; - s.binary(&type, 1); - } - ser_members(in, s); - } else if (v.type() == typeid(RingSignature3)) { - auto &in = boost::get(v); - s.object_key("type"); - if (dynamic_cast(&s)) { - std::string str_type_tag = ring_signature_halfsize_tag_str; - ser(str_type_tag, s); + s.object_key("p"); + s.begin_array(sig_size, true); + for (auto &sig : sigs.p) { + ser(sig, s); + } + s.end_array(); + s.object_key("c0"); + ser(sigs.c0, s); + s.object_key("ra"); + s.begin_array(sig_size, true); + for (size_t i = 0; i < sig_size; ++i) { + invariant(prefix.inputs[i].type() == typeid(InputKey), + "Serialization error: input type wrong for transaction version"); + size_t signature_size = boost::get(prefix.inputs[i]).output_indexes.size(); + if (s.is_input()) { + sigs.ra[i].resize(signature_size); + s.begin_array(signature_size, true); + for (crypto::EllipticCurveScalar &sig : sigs.ra[i]) { + ser(sig, s); + } + s.end_array(); } else { - uint8_t type = ring_signature_halfsize_tag; - s.binary(&type, 1); + invariant(signature_size == sigs.ra[i].size(), "Serialization error: unexpected signatures size"); + s.begin_array(signature_size, true); + for (crypto::EllipticCurveScalar &sig : sigs.ra[i]) { + ser(sig, s); + } + s.end_array(); } - ser_members(in, s); - } else - throw std::runtime_error("Serialization error - unknown ring signature type"); + } + s.end_array(); + s.object_key("rb"); + s.begin_array(sig_size, true); + for (auto &sig : sigs.rb) { + ser(sig, s); + } + s.end_array(); + s.object_key("rc"); + s.begin_array(sig_size, true); + for (auto &sig : sigs.rc) { + ser(sig, s); + } + s.end_array(); + s.end_object(); } -// Serializing in the context of transaction - sizes and types are known from transaction prefix void ser_members(cn::TransactionSignatures &v, ISeria &s, const TransactionPrefix &prefix) { const bool is_base = (prefix.inputs.size() == 1) && (prefix.inputs[0].type() == typeid(InputCoinbase)); if (is_base) return; // No signatures in base transaction - size_t sig_size = prefix.inputs.size(); const bool is_tx_amethyst = (prefix.version >= parameters::TRANSACTION_VERSION_AMETHYST); s.object_key("signatures"); if (is_tx_amethyst) { s.begin_object(); if (s.is_input()) - v = RingSignature3{}; - auto &sigs = boost::get(v); - if (s.is_input()) - sigs.r.resize(sig_size); - s.object_key("c0"); - ser(sigs.c0, s); - s.object_key("r"); - s.begin_array(sig_size, true); - for (size_t i = 0; i < sig_size; ++i) { - invariant(prefix.inputs[i].type() == typeid(InputKey), - "Serialization error: input type wrong for transaction version"); - size_t signature_size = boost::get(prefix.inputs[i]).output_indexes.size(); - if (s.is_input()) { - sigs.r[i].resize(signature_size); - s.begin_array(signature_size, true); - for (crypto::EllipticCurveScalar &sig : sigs.r[i]) { - ser(sig, s); - } - s.end_array(); - } else { - invariant(signature_size == sigs.r[i].size(), "Serialization error: unexpected signatures size"); - s.begin_array(signature_size, true); - for (crypto::EllipticCurveScalar &sig : sigs.r[i]) { - ser(sig, s); - } - s.end_array(); - } - } - s.end_array(); - s.end_object(); + v = RingSignatureAmethyst{}; + auto &sigs = boost::get(v); + ser_members(sigs, s, prefix); } else { + size_t sig_size = prefix.inputs.size(); s.begin_array(sig_size, true); if (s.is_input()) v = RingSignatures{}; @@ -440,7 +486,7 @@ void ser_members(TransactionPrefix &v, ISeria &s) { seria_kv("version", v.version, s); const bool is_tx_amethyst = (v.version >= parameters::TRANSACTION_VERSION_AMETHYST); seria_kv("unlock_block_or_timestamp", v.unlock_block_or_timestamp, s); - seria_kv("inputs", v.inputs, s, is_tx_amethyst); + seria_kv("inputs", v.inputs, s); seria_kv("outputs", v.outputs, s, is_tx_amethyst); seria_kv("extra", v.extra, s); } diff --git a/src/CryptoNote.hpp b/src/CryptoNote.hpp index 5324bb41..4c4514de 100644 --- a/src/CryptoNote.hpp +++ b/src/CryptoNote.hpp @@ -14,7 +14,7 @@ // We define here, as CryptoNoteConfig.h is never included anywhere anymore #define bytecoin_ALLOW_DEBUG_COMMANDS 1 -#define bytecoin_ALLOW_CM 1 +#define bytecoin_ALLOW_CM 0 namespace cn { @@ -24,8 +24,9 @@ using crypto::KeyImage; using crypto::KeyPair; using crypto::PublicKey; using crypto::RingSignature; -using crypto::RingSignature3; +using crypto::RingSignatureAmethyst; using crypto::SecretKey; +using crypto::SendproofSignatureAmethyst; using crypto::Signature; using common::BinaryArray; @@ -50,7 +51,6 @@ struct InputKey { Amount amount = 0; std::vector output_indexes; KeyImage key_image; - std::array encrypted_real_index{}; // serialized only in amethyst if output_indexes.size >= 2 enum { type_tag = 2 }; static std::string str_type_tag() { return "key"; } }; @@ -61,11 +61,8 @@ struct OutputKey { PublicKey encrypted_secret; // serialized only in amethyst uint8_t encrypted_address_type = 0; // serialized only in amethyst - bool is_auditable = false; - enum { type_tag = 2, type_tag_auditable = 32 + 2 }; // we treat it similar to a flag - // type_tag_auditable is only allowed in amethyst + enum { type_tag = 2 }; static std::string str_type_tag() { return "key"; } - static std::string str_type_tag_auditable() { return "key_auditable"; } }; typedef boost::variant TransactionInput; @@ -87,7 +84,7 @@ struct RingSignatures { std::vector signatures; }; -typedef boost::variant TransactionSignatures; +typedef boost::variant TransactionSignatures; struct Transaction : public TransactionPrefix { TransactionSignatures signatures; @@ -116,9 +113,8 @@ struct BlockHeader { RootBlock root_block; // For block with is_merge_mined() true std::vector cm_merkle_branch; // For blocks with is_cm_mined() true - - bool is_merge_mined() const { return major_version == 2 || major_version == 3 || major_version == 4; } bool is_cm_mined() const { return major_version == 5; } + bool is_merge_mined() const { return major_version == 2 || major_version == 3 || major_version == 4; } }; struct BlockBodyProxy { @@ -136,49 +132,48 @@ struct BlockTemplate : public BlockHeader { enum BlockSeriaType { NORMAL, PREHASH, BLOCKHASH, LONG_BLOCKHASH }; struct AccountAddressSimple { - PublicKey spend_public_key; - PublicKey view_public_key; + PublicKey S; + PublicKey V; enum { type_tag = 0 }; static std::string str_type_tag() { return "simple"; } }; struct AccountAddressUnlinkable { - PublicKey s; - PublicKey sv; - bool is_auditable = false; - enum { type_tag = 1, type_tag_auditable = 2 }; + PublicKey S; + PublicKey Sv; + enum { type_tag = 1 }; static std::string str_type_tag() { return "unlinkable"; } - static std::string str_type_tag_auditable() { return "unlinkable_auditable"; } }; typedef boost::variant AccountAddress; struct SendproofKey { + Hash transaction_hash; + std::string message; + std::string address; KeyDerivation derivation; Signature signature; // pair of derivation and signature form a proof of only fact that creator knows transaction private key and // he or she wished to include public view key of address into proof. To further check, look up tx_hash in // main chain and sum amounts of outputs which have spend keys corresponding to address public spend key // For unlinkable addresses - static std::string str_type_tag() { return ""; } // legacy sendproofs lack explicit type }; + struct SendproofAmethyst { + uint8_t version = 0; + Hash transaction_hash; + std::string message; + // fields for version 1-3 + AccountAddressSimple address_simple; + KeyDerivation derivation; + Signature signature; + // fields for version amethyst struct Element { size_t out_index = 0; PublicKey deterministic_public_key; - Signature signature; }; std::vector elements; - // signature per output, proving that creator knows deterministic output private key - static std::string str_type_tag() { return "amethyst"; } -}; - -struct Sendproof { // proofing that some tx actually sent amount to particular address - Hash transaction_hash; - AccountAddress address; - Amount amount = 0; - std::string message; - boost::variant proof; + // followed by RingSignatureAmethyst signature in serialization. }; struct RawBlock { @@ -215,21 +210,21 @@ struct SignedCheckpoint : public Checkpoint { // Predicates for using in maps, sets, etc inline bool operator==(const AccountAddressSimple &a, const AccountAddressSimple &b) { - return std::tie(a.view_public_key, a.spend_public_key) == std::tie(b.view_public_key, b.spend_public_key); + return std::tie(a.V, a.S) == std::tie(b.V, b.S); } inline bool operator!=(const AccountAddressSimple &a, const AccountAddressSimple &b) { return !operator==(a, b); } inline bool operator<(const AccountAddressSimple &a, const AccountAddressSimple &b) { - return std::tie(a.view_public_key, a.spend_public_key) < std::tie(b.view_public_key, b.spend_public_key); + return std::tie(a.V, a.S) < std::tie(b.V, b.S); } inline bool operator==(const AccountAddressUnlinkable &a, const AccountAddressUnlinkable &b) { - return std::tie(a.s, a.sv) == std::tie(b.s, b.sv); + return std::tie(a.S, a.Sv) == std::tie(b.S, b.Sv); } inline bool operator!=(const AccountAddressUnlinkable &a, const AccountAddressUnlinkable &b) { return !operator==(a, b); } inline bool operator<(const AccountAddressUnlinkable &a, const AccountAddressUnlinkable &b) { - return std::tie(a.s, a.sv) < std::tie(b.s, b.sv); + return std::tie(a.S, a.Sv) < std::tie(b.S, b.Sv); } class Currency; // For ser_members of cn::Sendproof @@ -253,15 +248,17 @@ void ser_members(cn::AccountAddress &v, ISeria &s); void ser_members(cn::SendproofKey &v, ISeria &s); void ser_members(cn::SendproofAmethyst::Element &v, ISeria &s); void ser_members(cn::SendproofAmethyst &v, ISeria &s); -void ser_members(cn::Sendproof &v, ISeria &s, const cn::Currency &); -void ser_members(cn::TransactionInput &v, ISeria &s, bool is_tx_amethyst); +void ser_members(cn::TransactionInput &v, ISeria &s); void ser_members(cn::TransactionOutput &v, ISeria &s, bool is_tx_amethyst); void ser_members(cn::InputCoinbase &v, ISeria &s); -void ser_members(cn::InputKey &v, ISeria &s, bool is_tx_amethyst); +void ser_members(cn::InputKey &v, ISeria &s); void ser_members(cn::RingSignatures &v, ISeria &s); -void ser_members(cn::RingSignature3 &v, ISeria &s); -void ser_members(cn::TransactionSignatures &v, ISeria &s); +void ser_members(cn::RingSignatureAmethyst &v, ISeria &s); +void ser_members(cn::SendproofSignatureAmethyst &v, ISeria &s); + +// void ser_members(cn::TransactionSignatures &v, ISeria &s); +void ser_members(cn::RingSignatureAmethyst &v, ISeria &s, const cn::TransactionPrefix &prefix); void ser_members(cn::TransactionSignatures &v, ISeria &s, const cn::TransactionPrefix &prefix); void ser_members(cn::OutputKey &v, ISeria &s, bool is_tx_amethyst); diff --git a/src/CryptoNoteConfig.hpp b/src/CryptoNoteConfig.hpp index 8f930bfa..85c258ad 100644 --- a/src/CryptoNoteConfig.hpp +++ b/src/CryptoNoteConfig.hpp @@ -46,9 +46,10 @@ const Amount MIN_DUST_THRESHOLD = 1000000; // Everything smaller w const Amount MAX_DUST_THRESHOLD = 30000000000000000; // Everything larger is dust because very few coins const Amount SELF_DUST_THRESHOLD = 1000; // forfeit outputs smaller than this in a change -const BinaryArray ADDRESS_BASE58_PREFIX{6}; // legacy addresses start with "2" -const BinaryArray ADDRESS_BASE58_PREFIX_UNLINKABLE{0xce, 0xf5, 0xe2}; // addresses start with "bcn1" -const BinaryArray ADDRESS_BASE58_PREFIX_AUDITABLE_UNLINKABLE{0xce, 0xf5, 0xe4}; // addresses start with "bcn2" +const uint64_t ADDRESS_BASE58_PREFIX = 6; // legacy addresses start with "2" +const uint64_t ADDRESS_BASE58_PREFIX_AMETHYST = 572238; // addresses start with "bcnZ", varintdata={0xce, 0xf6, 0x22} +const uint64_t SENDPROOF_BASE58_PREFIX = + 86762904402638; // proofs start with "bcn1PRoof", varintdata={0xce, 0xf5, 0xe2, 0x80, 0x91, 0xdd, 0x13} const char BLOCKS_FILENAME[] = "blocks.bin"; const char BLOCKINDEXES_FILENAME[] = "blockindexes.bin"; @@ -212,9 +213,11 @@ constexpr const HardCheckpoint CHECKPOINTS[] = { {1579000, common::pfh("debfa79d14ff49dc7e8c24e5e27a22f9a67819124a7dcd187c67493a969044be")}, {1605000, common::pfh("a34a41f2b5091f28f234b55a6255a9727fed355ca41233d59f779b2f87d1a359")}, {1628000, common::pfh("4e7b55e53402c71c45cb97f8ed78ed3f128c802008c83b0153aa52c30b740c68")}, - {1670000, common::pfh("58770b800108c72512a386783fd0a4326c74dc9f99b538337a195945b89a9a6f")}}; + {1670000, common::pfh("58770b800108c72512a386783fd0a4326c74dc9f99b538337a195945b89a9a6f")}, + {1709000, common::pfh("82185d3365e730074c4804b151c19d29ee4b2407772467853f96839567d8b45a")}}; constexpr const HardCheckpoint CHECKPOINTS_STAGENET[] = { {450, common::pfh("c69823a6b3e0c1f724411e697219a9d31a2df900cb49bb0488b1a91a9989a805")}, - {30000, common::pfh("4a3b02206d120bab6c3bef4a7bcbc1934b5327c27c181d790f4db407dc92c640")}}; + {30000, common::pfh("4a3b02206d120bab6c3bef4a7bcbc1934b5327c27c181d790f4db407dc92c640")}, + {49000, common::pfh("1960a677cda6afd47dd4a928bf876b7cb7c9bd86107e3193ca9b0fd0926bad4c")}}; }} // namespace cn::parameters diff --git a/src/common/BIPs.cpp b/src/common/BIPs.cpp index 50955389..1d9cf27f 100644 --- a/src/common/BIPs.cpp +++ b/src/common/BIPs.cpp @@ -11,13 +11,13 @@ #include #include #include -#include #include #include #include "common/Invariant.hpp" #include "common/StringTools.hpp" #include "common/Varint.hpp" #include "common/Words.hpp" +#include "crypto/crypto.hpp" struct EC_GROUPw { EC_GROUP *pgroup = nullptr; @@ -186,6 +186,8 @@ std::string Bip32Key::create_random_bip39_mnemonic(size_t bits) { } std::string Bip32Key::check_bip39_mnemonic(const std::string &bip39_mnemonic) { + if (bip39_mnemonic.empty()) + throw Exception("Mnemonic is empty"); std::string str = bip39_mnemonic; // Not the fastest way to split into words by set of strings std::vector word_bits; diff --git a/src/common/Base58.cpp b/src/common/Base58.cpp index 1410f823..524f3a10 100644 --- a/src/common/Base58.cpp +++ b/src/common/Base58.cpp @@ -6,9 +6,9 @@ #include #include #include +#include #include -#include "CRC32.hpp" #include "Invariant.hpp" #include "StringTools.hpp" #include "Varint.hpp" @@ -217,15 +217,15 @@ bool decode(const std::string &enc, BinaryArray *data) { return true; } -std::string encode_addr(const BinaryArray &tag, const BinaryArray &data) { - BinaryArray buf = tag; +std::string encode_addr(uint64_t tag, const BinaryArray &data) { + BinaryArray buf = get_varint_data(tag); append(buf, data.begin(), data.end()); crypto::Hash hash = crypto::cn_fast_hash(buf.data(), buf.size()); append(buf, hash.data, hash.data + addr_checksum_size); return encode(buf); } -bool decode_addr(std::string addr, size_t body_size, BinaryArray *tag, BinaryArray *data) { +bool decode_addr(std::string addr, uint64_t *tag, BinaryArray *data) { BinaryArray addr_data; bool r = decode(addr, &addr_data); if (!r) @@ -245,35 +245,13 @@ bool decode_addr(std::string addr, size_t body_size, BinaryArray *tag, BinaryArr if (expected_checksum != checksum) return false; - if (addr_data.size() < body_size) + int read = common::read_varint(addr_data.begin(), addr_data.end(), tag); + if (read <= 0) return false; - tag->assign(addr_data.begin(), addr_data.end() - body_size); - data->assign(addr_data.end() - body_size, addr_data.end()); + data->assign(addr_data.begin() + read, addr_data.end()); + // tag->assign(addr_data.begin(), addr_data.end() - body_size); + // data->assign(addr_data.end() - body_size, addr_data.end()); return true; } -BinaryArray find_tag(const std::string &prefix) { - invariant(prefix.size() <= full_encoded_block_size, ""); - std::string str1 = prefix + std::string(full_encoded_block_size - prefix.size(), alphabet[0]); - std::string str2 = prefix + std::string(full_encoded_block_size - prefix.size(), alphabet[alphabet_size - 1]); - uint8_t result1[full_block_size]{}; - uint8_t result2[full_block_size]{}; - invariant(decode_block(str1.data(), str1.size(), result1), ""); - invariant(decode_block(str2.data(), str2.size(), result2), ""); - std::cout << "decode of " << str1 << " = " << common::to_hex(result1, full_block_size) << std::endl; - std::cout << "decode of " << str2 << " = " << common::to_hex(result2, full_block_size) << std::endl; - size_t pos = 0; - for (; pos != full_block_size; ++pos) - if (result1[pos] != result2[pos]) { - result1[pos] += 1; - pos += 1; - break; - } - BinaryArray tag(result1, result1 + pos); - std::cout << "tag= " << common::to_hex(tag) << std::endl; - std::cout << "address min= " << encode_addr(tag, BinaryArray(64, 0)) << std::endl; - std::cout << "address max= " << encode_addr(tag, BinaryArray(64, 0xff)) << std::endl; - return tag; -} - }} // namespace common::base58 diff --git a/src/common/Base58.hpp b/src/common/Base58.hpp index dc107c33..fc76d053 100644 --- a/src/common/Base58.hpp +++ b/src/common/Base58.hpp @@ -12,8 +12,7 @@ namespace common { namespace base58 { std::string encode(const BinaryArray &data); bool decode(const std::string &enc, BinaryArray *data); -std::string encode_addr(const BinaryArray &tag, const BinaryArray &data); -bool decode_addr(std::string addr, size_t body_size, BinaryArray *tag, BinaryArray *data); +std::string encode_addr(uint64_t tag, const BinaryArray &data); +bool decode_addr(std::string addr, uint64_t *tag, BinaryArray *data); -BinaryArray find_tag(const std::string &prefix); }} // namespace common::base58 diff --git a/src/common/BinaryArray.hpp b/src/common/BinaryArray.hpp index 87866a84..ce18c9ca 100644 --- a/src/common/BinaryArray.hpp +++ b/src/common/BinaryArray.hpp @@ -132,5 +132,15 @@ inline BinaryArray::iterator append(BinaryArray &ba, const BinaryArray &other) { return ba.insert(ba.end(), other.begin(), other.end()); } +inline BinaryArray &operator|=(BinaryArray &a, const BinaryArray &b) { + common::append(a, b); + return a; +} + +inline BinaryArray operator|(const BinaryArray &a, const BinaryArray &b) { + BinaryArray tmp(a); + return tmp |= b; +} + const unsigned char *slow_memmem(const unsigned char *buf, size_t buflen, const unsigned char *pat, size_t patlen); } // namespace common diff --git a/src/common/CRC32.cpp b/src/common/CRC32.cpp deleted file mode 100644 index bdb2ff9a..00000000 --- a/src/common/CRC32.cpp +++ /dev/null @@ -1,65 +0,0 @@ -// This file is generated by CRC32_generate.py. -#include - -#include "CRC32.hpp" - -namespace common { - -const uint32_t crc32_table[] = {3523407757, 2768625435, 1007455905, 1259060791, 3580832660, 2724731650, 996231864, - 1281784366, 3705235391, 2883475241, 852952723, 1171273221, 3686048678, 2897449776, 901431946, 1119744540, - 3484811241, 3098726271, 565944005, 1455205971, 3369614320, 3219065702, 651582172, 1372678730, 3245242331, - 3060352845, 794826487, 1483155041, 3322131394, 2969862996, 671994606, 1594548856, 3916222277, 2657877971, 123907689, - 1885708031, 3993045852, 2567322570, 1010288, 1997036262, 3887548279, 2427484129, 163128923, 2126386893, 3772416878, - 2547889144, 248832578, 2043925204, 4108050209, 2212294583, 450215437, 1842515611, 4088798008, 2226203566, 498629140, - 1790921346, 4194326291, 2366072709, 336475711, 1661535913, 4251816714, 2322244508, 325317158, 1684325040, - 2766056989, 3554254475, 1255198513, 1037565863, 2746444292, 3568589458, 1304234792, 985283518, 2852464175, - 3707901625, 1141589763, 856455061, 2909332022, 3664761504, 1130791706, 878818188, 3110715001, 3463352047, - 1466425173, 543223747, 3187964512, 3372436214, 1342839628, 655174618, 3081909835, 3233089245, 1505515367, 784033777, - 2967466578, 3352871620, 1590793086, 701932520, 2679148245, 3904355907, 1908338681, 112844655, 2564639436, - 4024072794, 1993550816, 30677878, 2439710439, 3865851505, 2137352139, 140662621, 2517025534, 3775001192, 2013832146, - 252678980, 2181537457, 4110462503, 1812594589, 453955339, 2238339752, 4067256894, 1801730948, 476252946, 2363233923, - 4225443349, 1657960367, 366298937, 2343686810, 4239843852, 1707062198, 314082080, 1069182125, 1220369467, - 3518238081, 2796764439, 953657524, 1339070498, 3604597144, 2715744526, 828499103, 1181144073, 3748627891, - 2825434405, 906764422, 1091244048, 3624026538, 2936369468, 571309257, 1426738271, 3422756325, 3137613171, 627095760, - 1382516806, 3413039612, 3161057642, 752284923, 1540473965, 3268974039, 3051332929, 733688034, 1555824756, - 3316994510, 2998034776, 81022053, 1943239923, 3940166985, 2648514015, 62490748, 1958656234, 3988253008, 2595281350, - 168805463, 2097738945, 3825313147, 2466682349, 224526414, 2053451992, 3815530850, 2490061300, 425942017, 1852075159, - 4151131437, 2154433979, 504272920, 1762240654, 4026595636, 2265434530, 397988915, 1623188645, 4189500703, - 2393998729, 282398762, 1741824188, 4275794182, 2312913296, 1231433021, 1046551979, 2808630289, 3496967303, - 1309403428, 957143474, 2684717064, 3607279774, 1203610895, 817534361, 2847130659, 3736401077, 1087398166, 936857984, - 2933784634, 3654889644, 1422998873, 601230799, 3135200373, 3453512931, 1404893504, 616286678, 3182598252, - 3400902906, 1510651243, 755860989, 3020215367, 3271812305, 1567060338, 710951396, 3010007134, 3295551688, - 1913130485, 84884835, 2617666777, 3942734927, 1969605100, 40040826, 2607524032, 3966539862, 2094237127, 198489425, - 2464015595, 3856323709, 2076066270, 213479752, 2511347954, 3803648100, 1874795921, 414723335, 2175892669, - 4139142187, 1758648712, 534112542, 2262612132, 4057696306, 1633981859, 375629109, 2406151311, 4167943193, - 1711886778, 286155052, 2282172566, 4278190080}; - -const uint32_t crc32_reverse_table[] = {258633766, 3558569575, 1660517093, 3112729764, 3561776544, 255681505, - 3118428003, 1655597346, 1649892715, 3106298666, 264525736, 3568652777, 3111209197, 1644187308, 3571597870, - 261309551, 3574606524, 241249533, 3095601279, 1676813886, 238436154, 3578214779, 1671771641, 3100914616, 3089958897, - 1666974128, 3586002226, 248449907, 1661670007, 3095008310, 244848820, 3588824821, 1626325843, 3147744530, 226281872, - 3591229393, 3150567125, 1622724756, 3596278806, 220977751, 231927326, 3601066079, 1614931165, 3140543132, - 3606379416, 226885081, 3144151387, 1612117786, 3130366409, 1642373000, 3607532298, 209163595, 1639156815, - 3133311502, 203458188, 3612442829, 3618157700, 215593669, 3124477511, 1632286726, 210673922, 3623855939, 1629334465, - 3127684480, 3496023756, 185602189, 3186760719, 1719867982, 189203274, 3493201163, 1725172105, 3181711304, - 3176938369, 1714240960, 3503239490, 197015299, 1719283207, 3171625030, 199828676, 3499631237, 169561174, 3513403927, - 1736984213, 3170451668, 3510458832, 172777361, 3165541139, 1742689618, 1730568475, 3159844698, 179661784, - 3519311257, 3154146461, 1735488220, 3516104286, 182614047, 3219432889, 1687528440, 3531059066, 151431483, - 1690480703, 3216225918, 156351228, 3525360829, 3537471732, 162041525, 3209331255, 1681622134, 167746930, 3532561203, - 1684838321, 3206386160, 1704918819, 3203397986, 135132640, 3548181409, 3199789733, 1707732196, 3542868070, - 140174887, 144953966, 3553809455, 1697700013, 3191987948, 3548760040, 150258089, 3189165355, 1701301098, 1779284915, - 2977742322, 127840624, 3706697521, 2972429877, 1784326260, 3703088374, 130654903, 120624894, 3695284415, 1789107261, - 2983369340, 3692462968, 124224825, 2978318779, 1794412538, 2960646441, 1795614568, 3722767338, 110489003, - 1800533167, 2954949358, 113442412, 3719559213, 3712666724, 104581669, 2967062183, 1806221542, 107796962, 3709722531, - 1811927841, 2962150752, 92801222, 3740864135, 1746600453, 3010069572, 3735952704, 98507521, 3007125379, 1749815746, - 1756702091, 3015975882, 86388552, 3730254089, 3012767757, 1759655500, 3724557006, 91307151, 3757199964, 75715613, - 2992724127, 1762680542, 81020890, 3752149403, 1766280473, 2989902680, 2999942929, 1774090576, 3747378642, 70087571, - 1776904855, 2996333782, 75128916, 3742066197, 3037125977, 1853348632, 3633698714, 65327579, 1847642335, 3042037406, - 62112284, 3636642909, 3627806740, 55244373, 3047750359, 1859779734, 52290962, 3631014867, 1854861137, 3053447440, - 1870432195, 3020784002, 49253632, 3651046209, 3025834565, 1865126916, 3653867654, 45653703, 37857934, 3643845839, - 1876074573, 3030623756, 3647454984, 35043657, 3035936203, 1871033226, 3666046508, 32663661, 3071304943, 1818321582, - 27622314, 3671358955, 1815507305, 3074914088, 3082699617, 1825522976, 3660401058, 22826979, 1821923047, 3085521062, - 17521700, 3665451621, 16331958, 3683136247, 1835679349, 3055237172, 3688833328, 11413361, 3058445299, 1832725938, - 1841568251, 3065323450, 5706552, 3676706169, 3068267645, 1838352956, 3681617598, 255}; - -} // namespace common \ No newline at end of file diff --git a/src/common/CRC32.hpp b/src/common/CRC32.hpp deleted file mode 100644 index 9e5252b3..00000000 --- a/src/common/CRC32.hpp +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) 2012-2018, The CryptoNote developers, The Bytecoin developers. -// Licensed under the GNU Lesser General Public License. See LICENSE for details. - -#pragma once - -#include -#include - -namespace common { - -extern const uint32_t crc32_table[]; - -inline uint32_t crc32_step_zero(uint32_t state) { return (state >> 8) ^ crc32_table[state & 0xff]; } - -inline uint32_t crc32_step(uint32_t state, char data) { return crc32_step_zero(state ^ data); } - -inline uint32_t crc32(const char *data, size_t size, uint32_t state = 0) { - for (const char *cur = data; cur != data + size; cur++) { - state = crc32_step(state, *cur); - } - return state; -} - -extern const uint32_t crc32_reverse_table[]; - -inline uint32_t crc32_reverse_step_zero(uint32_t state) { return (state << 8) ^ crc32_reverse_table[state >> 24]; } - -} // namespace common \ No newline at end of file diff --git a/src/common/CRC32_generate.py b/src/common/CRC32_generate.py deleted file mode 100644 index 193ebcfa..00000000 --- a/src/common/CRC32_generate.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2012-2018, The CryptoNote developers, The Bytecoin developers. -# Licensed under the GNU Lesser General Public License. See LICENSE for details. - -from sys import argv -from zlib import crc32 - -output = "CRC32.cpp" - -# When changing the script, run and commit artefacts - -crc32_reverse = [0] -for i in range(1, 256): - v = (crc32_reverse[i >> 1] << 1) ^ ((i & 1) << 32) - crc32_reverse.append(v if v & (1 << 32) == 0 else v ^ 0x1db710641) - -with open(output, 'w', encoding='ASCII', newline='') as fd: - fd.write( -f'''// This file is generated by CRC32_generate.py. -#include - -#include "CRC32.hpp" - -namespace common {{ - -const uint32_t crc32_table[] = {{{', '.join(f'{crc32(bytes((i,)))}' for i in range(256))}}}; - -const uint32_t crc32_reverse_table[] = {{{', '.join(f'{i ^ 0xff}' for i in reversed(crc32_reverse))}}}; - -}}''') \ No newline at end of file diff --git a/src/common/CommandLine.cpp b/src/common/CommandLine.cpp index 30926252..942ba9ec 100644 --- a/src/common/CommandLine.cpp +++ b/src/common/CommandLine.cpp @@ -120,33 +120,33 @@ const std::vector &CommandLine::get_positional(const char *depreca return positional; } -bool CommandLine::should_quit(const char *help_text, const char *version_text) { +int CommandLine::should_quit(const char *help_text, const char *version_text) { const bool v1 = get_bool("--version"); const bool v2 = get_bool("-v"); if (version_text && (v1 || v2)) { // in case both specified printf("%s\n", version_text); - return true; // No more output so scripts get version only + return 1; // No more output so scripts get version only } - bool quit = false; + int quit = 0; const bool h1 = get_bool("--help"); const bool h2 = get_bool("-h"); if (help_text && (h1 || h2)) { // in case both specified printf("%s\n", help_text); - quit = true; + quit = 1; } if (!positional_used) for (auto &&po : positional) { printf("Positional args are not allowed - you specified '%s' (typo?)\n", po); - quit = true; + quit = 2; } for (auto &&op : options) { if (!op.used) { printf("Command line option '%s' has no meaning (typo?)\n", op.key.data); - quit = true; + quit = 2; } if (op.wrong_type_message) { printf("Command line option '%s' %s\n", op.key.data, op.wrong_type_message); - quit = true; + quit = 2; } } return quit; @@ -182,8 +182,8 @@ int CommandLine::toy_main(int argc, const char *argv[]) { if (cmd.get_bool("--pos")) for (auto &&el : cmd.get_positional()) printf("positional value={%s}\n", el); - if (cmd.should_quit(TOY_USAGE, "Toy cmd v 1.1")) - return 0; + if (int r = cmd.should_quit(TOY_USAGE, "Toy cmd v 1.1")) + return r != 1; printf("Toy is launching...\n"); return 0; } diff --git a/src/common/CommandLine.hpp b/src/common/CommandLine.hpp index a8e1fc6a..18e810c4 100644 --- a/src/common/CommandLine.hpp +++ b/src/common/CommandLine.hpp @@ -40,7 +40,8 @@ class CommandLine { // Lean command line parsing. Best! :) const std::vector &get_array(const char *key, const char *deprecation_text = nullptr); const std::vector &get_positional(const char *deprecation_text = nullptr); // after everything is parsed call this fun to quit if there were errors or help, version was specified - bool should_quit(const char *help_text = nullptr, const char *version_text = nullptr); + // returns 1 when no errors, 2 otherwise + int should_quit(const char *help_text = nullptr, const char *version_text = nullptr); static int toy_main(int argc, const char *argv[]); // For testing }; diff --git a/src/common/Invariant.cpp b/src/common/Invariant.cpp index dc293e7c..ca20bc9c 100644 --- a/src/common/Invariant.cpp +++ b/src/common/Invariant.cpp @@ -2,6 +2,7 @@ // Licensed under the GNU Lesser General Public License. See LICENSE for details. #include "Invariant.hpp" +#include #include #include "string.hpp" diff --git a/src/crypto/bernstein/crypto-ops-data.c b/src/crypto/bernstein/crypto-ops-data.c index 02f162b8..852aec03 100644 --- a/src/crypto/bernstein/crypto-ops-data.c +++ b/src/crypto/bernstein/crypto-ops-data.c @@ -843,3 +843,9 @@ const fe fe_fffb1 = {-31702527, -2466483, -26106795, -12203692, -12169197, -3210 const fe fe_fffb2 = {8166131, -6741800, -17040804, 3154616, 21461005, 1466302, -30876704, -6368709, 10503587, -13363080}; /* sqrt(2 * A * (A + 2)) */ const fe fe_fffb3 = {-13620103, 14639558, 4532995, 7679154, 16815101, -15883539, -22863840, -14813421, 13716513, -6477756}; /* sqrt(-sqrt(-1) * A * (A + 2)) */ const fe fe_fffb4 = {-21786234, -12173074, 21573800, 4524538, -4645904, 16204591, 8012863, -8444712, 3212926, 6885324}; /* sqrt(sqrt(-1) * A * (A + 2)) */ + +/* where l = 2^252 + 27742317777372353535851937790883648493. + * last num is 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed + * 1 <124 zeros> 0x14DEF9DE 0xA2F79CD6 0x5812631A 0x5CF5D3ED + */ +const struct cryptoEllipticCurveScalar crypto_L = {{0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10}}; diff --git a/src/crypto/bernstein/crypto-ops-data.h b/src/crypto/bernstein/crypto-ops-data.h index 5199c3a8..aaed6d96 100644 --- a/src/crypto/bernstein/crypto-ops-data.h +++ b/src/crypto/bernstein/crypto-ops-data.h @@ -34,6 +34,8 @@ extern const fe fe_fffb2; extern const fe fe_fffb3; extern const fe fe_fffb4; +extern const struct cryptoEllipticCurveScalar crypto_L; + #if defined(__cplusplus) } #endif diff --git a/src/crypto/bernstein/crypto-ops.c b/src/crypto/bernstein/crypto-ops.c index 6deb0fb9..b6aa023f 100644 --- a/src/crypto/bernstein/crypto-ops.c +++ b/src/crypto/bernstein/crypto-ops.c @@ -63,7 +63,7 @@ static void fe_0(fe h) { h = 1 */ -static void fe_1(fe h) { +void fe_1(fe h) { h[0] = 1; h[1] = 0; h[2] = 0; @@ -203,7 +203,7 @@ static void fe_cmov(fe f, const fe g, unsigned int b) { h = f */ -static void fe_copy(fe h, const fe f) { +void fe_copy(fe h, const fe f) { int32_t f0 = f[0]; int32_t f1 = f[1]; int32_t f2 = f[2]; @@ -1144,7 +1144,8 @@ static void slide(signed char *r, const unsigned char *a) { } } -void ge_dsm_precomp(ge_dsmp r, const ge_p3 *s) { +void ge_dsm_precomp(ge_dsmp * rr, const ge_p3 *s) { + ge_cached * r = rr->ca; ge_p1p1 t; ge_p3 s2, u; ge_p3_to_cached(&r[0], s); @@ -1165,19 +1166,28 @@ and b = b[0]+256*b[1]+...+256^31 b[31]. B is the Ed25519 base point (x,4/5) with x positive. */ -void ge_double_scalarmult_base_vartime(ge_p2 *r, const struct cryptoEllipticCurveScalar *aa, const ge_p3 *A, const struct cryptoEllipticCurveScalar *bb) { +/* From ge_p3_0.c */ + +static void ge_p3_0(ge_p3 *h) { + fe_0(h->X); + fe_1(h->Y); + fe_1(h->Z); + fe_0(h->T); +} + +/*void ge_double_scalarmult_base_vartime(ge_p2 *r, const struct cryptoEllipticCurveScalar *aa, const ge_p3 *A, const struct cryptoEllipticCurveScalar *bb) { const unsigned char * a = aa->data; const unsigned char * b = bb->data; signed char aslide[256]; signed char bslide[256]; - ge_dsmp Ai; /* A, 3A, 5A, 7A, 9A, 11A, 13A, 15A */ + ge_dsmp Ai; // A, 3A, 5A, 7A, 9A, 11A, 13A, 15A ge_p1p1 t; ge_p3 u; int i; slide(aslide, a); slide(bslide, b); - ge_dsm_precomp(Ai, A); + ge_dsm_precomp(&Ai, A); ge_p2_0(r); @@ -1190,10 +1200,10 @@ void ge_double_scalarmult_base_vartime(ge_p2 *r, const struct cryptoEllipticCurv if (aslide[i] > 0) { ge_p1p1_to_p3(&u, &t); - ge_add(&t, &u, &Ai[aslide[i]/2]); + ge_add(&t, &u, &Ai.ca[aslide[i]/2]); } else if (aslide[i] < 0) { ge_p1p1_to_p3(&u, &t); - ge_sub(&t, &u, &Ai[(-aslide[i])/2]); + ge_sub(&t, &u, &Ai.ca[(-aslide[i])/2]); } if (bslide[i] > 0) { @@ -1206,8 +1216,55 @@ void ge_double_scalarmult_base_vartime(ge_p2 *r, const struct cryptoEllipticCurv ge_p1p1_to_p2(r, &t); } -} +}*/ +void ge_double_scalarmult_base_vartime3(ge_p3 *rr, const struct cryptoEllipticCurveScalar *aa, const ge_p3 *A, const struct cryptoEllipticCurveScalar *bb) { + const unsigned char * a = aa->data; + const unsigned char * b = bb->data; + signed char aslide[256]; + signed char bslide[256]; + ge_dsmp Ai; /* A, 3A, 5A, 7A, 9A, 11A, 13A, 15A */ + ge_p1p1 t; + ge_p3 u; + int i; + ge_p2 r; + + slide(aslide, a); + slide(bslide, b); + ge_dsm_precomp(&Ai, A); + + ge_p2_0(&r); + ge_p3_0(rr); // We will not enter "for" below for some inputs + + for (i = 255; i >= 0; --i) { + if (aslide[i] || bslide[i]) break; + } + + for (; i >= 0; --i) { + ge_p2_dbl(&t, &r); + + if (aslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_add(&t, &u, &Ai.ca[aslide[i]/2]); + } else if (aslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_sub(&t, &u, &Ai.ca[(-aslide[i])/2]); + } + + if (bslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_madd(&t, &u, &ge_Bi[bslide[i]/2]); + } else if (bslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_msub(&t, &u, &ge_Bi[(-bslide[i])/2]); + } + + if(i == 0) + ge_p1p1_to_p3(rr, &t); + else + ge_p1p1_to_p2(&r, &t); + } +} /* From ge_frombytes.c, modified */ int ge_frombytes_vartime(ge_p3 *h, const struct cryptoEllipticCurvePoint *ss) { @@ -1395,15 +1452,6 @@ void ge_p2_dbl(ge_p1p1 *r, const ge_p2 *p) { fe_sub(r->T, r->T, r->Z); } -/* From ge_p3_0.c */ - -static void ge_p3_0(ge_p3 *h) { - fe_0(h->X); - fe_1(h->Y); - fe_1(h->Z); - fe_0(h->T); -} - /* From ge_p3_dbl.c */ /* @@ -1441,6 +1489,13 @@ void ge_p3_to_p2(ge_p2 *r, const ge_p3 *p) { fe_copy(r->Z, p->Z); } +// naive impl fe_1(r->T); leads to overflows later in ge_add +//void ge_p2_to_p3(ge_p3 * r, const ge_p2 *p) { +// fe_copy(r->X, p->X); +// fe_copy(r->Y, p->Y); +// fe_copy(r->Z, p->Z); +// r->T = X*Y*inv(Z) <-- this is too long to calc, so we do not use this fun +//} /* From ge_p3_tobytes.c */ void ge_p3_tobytes(struct cryptoEllipticCurvePoint * ss, const ge_p3 *h) { @@ -1607,7 +1662,7 @@ void ge_tobytes(struct cryptoEllipticCurvePoint *ss, const ge_p2 *h) { where l = 2^252 + 27742317777372353535851937790883648493. Overwrites s in place. */ -void sc_reduce(struct cryptoEllipticCurveScalar * aa, const unsigned char s[64]) { +void sc_reduce64(struct cryptoEllipticCurveScalar * aa, const unsigned char s[64]) { int64_t s0 = 2097151 & load_3(s); int64_t s1 = 2097151 & (load_4(s + 2) >> 5); int64_t s2 = 2097151 & (load_3(s + 5) >> 2); @@ -1917,65 +1972,65 @@ static void ge_cached_cmov(ge_cached *t, const ge_cached *u, unsigned char b) { } /* Assumes that a[31] <= 127 */ -void ge_scalarmult(ge_p2 *r, const struct cryptoEllipticCurveScalar *a, const ge_p3 *A) { - signed char e[64]; - int carry, carry2, i; - ge_cached Ai[8]; /* 1 * A, 2 * A, ..., 8 * A */ - ge_p1p1 t; - ge_p3 u; - - carry = 0; /* 0..1 */ - for (i = 0; i < 31; i++) { - carry += a->data[i]; /* 0..256 */ - carry2 = (carry + 8) >> 4; /* 0..16 */ - e[2 * i] = carry - (carry2 << 4); /* -8..7 */ - carry = (carry2 + 8) >> 4; /* 0..1 */ - e[2 * i + 1] = carry2 - (carry << 4); /* -8..7 */ - } - carry += a->data[31]; /* 0..128 */ - carry2 = (carry + 8) >> 4; /* 0..8 */ - e[62] = carry - (carry2 << 4); /* -8..7 */ - e[63] = carry2; /* 0..8 */ - - ge_p3_to_cached(&Ai[0], A); - for (i = 0; i < 7; i++) { - ge_add(&t, A, &Ai[i]); - ge_p1p1_to_p3(&u, &t); - ge_p3_to_cached(&Ai[i + 1], &u); - } - - ge_p2_0(r); - for (i = 63; i >= 0; i--) { - signed char b = e[i]; - unsigned char bnegative = negative(b); - unsigned char babs = b - (((-bnegative) & b) << 1); - ge_cached cur, minuscur; - ge_p2_dbl(&t, r); - ge_p1p1_to_p2(r, &t); - ge_p2_dbl(&t, r); - ge_p1p1_to_p2(r, &t); - ge_p2_dbl(&t, r); - ge_p1p1_to_p2(r, &t); - ge_p2_dbl(&t, r); - ge_p1p1_to_p3(&u, &t); - ge_cached_0(&cur); - ge_cached_cmov(&cur, &Ai[0], equal(babs, 1)); - ge_cached_cmov(&cur, &Ai[1], equal(babs, 2)); - ge_cached_cmov(&cur, &Ai[2], equal(babs, 3)); - ge_cached_cmov(&cur, &Ai[3], equal(babs, 4)); - ge_cached_cmov(&cur, &Ai[4], equal(babs, 5)); - ge_cached_cmov(&cur, &Ai[5], equal(babs, 6)); - ge_cached_cmov(&cur, &Ai[6], equal(babs, 7)); - ge_cached_cmov(&cur, &Ai[7], equal(babs, 8)); - fe_copy(minuscur.YplusX, cur.YminusX); - fe_copy(minuscur.YminusX, cur.YplusX); - fe_copy(minuscur.Z, cur.Z); - fe_neg(minuscur.T2d, cur.T2d); - ge_cached_cmov(&cur, &minuscur, bnegative); - ge_add(&t, &u, &cur); - ge_p1p1_to_p2(r, &t); - } -} +//void ge_scalarmult(ge_p2 *r, const struct cryptoEllipticCurveScalar *a, const ge_p3 *A) { +// signed char e[64]; +// int carry, carry2, i; +// ge_cached Ai[8]; /* 1 * A, 2 * A, ..., 8 * A */ +// ge_p1p1 t; +// ge_p3 u; +// +// carry = 0; /* 0..1 */ +// for (i = 0; i < 31; i++) { +// carry += a->data[i]; /* 0..256 */ +// carry2 = (carry + 8) >> 4; /* 0..16 */ +// e[2 * i] = carry - (carry2 << 4); /* -8..7 */ +// carry = (carry2 + 8) >> 4; /* 0..1 */ +// e[2 * i + 1] = carry2 - (carry << 4); /* -8..7 */ +// } +// carry += a->data[31]; /* 0..128 */ +// carry2 = (carry + 8) >> 4; /* 0..8 */ +// e[62] = carry - (carry2 << 4); /* -8..7 */ +// e[63] = carry2; /* 0..8 */ +// +// ge_p3_to_cached(&Ai[0], A); +// for (i = 0; i < 7; i++) { +// ge_add(&t, A, &Ai[i]); +// ge_p1p1_to_p3(&u, &t); +// ge_p3_to_cached(&Ai[i + 1], &u); +// } +// +// ge_p2_0(r); +// for (i = 63; i >= 0; i--) { +// signed char b = e[i]; +// unsigned char bnegative = negative(b); +// unsigned char babs = b - (((-bnegative) & b) << 1); +// ge_cached cur, minuscur; +// ge_p2_dbl(&t, r); +// ge_p1p1_to_p2(r, &t); +// ge_p2_dbl(&t, r); +// ge_p1p1_to_p2(r, &t); +// ge_p2_dbl(&t, r); +// ge_p1p1_to_p2(r, &t); +// ge_p2_dbl(&t, r); +// ge_p1p1_to_p3(&u, &t); +// ge_cached_0(&cur); +// ge_cached_cmov(&cur, &Ai[0], equal(babs, 1)); +// ge_cached_cmov(&cur, &Ai[1], equal(babs, 2)); +// ge_cached_cmov(&cur, &Ai[2], equal(babs, 3)); +// ge_cached_cmov(&cur, &Ai[3], equal(babs, 4)); +// ge_cached_cmov(&cur, &Ai[4], equal(babs, 5)); +// ge_cached_cmov(&cur, &Ai[5], equal(babs, 6)); +// ge_cached_cmov(&cur, &Ai[6], equal(babs, 7)); +// ge_cached_cmov(&cur, &Ai[7], equal(babs, 8)); +// fe_copy(minuscur.YplusX, cur.YminusX); +// fe_copy(minuscur.YminusX, cur.YplusX); +// fe_copy(minuscur.Z, cur.Z); +// fe_neg(minuscur.T2d, cur.T2d); +// ge_cached_cmov(&cur, &minuscur, bnegative); +// ge_add(&t, &u, &cur); +// ge_p1p1_to_p2(r, &t); +// } +//} /* Assumes that a[31] <= 127 */ void ge_scalarmult3(ge_p3 *rr, const struct cryptoEllipticCurveScalar *a, const ge_p3 *A) { @@ -2042,19 +2097,19 @@ void ge_scalarmult3(ge_p3 *rr, const struct cryptoEllipticCurveScalar *a, const } } -void ge_double_scalarmult_precomp_vartime(ge_p2 *r, const struct cryptoEllipticCurveScalar *aa, const ge_p3 *A, const struct cryptoEllipticCurveScalar *bb, const ge_dsmp Bi) { +/*void ge_double_scalarmult_precomp_vartime(ge_p2 *r, const struct cryptoEllipticCurveScalar *aa, const ge_p3 *A, const struct cryptoEllipticCurveScalar *bb, const ge_dsmp *Bi) { const unsigned char * a = aa->data; const unsigned char * b = bb->data; signed char aslide[256]; signed char bslide[256]; - ge_dsmp Ai; /* A, 3A, 5A, 7A, 9A, 11A, 13A, 15A */ + ge_dsmp Ai; // A, 3A, 5A, 7A, 9A, 11A, 13A, 15A ge_p1p1 t; ge_p3 u; int i; slide(aslide, a); slide(bslide, b); - ge_dsm_precomp(Ai, A); + ge_dsm_precomp(&Ai, A); ge_p2_0(r); @@ -2067,25 +2122,74 @@ void ge_double_scalarmult_precomp_vartime(ge_p2 *r, const struct cryptoEllipticC if (aslide[i] > 0) { ge_p1p1_to_p3(&u, &t); - ge_add(&t, &u, &Ai[aslide[i]/2]); + ge_add(&t, &u, &Ai.ca[aslide[i]/2]); } else if (aslide[i] < 0) { ge_p1p1_to_p3(&u, &t); - ge_sub(&t, &u, &Ai[(-aslide[i])/2]); + ge_sub(&t, &u, &Ai.ca[(-aslide[i])/2]); } if (bslide[i] > 0) { ge_p1p1_to_p3(&u, &t); - ge_add(&t, &u, &Bi[bslide[i]/2]); + ge_add(&t, &u, &Bi->ca[bslide[i]/2]); } else if (bslide[i] < 0) { ge_p1p1_to_p3(&u, &t); - ge_sub(&t, &u, &Bi[(-bslide[i])/2]); + ge_sub(&t, &u, &Bi->ca[(-bslide[i])/2]); } ge_p1p1_to_p2(r, &t); } +}*/ + +void ge_double_scalarmult_precomp_vartime3(ge_p3 *rr, const struct cryptoEllipticCurveScalar *aa, const ge_p3 *A, const struct cryptoEllipticCurveScalar *bb, const ge_dsmp *Bi) { + const unsigned char * a = aa->data; + const unsigned char * b = bb->data; + signed char aslide[256]; + signed char bslide[256]; + ge_dsmp Ai; /* A, 3A, 5A, 7A, 9A, 11A, 13A, 15A */ + ge_p1p1 t; + ge_p3 u; + int i; + ge_p2 r; + + slide(aslide, a); + slide(bslide, b); + ge_dsm_precomp(&Ai, A); + + ge_p2_0(&r); + ge_p3_0(rr); // We will not enter "for" below for some inputs + + for (i = 255; i >= 0; --i) { + if (aslide[i] || bslide[i]) break; + } + + for (; i >= 0; --i) { + ge_p2_dbl(&t, &r); + + if (aslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_add(&t, &u, &Ai.ca[aslide[i]/2]); + } else if (aslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_sub(&t, &u, &Ai.ca[(-aslide[i])/2]); + } + + if (bslide[i] > 0) { + ge_p1p1_to_p3(&u, &t); + ge_add(&t, &u, &Bi->ca[bslide[i]/2]); + } else if (bslide[i] < 0) { + ge_p1p1_to_p3(&u, &t); + ge_sub(&t, &u, &Bi->ca[(-bslide[i])/2]); + } + + if(i == 0) + ge_p1p1_to_p3(rr, &t); + else + ge_p1p1_to_p2(&r, &t); + } } -int ge_check_subgroup_precomp_vartime(const ge_dsmp p) { +int ge_check_subgroup_precomp_vartime(const ge_dsmp * pp) { + const ge_cached * p = pp->ca; ge_p3 s; ge_p1p1 t; ge_p2 u; @@ -2637,7 +2741,7 @@ int ge_check_subgroup_precomp_vartime(const ge_dsmp p) { return fe_isnonzero(t.Y); } -void ge_mul8(ge_p1p1 *r, const ge_p2 *t) { +void ge_mul8_p2(ge_p1p1 *r, const ge_p2 *t) { ge_p2 u; ge_p2_dbl(r, t); ge_p1p1_to_p2(&u, r); @@ -2646,6 +2750,15 @@ void ge_mul8(ge_p1p1 *r, const ge_p2 *t) { ge_p2_dbl(r, &u); } +void ge_mul8(ge_p1p1 *r, const ge_p3 *t) { + ge_p2 u; + ge_p3_dbl(r, t); + ge_p1p1_to_p2(&u, r); + ge_p2_dbl(r, &u); + ge_p1p1_to_p2(&u, r); + ge_p2_dbl(r, &u); +} + void ge_fromfe_frombytes_vartime(ge_p2 *r, const unsigned char s[32]) { fe u, v, w, x, y, z; unsigned char sign; @@ -2894,6 +3007,7 @@ void sc_reduce32(struct cryptoEllipticCurveScalar * aa, const unsigned char s[32 a[31] = (unsigned char) (s11 >> 17); } +/* Computes s = a + b, then applies sc_reduce32. */ void sc_add(struct cryptoEllipticCurveScalar *s, const struct cryptoEllipticCurveScalar *a, const struct cryptoEllipticCurveScalar *b) { int64_t a0 = 2097151 & load_3(a->data); int64_t a1 = 2097151 & (load_4(a->data + 2) >> 5); @@ -3033,6 +3147,7 @@ void sc_add(struct cryptoEllipticCurveScalar *s, const struct cryptoEllipticCurv s->data[31] = (unsigned char) (s11 >> 17); } +/* Computes s = a + b, then applies sc_reduce32. */ void sc_sub(struct cryptoEllipticCurveScalar *s, const struct cryptoEllipticCurveScalar *a, const struct cryptoEllipticCurveScalar *b) { int64_t a0 = 2097151 & load_3(a->data); int64_t a1 = 2097151 & (load_4(a->data + 2) >> 5); @@ -3181,6 +3296,8 @@ void sc_sub(struct cryptoEllipticCurveScalar *s, const struct cryptoEllipticCurv Output: s[0]+256*s[1]+...+256^31*s[31] = (c-ab) mod l where l = 2^252 + 27742317777372353535851937790883648493. + + Unpacks two 32-byte sequences, multiplies them into an unpacked 64-byte sequence and applies sc_reduce64. */ void sc_mulsub(struct cryptoEllipticCurveScalar *ss, const struct cryptoEllipticCurveScalar *aa, const struct cryptoEllipticCurveScalar *bb, const struct cryptoEllipticCurveScalar *cc) { @@ -3507,39 +3624,333 @@ void sc_mulsub(struct cryptoEllipticCurveScalar *ss, const struct cryptoElliptic s[30] = (unsigned char) (s11 >> 9); s[31] = (unsigned char) (s11 >> 17); } +void sc_mul(struct cryptoEllipticCurveScalar *ss, const struct cryptoEllipticCurveScalar *aa, const struct cryptoEllipticCurveScalar *bb) { + unsigned char * s = ss->data; + const unsigned char * a = aa->data; + const unsigned char * b = bb->data; + int64_t a0 = 2097151 & load_3(a); + int64_t a1 = 2097151 & (load_4(a + 2) >> 5); + int64_t a2 = 2097151 & (load_3(a + 5) >> 2); + int64_t a3 = 2097151 & (load_4(a + 7) >> 7); + int64_t a4 = 2097151 & (load_4(a + 10) >> 4); + int64_t a5 = 2097151 & (load_3(a + 13) >> 1); + int64_t a6 = 2097151 & (load_4(a + 15) >> 6); + int64_t a7 = 2097151 & (load_3(a + 18) >> 3); + int64_t a8 = 2097151 & load_3(a + 21); + int64_t a9 = 2097151 & (load_4(a + 23) >> 5); + int64_t a10 = 2097151 & (load_3(a + 26) >> 2); + int64_t a11 = (load_4(a + 28) >> 7); + int64_t b0 = 2097151 & load_3(b); + int64_t b1 = 2097151 & (load_4(b + 2) >> 5); + int64_t b2 = 2097151 & (load_3(b + 5) >> 2); + int64_t b3 = 2097151 & (load_4(b + 7) >> 7); + int64_t b4 = 2097151 & (load_4(b + 10) >> 4); + int64_t b5 = 2097151 & (load_3(b + 13) >> 1); + int64_t b6 = 2097151 & (load_4(b + 15) >> 6); + int64_t b7 = 2097151 & (load_3(b + 18) >> 3); + int64_t b8 = 2097151 & load_3(b + 21); + int64_t b9 = 2097151 & (load_4(b + 23) >> 5); + int64_t b10 = 2097151 & (load_3(b + 26) >> 2); + int64_t b11 = (load_4(b + 28) >> 7); + int64_t s0; + int64_t s1; + int64_t s2; + int64_t s3; + int64_t s4; + int64_t s5; + int64_t s6; + int64_t s7; + int64_t s8; + int64_t s9; + int64_t s10; + int64_t s11; + int64_t s12; + int64_t s13; + int64_t s14; + int64_t s15; + int64_t s16; + int64_t s17; + int64_t s18; + int64_t s19; + int64_t s20; + int64_t s21; + int64_t s22; + int64_t s23; + int64_t carry0; + int64_t carry1; + int64_t carry2; + int64_t carry3; + int64_t carry4; + int64_t carry5; + int64_t carry6; + int64_t carry7; + int64_t carry8; + int64_t carry9; + int64_t carry10; + int64_t carry11; + int64_t carry12; + int64_t carry13; + int64_t carry14; + int64_t carry15; + int64_t carry16; + int64_t carry17; + int64_t carry18; + int64_t carry19; + int64_t carry20; + int64_t carry21; + int64_t carry22; + + s0 = a0*b0; + s1 = (a0*b1 + a1*b0); + s2 = (a0*b2 + a1*b1 + a2*b0); + s3 = (a0*b3 + a1*b2 + a2*b1 + a3*b0); + s4 = (a0*b4 + a1*b3 + a2*b2 + a3*b1 + a4*b0); + s5 = (a0*b5 + a1*b4 + a2*b3 + a3*b2 + a4*b1 + a5*b0); + s6 = (a0*b6 + a1*b5 + a2*b4 + a3*b3 + a4*b2 + a5*b1 + a6*b0); + s7 = (a0*b7 + a1*b6 + a2*b5 + a3*b4 + a4*b3 + a5*b2 + a6*b1 + a7*b0); + s8 = (a0*b8 + a1*b7 + a2*b6 + a3*b5 + a4*b4 + a5*b3 + a6*b2 + a7*b1 + a8*b0); + s9 = (a0*b9 + a1*b8 + a2*b7 + a3*b6 + a4*b5 + a5*b4 + a6*b3 + a7*b2 + a8*b1 + a9*b0); + s10 = (a0*b10 + a1*b9 + a2*b8 + a3*b7 + a4*b6 + a5*b5 + a6*b4 + a7*b3 + a8*b2 + a9*b1 + a10*b0); + s11 = (a0*b11 + a1*b10 + a2*b9 + a3*b8 + a4*b7 + a5*b6 + a6*b5 + a7*b4 + a8*b3 + a9*b2 + a10*b1 + a11*b0); + s12 = (a1*b11 + a2*b10 + a3*b9 + a4*b8 + a5*b7 + a6*b6 + a7*b5 + a8*b4 + a9*b3 + a10*b2 + a11*b1); + s13 = (a2*b11 + a3*b10 + a4*b9 + a5*b8 + a6*b7 + a7*b6 + a8*b5 + a9*b4 + a10*b3 + a11*b2); + s14 = (a3*b11 + a4*b10 + a5*b9 + a6*b8 + a7*b7 + a8*b6 + a9*b5 + a10*b4 + a11*b3); + s15 = (a4*b11 + a5*b10 + a6*b9 + a7*b8 + a8*b7 + a9*b6 + a10*b5 + a11*b4); + s16 = (a5*b11 + a6*b10 + a7*b9 + a8*b8 + a9*b7 + a10*b6 + a11*b5); + s17 = (a6*b11 + a7*b10 + a8*b9 + a9*b8 + a10*b7 + a11*b6); + s18 = (a7*b11 + a8*b10 + a9*b9 + a10*b8 + a11*b7); + s19 = (a8*b11 + a9*b10 + a10*b9 + a11*b8); + s20 = (a9*b11 + a10*b10 + a11*b9); + s21 = (a10*b11 + a11*b10); + s22 = a11*b11; + s23 = 0; + + carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + carry12 = (s12 + (1<<20)) >> 21; s13 += carry12; s12 -= carry12 << 21; + carry14 = (s14 + (1<<20)) >> 21; s15 += carry14; s14 -= carry14 << 21; + carry16 = (s16 + (1<<20)) >> 21; s17 += carry16; s16 -= carry16 << 21; + carry18 = (s18 + (1<<20)) >> 21; s19 += carry18; s18 -= carry18 << 21; + carry20 = (s20 + (1<<20)) >> 21; s21 += carry20; s20 -= carry20 << 21; + carry22 = (s22 + (1<<20)) >> 21; s23 += carry22; s22 -= carry22 << 21; + + carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + carry13 = (s13 + (1<<20)) >> 21; s14 += carry13; s13 -= carry13 << 21; + carry15 = (s15 + (1<<20)) >> 21; s16 += carry15; s15 -= carry15 << 21; + carry17 = (s17 + (1<<20)) >> 21; s18 += carry17; s17 -= carry17 << 21; + carry19 = (s19 + (1<<20)) >> 21; s20 += carry19; s19 -= carry19 << 21; + carry21 = (s21 + (1<<20)) >> 21; s22 += carry21; s21 -= carry21 << 21; + + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; -/* where l = 2^252 + 27742317777372353535851937790883648493. - * last num is 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed - * -2 - * is - * 1 <124 zeros> 0x14DEF9DE 0xA2F79CD6 0x5812631A 0x5CF5D3EB - */ - -void sc_mul(struct cryptoEllipticCurveScalar * rr, const struct cryptoEllipticCurveScalar * aa, const struct cryptoEllipticCurveScalar * bb) { - struct cryptoEllipticCurveScalar zz, minus_rr; - sc_0(&zz); - sc_mulsub(&minus_rr, aa, bb, &zz); - sc_sub(rr, &zz, &minus_rr); - // TODO - optimize + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + carry12 = (s12 + (1<<20)) >> 21; s13 += carry12; s12 -= carry12 << 21; + carry14 = (s14 + (1<<20)) >> 21; s15 += carry14; s14 -= carry14 << 21; + carry16 = (s16 + (1<<20)) >> 21; s17 += carry16; s16 -= carry16 << 21; + + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + carry13 = (s13 + (1<<20)) >> 21; s14 += carry13; s13 -= carry13 << 21; + carry15 = (s15 + (1<<20)) >> 21; s16 += carry15; s15 -= carry15 << 21; + + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = (s0 + (1<<20)) >> 21; s1 += carry0; s0 -= carry0 << 21; + carry2 = (s2 + (1<<20)) >> 21; s3 += carry2; s2 -= carry2 << 21; + carry4 = (s4 + (1<<20)) >> 21; s5 += carry4; s4 -= carry4 << 21; + carry6 = (s6 + (1<<20)) >> 21; s7 += carry6; s6 -= carry6 << 21; + carry8 = (s8 + (1<<20)) >> 21; s9 += carry8; s8 -= carry8 << 21; + carry10 = (s10 + (1<<20)) >> 21; s11 += carry10; s10 -= carry10 << 21; + + carry1 = (s1 + (1<<20)) >> 21; s2 += carry1; s1 -= carry1 << 21; + carry3 = (s3 + (1<<20)) >> 21; s4 += carry3; s3 -= carry3 << 21; + carry5 = (s5 + (1<<20)) >> 21; s6 += carry5; s5 -= carry5 << 21; + carry7 = (s7 + (1<<20)) >> 21; s8 += carry7; s7 -= carry7 << 21; + carry9 = (s9 + (1<<20)) >> 21; s10 += carry9; s9 -= carry9 << 21; + carry11 = (s11 + (1<<20)) >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + carry11 = s11 >> 21; s12 += carry11; s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + + carry0 = s0 >> 21; s1 += carry0; s0 -= carry0 << 21; + carry1 = s1 >> 21; s2 += carry1; s1 -= carry1 << 21; + carry2 = s2 >> 21; s3 += carry2; s2 -= carry2 << 21; + carry3 = s3 >> 21; s4 += carry3; s3 -= carry3 << 21; + carry4 = s4 >> 21; s5 += carry4; s4 -= carry4 << 21; + carry5 = s5 >> 21; s6 += carry5; s5 -= carry5 << 21; + carry6 = s6 >> 21; s7 += carry6; s6 -= carry6 << 21; + carry7 = s7 >> 21; s8 += carry7; s7 -= carry7 << 21; + carry8 = s8 >> 21; s9 += carry8; s8 -= carry8 << 21; + carry9 = s9 >> 21; s10 += carry9; s9 -= carry9 << 21; + carry10 = s10 >> 21; s11 += carry10; s10 -= carry10 << 21; + + s[0] = (unsigned char) (s0 >> 0); + s[1] = (unsigned char) (s0 >> 8); + s[2] = (unsigned char) ((s0 >> 16) | (s1 << 5)); + s[3] = (unsigned char) (s1 >> 3); + s[4] = (unsigned char) (s1 >> 11); + s[5] = (unsigned char) ((s1 >> 19) | (s2 << 2)); + s[6] = (unsigned char) (s2 >> 6); + s[7] = (unsigned char) ((s2 >> 14) | (s3 << 7)); + s[8] = (unsigned char) (s3 >> 1); + s[9] = (unsigned char) (s3 >> 9); + s[10] = (unsigned char) ((s3 >> 17) | (s4 << 4)); + s[11] = (unsigned char) (s4 >> 4); + s[12] = (unsigned char) (s4 >> 12); + s[13] = (unsigned char) ((s4 >> 20) | (s5 << 1)); + s[14] = (unsigned char) (s5 >> 7); + s[15] = (unsigned char) ((s5 >> 15) | (s6 << 6)); + s[16] = (unsigned char) (s6 >> 2); + s[17] = (unsigned char) (s6 >> 10); + s[18] = (unsigned char) ((s6 >> 18) | (s7 << 3)); + s[19] = (unsigned char) (s7 >> 5); + s[20] = (unsigned char) (s7 >> 13); + s[21] = (unsigned char) (s8 >> 0); + s[22] = (unsigned char) (s8 >> 8); + s[23] = (unsigned char) ((s8 >> 16) | (s9 << 5)); + s[24] = (unsigned char) (s9 >> 3); + s[25] = (unsigned char) (s9 >> 11); + s[26] = (unsigned char) ((s9 >> 19) | (s10 << 2)); + s[27] = (unsigned char) (s10 >> 6); + s[28] = (unsigned char) ((s10 >> 14) | (s11 << 7)); + s[29] = (unsigned char) (s11 >> 1); + s[30] = (unsigned char) (s11 >> 9); + s[31] = (unsigned char) (s11 >> 17); } -static void sc_invert_helper(struct cryptoEllipticCurveScalar * rr, const struct cryptoEllipticCurveScalar * xx, unsigned bits){ - for(unsigned i = 32; i-- > 0;){ +static void sc_invert_helper(struct cryptoEllipticCurveScalar * rr, const struct cryptoEllipticCurveScalar * xx, uint8_t bits){ + for(unsigned i = 8; i-- > 0;){ sc_mul(rr, rr, rr); if(bits & (1U << i)) sc_mul(rr, rr, xx); } } -// TODO - single bit array void sc_invert(struct cryptoEllipticCurveScalar * rr, const struct cryptoEllipticCurveScalar * xx){ *rr = *xx; // first bit - for(int i = 0; i != 124; ++i) // 124 zeroes + for(int i = 0; i != 124; ++i) // 124 zero bits sc_mul(rr, rr, rr); - sc_invert_helper(rr, xx, 0x14DEF9DEU); - sc_invert_helper(rr, xx, 0xA2F79CD6U); - sc_invert_helper(rr, xx, 0x5812631AU); - sc_invert_helper(rr, xx, 0x5CF5D3EBU); + for(int i = 15; i != 0; --i) + sc_invert_helper(rr, xx, crypto_L.data[i]); + sc_invert_helper(rr, xx, crypto_L.data[0] - 2); // inv(x) = x^(L-2) } /* Assumes that a != INT64_MIN */ diff --git a/src/crypto/bernstein/crypto-ops.h b/src/crypto/bernstein/crypto-ops.h index a11c96d9..7ef3739b 100644 --- a/src/crypto/bernstein/crypto-ops.h +++ b/src/crypto/bernstein/crypto-ops.h @@ -13,6 +13,9 @@ extern "C" { typedef int32_t fe[10]; +void fe_copy(fe h, const fe f); +void fe_1(fe h); + /* From ge.h */ typedef struct { @@ -54,9 +57,13 @@ void ge_add(ge_p1p1 *, const ge_p3 *, const ge_cached *); /* From ge_double_scalarmult.c, modified */ -typedef ge_cached ge_dsmp[8]; -void ge_dsm_precomp(ge_dsmp r, const ge_p3 *s); -void ge_double_scalarmult_base_vartime(ge_p2 *, const struct cryptoEllipticCurveScalar *, const ge_p3 *, const struct cryptoEllipticCurveScalar *); +typedef struct { + ge_cached ca[8]; +} ge_dsmp; + +void ge_dsm_precomp(ge_dsmp * r, const ge_p3 *s); +//void ge_double_scalarmult_base_vartime(ge_p2 *, const struct cryptoEllipticCurveScalar *, const ge_p3 *, const struct cryptoEllipticCurveScalar *); +void ge_double_scalarmult_base_vartime3(ge_p3 *, const struct cryptoEllipticCurveScalar *, const ge_p3 *, const struct cryptoEllipticCurveScalar *); /* From ge_frombytes.c, modified */ @@ -81,6 +88,7 @@ void ge_p3_to_cached(ge_cached *, const ge_p3 *); /* From ge_p3_to_p2.c */ void ge_p3_to_p2(ge_p2 *, const ge_p3 *); +//void ge_p2_to_p3(ge_p3 *, const ge_p2 *); /* From ge_p3_tobytes.c */ @@ -100,17 +108,20 @@ void ge_tobytes(struct cryptoEllipticCurvePoint *, const ge_p2 *); /* From sc_reduce.c */ -void sc_reduce(struct cryptoEllipticCurveScalar *, const unsigned char[64]); +void sc_reduce64(struct cryptoEllipticCurveScalar *, const unsigned char[64]); /* New code */ -void ge_scalarmult(ge_p2 *, const struct cryptoEllipticCurveScalar *, const ge_p3 *); +//void ge_scalarmult(ge_p2 *, const struct cryptoEllipticCurveScalar *, const ge_p3 *); void ge_scalarmult3(ge_p3 *, const struct cryptoEllipticCurveScalar *, const ge_p3 *); // TODO - ge_scalarmult3 is quick fix. conversion of p2 -> p1p1 would also work -void ge_double_scalarmult_precomp_vartime(ge_p2 *, const struct cryptoEllipticCurveScalar *, const ge_p3 *, const struct cryptoEllipticCurveScalar *, const ge_dsmp); -int ge_check_subgroup_precomp_vartime(const ge_dsmp); -void ge_mul8(ge_p1p1 *, const ge_p2 *); +//void ge_double_scalarmult_precomp_vartime(ge_p2 *, const struct cryptoEllipticCurveScalar *, const ge_p3 *, const struct cryptoEllipticCurveScalar *, const ge_dsmp *); +void ge_double_scalarmult_precomp_vartime3(ge_p3 *r, const struct cryptoEllipticCurveScalar *aa, const ge_p3 *A, const struct cryptoEllipticCurveScalar *bb, const ge_dsmp *Bi); + +int ge_check_subgroup_precomp_vartime(const ge_dsmp *); +void ge_mul8_p2(ge_p1p1 *, const ge_p2 *); +void ge_mul8(ge_p1p1 *, const ge_p3 *); void ge_fromfe_frombytes_vartime(ge_p2 *, const unsigned char[32]); // Arbirtrary bytes to Point void sc_0(struct cryptoEllipticCurveScalar *); void sc_1(struct cryptoEllipticCurveScalar *); diff --git a/src/crypto/chacha.hpp b/src/crypto/chacha.hpp index 1de67727..b868e5af 100644 --- a/src/crypto/chacha.hpp +++ b/src/crypto/chacha.hpp @@ -11,6 +11,8 @@ struct chacha_key { chacha_key() : data{} {} explicit chacha_key(const Hash &ha); ~chacha_key(); + + std::vector as_binary_array() const { return std::vector{std::begin(data), std::end(data)}; } }; struct chacha_iv { uint8_t data[8]{}; diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp index c624b3e6..75bef19f 100644 --- a/src/crypto/crypto.cpp +++ b/src/crypto/crypto.cpp @@ -12,160 +12,77 @@ #include "bernstein/crypto-ops.h" #include "crypto.hpp" +#include "crypto_helpers.hpp" #include "hash.hpp" #include "random.h" namespace crypto { -static bool paranoid_checks = false; // instead of stupid NDEBUG -// Helpers that allow to write normal "x = f(y)" code instead of stupid f(&x, &y) +//#define DEBUG_PRINT(expr) do { expr; } while (0) +#define DEBUG_PRINT(expr) -static SecretKey sc_invert(const EllipticCurveScalar &sec) { - SecretKey result; - sc_invert(&result, &sec); - return result; -} - -static ge_p3 ge_scalarmult_base(const EllipticCurveScalar &sec) { - ge_p3 point; - ge_scalarmult_base(&point, &sec); - return point; -} - -static PublicKey ge_tobytes(const ge_p3 &point3) { - PublicKey result; - ge_p3_tobytes(&result, &point3); - return result; -} - -static PublicKey ge_tobytes(const ge_p2 &point2) { - PublicKey result; - ge_tobytes(&result, &point2); - return result; -} +//#define PARANOID_CHECK(expr, msg) do { if (!(expr)) throw Error(msg); } while (0) +#define PARANOID_CHECK(expr, msg) -static void check_scalar(const EllipticCurveScalar &scalar) { - if (!sc_isvalid_vartime(&scalar)) - throw Error("Secret Key Invalid"); -} +const ge_p3 H_p3 = {{7329926, -15101362, 31411471, 7614783, 27996851, -3197071, -11157635, -6878293, 466949, -7986503}, + {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}}; -static ge_p3 ge_frombytes_vartime(const EllipticCurvePoint &point) { - ge_p3 result_p3; - if (ge_frombytes_vartime(&result_p3, &point) != 0) - throw Error("Public Key Invalid"); - return result_p3; +PublicKey get_G() { // 5866666666666666666666666666666666666666666666666666666666666666 + SecretKey one; + sc_1(&one); + return ge_tobytes(ge_scalarmult_base(one)); } -static ge_p2 ge_scalarmult(const EllipticCurveScalar &sec, const ge_p3 &point_base) { - ge_p2 point2; - ge_scalarmult(&point2, &sec, &point_base); - return point2; -} +PublicKey get_H() { return ge_tobytes(H_p3); } // 8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94 -static ge_p3 ge_scalarmult3(const EllipticCurveScalar &sec, const ge_p3 &point_base) { - ge_p3 point3; - ge_scalarmult3(&point3, &sec, &point_base); - return point3; -} +const ge_p3 &get_H_p3() { return H_p3; } -static ge_p2 ge_double_scalarmult_base_vartime( - const EllipticCurveScalar &a, const ge_p3 &A, const EllipticCurveScalar &b) { - ge_p2 tmp2; - ge_double_scalarmult_base_vartime(&tmp2, &a, &A, &b); - return tmp2; +PublicKey test_get_H() { + PublicKey G = get_G(); + Hash hash = cn_fast_hash(G.data, sizeof(G.data)); + PublicKey hash_as_pk; + memcpy(hash_as_pk.data, hash.data, 32); // reintrepret hash as a point :) + return ge_tobytes(ge_p1p1_to_p3(ge_mul8(ge_frombytes_vartime(hash_as_pk)))); } -static ge_p2 ge_double_scalarmult_precomp_vartime( - const EllipticCurveScalar &a, const ge_p3 &A, const EllipticCurveScalar &b, const ge_dsmp B) { - ge_p2 tmp2; - ge_double_scalarmult_precomp_vartime(&tmp2, &a, &A, &b, B); - return tmp2; +KeccakStream &KeccakStream::append(size_t i) { // varint + enum { max_varint_size = (std::numeric_limits::digits + 6) / 7 }; + unsigned char data[max_varint_size]; + unsigned char *p = data; + for (; i >= 0x80; i >>= 7) + *p++ = static_cast((i & 0x7f) | 0x80); + *p++ = static_cast(i); + append(data, p - data); + return *this; } -static bool ge_dsm_frombytes_vartime(ge_dsmp image_dsm, const EllipticCurvePoint &image) { - ge_p3 image_p3; - if (ge_frombytes_vartime(&image_p3, &image) != 0) - return false; - ge_dsm_precomp(image_dsm, &image_p3); - return true; -} -static ge_p1p1 ge_mul8(const ge_p2 &p2) { - ge_p1p1 p1; - ge_mul8(&p1, &p2); - return p1; -} - -static ge_p2 ge_p1p1_to_p2(const ge_p1p1 &p1) { - ge_p2 p2; - ge_p1p1_to_p2(&p2, &p1); - return p2; +SecretKey KeccakStream::hash_to_scalar() { + Hash h = cn_fast_hash(); + SecretKey result; + sc_reduce32(&result, h.data); + return result; } - -static ge_p3 ge_p1p1_to_p3(const ge_p1p1 &p1) { - ge_p3 p3; - ge_p1p1_to_p3(&p3, &p1); - return p3; +SecretKey KeccakStream::hash_to_scalar64() { + Hash h = cn_fast_hash(); + crypto_keccak_init(&impl, 256, 1); // reuse same impl + crypto_keccak_update(&impl, h.data, sizeof(h.data)); + Hash h2 = cn_fast_hash(); + uint8_t buf[64]{}; + memcpy(buf, h.data, 32); + memcpy(buf + 32, h2.data, 32); + SecretKey result; + sc_reduce64(&result, buf); + return result; } -static ge_p2 ge_p3_to_p2(const ge_p3 &p3) { - ge_p2 p2; - ge_p3_to_p2(&p2, &p3); - return p2; -} -ge_cached ge_p3_to_cached(const ge_p3 &p3) { - ge_cached ca; - ge_p3_to_cached(&ca, &p3); - return ca; +PublicKey KeccakStream::hash_to_point() { + const Hash h = cn_fast_hash(); + ge_p2 point_p2; + ge_fromfe_frombytes_vartime(&point_p2, h.data); + return ge_tobytes(ge_p1p1_to_p3(ge_mul8_p2(point_p2))); } -/*ge_cached ge_p2_to_cached(const ge_p2 &p2) { - // TODO - faster? - auto by = ge_tobytes(p2); - auto p3 = ge_frombytes_vartime(by); - ge_cached ca; - ge_p3_to_cached(&ca, &p3); - return ca; -}*/ -// Integer parameters of all funs in crypto.cpp are size_t -enum { max_varint_size = (std::numeric_limits::digits + 6) / 7 }; - -struct MiniBuffer { - MiniBuffer(uint8_t *buf, size_t max_size) : buf(buf), max_size(max_size) {} - uint8_t *const buf; - const size_t max_size; - size_t pos = 0; - void check_overflow(size_t size) { - if (pos + size > max_size) - throw Error("FixedBuffer overflow"); - } - void append(const void *ptr, size_t size) { - check_overflow(size); - memcpy(buf + pos, ptr, size); - pos += size; - } - template - void append(const char (&h)[S]) { - append(h, S - 1); - } - void append(const Hash &h) { append(h.data, sizeof(h.data)); } - void append(const EllipticCurvePoint &h) { append(h.data, sizeof(h.data)); } - void append(const EllipticCurveScalar &h) { append(h.data, sizeof(h.data)); } - void append(size_t i) { // varint - check_overflow(max_varint_size); - for (; i >= 0x80; i >>= 7) - buf[pos++] = static_cast((i & 0x7f) | 0x80); - buf[pos++] = static_cast(i); - check_overflow(0); // cheap paranoid check - } - Hash cn_fast_hash() const { return crypto::cn_fast_hash(buf, pos); } - SecretKey hash_to_scalar() const { return crypto::hash_to_scalar(buf, pos); } - SecretKey hash_to_scalar64() const { return crypto::hash_to_scalar64(buf, pos); } -}; - -template -struct FixedBuffer : MiniBuffer { - uint8_t space[S]{}; - FixedBuffer() : MiniBuffer{space, S} {} -}; static std::mutex random_lock; @@ -179,37 +96,30 @@ SecretKey random_scalar() { uint8_t tmp[64]{}; generate_random_bytes(tmp, sizeof(tmp)); SecretKey result; - sc_reduce(&result, tmp); + sc_reduce64(&result, tmp); return result; } +PublicKey hash_to_good_point(const void *data, size_t length) { + return ge_tobytes(hash_to_good_point_p3(data, length)); +} + SecretKey hash_to_scalar(const void *data, size_t length) { - Hash h = cn_fast_hash(data, length); - SecretKey result; - sc_reduce32(&result, h.data); - return result; + return KeccakStream().append(data, length).hash_to_scalar(); } +// TODO - check security of this approach. SecretKey hash_to_scalar64(const void *data, size_t length) { - uint8_t buf[64]{}; - crypto_cn_fast_hash64(data, length, buf); - SecretKey result; - sc_reduce(&result, buf); - return result; + return KeccakStream().append(data, length).hash_to_scalar64(); } -PublicKey hash_to_point(const void *data, size_t length) { - Hash h = cn_fast_hash(data, length); +PublicKey bytes_to_bad_point(const Hash &h) { ge_p2 point; ge_fromfe_frombytes_vartime(&point, h.data); return ge_tobytes(point); } -EllipticCurvePoint hash_to_point_for_tests(const Hash &h) { - ge_p2 point; - ge_fromfe_frombytes_vartime(&point, h.data); - return ge_tobytes(point); -} +PublicKey hash_to_bad_point(const void *data, size_t length) { return bytes_to_bad_point(cn_fast_hash(data, length)); } void random_keypair(PublicKey &pub, SecretKey &sec) { sec = random_scalar(); @@ -221,6 +131,35 @@ bool key_isvalid(const PublicKey &key) { return ge_frombytes_vartime(&point, &key) == 0; } +bool key_in_main_subgroup(const EllipticCurvePoint &key) { + ge_dsmp key_dsm; + if (!ge_dsm_frombytes_vartime(&key_dsm, key)) + return false; + return ge_check_subgroup_precomp_vartime(&key_dsm) == 0; + // All historic key images that fail subgroup check + // c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa (in tx + // 56da63a36a60cc2151e322528f8685c927fdad9578a5678af8023f87dd27430c) + // c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a (in tx + // f5e6754d7859ff4abf7a9733d5852d5ba35a77cab3dff4bb929c626cf1737b5a) + // 0000000000000000000000000000000000000000000000000000000000000080 (in tx + // 17320545c428fe7d67ff2c8140eef5c970adfc5eecab978986ac8b4b12a1dd84) + // 0100000000000000000000000000000000000000000000000000000000000000 (in tx + // 5a3db49ef69e1f9dd9b740cabea7328cd3499c29fc4f3295bac3fa5e55384626) + // 26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05 (in tx + // cef289d7fab6e35ac123db8a3f06f7675b48067e0dff185c72b140845b8b3b23) + // 26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85 (in tx + // 7e418cc77935cc349f007cd5409d2b6908e4130321fa6f97ee0fee64b000ff85) + // ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f (in tx + // 74298d301eb4b4da30c06989e0f7ff24a26c90bf4ffc4f2c18f34b7a22cf1136) + // All historic output public keys that fail subgroup check + // 9b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd088071 (in tx + // 2734b067c7cfc24d68f6bb1049d8b6fb10f9d9e21e31fd9a86b4d6ae5d24fab5) + // 26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05 (in tx + // 07a09e3c26d8ffc2e890713a69974e943a23ef6ad65b3bcbfc2b0f0da1add8f4, + // 2eb6eba0c298c9286accc0d9624173e8059bbeb09554aeb7ef1e2b7c373e3adb, + // 4bf32408756a8c914f2dea12cb17b38400a8d4b9bf6edcced2c03fc23fb27a0d) +} + bool keys_match(const SecretKey &secret_key, const PublicKey &expected_public_key) { PublicKey pub; bool r = secret_key_to_public_key(secret_key, &pub); @@ -235,54 +174,63 @@ bool secret_key_to_public_key(const SecretKey &sec, PublicKey *pub) { } Signature generate_signature(const Hash &prefix_hash, const PublicKey &pub, const SecretKey &sec) { - if (paranoid_checks && !keys_match(sec, pub)) - throw Error("Keys do not match in generate_signature"); + PARANOID_CHECK(keys_match(sec, pub), "Keys do not match in generate_signature"); const EllipticCurveScalar k = random_scalar(); - FixedBuffer buf; - buf.append(prefix_hash); - buf.append(pub); - buf.append(ge_tobytes(ge_scalarmult_base(k))); + KeccakStream buf; + // buf << prefix_hash << pub << ge_tobytes(ge_scalarmult_base(k)); + buf << prefix_hash << pub << to_bytes(G * k); Signature sig; sig.c = buf.hash_to_scalar(); - sc_mulsub(&sig.r, &sig.c, &sec, &k); + // sc_mulsub(&sig.r, &sig.c, &sec, &k); + sig.r = k - sig.c * sec; return sig; } bool check_signature(const Hash &prefix_hash, const PublicKey &pub, const Signature &sig) { if (!sc_isvalid_vartime(&sig.c) || !sc_isvalid_vartime(&sig.r)) return false; - const ge_p3 pub_p3 = ge_frombytes_vartime(pub); - FixedBuffer buf; - buf.append(prefix_hash); - buf.append(pub); - buf.append(ge_tobytes(ge_double_scalarmult_base_vartime(sig.c, pub_p3, sig.r))); + KeccakStream buf; + buf << prefix_hash << pub << to_bytes(sig.c * P3(pub) + sig.r * G); - EllipticCurveScalar c = buf.hash_to_scalar(); - sc_sub(&c, &c, &sig.c); + EllipticCurveScalar c = buf.hash_to_scalar() - sig.c; return sc_iszero(&c) != 0; } -static ge_p3 hash_to_ec_p3(const PublicKey &key) { - ge_p2 point_p2; - const Hash h = cn_fast_hash(&key, sizeof(PublicKey)); - ge_fromfe_frombytes_vartime(&point_p2, h.data); - return ge_p1p1_to_p3(ge_mul8(point_p2)); +Signature generate_signature_H(const Hash &prefix_hash, const PublicKey &sec_H, const SecretKey &sec) { + const EllipticCurveScalar k = random_scalar(); + + KeccakStream buf; + buf << prefix_hash << sec_H << to_bytes(H * k); + + Signature sig; + sig.c = buf.hash_to_scalar(); + sig.r = k - sig.c * sec; + return sig; } -PublicKey hash_to_ec(const PublicKey &key) { return ge_tobytes(hash_to_ec_p3(key)); } +bool check_signature_H(const Hash &prefix_hash, const PublicKey &sec_H, const Signature &sig) { + if (!sc_isvalid_vartime(&sig.c) || !sc_isvalid_vartime(&sig.r)) + return false; + KeccakStream buf; + buf << prefix_hash << sec_H << to_bytes(sig.c * P3(sec_H) + sig.r * H); + + EllipticCurveScalar c = buf.hash_to_scalar() - sig.c; + return sc_iszero(&c) != 0; +} KeyImage generate_key_image(const PublicKey &pub, const SecretKey &sec) { check_scalar(sec); - const ge_p3 pub_hash_p3 = hash_to_ec_p3(pub); - KeyImage image; - static_cast(image) = ge_tobytes(ge_scalarmult(sec, pub_hash_p3)); - return image; -} + // const ge_p3 pub_hash_p3 = hash_to_good_point_p3(pub); + // KeyImage image; + // static_cast(image) = ge_tobytes(ge_scalarmult(sec, pub_hash_p3)); + // return image; -static size_t rs_comm_size(size_t pubs_count) { return sizeof(Hash) + pubs_count * 2 * sizeof(EllipticCurvePoint); } + P3 pub_hash_p3 = hash_to_good_point_p3(pub); + return to_bytes(pub_hash_p3 * sec); +} RingSignature generate_ring_signature(const Hash &prefix_hash, const KeyImage &image, const PublicKey pubs[], size_t pubs_count, const SecretKey &sec, size_t sec_index) { @@ -291,30 +239,27 @@ RingSignature generate_ring_signature(const Hash &prefix_hash, const KeyImage &i check_scalar(sec); RingSignature sig; sig.resize(pubs_count); - if (paranoid_checks && !keys_match(sec, pubs[sec_index])) - throw Error("Keys do not match in generate_ring_signature"); - if (paranoid_checks && generate_key_image(pubs[sec_index], sec) != image) - throw Error("Keyimage does not match keys in generate_ring_signature"); + PARANOID_CHECK(keys_match(sec, pubs[sec_index]), "Keys do not match in generate_ring_signature"); + PARANOID_CHECK( + generate_key_image(pubs[sec_index], sec) == image, "Keyimage does not match keys in generate_ring_signature"); ge_dsmp image_dsm; - if (!ge_dsm_frombytes_vartime(image_dsm, image)) + if (!ge_dsm_frombytes_vartime(&image_dsm, image)) throw Error("Keyimage is invalid"); - const size_t buf_size = rs_comm_size(pubs_count); - MiniBuffer buf(reinterpret_cast(alloca(buf_size)), buf_size); + KeccakStream buf; EllipticCurveScalar sum, k; sc_0(&sum); - buf.append(prefix_hash); + buf << prefix_hash; for (size_t i = 0; i < pubs_count; i++) { - const ge_p3 hash_pubs_i_p3 = hash_to_ec_p3(pubs[i]); + const ge_p3 hash_pubs_i_p3 = hash_to_good_point_p3(pubs[i]); if (i == sec_index) { k = random_scalar(); - buf.append(ge_tobytes(ge_scalarmult_base(k))); - buf.append(ge_tobytes(ge_scalarmult(k, hash_pubs_i_p3))); + buf << ge_tobytes(ge_scalarmult_base(k)) << ge_tobytes(ge_scalarmult3(k, hash_pubs_i_p3)); } else { const ge_p3 pubs_i_p3 = ge_frombytes_vartime(pubs[i]); sig[i].c = random_scalar(); sig[i].r = random_scalar(); - buf.append(ge_tobytes(ge_double_scalarmult_base_vartime(sig[i].c, pubs_i_p3, sig[i].r))); - buf.append(ge_tobytes(ge_double_scalarmult_precomp_vartime(sig[i].r, hash_pubs_i_p3, sig[i].c, image_dsm))); + buf << ge_tobytes(ge_double_scalarmult_base_vartime3(sig[i].c, pubs_i_p3, sig[i].r)); + buf << ge_tobytes(ge_double_scalarmult_precomp_vartime3(sig[i].r, hash_pubs_i_p3, sig[i].c, image_dsm)); sc_add(&sum, &sum, &sig[i].c); } } @@ -325,41 +270,22 @@ RingSignature generate_ring_signature(const Hash &prefix_hash, const KeyImage &i } bool check_ring_signature(const Hash &prefix_hash, const KeyImage &image, const PublicKey pubs[], size_t pubs_count, - const RingSignature &sig, bool key_image_subgroup_check) { + const RingSignature &sig) { ge_dsmp image_dsm; - if (!ge_dsm_frombytes_vartime(image_dsm, image)) + if (!ge_dsm_frombytes_vartime(&image_dsm, image)) return false; // key_image is considered part of signature, we do not throw if it is invalid - if (key_image_subgroup_check && ge_check_subgroup_precomp_vartime(image_dsm) != 0) { - // Example of key_images that fail subgroup check - // c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa (in tx - // 56da63a36a60cc2151e322528f8685c927fdad9578a5678af8023f87dd27430c) - // c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a (in tx - // f5e6754d7859ff4abf7a9733d5852d5ba35a77cab3dff4bb929c626cf1737b5a) - // 0000000000000000000000000000000000000000000000000000000000000080 (in tx - // 17320545c428fe7d67ff2c8140eef5c970adfc5eecab978986ac8b4b12a1dd84) - // 0100000000000000000000000000000000000000000000000000000000000000 (in tx - // 5a3db49ef69e1f9dd9b740cabea7328cd3499c29fc4f3295bac3fa5e55384626) - // 26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05 (in tx - // cef289d7fab6e35ac123db8a3f06f7675b48067e0dff185c72b140845b8b3b23) - // 26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85 (in tx - // 7e418cc77935cc349f007cd5409d2b6908e4130321fa6f97ee0fee64b000ff85) - // ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f (in tx - // 74298d301eb4b4da30c06989e0f7ff24a26c90bf4ffc4f2c18f34b7a22cf1136) - return false; - } - const size_t buf_size = rs_comm_size(pubs_count); - MiniBuffer buf(reinterpret_cast(alloca(buf_size)), buf_size); + KeccakStream buf; EllipticCurveScalar sum; sc_0(&sum); - buf.append(prefix_hash); + buf << prefix_hash; for (size_t i = 0; i < pubs_count; i++) { if (!sc_isvalid_vartime(&sig[i].c) || !sc_isvalid_vartime(&sig[i].r)) return false; const ge_p3 pubs_i_p3 = ge_frombytes_vartime(pubs[i]); - const ge_p3 hash_pubs_i_p3 = hash_to_ec_p3(pubs[i]); + const ge_p3 hash_pubs_i_p3 = hash_to_good_point_p3(pubs[i]); - buf.append(ge_tobytes(ge_double_scalarmult_base_vartime(sig[i].c, pubs_i_p3, sig[i].r))); - buf.append(ge_tobytes(ge_double_scalarmult_precomp_vartime(sig[i].r, hash_pubs_i_p3, sig[i].c, image_dsm))); + buf << ge_tobytes(ge_double_scalarmult_base_vartime3(sig[i].c, pubs_i_p3, sig[i].r)); + buf << ge_tobytes(ge_double_scalarmult_precomp_vartime3(sig[i].r, hash_pubs_i_p3, sig[i].c, image_dsm)); sc_add(&sum, &sum, &sig[i].c); } EllipticCurveScalar h = buf.hash_to_scalar(); @@ -367,194 +293,336 @@ bool check_ring_signature(const Hash &prefix_hash, const KeyImage &image, const return sc_iszero(&h) != 0; } -RingSignature3 generate_ring_signature3(const Hash &prefix_hash, const std::vector &images, - const std::vector> &pubs, const std::vector &secs, - const std::vector &sec_indexes, const SecretKey &view_secret_key) { - if (images.empty() || images.size() != pubs.size() || images.size() != secs.size()) - throw Error("inconsistent images/pubs/secs size in generate_ring_signature3"); - RingSignature3 sig; - sig.r.resize(images.size()); - // std::cout << "generate_ring_signature3" << std::endl; +static SecretKey generate_sign_secret( + size_t i, const Hash &random_seed1, const SecretKey &random_seed2, const char secret_name[2]) { + KeccakStream k_buf; + k_buf << random_seed1 << random_seed2; + k_buf.append_byte(secret_name[0]).append_byte(secret_name[1]); + k_buf << i; + SecretKey b = k_buf.hash_to_scalar64(); + DEBUG_PRINT(std::cout << secret_name[0] << secret_name[1] << "[" << i << "]=" << b << std::endl); + return b; +} + +void generate_ring_signature_auditable_loop1(size_t i, const Hash &prefix_hash, const P3 &image_p3, const P3 &p_p3, + const P3 &G_plus_B_p3, size_t sec_index, const std::vector &pubs, std::vector *ra, + EllipticCurvePoint *x, EllipticCurvePoint *y) { + ra->resize(pubs.size()); + for (size_t j = sec_index + 1; j < pubs.size(); ++j) { + DEBUG_PRINT(std::cout << "pk[" << i << ", " << j << "]=" << pubs[j] << std::endl); + const P3 pubs_i_p3(pubs[j]); + const P3 hash_pubs_i_p3(hash_to_good_point_p3(pubs[j])); + + EllipticCurveScalar &r = (*ra)[j]; + r = random_scalar(); + // FixedBuffer r_buf; + // r_buf << mega_random_seed << "r" << i << j; + // r = r_buf.hash_to_scalar64(); + DEBUG_PRINT(std::cout << "ra[" << i << ", " << j << "]=" << r << std::endl); + + KeccakStream c_buf; + c_buf << prefix_hash << *x << *y; + const EllipticCurveScalar c = c_buf.hash_to_scalar(); + DEBUG_PRINT(std::cout << "c[" << i << ", " << j << "]=" << c << std::endl); + + *x = to_bytes(c * (pubs_i_p3 - p_p3) + r * G_plus_B_p3); + *y = to_bytes(c * image_p3 + r * hash_pubs_i_p3); + DEBUG_PRINT(std::cout << "x[" << i << ", " << j << "]=" << *x << std::endl); + DEBUG_PRINT(std::cout << "y[" << i << ", " << j << "]=" << *y << std::endl); + } +} + +void generate_ring_signature_auditable_loop2(size_t i, const Hash &prefix_hash, const P3 &image_p3, const P3 &p_p3, + const P3 &G_plus_B_p3, size_t sec_index, const std::vector &pubs, std::vector *ra, + EllipticCurveScalar *next_c) { + for (size_t j = 0; j != sec_index; ++j) { + DEBUG_PRINT(std::cout << "pk[" << i << ", " << j << "]=" << pubs[j] << std::endl); + const P3 pubs_i_p3(pubs[j]); + const P3 hash_pubs_i_p3(hash_to_good_point_p3(pubs[j])); + + EllipticCurveScalar &r = (*ra)[j]; + r = random_scalar(); + // FixedBuffer r_buf; + // r_buf << mega_random_seed << "r" << i << j; + // r = r_buf.hash_to_scalar64(); + DEBUG_PRINT(std::cout << "ra[" << i << ", " << j << "]=" << r << std::endl); + + const auto x = to_bytes(*next_c * (pubs_i_p3 - p_p3) + r * G_plus_B_p3); + const auto y = to_bytes(*next_c * image_p3 + r * hash_pubs_i_p3); + DEBUG_PRINT(std::cout << "x[" << i << ", " << j << "]=" << x << std::endl); + DEBUG_PRINT(std::cout << "y[" << i << ", " << j << "]=" << y << std::endl); + + KeccakStream c_buf; + c_buf << prefix_hash << x << y; + *next_c = c_buf.hash_to_scalar(); + DEBUG_PRINT(std::cout << "c[" << i << ", " << j << "]=" << *next_c << std::endl); + } +} + +RingSignatureAmethyst generate_ring_signature_auditable(const Hash &prefix_hash, const std::vector &images, + const std::vector> &pubs, const std::vector &secs_spend, + const std::vector &secs_audit, const std::vector &sec_indexes) { + // sanity checks + if (images.empty() || images.size() != pubs.size() || images.size() != secs_spend.size() || + images.size() != secs_audit.size()) + throw Error("inconsistent images/pubs/secs size in generate_ring_signature_auditable"); + DEBUG_PRINT(std::cout << "generate_ring_signature_auditable" << std::endl); + + RingSignatureAmethyst sig; + sig.p.resize(images.size()); + sig.ra.resize(images.size()); + sig.rb.resize(images.size()); + sig.rc.resize(images.size()); + std::vector b_coins(images.size()); + + const Hash random_seed1 = Hash{}; // crypto::rand(); TODO - uncomment in final code + const SecretKey random_seed2 = secs_spend.at(0); // protection against owned rng + + KeccakStream buf; + buf << prefix_hash; + + DEBUG_PRINT(std::cout << "prefix_hash=" << prefix_hash << std::endl); - const size_t buf_size = sizeof(Hash) + images.size() * 2 * sizeof(EllipticCurvePoint); - MiniBuffer buf(reinterpret_cast(alloca(buf_size)), buf_size); - buf.append(prefix_hash); for (size_t i = 0; i != images.size(); ++i) { const size_t sec_index = sec_indexes[i]; + DEBUG_PRINT(std::cout << "image[" << i << "]=" << images[i] << std::endl); + // sanity checks if (pubs[i].empty() || sec_index >= pubs[i].size()) - throw Error("sec_index >= pubs_count in generate_ring_signature3"); - check_scalar(secs[i]); - if (paranoid_checks && !keys_match(secs[i], pubs[i][sec_index])) - throw Error("Keys do not match in generate_ring_signature"); - if (paranoid_checks && generate_key_image(pubs[i][sec_index], secs[i]) != images[i]) - throw Error("Keyimage does not match keys in generate_ring_signature"); - ge_dsmp image_dsm; // TODO - do not unpack image if it is not needed in this loop - if (!ge_dsm_frombytes_vartime(image_dsm, images[i])) - throw Error("Keyimage is invalid"); - sig.r[i].resize(pubs[i].size()); - FixedBuffer k_buf; - k_buf.append(prefix_hash); - k_buf.append(secs.at(i)); - const EllipticCurveScalar k = k_buf.hash_to_scalar64(); - // std::cout << "k[" << i << "]=" << k << std::endl; - EllipticCurvePoint a = ge_tobytes(ge_scalarmult_base(k)); - // std::cout << "a[" << i << ", " << sec_index[i] << "]=" << a << std::endl; - const ge_p3 hash_pubs_sec_p3 = hash_to_ec_p3(pubs[i][sec_index]); - EllipticCurvePoint b = ge_tobytes(ge_scalarmult(k, hash_pubs_sec_p3)); - // std::cout << "b[" << i << ", " << sec_index[i] << "]=" << b << std::endl; - for (size_t j = sec_indexes[i] + 1; j < pubs[i].size(); ++j) { - const ge_p3 pubs_i_p3 = ge_frombytes_vartime(pubs[i][j]); - const ge_p3 hash_pubs_i_p3 = hash_to_ec_p3(pubs[i][j]); - - EllipticCurveScalar &r = sig.r[i][j]; - FixedBuffer r_buf; - r_buf.append(view_secret_key); - r_buf.append(prefix_hash); - r_buf.append(i); - r_buf.append(j); - r = r_buf.hash_to_scalar64(); - // std::cout << "r[" << i << ", " << j << "]=" << r << std::endl; - - FixedBuffer c_buf; - c_buf.append(prefix_hash); - c_buf.append(a); - c_buf.append(b); - const EllipticCurveScalar c = c_buf.hash_to_scalar(); - // std::cout << "c[" << i << ", " << j << "]=" << c << std::endl; - - a = ge_tobytes(ge_double_scalarmult_base_vartime(c, pubs_i_p3, r)); - b = ge_tobytes(ge_double_scalarmult_precomp_vartime(r, hash_pubs_i_p3, c, image_dsm)); - // std::cout << "a[" << i << ", " << j << "]=" << a << std::endl; - // std::cout << "b[" << i << ", " << j << "]=" << b << std::endl; - } - buf.append(a); - buf.append(b); + throw Error("sec_index >= pubs_count in generate_ring_signature_auditable"); + check_scalar(secs_spend[i]); + check_scalar(secs_audit[i]); + PARANOID_CHECK(secret_keys_to_public_key(secs_audit[i], secs_spend[i]) == pubs[i][sec_index], + "Keys do not match in generate_ring_signature_auditable"); + PARANOID_CHECK(generate_key_image(pubs[i][sec_index], secs_audit[i]) == images[i], + "Keyimage does not match keys in generate_ring_signature_auditable"); + + const P3 b_coin_p3(hash_to_good_point_p3(images[i])); + b_coins[i] = to_bytes(b_coin_p3); + const P3 hash_pubs_sec_p3(hash_to_good_point_p3(pubs[i][sec_index])); + DEBUG_PRINT(std::cout << "b_coin[" << i << "]=" << b_coins[i] << std::endl); + const P3 p_p3 = H * secs_spend[i] - b_coin_p3 * secs_audit[i]; + sig.p[i] = to_bytes(p_p3); + buf << sig.p[i]; + DEBUG_PRINT(std::cout << "p[" << i << "]=" << sig.p[i] << std::endl); + + const SecretKey ka = generate_sign_secret(i, random_seed1, random_seed2, "ka"); + const SecretKey kb = generate_sign_secret(i, random_seed1, random_seed2, "kb"); + const SecretKey kc = generate_sign_secret(i, random_seed1, random_seed2, "kc"); + + const PublicKey z = to_bytes(kb * H + kc * b_coin_p3); + buf << z; + DEBUG_PRINT(std::cout << "z[" << i << "]=" << z << std::endl); + + const P3 G_plus_B_p3 = P3(G) + b_coin_p3; + DEBUG_PRINT(std::cout << "pk[" << i << ", " << sec_index << "]=" << pubs[i][sec_index] << std::endl); + EllipticCurvePoint x = to_bytes(ka * G_plus_B_p3); + DEBUG_PRINT(std::cout << "x[" << i << ", " << sec_index << "]=" << x << std::endl); + EllipticCurvePoint y = to_bytes(ka * hash_pubs_sec_p3); + DEBUG_PRINT(std::cout << "y[" << i << ", " << sec_index << "]=" << y << std::endl); + + const P3 image_p3(images[i]); + generate_ring_signature_auditable_loop1( + i, prefix_hash, image_p3, p_p3, G_plus_B_p3, sec_indexes[i], pubs[i], &sig.ra[i], &x, &y); + buf << x << y; + for (size_t j = 0; j != pubs[i].size(); ++j) + buf << pubs[i][j]; } + // glued point of Borromean ring signature sig.c0 = buf.hash_to_scalar(); - // std::cout << "c0=" << sigs[0].c0 << std::endl; + DEBUG_PRINT(std::cout << "c0=" << sig.c0 << std::endl); + for (size_t i = 0; i != images.size(); ++i) { const size_t sec_index = sec_indexes[i]; - ge_dsmp image_dsm; // TODO - do not unpack image if it is not needed in this loop - if (!ge_dsm_frombytes_vartime(image_dsm, images[i])) - throw Error("Keyimage is invalid"); - FixedBuffer k_buf; - k_buf.append(prefix_hash); - k_buf.append(secs.at(i)); - const EllipticCurveScalar k = k_buf.hash_to_scalar64(); // TODO - k must be the same in both fors - // std::cout << "k[" << i << "]=" << k << std::endl; + const P3 image_p3(images[i]); + DEBUG_PRINT(std::cout << "image[" << i << "]=" << images[i] << std::endl); + + const P3 b_coin_p3(b_coins[i]); + const P3 G_plus_B_p3 = P3(G) + b_coin_p3; + const P3 p_p3(sig.p[i]); + + const SecretKey ka = generate_sign_secret(i, random_seed1, random_seed2, "ka"); + const SecretKey kb = generate_sign_secret(i, random_seed1, random_seed2, "kb"); + const SecretKey kc = generate_sign_secret(i, random_seed1, random_seed2, "kc"); + + sig.rb[i] = kb - sig.c0 * secs_spend[i]; + sig.rc[i] = kc + sig.c0 * secs_audit[i]; + + DEBUG_PRINT(std::cout << "aha=" << to_bytes(sig.rb[i] * H + sig.rc[i] * b_coin_p3) << " " << to_bytes(sig.c0 * p_p3) + << std::endl); + + DEBUG_PRINT(std::cout << "rb[" << i << "]=" << sig.rb[i] << std::endl); + DEBUG_PRINT(std::cout << "rc[" << i << "]=" << sig.rc[i] << std::endl); + EllipticCurveScalar next_c = sig.c0; - for (size_t j = 0; j != sec_index; ++j) { - const ge_p3 pubs_i_p3 = ge_frombytes_vartime(pubs[i][j]); - const ge_p3 hash_pubs_i_p3 = hash_to_ec_p3(pubs[i][j]); - - EllipticCurveScalar &r = sig.r[i][j]; - FixedBuffer<2 * sizeof(Hash) + sizeof(KeyImage) + max_varint_size> r_buf; - r_buf.append(view_secret_key); - r_buf.append(prefix_hash); - r_buf.append(i); - r_buf.append(j); - r = r_buf.hash_to_scalar64(); - // std::cout << "r[" << i << ", " << j << "]=" << r << std::endl; - - const auto a = ge_tobytes(ge_double_scalarmult_base_vartime(next_c, pubs_i_p3, r)); - const auto b = ge_tobytes(ge_double_scalarmult_precomp_vartime(r, hash_pubs_i_p3, next_c, image_dsm)); - // std::cout << "a[" << i << ", " << j << "]=" << a << std::endl; - // std::cout << "b[" << i << ", " << j << "]=" << b << std::endl; - - FixedBuffer c_buf; - c_buf.append(prefix_hash); - c_buf.append(a); - c_buf.append(b); - next_c = c_buf.hash_to_scalar(); - // std::cout << "next_c[" << i << ", " << j << "]=" << next_c << std::endl; - } - sc_mulsub(&sig.r[i][sec_index], &next_c, &secs[i], &k); - // std::cout << "r[" << i << ", " << sec_index[i] << "]=" << sigs[i].r[sec_index[i]] << std::endl; + generate_ring_signature_auditable_loop2( + i, prefix_hash, image_p3, p_p3, G_plus_B_p3, sec_indexes[i], pubs[i], &sig.ra[i], &next_c); + sig.ra[i][sec_index] = ka - next_c * secs_audit[i]; + DEBUG_PRINT(std::cout << "ra[" << i << ", " << sec_index << "]=" << sig.ra[i][sec_index] << std::endl); } return sig; } -bool check_ring_signature3(const Hash &prefix_hash, const std::vector &images, - const std::vector> &pubs, const RingSignature3 &sig) { - if (images.empty() || images.size() != pubs.size() || images.size() != sig.r.size()) - throw Error("inconsistent images/pubs/sigs size in check_ring_signature3"); - // std::cout << "check_ring_signature3" << std::endl; - const size_t buf_size = sizeof(Hash) + images.size() * 2 * sizeof(EllipticCurvePoint); - MiniBuffer buf(reinterpret_cast(alloca(buf_size)), buf_size); - buf.append(prefix_hash); +bool check_ring_signature_auditable(const Hash &prefix_hash, const std::vector &images, + const std::vector> &pubs, const RingSignatureAmethyst &sig) { + // sanity checks + if (images.empty() || images.size() != pubs.size() || images.size() != sig.p.size() || + images.size() != sig.ra.size() || images.size() != sig.rb.size() || images.size() != sig.rc.size()) + throw Error("inconsistent images/pubs/sigs size in check_ring_signature_auditable"); + if (!sc_isvalid_vartime(&sig.c0)) + return false; + DEBUG_PRINT(std::cout << "check_ring_signature_auditable" << std::endl); + + DEBUG_PRINT(std::cout << "prefix_hash=" << prefix_hash << std::endl); + KeccakStream buf; + buf << prefix_hash; for (size_t i = 0; i != images.size(); ++i) { - if (pubs[i].empty() || pubs[i].size() != sig.r[i].size()) - throw Error("inconsistent pubs/sigs size in check_ring_signature3"); - ge_dsmp image_dsm; - if (!ge_dsm_frombytes_vartime(image_dsm, images[i])) - return false; // key_image is considered part of signature, we do not throw if it is invalid - if (ge_check_subgroup_precomp_vartime(image_dsm) != 0) + if (pubs[i].empty() || pubs[i].size() != sig.ra[i].size()) + throw Error("inconsistent pubs/sigs size in check_ring_signature_auditable"); + DEBUG_PRINT(std::cout << "image[" << i << "]=" << images[i] << std::endl); + const P3 b_coin_p3(hash_to_good_point_p3(images[i])); + const P3 G_plus_B_p3 = P3(G) + b_coin_p3; + if (!key_in_main_subgroup(sig.p[i])) + return false; + if (!sc_isvalid_vartime(&sig.rb[i]) || !sc_isvalid_vartime(&sig.rc[i])) return false; + + const P3 p_p3(sig.p[i]); + + buf << sig.p[i]; + DEBUG_PRINT(std::cout << "b_coin[" << i << "]=" << to_bytes(b_coin_p3) << std::endl); + DEBUG_PRINT(std::cout << "p[" << i << "]=" << sig.p[i] << std::endl); + + const PublicKey z = to_bytes(P3(sig.c0 * p_p3) + P3(sig.rb[i] * H) + P3(sig.rc[i] * b_coin_p3)); + DEBUG_PRINT(std::cout << "z[" << i << "]=" << z << std::endl); + buf << z; + + const P3 image_p3(images[i]); + auto next_c = sig.c0; for (size_t j = 0; j != pubs[i].size(); ++j) { - // std::cout << "c[" << i << ", " << j << "]=" << next_c << std::endl; - const ge_p3 pubs_i_p3 = ge_frombytes_vartime(pubs[i][j]); - const ge_p3 hash_pubs_i_p3 = hash_to_ec_p3(pubs[i][j]); - const EllipticCurveScalar &r = sig.r[i][j]; - // std::cout << "r[" << i << ", " << j << "]=" << r << std::endl; + DEBUG_PRINT(std::cout << "pk[" << i << ", " << j << "]=" << pubs[i][j] << std::endl); + DEBUG_PRINT(std::cout << "c[" << i << ", " << j << "]=" << next_c << std::endl); - const auto a = ge_tobytes(ge_double_scalarmult_base_vartime(next_c, pubs_i_p3, r)); - const auto b = ge_tobytes(ge_double_scalarmult_precomp_vartime(r, hash_pubs_i_p3, next_c, image_dsm)); - // std::cout << "a[" << i << ", " << j << "]=" << a << std::endl; - // std::cout << "b[" << i << ", " << j << "]=" << b << std::endl; + const P3 pubs_i_p3(pubs[i][j]); + const P3 hash_pubs_i_p3(hash_to_good_point_p3(pubs[i][j])); + const EllipticCurveScalar &ra = sig.ra[i][j]; + if (!sc_isvalid_vartime(&ra)) + return false; + DEBUG_PRINT(std::cout << "ra[" << i << ", " << j << "]=" << ra << std::endl); + + const auto x = to_bytes(next_c * (pubs_i_p3 - p_p3) + ra * G_plus_B_p3); + const auto y = to_bytes(next_c * image_p3 + ra * hash_pubs_i_p3); + DEBUG_PRINT(std::cout << "x[" << i << ", " << j << "]=" << x << std::endl); + DEBUG_PRINT(std::cout << "y[" << i << ", " << j << "]=" << y << std::endl); if (j == pubs[i].size() - 1) { - buf.append(a); - buf.append(b); + buf << x << y; } else { - FixedBuffer c_buf; - c_buf.append(prefix_hash); - c_buf.append(a); - c_buf.append(b); + KeccakStream c_buf; + c_buf << prefix_hash << x << y; next_c = c_buf.hash_to_scalar(); + DEBUG_PRINT(std::cout << "c[" << i << ", " << j << "]=" << next_c << std::endl); } } + for (size_t j = 0; j != pubs[i].size(); ++j) + buf << pubs[i][j]; } - auto c = buf.hash_to_scalar(); - sc_sub(&c, &c, &sig.c0); + const auto c = buf.hash_to_scalar() - sig.c0; + return sc_iszero(&c) != 0; +} + +SendproofSignatureAmethyst generate_sendproof_signature_auditable( + const Hash &prefix_hash, const KeyImage &image, const SecretKey &sec_spend, const SecretKey &sec_audit) { + check_scalar(sec_spend); + check_scalar(sec_audit); + DEBUG_PRINT(std::cout << "generate_sendproof_signature_auditable" << std::endl); + + KeccakStream buf; + buf << prefix_hash; + + DEBUG_PRINT(std::cout << "prefix_hash=" << prefix_hash << std::endl); + + DEBUG_PRINT(std::cout << "image=" << image << std::endl); + + const P3 b_coin_p3(hash_to_good_point_p3(image)); + // b_coins[i] = to_bytes(b_coin_p3); + // DEBUG_PRINT(std::cout << "b_coin[" << i << "]=" << b_coins[i] << std::endl); + const P3 p_p3 = H * sec_spend - b_coin_p3 * sec_audit; + auto ps = to_bytes(p_p3); + buf << ps; + DEBUG_PRINT(std::cout << "p[" << i << "]=" << ps << std::endl); + + const EllipticCurveScalar kb = random_scalar(); + const EllipticCurveScalar kc = random_scalar(); + + const PublicKey z = to_bytes(kb * H + kc * b_coin_p3); + buf << z; + DEBUG_PRINT(std::cout << "z[" << i << "]=" << z << std::endl); + + SendproofSignatureAmethyst result; + result.c0 = buf.hash_to_scalar(); + DEBUG_PRINT(std::cout << "c0=" << result.c0 << std::endl); + + result.rb = kb - result.c0 * sec_spend; + result.rc = kc + result.c0 * sec_audit; + + DEBUG_PRINT(std::cout << "rb[" << i << "]=" << result.rb << std::endl); + DEBUG_PRINT(std::cout << "rc[" << i << "]=" << result.rc << std::endl); + + return result; +} + +bool check_sendproof_signature_auditable( + const Hash &prefix_hash, const KeyImage &image, const PublicKey &ps, const SendproofSignatureAmethyst &sig) { + // sanity checks + if (!sc_isvalid_vartime(&sig.c0) || !sc_isvalid_vartime(&sig.rb) || !sc_isvalid_vartime(&sig.rc)) + return false; + DEBUG_PRINT(std::cout << "check_sendproof_signature_auditable" << std::endl); + + DEBUG_PRINT(std::cout << "prefix_hash=" << prefix_hash << std::endl); + KeccakStream buf; + buf << prefix_hash << ps; + const P3 b_coin_p3(hash_to_good_point_p3(image)); + const PublicKey z = to_bytes(P3(sig.c0 * P3(ps)) + P3(sig.rb * H) + P3(sig.rc * b_coin_p3)); + DEBUG_PRINT(std::cout << "z[" << i << "]=" << z << std::endl); + buf << z; + const auto c = buf.hash_to_scalar() - sig.c0; return sc_iszero(&c) != 0; } KeyDerivation generate_key_derivation(const PublicKey &tx_public_key, const SecretKey &view_secret_key) { check_scalar(view_secret_key); const ge_p3 tx_public_key_p3 = ge_frombytes_vartime(tx_public_key); - const ge_p2 point2 = ge_scalarmult(view_secret_key, tx_public_key_p3); + const ge_p3 point3 = ge_scalarmult3(view_secret_key, tx_public_key_p3); KeyDerivation derivation; - static_cast(derivation) = ge_tobytes(ge_p1p1_to_p2(ge_mul8(point2))); + static_cast(derivation) = ge_tobytes(ge_p1p1_to_p2(ge_mul8(point3))); return derivation; } static SecretKey derivation_to_scalar(const KeyDerivation &derivation, size_t output_index) { - FixedBuffer buf; - buf.append(derivation); - buf.append(output_index); + KeccakStream buf; + buf << derivation << output_index; return buf.hash_to_scalar(); } -PublicKey derive_public_key(const KeyDerivation &derivation, size_t output_index, const PublicKey &spend_public_key) { - const ge_p3 spend_public_key_g3 = ge_frombytes_vartime(spend_public_key); +PublicKey derive_output_public_key(const KeyDerivation &derivation, size_t output_index, const PublicKey &address_S) { + const ge_p3 address_S_g3 = ge_frombytes_vartime(address_S); const EllipticCurveScalar scalar = derivation_to_scalar(derivation, output_index); const ge_cached point3 = ge_p3_to_cached(ge_scalarmult_base(scalar)); ge_p1p1 point_sum; - ge_add(&point_sum, &spend_public_key_g3, &point3); + ge_add(&point_sum, &address_S_g3, &point3); return ge_tobytes(ge_p1p1_to_p2(point_sum)); } -SecretKey derive_secret_key(const KeyDerivation &derivation, size_t output_index, const SecretKey &spend_secret_key) { - check_scalar(spend_secret_key); +SecretKey derive_output_secret_key(const KeyDerivation &derivation, size_t output_index, const SecretKey &address_s) { + check_scalar(address_s); const EllipticCurveScalar scalar = derivation_to_scalar(derivation, output_index); SecretKey output_secret_key; - sc_add(&output_secret_key, &spend_secret_key, &scalar); + sc_add(&output_secret_key, &address_s, &scalar); return output_secret_key; } -PublicKey underive_public_key( - const KeyDerivation &derivation, size_t output_index, const PublicKey &output_public_key) { +PublicKey underive_address_S(const KeyDerivation &derivation, size_t output_index, const PublicKey &output_public_key) { const ge_p3 output_public_key_p3 = ge_frombytes_vartime(output_public_key); const EllipticCurveScalar scalar = derivation_to_scalar(derivation, output_index); const ge_cached point3 = ge_p3_to_cached(ge_scalarmult_base(scalar)); @@ -564,18 +632,14 @@ PublicKey underive_public_key( } Signature generate_sendproof(const PublicKey &txkey_pub, const SecretKey &txkey_sec, - const PublicKey &receiver_view_key_pub, const KeyDerivation &derivation, const Hash &message_hash) { - const ge_p3 receiver_view_key_pub_p3 = ge_frombytes_vartime(receiver_view_key_pub); - const EllipticCurveScalar k = random_scalar(); - FixedBuffer cr_comm; - cr_comm.append(message_hash); - cr_comm.append(txkey_pub); - cr_comm.append(receiver_view_key_pub); - cr_comm.append(derivation); - cr_comm.append(ge_tobytes(ge_scalarmult_base(k))); - - const ge_p2 tmp2 = ge_scalarmult(k, receiver_view_key_pub_p3); - cr_comm.append(ge_tobytes(ge_p1p1_to_p2(ge_mul8(tmp2)))); + const PublicKey &receiver_address_V, const KeyDerivation &derivation, const Hash &message_hash) { + const ge_p3 receiver_address_V_p3 = ge_frombytes_vartime(receiver_address_V); + const EllipticCurveScalar k = random_scalar(); + + KeccakStream cr_comm; + cr_comm << message_hash << txkey_pub << receiver_address_V << derivation << ge_tobytes(ge_scalarmult_base(k)); + const ge_p3 tmp3 = ge_scalarmult3(k, receiver_address_V_p3); + cr_comm.append(ge_tobytes(ge_p1p1_to_p2(ge_mul8(tmp3)))); Signature proof; proof.c = cr_comm.hash_to_scalar(); @@ -583,68 +647,101 @@ Signature generate_sendproof(const PublicKey &txkey_pub, const SecretKey &txkey_ return proof; } -bool check_sendproof(const PublicKey &txkey_pub, const PublicKey &receiver_view_key_pub, - const KeyDerivation &derivation, const Hash &message_hash, const Signature &proof) { +bool check_sendproof(const PublicKey &txkey_pub, const PublicKey &receiver_address_V, const KeyDerivation &derivation, + const Hash &message_hash, const Signature &proof) { if (!sc_isvalid_vartime(&proof.c) || !sc_isvalid_vartime(&proof.r)) return false; ge_p3 txkey_pub_p3; if (ge_frombytes_vartime(&txkey_pub_p3, &txkey_pub) != 0) return false; // tx public keys are not checked by daemon and can be invalid - const ge_p3 receiver_view_key_pub_g3 = ge_frombytes_vartime(receiver_view_key_pub); // checked as part of address + const ge_p3 receiver_address_V_g3 = ge_frombytes_vartime(receiver_address_V); // checked as part of address ge_dsmp derivation_dsmp; - if (!ge_dsm_frombytes_vartime(derivation_dsmp, derivation)) + if (!ge_dsm_frombytes_vartime(&derivation_dsmp, derivation)) return false; - if (ge_check_subgroup_precomp_vartime(derivation_dsmp) != 0) + if (ge_check_subgroup_precomp_vartime(&derivation_dsmp) != 0) return false; - FixedBuffer cr_comm; - cr_comm.append(message_hash); - cr_comm.append(txkey_pub); - cr_comm.append(receiver_view_key_pub); - cr_comm.append(derivation); - cr_comm.append(ge_tobytes(ge_double_scalarmult_base_vartime(proof.c, txkey_pub_p3, proof.r))); - ge_p3 tmp3 = ge_p1p1_to_p3(ge_mul8(ge_p3_to_p2(receiver_view_key_pub_g3))); - cr_comm.append(ge_tobytes(ge_double_scalarmult_precomp_vartime(proof.r, tmp3, proof.c, derivation_dsmp))); + KeccakStream cr_comm; + cr_comm << message_hash << txkey_pub << receiver_address_V << derivation; + cr_comm << ge_tobytes(ge_double_scalarmult_base_vartime3(proof.c, txkey_pub_p3, proof.r)); + ge_p3 tmp3 = ge_p1p1_to_p3(ge_mul8(receiver_address_V_g3)); + cr_comm << ge_tobytes(ge_double_scalarmult_precomp_vartime3(proof.r, tmp3, proof.c, derivation_dsmp)); EllipticCurveScalar h = cr_comm.hash_to_scalar(); sc_sub(&h, &h, &proof.c); return sc_iszero(&h) != 0; } void generate_hd_spendkeys( - const KeyPair &base, const Hash &keys_generation_seed, size_t index, std::vector *result) { - const ge_p3 point_base = ge_frombytes_vartime(base.public_key); - const ge_cached point_base_cached = ge_p3_to_cached(point_base); + const SecretKey &a0, const PublicKey &A_plus_SH, size_t index, std::vector *result) { + const ge_p3 A_plus_SH_p3 = ge_frombytes_vartime(A_plus_SH); + const ge_cached A_plus_SH_cached = ge_p3_to_cached(A_plus_SH_p3); for (size_t d = 0; d != result->size(); ++d) { KeyPair &res = result->at(d); - FixedBuffer cr_comm; - cr_comm.append(keys_generation_seed); - cr_comm.append("address"); - cr_comm.append(index + d); - SecretKey delta_secret_key = cr_comm.hash_to_scalar(); - ge_p3 delta_public_key_g3; - ge_scalarmult_base(&delta_public_key_g3, &delta_secret_key); + KeccakStream cr_comm; + cr_comm << A_plus_SH << "address" << (index + d); + const SecretKey delta_secret_key = cr_comm.hash_to_scalar(); + const ge_p3 delta_public_key_g3 = ge_scalarmult_base(delta_secret_key); ge_p1p1 point_sum; - ge_add(&point_sum, &delta_public_key_g3, &point_base_cached); + ge_add(&point_sum, &delta_public_key_g3, &A_plus_SH_cached); res.public_key = ge_tobytes(ge_p1p1_to_p2(point_sum)); - if (base.secret_key == SecretKey{}) { + if (a0 == SecretKey{}) { res.secret_key = SecretKey{}; } else { - sc_add(&res.secret_key, &delta_secret_key, &base.secret_key); - if (paranoid_checks && !keys_match(res.secret_key, res.public_key)) - throw Error("Invariant failed dring hd address beneration"); + sc_add(&res.secret_key, &delta_secret_key, &a0); } } } -PublicKey generate_address_s_v(const PublicKey &spend_public_key, const SecretKey &view_secret_key) { - const ge_p3 spend_public_key_p3 = ge_frombytes_vartime(spend_public_key); - check_scalar(view_secret_key); - return ge_tobytes(ge_scalarmult(view_secret_key, spend_public_key_p3)); +// base + Hs(seed|index)*mul +PublicKey generate_hd_spendkey( + const PublicKey &v_mul_A_plus_SH, const PublicKey &A_plus_SH, const PublicKey &V, size_t index) { + const ge_p3 v_mul_A_plus_SH_p3 = ge_frombytes_vartime(v_mul_A_plus_SH); + const ge_p3 V_p3 = ge_frombytes_vartime(V); + KeccakStream cr_comm; + cr_comm << A_plus_SH << "address" << index; + const SecretKey delta_secret_key = cr_comm.hash_to_scalar(); + return ge_tobytes(ge_add(v_mul_A_plus_SH_p3, ge_scalarmult3(delta_secret_key, V_p3))); +} + +SecretKey generate_hd_secretkey(const SecretKey &a0, const PublicKey &A_plus_SH, size_t index) { + KeccakStream cr_comm; + cr_comm << A_plus_SH << "address" << index; + SecretKey delta_secret_key = cr_comm.hash_to_scalar(); + SecretKey result; + sc_add(&result, &delta_secret_key, &a0); + return result; +} + +PublicKey A_plus_b_H(const PublicKey &A, const SecretKey &b) { + const ge_p3 A_p3 = ge_frombytes_vartime(A); + return ge_tobytes(ge_add(A_p3, ge_scalarmult3(b, H_p3))); +} + +PublicKey A_plus_B(const PublicKey &A, const PublicKey &B) { return to_bytes(P3(A) + P3(B)); } + +PublicKey A_minus_B(const PublicKey &A, const PublicKey &B) { return to_bytes(P3(A) - P3(B)); } + +PublicKey A_minus_b_H(const PublicKey &A, const SecretKey &b) { + const ge_p3 A_p3 = ge_frombytes_vartime(A); + return ge_tobytes(ge_sub(A_p3, ge_scalarmult3(b, H_p3))); +} + +PublicKey A_mul_b(const PublicKey &A, const SecretKey &b) { + const ge_p3 A_p3 = ge_frombytes_vartime(A); + check_scalar(b); + return ge_tobytes(ge_scalarmult3(b, A_p3)); } -// create map s*G -> WalletRecord +PublicKey secret_keys_to_public_key(const SecretKey &a, const SecretKey &s) { + return ge_tobytes(ge_add(ge_scalarmult_base(a), ge_scalarmult3(s, H_p3))); +} + +// v, s - common for all addresses *** v common for all addresses, s = 0 +// S(i) = (a(i)*G+s*H) *** S(i) = a(i) * G +// V(i) = S(I)*v *** V(i) = v * G +// create map S -> WalletRecord // In tx we generate per-output deterministic secret // q = deterministic(wallet_seed_special_for_k | inputs | #o) @@ -655,57 +752,59 @@ PublicKey generate_address_s_v(const PublicKey &spend_public_key, const SecretKe // In tx, there is 2 values per output T (encrypted output secret) and P (output public key) -// T = K + inv(H(K|inputs|#o))*(v*s*G) *** T = k * (v*G) -// P = inv(H(K|inputs|#o))*s*G *** P = S + H(k*G|inputs|#o)*G +// T = K + inv(H(K|inputs|#o))*V(i) *** T = k * V(i) +// P = inv(H(K|inputs|#o))*S(i) *** P = S(i) + H(k*G|inputs|#o)*G // look for our output // K' = T - P*v *** D = inv(v) * T // S' = H(K'|inputs|#o)*P in map *** S' = P - H(D|inputs|#o)*G -// if found in map, then secret output key -// p = inv(H(K'|inputs|#o))*s *** p = s + H(D|inputs|#o) +// if found in map, then secret output key(s) +// p_s = inv(H(K'|inputs|#o))*s *** p_s = 0 +// p_a = inv(H(K'|inputs|#o))*a(i) *** p_a = a(i) + H(D|inputs|#o) // send_proof -// proof of send to address s*G is -// (Q, txid, message, s*G, v*s*G) *** (Q, txid, message, s*G, v*G) +// proof of send to address s*G is array of +// (Q, txid, message, address) *** (Q, txid, message, address) +// plus count of records (so that proof cannot be split) // signed by q -PublicKey linkable_derive_public_key(const SecretKey &output_secret, const Hash &tx_inputs_hash, size_t output_index, - const PublicKey &spend_public_key, const PublicKey &view_public_key, PublicKey *encrypted_output_secret) { +PublicKey linkable_derive_output_public_key(const SecretKey &output_secret, const Hash &tx_inputs_hash, + size_t output_index, const PublicKey &address_S, const PublicKey &address_V, PublicKey *encrypted_output_secret) { check_scalar(output_secret); - const ge_p3 view_public_key_p3 = ge_frombytes_vartime(view_public_key); - *encrypted_output_secret = ge_tobytes(ge_scalarmult(output_secret, view_public_key_p3)); + const ge_p3 view_secret_key_p3 = ge_frombytes_vartime(address_V); + *encrypted_output_secret = ge_tobytes(ge_scalarmult3(output_secret, view_secret_key_p3)); const EllipticCurvePoint derivation = ge_tobytes(ge_scalarmult_base(output_secret)); // std::cout << "derivation=" << derivation << std::endl; - FixedBuffer cr_comm; - cr_comm.append(derivation); - cr_comm.append(tx_inputs_hash); - cr_comm.append(output_index); + + KeccakStream cr_comm; + cr_comm << derivation << tx_inputs_hash << output_index; const EllipticCurveScalar derivation_hash = cr_comm.hash_to_scalar(); // std::cout << "derivation_hash=" << derivation_hash << std::endl; + const ge_cached point3 = ge_p3_to_cached(ge_scalarmult_base(derivation_hash)); - const ge_p3 spend_public_key_g3 = ge_frombytes_vartime(spend_public_key); + const ge_p3 address_s_g3 = ge_frombytes_vartime(address_S); ge_p1p1 point_sum; - ge_add(&point_sum, &spend_public_key_g3, &point3); + ge_add(&point_sum, &address_s_g3, &point3); return ge_tobytes(ge_p1p1_to_p2(point_sum)); } -PublicKey linkable_underive_public_key(const SecretKey &inv_view_secret_key, const Hash &tx_inputs_hash, +PublicKey linkable_underive_address_S(const SecretKey &inv_view_secret_key, const Hash &tx_inputs_hash, size_t output_index, const PublicKey &output_public_key, const PublicKey &encrypted_output_secret, SecretKey *spend_scalar) { check_scalar(inv_view_secret_key); const ge_p3 encrypted_output_secret_p3 = ge_frombytes_vartime(encrypted_output_secret); - const EllipticCurvePoint derivation = ge_tobytes(ge_scalarmult(inv_view_secret_key, encrypted_output_secret_p3)); + const EllipticCurvePoint derivation = ge_tobytes(ge_scalarmult3(inv_view_secret_key, encrypted_output_secret_p3)); // std::cout << "derivation=" << derivation << std::endl; - FixedBuffer cr_comm; - cr_comm.append(derivation); - cr_comm.append(tx_inputs_hash); - cr_comm.append(output_index); + + KeccakStream cr_comm; + cr_comm << derivation << tx_inputs_hash << output_index; *spend_scalar = cr_comm.hash_to_scalar(); // std::cout << "derivation_hash=" << *spend_scalar << std::endl; + const ge_p3 output_public_key_g3 = ge_frombytes_vartime(output_public_key); const ge_cached point3 = ge_p3_to_cached(ge_scalarmult_base(*spend_scalar)); ge_p1p1 point_diff; @@ -713,35 +812,35 @@ PublicKey linkable_underive_public_key(const SecretKey &inv_view_secret_key, con return ge_tobytes(ge_p1p1_to_p2(point_diff)); } -SecretKey linkable_derive_secret_key(const SecretKey &spend_secret_key, const SecretKey &spend_scalar) { - check_scalar(spend_secret_key); +SecretKey linkable_derive_output_secret_key(const SecretKey &address_s, const SecretKey &spend_scalar) { + check_scalar(address_s); check_scalar(spend_scalar); SecretKey output_secret_key; - sc_add(&output_secret_key, &spend_secret_key, &spend_scalar); + sc_add(&output_secret_key, &address_s, &spend_scalar); return output_secret_key; } void linkable_underive_address(const SecretKey &output_secret, const Hash &tx_inputs_hash, size_t output_index, - const PublicKey &output_public_key, const PublicKey &encrypted_output_secret, PublicKey *spend_public_key, - PublicKey *view_public_key) { + const PublicKey &output_public_key, const PublicKey &encrypted_output_secret, PublicKey *address_S, + PublicKey *address_V) { check_scalar(output_secret); const EllipticCurvePoint derivation = ge_tobytes(ge_scalarmult_base(output_secret)); - FixedBuffer cr_comm; - cr_comm.append(derivation); - cr_comm.append(tx_inputs_hash); - cr_comm.append(output_index); + + KeccakStream cr_comm; + cr_comm << derivation << tx_inputs_hash << output_index; const EllipticCurveScalar derivation_hash = cr_comm.hash_to_scalar(); - const ge_cached point3 = ge_p3_to_cached(ge_scalarmult_base(derivation_hash)); + + const ge_cached point3 = ge_p3_to_cached(ge_scalarmult_base(derivation_hash)); const ge_p3 output_public_key_g3 = ge_frombytes_vartime(output_public_key); ge_p1p1 point_diff; ge_sub(&point_diff, &output_public_key_g3, &point3); - *spend_public_key = ge_tobytes(ge_p1p1_to_p2(point_diff)); + *address_S = ge_tobytes(ge_p1p1_to_p2(point_diff)); const SecretKey inv_output_secret = sc_invert(output_secret); const ge_p3 encrypted_output_secret_g3 = ge_frombytes_vartime(encrypted_output_secret); - *view_public_key = ge_tobytes(ge_scalarmult(inv_output_secret, encrypted_output_secret_g3)); + *address_V = ge_tobytes(ge_scalarmult3(inv_output_secret, encrypted_output_secret_g3)); } void test_linkable() { @@ -753,49 +852,43 @@ void test_linkable() { const SecretKey inv_view_secret_key = sc_invert(view_keypair.secret_key); PublicKey encrypted_output_secret; - PublicKey output_public_key = linkable_derive_public_key(output_secret, tx_inputs_hash, output_index, + PublicKey output_public_key = linkable_derive_output_public_key(output_secret, tx_inputs_hash, output_index, spend_keypair.public_key, view_keypair.public_key, &encrypted_output_secret); SecretKey spend_scalar; - PublicKey spend_public_key2 = linkable_underive_public_key( + PublicKey address_S2 = linkable_underive_address_S( inv_view_secret_key, tx_inputs_hash, output_index, output_public_key, encrypted_output_secret, &spend_scalar); - if (spend_public_key2 != spend_keypair.public_key) + if (address_S2 != spend_keypair.public_key) throw Error("Aha"); - SecretKey output_secret_key2 = linkable_derive_secret_key(spend_keypair.secret_key, spend_scalar); + SecretKey output_secret_key2 = linkable_derive_output_secret_key(spend_keypair.secret_key, spend_scalar); PublicKey output_public_key2; if (!secret_key_to_public_key(output_secret_key2, &output_public_key2) || output_public_key2 != output_public_key) throw Error("Oho"); - PublicKey spend_public_key3; - PublicKey view_public_key3; + PublicKey address_S3; + PublicKey address_V3; linkable_underive_address(output_secret, tx_inputs_hash, output_index, output_public_key, encrypted_output_secret, - &spend_public_key3, &view_public_key3); - if (spend_public_key3 != spend_keypair.public_key || view_public_key3 != view_keypair.public_key) + &address_S3, &address_V3); + if (address_S3 != spend_keypair.public_key || address_V3 != view_keypair.public_key) throw Error("Uhu"); } -PublicKey unlinkable_derive_public_key(const PublicKey &output_secret, const Hash &tx_inputs_hash, size_t output_index, - const PublicKey &spend_public_key, const PublicKey &vs_public_key, PublicKey *encrypted_output_secret) { - // std::cout << "output_secret=" << output_secret << std::endl; - const ge_p3 spend_public_key_p3 = ge_frombytes_vartime(spend_public_key); - const ge_p3 vs_public_key_p3 = ge_frombytes_vartime(vs_public_key); - const ge_p3 output_secret_p3 = ge_frombytes_vartime(output_secret); - - FixedBuffer cr_comm; - cr_comm.append(output_secret); - cr_comm.append(tx_inputs_hash); - cr_comm.append(output_index); - const SecretKey spend_scalar = cr_comm.hash_to_scalar(); - // std::cout << "spend_scalar=" << spend_scalar << std::endl; - const SecretKey inv_spend_scalar = sc_invert(spend_scalar); - // std::cout << "inv_spend_scalar=" << inv_spend_scalar << std::endl; - PublicKey output_public_key = ge_tobytes(ge_scalarmult(inv_spend_scalar, spend_public_key_p3)); +PublicKey unlinkable_derive_output_public_key(const PublicKey &output_secret, const Hash &tx_inputs_hash, + size_t output_index, const PublicKey &address_S, const PublicKey &address_SV, PublicKey *encrypted_output_secret) { + DEBUG_PRINT(std::cout << "output_secret=" << output_secret << std::endl); + const ge_p3 address_s_p3 = ge_frombytes_vartime(address_S); + const ge_p3 address_sv_p3 = ge_frombytes_vartime(address_SV); + const ge_p3 output_secret_p3 = ge_frombytes_vartime(output_secret); - const ge_cached p_v = ge_p3_to_cached(ge_scalarmult3(inv_spend_scalar, vs_public_key_p3)); + KeccakStream cr_comm; + cr_comm << output_secret << tx_inputs_hash << output_index; + const SecretKey spend_scalar = cr_comm.hash_to_scalar(); + DEBUG_PRINT(std::cout << "spend_scalar=" << spend_scalar << std::endl); - ge_p1p1 point_sum; - ge_add(&point_sum, &output_secret_p3, &p_v); + const SecretKey inv_spend_scalar = sc_invert(spend_scalar); + DEBUG_PRINT(std::cout << "inv_spend_scalar=" << inv_spend_scalar << std::endl); + PublicKey output_public_key = ge_tobytes(ge_scalarmult3(inv_spend_scalar, address_s_p3)); - *encrypted_output_secret = ge_tobytes(ge_p1p1_to_p2(point_sum)); + *encrypted_output_secret = ge_tobytes(ge_add(output_secret_p3, ge_scalarmult3(inv_spend_scalar, address_sv_p3))); return output_public_key; } @@ -806,50 +899,82 @@ PublicKey unlinkable_derive_public_key(const PublicKey &output_secret, const Has // K' = T - P*v *** D = inv(v) * T // S' = H(K'|inputs|#o)*P in map *** S' = P - H(D|inputs|#o)*G -PublicKey unlinkable_underive_public_key(const SecretKey &view_secret_key, const Hash &tx_inputs_hash, +PublicKey unlinkable_underive_address_S(const SecretKey &view_secret_key, const Hash &tx_inputs_hash, size_t output_index, const PublicKey &output_public_key, const PublicKey &encrypted_output_secret, SecretKey *spend_scalar) { check_scalar(view_secret_key); const ge_p3 output_public_key_p3 = ge_frombytes_vartime(output_public_key); const ge_p3 encrypted_output_secret_p3 = ge_frombytes_vartime(encrypted_output_secret); - const ge_cached p_v = ge_p3_to_cached(ge_scalarmult3(view_secret_key, output_public_key_p3)); + // const ge_cached p_v_cached = ge_p3_to_cached(); + // ge_p1p1 point_diff; + // ge_sub(&point_diff, &encrypted_output_secret_p3, &p_v_cached); + + const PublicKey output_secret = + ge_tobytes(ge_sub(encrypted_output_secret_p3, ge_scalarmult3(view_secret_key, output_public_key_p3))); + DEBUG_PRINT(std::cout << "output_secret=" << output_secret << std::endl); + + KeccakStream cr_comm; + cr_comm << output_secret << tx_inputs_hash << output_index; + *spend_scalar = cr_comm.hash_to_scalar(); + DEBUG_PRINT(std::cout << "spend_scalar=" << *spend_scalar << std::endl); + + PublicKey result = ge_tobytes(ge_scalarmult3(*spend_scalar, output_public_key_p3)); + { + PublicKey P_v2 = unlinkable_underive_address_S_step1(view_secret_key, output_public_key); + SecretKey spend_scalar2; + PublicKey result2 = unlinkable_underive_address_S_step2( + P_v2, tx_inputs_hash, output_index, output_public_key, encrypted_output_secret, &spend_scalar2); + if (result != result2 || *spend_scalar != spend_scalar2) + throw Error("unlinkable_underive_public_key steps error"); + } + return result; +} + +PublicKey unlinkable_underive_address_S_step1(const SecretKey &view_secret_key, const PublicKey &output_public_key) { + check_scalar(view_secret_key); + const ge_p3 output_public_key_p3 = ge_frombytes_vartime(output_public_key); + return ge_tobytes(ge_scalarmult3(view_secret_key, output_public_key_p3)); +} + +PublicKey unlinkable_underive_address_S_step2(const PublicKey &P_v, const Hash &tx_inputs_hash, size_t output_index, + const PublicKey &output_public_key, const PublicKey &encrypted_output_secret, SecretKey *spend_scalar) { + const ge_p3 p_v_p3 = ge_frombytes_vartime(P_v); + const ge_p3 output_public_key_p3 = ge_frombytes_vartime(output_public_key); + const ge_p3 encrypted_output_secret_p3 = ge_frombytes_vartime(encrypted_output_secret); + const ge_cached p_v_cached = ge_p3_to_cached(p_v_p3); ge_p1p1 point_diff; - ge_sub(&point_diff, &encrypted_output_secret_p3, &p_v); + ge_sub(&point_diff, &encrypted_output_secret_p3, &p_v_cached); const PublicKey output_secret = ge_tobytes(ge_p1p1_to_p2(point_diff)); // std::cout << "output_secret=" << output_secret << std::endl; - FixedBuffer cr_comm; - cr_comm.append(output_secret); - cr_comm.append(tx_inputs_hash); - cr_comm.append(output_index); + KeccakStream cr_comm; + cr_comm << output_secret << tx_inputs_hash << output_index; *spend_scalar = cr_comm.hash_to_scalar(); // std::cout << "spend_scalar=" << *spend_scalar << std::endl; - return ge_tobytes(ge_scalarmult(*spend_scalar, output_public_key_p3)); + return ge_tobytes(ge_scalarmult3(*spend_scalar, output_public_key_p3)); } -SecretKey unlinkable_derive_secret_key(const SecretKey &spend_secret_key, const SecretKey &spend_scalar) { - check_scalar(spend_secret_key); +SecretKey unlinkable_derive_output_secret_key(const SecretKey &address_secret, const SecretKey &spend_scalar) { + check_scalar(address_secret); check_scalar(spend_scalar); const SecretKey inv_spend_scalar = sc_invert(spend_scalar); - SecretKey output_secret_key; - sc_mul(&output_secret_key, &inv_spend_scalar, &spend_secret_key); - return output_secret_key; + return sc_mul(inv_spend_scalar, address_secret); } -void unlinkable_underive_address(const PublicKey &output_secret, const Hash &tx_inputs_hash, size_t output_index, - const PublicKey &output_public_key, const PublicKey &encrypted_output_secret, PublicKey *spend_public_key, - PublicKey *vs_public_key) { +void unlinkable_underive_address(PublicKey *address_S, PublicKey *address_Sv, const PublicKey &output_secret, + const Hash &tx_inputs_hash, size_t output_index, const PublicKey &output_public_key, + const PublicKey &encrypted_output_secret) { const ge_p3 output_public_key_p3 = ge_frombytes_vartime(output_public_key); const ge_p3 output_secret_p3 = ge_frombytes_vartime(output_secret); const ge_p3 encrypted_output_secret_p3 = ge_frombytes_vartime(encrypted_output_secret); - FixedBuffer cr_comm; - cr_comm.append(output_secret); - cr_comm.append(tx_inputs_hash); - cr_comm.append(output_index); + + KeccakStream cr_comm; + cr_comm << output_secret << tx_inputs_hash << output_index; const SecretKey spend_scalar = cr_comm.hash_to_scalar(); - *spend_public_key = ge_tobytes(ge_scalarmult(spend_scalar, output_public_key_p3)); + + *address_S = ge_tobytes(ge_scalarmult3(spend_scalar, output_public_key_p3)); const ge_cached p_v = ge_p3_to_cached(output_secret_p3); ge_p1p1 point_diff; @@ -857,60 +982,91 @@ void unlinkable_underive_address(const PublicKey &output_secret, const Hash &tx_ const ge_p3 t_minus_k = ge_p1p1_to_p3(point_diff); - *vs_public_key = ge_tobytes(ge_scalarmult(spend_scalar, t_minus_k)); + *address_Sv = ge_tobytes(ge_scalarmult3(spend_scalar, t_minus_k)); } void test_unlinkable() { const PublicKey output_secret = random_keypair().public_key; const Hash tx_inputs_hash = rand(); - const size_t output_index = rand(); + const size_t output_index = rand() % 100; const KeyPair spend_keypair = random_keypair(); - const KeyPair view_keypair = random_keypair(); - const PublicKey address_s_v = generate_address_s_v(spend_keypair.public_key, view_keypair.secret_key); + // KeyPair spend_keypair; + // secret_key_to_public_key(spend_keypair.secret_key, &spend_keypair.public_key); + const KeyPair view_keypair = random_keypair(); + const KeyPair audit_key_base_pair = random_keypair(); + + // auto A_plus_SH = A_plus_b_H(audit_key_base_pair.public_key, spend_keypair.secret_key); + + // std::vector key_result; + // key_result.resize(result.size()); + // crypto::generate_hd_spendkeys(m_audit_key_base.secret_key, m_A_plus_SH, counter, &key_result); + + const PublicKey address_S = A_plus_b_H(audit_key_base_pair.public_key, spend_keypair.secret_key); + const PublicKey address_Sv = A_mul_b(address_S, view_keypair.secret_key); + + std::cout << "address_S=" << address_S << std::endl; + std::cout << "address_Sv=" << address_Sv << std::endl; PublicKey encrypted_output_secret; - PublicKey output_public_key = unlinkable_derive_public_key( - output_secret, tx_inputs_hash, output_index, spend_keypair.public_key, address_s_v, &encrypted_output_secret); + PublicKey output_public_key = unlinkable_derive_output_public_key( + output_secret, tx_inputs_hash, output_index, address_S, address_Sv, &encrypted_output_secret); SecretKey spend_scalar; - PublicKey spend_public_key2 = unlinkable_underive_public_key(view_keypair.secret_key, tx_inputs_hash, output_index, + PublicKey address_S2 = unlinkable_underive_address_S(view_keypair.secret_key, tx_inputs_hash, output_index, output_public_key, encrypted_output_secret, &spend_scalar); - if (spend_public_key2 != spend_keypair.public_key) + std::cout << "address_s2=" << address_S2 << std::endl; + if (address_S2 != address_S) throw Error("Aha"); - SecretKey output_secret_key2 = unlinkable_derive_secret_key(spend_keypair.secret_key, spend_scalar); - PublicKey output_public_key2; - if (!secret_key_to_public_key(output_secret_key2, &output_public_key2) || output_public_key2 != output_public_key) + SecretKey output_secret_key2_s = unlinkable_derive_output_secret_key(spend_keypair.secret_key, spend_scalar); + SecretKey output_secret_key2_a = unlinkable_derive_output_secret_key(audit_key_base_pair.secret_key, spend_scalar); + PublicKey output_public_key2 = secret_keys_to_public_key(output_secret_key2_a, output_secret_key2_s); + if (output_public_key2 != output_public_key) throw Error("Oho"); - PublicKey spend_public_key3; - PublicKey address_s_v3; - unlinkable_underive_address(output_secret, tx_inputs_hash, output_index, output_public_key, encrypted_output_secret, - &spend_public_key3, &address_s_v3); - if (spend_public_key3 != spend_keypair.public_key || address_s_v3 != address_s_v) + const auto keyimage = generate_key_image(output_public_key, output_secret_key2_a); + PublicKey address_S3; + PublicKey address_Sv3; + unlinkable_underive_address(&address_S3, &address_Sv3, output_secret, tx_inputs_hash, output_index, + output_public_key, encrypted_output_secret); + if (address_S3 != address_S || address_Sv3 != address_Sv) throw Error("Uhu"); + std::vector images{keyimage}; + std::vector> pubs(1); + pubs.back().push_back(random_keypair().public_key); + pubs.back().push_back(random_keypair().public_key); + pubs.back().push_back(output_public_key); + pubs.back().push_back(random_keypair().public_key); + + std::vector sec_s{output_secret_key2_s}; + std::vector sec_a{output_secret_key2_a}; + std::vector sec_indexes{2}; + + const Hash tx_prefix_hash = rand(); + auto sig = generate_ring_signature_auditable(tx_prefix_hash, images, pubs, sec_s, sec_a, sec_indexes); + if (!check_ring_signature_auditable(tx_prefix_hash, images, pubs, sig)) + throw Error("Yhy"); + sig.ra.back().back().data[0] += 1; + if (check_ring_signature_auditable(tx_prefix_hash, images, pubs, sig)) + throw Error("Xhx"); } -Signature amethyst_generate_sendproof(const KeyPair &output_keys, const Hash &tid, const Hash &message_hash, - const PublicKey &address_spend_key, const PublicKey &address_other_key) { - FixedBuffer<3 * sizeof(PublicKey) + 2 * sizeof(Hash)> cr_comm; - cr_comm.append(output_keys.public_key); - cr_comm.append(tid); - cr_comm.append(message_hash); - cr_comm.append(address_spend_key); - cr_comm.append(address_other_key); - const Hash hash = cr_comm.cn_fast_hash(); - return generate_signature(hash, output_keys.public_key, output_keys.secret_key); -} - -bool amethyst_check_sendproof(const PublicKey &output_public_key, const Hash &tid, const Hash &message_hash, - const PublicKey &address_spend_key, const PublicKey &address_other_key, const Signature &sig) { - FixedBuffer<3 * sizeof(PublicKey) + 2 * sizeof(Hash)> cr_comm; - cr_comm.append(output_public_key); - cr_comm.append(tid); - cr_comm.append(message_hash); - cr_comm.append(address_spend_key); - cr_comm.append(address_other_key); - const Hash hash = cr_comm.cn_fast_hash(); - return check_signature(hash, output_public_key, sig); +// outputs_count protects against splitting proof into valid proofs with smaller amounts +// of course, send proof creator can choose to include any set of outputs +/*Signature amethyst_generate_sendproof(const KeyPair &output_det_keys, const Hash &tid, const Hash &message_hash, + const std::string &address, size_t outputs_count) { + KeccakStream cr_comm; + cr_comm << tid << message_hash << outputs_count; + cr_comm.append(address.data(), address.size()); + const Hash hash = cr_comm.cn_fast_hash(); + return generate_signature(hash, output_det_keys.public_key, output_det_keys.secret_key); } +bool amethyst_check_sendproof(const PublicKey &output_det_key, const Hash &tid, const Hash &message_hash, + const std::string &address, size_t outputs_count, const Signature &sig) { + KeccakStream cr_comm; + cr_comm << tid << message_hash << outputs_count; + cr_comm.append(address.data(), address.size()); + const Hash hash = cr_comm.cn_fast_hash(); + return check_signature(hash, output_det_key, sig); +}*/ + } // namespace crypto diff --git a/src/crypto/crypto.hpp b/src/crypto/crypto.hpp index ecfd33ae..1314dc46 100644 --- a/src/crypto/crypto.hpp +++ b/src/crypto/crypto.hpp @@ -54,17 +54,58 @@ inline KeyPair random_keypair() { return k; } +class KeccakStream { + cryptoKeccakHasher impl; + +public: + KeccakStream() { crypto_keccak_init(&impl, 256, 1); } + KeccakStream &append(const void *buf, size_t count) { + crypto_keccak_update(&impl, buf, count); + return *this; + } + template + KeccakStream &append(const char (&h)[S]) { + append(h, S - 1); + return *this; + } + KeccakStream &append_byte(uint8_t byte) { + append(&byte, 1); + return *this; + } + KeccakStream &append(size_t i); // varint + KeccakStream &append(const Hash &h) { return append(h.data, sizeof(h.data)); } + KeccakStream &append(const EllipticCurvePoint &h) { return append(h.data, sizeof(h.data)); } + KeccakStream &append(const EllipticCurveScalar &h) { return append(h.data, sizeof(h.data)); } + Hash cn_fast_hash() { + Hash result; + crypto_keccak_final(&impl, result.data, sizeof(result.data)); + return result; + } + SecretKey hash_to_scalar(); + SecretKey hash_to_scalar64(); + PublicKey hash_to_point(); +}; + +template +KeccakStream &operator<<(KeccakStream &buffer, const T &value) { + buffer.append(value); + return buffer; +} + // Check a public key. Returns true if it is valid, false otherwise. bool key_isvalid(const PublicKey &key); +bool key_in_main_subgroup(const EllipticCurvePoint &key); // Checks a private key and computes the corresponding public key. bool secret_key_to_public_key(const SecretKey &sec, PublicKey *pub); bool keys_match(const SecretKey &secret_key, const PublicKey &expected_public_key); // returns false if keys are corrupted/invalid Signature generate_signature(const Hash &prefix_hash, const PublicKey &pub, const SecretKey &sec); - bool check_signature(const Hash &prefix_hash, const PublicKey &pub, const Signature &sig); +Signature generate_signature_H(const Hash &prefix_hash, const PublicKey &sec_H, const SecretKey &sec); +bool check_signature_H(const Hash &prefix_hash, const PublicKey &sec_H, const Signature &sig); + // To send money to a key: // The sender generates an ephemeral key and includes it in transaction output. // To spend the money, the receiver generates a key image from it. @@ -79,26 +120,30 @@ RingSignature generate_ring_signature(const Hash &prefix_hash, const KeyImage &i std::size_t pubs_count, const SecretKey &sec, std::size_t sec_index); bool check_ring_signature(const Hash &prefix_hash, const KeyImage &image, const PublicKey pubs[], size_t pubs_count, - const RingSignature &sig, bool key_image_subgroup_check); + const RingSignature &sig); -RingSignature3 generate_ring_signature3(const Hash &prefix_hash, const std::vector &images, - const std::vector> &pubs, const std::vector &secs, - const std::vector &sec_indexes, const SecretKey &view_secret_key); -// returns false if keys are corrupted/invalid +RingSignatureAmethyst generate_ring_signature_auditable(const Hash &prefix_hash, const std::vector &images, + const std::vector> &pubs, const std::vector &secs_spend, + const std::vector &secs_audit, const std::vector &sec_indexes); +bool check_ring_signature_auditable(const Hash &prefix_hash, const std::vector &images, + const std::vector> &pubs, const RingSignatureAmethyst &sig); -bool check_ring_signature3(const Hash &prefix_hash, const std::vector &image, - const std::vector> &pubs, const RingSignature3 &sig); +SendproofSignatureAmethyst generate_sendproof_signature_auditable( + const Hash &prefix_hash, const KeyImage &image, const SecretKey &sec_spend, const SecretKey &sec_audit); +bool check_sendproof_signature_auditable( + const Hash &prefix_hash, const KeyImage &image, const PublicKey &ps, const SendproofSignatureAmethyst &sig); SecretKey hash_to_scalar(const void *data, size_t length); SecretKey hash_to_scalar64(const void *data, size_t length); -PublicKey hash_to_point(const void *data, size_t length); -EllipticCurvePoint hash_to_point_for_tests(const Hash &h); // Used only in tests -PublicKey hash_to_ec(const PublicKey &key); -// result size should be set to number of desired spend keys -void generate_hd_spendkeys( - const KeyPair &base, const Hash &keys_generation_seed, size_t index, std::vector *result); -PublicKey generate_address_s_v(const PublicKey &spend_public_key, const SecretKey &view_secret_key); +// any 32 bytes into valid point (potentially outside main subgroup) +PublicKey bytes_to_bad_point(const Hash &h); +// hash of (data, length) into valid point (potentially outside main subgroup) +PublicKey hash_to_bad_point(const void *data, size_t length); + +// hash of (data, length) into valid point (inside main subgroup) +PublicKey hash_to_good_point(const void *data, size_t length); +inline PublicKey hash_to_good_point(const PublicKey &key) { return hash_to_good_point(key.data, sizeof(key.data)); } // Legacy crypto // To generate an ephemeral key used to send money to: @@ -109,59 +154,92 @@ PublicKey generate_address_s_v(const PublicKey &spend_public_key, const SecretKe // The receiver can either derive the public key (to check that the transaction is addressed to him) or the private key // (to spend the money). +// shared secret - +// tx_public_key * view_secret_key for receiver +// tx_secret_key * address_V for sender KeyDerivation generate_key_derivation(const PublicKey &tx_public_key, const SecretKey &view_secret_key); -PublicKey derive_public_key(const KeyDerivation &derivation, size_t output_index, const PublicKey &spend_public_key); +PublicKey derive_output_public_key(const KeyDerivation &derivation, size_t output_index, const PublicKey &address_S); -PublicKey underive_public_key(const KeyDerivation &derivation, size_t output_index, const PublicKey &output_public_key); +PublicKey underive_address_S(const KeyDerivation &derivation, size_t output_index, const PublicKey &output_public_key); -SecretKey derive_secret_key( - const KeyDerivation &derivation, std::size_t output_index, const SecretKey &spend_secret_key); +SecretKey derive_output_secret_key( + const KeyDerivation &derivation, std::size_t output_index, const SecretKey &address_s); Signature generate_sendproof(const PublicKey &txkey_pub, const SecretKey &txkey_sec, - const PublicKey &receiver_view_key_pub, const KeyDerivation &derivation, const Hash &message_hash); + const PublicKey &receiver_address_V, const KeyDerivation &derivation, const Hash &message_hash); // Transaction key and the derivation supplied with the proof can be invalid, this just means that the proof is invalid. -bool check_sendproof(const PublicKey &txkey_pub, const PublicKey &receiver_view_key_pub, - const KeyDerivation &derivation, const Hash &message_hash, const Signature &proof); +bool check_sendproof(const PublicKey &txkey_pub, const PublicKey &receiver_address_V, const KeyDerivation &derivation, + const Hash &message_hash, const Signature &proof); // Linkable crypto, spend_scalar is temporary value that is expensive to calc, we pass it around // Old addresses use improved crypto in amethyst, because we need to enforce unique output public keys // on crypto level. Enforcing on daemon DB index level does not work (each of 2 solutions is vulnerable attack). -PublicKey linkable_derive_public_key(const SecretKey &output_secret, const Hash &tx_inputs_hash, size_t output_index, - const PublicKey &spend_public_key, const PublicKey &view_public_key, PublicKey *encrypted_output_secret); +// sender, sending +PublicKey linkable_derive_output_public_key(const SecretKey &output_secret, const Hash &tx_inputs_hash, + size_t output_index, const PublicKey &address_S, const PublicKey &address_V, PublicKey *encrypted_output_secret); -PublicKey linkable_underive_public_key(const SecretKey &inv_view_secret_key, const Hash &tx_inputs_hash, +// receiver lloking for outputs +PublicKey linkable_underive_address_S(const SecretKey &inv_view_secret_key, const Hash &tx_inputs_hash, size_t output_index, const PublicKey &output_public_key, const PublicKey &encrypted_output_secret, - Hash *spend_scalar); + SecretKey *spend_scalar); -SecretKey linkable_derive_secret_key(const SecretKey &spend_secret_key, const SecretKey &spend_scalar); +// receiver +SecretKey linkable_derive_output_secret_key(const SecretKey &address_s, const SecretKey &spend_scalar); +// sender, restoring destination address void linkable_underive_address(const SecretKey &output_secret, const Hash &tx_inputs_hash, size_t output_index, - const PublicKey &output_public_key, const PublicKey &encrypted_output_secret, PublicKey *spend_public_key, - PublicKey *view_public_key); + const PublicKey &output_public_key, const PublicKey &encrypted_output_secret, PublicKey *address_S, + PublicKey *address_V); void test_linkable(); // Unlinkable crypto, spend_scalar is temporary value that is expensive to calc, we pass it around -PublicKey unlinkable_derive_public_key(const PublicKey &output_secret, const Hash &tx_inputs_hash, size_t output_index, - const PublicKey &spend_public_key, const PublicKey &vs_public_key, PublicKey *encrypted_output_secret); -PublicKey unlinkable_underive_public_key(const SecretKey &view_secret_key, const Hash &tx_inputs_hash, +// result size should be set to number of desired spend keys +void generate_hd_spendkeys(const SecretKey &a0, const PublicKey &A_plus_SH, size_t index, std::vector *result); +PublicKey generate_hd_spendkey( + const PublicKey &v_mul_A_plus_SH, const PublicKey &A_plus_SH, const PublicKey &V, size_t index); +// generate_hd_secretkey function emulate hardware wallet +SecretKey generate_hd_secretkey(const SecretKey &a0, const PublicKey &A_plus_SH, size_t index); + +PublicKey A_plus_b_H(const PublicKey &A, const SecretKey &b); +PublicKey A_plus_B(const PublicKey &A, const PublicKey &B); +PublicKey A_minus_B(const PublicKey &A, const PublicKey &B); +PublicKey A_minus_b_H(const PublicKey &A, const SecretKey &b); +PublicKey A_mul_b(const PublicKey &A, const SecretKey &b); + +// a*G + s*H +PublicKey secret_keys_to_public_key(const SecretKey &a, const SecretKey &s); + +// sender sending +PublicKey unlinkable_derive_output_public_key(const PublicKey &output_secret, const Hash &tx_inputs_hash, + size_t output_index, const PublicKey &address_S, const PublicKey &address_SV, PublicKey *encrypted_output_secret); + +// receiver looking for outputs +PublicKey unlinkable_underive_address_S(const SecretKey &view_secret_key, const Hash &tx_inputs_hash, size_t output_index, const PublicKey &output_public_key, const PublicKey &encrypted_output_secret, SecretKey *spend_scalar); -SecretKey unlinkable_derive_secret_key(const SecretKey &spend_secret_key, const SecretKey &spend_scalar); +// 2-step functions emulate hardware wallet +PublicKey unlinkable_underive_address_S_step1(const SecretKey &view_secret_key, const PublicKey &output_public_key); +PublicKey unlinkable_underive_address_S_step2(const PublicKey &P_v, const Hash &tx_inputs_hash, size_t output_index, + const PublicKey &output_public_key, const PublicKey &encrypted_output_secret, SecretKey *spend_scalar); -void unlinkable_underive_address(const PublicKey &output_secret, const Hash &tx_inputs_hash, size_t output_index, - const PublicKey &output_public_key, const PublicKey &encrypted_output_secret, PublicKey *spend_public_key, - PublicKey *vs_public_key); -void test_unlinkable(); +SecretKey unlinkable_derive_output_secret_key(const SecretKey &address_secret, const SecretKey &spend_scalar); +// address_secret can be audit_secret_key or spend_secret_key -Signature amethyst_generate_sendproof(const KeyPair &output_keys, const Hash &tid, const Hash &message_hash, - const PublicKey &address_spend_key, const PublicKey &address_other_key); +// sender, restoring destination address +void unlinkable_underive_address(PublicKey *address_S, PublicKey *address_Sv, const PublicKey &output_secret, + const Hash &tx_inputs_hash, size_t output_index, const PublicKey &output_public_key, + const PublicKey &encrypted_output_secret); +void test_unlinkable(); -bool amethyst_check_sendproof(const PublicKey &output_public_key, const Hash &tid, const Hash &message_hash, - const PublicKey &address_spend_key, const PublicKey &address_other_key, const Signature &sig); +/*Signature amethyst_generate_sendproof(const KeyPair &output_det_keys, const Hash &tid, const Hash &message_hash, + const std::string &address, size_t outputs_count); +bool amethyst_check_sendproof(const PublicKey &output_det_key, const Hash &tid, const Hash &message_hash, + const std::string &address, size_t outputs_count, const Signature &sig); +*/ } // namespace crypto diff --git a/src/crypto/crypto_helpers.hpp b/src/crypto/crypto_helpers.hpp new file mode 100644 index 00000000..3f012412 --- /dev/null +++ b/src/crypto/crypto_helpers.hpp @@ -0,0 +1,321 @@ +// Copyright (c) 2012-2018, The CryptoNote developers, The Bytecoin developers. +// Licensed under the GNU Lesser General Public License. See LICENSE for details. + +#pragma once + +#include "bernstein/crypto-ops.h" +#include "crypto.hpp" +#include "hash.hpp" + +// Helpers that allow to write normal "x = f(y)" code instead of stupid f(&x, &y) + +// Experimental helpers that allow to write normal a = b * c + d + +namespace crypto { + +struct P3MulResult { + const ge_p3 &p3; + const EllipticCurveScalar &s; + P3MulResult(const ge_p3 &p3, const EllipticCurveScalar &s) : p3(p3), s(s) {} +}; +struct P3MulResultG { + const EllipticCurveScalar &s; + P3MulResultG(const EllipticCurveScalar &s) : s(s) {} +}; + +struct G3_type {}; + +struct P3 { + ge_p3 p3; + + constexpr P3() : p3{{0}, {1, 0}, {1, 0}, {0}} { // identity point + } + constexpr P3(const ge_p3 &other) : p3(other) {} + P3(const G3_type &other) { // TODO - better impl + SecretKey one; + sc_1(&one); + ge_scalarmult_base(&p3, &one); + } + explicit P3(const EllipticCurvePoint &other) { + if (ge_frombytes_vartime(&p3, &other) != 0) + throw Error("Public Key Invalid"); + } + P3(const P3MulResult &other) { ge_scalarmult3(&p3, &other.s, &other.p3); } + P3(const P3MulResultG &other) { ge_scalarmult_base(&p3, &other.s); } +}; + +inline PublicKey to_bytes(const P3 &other) { + PublicKey result; + ge_p3_tobytes(&result, &other.p3); + return result; +} +template +T to_bytes(const P3 &other) { + T result; + ge_p3_tobytes(&result, &other.p3); + return result; +} + +constexpr G3_type G{}; +constexpr P3 I{ge_p3{{0}, {1, 0}, {1, 0}, {0}}}; +constexpr P3 H{ge_p3{{7329926, -15101362, 31411471, 7614783, 27996851, -3197071, -11157635, -6878293, 466949, -7986503}, + {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}}}; + +inline P3 &operator*=(P3 &point_base, const EllipticCurveScalar &sec) { + point_base = P3MulResult(point_base.p3, sec); + return point_base; +} +inline P3MulResultG operator*(const G3_type &, const EllipticCurveScalar &sec) { return P3MulResultG(sec); } +inline P3MulResultG operator*(const EllipticCurveScalar &sec, const G3_type &) { return P3MulResultG(sec); } + +inline P3MulResult operator*(const P3 &point_base, const EllipticCurveScalar &sec) { + return P3MulResult(point_base.p3, sec); +} +inline P3MulResult operator*(const EllipticCurveScalar &sec, const P3 &point_base) { + return P3MulResult(point_base.p3, sec); +} +inline P3 operator-(const P3 &a, const P3 &b) { + ge_cached b_cached; + ge_p3_to_cached(&b_cached, &b.p3); + ge_p1p1 result_p1p1; + ge_sub(&result_p1p1, &a.p3, &b_cached); + P3 result; + ge_p1p1_to_p3(&result.p3, &result_p1p1); + return result; +} +inline P3 &operator-=(P3 &a, const P3 &b) { + a = a - b; + return a; +} +inline P3 operator+(const P3 &a, const P3 &b) { + ge_cached b_cached; + ge_p3_to_cached(&b_cached, &b.p3); + ge_p1p1 result_p1p1; + ge_add(&result_p1p1, &a.p3, &b_cached); + P3 result; + ge_p1p1_to_p3(&result.p3, &result_p1p1); + return result; +} +inline P3 &operator+=(P3 &a, const P3 &b) { + a = a + b; + return a; +} +inline P3 operator+(const P3MulResult &r1, const P3MulResult &r2) { + // return P3(r1) + P3(r2); + ge_dsmp dsm; + ge_dsm_precomp(&dsm, &r2.p3); + P3 res_p3; + ge_double_scalarmult_precomp_vartime3(&res_p3.p3, &r1.s, &r1.p3, &r2.s, &dsm); + return res_p3; +} +// TODO ge_double_scalarmult_base_vartime3 +inline P3 operator+(const P3MulResultG &r1, const P3MulResult &r2) { + P3 res_p3; + ge_double_scalarmult_base_vartime3(&res_p3.p3, &r2.s, &r2.p3, &r1.s); + return res_p3; +} +inline P3 operator+(const P3MulResult &r1, const P3MulResultG &r2) { return r2 + r1; } + +struct ScalarMulResult { + const EllipticCurveScalar &a; + const EllipticCurveScalar &b; + ScalarMulResult(const EllipticCurveScalar &a, const EllipticCurveScalar &b) : a(a), b(b) {} + operator SecretKey() { + SecretKey result; + sc_mul(&result, &a, &b); + return result; + } +}; +inline ScalarMulResult operator*(const EllipticCurveScalar &a, const EllipticCurveScalar &b) { + return ScalarMulResult(a, b); +} +inline EllipticCurveScalar &operator*=(EllipticCurveScalar &a, const EllipticCurveScalar &b) { + a = ScalarMulResult(a, b); + return a; +} + +inline SecretKey operator-(const EllipticCurveScalar &c, const ScalarMulResult &ab) { + SecretKey result; + sc_mulsub(&result, &ab.a, &ab.b, &c); + return result; +} +inline EllipticCurveScalar &operator-=(EllipticCurveScalar &c, const ScalarMulResult &ab) { + c = c - ab; + return c; +} +inline SecretKey operator-(const EllipticCurveScalar &a, const EllipticCurveScalar &b) { + SecretKey result; + sc_sub(&result, &a, &b); + return result; +} +inline EllipticCurveScalar &operator-=(EllipticCurveScalar &a, const EllipticCurveScalar &b) { + a = a - b; + return a; +} +inline SecretKey operator+(const EllipticCurveScalar &a, const EllipticCurveScalar &b) { + SecretKey result; + sc_add(&result, &a, &b); + return result; +} +inline EllipticCurveScalar &operator+=(EllipticCurveScalar &a, const EllipticCurveScalar &b) { + a = a + b; + return a; +} + +PublicKey get_G(); // slow, for reference only +PublicKey get_H(); // slow, for reference only +const ge_p3 &get_H_p3(); // fast +PublicKey test_get_H(); // performs actual steps to calc H_p3 + +inline SecretKey sc_invert(const EllipticCurveScalar &sec) { + SecretKey result; + sc_invert(&result, &sec); + return result; +} + +inline ge_p3 ge_scalarmult_base(const EllipticCurveScalar &sec) { + ge_p3 point; + ge_scalarmult_base(&point, &sec); + return point; +} + +inline PublicKey ge_tobytes(const ge_p3 &point3) { + PublicKey result; + ge_p3_tobytes(&result, &point3); + return result; +} + +inline PublicKey ge_tobytes(const ge_p2 &point2) { + PublicKey result; + ge_tobytes(&result, &point2); + return result; +} + +inline void check_scalar(const EllipticCurveScalar &scalar) { + if (!sc_isvalid_vartime(&scalar)) + throw Error("Secret Key Invalid"); +} + +inline ge_p3 ge_frombytes_vartime(const EllipticCurvePoint &point) { + ge_p3 result_p3; + if (ge_frombytes_vartime(&result_p3, &point) != 0) + throw Error("Public Key Invalid"); + return result_p3; +} + +inline ge_p3 ge_scalarmult3(const EllipticCurveScalar &sec, const ge_p3 &point_base) { + ge_p3 point3; + ge_scalarmult3(&point3, &sec, &point_base); + return point3; +} + +inline ge_p3 ge_double_scalarmult_base_vartime3( + const EllipticCurveScalar &a, const ge_p3 &A, const EllipticCurveScalar &b) { + ge_p3 tmp3; + ge_double_scalarmult_base_vartime3(&tmp3, &a, &A, &b); + return tmp3; +} + +inline ge_p3 ge_double_scalarmult_precomp_vartime3( + const EllipticCurveScalar &a, const ge_p3 &A, const EllipticCurveScalar &b, const ge_dsmp &B) { + ge_p3 tmp3; + ge_double_scalarmult_precomp_vartime3(&tmp3, &a, &A, &b, &B); + return tmp3; +} + +/*inline ge_p2 ge_double_scalarmult_base_vartime( + const EllipticCurveScalar &a, const ge_p3 &A, const EllipticCurveScalar &b) { + ge_p2 tmp3; + ge_double_scalarmult_base_vartime(&tmp3, &a, &A, &b); + return tmp3; +} + +inline ge_p2 ge_double_scalarmult_precomp_vartime( + const EllipticCurveScalar &a, const ge_p3 &A, const EllipticCurveScalar &b, const ge_dsmp &B) { + ge_p2 tmp3; + ge_double_scalarmult_precomp_vartime(&tmp3, &a, &A, &b, &B); + return tmp3; +}*/ + +inline bool ge_dsm_frombytes_vartime(ge_dsmp *image_dsm, const EllipticCurvePoint &image) { + ge_p3 image_p3; + if (ge_frombytes_vartime(&image_p3, &image) != 0) + return false; + ge_dsm_precomp(image_dsm, &image_p3); + return true; +} +inline ge_p1p1 ge_mul8(const ge_p3 &p3) { + ge_p1p1 p1; + ge_mul8(&p1, &p3); + return p1; +} +inline ge_p1p1 ge_mul8_p2(const ge_p2 &p2) { + ge_p1p1 p1; + ge_mul8_p2(&p1, &p2); + return p1; +} + +inline ge_p2 ge_p1p1_to_p2(const ge_p1p1 &p1) { + ge_p2 p2; + ge_p1p1_to_p2(&p2, &p1); + return p2; +} + +inline ge_p3 ge_p1p1_to_p3(const ge_p1p1 &p1) { + ge_p3 p3; + ge_p1p1_to_p3(&p3, &p1); + return p3; +} + +inline ge_p2 ge_p3_to_p2(const ge_p3 &p3) { + ge_p2 p2; + ge_p3_to_p2(&p2, &p3); + return p2; +} +inline ge_cached ge_p3_to_cached(const ge_p3 &p3) { + ge_cached ca; + ge_p3_to_cached(&ca, &p3); + return ca; +} + +inline ge_p3 hash_to_good_point_p3(const void *data, size_t length) { + ge_p2 point_p2; + const Hash h = cn_fast_hash(data, length); + ge_fromfe_frombytes_vartime(&point_p2, h.data); + return ge_p1p1_to_p3(ge_mul8_p2(point_p2)); +} + +inline ge_p3 hash_to_good_point_p3(const EllipticCurvePoint &key) { + return hash_to_good_point_p3(key.data, sizeof(key.data)); +} + +inline ge_p3 ge_add(const ge_p3 &a, const ge_p3 &b) { + ge_cached b_cached = ge_p3_to_cached(b); + ge_p1p1 result; + ge_add(&result, &a, &b_cached); + return ge_p1p1_to_p3(result); +} + +inline ge_p3 ge_sub(const ge_p3 &a, const ge_p3 &b) { + ge_cached b_cached = ge_p3_to_cached(b); + ge_p1p1 result; + ge_sub(&result, &a, &b_cached); + return ge_p1p1_to_p3(result); +} + +inline SecretKey sc_mul(const struct cryptoEllipticCurveScalar &aa, const struct cryptoEllipticCurveScalar &bb) { + SecretKey rr; + sc_mul(&rr, &aa, &bb); + return rr; +} + +void generate_ring_signature_auditable_loop1(size_t i, const Hash &prefix_hash, const P3 &image_p3, const P3 &p_p3, + const P3 &G_plus_B_p3, size_t sec_index, const std::vector &pubs, std::vector *ra, + EllipticCurvePoint *x, EllipticCurvePoint *y); +void generate_ring_signature_auditable_loop2(size_t i, const Hash &prefix_hash, const P3 &image_p3, const P3 &p_p3, + const P3 &G_plus_B_p3, size_t sec_index, const std::vector &pubs, std::vector *ra, + EllipticCurveScalar *next_c); + +} // namespace crypto diff --git a/src/crypto/hash-keccak.c b/src/crypto/hash-keccak.c index b8bedf9d..608602af 100644 --- a/src/crypto/hash-keccak.c +++ b/src/crypto/hash-keccak.c @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include "hash.h" @@ -46,13 +48,68 @@ void crypto_keccak_into_state(const uint8_t *buf, size_t count, struct cryptoKec } void crypto_cn_fast_hash(const void *data, size_t length, struct cryptoHash *hash) { + struct cryptoHash hash2; + struct cryptoKeccakHasher hasher; + crypto_keccak_init(&hasher, 256, 1); + crypto_keccak_update(&hasher, data, length); + crypto_keccak_final(&hasher, hash2.data, sizeof(hash2.data)); + struct cryptoKeccakState state; crypto_keccak_into_state(data, length, &state); memcpy(hash->data, &state, sizeof(struct cryptoHash)); + + if (memcmp(hash->data, hash2.data, sizeof(hash2.data)) != 0) { + fprintf(stderr, "keccak stream failure for data length %d", (int)length); + exit(-1); + } } -void crypto_cn_fast_hash64(const void *data, size_t length, unsigned char hash[64]) { - struct cryptoKeccakState state; - crypto_keccak_into_state(data, length, &state); - memcpy(hash, &state, 64); +void crypto_keccak_init(struct cryptoKeccakHasher *hasher, size_t mdlen, uint8_t delim) { + hasher->rate = 200 - 2 * mdlen / 8; + hasher->delim = delim; + hasher->offset = 0; + memset(hasher->state.b, 0, sizeof(hasher->state.b)); +} + +void crypto_keccak_update(struct cryptoKeccakHasher *hasher, const void *vin, size_t inlen) { + const unsigned char *in = (const unsigned char *)vin; + size_t rsiz = hasher->rate - hasher->offset; + size_t offset = hasher->offset; + unsigned char *b = hasher->state.b; + + while (inlen >= rsiz) { + for (size_t i = 0; i < rsiz; i++) + b[offset + i] ^= in[i]; + KeccakF1600_StatePermute(b); + inlen -= rsiz; + in += rsiz; + rsiz = hasher->rate; + offset = 0; + } + for (size_t i = 0; i < inlen; i++) + b[offset + i] ^= in[i]; + hasher->offset = offset + inlen; +} + +void crypto_keccak_final(struct cryptoKeccakHasher *hasher, uint8_t *out, size_t outlen) { + unsigned char *b = hasher->state.b; + size_t rate = hasher->rate; + b[hasher->offset] ^= hasher->delim; + b[rate - 1] ^= 0x80; + + KeccakF1600_StatePermute(b); + + for (; outlen >= rate; outlen -= rate, out += rate) { + for (size_t i = 0; i < rate; i++) + out[i] = b[i]; + KeccakF1600_StatePermute(b); + } + for (size_t i = 0; i < outlen; i++) + out[i] = b[i]; } + +// void crypto_cn_fast_hash64(const void *data, size_t length, unsigned char hash[64]) { +// struct cryptoKeccakState state; +// crypto_keccak_into_state(data, length, &state); +// memcpy(hash, &state, 64); +//} diff --git a/src/crypto/hash.h b/src/crypto/hash.h index 3db991e6..bf1bb597 100644 --- a/src/crypto/hash.h +++ b/src/crypto/hash.h @@ -20,7 +20,7 @@ struct cryptoHash { enum { HASH_DATA_AREA = 136, SLOW_HASH_CONTEXT_SIZE = 2097552 }; void crypto_cn_fast_hash(const void *data, size_t length, struct cryptoHash *hash); -void crypto_cn_fast_hash64(const void *data, size_t length, unsigned char hash[64]); +// void crypto_cn_fast_hash64(const void *data, size_t length, unsigned char hash[64]); void crypto_cn_slow_hash(void *scratchpad, const void *data, size_t length, struct cryptoHash *hash); void crypto_cn_slow_hash_platform_independent( @@ -30,9 +30,30 @@ struct cryptoKeccakState { uint8_t b[200]; }; +struct cryptoKeccakHasher { + struct cryptoKeccakState state; + size_t offset; + size_t rate; + uint8_t delim; +}; + void crypto_keccak_permutation(struct cryptoKeccakState *state); void crypto_keccak_into_state(const uint8_t *buf, size_t count, struct cryptoKeccakState *state); +// shake128: 128, 0x1f +// shake256: 256, 0x1f +// keccak224: 224, 0x01 +// keccak256: 256, 0x01 <-- cn_fast_hash +// keccak384: 384, 0x01 +// keccak512: 512, 0x01 +// sha3_224: 224, 0x06 +// sha3_256: 256, 0x06 +// sha3_384: 384, 0x06 +// sha3_512: 512, 0x06 +void crypto_keccak_init(struct cryptoKeccakHasher *, size_t mdlen, uint8_t delim); +void crypto_keccak_update(struct cryptoKeccakHasher *, const void *buf, size_t count); +void crypto_keccak_final(struct cryptoKeccakHasher *, uint8_t *result, size_t count); + void crypto_hash_extra_blake(const void *data, size_t length, struct cryptoHash *hash); void crypto_hash_extra_groestl(const void *data, size_t length, struct cryptoHash *hash); void crypto_hash_extra_jh(const void *data, size_t length, struct cryptoHash *hash); diff --git a/src/crypto/hash.hpp b/src/crypto/hash.hpp index 9545938a..b9465ffb 100644 --- a/src/crypto/hash.hpp +++ b/src/crypto/hash.hpp @@ -17,6 +17,11 @@ inline Hash cn_fast_hash(const void *data, size_t length) { crypto_cn_fast_hash(data, length, &h); return h; } +inline Hash cn_fast_hash(const std::vector &data) { + Hash h; + crypto_cn_fast_hash(data.data(), data.size(), &h); + return h; +} class CryptoNightContext { public: diff --git a/src/crypto/types.hpp b/src/crypto/types.hpp index 54eed4f8..be3e1704 100644 --- a/src/crypto/types.hpp +++ b/src/crypto/types.hpp @@ -17,13 +17,21 @@ namespace crypto { #pragma pack(push, 1) struct Hash : public cryptoHash { constexpr Hash() : cryptoHash{} {} + + std::vector as_binary_array() const { return std::vector{std::begin(data), std::end(data)}; } }; struct EllipticCurvePoint : public cryptoEllipticCurvePoint { constexpr EllipticCurvePoint() : cryptoEllipticCurvePoint{} {} + // Default initialisation produces point outside main subgroup + // Good or bad, this is done so that Point{} can be used as "null value". + + std::vector as_binary_array() const { return std::vector{std::begin(data), std::end(data)}; } }; struct EllipticCurveScalar : public cryptoEllipticCurveScalar { constexpr EllipticCurveScalar() : cryptoEllipticCurveScalar{} {} + + std::vector as_binary_array() const { return std::vector{std::begin(data), std::end(data)}; } }; struct PublicKey : public EllipticCurvePoint {}; @@ -61,9 +69,16 @@ struct KeyPair { typedef std::vector RingSignature; -struct RingSignature3 { // New half-size signatures - EllipticCurveScalar c0; // c0 - std::vector> r; // r[i, j] +struct RingSignatureAmethyst { // New auditable signatures + std::vector p; + EllipticCurveScalar c0; + std::vector> ra; + std::vector rb; + std::vector rc; +}; + +struct SendproofSignatureAmethyst { + EllipticCurveScalar c0, rb, rc; }; std::ostream &operator<<(std::ostream &out, const EllipticCurvePoint &v); diff --git a/src/http/Server.cpp b/src/http/Server.cpp index 68b2893e..ae89b4a2 100644 --- a/src/http/Server.cpp +++ b/src/http/Server.cpp @@ -20,7 +20,7 @@ using namespace http; Server::Server(const std::string &address, uint16_t port, request_handler &&r_handler, disconnect_handler &&d_handler) - : la_socket{new platform::TCPAcceptor{address, port, std::bind(&Server::accept_all, this)}} + : la_socket{std::make_unique(address, port, std::bind(&Server::accept_all, this))} , r_handler(std::move(r_handler)) , d_handler(std::move(d_handler)) { accept_all(); diff --git a/src/logging/LoggerManager.cpp b/src/logging/LoggerManager.cpp index 2daaec9a..9e3909cb 100644 --- a/src/logging/LoggerManager.cpp +++ b/src/logging/LoggerManager.cpp @@ -21,16 +21,16 @@ void LoggerManager::configure_default( loggers.clear(); LoggerGroup::loggers.clear(); - std::unique_ptr logger( - new FileLogger(log_folder + "/" + log_prefix + "verbose", 1024 * 1024, TRACE)); + std::unique_ptr logger = + std::make_unique(log_folder + "/" + log_prefix + "verbose", 1024 * 1024, DEBUGGING); loggers.emplace_back(std::move(logger)); add_logger(*loggers.back()); - logger.reset(new FileLogger(log_folder + "/" + log_prefix + "errors", 1024 * 1024, ERROR)); + logger = std::make_unique(log_folder + "/" + log_prefix + "errors", 1024 * 1024, ERROR); loggers.emplace_back(std::move(logger)); add_logger(*loggers.back()); - logger.reset(new ConsoleLogger(INFO)); + logger = std::make_unique(INFO); logger->set_pattern("%T %l %C "); loggers.emplace_back(std::move(logger)); add_logger(*loggers.back()); diff --git a/src/main_bytecoind.cpp b/src/main_bytecoind.cpp index 1b4b24c0..0e90fe50 100644 --- a/src/main_bytecoind.cpp +++ b/src/main_bytecoind.cpp @@ -41,9 +41,6 @@ static const char USAGE[] = R"(bytecoind )" bytecoin_VERSION_STRING R"(. --bytecoind-authorization-private= HTTP basic authentication credentials for get_statistics and get_archive methods.)"; int main(int argc, const char *argv[]) try { - crypto::test_unlinkable(); - crypto::test_linkable(); - common::console::UnicodeConsoleSetup console_setup; auto idea_start = std::chrono::high_resolution_clock::now(); common::CommandLine cmd(argc, argv); @@ -54,18 +51,23 @@ int main(int argc, const char *argv[]) try { std::string export_blocks; if (const char *pa = cmd.get("--export-blocks")) export_blocks = platform::normalize_folder(pa); + Height max_height = std::numeric_limits::max(); + if (const char *pa = cmd.get("--max-height")) // for export, import + max_height = boost::lexical_cast(pa); std::string backup_blockchain; if (const char *pa = cmd.get("--backup-blockchain")) backup_blockchain = platform::normalize_folder(pa); Config config(cmd); Currency currency(config.net); - - Height print_structure = Height(-1); + // config.db_commit_every_n_blocks = 10000; + Height print_structure = std::numeric_limits::max(); if (const char *pa = cmd.get("--print-structure")) - print_structure = std::stoi(pa); - const bool print_outputs = cmd.get_bool("--print-outputs"); - if (cmd.should_quit(Config::prepare_usage(USAGE).c_str(), cn::app_version())) - return 0; + print_structure = boost::lexical_cast(pa); + size_t dump_outputs_quality = 0; + if (const char *pa = cmd.get("--dump-outputs-quality")) + dump_outputs_quality = boost::lexical_cast(pa); + if (int r = cmd.should_quit(Config::prepare_usage(USAGE).c_str(), cn::app_version())) + return r == 1 ? 0 : api::BYTECOIND_WRONG_ARGS; const std::string coin_folder = config.get_data_folder(); if (!export_blocks.empty() && !backup_blockchain.empty()) { @@ -88,20 +90,20 @@ int main(int argc, const char *argv[]) try { std::cout << "Finished blockchain backup." << std::endl; return 0; } - if (!export_blocks.empty() || print_structure != Height(-1) || print_outputs) { + if (!export_blocks.empty() || print_structure != std::numeric_limits::max() || dump_outputs_quality != 0) { logging::ConsoleLogger log_console; BlockChainState block_chain_read_only(log_console, config, currency, true); if (!export_blocks.empty()) { if (!LegacyBlockChainWriter::export_blockchain2(export_blocks + "/" + config.block_indexes_file_name, - export_blocks + "/" + config.blocks_file_name, block_chain_read_only)) + export_blocks + "/" + config.blocks_file_name, block_chain_read_only, max_height)) return 1; return 0; } - if (print_structure != Height(-1)) + if (print_structure != std::numeric_limits::max()) block_chain_read_only.test_print_structure(print_structure); - if (print_outputs) - block_chain_read_only.test_print_outputs(); + if (dump_outputs_quality != 0) + block_chain_read_only.dump_outputs_quality(dump_outputs_quality); return 0; } @@ -111,19 +113,21 @@ int main(int argc, const char *argv[]) try { log_manager.configure_default(config.get_data_folder("logs"), CRYPTONOTE_NAME "d-", cn::app_version()); BlockChainState block_chain(log_manager, config, currency, false); - // block_chain.test_undo_everything(0); - // return 0; if (!import_blocks.empty()) { LegacyBlockChainReader::import_blockchain2(import_blocks + "/" + config.block_indexes_file_name, - import_blocks + "/" + config.blocks_file_name, &block_chain); + import_blocks + "/" + config.blocks_file_name, &block_chain, max_height); + std::cin >> max_height; return 0; } // block_chain.test_undo_everything(0); + // return 0; // block_chain.test_print_tips(); // while(block_chain.test_prune_oldest()){ // block_chain.test_print_tips(); // } + // test_trezor(); + // return 0; boost::asio::io_service io; platform::EventLoop run_loop(io); diff --git a/src/main_tests.cpp b/src/main_tests.cpp index 76e677b7..202bcb33 100644 --- a/src/main_tests.cpp +++ b/src/main_tests.cpp @@ -1,10 +1,16 @@ // Copyright (c) 2012-2018, The CryptoNote developers, The Bytecoin developers. // Licensed under the GNU Lesser General Public License. See LICENSE for details. +#include +#include #include +#include #include +//#include +#include #include "common/BIPs.hpp" +#include "common/Base58.hpp" #include "common/CommandLine.hpp" #include "common/Invariant.hpp" #include "common/StringTools.hpp" @@ -12,31 +18,163 @@ #include "version.hpp" #include "../tests/blockchain/test_blockchain.hpp" +#include "../tests/crypto/benchmarks.hpp" #include "../tests/crypto/test_crypto.hpp" #include "../tests/hash/test_hash.hpp" #include "../tests/json/test_json.hpp" #include "../tests/wallet_file/test_wallet_file.hpp" #include "../tests/wallet_state/test_wallet_state.hpp" -static const char USAGE[] = - R"(tests. return code 0 means success +// namespace po = boost::program_options; -uses relative paths and should be run from bin folder - -Usage: - tests [options] +std::string format_test_name(const std::string &name) { + std::stringstream stream; + const int linewidth = 70; + stream << std::endl; + stream << "<" << std::setw(linewidth) << std::setfill('-') << "-" + << ">" << std::endl; + stream << std::setw(linewidth / 4) << std::setfill(' ') << "" << name << std::endl; + stream << "<" << std::setw(linewidth) << std::setfill('-') << "-" + << ">" << std::endl; + stream << std::endl; + return stream.str(); +} -Options: - -h --help Show this screen. - -v --version Show version. -)"; +void test_bip32(); int main(int argc, const char *argv[]) { + /* const std::string USAGE( + "Execute subsystem tests. Return code 0 means success.\n" + "Uses relative paths and should be run from the {PROJECT_ROOT}/bin folder. " + "This is the default when building the project with CMake.\n" + ); + + po::options_description all_options("Available options"); + + po::options_description test_flags("Flags to test individual subsystems. If none are set, test everything"); + test_flags.add_options() + ("crypto", "test cryptographic primitives") + ("benchmark", "run cryptography benchmarks") + ("blockchain", "test blockchain subsystem") + ("db", "test database operations") + ("json", "test JSON (de-)serialization") + ("hash", "test hash calculations") + ("wallet", "test wallet file operations") + ("wallet-state", "test wallet state integrity"); + + // test_flags.add_options() + // ("crypto", po::bool_switch(), "test cryptographic primitives") + // ("benchmark", po::bool_switch(), "run cryptography benchmarks") + // ("blockchain", po::bool_switch(), "test blockchain subsystem") + // ("db", po::bool_switch(), "test database operations") + // ("json", po::bool_switch(), "test JSON (de-)serialization") + // ("hash", po::bool_switch(), "test hash calculations") + // ("wallet", po::bool_switch(), "test wallet file operations") + // ("wallet-state", po::bool_switch(), "test wallet state integrity"); + // + po::options_description help_messages("Print help message and quit"); + help_messages.add_options() + ("help,h", "display this help message and quit") + ("version,v", "show test module version"); + + all_options.add(test_flags).add(help_messages); + + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, all_options), vm); + vm.notify(); + + if (vm.count("help") + vm.count("version")) { + std::cout << "Bytecoin version " << cn::app_version() << std::endl; + if (vm.count("help")) { + std::cout << USAGE << std::endl; + std::cout << all_options << std::endl; + } + return 0; + } + + // if (vm.count("crypto") || vm.empty()) { + std::cout << format_test_name("Testing Crypto...") << std::endl; + test_crypto("../tests/crypto/tests.txt"); + // } + + if (vm.count("benchmark") || vm.empty()) { + std::cout << format_test_name("Running cryptographic primitives benchmark...") << std::endl; + benchmark_crypto_ops(10000, std::cout); + } + + common::CommandLine cmd(argc, argv); + + if (vm.count("wallet-state") || vm.empty()) { + std::cout << format_test_name("Testing Wallet State...") << std::endl; + test_wallet_state(cmd); + } + + if (vm.count("hash") || vm.empty()) { + std::cout << format_test_name("Testing Hashes...") << std::endl; + test_hashes("../tests/hash"); + } + + if (vm.count("wallet") || vm.empty()) { + std::cout << format_test_name("Testing Wallet Files...") << std::endl; + test_wallet_file("../tests/wallet_file"); + } + + if (vm.count("blockchain") || vm.empty()) { + std::cout << format_test_name("Testing Block Chain...") << std::endl; + test_blockchain(cmd); + } + + if (vm.count("db") || vm.empty()) { + std::cout << format_test_name("Testing DB...") << std::endl; + platform::DB::run_tests(); + } + + if (vm.count("json") || vm.empty()) { + std::cout << format_test_name("Testing Json...") << std::endl; + test_json("../tests/json"); + } + */ + std::string USAGE( + "Execute subsystem tests. Return code 0 means success.\n" + "Uses relative paths and should be run from the {PROJECT_ROOT}/build folder. " + "This is the default when building the project with CMake.\n" + "Available options (each runs corresponding test)\n"); common::CommandLine cmd(argc, argv); - std::cout << "Testing Wallet State" << std::endl; - test_wallet_state(cmd); + std::map> all; + + all["--crypto"] = std::bind(test_crypto, "../tests/crypto/tests.txt"); + all["--bip32"] = test_bip32; + all["--benchmark"] = std::bind(benchmark_crypto_ops, 10000, std::ref(std::cout)); + all["--blockchain"] = std::bind(test_blockchain, std::ref(cmd)); + all["--db"] = platform::DB::run_tests; + all["--json"] = std::bind(test_json, "../tests/json"); + all["--hash"] = std::bind(test_hashes, "../tests/hash"); + all["--wallet"] = std::bind(test_wallet_file, "../tests/wallet_file"); + all["--wallet-state"] = std::bind(test_wallet_state, std::ref(cmd)); + + int found_on_cmd_line = 0; + for (const auto &t : all) { + USAGE += " " + t.first + "\n"; + if (cmd.get_bool(t.first.c_str())) + found_on_cmd_line += 1; + } + if (int r = cmd.should_quit(USAGE.c_str(), cn::app_version())) + return r == 1 ? 0 : -1; + + for (const auto &t : all) + if (found_on_cmd_line == 0 || cmd.get_bool(t.first.c_str())) { + std::cout << format_test_name("Running test " + t.first) << std::endl; + t.second(); + } + std::cout << format_test_name("Done!") << std::endl; + return 0; +} + +void test_bip32() { + // TODO move this code to a proper test suite cn::Bip32Key master_key = cn::Bip32Key::create_master_key( "sausage coast tank shrug idle hub fun amused display inquiry bone unfold fish stumble clerk skate mango pause cage glide lens armed point segment", std::string()); @@ -47,7 +185,8 @@ int main(int argc, const char *argv[]) { cn::Bip32Key k4 = k3.derive_key(2); // bip39 - // seed=d0cc66008a89740ea666c4b3250e5d25a63a5666a6a5f77284d33f760cff053d712b3c78b42195666dd405945b15a2724c8e7ff3b79b684ce5bd43c4cffa5528 + // + // seed=d0cc66008a89740ea666c4b3250e5d25a63a5666a6a5f77284d33f760cff053d712b3c78b42195666dd405945b15a2724c8e7ff3b79b684ce5bd43c4cffa5528 // bip39 master chain code=9fe854cc0cbf704f8eeef3f2b61176ca2e00504a38c791d49b55e2fdcb7218bc // bip39 master key=aae196e2b5bb55d152fbdf0e9583bbd16505bbae1bcb6fd19368363965afd284 // pub_key=03e28d0b5e906ea2aefc19420dd3a357b6bcf7e4c27b1788283f829ae060fbffa3 @@ -70,26 +209,187 @@ int main(int argc, const char *argv[]) { common::to_hex(k4.get_priv_key()) == "897bbe02c75ec6f982d656985a4cbf5ef9cf423a7bd5a5edf9d1a69e1e650b6a", ""); invariant( common::to_hex(k4.get_chain_code()) == "cb2c6daa6205f9f6fb71a0cb855ba9169e6784adbb4d280ecff4b4f734a3102c", ""); - - std::cout << "Testing Crypto" << std::endl; - test_crypto("../tests/crypto/tests.txt"); - - std::cout << "Testing Hashes" << std::endl; - test_hashes("../tests/hash"); - - std::cout << "Testing Wallet Files" << std::endl; - test_wallet_file("../tests/wallet_file"); - - std::cout << "Testing Block Chain" << std::endl; - test_blockchain(cmd); - - std::cout << "Testing DB" << std::endl; - platform::DB::run_tests(); - - std::cout << "Testing Json" << std::endl; - test_json("../tests/json"); - - if (cmd.should_quit(USAGE, cn::app_version())) - return 0; - return 0; } + +////[.. 00] ffffff < prefix 111111111111111111 +////[.. 01] ffffff < prefix 111111111111111111 +// +////[.. k-1] ffffff < prefix 111111111111111111 +////[.. k ] ffffff >= prefix 111111111111111111 +// +// bool interactive_find_mi(const std::string &prefix, BinaryArray & tag){ +// tag.push_back(0); +// for(size_t to = 0; to < 0x100; ++to){ +// tag.back() = to; +// BinaryArray buf = tag; +// append(buf, BinaryArray(64, 0xff)); +// std::string a1 = encode(buf); +// if(a1.substr(0, prefix.size()) >= prefix){ +// if(tag.size() >= 16){ +// std::cout << "mi: " << common::to_hex(tag) << std::endl; +// return true; +// } +// return interactive_find_mi(prefix, tag); +// } +// } +// return false; +//} +// +////[.. ff] 000000 substr > prefix +////[.. fe] 000000 > prefix zzzzzzzzzzzzzzzzzz +// +////[.. k+1] 000000 > prefix zzzzzzzzzzzzzzzzzz +////[.. k ] 000000 <= prefix zzzzzzzzzzzzzzzzzz +// +// bool interactive_find_ma(const std::string &prefix, BinaryArray & tag){ +// tag.push_back(0); +// for(size_t to = 0x100; to-- > 1;){ +// tag.back() = to; +// BinaryArray buf = tag; +// append(buf, BinaryArray(64, 0)); +// std::string a1 = encode(buf); +// if(a1.substr(0, prefix.size()) <= prefix){ +// if(tag.size() >= 16){ +// std::cout << "ma: " << common::to_hex(tag) << std::endl; +// return true; +// } +// return interactive_find_ma(prefix, tag); +// } +// } +// return false; +//} +// +// bool inc_before_pos(BinaryArray & tag, size_t pos){ +// size_t carry = 1; +// for(size_t j = pos; j-- > 0; ) +// if(tag.at(j) + carry == 0x100){ +// tag.at(j) = 0; +// carry = 1; +// }else{ +// tag.at(j) += carry; +// carry = 0; +// } +// return carry == 0; +//} +// +// bool find_shortest_varint_between(BinaryArray & tag, size_t max_depth, const BinaryArray & ma){ +// if(tag >= ma) +// return false; +// for(size_t i = 0; i != max_depth; ++i){ +// if(i == max_depth - 1 && tag.at(i) == 0){ +// tag.at(i) = 1; +// } +// if(i == max_depth - 1 && tag.at(i) >= 0x80){ +// tag.at(i) = 1; +// for(size_t j = i + 1; j != tag.size(); ++j) +// tag.at(j) = 0; +// if(!inc_before_pos(tag, i)) +// return false; +// } +// if(i != max_depth - 1 && tag.at(i) < 0x80){ +// tag.at(i) = 0x80; +// for(size_t j = i + 1; j != tag.size(); ++j) +// tag.at(j) = 0; +// } +// } +// if(tag >= ma) +// return false; +// return true; +//} +// +//// for(; from < 0x100; ++from){ +//// invariant (common::read_varint<64>(be, en, &utag) > 0, ""); +//// auto a1 = encode_addr(utag, BinaryArray(64, 0)); +//// auto a2 = encode_addr(utag, BinaryArray(64, 0xff)); +//// if (a1.substr(0, prefix.size()) != prefix || a2.substr(0, prefix.size()) != prefix )// || a1.substr(0, 4) != +/// a2.substr(0, 4)) / continue; / std::cout << a1.substr(0, 18) << "..." << a1.substr(0, 18) << " " <(be, en, &utag) <= 0) +// continue; +// auto a1 = encode_addr(utag, BinaryArray(64, 0)); +// auto a2 = encode_addr(utag, BinaryArray(64, 0xff)); +// std::cout << a1.substr(0, 18) << " - " << a2.substr(0, 18) << " " << utag << " " << common::to_hex(tag) +//<< std::endl; if(!inc_before_pos(tag, varintsize)) break; +// if(!find_shortest_varint_between(tag, varintsize, interactive_ma)) break; +// } +// break; +// } +// } +// invariant(prefix.size() <= full_encoded_block_size - 1, ""); +// std::string good; +// bool first_good = false; +// BinaryArray result_all; +// for(size_t i = 0; i != alphabet_size; ++i){ +// std::string str1 = prefix + std::string(full_encoded_block_size - prefix.size(), alphabet[i]); +//// std::string str2 = prefix + std::string(full_encoded_block_size - prefix.size(), alphabet[alphabet_size - +/// 1]); +// uint8_t result1[full_block_size]{}; +// if(!decode_block(str1.data(), str1.size(), result1)) +// continue; +// if(!first_good){ +// first_good = true; +// result_all.assign(std::begin(result1), std::end(result1)); +// continue; +// } +// size_t pre = 0; +// for(; pre != result_all.size(); ++pre) +// if(result_all[pre] != result1[pre]) +// break; +// result_all.resize(pre); +// } +// if( !first_good || result_all.empty() ){ +// std::cout << "Prefix too short" << std::endl; +// return 0; +// } +// std::cout << "Common bytes are: " << common::to_hex(result_all) << std::endl; +// std::cout << "Possible prefixes" << std::endl; +// std::set used; +// uint64_t utag = 0; +// // for(size_t j = 0xe1; j != 0xe5; ++j) +// for (size_t i = 0x0; i != 0x80; ++i) { +// // BinaryArray tag{0xce, 0xf5, uint8_t(j), uint8_t(i)}; +// BinaryArray tag{0xce, 0xf5, 0xe2, 0x80, 0x91, 0xdd, uint8_t(i)}; +// auto a1 = encode_addr(utag, BinaryArray(64, 0)); +// auto a2 = encode_addr(utag, BinaryArray(64, 0xff)); +//// std::cout << a1.substr(0, 18) << " " << a2.substr(0, 18) << std::endl; +// // if(a1.substr(0, 4) != "bcn1" || a1.substr(0, 5) != a2.substr(0, 5)) +// // continue; +// if (a1.substr(0, prefix.size()) != prefix || a2.substr(0, prefix.size()) != prefix )// || a1.substr(0, 4) != +// a2.substr(0, 4)) continue; +// // std::cout << "tag= " << common::to_hex(tag) << std::endl; +// // std::cout << "address min= " << a1 << std::endl; +// // std::cout << "address max= " << a2 << std::endl; +//// if (!used.insert(a2.substr(0, prefix.size())).second) +//// continue; +// std::cout << a2.substr(0, prefix.size()) << " - " << a2.substr(0, 18) << "... tag=" << utag +// << " varintdata=" << common::to_hex(tag) << std::endl; +// } +// return utag; +//} diff --git a/src/main_walletd.cpp b/src/main_walletd.cpp index eda74908..49e422ff 100644 --- a/src/main_walletd.cpp +++ b/src/main_walletd.cpp @@ -29,20 +29,20 @@ static const char USAGE[] = R"(walletd )" bytecoin_VERSION_STRING R"(. Options: --wallet-file= Path to wallet file to open. - --create-legacy-wallet Create wallet file with new random keys, then exit. Must be used with --wallet-file option. - --import-keys Create wallet file with imported keys read as a line from stdin. Must be used with --create-legacy-wallet. + --import-keys Create wallet file with imported keys read as a line from stdin. Must be used with --wallet-type=legacy. --create-wallet Create wallet file with existing BIP39 mnemonic read as a line from stdin. - --creation-timestamp= When creating wallet file, set wallet creation timestamp to (now is possible value) [default: 0]. Must be used with --import-keys or --create-wallet option. - --address-type=auditable Create an auditable wallet instead of standard wallet. Must be used with --create-wallet. + --creation-timestamp= When creating wallet file, set wallet creation timestamp to (now is possible value) [default: 0]. Must be used with --create-wallet option. --address-count= When creating wallet file, immediately generate addresses [default: 1]. - --create-mnemonic Create a new random BIP39 mnemonic. If --create-wallet is specified, create wallet from mnemonic, otherwise print mnemonic and exit. + --wallet-type= Used with --create-mnemonic, possible values are 'amethyst', 'legacy', 'hardware' [default: 'amethyst'] + --create-mnemonic Create a new random BIP39 mnemonic, then exit. --mnemonic-strength= Used with --create-mnemonic, [default: 256]. - --secrets-via-api Specify to allow getting secrets using 'get_wallet_info' json RPC method. + --secrets-via-api Specify to allow getting secrets using 'get_wallet_info' json RPC method (off by default for security reasons). --set-password Read new password as a line from stdin (twice) and re-encrypt wallet file, then exit. - --launch-after-command Instead of exiting, continue launching after --create-wallet, --create-legacy-wallet and --set-password commands + --launch-after-command Instead of exiting, continue launching after --create-wallet and --set-password commands --export-view-only= Export view-only version of wallet file, then exit. Add --set-password to export with different password. --view-outgoing-addresses Used only with --export-view-only=<> and HD wallet. if set, exported view-only wallet will be able to see destination addresses in tracked transactions. - --export-keys Export unencrypted wallet keys to stdout, then exit. + --export-keys Export unencrypted wallet keys to stdout, then exit. (Only for legacy wallets) + --export-mnemonic Export mnemonic to stdout, then exit. (Only for deterministic wallets) --walletd-bind-address= IP and port for walletd RPC API [default: 127.0.0.1:8070]. --data-folder= Folder for wallet cache, blockchain, logs and peer DB [default: )" platform_DEFAULT_DATA_FOLDER_PATH_PREFIX R"(bytecoin]. @@ -68,67 +68,33 @@ int main(int argc, const char *argv[]) try { common::console::UnicodeConsoleSetup console_setup; auto idea_start = std::chrono::high_resolution_clock::now(); common::CommandLine cmd(argc, argv); - std::string wallet_file, password, new_password, walletd_http_auth, export_view_only, import_keys_value, - backup_wallet; - const bool launch_after_command = cmd.get_bool("--launch-after-command"); - // used by GUI wallet, launch normally after create-wallet and set_password - const bool set_password = cmd.get_bool("--set-password"); - bool ask_password = true; - bool ask_walletd_http_auth = true; - const bool export_keys = cmd.get_bool("--export-keys"); - const bool create_wallet = cmd.get_bool("--create-wallet"); - const bool check_mnemonic = cmd.get_bool("--check-mnemonic"); // Undocumented, used by GUI for now - const bool create_legacy_wallet = cmd.get_bool("--create-legacy-wallet"); - const bool import_keys = cmd.get_bool("--import-keys"); - bool view_outgoing_addresses = false; - std::string mnemonic; - std::string mnemonic_password; - int address_type = 1; - size_t address_count = 0; - if (const char *pa = cmd.get("--address-type")) { - if (std::string(pa) == "auditable" || std::string(pa) == "2") - address_type = 2; - else if (std::string(pa) == "standard" || std::string(pa) == "1") - address_type = 1; - else { - std::cout << "If specified, address type must be 'auditable' or 'standard'" << std::endl; - return 1; - } - } - if (const char *pa = cmd.get("--address-count")) - address_count = boost::lexical_cast(pa); - Timestamp creation_timestamp = 0; + if (cmd.get_bool("--create-mnemonic")) { size_t bits = 256; if (const char *pa = cmd.get("--mnemonic-strength")) bits = boost::lexical_cast(pa); - mnemonic = cn::Bip32Key::create_random_bip39_mnemonic(bits); - if (!create_wallet) { - std::cout << mnemonic << std::endl; - return 0; + if (cmd.should_quit(Config::prepare_usage(USAGE).c_str(), cn::app_version())) { + std::cout + << "--create-mnemonic must be used with no other options or with --mnemonic-strength= option" + << std::endl; + return api::WALLETD_WRONG_ARGS; } - creation_timestamp = platform::now_unix_timestamp(); // We know, we know... - } - if (create_wallet || create_legacy_wallet) { - if (const char *pa = cmd.get("--creation-timestamp")) - creation_timestamp = - std::string(pa) == "now" ? platform::now_unix_timestamp() : boost::lexical_cast(pa); - } - if (create_wallet && create_legacy_wallet) { - std::cout << "You cannot ask to create both legacy and new wallet" << std::endl; - return 1; + try { + std::cout << cn::Bip32Key::create_random_bip39_mnemonic(bits) << std::endl; + } catch (const Bip32Key::Exception &e) { + std::cerr << "Invalid mnemonic parameters. " << e.what() << std::endl; + return api::WALLETD_WRONG_ARGS; + } + return 0; } - if ((create_wallet && mnemonic.empty()) || check_mnemonic) { - if (!check_mnemonic) - std::cout << "Enter BIP39 mnemonic: " << std::flush; - if (!console_setup.getline(mnemonic)) { - std::cout << "Unexpected end of stdin" << std::endl; + if (cmd.get_bool("--check-mnemonic")) { // Undocumented, used by GUI for now + if (cmd.should_quit(Config::prepare_usage(USAGE).c_str(), cn::app_version())) { + std::cout << "--check-mnemonic cannot be used with other options" << std::endl; return api::WALLETD_WRONG_ARGS; } - if (!check_mnemonic) - std::cout << std::endl; - if (mnemonic.empty()) { - std::cout << "Mnemonic should not be empty" << std::endl; + std::string mnemonic; + if (!console_setup.getline(mnemonic)) { + std::cout << "Unexpected end of stdin" << std::endl; return api::WALLETD_WRONG_ARGS; } try { @@ -137,62 +103,103 @@ int main(int argc, const char *argv[]) try { std::cout << "Mnemonic invalid - " << common::what(ex) << std::endl; return api::WALLETD_MNEMONIC_CRC; } - if (check_mnemonic) - return 0; - std::cout << "Enter BIP39 mnemonic password (empty recommended): " << std::flush; - if (!console_setup.getline(mnemonic_password)) { - std::cout << "Unexpected end of stdin" << std::endl; - return api::WALLETD_WRONG_ARGS; - } - std::cout << std::endl; - boost::algorithm::trim(mnemonic_password); - } - if (import_keys && !create_legacy_wallet) { - std::cout - << "When importing keys, you should use --create-legacy-wallet. You cannot import into existing wallet." - << std::endl; - return api::WALLETD_WRONG_ARGS; + return 0; } + // All other scenarios require wallet file + std::string wallet_file; if (const char *pa = cmd.get("--wallet-file")) wallet_file = pa; - if (const char *pa = cmd.get("--export-view-only")) { - if (import_keys || create_wallet || create_legacy_wallet || export_keys) { - std::cout - << "When exporting view-only version of wallet you cannot import keys, export keys, create wallet." - << std::endl; - return api::WALLETD_WRONG_ARGS; - } - export_view_only = pa; - view_outgoing_addresses = cmd.get_bool("--view-outgoing-addresses"); + if (wallet_file.empty()) { + if (int r = cmd.should_quit(Config::prepare_usage(USAGE).c_str(), cn::app_version())) + return r == 1 ? 0 : api::WALLETD_WRONG_ARGS; + std::cout << "--wallet-file= argument is mandatory" << std::endl; + return api::WALLETD_WRONG_ARGS; + } + boost::optional password; + boost::optional walletd_http_auth; + if (const char *pa = cmd.get("--wallet-password")) { // Undocumented, used for debugging + password = pa; + common::console::set_text_color(common::console::BrightRed); + std::cout << "Password on command line is a security risk. Use 'echo | ./walletd' or 'cat secrets.txt | " + "./walletd'" + << std::endl; + common::console::set_text_color(common::console::Default); } + if (const char *pa = cmd.get("--walletd-http-auth")) // Undocumented, used for debugging + walletd_http_auth = pa; + const bool create_wallet = cmd.get_bool("--create-wallet"); + const bool set_password = cmd.get_bool("--set-password"); + const bool export_keys = cmd.get_bool("--export-keys"); + const bool export_mnemonic = cmd.get_bool("--export-mnemonic"); + std::string export_view_only; + if (const char *pa = cmd.get("--export-view-only")) + export_view_only = pa; + std::string backup_wallet_data; if (const char *pa = cmd.get("--backup-wallet", "Deprecated, use --backup-wallet-data")) - backup_wallet = pa; + backup_wallet_data = pa; if (const char *pa = cmd.get("--backup-wallet-data")) - backup_wallet = platform::normalize_folder(pa); - if (const char *pa = cmd.get("--wallet-password")) { // Undocumented, usefull for debugging - password = pa; - ask_password = false; + backup_wallet_data = platform::normalize_folder(pa); + + // TODO check that only 1 of those commands used + // create_wallet export_keys | export_view_only | backup_wallet_data + // set_password can be used by itself or with export_view_only | backup_wallet_data + + const bool import_keys = cmd.get_bool("--import-keys"); + const bool view_outgoing_addresses = cmd.get_bool("--view-outgoing-addresses"); + if (view_outgoing_addresses && export_view_only.empty()) { + std::cout << "--view-outgoing-addresses can only be used with --export-view-only" << std::endl; + return api::WALLETD_WRONG_ARGS; } - if (const char *pa = cmd.get("--walletd-http-auth")) { // Undocumented, usefull for debugging - walletd_http_auth = pa; - ask_walletd_http_auth = false; + const bool launch_after_command = cmd.get_bool("--launch-after-command"); // undocumented, used by GUI + if (launch_after_command && !(create_wallet || set_password)) { + std::cout << "--launch-after-command can only be used with commands" << std::endl; + return api::WALLETD_WRONG_ARGS; } - if (!ask_password && (create_wallet || create_legacy_wallet)) { - std::cout << "When generating wallet, you cannot use --wallet-password. Wallet password will be read from stdin" - << std::endl; + std::string wallet_type = "amethyst"; + if (const char *pa = cmd.get("--wallet-type")) { + if (!create_wallet) { + std::cout << "--wallet-type can only be used with --create-wallet" << std::endl; + return api::WALLETD_WRONG_ARGS; + } + wallet_type = pa; + if(wallet_type != "amethyst" && wallet_type != "legacy" && wallet_type != "hardware"){ + std::cout << "--wallet-type= value can be 'amethyst', 'legacy' or 'hardware'" << std::endl; + return api::WALLETD_WRONG_ARGS; + } + } + if (import_keys && (!create_wallet || wallet_type != "legacy")) { + std::cout << "--import-keys can only be used with --create-wallet --wallet-type=legacy" << std::endl; return api::WALLETD_WRONG_ARGS; } + Timestamp creation_timestamp = 0; + if (const char *pa = cmd.get("--creation-timestamp")) { + if (!create_wallet) { + std::cout << "--creation-timestamp can only be used when creating wallet" << std::endl; + return api::WALLETD_WRONG_ARGS; + } + creation_timestamp = + std::string(pa) == "now" ? platform::now_unix_timestamp() : boost::lexical_cast(pa); + } + size_t address_count = 0; + if (const char *pa = cmd.get("--address-count")) { + if (!create_wallet || wallet_type == "legacy" ) { + std::cout << "--address-count cannot be used with --wallet-type=legacy" << std::endl; + return api::WALLETD_WRONG_ARGS; + } + address_count = boost::lexical_cast(pa); + } + Config config(cmd); Currency currency(config.net); - if (cmd.should_quit(Config::prepare_usage(USAGE).c_str(), cn::app_version())) - return api::WALLETD_WRONG_ARGS; + if (const char *pa = cmd.get("--emulate-hardware-wallet")) // Undocumented, used by devs + hw::Emulator::debug_set_mnemonic(pa); - if (wallet_file.empty()) { - std::cout << "--wallet-file= argument is mandatory" << std::endl; - return api::WALLETD_WRONG_ARGS; - } - if (create_legacy_wallet && import_keys) { + if (int r = cmd.should_quit(Config::prepare_usage(USAGE).c_str(), cn::app_version())) + return r == 1 ? 0 : api::WALLETD_WRONG_ARGS; + + std::string import_keys_value; + if (import_keys) { std::cout << "Enter imported keys as hex bytes (05AB6F... etc.): " << std::flush; if (!console_setup.getline(import_keys_value)) { std::cout << "Unexpected end of stdin" << std::endl; @@ -205,28 +212,41 @@ int main(int argc, const char *argv[]) try { return api::WALLETD_WRONG_ARGS; } } - if (!config.bytecoind_remote_port && !create_wallet && !create_legacy_wallet && !set_password && !export_keys && - export_view_only.empty() && backup_wallet.empty()) { - common::console::set_text_color(common::console::BrightRed); - std::cout << "Warning: inproc " CRYPTONOTE_NAME "d is deprecated and will be removed soon." << std::endl; - std::cout << " Please run bytecoind separately, then specify --remote-" CRYPTONOTE_NAME - "d-address=: argument to walletd" - << std::endl; - std::cout << " This is important to prevent " CRYPTONOTE_NAME - "d P2P attack vectors from reaching walletd address space where wallet keys reside" - << std::endl; - common::console::set_text_color(common::console::Default); + + std::string mnemonic, mnemonic_password; + if (create_wallet && wallet_type == "amethyst") { + std::cout << "Enter BIP39 mnemonic: " << std::flush; + if (!console_setup.getline(mnemonic)) { + std::cout << "Unexpected end of stdin" << std::endl; + return api::WALLETD_WRONG_ARGS; + } + try { + mnemonic = cn::Bip32Key::check_bip39_mnemonic(mnemonic); + } catch (const Bip32Key::Exception &ex) { + std::cout << "Mnemonic invalid - " << common::what(ex) << std::endl; + return api::WALLETD_MNEMONIC_CRC; + } + std::cout << "Enter BIP39 mnemonic password (empty recommended): " << std::flush; + if (!console_setup.getline(mnemonic_password)) { + std::cout << "Unexpected end of stdin" << std::endl; + return api::WALLETD_WRONG_ARGS; + } + std::cout << std::endl; + boost::algorithm::trim(mnemonic_password); } - if (!create_wallet && !create_legacy_wallet && ask_password) { + + if (!create_wallet && !password) { std::cout << "Enter current wallet file password: " << std::flush; - if (!console_setup.getline(password, true)) { + password = std::string(); + if (!console_setup.getline(password.get(), true)) { std::cout << "Unexpected end of stdin" << std::endl; return api::WALLETD_WRONG_ARGS; } std::cout << std::endl; - boost::algorithm::trim(password); + boost::algorithm::trim(password.get()); } - if (create_wallet || create_legacy_wallet || set_password) { + std::string new_password; + if ((create_wallet && wallet_type != "hardware") || set_password) { std::cout << "Enter new wallet file password: " << std::flush; if (!console_setup.getline(new_password, true)) { std::cout << "Unexpected end of stdin" << std::endl; @@ -247,29 +267,49 @@ int main(int argc, const char *argv[]) try { return api::WALLETD_WRONG_ARGS; } } + const std::string coin_folder = config.get_data_folder(); // if (wallet_file.empty() && !generate_wallet) // No args can be provided when debugging with MSVC // wallet_file = "C:\\Users\\user\\test.wallet"; logging::LoggerManager logManagerWalletNode; logManagerWalletNode.configure_default(config.get_data_folder("logs"), "walletd-", cn::app_version()); + + if (!config.bytecoind_remote_port && !create_wallet && + !set_password && !export_keys && !export_mnemonic && export_view_only.empty() && backup_wallet_data.empty()) { + common::console::set_text_color(common::console::BrightRed); + std::cout << "Warning: inproc " CRYPTONOTE_NAME "d is deprecated and will be removed soon." << std::endl; + std::cout << " Please run bytecoind separately, then specify --remote-" CRYPTONOTE_NAME + "d-address=: argument to walletd" + << std::endl; + std::cout << " This is important to prevent " CRYPTONOTE_NAME + "d P2P attack vectors from reaching walletd address space where wallet keys reside" + << std::endl; + common::console::set_text_color(common::console::Default); + } std::unique_ptr wallet; try { - if (create_wallet) { + if (create_wallet && wallet_type == "hardware") { wallet = std::make_unique(currency, logManagerWalletNode, wallet_file, new_password, mnemonic, - address_type, creation_timestamp, mnemonic_password); + creation_timestamp, mnemonic_password, true); wallet->create_look_ahead_records(address_count); - } else if (create_legacy_wallet) { + } else if (create_wallet && wallet_type == "amethyst") { + wallet = std::make_unique(currency, logManagerWalletNode, wallet_file, new_password, mnemonic, + creation_timestamp, mnemonic_password, false); + wallet->create_look_ahead_records(address_count); + } else if (create_wallet && wallet_type == "legacy") { wallet = std::make_unique( currency, logManagerWalletNode, wallet_file, new_password, import_keys_value, creation_timestamp); } else { - const bool readonly = !backup_wallet.empty() || !export_view_only.empty() || export_keys; + const bool readonly = + !backup_wallet_data.empty() || !export_view_only.empty() || export_keys || export_mnemonic; const bool is_sqlite = WalletHD::is_sqlite(wallet_file); if (is_sqlite) - wallet = std::make_unique(currency, logManagerWalletNode, wallet_file, password, readonly); - else wallet = - std::make_unique(currency, logManagerWalletNode, wallet_file, password); + std::make_unique(currency, logManagerWalletNode, wallet_file, password.get(), readonly); + else + wallet = std::make_unique( + currency, logManagerWalletNode, wallet_file, password.get()); } } catch (const common::StreamError &ex) { std::cout << common::what(ex) << std::endl; @@ -278,24 +318,22 @@ int main(int argc, const char *argv[]) try { std::cout << common::what(ex) << std::endl; return ex.return_code; } - if (create_wallet || create_legacy_wallet) { - std::cout << "Successfully created wallet with address " + if (create_wallet) { + std::cout << "Successfully created wallet with first address " << currency.account_address_as_string(wallet->get_first_address()) << std::endl; if (!launch_after_command) return 0; } + std::cout << "Opened wallet with first address " << currency.account_address_as_string(wallet->get_first_address()) + << std::endl; try { - if (!backup_wallet.empty()) { - if (import_keys || create_wallet || create_legacy_wallet || export_keys) { - std::cout << "When doing wallet backup you cannot import keys, export keys, create wallet." - << std::endl; - return api::WALLETD_WRONG_ARGS; - } + if (!backup_wallet_data.empty()) { const std::string name = platform::get_filename_without_folder(wallet_file); - const std::string dst_name = backup_wallet + "/" + name; - const std::string dst_cache = backup_wallet + "/wallet_cache/" + wallet->get_cache_name(); - if (!platform::create_folder_if_necessary(backup_wallet + "/wallet_cache")) { - std::cout << "Could not create folder for backup " << (backup_wallet + "/wallet_cache") << std::endl; + const std::string dst_name = backup_wallet_data + "/" + name; + const std::string dst_cache = backup_wallet_data + "/wallet_cache/" + wallet->get_cache_name(); + if (!platform::create_folder_if_necessary(backup_wallet_data + "/wallet_cache")) { + std::cout << "Could not create folder for backup " << (backup_wallet_data + "/wallet_cache") + << std::endl; return 1; } if (!platform::create_folder_if_necessary(dst_cache)) { @@ -303,7 +341,7 @@ int main(int argc, const char *argv[]) try { return 1; } std::cout << "Backing up wallet file to " << dst_name << std::endl; - wallet->backup(dst_name, set_password ? new_password : password); + wallet->backup(dst_name, set_password ? new_password : password.get()); common::console::set_text_color(common::console::BrightRed); std::cout << "There will be no progress printed for 1-20 minutes, depending on wallet size and computer speed." @@ -328,14 +366,23 @@ int main(int argc, const char *argv[]) try { return api::WALLETD_WRONG_ARGS; } wallet->export_wallet( - export_view_only, set_password ? new_password : password, true, view_outgoing_addresses); + export_view_only, set_password ? new_password : password.get(), true, view_outgoing_addresses); std::cout << "Successfully exported view-only copy of the wallet" << std::endl; return 0; } if (export_keys) { - if (!wallet->is_deterministic() && wallet->get_records().size() != 1) + if (wallet->is_amethyst()) + throw Wallet::Exception(api::WALLETD_WRONG_ARGS, "You can only export keys from a legacy wallet"); + if (wallet->get_actual_records_count() != 1) + throw Wallet::Exception(api::WALLETD_EXPORTKEYS_MORETHANONE, + "You can only export keys from a legacy wallet if it is containing 1 address, otherwise just back it up"); + std::cout << wallet->export_keys() << std::endl; // exports mnemonic for HD wallet + return 0; + } + if (export_mnemonic) { + if (!wallet->is_amethyst()) throw Wallet::Exception( - api::WALLETD_EXPORTKEYS_MORETHANONE, "You can only export keys from a wallet containing 1 address"); + api::WALLETD_WRONG_ARGS, "You can only export mnemonic from a deterministic wallet"); std::cout << wallet->export_keys() << std::endl; // exports mnemonic for HD wallet return 0; } @@ -355,6 +402,27 @@ int main(int argc, const char *argv[]) try { std::cout << common::what(ex) << std::endl; return 1; } + if (!walletd_http_auth) { + std::cout << "Enter HTTP authorization : for walletd RPC: " << std::flush; + walletd_http_auth = std::string(); + if (!console_setup.getline(walletd_http_auth.get(), true)) { + std::cout << "Unexpected end of stdin" << std::endl; + return api::WALLETD_WRONG_ARGS; + } + std::cout << std::endl; + } + boost::algorithm::trim(walletd_http_auth.get()); + config.walletd_authorization = common::base64::encode(common::as_binary_array(walletd_http_auth.get())); + if (config.walletd_authorization.empty()) { + common::console::set_text_color(common::console::BrightRed); + std::cout << "No authorization for RPC is a security risk. Use username with a strong password" << std::endl; + common::console::set_text_color(common::console::Default); + } else { + if (walletd_http_auth.get().find(":") == std::string::npos) { + std::cout << "HTTP authorization must be in the format :" << std::endl; + return api::WALLETD_WRONG_ARGS; + } + } std::unique_ptr blockchain_lock; try { if (!config.bytecoind_remote_port) @@ -373,34 +441,6 @@ int main(int argc, const char *argv[]) try { std::cout << "Wallet with the same first address is in use - " << common::what(ex) << std::endl; return api::WALLET_WITH_SAME_KEYS_IN_USE; } - if (!ask_password) { - common::console::set_text_color(common::console::BrightRed); - std::cout << "Password on command line is a security risk. Use 'echo | ./walletd' or 'cat secrets.txt | " - "./walletd'" - << std::endl; - common::console::set_text_color(common::console::Default); - } - if (ask_walletd_http_auth) { - std::cout << "Enter HTTP authorization : for walletd RPC: " << std::flush; - if (!console_setup.getline(walletd_http_auth, true)) { - std::cout << "Unexpected end of stdin" << std::endl; - return api::WALLETD_WRONG_ARGS; - } - std::cout << std::endl; - } - boost::algorithm::trim(walletd_http_auth); - config.walletd_authorization = common::base64::encode( - BinaryArray(walletd_http_auth.data(), walletd_http_auth.data() + walletd_http_auth.size())); - if (config.walletd_authorization.empty()) { - common::console::set_text_color(common::console::BrightRed); - std::cout << "No authorization for RPC is a security risk. Use username with a strong password" << std::endl; - common::console::set_text_color(common::console::Default); - } else { - if (walletd_http_auth.find(":") == std::string::npos) { - std::cout << "HTTP authorization must be in the format :" << std::endl; - return api::WALLETD_WRONG_ARGS; - } - } WalletState wallet_state(*wallet, logManagerWalletNode, config, currency); // wallet_state.test_undo_blocks(); boost::asio::io_service io; diff --git a/src/platform/DBlmdb.cpp b/src/platform/DBlmdb.cpp index ab83df5b..3d8ed407 100644 --- a/src/platform/DBlmdb.cpp +++ b/src/platform/DBlmdb.cpp @@ -88,8 +88,8 @@ DBlmdb::DBlmdb(OpenMode open_mode, const std::string &full_path, uint64_t max_db MDB_NOMETASYNC | (open_mode == O_READ_EXISTING ? MDB_RDONLY : 0), 0644), "Failed to open database " + full_path + " in mdb_env_open "); // MDB_NOMETASYNC - We agree to trade chance of losing 1 last transaction for 2x performance boost - db_txn.reset(new lmdb::Txn(db_env)); - db_dbi.reset(new lmdb::Dbi(*db_txn)); + db_txn = std::make_unique(db_env); + db_dbi = std::make_unique(*db_txn); } size_t DBlmdb::test_get_approximate_size() const { @@ -168,7 +168,7 @@ DBlmdb::Cursor DBlmdb::rbegin(const std::string &prefix, const std::string &midd void DBlmdb::commit_db_txn() { db_txn->commit(); db_txn.reset(); - db_txn.reset(new lmdb::Txn(db_env)); + db_txn = std::make_unique(db_env); } void DBlmdb::put(const std::string &key, const common::BinaryArray &value, bool nooverwrite) { diff --git a/src/platform/DBsqlite3.cpp b/src/platform/DBsqlite3.cpp index fc88854a..b76466b7 100644 --- a/src/platform/DBsqlite3.cpp +++ b/src/platform/DBsqlite3.cpp @@ -2,9 +2,9 @@ // Licensed under the GNU Lesser General Public License. See LICENSE for details. #include "DBsqlite3.hpp" -#include #include #include "PathTools.hpp" +#include "common/Invariant.hpp" #include "common/Math.hpp" #include "common/string.hpp" diff --git a/src/platform/Ledger.cpp b/src/platform/Ledger.cpp deleted file mode 100644 index e81046c9..00000000 --- a/src/platform/Ledger.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2012-2018, The CryptoNote developers, The Bytecoin developers. -// Licensed under the GNU Lesser General Public License. See LICENSE for details. - -#include "Ledger.hpp" -#include -#include -#include "common/Invariant.hpp" - -using namespace platform; -using namespace crypto; - -Ledger::Ledger() { - // read m_wallet_key, m_spend_key_base_public_key from device - - // Code to be run on device, m_address_type == 1 for now - - // cn::Bip32Key k0 = master_key.derive_key(0x8000002c); - // cn::Bip32Key k1 = k0.derive_key(0x80000300); - // cn::Bip32Key k2 = k1.derive_key(0x80000000 + m_address_type); - // cn::Bip32Key k3 = k2.derive_key(0); - // cn::Bip32Key k4 = k3.derive_key(0); - // m_seed = crypto::cn_fast_hash(k4.get_priv_key().data(), k4.get_priv_key().size()); - // m_tx_derivation_seed = derive_from_seed(m_seed, "tx_derivation"); - // BinaryArray sk_data = m_seed | "spend_key_base"; - // m_spend_key_base.secret_key = crypto::hash_to_scalar(sk_data.data(), sk_data.size()); - // invariant(crypto::secret_key_to_public_key(m_spend_key_base.secret_key, &m_spend_key_base.public_key), ""); - // BinaryArray vk_data = - // BinaryArray{std::begin(m_spend_key_base.public_key.data), std::end(m_spend_key_base.public_key.data)} | - // "view_key"; - // m_view_secret_key = crypto::hash_to_scalar(vk_data.data(), vk_data.size()); - // invariant(crypto::secret_key_to_public_key(m_view_secret_key, &m_view_public_key), ""); - - // m_wallet_key = chacha_key{derive_from_seed(m_seed, "wallet_key")}; -} - -Ledger::~Ledger() {} - -std::vector Ledger::mul_by_view_secret_key(const std::vector &output_public_keys) const { - // multiply by m_view_secret_key on device, throw if PublicKey detected to be invalid by device - - // const ge_p3 output_public_key_p3 = ge_frombytes_vartime(output_public_key); - // const ge_p3 p_v = ge_scalarmult3(view_secret_key, output_public_key_p3); - - // then either convert ge_p3 to PublicKey on device or computer - - // const PublicKey p_v_packed = ge_tobytes(p_v); - - return std::vector(); -} diff --git a/src/platform/Ledger.hpp b/src/platform/Ledger.hpp deleted file mode 100644 index 1fc5328b..00000000 --- a/src/platform/Ledger.hpp +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2012-2018, The CryptoNote developers, The Bytecoin developers. -// Licensed under the GNU Lesser General Public License. See LICENSE for details. - -#pragma once - -#include -#include "crypto/chacha.hpp" -#include "crypto/crypto.hpp" - -namespace platform { - -// Prototype - max simplified synchronous calls - -// All funs including constructor throw std::runtime_error when connection to ledger lost before end of fun. -// All funs must quickly try reestablishing connection at the start if it was lost during previous call -// Calls might be from different threads, but will be externally synchronized - -class Ledger { - crypto::chacha_key m_wallet_key; // wallet encryption key, derived from secret - crypto::PublicKey m_spend_key_base_public_key; // derived from secret -public: - Ledger(); - ~Ledger(); - crypto::chacha_key get_wallet_key() const { return m_wallet_key; } - crypto::PublicKey get_spend_key_base_public_key() const { return m_spend_key_base_public_key; } - std::vector mul_by_view_secret_key( - const std::vector &output_public_keys) const; - bool sign_transaction() const { return false; } // TODO - params - bool create_sendproof() const { return false; } // TODO - params -}; - -} // namespace platform diff --git a/src/rpc_api.cpp b/src/rpc_api.cpp index 2d2d3e05..d47f5c91 100644 --- a/src/rpc_api.cpp +++ b/src/rpc_api.cpp @@ -83,11 +83,8 @@ namespace seria { void ser_members(api::Output &v, ISeria &s, bool only_bytecoind_fields) { seria_kv("amount", v.amount, s); seria_kv("public_key", v.public_key, s); - seria_kv("index", v.index, s); - if (dynamic_cast(&s)) - seria_kv("global_index", v.index, s); // deprecated - if (dynamic_cast(&s)) - seria_kv("global_index", v.index, s); // deprecated + seria_kv("stack_index", v.stack_index, s); + seria_kv("global_index", v.global_index, s); seria_kv("height", v.height, s); seria_kv("unlock_block_or_timestamp", v.unlock_block_or_timestamp, s); if (dynamic_cast(&s)) @@ -130,6 +127,7 @@ void ser_members(api::BlockHeader &v, ISeria &s) { seria_kv("transactions_size", v.transactions_size, s); seria_kv("already_generated_coins", v.already_generated_coins, s); seria_kv("already_generated_transactions", v.already_generated_transactions, s); + seria_kv("already_generated_key_outputs", v.already_generated_key_outputs, s); seria_kv("block_capacity_vote", v.block_capacity_vote, s); seria_kv("block_capacity_vote_median", v.block_capacity_vote_median, s); seria_kv("size_median", v.size_median, s); @@ -192,9 +190,8 @@ void ser_members(api::RawBlock &v, ISeria &s) { seria_kv("header", v.header, s); seria_kv("raw_header", v.raw_header, s); seria_kv("raw_transactions", v.raw_transactions, s); - seria_kv("signatures", v.signatures, s); seria_kv("transactions", v.transactions, s); - seria_kv("output_indexes", v.output_indexes, s); + seria_kv("output_stack_indexes", v.output_stack_indexes, s); } void ser_members(api::Balance &v, ISeria &s) { @@ -227,9 +224,7 @@ void ser_members(cn::api::walletd::GetWalletInfo::Request &v, ISeria &s) { void ser_members(cn::api::walletd::GetWalletInfo::Response &v, ISeria &s) { seria_kv("view_only", v.view_only, s); - seria_kv("deterministic", v.deterministic, s); - seria_kv("auditable", v.auditable, s); - seria_kv("unlinkable", v.unlinkable, s); + seria_kv("amethyst", v.amethyst, s); seria_kv("can_view_outgoing_addresses", v.can_view_outgoing_addresses, s); seria_kv("wallet_creation_timestamp", v.wallet_creation_timestamp, s); seria_kv("total_address_count", v.total_address_count, s); @@ -384,7 +379,6 @@ void ser_members(api::cnd::GetBlockHeader::Response &v, ISeria &s) { void ser_members(api::cnd::GetRawBlock::Request &v, ISeria &s) { seria_kv("hash", v.hash, s); seria_kv("height_or_depth", v.height_or_depth, s); - seria_kv("need_signatures", v.need_signatures, s); } void ser_members(api::cnd::GetRawBlock::Response &v, ISeria &s) { @@ -398,7 +392,6 @@ void ser_members(api::cnd::SyncBlocks::Request &v, ISeria &s) { seria_kv("first_block_timestamp", v.first_block_timestamp, s); seria_kv("max_count", v.max_count, s); seria_kv("max_size", v.max_size, s); - seria_kv("need_signatures", v.need_signatures, s); seria_kv("need_redundant_data", v.need_redundant_data, s); } @@ -408,15 +401,12 @@ void ser_members(api::cnd::SyncBlocks::Response &v, ISeria &s) { seria_kv("status", v.status, s); } -void ser_members(api::cnd::GetRawTransaction::Request &v, ISeria &s) { - seria_kv("hash", v.hash, s); - seria_kv("need_signatures", v.need_signatures, s); -} +void ser_members(api::cnd::GetRawTransaction::Request &v, ISeria &s) { seria_kv("hash", v.hash, s); } void ser_members(api::cnd::GetRawTransaction::Response &v, ISeria &s) { seria_kv("transaction", v.transaction, s); seria_kv("raw_transaction", v.raw_transaction, s); - seria_kv("signatures", v.signatures, s); + seria_kv("mixed_public_keys", v.mixed_public_keys, s); } void ser_members(api::cnd::SyncMemPool::Request &v, ISeria &s) { @@ -426,7 +416,6 @@ void ser_members(api::cnd::SyncMemPool::Request &v, ISeria &s) { if (s.is_input() && !std::is_sorted(v.known_hashes.begin(), v.known_hashes.end())) throw std::runtime_error( "SyncMemPool::Request known_hashes must be sorted in increasing order (from [0000..] to [ffff..])"); - seria_kv("need_signatures", v.need_signatures, s); seria_kv("need_redundant_data", v.need_redundant_data, s); } diff --git a/src/rpc_api.hpp b/src/rpc_api.hpp index a6061f29..1a38a689 100644 --- a/src/rpc_api.hpp +++ b/src/rpc_api.hpp @@ -46,7 +46,8 @@ constexpr HeightOrDepth DEFAULT_CONFIRMATIONS = 5; struct Output { Amount amount = 0; PublicKey public_key; - size_t index = 0; + size_t stack_index = 0; + size_t global_index = 0; // Added from transaction Height height = 0; // Added from block BlockOrTimestamp unlock_block_or_timestamp = 0; @@ -113,6 +114,7 @@ struct BlockHeader { Amount already_generated_coins = 0; size_t already_generated_transactions = 0; + size_t already_generated_key_outputs = 0; size_t size_median = 0; // median of transactions_size, 0 in amethyst size_t effective_size_median = 0; // median of transactions_size, 0 in amethyst size_t block_capacity_vote = 0; // 0 before amethyst @@ -134,11 +136,10 @@ struct RawBlock { api::BlockHeader header; BlockTemplate raw_header; std::vector raw_transactions; - std::vector signatures; // empty unless request.signatures set std::vector transactions; // for each transaction + coinbase, contain only info known to bytecoind std::vector> - output_indexes; // for each transaction + coinbase, not empty only if block in main chain + output_stack_indexes; // for each transaction + coinbase, not empty only if block in main chain }; // In legacy view-only wallets sum of incoming outputs can be arbitrary large and overflow @@ -176,7 +177,9 @@ enum return_code { 210, // Another walletd instance is using the same or another wallet file with the same keys. WALLETD_WRONG_ARGS = 211, WALLETD_EXPORTKEYS_MORETHANONE = 212, // We can export keys only if wallet file contains exactly 1 spend keypair - WALLETD_MNEMONIC_CRC = 213 // Unknown version or wrong crc + WALLETD_MNEMONIC_CRC = 213, // Unknown version or wrong crc + WALLET_FILE_HARDWARE_DECRYPT_ERROR = + 214 // This wallet file is backed by hardware and no hardware could decrypt wallet file }; // Returned from many methods @@ -274,11 +277,9 @@ struct GetWalletInfo { }; struct Response { bool view_only = false; - bool deterministic = false; - bool auditable = false; - bool unlinkable = false; - bool can_view_outgoing_addresses = false; - Timestamp wallet_creation_timestamp = 0; // O if not known (restored form keys and did not sync yet) + bool amethyst = false; // combines deterministic, auditable, unlinkable properties + bool can_view_outgoing_addresses = false; // can be false for some view-only wallets + Timestamp wallet_creation_timestamp = 0; // O if not known (restored form keys and did not sync yet) std::string first_address; size_t total_address_count = 0; // Useful when iterating std::string net; // network walletd is currently operating on @@ -559,7 +560,6 @@ struct GetRawBlock { struct Request { Hash hash; HeightOrDepth height_or_depth = std::numeric_limits::max(); - bool need_signatures = false; // signatures are usually of no interest, they all are checked and valid }; struct Response { api::RawBlock block; @@ -591,7 +591,7 @@ struct GetBlockHeader { struct SyncBlocks { // Used by walletd, block explorer, etc to sync to bytecoind static std::string method() { return "sync_blocks"; } - static std::string bin_method() { return "sync_blocks_v3.4"; } + static std::string bin_method() { return "sync_blocks_v3.4.0"; } // we increment bin method version when binary format changes struct Request { @@ -600,8 +600,7 @@ struct SyncBlocks { // Used by walletd, block explorer, etc to sync to bytecoin Timestamp first_block_timestamp = 0; size_t max_count = MAX_COUNT / 10; size_t max_size = 10 * 1024 * 1024; // No more than 10 megabytes of blocks + 1 block - bool need_signatures = false; // signatures are usually of no interest, they all are checked and valid - bool need_redundant_data = true; // walletd and smart clients can save traffic + bool need_redundant_data = true; // walletd and smart clients can save traffic }; struct Response { std::vector blocks; @@ -614,12 +613,12 @@ struct GetRawTransaction { static std::string method() { return "get_raw_transaction"; } struct Request { Hash hash; - bool need_signatures = false; // signatures are usually of no interest, they all are checked and valid }; struct Response { api::Transaction transaction; // contain only info known to bytecoind TransactionPrefix raw_transaction; - TransactionSignatures signatures; // empty unless request.signatures set + std::vector> mixed_public_keys; // not documented yet + // TransactionPrefix contains only indexes, we need public keys to sign sendproof }; enum { HASH_NOT_FOUND = -5 // Neither in main nor in side chain @@ -629,17 +628,15 @@ struct GetRawTransaction { // Signature of this method will stabilize to the end of beta struct SyncMemPool { // Used by walletd sync process static std::string method() { return "sync_mem_pool"; } - static std::string bin_method() { return "sync_mem_pool_v3.4"; } + static std::string bin_method() { return "sync_mem_pool_v3.4.0"; } // we increment bin method version when binary format changes struct Request { - std::vector known_hashes; // Should be sent sorted - bool need_signatures = false; // signatures are usually of no interest, they all are checked and valid - bool need_redundant_data = true; // walletd and smart clients can save traffic + std::vector known_hashes; // Should be sent sorted + bool need_redundant_data = true; // walletd and smart clients can save traffic }; struct Response { std::vector removed_hashes; // Hashes no more in pool std::vector added_raw_transactions; // New raw transactions in pool - std::vector added_signatures; // empty unless request.signatures set std::vector added_transactions; // contain only info known to bytecoind GetStatus::Response status; // We save roundtrip during sync by also sending status here }; diff --git a/src/version.hpp b/src/version.hpp index c9750d41..0a3f7bb6 100644 --- a/src/version.hpp +++ b/src/version.hpp @@ -4,8 +4,8 @@ #pragma once // defines are for Windows resource compiler -#define bytecoin_VERSION_WINDOWS_COMMA 3, 18, 12, 18 -#define bytecoin_VERSION_STRING "3.4.0-beta-20181218 (amethyst)" +#define bytecoin_VERSION_WINDOWS_COMMA 3, 19, 2, 7 +#define bytecoin_VERSION_STRING "3.4.0" #ifndef RC_INVOKED // Windows resource compiler diff --git a/tests/crypto/benchmarks.cpp b/tests/crypto/benchmarks.cpp new file mode 100644 index 00000000..46bb264c --- /dev/null +++ b/tests/crypto/benchmarks.cpp @@ -0,0 +1,263 @@ +#include "benchmarks.hpp" +#include +#include +#include +#include +#include "crypto/bernstein/crypto-ops.h" +#include "crypto/crypto_helpers.hpp" +#include "crypto/hash.hpp" + +using std::endl; +using std::left; +using std::right; +using std::setfill; +using std::setprecision; +using std::setw; + +using namespace crypto; + +int min(int a, int b) { return (a < b) ? a : b; } + +typedef crypto::EllipticCurveScalar Scalar; +typedef ge_p3 Point; + +static uint8_t global_result = 0; +void update_global_result(const void *data, size_t size) { + for (size_t i = 0; i != size; ++i) + global_result ^= reinterpret_cast(data)[i]; +} + +#define CLOCK(X) \ + auto start = std::chrono::high_resolution_clock::now(); \ + for (int i = 0; i < count; ++i) { \ + X; \ + update_global_result(&result, sizeof(result)); \ + static_assert(std::is_trivially_copyable::value, "result must be trivially copyable"); \ + } \ + auto finish = std::chrono::high_resolution_clock::now(); \ + auto time_delta = std::chrono::duration_cast(finish - start).count(); \ + return time_delta; + +Point p2_to_p3(const ge_p2 &p) { + Point result{}; + fe_copy(result.X, p.X); + fe_copy(result.Y, p.Y); + fe_copy(result.Z, p.Z); + fe_1(result.T); + return result; +} + +long test_scalarmult_base(int count, const Scalar *scalars) { + CLOCK(const Scalar &s = scalars[i]; + + Point result; + ge_scalarmult_base(&result, &s);) +} + +long test_scalarmult(int count, const Scalar *scalars, const Point *points) { + CLOCK(const Scalar &s = scalars[i]; const Point &p = points[i]; + + Point result; + ge_scalarmult3(&result, &s, &p);) +} + +long test_scalarmult_aligned(int count, const std::pair *merged_sp) { + CLOCK(Scalar s; Point p; std::tie(s, p) = merged_sp[i]; + + Point result; + ge_scalarmult3(&result, &s, &p);) +} + +long test_scalarmult_via_phantom_point(int count, const Scalar *scalars, const Point *points) { + Scalar sc0; + sc_0(&sc0); + CLOCK(const Scalar &s = scalars[i]; const Point &p = points[i]; + + ge_p3 result; + ge_double_scalarmult_base_vartime3(&result, &sc0, &p, &s);) +} + +long test_scalarmult_via_double_phantom_aligned(int count, const std::pair *merged_sp) { + Scalar sc0; + sc_0(&sc0); + CLOCK(Scalar s; Point p; std::tie(s, p) = merged_sp[i]; + + ge_p3 result; + ge_double_scalarmult_base_vartime3(&result, &sc0, &p, &s);) +} + +long test_double_scalarmult_base(int count, const Scalar *scalars, const Point *points) { + CLOCK(const Scalar &s = scalars[i]; const Point &p = points[i]; + + ge_p3 result; + ge_double_scalarmult_base_vartime3(&result, &s, &p, &s);) +} + +long test_double_scalarmult_badprecomp(int count, const Scalar *scalars, const Point *points) { + CLOCK(const Scalar &s = scalars[i]; const Point &p = points[i]; + + ge_p3 result; + ge_dsmp cache; + ge_dsm_precomp(&cache, &p); + ge_double_scalarmult_precomp_vartime3(&result, &s, &p, &s, &cache);) +} + +long test_double_scalarmult_precomp(int count, const Scalar *scalars, const Point *points, const ge_dsmp *precomp) { + CLOCK(const Scalar &s = scalars[i]; const Point &p = points[i]; const ge_dsmp &cache = precomp[i]; + + ge_p3 result; + ge_double_scalarmult_precomp_vartime3(&result, &s, &p, &s, &cache);) +} + +long test_double_scalarmult(int count, const Scalar *scalars, const Point *points) { + CLOCK(const Point &p = points[i]; const Scalar &s = scalars[i]; cryptoEllipticCurveScalar s2{}; + + sc_add(&s2, &s, &s); + ge_dsmp dsm_precomp; + ge_dsm_precomp(&dsm_precomp, &p); + ge_p3 result; + ge_double_scalarmult_precomp_vartime3(&result, &s, &p, &s2, &dsm_precomp);) +} + +long test_double_scalarmult_simple(int count, const Scalar *scalars, const Point *points) { + CLOCK(const Point &p = points[i]; const Scalar &s = scalars[i]; Scalar s2{}; + + sc_add(&s2, &s, &s); + auto pmul1 = crypto::ge_scalarmult3(s, p); + auto pmul2 = crypto::ge_scalarmult3(s2, p); + auto result = crypto::ge_add(pmul1, pmul2);) +} + +long test_double_scalarmult_simple_opt(int count, const Scalar *scalars, const Point *points) { + Scalar sc0; + sc_0(&sc0); + CLOCK(const Scalar &s = scalars[i]; const Point &p = points[i]; Scalar s2{}; + + sc_add(&s2, &s, &s); + auto pmul1 = crypto::ge_double_scalarmult_base_vartime3(s, p, sc0); + auto pmul2 = crypto::ge_double_scalarmult_base_vartime3(s2, p, sc0); + auto result = crypto::ge_add(pmul1, pmul2);) +} + +long test_double_scalarmult_simple_aligned(int count, + const std::tuple *merged_double_points) { + Scalar sc0; + sc_0(&sc0); + CLOCK(Scalar s1; Scalar s2; Point p1; Point p2; std::tie(s1, s2, p1, p2) = merged_double_points[i]; + + auto pmul1 = crypto::ge_double_scalarmult_base_vartime3(s1, p1, sc0); + auto pmul2 = crypto::ge_double_scalarmult_base_vartime3(s2, p2, sc0); + auto result = crypto::ge_add(pmul1, pmul2);) +} + +long test_frombytes(int count, const EllipticCurvePoint *bytes) { + CLOCK(auto &b = bytes[i]; + + auto result = ge_frombytes_vartime(b);) +} + +long test_fromfe_frombytes(int count, const EllipticCurvePoint *bytes) { + CLOCK(auto &b = bytes[i]; + + ge_p2 result; + ge_fromfe_frombytes_vartime(&result, b.data);) +} + +long test_check_subgroup(int count, const Point *points) { + CLOCK(auto &p = points[i]; ge_dsmp cache; ge_dsm_precomp(&cache, &p); + int result = ge_check_subgroup_precomp_vartime(&cache);) +} + +long test_sc_mul(int count, const Scalar *a, const Scalar *b) { + CLOCK(EllipticCurveScalar result; sc_mul(&result, &a[i], &b[i])); +} + +long test_sc_sub(int count, const Scalar *a, const Scalar *b) { + CLOCK(EllipticCurveScalar result; sc_sub(&result, &a[i], &b[i])); +} + +long test_sc_mul_sub(int count, const Scalar *a, const Scalar *b, const Scalar *c) { + CLOCK(EllipticCurveScalar result; sc_mulsub(&result, &a[i], &b[i], &c[i])); +} + +long test_sc_invert(int count, const Scalar *a) { CLOCK(EllipticCurveScalar result; sc_invert(&result, &a[i])); } + +long test_precomp(int count, const Point *a) { CLOCK(ge_dsmp result; ge_dsm_precomp(&result, &a[i])); } + +// Example with std::string will fail compilation +// long test_string(int count){ +// CLOCK(std::string result;) +//} + +void benchmark_crypto_ops(int count, std::ostream &out) { + std::vector scalars(count); + std::vector points(count); + std::vector precomp(count); + std::vector bytes(count); + std::vector> merged_sp(count); + auto merged_double_points = + std::make_unique[]>(count); // Also ok in C++14 + + for (int i = 0; i < count; ++i) { + KeyPair k = random_keypair(); + bytes[i] = k.public_key; + auto s = k.secret_key; + auto p = ge_frombytes_vartime(k.public_key); + scalars[i] = s; + points[i] = p; + merged_sp[i] = std::make_pair(s, p); + + k = random_keypair(); + auto s2 = k.secret_key; + auto p2 = ge_frombytes_vartime(k.public_key); + merged_double_points.get()[i] = std::make_tuple(s, s2, p, p2); + + ge_dsm_precomp(&precomp[i], &p); + } + + std::map benchmark_results; + + benchmark_results.insert(std::make_pair("test_frombytes", test_frombytes(count, bytes.data()))); + benchmark_results.insert(std::make_pair("test_fromfe_frombytes", test_fromfe_frombytes(count, bytes.data()))); + benchmark_results.insert(std::make_pair("test_check_subgroup", test_check_subgroup(count, points.data()))); + benchmark_results.insert(std::make_pair( + "test_double_scalarmult_base", test_double_scalarmult_base(count, scalars.data(), points.data()))); + benchmark_results.insert(std::make_pair( + "test_double_scalarmult_badprecomp", test_double_scalarmult_badprecomp(count, scalars.data(), points.data()))); + benchmark_results.insert(std::make_pair("test_double_scalarmult_precomp", + test_double_scalarmult_precomp(count, scalars.data(), points.data(), precomp.data()))); + benchmark_results.insert(std::make_pair( + "test_double_scalarmult_simple", test_double_scalarmult_simple(count, scalars.data(), points.data()))); + benchmark_results.insert(std::make_pair( + "test_double_scalarmult_simple_opt", test_double_scalarmult_simple_opt(count, scalars.data(), points.data()))); + benchmark_results.insert(std::make_pair("test_double_scalarmult_simple_aligned", + test_double_scalarmult_simple_aligned(count, merged_double_points.get()))); + benchmark_results.insert(std::make_pair("test_scalarmult_base", test_scalarmult_base(count, scalars.data()))); + benchmark_results.insert(std::make_pair("test_scalarmult", test_scalarmult(count, scalars.data(), points.data()))); + benchmark_results.insert( + std::make_pair("test_scalarmult_aligned", test_scalarmult_aligned(count, merged_sp.data()))); + benchmark_results.insert(std::make_pair( + "test_scalarmult_via_phantom_point", test_scalarmult_via_phantom_point(count, scalars.data(), points.data()))); + benchmark_results.insert(std::make_pair("test_scalarmult_via_double_phantom_aligned", + test_scalarmult_via_double_phantom_aligned(count, merged_sp.data()))); + benchmark_results.insert( + std::make_pair("test_double_scalarmult", test_double_scalarmult(count, scalars.data(), points.data()))); + + benchmark_results["test_precomp"] = test_precomp(count, points.data()); + benchmark_results["test_sc_mul"] = test_sc_mul(count, scalars.data(), scalars.data()); + benchmark_results["test_sc_sub"] = test_sc_sub(count, scalars.data(), scalars.data()); + benchmark_results["test_sc_mul_sub"] = test_sc_mul_sub(count, scalars.data(), scalars.data(), scalars.data()); + benchmark_results["test_sc_invert"] = test_sc_invert(count, scalars.data()); + + for (auto &tup : benchmark_results) { + auto &name = tup.first; + auto &time_delta = tup.second; + std::stringstream output; + double total_ms = time_delta / 1000000.; + output << left << setw(6) << count << " cycles " << right << setw(10) << std::fixed << setprecision(3) + << total_ms << " ms " << right << setw(7) << std::fixed << setprecision(3) << total_ms / count + << " ms/op " << left << name << endl; + out << output.str(); + } + out << "global_result=" << int(global_result) << endl; // so compiler cannot optimize calcs out +} diff --git a/tests/crypto/benchmarks.hpp b/tests/crypto/benchmarks.hpp new file mode 100644 index 00000000..bee9fd1b --- /dev/null +++ b/tests/crypto/benchmarks.hpp @@ -0,0 +1,14 @@ +// +// Created by user on 21-1-19. +// + +#pragma once + +#ifndef BYTECOIN_BENCHMARKS_HPP +#define BYTECOIN_BENCHMARKS_HPP + +#include + +void benchmark_crypto_ops(int count, std::ostream &out); + +#endif // BYTECOIN_BENCHMARKS_HPP diff --git a/tests/crypto/test_crypto.cpp b/tests/crypto/test_crypto.cpp index 67d85657..e4e0a5cc 100644 --- a/tests/crypto/test_crypto.cpp +++ b/tests/crypto/test_crypto.cpp @@ -5,6 +5,7 @@ //#include //#include #include +#include #include #include "common/Invariant.hpp" #include "crypto/bernstein/crypto-ops.h" @@ -12,9 +13,11 @@ #include "test_crypto.hpp" #include "../io.hpp" +#include "benchmarks.hpp" #include "common/StringTools.hpp" #include "crypto/bernstein/crypto-ops.h" #include "crypto/crypto.hpp" +#include "crypto/crypto_helpers.hpp" #include "crypto/hash.hpp" #include "crypto/random.h" @@ -27,287 +30,440 @@ static void check(bool expr, size_t test) { using namespace crypto; -class FastHashStream { -public: - void append(const void *data, size_t size) {} - Hash cn_fast_hash() const { return Hash{}; } -}; - -class LedgerProxy { - PublicKey m_view_public_key; - SecretKey m_view_secret_key; - Hash m_seed; - Hash m_tx_derivation_seed; - -public: - ge_p3 unlinkable_underive_public_key_step1(const PublicKey &output_public_key) { - // const ge_p3 output_public_key_p3 = ge_frombytes_vartime(output_public_key); - // const ge_cached p_v = ge_p3_to_cached(ge_scalarmult3(view_secret_key, output_public_key_p3)); - // ge_p1p1 point_diff; - // ge_sub(&point_diff, &encrypted_output_secret_p3, &p_v); - return ge_p3{}; +void test_check_scalar(std::fstream &input, size_t test) { + EllipticCurveScalar scalar; + bool expected = false; + get(input, scalar, expected); + const bool actual = sc_isvalid_vartime(&scalar) != 0; + check(expected == actual, test); +} + +void test_hash_to_scalar(std::fstream &input, size_t test) { + std::vector data; + EllipticCurveScalar expected; + get(input, data, expected); + const auto actual = hash_to_scalar(data.data(), data.size()); + check(expected == actual, test); +} + +void test_random_scalar(std::fstream &input, size_t test) { + EllipticCurveScalar expected, actual; + get(input, expected); + actual = random_scalar(); + check(expected == actual, test); + + // We do not have separate tests for inversions, so perform it on + // random_scalars + EllipticCurveScalar inv_actual, inv_inv_actual; + EllipticCurveScalar b, inv_b, inv_inv_b, a_b, inv_a_inv_b, inv_a_b; + b = hash_to_scalar(actual.data, sizeof(actual.data)); + // FIXME move inversion tests to separate function + sc_invert(&inv_actual, &actual); + sc_invert(&inv_inv_actual, &inv_actual); + sc_invert(&inv_b, &b); + sc_invert(&inv_inv_b, &inv_b); + invariant(actual == inv_inv_actual, ""); + invariant(b == inv_inv_b, ""); + sc_mul(&a_b, &actual, &b); + sc_mul(&inv_a_inv_b, &inv_actual, &inv_b); + sc_invert(&inv_a_b, &a_b); + invariant(inv_a_inv_b == inv_a_b, ""); + + EllipticCurveScalar one, inv_one, real_one; + sc_1(&real_one); + sc_mul(&one, &actual, &inv_actual); + invariant(one == real_one, ""); + sc_mul(&one, &b, &inv_b); + invariant(one == real_one, ""); + sc_invert(&inv_one, &one); + invariant(one == inv_one, ""); +} + +void test_generate_keys(std::fstream &input, size_t test) { + PublicKey expected1, actual1; + SecretKey expected2, actual2; + get(input, expected1, expected2); + random_keypair(actual1, actual2); + check(expected1 == actual1, test); + check(expected2 == actual2, test); +} + +void test_check_key(std::fstream &input, size_t test) { + PublicKey key; + bool expected = false; + get(input, key, expected); + const bool actual = key_isvalid(key); + check(expected == actual, test); +} + +void test_secret_key_to_public_key(std::fstream &input, size_t test) { + SecretKey sec; + bool expected1 = false; + PublicKey expected2, actual2; + get(input, sec, expected1); + if (expected1) { + get(input, expected2); + } + const bool actual1 = secret_key_to_public_key(sec, &actual2); + check(!(expected1 != actual1 || (expected1 && expected2 != actual2)), test); +} + +void test_generate_key_derivation(std::fstream &input, size_t test) { + PublicKey key1; + SecretKey key2; + bool expected1 = false; + KeyDerivation expected2; + get(input, key1, key2, expected1); + if (expected1) { + get(input, expected2); + } + try { + const auto actual2 = generate_key_derivation(key1, key2); + check(actual2 == expected2, test); + } catch (const std::exception &) { + check(!expected1, test); + } +} + +void test_derive_public_key(std::fstream &input, size_t test) { + KeyDerivation derivation; + size_t output_index; + PublicKey base; + bool expected1 = false; + PublicKey expected2; + get(input, derivation, output_index, base, expected1); + if (expected1) { + get(input, expected2); + } + try { + const auto actual2 = derive_output_public_key(derivation, output_index, base); + check(actual2 == expected2, test); + } catch (const std::exception &) { + check(!expected1, test); + } +} + +void test_derive_secret_key(std::fstream &input, size_t test) { + KeyDerivation derivation; + size_t output_index; + SecretKey base; + SecretKey expected, actual; + get(input, derivation, output_index, base, expected); + // try { + actual = derive_output_secret_key(derivation, output_index, base); + // }catch(const std::exception &){ + // } + check(expected == actual, test); +} + +void test_underive_public_key(std::fstream &input, size_t test) { + KeyDerivation derivation; + size_t output_index; + PublicKey derived_key; + bool expected1 = false; + PublicKey expected2; + get(input, derivation, output_index, derived_key, expected1); + if (expected1) { + get(input, expected2); + } + try { + const auto actual2 = underive_address_S(derivation, output_index, derived_key); + check(actual2 == expected2, test); + } catch (const std::exception &) { + check(!expected1, test); + } +} + +void test_generate_signature(std::fstream &input, size_t test) { + Hash prefix_hash; + PublicKey pub; + SecretKey sec; + Signature expected; + get(input, prefix_hash, pub, sec, expected); + const auto actual = generate_signature(prefix_hash, pub, sec); + check(expected == actual, test); +} + +void test_check_signature(std::fstream &input, size_t test) { + Hash prefix_hash; + PublicKey pub; + Signature sig; + bool expected = false; + get(input, prefix_hash, pub, sig, expected); + const bool actual = check_signature(prefix_hash, pub, sig); + check(expected == actual, test); +} + +void test_hash_to_point(std::fstream &input, size_t test) { + Hash h; + EllipticCurvePoint expected; + get(input, h, expected); + const auto actual = bytes_to_bad_point(h); + check(expected == actual, test); +} + +void test_hash_to_ec(std::fstream &input, size_t test) { + PublicKey key; + EllipticCurvePoint expected; + get(input, key, expected); + const auto actual = hash_to_good_point(key); + check(expected == actual, test); +} + +void test_generate_key_image(std::fstream &input, size_t test) { + PublicKey pub; + SecretKey sec; + KeyImage expected; + get(input, pub, sec, expected); + const auto actual = generate_key_image(pub, sec); + check(expected == actual, test); +} + +void test_generate_ring_signature(std::fstream &input, size_t test) { + Hash prefix_hash; + KeyImage image; + std::vector vpubs; + // std::vector pubs; + size_t pubs_count; + SecretKey sec; + size_t sec_index; + RingSignature expected; + get(input, prefix_hash, image, pubs_count); + vpubs.resize(pubs_count); + // pubs.resize(pubs_count); + for (size_t i = 0; i < pubs_count; i++) { + get(input, vpubs[i]); + // pubs[i] = &vpubs[i]; } -}; + get(input, sec, sec_index); + expected.resize(pubs_count); + getvar(input, pubs_count * sizeof(Signature), expected.data()); + const auto actual = generate_ring_signature(prefix_hash, image, vpubs.data(), vpubs.size(), sec, sec_index); + check(expected == actual, test); + // TODO - better tests for half-size ring signatures -void generate_ring_signature3(const Hash &prefix_hash, const std::vector &images, - const std::vector> &pubs, const std::vector &secs, - const std::vector &sec_indexes, const SecretKey &view_secret_key); + /* We started to use rng in borromean and auditable signatures, so we cannot + pass tests that rely on deterministic rng + static std::vector images; + static std::vector secs; + static std::vector> pubss; + static std::vector sec_indexes; + images.push_back(image); + secs.push_back(sec); + pubss.push_back(vpubs); + sec_indexes.push_back(sec_index); + if (images.size() > 32) { + crypto::RingSignatureBorromean sig3 = + crypto::generate_ring_signature_borromean(prefix_hash, images, pubss, secs, sec_indexes); + bool checked = crypto::check_ring_signature_borromean(prefix_hash, images, pubss, sig3); + sig3.r.at(0).at(0).data[13] += 1; + bool checked2 = crypto::check_ring_signature_borromean(prefix_hash, images, pubss, sig3); + invariant(checked && !checked2, ""); -RingSignature3 create_signature(); + images.clear(); + secs.clear(); + pubss.clear(); + sec_indexes.clear(); + }*/ +} + +void test_check_ring_signature(std::fstream &input, size_t test) { + Hash prefix_hash; + KeyImage image; + std::vector vpubs; + // std::vector pubs; + size_t pubs_count; + RingSignature sigs; + bool expected = false; + size_t i; + get(input, prefix_hash, image, pubs_count); + vpubs.resize(pubs_count); + for (i = 0; i < pubs_count; i++) { + get(input, vpubs[i]); + } + sigs.resize(pubs_count); + getvar(input, pubs_count * sizeof(Signature), sigs.data()); + get(input, expected); + const bool actual = check_ring_signature(prefix_hash, image, vpubs.data(), vpubs.size(), sigs); + check(expected == actual, test); +} + +void strange_add() { + SecretKey a, b, c; + PublicKey A, B, C; + invariant(common::pod_from_hex("3930bd5216d5e7fe00625982026b40553457870474fd3466b4454a7b49398d0a", &a), ""); + invariant(common::pod_from_hex("f303536f2dee4b4f38232f41b12411eeb02d3fc76e0536fc23f81b2630489f0d", &b), ""); + invariant(common::pod_from_hex("1cbd29783c1ccf7868c888319717fc5fe76fc83b251a057fe8eef19750ac8b0f", &c), ""); + check_scalar(a); + check_scalar(b); + check_scalar(c); + + invariant(common::pod_from_hex("4c7335be6898dcd7a3c8c5b9436b93bace5209f53486a732bc4ec00a2db5d6ee", &A), ""); + invariant(common::pod_from_hex("8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94", &B), ""); + invariant(common::pod_from_hex("d3ed97a11ce0d238382ed8ee60064d5fbcef928988c658e7c1ca42ec8b1a1a2e", &C), ""); + invariant(key_in_main_subgroup(A), ""); + invariant(key_in_main_subgroup(B), ""); + invariant(key_in_main_subgroup(C), ""); + + const ge_p3 A_p3 = ge_frombytes_vartime(A); + const ge_p3 B_p3 = ge_frombytes_vartime(B); // or get_H_p3(); + const ge_p3 C_p3 = ge_frombytes_vartime(C); + + const ge_p3 tmp_a_A = ge_scalarmult3(a, A_p3); + const ge_p3 tmp_b_B = ge_scalarmult3(b, B_p3); + const ge_p3 tmp_c_C = ge_scalarmult3(c, C_p3); + invariant(key_in_main_subgroup(ge_tobytes(tmp_a_A)), ""); + invariant(key_in_main_subgroup(ge_tobytes(tmp_b_B)), ""); + invariant(key_in_main_subgroup(ge_tobytes(tmp_c_C)), ""); + { + ge_dsmp dsm; + ge_dsm_precomp(&dsm, &B_p3); + ge_p3 tmp1 = ge_double_scalarmult_precomp_vartime3(a, A_p3, b, dsm); + ge_p3 result1 = ge_add(tmp1, tmp_c_C); + std::cout << "result1=" << ge_tobytes(result1) << std::endl; + std::cout << "tmp1=" << ge_tobytes(tmp1) << std::endl; + std::cout << "a_A + b_B =" << ge_tobytes(ge_add(tmp_a_A, tmp_b_B)) << std::endl; + tmp1 = ge_frombytes_vartime(ge_tobytes(tmp1)); + result1 = ge_add(tmp1, tmp_c_C); + std::cout << "result1(to-from)=" << ge_tobytes(result1) << std::endl; + } + { + ge_dsmp dsm; + ge_dsm_precomp(&dsm, &C_p3); + ge_p3 tmp2 = ge_double_scalarmult_precomp_vartime3(b, B_p3, c, dsm); + ge_p3 result2 = ge_add(tmp_a_A, tmp2); + std::cout << "result2=" << ge_tobytes(result2) << std::endl; + std::cout << "tmp2=" << ge_tobytes(tmp2) << std::endl; + std::cout << "b_B + c_C =" << ge_tobytes(ge_add(tmp_b_B, tmp_c_C)) << std::endl; + tmp2 = ge_frombytes_vartime(ge_tobytes(tmp2)); + result2 = ge_add(tmp_a_A, tmp2); + std::cout << "result1(to-from)=" << ge_tobytes(result2) << std::endl; + } + { + ge_p3 result3 = ge_add(ge_add(tmp_a_A, tmp_b_B), tmp_c_C); + std::cout << "result3=" << ge_tobytes(result3) << std::endl; + } +} void test_crypto(const std::string &test_vectors_filename) { + // strange_add(); + std::cout << "G=" << crypto::get_G() << std::endl; + std::cout << "H=" << crypto::get_H() << std::endl; + invariant(crypto::get_H() == crypto::test_get_H(), ""); + + // SecretKey sk; + // sk.data[0] = 8; + // std::cout << ge_tobytes(ge_scalarmult_base(sk)) << std::endl; + PublicKey pk = common::pfh("409c2e98dfebfd5ea008ecd4b60b4535fb553a63175396e0f37811c6e84ff134"); + + std::cout << "cn_fast_hash(pk): " << crypto::cn_fast_hash(pk.data, sizeof(pk.data)) << std::endl; + std::cout << "hash_to_scalar(pk)" << crypto::hash_to_scalar(pk.data, sizeof(pk.data)) << std::endl; + Hash h = cn_fast_hash(pk.data, sizeof(pk.data)); + Hash h2 = cn_fast_hash(h.data, sizeof(h.data)); + std::cout << "cn_fast_hash(pk): " << h << std::endl; + std::cout << "cn_fast_hash(cn_fast_hash(pk)): " << h2 << std::endl; + + uint8_t buf[64]{}; + memcpy(buf, h.data, 32); + memcpy(buf + 32, h2.data, 32); + SecretKey result; + sc_reduce64(&result, buf); + std::cout << result << std::endl; + // std::cout << crypto::hash_to_scalar64(pk.data, sizeof(pk.data)) << std::endl; + + P3 p1 = crypto::hash_to_good_point_p3(pk); + P3 p2 = crypto::hash_to_good_point_p3(pk); + SecretKey s1 = crypto::hash_to_scalar(pk.data, sizeof(pk.data)); + SecretKey s2 = crypto::hash_to_scalar(pk.data, sizeof(pk.data)); + + std::cout << "Identity group element as EllipticCurvePoint: " << to_bytes(I) << std::endl; + + SecretKey s3 = s1 - s2; + s3 -= s2; + SecretKey s5 = s1 + s2; + s5 += s2; + SecretKey s4 = s1 - s2 * s3; + s4 -= s3 * s4; + + P3 r1 = s1 * G + s2 * p2; + P3 r2 = s1 * p1 + s2 * p2; + P3 r3 = s1 * p1 + s2 * G; + P3 r4 = p1 + s2 * G; + P3 r5 = s1 * p1 + p2; + P3 r6 = p1 + s1 * G + s2 * p2; + P3 r7 = s1 * p1 + s2 * G + p2; + P3 r8 = s1 * (p1 + p2) + s2 * G; + + r2 += r3 + G; + r4 += r5; + r6 *= s1; + + P3 zero_point(PublicKey{}); + std::cout << key_in_main_subgroup(PublicKey{}) << std::endl; + zero_point = P3(); + std::cout << "Zero EllipticCurvePoint is in main subgroup: " << key_in_main_subgroup(PublicKey{}) << std::endl; + + std::cout << "Sum of two zero EllipticCurvePoint's: " << to_bytes(zero_point + zero_point) << std::endl; + std::cout << "Sum of two identity elements: " << to_bytes(I + I) << std::endl; + std::cout << to_bytes(r1) << " " << to_bytes(r7) << " " << to_bytes(r8) << std::endl; + + crypto::test_unlinkable(); + + std::map test_function; + test_function["check_scalar"] = test_check_scalar; + test_function["random_scalar"] = test_random_scalar; + test_function["hash_to_scalar"] = test_hash_to_scalar; + test_function["generate_keys"] = test_generate_keys; + test_function["check_key"] = test_check_key; + test_function["secret_key_to_public_key"] = test_secret_key_to_public_key; + test_function["generate_key_derivation"] = test_generate_key_derivation; + test_function["derive_public_key"] = test_derive_public_key; + test_function["derive_secret_key"] = test_derive_secret_key; + test_function["underive_public_key"] = test_underive_public_key; + test_function["generate_signature"] = test_generate_signature; + test_function["check_signature"] = test_check_signature; + test_function["hash_to_point"] = test_hash_to_point; + test_function["hash_to_ec"] = test_hash_to_ec; + test_function["generate_key_image"] = test_generate_key_image; + test_function["generate_ring_signature"] = test_generate_ring_signature; + test_function["check_ring_signature"] = test_check_ring_signature; + std::fstream input; std::string cmd; - size_t test = 0; crypto_initialize_random_for_tests(); + + std::map test_counts; input.open(test_vectors_filename, std::ios_base::in); - for (;;) { - ++test; + for (size_t test = 1;; ++test) { input.exceptions(std::ios_base::badbit); if (!(input >> cmd)) { break; } input.exceptions(std::ios_base::badbit | std::ios_base::failbit | std::ios_base::eofbit); - if (cmd == "check_scalar") { - crypto::EllipticCurveScalar scalar; - bool expected = false; - get(input, scalar, expected); - const bool actual = sc_isvalid_vartime(&scalar) != 0; - check(expected == actual, test); - } else if (cmd == "random_scalar") { - crypto::EllipticCurveScalar expected, actual; - get(input, expected); - actual = crypto::random_scalar(); - check(expected == actual, test); - - // We do not have separate tests for inversions, so perform it on - // random_scalars - crypto::EllipticCurveScalar inv_actual, inv_inv_actual; - crypto::EllipticCurveScalar b, inv_b, inv_inv_b, a_b, inv_a_inv_b, inv_a_b; - b = crypto::hash_to_scalar(actual.data, sizeof(actual.data)); - - sc_invert(&inv_actual, &actual); - sc_invert(&inv_inv_actual, &inv_actual); - sc_invert(&inv_b, &b); - sc_invert(&inv_inv_b, &inv_b); - invariant(actual == inv_inv_actual, ""); - invariant(b == inv_inv_b, ""); - sc_mul(&a_b, &actual, &b); - sc_mul(&inv_a_inv_b, &inv_actual, &inv_b); - sc_invert(&inv_a_b, &a_b); - invariant(inv_a_inv_b == inv_a_b, ""); - - crypto::EllipticCurveScalar one, inv_one, real_one; - sc_1(&real_one); - sc_mul(&one, &actual, &inv_actual); - invariant(one == real_one, ""); - sc_mul(&one, &b, &inv_b); - invariant(one == real_one, ""); - sc_invert(&inv_one, &one); - invariant(one == inv_one, ""); - } else if (cmd == "hash_to_scalar") { - std::vector data; - crypto::EllipticCurveScalar expected; - get(input, data, expected); - const auto actual = crypto::hash_to_scalar(data.data(), data.size()); - check(expected == actual, test); - } else if (cmd == "generate_keys") { - crypto::PublicKey expected1, actual1; - crypto::SecretKey expected2, actual2; - get(input, expected1, expected2); - random_keypair(actual1, actual2); - check(expected1 == actual1, test); - check(expected2 == actual2, test); - } else if (cmd == "check_key") { - crypto::PublicKey key; - bool expected = false; - get(input, key, expected); - const bool actual = key_isvalid(key); - check(expected == actual, test); - } else if (cmd == "secret_key_to_public_key") { - crypto::SecretKey sec; - bool expected1 = false; - crypto::PublicKey expected2, actual2; - get(input, sec, expected1); - if (expected1) { - get(input, expected2); - } - const bool actual1 = secret_key_to_public_key(sec, &actual2); - check(!(expected1 != actual1 || (expected1 && expected2 != actual2)), test); - } else if (cmd == "generate_key_derivation") { - crypto::PublicKey key1; - crypto::SecretKey key2; - bool expected1 = false; - crypto::KeyDerivation expected2; - get(input, key1, key2, expected1); - if (expected1) { - get(input, expected2); - } - try { - const auto actual2 = generate_key_derivation(key1, key2); - check(actual2 == expected2, test); - } catch (const std::exception &) { - check(!expected1, test); - } - } else if (cmd == "derive_public_key") { - crypto::KeyDerivation derivation; - size_t output_index; - crypto::PublicKey base; - bool expected1 = false; - crypto::PublicKey expected2; - get(input, derivation, output_index, base, expected1); - if (expected1) { - get(input, expected2); - } - try { - const auto actual2 = derive_public_key(derivation, output_index, base); - check(actual2 == expected2, test); - } catch (const std::exception &) { - check(!expected1, test); - } - } else if (cmd == "derive_secret_key") { - crypto::KeyDerivation derivation; - size_t output_index; - crypto::SecretKey base; - crypto::SecretKey expected, actual; - get(input, derivation, output_index, base, expected); - // try { - actual = derive_secret_key(derivation, output_index, base); - // }catch(const std::exception &){ - // } - check(expected == actual, test); - } else if (cmd == "underive_public_key") { - crypto::KeyDerivation derivation; - size_t output_index; - crypto::PublicKey derived_key; - bool expected1 = false; - crypto::PublicKey expected2; - get(input, derivation, output_index, derived_key, expected1); - if (expected1) { - get(input, expected2); - } - try { - const auto actual2 = underive_public_key(derivation, output_index, derived_key); - check(actual2 == expected2, test); - } catch (const std::exception &) { - check(!expected1, test); - } - } else if (cmd == "generate_signature") { - crypto::Hash prefix_hash; - crypto::PublicKey pub; - crypto::SecretKey sec; - crypto::Signature expected; - get(input, prefix_hash, pub, sec, expected); - const auto actual = generate_signature(prefix_hash, pub, sec); - check(expected == actual, test); - } else if (cmd == "check_signature") { - crypto::Hash prefix_hash; - crypto::PublicKey pub; - crypto::Signature sig; - bool expected = false; - get(input, prefix_hash, pub, sig, expected); - const bool actual = check_signature(prefix_hash, pub, sig); - check(expected == actual, test); - } else if (cmd == "hash_to_point") { - crypto::Hash h; - crypto::EllipticCurvePoint expected; - get(input, h, expected); - const auto actual = hash_to_point_for_tests(h); - check(expected == actual, test); - } else if (cmd == "hash_to_ec") { - crypto::PublicKey key; - crypto::EllipticCurvePoint expected; - get(input, key, expected); - const auto actual = hash_to_ec(key); - check(expected == actual, test); - } else if (cmd == "generate_key_image") { - crypto::PublicKey pub; - crypto::SecretKey sec; - crypto::KeyImage expected; - get(input, pub, sec, expected); - const auto actual = generate_key_image(pub, sec); - check(expected == actual, test); - } else if (cmd == "generate_ring_signature") { - crypto::Hash prefix_hash; - crypto::KeyImage image; - std::vector vpubs; - // std::vector pubs; - size_t pubs_count; - crypto::SecretKey sec; - size_t sec_index; - crypto::RingSignature expected; - get(input, prefix_hash, image, pubs_count); - vpubs.resize(pubs_count); - // pubs.resize(pubs_count); - for (size_t i = 0; i < pubs_count; i++) { - get(input, vpubs[i]); - // pubs[i] = &vpubs[i]; - } - get(input, sec, sec_index); - expected.resize(pubs_count); - getvar(input, pubs_count * sizeof(crypto::Signature), expected.data()); - crypto::SecretKey view_secret_key; - const auto actual = generate_ring_signature(prefix_hash, image, vpubs.data(), vpubs.size(), sec, sec_index); - check(expected == actual, test); - // TODO - better tests for half-size ring signatures - - static std::vector images; - static std::vector secs; - static std::vector> pubss; - static std::vector sec_indexes; - images.push_back(image); - secs.push_back(sec); - pubss.push_back(vpubs); - sec_indexes.push_back(sec_index); - if (images.size() > 32) { - crypto::RingSignature3 sig3 = - crypto::generate_ring_signature3(prefix_hash, images, pubss, secs, sec_indexes, view_secret_key); - bool checked = crypto::check_ring_signature3(prefix_hash, images, pubss, sig3); - // for (size_t i = 0; i != images.size(); ++i) { - // size_t found_sec_index = - // crypto::find_deterministic_input3(prefix_hash, i, sig3.r.at(i), - // view_secret_key); invariant(sec_indexes[i] == found_sec_index, ""); - // } - sig3.r.at(0).at(0).data[13] += 1; - bool checked2 = crypto::check_ring_signature3(prefix_hash, images, pubss, sig3); - invariant(checked && !checked2, ""); - - images.clear(); - secs.clear(); - pubss.clear(); - sec_indexes.clear(); - } - } else if (cmd == "check_ring_signature") { - crypto::Hash prefix_hash; - crypto::KeyImage image; - std::vector vpubs; - // std::vector pubs; - size_t pubs_count; - crypto::RingSignature sigs; - bool expected = false; - size_t i; - get(input, prefix_hash, image, pubs_count); - vpubs.resize(pubs_count); - for (i = 0; i < pubs_count; i++) { - get(input, vpubs[i]); - } - sigs.resize(pubs_count); - getvar(input, pubs_count * sizeof(crypto::Signature), sigs.data()); - get(input, expected); - const bool actual = check_ring_signature(prefix_hash, image, vpubs.data(), vpubs.size(), sigs, true); - check(expected == actual, test); + if (test_function.count(cmd)) { + test_function[cmd](input, test); + test_counts[cmd] += 1; } else { throw std::ios_base::failure("Unknown function: " + cmd); } } + + std::cout << "Passed successfully:" << std::endl; + for (auto &item : test_counts) { + std::string name; + int count; + std::tie(name, count) = item; + std::cout << " " << name << ": " << count << " tests;" << std::endl; + } + crypto::KeyPair test_keypair1 = crypto::random_keypair(); crypto::KeyPair test_keypair2 = crypto::random_keypair(); crypto::SecretKey actual; int COUNT = 1000; auto idea_start = std::chrono::high_resolution_clock::now(); for (int count = 0; count != COUNT; ++count) { - crypto::KeyDerivation der = crypto::generate_key_derivation(test_keypair1.public_key, test_keypair2.secret_key); - test_keypair2.secret_key = derive_secret_key(der, 0, test_keypair1.secret_key); + crypto::KeyDerivation der = generate_key_derivation(test_keypair1.public_key, test_keypair2.secret_key); + test_keypair2.secret_key = derive_output_secret_key(der, 0, test_keypair1.secret_key); } auto idea_ms = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - idea_start); diff --git a/tests/hash/test_hash.cpp b/tests/hash/test_hash.cpp index e08d3c28..23c3a26d 100644 --- a/tests/hash/test_hash.cpp +++ b/tests/hash/test_hash.cpp @@ -4,11 +4,7 @@ #include "test_hash.hpp" -//#include #include -//#include -//#include -//#include #include "../io.hpp" #include "common/Invariant.hpp" @@ -19,10 +15,6 @@ static crypto::CryptoNightContext context; -//#ifdef _MSC_VER -//#pragma warning(disable : 4297) -//#endif - static void hash_tree(const void *vdata, size_t vlength, cryptoHash *hash) { if (vlength % 32 != 0) throw std::ios_base::failure("Invalid input length for tree_hash"); @@ -73,9 +65,6 @@ void test_hash(const char *test_fun_name, const std::string &test_vectors_filena break; } } - // if (f == slow_hash) { - // context = new Crypto::cn_context(); - // } input.open(test_vectors_filename, std::ios_base::in); for (;;) { ++test; @@ -99,7 +88,48 @@ void test_hash(const char *test_fun_name, const std::string &test_vectors_filena } } +void keccak_any(const void *in, size_t inlen, unsigned char *md, size_t mdlen, uint8_t delim) { + cryptoKeccakHasher hasher; + crypto_keccak_init(&hasher, mdlen, delim); + crypto_keccak_update(&hasher, in, inlen); + crypto_keccak_final(&hasher, md, mdlen / 8); +} void test_hashes(const std::string &test_vectors_folder) { + std::string any_string("Keccak family)"); + unsigned char md[64]; + keccak_any(any_string.data(), any_string.size(), md, 128, 0x1f); + invariant(common::to_hex(md, 16) == "db647745ba790814315c70e0768eb9db", ""); + keccak_any(any_string.data(), any_string.size(), md, 256, 0x1f); + invariant(common::to_hex(md, 32) == "65e6c908348094fe28ac73387c6975edccaf31eb69ff69c78051203c088e7af9", ""); + + keccak_any(any_string.data(), any_string.size(), md, 224, 0x01); + invariant(common::to_hex(md, 28) == "4275c0dde3fd65def5e4e3073b48e4a8a4e7d54f40967f144e2bc222", ""); + keccak_any(any_string.data(), any_string.size(), md, 256, 0x01); + invariant(common::to_hex(md, 32) == "24c9a98982d55a9c0012e751f7fb3c745d4c99b1a16318f828f58fd342bed3d0", ""); + keccak_any(any_string.data(), any_string.size(), md, 384, 0x01); + invariant(common::to_hex(md, 48) == + "bd3b8b6548e9a450d160a019b7fadb23ed61f0ac2ecbecd5458ba40af3a67dc9c01cbae9b962e4e76836f7642d3468f3", + ""); + keccak_any(any_string.data(), any_string.size(), md, 512, 0x01); + invariant( + common::to_hex(md, 64) == + "45e07a8ad2f7da730573cea596d232dd1b2cfe7ac6ef1ec610732bada98464d99513dfb0712705963f82c998e529d185aff2368b68e12f8e6228d72bc8b58f3f", + ""); + + keccak_any(any_string.data(), any_string.size(), md, 224, 0x06); + invariant(common::to_hex(md, 28) == "92d1903aec6144f655d8a169398c36425db0260184a58ea32a0d79e2", ""); + keccak_any(any_string.data(), any_string.size(), md, 256, 0x06); + invariant(common::to_hex(md, 32) == "e9492b5c4fed0ee624e8bddc79ac1a998493fd8c222e54f65555a6ba4c9539e8", ""); + keccak_any(any_string.data(), any_string.size(), md, 384, 0x06); + invariant(common::to_hex(md, 48) == + "fcb0edcb07f67859ce296b53731602d974dc0ff4e355e696a8c3a3cb0cfef6fdc7d9d600089eacad240879a238b8362f", + ""); + keccak_any(any_string.data(), any_string.size(), md, 512, 0x06); + invariant( + common::to_hex(md, 64) == + "53fdfee3ee9f749c8132df285e2ab5ef31cda10b9ad36dab70d75bb6ea79d3325b63662e9ef054167827f41468877a37cf2099a5ec40fd5619c199c1b4c99c31", + ""); + size_t depths[17] = {0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4}; for (size_t i = 1; i < sizeof(depths) / sizeof(*depths); ++i) invariant(crypto_coinbase_tree_depth(i) == depths[i], ""); diff --git a/tests/wallet_file/test_wallet_file.cpp b/tests/wallet_file/test_wallet_file.cpp index d2408b04..60070f66 100644 --- a/tests/wallet_file/test_wallet_file.cpp +++ b/tests/wallet_file/test_wallet_file.cpp @@ -66,7 +66,7 @@ static void test_body(const Currency ¤cy, const std::string &path, const s WalletContainerStorage wallet(currency, logger, tmp_name, password); if (wallet.is_view_only() != view_only) throw std::runtime_error("view_only test failed for " + path); - auto records = wallet.get_records(); + auto records = wallet.test_get_records(); if (!crypto::keys_match(wallet.get_view_secret_key(), wallet.get_view_public_key())) throw std::runtime_error("view keys do not match for " + path); WalletRecord first_record = records.at(0); @@ -76,11 +76,11 @@ static void test_body(const Currency ¤cy, const std::string &path, const s throw std::runtime_error("failed to parse address " + a); invariant(v_address.type() == typeid(AccountAddressSimple), ""); auto &address = boost::get(v_address); - if (address.view_public_key != wallet.get_view_public_key()) + if (address.V != wallet.get_view_public_key()) throw std::runtime_error("view_public_key test failed for " + path); size_t pos = 0; for (; pos != records.size(); ++pos) - if (records.at(pos).spend_public_key == address.spend_public_key) + if (records.at(pos).spend_public_key == address.S) break; if (pos == records.size()) throw std::runtime_error("spend_public_key not found for " + path); @@ -88,7 +88,7 @@ static void test_body(const Currency ¤cy, const std::string &path, const s throw std::runtime_error("non empty secret spend key for " + path); if (!view_only && !crypto::keys_match(records.at(pos).spend_secret_key, records.at(pos).spend_public_key)) throw std::runtime_error("spend keys do not match for " + path); - if (address.spend_public_key != records.at(pos).spend_public_key) + if (address.S != records.at(pos).spend_public_key) throw std::runtime_error("spend_public_key test failed for " + path); records.erase(records.begin() + pos); } @@ -98,9 +98,10 @@ static void test_body(const Currency ¤cy, const std::string &path, const s return; const auto initial_oldest_timestamp = wallet.get_oldest_timestamp(); bool rescan_from_ct = false; + std::vector new_addresses; try { wallet.generate_new_addresses({first_record.spend_secret_key}, first_record.creation_timestamp + 1, - first_record.creation_timestamp + 1, &rescan_from_ct); + first_record.creation_timestamp + 1, &new_addresses, &rescan_from_ct); } catch (const Wallet::Exception &) { if (!view_only) throw; @@ -111,12 +112,12 @@ static void test_body(const Currency ¤cy, const std::string &path, const s std::cout << "Oldest timestamp is " << wallet.get_oldest_timestamp() << std::endl; if (rescan_from_ct || initial_oldest_timestamp != wallet.get_oldest_timestamp()) throw std::runtime_error("Increasing timestamp of exising address should not lead to rescan " + path); - wallet.generate_new_addresses({crypto::SecretKey{}}, 0, 1600000000, &rescan_from_ct); + wallet.generate_new_addresses({crypto::SecretKey{}}, 0, 1600000000, &new_addresses, &rescan_from_ct); std::cout << "Oldest timestamp is " << wallet.get_oldest_timestamp() << std::endl; if (rescan_from_ct || initial_oldest_timestamp != wallet.get_oldest_timestamp()) throw std::runtime_error("Adding new secret key should not lead to rescan " + path); wallet.generate_new_addresses({first_record.spend_secret_key}, first_record.creation_timestamp - 1, - first_record.creation_timestamp + 1, &rescan_from_ct); + first_record.creation_timestamp + 1, &new_addresses, &rescan_from_ct); std::cout << "Oldest timestamp is " << wallet.get_oldest_timestamp() << std::endl; if (!rescan_from_ct || initial_oldest_timestamp == wallet.get_oldest_timestamp()) throw std::runtime_error("Reducing timestamp of exising address should lead to rescan " + path); diff --git a/tests/wallet_state/test_wallet_state.cpp b/tests/wallet_state/test_wallet_state.cpp index 646befd1..e3edd836 100644 --- a/tests/wallet_state/test_wallet_state.cpp +++ b/tests/wallet_state/test_wallet_state.cpp @@ -16,16 +16,13 @@ class WalletStateTest : public WalletStateBasic { public: std::map memory_spent; explicit WalletStateTest(logging::ILogger &log, const Config &config, const Currency ¤cy) - : WalletStateBasic(log, config, currency, "test_wallet_state", false) {} + : WalletStateBasic(log, config, currency, "test_wallet_state") {} Amount add_incoming_output(const api::Output &output, const Hash &tid) override { return WalletStateBasic::add_incoming_output(output, tid); } Amount add_incoming_keyimage(Height block_height, const KeyImage &ki) override { return WalletStateBasic::add_incoming_keyimage(block_height, ki); } - Amount add_incoming_deterministic_input(Height block_height, Amount am, size_t gi, const PublicKey &pk) override { - return 0; // TODO - } bool try_add_incoming_output(const api::Output &output, Amount *confirmed_balance_delta) const { return WalletStateBasic::try_add_incoming_output(output, confirmed_balance_delta); } @@ -41,10 +38,10 @@ class WalletStateTest : public WalletStateBasic { }; static bool less_output(const api::Output &a, const api::Output &b) { - return std::tie(a.height, a.amount, a.index) < std::tie(b.height, b.amount, b.index); + return std::tie(a.height, a.global_index) < std::tie(b.height, b.global_index); } static bool eq_output(const api::Output &a, const api::Output &b) { - return std::tie(a.height, a.amount, a.index) == std::tie(b.height, b.amount, b.index); + return std::tie(a.height, a.global_index) == std::tie(b.height, b.global_index); } // We will check that WalletStateBasic and model have the same behaviour @@ -61,14 +58,13 @@ class WalletStateModel : public IWalletState { const Currency &m_currency; explicit WalletStateModel(const Currency ¤cy) : m_currency(currency) {} - std::map> all_keyimages; - std::map, api::Output> outputs; + std::map all_keyimages; + std::map outputs; // std::map> transactions; std::map transfers; std::vector locked_outputs; - std::map, std::pair> - unlocked_outputs; // height of unlock and adjusted amount + std::map> unlocked_outputs; // height of unlock and adjusted amount Amount add_incoming_output(Height block_height, const api::Output &output, bool just_unlocked) { bool ki_exists = all_keyimages.count(output.key_image) != 0; @@ -81,16 +77,16 @@ class WalletStateModel : public IWalletState { } Amount added_amount = output.amount; if (ki_exists) { - auto existing_output = outputs.at(all_keyimages.at(output.key_image)); - if (output.amount <= existing_output.amount || output.address != existing_output.address) - return 0; - added_amount = output.amount - existing_output.amount; - invariant(outputs.erase(all_keyimages.at(output.key_image)) == 1, ""); + return 0; + // auto existing_output = outputs.at(all_keyimages.at(output.key_image)); + // if (output.amount <= existing_output.amount || output.address != existing_output.address) + // return 0; + // added_amount = output.amount - existing_output.amount; + // invariant(outputs.erase(all_keyimages.at(output.key_image)) == 1, ""); } - auto pa = std::make_pair(output.amount, output.index); - invariant(outputs.insert(std::make_pair(pa, output)).second, ""); + invariant(outputs.insert(std::make_pair(output.global_index, output)).second, ""); if (output.key_image != KeyImage{}) - all_keyimages[output.key_image] = pa; + all_keyimages[output.key_image] = output.global_index; // if(!just_unlocked) { if (transfers.count(block_height) == 0) { transfers[block_height].header.height = block_height; @@ -107,15 +103,17 @@ class WalletStateModel : public IWalletState { } void unlock(Height block_height, const api::Output &output) { for (size_t i = 0; i != locked_outputs.size(); ++i) { - if (locked_outputs.at(i).amount == output.amount && locked_outputs.at(i).index == output.index) { + if (locked_outputs.at(i).amount == output.amount && + locked_outputs.at(i).global_index == output.global_index) { locked_outputs.erase(locked_outputs.begin() + i); --i; } } Amount adjusted_amount = add_incoming_output(block_height, output, true); - auto pa = std::make_pair(output.amount, output.index); invariant( - unlocked_outputs.insert(std::make_pair(pa, std::make_pair(block_height, adjusted_amount))).second, ""); + unlocked_outputs.insert(std::make_pair(output.global_index, std::make_pair(block_height, adjusted_amount))) + .second, + ""); } public: @@ -137,7 +135,7 @@ class WalletStateModel : public IWalletState { for (auto &&unl : to_unlock) unlock(height, unl); } - virtual Amount add_incoming_keyimage(Height block_height, const KeyImage &ki) override { + Amount add_incoming_keyimage(Height block_height, const KeyImage &ki) override { if (ki == KeyImage{}) return 0; std::vector to_unlock; @@ -165,9 +163,6 @@ class WalletStateModel : public IWalletState { transfers[block_height].transactions.back().transfers.push_back(transfer); return existing_output.amount; } - Amount add_incoming_deterministic_input(Height block_height, Amount am, size_t gi, const PublicKey &pk) override { - return 0; // TODO - } void add_transaction( Height height, const Hash &tid, const TransactionPrefix &tx, const api::Transaction &ptx) override { // transactions[height].push_back(ptx); @@ -255,7 +250,8 @@ void test_wallet_state(common::CommandLine &cmd) { WalletStateTest ws(logger, config, currency); WalletStateModel wm(currency); - std::map next_gi; + std::map next_si; + size_t next_gi = 0; std::set used_keyimages; std::set output_keyimages; @@ -284,8 +280,6 @@ void test_wallet_state(common::CommandLine &cmd) { for (Height ha = 1; ha != TEST_HEIGHT; ++ha) { api::Transaction ptx; size_t spend_outputs = (random() % 16); - if (ha == 36) - std::cout << "Aha"; if (spend_outputs < 10 && ha != TEST_HEIGHT - 1) { for (size_t j = 0; j != spend_outputs; ++j) { KeyImage ki; @@ -323,14 +317,16 @@ void test_wallet_state(common::CommandLine &cmd) { output.height = ha; output.amount = am * Currency::DECIMAL_PLACES.at(dc); // output.dust = currency.is_dust(output.amount); - output.index = next_gi[output.amount]; + output.stack_index = next_si[output.amount]; + output.global_index = next_gi; + next_si[output.amount] += 1; + next_gi += 1; output.address = addresses.at(random() % (addresses.size() - 1)); // last one is empty if (random() % 20 == 0) output.unlock_block_or_timestamp = (3 * ha / 4) + random() % (TEST_HEIGHT / 2); else if (random() % 20 == 1) output.unlock_block_or_timestamp = TEST_TIMESTAMP + currency.difficulty_target * ((3 * ha / 4) + random() % (TEST_HEIGHT / 2)); - next_gi[output.amount] += 1; if (!VIEW_ONLY) { output.key_image.data[0] = random() % 256; output.key_image.data[1] = random() % 16; @@ -381,8 +377,6 @@ void test_wallet_state(common::CommandLine &cmd) { for (Height wi = 0; wi != 25; ++wi) { // [-20..5) range around tip if (ha + wi < 20) continue; - // if(ha == 19 && wi == 1) - // std::cout << "Aha"; for (const auto &addr : addresses) { auto ba1 = wm.get_balance(addr, ha + wi - 20); auto ba2 = ws.get_balance(addr, ha + wi - 20);