device/trezor: trezor support added
This commit is contained in:
parent
963d247154
commit
29ffb6bba8
|
@ -9,3 +9,6 @@
|
|||
[submodule "external/rapidjson"]
|
||||
path = external/rapidjson
|
||||
url = https://github.com/Tencent/rapidjson
|
||||
[submodule "external/trezor-common"]
|
||||
path = external/trezor-common
|
||||
url = https://github.com/trezor/trezor-common.git
|
||||
|
|
|
@ -190,6 +190,7 @@ if(NOT MANUAL_SUBMODULES)
|
|||
check_submodule(external/miniupnp)
|
||||
check_submodule(external/unbound)
|
||||
check_submodule(external/rapidjson)
|
||||
check_submodule(external/trezor-common)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
@ -512,6 +513,16 @@ else (HIDAPI_FOUND)
|
|||
message(STATUS "Could not find HIDAPI")
|
||||
endif()
|
||||
|
||||
# Protobuf, optional. Required for TREZOR.
|
||||
include(FindProtobuf)
|
||||
find_package(Protobuf)
|
||||
if(Protobuf_FOUND)
|
||||
set(HAVE_PROTOBUF 1)
|
||||
add_definitions(-DHAVE_PROTOBUF=1)
|
||||
else(Protobuf_FOUND)
|
||||
message(STATUS "Could not find Protobuf")
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
add_definitions("/bigobj /MP /W3 /GS- /D_CRT_SECURE_NO_WARNINGS /wd4996 /wd4345 /D_WIN32_WINNT=0x0600 /DWIN32_LEAN_AND_MEAN /DGTEST_HAS_TR1_TUPLE=0 /FIinline_c.h /D__SSE4_1__")
|
||||
# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Dinline=__inline")
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 588f8e03f5ac111adf719f0a437de67481a26aed
|
|
@ -145,3 +145,4 @@ if(PER_BLOCK_CHECKPOINT)
|
|||
endif()
|
||||
|
||||
add_subdirectory(device)
|
||||
add_subdirectory(device_trezor)
|
||||
|
|
|
@ -139,6 +139,15 @@ DISABLE_VS_WARNINGS(4244 4345)
|
|||
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()
|
||||
{
|
||||
m_keys.m_spend_secret_key = crypto::secret_key();
|
||||
|
@ -206,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
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -44,6 +44,7 @@ set(device_headers
|
|||
device.hpp
|
||||
device_io.hpp
|
||||
device_default.hpp
|
||||
device_cold.hpp
|
||||
log.hpp
|
||||
)
|
||||
|
||||
|
@ -72,5 +73,6 @@ target_link_libraries(device
|
|||
cncrypto
|
||||
ringct_basic
|
||||
${OPENSSL_CRYPTO_LIBRARIES}
|
||||
${Boost_SERIALIZATION_LIBRARY}
|
||||
PRIVATE
|
||||
${EXTRA_LIBRARIES})
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
#include "crypto/crypto.h"
|
||||
#include "crypto/chacha.h"
|
||||
#include "ringct/rctTypes.h"
|
||||
#include "cryptonote_config.h"
|
||||
|
||||
|
||||
#ifndef USE_DEVICE_LEDGER
|
||||
|
@ -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 */
|
||||
/* ======================================================================= */
|
||||
|
@ -120,6 +128,7 @@ namespace hw {
|
|||
|
||||
virtual device_type get_type() const = 0;
|
||||
|
||||
virtual device_protocol_t device_protocol() const { return PROTOCOL_DEFAULT; };
|
||||
|
||||
/* ======================================================================= */
|
||||
/* LOCKER */
|
||||
|
@ -204,6 +213,11 @@ namespace hw {
|
|||
|
||||
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;
|
||||
} ;
|
||||
|
|
|
@ -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
|
|
@ -69,17 +69,17 @@ 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) {
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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()
|
|
@ -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>> ®istry) {
|
||||
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>> ®istry) {
|
||||
}
|
||||
|
||||
void register_all() {
|
||||
}
|
||||
|
||||
#endif //WITH_DEVICE_TREZOR
|
||||
}}
|
|
@ -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>> ®istry);
|
||||
|
||||
#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
|
|
@ -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
|
||||
}}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
# protobuf generated code
|
||||
*
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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 */
|
||||
|
|
|
@ -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())
|
||||
|
@ -2537,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"),
|
||||
|
@ -2728,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;
|
||||
|
@ -4834,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
|
||||
|
@ -5123,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");
|
||||
|
@ -5545,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");
|
||||
|
@ -7794,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())
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -57,6 +57,7 @@ target_link_libraries(wallet
|
|||
common
|
||||
cryptonote_core
|
||||
mnemonics
|
||||
device_trezor
|
||||
${LMDB_LIBRARY}
|
||||
${Boost_CHRONO_LIBRARY}
|
||||
${Boost_SERIALIZATION_LIBRARY}
|
||||
|
|
|
@ -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"
|
||||
{
|
||||
|
@ -768,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
|
||||
|
||||
|
@ -1060,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");
|
||||
|
@ -2944,6 +2952,7 @@ bool wallet2::deinit()
|
|||
{
|
||||
m_is_initialized=false;
|
||||
unlock_keys_file();
|
||||
m_account.deinit();
|
||||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
|
@ -3413,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");
|
||||
|
@ -3450,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);
|
||||
|
||||
|
@ -3474,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;
|
||||
}
|
||||
|
@ -3914,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();
|
||||
|
@ -5781,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;
|
||||
|
||||
|
@ -6346,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)
|
||||
|
@ -9084,6 +9100,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);
|
||||
|
@ -10240,6 +10312,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;
|
||||
|
@ -10785,6 +10870,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;
|
||||
|
|
|
@ -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 ¬e);
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue