Browse Source

device/trezor: trezor support added

Dusan Klinec 9 months ago
parent
commit
29ffb6bba8
No account linked to committer's email address

+ 3
- 0
.gitmodules View File

@@ -9,3 +9,6 @@
9 9
 [submodule "external/rapidjson"]
10 10
 	path = external/rapidjson
11 11
 	url = https://github.com/Tencent/rapidjson
12
+[submodule "external/trezor-common"]
13
+	path = external/trezor-common
14
+	url = https://github.com/trezor/trezor-common.git

+ 11
- 0
CMakeLists.txt View File

@@ -190,6 +190,7 @@ if(NOT MANUAL_SUBMODULES)
190 190
     check_submodule(external/miniupnp)
191 191
     check_submodule(external/unbound)
192 192
     check_submodule(external/rapidjson)
193
+    check_submodule(external/trezor-common)
193 194
   endif()
194 195
 endif()
195 196
 
@@ -512,6 +513,16 @@ else (HIDAPI_FOUND)
512 513
   message(STATUS "Could not find HIDAPI")
513 514
 endif()
514 515
 
516
+# Protobuf, optional. Required for TREZOR.
517
+include(FindProtobuf)
518
+find_package(Protobuf)
519
+if(Protobuf_FOUND)
520
+  set(HAVE_PROTOBUF 1)
521
+  add_definitions(-DHAVE_PROTOBUF=1)
522
+else(Protobuf_FOUND)
523
+  message(STATUS "Could not find Protobuf")
524
+endif()
525
+
515 526
 if(MSVC)
516 527
   add_definitions("/bigobj /MP /W3 /GS- /D_CRT_SECURE_NO_WARNINGS /wd4996 /wd4345 /D_WIN32_WINNT=0x0600 /DWIN32_LEAN_AND_MEAN /DGTEST_HAS_TR1_TUPLE=0 /FIinline_c.h /D__SSE4_1__")
517 528
   # set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Dinline=__inline")

+ 1
- 0
external/trezor-common

@@ -0,0 +1 @@
1
+Subproject commit 588f8e03f5ac111adf719f0a437de67481a26aed

+ 1
- 0
src/CMakeLists.txt View File

@@ -145,3 +145,4 @@ if(PER_BLOCK_CHECKPOINT)
145 145
 endif()
146 146
 
147 147
 add_subdirectory(device)
148
+add_subdirectory(device_trezor)

+ 19
- 5
src/cryptonote_basic/account.cpp View File

@@ -139,6 +139,15 @@ DISABLE_VS_WARNINGS(4244 4345)
139 139
     m_creation_timestamp = 0;
140 140
   }
141 141
   //-----------------------------------------------------------------
142
+  void account_base::deinit()
143
+  {
144
+    try{
145
+      m_keys.get_device().disconnect();
146
+    } catch (const std::exception &e){
147
+      MERROR("Device disconnect exception: " << e.what());
148
+    }
149
+  }
150
+  //-----------------------------------------------------------------
142 151
   void account_base::forget_spend_key()
