aboutsummaryrefslogtreecommitdiff
path: root/src/device_trezor/trezor
diff options
context:
space:
mode:
authorDusan Klinec <dusan.klinec@gmail.com>2018-08-23 23:50:53 +0200
committerDusan Klinec <dusan.klinec@gmail.com>2018-11-02 21:36:39 +0100
commit29ffb6bba8867586986345b4f0c560e5ea5fce85 (patch)
tree544386c5c4158ab4d8edfb50ab3792e25a97439d /src/device_trezor/trezor
parentMerge pull request #4676 (diff)
downloadmonero-29ffb6bba8867586986345b4f0c560e5ea5fce85.tar.xz
device/trezor: trezor support added
Diffstat (limited to 'src/device_trezor/trezor')
-rw-r--r--src/device_trezor/trezor/exceptions.hpp193
-rw-r--r--src/device_trezor/trezor/messages/.gitignore2
-rw-r--r--src/device_trezor/trezor/messages_map.cpp125
-rw-r--r--src/device_trezor/trezor/messages_map.hpp94
-rw-r--r--src/device_trezor/trezor/protocol.cpp891
-rw-r--r--src/device_trezor/trezor/protocol.hpp300
-rw-r--r--src/device_trezor/trezor/tools/README.md36
-rw-r--r--src/device_trezor/trezor/tools/build_protob.py38
-rw-r--r--src/device_trezor/trezor/tools/pb2cpp.py186
-rw-r--r--src/device_trezor/trezor/transport.cpp651
-rw-r--r--src/device_trezor/trezor/transport.hpp331
-rw-r--r--src/device_trezor/trezor/trezor_defs.hpp48
12 files changed, 2895 insertions, 0 deletions
diff --git a/src/device_trezor/trezor/exceptions.hpp b/src/device_trezor/trezor/exceptions.hpp
new file mode 100644
index 000000000..197dc43a4
--- /dev/null
+++ b/src/device_trezor/trezor/exceptions.hpp
@@ -0,0 +1,193 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef MONERO_EXCEPTIONS_H
+#define MONERO_EXCEPTIONS_H
+
+#include <exception>
+#include <string>
+#include <boost/optional.hpp>
+
+namespace hw {
+namespace trezor {
+namespace exc {
+
+ class SecurityException : public std::exception {
+ protected:
+ boost::optional<std::string> reason;
+
+ public:
+ SecurityException(): reason("General Security exception"){}
+ explicit SecurityException(std::string what): reason(what){}
+
+ virtual const char* what() const throw() {
+ return reason.get().c_str();
+ }
+ };
+
+ class Poly1305TagInvalid: public SecurityException {
+ public:
+ using SecurityException::SecurityException;
+ Poly1305TagInvalid(): SecurityException("Poly1305 authentication tag invalid"){}
+ };
+
+ class TrezorException : public std::exception {
+ protected:
+ boost::optional<std::string> reason;
+
+ public:
+ TrezorException(): reason("General Trezor exception"){}
+ explicit TrezorException(std::string what): reason(what){}
+
+ virtual const char* what() const throw() {
+ return reason.get().c_str();
+ }
+ };
+
+ class CommunicationException: public TrezorException {
+ public:
+ using TrezorException::TrezorException;
+ CommunicationException(): TrezorException("Trezor communication error"){}
+ };
+
+ class EncodingException: public CommunicationException {
+ public:
+ using CommunicationException::CommunicationException;
+ EncodingException(): CommunicationException("Trezor message encoding error"){}
+ };
+
+ class NotConnectedException : public CommunicationException {
+ public:
+ using CommunicationException::CommunicationException;
+ NotConnectedException(): CommunicationException("Trezor not connected"){}
+ };
+
+ class DeviceNotResponsiveException : public CommunicationException {
+ public:
+ using CommunicationException::CommunicationException;
+ DeviceNotResponsiveException(): CommunicationException("Trezor does not respond to ping"){}
+ };
+
+ class DeviceAcquireException : public CommunicationException {
+ public:
+ using CommunicationException::CommunicationException;
+ DeviceAcquireException(): CommunicationException("Trezor could not be acquired"){}
+ };
+
+ class SessionException: public CommunicationException {
+ public:
+ using CommunicationException::CommunicationException;
+ SessionException(): CommunicationException("Trezor session expired"){}
+ };
+
+ class TimeoutException: public CommunicationException {
+ public:
+ using CommunicationException::CommunicationException;
+ TimeoutException(): CommunicationException("Trezor communication timeout"){}
+ };
+
+ class ProtocolException: public CommunicationException {
+ public:
+ using CommunicationException::CommunicationException;
+ ProtocolException(): CommunicationException("Trezor protocol error"){}
+ };
+
+ // Communication protocol namespace
+ // Separated to distinguish between client and Trezor side exceptions.
+namespace proto {
+
+ class SecurityException : public ProtocolException {
+ public:
+ using ProtocolException::ProtocolException;
+ SecurityException(): ProtocolException("Security assertion violated in the protocol"){}
+ };
+
+ class FailureException : public ProtocolException {
+ private:
+ boost::optional<uint32_t> code;
+ boost::optional<std::string> message;
+ public:
+ using ProtocolException::ProtocolException;
+ FailureException(): ProtocolException("Trezor returned failure"){}
+ FailureException(boost::optional<uint32_t> code,
+ boost::optional<std::string> message)
+ : code(code), message(message) {
+ reason = "Trezor returned failure: code="
+ + (code ? std::to_string(code.get()) : "")
+ + ", message=" + (message ? message.get() : "");
+ };
+ };
+
+ class UnexpectedMessageException : public FailureException {
+ public:
+ using FailureException::FailureException;
+ UnexpectedMessageException(): FailureException("Trezor claims unexpected message received"){}
+ };
+
+ class CancelledException : public FailureException {
+ public:
+ using FailureException::FailureException;
+ CancelledException(): FailureException("Trezor returned: cancelled operation"){}
+ };
+
+ class PinExpectedException : public FailureException {
+ public:
+ using FailureException::FailureException;
+ PinExpectedException(): FailureException("Trezor claims PIN is expected"){}
+ };
+
+ class InvalidPinException : public FailureException {
+ public:
+ using FailureException::FailureException;
+ InvalidPinException(): FailureException("Trezor claims PIN is invalid"){}
+ };
+
+ class NotEnoughFundsException : public FailureException {
+ public:
+ using FailureException::FailureException;
+ NotEnoughFundsException(): FailureException("Trezor claims not enough funds"){}
+ };
+
+ class NotInitializedException : public FailureException {
+ public:
+ using FailureException::FailureException;
+ NotInitializedException(): FailureException("Trezor claims not initialized"){}
+ };
+
+ class FirmwareErrorException : public FailureException {
+ public:
+ using FailureException::FailureException;
+ FirmwareErrorException(): FailureException("Trezor returned firmware error"){}
+ };
+
+}
+}
+}
+}
+#endif //MONERO_EXCEPTIONS_H
diff --git a/src/device_trezor/trezor/messages/.gitignore b/src/device_trezor/trezor/messages/.gitignore
new file mode 100644
index 000000000..32f7a77e5
--- /dev/null
+++ b/src/device_trezor/trezor/messages/.gitignore
@@ -0,0 +1,2 @@
+# protobuf generated code
+*
diff --git a/src/device_trezor/trezor/messages_map.cpp b/src/device_trezor/trezor/messages_map.cpp
new file mode 100644
index 000000000..b0d1aa254
--- /dev/null
+++ b/src/device_trezor/trezor/messages_map.cpp
@@ -0,0 +1,125 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include "messages_map.hpp"
+#include "messages/messages.pb.h"
+#include "messages/messages-common.pb.h"
+#include "messages/messages-management.pb.h"
+#include "messages/messages-monero.pb.h"
+
+using namespace std;
+using namespace hw::trezor;
+
+namespace hw{
+namespace trezor
+{
+
+ const char * TYPE_PREFIX = "MessageType_";
+ const char * PACKAGES[] = {
+ "hw.trezor.messages.",
+ "hw.trezor.messages.common.",
+ "hw.trezor.messages.management.",
+ "hw.trezor.messages.monero."
+ };
+
+ google::protobuf::Message * MessageMapper::get_message(int wire_number) {
+ return MessageMapper::get_message(static_cast<messages::MessageType>(wire_number));
+ }
+
+ google::protobuf::Message * MessageMapper::get_message(messages::MessageType wire_number) {
+ const string &messageTypeName = hw::trezor::messages::MessageType_Name(wire_number);
+ if (messageTypeName.empty()) {
+ throw exc::EncodingException(std::string("Message descriptor not found: ") + std::to_string(wire_number));
+ }
+
+ string messageName = messageTypeName.substr(strlen(TYPE_PREFIX));
+ return MessageMapper::get_message(messageName);
+ }
+
+ google::protobuf::Message * MessageMapper::get_message(const std::string & msg_name) {
+ // Each package instantiation so lookup works
+ hw::trezor::messages::common::Success::default_instance();
+ hw::trezor::messages::management::Cancel::default_instance();
+ hw::trezor::messages::monero::MoneroGetAddress::default_instance();
+
+ google::protobuf::Descriptor const * desc = nullptr;
+ for(const string &text : PACKAGES){
+ desc = google::protobuf::DescriptorPool::generated_pool()
+ ->FindMessageTypeByName(text + msg_name);
+ if (desc != nullptr){
+ break;
+ }
+ }
+
+ if (desc == nullptr){
+ throw exc::EncodingException(std::string("Message not found: ") + msg_name);
+ }
+
+ google::protobuf::Message* message =
+ google::protobuf::MessageFactory::generated_factory()
+ ->GetPrototype(desc)->New();
+
+ return message;
+
+// // CODEGEN way, fast
+// switch(wire_number){
+// case 501:
+// return new messages::monero::MoneroTransactionSignRequest();
+// default:
+// throw std::runtime_error("not implemented");
+// }
+//
+// // CODEGEN message -> number: specification
+// // messages::MessageType get_message_wire_number(const messages::monero::MoneroTransactionSignRequest * msg) { return 501; }
+// // messages::MessageType get_message_wire_number(const messages::management::ping * msg)
+//
+ }
+
+ messages::MessageType MessageMapper::get_message_wire_number(const google::protobuf::Message * msg){
+ return MessageMapper::get_message_wire_number(msg->GetDescriptor()->name());
+ }
+
+ messages::MessageType MessageMapper::get_message_wire_number(const google::protobuf::Message & msg){
+ return MessageMapper::get_message_wire_number(msg.GetDescriptor()->name());
+ }
+
+ messages::MessageType MessageMapper::get_message_wire_number(const std::string & msg_name){
+ string enumMessageName = std::string(TYPE_PREFIX) + msg_name;
+
+ messages::MessageType res;
+ bool r = hw::trezor::messages::MessageType_Parse(enumMessageName, &res);
+ if (!r){
+ throw exc::EncodingException(std::string("Message ") + msg_name + " not found");
+ }
+
+ return res;
+ }
+
+}
+}
diff --git a/src/device_trezor/trezor/messages_map.hpp b/src/device_trezor/trezor/messages_map.hpp
new file mode 100644
index 000000000..f61338f09
--- /dev/null
+++ b/src/device_trezor/trezor/messages_map.hpp
@@ -0,0 +1,94 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef MONERO_MESSAGES_MAP_H
+#define MONERO_MESSAGES_MAP_H
+
+#include <string>
+#include <type_traits>
+#include <memory>
+#include "exceptions.hpp"
+
+#include "trezor_defs.hpp"
+
+#include <google/protobuf/stubs/common.h>
+#include <google/protobuf/generated_message_util.h>
+#include <google/protobuf/repeated_field.h>
+#include <google/protobuf/extension_set.h>
+#include <google/protobuf/generated_enum_reflection.h>
+#include "google/protobuf/descriptor.pb.h"
+
+#include "messages/messages.pb.h"
+
+namespace hw {
+namespace trezor {
+
+ class MessageMapper{
+ public:
+ MessageMapper() {
+
+ }
+
+ static ::google::protobuf::Message * get_message(int wire_number);
+ static ::google::protobuf::Message * get_message(messages::MessageType);
+ static ::google::protobuf::Message * get_message(const std::string & msg_name);
+ static messages::MessageType get_message_wire_number(const google::protobuf::Message * msg);
+ static messages::MessageType get_message_wire_number(const google::protobuf::Message & msg);
+ static messages::MessageType get_message_wire_number(const std::string & msg_name);
+
+ template<class t_message>
+ static messages::MessageType get_message_wire_number() {
+ BOOST_STATIC_ASSERT(boost::is_base_of<google::protobuf::Message, t_message>::value);
+ return get_message_wire_number(t_message::default_instance().GetDescriptor()->name());
+ }
+ };
+
+ template<class t_message>
+ std::shared_ptr<t_message> message_ptr_retype(std::shared_ptr<google::protobuf::Message> & in){
+ BOOST_STATIC_ASSERT(boost::is_base_of<google::protobuf::Message, t_message>::value);
+ if (!in){
+ return nullptr;
+ }
+
+ return std::dynamic_pointer_cast<t_message>(in);
+ }
+
+ template<class t_message>
+ std::shared_ptr<t_message> message_ptr_retype_static(std::shared_ptr<google::protobuf::Message> & in){
+ BOOST_STATIC_ASSERT(boost::is_base_of<google::protobuf::Message, t_message>::value);
+ if (!in){
+ return nullptr;
+ }
+
+ return std::static_pointer_cast<t_message>(in);
+ }
+
+}}
+
+#endif //MONERO_MESSAGES_MAP_H
diff --git a/src/device_trezor/trezor/protocol.cpp b/src/device_trezor/trezor/protocol.cpp
new file mode 100644
index 000000000..c4a92426c
--- /dev/null
+++ b/src/device_trezor/trezor/protocol.cpp
@@ -0,0 +1,891 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include "protocol.hpp"
+#include <unordered_map>
+#include <set>
+#include <utility>
+#include <boost/endian/conversion.hpp>
+#include <common/apply_permutation.h>
+#include <ringct/rctSigs.h>
+#include <ringct/bulletproofs.h>
+#include "cryptonote_config.h"
+#include <sodium.h>
+#include <sodium/crypto_verify_32.h>
+#include <sodium/crypto_aead_chacha20poly1305.h>
+
+namespace hw{
+namespace trezor{
+namespace protocol{
+
+ std::string key_to_string(const ::crypto::ec_point & key){
+ return std::string(key.data, sizeof(key.data));
+ }
+
+ std::string key_to_string(const ::crypto::ec_scalar & key){
+ return std::string(key.data, sizeof(key.data));
+ }
+
+ std::string key_to_string(const ::crypto::hash & key){
+ return std::string(key.data, sizeof(key.data));
+ }
+
+ std::string key_to_string(const ::rct::key & key){
+ return std::string(reinterpret_cast<const char*>(key.bytes), sizeof(key.bytes));
+ }
+
+ void string_to_key(::crypto::ec_scalar & key, const std::string & str){
+ if (str.size() != sizeof(key.data)){
+ throw std::invalid_argument(std::string("Key has to have ") + std::to_string(sizeof(key.data)) + " B");
+ }
+ memcpy(key.data, str.data(), sizeof(key.data));
+ }
+
+ void string_to_key(::crypto::ec_point & key, const std::string & str){
+ if (str.size() != sizeof(key.data)){
+ throw std::invalid_argument(std::string("Key has to have ") + std::to_string(sizeof(key.data)) + " B");
+ }
+ memcpy(key.data, str.data(), sizeof(key.data));
+ }
+
+ void string_to_key(::rct::key & key, const std::string & str){
+ if (str.size() != sizeof(key.bytes)){
+ throw std::invalid_argument(std::string("Key has to have ") + std::to_string(sizeof(key.bytes)) + " B");
+ }
+ memcpy(key.bytes, str.data(), sizeof(key.bytes));
+ }
+
+namespace crypto {
+namespace chacha {
+
+ void decrypt(const void* ciphertext, size_t length, const uint8_t* key, const uint8_t* iv, char* plaintext){
+ if (length < 16){
+ throw std::invalid_argument("Ciphertext length too small");
+ }
+
+ unsigned long long int cip_len = length;
+ auto r = crypto_aead_chacha20poly1305_ietf_decrypt(
+ reinterpret_cast<unsigned char *>(plaintext), &cip_len, nullptr,
+ static_cast<const unsigned char *>(ciphertext), length, nullptr, 0, iv, key);
+
+ if (r != 0){
+ throw exc::Poly1305TagInvalid();
+ }
+ }
+
+}
+}
+
+
+// Cold Key image sync
+namespace ki {
+
+ bool key_image_data(wallet_shim * wallet,
+ const std::vector<tools::wallet2::transfer_details> & transfers,
+ std::vector<MoneroTransferDetails> & res)
+ {
+ for(auto & td : transfers){
+ ::crypto::public_key tx_pub_key = wallet->get_tx_pub_key_from_received_outs(td);
+ const std::vector<::crypto::public_key> additional_tx_pub_keys = cryptonote::get_additional_tx_pub_keys_from_extra(td.m_tx);
+
+ res.emplace_back();
+ auto & cres = res.back();
+
+ cres.set_out_key(key_to_string(boost::get<cryptonote::txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key));
+ cres.set_tx_pub_key(key_to_string(tx_pub_key));
+ cres.set_internal_output_index(td.m_internal_output_index);
+ for(auto & aux : additional_tx_pub_keys){
+ cres.add_additional_tx_pub_keys(key_to_string(aux));
+ }
+ }
+
+ return true;
+ }
+
+ std::string compute_hash(const MoneroTransferDetails & rr){
+ KECCAK_CTX kck;
+ uint8_t md[32];
+
+ CHECK_AND_ASSERT_THROW_MES(rr.out_key().size() == 32, "Invalid out_key size");
+ CHECK_AND_ASSERT_THROW_MES(rr.tx_pub_key().size() == 32, "Invalid tx_pub_key size");
+
+ keccak_init(&kck);
+ keccak_update(&kck, reinterpret_cast<const uint8_t *>(rr.out_key().data()), 32);
+ keccak_update(&kck, reinterpret_cast<const uint8_t *>(rr.tx_pub_key().data()), 32);
+ for (const auto &aux : rr.additional_tx_pub_keys()){
+ CHECK_AND_ASSERT_THROW_MES(aux.size() == 32, "Invalid aux size");
+ keccak_update(&kck, reinterpret_cast<const uint8_t *>(aux.data()), 32);
+ }
+
+ auto index_serialized = tools::get_varint_data(rr.internal_output_index());
+ keccak_update(&kck, reinterpret_cast<const uint8_t *>(index_serialized.data()), index_serialized.size());
+ keccak_finish(&kck, md);
+ return std::string(reinterpret_cast<const char*>(md), sizeof(md));
+ }
+
+ void generate_commitment(std::vector<MoneroTransferDetails> & mtds,
+ const std::vector<tools::wallet2::transfer_details> & transfers,
+ std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req)
+ {
+ req = std::make_shared<messages::monero::MoneroKeyImageExportInitRequest>();
+
+ KECCAK_CTX kck;
+ uint8_t final_hash[32];
+ keccak_init(&kck);
+
+ for(auto &cur : mtds){
+ auto hash = compute_hash(cur);
+ keccak_update(&kck, reinterpret_cast<const uint8_t *>(hash.data()), hash.size());
+ }
+ keccak_finish(&kck, final_hash);
+
+ req = std::make_shared<messages::monero::MoneroKeyImageExportInitRequest>();
+ req->set_hash(std::string(reinterpret_cast<const char*>(final_hash), 32));
+ req->set_num(transfers.size());
+
+ std::unordered_map<uint32_t, std::set<uint32_t>> sub_indices;
+ for (auto &cur : transfers){
+ auto search = sub_indices.emplace(cur.m_subaddr_index.major, std::set<uint32_t>());
+ auto & st = search.first->second;
+ st.insert(cur.m_subaddr_index.minor);
+ }
+
+ for (auto& x: sub_indices){
+ auto subs = req->add_subs();
+ subs->set_account(x.first);
+ for(auto minor : x.second){
+ subs->add_minor_indices(minor);
+ }
+ }
+ }
+
+}
+
+// Cold transaction signing
+namespace tx {
+
+ void translate_address(MoneroAccountPublicAddress * dst, const cryptonote::account_public_address * src){
+ dst->set_view_public_key(key_to_string(src->m_view_public_key));
+ dst->set_spend_public_key(key_to_string(src->m_spend_public_key));
+ }
+
+ void translate_dst_entry(MoneroTransactionDestinationEntry * dst, const cryptonote::tx_destination_entry * src){
+ dst->set_amount(src->amount);
+ dst->set_is_subaddress(src->is_subaddress);
+ translate_address(dst->mutable_addr(), &(src->addr));
+ }
+
+ void translate_src_entry(MoneroTransactionSourceEntry * dst, const cryptonote::tx_source_entry * src){
+ for(auto & cur : src->outputs){
+ auto out = dst->add_outputs();
+ out->set_idx(cur.first);
+ translate_rct_key(out->mutable_key(), &(cur.second));
+ }
+
+ dst->set_real_output(src->real_output);
+ dst->set_real_out_tx_key(key_to_string(src->real_out_tx_key));
+ for(auto & cur : src->real_out_additional_tx_keys){
+ dst->add_real_out_additional_tx_keys(key_to_string(cur));
+ }
+
+ dst->set_real_output_in_tx_index(src->real_output_in_tx_index);
+ dst->set_amount(src->amount);
+ dst->set_rct(src->rct);
+ dst->set_mask(key_to_string(src->mask));
+ translate_klrki(dst->mutable_multisig_klrki(), &(src->multisig_kLRki));
+ }
+
+ void translate_klrki(MoneroMultisigKLRki * dst, const rct::multisig_kLRki * src){
+ dst->set_k(key_to_string(src->k));
+ dst->set_l(key_to_string(src->L));
+ dst->set_r(key_to_string(src->R));
+ dst->set_ki(key_to_string(src->ki));
+ }
+
+ void translate_rct_key(MoneroRctKey * dst, const rct::ctkey * src){
+ dst->set_dest(key_to_string(src->dest));
+ dst->set_commitment(key_to_string(src->mask));
+ }
+
+ std::string hash_addr(const MoneroAccountPublicAddress * addr, boost::optional<uint64_t> amount, boost::optional<bool> is_subaddr){
+ return hash_addr(addr->spend_public_key(), addr->view_public_key(), amount, is_subaddr);
+ }
+
+ std::string hash_addr(const std::string & spend_key, const std::string & view_key, boost::optional<uint64_t> amount, boost::optional<bool> is_subaddr){
+ ::crypto::public_key spend{}, view{};
+ if (spend_key.size() != 32 || view_key.size() != 32){
+ throw std::invalid_argument("Public keys have invalid sizes");
+ }
+
+ memcpy(spend.data, spend_key.data(), 32);
+ memcpy(view.data, view_key.data(), 32);
+ return hash_addr(&spend, &view, amount, is_subaddr);
+ }
+
+ std::string hash_addr(const ::crypto::public_key * spend_key, const ::crypto::public_key * view_key, boost::optional<uint64_t> amount, boost::optional<bool> is_subaddr){
+ char buff[64+8+1];
+ size_t offset = 0;
+
+ memcpy(buff + offset, spend_key->data, 32); offset += 32;
+ memcpy(buff + offset, view_key->data, 32); offset += 32;
+
+ if (amount){
+ memcpy(buff + offset, (uint8_t*) &(amount.get()), sizeof(amount.get())); offset += sizeof(amount.get());
+ }
+
+ if (is_subaddr){
+ buff[offset] = is_subaddr.get();
+ offset += 1;
+ }
+
+ return std::string(buff, offset);
+ }
+
+ TData::TData() {
+ in_memory = false;
+ rsig_type = 0;
+ cur_input_idx = 0;
+ cur_output_idx = 0;
+ cur_batch_idx = 0;
+ cur_output_in_batch_idx = 0;
+ }
+
+ Signer::Signer(wallet_shim *wallet2, const unsigned_tx_set * unsigned_tx, size_t tx_idx, hw::tx_aux_data * aux_data) {
+ m_wallet2 = wallet2;
+ m_unsigned_tx = unsigned_tx;
+ m_aux_data = aux_data;
+ m_tx_idx = tx_idx;
+ m_ct.tx_data = cur_tx();
+ m_multisig = false;
+ }
+
+ void Signer::extract_payment_id(){
+ const std::vector<uint8_t>& tx_extra = cur_tx().extra;
+ m_ct.tsx_data.set_payment_id("");
+
+ std::vector<cryptonote::tx_extra_field> tx_extra_fields;
+ cryptonote::parse_tx_extra(tx_extra, tx_extra_fields); // ok if partially parsed
+ cryptonote::tx_extra_nonce extra_nonce;
+
+ ::crypto::hash payment_id{};
+ if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce))
+ {
+ ::crypto::hash8 payment_id8{};
+ if(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8))
+ {
+ m_ct.tsx_data.set_payment_id(std::string(payment_id8.data, 8));
+ }
+ else if (cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id))
+ {
+ m_ct.tsx_data.set_payment_id(std::string(payment_id.data, 32));
+ }
+ }
+ }
+
+ static unsigned get_rsig_type(bool use_bulletproof, size_t num_outputs){
+ if (!use_bulletproof){
+ return rct::RangeProofBorromean;
+ } else if (num_outputs > BULLETPROOF_MAX_OUTPUTS){
+ return rct::RangeProofMultiOutputBulletproof;
+ } else {
+ return rct::RangeProofPaddedBulletproof;
+ }
+ }
+
+ static void generate_rsig_batch_sizes(std::vector<uint64_t> &batches, unsigned rsig_type, size_t num_outputs){
+ size_t amount_batched = 0;
+
+ while(amount_batched < num_outputs){
+ if (rsig_type == rct::RangeProofBorromean || rsig_type == rct::RangeProofBulletproof) {
+ batches.push_back(1);
+ amount_batched += 1;
+
+ } else if (rsig_type == rct::RangeProofPaddedBulletproof){
+ if (num_outputs > BULLETPROOF_MAX_OUTPUTS){
+ throw std::invalid_argument("BP padded can support only BULLETPROOF_MAX_OUTPUTS statements");
+ }
+ batches.push_back(num_outputs);
+ amount_batched += num_outputs;
+
+ } else if (rsig_type == rct::RangeProofMultiOutputBulletproof){
+ size_t batch_size = 1;
+ while (batch_size * 2 + amount_batched <= num_outputs && batch_size * 2 <= BULLETPROOF_MAX_OUTPUTS){
+ batch_size *= 2;
+ }
+ batch_size = std::min(batch_size, num_outputs - amount_batched);
+ batches.push_back(batch_size);
+ amount_batched += batch_size;
+
+ } else {
+ throw std::invalid_argument("Unknown rsig type");
+ }
+ }
+ }
+
+ void Signer::compute_integrated_indices(TsxData * tsx_data){
+ if (m_aux_data == nullptr || m_aux_data->tx_recipients.empty()){
+ return;
+ }
+
+ auto & chg = tsx_data->change_dts();
+ std::string change_hash = hash_addr(&chg.addr(), chg.amount(), chg.is_subaddress());
+
+ std::vector<uint32_t> integrated_indices;
+ std::set<std::string> integrated_hashes;
+ for (auto & cur : m_aux_data->tx_recipients){
+ if (!cur.has_payment_id){
+ continue;
+ }
+ integrated_hashes.emplace(hash_addr(&cur.address.m_spend_public_key, &cur.address.m_view_public_key));
+ }
+
+ ssize_t idx = -1;
+ for (auto & cur : tsx_data->outputs()){
+ idx += 1;
+
+ std::string c_hash = hash_addr(&cur.addr(), cur.amount(), cur.is_subaddress());
+ if (c_hash == change_hash || cur.is_subaddress()){
+ continue;
+ }
+
+ c_hash = hash_addr(&cur.addr());
+ if (integrated_hashes.find(c_hash) != integrated_hashes.end()){
+ integrated_indices.push_back((uint32_t)idx);
+ }
+ }
+
+ if (!integrated_indices.empty()){
+ assign_to_repeatable(tsx_data->mutable_integrated_indices(), integrated_indices.begin(), integrated_indices.end());
+ }
+ }
+
+ std::shared_ptr<messages::monero::MoneroTransactionInitRequest> Signer::step_init(){
+ // extract payment ID from construction data
+ auto & tsx_data = m_ct.tsx_data;
+ auto & tx = cur_tx();
+
+ m_ct.tx.version = 2;
+ m_ct.tx.unlock_time = tx.unlock_time;
+
+ tsx_data.set_version(1);
+ tsx_data.set_unlock_time(tx.unlock_time);
+ tsx_data.set_num_inputs(static_cast<google::protobuf::uint32>(tx.sources.size()));
+ tsx_data.set_mixin(static_cast<google::protobuf::uint32>(tx.sources[0].outputs.size() - 1));
+ tsx_data.set_account(tx.subaddr_account);
+ assign_to_repeatable(tsx_data.mutable_minor_indices(), tx.subaddr_indices.begin(), tx.subaddr_indices.end());
+
+ // Rsig decision
+ auto rsig_data = tsx_data.mutable_rsig_data();
+ m_ct.rsig_type = get_rsig_type(tx.use_bulletproofs, tx.splitted_dsts.size());
+ rsig_data->set_rsig_type(m_ct.rsig_type);
+
+ generate_rsig_batch_sizes(m_ct.grouping_vct, m_ct.rsig_type, tx.splitted_dsts.size());
+ assign_to_repeatable(rsig_data->mutable_grouping(), m_ct.grouping_vct.begin(), m_ct.grouping_vct.end());
+
+ translate_dst_entry(tsx_data.mutable_change_dts(), &(tx.change_dts));
+ for(auto & cur : tx.splitted_dsts){
+ auto dst = tsx_data.mutable_outputs()->Add();
+ translate_dst_entry(dst, &cur);
+ }
+
+ compute_integrated_indices(&tsx_data);
+
+ int64_t fee = 0;
+ for(auto & cur_in : tx.sources){
+ fee += cur_in.amount;
+ }
+ for(auto & cur_out : tx.splitted_dsts){
+ fee -= cur_out.amount;
+ }
+ if (fee < 0){
+ throw std::invalid_argument("Fee cannot be negative");
+ }
+
+ tsx_data.set_fee(static_cast<google::protobuf::uint64>(fee));
+ this->extract_payment_id();
+
+ auto init_req = std::make_shared<messages::monero::MoneroTransactionInitRequest>();
+ init_req->set_version(0);
+ init_req->mutable_tsx_data()->CopyFrom(tsx_data);
+ return init_req;
+ }
+
+ void Signer::step_init_ack(std::shared_ptr<const messages::monero::MoneroTransactionInitAck> ack){
+ m_ct.in_memory = false;
+ if (ack->has_rsig_data()){
+ m_ct.rsig_param = std::make_shared<MoneroRsigData>(ack->rsig_data());
+ }
+
+ assign_from_repeatable(&(m_ct.tx_out_entr_hmacs), ack->hmacs().begin(), ack->hmacs().end());
+ }
+
+ std::shared_ptr<messages::monero::MoneroTransactionSetInputRequest> Signer::step_set_input(size_t idx){
+ CHECK_AND_ASSERT_THROW_MES(idx < cur_tx().sources.size(), "Invalid source index");
+ m_ct.cur_input_idx = idx;
+ auto res = std::make_shared<messages::monero::MoneroTransactionSetInputRequest>();
+ translate_src_entry(res->mutable_src_entr(), &(cur_tx().sources[idx]));
+ return res;
+ }
+
+ void Signer::step_set_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetInputAck> ack){
+ auto & vini_str = ack->vini();
+
+ cryptonote::txin_v vini;
+ if (!cn_deserialize(vini_str.data(), vini_str.size(), vini)){
+ throw exc::ProtocolException("Cannot deserialize vin[i]");
+ }
+
+ m_ct.tx.vin.emplace_back(vini);
+ m_ct.tx_in_hmacs.push_back(ack->vini_hmac());
+ m_ct.pseudo_outs.push_back(ack->pseudo_out());
+ m_ct.pseudo_outs_hmac.push_back(ack->pseudo_out_hmac());
+ m_ct.alphas.push_back(ack->pseudo_out_alpha());
+ m_ct.spend_encs.push_back(ack->spend_key());
+ }
+
+ void Signer::sort_ki(){
+ const size_t input_size = cur_tx().sources.size();
+
+ m_ct.source_permutation.clear();
+ for (size_t n = 0; n < input_size; ++n){
+ m_ct.source_permutation.push_back(n);
+ }
+
+ CHECK_AND_ASSERT_THROW_MES(m_ct.tx.vin.size() == input_size, "Invalid vector size");
+ std::sort(m_ct.source_permutation.begin(), m_ct.source_permutation.end(), [&](const size_t i0, const size_t i1) {
+ const cryptonote::txin_to_key &tk0 = boost::get<cryptonote::txin_to_key>(m_ct.tx.vin[i0]);
+ const cryptonote::txin_to_key &tk1 = boost::get<cryptonote::txin_to_key>(m_ct.tx.vin[i1]);
+ return memcmp(&tk0.k_image, &tk1.k_image, sizeof(tk0.k_image)) > 0;
+ });
+
+ CHECK_AND_ASSERT_THROW_MES(m_ct.tx_in_hmacs.size() == input_size, "Invalid vector size");
+ CHECK_AND_ASSERT_THROW_MES(m_ct.pseudo_outs.size() == input_size, "Invalid vector size");
+ CHECK_AND_ASSERT_THROW_MES(m_ct.pseudo_outs_hmac.size() == input_size, "Invalid vector size");
+ CHECK_AND_ASSERT_THROW_MES(m_ct.alphas.size() == input_size, "Invalid vector size");
+ CHECK_AND_ASSERT_THROW_MES(m_ct.spend_encs.size() == input_size, "Invalid vector size");
+ CHECK_AND_ASSERT_THROW_MES(m_ct.tx_data.sources.size() == input_size, "Invalid vector size");
+
+ tools::apply_permutation(m_ct.source_permutation, [&](size_t i0, size_t i1){
+ std::swap(m_ct.tx.vin[i0], m_ct.tx.vin[i1]);
+ std::swap(m_ct.tx_in_hmacs[i0], m_ct.tx_in_hmacs[i1]);
+ std::swap(m_ct.pseudo_outs[i0], m_ct.pseudo_outs[i1]);
+ std::swap(m_ct.pseudo_outs_hmac[i0], m_ct.pseudo_outs_hmac[i1]);
+ std::swap(m_ct.alphas[i0], m_ct.alphas[i1]);
+ std::swap(m_ct.spend_encs[i0], m_ct.spend_encs[i1]);
+ std::swap(m_ct.tx_data.sources[i0], m_ct.tx_data.sources[i1]);
+ });
+ }
+
+ std::shared_ptr<messages::monero::MoneroTransactionInputsPermutationRequest> Signer::step_permutation(){
+ sort_ki();
+
+ if (in_memory()){
+ return nullptr;
+ }
+
+ auto res = std::make_shared<messages::monero::MoneroTransactionInputsPermutationRequest>();
+ assign_to_repeatable(res->mutable_perm(), m_ct.source_permutation.begin(), m_ct.source_permutation.end());
+
+ return res;
+ }
+
+ void Signer::step_permutation_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputsPermutationAck> ack){
+ if (in_memory()){
+ return;
+ }
+ }
+
+ std::shared_ptr<messages::monero::MoneroTransactionInputViniRequest> Signer::step_set_vini_input(size_t idx){
+ if (in_memory()){
+ return nullptr;
+ }
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_data.sources.size(), "Invalid transaction index");
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx.vin.size(), "Invalid transaction index");
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_in_hmacs.size(), "Invalid transaction index");
+
+ m_ct.cur_input_idx = idx;
+ auto tx = m_ct.tx_data;
+ auto res = std::make_shared<messages::monero::MoneroTransactionInputViniRequest>();
+ auto & vini = m_ct.tx.vin[idx];
+ translate_src_entry(res->mutable_src_entr(), &(tx.sources[idx]));
+ res->set_vini(cryptonote::t_serializable_object_to_blob(vini));
+ res->set_vini_hmac(m_ct.tx_in_hmacs[idx]);
+ if (!in_memory()) {
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.pseudo_outs.size(), "Invalid transaction index");
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.pseudo_outs_hmac.size(), "Invalid transaction index");
+ res->set_pseudo_out(m_ct.pseudo_outs[idx]);
+ res->set_pseudo_out_hmac(m_ct.pseudo_outs_hmac[idx]);
+ }
+
+ return res;
+ }
+
+ void Signer::step_set_vini_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputViniAck> ack){
+ if (in_memory()){
+ return;
+ }
+ }
+
+ std::shared_ptr<messages::monero::MoneroTransactionAllInputsSetRequest> Signer::step_all_inputs_set(){
+ return std::make_shared<messages::monero::MoneroTransactionAllInputsSetRequest>();
+ }
+
+ void Signer::step_all_inputs_set_ack(std::shared_ptr<const messages::monero::MoneroTransactionAllInputsSetAck> ack){
+ if (is_offloading()){
+ // If offloading, expect rsig configuration.
+ if (!ack->has_rsig_data()){
+ throw exc::ProtocolException("Rsig offloading requires rsig param");
+ }
+
+ auto & rsig_data = ack->rsig_data();
+ if (!rsig_data.has_mask()){
+ throw exc::ProtocolException("Gamma masks not present in offloaded version");
+ }
+
+ auto & mask = rsig_data.mask();
+ if (mask.size() != 32 * num_outputs()){
+ throw exc::ProtocolException("Invalid number of gamma masks");
+ }
+
+ m_ct.rsig_gamma.reserve(num_outputs());
+ for(size_t c=0; c < num_outputs(); ++c){
+ rct::key cmask{};
+ memcpy(cmask.bytes, mask.data() + c * 32, 32);
+ m_ct.rsig_gamma.emplace_back(cmask);
+ }
+ }
+ }
+
+ std::shared_ptr<messages::monero::MoneroTransactionSetOutputRequest> Signer::step_set_output(size_t idx){
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_data.splitted_dsts.size(), "Invalid transaction index");
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_out_entr_hmacs.size(), "Invalid transaction index");
+
+ m_ct.cur_output_idx = idx;
+ m_ct.cur_output_in_batch_idx += 1; // assumes sequential call to step_set_output()
+
+ auto res = std::make_shared<messages::monero::MoneroTransactionSetOutputRequest>();
+ auto & cur_dst = m_ct.tx_data.splitted_dsts[idx];
+ translate_dst_entry(res->mutable_dst_entr(), &cur_dst);
+ res->set_dst_entr_hmac(m_ct.tx_out_entr_hmacs[idx]);
+
+ // Range sig offloading to the host
+ if (!is_offloading()) {
+ return res;
+ }
+
+ CHECK_AND_ASSERT_THROW_MES(m_ct.cur_batch_idx < m_ct.grouping_vct.size(), "Invalid batch index");
+ if (m_ct.grouping_vct[m_ct.cur_batch_idx] > m_ct.cur_output_in_batch_idx) {
+ return res;
+ }
+
+ auto rsig_data = res->mutable_rsig_data();
+ auto batch_size = m_ct.grouping_vct[m_ct.cur_batch_idx];
+
+ if (!is_req_bulletproof()){
+ if (batch_size > 1){
+ throw std::invalid_argument("Borromean cannot batch outputs");
+ }
+
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.rsig_gamma.size(), "Invalid gamma index");
+ rct::key C{}, mask = m_ct.rsig_gamma[idx];
+ auto genRsig = rct::proveRange(C, mask, cur_dst.amount); // TODO: rsig with given mask
+ auto serRsig = cn_serialize(genRsig);
+ m_ct.tx_out_rsigs.emplace_back(genRsig);
+ rsig_data->set_rsig(serRsig);
+
+ } else {
+ std::vector<uint64_t> amounts;
+ rct::keyV masks;
+ CHECK_AND_ASSERT_THROW_MES(idx + 1 >= batch_size, "Invalid index for batching");
+
+ for(size_t i = 0; i < batch_size; ++i){
+ const size_t bidx = 1 + idx - batch_size + i;
+ CHECK_AND_ASSERT_THROW_MES(bidx < m_ct.tx_data.splitted_dsts.size(), "Invalid gamma index");
+ CHECK_AND_ASSERT_THROW_MES(bidx < m_ct.rsig_gamma.size(), "Invalid gamma index");
+
+ amounts.push_back(m_ct.tx_data.splitted_dsts[bidx].amount);
+ masks.push_back(m_ct.rsig_gamma[bidx]);
+ }
+
+ auto bp = bulletproof_PROVE(amounts, masks);
+ auto serRsig = cn_serialize(bp);
+ m_ct.tx_out_rsigs.emplace_back(bp);
+ rsig_data->set_rsig(serRsig);
+ }
+
+ return res;
+ }
+
+ void Signer::step_set_output_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetOutputAck> ack){
+ cryptonote::tx_out tx_out;
+ rct::rangeSig range_sig{};
+ rct::Bulletproof bproof{};
+ rct::ctkey out_pk{};
+ rct::ecdhTuple ecdh{};
+
+ bool has_rsig = false;
+ std::string rsig_buff;
+
+ if (ack->has_rsig_data()){
+ auto & rsig_data = ack->rsig_data();
+
+ if (rsig_data.has_rsig() && !rsig_data.rsig().empty()){
+ has_rsig = true;
+ rsig_buff = rsig_data.rsig();
+
+ } else if (rsig_data.rsig_parts_size() > 0){
+ has_rsig = true;
+ for (const auto &it : rsig_data.rsig_parts()) {
+ rsig_buff += it;
+ }
+ }
+ }
+
+ if (!cn_deserialize(ack->tx_out(), tx_out)){
+ throw exc::ProtocolException("Cannot deserialize vout[i]");
+ }
+
+ if (!cn_deserialize(ack->out_pk(), out_pk)){
+ throw exc::ProtocolException("Cannot deserialize out_pk");
+ }
+
+ if (!cn_deserialize(ack->ecdh_info(), ecdh)){
+ throw exc::ProtocolException("Cannot deserialize ecdhtuple");
+ }
+
+ if (has_rsig && !is_req_bulletproof() && !cn_deserialize(rsig_buff, range_sig)){
+ throw exc::ProtocolException("Cannot deserialize rangesig");
+ }
+
+ if (has_rsig && is_req_bulletproof() && !cn_deserialize(rsig_buff, bproof)){
+ throw exc::ProtocolException("Cannot deserialize bulletproof rangesig");
+ }
+
+ m_ct.tx.vout.emplace_back(tx_out);
+ m_ct.tx_out_hmacs.push_back(ack->vouti_hmac());
+ m_ct.tx_out_pk.emplace_back(out_pk);
+ m_ct.tx_out_ecdh.emplace_back(ecdh);
+
+ if (!has_rsig){
+ return;
+ }
+
+ if (is_req_bulletproof()){
+ CHECK_AND_ASSERT_THROW_MES(m_ct.cur_batch_idx < m_ct.grouping_vct.size(), "Invalid batch index");
+ auto batch_size = m_ct.grouping_vct[m_ct.cur_batch_idx];
+ for (size_t i = 0; i < batch_size; ++i){
+ const size_t bidx = 1 + m_ct.cur_output_idx - batch_size + i;
+ CHECK_AND_ASSERT_THROW_MES(bidx < m_ct.tx_out_pk.size(), "Invalid out index");
+
+ rct::key commitment = m_ct.tx_out_pk[bidx].mask;
+ commitment = rct::scalarmultKey(commitment, rct::INV_EIGHT);
+ bproof.V.push_back(commitment);
+ }
+
+ m_ct.tx_out_rsigs.emplace_back(bproof);
+ if (!rct::bulletproof_VERIFY(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs.back()))) {
+ throw exc::ProtocolException("Returned range signature is invalid");
+ }
+
+ } else {
+ m_ct.tx_out_rsigs.emplace_back(range_sig);
+
+ if (!rct::verRange(out_pk.mask, boost::get<rct::rangeSig>(m_ct.tx_out_rsigs.back()))) {
+ throw exc::ProtocolException("Returned range signature is invalid");
+ }
+ }
+
+ m_ct.cur_batch_idx += 1;
+ m_ct.cur_output_in_batch_idx = 0;
+ }
+
+ std::shared_ptr<messages::monero::MoneroTransactionAllOutSetRequest> Signer::step_all_outs_set(){
+ return std::make_shared<messages::monero::MoneroTransactionAllOutSetRequest>();
+ }
+
+ void Signer::step_all_outs_set_ack(std::shared_ptr<const messages::monero::MoneroTransactionAllOutSetAck> ack, hw::device &hwdev){
+ m_ct.rv = std::make_shared<rct::rctSig>();
+ m_ct.rv->txnFee = ack->rv().txn_fee();
+ m_ct.rv->type = static_cast<uint8_t>(ack->rv().rv_type());
+ string_to_key(m_ct.rv->message, ack->rv().message());
+
+ // Extra copy
+ m_ct.tx.extra.clear();
+ auto extra = ack->extra();
+ auto extra_data = extra.data();
+ m_ct.tx.extra.reserve(extra.size());
+ for(size_t i = 0; i < extra.size(); ++i){
+ m_ct.tx.extra.push_back(static_cast<uint8_t>(extra_data[i]));
+ }
+
+ ::crypto::hash tx_prefix_hash{};
+ cryptonote::get_transaction_prefix_hash(m_ct.tx, tx_prefix_hash);
+ m_ct.tx_prefix_hash = key_to_string(tx_prefix_hash);
+ if (crypto_verify_32(reinterpret_cast<const unsigned char *>(tx_prefix_hash.data),
+ reinterpret_cast<const unsigned char *>(ack->tx_prefix_hash().data()))){
+ throw exc::proto::SecurityException("Transaction prefix has does not match to the computed value");
+ }
+
+ // RctSig
+ auto num_sources = m_ct.tx_data.sources.size();
+ if (is_simple() || is_req_bulletproof()){
+ auto dst = &m_ct.rv->pseudoOuts;
+ if (is_bulletproof()){
+ dst = &m_ct.rv->p.pseudoOuts;
+ }
+
+ dst->clear();
+ for (const auto &pseudo_out : m_ct.pseudo_outs) {
+ dst->emplace_back();
+ string_to_key(dst->back(), pseudo_out);
+ }
+
+ m_ct.rv->mixRing.resize(num_sources);
+ } else {
+ m_ct.rv->mixRing.resize(m_ct.tsx_data.mixin());
+ m_ct.rv->mixRing[0].resize(num_sources);
+ }
+
+ CHECK_AND_ASSERT_THROW_MES(m_ct.tx_out_pk.size() == m_ct.tx_out_ecdh.size(), "Invalid vector sizes");
+ for(size_t i = 0; i < m_ct.tx_out_ecdh.size(); ++i){
+ m_ct.rv->outPk.push_back(m_ct.tx_out_pk[i]);
+ m_ct.rv->ecdhInfo.push_back(m_ct.tx_out_ecdh[i]);
+ }
+
+ for(size_t i = 0; i < m_ct.tx_out_rsigs.size(); ++i){
+ if (is_bulletproof()){
+ m_ct.rv->p.bulletproofs.push_back(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs[i]));
+ } else {
+ m_ct.rv->p.rangeSigs.push_back(boost::get<rct::rangeSig>(m_ct.tx_out_rsigs[i]));
+ }
+ }
+
+ rct::key hash_computed = rct::get_pre_mlsag_hash(*(m_ct.rv), hwdev);
+ auto & hash = ack->full_message_hash();
+
+ if (hash.size() != 32){
+ throw exc::ProtocolException("Returned mlsag hash has invalid size");
+ }
+
+ if (crypto_verify_32(reinterpret_cast<const unsigned char *>(hash_computed.bytes),
+ reinterpret_cast<const unsigned char *>(hash.data()))){
+ throw exc::proto::SecurityException("Computed MLSAG does not match");
+ }
+ }
+
+ std::shared_ptr<messages::monero::MoneroTransactionSignInputRequest> Signer::step_sign_input(size_t idx){
+ m_ct.cur_input_idx = idx;
+
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_data.sources.size(), "Invalid transaction index");
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx.vin.size(), "Invalid transaction index");
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_in_hmacs.size(), "Invalid transaction index");
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.alphas.size(), "Invalid transaction index");
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.spend_encs.size(), "Invalid transaction index");
+
+ auto res = std::make_shared<messages::monero::MoneroTransactionSignInputRequest>();
+ translate_src_entry(res->mutable_src_entr(), &(m_ct.tx_data.sources[idx]));
+ res->set_vini(cryptonote::t_serializable_object_to_blob(m_ct.tx.vin[idx]));
+ res->set_vini_hmac(m_ct.tx_in_hmacs[idx]);
+ res->set_pseudo_out_alpha(m_ct.alphas[idx]);
+ res->set_spend_key(m_ct.spend_encs[idx]);
+ if (!in_memory()){
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.pseudo_outs.size(), "Invalid transaction index");
+ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.pseudo_outs_hmac.size(), "Invalid transaction index");
+ res->set_pseudo_out(m_ct.pseudo_outs[idx]);
+ res->set_pseudo_out_hmac(m_ct.pseudo_outs_hmac[idx]);
+ }
+ return res;
+ }
+
+ void Signer::step_sign_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionSignInputAck> ack){
+ rct::mgSig mg;
+ if (!cn_deserialize(ack->signature(), mg)){
+ throw exc::ProtocolException("Cannot deserialize mg[i]");
+ }
+
+ m_ct.rv->p.MGs.push_back(mg);
+ }
+
+ std::shared_ptr<messages::monero::MoneroTransactionFinalRequest> Signer::step_final(){
+ m_ct.tx.rct_signatures = *(m_ct.rv);
+ return std::make_shared<messages::monero::MoneroTransactionFinalRequest>();
+ }
+
+ void Signer::step_final_ack(std::shared_ptr<const messages::monero::MoneroTransactionFinalAck> ack){
+ if (m_multisig){
+ auto & cout_key = ack->cout_key();
+ for(auto & cur : m_ct.couts){
+ if (cur.size() != 12 + 32){
+ throw std::invalid_argument("Encrypted cout has invalid length");
+ }
+
+ char buff[32];
+ auto data = cur.data();
+
+ crypto::chacha::decrypt(data + 12, 32, reinterpret_cast<const uint8_t *>(cout_key.data()), reinterpret_cast<const uint8_t *>(data), buff);
+ m_ct.couts_dec.emplace_back(buff, 32);
+ }
+ }
+
+ m_ct.enc_salt1 = ack->salt();
+ m_ct.enc_salt2 = ack->rand_mult();
+ m_ct.enc_keys = ack->tx_enc_keys();
+ }
+
+ std::string Signer::store_tx_aux_info(){
+ rapidjson::StringBuffer sb;
+ rapidjson::Writer<rapidjson::StringBuffer> writer(sb);
+
+ rapidjson::Document json;
+ json.SetObject();
+
+ rapidjson::Value valueS(rapidjson::kStringType);
+ rapidjson::Value valueI(rapidjson::kNumberType);
+
+ valueI.SetInt(1);
+ json.AddMember("version", valueI, json.GetAllocator());
+
+ valueS.SetString(m_ct.enc_salt1.c_str(), m_ct.enc_salt1.size());
+ json.AddMember("salt1", valueS, json.GetAllocator());
+
+ valueS.SetString(m_ct.enc_salt2.c_str(), m_ct.enc_salt2.size());
+ json.AddMember("salt2", valueS, json.GetAllocator());
+
+ valueS.SetString(m_ct.enc_keys.c_str(), m_ct.enc_keys.size());
+ json.AddMember("enc_keys", valueS, json.GetAllocator());
+
+ json.Accept(writer);
+ return sb.GetString();
+ }
+
+
+}
+}
+}
+}
diff --git a/src/device_trezor/trezor/protocol.hpp b/src/device_trezor/trezor/protocol.hpp
new file mode 100644
index 000000000..99211efed
--- /dev/null
+++ b/src/device_trezor/trezor/protocol.hpp
@@ -0,0 +1,300 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef MONERO_PROTOCOL_H
+#define MONERO_PROTOCOL_H
+
+#include "trezor_defs.hpp"
+#include "device/device_cold.hpp"
+#include "messages_map.hpp"
+#include "transport.hpp"
+#include "wallet/wallet2.h"
+
+namespace hw{
+namespace trezor{
+namespace protocol{
+
+ std::string key_to_string(const ::crypto::ec_point & key);
+ std::string key_to_string(const ::crypto::ec_scalar & key);
+ std::string key_to_string(const ::crypto::hash & key);
+ std::string key_to_string(const ::rct::key & key);
+
+ void string_to_key(::crypto::ec_scalar & key, const std::string & str);
+ void string_to_key(::crypto::ec_point & key, const std::string & str);
+ void string_to_key(::rct::key & key, const std::string & str);
+
+ template<class sub_t, class InputIterator>
+ void assign_to_repeatable(::google::protobuf::RepeatedField<sub_t> * dst, const InputIterator begin, const InputIterator end){
+ for (InputIterator it = begin; it != end; it++) {
+ auto s = dst->Add();
+ *s = *it;
+ }
+ }
+
+ template<class sub_t, class InputIterator>
+ void assign_from_repeatable(std::vector<sub_t> * dst, const InputIterator begin, const InputIterator end){
+ for (InputIterator it = begin; it != end; it++) {
+ dst->push_back(*it);
+ }
+ };
+
+ template<typename T>
+ bool cn_deserialize(const void * buff, size_t len, T & dst){
+ std::stringstream ss;
+ ss.write(static_cast<const char *>(buff), len); //ss << tx_blob;
+ binary_archive<false> ba(ss);
+ bool r = ::serialization::serialize(ba, dst);
+ return r;
+ }
+
+ template<typename T>
+ bool cn_deserialize(const std::string & str, T & dst){
+ return cn_deserialize(str.data(), str.size(), dst);
+ }
+
+ template<typename T>
+ std::string cn_serialize(T & obj){
+ std::ostringstream oss;
+ binary_archive<true> oar(oss);
+ bool success = ::serialization::serialize(oar, obj);
+ if (!success){
+ throw exc::EncodingException("Could not CN serialize given object");
+ }
+ return oss.str();
+ }
+
+// Crypto / encryption
+namespace crypto {
+namespace chacha {
+
+ /**
+ * Chacha20Poly1305 decryption with tag verification. RFC 7539.
+ */
+ void decrypt(const void* ciphertext, size_t length, const uint8_t* key, const uint8_t* iv, char* plaintext);
+
+}
+}
+
+
+// Cold Key image sync
+namespace ki {
+
+ using MoneroTransferDetails = messages::monero::MoneroKeyImageSyncStepRequest_MoneroTransferDetails;
+ using MoneroSubAddressIndicesList = messages::monero::MoneroKeyImageExportInitRequest_MoneroSubAddressIndicesList;
+ using MoneroExportedKeyImage = messages::monero::MoneroKeyImageSyncStepAck_MoneroExportedKeyImage;
+ using exported_key_image = hw::device_cold::exported_key_image;
+
+ /**
+ * Converts transfer details to the MoneroTransferDetails required for KI sync
+ */
+ bool key_image_data(wallet_shim * wallet,
+ const std::vector<tools::wallet2::transfer_details> & transfers,
+ std::vector<MoneroTransferDetails> & res);
+
+ /**
+ * Computes a hash over MoneroTransferDetails. Commitment used in the KI sync.
+ */
+ std::string compute_hash(const MoneroTransferDetails & rr);
+
+ /**
+ * Generates KI sync request with commitments computed.
+ */
+ void generate_commitment(std::vector<MoneroTransferDetails> & mtds,
+ const std::vector<tools::wallet2::transfer_details> & transfers,
+ std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req);
+
+}
+
+// Cold transaction signing
+namespace tx {
+ using TsxData = messages::monero::MoneroTransactionInitRequest_MoneroTransactionData;
+ using MoneroTransactionDestinationEntry = messages::monero::MoneroTransactionDestinationEntry;
+ using MoneroAccountPublicAddress = messages::monero::MoneroTransactionDestinationEntry_MoneroAccountPublicAddress;
+ using MoneroTransactionSourceEntry = messages::monero::MoneroTransactionSourceEntry;
+ using MoneroMultisigKLRki = messages::monero::MoneroTransactionSourceEntry_MoneroMultisigKLRki;
+ using MoneroOutputEntry = messages::monero::MoneroTransactionSourceEntry_MoneroOutputEntry;
+ using MoneroRctKey = messages::monero::MoneroTransactionSourceEntry_MoneroOutputEntry_MoneroRctKeyPublic;
+ using MoneroRsigData = messages::monero::MoneroTransactionRsigData;
+
+ using tx_construction_data = tools::wallet2::tx_construction_data;
+ using unsigned_tx_set = tools::wallet2::unsigned_tx_set;
+
+ void translate_address(MoneroAccountPublicAddress * dst, const cryptonote::account_public_address * src);
+ void translate_dst_entry(MoneroTransactionDestinationEntry * dst, const cryptonote::tx_destination_entry * src);
+ void translate_src_entry(MoneroTransactionSourceEntry * dst, const cryptonote::tx_source_entry * src);
+ void translate_klrki(MoneroMultisigKLRki * dst, const rct::multisig_kLRki * src);
+ void translate_rct_key(MoneroRctKey * dst, const rct::ctkey * src);
+ std::string hash_addr(const MoneroAccountPublicAddress * addr, boost::optional<uint64_t> amount = boost::none, boost::optional<bool> is_subaddr = boost::none);
+ std::string hash_addr(const std::string & spend_key, const std::string & view_key, boost::optional<uint64_t> amount = boost::none, boost::optional<bool> is_subaddr = boost::none);
+ std::string hash_addr(const ::crypto::public_key * spend_key, const ::crypto::public_key * view_key, boost::optional<uint64_t> amount = boost::none, boost::optional<bool> is_subaddr = boost::none);
+
+ typedef boost::variant<rct::rangeSig, rct::Bulletproof> rsig_v;
+
+ /**
+ * Transaction signer state holder.
+ */
+ class TData {
+ public:
+ TsxData tsx_data;
+ tx_construction_data tx_data;
+ cryptonote::transaction tx;
+ bool in_memory;
+ unsigned rsig_type;
+ std::vector<uint64_t> grouping_vct;
+ std::shared_ptr<MoneroRsigData> rsig_param;
+ size_t cur_input_idx;
+ size_t cur_output_idx;
+ size_t cur_batch_idx;
+ size_t cur_output_in_batch_idx;
+
+ std::vector<std::string> tx_in_hmacs;
+ std::vector<std::string> tx_out_entr_hmacs;
+ std::vector<std::string> tx_out_hmacs;
+ std::vector<rsig_v> tx_out_rsigs;
+ std::vector<rct::ctkey> tx_out_pk;
+ std::vector<rct::ecdhTuple> tx_out_ecdh;
+ std::vector<size_t> source_permutation;
+ std::vector<std::string> alphas;
+ std::vector<std::string> spend_encs;
+ std::vector<std::string> pseudo_outs;
+ std::vector<std::string> pseudo_outs_hmac;
+ std::vector<std::string> couts;
+ std::vector<std::string> couts_dec;
+ std::vector<rct::key> rsig_gamma;
+ std::string tx_prefix_hash;
+ std::string enc_salt1;
+ std::string enc_salt2;
+ std::string enc_keys;
+
+ std::shared_ptr<rct::rctSig> rv;
+
+ TData();
+ };
+
+ class Signer {
+ private:
+ TData m_ct;
+ wallet_shim * m_wallet2;
+
+ size_t m_tx_idx;
+ const unsigned_tx_set * m_unsigned_tx;
+ hw::tx_aux_data * m_aux_data;
+
+ bool m_multisig;
+
+ const tx_construction_data & cur_tx(){
+ CHECK_AND_ASSERT_THROW_MES(m_tx_idx < m_unsigned_tx->txes.size(), "Invalid transaction index");
+ return m_unsigned_tx->txes[m_tx_idx];
+ }
+
+ void extract_payment_id();
+ void compute_integrated_indices(TsxData * tsx_data);
+
+ public:
+ Signer(wallet_shim * wallet2, const unsigned_tx_set * unsigned_tx, size_t tx_idx = 0, hw::tx_aux_data * aux_data = nullptr);
+
+ std::shared_ptr<messages::monero::MoneroTransactionInitRequest> step_init();
+ void step_init_ack(std::shared_ptr<const messages::monero::MoneroTransactionInitAck> ack);
+
+ std::shared_ptr<messages::monero::MoneroTransactionSetInputRequest> step_set_input(size_t idx);
+ void step_set_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetInputAck> ack);
+
+ void sort_ki();
+ std::shared_ptr<messages::monero::MoneroTransactionInputsPermutationRequest> step_permutation();
+ void step_permutation_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputsPermutationAck> ack);
+
+ std::shared_ptr<messages::monero::MoneroTransactionInputViniRequest> step_set_vini_input(size_t idx);
+ void step_set_vini_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputViniAck> ack);
+
+ std::shared_ptr<messages::monero::MoneroTransactionAllInputsSetRequest> step_all_inputs_set();
+ void step_all_inputs_set_ack(std::shared_ptr<const messages::monero::MoneroTransactionAllInputsSetAck> ack);
+
+ std::shared_ptr<messages::monero::MoneroTransactionSetOutputRequest> step_set_output(size_t idx);
+ void step_set_output_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetOutputAck> ack);
+
+ std::shared_ptr<messages::monero::MoneroTransactionAllOutSetRequest> step_all_outs_set();
+ void step_all_outs_set_ack(std::shared_ptr<const messages::monero::MoneroTransactionAllOutSetAck> ack, hw::device &hwdev);
+
+ std::shared_ptr<messages::monero::MoneroTransactionSignInputRequest> step_sign_input(size_t idx);
+ void step_sign_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionSignInputAck> ack);
+
+ std::shared_ptr<messages::monero::MoneroTransactionFinalRequest> step_final();
+ void step_final_ack(std::shared_ptr<const messages::monero::MoneroTransactionFinalAck> ack);
+
+ std::string store_tx_aux_info();
+
+ bool in_memory() const {
+ return m_ct.in_memory;
+ }
+
+ bool is_simple() const {
+ if (!m_ct.rv){
+ throw std::invalid_argument("RV not initialized");
+ }
+ auto tp = m_ct.rv->type;
+ return tp == rct::RCTTypeSimple;
+ }
+
+ bool is_req_bulletproof() const {
+ return m_ct.tx_data.use_bulletproofs;
+ }
+
+ bool is_bulletproof() const {
+ if (!m_ct.rv){
+ throw std::invalid_argument("RV not initialized");
+ }
+ auto tp = m_ct.rv->type;
+ return tp == rct::RCTTypeBulletproof;
+ }
+
+ bool is_offloading() const {
+ return m_ct.rsig_param && m_ct.rsig_param->offload_type() != 0;
+ }
+
+ size_t num_outputs() const {
+ return m_ct.tx_data.splitted_dsts.size();
+ }
+
+ size_t num_inputs() const {
+ return m_ct.tx_data.sources.size();
+ }
+
+ const TData & tdata() const {
+ return m_ct;
+ }
+ };
+
+}
+
+}
+}
+}
+
+
+#endif //MONERO_PROTOCOL_H
diff --git a/src/device_trezor/trezor/tools/README.md b/src/device_trezor/trezor/tools/README.md
new file mode 100644
index 000000000..91a8fb3f0
--- /dev/null
+++ b/src/device_trezor/trezor/tools/README.md
@@ -0,0 +1,36 @@
+# Trezor
+
+## Messages rebuild
+
+Install `protoc` for your distribution.
+
+- `protobuf-compiler`
+- `libprotobuf-dev`
+- `libprotoc-dev`
+- `python-protobuf`
+
+Python 3 is required. If you don't have python 3 quite an easy way is
+to use [pyenv].
+
+It is also advised to create own python virtual environment so dependencies
+are installed in this project-related virtual environment.
+
+```bash
+python -m venv /
+```
+
+Make sure your python has `protobuf` package installed
+
+```bash
+pip install protobuf
+```
+
+Regenerate messages:
+
+```
+./venv/bin/python3 src/device_trezor/trezor/tools/build_protob.py
+```
+
+The messages regeneration is done also automatically via cmake.
+
+[pyenv]: https://github.com/pyenv/pyenv
diff --git a/src/device_trezor/trezor/tools/build_protob.py b/src/device_trezor/trezor/tools/build_protob.py
new file mode 100644
index 000000000..2611f3296
--- /dev/null
+++ b/src/device_trezor/trezor/tools/build_protob.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+import os
+import subprocess
+import sys
+
+CWD = os.path.dirname(os.path.realpath(__file__))
+ROOT_DIR = os.path.abspath(os.path.join(CWD, "..", "..", "..", ".."))
+TREZOR_COMMON = os.path.join(ROOT_DIR, "external", "trezor-common")
+TREZOR_MESSAGES = os.path.join(CWD, "..", "messages")
+
+# check for existence of the submodule directory
+common_defs = os.path.join(TREZOR_COMMON, "defs")
+if not os.path.exists(common_defs):
+ raise ValueError(
+ "trezor-common submodule seems to be missing.\n"
+ + 'Use "git submodule update --init --recursive" to retrieve it.'
+ )
+
+# regenerate messages
+try:
+ selected = [
+ "messages.proto",
+ "messages-common.proto",
+ "messages-management.proto",
+ "messages-monero.proto",
+ ]
+ proto_srcs = [os.path.join(TREZOR_COMMON, "protob", x) for x in selected]
+ exec_args = [
+ sys.executable,
+ os.path.join(CWD, "pb2cpp.py"),
+ "-o",
+ TREZOR_MESSAGES,
+ ] + proto_srcs
+
+ subprocess.check_call(exec_args)
+
+except Exception as e:
+ raise
diff --git a/src/device_trezor/trezor/tools/pb2cpp.py b/src/device_trezor/trezor/tools/pb2cpp.py
new file mode 100644
index 000000000..eaa8a90ed
--- /dev/null
+++ b/src/device_trezor/trezor/tools/pb2cpp.py
@@ -0,0 +1,186 @@
+#!/usr/bin/env python3
+# Converts Google's protobuf python definitions of TREZOR wire messages
+# to plain-python objects as used in TREZOR Core and python-trezor
+
+import argparse
+import logging
+import os
+import re
+import shutil
+import subprocess
+import sys
+import glob
+import tempfile
+import hashlib
+
+
+AUTO_HEADER = "# Automatically generated by pb2cpp\n"
+
+# Fixing GCC7 compilation error
+UNDEF_STATEMENT = """
+#ifdef minor
+#undef minor
+#endif
+"""
+
+
+def which(pgm):
+ path = os.getenv('PATH')
+ for p in path.split(os.path.pathsep):
+ p = os.path.join(p, pgm)
+ if os.path.exists(p) and os.access(p, os.X_OK):
+ return p
+
+
+PROTOC = which("protoc")
+if not PROTOC:
+ print("protoc command not found")
+ sys.exit(1)
+
+PROTOC_PREFIX = os.path.dirname(os.path.dirname(PROTOC))
+PROTOC_INCLUDE = os.path.join(PROTOC_PREFIX, "include")
+
+
+def namespace_file(fpath, package):
+ """Adds / replaces package name. Simple regex parsing, may use https://github.com/ph4r05/plyprotobuf later"""
+ with open(fpath) as fh:
+ fdata = fh.read()
+
+ re_syntax = re.compile(r"^syntax\s*=")
+ re_package = re.compile(r"^package\s+([^;]+?)\s*;\s*$")
+ lines = fdata.split("\n")
+
+ line_syntax = None
+ line_package = None
+ for idx, line in enumerate(lines):
+ if line_syntax is None and re_syntax.match(line):
+ line_syntax = idx
+ if line_package is None and re_package.match(line):
+ line_package = idx
+
+ if package is None:
+ if line_package is None:
+ return
+ else:
+ lines.pop(line_package)
+
+ else:
+ new_package = "package %s;" % package
+ if line_package is None:
+ lines.insert(line_syntax + 1 if line_syntax is not None else 0, new_package)
+ else:
+ lines[line_package] = new_package
+
+ new_fdat = "\n".join(lines)
+ with open(fpath, "w+") as fh:
+ fh.write(new_fdat)
+ return new_fdat
+
+
+def protoc(files, out_dir, additional_includes=(), package=None, force=False):
+ """Compile code with protoc and return the data."""
+
+ include_dirs = set()
+ include_dirs.add(PROTOC_INCLUDE)
+ include_dirs.update(additional_includes)
+
+ with tempfile.TemporaryDirectory() as tmpdir_protob, tempfile.TemporaryDirectory() as tmpdir_out:
+ include_dirs.add(tmpdir_protob)
+
+ new_files = []
+ for file in files:
+ bname = os.path.basename(file)
+ tmp_file = os.path.join(tmpdir_protob, bname)
+
+ shutil.copy(file, tmp_file)
+ if package is not None:
+ namespace_file(tmp_file, package)
+ new_files.append(tmp_file)
+
+ protoc_includes = ["-I" + dir for dir in include_dirs if dir]
+
+ exec_args = (
+ [
+ PROTOC,
+ "--cpp_out",
+ tmpdir_out,
+ ]
+ + protoc_includes
+ + new_files
+ )
+
+ subprocess.check_call(exec_args)
+
+ # Fixing gcc compilation and clashes with "minor" field name
+ add_undef(tmpdir_out)
+
+ # Scan output dir, check file differences
+ update_message_files(tmpdir_out, out_dir, force)
+
+
+def update_message_files(tmpdir_out, out_dir, force=False):
+ files = glob.glob(os.path.join(tmpdir_out, '*.pb.*'))
+ for fname in files:
+ bname = os.path.basename(fname)
+ dest_file = os.path.join(out_dir, bname)
+ if not force and os.path.exists(dest_file):
+ data = open(fname, 'rb').read()
+ data_hash = hashlib.sha3_256(data).digest()
+ data_dest = open(dest_file, 'rb').read()
+ data_dest_hash = hashlib.sha3_256(data_dest).digest()
+ if data_hash == data_dest_hash:
+ continue
+
+ shutil.copy(fname, dest_file)
+
+
+def add_undef(out_dir):
+ files = glob.glob(os.path.join(out_dir, '*.pb.*'))
+ for fname in files:
+ with open(fname) as fh:
+ lines = fh.readlines()
+
+ idx_insertion = None
+ for idx in range(len(lines)):
+ if '@@protoc_insertion_point(includes)' in lines[idx]:
+ idx_insertion = idx
+ break
+
+ if idx_insertion is None:
+ pass
+
+ lines.insert(idx_insertion + 1, UNDEF_STATEMENT)
+ with open(fname, 'w') as fh:
+ fh.write("".join(lines))
+
+
+def strip_leader(s, prefix):
+ """Remove given prefix from underscored name."""
+ leader = prefix + "_"
+ if s.startswith(leader):
+ return s[len(leader) :]
+ else:
+ return s
+
+
+if __name__ == "__main__":
+ logging.basicConfig(level=logging.DEBUG)
+
+ parser = argparse.ArgumentParser()
+ # fmt: off
+ parser.add_argument("proto", nargs="+", help="Protobuf definition files")
+ parser.add_argument("-o", "--out-dir", help="Directory for generated source code")
+ parser.add_argument("-n", "--namespace", default=None, help="Message namespace")
+ parser.add_argument("-I", "--protoc-include", action="append", help="protoc include path")
+ parser.add_argument("-P", "--protobuf-module", default="protobuf", help="Name of protobuf module")
+ parser.add_argument("-f", "--force", default=False, help="Overwrite existing files")
+ # fmt: on
+ args = parser.parse_args()
+
+ protoc_includes = args.protoc_include or (os.environ.get("PROTOC_INCLUDE"),)
+
+ protoc(
+ args.proto, args.out_dir, protoc_includes, package=args.namespace, force=args.force
+ )
+
+
diff --git a/src/device_trezor/trezor/transport.cpp b/src/device_trezor/trezor/transport.cpp
new file mode 100644
index 000000000..fc86177e1
--- /dev/null
+++ b/src/device_trezor/trezor/transport.cpp
@@ -0,0 +1,651 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <boost/endian/conversion.hpp>
+#include <boost/asio/io_service.hpp>
+#include <boost/asio/ip/udp.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+#include "transport.hpp"
+#include "messages/messages-common.pb.h"
+
+using namespace std;
+using json = rapidjson::Document;
+
+
+namespace hw{
+namespace trezor{
+
+ bool t_serialize(const std::string & in, std::string & out){
+ out = in;
+ return true;
+ }
+
+ bool t_serialize(const json_val & in, std::string & out){
+ rapidjson::StringBuffer sb;
+ rapidjson::Writer<rapidjson::StringBuffer> writer(sb);
+ in.Accept(writer);
+ out = sb.GetString();
+ return true;
+ }
+
+ std::string t_serialize(const json_val & in){
+ std::string ret;
+ t_serialize(in, ret);
+ return ret;
+ }
+
+ bool t_deserialize(const std::string & in, std::string & out){
+ out = in;
+ return true;
+ }
+
+ bool t_deserialize(const std::string & in, json & out){
+ if (out.Parse(in.c_str()).HasParseError()) {
+ throw exc::CommunicationException("JSON parse error");
+ }
+ return true;
+ }
+
+ static std::string json_get_string(const rapidjson::Value & in){
+ return std::string(in.GetString());
+ }
+
+ //
+ // Helpers
+ //
+
+#define PROTO_HEADER_SIZE 6
+
+ static size_t message_size(const google::protobuf::Message &req){
+ return static_cast<size_t>(req.ByteSize());
+ }
+
+ static size_t serialize_message_buffer_size(size_t msg_size) {
+ return PROTO_HEADER_SIZE + msg_size; // tag 2B + len 4B
+ }
+
+ static void serialize_message_header(void * buff, uint16_t tag, uint32_t len){
+ uint16_t wire_tag = boost::endian::native_to_big(static_cast<uint16_t>(tag));
+ uint32_t wire_len = boost::endian::native_to_big(static_cast<uint32_t>(len));
+ memcpy(buff, (void *) &wire_tag, 2);
+ memcpy((uint8_t*)buff + 2, (void *) &wire_len, 4);
+ }
+
+ static void deserialize_message_header(const void * buff, uint16_t & tag, uint32_t & len){
+ uint16_t wire_tag;
+ uint32_t wire_len;
+ memcpy(&wire_tag, buff, 2);
+ memcpy(&wire_len, (uint8_t*)buff + 2, 4);
+
+ tag = boost::endian::big_to_native(wire_tag);
+ len = boost::endian::big_to_native(wire_len);
+ }
+
+ static void serialize_message(const google::protobuf::Message &req, size_t msg_size, uint8_t * buff, size_t buff_size) {
+ auto msg_wire_num = MessageMapper::get_message_wire_number(req);
+ const auto req_buffer_size = serialize_message_buffer_size(msg_size);
+ if (req_buffer_size > buff_size){
+ throw std::invalid_argument("Buffer too small");
+ }
+
+ serialize_message_header(buff, msg_wire_num, msg_size);
+ if (!req.SerializeToArray(buff + 6, msg_size)){
+ throw exc::EncodingException("Message serialization error");
+ }
+ }
+
+ //
+ // Communication protocol
+ //
+
+#define REPLEN 64
+
+ void ProtocolV1::write(Transport & transport, const google::protobuf::Message & req){
+ const auto msg_size = message_size(req);
+ const auto buff_size = serialize_message_buffer_size(msg_size) + 2;
+
+ std::unique_ptr<uint8_t[]> req_buff(new uint8_t[buff_size]);
+ uint8_t * req_buff_raw = req_buff.get();
+ req_buff_raw[0] = '#';
+ req_buff_raw[1] = '#';
+
+ serialize_message(req, msg_size, req_buff_raw + 2, buff_size - 2);
+
+ size_t offset = 0;
+ uint8_t chunk_buff[REPLEN];
+
+ // Chunk by chunk upload
+ while(offset < buff_size){
+ auto to_copy = std::min((size_t)(buff_size - offset), (size_t)(REPLEN - 1));
+
+ chunk_buff[0] = '?';
+ memcpy(chunk_buff + 1, req_buff_raw + offset, to_copy);
+
+ // Pad with zeros
+ if (to_copy < REPLEN - 1){
+ memset(chunk_buff + 1 + to_copy, 0, REPLEN - 1 - to_copy);
+ }
+
+ transport.write_chunk(chunk_buff, REPLEN);
+ offset += REPLEN - 1;
+ }
+ }
+
+ void ProtocolV1::read(Transport & transport, std::shared_ptr<google::protobuf::Message> & msg, messages::MessageType * msg_type){
+ char chunk[REPLEN];
+
+ // Initial chunk read
+ size_t nread = transport.read_chunk(chunk, REPLEN);
+ if (nread != REPLEN){
+ throw exc::CommunicationException("Read chunk has invalid size");
+ }
+
+ if (strncmp(chunk, "?##", 3) != 0){
+ throw exc::CommunicationException("Malformed chunk");
+ }
+
+ uint16_t tag;
+ uint32_t len;
+ nread -= 3 + 6;
+ deserialize_message_header(chunk + 3, tag, len);
+
+ std::string data_acc(chunk + 3 + 6, nread);
+ data_acc.reserve(len);
+
+ while(nread < len){
+ const size_t cur = transport.read_chunk(chunk, REPLEN);
+ if (chunk[0] != '?'){
+ throw exc::CommunicationException("Chunk malformed");
+ }
+
+ data_acc.append(chunk + 1, cur - 1);
+ nread += cur - 1;
+ }
+
+ if (msg_type){
+ *msg_type = static_cast<messages::MessageType>(tag);
+ }
+
+ if (nread < len){
+ throw exc::CommunicationException("Response incomplete");
+ }
+
+ std::shared_ptr<google::protobuf::Message> msg_wrap(MessageMapper::get_message(tag));
+ if (!msg_wrap->ParseFromArray(data_acc.c_str(), len)){
+ throw exc::CommunicationException("Message could not be parsed");
+ }
+
+ msg = msg_wrap;
+ }
+
+ //
+ // Bridge transport
+ //
+
+ const char * BridgeTransport::PATH_PREFIX = "bridge:";
+
+ std::string BridgeTransport::get_path() const {
+ if (!m_device_path){
+ return "";
+ }
+
+ std::string path(PATH_PREFIX);
+ return path + m_device_path.get();
+ }
+
+ void BridgeTransport::enumerate(t_transport_vect & res) {
+ json bridge_res;
+ std::string req;
+
+ bool req_status = invoke_bridge_http("/enumerate", req, bridge_res, m_http_client);
+ if (!req_status){
+ throw exc::CommunicationException("Bridge enumeration failed");
+ }
+
+ for(rapidjson::Value::ConstValueIterator itr = bridge_res.Begin(); itr != bridge_res.End(); ++itr){
+ auto element = itr->GetObject();
+ auto t = std::make_shared<BridgeTransport>(boost::make_optional(json_get_string(element["path"])));
+ t->m_device_info.emplace();
+ t->m_device_info->CopyFrom(*itr, t->m_device_info->GetAllocator());
+ res.push_back(t);
+ }
+ }
+
+ void BridgeTransport::open() {
+ if (!m_device_path){
+ throw exc::CommunicationException("Coud not open, empty device path");
+ }
+
+ std::string uri = "/acquire/" + m_device_path.get() + "/null";
+ std::string req;
+ json bridge_res;
+ bool req_status = invoke_bridge_http(uri, req, bridge_res, m_http_client);
+ if (!req_status){
+ throw exc::CommunicationException("Failed to acquire device");
+ }
+
+ m_session = boost::make_optional(json_get_string(bridge_res["session"]));
+ }
+
+ void BridgeTransport::close() {
+ if (!m_device_path || !m_session){
+ throw exc::CommunicationException("Device not open");
+ }
+
+ std::string uri = "/release/" + m_session.get();
+ std::string req;
+ json bridge_res;
+ bool req_status = invoke_bridge_http(uri, req, bridge_res, m_http_client);
+ if (!req_status){
+ throw exc::CommunicationException("Failed to release device");
+ }
+
+ m_session = boost::none;
+ }
+
+ void BridgeTransport::write(const google::protobuf::Message &req) {
+ m_response = boost::none;
+
+ const auto msg_size = message_size(req);
+ const auto buff_size = serialize_message_buffer_size(msg_size);
+
+ std::unique_ptr<uint8_t[]> req_buff(new uint8_t[buff_size]);
+ uint8_t * req_buff_raw = req_buff.get();
+
+ serialize_message(req, msg_size, req_buff_raw, buff_size);
+
+ std::string uri = "/call/" + m_session.get();
+ std::string req_hex = epee::to_hex::string(epee::span<const std::uint8_t>(req_buff_raw, buff_size));
+ std::string res_hex;
+
+ bool req_status = invoke_bridge_http(uri, req_hex, res_hex, m_http_client);
+ if (!req_status){
+ throw exc::CommunicationException("Call method failed");
+ }
+
+ m_response = res_hex;
+ }
+
+ void BridgeTransport::read(std::shared_ptr<google::protobuf::Message> & msg, messages::MessageType * msg_type) {
+ if (!m_response){
+ throw exc::CommunicationException("Could not read, no response stored");
+ }
+
+ std::string bin_data;
+ if (!epee::string_tools::parse_hexstr_to_binbuff(m_response.get(), bin_data)){
+ throw exc::CommunicationException("Response is not well hexcoded");
+ }
+
+ uint16_t msg_tag;
+ uint32_t msg_len;
+ deserialize_message_header(bin_data.c_str(), msg_tag, msg_len);
+ if (bin_data.size() != msg_len + 6){
+ throw exc::CommunicationException("Response is not well hexcoded");
+ }
+
+ if (msg_type){
+ *msg_type = static_cast<messages::MessageType>(msg_tag);
+ }
+
+ std::shared_ptr<google::protobuf::Message> msg_wrap(MessageMapper::get_message(msg_tag));
+ if (!msg_wrap->ParseFromArray(bin_data.c_str() + 6, msg_len)){
+ throw exc::EncodingException("Response is not well hexcoded");
+ }
+ msg = msg_wrap;
+ }
+
+ const boost::optional<json> & BridgeTransport::device_info() const {
+ return m_device_info;
+ }
+
+ std::ostream& BridgeTransport::dump(std::ostream& o) const {
+ return o << "BridgeTransport<path=" << (m_device_path ? get_path() : "None")
+ << ", info=" << (m_device_info ? t_serialize(m_device_info.get()) : "None")
+ << ", session=" << (m_session ? m_session.get() : "None")
+ << ">";
+ }
+
+ //
+ // UdpTransport
+ //
+ const char * UdpTransport::PATH_PREFIX = "udp:";
+ const char * UdpTransport::DEFAULT_HOST = "127.0.0.1";
+ const int UdpTransport::DEFAULT_PORT = 21324;
+
+ UdpTransport::UdpTransport(boost::optional<std::string> device_path,
+ boost::optional<std::shared_ptr<Protocol>> proto) :
+ m_io_service(), m_deadline(m_io_service)
+ {
+ m_device_port = DEFAULT_PORT;
+ if (device_path) {
+ const std::string device_str = device_path.get();
+ auto delim = device_str.find(':');
+ if (delim == std::string::npos) {
+ m_device_host = device_str;
+ } else {
+ m_device_host = device_str.substr(0, delim);
+ m_device_port = std::stoi(device_str.substr(delim + 1));
+ }
+ } else {
+ m_device_host = DEFAULT_HOST;
+ }
+
+ if (m_device_port <= 1024 || m_device_port > 65535){
+ throw std::invalid_argument("Port number invalid");
+ }
+
+ if (m_device_host != "localhost" && m_device_host != DEFAULT_HOST){
+ throw std::invalid_argument("Local endpoint allowed only");
+ }
+
+ m_proto = proto ? proto.get() : std::make_shared<ProtocolV1>();
+ }
+
+ std::string UdpTransport::get_path() const {
+ std::string path(PATH_PREFIX);
+ return path + m_device_host + ":" + std::to_string(m_device_port);
+ }
+
+ void UdpTransport::require_socket(){
+ if (!m_socket){
+ throw exc::NotConnectedException("Socket not connected");
+ }
+ }
+
+ bool UdpTransport::ping(){
+ return ping_int();
+ }
+
+ bool UdpTransport::ping_int(boost::posix_time::time_duration timeout){
+ require_socket();
+ try {
+ std::string req = "PINGPING";
+ char res[8];
+
+ m_socket->send_to(boost::asio::buffer(req.c_str(), req.size()), m_endpoint);
+ receive(res, 8, nullptr, false, timeout);
+
+ return memcmp(res, "PONGPONG", 8) == 0;
+
+ } catch(...){
+ return false;
+ }
+ }
+
+ void UdpTransport::enumerate(t_transport_vect & res) {
+ std::shared_ptr<UdpTransport> t = std::make_shared<UdpTransport>();
+ bool t_works = false;
+
+ try{
+ t->open();
+ t_works = t->ping();
+ } catch(...) {
+
+ }
+ t->close();
+ if (t_works){
+ res.push_back(t);
+ }
+ }
+
+ void UdpTransport::open() {
+ udp::resolver resolver(m_io_service);
+ udp::resolver::query query(udp::v4(), m_device_host, std::to_string(m_device_port));
+ m_endpoint = *resolver.resolve(query);
+
+ m_socket.reset(new udp::socket(m_io_service));
+ m_socket->open(udp::v4());
+
+ m_deadline.expires_at(boost::posix_time::pos_infin);
+ check_deadline();
+
+ m_proto->session_begin(*this);
+ }
+
+ void UdpTransport::close() {
+ if (!m_socket){
+ throw exc::CommunicationException("Socket is already closed");
+ }
+
+ m_proto->session_end(*this);
+ m_socket->close();
+ m_socket = nullptr;
+ }
+
+ void UdpTransport::write_chunk(const void * buff, size_t size){
+ require_socket();
+
+ if (size != 64){
+ throw exc::CommunicationException("Invalid chunk size");
+ }
+
+ auto written = m_socket->send_to(boost::asio::buffer(buff, size), m_endpoint);
+ if (size != written){
+ throw exc::CommunicationException("Could not send the whole chunk");
+ }
+ }
+
+ size_t UdpTransport::read_chunk(void * buff, size_t size){
+ require_socket();
+ if (size < 64){
+ throw std::invalid_argument("Buffer too small");
+ }
+
+ ssize_t len;
+ while(true) {
+ try {
+ boost::system::error_code ec;
+ len = receive(buff, size, &ec, true);
+ if (ec == boost::asio::error::operation_aborted) {
+ continue;
+ } else if (ec) {
+ throw exc::CommunicationException(std::string("Comm error: ") + ec.message());
+ }
+
+ if (len != 64) {
+ throw exc::CommunicationException("Invalid chunk size");
+ }
+
+ break;
+
+ } catch(exc::CommunicationException const& e){
+ throw;
+ } catch(std::exception const& e){
+ MWARNING("Error reading chunk, reason: " << e.what());
+ throw exc::CommunicationException(std::string("Chunk read error: ") + std::string(e.what()));
+ }
+ }
+
+ return static_cast<size_t>(len);
+ }
+
+ ssize_t UdpTransport::receive(void * buff, size_t size, boost::system::error_code * error_code, bool no_throw, boost::posix_time::time_duration timeout){
+ boost::system::error_code ec;
+ boost::asio::mutable_buffer buffer = boost::asio::buffer(buff, size);
+
+ require_socket();
+
+ // Set a deadline for the asynchronous operation.
+ m_deadline.expires_from_now(timeout);
+
+ // Set up the variables that receive the result of the asynchronous
+ // operation. The error code is set to would_block to signal that the
+ // operation is incomplete. Asio guarantees that its asynchronous
+ // operations will never fail with would_block, so any other value in
+ // ec indicates completion.
+ ec = boost::asio::error::would_block;
+ std::size_t length = 0;
+
+ // Start the asynchronous operation itself. The handle_receive function
+ // used as a callback will update the ec and length variables.
+ m_socket->async_receive_from(boost::asio::buffer(buffer), m_endpoint,
+ boost::bind(&UdpTransport::handle_receive, _1, _2, &ec, &length));
+
+ // Block until the asynchronous operation has completed.
+ do {
+ m_io_service.run_one();
+ }
+ while (ec == boost::asio::error::would_block);
+
+ if (error_code){
+ *error_code = ec;
+ }
+
+ if (no_throw){
+ return length;
+ }
+
+ // Operation result
+ if (ec == boost::asio::error::operation_aborted){
+ throw exc::TimeoutException();
+
+ } else if (ec) {
+ MWARNING("Reading from UDP socket failed: " << ec.message());
+ throw exc::CommunicationException();
+
+ }
+
+ return length;
+ }
+
+ void UdpTransport::write(const google::protobuf::Message &req) {
+ m_proto->write(*this, req);
+ }
+
+ void UdpTransport::read(std::shared_ptr<google::protobuf::Message> & msg, messages::MessageType * msg_type) {
+ m_proto->read(*this, msg, msg_type);
+ }
+
+ void UdpTransport::check_deadline(){
+ if (!m_socket){
+ return; // no active socket.
+ }
+
+ // Check whether the deadline has passed. We compare the deadline against
+ // the current time since a new asynchronous operation may have moved the
+ // deadline before this actor had a chance to run.
+ if (m_deadline.expires_at() <= boost::asio::deadline_timer::traits_type::now())
+ {
+ // The deadline has passed. The outstanding asynchronous operation needs
+ // to be cancelled so that the blocked receive() function will return.
+ //
+ // Please note that cancel() has portability issues on some versions of
+ // Microsoft Windows, and it may be necessary to use close() instead.
+ // Consult the documentation for cancel() for further information.
+ m_socket->cancel();
+
+ // There is no longer an active deadline. The expiry is set to positive
+ // infinity so that the actor takes no action until a new deadline is set.
+ m_deadline.expires_at(boost::posix_time::pos_infin);
+ }
+
+ // Put the actor back to sleep.
+ m_deadline.async_wait(boost::bind(&UdpTransport::check_deadline, this));
+ }
+
+ void UdpTransport::handle_receive(const boost::system::error_code &ec, std::size_t length,
+ boost::system::error_code *out_ec, std::size_t *out_length) {
+ *out_ec = ec;
+ *out_length = length;
+ }
+
+ std::ostream& UdpTransport::dump(std::ostream& o) const {
+ return o << "UdpTransport<path=" << get_path()
+ << ", socket_alive=" << (m_socket ? "true" : "false")
+ << ">";
+ }
+
+ void enumerate(t_transport_vect & res){
+ BridgeTransport bt;
+ bt.enumerate(res);
+
+ hw::trezor::UdpTransport btu;
+ btu.enumerate(res);
+ }
+
+ std::shared_ptr<Transport> transport(const std::string & path){
+ if (boost::starts_with(path, BridgeTransport::PATH_PREFIX)){
+ return std::make_shared<BridgeTransport>(path.substr(strlen(BridgeTransport::PATH_PREFIX)));
+
+ } else if (boost::starts_with(path, UdpTransport::PATH_PREFIX)){
+ return std::make_shared<UdpTransport>(path.substr(strlen(UdpTransport::PATH_PREFIX)));
+
+ } else {
+ throw std::invalid_argument("Unknown Trezor device path: " + path);
+
+ }
+ }
+
+ void throw_failure_exception(const messages::common::Failure * failure) {
+ if (failure == nullptr){
+ throw std::invalid_argument("Failure message cannot be null");
+ }
+
+ boost::optional<std::string> message = failure->has_message() ? boost::make_optional(failure->message()) : boost::none;
+ boost::optional<uint32_t> code = failure->has_code() ? boost::make_optional(static_cast<uint32_t>(failure->code())) : boost::none;
+ if (!code){
+ throw exc::proto::FailureException(code, message);
+ }
+
+ auto ecode = failure->code();
+ if (ecode == messages::common::Failure_FailureType_Failure_UnexpectedMessage){
+ throw exc::proto::UnexpectedMessageException(code, message);
+ } else if (ecode == messages::common::Failure_FailureType_Failure_ActionCancelled){
+ throw exc::proto::CancelledException(code, message);
+ } else if (ecode == messages::common::Failure_FailureType_Failure_PinExpected){
+ throw exc::proto::PinExpectedException(code, message);
+ } else if (ecode == messages::common::Failure_FailureType_Failure_PinInvalid){
+ throw exc::proto::InvalidPinException(code, message);
+ } else if (ecode == messages::common::Failure_FailureType_Failure_NotEnoughFunds){
+ throw exc::proto::NotEnoughFundsException(code, message);
+ } else if (ecode == messages::common::Failure_FailureType_Failure_NotInitialized){
+ throw exc::proto::NotInitializedException(code, message);
+ } else if (ecode == messages::common::Failure_FailureType_Failure_FirmwareError){
+ throw exc::proto::FirmwareErrorException(code, message);
+ } else {
+ throw exc::proto::FailureException(code, message);
+ }
+ }
+
+ std::ostream& operator<<(std::ostream& o, hw::trezor::Transport const& t){
+ return t.dump(o);
+ }
+
+ std::ostream& operator<<(std::ostream& o, std::shared_ptr<hw::trezor::Transport> const& t){
+ if (!t){
+ return o << "None";
+ }
+
+ return t->dump(o);
+ }
+
+}
+}
+
+
diff --git a/src/device_trezor/trezor/transport.hpp b/src/device_trezor/trezor/transport.hpp
new file mode 100644
index 000000000..7b82fd06f
--- /dev/null
+++ b/src/device_trezor/trezor/transport.hpp
@@ -0,0 +1,331 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef MONERO_TRANSPORT_H
+#define MONERO_TRANSPORT_H
+
+
+#include <boost/asio.hpp>
+#include <boost/asio/deadline_timer.hpp>
+#include <boost/array.hpp>
+#include <boost/utility/string_ref.hpp>
+
+#include <typeinfo>
+#include <type_traits>
+#include "net/http_client.h"
+
+#include "rapidjson/document.h"
+#include "rapidjson/writer.h"
+#include "rapidjson/stringbuffer.h"
+
+#include "exceptions.hpp"
+#include "trezor_defs.hpp"
+#include "messages_map.hpp"
+
+#include "messages/messages.pb.h"
+#include "messages/messages-common.pb.h"
+#include "messages/messages-management.pb.h"
+#include "messages/messages-monero.pb.h"
+
+namespace hw {
+namespace trezor {
+
+ using json = rapidjson::Document;
+ using json_val = rapidjson::Value;
+ namespace http = epee::net_utils::http;
+
+ const std::string DEFAULT_BRIDGE = "127.0.0.1:21325";
+
+ // Base HTTP comm serialization.
+ bool t_serialize(const std::string & in, std::string & out);
+ bool t_serialize(const json_val & in, std::string & out);
+ std::string t_serialize(const json_val & in);
+
+ bool t_deserialize(const std::string & in, std::string & out);
+ bool t_deserialize(const std::string & in, json & out);
+
+ // Flexible json serialization. HTTP client tailored for bridge API
+ template<class t_req, class t_res, class t_transport>
+ bool invoke_bridge_http(const boost::string_ref uri, const t_req & out_struct, t_res & result_struct, t_transport& transport, const boost::string_ref method = "POST", std::chrono::milliseconds timeout = std::chrono::seconds(180))
+ {
+ std::string req_param;
+ t_serialize(out_struct, req_param);
+
+ http::fields_list additional_params;
+ additional_params.push_back(std::make_pair("Origin","https://monero.trezor.io"));
+ additional_params.push_back(std::make_pair("Content-Type","application/json; charset=utf-8"));
+
+ const http::http_response_info* pri = nullptr;
+ if(!transport.invoke(uri, method, req_param, timeout, &pri, std::move(additional_params)))
+ {
+ MERROR("Failed to invoke http request to " << uri);
+ return false;
+ }
+
+ if(!pri)
+ {
+ MERROR("Failed to invoke http request to " << uri << ", internal error (null response ptr)");
+ return false;
+ }
+
+ if(pri->m_response_code != 200)
+ {
+ MERROR("Failed to invoke http request to " << uri << ", wrong response code: " << pri->m_response_code
+ << " Response Body: " << pri->m_body);
+ return false;
+ }
+
+ return t_deserialize(pri->m_body, result_struct);
+ }
+
+ // Forward decl
+ class Transport;
+ class Protocol;
+
+ // Communication protocol
+ class Protocol {
+ public:
+ Protocol() = default;
+ virtual ~Protocol() = default;
+ virtual void session_begin(Transport & transport){ };
+ virtual void session_end(Transport & transport){ };
+ virtual void write(Transport & transport, const google::protobuf::Message & req)= 0;
+ virtual void read(Transport & transport, std::shared_ptr<google::protobuf::Message> & msg, messages::MessageType * msg_type=nullptr)= 0;
+ };
+
+ class ProtocolV1 : public Protocol {
+ public:
+ ProtocolV1() = default;
+ virtual ~ProtocolV1() = default;
+
+ void write(Transport & transport, const google::protobuf::Message & req) override;
+ void read(Transport & transport, std::shared_ptr<google::protobuf::Message> & msg, messages::MessageType * msg_type=nullptr) override;
+ };
+
+
+ // Base transport
+ typedef std::vector<std::shared_ptr<Transport>> t_transport_vect;
+
+ class Transport {
+ public:
+ Transport() = default;
+ virtual ~Transport() = default;
+
+ virtual bool ping() { return false; };
+ virtual std::string get_path() const { return ""; };
+ virtual void enumerate(t_transport_vect & res){};
+ virtual void open(){};
+ virtual void close(){};
+ virtual void write(const google::protobuf::Message & req) =0;
+ virtual void read(std::shared_ptr<google::protobuf::Message> & msg, messages::MessageType * msg_type=nullptr) =0;
+
+ virtual void write_chunk(const void * buff, size_t size) { };
+ virtual size_t read_chunk(void * buff, size_t size) { return 0; };
+ virtual std::ostream& dump(std::ostream& o) const { return o << "Transport<>"; }
+ };
+
+ // Bridge transport
+ class BridgeTransport : public Transport {
+ public:
+ BridgeTransport(
+ boost::optional<std::string> device_path = boost::none,
+ boost::optional<std::string> bridge_host = boost::none):
+ m_device_path(device_path),
+ m_bridge_host(bridge_host ? bridge_host.get() : DEFAULT_BRIDGE),
+ m_response(boost::none),
+ m_session(boost::none),
+ m_device_info(boost::none)
+ {
+ m_http_client.set_server(m_bridge_host, boost::none, false);
+ }
+
+ virtual ~BridgeTransport() = default;
+
+ static const char * PATH_PREFIX;
+
+ std::string get_path() const override;
+ void enumerate(t_transport_vect & res) override;
+
+ void open() override;
+ void close() override;
+
+ void write(const google::protobuf::Message &req) override;
+ void read(std::shared_ptr<google::protobuf::Message> & msg, messages::MessageType * msg_type=nullptr) override;
+
+ const boost::optional<json> & device_info() const;
+ std::ostream& dump(std::ostream& o) const override;
+
+ private:
+ epee::net_utils::http::http_simple_client m_http_client;
+ std::string m_bridge_host;
+ boost::optional<std::string> m_device_path;
+ boost::optional<std::string> m_session;
+ boost::optional<std::string> m_response;
+ boost::optional<json> m_device_info;
+ };
+
+ // UdpTransport transport
+ using boost::asio::ip::udp;
+
+ class UdpTransport : public Transport {
+ public:
+
+ explicit UdpTransport(
+ boost::optional<std::string> device_path=boost::none,
+ boost::optional<std::shared_ptr<Protocol>> proto=boost::none);
+
+ virtual ~UdpTransport() = default;
+
+ static const char * PATH_PREFIX;
+ static const char * DEFAULT_HOST;
+ static const int DEFAULT_PORT;
+
+ bool ping() override;
+ std::string get_path() const override;
+ void enumerate(t_transport_vect & res) override;
+
+ void open() override;
+ void close() override;
+
+ void write(const google::protobuf::Message &req) override;
+ void read(std::shared_ptr<google::protobuf::Message> & msg, messages::MessageType * msg_type=nullptr) override;
+
+ void write_chunk(const void * buff, size_t size) override;
+ size_t read_chunk(void * buff, size_t size) override;
+
+ std::ostream& dump(std::ostream& o) const override;
+
+ private:
+ void require_socket();
+ ssize_t receive(void * buff, size_t size, boost::system::error_code * error_code=nullptr, bool no_throw=false, boost::posix_time::time_duration timeout=boost::posix_time::seconds(10));
+ void check_deadline();
+ static void handle_receive(const boost::system::error_code& ec, std::size_t length,
+ boost::system::error_code* out_ec, std::size_t* out_length);
+ bool ping_int(boost::posix_time::time_duration timeout=boost::posix_time::milliseconds(1500));
+
+ std::shared_ptr<Protocol> m_proto;
+ std::string m_device_host;
+ int m_device_port;
+
+ std::unique_ptr<udp::socket> m_socket;
+ boost::asio::io_service m_io_service;
+ boost::asio::deadline_timer m_deadline;
+ udp::endpoint m_endpoint;
+ };
+
+ //
+ // General helpers
+ //
+
+ /**
+ * Enumerates all transports
+ */
+ void enumerate(t_transport_vect & res);
+
+ /**
+ * Transforms path to the transport
+ */
+ std::shared_ptr<Transport> transport(const std::string & path);
+
+ /**
+ * Transforms path to the particular transport
+ */
+ template<class t_transport>
+ std::shared_ptr<t_transport> transport_typed(const std::string & path){
+ auto t = transport(path);
+ if (!t){
+ return nullptr;
+ }
+
+ return std::dynamic_pointer_cast<t_transport>(t);
+ }
+
+ // Exception carries unexpected message being received
+ namespace exc {
+ class UnexpectedMessageException: public ProtocolException {
+ protected:
+ hw::trezor::messages::MessageType recvType;
+ std::shared_ptr<google::protobuf::Message> recvMsg;
+
+ public:
+ using ProtocolException::ProtocolException;
+ UnexpectedMessageException(): ProtocolException("Trezor returned unexpected message") {};
+ UnexpectedMessageException(hw::trezor::messages::MessageType recvType,
+ const std::shared_ptr<google::protobuf::Message> & recvMsg)
+ : recvType(recvType), recvMsg(recvMsg) {
+ reason = std::string("Trezor returned unexpected message: ") + std::to_string(recvType);
+ }
+ };
+ }
+
+ /**
+ * Throws corresponding failure exception.
+ */
+ [[ noreturn ]] void throw_failure_exception(const messages::common::Failure * failure);
+
+ /**
+ * Simple wrapper for write-read message exchange with expected message response type.
+ *
+ * @throws UnexpectedMessageException if the response message type is different than expected.
+ * Exception contains message type and the message itself.
+ */
+ template<class t_message>
+ std::shared_ptr<t_message>
+ exchange_message(Transport & transport, const google::protobuf::Message & req,
+ boost::optional<messages::MessageType> resp_type = boost::none)
+ {
+ // Require strictly protocol buffers response in the template.
+ BOOST_STATIC_ASSERT(boost::is_base_of<google::protobuf::Message, t_message>::value);
+
+ // Write the request
+ transport.write(req);
+
+ // Read the response
+ std::shared_ptr<google::protobuf::Message> msg_resp;
+ hw::trezor::messages::MessageType msg_resp_type;
+ transport.read(msg_resp, &msg_resp_type);
+
+ // Determine type of expected message response
+ messages::MessageType required_type = resp_type ? resp_type.get() : MessageMapper::get_message_wire_number<t_message>();
+
+ if (msg_resp_type == required_type) {
+ return message_ptr_retype<t_message>(msg_resp);
+ } else if (msg_resp_type == messages::MessageType_Failure){
+ throw_failure_exception(dynamic_cast<messages::common::Failure*>(msg_resp.get()));
+ } else {
+ throw exc::UnexpectedMessageException(msg_resp_type, msg_resp);
+ }
+ }
+
+ std::ostream& operator<<(std::ostream& o, hw::trezor::Transport const& t);
+ std::ostream& operator<<(std::ostream& o, std::shared_ptr<hw::trezor::Transport> const& t);
+}}
+
+
+#endif //MONERO_TRANSPORT_H
diff --git a/src/device_trezor/trezor/trezor_defs.hpp b/src/device_trezor/trezor/trezor_defs.hpp
new file mode 100644
index 000000000..951a8f802
--- /dev/null
+++ b/src/device_trezor/trezor/trezor_defs.hpp
@@ -0,0 +1,48 @@
+// Copyright (c) 2017-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+#if defined(HAVE_PROTOBUF) && !defined(WITHOUT_TREZOR)
+ #define WITH_DEVICE_TREZOR 1
+#else
+ #define WITH_DEVICE_TREZOR 0
+#endif
+
+#ifndef WITH_DEVICE_TREZOR_LITE
+#define WITH_DEVICE_TREZOR_LITE 0
+#endif
+
+// Avoids protobuf undefined macro warning
+#ifndef PROTOBUF_INLINE_NOT_IN_HEADERS
+#define PROTOBUF_INLINE_NOT_IN_HEADERS 0
+#endif
+
+// Fixes gcc7 problem with minor macro defined clashing with minor() field.
+#ifdef minor
+#undef minor
+#endif