aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/blockchain_db/blockchain_db.h2
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.cpp34
-rw-r--r--src/blockchain_utilities/CMakeLists.txt20
-rw-r--r--src/blockchain_utilities/blockchain_import.cpp8
-rw-r--r--src/blocks/CMakeLists.txt35
-rw-r--r--src/blocks/blockexports.c87
-rw-r--r--src/blocks/blocks.cpp31
-rw-r--r--src/blocks/blocks.dat0
-rw-r--r--src/blocks/blocks.h14
-rw-r--r--src/common/perf_timer.cpp8
-rw-r--r--src/common/perf_timer.h18
-rw-r--r--src/common/util.cpp25
-rw-r--r--src/cryptonote_basic/account.cpp25
-rw-r--r--src/cryptonote_basic/account.h1
-rw-r--r--src/cryptonote_basic/cryptonote_basic.h1
-rw-r--r--src/cryptonote_basic/cryptonote_format_utils.cpp85
-rw-r--r--src/cryptonote_basic/cryptonote_format_utils.h2
-rw-r--r--src/cryptonote_basic/miner.cpp2
-rw-r--r--src/cryptonote_config.h1
-rw-r--r--src/cryptonote_core/CMakeLists.txt7
-rw-r--r--src/cryptonote_core/blockchain.cpp38
-rw-r--r--src/cryptonote_core/blockchain.h23
-rw-r--r--src/cryptonote_core/cryptonote_core.cpp51
-rw-r--r--src/cryptonote_core/cryptonote_core.h15
-rw-r--r--src/cryptonote_core/cryptonote_tx_utils.cpp6
-rw-r--r--src/daemon/CMakeLists.txt21
-rw-r--r--src/daemon/core.h8
-rw-r--r--src/daemon/rpc_command_executor.cpp16
-rw-r--r--src/device/CMakeLists.txt9
-rw-r--r--src/device/device.hpp24
-rw-r--r--src/device/device_cold.hpp71
-rw-r--r--src/device/device_default.cpp10
-rw-r--r--src/device/device_io_hid.cpp15
-rw-r--r--src/device/device_ledger.cpp2
-rw-r--r--src/device/device_ledger.hpp1
-rw-r--r--src/device_trezor/CMakeLists.txt123
-rw-r--r--src/device_trezor/device_trezor.cpp363
-rw-r--r--src/device_trezor/device_trezor.hpp132
-rw-r--r--src/device_trezor/device_trezor_base.cpp301
-rw-r--r--src/device_trezor/device_trezor_base.hpp301
-rw-r--r--src/device_trezor/trezor.hpp44
-rw-r--r--src/device_trezor/trezor/exceptions.hpp193
-rw-r--r--src/device_trezor/trezor/messages/.gitignore2
-rw-r--r--src/device_trezor/trezor/messages_map.cpp125
-rw-r--r--src/device_trezor/trezor/messages_map.hpp94
-rw-r--r--src/device_trezor/trezor/protocol.cpp891
-rw-r--r--src/device_trezor/trezor/protocol.hpp300
-rw-r--r--src/device_trezor/trezor/tools/README.md36
-rw-r--r--src/device_trezor/trezor/tools/build_protob.py38
-rw-r--r--src/device_trezor/trezor/tools/pb2cpp.py186
-rw-r--r--src/device_trezor/trezor/transport.cpp651
-rw-r--r--src/device_trezor/trezor/transport.hpp331
-rw-r--r--src/device_trezor/trezor/trezor_defs.hpp48
-rw-r--r--src/mnemonics/electrum-words.cpp2
-rw-r--r--src/p2p/net_node.inl16
-rw-r--r--src/ringct/rctSigs.cpp49
-rw-r--r--src/ringct/rctSigs.h2
-rw-r--r--src/rpc/core_rpc_server.cpp31
-rw-r--r--src/rpc/core_rpc_server_commands_defs.h3
-rw-r--r--src/rpc/daemon_messages.h2
-rw-r--r--src/rpc/message.h2
-rw-r--r--src/simplewallet/simplewallet.cpp166
-rw-r--r--src/simplewallet/simplewallet.h3
-rw-r--r--src/wallet/CMakeLists.txt1
-rw-r--r--src/wallet/api/wallet.cpp26
-rw-r--r--src/wallet/api/wallet.h3
-rw-r--r--src/wallet/api/wallet2_api.h11
-rw-r--r--src/wallet/wallet2.cpp201
-rw-r--r--src/wallet/wallet2.h22
-rw-r--r--src/wallet/wallet_errors.h9
-rw-r--r--src/wallet/wallet_rpc_server.cpp173
-rw-r--r--src/wallet/wallet_rpc_server.h2
-rw-r--r--src/wallet/wallet_rpc_server_commands_defs.h67
-rw-r--r--src/wallet/wallet_rpc_server_error_codes.h1
75 files changed, 5350 insertions, 318 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 3b71c38cd..6ee7effdd 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -145,3 +145,4 @@ if(PER_BLOCK_CHECKPOINT)
endif()
add_subdirectory(device)
+add_subdirectory(device_trezor)
diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h
index 396ae7544..71c46d76b 100644
--- a/src/blockchain_db/blockchain_db.h
+++ b/src/blockchain_db/blockchain_db.h
@@ -544,7 +544,7 @@ public:
/**
* @brief An empty constructor.
*/
- BlockchainDB(): m_open(false) { }
+ BlockchainDB(): m_hardfork(NULL), m_open(false) { }
/**
* @brief An empty destructor.
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp
index bd91f308a..84a083c26 100644
--- a/src/blockchain_db/lmdb/db_lmdb.cpp
+++ b/src/blockchain_db/lmdb/db_lmdb.cpp
@@ -1208,7 +1208,7 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags)
if (is_hdd_result)
{
if (is_hdd_result.value())
- MCLOG_RED(el::Level::Warning, "global", "The blockchain is on a rotating drive: this will be very slow, use a SSD if possible");
+ MCLOG_RED(el::Level::Warning, "global", "The blockchain is on a rotating drive: this will be very slow, use an SSD if possible");
}
m_folder = filename;
@@ -1980,22 +1980,36 @@ std::vector<uint64_t> BlockchainLMDB::get_block_cumulative_rct_outputs(const std
MDB_val v;
uint64_t prev_height = heights[0];
+ uint64_t range_begin = 0, range_end = 0;
for (uint64_t height: heights)
{
- if (height == prev_height + 1)
+ if (height >= range_begin && height < range_end)
{
- MDB_val k2;
- result = mdb_cursor_get(m_cur_block_info, &k2, &v, MDB_NEXT);
+ // nohting to do
}
else
{
- v.mv_size = sizeof(uint64_t);
- v.mv_data = (void*)&height;
- result = mdb_cursor_get(m_cur_block_info, (MDB_val *)&zerokval, &v, MDB_GET_BOTH);
+ if (height == prev_height + 1)
+ {
+ MDB_val k2;
+ result = mdb_cursor_get(m_cur_block_info, &k2, &v, MDB_NEXT_MULTIPLE);
+ range_begin = ((const mdb_block_info*)v.mv_data)->bi_height;
+ range_end = range_begin + v.mv_size / sizeof(mdb_block_info); // whole records please
+ if (height < range_begin || height >= range_end)
+ throw0(DB_ERROR(("Height " + std::to_string(height) + " not included in multuple record range: " + std::to_string(range_begin) + "-" + std::to_string(range_end)).c_str()));
+ }
+ else
+ {
+ v.mv_size = sizeof(uint64_t);
+ v.mv_data = (void*)&height;
+ result = mdb_cursor_get(m_cur_block_info, (MDB_val *)&zerokval, &v, MDB_GET_BOTH);
+ range_begin = height;
+ range_end = range_begin + 1;
+ }
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Error attempting to retrieve rct distribution from the db: ", result).c_str()));
}
- if (result)
- throw0(DB_ERROR(lmdb_error("Error attempting to retrieve rct distribution from the db: ", result).c_str()));
- const mdb_block_info *bi = (const mdb_block_info *)v.mv_data;
+ const mdb_block_info *bi = ((const mdb_block_info *)v.mv_data) + (height - range_begin);
res.push_back(bi->bi_cum_rct);
prev_height = height;
}
diff --git a/src/blockchain_utilities/CMakeLists.txt b/src/blockchain_utilities/CMakeLists.txt
index 24a750eb0..6e6e4c6f1 100644
--- a/src/blockchain_utilities/CMakeLists.txt
+++ b/src/blockchain_utilities/CMakeLists.txt
@@ -26,20 +26,6 @@
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-set(blocksdat "")
-if(PER_BLOCK_CHECKPOINT)
- if(APPLE AND DEPENDS)
- add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && touch stub.c && ${CMAKE_C_COMPILER} --target=x86_64-apple-darwin11 -o stub.o -c stub.c COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -r -sectcreate __DATA __blocks_dat ../blocks/checkpoints.dat -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o stub.o && rm -f stub.*)
- elseif(APPLE AND NOT DEPENDS)
- add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && touch stub.c && ${CMAKE_C_COMPILER} -o stub.o -c stub.c COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -sectcreate __DATA __blocks_dat ../blocks/checkpoints.dat -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o stub.o && rm -f stub.*)
- elseif(LINUX_32)
- add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && cp ../blocks/checkpoints.dat blocks.dat && ${CMAKE_LINKER} -m elf_i386 ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o blocks.dat && rm -f blocks.dat)
- else()
- add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && cp ../blocks/checkpoints.dat blocks.dat && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o blocks.dat && rm -f blocks.dat)
- endif()
- set(blocksdat "blocksdat.o")
-endif()
-
set(blockchain_import_sources
blockchain_import.cpp
bootstrap_file.cpp
@@ -119,8 +105,7 @@ monero_private_headers(blockchain_depth
monero_add_executable(blockchain_import
${blockchain_import_sources}
- ${blockchain_import_private_headers}
- ${blocksdat})
+ ${blockchain_import_private_headers})
target_link_libraries(blockchain_import
PRIVATE
@@ -132,7 +117,8 @@ target_link_libraries(blockchain_import
${Boost_SYSTEM_LIBRARY}
${Boost_THREAD_LIBRARY}
${CMAKE_THREAD_LIBS_INIT}
- ${EXTRA_LIBRARIES})
+ ${EXTRA_LIBRARIES}
+ ${Blocks})
if(ARCH_WIDTH)
target_compile_definitions(blockchain_import
diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp
index 9ec768d26..7f92ecd87 100644
--- a/src/blockchain_utilities/blockchain_import.cpp
+++ b/src/blockchain_utilities/blockchain_import.cpp
@@ -37,6 +37,7 @@
#include "misc_log_ex.h"
#include "bootstrap_file.h"
#include "bootstrap_serialization.h"
+#include "blocks/blocks.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "serialization/binary_utils.h" // dump_binary(), parse_binary()
#include "serialization/json_utils.h" // dump_json()
@@ -758,7 +759,12 @@ int main(int argc, char* argv[])
{
core.disable_dns_checkpoints(true);
- if (!core.init(vm, NULL))
+#if defined(PER_BLOCK_CHECKPOINT)
+ const GetCheckpointsCallback& get_checkpoints = blocks::GetCheckpointsData;
+#else
+ const GetCheckpointsCallback& get_checkpoints = nullptr;
+#endif
+ if (!core.init(vm, nullptr, nullptr, get_checkpoints))
{
std::cerr << "Failed to initialize core" << ENDL;
return 1;
diff --git a/src/blocks/CMakeLists.txt b/src/blocks/CMakeLists.txt
index ebb5408cc..30d85adbf 100644
--- a/src/blocks/CMakeLists.txt
+++ b/src/blocks/CMakeLists.txt
@@ -26,20 +26,23 @@
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-if(APPLE)
- add_library(blocks STATIC blockexports.c)
- set_target_properties(blocks PROPERTIES LINKER_LANGUAGE C)
-else()
- if(LINUX_32)
- add_custom_command(OUTPUT blocks.o MAIN_DEPENDENCY blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -m elf_i386 ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocks.o blocks.dat)
- add_custom_command(OUTPUT testnet_blocks.o MAIN_DEPENDENCY testnet_blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -m elf_i386 ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/testnet_blocks.o testnet_blocks.dat)
- add_custom_command(OUTPUT stagenet_blocks.o MAIN_DEPENDENCY stagenet_blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -m elf_i386 ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/stagenet_blocks.o stagenet_blocks.dat)
- else()
- add_custom_command(OUTPUT blocks.o MAIN_DEPENDENCY blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocks.o blocks.dat)
- add_custom_command(OUTPUT testnet_blocks.o MAIN_DEPENDENCY testnet_blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/testnet_blocks.o testnet_blocks.dat)
- add_custom_command(OUTPUT stagenet_blocks.o MAIN_DEPENDENCY stagenet_blocks.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/stagenet_blocks.o stagenet_blocks.dat)
- endif()
- add_library(blocks STATIC blocks.o testnet_blocks.o stagenet_blocks.o blockexports.c)
- set_target_properties(blocks PROPERTIES LINKER_LANGUAGE C)
-endif()
+set(GENERATED_SOURCES "")
+foreach(BLOB_NAME checkpoints testnet_blocks stagenet_blocks)
+ set(OUTPUT_C_SOURCE "generated_${BLOB_NAME}.c")
+ list(APPEND GENERATED_SOURCES ${OUTPUT_C_SOURCE})
+ set(INPUT_DAT_FILE "${BLOB_NAME}.dat")
+ add_custom_command(
+ OUTPUT ${OUTPUT_C_SOURCE}
+ MAIN_DEPENDENCY ${INPUT_DAT_FILE}
+ COMMAND
+ cd ${CMAKE_CURRENT_BINARY_DIR} &&
+ echo "'#include\t<stddef.h>'" > ${OUTPUT_C_SOURCE} &&
+ echo "'const\tunsigned\tchar\t${BLOB_NAME}[]={'" >> ${OUTPUT_C_SOURCE} &&
+ od -v -An -tu1 ${CMAKE_CURRENT_SOURCE_DIR}/${INPUT_DAT_FILE} | sed -e "'s/[0-9]\\{1,\\}/&,/g'" -e "'$$s/.$$//'" >> ${OUTPUT_C_SOURCE} &&
+ echo "'};'" >> ${OUTPUT_C_SOURCE} &&
+ echo "'const\tsize_t\t${BLOB_NAME}_len\t=\tsizeof(${BLOB_NAME});'" >> ${OUTPUT_C_SOURCE}
+ )
+endforeach()
+
+add_library(blocks STATIC blocks.cpp ${GENERATED_SOURCES})
diff --git a/src/blocks/blockexports.c b/src/blocks/blockexports.c
deleted file mode 100644
index 0154b0413..000000000
--- a/src/blocks/blockexports.c
+++ /dev/null
@@ -1,87 +0,0 @@
-#include <stddef.h>
-
-#if defined(__APPLE__)
-#include <mach-o/getsect.h>
-#ifdef BUILD_SHARED_LIBS
-#if !defined(__LP64__)
-const struct mach_header _mh_execute_header;
-#else
-const struct mach_header_64 _mh_execute_header;
-#endif
-#else
-#if !defined(__LP64__)
-extern const struct mach_header _mh_execute_header;
-#else
-extern const struct mach_header_64 _mh_execute_header;
-#endif
-#endif
-
-const unsigned char *get_blocks_dat_start(int testnet, int stagenet)
-{
- size_t size;
- if (testnet)
- return getsectiondata(&_mh_execute_header, "__DATA", "__testnet_blocks_dat", &size);
- else if (stagenet)
- return getsectiondata(&_mh_execute_header, "__DATA", "__stagenet_blocks_dat", &size);
- else
- return getsectiondata(&_mh_execute_header, "__DATA", "__blocks_dat", &size);
-}
-
-size_t get_blocks_dat_size(int testnet, int stagenet)
-{
- size_t size;
- if (testnet)
- getsectiondata(&_mh_execute_header, "__DATA", "__testnet_blocks_dat", &size);
- else if (stagenet)
- getsectiondata(&_mh_execute_header, "__DATA", "__stagenet_blocks_dat", &size);
- else
- getsectiondata(&_mh_execute_header, "__DATA", "__blocks_dat", &size);
- return size;
-}
-
-#else
-
-#if defined(_WIN32) && !defined(_WIN64)
-#define _binary_blocks_start binary_blocks_dat_start
-#define _binary_blocks_end binary_blocks_dat_end
-#define _binary_testnet_blocks_start binary_testnet_blocks_dat_start
-#define _binary_testnet_blocks_end binary_testnet_blocks_dat_end
-#define _binary_stagenet_blocks_start binary_stagenet_blocks_dat_start
-#define _binary_stagenet_blocks_end binary_stagenet_blocks_dat_end
-#else
-#define _binary_blocks_start _binary_blocks_dat_start
-#define _binary_blocks_end _binary_blocks_dat_end
-#define _binary_testnet_blocks_start _binary_testnet_blocks_dat_start
-#define _binary_testnet_blocks_end _binary_testnet_blocks_dat_end
-#define _binary_stagenet_blocks_start _binary_stagenet_blocks_dat_start
-#define _binary_stagenet_blocks_end _binary_stagenet_blocks_dat_end
-#endif
-
-extern const unsigned char _binary_blocks_start[];
-extern const unsigned char _binary_blocks_end[];
-extern const unsigned char _binary_testnet_blocks_start[];
-extern const unsigned char _binary_testnet_blocks_end[];
-extern const unsigned char _binary_stagenet_blocks_start[];
-extern const unsigned char _binary_stagenet_blocks_end[];
-
-const unsigned char *get_blocks_dat_start(int testnet, int stagenet)
-{
- if (testnet)
- return _binary_testnet_blocks_start;
- else if (stagenet)
- return _binary_stagenet_blocks_start;
- else
- return _binary_blocks_start;
-}
-
-size_t get_blocks_dat_size(int testnet, int stagenet)
-{
- if (testnet)
- return (size_t) (_binary_testnet_blocks_end - _binary_testnet_blocks_start);
- else if (stagenet)
- return (size_t) (_binary_stagenet_blocks_end - _binary_stagenet_blocks_start);
- else
- return (size_t) (_binary_blocks_end - _binary_blocks_start);
-}
-
-#endif
diff --git a/src/blocks/blocks.cpp b/src/blocks/blocks.cpp
new file mode 100644
index 000000000..0661f8448
--- /dev/null
+++ b/src/blocks/blocks.cpp
@@ -0,0 +1,31 @@
+#include "blocks.h"
+
+#include <unordered_map>
+
+extern const unsigned char checkpoints[];
+extern const size_t checkpoints_len;
+extern const unsigned char stagenet_blocks[];
+extern const size_t stagenet_blocks_len;
+extern const unsigned char testnet_blocks[];
+extern const size_t testnet_blocks_len;
+
+namespace blocks
+{
+
+ const std::unordered_map<cryptonote::network_type, const epee::span<const unsigned char>, std::hash<size_t>> CheckpointsByNetwork = {
+ {cryptonote::network_type::MAINNET, {checkpoints, checkpoints_len}},
+ {cryptonote::network_type::STAGENET, {stagenet_blocks, stagenet_blocks_len}},
+ {cryptonote::network_type::TESTNET, {testnet_blocks, testnet_blocks_len}}
+ };
+
+ const epee::span<const unsigned char> GetCheckpointsData(cryptonote::network_type network)
+ {
+ const auto it = CheckpointsByNetwork.find(network);
+ if (it != CheckpointsByNetwork.end())
+ {
+ return it->second;
+ }
+ return nullptr;
+ }
+
+}
diff --git a/src/blocks/blocks.dat b/src/blocks/blocks.dat
deleted file mode 100644
index e69de29bb..000000000
--- a/src/blocks/blocks.dat
+++ /dev/null
diff --git a/src/blocks/blocks.h b/src/blocks/blocks.h
index ec683f47e..14e391319 100644
--- a/src/blocks/blocks.h
+++ b/src/blocks/blocks.h
@@ -1,16 +1,12 @@
#ifndef SRC_BLOCKS_BLOCKS_H_
#define SRC_BLOCKS_BLOCKS_H_
-#ifdef __cplusplus
-extern "C" {
-#endif
+#include "cryptonote_config.h"
+#include "span.h"
-const unsigned char *get_blocks_dat_start(int testnet, int stagenet);
-size_t get_blocks_dat_size(int testnet, int stagenet);
-
-#ifdef __cplusplus
+namespace blocks
+{
+ const epee::span<const unsigned char> GetCheckpointsData(cryptonote::network_type network);
}
-#endif
-
#endif /* SRC_BLOCKS_BLOCKS_H_ */
diff --git a/src/common/perf_timer.cpp b/src/common/perf_timer.cpp
index 6910ebdd4..47f01de65 100644
--- a/src/common/perf_timer.cpp
+++ b/src/common/perf_timer.cpp
@@ -104,11 +104,11 @@ PerformanceTimer::PerformanceTimer(bool paused): started(true), paused(paused)
ticks = get_tick_count();
}
-LoggingPerformanceTimer::LoggingPerformanceTimer(const std::string &s, uint64_t unit, el::Level l): PerformanceTimer(), name(s), unit(unit), level(l)
+LoggingPerformanceTimer::LoggingPerformanceTimer(const std::string &s, const std::string &cat, uint64_t unit, el::Level l): PerformanceTimer(), name(s), cat(cat), unit(unit), level(l)
{
if (!performance_timers)
{
- MLOG(level, "PERF ----------");
+ MCLOG(level, cat.c_str(), "PERF ----------");
performance_timers = new std::vector<LoggingPerformanceTimer*>();
}
else
@@ -117,7 +117,7 @@ LoggingPerformanceTimer::LoggingPerformanceTimer(const std::string &s, uint64_t
if (!pt->started && !pt->paused)
{
size_t size = 0; for (const auto *tmp: *performance_timers) if (!tmp->paused) ++size;
- MLOG(pt->level, "PERF " << std::string((size-1) * 2, ' ') << " " << pt->name);
+ MCLOG(pt->level, cat.c_str(), "PERF " << std::string((size-1) * 2, ' ') << " " << pt->name);
pt->started = true;
}
}
@@ -137,7 +137,7 @@ LoggingPerformanceTimer::~LoggingPerformanceTimer()
char s[12];
snprintf(s, sizeof(s), "%8llu ", (unsigned long long)(ticks_to_ns(ticks) / (1000000000 / unit)));
size_t size = 0; for (const auto *tmp: *performance_timers) if (!tmp->paused || tmp==this) ++size;
- MLOG(level, "PERF " << s << std::string(size * 2, ' ') << " " << name);
+ MCLOG(level, cat.c_str(), "PERF " << s << std::string(size * 2, ' ') << " " << name);
if (performance_timers->empty())
{
delete performance_timers;
diff --git a/src/common/perf_timer.h b/src/common/perf_timer.h
index 675d6234d..1d4dee5b5 100644
--- a/src/common/perf_timer.h
+++ b/src/common/perf_timer.h
@@ -33,9 +33,6 @@
#include <memory>
#include "misc_log_ex.h"
-#undef MONERO_DEFAULT_LOG_CATEGORY
-#define MONERO_DEFAULT_LOG_CATEGORY "perf"
-
namespace tools
{
@@ -67,23 +64,24 @@ protected:
class LoggingPerformanceTimer: public PerformanceTimer
{
public:
- LoggingPerformanceTimer(const std::string &s, uint64_t unit, el::Level l = el::Level::Debug);
+ LoggingPerformanceTimer(const std::string &s, const std::string &cat, uint64_t unit, el::Level l = el::Level::Debug);
~LoggingPerformanceTimer();
private:
std::string name;
+ std::string cat;
uint64_t unit;
el::Level level;
};
void set_performance_timer_log_level(el::Level level);
-#define PERF_TIMER_UNIT(name, unit) tools::LoggingPerformanceTimer pt_##name(#name, unit, tools::performance_timer_log_level)
-#define PERF_TIMER_UNIT_L(name, unit, l) tools::LoggingPerformanceTimer pt_##name(#name, unit, l)
-#define PERF_TIMER(name) PERF_TIMER_UNIT(name, 1000)
-#define PERF_TIMER_L(name, l) PERF_TIMER_UNIT_L(name, 1000, l)
-#define PERF_TIMER_START_UNIT(name, unit) std::unique_ptr<tools::LoggingPerformanceTimer> pt_##name(new tools::LoggingPerformanceTimer(#name, unit, el::Level::Info))
-#define PERF_TIMER_START(name) PERF_TIMER_START_UNIT(name, 1000)
+#define PERF_TIMER_UNIT(name, unit) tools::LoggingPerformanceTimer pt_##name(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, tools::performance_timer_log_level)
+#define PERF_TIMER_UNIT_L(name, unit, l) tools::LoggingPerformanceTimer pt_##name(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, l)
+#define PERF_TIMER(name) PERF_TIMER_UNIT(name, 1000000)
+#define PERF_TIMER_L(name, l) PERF_TIMER_UNIT_L(name, 1000000, l)
+#define PERF_TIMER_START_UNIT(name, unit) std::unique_ptr<tools::LoggingPerformanceTimer> pt_##name(new tools::LoggingPerformanceTimer(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, el::Level::Info))
+#define PERF_TIMER_START(name) PERF_TIMER_START_UNIT(name, 1000000)
#define PERF_TIMER_STOP(name) do { pt_##name.reset(NULL); } while(0)
#define PERF_TIMER_PAUSE(name) pt_##name->pause()
#define PERF_TIMER_RESUME(name) pt_##name->resume()
diff --git a/src/common/util.cpp b/src/common/util.cpp
index f91230528..43973c511 100644
--- a/src/common/util.cpp
+++ b/src/common/util.cpp
@@ -308,10 +308,19 @@ namespace tools
StringCchCopy(pszOS, BUFSIZE, TEXT("Microsoft "));
// Test for the specific product.
+ if ( osvi.dwMajorVersion == 10 )
+ {
+ if ( osvi.dwMinorVersion == 0 )
+ {
+ if( osvi.wProductType == VER_NT_WORKSTATION )
+ StringCchCat(pszOS, BUFSIZE, TEXT("Windows 10 "));
+ else StringCchCat(pszOS, BUFSIZE, TEXT("Windows Server 2016 " ));
+ }
+ }
if ( osvi.dwMajorVersion == 6 )
{
- if( osvi.dwMinorVersion == 0 )
+ if ( osvi.dwMinorVersion == 0 )
{
if( osvi.wProductType == VER_NT_WORKSTATION )
StringCchCat(pszOS, BUFSIZE, TEXT("Windows Vista "));
@@ -325,6 +334,20 @@ namespace tools
else StringCchCat(pszOS, BUFSIZE, TEXT("Windows Server 2008 R2 " ));
}
+ if ( osvi.dwMinorVersion == 2 )
+ {
+ if( osvi.wProductType == VER_NT_WORKSTATION )
+ StringCchCat(pszOS, BUFSIZE, TEXT("Windows 8 "));
+ else StringCchCat(pszOS, BUFSIZE, TEXT("Windows Server 2012 " ));
+ }
+
+ if ( osvi.dwMinorVersion == 3 )
+ {
+ if( osvi.wProductType == VER_NT_WORKSTATION )
+ StringCchCat(pszOS, BUFSIZE, TEXT("Windows 8.1 "));
+ else StringCchCat(pszOS, BUFSIZE, TEXT("Windows Server 2012 R2 " ));
+ }
+
pGPI = (PGPI) GetProcAddress(
GetModuleHandle(TEXT("kernel32.dll")),
"GetProductInfo");
diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp
index e891a748d..edbc2c561 100644
--- a/src/cryptonote_basic/account.cpp
+++ b/src/cryptonote_basic/account.cpp
@@ -136,6 +136,16 @@ DISABLE_VS_WARNINGS(4244 4345)
void account_base::set_null()
{
m_keys = account_keys();
+ m_creation_timestamp = 0;
+ }
+ //-----------------------------------------------------------------
+ void account_base::deinit()
+ {
+ try{
+ m_keys.get_device().disconnect();
+ } catch (const std::exception &e){
+ MERROR("Device disconnect exception: " << e.what());
+ }
}
//-----------------------------------------------------------------
void account_base::forget_spend_key()
@@ -205,11 +215,16 @@ DISABLE_VS_WARNINGS(4244 4345)
void account_base::create_from_device(hw::device &hwdev)
{
m_keys.set_device(hwdev);
- MCDEBUG("ledger", "device type: "<<typeid(hwdev).name());
- hwdev.init();
- hwdev.connect();
- hwdev.get_public_address(m_keys.m_account_address);
- hwdev.get_secret_keys(m_keys.m_view_secret_key, m_keys.m_spend_secret_key);
+ MCDEBUG("device", "device type: "<<typeid(hwdev).name());
+ CHECK_AND_ASSERT_THROW_MES(hwdev.init(), "Device init failed");
+ CHECK_AND_ASSERT_THROW_MES(hwdev.connect(), "Device connect failed");
+ try {
+ CHECK_AND_ASSERT_THROW_MES(hwdev.get_public_address(m_keys.m_account_address), "Cannot get a device address");
+ CHECK_AND_ASSERT_THROW_MES(hwdev.get_secret_keys(m_keys.m_view_secret_key, m_keys.m_spend_secret_key), "Cannot get device secret");
+ } catch (const std::exception &e){
+ hwdev.disconnect();
+ throw;
+ }
struct tm timestamp = {0};
timestamp.tm_year = 2014 - 1900; // year 2014
timestamp.tm_mon = 4 - 1; // month april
diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h
index 98bba55b1..021f84029 100644
--- a/src/cryptonote_basic/account.h
+++ b/src/cryptonote_basic/account.h
@@ -89,6 +89,7 @@ namespace cryptonote
hw::device& get_device() const {return m_keys.get_device();}
void set_device( hw::device &hwdev) {m_keys.set_device(hwdev);}
+ void deinit();
uint64_t get_createtime() const { return m_creation_timestamp; }
void set_createtime(uint64_t val) { m_creation_timestamp = val; }
diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h
index d4558ef7b..b0eabb0aa 100644
--- a/src/cryptonote_basic/cryptonote_basic.h
+++ b/src/cryptonote_basic/cryptonote_basic.h
@@ -47,7 +47,6 @@
#include "crypto/crypto.h"
#include "crypto/hash.h"
#include "misc_language.h"
-#include "tx_extra.h"
#include "ringct/rctTypes.h"
#include "device/device.hpp"
diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp
index 9e9c12605..e26aac76b 100644
--- a/src/cryptonote_basic/cryptonote_format_utils.cpp
+++ b/src/cryptonote_basic/cryptonote_format_utils.cpp
@@ -445,6 +445,91 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------
+ template<typename T>
+ static bool pick(binary_archive<true> &ar, std::vector<tx_extra_field> &fields, uint8_t tag)
+ {
+ std::vector<tx_extra_field>::iterator it;
+ while ((it = std::find_if(fields.begin(), fields.end(), [](const tx_extra_field &f) { return f.type() == typeid(T); })) != fields.end())
+ {
+ bool r = ::do_serialize(ar, tag);
+ CHECK_AND_NO_ASSERT_MES_L1(r, false, "failed to serialize tx extra field");
+ r = ::do_serialize(ar, boost::get<T>(*it));
+ CHECK_AND_NO_ASSERT_MES_L1(r, false, "failed to serialize tx extra field");
+ fields.erase(it);
+ }
+ return true;
+ }
+ //---------------------------------------------------------------
+ bool sort_tx_extra(const std::vector<uint8_t>& tx_extra, std::vector<uint8_t> &sorted_tx_extra, bool allow_partial)
+ {
+ std::vector<tx_extra_field> tx_extra_fields;
+
+ if(tx_extra.empty())
+ {
+ sorted_tx_extra.clear();
+ return true;
+ }
+
+ std::string extra_str(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size());
+ std::istringstream iss(extra_str);
+ binary_archive<false> ar(iss);
+
+ bool eof = false;
+ size_t processed = 0;
+ while (!eof)
+ {
+ tx_extra_field field;
+ bool r = ::do_serialize(ar, field);
+ if (!r)
+ {
+ MWARNING("failed to deserialize extra field. extra = " << string_tools::buff_to_hex_nodelimer(std::string(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size())));
+ if (!allow_partial)
+ return false;
+ break;
+ }
+ tx_extra_fields.push_back(field);
+ processed = iss.tellg();
+
+ std::ios_base::iostate state = iss.rdstate();
+ eof = (EOF == iss.peek());
+ iss.clear(state);
+ }
+ if (!::serialization::check_stream_state(ar))
+ {
+ MWARNING("failed to deserialize extra field. extra = " << string_tools::buff_to_hex_nodelimer(std::string(reinterpret_cast<const char*>(tx_extra.data()), tx_extra.size())));
+ if (!allow_partial)
+ return false;
+ }
+ MTRACE("Sorted " << processed << "/" << tx_extra.size());
+
+ std::ostringstream oss;
+ binary_archive<true> nar(oss);
+
+ // sort by:
+ if (!pick<tx_extra_pub_key>(nar, tx_extra_fields, TX_EXTRA_TAG_PUBKEY)) return false;
+ if (!pick<tx_extra_additional_pub_keys>(nar, tx_extra_fields, TX_EXTRA_TAG_ADDITIONAL_PUBKEYS)) return false;
+ if (!pick<tx_extra_nonce>(nar, tx_extra_fields, TX_EXTRA_NONCE)) return false;
+ if (!pick<tx_extra_merge_mining_tag>(nar, tx_extra_fields, TX_EXTRA_MERGE_MINING_TAG)) return false;
+ if (!pick<tx_extra_mysterious_minergate>(nar, tx_extra_fields, TX_EXTRA_MYSTERIOUS_MINERGATE_TAG)) return false;
+ if (!pick<tx_extra_padding>(nar, tx_extra_fields, TX_EXTRA_TAG_PADDING)) return false;
+
+ // if not empty, someone added a new type and did not add a case above
+ if (!tx_extra_fields.empty())
+ {
+ MERROR("tx_extra_fields not empty after sorting, someone forgot to add a case above");
+ return false;
+ }
+
+ std::string oss_str = oss.str();
+ if (allow_partial && processed < tx_extra.size())
+ {
+ MDEBUG("Appending unparsed data");
+ oss_str += std::string((const char*)tx_extra.data() + processed, tx_extra.size() - processed);
+ }
+ sorted_tx_extra = std::vector<uint8_t>(oss_str.begin(), oss_str.end());
+ return true;
+ }
+ //---------------------------------------------------------------
crypto::public_key get_tx_pub_key_from_extra(const std::vector<uint8_t>& tx_extra, size_t pk_index)
{
std::vector<tx_extra_field> tx_extra_fields;
diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h
index 725c75f4e..8d33b1ca4 100644
--- a/src/cryptonote_basic/cryptonote_format_utils.h
+++ b/src/cryptonote_basic/cryptonote_format_utils.h
@@ -31,6 +31,7 @@
#pragma once
#include "blobdatatype.h"
#include "cryptonote_basic_impl.h"
+#include "tx_extra.h"
#include "account.h"
#include "subaddress_index.h"
#include "include_base_utils.h"
@@ -65,6 +66,7 @@ namespace cryptonote
}
bool parse_tx_extra(const std::vector<uint8_t>& tx_extra, std::vector<tx_extra_field>& tx_extra_fields);
+ bool sort_tx_extra(const std::vector<uint8_t>& tx_extra, std::vector<uint8_t> &sorted_tx_extra, bool allow_partial = false);
crypto::public_key get_tx_pub_key_from_extra(const std::vector<uint8_t>& tx_extra, size_t pk_index = 0);
crypto::public_key get_tx_pub_key_from_extra(const transaction_prefix& tx, size_t pk_index = 0);
crypto::public_key get_tx_pub_key_from_extra(const transaction& tx, size_t pk_index = 0);
diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp
index d0b03593e..d8ca2dd35 100644
--- a/src/cryptonote_basic/miner.cpp
+++ b/src/cryptonote_basic/miner.cpp
@@ -637,7 +637,7 @@ namespace cryptonote
boost::tribool battery_powered(on_battery_power());
if(!indeterminate( battery_powered ))
{
- on_ac_power = !battery_powered;
+ on_ac_power = !(bool)battery_powered;
}
}
diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h
index a6858ce7c..c62eeb738 100644
--- a/src/cryptonote_config.h
+++ b/src/cryptonote_config.h
@@ -30,6 +30,7 @@
#pragma once
+#include <stdexcept>
#include <string>
#include <boost/uuid/uuid.hpp>
diff --git a/src/cryptonote_core/CMakeLists.txt b/src/cryptonote_core/CMakeLists.txt
index 72844db66..231489fdb 100644
--- a/src/cryptonote_core/CMakeLists.txt
+++ b/src/cryptonote_core/CMakeLists.txt
@@ -41,12 +41,6 @@ set(cryptonote_core_private_headers
tx_pool.h
cryptonote_tx_utils.h)
-if(PER_BLOCK_CHECKPOINT)
- set(Blocks "blocks")
-else()
- set(Blocks "")
-endif()
-
monero_private_headers(cryptonote_core
${cryptonote_core_private_headers})
monero_add_library(cryptonote_core
@@ -69,5 +63,4 @@ target_link_libraries(cryptonote_core
${Boost_SYSTEM_LIBRARY}
${Boost_THREAD_LIBRARY}
PRIVATE
- ${Blocks}
${EXTRA_LIBRARIES})
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index 1ec2366e4..77b6d0b69 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -53,9 +53,6 @@
#include "ringct/rctSigs.h"
#include "common/perf_timer.h"
#include "common/notify.h"
-#if defined(PER_BLOCK_CHECKPOINT)
-#include "blocks/blocks.h"
-#endif
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "blockchain"
@@ -341,7 +338,7 @@ uint64_t Blockchain::get_current_blockchain_height() const
//------------------------------------------------------------------
//FIXME: possibly move this into the constructor, to avoid accidentally
// dereferencing a null BlockchainDB pointer
-bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline, const cryptonote::test_options *test_options, difficulty_type fixed_difficulty)
+bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline, const cryptonote::test_options *test_options, difficulty_type fixed_difficulty, const GetCheckpointsCallback& get_checkpoints/* = nullptr*/)
{
LOG_PRINT_L3("Blockchain::" << __func__);
@@ -442,7 +439,7 @@ bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline
#if defined(PER_BLOCK_CHECKPOINT)
if (m_nettype != FAKECHAIN)
- load_compiled_in_block_hashes();
+ load_compiled_in_block_hashes(get_checkpoints);
#endif
MINFO("Blockchain initialized. last block: " << m_db->height() - 1 << ", " << epee::misc_utils::get_time_interval_string(timestamp_diff) << " time ago, current difficulty: " << get_difficulty_for_next_block());
@@ -886,6 +883,15 @@ difficulty_type Blockchain::get_difficulty_for_next_block()
return diff;
}
//------------------------------------------------------------------
+std::vector<time_t> Blockchain::get_last_block_timestamps(unsigned int blocks) const
+{
+ std::vector<time_t> timestamps(blocks);
+ uint64_t height = m_db->height();
+ while (blocks--)
+ timestamps[blocks] = m_db->get_block_timestamp(height - blocks - 1);
+ return timestamps;
+}
+//------------------------------------------------------------------
// This function removes blocks from the blockchain until it gets to the
// position where the blockchain switch started and then re-adds the blocks
// that had been removed.
@@ -4407,19 +4413,21 @@ void Blockchain::cancel()
#if defined(PER_BLOCK_CHECKPOINT)
static const char expected_block_hashes_hash[] = "954cb2bbfa2fe6f74b2cdd22a1a4c767aea249ad47ad4f7c9445f0f03260f511";
-void Blockchain::load_compiled_in_block_hashes()
+void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get_checkpoints)
{
- const bool testnet = m_nettype == TESTNET;
- const bool stagenet = m_nettype == STAGENET;
- if (m_fast_sync && get_blocks_dat_start(testnet, stagenet) != nullptr && get_blocks_dat_size(testnet, stagenet) > 0)
+ if (get_checkpoints == nullptr || !m_fast_sync)
{
- MINFO("Loading precomputed blocks (" << get_blocks_dat_size(testnet, stagenet) << " bytes)");
-
+ return;
+ }
+ const epee::span<const unsigned char> &checkpoints = get_checkpoints(m_nettype);
+ if (!checkpoints.empty())
+ {
+ MINFO("Loading precomputed blocks (" << checkpoints.size() << " bytes)");
if (m_nettype == MAINNET)
{
// first check hash
crypto::hash hash;
- if (!tools::sha256sum(get_blocks_dat_start(testnet, stagenet), get_blocks_dat_size(testnet, stagenet), hash))
+ if (!tools::sha256sum(checkpoints.data(), checkpoints.size(), hash))
{
MERROR("Failed to hash precomputed blocks data");
return;
@@ -4439,9 +4447,9 @@ void Blockchain::load_compiled_in_block_hashes()
}
}
- if (get_blocks_dat_size(testnet, stagenet) > 4)
+ if (checkpoints.size() > 4)
{
- const unsigned char *p = get_blocks_dat_start(testnet, stagenet);
+ const unsigned char *p = checkpoints.data();
const uint32_t nblocks = *p | ((*(p+1))<<8) | ((*(p+2))<<16) | ((*(p+3))<<24);
if (nblocks > (std::numeric_limits<uint32_t>::max() - 4) / sizeof(hash))
{
@@ -4449,7 +4457,7 @@ void Blockchain::load_compiled_in_block_hashes()
return;
}
const size_t size_needed = 4 + nblocks * sizeof(crypto::hash);
- if(nblocks > 0 && nblocks > (m_db->height() + HASH_OF_HASHES_STEP - 1) / HASH_OF_HASHES_STEP && get_blocks_dat_size(testnet, stagenet) >= size_needed)
+ if(nblocks > 0 && nblocks > (m_db->height() + HASH_OF_HASHES_STEP - 1) / HASH_OF_HASHES_STEP && checkpoints.size() >= size_needed)
{
p += sizeof(uint32_t);
m_blocks_hash_of_hashes.reserve(nblocks);
diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h
index ab66fac8b..f140d7719 100644
--- a/src/cryptonote_core/blockchain.h
+++ b/src/cryptonote_core/blockchain.h
@@ -38,9 +38,11 @@
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/member.hpp>
#include <atomic>
+#include <functional>
#include <unordered_map>
#include <unordered_set>
+#include "span.h"
#include "syncobj.h"
#include "string_tools.h"
#include "cryptonote_basic/cryptonote_basic.h"
@@ -73,6 +75,15 @@ namespace cryptonote
db_nosync //!< Leave syncing up to the backing db (safest, but slowest because of disk I/O)
};
+ /**
+ * @brief Callback routine that returns checkpoints data for specific network type
+ *
+ * @param network network type
+ *
+ * @return checkpoints data, empty span if there ain't any checkpoints for specific network type
+ */
+ typedef std::function<const epee::span<const unsigned char>(cryptonote::network_type network)> GetCheckpointsCallback;
+
/************************************************************************/
/* */
/************************************************************************/
@@ -117,10 +128,11 @@ namespace cryptonote
* @param offline true if running offline, else false
* @param test_options test parameters
* @param fixed_difficulty fixed difficulty for testing purposes; 0 means disabled
+ * @param get_checkpoints if set, will be called to get checkpoints data
*
* @return true on success, false if any initialization steps fail
*/
- bool init(BlockchainDB* db, const network_type nettype = MAINNET, bool offline = false, const cryptonote::test_options *test_options = NULL, difficulty_type fixed_difficulty = 0);
+ bool init(BlockchainDB* db, const network_type nettype = MAINNET, bool offline = false, const cryptonote::test_options *test_options = NULL, difficulty_type fixed_difficulty = 0, const GetCheckpointsCallback& get_checkpoints = nullptr);
/**
* @brief Initialize the Blockchain state
@@ -952,6 +964,11 @@ namespace cryptonote
*/
void on_new_tx_from_block(const cryptonote::transaction &tx);
+ /**
+ * @brief returns the timestamps of the last N blocks
+ */
+ std::vector<time_t> get_last_block_timestamps(unsigned int blocks) const;
+
private:
// TODO: evaluate whether or not each of these typedefs are left over from blockchain_storage
@@ -1369,8 +1386,10 @@ namespace cryptonote
* A (possibly empty) set of block hashes can be compiled into the
* monero daemon binary. This function loads those hashes into
* a useful state.
+ *
+ * @param get_checkpoints if set, will be called to get checkpoints data
*/
- void load_compiled_in_block_hashes();
+ void load_compiled_in_block_hashes(const GetCheckpointsCallback& get_checkpoints);
/**
* @brief expands v2 transaction data from blockchain
diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp
index 2fec6b613..4b806c282 100644
--- a/src/cryptonote_core/cryptonote_core.cpp
+++ b/src/cryptonote_core/cryptonote_core.cpp
@@ -389,7 +389,7 @@ namespace cryptonote
return m_blockchain_storage.get_alternative_blocks_count();
}
//-----------------------------------------------------------------------------------------------
- bool core::init(const boost::program_options::variables_map& vm, const char *config_subdir, const cryptonote::test_options *test_options)
+ bool core::init(const boost::program_options::variables_map& vm, const char *config_subdir, const cryptonote::test_options *test_options, const GetCheckpointsCallback& get_checkpoints/* = nullptr */)
{
start_time = std::time(nullptr);
@@ -567,7 +567,7 @@ namespace cryptonote
regtest_hard_forks
};
const difficulty_type fixed_difficulty = command_line::get_arg(vm, arg_fixed_difficulty);
- r = m_blockchain_storage.init(db.release(), m_nettype, m_offline, regtest ? &regtest_test_options : test_options, fixed_difficulty);
+ r = m_blockchain_storage.init(db.release(), m_nettype, m_offline, regtest ? &regtest_test_options : test_options, fixed_difficulty, get_checkpoints);
r = m_mempool.init(max_txpool_weight);
CHECK_AND_ASSERT_MES(r, false, "Failed to initialize memory pool");
@@ -1494,6 +1494,7 @@ namespace cryptonote
m_txpool_auto_relayer.do_call(boost::bind(&core::relay_txpool_transactions, this));
m_check_updates_interval.do_call(boost::bind(&core::check_updates, this));
m_check_disk_space_interval.do_call(boost::bind(&core::check_disk_space, this));
+ m_block_rate_interval.do_call(boost::bind(&core::check_block_rate, this));
m_miner.on_idle();
m_mempool.on_idle();
return true;
@@ -1682,6 +1683,52 @@ namespace cryptonote
return true;
}
//-----------------------------------------------------------------------------------------------
+ double factorial(unsigned int n)
+ {
+ if (n <= 1)
+ return 1.0;
+ double f = n;
+ while (n-- > 1)
+ f *= n;
+ return f;
+ }
+ //-----------------------------------------------------------------------------------------------
+ static double probability(unsigned int blocks, unsigned int expected)
+ {
+ // https://www.umass.edu/wsp/resources/poisson/#computing
+ return pow(expected, blocks) / (factorial(blocks) * exp(expected));
+ }
+ //-----------------------------------------------------------------------------------------------
+ bool core::check_block_rate()
+ {
+ if (m_offline || m_target_blockchain_height > get_current_blockchain_height())
+ {
+ MDEBUG("Not checking block rate, offline or syncing");
+ return true;
+ }
+
+ static constexpr double threshold = 1. / (864000 / DIFFICULTY_TARGET_V2); // one false positive every 10 days
+
+ const time_t now = time(NULL);
+ const std::vector<time_t> timestamps = m_blockchain_storage.get_last_block_timestamps(60);
+
+ static const unsigned int seconds[] = { 5400, 1800, 600 };
+ for (size_t n = 0; n < sizeof(seconds)/sizeof(seconds[0]); ++n)
+ {
+ unsigned int b = 0;
+ for (time_t ts: timestamps) b += ts >= now - static_cast<time_t>(seconds[n]);
+ const double p = probability(b, seconds[n] / DIFFICULTY_TARGET_V2);
+ MDEBUG("blocks in the last " << seconds[n] / 60 << " minutes: " << b << " (probability " << p << ")");
+ if (p < threshold)
+ {
+ MWARNING("There were " << b << " blocks in the last " << seconds[n] / 60 << " minutes, there might be large hash rate changes, or we might be partitioned, cut off from the Monero network or under attack. Or it could be just sheer bad luck.");
+ break; // no need to look further
+ }
+ }
+
+ return true;
+ }
+ //-----------------------------------------------------------------------------------------------
void core::set_target_blockchain_height(uint64_t target_blockchain_height)
{
m_target_blockchain_height = target_blockchain_height;
diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h
index 58fe5b7b5..4eca2a57b 100644
--- a/src/cryptonote_core/cryptonote_core.h
+++ b/src/cryptonote_core/cryptonote_core.h
@@ -117,7 +117,7 @@ namespace cryptonote
* @param relayed whether or not the transaction was relayed to us
* @param do_not_relay whether to prevent the transaction from being relayed
*
- * @return true if the transaction made it to the transaction pool, otherwise false
+ * @return true if the transaction was accepted, false otherwise
*/
bool handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay);
@@ -133,7 +133,7 @@ namespace cryptonote
* @param relayed whether or not the transactions were relayed to us
* @param do_not_relay whether to prevent the transactions from being relayed
*
- * @return true if the transactions made it to the transaction pool, otherwise false
+ * @return true if the transactions were accepted, false otherwise
*/
bool handle_incoming_txs(const std::vector<blobdata>& tx_blobs, std::vector<tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay);
@@ -244,10 +244,11 @@ namespace cryptonote
* @param vm command line parameters
* @param config_subdir subdirectory for config storage
* @param test_options configuration options for testing
+ * @param get_checkpoints if set, will be called to get checkpoints data, must return checkpoints data pointer and size or nullptr if there ain't any checkpoints for specific network type
*
* @return false if one of the init steps fails, otherwise true
*/
- bool init(const boost::program_options::variables_map& vm, const char *config_subdir = NULL, const test_options *test_options = NULL);
+ bool init(const boost::program_options::variables_map& vm, const char *config_subdir = NULL, const test_options *test_options = NULL, const GetCheckpointsCallback& get_checkpoints = nullptr);
/**
* @copydoc Blockchain::reset_and_set_genesis_block
@@ -945,6 +946,13 @@ namespace cryptonote
*/
bool check_disk_space();
+ /**
+ * @brief checks block rate, and warns if it's too slow
+ *
+ * @return true on success, false otherwise
+ */
+ bool check_block_rate();
+
bool m_test_drop_download = true; //!< whether or not to drop incoming blocks (for testing)
uint64_t m_test_drop_download_height = 0; //!< height under which to drop incoming blocks, if doing so
@@ -969,6 +977,7 @@ namespace cryptonote
epee::math_helper::once_a_time_seconds<60*2, false> m_txpool_auto_relayer; //!< interval for checking re-relaying txpool transactions
epee::math_helper::once_a_time_seconds<60*60*12, true> m_check_updates_interval; //!< interval for checking for new versions
epee::math_helper::once_a_time_seconds<60*10, true> m_check_disk_space_interval; //!< interval for checking for disk space
+ epee::math_helper::once_a_time_seconds<90, false> m_block_rate_interval; //!< interval for checking block rate
std::atomic<bool> m_starter_message_showed; //!< has the "daemon will sync now" message been shown?
diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp
index 4bc33b56b..4fc2736a6 100644
--- a/src/cryptonote_core/cryptonote_tx_utils.cpp
+++ b/src/cryptonote_core/cryptonote_tx_utils.cpp
@@ -38,6 +38,7 @@ using namespace epee;
#include "cryptonote_tx_utils.h"
#include "cryptonote_config.h"
#include "cryptonote_basic/miner.h"
+#include "cryptonote_basic/tx_extra.h"
#include "crypto/crypto.h"
#include "crypto/hash.h"
#include "ringct/rctSigs.h"
@@ -84,6 +85,8 @@ namespace cryptonote
if(!extra_nonce.empty())
if(!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce))
return false;
+ if (!sort_tx_extra(tx.extra, tx.extra))
+ return false;
txin_gen in;
in.height = height;
@@ -434,6 +437,9 @@ namespace cryptonote
add_additional_tx_pub_keys_to_extra(tx.extra, additional_tx_public_keys);
}
+ if (!sort_tx_extra(tx.extra, tx.extra))
+ return false;
+
//check money
if(summary_outs_money > summary_inputs_money )
{
diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt
index f645836a4..117790455 100644
--- a/src/daemon/CMakeLists.txt
+++ b/src/daemon/CMakeLists.txt
@@ -26,20 +26,6 @@
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-set(blocksdat "")
-if(PER_BLOCK_CHECKPOINT)
- if(APPLE AND DEPENDS)
- add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && touch stub.c && ${CMAKE_C_COMPILER} --target=x86_64-apple-darwin11 -o stub.o -c stub.c COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} -r -sectcreate __DATA __blocks_dat ../blocks/checkpoints.dat -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o stub.o && rm -f stub.*)
- elseif(APPLE AND NOT DEPENDS)
- add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && touch stub.c && ${CMAKE_C_COMPILER} -o stub.o -c stub.c COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -sectcreate __DATA __blocks_dat ../blocks/checkpoints.dat -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o stub.o && rm -f stub.*)
- elseif(LINUX_32)
- add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && cp ../blocks/checkpoints.dat blocks.dat && ${CMAKE_LINKER} -m elf_i386 ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o blocks.dat && rm -f blocks.dat)
- else()
- add_custom_command(OUTPUT blocksdat.o MAIN_DEPENDENCY ../blocks/checkpoints.dat COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR} && cp ../blocks/checkpoints.dat blocks.dat && ${CMAKE_LINKER} ${LD_RAW_FLAGS} -r -b binary -o ${CMAKE_CURRENT_BINARY_DIR}/blocksdat.o blocks.dat && rm -f blocks.dat)
- endif()
- set(blocksdat "blocksdat.o")
-endif()
-
set(daemon_sources
command_parser_executor.cpp
command_server.cpp
@@ -81,9 +67,7 @@ monero_private_headers(daemon
monero_add_executable(daemon
${daemon_sources}
${daemon_headers}
- ${daemon_private_headers}
- ${blocksdat}
-)
+ ${daemon_private_headers})
target_link_libraries(daemon
PRIVATE
rpc
@@ -106,7 +90,8 @@ target_link_libraries(daemon
${CMAKE_THREAD_LIBS_INIT}
${ZMQ_LIB}
${GNU_READLINE_LIBRARY}
- ${EXTRA_LIBRARIES})
+ ${EXTRA_LIBRARIES}
+ ${Blocks})
set_property(TARGET daemon
PROPERTY
OUTPUT_NAME "monerod")
diff --git a/src/daemon/core.h b/src/daemon/core.h
index 475f418d6..d1defd573 100644
--- a/src/daemon/core.h
+++ b/src/daemon/core.h
@@ -28,6 +28,7 @@
#pragma once
+#include "blocks/blocks.h"
#include "cryptonote_core/cryptonote_core.h"
#include "cryptonote_protocol/cryptonote_protocol_handler.h"
#include "misc_log_ex.h"
@@ -85,7 +86,12 @@ public:
//initialize core here
MGINFO("Initializing core...");
std::string config_subdir = get_config_subdir();
- if (!m_core.init(m_vm_HACK, config_subdir.empty() ? NULL : config_subdir.c_str()))
+#if defined(PER_BLOCK_CHECKPOINT)
+ const cryptonote::GetCheckpointsCallback& get_checkpoints = blocks::GetCheckpointsData;
+#else
+ const cryptonote::GetCheckpointsCallback& get_checkpoints = nullptr;
+#endif
+ if (!m_core.init(m_vm_HACK, config_subdir.empty() ? NULL : config_subdir.c_str(), nullptr, get_checkpoints))
{
return false;
}
diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp
index 6464d372f..34d74d642 100644
--- a/src/daemon/rpc_command_executor.cpp
+++ b/src/daemon/rpc_command_executor.cpp
@@ -449,7 +449,7 @@ bool t_rpc_command_executor::show_status() {
% get_sync_percentage(ires)
% (ires.testnet ? "testnet" : ires.stagenet ? "stagenet" : "mainnet")
% bootstrap_msg
- % (!has_mining_info ? "mining info unavailable" : mining_busy ? "syncing" : mres.active ? ( ( mres.is_background_mining_enabled ? "smart " : "" ) + std::string("mining at ") + get_mining_speed(mres.speed) ) : "not mining")
+ % (!has_mining_info ? "mining info unavailable" : mining_busy ? "syncing" : mres.active ? ( ( mres.is_background_mining_enabled ? "smart " : "" ) + std::string("mining at ") + get_mining_speed(mres.speed) + std::string(" to ") + mres.address ) : "not mining")
% get_mining_speed(ires.difficulty / ires.target)
% (unsigned)hfres.version
% get_fork_extra_info(hfres.earliest_height, net_height, ires.target)
@@ -1717,11 +1717,14 @@ bool t_rpc_command_executor::print_blockchain_dynamic_stats(uint64_t nblocks)
cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::response bhres;
cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::request fereq;
cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::response feres;
+ cryptonote::COMMAND_RPC_HARD_FORK_INFO::request hfreq;
+ cryptonote::COMMAND_RPC_HARD_FORK_INFO::response hfres;
epee::json_rpc::error error_resp;
std::string fail_message = "Problem fetching info";
fereq.grace_blocks = 0;
+ hfreq.version = HF_VERSION_PER_BYTE_FEE;
if (m_is_rpc)
{
if (!m_rpc_client->rpc_request(ireq, ires, "/getinfo", fail_message.c_str()))
@@ -1732,6 +1735,10 @@ bool t_rpc_command_executor::print_blockchain_dynamic_stats(uint64_t nblocks)
{
return true;
}
+ if (!m_rpc_client->json_rpc_request(hfreq, hfres, "hard_fork_info", fail_message.c_str()))
+ {
+ return true;
+ }
}
else
{
@@ -1745,10 +1752,15 @@ bool t_rpc_command_executor::print_blockchain_dynamic_stats(uint64_t nblocks)
tools::fail_msg_writer() << make_error(fail_message, feres.status);
return true;
}
+ if (!m_rpc_server->on_hard_fork_info(hfreq, hfres, error_resp) || hfres.status != CORE_RPC_STATUS_OK)
+ {
+ tools::fail_msg_writer() << make_error(fail_message, hfres.status);
+ return true;
+ }
}
tools::msg_writer() << "Height: " << ires.height << ", diff " << ires.difficulty << ", cum. diff " << ires.cumulative_difficulty
- << ", target " << ires.target << " sec" << ", dyn fee " << cryptonote::print_money(feres.fee) << "/kB";
+ << ", target " << ires.target << " sec" << ", dyn fee " << cryptonote::print_money(feres.fee) << "/" << (hfres.enabled ? "byte" : "kB");
if (nblocks > 0)
{
diff --git a/src/device/CMakeLists.txt b/src/device/CMakeLists.txt
index 8f446f42a..91d670b73 100644
--- a/src/device/CMakeLists.txt
+++ b/src/device/CMakeLists.txt
@@ -44,6 +44,7 @@ set(device_headers
device.hpp
device_io.hpp
device_default.hpp
+ device_cold.hpp
log.hpp
)
@@ -58,12 +59,6 @@ endif()
set(device_private_headers)
-if(PER_BLOCK_CHECKPOINT)
- set(Blocks "blocks")
-else()
- set(Blocks "")
-endif()
-
monero_private_headers(device
${device_private_headers})
@@ -78,6 +73,6 @@ target_link_libraries(device
cncrypto
ringct_basic
${OPENSSL_CRYPTO_LIBRARIES}
+ ${Boost_SERIALIZATION_LIBRARY}
PRIVATE
- ${Blocks}
${EXTRA_LIBRARIES})
diff --git a/src/device/device.hpp b/src/device/device.hpp
index cb9117650..dd9ad4332 100644
--- a/src/device/device.hpp
+++ b/src/device/device.hpp
@@ -47,6 +47,7 @@
#include "crypto/crypto.h"
#include "crypto/chacha.h"
#include "ringct/rctTypes.h"
+#include "cryptonote_config.h"
#ifndef USE_DEVICE_LEDGER
@@ -85,7 +86,7 @@ namespace hw {
public:
- device() {}
+ device(): mode(NONE) {}
device(const device &hwdev) {}
virtual ~device() {}
@@ -99,10 +100,17 @@ namespace hw {
enum device_type
{
SOFTWARE = 0,
- LEDGER = 1
+ LEDGER = 1,
+ TREZOR = 2
};
+ enum device_protocol_t {
+ PROTOCOL_DEFAULT,
+ PROTOCOL_PROXY, // Originally defined by Ledger
+ PROTOCOL_COLD, // Originally defined by Trezor
+ };
+
/* ======================================================================= */
/* SETUP/TEARDOWN */
/* ======================================================================= */
@@ -115,10 +123,12 @@ namespace hw {
virtual bool connect(void) = 0;
virtual bool disconnect(void) = 0;
- virtual bool set_mode(device_mode mode) = 0;
+ virtual bool set_mode(device_mode mode) { this->mode = mode; return true; }
+ virtual device_mode get_mode() const { return mode; }
virtual device_type get_type() const = 0;
+ virtual device_protocol_t device_protocol() const { return PROTOCOL_DEFAULT; };
/* ======================================================================= */
/* LOCKER */
@@ -202,6 +212,14 @@ namespace hw {
virtual bool mlsag_sign(const rct::key &c, const rct::keyV &xx, const rct::keyV &alpha, const size_t rows, const size_t dsRows, rct::keyV &ss) = 0;
virtual bool close_tx(void) = 0;
+
+ virtual bool has_ki_cold_sync(void) const { return false; }
+ virtual bool has_tx_cold_sign(void) const { return false; }
+
+ virtual void set_network_type(cryptonote::network_type network_type) { }
+
+ protected:
+ device_mode mode;
} ;
struct reset_mode {
diff --git a/src/device/device_cold.hpp b/src/device/device_cold.hpp
new file mode 100644
index 000000000..22128cec1
--- /dev/null
+++ b/src/device/device_cold.hpp
@@ -0,0 +1,71 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef MONERO_DEVICE_COLD_H
+#define MONERO_DEVICE_COLD_H
+
+#include "wallet/wallet2.h"
+#include <boost/function.hpp>
+
+
+namespace hw {
+
+ typedef struct wallet_shim {
+ boost::function<crypto::public_key (const tools::wallet2::transfer_details &td)> get_tx_pub_key_from_received_outs;
+ } wallet_shim;
+
+ class tx_aux_data {
+ public:
+ std::vector<std::string> tx_device_aux; // device generated aux data
+ std::vector<cryptonote::address_parse_info> tx_recipients; // as entered by user
+ };
+
+ class device_cold {
+ public:
+
+ using exported_key_image = std::vector<std::pair<crypto::key_image, crypto::signature>>;
+
+ /**
+ * Key image sync with the cold protocol.
+ */
+ virtual void ki_sync(wallet_shim * wallet,
+ const std::vector<::tools::wallet2::transfer_details> & transfers,
+ exported_key_image & ski) =0;
+
+ /**
+ * Signs unsigned transaction with the cold protocol.
+ */
+ virtual void tx_sign(wallet_shim * wallet,
+ const ::tools::wallet2::unsigned_tx_set & unsigned_tx,
+ ::tools::wallet2::signed_tx_set & signed_tx,
+ tx_aux_data & aux_data) =0;
+ };
+}
+
+#endif //MONERO_DEVICE_COLD_H
diff --git a/src/device/device_default.cpp b/src/device/device_default.cpp
index a4f40e041..1e3d80949 100644
--- a/src/device/device_default.cpp
+++ b/src/device/device_default.cpp
@@ -69,21 +69,21 @@ namespace hw {
}
bool device_default::init(void) {
- dfns();
+ return true;
}
bool device_default::release() {
- dfns();
+ return true;
}
bool device_default::connect(void) {
- dfns();
+ return true;
}
bool device_default::disconnect() {
- dfns();
+ return true;
}
bool device_default::set_mode(device_mode mode) {
- return true;
+ return device::set_mode(mode);
}
/* ======================================================================= */
diff --git a/src/device/device_io_hid.cpp b/src/device/device_io_hid.cpp
index 666255cb3..1aadfb9ea 100644
--- a/src/device/device_io_hid.cpp
+++ b/src/device/device_io_hid.cpp
@@ -1,3 +1,18 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp
index d879ee95a..0a86e6987 100644
--- a/src/device/device_ledger.cpp
+++ b/src/device/device_ledger.cpp
@@ -396,7 +396,7 @@ namespace hw {
CHECK_AND_ASSERT_THROW_MES(false, " device_ledger::set_mode(unsigned int mode): invalid mode: "<<mode);
}
MDEBUG("Switch to mode: " <<mode);
- return true;
+ return device::set_mode(mode);
}
diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp
index dde69fbfd..2f5beb044 100644
--- a/src/device/device_ledger.hpp
+++ b/src/device/device_ledger.hpp
@@ -141,6 +141,7 @@ namespace hw {
bool set_mode(device_mode mode) override;
device_type get_type() const override {return device_type::LEDGER;};
+ device_protocol_t device_protocol() const override { return PROTOCOL_PROXY; };
/* ======================================================================= */
/* LOCKER */
diff --git a/src/device_trezor/CMakeLists.txt b/src/device_trezor/CMakeLists.txt
new file mode 100644
index 000000000..c555e9fcd
--- /dev/null
+++ b/src/device_trezor/CMakeLists.txt
@@ -0,0 +1,123 @@
+# Copyright (c) 2014-2017, The Monero Project
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification, are
+# permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of
+# conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list
+# of conditions and the following disclaimer in the documentation and/or other
+# materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be
+# used to endorse or promote products derived from this software without specific
+# prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+set(TREZOR_PROTOB_H
+ trezor/messages/messages.pb.h
+ trezor/messages/messages-common.pb.h
+ trezor/messages/messages-management.pb.h
+ trezor/messages/messages-monero.pb.h
+)
+
+set(TREZOR_PROTOB_CPP
+ trezor/messages/messages.pb.cc
+ trezor/messages/messages-common.pb.cc
+ trezor/messages/messages-management.pb.cc
+ trezor/messages/messages-monero.pb.cc
+)
+
+set(trezor_headers
+ trezor/exceptions.hpp
+ trezor/messages_map.hpp
+ trezor/protocol.hpp
+ trezor/transport.hpp
+ device_trezor_base.hpp
+ device_trezor.hpp
+ trezor.hpp
+ ${TREZOR_PROTOB_H}
+)
+
+set(trezor_sources
+ trezor/messages_map.cpp
+ trezor/protocol.cpp
+ trezor/transport.cpp
+ device_trezor_base.cpp
+ device_trezor.cpp
+ ${TREZOR_PROTOB_CPP}
+)
+
+set(trezor_private_headers)
+
+
+include(FindProtobuf)
+find_package(Protobuf) # REQUIRED
+
+# Test for HAVE_PROTOBUF from the parent
+if(Protobuf_FOUND AND HAVE_PROTOBUF)
+ if ("$ENV{PYTHON3}" STREQUAL "")
+ set(PYTHON3 "python3")
+ else()
+ set(PYTHON3 "$ENV{PYTHON3}" CACHE INTERNAL "Copied from environment variable")
+ endif()
+
+ execute_process(COMMAND ${PYTHON3} tools/build_protob.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/trezor RESULT_VARIABLE RET OUTPUT_VARIABLE OUT ERROR_VARIABLE ERR)
+ if(RET)
+ message(WARNING "Trezor protobuf messages could not be regenerated (err=${RET}, python ${PYTHON})."
+ "OUT: ${OUT}, ERR: ${ERR}."
+ "Please read src/device_trezor/trezor/tools/README.md")
+ else()
+ message(STATUS "Trezor protobuf messages regenerated ${OUT}")
+ set(TREZOR_PROTOBUF_GENERATED 1)
+ endif()
+endif()
+
+
+if(TREZOR_PROTOBUF_GENERATED)
+ message(STATUS "Trezor support enabled")
+
+ add_definitions(-DPROTOBUF_INLINE_NOT_IN_HEADERS=0)
+
+ monero_private_headers(device_trezor
+ ${device_private_headers}
+ ${PROTOBUF_INCLUDE_DIR})
+
+ monero_add_library(device_trezor
+ ${trezor_sources}
+ ${trezor_headers}
+ ${trezor_private_headers})
+
+ target_link_libraries(device_trezor
+ PUBLIC
+ device
+ cncrypto
+ ringct_basic
+ cryptonote_core
+ common
+ ${SODIUM_LIBRARY}
+ ${Boost_CHRONO_LIBRARY}
+ ${PROTOBUF_LIBRARY}
+ PRIVATE
+ ${EXTRA_LIBRARIES})
+
+ # set(WITH_DEVICE_TREZOR 1 PARENT_SCOPE)
+ # add_definitions(-DWITH_DEVICE_TREZOR=1)
+
+else()
+ monero_private_headers(device_trezor)
+ monero_add_library(device_trezor device_trezor.cpp)
+ target_link_libraries(device_trezor PUBLIC cncrypto)
+endif()
diff --git a/src/device_trezor/device_trezor.cpp b/src/device_trezor/device_trezor.cpp
new file mode 100644
index 000000000..07c03fc66
--- /dev/null
+++ b/src/device_trezor/device_trezor.cpp
@@ -0,0 +1,363 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include "device_trezor.hpp"
+
+namespace hw {
+namespace trezor {
+
+#if WITH_DEVICE_TREZOR
+
+#undef MONERO_DEFAULT_LOG_CATEGORY
+#define MONERO_DEFAULT_LOG_CATEGORY "device.trezor"
+
+#define HW_TREZOR_NAME "Trezor"
+#define HW_TREZOR_NAME_LITE "TrezorLite"
+
+ static device_trezor *trezor_device = nullptr;
+ static device_trezor *ensure_trezor_device(){
+ if (!trezor_device) {
+ trezor_device = new device_trezor();
+ trezor_device->set_name(HW_TREZOR_NAME);
+ }
+ return trezor_device;
+ }
+
+ void register_all(std::map<std::string, std::unique_ptr<device>> &registry) {
+ registry.insert(std::make_pair(HW_TREZOR_NAME, std::unique_ptr<device>(ensure_trezor_device())));
+ }
+
+ void register_all() {
+ hw::register_device(HW_TREZOR_NAME, ensure_trezor_device());
+ }
+
+ device_trezor::device_trezor() {
+
+ }
+
+ device_trezor::~device_trezor() {
+ try {
+ disconnect();
+ release();
+ } catch(std::exception const& e){
+ MWARNING("Could not disconnect and release: " << e.what());
+ }
+ }
+
+ /* ======================================================================= */
+ /* WALLET & ADDRESS */
+ /* ======================================================================= */
+
+ bool device_trezor::get_public_address(cryptonote::account_public_address &pubkey) {
+ try {
+ auto res = get_address();
+
+ cryptonote::address_parse_info info{};
+ bool r = cryptonote::get_account_address_from_str(info, this->network_type, res->address());
+ CHECK_AND_ASSERT_MES(r, false, "Could not parse returned address. Address parse failed: " + res->address());
+ CHECK_AND_ASSERT_MES(!info.is_subaddress, false, "Trezor returned a sub address");
+
+ pubkey = info.address;
+ return true;
+
+ } catch(std::exception const& e){
+ MERROR("Get public address exception: " << e.what());
+ return false;
+ }
+ }
+
+ bool device_trezor::get_secret_keys(crypto::secret_key &viewkey , crypto::secret_key &spendkey) {
+ try {
+ MDEBUG("Loading view-only key from the Trezor. Please check the Trezor for a confirmation.");
+ auto res = get_view_key();
+ CHECK_AND_ASSERT_MES(res->watch_key().size() == 32, false, "Trezor returned invalid view key");
+
+ spendkey = crypto::null_skey; // not given
+ memcpy(viewkey.data, res->watch_key().data(), 32);
+
+ return true;
+
+ } catch(std::exception const& e){
+ MERROR("Get secret keys exception: " << e.what());
+ return false;
+ }
+ }
+
+ /* ======================================================================= */
+ /* Helpers */
+ /* ======================================================================= */
+
+ /* ======================================================================= */
+ /* TREZOR PROTOCOL */
+ /* ======================================================================= */
+
+ std::shared_ptr<messages::monero::MoneroAddress> device_trezor::get_address(
+ const boost::optional<std::vector<uint32_t>> & path,
+ const boost::optional<cryptonote::network_type> & network_type){
+ AUTO_LOCK_CMD();
+ require_connected();
+ test_ping();
+
+ auto req = std::make_shared<messages::monero::MoneroGetAddress>();
+ this->set_msg_addr<messages::monero::MoneroGetAddress>(req.get(), path, network_type);
+
+ auto response = this->client_exchange<messages::monero::MoneroAddress>(req);
+ MTRACE("Get address response received");
+ return response;
+ }
+
+ std::shared_ptr<messages::monero::MoneroWatchKey> device_trezor::get_view_key(
+ const boost::optional<std::vector<uint32_t>> & path,
+ const boost::optional<cryptonote::network_type> & network_type){
+ AUTO_LOCK_CMD();
+ require_connected();
+ test_ping();
+
+ auto req = std::make_shared<messages::monero::MoneroGetWatchKey>();
+ this->set_msg_addr<messages::monero::MoneroGetWatchKey>(req.get(), path, network_type);
+
+ auto response = this->client_exchange<messages::monero::MoneroWatchKey>(req);
+ MTRACE("Get watch key response received");
+ return response;
+ }
+
+ void device_trezor::ki_sync(wallet_shim * wallet,
+ const std::vector<tools::wallet2::transfer_details> & transfers,
+ hw::device_cold::exported_key_image & ski)
+ {
+ AUTO_LOCK_CMD();
+ require_connected();
+ test_ping();
+
+ std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> req;
+
+ std::vector<protocol::ki::MoneroTransferDetails> mtds;
+ std::vector<protocol::ki::MoneroExportedKeyImage> kis;
+ protocol::ki::key_image_data(wallet, transfers, mtds);
+ protocol::ki::generate_commitment(mtds, transfers, req);
+
+ this->set_msg_addr<messages::monero::MoneroKeyImageExportInitRequest>(req.get());
+ auto ack1 = this->client_exchange<messages::monero::MoneroKeyImageExportInitAck>(req);
+
+ const auto batch_size = 10;
+ const auto num_batches = (mtds.size() + batch_size - 1) / batch_size;
+ for(uint64_t cur = 0; cur < num_batches; ++cur){
+ auto step_req = std::make_shared<messages::monero::MoneroKeyImageSyncStepRequest>();
+ auto idx_finish = std::min(static_cast<uint64_t>((cur + 1) * batch_size), static_cast<uint64_t>(mtds.size()));
+ for(uint64_t idx = cur * batch_size; idx < idx_finish; ++idx){
+ auto added_tdis = step_req->add_tdis();
+ CHECK_AND_ASSERT_THROW_MES(idx < mtds.size(), "Invalid transfer detail index");
+ *added_tdis = mtds[idx];
+ }
+
+ auto step_ack = this->client_exchange<messages::monero::MoneroKeyImageSyncStepAck>(step_req);
+ auto kis_size = step_ack->kis_size();
+ kis.reserve(static_cast<size_t>(kis_size));
+ for(int i = 0; i < kis_size; ++i){
+ auto ckis = step_ack->kis(i);
+ kis.push_back(ckis);
+ }
+
+ MTRACE("Batch " << cur << " / " << num_batches << " batches processed");
+ }
+
+ auto final_req = std::make_shared<messages::monero::MoneroKeyImageSyncFinalRequest>();
+ auto final_ack = this->client_exchange<messages::monero::MoneroKeyImageSyncFinalAck>(final_req);
+ ski.reserve(kis.size());
+
+ for(auto & sub : kis){
+ char buff[32*3];
+ protocol::crypto::chacha::decrypt(sub.blob().data(), sub.blob().size(),
+ reinterpret_cast<const uint8_t *>(final_ack->enc_key().data()),
+ reinterpret_cast<const uint8_t *>(sub.iv().data()), buff);
+
+ ::crypto::signature sig{};
+ ::crypto::key_image ki;
+ memcpy(ki.data, buff, 32);
+ memcpy(sig.c.data, buff + 32, 32);
+ memcpy(sig.r.data, buff + 64, 32);
+ ski.push_back(std::make_pair(ki, sig));
+ }
+ }
+
+
+ void device_trezor::tx_sign(wallet_shim * wallet,
+ const tools::wallet2::unsigned_tx_set & unsigned_tx,
+ tools::wallet2::signed_tx_set & signed_tx,
+ hw::tx_aux_data & aux_data)
+ {
+ size_t num_tx = unsigned_tx.txes.size();
+ signed_tx.key_images.clear();
+ signed_tx.key_images.resize(unsigned_tx.transfers.size());
+
+ for(size_t tx_idx = 0; tx_idx < num_tx; ++tx_idx) {
+ std::shared_ptr<protocol::tx::Signer> signer;
+ tx_sign(wallet, unsigned_tx, tx_idx, aux_data, signer);
+
+ auto & cdata = signer->tdata();
+ auto aux_info_cur = signer->store_tx_aux_info();
+ aux_data.tx_device_aux.emplace_back(aux_info_cur);
+
+ // Pending tx reconstruction
+ signed_tx.ptx.emplace_back();
+ auto & cpend = signed_tx.ptx.back();
+ cpend.tx = cdata.tx;
+ cpend.dust = 0;
+ cpend.fee = 0;
+ cpend.dust_added_to_fee = false;
+ cpend.change_dts = cdata.tx_data.change_dts;
+ cpend.selected_transfers = cdata.tx_data.selected_transfers;
+ cpend.key_images = "";
+ cpend.dests = cdata.tx_data.dests;
+ cpend.construction_data = cdata.tx_data;
+
+ // Transaction check
+ cryptonote::blobdata tx_blob;
+ cryptonote::transaction tx_deserialized;
+ bool r = cryptonote::t_serializable_object_to_blob(cpend.tx, tx_blob);
+ CHECK_AND_ASSERT_THROW_MES(r, "Transaction serialization failed");
+ r = cryptonote::parse_and_validate_tx_from_blob(tx_blob, tx_deserialized);
+ CHECK_AND_ASSERT_THROW_MES(r, "Transaction deserialization failed");
+
+ std::string key_images;
+ bool all_are_txin_to_key = std::all_of(cdata.tx.vin.begin(), cdata.tx.vin.end(), [&](const cryptonote::txin_v& s_e) -> bool
+ {
+ CHECKED_GET_SPECIFIC_VARIANT(s_e, const cryptonote::txin_to_key, in, false);
+ key_images += boost::to_string(in.k_image) + " ";
+ return true;
+ });
+ if(!all_are_txin_to_key) {
+ throw std::invalid_argument("Not all are txin_to_key");
+ }
+ cpend.key_images = key_images;
+
+ // KI sync
+ size_t num_sources = cdata.tx_data.sources.size();
+ CHECK_AND_ASSERT_THROW_MES(num_sources == cdata.source_permutation.size(), "Invalid permutation size");
+ CHECK_AND_ASSERT_THROW_MES(num_sources == cdata.tx.vin.size(), "Invalid tx.vin size");
+ for(size_t src_idx = 0; src_idx < num_sources; ++src_idx){
+ size_t idx_mapped = cdata.source_permutation[src_idx];
+ CHECK_AND_ASSERT_THROW_MES(idx_mapped < cdata.tx_data.selected_transfers.size(), "Invalid idx_mapped");
+ CHECK_AND_ASSERT_THROW_MES(src_idx < cdata.tx.vin.size(), "Invalid idx_mapped");
+
+ size_t idx_map_src = cdata.tx_data.selected_transfers[idx_mapped];
+ auto vini = boost::get<cryptonote::txin_to_key>(cdata.tx.vin[src_idx]);
+
+ CHECK_AND_ASSERT_THROW_MES(idx_map_src < signed_tx.key_images.size(), "Invalid key image index");
+ signed_tx.key_images[idx_map_src] = vini.k_image;
+ }
+ }
+ }
+
+ void device_trezor::tx_sign(wallet_shim * wallet,
+ const tools::wallet2::unsigned_tx_set & unsigned_tx,
+ size_t idx,
+ hw::tx_aux_data & aux_data,
+ std::shared_ptr<protocol::tx::Signer> & signer)
+ {
+ AUTO_LOCK_CMD();
+ require_connected();
+ test_ping();
+
+ CHECK_AND_ASSERT_THROW_MES(idx < unsigned_tx.txes.size(), "Invalid transaction index");
+ signer = std::make_shared<protocol::tx::Signer>(wallet, &unsigned_tx, idx, &aux_data);
+ const tools::wallet2::tx_construction_data & cur_tx = unsigned_tx.txes[idx];
+ unsigned long num_sources = cur_tx.sources.size();
+ unsigned long num_outputs = cur_tx.splitted_dsts.size();
+
+ // Step: Init
+ auto init_msg = signer->step_init();
+ this->set_msg_addr(init_msg.get());
+
+ auto response = this->client_exchange<messages::monero::MoneroTransactionInitAck>(init_msg);
+ signer->step_init_ack(response);
+
+ // Step: Set transaction inputs
+ for(size_t cur_src = 0; cur_src < num_sources; ++cur_src){
+ auto src = signer->step_set_input(cur_src);
+ auto ack = this->client_exchange<messages::monero::MoneroTransactionSetInputAck>(src);
+ signer->step_set_input_ack(ack);
+ }
+
+ // Step: sort
+ auto perm_req = signer->step_permutation();
+ if (perm_req){
+ auto perm_ack = this->client_exchange<messages::monero::MoneroTransactionInputsPermutationAck>(perm_req);
+ signer->step_permutation_ack(perm_ack);
+ }
+
+ // Step: input_vini
+ if (!signer->in_memory()){
+ for(size_t cur_src = 0; cur_src < num_sources; ++cur_src){
+ auto src = signer->step_set_vini_input(cur_src);
+ auto ack = this->client_exchange<messages::monero::MoneroTransactionInputViniAck>(src);
+ signer->step_set_vini_input_ack(ack);
+ }
+ }
+
+ // Step: all inputs set
+ auto all_inputs_set = signer->step_all_inputs_set();
+ auto ack_all_inputs = this->client_exchange<messages::monero::MoneroTransactionAllInputsSetAck>(all_inputs_set);
+ signer->step_all_inputs_set_ack(ack_all_inputs);
+
+ // Step: outputs
+ for(size_t cur_dst = 0; cur_dst < num_outputs; ++cur_dst){
+ auto src = signer->step_set_output(cur_dst);
+ auto ack = this->client_exchange<messages::monero::MoneroTransactionSetOutputAck>(src);
+ signer->step_set_output_ack(ack);
+ }
+
+ // Step: all outs set
+ auto all_out_set = signer->step_all_outs_set();
+ auto ack_all_out_set = this->client_exchange<messages::monero::MoneroTransactionAllOutSetAck>(all_out_set);
+ signer->step_all_outs_set_ack(ack_all_out_set, *this);
+
+ // Step: sign each input
+ for(size_t cur_src = 0; cur_src < num_sources; ++cur_src){
+ auto src = signer->step_sign_input(cur_src);
+ auto ack_sign = this->client_exchange<messages::monero::MoneroTransactionSignInputAck>(src);
+ signer->step_sign_input_ack(ack_sign);
+ }
+
+ // Step: final
+ auto final_msg = signer->step_final();
+ auto ack_final = this->client_exchange<messages::monero::MoneroTransactionFinalAck>(final_msg);
+ signer->step_final_ack(ack_final);
+ }
+
+#else //WITH_DEVICE_TREZOR
+
+ void register_all(std::map<std::string, std::unique_ptr<device>> &registry) {
+ }
+
+ void register_all() {
+ }
+
+#endif //WITH_DEVICE_TREZOR
+}} \ No newline at end of file
diff --git a/src/device_trezor/device_trezor.hpp b/src/device_trezor/device_trezor.hpp
new file mode 100644
index 000000000..765c9b82c
--- /dev/null
+++ b/src/device_trezor/device_trezor.hpp
@@ -0,0 +1,132 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef MONERO_DEVICE_TREZOR_H
+#define MONERO_DEVICE_TREZOR_H
+
+
+#include <cstddef>
+#include <string>
+#include "device/device.hpp"
+#include "device/device_default.hpp"
+#include "device/device_cold.hpp"
+#include <boost/scope_exit.hpp>
+#include <boost/thread/mutex.hpp>
+#include <boost/thread/recursive_mutex.hpp>
+#include "cryptonote_config.h"
+#include "trezor.hpp"
+#include "device_trezor_base.hpp"
+
+namespace hw {
+namespace trezor {
+
+ void register_all();
+ void register_all(std::map<std::string, std::unique_ptr<device>> &registry);
+
+#if WITH_DEVICE_TREZOR
+ class device_trezor;
+
+ /**
+ * Main device
+ */
+ class device_trezor : public hw::trezor::device_trezor_base, public hw::device_cold {
+ protected:
+ // To speed up blockchain parsing the view key maybe handle here.
+ crypto::secret_key viewkey;
+ bool has_view_key;
+
+ public:
+ device_trezor();
+ virtual ~device_trezor() override;
+
+ device_trezor(const device_trezor &device) = delete ;
+ device_trezor& operator=(const device_trezor &device) = delete;
+
+ explicit operator bool() const override {return true;}
+
+ device_protocol_t device_protocol() const override { return PROTOCOL_COLD; };
+
+ bool has_ki_cold_sync() const override { return true; }
+ bool has_tx_cold_sign() const override { return true; }
+ void set_network_type(cryptonote::network_type network_type) override { this->network_type = network_type; }
+
+ /* ======================================================================= */
+ /* WALLET & ADDRESS */
+ /* ======================================================================= */
+ bool get_public_address(cryptonote::account_public_address &pubkey) override;
+ bool get_secret_keys(crypto::secret_key &viewkey , crypto::secret_key &spendkey) override;
+
+ /* ======================================================================= */
+ /* TREZOR PROTOCOL */
+ /* ======================================================================= */
+
+ /**
+ * Get address. Throws.
+ */
+ std::shared_ptr<messages::monero::MoneroAddress> get_address(
+ const boost::optional<std::vector<uint32_t>> & path = boost::none,
+ const boost::optional<cryptonote::network_type> & network_type = boost::none);
+
+ /**
+ * Get watch key from device. Throws.
+ */
+ std::shared_ptr<messages::monero::MoneroWatchKey> get_view_key(
+ const boost::optional<std::vector<uint32_t>> & path = boost::none,
+ const boost::optional<cryptonote::network_type> & network_type = boost::none);
+
+ /**
+ * Key image sync with the Trezor.
+ */
+ void ki_sync(wallet_shim * wallet,
+ const std::vector<::tools::wallet2::transfer_details> & transfers,
+ hw::device_cold::exported_key_image & ski) override;
+
+ /**
+ * Signs particular transaction idx in the unsigned set, keeps state in the signer
+ */
+ void tx_sign(wallet_shim * wallet,
+ const ::tools::wallet2::unsigned_tx_set & unsigned_tx,
+ size_t idx,
+ hw::tx_aux_data & aux_data,
+ std::shared_ptr<protocol::tx::Signer> & signer);
+
+ /**
+ * Signs unsigned transaction with the Trezor.
+ */
+ void tx_sign(wallet_shim * wallet,
+ const ::tools::wallet2::unsigned_tx_set & unsigned_tx,
+ ::tools::wallet2::signed_tx_set & signed_tx,
+ hw::tx_aux_data & aux_data) override;
+ };
+
+#endif
+
+}
+}
+#endif //MONERO_DEVICE_TREZOR_H
diff --git a/src/device_trezor/device_trezor_base.cpp b/src/device_trezor/device_trezor_base.cpp
new file mode 100644
index 000000000..3a98bba5a
--- /dev/null
+++ b/src/device_trezor/device_trezor_base.cpp
@@ -0,0 +1,301 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include "device_trezor_base.hpp"
+
+namespace hw {
+namespace trezor {
+
+#if WITH_DEVICE_TREZOR
+
+#undef MONERO_DEFAULT_LOG_CATEGORY
+#define MONERO_DEFAULT_LOG_CATEGORY "device.trezor"
+
+ std::shared_ptr<google::protobuf::Message> trezor_protocol_callback::on_button_request(const messages::common::ButtonRequest * msg){
+ MDEBUG("on_button_request");
+ device.on_button_request();
+ return std::make_shared<messages::common::ButtonAck>();
+ }
+
+ std::shared_ptr<google::protobuf::Message> trezor_protocol_callback::on_pin_matrix_request(const messages::common::PinMatrixRequest * msg){
+ MDEBUG("on_pin_request");
+ epee::wipeable_string pin;
+ device.on_pin_request(pin);
+ auto resp = std::make_shared<messages::common::PinMatrixAck>();
+ resp->set_pin(pin.data(), pin.size());
+ return resp;
+ }
+
+ std::shared_ptr<google::protobuf::Message> trezor_protocol_callback::on_passphrase_request(const messages::common::PassphraseRequest * msg){
+ MDEBUG("on_passhprase_request");
+ epee::wipeable_string passphrase;
+ device.on_passphrase_request(msg->on_device(), passphrase);
+ auto resp = std::make_shared<messages::common::PassphraseAck>();
+ if (!msg->on_device()){
+ resp->set_passphrase(passphrase.data(), passphrase.size());
+ }
+ return resp;
+ }
+
+ std::shared_ptr<google::protobuf::Message> trezor_protocol_callback::on_passphrase_state_request(const messages::common::PassphraseStateRequest * msg){
+ MDEBUG("on_passhprase_state_request");
+ device.on_passphrase_state_request(msg->state());
+ return std::make_shared<messages::common::PassphraseStateAck>();
+ }
+
+ const uint32_t device_trezor_base::DEFAULT_BIP44_PATH[] = {0x8000002c, 0x80000080, 0x80000000};
+
+ device_trezor_base::device_trezor_base() {
+
+ }
+
+ device_trezor_base::~device_trezor_base() {
+ try {
+ disconnect();
+ release();
+ } catch(std::exception const& e){
+ MERROR("Could not disconnect and release: " << e.what());
+ }
+ }
+
+ /* ======================================================================= */
+ /* SETUP/TEARDOWN */
+ /* ======================================================================= */
+
+ bool device_trezor_base::reset() {
+ return false;
+ }
+
+ bool device_trezor_base::set_name(const std::string & name) {
+ this->full_name = name;
+ this->name = "";
+
+ auto delim = name.find(':');
+ if (delim != std::string::npos && delim + 1 < name.length()) {
+ this->name = name.substr(delim + 1);
+ }
+
+ return true;
+ }
+
+ const std::string device_trezor_base::get_name() const {
+ if (this->full_name.empty()) {
+ return std::string("<disconnected:").append(this->name).append(">");
+ }
+ return this->full_name;
+ }
+
+ bool device_trezor_base::init() {
+ if (!release()){
+ MERROR("Release failed");
+ return false;
+ }
+
+ if (!m_protocol_callback){
+ m_protocol_callback = std::make_shared<trezor_protocol_callback>(*this);
+ }
+ return true;
+ }
+
+ bool device_trezor_base::release() {
+ try {
+ disconnect();
+ return true;
+
+ } catch(std::exception const& e){
+ MERROR("Release exception: " << e.what());
+ return false;
+ }
+ }
+
+ bool device_trezor_base::connect() {
+ disconnect();
+
+ // Enumerate all available devices
+ try {
+ hw::trezor::t_transport_vect trans;
+
+ MDEBUG("Enumerating Trezor devices...");
+ enumerate(trans);
+
+ MDEBUG("Enumeration yielded " << trans.size() << " devices");
+ for (auto &cur : trans) {
+ MDEBUG(" device: " << *(cur.get()));
+ std::string cur_path = cur->get_path();
+ if (boost::starts_with(cur_path, this->name)) {
+ MDEBUG("Device Match: " << cur_path);
+ m_transport = cur;
+ break;
+ }
+ }
+
+ if (!m_transport) {
+ MERROR("No matching Trezor device found. Device specifier: \"" + this->name + "\"");
+ return false;
+ }
+
+ m_transport->open();
+ return true;
+
+ } catch(std::exception const& e){
+ MERROR("Open exception: " << e.what());
+ return false;
+ }
+ }
+
+ bool device_trezor_base::disconnect() {
+ if (m_transport){
+ try {
+ m_transport->close();
+ m_transport = nullptr;
+
+ } catch(std::exception const& e){
+ MERROR("Disconnect exception: " << e.what());
+ m_transport = nullptr;
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /* ======================================================================= */
+ /* LOCKER */
+ /* ======================================================================= */
+
+ //lock the device for a long sequence
+ void device_trezor_base::lock() {
+ MTRACE("Ask for LOCKING for device " << this->name << " in thread ");
+ device_locker.lock();
+ MTRACE("Device " << this->name << " LOCKed");
+ }
+
+ //lock the device for a long sequence
+ bool device_trezor_base::try_lock() {
+ MTRACE("Ask for LOCKING(try) for device " << this->name << " in thread ");
+ bool r = device_locker.try_lock();
+ if (r) {
+ MTRACE("Device " << this->name << " LOCKed(try)");
+ } else {
+ MDEBUG("Device " << this->name << " not LOCKed(try)");
+ }
+ return r;
+ }
+
+ //unlock the device
+ void device_trezor_base::unlock() {
+ MTRACE("Ask for UNLOCKING for device " << this->name << " in thread ");
+ device_locker.unlock();
+ MTRACE("Device " << this->name << " UNLOCKed");
+ }
+
+ /* ======================================================================= */
+ /* Helpers */
+ /* ======================================================================= */
+
+ void device_trezor_base::require_connected(){
+ if (!m_transport){
+ throw exc::NotConnectedException();
+ }
+ }
+
+ void device_trezor_base::call_ping_unsafe(){
+ auto pingMsg = std::make_shared<messages::management::Ping>();
+ pingMsg->set_message("PING");
+
+ auto success = this->client_exchange<messages::common::Success>(pingMsg); // messages::MessageType_Success
+ MDEBUG("Ping response " << success->message());
+ (void)success;
+ }
+
+ void device_trezor_base::test_ping(){
+ require_connected();
+
+ try {
+ this->call_ping_unsafe();
+
+ } catch(exc::TrezorException const& e){
+ MINFO("Trezor does not respond: " << e.what());
+ throw exc::DeviceNotResponsiveException(std::string("Trezor not responding: ") + e.what());
+ }
+ }
+
+ /* ======================================================================= */
+ /* TREZOR PROTOCOL */
+ /* ======================================================================= */
+
+ bool device_trezor_base::ping() {
+ AUTO_LOCK_CMD();
+ if (!m_transport){
+ MINFO("Ping failed, device not connected");
+ return false;
+ }
+
+ try {
+ this->call_ping_unsafe();
+ return true;
+
+ } catch(std::exception const& e) {
+ MERROR("Ping failed, exception thrown " << e.what());
+ } catch(...){
+ MERROR("Ping failed, general exception thrown" << boost::current_exception_diagnostic_information());
+ }
+
+ return false;
+ }
+
+ void device_trezor_base::on_button_request()
+ {
+ if (m_callback){
+ m_callback->on_button_request();
+ }
+ }
+
+ void device_trezor_base::on_pin_request(epee::wipeable_string & pin)
+ {
+ if (m_callback){
+ m_callback->on_pin_request(pin);
+ }
+ }
+
+ void device_trezor_base::on_passphrase_request(bool on_device, epee::wipeable_string & passphrase)
+ {
+ if (m_callback){
+ m_callback->on_passphrase_request(on_device, passphrase);
+ }
+ }
+
+ void device_trezor_base::on_passphrase_state_request(const std::string & state)
+ {
+ if (m_callback){
+ m_callback->on_passphrase_state_request(state);
+ }
+ }
+
+#endif //WITH_DEVICE_TREZOR
+}}
diff --git a/src/device_trezor/device_trezor_base.hpp b/src/device_trezor/device_trezor_base.hpp
new file mode 100644
index 000000000..644a49332
--- /dev/null
+++ b/src/device_trezor/device_trezor_base.hpp
@@ -0,0 +1,301 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef MONERO_DEVICE_TREZOR_BASE_H
+#define MONERO_DEVICE_TREZOR_BASE_H
+
+
+#include <cstddef>
+#include <string>
+#include "device/device.hpp"
+#include "device/device_default.hpp"
+#include "device/device_cold.hpp"
+#include <boost/scope_exit.hpp>
+#include <boost/thread/mutex.hpp>
+#include <boost/thread/recursive_mutex.hpp>
+#include "cryptonote_config.h"
+#include "trezor.hpp"
+
+//automatic lock one more level on device ensuring the current thread is allowed to use it
+#define AUTO_LOCK_CMD() \
+ /* lock both mutexes without deadlock*/ \
+ boost::lock(device_locker, command_locker); \
+ /* make sure both already-locked mutexes are unlocked at the end of scope */ \
+ boost::lock_guard<boost::recursive_mutex> lock1(device_locker, boost::adopt_lock); \
+ boost::lock_guard<boost::mutex> lock2(command_locker, boost::adopt_lock)
+
+
+namespace hw {
+namespace trezor {
+
+#if WITH_DEVICE_TREZOR
+ class device_trezor_base;
+
+ /**
+ * Trezor device callbacks
+ */
+ class trezor_callback {
+ public:
+ virtual void on_button_request() {};
+ virtual void on_pin_request(epee::wipeable_string & pin) {};
+ virtual void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase) {};
+ virtual void on_passphrase_state_request(const std::string & state) {};
+ };
+
+ /**
+ * Default Trezor protocol client callback
+ */
+ class trezor_protocol_callback {
+ protected:
+ device_trezor_base & device;
+
+ public:
+ explicit trezor_protocol_callback(device_trezor_base & device): device(device) {}
+
+ std::shared_ptr<google::protobuf::Message> on_button_request(const messages::common::ButtonRequest * msg);
+ std::shared_ptr<google::protobuf::Message> on_pin_matrix_request(const messages::common::PinMatrixRequest * msg);
+ std::shared_ptr<google::protobuf::Message> on_passphrase_request(const messages::common::PassphraseRequest * msg);
+ std::shared_ptr<google::protobuf::Message> on_passphrase_state_request(const messages::common::PassphraseStateRequest * msg);
+
+ std::shared_ptr<google::protobuf::Message> on_message(const google::protobuf::Message * msg, messages::MessageType message_type){
+ MDEBUG("on_general_message");
+ return on_message_dispatch(msg, message_type);
+ }
+
+ std::shared_ptr<google::protobuf::Message> on_message_dispatch(const google::protobuf::Message * msg, messages::MessageType message_type){
+ if (message_type == messages::MessageType_ButtonRequest){
+ return on_button_request(dynamic_cast<const messages::common::ButtonRequest*>(msg));
+ } else if (message_type == messages::MessageType_PassphraseRequest) {
+ return on_passphrase_request(dynamic_cast<const messages::common::PassphraseRequest*>(msg));
+ } else if (message_type == messages::MessageType_PassphraseStateRequest) {
+ return on_passphrase_state_request(dynamic_cast<const messages::common::PassphraseStateRequest*>(msg));
+ } else if (message_type == messages::MessageType_PinMatrixRequest) {
+ return on_pin_matrix_request(dynamic_cast<const messages::common::PinMatrixRequest*>(msg));
+ } else {
+ return nullptr;
+ }
+ }
+ };
+
+ /**
+ * TREZOR device template with basic functions
+ */
+ class device_trezor_base : public hw::core::device_default {
+ protected:
+
+ // Locker for concurrent access
+ mutable boost::recursive_mutex device_locker;
+ mutable boost::mutex command_locker;
+
+ std::shared_ptr<Transport> m_transport;
+ std::shared_ptr<trezor_protocol_callback> m_protocol_callback;
+ std::shared_ptr<trezor_callback> m_callback;
+
+ std::string full_name;
+
+ cryptonote::network_type network_type;
+
+ //
+ // Internal methods
+ //
+
+ void require_connected();
+ void call_ping_unsafe();
+ void test_ping();
+
+ /**
+ * Client communication wrapper, handles specific Trezor protocol.
+ *
+ * @throws UnexpectedMessageException if the response message type is different than expected.
+ * Exception contains message type and the message itself.
+ */
+ template<class t_message>
+ std::shared_ptr<t_message>
+ client_exchange(const std::shared_ptr<const google::protobuf::Message> &req,
+ const boost::optional<messages::MessageType> & resp_type = boost::none,
+ const boost::optional<std::vector<messages::MessageType>> & resp_types = boost::none,
+ const boost::optional<messages::MessageType*> & resp_type_ptr = boost::none,
+ bool open_session = false,
+ unsigned depth=0)
+ {
+ // Require strictly protocol buffers response in the template.
+ BOOST_STATIC_ASSERT(boost::is_base_of<google::protobuf::Message, t_message>::value);
+ const bool accepting_base = boost::is_same<google::protobuf::Message, t_message>::value;
+ if (resp_types && !accepting_base){
+ throw std::invalid_argument("Cannot specify list of accepted types and not using generic response");
+ }
+
+ // Open session if required
+ if (open_session && depth == 0){
+ try {
+ m_transport->open();
+ } catch (const std::exception& e) {
+ std::throw_with_nested(exc::SessionException("Could not open session"));
+ }
+ }
+
+ // Scoped session closer
+ BOOST_SCOPE_EXIT_ALL(&, this) {
+ if (open_session && depth == 0){
+ this->getTransport()->close();
+ }
+ };
+
+ // Write the request
+ CHECK_AND_ASSERT_THROW_MES(req, "Request is null");
+ this->getTransport()->write(*req);
+
+ // Read the response
+ std::shared_ptr<google::protobuf::Message> msg_resp;
+ hw::trezor::messages::MessageType msg_resp_type;
+
+ // We may have several roundtrips with the handler
+ this->getTransport()->read(msg_resp, &msg_resp_type);
+ if (resp_type_ptr){
+ *(resp_type_ptr.get()) = msg_resp_type;
+ }
+
+ // Determine type of expected message response
+ messages::MessageType required_type = accepting_base ? messages::MessageType_Success :
+ (resp_type ? resp_type.get() : MessageMapper::get_message_wire_number<t_message>());
+
+ if (msg_resp_type == messages::MessageType_Failure) {
+ throw_failure_exception(dynamic_cast<messages::common::Failure *>(msg_resp.get()));
+
+ } else if (!accepting_base && msg_resp_type == required_type) {
+ return message_ptr_retype<t_message>(msg_resp);
+
+ } else {
+ auto resp = this->getProtocolCallback()->on_message(msg_resp.get(), msg_resp_type);
+ if (resp) {
+ return this->client_exchange<t_message>(resp, boost::none, resp_types, resp_type_ptr, false, depth + 1);
+
+ } else if (accepting_base && (!resp_types ||
+ std::find(resp_types.get().begin(), resp_types.get().end(), msg_resp_type) != resp_types.get().end())) {
+ return message_ptr_retype<t_message>(msg_resp);
+
+ } else {
+ throw exc::UnexpectedMessageException(msg_resp_type, msg_resp);
+ }
+ }
+ }
+
+ /**
+ * Utility method to set address_n and network type to the message requets.
+ */
+ template<class t_message>
+ void set_msg_addr(t_message * msg,
+ const boost::optional<std::vector<uint32_t>> & path = boost::none,
+ const boost::optional<cryptonote::network_type> & network_type = boost::none)
+ {
+ CHECK_AND_ASSERT_THROW_MES(msg, "Message is null");
+ msg->clear_address_n();
+ if (path){
+ for(auto x : path.get()){
+ msg->add_address_n(x);
+ }
+ } else {
+ for (unsigned int i : DEFAULT_BIP44_PATH) {
+ msg->add_address_n(i);
+ }
+ }
+
+ if (network_type){
+ msg->set_network_type(static_cast<uint32_t>(network_type.get()));
+ } else {
+ msg->set_network_type(static_cast<uint32_t>(this->network_type));
+ }
+ }
+
+ public:
+ device_trezor_base();
+ ~device_trezor_base() override;
+
+ device_trezor_base(const device_trezor_base &device) = delete ;
+ device_trezor_base& operator=(const device_trezor_base &device) = delete;
+
+ explicit operator bool() const override {return true;}
+ device_type get_type() const override {return device_type::TREZOR;};
+
+ bool reset();
+
+ // Default derivation path for Monero
+ static const uint32_t DEFAULT_BIP44_PATH[3];
+
+ std::shared_ptr<Transport> getTransport(){
+ return m_transport;
+ }
+
+ std::shared_ptr<trezor_protocol_callback> getProtocolCallback(){
+ return m_protocol_callback;
+ }
+
+ std::shared_ptr<trezor_callback> getCallback(){
+ return m_callback;
+ }
+
+ /* ======================================================================= */
+ /* SETUP/TEARDOWN */
+ /* ======================================================================= */
+ bool set_name(const std::string &name) override;
+
+ const std::string get_name() const override;
+ bool init() override;
+ bool release() override;
+ bool connect() override;
+ bool disconnect() override;
+
+ /* ======================================================================= */
+ /* LOCKER */
+ /* ======================================================================= */
+ void lock() override;
+ void unlock() override;
+ bool try_lock() override;
+
+ /* ======================================================================= */
+ /* TREZOR PROTOCOL */
+ /* ======================================================================= */
+
+ /**
+ * Device ping, no-throw
+ */
+ bool ping();
+
+ // Protocol callbacks
+ void on_button_request();
+ void on_pin_request(epee::wipeable_string & pin);
+ void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase);
+ void on_passphrase_state_request(const std::string & state);
+ };
+
+#endif
+
+}
+}
+#endif //MONERO_DEVICE_TREZOR_BASE_H
diff --git a/src/device_trezor/trezor.hpp b/src/device_trezor/trezor.hpp
new file mode 100644
index 000000000..8abdd2c18
--- /dev/null
+++ b/src/device_trezor/trezor.hpp
@@ -0,0 +1,44 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef MONERO_TREZOR_HPP
+#define MONERO_TREZOR_HPP
+
+#include "trezor/trezor_defs.hpp"
+
+#ifdef HAVE_PROTOBUF
+#include "trezor/transport.hpp"
+#include "trezor/messages/messages.pb.h"
+#include "trezor/messages/messages-common.pb.h"
+#include "trezor/messages/messages-management.pb.h"
+#include "trezor/messages/messages-monero.pb.h"
+#include "trezor/protocol.hpp"
+#endif
+
+#endif //MONERO_TREZOR_HPP
diff --git a/src/device_trezor/trezor/exceptions.hpp b/src/device_trezor/trezor/exceptions.hpp
new file mode 100644
index 000000000..197dc43a4
--- /dev/null
+++ b/src/device_trezor/trezor/exceptions.hpp
@@ -0,0 +1,193 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef MONERO_EXCEPTIONS_H
+#define MONERO_EXCEPTIONS_H
+
+#include <exception>
+#include <string>
+#include <boost/optional.hpp>
+
+namespace hw {
+namespace trezor {
+namespace exc {
+
+ class SecurityException : public std::exception {
+ protected:
+ boost::optional<std::string> reason;
+
+ public:
+ SecurityException(): reason("General Security exception"){}
+ explicit SecurityException(std::string what): reason(what){}
+
+ virtual const char* what() const throw() {
+ return reason.get().c_str();
+ }
+ };
+
+ class Poly1305TagInvalid: public SecurityException {
+ public:
+ using SecurityException::SecurityException;
+ Poly1305TagInvalid(): SecurityException("Poly1305 authentication tag invalid"){}
+ };
+
+ class TrezorException : public std::exception {
+ protected:
+ boost::optional<std::string> reason;
+
+ public:
+ TrezorException(): reason("General Trezor exception"){}
+ explicit TrezorException(std::string what): reason(what){}
+
+ virtual const char* what() const throw() {
+ return reason.get().c_str();
+ }
+ };
+
+ class CommunicationException: public TrezorException {
+ public:
+ using TrezorException::TrezorException;
+ CommunicationException(): TrezorException("Trezor communication error"){}
+ };
+
+ class EncodingException: public CommunicationException {
+ public:
+ using CommunicationException::CommunicationException;
+ EncodingException(): CommunicationException("Trezor message encoding error"){}
+ };
+
+ class NotConnectedException : public CommunicationException {
+ public:
+ using CommunicationException::CommunicationException;
+ NotConnectedException(): CommunicationException("Trezor not connected"){}
+ };
+
+ class DeviceNotResponsiveException : public CommunicationException {
+ public:
+ using CommunicationException::CommunicationException;
+ DeviceNotResponsiveException(): CommunicationException("Trezor does not respond to ping"){}
+ };
+
+ class DeviceAcquireException : public CommunicationException {
+ public:
+ using CommunicationException::CommunicationException;
+ DeviceAcquireException(): CommunicationException("Trezor could not be acquired"){}
+ };
+
+ class SessionException: public CommunicationException {
+ public:
+ using CommunicationException::CommunicationException;
+ SessionException(): CommunicationException("Trezor session expired"){}
+ };
+
+ class TimeoutException: public CommunicationException {
+ public:
+ using CommunicationException::CommunicationException;
+ TimeoutException(): CommunicationException("Trezor communication timeout"){}
+ };
+
+ class ProtocolException: public CommunicationException {
+ public:
+ using CommunicationException::CommunicationException;
+ ProtocolException(): CommunicationException("Trezor protocol error"){}
+ };
+
+ // Communication protocol namespace
+ // Separated to distinguish between client and Trezor side exceptions.
+namespace proto {
+
+ class SecurityException : public ProtocolException {
+ public:
+ using ProtocolException::ProtocolException;
+ SecurityException(): ProtocolException("Security assertion violated in the protocol"){}
+ };
+
+ class FailureException : public ProtocolException {
+ private:
+ boost::optional<uint32_t> code;
+ boost::optional<std::string> message;
+ public:
+ using ProtocolException::ProtocolException;
+ FailureException(): ProtocolException("Trezor returned failure"){}
+ FailureException(boost::optional<uint32_t> code,
+ boost::optional<std::string> message)
+ : code(code), message(message) {
+ reason = "Trezor returned failure: code="
+ + (code ? std::to_string(code.get()) : "")
+ + ", message=" + (message ? message.get() : "");
+ };
+ };
+
+ class UnexpectedMessageException : public FailureException {
+ public:
+ using FailureException::FailureException;
+ UnexpectedMessageException(): FailureException("Trezor claims unexpected message received"){}
+ };
+
+ class CancelledException : public FailureException {
+ public:
+ using FailureException::FailureException;
+ CancelledException(): FailureException("Trezor returned: cancelled operation"){}
+ };
+
+ class PinExpectedException : public FailureException {
+ public:
+ using FailureException::FailureException;
+ PinExpectedException(): FailureException("Trezor claims PIN is expected"){}
+ };
+
+ class InvalidPinException : public FailureException {
+ public:
+ using FailureException::FailureException;
+ InvalidPinException(): FailureException("Trezor claims PIN is invalid"){}
+ };
+
+ class NotEnoughFundsException : public FailureException {
+ public:
+ using FailureException::FailureException;
+ NotEnoughFundsException(): FailureException("Trezor claims not enough funds"){}
+ };
+
+ class NotInitializedException : public FailureException {
+ public:
+ using FailureException::FailureException;
+ NotInitializedException(): FailureException("Trezor claims not initialized"){}
+ };
+
+ class FirmwareErrorException : public FailureException {
+ public:
+ using FailureException::FailureException;
+ FirmwareErrorException(): FailureException("Trezor returned firmware error"){}
+ };
+
+}
+}
+}
+}
+#endif //MONERO_EXCEPTIONS_H
diff --git a/src/device_trezor/trezor/messages/.gitignore b/src/device_trezor/trezor/messages/.gitignore
new file mode 100644
index 000000000..32f7a77e5
--- /dev/null
+++ b/src/device_trezor/trezor/messages/.gitignore
@@ -0,0 +1,2 @@
+# protobuf generated code
+*
diff --git a/src/device_trezor/trezor/messages_map.cpp b/src/device_trezor/trezor/messages_map.cpp
new file mode 100644
index 000000000..b0d1aa254
--- /dev/null
+++ b/src/device_trezor/trezor/messages_map.cpp
@@ -0,0 +1,125 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include "messages_map.hpp"
+#include "messages/messages.pb.h"
+#include "messages/messages-common.pb.h"
+#include "messages/messages-management.pb.h"
+#include "messages/messages-monero.pb.h"
+
+using namespace std;
+using namespace hw::trezor;
+
+namespace hw{
+namespace trezor
+{
+
+ const char * TYPE_PREFIX = "MessageType_";
+ const char * PACKAGES[] = {
+ "hw.trezor.messages.",
+ "hw.trezor.messages.common.",
+ "hw.trezor.messages.management.",
+ "hw.trezor.messages.monero."
+ };
+
+ google::protobuf::Message * MessageMapper::get_message(int wire_number) {
+ return MessageMapper::get_message(static_cast<messages::MessageType>(wire_number));
+ }
+
+ google::protobuf::Message * MessageMapper::get_message(messages::MessageType wire_number) {
+ const string &messageTypeName = hw::trezor::messages::MessageType_Name(wire_number);
+ if (messageTypeName.empty()) {
+ throw exc::EncodingException(std::string("Message descriptor not found: ") + std::to_string(wire_number));
+ }
+
+ string messageName = messageTypeName.substr(strlen(TYPE_PREFIX));
+ return MessageMapper::get_message(messageName);
+ }
+
+ google::protobuf::Message * MessageMapper::get_message(const std::string & msg_name) {
+ // Each package instantiation so lookup works
+ hw::trezor::messages::common::Success::default_instance();
+ hw::trezor::messages::management::Cancel::default_instance();
+ hw::trezor::messages::monero::MoneroGetAddress::default_instance();
+
+ google::protobuf::Descriptor const * desc = nullptr;
+ for(const string &text : PACKAGES){
+ desc = google::protobuf::DescriptorPool::generated_pool()
+ ->FindMessageTypeByName(text + msg_name);
+ if (desc != nullptr){
+ break;
+ }
+ }
+
+ if (desc == nullptr){
+ throw exc::EncodingException(std::string("Message not found: ") + msg_name);
+ }
+
+ google::protobuf::Message* message =
+ google::protobuf::MessageFactory::generated_factory()
+ ->GetPrototype(desc)->New();
+
+ return message;
+
+// // CODEGEN way, fast
+// switch(wire_number){
+// case 501:
+// return new messages::monero::MoneroTransactionSignRequest();
+// default:
+// throw std::runtime_error("not implemented");
+// }
+//
+// // CODEGEN message -> number: specification
+// // messages::MessageType get_message_wire_number(const messages::monero::MoneroTransactionSignRequest * msg) { return 501; }
+// // messages::MessageType get_message_wire_number(const messages::management::ping * msg)
+//
+ }
+
+ messages::MessageType MessageMapper::get_message_wire_number(const google::protobuf::Message * msg){
+ return MessageMapper::get_message_wire_number(msg->GetDescriptor()->name());
+ }
+
+ messages::MessageType MessageMapper::get_message_wire_number(const google::protobuf::Message & msg){
+ return MessageMapper::get_message_wire_number(msg.GetDescriptor()->name());
+ }
+
+ messages::MessageType MessageMapper::get_message_wire_number(const std::string & msg_name){
+ string enumMessageName = std::string(TYPE_PREFIX) + msg_name;
+
+ messages::MessageType res;
+ bool r = hw::trezor::messages::MessageType_Parse(enumMessageName, &res);
+ if (!r){
+ throw exc::EncodingException(std::string("Message ") + msg_name + " not found");
+ }
+
+ return res;
+ }
+
+}
+}
diff --git a/src/device_trezor/trezor/messages_map.hpp b/src/device_trezor/trezor/messages_map.hpp
new file mode 100644
index 000000000..f61338f09
--- /dev/null
+++ b/src/device_trezor/trezor/messages_map.hpp
@@ -0,0 +1,94 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef MONERO_MESSAGES_MAP_H
+#define MONERO_MESSAGES_MAP_H
+
+#include <string>
+#include <type_traits>
+#include <memory>
+#include "exceptions.hpp"
+
+#include "trezor_defs.hpp"
+
+#include <google/protobuf/stubs/common.h>
+#include <google/protobuf/generated_message_util.h>
+#include <google/protobuf/repeated_field.h>
+#include <google/protobuf/extension_set.h>
+#include <google/protobuf/generated_enum_reflection.h>
+#include "google/protobuf/descriptor.pb.h"
+
+#include "messages/messages.pb.h"
+
+namespace hw {
+namespace trezor {
+
+ class MessageMapper{
+ public:
+ MessageMapper() {
+
+ }
+
+ static ::google::protobuf::Message * get_message(int wire_number);
+ static ::google::protobuf::Message * get_message(messages::MessageType);
+ static ::google::protobuf::Message * get_message(const std::string & msg_name);
+ static messages::MessageType get_message_wire_number(const google::protobuf::Message * msg);
+ static messages::MessageType get_message_wire_number(const google::protobuf::Message & msg);
+ static messages::MessageType get_message_wire_number(const std::string & msg_name);
+
+ template<class t_message>
+ static messages::MessageType get_message_wire_number() {
+ BOOST_STATIC_ASSERT(boost::is_base_of<google::protobuf::Message, t_message>::value);
+ return get_message_wire_number(t_message::default_instance().GetDescriptor()->name());
+ }
+ };
+
+ template<class t_message>
+ std::shared_ptr<t_message> message_ptr_retype(std::shared_ptr<google::protobuf::Message> & in){
+ BOOST_STATIC_ASSERT(boost::is_base_of<google::protobuf::Message, t_message>::value);
+ if (!in){
+ return nullptr;
+ }
+
+ return std::dynamic_pointer_cast<t_message>(in);
+ }
+
+ template<class t_message>
+ std::shared_ptr<t_message> message_ptr_retype_static(std::shared_ptr<google::protobuf::Message> & in){
+ BOOST_STATIC_ASSERT(boost::is_base_of<google::protobuf::Message, t_message>::value);
+ if (!in){
+ return nullptr;
+ }
+
+ return std::static_pointer_cast<t_message>(in);
+ }
+
+}}
+
+#endif //MONERO_MESSAGES_MAP_H
diff --git a/src/device_trezor/trezor/protocol.cpp b/src/device_trezor/trezor/protocol.cpp
new file mode 100644
index 000000000..c4a92426c
--- /dev/null
+++ b/src/device_trezor/trezor/protocol.cpp
@@ -0,0 +1,891 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include "protocol.hpp"
+#include <unordered_map>
+#include <set>
+#include <utility>
+#include <boost/endian/conversion.hpp>
+#include <common/apply_permutation.h>
+#include <ringct/rctSigs.h>
+#include <ringct/bulletproofs.h>
+#include "cryptonote_config.h"
+#include <sodium.h>
+#include <sodium/crypto_verify_32.h>
+#include <sodium/crypto_aead_chacha20poly1305.h>
+
+namespace hw{
+namespace trezor{
+namespace protocol{
+
+ std::string key_to_string(const ::crypto::ec_point & key){
+ return std::string(key.data, sizeof(key.data));
+ }
+
+ std::string key_to_string(const ::crypto::ec_scalar & key){
+ return std::string(key.data, sizeof(key.data));
+ }
+
+ std::string key_to_string(const ::crypto::hash & key){
+ return std::string(key.data, sizeof(key.data));
+ }
+
+ std::string key_to_string(const ::rct::key & key){
+ return std::string(reinterpret_cast<const char*>(key.bytes), sizeof(key.bytes));
+ }
+
+ void string_to_key(::crypto::ec_scalar & key, const std::string & str){
+ if (str.size() != sizeof(key.data)){
+ throw std::invalid_argument(std::string("Key has to have ") + std::to_string(sizeof(key.data)) + " B");
+ }
+ memcpy(key.data, str.data(), sizeof(key.data));
+ }
+
+ void string_to_key(::crypto::ec_point & key, const std::string & str){
+ if (str.size() != sizeof(key.data)){
+ throw std::invalid_argument(std::string("Key has to have ") + std::to_string(sizeof(key.data)) + " B");
+ }
+ memcpy(key.data, str.data(), sizeof(key.data));
+ }
+
+ void string_to_key(::rct::key & key, const std::string & str){
+ if (str.size() != sizeof(key.bytes)){
+ throw std::invalid_argument(std::string("Key has to have ") + std::to_string(sizeof(key.bytes)) + " B");
+ }
+ memcpy(key.bytes, str.data(), sizeof(key.bytes));
+ }
+
+namespace crypto {
+namespace chacha {
+
+ void decrypt(const void* ciphertext, size_t length, const uint8_t* key, const uint8_t* iv, char* plaintext){
+ if (length < 16){
+ throw std::invalid_argument("Ciphertext length too small");
+ }
+
+ unsigned long long int cip_len = length;
+ auto r = crypto_aead_chacha20poly1305_ietf_decrypt(
+ reinterpret_cast<unsigned char *>(plaintext), &cip_len, nullptr,
+ static_cast<const unsigned char *>(ciphertext), length, nullptr, 0, iv, key);
+
+ if (r != 0){
+ throw exc::Poly1305TagInvalid();
+ }
+ }
+
+}
+}
+
+
+// Cold Key image sync
+namespace ki {
+
+ bool key_image_data(wallet_shim * wallet,
+ const std::vector<tools::wallet2::transfer_details> & transfers,
+ std::vector<MoneroTransferDetails> & res)
+ {
+ for(auto & td : transfers){
+ ::crypto::public_key tx_pub_key = wallet->get_tx_pub_key_from_received_outs(td);
+ const std::vector<::crypto::public_key> additional_tx_pub_keys = cryptonote::get_additional_tx_pub_keys_from_extra(td.m_tx);
+
+ res.emplace_back();
+ auto & cres = res.back();
+
+ cres.set_out_key(key_to_string(boost::get<cryptonote::txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key));
+ cres.set_tx_pub_key(key_to_string(tx_pub_key));
+ cres.set_internal_output_index(td.m_internal_output_index);
+ for(auto & aux : additional_tx_pub_keys){
+ cres.add_additional_tx_pub_keys(key_to_string(aux));
+ }
+ }
+
+ return true;
+ }
+
+ std::string compute_hash(const MoneroTransferDetails & rr){
+ KECCAK_CTX kck;
+ uint8_t md[32];
+
+ CHECK_AND_ASSERT_THROW_MES(rr.out_key().size() == 32, "Invalid out_key size");
+ CHECK_AND_ASSERT_THROW_MES(rr.tx_pub_key().size() == 32, "Invalid tx_pub_key size");
+
+ keccak_init(&kck);
+ keccak_update(&kck, reinterpret_cast<const uint8_t *>(rr.out_key().data()), 32);
+ keccak_update(&kck, reinterpret_cast<const uint8_t *>(rr.tx_pub_key().data()), 32);
+ for (const auto &aux : rr.additional_tx_pub_keys()){
+ CHECK_AND_ASSERT_THROW_MES(aux.size() == 32, "Invalid aux size");
+ keccak_update(&kck, reinterpret_cast<const uint8_t *>(aux.data()), 32);
+ }
+
+ auto index_serialized = tools::get_varint_data(rr.internal_output_index());
+ keccak_update(&kck, reinterpret_cast<const uint8_t *>(index_serialized.data()), index_serialized.size());
+ keccak_finish(&kck, md);
+ return std::string(reinterpret_cast<const char*>(md), sizeof(md));
+ }
+
+ void generate_commitment(std::vector<MoneroTransferDetails> & mtds,
+ const std::vector<tools::wallet2::transfer_details> & transfers,
+ std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req)
+ {
+ req = std::make_shared<messages::monero::MoneroKeyImageExportInitRequest>();
+
+ KECCAK_CTX kck;
+ uint8_t final_hash[32];
+ keccak_init(&kck);
+
+ for(auto &cur : mtds){
+ auto hash = compute_hash(cur);
+ keccak_update(&kck, reinterpret_cast<const uint8_t *>(hash.data()), hash.size());
+ }
+ keccak_finish(&kck, final_hash);
+
+ req = std::make_shared<messages::monero::MoneroKeyImageExportInitRequest>();
+ req->set_hash(std::string(reinterpret_cast<const char*>(final_hash), 32));
+ req->set_num(transfers.size());
+
+ std::unordered_map<uint32_t, std::set<uint32_t>> sub_indices;
+ for (auto &cur : transfers){
+ auto search = sub_indices.emplace(cur.m_subaddr_index.major, std::set<uint32_t>());
+ auto & st = search.first->second;
+ st.insert(cur.m_subaddr_index.minor);
+ }
+
+ for (auto& x: sub_indices){
+ auto subs = req->add_subs();
+ subs->set_account(x.first);
+ for(auto minor : x.second){
+ subs->add_minor_indices(minor);
+ }
+ }
+ }
+
+}
+
+// Cold transaction signing
+namespace tx {
+
+ void translate_address(MoneroAccountPublicAddress * dst, const cryptonote::account_public_address * src){
+ dst->set_view_public_key(key_to_string(src->m_view_public_key));
+ dst->set_spend_public_key(key_to_string(src->m_spend_public_key));
+ }
+
+ void translate_dst_entry(MoneroTransactionDestinationEntry * dst, const cryptonote::tx_destination_entry * src){
+ dst->set_amount(src->amount);
+ dst->set_is_subaddress(src->is_subaddress);
+ translate_address(dst->mutable_addr(), &(src->addr));
+ }
+
+ void translate_src_entry(MoneroTransactionSourceEntry * dst, const cryptonote::tx_source_entry * src){
+ for(auto & cur : src->outputs){
+ auto out = dst->add_outputs();
+ out->set_idx(cur.first);
+ translate_rct_key(out->mutable_key(), &(cur.second));
+ }
+
+ dst->set_real_output(src->real_output);
+ dst->set_real_out_tx_key(key_to_string(src->real_out_tx_key));
+ for(auto & cur : src->real_out_additional_tx_keys){
+ dst->add_real_out_additional_tx_keys(key_to_string(cur));
+ }
+
+ dst->set_real_output_in_tx_index(src->real_output_in_tx_index);
+ dst->set_amount(src->amount);
+ dst->set_rct(src->rct);
+ dst->set_mask(key_to_string(src->mask));
+ translate_klrki(dst->mutable_multisig_klrki(), &(src->multisig_kLRki));
+ }
+
+ void translate_klrki(MoneroMultisigKLRki * dst, const rct::multisig_kLRki * src){
+ dst->set_k(key_to_string(src->k));
+ dst->set_l(key_to_string(src->L));
+ dst->set_r(key_to_string(src->R));
+ dst->set_ki(key_to_string(src->ki));
+ }
+
+ void translate_rct_key(MoneroRctKey * dst, const rct::ctkey * src){
+ dst->set_dest(key_to_string(src->dest));
+ dst->set_commitment(key_to_string(src->mask));
+ }
+
+ std::string hash_addr(const MoneroAccountPublicAddress * addr, boost::optional<uint64_t> amount, boost::optional<bool> is_subaddr){
+ return hash_addr(addr->spend_public_key(), addr->view_public_key(), amount, is_subaddr);
+ }
+
+ std::string hash_addr(const std::string & spend_key, const std::string & view_key, boost::optional<uint64_t> amount, boost::optional<bool> is_subaddr){
+ ::crypto::public_key spend{}, view{};
+ if (spend_key.size() != 32 || view_key.size() != 32){
+ throw std::invalid_argument("Public keys have invalid sizes");
+ }
+
+ memcpy(spend.data, spend_key.data(), 32);
+ memcpy(view.data, view_key.data(), 32);
+ return hash_addr(&spend, &view, amount, is_subaddr);
+ }
+
+ std::string hash_addr(const ::crypto::public_key * spend_key, const ::crypto::public_key * view_key, boost::optional<uint64_t> amount, boost::optional<bool> is_subaddr){
+ char buff[64+8+1];
+ size_t offset = 0;
+
+ memcpy(buff + offset, spend_key->data, 32); offset += 32;
+ memcpy(buff + offset, view_key->data, 32); offset += 32;
+
+ if (amount){
+ memcpy(buff + offset, (uint8_t*) &(amount.get()), sizeof(amount.get())); offset += sizeof(amount.get());
+ }
+
+ if (is_subaddr){
+ buff[offset] = is_subaddr.get();
+ offset += 1;
+ }
+
+ return std::string(buff, offset);
+ }
+
+ TData::TData() {
+ in_memory = false;
+ rsig_type = 0;
+ cur_input_idx = 0;
+ cur_output_idx = 0;
+ cur_batch_idx = 0;
+ cur_output_in_batch_idx = 0;
+ }
+
+ Signer::Signer(wallet_shim *wallet2, const unsigned_tx_set * unsigned_tx, size_t tx_idx, hw::tx_aux_data * aux_data) {
+ m_wallet2 = wallet2;
+ m_unsigned_tx = unsigned_tx;
+ m_aux_data = aux_data;
+ m_tx_idx = tx_idx;
+ m_ct.tx_data = cur_tx();
+ m_multisig = false;
+ }
+
+ void Signer::extract_payment_id(){
+ const std::vector<uint8_t>& tx_extra = cur_tx().extra;
+ m_ct.tsx_data.set_payment_id("");
+
+ std::vector<cryptonote::tx_extra_field> tx_extra_fields;
+ cryptonote::parse_tx_extra(tx_extra, tx_extra_fields); // ok if partially parsed
+ cryptonote::tx_extra_nonce extra_nonce;
+
+ ::crypto::hash payment_id{};
+ if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce))
+ {
+ ::crypto::hash8 payment_id8{};
+ if(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8))
+ {
+ m_ct.tsx_data.set_payment_id(std::string(payment_id8.data, 8));
+ }
+ else if (cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id))
+ {
+ m_ct.tsx_data.set_payment_id(std::string(payment_id.data, 32));
+ }
+ }
+ }
+
+ static unsigned get_rsig_type(bool use_bulletproof, size_t num_outputs){
+ if (!use_bulletproof){
+ return rct::RangeProofBorromean;
+ } else if (num_outputs > BULLETPROOF_MAX_OUTPUTS){
+ return rct::RangeProofMultiOutputBulletproof;
+ } else {
+ return rct::RangeProofPaddedBulletproof;
+ }
+ }
+
+ static void generate_rsig_batch_sizes(std::vector<uint64_t> &batches, unsigned rsig_type, size_t num_outputs){
+ size_t amount_batched = 0;
+
+ while(amount_batched < num_outputs){
+ if (rsig_type == rct::RangeProofBorromean || rsig_type == rct::RangeProofBulletproof) {
+ batches.push_back(1);
+ amount_batched += 1;
+
+ } else if (rsig_type == rct::RangeProofPaddedBulletproof){
+ if (num_outputs > BULLETPROOF_MAX_OUTPUTS){
+ throw std::invalid_argument("BP padded can support only BULLETPROOF_MAX_OUTPUTS statements");
+ }
+ batches.push_back(num_outputs);
+ amount_batched += num_outputs;
+
+ } else if (rsig_type == rct::RangeProofMultiOutputBulletproof){
+ size_t batch_size = 1;
+ while (batch_size * 2 + amount_batched <= num_outputs && batch_size * 2 <= BULLETPROOF_MAX_OUTPUTS){
+ batch_size *= 2;
+ }
+ batch_size = std::min(batch_size, num_outputs - amount_batched);
+ batches.push_back(batch_size);
+ amount_batched += batch_size;
+
+ } else {
+ throw std::invalid_argument("Unknown rsig type");
+ }
+ }
+ }
+
+ void Signer::compute_integrated_indices(TsxData * tsx_data){
+ if (m_aux_data == nullptr || m_aux_data->tx_recipients.empty()){
+ return;
+ }
+
+ auto & chg = tsx_data->change_dts();
+ std::string change_hash = hash_addr(&chg.addr(), chg.amount(), chg.is_subaddress());
+
+ std::vector<uint32_t> integrated_indices;
+ std::set<std::string> integrated_hashes;
+ for (auto & cur : m_aux_data->tx_recipients){
+ if (!cur.has_payment_id){
+ continue;
+ }
+ integrated_hashes.emplace(hash_addr(&cur.address.m_spend_public_key, &cur.address.m_view_public_key));
+ }
+
+ ssize_t idx = -1;
+ for (auto & cur : tsx_data->outputs()){
+ idx += 1;
+
+ std::string c_hash = hash_addr(&cur.addr(), cur.amount(), cur.is_subaddress());
+ if (c_hash == change_hash || cur.is_subaddress()){
+ continue;
+ }
+
+ c_hash = hash_addr(&cur.addr());
+ if (integrated_hashes.find(c_hash) != integrated_hashes.end()){
+ integrated_indices.push_back((uint32_t)idx);
+ }
+ }
+
+ if (!integrated_indices.empty()){
+ assign_to_repeatable(tsx_data->mutable_integrated_indices(), integrated_indices.begin(), integrated_indices.end());
+ }
+ }
+
+ std::shared_ptr<messages::monero::MoneroTransactionInitRequest> Signer::step_init(){
+ // extract payment ID from construction data
+ auto & tsx_data = m_ct.tsx_data;
+ auto & tx = cur_tx();
+
+ m_ct.tx.version = 2;
+ m_ct.tx.unlock_time = tx.unlock_time;
+
+ tsx_data.set_version(1);
+ tsx_data.set_unlock_time(tx.unlock_time);
+ tsx_data.set_num_inputs(static_cast<google::protobuf::uint32>(tx.sources.size()));
+ tsx_data.set_mixin(static_cast<google::protobuf::uint32>(tx.sources[0].outputs.size() - 1));
+ tsx_data.set_account(tx.subaddr_account);
+ assign_to_repeatable(tsx_data.mutable_minor_indices(), tx.subaddr_indices.begin(), tx.subaddr_indices.end());
+
+ // Rsig decision
+ auto rsig_data = tsx_data.mutable_rsig_data();
+ m_ct.rsig_type = get_rsig_type(tx.use_bulletproofs, tx.splitted_dsts.size());
+ rsig_data->set_rsig_type(m_ct.rsig_type);
+
+ generate_rsig_batch_sizes(m_ct.grouping_vct, m_ct.rsig_type, tx.splitted_dsts.size());
+ assign_to_repeatable(rsig_data->mutable_grouping(), m_ct.grouping_vct.begin(), m_ct.grouping_vct.end());
+
+ translate_dst_entry(tsx_data.mutable_change_dts(), &(tx.change_dts));
+ for(auto & cur : tx.splitted_dsts){
+ auto dst = tsx_data.mutable_outputs()->Add();
+ translate_dst_entry(dst, &cur);
+ }
+
+ compute_integrated_indices(&tsx_data);
+
+ int64_t fee = 0;
+ for(auto & cur_in : tx.sources){
+ fee += cur_in.amount;
+ }
+ for(auto & cur_out : tx.splitted_dsts){
+ fee -= cur_out.amount;
+ }
+ if (fee < 0){
+ throw std::invalid_argument("Fee cannot be negative");
+ }
+
+ tsx_data.set_fee(static_cast<google::protobuf::uint64>(fee));
+ this->extract_payment_id();
+
+ auto init_req = std::make_shared<messages::monero::MoneroTransactionInitRequest>();
+ init_req->set_version(0);
+ init_req->mutable_tsx_data()->CopyFrom(tsx_data);
+ return init_req;
+ }
+
+ void Signer::step_init_ack(std::shared_ptr<const messages::monero::MoneroTransactionInitAck> ack){
+ m_ct.in_memory = false;
+ if (ack->has_rsig_data()){
+ m_ct.rsig_param = std::make_shared<MoneroRsigData>(ack->rsig_data());
+ }
+
+ assign_from_repeatable(&(m_ct.tx_out_entr_hmacs), ack->hmacs().begin(), ack->hmacs().end());
+ }
+
+ std::shared_ptr<messages::monero::MoneroTransactionSetInputRequest> Signer::step_set_input(size_t idx){
+ CHECK_AND_ASSERT_THROW_MES(idx < cur_tx().sources.size(), "Invalid source index");
+ m_ct.cur_input_idx = idx;
+ auto res = std::make_shared<messages::monero::MoneroTransactionSetInputRequest>();
+ translate_src_entry(res->mutable_src_entr(), &(cur_tx().sources[idx]));
+ return res;
+ }
+
+ void Signer::step_set_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetInputAck> ack){
+ auto & vini_str = ack->vini();
+
+ cryptonote::txin_v vini;
+ if (!cn_deserialize(vini_str.data(), vini_str.size(), vini)){
+ throw exc::ProtocolException("Cannot deserialize vin[i]");
+ }
+
+ m_ct.tx.vin.emplace_back(vini);
+ m_ct.tx_in_hmacs.push_back(ack->vini_hmac());
+ m_ct.pseudo_outs.push_back(ack->pseudo_out());
+ m_ct.pseudo_outs_hmac.push_back(ack->pseudo_out_hmac());
+ m_ct.alphas.push_back(ack->pseudo_out_alpha());
+ m_ct.spend_encs.push_back(ack->spend_key());
+ }
+
+ void Signer::sort_ki(){
+ const size_t input_size = cur_tx().sources.size();
+
+ m_ct.source_permutation.clear();
+ for (size_t n = 0; n < input_size; ++n){
+ m_ct.source_permutation.push_back(n);
+ }
+
+ CHECK_AND_ASSERT_THROW_MES(m_ct.tx.vin.size() == input_size, "Invalid vector size");
+ std::sort(m_ct.source_permutation.begin(), m_ct.source_permutation.end(), [&](const size_t i0, const size_t i1) {
+ const cryptonote::txin_to_key &tk0 = boost::get<cryptonote::txin_to_key>(m_ct.tx.vin[i0]);
+ const cryptonote::txin_to_key &tk1 = boost::get<cryptonote::txin_to_key>(m_ct.tx.vin[i1]);
+ return memcmp(&tk0.k_image, &tk1.k_image, sizeof(tk0.k_image)) > 0;
+ });
+
+ CHECK_AND_ASSERT_THROW_MES(m_ct.tx_in_hmacs.size() == input_size, "Invalid vector size");
+ CHECK_AND_ASSERT_THROW_MES(m_ct.pseudo_outs.size() == input_size, "Invalid vector size");
+ CHECK_AND_ASSERT_THROW_MES(m_ct.pseudo_outs_hmac.size() == input_size, "Invalid vector size");
+ CHECK_AND_ASSERT_THROW_MES(m_ct.alphas.size() == input_size, "Invalid vector size");
+ CHECK_AND_ASSERT_THROW_MES(m_ct.spend_encs.size() == input_size, "Invalid vector size");
+ CHECK_AND_ASSERT_THROW_MES(m_ct.tx_data.sources.size() == input_size, "Invalid vector size");
+
+ tools::apply_permutation(m_ct.source_permutation, [&](size_t i0, size_t i1){
+ std::swap(m_ct.tx.vin[i0], m_ct.tx.vin[i1]);
+ std::swap(m_ct.tx_in_hmacs[i0], m_ct.tx_in_hmacs[i1]);
+ std::swap(m_ct.pseudo_outs[i0], m_ct.pseudo_outs[i1]);
+ std::swap(m_ct.pseudo_outs_hmac[i0], m_ct.pseudo_outs_hmac[i1]);
+ std::swap(m_ct.alphas[i0], m_ct.alphas[i1]);
+ std::swap(m_ct.spend_encs[i0], m_ct.spend_encs[i1]);
+ std::swap(m_ct.tx_data.sources[i0], m_ct.tx_data.sources[i1]);
+ });
+ }
+
+ std::shared_ptr<messages::monero::MoneroTransactionInputsPermutationRequest> Signer::step_permutation(){
+ sort_ki();
+
+ if (in_memory()){
+ return nullptr;
+ }
+
+ auto res = std::make_shared<messages::monero::MoneroTransactionInputsPermutationRequest>();
+ assign_to_repeatable(res->mutable_perm(), m_ct.source_permutation.begin(), m_ct.source_permutation.end());
+
+ return res;
+ }
+
+ void Signer::step_permutation_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputsPermutationAck> ack){
+ if (in_memory()){
+ return;
+ }
+ }
+
+ std::shared_ptr<messages::monero::MoneroTransactionInputViniRequest> Signer::step_set_vini_input(size_t idx){
+ if (in_memory()){
+ return nullptr;
+ }
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_data.sources.size(), "Invalid transaction index");
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx.vin.size(), "Invalid transaction index");
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_in_hmacs.size(), "Invalid transaction index");
+
+ m_ct.cur_input_idx = idx;
+ auto tx = m_ct.tx_data;
+ auto res = std::make_shared<messages::monero::MoneroTransactionInputViniRequest>();
+ auto & vini = m_ct.tx.vin[idx];
+ translate_src_entry(res->mutable_src_entr(), &(tx.sources[idx]));
+ res->set_vini(cryptonote::t_serializable_object_to_blob(vini));
+ res->set_vini_hmac(m_ct.tx_in_hmacs[idx]);
+ if (!in_memory()) {
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.pseudo_outs.size(), "Invalid transaction index");
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.pseudo_outs_hmac.size(), "Invalid transaction index");
+ res->set_pseudo_out(m_ct.pseudo_outs[idx]);
+ res->set_pseudo_out_hmac(m_ct.pseudo_outs_hmac[idx]);
+ }
+
+ return res;
+ }
+
+ void Signer::step_set_vini_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputViniAck> ack){
+ if (in_memory()){
+ return;
+ }
+ }
+
+ std::shared_ptr<messages::monero::MoneroTransactionAllInputsSetRequest> Signer::step_all_inputs_set(){
+ return std::make_shared<messages::monero::MoneroTransactionAllInputsSetRequest>();
+ }
+
+ void Signer::step_all_inputs_set_ack(std::shared_ptr<const messages::monero::MoneroTransactionAllInputsSetAck> ack){
+ if (is_offloading()){
+ // If offloading, expect rsig configuration.
+ if (!ack->has_rsig_data()){
+ throw exc::ProtocolException("Rsig offloading requires rsig param");
+ }
+
+ auto & rsig_data = ack->rsig_data();
+ if (!rsig_data.has_mask()){
+ throw exc::ProtocolException("Gamma masks not present in offloaded version");
+ }
+
+ auto & mask = rsig_data.mask();
+ if (mask.size() != 32 * num_outputs()){
+ throw exc::ProtocolException("Invalid number of gamma masks");
+ }
+
+ m_ct.rsig_gamma.reserve(num_outputs());
+ for(size_t c=0; c < num_outputs(); ++c){
+ rct::key cmask{};
+ memcpy(cmask.bytes, mask.data() + c * 32, 32);
+ m_ct.rsig_gamma.emplace_back(cmask);
+ }
+ }
+ }
+
+ std::shared_ptr<messages::monero::MoneroTransactionSetOutputRequest> Signer::step_set_output(size_t idx){
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_data.splitted_dsts.size(), "Invalid transaction index");
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_out_entr_hmacs.size(), "Invalid transaction index");
+
+ m_ct.cur_output_idx = idx;
+ m_ct.cur_output_in_batch_idx += 1; // assumes sequential call to step_set_output()
+
+ auto res = std::make_shared<messages::monero::MoneroTransactionSetOutputRequest>();
+ auto & cur_dst = m_ct.tx_data.splitted_dsts[idx];
+ translate_dst_entry(res->mutable_dst_entr(), &cur_dst);
+ res->set_dst_entr_hmac(m_ct.tx_out_entr_hmacs[idx]);
+
+ // Range sig offloading to the host
+ if (!is_offloading()) {
+ return res;
+ }
+
+ CHECK_AND_ASSERT_THROW_MES(m_ct.cur_batch_idx < m_ct.grouping_vct.size(), "Invalid batch index");
+ if (m_ct.grouping_vct[m_ct.cur_batch_idx] > m_ct.cur_output_in_batch_idx) {
+ return res;
+ }
+
+ auto rsig_data = res->mutable_rsig_data();
+ auto batch_size = m_ct.grouping_vct[m_ct.cur_batch_idx];
+
+ if (!is_req_bulletproof()){
+ if (batch_size > 1){
+ throw std::invalid_argument("Borromean cannot batch outputs");
+ }
+
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.rsig_gamma.size(), "Invalid gamma index");
+ rct::key C{}, mask = m_ct.rsig_gamma[idx];
+ auto genRsig = rct::proveRange(C, mask, cur_dst.amount); // TODO: rsig with given mask
+ auto serRsig = cn_serialize(genRsig);
+ m_ct.tx_out_rsigs.emplace_back(genRsig);
+ rsig_data->set_rsig(serRsig);
+
+ } else {
+ std::vector<uint64_t> amounts;
+ rct::keyV masks;
+ CHECK_AND_ASSERT_THROW_MES(idx + 1 >= batch_size, "Invalid index for batching");
+
+ for(size_t i = 0; i < batch_size; ++i){
+ const size_t bidx = 1 + idx - batch_size + i;
+ CHECK_AND_ASSERT_THROW_MES(bidx < m_ct.tx_data.splitted_dsts.size(), "Invalid gamma index");
+ CHECK_AND_ASSERT_THROW_MES(bidx < m_ct.rsig_gamma.size(), "Invalid gamma index");
+
+ amounts.push_back(m_ct.tx_data.splitted_dsts[bidx].amount);
+ masks.push_back(m_ct.rsig_gamma[bidx]);
+ }
+
+ auto bp = bulletproof_PROVE(amounts, masks);
+ auto serRsig = cn_serialize(bp);
+ m_ct.tx_out_rsigs.emplace_back(bp);
+ rsig_data->set_rsig(serRsig);
+ }
+
+ return res;
+ }
+
+ void Signer::step_set_output_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetOutputAck> ack){
+ cryptonote::tx_out tx_out;
+ rct::rangeSig range_sig{};
+ rct::Bulletproof bproof{};
+ rct::ctkey out_pk{};
+ rct::ecdhTuple ecdh{};
+
+ bool has_rsig = false;
+ std::string rsig_buff;
+
+ if (ack->has_rsig_data()){
+ auto & rsig_data = ack->rsig_data();
+
+ if (rsig_data.has_rsig() && !rsig_data.rsig().empty()){
+ has_rsig = true;
+ rsig_buff = rsig_data.rsig();
+
+ } else if (rsig_data.rsig_parts_size() > 0){
+ has_rsig = true;
+ for (const auto &it : rsig_data.rsig_parts()) {
+ rsig_buff += it;
+ }
+ }
+ }
+
+ if (!cn_deserialize(ack->tx_out(), tx_out)){
+ throw exc::ProtocolException("Cannot deserialize vout[i]");
+ }
+
+ if (!cn_deserialize(ack->out_pk(), out_pk)){
+ throw exc::ProtocolException("Cannot deserialize out_pk");
+ }
+
+ if (!cn_deserialize(ack->ecdh_info(), ecdh)){
+ throw exc::ProtocolException("Cannot deserialize ecdhtuple");
+ }
+
+ if (has_rsig && !is_req_bulletproof() && !cn_deserialize(rsig_buff, range_sig)){
+ throw exc::ProtocolException("Cannot deserialize rangesig");
+ }
+
+ if (has_rsig && is_req_bulletproof() && !cn_deserialize(rsig_buff, bproof)){
+ throw exc::ProtocolException("Cannot deserialize bulletproof rangesig");
+ }
+
+ m_ct.tx.vout.emplace_back(tx_out);
+ m_ct.tx_out_hmacs.push_back(ack->vouti_hmac());
+ m_ct.tx_out_pk.emplace_back(out_pk);
+ m_ct.tx_out_ecdh.emplace_back(ecdh);
+
+ if (!has_rsig){
+ return;
+ }
+
+ if (is_req_bulletproof()){
+ CHECK_AND_ASSERT_THROW_MES(m_ct.cur_batch_idx < m_ct.grouping_vct.size(), "Invalid batch index");
+ auto batch_size = m_ct.grouping_vct[m_ct.cur_batch_idx];
+ for (size_t i = 0; i < batch_size; ++i){
+ const size_t bidx = 1 + m_ct.cur_output_idx - batch_size + i;
+ CHECK_AND_ASSERT_THROW_MES(bidx < m_ct.tx_out_pk.size(), "Invalid out index");
+
+ rct::key commitment = m_ct.tx_out_pk[bidx].mask;
+ commitment = rct::scalarmultKey(commitment, rct::INV_EIGHT);
+ bproof.V.push_back(commitment);
+ }
+
+ m_ct.tx_out_rsigs.emplace_back(bproof);
+ if (!rct::bulletproof_VERIFY(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs.back()))) {
+ throw exc::ProtocolException("Returned range signature is invalid");
+ }
+
+ } else {
+ m_ct.tx_out_rsigs.emplace_back(range_sig);
+
+ if (!rct::verRange(out_pk.mask, boost::get<rct::rangeSig>(m_ct.tx_out_rsigs.back()))) {
+ throw exc::ProtocolException("Returned range signature is invalid");
+ }
+ }
+
+ m_ct.cur_batch_idx += 1;
+ m_ct.cur_output_in_batch_idx = 0;
+ }
+
+ std::shared_ptr<messages::monero::MoneroTransactionAllOutSetRequest> Signer::step_all_outs_set(){
+ return std::make_shared<messages::monero::MoneroTransactionAllOutSetRequest>();
+ }
+
+ void Signer::step_all_outs_set_ack(std::shared_ptr<const messages::monero::MoneroTransactionAllOutSetAck> ack, hw::device &hwdev){
+ m_ct.rv = std::make_shared<rct::rctSig>();
+ m_ct.rv->txnFee = ack->rv().txn_fee();
+ m_ct.rv->type = static_cast<uint8_t>(ack->rv().rv_type());
+ string_to_key(m_ct.rv->message, ack->rv().message());
+
+ // Extra copy
+ m_ct.tx.extra.clear();
+ auto extra = ack->extra();
+ auto extra_data = extra.data();
+ m_ct.tx.extra.reserve(extra.size());
+ for(size_t i = 0; i < extra.size(); ++i){
+ m_ct.tx.extra.push_back(static_cast<uint8_t>(extra_data[i]));
+ }
+
+ ::crypto::hash tx_prefix_hash{};
+ cryptonote::get_transaction_prefix_hash(m_ct.tx, tx_prefix_hash);
+ m_ct.tx_prefix_hash = key_to_string(tx_prefix_hash);
+ if (crypto_verify_32(reinterpret_cast<const unsigned char *>(tx_prefix_hash.data),
+ reinterpret_cast<const unsigned char *>(ack->tx_prefix_hash().data()))){
+ throw exc::proto::SecurityException("Transaction prefix has does not match to the computed value");
+ }
+
+ // RctSig
+ auto num_sources = m_ct.tx_data.sources.size();
+ if (is_simple() || is_req_bulletproof()){
+ auto dst = &m_ct.rv->pseudoOuts;
+ if (is_bulletproof()){
+ dst = &m_ct.rv->p.pseudoOuts;
+ }
+
+ dst->clear();
+ for (const auto &pseudo_out : m_ct.pseudo_outs) {
+ dst->emplace_back();
+ string_to_key(dst->back(), pseudo_out);
+ }
+
+ m_ct.rv->mixRing.resize(num_sources);
+ } else {
+ m_ct.rv->mixRing.resize(m_ct.tsx_data.mixin());
+ m_ct.rv->mixRing[0].resize(num_sources);
+ }
+
+ CHECK_AND_ASSERT_THROW_MES(m_ct.tx_out_pk.size() == m_ct.tx_out_ecdh.size(), "Invalid vector sizes");
+ for(size_t i = 0; i < m_ct.tx_out_ecdh.size(); ++i){
+ m_ct.rv->outPk.push_back(m_ct.tx_out_pk[i]);
+ m_ct.rv->ecdhInfo.push_back(m_ct.tx_out_ecdh[i]);
+ }
+
+ for(size_t i = 0; i < m_ct.tx_out_rsigs.size(); ++i){
+ if (is_bulletproof()){
+ m_ct.rv->p.bulletproofs.push_back(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs[i]));
+ } else {
+ m_ct.rv->p.rangeSigs.push_back(boost::get<rct::rangeSig>(m_ct.tx_out_rsigs[i]));
+ }
+ }
+
+ rct::key hash_computed = rct::get_pre_mlsag_hash(*(m_ct.rv), hwdev);
+ auto & hash = ack->full_message_hash();
+
+ if (hash.size() != 32){
+ throw exc::ProtocolException("Returned mlsag hash has invalid size");
+ }
+
+ if (crypto_verify_32(reinterpret_cast<const unsigned char *>(hash_computed.bytes),
+ reinterpret_cast<const unsigned char *>(hash.data()))){
+ throw exc::proto::SecurityException("Computed MLSAG does not match");
+ }
+ }
+
+ std::shared_ptr<messages::monero::MoneroTransactionSignInputRequest> Signer::step_sign_input(size_t idx){
+ m_ct.cur_input_idx = idx;
+
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_data.sources.size(), "Invalid transaction index");
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx.vin.size(), "Invalid transaction index");
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_in_hmacs.size(), "Invalid transaction index");
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.alphas.size(), "Invalid transaction index");
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.spend_encs.size(), "Invalid transaction index");
+
+ auto res = std::make_shared<messages::monero::MoneroTransactionSignInputRequest>();
+ translate_src_entry(res->mutable_src_entr(), &(m_ct.tx_data.sources[idx]));
+ res->set_vini(cryptonote::t_serializable_object_to_blob(m_ct.tx.vin[idx]));
+ res->set_vini_hmac(m_ct.tx_in_hmacs[idx]);
+ res->set_pseudo_out_alpha(m_ct.alphas[idx]);
+ res->set_spend_key(m_ct.spend_encs[idx]);
+ if (!in_memory()){
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.pseudo_outs.size(), "Invalid transaction index");
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.pseudo_outs_hmac.size(), "Invalid transaction index");
+ res->set_pseudo_out(m_ct.pseudo_outs[idx]);
+ res->set_pseudo_out_hmac(m_ct.pseudo_outs_hmac[idx]);
+ }
+ return res;
+ }
+
+ void Signer::step_sign_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionSignInputAck> ack){
+ rct::mgSig mg;
+ if (!cn_deserialize(ack->signature(), mg)){
+ throw exc::ProtocolException("Cannot deserialize mg[i]");
+ }
+
+ m_ct.rv->p.MGs.push_back(mg);
+ }
+
+ std::shared_ptr<messages::monero::MoneroTransactionFinalRequest> Signer::step_final(){
+ m_ct.tx.rct_signatures = *(m_ct.rv);
+ return std::make_shared<messages::monero::MoneroTransactionFinalRequest>();
+ }
+
+ void Signer::step_final_ack(std::shared_ptr<const messages::monero::MoneroTransactionFinalAck> ack){
+ if (m_multisig){
+ auto & cout_key = ack->cout_key();
+ for(auto & cur : m_ct.couts){
+ if (cur.size() != 12 + 32){
+ throw std::invalid_argument("Encrypted cout has invalid length");
+ }
+
+ char buff[32];
+ auto data = cur.data();
+
+ crypto::chacha::decrypt(data + 12, 32, reinterpret_cast<const uint8_t *>(cout_key.data()), reinterpret_cast<const uint8_t *>(data), buff);
+ m_ct.couts_dec.emplace_back(buff, 32);
+ }
+ }
+
+ m_ct.enc_salt1 = ack->salt();
+ m_ct.enc_salt2 = ack->rand_mult();
+ m_ct.enc_keys = ack->tx_enc_keys();
+ }
+
+ std::string Signer::store_tx_aux_info(){
+ rapidjson::StringBuffer sb;
+ rapidjson::Writer<rapidjson::StringBuffer> writer(sb);
+
+ rapidjson::Document json;
+ json.SetObject();
+
+ rapidjson::Value valueS(rapidjson::kStringType);
+ rapidjson::Value valueI(rapidjson::kNumberType);
+
+ valueI.SetInt(1);
+ json.AddMember("version", valueI, json.GetAllocator());
+
+ valueS.SetString(m_ct.enc_salt1.c_str(), m_ct.enc_salt1.size());
+ json.AddMember("salt1", valueS, json.GetAllocator());
+
+ valueS.SetString(m_ct.enc_salt2.c_str(), m_ct.enc_salt2.size());
+ json.AddMember("salt2", valueS, json.GetAllocator());
+
+ valueS.SetString(m_ct.enc_keys.c_str(), m_ct.enc_keys.size());
+ json.AddMember("enc_keys", valueS, json.GetAllocator());
+
+ json.Accept(writer);
+ return sb.GetString();
+ }
+
+
+}
+}
+}
+}
diff --git a/src/device_trezor/trezor/protocol.hpp b/src/device_trezor/trezor/protocol.hpp
new file mode 100644
index 000000000..99211efed
--- /dev/null
+++ b/src/device_trezor/trezor/protocol.hpp
@@ -0,0 +1,300 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef MONERO_PROTOCOL_H
+#define MONERO_PROTOCOL_H
+
+#include "trezor_defs.hpp"
+#include "device/device_cold.hpp"
+#include "messages_map.hpp"
+#include "transport.hpp"
+#include "wallet/wallet2.h"
+
+namespace hw{
+namespace trezor{
+namespace protocol{
+
+ std::string key_to_string(const ::crypto::ec_point & key);
+ std::string key_to_string(const ::crypto::ec_scalar & key);
+ std::string key_to_string(const ::crypto::hash & key);
+ std::string key_to_string(const ::rct::key & key);
+
+ void string_to_key(::crypto::ec_scalar & key, const std::string & str);
+ void string_to_key(::crypto::ec_point & key, const std::string & str);
+ void string_to_key(::rct::key & key, const std::string & str);
+
+ template<class sub_t, class InputIterator>
+ void assign_to_repeatable(::google::protobuf::RepeatedField<sub_t> * dst, const InputIterator begin, const InputIterator end){
+ for (InputIterator it = begin; it != end; it++) {
+ auto s = dst->Add();
+ *s = *it;
+ }
+ }
+
+ template<class sub_t, class InputIterator>
+ void assign_from_repeatable(std::vector<sub_t> * dst, const InputIterator begin, const InputIterator end){
+ for (InputIterator it = begin; it != end; it++) {
+ dst->push_back(*it);
+ }
+ };
+
+ template<typename T>
+ bool cn_deserialize(const void * buff, size_t len, T & dst){
+ std::stringstream ss;
+ ss.write(static_cast<const char *>(buff), len); //ss << tx_blob;
+ binary_archive<false> ba(ss);
+ bool r = ::serialization::serialize(ba, dst);
+ return r;
+ }
+
+ template<typename T>
+ bool cn_deserialize(const std::string & str, T & dst){
+ return cn_deserialize(str.data(), str.size(), dst);
+ }
+
+ template<typename T>
+ std::string cn_serialize(T & obj){
+ std::ostringstream oss;
+ binary_archive<true> oar(oss);
+ bool success = ::serialization::serialize(oar, obj);
+ if (!success){
+ throw exc::EncodingException("Could not CN serialize given object");
+ }
+ return oss.str();
+ }
+
+// Crypto / encryption
+namespace crypto {
+namespace chacha {
+
+ /**
+ * Chacha20Poly1305 decryption with tag verification. RFC 7539.
+ */
+ void decrypt(const void* ciphertext, size_t length, const uint8_t* key, const uint8_t* iv, char* plaintext);
+
+}
+}
+
+
+// Cold Key image sync
+namespace ki {
+
+ using MoneroTransferDetails = messages::monero::MoneroKeyImageSyncStepRequest_MoneroTransferDetails;
+ using MoneroSubAddressIndicesList = messages::monero::MoneroKeyImageExportInitRequest_MoneroSubAddressIndicesList;
+ using MoneroExportedKeyImage = messages::monero::MoneroKeyImageSyncStepAck_MoneroExportedKeyImage;
+ using exported_key_image = hw::device_cold::exported_key_image;
+
+ /**
+ * Converts transfer details to the MoneroTransferDetails required for KI sync
+ */
+ bool key_image_data(wallet_shim * wallet,
+ const std::vector<tools::wallet2::transfer_details> & transfers,
+ std::vector<MoneroTransferDetails> & res);
+
+ /**
+ * Computes a hash over MoneroTransferDetails. Commitment used in the KI sync.
+ */
+ std::string compute_hash(const MoneroTransferDetails & rr);
+
+ /**
+ * Generates KI sync request with commitments computed.
+ */
+ void generate_commitment(std::vector<MoneroTransferDetails> & mtds,
+ const std::vector<tools::wallet2::transfer_details> & transfers,
+ std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req);
+
+}
+
+// Cold transaction signing
+namespace tx {
+ using TsxData = messages::monero::MoneroTransactionInitRequest_MoneroTransactionData;
+ using MoneroTransactionDestinationEntry = messages::monero::MoneroTransactionDestinationEntry;
+ using MoneroAccountPublicAddress = messages::monero::MoneroTransactionDestinationEntry_MoneroAccountPublicAddress;
+ using MoneroTransactionSourceEntry = messages::monero::MoneroTransactionSourceEntry;
+ using MoneroMultisigKLRki = messages::monero::MoneroTransactionSourceEntry_MoneroMultisigKLRki;
+ using MoneroOutputEntry = messages::monero::MoneroTransactionSourceEntry_MoneroOutputEntry;
+ using MoneroRctKey = messages::monero::MoneroTransactionSourceEntry_MoneroOutputEntry_MoneroRctKeyPublic;
+ using MoneroRsigData = messages::monero::MoneroTransactionRsigData;
+
+ using tx_construction_data = tools::wallet2::tx_construction_data;
+ using unsigned_tx_set = tools::wallet2::unsigned_tx_set;
+
+ void translate_address(MoneroAccountPublicAddress * dst, const cryptonote::account_public_address * src);
+ void translate_dst_entry(MoneroTransactionDestinationEntry * dst, const cryptonote::tx_destination_entry * src);
+ void translate_src_entry(MoneroTransactionSourceEntry * dst, const cryptonote::tx_source_entry * src);
+ void translate_klrki(MoneroMultisigKLRki * dst, const rct::multisig_kLRki * src);
+ void translate_rct_key(MoneroRctKey * dst, const rct::ctkey * src);
+ std::string hash_addr(const MoneroAccountPublicAddress * addr, boost::optional<uint64_t> amount = boost::none, boost::optional<bool> is_subaddr = boost::none);
+ std::string hash_addr(const std::string & spend_key, const std::string & view_key, boost::optional<uint64_t> amount = boost::none, boost::optional<bool> is_subaddr = boost::none);
+ std::string hash_addr(const ::crypto::public_key * spend_key, const ::crypto::public_key * view_key, boost::optional<uint64_t> amount = boost::none, boost::optional<bool> is_subaddr = boost::none);
+
+ typedef boost::variant<rct::rangeSig, rct::Bulletproof> rsig_v;
+
+ /**
+ * Transaction signer state holder.
+ */
+ class TData {
+ public:
+ TsxData tsx_data;
+ tx_construction_data tx_data;
+ cryptonote::transaction tx;
+ bool in_memory;
+ unsigned rsig_type;
+ std::vector<uint64_t> grouping_vct;
+ std::shared_ptr<MoneroRsigData> rsig_param;
+ size_t cur_input_idx;
+ size_t cur_output_idx;
+ size_t cur_batch_idx;
+ size_t cur_output_in_batch_idx;
+
+ std::vector<std::string> tx_in_hmacs;
+ std::vector<std::string> tx_out_entr_hmacs;
+ std::vector<std::string> tx_out_hmacs;
+ std::vector<rsig_v> tx_out_rsigs;
+ std::vector<rct::ctkey> tx_out_pk;
+ std::vector<rct::ecdhTuple> tx_out_ecdh;
+ std::vector<size_t> source_permutation;
+ std::vector<std::string> alphas;
+ std::vector<std::string> spend_encs;
+ std::vector<std::string> pseudo_outs;
+ std::vector<std::string> pseudo_outs_hmac;
+ std::vector<std::string> couts;
+ std::vector<std::string> couts_dec;
+ std::vector<rct::key> rsig_gamma;
+ std::string tx_prefix_hash;
+ std::string enc_salt1;
+ std::string enc_salt2;
+ std::string enc_keys;
+
+ std::shared_ptr<rct::rctSig> rv;
+
+ TData();
+ };
+
+ class Signer {
+ private:
+ TData m_ct;
+ wallet_shim * m_wallet2;
+
+ size_t m_tx_idx;
+ const unsigned_tx_set * m_unsigned_tx;
+ hw::tx_aux_data * m_aux_data;
+
+ bool m_multisig;
+
+ const tx_construction_data & cur_tx(){
+ CHECK_AND_ASSERT_THROW_MES(m_tx_idx < m_unsigned_tx->txes.size(), "Invalid transaction index");
+ return m_unsigned_tx->txes[m_tx_idx];
+ }
+
+ void extract_payment_id();
+ void compute_integrated_indices(TsxData * tsx_data);
+
+ public:
+ Signer(wallet_shim * wallet2, const unsigned_tx_set * unsigned_tx, size_t tx_idx = 0, hw::tx_aux_data * aux_data = nullptr);
+
+ std::shared_ptr<messages::monero::MoneroTransactionInitRequest> step_init();
+ void step_init_ack(std::shared_ptr<const messages::monero::MoneroTransactionInitAck> ack);
+
+ std::shared_ptr<messages::monero::MoneroTransactionSetInputRequest> step_set_input(size_t idx);
+ void step_set_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetInputAck> ack);
+
+ void sort_ki();
+ std::shared_ptr<messages::monero::MoneroTransactionInputsPermutationRequest> step_permutation();
+ void step_permutation_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputsPermutationAck> ack);
+
+ std::shared_ptr<messages::monero::MoneroTransactionInputViniRequest> step_set_vini_input(size_t idx);
+ void step_set_vini_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputViniAck> ack);
+
+ std::shared_ptr<messages::monero::MoneroTransactionAllInputsSetRequest> step_all_inputs_set();
+ void step_all_inputs_set_ack(std::shared_ptr<const messages::monero::MoneroTransactionAllInputsSetAck> ack);
+
+ std::shared_ptr<messages::monero::MoneroTransactionSetOutputRequest> step_set_output(size_t idx);
+ void step_set_output_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetOutputAck> ack);
+
+ std::shared_ptr<messages::monero::MoneroTransactionAllOutSetRequest> step_all_outs_set();
+ void step_all_outs_set_ack(std::shared_ptr<const messages::monero::MoneroTransactionAllOutSetAck> ack, hw::device &hwdev);
+
+ std::shared_ptr<messages::monero::MoneroTransactionSignInputRequest> step_sign_input(size_t idx);
+ void step_sign_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionSignInputAck> ack);
+
+ std::shared_ptr<messages::monero::MoneroTransactionFinalRequest> step_final();
+ void step_final_ack(std::shared_ptr<const messages::monero::MoneroTransactionFinalAck> ack);
+
+ std::string store_tx_aux_info();
+
+ bool in_memory() const {
+ return m_ct.in_memory;
+ }
+
+ bool is_simple() const {
+ if (!m_ct.rv){
+ throw std::invalid_argument("RV not initialized");
+ }
+ auto tp = m_ct.rv->type;
+ return tp == rct::RCTTypeSimple;
+ }
+
+ bool is_req_bulletproof() const {
+ return m_ct.tx_data.use_bulletproofs;
+ }
+
+ bool is_bulletproof() const {
+ if (!m_ct.rv){
+ throw std::invalid_argument("RV not initialized");
+ }
+ auto tp = m_ct.rv->type;
+ return tp == rct::RCTTypeBulletproof;
+ }
+
+ bool is_offloading() const {
+ return m_ct.rsig_param && m_ct.rsig_param->offload_type() != 0;
+ }
+
+ size_t num_outputs() const {
+ return m_ct.tx_data.splitted_dsts.size();
+ }
+
+ size_t num_inputs() const {
+ return m_ct.tx_data.sources.size();
+ }
+
+ const TData & tdata() const {
+ return m_ct;
+ }
+ };
+
+}
+
+}
+}
+}
+
+
+#endif //MONERO_PROTOCOL_H
diff --git a/src/device_trezor/trezor/tools/README.md b/src/device_trezor/trezor/tools/README.md
new file mode 100644
index 000000000..91a8fb3f0
--- /dev/null
+++ b/src/device_trezor/trezor/tools/README.md
@@ -0,0 +1,36 @@
+# Trezor
+
+## Messages rebuild
+
+Install `protoc` for your distribution.
+
+- `protobuf-compiler`
+- `libprotobuf-dev`
+- `libprotoc-dev`
+- `python-protobuf`
+
+Python 3 is required. If you don't have python 3 quite an easy way is
+to use [pyenv].
+
+It is also advised to create own python virtual environment so dependencies
+are installed in this project-related virtual environment.
+
+```bash
+python -m venv /
+```
+
+Make sure your python has `protobuf` package installed
+
+```bash
+pip install protobuf
+```
+
+Regenerate messages:
+
+```
+./venv/bin/python3 src/device_trezor/trezor/tools/build_protob.py
+```
+
+The messages regeneration is done also automatically via cmake.
+
+[pyenv]: https://github.com/pyenv/pyenv
diff --git a/src/device_trezor/trezor/tools/build_protob.py b/src/device_trezor/trezor/tools/build_protob.py
new file mode 100644
index 000000000..2611f3296
--- /dev/null
+++ b/src/device_trezor/trezor/tools/build_protob.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+import os
+import subprocess
+import sys
+
+CWD = os.path.dirname(os.path.realpath(__file__))
+ROOT_DIR = os.path.abspath(os.path.join(CWD, "..", "..", "..", ".."))
+TREZOR_COMMON = os.path.join(ROOT_DIR, "external", "trezor-common")
+TREZOR_MESSAGES = os.path.join(CWD, "..", "messages")
+
+# check for existence of the submodule directory
+common_defs = os.path.join(TREZOR_COMMON, "defs")
+if not os.path.exists(common_defs):
+ raise ValueError(
+ "trezor-common submodule seems to be missing.\n"
+ + 'Use "git submodule update --init --recursive" to retrieve it.'
+ )
+
+# regenerate messages
+try:
+ selected = [
+ "messages.proto",
+ "messages-common.proto",
+ "messages-management.proto",
+ "messages-monero.proto",
+ ]
+ proto_srcs = [os.path.join(TREZOR_COMMON, "protob", x) for x in selected]
+ exec_args = [
+ sys.executable,
+ os.path.join(CWD, "pb2cpp.py"),
+ "-o",
+ TREZOR_MESSAGES,
+ ] + proto_srcs
+
+ subprocess.check_call(exec_args)
+
+except Exception as e:
+ raise
diff --git a/src/device_trezor/trezor/tools/pb2cpp.py b/src/device_trezor/trezor/tools/pb2cpp.py
new file mode 100644
index 000000000..eaa8a90ed
--- /dev/null
+++ b/src/device_trezor/trezor/tools/pb2cpp.py
@@ -0,0 +1,186 @@
+#!/usr/bin/env python3
+# Converts Google's protobuf python definitions of TREZOR wire messages
+# to plain-python objects as used in TREZOR Core and python-trezor
+
+import argparse
+import logging
+import os
+import re
+import shutil
+import subprocess
+import sys
+import glob
+import tempfile
+import hashlib
+
+
+AUTO_HEADER = "# Automatically generated by pb2cpp\n"
+
+# Fixing GCC7 compilation error
+UNDEF_STATEMENT = """
+#ifdef minor
+#undef minor
+#endif
+"""
+
+
+def which(pgm):
+ path = os.getenv('PATH')
+ for p in path.split(os.path.pathsep):
+ p = os.path.join(p, pgm)
+ if os.path.exists(p) and os.access(p, os.X_OK):
+ return p
+
+
+PROTOC = which("protoc")
+if not PROTOC:
+ print("protoc command not found")
+ sys.exit(1)
+
+PROTOC_PREFIX = os.path.dirname(os.path.dirname(PROTOC))
+PROTOC_INCLUDE = os.path.join(PROTOC_PREFIX, "include")
+
+
+def namespace_file(fpath, package):
+ """Adds / replaces package name. Simple regex parsing, may use https://github.com/ph4r05/plyprotobuf later"""
+ with open(fpath) as fh:
+ fdata = fh.read()
+
+ re_syntax = re.compile(r"^syntax\s*=")
+ re_package = re.compile(r"^package\s+([^;]+?)\s*;\s*$")
+ lines = fdata.split("\n")
+
+ line_syntax = None
+ line_package = None
+ for idx, line in enumerate(lines):
+ if line_syntax is None and re_syntax.match(line):
+ line_syntax = idx
+ if line_package is None and re_package.match(line):
+ line_package = idx
+
+ if package is None:
+ if line_package is None:
+ return
+ else:
+ lines.pop(line_package)
+
+ else:
+ new_package = "package %s;" % package
+ if line_package is None:
+ lines.insert(line_syntax + 1 if line_syntax is not None else 0, new_package)
+ else:
+ lines[line_package] = new_package
+
+ new_fdat = "\n".join(lines)
+ with open(fpath, "w+") as fh:
+ fh.write(new_fdat)
+ return new_fdat
+
+
+def protoc(files, out_dir, additional_includes=(), package=None, force=False):
+ """Compile code with protoc and return the data."""
+
+ include_dirs = set()
+ include_dirs.add(PROTOC_INCLUDE)
+ include_dirs.update(additional_includes)
+
+ with tempfile.TemporaryDirectory() as tmpdir_protob, tempfile.TemporaryDirectory() as tmpdir_out:
+ include_dirs.add(tmpdir_protob)
+
+ new_files = []
+ for file in files:
+ bname = os.path.basename(file)
+ tmp_file = os.path.join(tmpdir_protob, bname)
+
+ shutil.copy(file, tmp_file)
+ if package is not None:
+ namespace_file(tmp_file, package)
+ new_files.append(tmp_file)
+
+ protoc_includes = ["-I" + dir for dir in include_dirs if dir]
+
+ exec_args = (
+ [
+ PROTOC,
+ "--cpp_out",
+ tmpdir_out,
+ ]
+ + protoc_includes
+ + new_files
+ )
+
+ subprocess.check_call(exec_args)
+
+ # Fixing gcc compilation and clashes with "minor" field name
+ add_undef(tmpdir_out)
+
+ # Scan output dir, check file differences
+ update_message_files(tmpdir_out, out_dir, force)
+
+
+def update_message_files(tmpdir_out, out_dir, force=False):
+ files = glob.glob(os.path.join(tmpdir_out, '*.pb.*'))
+ for fname in files:
+ bname = os.path.basename(fname)
+ dest_file = os.path.join(out_dir, bname)
+ if not force and os.path.exists(dest_file):
+ data = open(fname, 'rb').read()
+ data_hash = hashlib.sha3_256(data).digest()
+ data_dest = open(dest_file, 'rb').read()
+ data_dest_hash = hashlib.sha3_256(data_dest).digest()
+ if data_hash == data_dest_hash:
+ continue
+
+ shutil.copy(fname, dest_file)
+
+
+def add_undef(out_dir):
+ files = glob.glob(os.path.join(out_dir, '*.pb.*'))
+ for fname in files:
+ with open(fname) as fh:
+ lines = fh.readlines()
+
+ idx_insertion = None
+ for idx in range(len(lines)):
+ if '@@protoc_insertion_point(includes)' in lines[idx]:
+ idx_insertion = idx
+ break
+
+ if idx_insertion is None:
+ pass
+
+ lines.insert(idx_insertion + 1, UNDEF_STATEMENT)
+ with open(fname, 'w') as fh:
+ fh.write("".join(lines))
+
+
+def strip_leader(s, prefix):
+ """Remove given prefix from underscored name."""
+ leader = prefix + "_"
+ if s.startswith(leader):
+ return s[len(leader) :]
+ else:
+ return s
+
+
+if __name__ == "__main__":
+ logging.basicConfig(level=logging.DEBUG)
+
+ parser = argparse.ArgumentParser()
+ # fmt: off
+ parser.add_argument("proto", nargs="+", help="Protobuf definition files")
+ parser.add_argument("-o", "--out-dir", help="Directory for generated source code")
+ parser.add_argument("-n", "--namespace", default=None, help="Message namespace")
+ parser.add_argument("-I", "--protoc-include", action="append", help="protoc include path")
+ parser.add_argument("-P", "--protobuf-module", default="protobuf", help="Name of protobuf module")
+ parser.add_argument("-f", "--force", default=False, help="Overwrite existing files")
+ # fmt: on
+ args = parser.parse_args()
+
+ protoc_includes = args.protoc_include or (os.environ.get("PROTOC_INCLUDE"),)
+
+ protoc(
+ args.proto, args.out_dir, protoc_includes, package=args.namespace, force=args.force
+ )
+
+
diff --git a/src/device_trezor/trezor/transport.cpp b/src/device_trezor/trezor/transport.cpp
new file mode 100644
index 000000000..fc86177e1
--- /dev/null
+++ b/src/device_trezor/trezor/transport.cpp
@@ -0,0 +1,651 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <boost/endian/conversion.hpp>
+#include <boost/asio/io_service.hpp>
+#include <boost/asio/ip/udp.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+#include "transport.hpp"
+#include "messages/messages-common.pb.h"
+
+using namespace std;
+using json = rapidjson::Document;
+
+
+namespace hw{
+namespace trezor{
+
+ bool t_serialize(const std::string & in, std::string & out){
+ out = in;
+ return true;
+ }
+
+ bool t_serialize(const json_val & in, std::string & out){
+ rapidjson::StringBuffer sb;
+ rapidjson::Writer<rapidjson::StringBuffer> writer(sb);
+ in.Accept(writer);
+ out = sb.GetString();
+ return true;
+ }
+
+ std::string t_serialize(const json_val & in){
+ std::string ret;
+ t_serialize(in, ret);
+ return ret;
+ }
+
+ bool t_deserialize(const std::string & in, std::string & out){
+ out = in;
+ return true;
+ }
+
+ bool t_deserialize(const std::string & in, json & out){
+ if (out.Parse(in.c_str()).HasParseError()) {
+ throw exc::CommunicationException("JSON parse error");
+ }
+ return true;
+ }
+
+ static std::string json_get_string(const rapidjson::Value & in){
+ return std::string(in.GetString());
+ }
+
+ //
+ // Helpers
+ //
+
+#define PROTO_HEADER_SIZE 6
+
+ static size_t message_size(const google::protobuf::Message &req){
+ return static_cast<size_t>(req.ByteSize());
+ }
+
+ static size_t serialize_message_buffer_size(size_t msg_size) {
+ return PROTO_HEADER_SIZE + msg_size; // tag 2B + len 4B
+ }
+
+ static void serialize_message_header(void * buff, uint16_t tag, uint32_t len){
+ uint16_t wire_tag = boost::endian::native_to_big(static_cast<uint16_t>(tag));
+ uint32_t wire_len = boost::endian::native_to_big(static_cast<uint32_t>(len));
+ memcpy(buff, (void *) &wire_tag, 2);
+ memcpy((uint8_t*)buff + 2, (void *) &wire_len, 4);
+ }
+
+ static void deserialize_message_header(const void * buff, uint16_t & tag, uint32_t & len){
+ uint16_t wire_tag;
+ uint32_t wire_len;
+ memcpy(&wire_tag, buff, 2);
+ memcpy(&wire_len, (uint8_t*)buff + 2, 4);
+
+ tag = boost::endian::big_to_native(wire_tag);
+ len = boost::endian::big_to_native(wire_len);
+ }
+
+ static void serialize_message(const google::protobuf::Message &req, size_t msg_size, uint8_t * buff, size_t buff_size) {
+ auto msg_wire_num = MessageMapper::get_message_wire_number(req);
+ const auto req_buffer_size = serialize_message_buffer_size(msg_size);
+ if (req_buffer_size > buff_size){
+ throw std::invalid_argument("Buffer too small");
+ }
+
+ serialize_message_header(buff, msg_wire_num, msg_size);
+ if (!req.SerializeToArray(buff + 6, msg_size)){
+ throw exc::EncodingException("Message serialization error");
+ }
+ }
+
+ //
+ // Communication protocol
+ //
+
+#define REPLEN 64
+
+ void ProtocolV1::write(Transport & transport, const google::protobuf::Message & req){
+ const auto msg_size = message_size(req);
+ const auto buff_size = serialize_message_buffer_size(msg_size) + 2;
+
+ std::unique_ptr<uint8_t[]> req_buff(new uint8_t[buff_size]);
+ uint8_t * req_buff_raw = req_buff.get();
+ req_buff_raw[0] = '#';
+ req_buff_raw[1] = '#';
+
+ serialize_message(req, msg_size, req_buff_raw + 2, buff_size - 2);
+
+ size_t offset = 0;
+ uint8_t chunk_buff[REPLEN];
+
+ // Chunk by chunk upload
+ while(offset < buff_size){
+ auto to_copy = std::min((size_t)(buff_size - offset), (size_t)(REPLEN - 1));
+
+ chunk_buff[0] = '?';
+ memcpy(chunk_buff + 1, req_buff_raw + offset, to_copy);
+
+ // Pad with zeros
+ if (to_copy < REPLEN - 1){
+ memset(chunk_buff + 1 + to_copy, 0, REPLEN - 1 - to_copy);
+ }
+
+ transport.write_chunk(chunk_buff, REPLEN);
+ offset += REPLEN - 1;
+ }
+ }
+
+ void ProtocolV1::read(Transport & transport, std::shared_ptr<google::protobuf::Message> & msg, messages::MessageType * msg_type){
+ char chunk[REPLEN];
+
+ // Initial chunk read
+ size_t nread = transport.read_chunk(chunk, REPLEN);
+ if (nread != REPLEN){
+ throw exc::CommunicationException("Read chunk has invalid size");
+ }
+
+ if (strncmp(chunk, "?##", 3) != 0){
+ throw exc::CommunicationException("Malformed chunk");
+ }
+
+ uint16_t tag;
+ uint32_t len;
+ nread -= 3 + 6;
+ deserialize_message_header(chunk + 3, tag, len);
+
+ std::string data_acc(chunk + 3 + 6, nread);
+ data_acc.reserve(len);
+
+ while(nread < len){
+ const size_t cur = transport.read_chunk(chunk, REPLEN);
+ if (chunk[0] != '?'){
+ throw exc::CommunicationException("Chunk malformed");
+ }
+
+ data_acc.append(chunk + 1, cur - 1);
+ nread += cur - 1;
+ }
+
+ if (msg_type){
+ *msg_type = static_cast<messages::MessageType>(tag);
+ }
+
+ if (nread < len){
+ throw exc::CommunicationException("Response incomplete");
+ }
+
+ std::shared_ptr<google::protobuf::Message> msg_wrap(MessageMapper::get_message(tag));
+ if (!msg_wrap->ParseFromArray(data_acc.c_str(), len)){
+ throw exc::CommunicationException("Message could not be parsed");
+ }
+
+ msg = msg_wrap;
+ }
+
+ //
+ // Bridge transport
+ //
+
+ const char * BridgeTransport::PATH_PREFIX = "bridge:";
+
+ std::string BridgeTransport::get_path() const {
+ if (!m_device_path){
+ return "";
+ }
+
+ std::string path(PATH_PREFIX);
+ return path + m_device_path.get();
+ }
+
+ void BridgeTransport::enumerate(t_transport_vect & res) {
+ json bridge_res;
+ std::string req;
+
+ bool req_status = invoke_bridge_http("/enumerate", req, bridge_res, m_http_client);
+ if (!req_status){
+ throw exc::CommunicationException("Bridge enumeration failed");
+ }
+
+ for(rapidjson::Value::ConstValueIterator itr = bridge_res.Begin(); itr != bridge_res.End(); ++itr){
+ auto element = itr->GetObject();
+ auto t = std::make_shared<BridgeTransport>(boost::make_optional(json_get_string(element["path"])));
+ t->m_device_info.emplace();
+ t->m_device_info->CopyFrom(*itr, t->m_device_info->GetAllocator());
+ res.push_back(t);
+ }
+ }
+
+ void BridgeTransport::open() {
+ if (!m_device_path){
+ throw exc::CommunicationException("Coud not open, empty device path");
+ }
+
+ std::string uri = "/acquire/" + m_device_path.get() + "/null";
+ std::string req;
+ json bridge_res;
+ bool req_status = invoke_bridge_http(uri, req, bridge_res, m_http_client);
+ if (!req_status){
+ throw exc::CommunicationException("Failed to acquire device");
+ }
+
+ m_session = boost::make_optional(json_get_string(bridge_res["session"]));
+ }
+
+ void BridgeTransport::close() {
+ if (!m_device_path || !m_session){
+ throw exc::CommunicationException("Device not open");
+ }
+
+ std::string uri = "/release/" + m_session.get();
+ std::string req;
+ json bridge_res;
+ bool req_status = invoke_bridge_http(uri, req, bridge_res, m_http_client);
+ if (!req_status){
+ throw exc::CommunicationException("Failed to release device");
+ }
+
+ m_session = boost::none;
+ }
+
+ void BridgeTransport::write(const google::protobuf::Message &req) {
+ m_response = boost::none;
+
+ const auto msg_size = message_size(req);
+ const auto buff_size = serialize_message_buffer_size(msg_size);
+
+ std::unique_ptr<uint8_t[]> req_buff(new uint8_t[buff_size]);
+ uint8_t * req_buff_raw = req_buff.get();
+
+ serialize_message(req, msg_size, req_buff_raw, buff_size);
+
+ std::string uri = "/call/" + m_session.get();
+ std::string req_hex = epee::to_hex::string(epee::span<const std::uint8_t>(req_buff_raw, buff_size));
+ std::string res_hex;
+
+ bool req_status = invoke_bridge_http(uri, req_hex, res_hex, m_http_client);
+ if (!req_status){
+ throw exc::CommunicationException("Call method failed");
+ }
+
+ m_response = res_hex;
+ }
+
+ void BridgeTransport::read(std::shared_ptr<google::protobuf::Message> & msg, messages::MessageType * msg_type) {
+ if (!m_response){
+ throw exc::CommunicationException("Could not read, no response stored");
+ }
+
+ std::string bin_data;
+ if (!epee::string_tools::parse_hexstr_to_binbuff(m_response.get(), bin_data)){
+ throw exc::CommunicationException("Response is not well hexcoded");
+ }
+
+ uint16_t msg_tag;
+ uint32_t msg_len;
+ deserialize_message_header(bin_data.c_str(), msg_tag, msg_len);
+ if (bin_data.size() != msg_len + 6){
+ throw exc::CommunicationException("Response is not well hexcoded");
+ }
+
+ if (msg_type){
+ *msg_type = static_cast<messages::MessageType>(msg_tag);
+ }
+
+ std::shared_ptr<google::protobuf::Message> msg_wrap(MessageMapper::get_message(msg_tag));
+ if (!msg_wrap->ParseFromArray(bin_data.c_str() + 6, msg_len)){
+ throw exc::EncodingException("Response is not well hexcoded");
+ }
+ msg = msg_wrap;
+ }
+
+ const boost::optional<json> & BridgeTransport::device_info() const {
+ return m_device_info;
+ }
+
+ std::ostream& BridgeTransport::dump(std::ostream& o) const {
+ return o << "BridgeTransport<path=" << (m_device_path ? get_path() : "None")
+ << ", info=" << (m_device_info ? t_serialize(m_device_info.get()) : "None")
+ << ", session=" << (m_session ? m_session.get() : "None")
+ << ">";
+ }
+
+ //
+ // UdpTransport
+ //
+ const char * UdpTransport::PATH_PREFIX = "udp:";
+ const char * UdpTransport::DEFAULT_HOST = "127.0.0.1";
+ const int UdpTransport::DEFAULT_PORT = 21324;
+
+ UdpTransport::UdpTransport(boost::optional<std::string> device_path,
+ boost::optional<std::shared_ptr<Protocol>> proto) :
+ m_io_service(), m_deadline(m_io_service)
+ {
+ m_device_port = DEFAULT_PORT;
+ if (device_path) {
+ const std::string device_str = device_path.get();
+ auto delim = device_str.find(':');
+ if (delim == std::string::npos) {
+ m_device_host = device_str;
+ } else {
+ m_device_host = device_str.substr(0, delim);
+ m_device_port = std::stoi(device_str.substr(delim + 1));
+ }
+ } else {
+ m_device_host = DEFAULT_HOST;
+ }
+
+ if (m_device_port <= 1024 || m_device_port > 65535){
+ throw std::invalid_argument("Port number invalid");
+ }
+
+ if (m_device_host != "localhost" && m_device_host != DEFAULT_HOST){
+ throw std::invalid_argument("Local endpoint allowed only");
+ }
+
+ m_proto = proto ? proto.get() : std::make_shared<ProtocolV1>();
+ }
+
+ std::string UdpTransport::get_path() const {
+ std::string path(PATH_PREFIX);
+ return path + m_device_host + ":" + std::to_string(m_device_port);
+ }
+
+ void UdpTransport::require_socket(){
+ if (!m_socket){
+ throw exc::NotConnectedException("Socket not connected");
+ }
+ }
+
+ bool UdpTransport::ping(){
+ return ping_int();
+ }
+
+ bool UdpTransport::ping_int(boost::posix_time::time_duration timeout){
+ require_socket();
+ try {
+ std::string req = "PINGPING";
+ char res[8];
+
+ m_socket->send_to(boost::asio::buffer(req.c_str(), req.size()), m_endpoint);
+ receive(res, 8, nullptr, false, timeout);
+
+ return memcmp(res, "PONGPONG", 8) == 0;
+
+ } catch(...){
+ return false;
+ }
+ }
+
+ void UdpTransport::enumerate(t_transport_vect & res) {
+ std::shared_ptr<UdpTransport> t = std::make_shared<UdpTransport>();
+ bool t_works = false;
+
+ try{
+ t->open();
+ t_works = t->ping();
+ } catch(...) {
+
+ }
+ t->close();
+ if (t_works){
+ res.push_back(t);
+ }
+ }
+
+ void UdpTransport::open() {
+ udp::resolver resolver(m_io_service);
+ udp::resolver::query query(udp::v4(), m_device_host, std::to_string(m_device_port));
+ m_endpoint = *resolver.resolve(query);
+
+ m_socket.reset(new udp::socket(m_io_service));
+ m_socket->open(udp::v4());
+
+ m_deadline.expires_at(boost::posix_time::pos_infin);
+ check_deadline();
+
+ m_proto->session_begin(*this);
+ }
+
+ void UdpTransport::close() {
+ if (!m_socket){
+ throw exc::CommunicationException("Socket is already closed");
+ }
+
+ m_proto->session_end(*this);
+ m_socket->close();
+ m_socket = nullptr;
+ }
+
+ void UdpTransport::write_chunk(const void * buff, size_t size){
+ require_socket();
+
+ if (size != 64){
+ throw exc::CommunicationException("Invalid chunk size");
+ }
+
+ auto written = m_socket->send_to(boost::asio::buffer(buff, size), m_endpoint);
+ if (size != written){
+ throw exc::CommunicationException("Could not send the whole chunk");
+ }
+ }
+
+ size_t UdpTransport::read_chunk(void * buff, size_t size){
+ require_socket();
+ if (size < 64){
+ throw std::invalid_argument("Buffer too small");
+ }
+
+ ssize_t len;
+ while(true) {
+ try {
+ boost::system::error_code ec;
+ len = receive(buff, size, &ec, true);
+ if (ec == boost::asio::error::operation_aborted) {
+ continue;
+ } else if (ec) {
+ throw exc::CommunicationException(std::string("Comm error: ") + ec.message());
+ }
+
+ if (len != 64) {
+ throw exc::CommunicationException("Invalid chunk size");
+ }
+
+ break;
+
+ } catch(exc::CommunicationException const& e){
+ throw;
+ } catch(std::exception const& e){
+ MWARNING("Error reading chunk, reason: " << e.what());
+ throw exc::CommunicationException(std::string("Chunk read error: ") + std::string(e.what()));
+ }
+ }
+
+ return static_cast<size_t>(len);
+ }
+
+ ssize_t UdpTransport::receive(void * buff, size_t size, boost::system::error_code * error_code, bool no_throw, boost::posix_time::time_duration timeout){
+ boost::system::error_code ec;
+ boost::asio::mutable_buffer buffer = boost::asio::buffer(buff, size);
+
+ require_socket();
+
+ // Set a deadline for the asynchronous operation.
+ m_deadline.expires_from_now(timeout);
+
+ // Set up the variables that receive the result of the asynchronous
+ // operation. The error code is set to would_block to signal that the
+ // operation is incomplete. Asio guarantees that its asynchronous
+ // operations will never fail with would_block, so any other value in
+ // ec indicates completion.
+ ec = boost::asio::error::would_block;
+ std::size_t length = 0;
+
+ // Start the asynchronous operation itself. The handle_receive function
+ // used as a callback will update the ec and length variables.
+ m_socket->async_receive_from(boost::asio::buffer(buffer), m_endpoint,
+ boost::bind(&UdpTransport::handle_receive, _1, _2, &ec, &length));
+
+ // Block until the asynchronous operation has completed.
+ do {
+ m_io_service.run_one();
+ }
+ while (ec == boost::asio::error::would_block);
+
+ if (error_code){
+ *error_code = ec;
+ }
+
+ if (no_throw){
+ return length;
+ }
+
+ // Operation result
+ if (ec == boost::asio::error::operation_aborted){
+ throw exc::TimeoutException();
+
+ } else if (ec) {
+ MWARNING("Reading from UDP socket failed: " << ec.message());
+ throw exc::CommunicationException();
+
+ }
+
+ return length;
+ }
+
+ void UdpTransport::write(const google::protobuf::Message &req) {
+ m_proto->write(*this, req);
+ }
+
+ void UdpTransport::read(std::shared_ptr<google::protobuf::Message> & msg, messages::MessageType * msg_type) {
+ m_proto->read(*this, msg, msg_type);
+ }
+
+ void UdpTransport::check_deadline(){
+ if (!m_socket){
+ return; // no active socket.
+ }
+
+ // Check whether the deadline has passed. We compare the deadline against
+ // the current time since a new asynchronous operation may have moved the
+ // deadline before this actor had a chance to run.
+ if (m_deadline.expires_at() <= boost::asio::deadline_timer::traits_type::now())
+ {
+ // The deadline has passed. The outstanding asynchronous operation needs
+ // to be cancelled so that the blocked receive() function will return.
+ //
+ // Please note that cancel() has portability issues on some versions of
+ // Microsoft Windows, and it may be necessary to use close() instead.
+ // Consult the documentation for cancel() for further information.
+ m_socket->cancel();
+
+ // There is no longer an active deadline. The expiry is set to positive
+ // infinity so that the actor takes no action until a new deadline is set.
+ m_deadline.expires_at(boost::posix_time::pos_infin);
+ }
+
+ // Put the actor back to sleep.
+ m_deadline.async_wait(boost::bind(&UdpTransport::check_deadline, this));
+ }
+
+ void UdpTransport::handle_receive(const boost::system::error_code &ec, std::size_t length,
+ boost::system::error_code *out_ec, std::size_t *out_length) {
+ *out_ec = ec;
+ *out_length = length;
+ }
+
+ std::ostream& UdpTransport::dump(std::ostream& o) const {
+ return o << "UdpTransport<path=" << get_path()
+ << ", socket_alive=" << (m_socket ? "true" : "false")
+ << ">";
+ }
+
+ void enumerate(t_transport_vect & res){
+ BridgeTransport bt;
+ bt.enumerate(res);
+
+ hw::trezor::UdpTransport btu;
+ btu.enumerate(res);
+ }
+
+ std::shared_ptr<Transport> transport(const std::string & path){
+ if (boost::starts_with(path, BridgeTransport::PATH_PREFIX)){
+ return std::make_shared<BridgeTransport>(path.substr(strlen(BridgeTransport::PATH_PREFIX)));
+
+ } else if (boost::starts_with(path, UdpTransport::PATH_PREFIX)){
+ return std::make_shared<UdpTransport>(path.substr(strlen(UdpTransport::PATH_PREFIX)));
+
+ } else {
+ throw std::invalid_argument("Unknown Trezor device path: " + path);
+
+ }
+ }
+
+ void throw_failure_exception(const messages::common::Failure * failure) {
+ if (failure == nullptr){
+ throw std::invalid_argument("Failure message cannot be null");
+ }
+
+ boost::optional<std::string> message = failure->has_message() ? boost::make_optional(failure->message()) : boost::none;
+ boost::optional<uint32_t> code = failure->has_code() ? boost::make_optional(static_cast<uint32_t>(failure->code())) : boost::none;
+ if (!code){
+ throw exc::proto::FailureException(code, message);
+ }
+
+ auto ecode = failure->code();
+ if (ecode == messages::common::Failure_FailureType_Failure_UnexpectedMessage){
+ throw exc::proto::UnexpectedMessageException(code, message);
+ } else if (ecode == messages::common::Failure_FailureType_Failure_ActionCancelled){
+ throw exc::proto::CancelledException(code, message);
+ } else if (ecode == messages::common::Failure_FailureType_Failure_PinExpected){
+ throw exc::proto::PinExpectedException(code, message);
+ } else if (ecode == messages::common::Failure_FailureType_Failure_PinInvalid){
+ throw exc::proto::InvalidPinException(code, message);
+ } else if (ecode == messages::common::Failure_FailureType_Failure_NotEnoughFunds){
+ throw exc::proto::NotEnoughFundsException(code, message);
+ } else if (ecode == messages::common::Failure_FailureType_Failure_NotInitialized){
+ throw exc::proto::NotInitializedException(code, message);
+ } else if (ecode == messages::common::Failure_FailureType_Failure_FirmwareError){
+ throw exc::proto::FirmwareErrorException(code, message);
+ } else {
+ throw exc::proto::FailureException(code, message);
+ }
+ }
+
+ std::ostream& operator<<(std::ostream& o, hw::trezor::Transport const& t){
+ return t.dump(o);
+ }
+
+ std::ostream& operator<<(std::ostream& o, std::shared_ptr<hw::trezor::Transport> const& t){
+ if (!t){
+ return o << "None";
+ }
+
+ return t->dump(o);
+ }
+
+}
+}
+
+
diff --git a/src/device_trezor/trezor/transport.hpp b/src/device_trezor/trezor/transport.hpp
new file mode 100644
index 000000000..7b82fd06f
--- /dev/null
+++ b/src/device_trezor/trezor/transport.hpp
@@ -0,0 +1,331 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef MONERO_TRANSPORT_H
+#define MONERO_TRANSPORT_H
+
+
+#include <boost/asio.hpp>
+#include <boost/asio/deadline_timer.hpp>
+#include <boost/array.hpp>
+#include <boost/utility/string_ref.hpp>
+
+#include <typeinfo>
+#include <type_traits>
+#include "net/http_client.h"
+
+#include "rapidjson/document.h"
+#include "rapidjson/writer.h"
+#include "rapidjson/stringbuffer.h"
+
+#include "exceptions.hpp"
+#include "trezor_defs.hpp"
+#include "messages_map.hpp"
+
+#include "messages/messages.pb.h"
+#include "messages/messages-common.pb.h"
+#include "messages/messages-management.pb.h"
+#include "messages/messages-monero.pb.h"
+
+namespace hw {
+namespace trezor {
+
+ using json = rapidjson::Document;
+ using json_val = rapidjson::Value;
+ namespace http = epee::net_utils::http;
+
+ const std::string DEFAULT_BRIDGE = "127.0.0.1:21325";
+
+ // Base HTTP comm serialization.
+ bool t_serialize(const std::string & in, std::string & out);
+ bool t_serialize(const json_val & in, std::string & out);
+ std::string t_serialize(const json_val & in);
+
+ bool t_deserialize(const std::string & in, std::string & out);
+ bool t_deserialize(const std::string & in, json & out);
+
+ // Flexible json serialization. HTTP client tailored for bridge API
+ template<class t_req, class t_res, class t_transport>
+ bool invoke_bridge_http(const boost::string_ref uri, const t_req & out_struct, t_res & result_struct, t_transport& transport, const boost::string_ref method = "POST", std::chrono::milliseconds timeout = std::chrono::seconds(180))
+ {
+ std::string req_param;
+ t_serialize(out_struct, req_param);
+
+ http::fields_list additional_params;
+ additional_params.push_back(std::make_pair("Origin","https://monero.trezor.io"));
+ additional_params.push_back(std::make_pair("Content-Type","application/json; charset=utf-8"));
+
+ const http::http_response_info* pri = nullptr;
+ if(!transport.invoke(uri, method, req_param, timeout, &pri, std::move(additional_params)))
+ {
+ MERROR("Failed to invoke http request to " << uri);
+ return false;
+ }
+
+ if(!pri)
+ {
+ MERROR("Failed to invoke http request to " << uri << ", internal error (null response ptr)");
+ return false;
+ }
+
+ if(pri->m_response_code != 200)
+ {
+ MERROR("Failed to invoke http request to " << uri << ", wrong response code: " << pri->m_response_code
+ << " Response Body: " << pri->m_body);
+ return false;
+ }
+
+ return t_deserialize(pri->m_body, result_struct);
+ }
+
+ // Forward decl
+ class Transport;
+ class Protocol;
+
+ // Communication protocol
+ class Protocol {
+ public:
+ Protocol() = default;
+ virtual ~Protocol() = default;
+ virtual void session_begin(Transport & transport){ };
+ virtual void session_end(Transport & transport){ };
+ virtual void write(Transport & transport, const google::protobuf::Message & req)= 0;
+ virtual void read(Transport & transport, std::shared_ptr<google::protobuf::Message> & msg, messages::MessageType * msg_type=nullptr)= 0;
+ };
+
+ class ProtocolV1 : public Protocol {
+ public:
+ ProtocolV1() = default;
+ virtual ~ProtocolV1() = default;
+
+ void write(Transport & transport, const google::protobuf::Message & req) override;
+ void read(Transport & transport, std::shared_ptr<google::protobuf::Message> & msg, messages::MessageType * msg_type=nullptr) override;
+ };
+
+
+ // Base transport
+ typedef std::vector<std::shared_ptr<Transport>> t_transport_vect;
+
+ class Transport {
+ public:
+ Transport() = default;
+ virtual ~Transport() = default;
+
+ virtual bool ping() { return false; };
+ virtual std::string get_path() const { return ""; };
+ virtual void enumerate(t_transport_vect & res){};
+ virtual void open(){};
+ virtual void close(){};
+ virtual void write(const google::protobuf::Message & req) =0;
+ virtual void read(std::shared_ptr<google::protobuf::Message> & msg, messages::MessageType * msg_type=nullptr) =0;
+
+ virtual void write_chunk(const void * buff, size_t size) { };
+ virtual size_t read_chunk(void * buff, size_t size) { return 0; };
+ virtual std::ostream& dump(std::ostream& o) const { return o << "Transport<>"; }
+ };
+
+ // Bridge transport
+ class BridgeTransport : public Transport {
+ public:
+ BridgeTransport(
+ boost::optional<std::string> device_path = boost::none,
+ boost::optional<std::string> bridge_host = boost::none):
+ m_device_path(device_path),
+ m_bridge_host(bridge_host ? bridge_host.get() : DEFAULT_BRIDGE),
+ m_response(boost::none),
+ m_session(boost::none),
+ m_device_info(boost::none)
+ {
+ m_http_client.set_server(m_bridge_host, boost::none, false);
+ }
+
+ virtual ~BridgeTransport() = default;
+
+ static const char * PATH_PREFIX;
+
+ std::string get_path() const override;
+ void enumerate(t_transport_vect & res) override;
+
+ void open() override;
+ void close() override;
+
+ void write(const google::protobuf::Message &req) override;
+ void read(std::shared_ptr<google::protobuf::Message> & msg, messages::MessageType * msg_type=nullptr) override;
+
+ const boost::optional<json> & device_info() const;
+ std::ostream& dump(std::ostream& o) const override;
+
+ private:
+ epee::net_utils::http::http_simple_client m_http_client;
+ std::string m_bridge_host;
+ boost::optional<std::string> m_device_path;
+ boost::optional<std::string> m_session;
+ boost::optional<std::string> m_response;
+ boost::optional<json> m_device_info;
+ };
+
+ // UdpTransport transport
+ using boost::asio::ip::udp;
+
+ class UdpTransport : public Transport {
+ public:
+
+ explicit UdpTransport(
+ boost::optional<std::string> device_path=boost::none,
+ boost::optional<std::shared_ptr<Protocol>> proto=boost::none);
+
+ virtual ~UdpTransport() = default;
+
+ static const char * PATH_PREFIX;
+ static const char * DEFAULT_HOST;
+ static const int DEFAULT_PORT;
+
+ bool ping() override;
+ std::string get_path() const override;
+ void enumerate(t_transport_vect & res) override;
+
+ void open() override;
+ void close() override;
+
+ void write(const google::protobuf::Message &req) override;
+ void read(std::shared_ptr<google::protobuf::Message> & msg, messages::MessageType * msg_type=nullptr) override;
+
+ void write_chunk(const void * buff, size_t size) override;
+ size_t read_chunk(void * buff, size_t size) override;
+
+ std::ostream& dump(std::ostream& o) const override;
+
+ private:
+ void require_socket();
+ ssize_t receive(void * buff, size_t size, boost::system::error_code * error_code=nullptr, bool no_throw=false, boost::posix_time::time_duration timeout=boost::posix_time::seconds(10));
+ void check_deadline();
+ static void handle_receive(const boost::system::error_code& ec, std::size_t length,
+ boost::system::error_code* out_ec, std::size_t* out_length);
+ bool ping_int(boost::posix_time::time_duration timeout=boost::posix_time::milliseconds(1500));
+
+ std::shared_ptr<Protocol> m_proto;
+ std::string m_device_host;
+ int m_device_port;
+
+ std::unique_ptr<udp::socket> m_socket;
+ boost::asio::io_service m_io_service;
+ boost::asio::deadline_timer m_deadline;
+ udp::endpoint m_endpoint;
+ };
+
+ //
+ // General helpers
+ //
+
+ /**
+ * Enumerates all transports
+ */
+ void enumerate(t_transport_vect & res);
+
+ /**
+ * Transforms path to the transport
+ */
+ std::shared_ptr<Transport> transport(const std::string & path);
+
+ /**
+ * Transforms path to the particular transport
+ */
+ template<class t_transport>
+ std::shared_ptr<t_transport> transport_typed(const std::string & path){
+ auto t = transport(path);
+ if (!t){
+ return nullptr;
+ }
+
+ return std::dynamic_pointer_cast<t_transport>(t);
+ }
+
+ // Exception carries unexpected message being received
+ namespace exc {
+ class UnexpectedMessageException: public ProtocolException {
+ protected:
+ hw::trezor::messages::MessageType recvType;
+ std::shared_ptr<google::protobuf::Message> recvMsg;
+
+ public:
+ using ProtocolException::ProtocolException;
+ UnexpectedMessageException(): ProtocolException("Trezor returned unexpected message") {};
+ UnexpectedMessageException(hw::trezor::messages::MessageType recvType,
+ const std::shared_ptr<google::protobuf::Message> & recvMsg)
+ : recvType(recvType), recvMsg(recvMsg) {
+ reason = std::string("Trezor returned unexpected message: ") + std::to_string(recvType);
+ }
+ };
+ }
+
+ /**
+ * Throws corresponding failure exception.
+ */
+ [[ noreturn ]] void throw_failure_exception(const messages::common::Failure * failure);
+
+ /**
+ * Simple wrapper for write-read message exchange with expected message response type.
+ *
+ * @throws UnexpectedMessageException if the response message type is different than expected.
+ * Exception contains message type and the message itself.
+ */
+ template<class t_message>
+ std::shared_ptr<t_message>
+ exchange_message(Transport & transport, const google::protobuf::Message & req,
+ boost::optional<messages::MessageType> resp_type = boost::none)
+ {
+ // Require strictly protocol buffers response in the template.
+ BOOST_STATIC_ASSERT(boost::is_base_of<google::protobuf::Message, t_message>::value);
+
+ // Write the request
+ transport.write(req);
+
+ // Read the response
+ std::shared_ptr<google::protobuf::Message> msg_resp;
+ hw::trezor::messages::MessageType msg_resp_type;
+ transport.read(msg_resp, &msg_resp_type);
+
+ // Determine type of expected message response
+ messages::MessageType required_type = resp_type ? resp_type.get() : MessageMapper::get_message_wire_number<t_message>();
+
+ if (msg_resp_type == required_type) {
+ return message_ptr_retype<t_message>(msg_resp);
+ } else if (msg_resp_type == messages::MessageType_Failure){
+ throw_failure_exception(dynamic_cast<messages::common::Failure*>(msg_resp.get()));
+ } else {
+ throw exc::UnexpectedMessageException(msg_resp_type, msg_resp);
+ }
+ }
+
+ std::ostream& operator<<(std::ostream& o, hw::trezor::Transport const& t);
+ std::ostream& operator<<(std::ostream& o, std::shared_ptr<hw::trezor::Transport> const& t);
+}}
+
+
+#endif //MONERO_TRANSPORT_H
diff --git a/src/device_trezor/trezor/trezor_defs.hpp b/src/device_trezor/trezor/trezor_defs.hpp
new file mode 100644
index 000000000..951a8f802
--- /dev/null
+++ b/src/device_trezor/trezor/trezor_defs.hpp
@@ -0,0 +1,48 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if defined(HAVE_PROTOBUF) && !defined(WITHOUT_TREZOR)
+ #define WITH_DEVICE_TREZOR 1
+#else
+ #define WITH_DEVICE_TREZOR 0
+#endif
+
+#ifndef WITH_DEVICE_TREZOR_LITE
+#define WITH_DEVICE_TREZOR_LITE 0
+#endif
+
+// Avoids protobuf undefined macro warning
+#ifndef PROTOBUF_INLINE_NOT_IN_HEADERS
+#define PROTOBUF_INLINE_NOT_IN_HEADERS 0
+#endif
+
+// Fixes gcc7 problem with minor macro defined clashing with minor() field.
+#ifdef minor
+#undef minor
+#endif
diff --git a/src/mnemonics/electrum-words.cpp b/src/mnemonics/electrum-words.cpp
index 3d6338856..496e26858 100644
--- a/src/mnemonics/electrum-words.cpp
+++ b/src/mnemonics/electrum-words.cpp
@@ -215,7 +215,7 @@ namespace
}
boost::crc_32_type result;
result.process_bytes(trimmed_words.data(), trimmed_words.length());
- return result.checksum() % crypto::ElectrumWords::seed_length;
+ return result.checksum() % word_list.size();
}
/*!
diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl
index 9390626a8..a61b6107f 100644
--- a/src/p2p/net_node.inl
+++ b/src/p2p/net_node.inl
@@ -655,10 +655,14 @@ namespace nodetool
{
kill();
m_peerlist.deinit();
- m_net_server.deinit_server();
- // remove UPnP port mapping
- if(!m_no_igd)
- delete_upnp_port_mapping(m_listening_port);
+
+ if (!m_offline)
+ {
+ m_net_server.deinit_server();
+ // remove UPnP port mapping
+ if(!m_no_igd)
+ delete_upnp_port_mapping(m_listening_port);
+ }
return store_config();
}
//-----------------------------------------------------------------------------------
@@ -2042,7 +2046,7 @@ namespace nodetool
char lanAddress[64];
result = UPNP_GetValidIGD(deviceList, &urls, &igdData, lanAddress, sizeof lanAddress);
freeUPNPDevlist(deviceList);
- if (result != 0) {
+ if (result > 0) {
if (result == 1) {
std::ostringstream portString;
portString << port;
@@ -2088,7 +2092,7 @@ namespace nodetool
char lanAddress[64];
result = UPNP_GetValidIGD(deviceList, &urls, &igdData, lanAddress, sizeof lanAddress);
freeUPNPDevlist(deviceList);
- if (result != 0) {
+ if (result > 0) {
if (result == 1) {
std::ostringstream portString;
portString << port;
diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp
index 0d1789a38..181e89c45 100644
--- a/src/ringct/rctSigs.cpp
+++ b/src/ringct/rctSigs.cpp
@@ -44,6 +44,19 @@ using namespace std;
#define CHECK_AND_ASSERT_MES_L1(expr, ret, message) {if(!(expr)) {MCERROR("verify", message); return ret;}}
+namespace
+{
+ rct::Bulletproof make_dummy_bulletproof(size_t n_outs)
+ {
+ const rct::key I = rct::identity();
+ size_t nrl = 0;
+ while ((1u << nrl) < n_outs)
+ ++nrl;
+ nrl += 6;
+ return rct::Bulletproof{rct::keyV(n_outs, I), I, I, I, I, I, I, rct::keyV(nrl, I), rct::keyV(nrl, I), I, I, I};
+ }
+}
+
namespace rct {
Bulletproof proveRangeBulletproof(key &C, key &mask, uint64_t amount)
{
@@ -762,10 +775,20 @@ namespace rct {
if (range_proof_type == RangeProofPaddedBulletproof)
{
rct::keyV C, masks;
- rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, outamounts));
- #ifdef DBG
- CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof");
- #endif
+ if (hwdev.get_mode() == hw::device::TRANSACTION_CREATE_FAKE)
+ {
+ // use a fake bulletproof for speed
+ rv.p.bulletproofs.push_back(make_dummy_bulletproof(outamounts.size()));
+ C = rct::keyV(outamounts.size(), I);
+ masks = rct::keyV(outamounts.size(), I);
+ }
+ else
+ {
+ rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, outamounts));
+ #ifdef DBG
+ CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof");
+ #endif
+ }
for (i = 0; i < outamounts.size(); ++i)
{
rv.outPk[i].mask = rct::scalarmult8(C[i]);
@@ -782,10 +805,20 @@ namespace rct {
std::vector<uint64_t> batch_amounts(batch_size);
for (i = 0; i < batch_size; ++i)
batch_amounts[i] = outamounts[i + amounts_proved];
- rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, batch_amounts));
- #ifdef DBG
- CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof");
- #endif
+ if (hwdev.get_mode() == hw::device::TRANSACTION_CREATE_FAKE)
+ {
+ // use a fake bulletproof for speed
+ rv.p.bulletproofs.push_back(make_dummy_bulletproof(batch_amounts.size()));
+ C = rct::keyV(batch_amounts.size(), I);
+ masks = rct::keyV(batch_amounts.size(), I);
+ }
+ else
+ {
+ rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, batch_amounts));
+ #ifdef DBG
+ CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof");
+ #endif
+ }
for (i = 0; i < batch_size; ++i)
{
rv.outPk[i + amounts_proved].mask = rct::scalarmult8(C[i]);
diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h
index ae8bb91d7..b67a0b992 100644
--- a/src/ringct/rctSigs.h
+++ b/src/ringct/rctSigs.h
@@ -133,7 +133,7 @@ namespace rct {
xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i, hw::device &hwdev);
xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key & mask, hw::device &hwdev);
xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, hw::device &hwdev);
-
+ key get_pre_mlsag_hash(const rctSig &rv, hw::device &hwdev);
bool signMultisig(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key);
}
#endif /* RCTSIGS_H */
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index dc3860d60..574f6c126 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -213,23 +213,15 @@ namespace cryptonote
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
- static cryptonote::blobdata get_pruned_tx_blob(cryptonote::transaction &tx)
- {
- std::stringstream ss;
- binary_archive<true> ba(ss);
- bool r = tx.serialize_base(ba);
- CHECK_AND_ASSERT_MES(r, cryptonote::blobdata(), "Failed to serialize rct signatures base");
- return ss.str();
- }
- //------------------------------------------------------------------------------------------------------------------------------
- static cryptonote::blobdata get_pruned_tx_json(cryptonote::transaction &tx)
- {
- std::stringstream ss;
- json_archive<true> ar(ss);
- bool r = tx.serialize_base(ar);
- CHECK_AND_ASSERT_MES(r, cryptonote::blobdata(), "Failed to serialize rct signatures base");
- return ss.str();
- }
+ class pruned_transaction {
+ transaction& tx;
+ public:
+ pruned_transaction(transaction& tx) : tx(tx) {}
+ BEGIN_SERIALIZE_OBJECT()
+ bool r = tx.serialize_base(ar);
+ if (!r) return false;
+ END_SERIALIZE()
+ };
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_blocks(const COMMAND_RPC_GET_BLOCKS_FAST::request& req, COMMAND_RPC_GET_BLOCKS_FAST::response& res)
{
@@ -565,10 +557,11 @@ namespace cryptonote
crypto::hash tx_hash = *vhi++;
e.tx_hash = *txhi++;
- blobdata blob = req.prune ? get_pruned_tx_blob(tx) : t_serializable_object_to_blob(tx);
+ pruned_transaction pruned_tx{tx};
+ blobdata blob = req.prune ? t_serializable_object_to_blob(pruned_tx) : t_serializable_object_to_blob(tx);
e.as_hex = string_tools::buff_to_hex_nodelimer(blob);
if (req.decode_as_json)
- e.as_json = req.prune ? get_pruned_tx_json(tx) : obj_to_json_str(tx);
+ e.as_json = req.prune ? obj_to_json_str(pruned_tx) : obj_to_json_str(tx);
e.in_pool = pool_tx_hashes.find(tx_hash) != pool_tx_hashes.end();
if (e.in_pool)
{
diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h
index e8720cb14..8e8df7a52 100644
--- a/src/rpc/core_rpc_server_commands_defs.h
+++ b/src/rpc/core_rpc_server_commands_defs.h
@@ -783,9 +783,6 @@ namespace cryptonote
std::string tx_as_hex;
bool do_not_relay;
- request() {}
- explicit request(const transaction &);
-
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_as_hex)
KV_SERIALIZE_OPT(do_not_relay, false)
diff --git a/src/rpc/daemon_messages.h b/src/rpc/daemon_messages.h
index 70f4c50f1..d2014247c 100644
--- a/src/rpc/daemon_messages.h
+++ b/src/rpc/daemon_messages.h
@@ -68,7 +68,7 @@ class classname \
// NOTE: when using a type with multiple template parameters,
// replace any comma in the template specifier with the macro
// above, or the preprocessor will eat the comma in a bad way.
-#define RPC_MESSAGE_MEMBER(type, name) type name = type{}
+#define RPC_MESSAGE_MEMBER(type, name) type name = {}
namespace cryptonote
diff --git a/src/rpc/message.h b/src/rpc/message.h
index 16b8e92fc..56087b998 100644
--- a/src/rpc/message.h
+++ b/src/rpc/message.h
@@ -65,7 +65,7 @@ namespace rpc
static const char* STATUS_BAD_REQUEST;
static const char* STATUS_BAD_JSON;
- Message() : status(STATUS_OK) { }
+ Message() : status(STATUS_OK), rpc_version(0) { }
virtual ~Message() { }
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index 18b596662..58d4cdced 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -1801,6 +1801,27 @@ bool simple_wallet::version(const std::vector<std::string> &args)
return true;
}
+bool simple_wallet::cold_sign_tx(const std::vector<tools::wallet2::pending_tx>& ptx_vector, tools::wallet2::signed_tx_set &exported_txs, std::vector<cryptonote::address_parse_info> &dsts_info, std::function<bool(const tools::wallet2::signed_tx_set &)> accept_func)
+{
+ std::vector<std::string> tx_aux;
+
+ message_writer(console_color_white, false) << tr("Please confirm the transaction on the device");
+
+ m_wallet->cold_sign_tx(ptx_vector, exported_txs, dsts_info, tx_aux);
+
+ if (accept_func && !accept_func(exported_txs))
+ {
+ MERROR("Transactions rejected by callback");
+ return false;
+ }
+
+ // aux info
+ m_wallet->cold_tx_aux_import(exported_txs.ptx, tx_aux);
+
+ // import key images
+ return m_wallet->import_key_images(exported_txs.key_images);
+}
+
bool simple_wallet::set_always_confirm_transfers(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
const auto pwd_container = get_and_verify_password();
@@ -2253,6 +2274,33 @@ bool simple_wallet::set_ignore_fractional_outputs(const std::vector<std::string>
return true;
}
+bool simple_wallet::set_device_name(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
+{
+ const auto pwd_container = get_and_verify_password();
+ if (pwd_container)
+ {
+ if (args.size() == 0){
+ fail_msg_writer() << tr("Device name not specified");
+ return true;
+ }
+
+ m_wallet->device_name(args[0]);
+ bool r = false;
+ try {
+ r = m_wallet->reconnect_device();
+ if (!r){
+ fail_msg_writer() << tr("Device reconnect failed");
+ }
+
+ } catch(const std::exception & e){
+ MWARNING("Device reconnect failed: " << e.what());
+ fail_msg_writer() << tr("Device reconnect failed: ") << e.what();
+ }
+
+ }
+ return true;
+}
+
bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
if(args.empty())
@@ -2484,7 +2532,15 @@ simple_wallet::simple_wallet()
m_cmd_binder.set_handler("show_transfers",
boost::bind(&simple_wallet::show_transfers, this, _1),
tr("show_transfers [in|out|pending|failed|pool|coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]]"),
- tr("Show the incoming/outgoing transfers within an optional height range."));
+ // Seemingly broken formatting to compensate for the backslash before the quotes.
+ tr("Show the incoming/outgoing transfers within an optional height range.\n\n"
+ "Output format:\n"
+ "In or Coinbase: Block Number, \"block\"|\"in\", Time, Amount, Transaction Hash, Payment ID, Subaddress Index, \"-\", Note\n"
+ "Out: Block Number, \"out\", Time, Amount*, Transaction Hash, Payment ID, Fee, Destinations, Input addresses**, \"-\", Note\n"
+ "Pool: \"pool\", \"in\", Time, Amount, Transaction Hash, Payment Id, Subaddress Index, \"-\", Note, Double Spend Note\n"
+ "Pending or Failed: \"failed\"|\"pending\", \"out\", Time, Amount*, Transaction Hash, Payment ID, Fee, Input addresses**, \"-\", Note\n\n"
+ "* Excluding change and fee.\n"
+ "** Set of address indices used as inputs in this transfer."));
m_cmd_binder.set_handler("unspent_outputs",
boost::bind(&simple_wallet::unspent_outputs, this, _1),
tr("unspent_outputs [index=<N1>[,<N2>,...]] [<min_amount> [<max_amount>]]"),
@@ -2529,6 +2585,10 @@ simple_wallet::simple_wallet()
boost::bind(&simple_wallet::import_key_images, this, _1),
tr("import_key_images <file>"),
tr("Import a signed key images list and verify their spent status."));
+ m_cmd_binder.set_handler("hw_key_images_sync",
+ boost::bind(&simple_wallet::hw_key_images_sync, this, _1),
+ tr("hw_key_images_sync"),
+ tr("Synchronizes key images with the hw wallet."));
m_cmd_binder.set_handler("hw_reconnect",
boost::bind(&simple_wallet::hw_reconnect, this, _1),
tr("hw_reconnect"),
@@ -2720,6 +2780,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
CHECK_SIMPLE_VARIABLE("subaddress-lookahead", set_subaddress_lookahead, tr("<major>:<minor>"));
CHECK_SIMPLE_VARIABLE("segregation-height", set_segregation_height, tr("unsigned integer"));
CHECK_SIMPLE_VARIABLE("ignore-fractional-outputs", set_ignore_fractional_outputs, tr("0 or 1"));
+ CHECK_SIMPLE_VARIABLE("device-name", set_device_name, tr("<device_name[:device_spec]>"));
}
fail_msg_writer() << tr("set: unrecognized argument(s)");
return true;
@@ -4715,8 +4776,6 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
if (!try_connect_to_daemon())
return true;
- SCOPED_WALLET_UNLOCK();
-
std::vector<std::string> local_args = args_;
std::set<uint32_t> subaddr_indices;
@@ -4828,12 +4887,14 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
local_args.pop_back();
}
+ vector<cryptonote::address_parse_info> dsts_info;
vector<cryptonote::tx_destination_entry> dsts;
size_t num_subaddresses = 0;
for (size_t i = 0; i < local_args.size(); )
{
+ dsts_info.emplace_back();
+ cryptonote::address_parse_info & info = dsts_info.back();
cryptonote::tx_destination_entry de;
- cryptonote::address_parse_info info;
bool r = true;
// check for a URI
@@ -4936,6 +4997,8 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
}
}
+ SCOPED_WALLET_UNLOCK();
+
try
{
// figure out what tx will be necessary
@@ -5115,6 +5178,28 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "multisig_monero_tx";
}
}
+ else if (m_wallet->get_account().get_device().has_tx_cold_sign())
+ {
+ try
+ {
+ tools::wallet2::signed_tx_set signed_tx;
+ if (!cold_sign_tx(ptx_vector, signed_tx, dsts_info, [&](const tools::wallet2::signed_tx_set &tx){ return accept_loaded_tx(tx); })){
+ fail_msg_writer() << tr("Failed to cold sign transaction with HW wallet");
+ return true;
+ }
+
+ commit_or_save(signed_tx.ptx, m_do_not_relay);
+ }
+ catch (const std::exception& e)
+ {
+ handle_transfer_exception(std::current_exception(), m_wallet->is_trusted_daemon());
+ }
+ catch (...)
+ {
+ LOG_ERROR("Unknown error");
+ fail_msg_writer() << tr("unknown error");
+ }
+ }
else if (m_wallet->watch_only())
{
bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx");
@@ -5537,6 +5622,31 @@ bool simple_wallet::sweep_main(uint64_t below, bool locked, const std::vector<st
success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "multisig_monero_tx";
}
}
+ else if (m_wallet->get_account().get_device().has_tx_cold_sign())
+ {
+ try
+ {
+ tools::wallet2::signed_tx_set signed_tx;
+ std::vector<cryptonote::address_parse_info> dsts_info;
+ dsts_info.push_back(info);
+
+ if (!cold_sign_tx(ptx_vector, signed_tx, dsts_info, [&](const tools::wallet2::signed_tx_set &tx){ return accept_loaded_tx(tx); })){
+ fail_msg_writer() << tr("Failed to cold sign transaction with HW wallet");
+ return true;
+ }
+
+ commit_or_save(signed_tx.ptx, m_do_not_relay);
+ }
+ catch (const std::exception& e)
+ {
+ handle_transfer_exception(std::current_exception(), m_wallet->is_trusted_daemon());
+ }
+ catch (...)
+ {
+ LOG_ERROR("Unknown error");
+ fail_msg_writer() << tr("unknown error");
+ }
+ }
else if (m_wallet->watch_only())
{
bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx");
@@ -7050,12 +7160,6 @@ bool simple_wallet::run()
void simple_wallet::stop()
{
m_cmd_binder.stop_handling();
-
- m_idle_run.store(false, std::memory_order_relaxed);
- m_wallet->stop();
- // make the background refresh thread quit
- boost::unique_lock<boost::mutex> lock(m_idle_mutex);
- m_idle_cond.notify_one();
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
@@ -7792,6 +7896,48 @@ bool simple_wallet::import_key_images(const std::vector<std::string> &args)
return true;
}
//----------------------------------------------------------------------------------------------------
+bool simple_wallet::hw_key_images_sync(const std::vector<std::string> &args)
+{
+ if (!m_wallet->key_on_device())
+ {
+ fail_msg_writer() << tr("command only supported by HW wallet");
+ return true;
+ }
+ if (!m_wallet->get_account().get_device().has_ki_cold_sync())
+ {
+ fail_msg_writer() << tr("hw wallet does not support cold KI sync");
+ return true;
+ }
+ if (!m_wallet->is_trusted_daemon())
+ {
+ fail_msg_writer() << tr("this command requires a trusted daemon. Enable with --trusted-daemon");
+ return true;
+ }
+
+ LOCK_IDLE_SCOPE();
+ try
+ {
+ message_writer(console_color_white, false) << tr("Please confirm the key image sync on the device");
+
+ uint64_t spent = 0, unspent = 0;
+ uint64_t height = m_wallet->cold_key_image_sync(spent, unspent);
+ if (height > 0)
+ {
+ success_msg_writer() << tr("Signed key images imported to height ") << height << ", "
+ << print_money(spent) << tr(" spent, ") << print_money(unspent) << tr(" unspent");
+ } else {
+ fail_msg_writer() << tr("Failed to import key images");
+ }
+ }
+ catch (const std::exception &e)
+ {
+ fail_msg_writer() << tr("Failed to import key images: ") << e.what();
+ return true;
+ }
+
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
bool simple_wallet::hw_reconnect(const std::vector<std::string> &args)
{
if (!m_wallet->key_on_device())
diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h
index 39b715b73..7d813ceb0 100644
--- a/src/simplewallet/simplewallet.h
+++ b/src/simplewallet/simplewallet.h
@@ -139,6 +139,7 @@ namespace cryptonote
bool set_subaddress_lookahead(const std::vector<std::string> &args = std::vector<std::string>());
bool set_segregation_height(const std::vector<std::string> &args = std::vector<std::string>());
bool set_ignore_fractional_outputs(const std::vector<std::string> &args = std::vector<std::string>());
+ bool set_device_name(const std::vector<std::string> &args = std::vector<std::string>());
bool help(const std::vector<std::string> &args = std::vector<std::string>());
bool start_mining(const std::vector<std::string> &args);
bool stop_mining(const std::vector<std::string> &args);
@@ -200,6 +201,7 @@ namespace cryptonote
bool verify(const std::vector<std::string> &args);
bool export_key_images(const std::vector<std::string> &args);
bool import_key_images(const std::vector<std::string> &args);
+ bool hw_key_images_sync(const std::vector<std::string> &args);
bool hw_reconnect(const std::vector<std::string> &args);
bool export_outputs(const std::vector<std::string> &args);
bool import_outputs(const std::vector<std::string> &args);
@@ -224,6 +226,7 @@ namespace cryptonote
bool unblackball(const std::vector<std::string>& args);
bool blackballed(const std::vector<std::string>& args);
bool version(const std::vector<std::string>& args);
+ bool cold_sign_tx(const std::vector<tools::wallet2::pending_tx>& ptx_vector, tools::wallet2::signed_tx_set &exported_txs, std::vector<cryptonote::address_parse_info> &dsts_info, std::function<bool(const tools::wallet2::signed_tx_set &)> accept_func);
uint64_t get_daemon_blockchain_height(std::string& err);
bool try_connect_to_daemon(bool silent = false, uint32_t* version = nullptr);
diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt
index be10b9f62..4932dd4b6 100644
--- a/src/wallet/CMakeLists.txt
+++ b/src/wallet/CMakeLists.txt
@@ -57,6 +57,7 @@ target_link_libraries(wallet
common
cryptonote_core
mnemonics
+ device_trezor
${LMDB_LIBRARY}
${Boost_CHRONO_LIBRARY}
${Boost_SERIALIZATION_LIBRARY}
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
index 1b4370c36..ddf2d74ff 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -381,6 +381,7 @@ WalletImpl::WalletImpl(NetworkType nettype, uint64_t kdf_rounds)
, m_synchronized(false)
, m_rebuildWalletCache(false)
, m_is_connected(false)
+ , m_refreshShouldRescan(false)
{
m_wallet.reset(new tools::wallet2(static_cast<cryptonote::network_type>(nettype), kdf_rounds, true));
m_history.reset(new TransactionHistoryImpl(this));
@@ -1011,6 +1012,20 @@ void WalletImpl::refreshAsync()
m_refreshCV.notify_one();
}
+bool WalletImpl::rescanBlockchain()
+{
+ clearStatus();
+ m_refreshShouldRescan = true;
+ doRefresh();
+ return status() == Status_Ok;
+}
+
+void WalletImpl::rescanBlockchainAsync()
+{
+ m_refreshShouldRescan = true;
+ refreshAsync();
+}
+
void WalletImpl::setAutoRefreshInterval(int millis)
{
if (millis > MAX_REFRESH_INTERVAL_MILLIS) {
@@ -1984,6 +1999,7 @@ void WalletImpl::refreshThreadFunc()
LOG_PRINT_L3(__FUNCTION__ << ": refresh lock acquired...");
LOG_PRINT_L3(__FUNCTION__ << ": m_refreshEnabled: " << m_refreshEnabled);
LOG_PRINT_L3(__FUNCTION__ << ": m_status: " << status());
+ LOG_PRINT_L3(__FUNCTION__ << ": m_refreshShouldRescan: " << m_refreshShouldRescan);
if (m_refreshEnabled) {
LOG_PRINT_L3(__FUNCTION__ << ": refreshing...");
doRefresh();
@@ -1994,12 +2010,16 @@ void WalletImpl::refreshThreadFunc()
void WalletImpl::doRefresh()
{
+ bool rescan = m_refreshShouldRescan.exchange(false);
// synchronizing async and sync refresh calls
boost::lock_guard<boost::mutex> guarg(m_refreshMutex2);
- try {
+ do try {
+ LOG_PRINT_L3(__FUNCTION__ << ": doRefresh, rescan = "<<rescan);
// Syncing daemon and refreshing wallet simultaneously is very resource intensive.
// Disable refresh if wallet is disconnected or daemon isn't synced.
if (m_wallet->light_wallet() || daemonSynced()) {
+ if(rescan)
+ m_wallet->rescan_blockchain(false);
m_wallet->refresh(trustedDaemon());
if (!m_synchronized) {
m_synchronized = true;
@@ -2016,7 +2036,9 @@ void WalletImpl::doRefresh()
}
} catch (const std::exception &e) {
setStatusError(e.what());
- }
+ break;
+ }while(!rescan && (rescan=m_refreshShouldRescan.exchange(false))); // repeat if not rescanned and rescan was requested
+
if (m_wallet2Callback->getListener()) {
m_wallet2Callback->getListener()->refreshed();
}
diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h
index 6d343888b..b4637b8e6 100644
--- a/src/wallet/api/wallet.h
+++ b/src/wallet/api/wallet.h
@@ -114,6 +114,8 @@ public:
bool synchronized() const override;
bool refresh() override;
void refreshAsync() override;
+ bool rescanBlockchain() override;
+ void rescanBlockchainAsync() override;
void setAutoRefreshInterval(int millis) override;
int autoRefreshInterval() const override;
void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) override;
@@ -232,6 +234,7 @@ private:
std::atomic<bool> m_refreshEnabled;
std::atomic<bool> m_refreshThreadDone;
std::atomic<int> m_refreshIntervalMillis;
+ std::atomic<bool> m_refreshShouldRescan;
// synchronizing refresh loop;
boost::mutex m_refreshMutex;
diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h
index ec1a84877..82627de29 100644
--- a/src/wallet/api/wallet2_api.h
+++ b/src/wallet/api/wallet2_api.h
@@ -644,6 +644,17 @@ struct Wallet
virtual void refreshAsync() = 0;
/**
+ * @brief rescanBlockchain - rescans the wallet, updating transactions from daemon
+ * @return - true if refreshed successfully;
+ */
+ virtual bool rescanBlockchain() = 0;
+
+ /**
+ * @brief rescanBlockchainAsync - rescans wallet asynchronously, starting from genesys
+ */
+ virtual void rescanBlockchainAsync() = 0;
+
+ /**
* @brief setAutoRefreshInterval - setup interval for automatic refresh.
* @param seconds - interval in millis. if zero or less than zero - automatic refresh disabled;
*/
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 21793b58d..794d1d421 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -71,6 +71,8 @@ using namespace epee;
#include "common/notify.h"
#include "ringct/rctSigs.h"
#include "ringdb.h"
+#include "device/device_cold.hpp"
+#include "device_trezor/device_trezor.hpp"
extern "C"
{
@@ -123,6 +125,8 @@ using namespace cryptonote;
#define FIRST_REFRESH_GRANULARITY 1024
+#define GAMMA_PICK_HALF_WINDOW 5
+
static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1";
static const std::string MULTISIG_EXTRA_INFO_MAGIC = "MultisigxV1";
@@ -766,6 +770,11 @@ uint32_t get_subaddress_clamped_sum(uint32_t idx, uint32_t extra)
return idx + extra;
}
+static void setup_shim(hw::wallet_shim * shim, tools::wallet2 * wallet)
+{
+ shim->get_tx_pub_key_from_received_outs = boost::bind(&tools::wallet2::get_tx_pub_key_from_received_outs, wallet, _1);
+}
+
//-----------------------------------------------------------------
} //namespace
@@ -811,6 +820,7 @@ wallet_keys_unlocker::~wallet_keys_unlocker()
wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended):
m_multisig_rescan_info(NULL),
m_multisig_rescan_k(NULL),
+ m_upper_transaction_weight_limit(0),
m_run(true),
m_callback(0),
m_trusted_daemon(false),
@@ -843,6 +853,9 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended):
m_is_initialized(false),
m_kdf_rounds(kdf_rounds),
is_old_file_format(false),
+ m_watch_only(false),
+ m_multisig(false),
+ m_multisig_threshold(0),
m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex),
m_subaddress_lookahead_major(SUBADDRESS_LOOKAHEAD_MAJOR),
m_subaddress_lookahead_minor(SUBADDRESS_LOOKAHEAD_MINOR),
@@ -1054,8 +1067,9 @@ bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeabl
bool wallet2::reconnect_device()
{
bool r = true;
- hw::device &hwdev = hw::get_device(m_device_name);
+ hw::device &hwdev = lookup_device(m_device_name);
hwdev.set_name(m_device_name);
+ hwdev.set_network_type(m_nettype);
r = hwdev.init();
if (!r){
LOG_PRINT_L2("Could not init device");
@@ -2938,6 +2952,7 @@ bool wallet2::deinit()
{
m_is_initialized=false;
unlock_keys_file();
+ m_account.deinit();
return true;
}
//----------------------------------------------------------------------------------------------------
@@ -3407,13 +3422,20 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
r = epee::serialization::load_t_from_binary(m_account, account_data);
THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password);
- if (m_key_device_type == hw::device::device_type::LEDGER) {
+ if (m_key_device_type == hw::device::device_type::LEDGER || m_key_device_type == hw::device::device_type::TREZOR) {
LOG_PRINT_L0("Account on device. Initing device...");
- hw::device &hwdev = hw::get_device(m_device_name);
- hwdev.set_name(m_device_name);
- hwdev.init();
- hwdev.connect();
+ hw::device &hwdev = lookup_device(m_device_name);
+ THROW_WALLET_EXCEPTION_IF(!hwdev.set_name(m_device_name), error::wallet_internal_error, "Could not set device name " + m_device_name);
+ hwdev.set_network_type(m_nettype);
+ THROW_WALLET_EXCEPTION_IF(!hwdev.init(), error::wallet_internal_error, "Could not initialize the device " + m_device_name);
+ THROW_WALLET_EXCEPTION_IF(!hwdev.connect(), error::wallet_internal_error, "Could not connect to the device " + m_device_name);
m_account.set_device(hwdev);
+
+ account_public_address device_account_public_address;
+ THROW_WALLET_EXCEPTION_IF(!hwdev.get_public_address(device_account_public_address), error::wallet_internal_error, "Cannot get a device address");
+ THROW_WALLET_EXCEPTION_IF(device_account_public_address != m_account.get_keys().m_account_address, error::wallet_internal_error, "Device wallet does not match wallet address. "
+ "Device address: " + cryptonote::get_account_address_as_str(m_nettype, false, device_account_public_address) +
+ ", wallet address: " + m_account.get_public_address_str(m_nettype));
LOG_PRINT_L0("Device inited...");
} else if (key_on_device()) {
THROW_WALLET_EXCEPTION(error::wallet_internal_error, "hardware device not supported");
@@ -3444,7 +3466,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
const cryptonote::account_keys& keys = m_account.get_keys();
hw::device &hwdev = m_account.get_device();
r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key);
- if(!m_watch_only && !m_multisig)
+ if(!m_watch_only && !m_multisig && hwdev.device_protocol() != hw::device::PROTOCOL_COLD)
r = r && hwdev.verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key);
THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password);
@@ -3468,7 +3490,7 @@ bool wallet2::verify_password(const epee::wipeable_string& password)
{
// this temporary unlocking is necessary for Windows (otherwise the file couldn't be loaded).
unlock_keys_file();
- bool r = verify_password(m_keys_file, password, m_watch_only || m_multisig, m_account.get_device(), m_kdf_rounds);
+ bool r = verify_password(m_keys_file, password, m_account.get_device().device_protocol() == hw::device::PROTOCOL_COLD || m_watch_only || m_multisig, m_account.get_device(), m_kdf_rounds);
lock_keys_file();
return r;
}
@@ -3908,8 +3930,9 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p
THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file);
}
- auto &hwdev = hw::get_device(device_name);
+ auto &hwdev = lookup_device(device_name);
hwdev.set_name(device_name);
+ hwdev.set_network_type(m_nettype);
m_account.create_from_device(hwdev);
m_key_device_type = m_account.get_device().get_type();
@@ -5610,6 +5633,10 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin
ptx.construction_data = sd;
txs.push_back(ptx);
+
+ // add tx keys only to ptx
+ txs.back().tx_key = tx_key;
+ txs.back().additional_tx_keys = additional_tx_keys;
}
// add key images
@@ -5771,22 +5798,8 @@ bool wallet2::parse_tx_from_str(const std::string &signed_tx_st, std::vector<too
}
// import key images
- if (signed_txs.key_images.size() > m_transfers.size())
- {
- LOG_PRINT_L1("More key images returned that we know outputs for");
- return false;
- }
- for (size_t i = 0; i < signed_txs.key_images.size(); ++i)
- {
- transfer_details &td = m_transfers[i];
- if (td.m_key_image_known && !td.m_key_image_partial && td.m_key_image != signed_txs.key_images[i])
- LOG_PRINT_L0("WARNING: imported key image differs from previously known key image at index " << i << ": trusting imported one");
- td.m_key_image = signed_txs.key_images[i];
- m_key_images[m_transfers[i].m_key_image] = i;
- td.m_key_image_known = true;
- td.m_key_image_partial = false;
- m_pub_keys[m_transfers[i].get_public_key()] = i;
- }
+ bool r = import_key_images(signed_txs.key_images);
+ if (!r) return false;
ptx = signed_txs.ptx;
@@ -6336,6 +6349,19 @@ crypto::chacha_key wallet2::get_ringdb_key()
return *m_ringdb_key;
}
+void wallet2::register_devices(){
+ hw::trezor::register_all();
+}
+
+hw::device& wallet2::lookup_device(const std::string & device_descriptor){
+ if (!m_devices_registered){
+ m_devices_registered = true;
+ register_devices();
+ }
+
+ return hw::get_device(device_descriptor);
+}
+
bool wallet2::add_rings(const crypto::chacha_key &key, const cryptonote::transaction_prefix &tx)
{
if (!m_ringdb)
@@ -6795,10 +6821,29 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
error::get_output_distribution, "Decreasing offsets in rct distribution: " +
std::to_string(block_offset) + ": " + std::to_string(rct_offsets[block_offset]) + ", " +
std::to_string(block_offset + 1) + ": " + std::to_string(rct_offsets[block_offset + 1]));
- uint64_t n_rct = rct_offsets[block_offset + 1] - rct_offsets[block_offset];
+ uint64_t first_block_offset = block_offset, last_block_offset = block_offset;
+ for (size_t half_window = 0; half_window < GAMMA_PICK_HALF_WINDOW; ++half_window)
+ {
+ // end when we have a non empty block
+ uint64_t cum0 = first_block_offset > 0 ? rct_offsets[first_block_offset] - rct_offsets[first_block_offset - 1] : rct_offsets[0];
+ if (cum0 > 1)
+ break;
+ uint64_t cum1 = last_block_offset > 0 ? rct_offsets[last_block_offset] - rct_offsets[last_block_offset - 1] : rct_offsets[0];
+ if (cum1 > 1)
+ break;
+ if (first_block_offset == 0 && last_block_offset >= rct_offsets.size() - 2)
+ break;
+ // expand up to bounds
+ if (first_block_offset > 0)
+ --first_block_offset;
+ if (last_block_offset < rct_offsets.size() - 1)
+ ++last_block_offset;
+ }
+ const uint64_t n_rct = rct_offsets[last_block_offset] - (first_block_offset == 0 ? 0 : rct_offsets[first_block_offset - 1]);
if (n_rct == 0)
return rct_offsets[block_offset] ? rct_offsets[block_offset] - 1 : 0;
- return rct_offsets[block_offset] + crypto::rand<uint64_t>() % n_rct;
+ MDEBUG("Picking 1/" << n_rct << " in " << (last_block_offset - first_block_offset + 1) << " blocks centered around " << block_offset);
+ return rct_offsets[first_block_offset] + crypto::rand<uint64_t>() % n_rct;
};
size_t num_selected_transfers = 0;
@@ -8404,12 +8449,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
}
}
- // shuffle & sort output indices
+ // sort output indices
{
- std::random_device rd;
- std::mt19937 g(rd());
- std::shuffle(unused_transfers_indices_per_subaddr.begin(), unused_transfers_indices_per_subaddr.end(), g);
- std::shuffle(unused_dust_indices_per_subaddr.begin(), unused_dust_indices_per_subaddr.end(), g);
auto sort_predicate = [&unlocked_balance_per_subaddr] (const std::pair<uint32_t, std::vector<size_t>>& x, const std::pair<uint32_t, std::vector<size_t>>& y)
{
return unlocked_balance_per_subaddr[x.first] > unlocked_balance_per_subaddr[y.first];
@@ -8601,7 +8642,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
cryptonote::transaction test_tx;
pending_tx test_ptx;
- needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, base_fee, fee_multiplier, fee_multiplier);
+ needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, base_fee, fee_multiplier, fee_quantization_mask);
uint64_t inputs = 0, outputs = needed_fee;
for (size_t idx: tx.selected_transfers) inputs += m_transfers[idx].amount();
@@ -9055,6 +9096,62 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
return ptx_vector;
}
//----------------------------------------------------------------------------------------------------
+void wallet2::cold_tx_aux_import(const std::vector<pending_tx> & ptx, const std::vector<std::string> & tx_device_aux)
+{
+ CHECK_AND_ASSERT_THROW_MES(ptx.size() == tx_device_aux.size(), "TX aux has invalid size");
+ for (size_t i = 0; i < ptx.size(); ++i){
+ crypto::hash txid;
+ txid = get_transaction_hash(ptx[i].tx);
+ set_tx_device_aux(txid, tx_device_aux[i]);
+ }
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::cold_sign_tx(const std::vector<pending_tx>& ptx_vector, signed_tx_set &exported_txs, std::vector<cryptonote::address_parse_info> &dsts_info, std::vector<std::string> & tx_device_aux)
+{
+ auto & hwdev = get_account().get_device();
+ if (!hwdev.has_tx_cold_sign()){
+ throw std::invalid_argument("Device does not support cold sign protocol");
+ }
+
+ unsigned_tx_set txs;
+ for (auto &tx: ptx_vector)
+ {
+ txs.txes.push_back(get_construction_data_with_decrypted_short_payment_id(tx, m_account.get_device()));
+ }
+ txs.transfers = m_transfers;
+
+ auto dev_cold = dynamic_cast<::hw::device_cold*>(&hwdev);
+ CHECK_AND_ASSERT_THROW_MES(dev_cold, "Device does not implement cold signing interface");
+
+ hw::tx_aux_data aux_data;
+ hw::wallet_shim wallet_shim;
+ setup_shim(&wallet_shim, this);
+ aux_data.tx_recipients = dsts_info;
+ dev_cold->tx_sign(&wallet_shim, txs, exported_txs, aux_data);
+ tx_device_aux = aux_data.tx_device_aux;
+
+ MDEBUG("Signed tx data from hw: " << exported_txs.ptx.size() << " transactions");
+ for (auto &c_ptx: exported_txs.ptx) LOG_PRINT_L0(cryptonote::obj_to_json_str(c_ptx.tx));
+}
+//----------------------------------------------------------------------------------------------------
+uint64_t wallet2::cold_key_image_sync(uint64_t &spent, uint64_t &unspent) {
+ auto & hwdev = get_account().get_device();
+ if (!hwdev.has_ki_cold_sync()){
+ throw std::invalid_argument("Device does not support cold ki sync protocol");
+ }
+
+ auto dev_cold = dynamic_cast<::hw::device_cold*>(&hwdev);
+ CHECK_AND_ASSERT_THROW_MES(dev_cold, "Device does not implement cold signing interface");
+
+ std::vector<std::pair<crypto::key_image, crypto::signature>> ski;
+ hw::wallet_shim wallet_shim;
+ setup_shim(&wallet_shim, this);
+
+ dev_cold->ki_sync(&wallet_shim, m_transfers, ski);
+
+ return import_key_images(ski, spent, unspent);
+}
+//----------------------------------------------------------------------------------------------------
void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height) const
{
boost::optional<std::string> result = m_node_rpc_proxy.get_earliest_height(version, earliest_height);
@@ -10211,6 +10308,19 @@ std::string wallet2::get_tx_note(const crypto::hash &txid) const
return i->second;
}
+void wallet2::set_tx_device_aux(const crypto::hash &txid, const std::string &aux)
+{
+ m_tx_device[txid] = aux;
+}
+
+std::string wallet2::get_tx_device_aux(const crypto::hash &txid) const
+{
+ std::unordered_map<crypto::hash, std::string>::const_iterator i = m_tx_device.find(txid);
+ if (i == m_tx_device.end())
+ return std::string();
+ return i->second;
+}
+
void wallet2::set_attribute(const std::string &key, const std::string &value)
{
m_attributes[key] = value;
@@ -10558,7 +10668,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
+ boost::lexical_cast<std::string>(signed_key_images.size()) + ", key image " + epee::string_tools::pod_to_hex(key_image));
THROW_WALLET_EXCEPTION_IF(!crypto::check_ring_signature((const crypto::hash&)key_image, key_image, pkeys, &signature),
- error::wallet_internal_error, "Signature check failed: input " + boost::lexical_cast<std::string>(n) + "/"
+ error::signature_check_failed, boost::lexical_cast<std::string>(n) + "/"
+ boost::lexical_cast<std::string>(signed_key_images.size()) + ", key image " + epee::string_tools::pod_to_hex(key_image)
+ ", signature " + epee::string_tools::pod_to_hex(signature) + ", pubkey " + epee::string_tools::pod_to_hex(*pkeys[0]));
@@ -10756,6 +10866,29 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
return m_transfers[signed_key_images.size() - 1].m_block_height;
}
+
+bool wallet2::import_key_images(std::vector<crypto::key_image> key_images)
+{
+ if (key_images.size() > m_transfers.size())
+ {
+ LOG_PRINT_L1("More key images returned that we know outputs for");
+ return false;
+ }
+ for (size_t i = 0; i < key_images.size(); ++i)
+ {
+ transfer_details &td = m_transfers[i];
+ if (td.m_key_image_known && !td.m_key_image_partial && td.m_key_image != key_images[i])
+ LOG_PRINT_L0("WARNING: imported key image differs from previously known key image at index " << i << ": trusting imported one");
+ td.m_key_image = key_images[i];
+ m_key_images[m_transfers[i].m_key_image] = i;
+ td.m_key_image_known = true;
+ td.m_key_image_partial = false;
+ m_pub_keys[m_transfers[i].get_public_key()] = i;
+ }
+
+ return true;
+}
+
wallet2::payment_container wallet2::export_payments() const
{
payment_container payments;
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 680196f01..7d78a3fee 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -54,6 +54,7 @@
#include "ringct/rctTypes.h"
#include "ringct/rctOps.h"
#include "checkpoints/checkpoints.h"
+#include "serialization/pair.h"
#include "wallet_errors.h"
#include "common/password.h"
@@ -764,6 +765,9 @@ namespace tools
std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices);
std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra);
std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra);
+ void cold_tx_aux_import(const std::vector<pending_tx>& ptx, const std::vector<std::string>& tx_device_aux);
+ void cold_sign_tx(const std::vector<pending_tx>& ptx_vector, signed_tx_set &exported_txs, std::vector<cryptonote::address_parse_info> &dsts_info, std::vector<std::string> & tx_device_aux);
+ uint64_t cold_key_image_sync(uint64_t &spent, uint64_t &unspent);
bool load_multisig_tx(cryptonote::blobdata blob, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func = NULL);
bool load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function<bool(const multisig_tx_set&)> accept_func = NULL);
bool sign_multisig_tx_from_file(const std::string &filename, std::vector<crypto::hash> &txids, std::function<bool(const multisig_tx_set&)> accept_func);
@@ -892,6 +896,9 @@ namespace tools
if(ver < 25)
return;
a & m_last_block_reward;
+ if(ver < 26)
+ return;
+ a & m_tx_device;
}
/*!
@@ -1020,6 +1027,9 @@ namespace tools
void set_tx_note(const crypto::hash &txid, const std::string &note);
std::string get_tx_note(const crypto::hash &txid) const;
+ void set_tx_device_aux(const crypto::hash &txid, const std::string &aux);
+ std::string get_tx_device_aux(const crypto::hash &txid) const;
+
void set_description(const std::string &description);
std::string get_description() const;
@@ -1074,6 +1084,8 @@ namespace tools
std::vector<std::pair<crypto::key_image, crypto::signature>> export_key_images() const;
uint64_t import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, uint64_t &spent, uint64_t &unspent, bool check_spent = true);
uint64_t import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent);
+ bool import_key_images(std::vector<crypto::key_image> key_images);
+ crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const;
void update_pool_state(bool refreshed = false);
void remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes);
@@ -1236,7 +1248,6 @@ namespace tools
void set_unspent(size_t idx);
void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count);
bool tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const;
- crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const;
bool should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector<size_t> &unused_transfers_indices, const std::vector<size_t> &unused_dust_indices) const;
std::vector<size_t> get_only_rct(const std::vector<size_t> &unused_dust_indices, const std::vector<size_t> &unused_transfers_indices) const;
void scan_output(const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs);
@@ -1253,6 +1264,9 @@ namespace tools
crypto::chacha_key get_ringdb_key();
void setup_keys(const epee::wipeable_string &password);
+ void register_devices();
+ hw::device& lookup_device(const std::string & device_descriptor);
+
bool get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution);
uint64_t get_segregation_fork_height() const;
@@ -1347,6 +1361,9 @@ namespace tools
size_t m_subaddress_lookahead_major, m_subaddress_lookahead_minor;
std::string m_device_name;
+ // Aux transaction data from device
+ std::unordered_map<crypto::hash, std::string> m_tx_device;
+
// Light wallet
bool m_light_wallet; /* sends view key to daemon for scanning */
uint64_t m_light_wallet_scanned_block_height;
@@ -1373,11 +1390,12 @@ namespace tools
boost::optional<epee::wipeable_string> m_encrypt_keys_after_refresh;
bool m_unattended;
+ bool m_devices_registered;
std::shared_ptr<tools::Notify> m_tx_notify;
};
}
-BOOST_CLASS_VERSION(tools::wallet2, 25)
+BOOST_CLASS_VERSION(tools::wallet2, 26)
BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 9)
BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1)
BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0)
diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h
index bc518d04a..b3141985d 100644
--- a/src/wallet/wallet_errors.h
+++ b/src/wallet/wallet_errors.h
@@ -72,6 +72,7 @@ namespace tools
// tx_parse_error
// get_tx_pool_error
// out_of_hashchain_bounds_error
+ // signature_check_failed
// transfer_error *
// get_outs_general_error
// not_enough_unlocked_money
@@ -418,6 +419,14 @@ namespace tools
std::string to_string() const { return refresh_error::to_string(); }
};
//----------------------------------------------------------------------------------------------------
+ struct signature_check_failed : public wallet_logic_error
+ {
+ explicit signature_check_failed(std::string&& loc, const std::string& message)
+ : wallet_logic_error(std::move(loc), "Signature check failed " + message)
+ {
+ }
+ };
+ //----------------------------------------------------------------------------------------------------
struct transfer_error : public wallet_logic_error
{
protected:
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index 1b63d65b6..5e6100dfd 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -981,6 +981,8 @@ namespace tools
for (auto &ptx: ptxs)
{
res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)));
+ if (req.get_tx_keys)
+ res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key));
}
if (req.export_raw)
@@ -994,6 +996,171 @@ namespace tools
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_describe_transfer(const wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::response& res, epee::json_rpc::error& er)
+ {
+ if (!m_wallet) return not_open(er);
+ if (m_restricted)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_DENIED;
+ er.message = "Command unavailable in restricted mode.";
+ return false;
+ }
+ if (m_wallet->key_on_device())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "command not supported by HW wallet";
+ return false;
+ }
+ if(m_wallet->watch_only())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY;
+ er.message = "command not supported by watch-only wallet";
+ return false;
+ }
+
+ tools::wallet2::unsigned_tx_set exported_txs;
+ try
+ {
+ cryptonote::blobdata blob;
+ if (!epee::string_tools::parse_hexstr_to_binbuff(req.unsigned_txset, blob))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_HEX;
+ er.message = "Failed to parse hex.";
+ return false;
+ }
+ if(!m_wallet->parse_unsigned_tx_from_str(blob, exported_txs))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA;
+ er.message = "cannot load unsigned_txset";
+ return false;
+ }
+ }
+ catch (const std::exception &e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA;
+ er.message = "failed to parse unsigned transfers: " + std::string(e.what());
+ return false;
+ }
+
+ std::vector<tools::wallet2::pending_tx> ptx;
+ try
+ {
+ // gather info to ask the user
+ std::unordered_map<cryptonote::account_public_address, std::pair<std::string, uint64_t>> dests;
+ int first_known_non_zero_change_index = -1;
+ for (size_t n = 0; n < exported_txs.txes.size(); ++n)
+ {
+ const tools::wallet2::tx_construction_data &cd = exported_txs.txes[n];
+ res.desc.push_back({0, 0, std::numeric_limits<uint32_t>::max(), 0, {}, "", 0, "", 0, 0, ""});
+ wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::transfer_description &desc = res.desc.back();
+
+ std::vector<cryptonote::tx_extra_field> tx_extra_fields;
+ bool has_encrypted_payment_id = false;
+ crypto::hash8 payment_id8 = crypto::null_hash8;
+ if (cryptonote::parse_tx_extra(cd.extra, tx_extra_fields))
+ {
+ cryptonote::tx_extra_nonce extra_nonce;
+ if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce))
+ {
+ crypto::hash payment_id;
+ if(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8))
+ {
+ desc.payment_id = epee::string_tools::pod_to_hex(payment_id8);
+ has_encrypted_payment_id = true;
+ }
+ else if (cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id))
+ {
+ desc.payment_id = epee::string_tools::pod_to_hex(payment_id);
+ }
+ }
+ }
+
+ for (size_t s = 0; s < cd.sources.size(); ++s)
+ {
+ desc.amount_in += cd.sources[s].amount;
+ size_t ring_size = cd.sources[s].outputs.size();
+ if (ring_size < desc.ring_size)
+ desc.ring_size = ring_size;
+ }
+ for (size_t d = 0; d < cd.splitted_dsts.size(); ++d)
+ {
+ const cryptonote::tx_destination_entry &entry = cd.splitted_dsts[d];
+ std::string address = cryptonote::get_account_address_as_str(m_wallet->nettype(), entry.is_subaddress, entry.addr);
+ if (has_encrypted_payment_id && !entry.is_subaddress)
+ address = cryptonote::get_account_integrated_address_as_str(m_wallet->nettype(), entry.addr, payment_id8);
+ auto i = dests.find(entry.addr);
+ if (i == dests.end())
+ dests.insert(std::make_pair(entry.addr, std::make_pair(address, entry.amount)));
+ else
+ i->second.second += entry.amount;
+ desc.amount_out += entry.amount;
+ }
+ if (cd.change_dts.amount > 0)
+ {
+ auto it = dests.find(cd.change_dts.addr);
+ if (it == dests.end())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA;
+ er.message = "Claimed change does not go to a paid address";
+ return false;
+ }
+ if (it->second.second < cd.change_dts.amount)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA;
+ er.message = "Claimed change is larger than payment to the change address";
+ return false;
+ }
+ if (cd.change_dts.amount > 0)
+ {
+ if (first_known_non_zero_change_index == -1)
+ first_known_non_zero_change_index = n;
+ const tools::wallet2::tx_construction_data &cdn = exported_txs.txes[first_known_non_zero_change_index];
+ if (memcmp(&cd.change_dts.addr, &cdn.change_dts.addr, sizeof(cd.change_dts.addr)))
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA;
+ er.message = "Change goes to more than one address";
+ return false;
+ }
+ }
+ desc.change_amount += cd.change_dts.amount;
+ it->second.second -= cd.change_dts.amount;
+ if (it->second.second == 0)
+ dests.erase(cd.change_dts.addr);
+ }
+
+ size_t n_dummy_outputs = 0;
+ for (auto i = dests.begin(); i != dests.end(); )
+ {
+ if (i->second.second > 0)
+ {
+ desc.recipients.push_back({i->second.first, i->second.second});
+ }
+ else
+ ++desc.dummy_outputs;
+ ++i;
+ }
+
+ if (desc.change_amount > 0)
+ {
+ const tools::wallet2::tx_construction_data &cd0 = exported_txs.txes[0];
+ desc.change_address = get_account_address_as_str(m_wallet->nettype(), cd0.subaddr_account > 0, cd0.change_dts.addr);
+ }
+
+ desc.fee = desc.amount_in - desc.amount_out;
+ desc.unlock_time = cd.unlock_time;
+ desc.extra = epee::to_hex::string({cd.extra.data(), cd.extra.size()});
+ }
+ }
+ catch (const std::exception &e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_BAD_UNSIGNED_TX_DATA;
+ er.message = "failed to parse unsigned transfers";
+ return false;
+ }
+
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_submit_transfer(const wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er)
{
if (!m_wallet) return not_open(er);
@@ -1577,6 +1744,7 @@ namespace tools
epee::wipeable_string seed;
if (!m_wallet->get_seed(seed))
{
+ er.code = WALLET_RPC_ERROR_CODE_NON_DETERMINISTIC;
er.message = "The wallet is non-deterministic. Cannot display seed.";
return false;
}
@@ -2905,6 +3073,11 @@ namespace tools
er.code = WALLET_RPC_ERROR_CODE_ADDRESS_INDEX_OUT_OF_BOUNDS;
er.message = e.what();
}
+ catch (const error::signature_check_failed& e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_WRONG_SIGNATURE;
+ er.message = e.what();
+ }
catch (const std::exception& e)
{
er.code = default_error_code;
diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h
index 35ea11902..887723ed5 100644
--- a/src/wallet/wallet_rpc_server.h
+++ b/src/wallet/wallet_rpc_server.h
@@ -87,6 +87,7 @@ namespace tools
MAP_JON_RPC_WE("transfer", on_transfer, wallet_rpc::COMMAND_RPC_TRANSFER)
MAP_JON_RPC_WE("transfer_split", on_transfer_split, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT)
MAP_JON_RPC_WE("sign_transfer", on_sign_transfer, wallet_rpc::COMMAND_RPC_SIGN_TRANSFER)
+ MAP_JON_RPC_WE("describe_transfer", on_describe_transfer, wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER)
MAP_JON_RPC_WE("submit_transfer", on_submit_transfer, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER)
MAP_JON_RPC_WE("sweep_dust", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST)
MAP_JON_RPC_WE("sweep_unmixable", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST)
@@ -167,6 +168,7 @@ namespace tools
bool on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er);
bool on_transfer_split(const wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::request& req, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::response& res, epee::json_rpc::error& er);
bool on_sign_transfer(const wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SIGN_TRANSFER::response& res, epee::json_rpc::error& er);
+ bool on_describe_transfer(const wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_DESCRIBE_TRANSFER::response& res, epee::json_rpc::error& er);
bool on_submit_transfer(const wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er);
bool on_sweep_dust(const wallet_rpc::COMMAND_RPC_SWEEP_DUST::request& req, wallet_rpc::COMMAND_RPC_SWEEP_DUST::response& res, epee::json_rpc::error& er);
bool on_sweep_all(const wallet_rpc::COMMAND_RPC_SWEEP_ALL::request& req, wallet_rpc::COMMAND_RPC_SWEEP_ALL::response& res, epee::json_rpc::error& er);
diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
index 2377b69e3..924f3a0f1 100644
--- a/src/wallet/wallet_rpc_server_commands_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -47,7 +47,7 @@
// advance which version they will stop working with
// Don't go over 32767 for any of these
#define WALLET_RPC_VERSION_MAJOR 1
-#define WALLET_RPC_VERSION_MINOR 4
+#define WALLET_RPC_VERSION_MINOR 5
#define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor))
#define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR)
namespace tools
@@ -531,16 +531,79 @@ namespace wallet_rpc
};
};
+ struct COMMAND_RPC_DESCRIBE_TRANSFER
+ {
+ struct recipient
+ {
+ std::string address;
+ uint64_t amount;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(address)
+ KV_SERIALIZE(amount)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct transfer_description
+ {
+ uint64_t amount_in;
+ uint64_t amount_out;
+ uint32_t ring_size;
+ uint64_t unlock_time;
+ std::list<recipient> recipients;
+ std::string payment_id;
+ uint64_t change_amount;
+ std::string change_address;
+ uint64_t fee;
+ uint32_t dummy_outputs;
+ std::string extra;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(amount_in)
+ KV_SERIALIZE(amount_out)
+ KV_SERIALIZE(ring_size)
+ KV_SERIALIZE(unlock_time)
+ KV_SERIALIZE(recipients)
+ KV_SERIALIZE(payment_id)
+ KV_SERIALIZE(change_amount)
+ KV_SERIALIZE(change_address)
+ KV_SERIALIZE(fee)
+ KV_SERIALIZE(dummy_outputs)
+ KV_SERIALIZE(extra)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct request
+ {
+ std::string unsigned_txset;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(unsigned_txset)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ std::list<transfer_description> desc;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(desc)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
struct COMMAND_RPC_SIGN_TRANSFER
{
struct request
{
std::string unsigned_txset;
bool export_raw;
+ bool get_tx_keys;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(unsigned_txset)
KV_SERIALIZE_OPT(export_raw, false)
+ KV_SERIALIZE_OPT(get_tx_keys, false)
END_KV_SERIALIZE_MAP()
};
@@ -549,11 +612,13 @@ namespace wallet_rpc
std::string signed_txset;
std::list<std::string> tx_hash_list;
std::list<std::string> tx_raw_list;
+ std::list<std::string> tx_key_list;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(signed_txset)
KV_SERIALIZE(tx_hash_list)
KV_SERIALIZE(tx_raw_list)
+ KV_SERIALIZE(tx_key_list)
END_KV_SERIALIZE_MAP()
};
};
diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h
index f127ae240..9b3a2847d 100644
--- a/src/wallet/wallet_rpc_server_error_codes.h
+++ b/src/wallet/wallet_rpc_server_error_codes.h
@@ -73,3 +73,4 @@
#define WALLET_RPC_ERROR_CODE_BAD_SIGNED_TX_DATA -40
#define WALLET_RPC_ERROR_CODE_SIGNED_SUBMISSION -41
#define WALLET_RPC_ERROR_CODE_SIGN_UNSIGNED -42
+#define WALLET_RPC_ERROR_CODE_NON_DETERMINISTIC -43