143 152
   {
144 153
     m_keys.m_spend_secret_key = crypto::secret_key();
@@ -206,11 +215,16 @@ DISABLE_VS_WARNINGS(4244 4345)
206 215
   void account_base::create_from_device(hw::device &hwdev)
207 216
   {
208 217
     m_keys.set_device(hwdev);
209
-    MCDEBUG("ledger", "device type: "<<typeid(hwdev).name());
210
-    hwdev.init();
211
-    hwdev.connect();
212
-    hwdev.get_public_address(m_keys.m_account_address);
213
-    hwdev.get_secret_keys(m_keys.m_view_secret_key, m_keys.m_spend_secret_key);
218
+    MCDEBUG("device", "device type: "<<typeid(hwdev).name());
219
+    CHECK_AND_ASSERT_THROW_MES(hwdev.init(), "Device init failed");
220
+    CHECK_AND_ASSERT_THROW_MES(hwdev.connect(), "Device connect failed");
221
+    try {
222
+      CHECK_AND_ASSERT_THROW_MES(hwdev.get_public_address(m_keys.m_account_address), "Cannot get a device address");
223
+      CHECK_AND_ASSERT_THROW_MES(hwdev.get_secret_keys(m_keys.m_view_secret_key, m_keys.m_spend_secret_key), "Cannot get device secret");
224
+    } catch (const std::exception &e){
225
+      hwdev.disconnect();
226
+      throw;
227
+    }
214 228
     struct tm timestamp = {0};
215 229
     timestamp.tm_year = 2014 - 1900;  // year 2014
216 230
     timestamp.tm_mon = 4 - 1;  // month april

+ 1
- 0
src/cryptonote_basic/account.h View File

@@ -89,6 +89,7 @@ namespace cryptonote
89 89
 
90 90
     hw::device& get_device() const  {return m_keys.get_device();}
91 91
     void set_device( hw::device &hwdev) {m_keys.set_device(hwdev);}
92
+    void deinit();
92 93
 
93 94
     uint64_t get_createtime() const { return m_creation_timestamp; }
94 95
     void set_createtime(uint64_t val) { m_creation_timestamp = val; }

+ 2
- 0
src/device/CMakeLists.txt View File

@@ -44,6 +44,7 @@ set(device_headers
44 44
   device.hpp
45 45
   device_io.hpp
46 46
   device_default.hpp
47
+  device_cold.hpp
47 48
   log.hpp
48 49
  )
49 50
 
@@ -72,5 +73,6 @@ target_link_libraries(device
72 73
     cncrypto
73 74
     ringct_basic
74 75
     ${OPENSSL_CRYPTO_LIBRARIES}
76
+    ${Boost_SERIALIZATION_LIBRARY}
75 77
   PRIVATE
76 78
     ${EXTRA_LIBRARIES})

+ 15
- 1
src/device/device.hpp View File

@@ -47,6 +47,7 @@
47 47
 #include "crypto/crypto.h"
48 48
 #include "crypto/chacha.h"
49 49
 #include "ringct/rctTypes.h"
50
+#include "cryptonote_config.h"
50 51
 
51 52
 
52 53
 #ifndef USE_DEVICE_LEDGER
@@ -99,10 +100,17 @@ namespace hw {
99 100
         enum device_type
100 101
         {
101 102
           SOFTWARE = 0,
102
-          LEDGER = 1
103
+          LEDGER = 1,
104
+          TREZOR = 2
103 105
         };
104 106
 
105 107
 
108
+        enum device_protocol_t {
109
+            PROTOCOL_DEFAULT,
110
+            PROTOCOL_PROXY,     // Originally defined by Ledger
111
+            PROTOCOL_COLD,      // Originally defined by Trezor
112
+        };
113
+
106 114
         /* ======================================================================= */
107 115
         /*                              SETUP/TEARDOWN                             */
108 116
         /* ======================================================================= */
@@ -120,6 +128,7 @@ namespace hw {
120 128
 
121 129
         virtual device_type get_type() const = 0;
122 130
 
131
+        virtual device_protocol_t device_protocol() const { return PROTOCOL_DEFAULT; };
123 132
 
124 133
         /* ======================================================================= */
125 134
         /*  LOCKER                                                                 */
@@ -204,6 +213,11 @@ namespace hw {
204 213
 
205 214
         virtual bool  close_tx(void) = 0;
206 215
 
216
+        virtual bool  has_ki_cold_sync(void) const { return false; }
217
+        virtual bool  has_tx_cold_sign(void) const { return false; }
218
+
219
+        virtual void  set_network_type(cryptonote::network_type network_type) { }
220
+
207 221
     protected:
208 222
         device_mode mode;
209 223
     } ;

+ 71
- 0
src/device/device_cold.hpp View File

@@ -0,0 +1,71 @@
1
+// Copyright (c) 2017-2018, The Monero Project
2
+//
3
+// All rights reserved.
4
+//
5
+// Redistribution and use in source and binary forms, with or without modification, are
6
+// permitted provided that the following conditions are met:
7
+//
8
+// 1. Redistributions of source code must retain the above copyright notice, this list of
9
+//    conditions and the following disclaimer.
10
+//
11
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
12
+//    of conditions and the following disclaimer in the documentation and/or other
13
+//    materials provided with the distribution.
14
+//
15
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
16
+//    used to endorse or promote products derived from this software without specific
17
+//    prior written permission.
18
+//
19
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+//
29
+
30
+#ifndef MONERO_DEVICE_COLD_H
31
+#define MONERO_DEVICE_COLD_H
32
+
33
+#include "wallet/wallet2.h"
34
+#include <boost/function.hpp>
35
+
36
+
37
+namespace hw {
38
+
39
+  typedef struct wallet_shim {
40
+    boost::function<crypto::public_key (const tools::wallet2::transfer_details &td)> get_tx_pub_key_from_received_outs;
41
+  } wallet_shim;
42
+
43
+  class tx_aux_data {
44
+  public:
45
+    std::vector<std::string> tx_device_aux;  // device generated aux data
46
+    std::vector<cryptonote::address_parse_info> tx_recipients;  // as entered by user
47
+  };
48
+
49
+  class device_cold {
50
+  public:
51
+
52
+    using exported_key_image = std::vector<std::pair<crypto::key_image, crypto::signature>>;
53
+
54
+    /**
55
+     * Key image sync with the cold protocol.
56
+     */
57
+    virtual void ki_sync(wallet_shim * wallet,
58
+                 const std::vector<::tools::wallet2::transfer_details> & transfers,
59
+                 exported_key_image & ski) =0;
60
+
61
+    /**
62
+     * Signs unsigned transaction with the cold protocol.
63
+     */
64
+    virtual void tx_sign(wallet_shim * wallet,
65
+                 const ::tools::wallet2::unsigned_tx_set & unsigned_tx,
66
+                 ::tools::wallet2::signed_tx_set & signed_tx,
67
+                 tx_aux_data & aux_data) =0;
68
+  };
69
+}
70
+
71
+#endif //MONERO_DEVICE_COLD_H

+ 4
- 4
src/device/device_default.cpp View File

@@ -69,17 +69,17 @@ namespace hw {
69 69
         }
70 70
         
71 71
         bool device_default::init(void) {
72
-            dfns();
72
+            return true;
73 73
         }
74 74
         bool device_default::release() {
75
-            dfns();
75
+            return true;
76 76
         }
77 77
 
78 78
         bool device_default::connect(void) {
79
-            dfns();
79
+            return true;
80 80
         }
81 81
         bool device_default::disconnect() {
82
-            dfns();
82
+            return true;
83 83
         }
84 84
 
85 85
         bool  device_default::set_mode(device_mode mode) {

+ 1
- 0
src/device/device_ledger.hpp View File

@@ -141,6 +141,7 @@ namespace hw {
141 141
         bool set_mode(device_mode mode) override;
142 142
 
143 143
         device_type get_type() const override {return device_type::LEDGER;};
144
+        device_protocol_t device_protocol() const override { return PROTOCOL_PROXY; };
144 145
 
145 146
         /* ======================================================================= */
146 147
         /*  LOCKER                                                                 */

+ 123
- 0
src/device_trezor/CMakeLists.txt View File

@@ -0,0 +1,123 @@
1
+# Copyright (c) 2014-2017, The Monero Project
2
+#
3
+# All rights reserved.
4
+#
5
+# Redistribution and use in source and binary forms, with or without modification, are
6
+# permitted provided that the following conditions are met:
7
+#
8
+# 1. Redistributions of source code must retain the above copyright notice, this list of
9
+#    conditions and the following disclaimer.
10
+#
11
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list
12
+#    of conditions and the following disclaimer in the documentation and/or other
13
+#    materials provided with the distribution.
14
+#
15
+# 3. Neither the name of the copyright holder nor the names of its contributors may be
16
+#    used to endorse or promote products derived from this software without specific
17
+#    prior written permission.
18
+#
19
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22
+# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+
29
+set(TREZOR_PROTOB_H
30
+        trezor/messages/messages.pb.h
31
+        trezor/messages/messages-common.pb.h
32
+        trezor/messages/messages-management.pb.h
33
+        trezor/messages/messages-monero.pb.h
34
+)
35
+
36
+set(TREZOR_PROTOB_CPP
37
+        trezor/messages/messages.pb.cc
38
+        trezor/messages/messages-common.pb.cc
39
+        trezor/messages/messages-management.pb.cc
40
+        trezor/messages/messages-monero.pb.cc
41
+)
42
+
43
+set(trezor_headers
44
+        trezor/exceptions.hpp
45
+        trezor/messages_map.hpp
46
+        trezor/protocol.hpp
47
+        trezor/transport.hpp
48
+        device_trezor_base.hpp
49
+        device_trezor.hpp
50
+        trezor.hpp
51
+        ${TREZOR_PROTOB_H}
52
+)
53
+
54
+set(trezor_sources
55
+        trezor/messages_map.cpp
56
+        trezor/protocol.cpp
57
+        trezor/transport.cpp
58
+        device_trezor_base.cpp
59
+        device_trezor.cpp
60
+        ${TREZOR_PROTOB_CPP}
61
+)
62
+
63
+set(trezor_private_headers)
64
+
65
+
66
+include(FindProtobuf)
67
+find_package(Protobuf)  # REQUIRED
68
+
69
+# Test for HAVE_PROTOBUF from the parent
70
+if(Protobuf_FOUND AND HAVE_PROTOBUF)
71
+    if ("$ENV{PYTHON3}" STREQUAL "")
72
+        set(PYTHON3 "python3")
73
+    else()
74
+        set(PYTHON3 "$ENV{PYTHON3}" CACHE INTERNAL "Copied from environment variable")
75
+    endif()
76
+
77
+    execute_process(COMMAND ${PYTHON3} tools/build_protob.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/trezor RESULT_VARIABLE RET OUTPUT_VARIABLE OUT ERROR_VARIABLE ERR)
78
+    if(RET)
79
+        message(WARNING "Trezor protobuf messages could not be regenerated (err=${RET}, python ${PYTHON})."
80
+                        "OUT: ${OUT}, ERR: ${ERR}."
81
+                        "Please read src/device_trezor/trezor/tools/README.md")
82
+    else()
83
+        message(STATUS "Trezor protobuf messages regenerated ${OUT}")
84
+        set(TREZOR_PROTOBUF_GENERATED 1)
85
+    endif()
86
+endif()
87
+
88
+
89
+if(TREZOR_PROTOBUF_GENERATED)
90
+    message(STATUS "Trezor support enabled")
91
+
92
+    add_definitions(-DPROTOBUF_INLINE_NOT_IN_HEADERS=0)
93
+
94
+    monero_private_headers(device_trezor
95
+            ${device_private_headers}
96
+            ${PROTOBUF_INCLUDE_DIR})
97
+
98
+    monero_add_library(device_trezor
99
+            ${trezor_sources}
100
+            ${trezor_headers}
101
+            ${trezor_private_headers})
102
+
103
+    target_link_libraries(device_trezor
104
+            PUBLIC
105
+            device
106
+            cncrypto
107
+            ringct_basic
108
+            cryptonote_core
109
+            common
110
+            ${SODIUM_LIBRARY}
111
+            ${Boost_CHRONO_LIBRARY}
112
+            ${PROTOBUF_LIBRARY}
113
+            PRIVATE
114
+            ${EXTRA_LIBRARIES})
115
+
116
+    # set(WITH_DEVICE_TREZOR 1 PARENT_SCOPE)
117
+    # add_definitions(-DWITH_DEVICE_TREZOR=1)
118
+
119
+else()
120
+    monero_private_headers(device_trezor)
121
+    monero_add_library(device_trezor device_trezor.cpp)
122
+    target_link_libraries(device_trezor PUBLIC cncrypto)
123
+endif()

+ 363
- 0
src/device_trezor/device_trezor.cpp View File

@@ -0,0 +1,363 @@
1
+// Copyright (c) 2017-2018, The Monero Project
2
+//
3
+// All rights reserved.
4
+//
5
+// Redistribution and use in source and binary forms, with or without modification, are
6
+// permitted provided that the following conditions are met:
7
+//
8
+// 1. Redistributions of source code must retain the above copyright notice, this list of
9
+//    conditions and the following disclaimer.
10
+//
11
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
12
+//    of conditions and the following disclaimer in the documentation and/or other
13
+//    materials provided with the distribution.
14
+//
15
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
16
+//    used to endorse or promote products derived from this software without specific
17
+//    prior written permission.
18
+//
19
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+//
29
+
30
+#include "device_trezor.hpp"
31
+
32
+namespace hw {
33
+namespace trezor {
34
+
35
+#if WITH_DEVICE_TREZOR
36
+
37
+#undef MONERO_DEFAULT_LOG_CATEGORY
38
+#define MONERO_DEFAULT_LOG_CATEGORY "device.trezor"
39
+
40
+#define HW_TREZOR_NAME "Trezor"
41
+#define HW_TREZOR_NAME_LITE "TrezorLite"
42
+
43
+    static device_trezor *trezor_device = nullptr;
44
+    static device_trezor *ensure_trezor_device(){
45
+      if (!trezor_device) {
46
+        trezor_device = new device_trezor();
47
+        trezor_device->set_name(HW_TREZOR_NAME);
48
+      }
49
+      return trezor_device;
50
+    }
51
+
52
+    void register_all(std::map<std::string, std::unique_ptr<device>> &registry) {
53
+      registry.insert(std::make_pair(HW_TREZOR_NAME, std::unique_ptr<device>(ensure_trezor_device())));
54
+    }
55
+
56
+    void register_all() {
57
+      hw::register_device(HW_TREZOR_NAME, ensure_trezor_device());
58
+    }
59
+
60
+    device_trezor::device_trezor() {
61
+
62
+    }
63
+
64
+    device_trezor::~device_trezor() {
65
+      try {
66
+        disconnect();
67
+        release();
68
+      } catch(std::exception const& e){
69
+        MWARNING("Could not disconnect and release: " << e.what());
70
+      }
71
+    }
72
+
73
+    /* ======================================================================= */
74
+    /*                             WALLET & ADDRESS                            */
75
+    /* ======================================================================= */
76
+
77
+    bool device_trezor::get_public_address(cryptonote::account_public_address &pubkey) {
78
+      try {
79
+        auto res = get_address();
80
+
81
+        cryptonote::address_parse_info info{};
82
+        bool r = cryptonote::get_account_address_from_str(info, this->network_type, res->address());
83
+        CHECK_AND_ASSERT_MES(r, false, "Could not parse returned address. Address parse failed: " + res->address());
84
+        CHECK_AND_ASSERT_MES(!info.is_subaddress, false, "Trezor returned a sub address");
85
+
86
+        pubkey = info.address;
87
+        return true;
88
+
89
+      } catch(std::exception const& e){
90
+        MERROR("Get public address exception: " << e.what());
91
+        return false;
92
+      }
93
+    }
94
+
95
+    bool device_trezor::get_secret_keys(crypto::secret_key &viewkey , crypto::secret_key &spendkey) {
96
+      try {
97
+        MDEBUG("Loading view-only key from the Trezor. Please check the Trezor for a confirmation.");
98
+        auto res = get_view_key();
99
+        CHECK_AND_ASSERT_MES(res->watch_key().size() == 32, false, "Trezor returned invalid view key");
100
+
101
+        spendkey = crypto::null_skey; // not given
102
+        memcpy(viewkey.data, res->watch_key().data(), 32);
103
+
104
+        return true;
105
+
106
+      } catch(std::exception const& e){
107
+        MERROR("Get secret keys exception: " << e.what());
108
+        return false;
109
+      }
110
+    }
111
+
112
+    /* ======================================================================= */
113
+    /*  Helpers                                                                */
114
+    /* ======================================================================= */
115
+
116
+    /* ======================================================================= */
117
+    /*                              TREZOR PROTOCOL                            */
118
+    /* ======================================================================= */
119
+
120
+    std::shared_ptr<messages::monero::MoneroAddress> device_trezor::get_address(
121
+        const boost::optional<std::vector<uint32_t>> & path,
122
+        const boost::optional<cryptonote::network_type> & network_type){
123
+      AUTO_LOCK_CMD();
124
+      require_connected();
125
+      test_ping();
126
+
127
+      auto req = std::make_shared<messages::monero::MoneroGetAddress>();
128
+      this->set_msg_addr<messages::monero::MoneroGetAddress>(req.get(), path, network_type);
129
+
130
+      auto response = this->client_exchange<messages::monero::MoneroAddress>(req);
131
+      MTRACE("Get address response received");
132
+      return response;
133
+    }
134
+
135
+    std::shared_ptr<messages::monero::MoneroWatchKey> device_trezor::get_view_key(
136
+        const boost::optional<std::vector<uint32_t>> & path,
137
+        const boost::optional<cryptonote::network_type> & network_type){
138
+      AUTO_LOCK_CMD();
139
+      require_connected();
140
+      test_ping();
141
+
142
+      auto req = std::make_shared<messages::monero::MoneroGetWatchKey>();
143
+      this->set_msg_addr<messages::monero::MoneroGetWatchKey>(req.get(), path, network_type);
144
+
145
+      auto response = this->client_exchange<messages::monero::MoneroWatchKey>(req);
146
+      MTRACE("Get watch key response received");
147
+      return response;
148
+    }
149
+
150
+    void device_trezor::ki_sync(wallet_shim * wallet,
151
+                                const std::vector<tools::wallet2::transfer_details> & transfers,
152
+                                hw::device_cold::exported_key_image & ski)
153
+    {
154
+      AUTO_LOCK_CMD();
155
+      require_connected();
156
+      test_ping();
157
+
158
+      std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> req;
159
+
160
+      std::vector<protocol::ki::MoneroTransferDetails> mtds;
161
+      std::vector<protocol::ki::MoneroExportedKeyImage> kis;
162
+      protocol::ki::key_image_data(wallet, transfers, mtds);
163
+      protocol::ki::generate_commitment(mtds, transfers, req);
164
+
165
+      this->set_msg_addr<messages::monero::MoneroKeyImageExportInitRequest>(req.get());
166
+      auto ack1 = this->client_exchange<messages::monero::MoneroKeyImageExportInitAck>(req);
167
+
168
+      const auto batch_size = 10;
169
+      const auto num_batches = (mtds.size() + batch_size - 1) / batch_size;
170
+      for(uint64_t cur = 0; cur < num_batches; ++cur){
171
+        auto step_req = std::make_shared<messages::monero::MoneroKeyImageSyncStepRequest>();
172
+        auto idx_finish = std::min(static_cast<uint64_t>((cur + 1) * batch_size), static_cast<uint64_t>(mtds.size()));
173
+        for(uint64_t idx = cur * batch_size; idx < idx_finish; ++idx){
174
+          auto added_tdis = step_req->add_tdis();
175
+          CHECK_AND_ASSERT_THROW_MES(idx < mtds.size(), "Invalid transfer detail index");
176
+          *added_tdis = mtds[idx];
177
+        }
178
+
179
+        auto step_ack = this->client_exchange<messages::monero::MoneroKeyImageSyncStepAck>(step_req);
180
+        auto kis_size = step_ack->kis_size();
181
+        kis.reserve(static_cast<size_t>(kis_size));
182
+        for(int i = 0; i < kis_size; ++i){
183
+          auto ckis = step_ack->kis(i);
184
+          kis.push_back(ckis);
185
+        }
186
+
187
+        MTRACE("Batch " << cur << " / " << num_batches << " batches processed");
188
+      }
189
+
190
+      auto final_req = std::make_shared<messages::monero::MoneroKeyImageSyncFinalRequest>();
191
+      auto final_ack = this->client_exchange<messages::monero::MoneroKeyImageSyncFinalAck>(final_req);
192
+      ski.reserve(kis.size());
193
+
194
+      for(auto & sub : kis){
195
+        char buff[32*3];
196
+        protocol::crypto::chacha::decrypt(sub.blob().data(), sub.blob().size(),
197
+                                          reinterpret_cast<const uint8_t *>(final_ack->enc_key().data()),
198
+                                          reinterpret_cast<const uint8_t *>(sub.iv().data()), buff);
199
+
200
+        ::crypto::signature sig{};
201
+        ::crypto::key_image ki;
202
+        memcpy(ki.data, buff, 32);
203
+        memcpy(sig.c.data, buff + 32, 32);
204
+        memcpy(sig.r.data, buff + 64, 32);
205
+        ski.push_back(std::make_pair(ki, sig));
206
+      }
207
+    }
208
+
209
+
210
+    void device_trezor::tx_sign(wallet_shim * wallet,
211
+                                const tools::wallet2::unsigned_tx_set & unsigned_tx,
212
+                                tools::wallet2::signed_tx_set & signed_tx,
213
+                                hw::tx_aux_data & aux_data)
214
+    {
215
+      size_t num_tx = unsigned_tx.txes.size();
216
+      signed_tx.key_images.clear();
217
+      signed_tx.key_images.resize(unsigned_tx.transfers.size());
218
+
219
+      for(size_t tx_idx = 0; tx_idx < num_tx; ++tx_idx) {
220
+        std::shared_ptr<protocol::tx::Signer> signer;
221
+        tx_sign(wallet, unsigned_tx, tx_idx, aux_data, signer);
222
+
223
+        auto & cdata = signer->tdata();
224
+        auto aux_info_cur = signer->store_tx_aux_info();
225
+        aux_data.tx_device_aux.emplace_back(aux_info_cur);
226
+
227
+        // Pending tx reconstruction
228
+        signed_tx.ptx.emplace_back();
229
+        auto & cpend = signed_tx.ptx.back();
230
+        cpend.tx = cdata.tx;
231
+        cpend.dust = 0;
232
+        cpend.fee = 0;
233
+        cpend.dust_added_to_fee = false;
234
+        cpend.change_dts = cdata.tx_data.change_dts;
235
+        cpend.selected_transfers = cdata.tx_data.selected_transfers;
236
+        cpend.key_images = "";
237
+        cpend.dests = cdata.tx_data.dests;
238
+        cpend.construction_data = cdata.tx_data;
239
+
240
+        // Transaction check
241
+        cryptonote::blobdata tx_blob;
242
+        cryptonote::transaction tx_deserialized;
243
+        bool r = cryptonote::t_serializable_object_to_blob(cpend.tx, tx_blob);
244
+        CHECK_AND_ASSERT_THROW_MES(r, "Transaction serialization failed");
245
+        r = cryptonote::parse_and_validate_tx_from_blob(tx_blob, tx_deserialized);
246
+        CHECK_AND_ASSERT_THROW_MES(r, "Transaction deserialization failed");
247
+
248
+        std::string key_images;
249
+        bool all_are_txin_to_key = std::all_of(cdata.tx.vin.begin(), cdata.tx.vin.end(), [&](const cryptonote::txin_v& s_e) -> bool
250
+        {
251
+          CHECKED_GET_SPECIFIC_VARIANT(s_e, const cryptonote::txin_to_key, in, false);
252
+          key_images += boost::to_string(in.k_image) + " ";
253
+          return true;
254
+        });
255
+        if(!all_are_txin_to_key) {
256
+          throw std::invalid_argument("Not all are txin_to_key");
257
+        }
258
+        cpend.key_images = key_images;
259
+
260
+        // KI sync
261
+        size_t num_sources = cdata.tx_data.sources.size();
262
+        CHECK_AND_ASSERT_THROW_MES(num_sources == cdata.source_permutation.size(), "Invalid permutation size");
263
+        CHECK_AND_ASSERT_THROW_MES(num_sources == cdata.tx.vin.size(), "Invalid tx.vin size");
264
+        for(size_t src_idx = 0; src_idx < num_sources; ++src_idx){
265
+          size_t idx_mapped = cdata.source_permutation[src_idx];
266
+          CHECK_AND_ASSERT_THROW_MES(idx_mapped < cdata.tx_data.selected_transfers.size(), "Invalid idx_mapped");
267
+          CHECK_AND_ASSERT_THROW_MES(src_idx < cdata.tx.vin.size(), "Invalid idx_mapped");
268
+
269
+          size_t idx_map_src = cdata.tx_data.selected_transfers[idx_mapped];
270
+          auto vini = boost::get<cryptonote::txin_to_key>(cdata.tx.vin[src_idx]);
271
+
272
+          CHECK_AND_ASSERT_THROW_MES(idx_map_src < signed_tx.key_images.size(), "Invalid key image index");
273
+          signed_tx.key_images[idx_map_src] = vini.k_image;
274
+        }
275
+      }
276
+    }
277
+
278
+    void device_trezor::tx_sign(wallet_shim * wallet,
279
+                   const tools::wallet2::unsigned_tx_set & unsigned_tx,
280
+                   size_t idx,
281
+                   hw::tx_aux_data & aux_data,
282
+                   std::shared_ptr<protocol::tx::Signer> & signer)
283
+    {
284
+      AUTO_LOCK_CMD();
285
+      require_connected();
286
+      test_ping();
287
+
288
+      CHECK_AND_ASSERT_THROW_MES(idx < unsigned_tx.txes.size(), "Invalid transaction index");
289
+      signer = std::make_shared<protocol::tx::Signer>(wallet, &unsigned_tx, idx, &aux_data);
290
+      const tools::wallet2::tx_construction_data & cur_tx = unsigned_tx.txes[idx];
291
+      unsigned long num_sources = cur_tx.sources.size();
292
+      unsigned long num_outputs = cur_tx.splitted_dsts.size();
293
+
294
+      // Step: Init
295
+      auto init_msg = signer->step_init();
296
+      this->set_msg_addr(init_msg.get());
297
+
298
+      auto response = this->client_exchange<messages::monero::MoneroTransactionInitAck>(init_msg);
299
+      signer->step_init_ack(response);
300
+
301
+      // Step: Set transaction inputs
302
+      for(size_t cur_src = 0; cur_src < num_sources; ++cur_src){
303
+        auto src = signer->step_set_input(cur_src);
304
+        auto ack = this->client_exchange<messages::monero::MoneroTransactionSetInputAck>(src);
305
+        signer->step_set_input_ack(ack);
306
+      }
307
+
308
+      // Step: sort
309
+      auto perm_req = signer->step_permutation();
310
+      if (perm_req){
311
+        auto perm_ack = this->client_exchange<messages::monero::MoneroTransactionInputsPermutationAck>(perm_req);
312
+        signer->step_permutation_ack(perm_ack);
313
+      }
314
+
315
+      // Step: input_vini
316
+      if (!signer->in_memory()){
317
+        for(size_t cur_src = 0; cur_src < num_sources; ++cur_src){
318
+          auto src = signer->step_set_vini_input(cur_src);
319
+          auto ack = this->client_exchange<messages::monero::MoneroTransactionInputViniAck>(src);
320
+          signer->step_set_vini_input_ack(ack);
321
+        }
322
+      }
323
+
324
+      // Step: all inputs set
325
+      auto all_inputs_set = signer->step_all_inputs_set();
326
+      auto ack_all_inputs = this->client_exchange<messages::monero::MoneroTransactionAllInputsSetAck>(all_inputs_set);
327
+      signer->step_all_inputs_set_ack(ack_all_inputs);
328
+
329
+      // Step: outputs
330
+      for(size_t cur_dst = 0; cur_dst < num_outputs; ++cur_dst){
331
+        auto src = signer->step_set_output(cur_dst);
332
+        auto ack = this->client_exchange<messages::monero::MoneroTransactionSetOutputAck>(src);
333
+        signer->step_set_output_ack(ack);
334
+      }
335
+
336
+      // Step: all outs set
337
+      auto all_out_set = signer->step_all_outs_set();
338
+      auto ack_all_out_set = this->client_exchange<messages::monero::MoneroTransactionAllOutSetAck>(all_out_set);
339
+      signer->step_all_outs_set_ack(ack_all_out_set, *this);
340
+
341
+      // Step: sign each input
342
+      for(size_t cur_src = 0; cur_src < num_sources; ++cur_src){
343
+        auto src = signer->step_sign_input(cur_src);
344
+        auto ack_sign = this->client_exchange<messages::monero::MoneroTransactionSignInputAck>(src);
345
+        signer->step_sign_input_ack(ack_sign);
346
+      }
347
+
348
+      // Step: final
349
+      auto final_msg = signer->step_final();
350
+      auto ack_final = this->client_exchange<messages::monero::MoneroTransactionFinalAck>(final_msg);
351
+      signer->step_final_ack(ack_final);
352
+    }
353
+
354
+#else //WITH_DEVICE_TREZOR
355
+
356
+    void register_all(std::map<std::string, std::unique_ptr<device>> &registry) {
357
+    }
358
+
359
+    void register_all() {
360
+    }
361
+
362
+#endif //WITH_DEVICE_TREZOR
363
+}}

+ 132
- 0
src/device_trezor/device_trezor.hpp View File

@@ -0,0 +1,132 @@
1
+// Copyright (c) 2017-2018, The Monero Project
2
+//
3
+// All rights reserved.
4
+//
5
+// Redistribution and use in source and binary forms, with or without modification, are
6
+// permitted provided that the following conditions are met:
7
+//
8
+// 1. Redistributions of source code must retain the above copyright notice, this list of
9
+//    conditions and the following disclaimer.
10
+//
11
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
12
+//    of conditions and the following disclaimer in the documentation and/or other
13
+//    materials provided with the distribution.
14
+//
15
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
16
+//    used to endorse or promote products derived from this software without specific
17
+//    prior written permission.
18
+//
19
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+//
29
+
30
+#ifndef MONERO_DEVICE_TREZOR_H
31
+#define MONERO_DEVICE_TREZOR_H
32
+
33
+
34
+#include <cstddef>
35
+#include <string>
36
+#include "device/device.hpp"
37
+#include "device/device_default.hpp"
38
+#include "device/device_cold.hpp"
39
+#include <boost/scope_exit.hpp>
40
+#include <boost/thread/mutex.hpp>
41
+#include <boost/thread/recursive_mutex.hpp>
42
+#include "cryptonote_config.h"
43
+#include "trezor.hpp"
44
+#include "device_trezor_base.hpp"
45
+
46
+namespace hw {
47
+namespace trezor {
48
+
49
+  void register_all();
50
+  void register_all(std::map<std::string, std::unique_ptr<device>> &registry);
51
+
52
+#if WITH_DEVICE_TREZOR
53
+  class device_trezor;
54
+
55
+  /**
56
+   * Main device
57
+   */
58
+  class device_trezor : public hw::trezor::device_trezor_base, public hw::device_cold {
59
+    protected:
60
+      // To speed up blockchain parsing the view key maybe handle here.
61
+      crypto::secret_key viewkey;
62
+      bool has_view_key;
63
+
64
+    public:
65
+      device_trezor();
66
+      virtual ~device_trezor() override;
67
+
68
+      device_trezor(const device_trezor &device) = delete ;
69
+      device_trezor& operator=(const device_trezor &device) = delete;
70
+
71
+      explicit operator bool() const override {return true;}
72
+
73
+      device_protocol_t device_protocol() const override { return PROTOCOL_COLD; };
74
+
75
+      bool  has_ki_cold_sync() const override { return true; }
76
+      bool  has_tx_cold_sign() const override { return true; }
77
+      void  set_network_type(cryptonote::network_type network_type) override { this->network_type = network_type; }
78
+
79
+      /* ======================================================================= */
80
+      /*                             WALLET & ADDRESS                            */
81
+      /* ======================================================================= */
82
+      bool  get_public_address(cryptonote::account_public_address &pubkey) override;
83
+      bool  get_secret_keys(crypto::secret_key &viewkey , crypto::secret_key &spendkey) override;
84
+
85
+      /* ======================================================================= */
86
+      /*                              TREZOR PROTOCOL                            */
87
+      /* ======================================================================= */
88
+
89
+      /**
90
+       * Get address. Throws.
91
+       */
92
+      std::shared_ptr<messages::monero::MoneroAddress> get_address(
93
+          const boost::optional<std::vector<uint32_t>> & path = boost::none,
94
+          const boost::optional<cryptonote::network_type> & network_type = boost::none);
95
+
96
+      /**
97
+       * Get watch key from device. Throws.
98
+       */
99
+      std::shared_ptr<messages::monero::MoneroWatchKey> get_view_key(
100
+          const boost::optional<std::vector<uint32_t>> & path = boost::none,
101
+          const boost::optional<cryptonote::network_type> & network_type = boost::none);
102
+
103
+      /**
104
+       * Key image sync with the Trezor.
105
+       */
106
+      void ki_sync(wallet_shim * wallet,
107
+                   const std::vector<::tools::wallet2::transfer_details> & transfers,
108
+                   hw::device_cold::exported_key_image & ski) override;
109
+
110
+      /**
111
+       * Signs particular transaction idx in the unsigned set, keeps state in the signer
112
+       */
113
+      void tx_sign(wallet_shim * wallet,
114
+                   const ::tools::wallet2::unsigned_tx_set & unsigned_tx,
115
+                   size_t idx,
116
+                   hw::tx_aux_data & aux_data,
117
+                   std::shared_ptr<protocol::tx::Signer> & signer);
118
+
119
+      /**
120
+       * Signs unsigned transaction with the Trezor.
121
+       */
122
+      void tx_sign(wallet_shim * wallet,
123
+                   const ::tools::wallet2::unsigned_tx_set & unsigned_tx,
124
+                   ::tools::wallet2::signed_tx_set & signed_tx,
125
+                   hw::tx_aux_data & aux_data) override;
126
+    };
127
+
128
+#endif
129
+
130
+}
131
+}
132
+#endif //MONERO_DEVICE_TREZOR_H

+ 301
- 0
src/device_trezor/device_trezor_base.cpp View File

@@ -0,0 +1,301 @@
1
+// Copyright (c) 2017-2018, The Monero Project
2
+//
3
+// All rights reserved.
4
+//
5
+// Redistribution and use in source and binary forms, with or without modification, are
6
+// permitted provided that the following conditions are met:
7
+//
8
+// 1. Redistributions of source code must retain the above copyright notice, this list of
9
+//    conditions and the following disclaimer.
10
+//
11
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
12
+//    of conditions and the following disclaimer in the documentation and/or other
13
+//    materials provided with the distribution.
14
+//
15
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
16
+//    used to endorse or promote products derived from this software without specific
17
+//    prior written permission.
18
+//
19
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+//
29
+
30
+#include "device_trezor_base.hpp"
31
+
32
+namespace hw {
33
+namespace trezor {
34
+
35
+#if WITH_DEVICE_TREZOR
36
+
37
+#undef MONERO_DEFAULT_LOG_CATEGORY
38
+#define MONERO_DEFAULT_LOG_CATEGORY "device.trezor"
39
+
40
+    std::shared_ptr<google::protobuf::Message> trezor_protocol_callback::on_button_request(const messages::common::ButtonRequest * msg){
41
+      MDEBUG("on_button_request");
42
+      device.on_button_request();
43
+      return std::make_shared<messages::common::ButtonAck>();
44
+    }
45
+
46
+    std::shared_ptr<google::protobuf::Message> trezor_protocol_callback::on_pin_matrix_request(const messages::common::PinMatrixRequest * msg){
47
+      MDEBUG("on_pin_request");
48
+      epee::wipeable_string pin;
49
+      device.on_pin_request(pin);
50
+      auto resp = std::make_shared<messages::common::PinMatrixAck>();
51
+      resp->set_pin(pin.data(), pin.size());
52
+      return resp;
53
+    }
54
+
55
+    std::shared_ptr<google::protobuf::Message> trezor_protocol_callback::on_passphrase_request(const messages::common::PassphraseRequest * msg){
56
+      MDEBUG("on_passhprase_request");
57
+      epee::wipeable_string passphrase;
58
+      device.on_passphrase_request(msg->on_device(), passphrase);
59
+      auto resp = std::make_shared<messages::common::PassphraseAck>();
60
+      if (!msg->on_device()){
61
+        resp->set_passphrase(passphrase.data(), passphrase.size());
62
+      }
63
+      return resp;
64
+    }
65
+
66
+    std::shared_ptr<google::protobuf::Message> trezor_protocol_callback::on_passphrase_state_request(const messages::common::PassphraseStateRequest * msg){
67
+      MDEBUG("on_passhprase_state_request");
68
+      device.on_passphrase_state_request(msg->state());
69
+      return std::make_shared<messages::common::PassphraseStateAck>();
70
+    }
71
+
72
+    const uint32_t device_trezor_base::DEFAULT_BIP44_PATH[] = {0x8000002c, 0x80000080, 0x80000000};
73
+
74
+    device_trezor_base::device_trezor_base() {
75
+
76
+    }
77
+
78
+    device_trezor_base::~device_trezor_base() {
79
+      try {
80
+        disconnect();
81
+        release();
82
+      } catch(std::exception const& e){
83
+        MERROR("Could not disconnect and release: " << e.what());
84
+      }
85
+    }
86
+
87
+    /* ======================================================================= */
88
+    /*                              SETUP/TEARDOWN                             */
89
+    /* ======================================================================= */
90
+
91
+    bool device_trezor_base::reset() {
92
+      return false;
93
+    }
94
+
95
+    bool device_trezor_base::set_name(const std::string & name) {
96
+      this->full_name = name;
97
+      this->name = "";
98
+
99
+      auto delim = name.find(':');
100
+      if (delim != std::string::npos && delim + 1 < name.length()) {
101
+        this->name = name.substr(delim + 1);
102
+      }
103
+
104
+      return true;
105
+    }
106
+
107
+    const std::string device_trezor_base::get_name() const {
108
+      if (this->full_name.empty()) {
109
+        return std::string("<disconnected:").append(this->name).append(">");
110
+      }
111
+      return this->full_name;
112
+    }
113
+
114
+    bool device_trezor_base::init() {
115
+      if (!release()){
116
+        MERROR("Release failed");
117
+        return false;
118
+      }
119
+
120
+      if (!m_protocol_callback){
121
+        m_protocol_callback = std::make_shared<trezor_protocol_callback>(*this);
122
+      }
123
+      return true;
124
+    }
125
+
126
+    bool device_trezor_base::release() {
127
+      try {
128
+        disconnect();
129
+        return true;
130
+
131
+      } catch(std::exception const& e){
132
+        MERROR("Release exception: " << e.what());
133
+        return false;
134
+      }
135
+    }
136
+
137
+    bool device_trezor_base::connect() {
138
+      disconnect();
139
+
140
+      // Enumerate all available devices
141
+      try {
142
+        hw::trezor::t_transport_vect trans;
143
+
144
+        MDEBUG("Enumerating Trezor devices...");
145
+        enumerate(trans);
146
+
147
+        MDEBUG("Enumeration yielded " << trans.size() << " devices");
148
+        for (auto &cur : trans) {
149
+          MDEBUG("  device: " << *(cur.get()));
150
+          std::string cur_path = cur->get_path();
151
+          if (boost::starts_with(cur_path, this->name)) {
152
+            MDEBUG("Device Match: " << cur_path);
153
+            m_transport = cur;
154
+            break;
155
+          }
156
+        }
157
+
158
+        if (!m_transport) {
159
+          MERROR("No matching Trezor device found. Device specifier: \"" + this->name + "\"");
160
+          return false;
161
+        }
162
+
163
+        m_transport->open();
164
+        return true;
165
+
166
+      } catch(std::exception const& e){
167
+        MERROR("Open exception: " << e.what());
168
+        return false;
169
+      }
170
+    }
171
+
172
+    bool device_trezor_base::disconnect() {
173
+      if (m_transport){
174
+        try {
175
+          m_transport->close();
176
+          m_transport = nullptr;
177
+
178
+        } catch(std::exception const& e){
179
+          MERROR("Disconnect exception: " << e.what());
180
+          m_transport = nullptr;
181
+          return false;
182
+        }
183
+      }
184
+      return true;
185
+    }
186
+
187
+    /* ======================================================================= */
188
+    /*  LOCKER                                                                 */
189
+    /* ======================================================================= */
190
+
191
+    //lock the device for a long sequence
192
+    void device_trezor_base::lock() {
193
+      MTRACE("Ask for LOCKING for device " << this->name << " in thread ");
194
+      device_locker.lock();
195
+      MTRACE("Device " << this->name << " LOCKed");
196
+    }
197
+
198
+    //lock the device for a long sequence
199
+    bool device_trezor_base::try_lock() {
200
+      MTRACE("Ask for LOCKING(try) for device " << this->name << " in thread ");
201
+      bool r = device_locker.try_lock();
202
+      if (r) {
203
+        MTRACE("Device " << this->name << " LOCKed(try)");
204
+      } else {
205
+        MDEBUG("Device " << this->name << " not LOCKed(try)");
206
+      }
207
+      return r;
208
+    }
209
+
210
+    //unlock the device
211
+    void device_trezor_base::unlock() {
212
+      MTRACE("Ask for UNLOCKING for device " << this->name << " in thread ");
213
+      device_locker.unlock();
214
+      MTRACE("Device " << this->name << " UNLOCKed");
215
+    }
216
+
217
+    /* ======================================================================= */
218
+    /*  Helpers                                                                */
219
+    /* ======================================================================= */
220
+
221
+    void device_trezor_base::require_connected(){
222
+      if (!m_transport){
223
+        throw exc::NotConnectedException();
224
+      }
225
+    }
226
+
227
+    void device_trezor_base::call_ping_unsafe(){
228
+      auto pingMsg = std::make_shared<messages::management::Ping>();
229
+      pingMsg->set_message("PING");
230
+
231
+      auto success = this->client_exchange<messages::common::Success>(pingMsg);  // messages::MessageType_Success
232
+      MDEBUG("Ping response " << success->message());
233
+      (void)success;
234
+    }
235
+
236
+    void device_trezor_base::test_ping(){
237
+      require_connected();
238
+
239
+      try {
240
+        this->call_ping_unsafe();
241
+
242
+      } catch(exc::TrezorException const& e){
243
+        MINFO("Trezor does not respond: " << e.what());
244
+        throw exc::DeviceNotResponsiveException(std::string("Trezor not responding: ") + e.what());
245
+      }
246
+    }
247
+
248
+    /* ======================================================================= */
249
+    /*                              TREZOR PROTOCOL                            */
250
+    /* ======================================================================= */
251
+
252
+    bool device_trezor_base::ping() {
253
+      AUTO_LOCK_CMD();
254
+      if (!m_transport){
255
+        MINFO("Ping failed, device not connected");
256
+        return false;
257
+      }
258
+
259
+      try {
260
+        this->call_ping_unsafe();
261
+        return true;
262
+
263
+      } catch(std::exception const& e) {
264
+        MERROR("Ping failed, exception thrown " << e.what());
265
+      } catch(...){
266
+        MERROR("Ping failed, general exception thrown" << boost::current_exception_diagnostic_information());
267
+      }
268
+
269
+      return false;
270
+    }
271
+
272
+    void device_trezor_base::on_button_request()
273
+    {
274
+      if (m_callback){
275
+        m_callback->on_button_request();
276
+      }
277
+    }
278
+
279
+    void device_trezor_base::on_pin_request(epee::wipeable_string & pin)
280
+    {
281
+      if (m_callback){
282
+        m_callback->on_pin_request(pin);
283
+      }
284
+    }
285
+
286
+    void device_trezor_base::on_passphrase_request(bool on_device, epee::wipeable_string & passphrase)
287
+    {
288
+      if (m_callback){
289
+        m_callback->on_passphrase_request(on_device, passphrase);
290
+      }
291
+    }
292
+
293
+    void device_trezor_base::on_passphrase_state_request(const std::string & state)
294
+    {
295
+      if (m_callback){
296
+        m_callback->on_passphrase_state_request(state);
297
+      }
298
+    }
299
+
300
+#endif //WITH_DEVICE_TREZOR
301
+}}

+ 301
- 0
src/device_trezor/device_trezor_base.hpp View File

@@ -0,0 +1,301 @@
1
+// Copyright (c) 2017-2018, The Monero Project
2
+//
3
+// All rights reserved.
4
+//
5
+// Redistribution and use in source and binary forms, with or without modification, are
6
+// permitted provided that the following conditions are met:
7
+//
8
+// 1. Redistributions of source code must retain the above copyright notice, this list of
9
+//    conditions and the following disclaimer.
10
+//
11
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
12
+//    of conditions and the following disclaimer in the documentation and/or other
13
+//    materials provided with the distribution.
14
+//
15
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
16
+//    used to endorse or promote products derived from this software without specific
17
+//    prior written permission.
18
+//
19
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+//
29
+
30
+#ifndef MONERO_DEVICE_TREZOR_BASE_H
31
+#define MONERO_DEVICE_TREZOR_BASE_H
32
+
33
+
34
+#include <cstddef>
35
+#include <string>
36
+#include "device/device.hpp"
37
+#include "device/device_default.hpp"
38
+#include "device/device_cold.hpp"
39
+#include <boost/scope_exit.hpp>
40
+#include <boost/thread/mutex.hpp>
41
+#include <boost/thread/recursive_mutex.hpp>
42
+#include "cryptonote_config.h"
43
+#include "trezor.hpp"
44
+
45
+//automatic lock one more level on device ensuring the current thread is allowed to use it
46
+#define AUTO_LOCK_CMD() \
47
+  /* lock both mutexes without deadlock*/ \
48
+  boost::lock(device_locker, command_locker); \
49
+  /* make sure both already-locked mutexes are unlocked at the end of scope */ \
50
+  boost::lock_guard<boost::recursive_mutex> lock1(device_locker, boost::adopt_lock); \
51
+  boost::lock_guard<boost::mutex> lock2(command_locker, boost::adopt_lock)
52
+
53
+  
54
+namespace hw {
55
+namespace trezor {
56
+
57
+#if WITH_DEVICE_TREZOR
58
+  class device_trezor_base;
59
+
60
+  /**
61
+   * Trezor device callbacks
62
+   */
63
+  class trezor_callback {
64
+  public:
65
+    virtual void on_button_request() {};
66
+    virtual void on_pin_request(epee::wipeable_string & pin) {};
67
+    virtual void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase) {};
68
+    virtual void on_passphrase_state_request(const std::string & state) {};
69
+  };
70
+
71
+  /**
72
+   * Default Trezor protocol client callback
73
+   */
74
+  class trezor_protocol_callback {
75
+  protected:
76
+    device_trezor_base & device;
77
+
78
+  public:
79
+    explicit trezor_protocol_callback(device_trezor_base & device): device(device) {}
80
+
81
+    std::shared_ptr<google::protobuf::Message> on_button_request(const messages::common::ButtonRequest * msg);
82
+    std::shared_ptr<google::protobuf::Message> on_pin_matrix_request(const messages::common::PinMatrixRequest * msg);
83
+    std::shared_ptr<google::protobuf::Message> on_passphrase_request(const messages::common::PassphraseRequest * msg);
84
+    std::shared_ptr<google::protobuf::Message> on_passphrase_state_request(const messages::common::PassphraseStateRequest * msg);
85
+
86
+    std::shared_ptr<google::protobuf::Message> on_message(const google::protobuf::Message * msg, messages::MessageType message_type){
87
+      MDEBUG("on_general_message");
88
+      return on_message_dispatch(msg, message_type);
89
+    }
90
+
91
+    std::shared_ptr<google::protobuf::Message> on_message_dispatch(const google::protobuf::Message * msg, messages::MessageType message_type){
92
+      if (message_type == messages::MessageType_ButtonRequest){
93
+        return on_button_request(dynamic_cast<const messages::common::ButtonRequest*>(msg));
94
+      } else if (message_type == messages::MessageType_PassphraseRequest) {
95
+        return on_passphrase_request(dynamic_cast<const messages::common::PassphraseRequest*>(msg));
96
+      } else if (message_type == messages::MessageType_PassphraseStateRequest) {
97
+        return on_passphrase_state_request(dynamic_cast<const messages::common::PassphraseStateRequest*>(msg));
98
+      } else if (message_type == messages::MessageType_PinMatrixRequest) {
99
+        return on_pin_matrix_request(dynamic_cast<const messages::common::PinMatrixRequest*>(msg));
100
+      } else {
101
+        return nullptr;
102
+      }
103
+    }
104
+  };
105
+
106
+  /**
107
+   * TREZOR device template with basic functions
108
+   */
109
+  class device_trezor_base : public hw::core::device_default {
110
+    protected:
111
+
112
+      // Locker for concurrent access
113
+      mutable boost::recursive_mutex  device_locker;
114
+      mutable boost::mutex  command_locker;
115
+
116
+      std::shared_ptr<Transport> m_transport;
117
+      std::shared_ptr<trezor_protocol_callback> m_protocol_callback;
118
+      std::shared_ptr<trezor_callback> m_callback;
119
+
120
+      std::string full_name;
121
+
122
+      cryptonote::network_type network_type;
123
+
124
+      //
125
+      // Internal methods
126
+      //
127
+
128
+      void require_connected();
129
+      void call_ping_unsafe();
130
+      void test_ping();
131
+
132
+      /**
133
+       * Client communication wrapper, handles specific Trezor protocol.
134
+       *
135
+       * @throws UnexpectedMessageException if the response message type is different than expected.
136
+       * Exception contains message type and the message itself.
137
+       */
138
+      template<class t_message>
139
+      std::shared_ptr<t_message>
140
+      client_exchange(const std::shared_ptr<const google::protobuf::Message> &req,
141
+                      const boost::optional<messages::MessageType> & resp_type = boost::none,
142
+                      const boost::optional<std::vector<messages::MessageType>> & resp_types = boost::none,
143
+                      const boost::optional<messages::MessageType*> & resp_type_ptr = boost::none,
144
+                      bool open_session = false,
145
+                      unsigned depth=0)
146
+      {
147
+        // Require strictly protocol buffers response in the template.
148
+        BOOST_STATIC_ASSERT(boost::is_base_of<google::protobuf::Message, t_message>::value);
149
+        const bool accepting_base = boost::is_same<google::protobuf::Message, t_message>::value;
150
+        if (resp_types && !accepting_base){
151
+          throw std::invalid_argument("Cannot specify list of accepted types and not using generic response");
152
+        }
153
+
154
+        // Open session if required
155
+        if (open_session && depth == 0){
156
+          try {
157
+            m_transport->open();
158
+          } catch (const std::exception& e) {
159
+            std::throw_with_nested(exc::SessionException("Could not open session"));
160
+          }
161
+        }
162
+
163
+        // Scoped session closer
164
+        BOOST_SCOPE_EXIT_ALL(&, this) {
165
+          if (open_session && depth == 0){
166
+            this->getTransport()->close();
167
+          }
168
+        };
169
+
170
+        // Write the request
171
+        CHECK_AND_ASSERT_THROW_MES(req, "Request is null");
172
+        this->getTransport()->write(*req);
173
+
174
+        // Read the response
175
+        std::shared_ptr<google::protobuf::Message> msg_resp;
176
+        hw::trezor::messages::MessageType msg_resp_type;
177
+
178
+        // We may have several roundtrips with the handler
179
+        this->getTransport()->read(msg_resp, &msg_resp_type);
180
+        if (resp_type_ptr){
181
+          *(resp_type_ptr.get()) = msg_resp_type;
182
+        }
183
+
184
+        // Determine type of expected message response
185
+        messages::MessageType required_type = accepting_base ? messages::MessageType_Success :
186
+            (resp_type ? resp_type.get() : MessageMapper::get_message_wire_number<t_message>());
187
+
188
+        if (msg_resp_type == messages::MessageType_Failure) {
189
+          throw_failure_exception(dynamic_cast<messages::common::Failure *>(msg_resp.get()));
190
+
191
+        } else if (!accepting_base && msg_resp_type == required_type) {
192
+          return message_ptr_retype<t_message>(msg_resp);
193
+
194
+        } else {
195
+          auto resp = this->getProtocolCallback()->on_message(msg_resp.get(), msg_resp_type);
196
+          if (resp) {
197
+            return this->client_exchange<t_message>(resp, boost::none, resp_types, resp_type_ptr, false, depth + 1);
198
+
199
+          } else if (accepting_base && (!resp_types ||
200
+                     std::find(resp_types.get().begin(), resp_types.get().end(), msg_resp_type) != resp_types.get().end())) {
201
+            return message_ptr_retype<t_message>(msg_resp);
202
+
203
+          } else {
204
+            throw exc::UnexpectedMessageException(msg_resp_type, msg_resp);
205
+          }
206
+        }
207
+      }
208
+
209
+      /**
210
+       * Utility method to set address_n and network type to the message requets.
211
+       */
212
+      template<class t_message>
213
+      void set_msg_addr(t_message * msg,
214
+                        const boost::optional<std::vector<uint32_t>> & path = boost::none,
215
+                        const boost::optional<cryptonote::network_type> & network_type = boost::none)
216
+      {
217
+        CHECK_AND_ASSERT_THROW_MES(msg, "Message is null");
218
+        msg->clear_address_n();
219
+        if (path){
220
+          for(auto x : path.get()){
221
+            msg->add_address_n(x);
222
+          }
223
+        } else {
224
+          for (unsigned int i : DEFAULT_BIP44_PATH) {
225
+            msg->add_address_n(i);
226
+          }
227
+        }
228
+
229
+        if (network_type){
230
+          msg->set_network_type(static_cast<uint32_t>(network_type.get()));
231
+        } else {
232
+          msg->set_network_type(static_cast<uint32_t>(this->network_type));
233
+        }
234
+      }
235
+
236
+    public:
237
+    device_trezor_base();
238
+    ~device_trezor_base() override;
239
+
240
+    device_trezor_base(const device_trezor_base &device) = delete ;
241
+    device_trezor_base& operator=(const device_trezor_base &device) = delete;
242
+
243
+    explicit operator bool() const override {return true;}
244
+    device_type get_type() const override {return device_type::TREZOR;};
245
+
246
+    bool reset();
247
+
248
+    // Default derivation path for Monero
249
+    static const uint32_t DEFAULT_BIP44_PATH[3];
250
+
251
+    std::shared_ptr<Transport> getTransport(){
252
+      return m_transport;
253
+    }
254
+
255
+    std::shared_ptr<trezor_protocol_callback> getProtocolCallback(){
256
+      return m_protocol_callback;
257
+    }
258
+
259
+    std::shared_ptr<trezor_callback> getCallback(){
260
+      return m_callback;
261
+    }
262
+
263
+    /* ======================================================================= */
264
+    /*                              SETUP/TEARDOWN                             */
265
+    /* ======================================================================= */
266
+    bool set_name(const std::string &name) override;
267
+
268
+    const std::string get_name() const override;
269
+    bool init() override;
270
+    bool release() override;
271
+    bool connect() override;
272
+    bool disconnect() override;
273
+
274
+    /* ======================================================================= */
275
+    /*  LOCKER                                                                 */
276
+    /* ======================================================================= */
277
+    void lock()  override;
278
+    void unlock() override;
279
+    bool try_lock() override;
280
+
281
+    /* ======================================================================= */
282
+    /*                              TREZOR PROTOCOL                            */
283
+    /* ======================================================================= */
284
+
285
+    /**
286
+     * Device ping, no-throw
287
+     */
288
+    bool ping();
289
+
290
+    // Protocol callbacks
291
+    void on_button_request();
292
+    void on_pin_request(epee::wipeable_string & pin);
293
+    void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase);
294
+    void on_passphrase_state_request(const std::string & state);
295
+  };
296
+
297
+#endif
298
+
299
+}
300
+}
301
+#endif //MONERO_DEVICE_TREZOR_BASE_H

+ 44
- 0
src/device_trezor/trezor.hpp View File

@@ -0,0 +1,44 @@
1
+// Copyright (c) 2017-2018, The Monero Project
2
+//
3
+// All rights reserved.
4
+//
5
+// Redistribution and use in source and binary forms, with or without modification, are
6
+// permitted provided that the following conditions are met:
7
+//
8
+// 1. Redistributions of source code must retain the above copyright notice, this list of
9
+//    conditions and the following disclaimer.
10
+//
11
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
12
+//    of conditions and the following disclaimer in the documentation and/or other
13
+//    materials provided with the distribution.
14
+//
15
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
16
+//    used to endorse or promote products derived from this software without specific
17
+//    prior written permission.
18
+//
19
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+//
29
+
30
+#ifndef MONERO_TREZOR_HPP
31
+#define MONERO_TREZOR_HPP
32
+
33
+#include "trezor/trezor_defs.hpp"
34
+
35
+#ifdef HAVE_PROTOBUF
36
+#include "trezor/transport.hpp"
37
+#include "trezor/messages/messages.pb.h"
38
+#include "trezor/messages/messages-common.pb.h"
39
+#include "trezor/messages/messages-management.pb.h"
40
+#include "trezor/messages/messages-monero.pb.h"
41
+#include "trezor/protocol.hpp"
42
+#endif
43
+
44
+#endif //MONERO_TREZOR_HPP

+ 193
- 0
src/device_trezor/trezor/exceptions.hpp View File

@@ -0,0 +1,193 @@
1
+// Copyright (c) 2017-2018, The Monero Project
2
+//
3
+// All rights reserved.
4
+//
5
+// Redistribution and use in source and binary forms, with or without modification, are
6
+// permitted provided that the following conditions are met:
7
+//
8
+// 1. Redistributions of source code must retain the above copyright notice, this list of
9
+//    conditions and the following disclaimer.
10
+//
11
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
12
+//    of conditions and the following disclaimer in the documentation and/or other
13
+//    materials provided with the distribution.
14
+//
15
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
16
+//    used to endorse or promote products derived from this software without specific
17
+//    prior written permission.
18
+//
19
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+//
29
+
30
+#ifndef MONERO_EXCEPTIONS_H
31
+#define MONERO_EXCEPTIONS_H
32
+
33
+#include <exception>
34
+#include <string>
35
+#include <boost/optional.hpp>
36
+
37
+namespace hw {
38
+namespace trezor {
39
+namespace exc {
40
+
41
+  class SecurityException : public std::exception {
42
+  protected:
43
+    boost::optional<std::string> reason;
44
+
45
+  public:
46
+    SecurityException(): reason("General Security exception"){}
47
+    explicit SecurityException(std::string what): reason(what){}
48
+
49
+    virtual const char* what() const throw() {
50
+      return reason.get().c_str();
51
+    }
52
+  };
53
+
54
+  class Poly1305TagInvalid: public SecurityException {
55
+  public:
56
+    using SecurityException::SecurityException;
57
+    Poly1305TagInvalid(): SecurityException("Poly1305 authentication tag invalid"){}
58
+  };
59
+
60
+  class TrezorException : public std::exception {
61
+  protected:
62
+    boost::optional<std::string> reason;
63
+
64
+  public:
65
+    TrezorException(): reason("General Trezor exception"){}
66
+    explicit TrezorException(std::string what): reason(what){}
67
+
68
+    virtual const char* what() const throw() {
69
+      return reason.get().c_str();
70
+    }
71
+  };
72
+
73
+  class CommunicationException: public TrezorException {
74
+  public:
75
+    using TrezorException::TrezorException;
76
+    CommunicationException(): TrezorException("Trezor communication error"){}
77
+  };
78
+
79
+  class EncodingException: public CommunicationException {
80
+  public:
81
+    using CommunicationException::CommunicationException;
82
+    EncodingException(): CommunicationException("Trezor message encoding error"){}
83
+  };
84
+
85
+  class NotConnectedException : public CommunicationException {
86
+  public:
87
+    using CommunicationException::CommunicationException;
88
+    NotConnectedException(): CommunicationException("Trezor not connected"){}
89
+  };
90
+
91
+  class DeviceNotResponsiveException : public CommunicationException {
92
+  public:
93
+    using CommunicationException::CommunicationException;
94
+    DeviceNotResponsiveException(): CommunicationException("Trezor does not respond to ping"){}
95
+  };
96
+
97
+  class DeviceAcquireException : public CommunicationException {
98
+  public:
99
+    using CommunicationException::CommunicationException;
100
+    DeviceAcquireException(): CommunicationException("Trezor could not be acquired"){}
101
+  };
102
+
103
+  class SessionException: public CommunicationException {
104
+  public:
105
+    using CommunicationException::CommunicationException;
106
+    SessionException(): CommunicationException("Trezor session expired"){}
107
+  };
108
+
109
+  class TimeoutException: public CommunicationException {
110
+  public:
111
+    using CommunicationException::CommunicationException;
112
+    TimeoutException(): CommunicationException("Trezor communication timeout"){}
113
+  };
114
+
115
+  class ProtocolException: public CommunicationException {
116
+  public:
117
+    using CommunicationException::CommunicationException;
118
+    ProtocolException(): CommunicationException("Trezor protocol error"){}
119
+  };
120
+
121
+  // Communication protocol namespace
122
+  // Separated to distinguish between client and Trezor side exceptions.
123
+namespace proto {
124
+
125
+  class SecurityException : public ProtocolException {
126
+  public:
127
+    using ProtocolException::ProtocolException;
128
+    SecurityException(): ProtocolException("Security assertion violated in the protocol"){}
129
+  };
130
+
131
+  class FailureException : public ProtocolException {
132
+  private:
133
+    boost::optional<uint32_t> code;
134
+    boost::optional<std::string> message;
135
+  public:
136
+    using ProtocolException::ProtocolException;
137
+    FailureException(): ProtocolException("Trezor returned failure"){}
138
+    FailureException(boost::optional<uint32_t> code,
139
+                     boost::optional<std::string> message)
140
+        : code(code), message(message) {
141
+      reason = "Trezor returned failure: code="
142
+               + (code ? std::to_string(code.get()) : "")
143
+               + ", message=" + (message ? message.get() : "");
144
+    };
145
+  };
146
+
147
+  class UnexpectedMessageException : public FailureException {
148
+  public:
149
+    using FailureException::FailureException;
150
+    UnexpectedMessageException(): FailureException("Trezor claims unexpected message received"){}
151
+  };
152
+
153
+  class CancelledException : public FailureException {
154
+  public:
155
+    using FailureException::FailureException;
156
+    CancelledException(): FailureException("Trezor returned: cancelled operation"){}
157
+  };
158
+
159
+  class PinExpectedException : public FailureException {
160
+  public:
161
+    using FailureException::FailureException;
162
+    PinExpectedException(): FailureException("Trezor claims PIN is expected"){}
163
+  };
164
+
165
+  class InvalidPinException : public FailureException {
166
+  public:
167
+    using FailureException::FailureException;
168
+    InvalidPinException(): FailureException("Trezor claims PIN is invalid"){}
169
+  };
170
+
171
+  class NotEnoughFundsException : public FailureException {
172
+  public:
173
+    using FailureException::FailureException;
174
+    NotEnoughFundsException(): FailureException("Trezor claims not enough funds"){}
175
+  };
176
+
177
+  class NotInitializedException : public FailureException {
178
+  public:
179
+    using FailureException::FailureException;
180
+    NotInitializedException(): FailureException("Trezor claims not initialized"){}
181
+  };
182
+
183
+  class FirmwareErrorException : public FailureException {
184
+  public:
185
+    using FailureException::FailureException;
186
+    FirmwareErrorException(): FailureException("Trezor returned firmware error"){}
187
+  };
188
+
189
+}
190
+}
191
+}
192
+}
193
+#endif //MONERO_EXCEPTIONS_H

+ 2
- 0
src/device_trezor/trezor/messages/.gitignore View File

@@ -0,0 +1,2 @@
1
+# protobuf generated code
2
+*

+ 125
- 0
src/device_trezor/trezor/messages_map.cpp View File

@@ -0,0 +1,125 @@
1
+// Copyright (c) 2017-2018, The Monero Project
2
+//
3
+// All rights reserved.
4
+//
5
+// Redistribution and use in source and binary forms, with or without modification, are
6
+// permitted provided that the following conditions are met:
7
+//
8
+// 1. Redistributions of source code must retain the above copyright notice, this list of
9
+//    conditions and the following disclaimer.
10
+//
11
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
12
+//    of conditions and the following disclaimer in the documentation and/or other
13
+//    materials provided with the distribution.
14
+//
15
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
16
+//    used to endorse or promote products derived from this software without specific
17
+//    prior written permission.
18
+//
19
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+//
29
+
30
+#include "messages_map.hpp"
31
+#include "messages/messages.pb.h"
32
+#include "messages/messages-common.pb.h"
33
+#include "messages/messages-management.pb.h"
34
+#include "messages/messages-monero.pb.h"
35
+
36
+using namespace std;
37
+using namespace hw::trezor;
38
+
39
+namespace hw{
40
+namespace trezor
41
+{
42
+
43
+  const char * TYPE_PREFIX = "MessageType_";
44
+  const char * PACKAGES[] = {
45
+      "hw.trezor.messages.",
46
+      "hw.trezor.messages.common.",
47
+      "hw.trezor.messages.management.",
48
+      "hw.trezor.messages.monero."
49
+  };
50
+
51
+  google::protobuf::Message * MessageMapper::get_message(int wire_number) {
52
+    return MessageMapper::get_message(static_cast<messages::MessageType>(wire_number));
53
+  }
54
+
55
+  google::protobuf::Message * MessageMapper::get_message(messages::MessageType wire_number) {
56
+    const string &messageTypeName = hw::trezor::messages::MessageType_Name(wire_number);
57
+    if (messageTypeName.empty()) {
58
+      throw exc::EncodingException(std::string("Message descriptor not found: ") + std::to_string(wire_number));
59
+    }
60
+
61
+    string messageName = messageTypeName.substr(strlen(TYPE_PREFIX));
62
+    return MessageMapper::get_message(messageName);
63
+  }
64
+
65
+  google::protobuf::Message * MessageMapper::get_message(const std::string & msg_name) {
66
+    // Each package instantiation so lookup works
67
+    hw::trezor::messages::common::Success::default_instance();
68
+    hw::trezor::messages::management::Cancel::default_instance();
69
+    hw::trezor::messages::monero::MoneroGetAddress::default_instance();
70
+
71
+    google::protobuf::Descriptor const * desc = nullptr;
72
+    for(const string &text : PACKAGES){
73
+      desc = google::protobuf::DescriptorPool::generated_pool()
74
+          ->FindMessageTypeByName(text + msg_name);
75
+      if (desc != nullptr){
76
+        break;
77
+      }
78
+    }
79
+
80
+    if (desc == nullptr){
81
+      throw exc::EncodingException(std::string("Message not found: ") + msg_name);
82
+    }
83
+
84
+    google::protobuf::Message* message =
85
+        google::protobuf::MessageFactory::generated_factory()
86
+            ->GetPrototype(desc)->New();
87
+
88
+    return message;
89
+
90
+//    // CODEGEN way, fast
91
+//    switch(wire_number){
92
+//      case 501:
93
+//        return new messages::monero::MoneroTransactionSignRequest();
94
+//      default:
95
+//        throw std::runtime_error("not implemented");
96
+//    }
97
+//
98
+//    // CODEGEN message -> number: specification
99
+//    //    messages::MessageType get_message_wire_number(const messages::monero::MoneroTransactionSignRequest * msg) { return 501; }
100
+//    //    messages::MessageType get_message_wire_number(const messages::management::ping * msg)
101
+//
102
+  }
103
+
104
+  messages::MessageType MessageMapper::get_message_wire_number(const google::protobuf::Message * msg){
105
+    return MessageMapper::get_message_wire_number(msg->GetDescriptor()->name());
106
+  }
107
+
108
+  messages::MessageType MessageMapper::get_message_wire_number(const google::protobuf::Message & msg){
109
+    return MessageMapper::get_message_wire_number(msg.GetDescriptor()->name());
110
+  }
111
+
112
+  messages::MessageType MessageMapper::get_message_wire_number(const std::string & msg_name){
113
+    string enumMessageName = std::string(TYPE_PREFIX) + msg_name;
114
+
115
+    messages::MessageType res;
116
+    bool r = hw::trezor::messages::MessageType_Parse(enumMessageName, &res);
117
+    if (!r){
118
+      throw exc::EncodingException(std::string("Message ") + msg_name + " not found");
119
+    }
120
+
121
+    return res;
122
+  }
123
+
124
+}
125
+}

+ 94
- 0
src/device_trezor/trezor/messages_map.hpp View File

@@ -0,0 +1,94 @@
1
+// Copyright (c) 2017-2018, The Monero Project
2
+//
3
+// All rights reserved.
4
+//
5
+// Redistribution and use in source and binary forms, with or without modification, are
6
+// permitted provided that the following conditions are met:
7
+//
8
+// 1. Redistributions of source code must retain the above copyright notice, this list of
9
+//    conditions and the following disclaimer.
10
+//
11
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
12
+//    of conditions and the following disclaimer in the documentation and/or other
13
+//    materials provided with the distribution.
14
+//
15
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
16
+//    used to endorse or promote products derived from this software without specific
17
+//    prior written permission.
18
+//
19
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+//
29
+
30
+#ifndef MONERO_MESSAGES_MAP_H
31
+#define MONERO_MESSAGES_MAP_H
32
+
33
+#include <string>
34
+#include <type_traits>
35
+#include <memory>
36
+#include "exceptions.hpp"
37
+
38
+#include "trezor_defs.hpp"
39
+
40
+#include <google/protobuf/stubs/common.h>
41
+#include <google/protobuf/generated_message_util.h>
42
+#include <google/protobuf/repeated_field.h>
43
+#include <google/protobuf/extension_set.h>
44
+#include <google/protobuf/generated_enum_reflection.h>
45
+#include "google/protobuf/descriptor.pb.h"
46
+
47
+#include "messages/messages.pb.h"
48
+
49
+namespace hw {
50
+namespace trezor {
51
+
52
+  class MessageMapper{
53
+    public:
54
+      MessageMapper() {
55
+
56
+      }
57
+
58
+    static ::google::protobuf::Message * get_message(int wire_number);
59
+    static ::google::protobuf::Message * get_message(messages::MessageType);
60
+    static ::google::protobuf::Message * get_message(const std::string & msg_name);
61
+    static messages::MessageType get_message_wire_number(const google::protobuf::Message * msg);
62
+    static messages::MessageType get_message_wire_number(const google::protobuf::Message & msg);
63
+    static messages::MessageType get_message_wire_number(const std::string & msg_name);
64
+
65
+    template<class t_message>
66
+    static messages::MessageType get_message_wire_number() {
67
+      BOOST_STATIC_ASSERT(boost::is_base_of<google::protobuf::Message, t_message>::value);
68
+      return get_message_wire_number(t_message::default_instance().GetDescriptor()->name());
69
+    }
70
+  };
71
+
72
+  template<class t_message>
73
+  std::shared_ptr<t_message> message_ptr_retype(std::shared_ptr<google::protobuf::Message> & in){
74
+    BOOST_STATIC_ASSERT(boost::is_base_of<google::protobuf::Message, t_message>::value);
75
+    if (!in){
76
+      return nullptr;
77
+    }
78
+
79
+    return std::dynamic_pointer_cast<t_message>(in);
80
+  }
81
+
82
+  template<class t_message>
83
+  std::shared_ptr<t_message> message_ptr_retype_static(std::shared_ptr<google::protobuf::Message> & in){
84
+    BOOST_STATIC_ASSERT(boost::is_base_of<google::protobuf::Message, t_message>::value);
85
+    if (!in){
86
+      return nullptr;
87
+    }
88
+
89
+    return std::static_pointer_cast<t_message>(in);
90
+  }
91
+
92
+}}
93
+
94
+#endif //MONERO_MESSAGES_MAP_H

+ 891
- 0
src/device_trezor/trezor/protocol.cpp View File

@@ -0,0 +1,891 @@
1
+// Copyright (c) 2017-2018, The Monero Project
2
+//
3
+// All rights reserved.
4
+//
5
+// Redistribution and use in source and binary forms, with or without modification, are
6
+// permitted provided that the following conditions are met:
7
+//
8
+// 1. Redistributions of source code must retain the above copyright notice, this list of
9
+//    conditions and the following disclaimer.
10
+//
11
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
12
+//    of conditions and the following disclaimer in the documentation and/or other
13
+//    materials provided with the distribution.
14
+//
15
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
16
+//    used to endorse or promote products derived from this software without specific
17
+//    prior written permission.
18
+//
19
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+//
29
+
30
+#include "protocol.hpp"
31
+#include <unordered_map>
32
+#include <set>
33
+#include <utility>
34
+#include <boost/endian/conversion.hpp>
35
+#include <common/apply_permutation.h>
36
+#include <ringct/rctSigs.h>
37
+#include <ringct/bulletproofs.h>
38
+#include "cryptonote_config.h"
39
+#include <sodium.h>
40
+#include <sodium/crypto_verify_32.h>
41
+#include <sodium/crypto_aead_chacha20poly1305.h>
42
+
43
+namespace hw{
44
+namespace trezor{
45
+namespace protocol{
46
+
47
+  std::string key_to_string(const ::crypto::ec_point & key){
48
+    return std::string(key.data, sizeof(key.data));
49
+  }
50
+
51
+  std::string key_to_string(const ::crypto::ec_scalar & key){
52
+    return std::string(key.data, sizeof(key.data));
53
+  }
54
+
55
+  std::string key_to_string(const ::crypto::hash & key){
56
+    return std::string(key.data, sizeof(key.data));
57
+  }
58
+
59
+  std::string key_to_string(const ::rct::key & key){
60
+    return std::string(reinterpret_cast<const char*>(key.bytes), sizeof(key.bytes));
61
+  }
62
+
63
+  void string_to_key(::crypto::ec_scalar & key, const std::string & str){
64
+    if (str.size() != sizeof(key.data)){
65
+      throw std::invalid_argument(std::string("Key has to have ") + std::to_string(sizeof(key.data)) + " B");
66
+    }
67
+    memcpy(key.data, str.data(), sizeof(key.data));
68
+  }
69
+
70
+  void string_to_key(::crypto::ec_point & key, const std::string & str){
71
+    if (str.size() != sizeof(key.data)){
72
+      throw std::invalid_argument(std::string("Key has to have ") + std::to_string(sizeof(key.data)) + " B");
73
+    }
74
+    memcpy(key.data, str.data(), sizeof(key.data));
75
+  }
76
+
77
+  void string_to_key(::rct::key & key, const std::string & str){
78
+    if (str.size() != sizeof(key.bytes)){
79
+      throw std::invalid_argument(std::string("Key has to have ") + std::to_string(sizeof(key.bytes)) + " B");
80
+    }
81
+    memcpy(key.bytes, str.data(), sizeof(key.bytes));
82
+  }
83
+
84
+namespace crypto {
85
+namespace chacha {
86
+
87
+  void decrypt(const void* ciphertext, size_t length, const uint8_t* key, const uint8_t* iv, char* plaintext){
88
+    if (length < 16){
89
+      throw std::invalid_argument("Ciphertext length too small");
90
+    }
91
+
92
+    unsigned long long int cip_len = length;
93
+    auto r = crypto_aead_chacha20poly1305_ietf_decrypt(
94
+        reinterpret_cast<unsigned char *>(plaintext), &cip_len, nullptr,
95
+        static_cast<const unsigned char *>(ciphertext), length, nullptr, 0, iv, key);
96
+
97
+    if (r != 0){
98
+      throw exc::Poly1305TagInvalid();
99
+    }
100
+  }
101
+
102
+}
103
+}
104
+
105
+
106
+// Cold Key image sync
107
+namespace ki {
108
+
109
+  bool key_image_data(wallet_shim * wallet,
110
+                      const std::vector<tools::wallet2::transfer_details> & transfers,
111
+                      std::vector<MoneroTransferDetails> & res)
112
+  {
113
+    for(auto & td : transfers){
114
+      ::crypto::public_key tx_pub_key = wallet->get_tx_pub_key_from_received_outs(td);
115
+      const std::vector<::crypto::public_key> additional_tx_pub_keys = cryptonote::get_additional_tx_pub_keys_from_extra(td.m_tx);
116
+
117
+      res.emplace_back();
118
+      auto & cres = res.back();
119
+
120
+      cres.set_out_key(key_to_string(boost::get<cryptonote::txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key));
121
+      cres.set_tx_pub_key(key_to_string(tx_pub_key));
122
+      cres.set_internal_output_index(td.m_internal_output_index);
123
+      for(auto & aux : additional_tx_pub_keys){
124
+        cres.add_additional_tx_pub_keys(key_to_string(aux));
125
+      }
126
+    }
127
+
128
+    return true;
129
+  }
130
+
131
+  std::string compute_hash(const MoneroTransferDetails & rr){
132
+    KECCAK_CTX kck;
133
+    uint8_t md[32];
134
+
135
+    CHECK_AND_ASSERT_THROW_MES(rr.out_key().size() == 32, "Invalid out_key size");
136
+    CHECK_AND_ASSERT_THROW_MES(rr.tx_pub_key().size() == 32, "Invalid tx_pub_key size");
137
+
138
+    keccak_init(&kck);
139
+    keccak_update(&kck, reinterpret_cast<const uint8_t *>(rr.out_key().data()), 32);
140
+    keccak_update(&kck, reinterpret_cast<const uint8_t *>(rr.tx_pub_key().data()), 32);
141
+    for (const auto &aux : rr.additional_tx_pub_keys()){
142
+      CHECK_AND_ASSERT_THROW_MES(aux.size() == 32, "Invalid aux size");
143
+      keccak_update(&kck, reinterpret_cast<const uint8_t *>(aux.data()), 32);
144
+    }
145
+
146
+    auto index_serialized = tools::get_varint_data(rr.internal_output_index());
147
+    keccak_update(&kck, reinterpret_cast<const uint8_t *>(index_serialized.data()), index_serialized.size());
148
+    keccak_finish(&kck, md);
149
+    return std::string(reinterpret_cast<const char*>(md), sizeof(md));
150
+  }
151
+
152
+  void generate_commitment(std::vector<MoneroTransferDetails> & mtds,
153
+                           const std::vector<tools::wallet2::transfer_details> & transfers,
154
+                           std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req)
155
+  {
156
+    req = std::make_shared<messages::monero::MoneroKeyImageExportInitRequest>();
157
+
158
+    KECCAK_CTX kck;
159
+    uint8_t final_hash[32];
160
+    keccak_init(&kck);
161
+
162
+    for(auto &cur : mtds){
163
+      auto hash = compute_hash(cur);
164
+      keccak_update(&kck, reinterpret_cast<const uint8_t *>(hash.data()), hash.size());
165
+    }
166
+    keccak_finish(&kck, final_hash);
167
+
168
+    req = std::make_shared<messages::monero::MoneroKeyImageExportInitRequest>();
169
+    req->set_hash(std::string(reinterpret_cast<const char*>(final_hash), 32));
170
+    req->set_num(transfers.size());
171
+
172
+    std::unordered_map<uint32_t, std::set<uint32_t>> sub_indices;
173
+    for (auto &cur : transfers){
174
+      auto search = sub_indices.emplace(cur.m_subaddr_index.major, std::set<uint32_t>());
175
+      auto & st = search.first->second;
176
+      st.insert(cur.m_subaddr_index.minor);
177
+    }
178
+
179
+    for (auto& x: sub_indices){
180
+      auto subs = req->add_subs();
181
+      subs->set_account(x.first);
182
+      for(auto minor : x.second){
183
+        subs->add_minor_indices(minor);
184
+      }
185
+    }
186
+  }
187
+
188
+}
189
+
190
+// Cold transaction signing
191
+namespace tx {
192
+
193
+  void translate_address(MoneroAccountPublicAddress * dst, const cryptonote::account_public_address * src){
194
+    dst->set_view_public_key(key_to_string(src->m_view_public_key));
195
+    dst->set_spend_public_key(key_to_string(src->m_spend_public_key));
196
+  }
197
+
198
+  void translate_dst_entry(MoneroTransactionDestinationEntry * dst, const cryptonote::tx_destination_entry * src){
199
+    dst->set_amount(src->amount);
200
+    dst->set_is_subaddress(src->is_subaddress);
201
+    translate_address(dst->mutable_addr(), &(src->addr));
202
+  }
203
+
204
+  void translate_src_entry(MoneroTransactionSourceEntry * dst, const cryptonote::tx_source_entry * src){
205
+    for(auto & cur : src->outputs){
206
+      auto out = dst->add_outputs();
207
+      out->set_idx(cur.first);
208
+      translate_rct_key(out->mutable_key(), &(cur.second));
209
+    }
210
+
211
+    dst->set_real_output(src->real_output);
212
+    dst->set_real_out_tx_key(key_to_string(src->real_out_tx_key));
213
+    for(auto & cur : src->real_out_additional_tx_keys){
214
+      dst->add_real_out_additional_tx_keys(key_to_string(cur));
215
+    }
216
+
217
+    dst->set_real_output_in_tx_index(src->real_output_in_tx_index);
218
+    dst->set_amount(src->amount);
219
+    dst->set_rct(src->rct);
220
+    dst->set_mask(key_to_string(src->mask));
221
+    translate_klrki(dst->mutable_multisig_klrki(), &(src->multisig_kLRki));
222
+  }
223
+
224
+  void translate_klrki(MoneroMultisigKLRki * dst, const rct::multisig_kLRki * src){
225
+    dst->set_k(key_to_string(src->k));
226
+    dst->set_l(key_to_string(src->L));
227
+    dst->set_r(key_to_string(src->R));
228
+    dst->set_ki(key_to_string(src->ki));
229
+  }
230
+
231
+  void translate_rct_key(MoneroRctKey * dst, const rct::ctkey * src){
232
+    dst->set_dest(key_to_string(src->dest));
233
+    dst->set_commitment(key_to_string(src->mask));
234
+  }
235
+
236
+  std::string hash_addr(const MoneroAccountPublicAddress * addr, boost::optional<uint64_t> amount, boost::optional<bool> is_subaddr){
237
+    return hash_addr(addr->spend_public_key(), addr->view_public_key(), amount, is_subaddr);
238
+  }
239
+
240
+  std::string hash_addr(const std::string & spend_key, const std::string & view_key, boost::optional<uint64_t> amount, boost::optional<bool> is_subaddr){
241
+    ::crypto::public_key spend{}, view{};
242
+    if (spend_key.size() != 32 || view_key.size() != 32){
243
+      throw std::invalid_argument("Public keys have invalid sizes");
244
+    }
245
+
246
+    memcpy(spend.data, spend_key.data(), 32);
247
+    memcpy(view.data, view_key.data(), 32);
248
+    return hash_addr(&spend, &view, amount, is_subaddr);
249
+  }
250
+
251
+  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){
252
+    char buff[64+8+1];
253
+    size_t offset = 0;
254
+
255
+    memcpy(buff + offset, spend_key->data, 32); offset += 32;
256
+    memcpy(buff + offset, view_key->data, 32); offset += 32;
257
+
258
+    if (amount){
259
+      memcpy(buff + offset, (uint8_t*) &(amount.get()), sizeof(amount.get())); offset += sizeof(amount.get());
260
+    }
261
+
262
+    if (is_subaddr){
263
+      buff[offset] = is_subaddr.get();
264
+      offset += 1;
265
+    }
266
+
267
+    return std::string(buff, offset);
268
+  }
269
+
270
+  TData::TData() {
271
+    in_memory = false;
272
+    rsig_type = 0;
273
+    cur_input_idx = 0;
274
+    cur_output_idx = 0;
275
+    cur_batch_idx = 0;
276
+    cur_output_in_batch_idx = 0;
277
+  }
278
+
279
+  Signer::Signer(wallet_shim *wallet2, const unsigned_tx_set * unsigned_tx, size_t tx_idx, hw::tx_aux_data * aux_data) {
280
+    m_wallet2 = wallet2;
281
+    m_unsigned_tx = unsigned_tx;
282
+    m_aux_data = aux_data;
283
+    m_tx_idx = tx_idx;
284
+    m_ct.tx_data = cur_tx();
285
+    m_multisig = false;
286
+  }
287
+
288
+  void Signer::extract_payment_id(){
289
+    const std::vector<uint8_t>& tx_extra = cur_tx().extra;
290
+    m_ct.tsx_data.set_payment_id("");
291
+
292
+    std::vector<cryptonote::tx_extra_field> tx_extra_fields;
293
+    cryptonote::parse_tx_extra(tx_extra, tx_extra_fields); // ok if partially parsed
294
+    cryptonote::tx_extra_nonce extra_nonce;
295
+
296
+    ::crypto::hash payment_id{};
297
+    if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce))
298
+    {
299
+      ::crypto::hash8 payment_id8{};
300
+      if(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8))
301
+      {
302
+        m_ct.tsx_data.set_payment_id(std::string(payment_id8.data, 8));
303
+      }
304
+      else if (cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id))
305
+      {
306
+        m_ct.tsx_data.set_payment_id(std::string(payment_id.data, 32));
307
+      }
308
+    }
309
+  }
310
+
311
+  static unsigned get_rsig_type(bool use_bulletproof, size_t num_outputs){
312
+    if (!use_bulletproof){
313
+      return rct::RangeProofBorromean;
314
+    } else if (num_outputs > BULLETPROOF_MAX_OUTPUTS){
315
+      return rct::RangeProofMultiOutputBulletproof;
316
+    } else {
317
+      return rct::RangeProofPaddedBulletproof;
318
+    }
319
+  }
320
+
321
+  static void generate_rsig_batch_sizes(std::vector<uint64_t> &batches, unsigned rsig_type, size_t num_outputs){
322
+    size_t amount_batched = 0;
323
+
324
+    while(amount_batched < num_outputs){
325
+      if (rsig_type == rct::RangeProofBorromean || rsig_type == rct::RangeProofBulletproof) {
326
+        batches.push_back(1);
327
+        amount_batched += 1;
328
+
329
+      } else if (rsig_type == rct::RangeProofPaddedBulletproof){
330
+        if (num_outputs > BULLETPROOF_MAX_OUTPUTS){
331
+          throw std::invalid_argument("BP padded can support only BULLETPROOF_MAX_OUTPUTS statements");
332
+        }
333
+        batches.push_back(num_outputs);
334
+        amount_batched += num_outputs;
335
+
336
+      } else if (rsig_type == rct::RangeProofMultiOutputBulletproof){
337
+        size_t batch_size = 1;
338
+        while (batch_size * 2 + amount_batched <= num_outputs && batch_size * 2 <= BULLETPROOF_MAX_OUTPUTS){
339
+          batch_size *= 2;
340
+        }
341
+        batch_size = std::min(batch_size, num_outputs - amount_batched);
342
+        batches.push_back(batch_size);
343
+        amount_batched += batch_size;
344
+
345
+      } else {
346
+        throw std::invalid_argument("Unknown rsig type");
347
+      }
348
+    }
349
+  }
350
+
351
+  void Signer::compute_integrated_indices(TsxData * tsx_data){
352
+    if (m_aux_data == nullptr || m_aux_data->tx_recipients.empty()){
353
+      return;
354
+    }
355
+
356
+    auto & chg = tsx_data->change_dts();
357
+    std::string change_hash = hash_addr(&chg.addr(), chg.amount(), chg.is_subaddress());
358
+
359
+    std::vector<uint32_t> integrated_indices;
360
+    std::set<std::string> integrated_hashes;
361
+    for (auto & cur : m_aux_data->tx_recipients){
362
+      if (!cur.has_payment_id){
363
+        continue;
364
+      }
365
+      integrated_hashes.emplace(hash_addr(&cur.address.m_spend_public_key, &cur.address.m_view_public_key));
366
+    }
367
+
368
+    ssize_t idx = -1;
369
+    for (auto & cur : tsx_data->outputs()){
370
+      idx += 1;
371
+
372
+      std::string c_hash = hash_addr(&cur.addr(), cur.amount(), cur.is_subaddress());
373
+      if (c_hash == change_hash || cur.is_subaddress()){
374
+        continue;
375
+      }
376
+
377
+      c_hash = hash_addr(&cur.addr());
378
+      if (integrated_hashes.find(c_hash) != integrated_hashes.end()){
379
+        integrated_indices.push_back((uint32_t)idx);
380
+      }
381
+    }
382
+
383
+    if (!integrated_indices.empty()){
384
+      assign_to_repeatable(tsx_data->mutable_integrated_indices(), integrated_indices.begin(), integrated_indices.end());
385
+    }
386
+  }
387
+
388
+  std::shared_ptr<messages::monero::MoneroTransactionInitRequest> Signer::step_init(){
389
+    // extract payment ID from construction data
390
+    auto & tsx_data = m_ct.tsx_data;
391
+    auto & tx = cur_tx();
392
+
393
+    m_ct.tx.version = 2;
394
+    m_ct.tx.unlock_time = tx.unlock_time;
395
+
396
+    tsx_data.set_version(1);
397
+    tsx_data.set_unlock_time(tx.unlock_time);
398
+    tsx_data.set_num_inputs(static_cast<google::protobuf::uint32>(tx.sources.size()));
399
+    tsx_data.set_mixin(static_cast<google::protobuf::uint32>(tx.sources[0].outputs.size() - 1));
400
+    tsx_data.set_account(tx.subaddr_account);
401
+    assign_to_repeatable(tsx_data.mutable_minor_indices(), tx.subaddr_indices.begin(), tx.subaddr_indices.end());
402
+
403
+    // Rsig decision
404
+    auto rsig_data = tsx_data.mutable_rsig_data();
405
+    m_ct.rsig_type = get_rsig_type(tx.use_bulletproofs, tx.splitted_dsts.size());
406
+    rsig_data->set_rsig_type(m_ct.rsig_type);
407
+
408
+    generate_rsig_batch_sizes(m_ct.grouping_vct, m_ct.rsig_type, tx.splitted_dsts.size());
409
+    assign_to_repeatable(rsig_data->mutable_grouping(), m_ct.grouping_vct.begin(), m_ct.grouping_vct.end());
410
+
411
+    translate_dst_entry(tsx_data.mutable_change_dts(), &(tx.change_dts));
412
+    for(auto & cur : tx.splitted_dsts){
413
+      auto dst = tsx_data.mutable_outputs()->Add();
414
+      translate_dst_entry(dst, &cur);
415
+    }
416
+
417
+    compute_integrated_indices(&tsx_data);
418
+
419
+    int64_t fee = 0;
420
+    for(auto & cur_in : tx.sources){
421
+      fee += cur_in.amount;
422
+    }
423
+    for(auto & cur_out : tx.splitted_dsts){
424
+      fee -= cur_out.amount;
425
+    }
426
+    if (fee < 0){
427
+      throw std::invalid_argument("Fee cannot be negative");
428
+    }
429
+
430
+    tsx_data.set_fee(static_cast<google::protobuf::uint64>(fee));
431
+    this->extract_payment_id();
432
+
433
+    auto init_req = std::make_shared<messages::monero::MoneroTransactionInitRequest>();
434
+    init_req->set_version(0);
435
+    init_req->mutable_tsx_data()->CopyFrom(tsx_data);
436
+    return init_req;
437
+  }
438
+
439
+  void Signer::step_init_ack(std::shared_ptr<const messages::monero::MoneroTransactionInitAck> ack){
440
+    m_ct.in_memory = false;
441
+    if (ack->has_rsig_data()){
442
+      m_ct.rsig_param = std::make_shared<MoneroRsigData>(ack->rsig_data());
443
+    }
444
+
445
+    assign_from_repeatable(&(m_ct.tx_out_entr_hmacs), ack->hmacs().begin(), ack->hmacs().end());
446
+  }
447
+
448
+  std::shared_ptr<messages::monero::MoneroTransactionSetInputRequest> Signer::step_set_input(size_t idx){
449
+    CHECK_AND_ASSERT_THROW_MES(idx < cur_tx().sources.size(), "Invalid source index");
450
+    m_ct.cur_input_idx = idx;
451
+    auto res = std::make_shared<messages::monero::MoneroTransactionSetInputRequest>();
452
+    translate_src_entry(res->mutable_src_entr(), &(cur_tx().sources[idx]));
453
+    return res;
454
+  }
455
+
456
+  void Signer::step_set_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetInputAck> ack){
457
+    auto & vini_str = ack->vini();
458
+
459
+    cryptonote::txin_v vini;
460
+    if (!cn_deserialize(vini_str.data(), vini_str.size(), vini)){
461
+      throw exc::ProtocolException("Cannot deserialize vin[i]");
462
+    }
463
+
464
+    m_ct.tx.vin.emplace_back(vini);
465
+    m_ct.tx_in_hmacs.push_back(ack->vini_hmac());
466
+    m_ct.pseudo_outs.push_back(ack->pseudo_out());
467
+    m_ct.pseudo_outs_hmac.push_back(ack->pseudo_out_hmac());
468
+    m_ct.alphas.push_back(ack->pseudo_out_alpha());
469
+    m_ct.spend_encs.push_back(ack->spend_key());
470
+  }
471
+
472
+  void Signer::sort_ki(){
473
+    const size_t input_size = cur_tx().sources.size();
474
+
475
+    m_ct.source_permutation.clear();
476
+    for (size_t n = 0; n < input_size; ++n){
477
+      m_ct.source_permutation.push_back(n);
478
+    }
479
+
480
+    CHECK_AND_ASSERT_THROW_MES(m_ct.tx.vin.size() == input_size, "Invalid vector size");
481
+    std::sort(m_ct.source_permutation.begin(), m_ct.source_permutation.end(), [&](const size_t i0, const size_t i1) {
482
+      const cryptonote::txin_to_key &tk0 = boost::get<cryptonote::txin_to_key>(m_ct.tx.vin[i0]);
483
+      const cryptonote::txin_to_key &tk1 = boost::get<cryptonote::txin_to_key>(m_ct.tx.vin[i1]);
484
+      return memcmp(&tk0.k_image, &tk1.k_image, sizeof(tk0.k_image)) > 0;
485
+    });
486
+
487
+    CHECK_AND_ASSERT_THROW_MES(m_ct.tx_in_hmacs.size() == input_size, "Invalid vector size");
488
+    CHECK_AND_ASSERT_THROW_MES(m_ct.pseudo_outs.size() == input_size, "Invalid vector size");
489
+    CHECK_AND_ASSERT_THROW_MES(m_ct.pseudo_outs_hmac.size() == input_size, "Invalid vector size");
490
+    CHECK_AND_ASSERT_THROW_MES(m_ct.alphas.size() == input_size, "Invalid vector size");
491
+    CHECK_AND_ASSERT_THROW_MES(m_ct.spend_encs.size() == input_size, "Invalid vector size");
492
+    CHECK_AND_ASSERT_THROW_MES(m_ct.tx_data.sources.size() == input_size, "Invalid vector size");
493
+
494
+    tools::apply_permutation(m_ct.source_permutation, [&](size_t i0, size_t i1){
495
+      std::swap(m_ct.tx.vin[i0], m_ct.tx.vin[i1]);
496
+      std::swap(m_ct.tx_in_hmacs[i0], m_ct.tx_in_hmacs[i1]);
497
+      std::swap(m_ct.pseudo_outs[i0], m_ct.pseudo_outs[i1]);
498
+      std::swap(m_ct.pseudo_outs_hmac[i0], m_ct.pseudo_outs_hmac[i1]);
499
+      std::swap(m_ct.alphas[i0], m_ct.alphas[i1]);
500
+      std::swap(m_ct.spend_encs[i0], m_ct.spend_encs[i1]);
501
+      std::swap(m_ct.tx_data.sources[i0], m_ct.tx_data.sources[i1]);
502
+    });
503
+  }
504
+
505
+  std::shared_ptr<messages::monero::MoneroTransactionInputsPermutationRequest> Signer::step_permutation(){
506
+    sort_ki();
507
+
508
+    if (in_memory()){
509
+      return nullptr;
510
+    }
511
+
512
+    auto res = std::make_shared<messages::monero::MoneroTransactionInputsPermutationRequest>();
513
+    assign_to_repeatable(res->mutable_perm(), m_ct.source_permutation.begin(), m_ct.source_permutation.end());
514
+
515
+    return res;
516
+  }
517
+
518
+  void Signer::step_permutation_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputsPermutationAck> ack){
519
+    if (in_memory()){
520
+      return;
521
+    }
522
+  }
523
+
524
+  std::shared_ptr<messages::monero::MoneroTransactionInputViniRequest> Signer::step_set_vini_input(size_t idx){
525
+    if (in_memory()){
526
+      return nullptr;
527
+    }
528
+    CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_data.sources.size(), "Invalid transaction index");
529
+    CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx.vin.size(), "Invalid transaction index");
530
+    CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_in_hmacs.size(), "Invalid transaction index");
531
+
532
+    m_ct.cur_input_idx = idx;
533
+    auto tx = m_ct.tx_data;
534
+    auto res = std::make_shared<messages::monero::MoneroTransactionInputViniRequest>();
535
+    auto & vini = m_ct.tx.vin[idx];
536
+    translate_src_entry(res->mutable_src_entr(), &(tx.sources[idx]));
537
+    res->set_vini(cryptonote::t_serializable_object_to_blob(vini));
538
+    res->set_vini_hmac(m_ct.tx_in_hmacs[idx]);
539
+    if (!in_memory()) {
540
+      CHECK_AND_ASSERT_THROW_MES(idx < m_ct.pseudo_outs.size(), "Invalid transaction index");
541
+      CHECK_AND_ASSERT_THROW_MES(idx < m_ct.pseudo_outs_hmac.size(), "Invalid transaction index");
542
+      res->set_pseudo_out(m_ct.pseudo_outs[idx]);
543
+      res->set_pseudo_out_hmac(m_ct.pseudo_outs_hmac[idx]);
544
+    }
545
+
546
+    return res;
547
+  }
548
+
549
+  void Signer::step_set_vini_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputViniAck> ack){
550
+    if (in_memory()){
551
+      return;
552
+    }
553
+  }
554
+
555
+  std::shared_ptr<messages::monero::MoneroTransactionAllInputsSetRequest> Signer::step_all_inputs_set(){
556
+    return std::make_shared<messages::monero::MoneroTransactionAllInputsSetRequest>();
557
+  }
558
+
559
+  void Signer::step_all_inputs_set_ack(std::shared_ptr<const messages::monero::MoneroTransactionAllInputsSetAck> ack){
560
+    if (is_offloading()){
561
+      // If offloading, expect rsig configuration.
562
+      if (!ack->has_rsig_data()){
563
+        throw exc::ProtocolException("Rsig offloading requires rsig param");
564
+      }
565
+
566
+      auto & rsig_data = ack->rsig_data();
567
+      if (!rsig_data.has_mask()){
568
+        throw exc::ProtocolException("Gamma masks not present in offloaded version");
569
+      }
570
+
571
+      auto & mask = rsig_data.mask();
572
+      if (mask.size() != 32 * num_outputs()){
573
+        throw exc::ProtocolException("Invalid number of gamma masks");
574
+      }
575
+
576
+      m_ct.rsig_gamma.reserve(num_outputs());
577
+      for(size_t c=0; c < num_outputs(); ++c){
578
+        rct::key cmask{};
579
+        memcpy(cmask.bytes, mask.data() + c * 32, 32);
580
+        m_ct.rsig_gamma.emplace_back(cmask);
581
+      }
582
+    }
583
+  }
584
+
585
+  std::shared_ptr<messages::monero::MoneroTransactionSetOutputRequest> Signer::step_set_output(size_t idx){
586
+    CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_data.splitted_dsts.size(), "Invalid transaction index");
587
+    CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_out_entr_hmacs.size(), "Invalid transaction index");
588
+
589
+    m_ct.cur_output_idx = idx;
590
+    m_ct.cur_output_in_batch_idx += 1;   // assumes sequential call to step_set_output()
591
+
592
+    auto res = std::make_shared<messages::monero::MoneroTransactionSetOutputRequest>();