summaryrefslogtreecommitdiff
path: root/patches
diff options
context:
space:
mode:
Diffstat (limited to 'patches')
-rw-r--r--patches/monero/0021-trezor-import-export-sign-functions.patch629
1 files changed, 529 insertions, 100 deletions
diff --git a/patches/monero/0021-trezor-import-export-sign-functions.patch b/patches/monero/0021-trezor-import-export-sign-functions.patch
index 6129263..b9b76b5 100644
--- a/patches/monero/0021-trezor-import-export-sign-functions.patch
+++ b/patches/monero/0021-trezor-import-export-sign-functions.patch
@@ -1,29 +1,80 @@
-From 8a56c766201269bd06319b30bd7400fdb4610a27 Mon Sep 17 00:00:00 2001
+From fc7c14b26225627c74b23467bb442247ca35d010 Mon Sep 17 00:00:00 2001
From: Czarek Nakamoto <cyjan@mrcyjanek.net>
-Date: Fri, 8 May 2026 21:40:35 -0400
-Subject: [PATCH] trezor import/export/sign functions
+Date: Mon, 18 May 2026 08:48:12 -0400
+Subject: [PATCH] trezor import export sign broadcast functions
---
- src/device_trezor/trezor/protocol.cpp | 250 ++++++++++++++++++++++++
- src/device_trezor/trezor/protocol.hpp | 8 +
- src/wallet/api/unsigned_transaction.cpp | 60 ++++++
- src/wallet/api/unsigned_transaction.h | 1 +
- src/wallet/api/wallet.cpp | 31 ++-
+ cmake/CheckTrezor.cmake | 28 +-
+ src/device_trezor/trezor/protocol.cpp | 453 ++++++++++++++++++++++++
+ src/device_trezor/trezor/protocol.hpp | 27 ++
+ src/wallet/api/pending_transaction.cpp | 143 ++++++++
+ src/wallet/api/pending_transaction.h | 18 +
+ src/wallet/api/unsigned_transaction.cpp | 2 +
+ src/wallet/api/wallet.cpp | 31 +-
src/wallet/api/wallet.h | 2 +
- src/wallet/api/wallet2_api.h | 14 ++
- src/wallet/wallet2.cpp | 144 ++++++++++++++
- src/wallet/wallet2.h | 3 +-
- 9 files changed, 511 insertions(+), 2 deletions(-)
+ src/wallet/api/wallet2_api.h | 15 +
+ src/wallet/wallet2.cpp | 158 +++++++++
+ src/wallet/wallet2.h | 5 +-
+ 11 files changed, 853 insertions(+), 29 deletions(-)
+diff --git a/cmake/CheckTrezor.cmake b/cmake/CheckTrezor.cmake
+index 4fae15fad..57e3d4866 100644
+--- a/cmake/CheckTrezor.cmake
++++ b/cmake/CheckTrezor.cmake
+@@ -40,9 +40,6 @@ if (USE_DEVICE_TREZOR)
+ # Protobuf handling the cache variables set in docker.
+ if(NOT Protobuf_FOUND AND NOT Protobuf_LIBRARY AND NOT Protobuf_PROTOC_EXECUTABLE AND NOT Protobuf_INCLUDE_DIR)
+ message(STATUS "Could not find Protobuf")
+- elseif(NOT Protobuf_LIBRARY OR NOT EXISTS "${Protobuf_LIBRARY}")
+- message(STATUS "Protobuf library not found: ${Protobuf_LIBRARY}")
+- unset(Protobuf_FOUND)
+ elseif(NOT Protobuf_PROTOC_EXECUTABLE OR NOT EXISTS "${Protobuf_PROTOC_EXECUTABLE}")
+ message(STATUS "Protobuf executable not found: ${Protobuf_PROTOC_EXECUTABLE}")
+ unset(Protobuf_FOUND)
+@@ -93,31 +90,8 @@ if(Protobuf_FOUND AND USE_DEVICE_TREZOR)
+ endif()
+ endif()
+
+-# Protobuf compilation test
+-if(Protobuf_FOUND AND USE_DEVICE_TREZOR AND TREZOR_PYTHON)
+- execute_process(COMMAND ${Protobuf_PROTOC_EXECUTABLE} -I "${CMAKE_CURRENT_LIST_DIR}" -I "${Protobuf_INCLUDE_DIR}" "${CMAKE_CURRENT_LIST_DIR}/test-protobuf.proto" --cpp_out ${CMAKE_BINARY_DIR} RESULT_VARIABLE RET OUTPUT_VARIABLE OUT ERROR_VARIABLE ERR)
+- if(RET)
+- message(STATUS "Protobuf test generation failed: ${OUT} ${ERR}")
+- endif()
+-
+- try_compile(Protobuf_COMPILE_TEST_PASSED
+- "${CMAKE_BINARY_DIR}"
+- SOURCES
+- "${CMAKE_BINARY_DIR}/test-protobuf.pb.cc"
+- "${CMAKE_CURRENT_LIST_DIR}/test-protobuf.cpp"
+- CMAKE_FLAGS
+- "-DINCLUDE_DIRECTORIES=${Protobuf_INCLUDE_DIR};${CMAKE_BINARY_DIR}"
+- "-DCMAKE_CXX_STANDARD=11"
+- LINK_LIBRARIES ${Protobuf_LIBRARY}
+- OUTPUT_VARIABLE OUTPUT
+- )
+- if(NOT Protobuf_COMPILE_TEST_PASSED)
+- message(STATUS "Protobuf Compilation test failed: ${OUTPUT}.")
+- endif()
+-endif()
+-
+ # Try to build protobuf messages
+-if(Protobuf_FOUND AND USE_DEVICE_TREZOR AND TREZOR_PYTHON AND Protobuf_COMPILE_TEST_PASSED)
++if(Protobuf_FOUND AND USE_DEVICE_TREZOR AND TREZOR_PYTHON)
+ set(ENV{PROTOBUF_INCLUDE_DIRS} "${Protobuf_INCLUDE_DIR}")
+ set(ENV{PROTOBUF_PROTOC_EXECUTABLE} "${Protobuf_PROTOC_EXECUTABLE}")
+ set(TREZOR_PROTOBUF_PARAMS "")
diff --git a/src/device_trezor/trezor/protocol.cpp b/src/device_trezor/trezor/protocol.cpp
-index 0e59a16ba..4f146e3f1 100644
+index 0e59a16ba..acbc5ac76 100644
--- a/src/device_trezor/trezor/protocol.cpp
+++ b/src/device_trezor/trezor/protocol.cpp
-@@ -29,12 +29,17 @@
+@@ -29,12 +29,19 @@
#include "version.h"
#include "protocol.hpp"
+#include "string_tools.h"
++#include "cryptonote_basic/cryptonote_format_utils.h"
++#include <boost/lexical_cast.hpp>
#include <unordered_map>
#include <set>
+#include <sstream>
@@ -37,7 +88,7 @@ index 0e59a16ba..4f146e3f1 100644
#include <crypto/hmac-keccak.h>
#include <ringct/rctSigs.h>
#include <ringct/bulletproofs.h>
-@@ -450,6 +455,10 @@ namespace tx {
+@@ -450,6 +457,10 @@ namespace tx {
}
}
@@ -48,7 +99,7 @@ index 0e59a16ba..4f146e3f1 100644
void Signer::set_tx_input(MoneroTransactionSourceEntry * dst, size_t idx, bool need_ring_keys, bool need_ring_indices){
const cryptonote::tx_source_entry & src = cur_tx().sources[idx];
const tools::wallet2::transfer_details & transfer = get_source_transfer(idx);
-@@ -1096,6 +1105,247 @@ namespace tx {
+@@ -1096,6 +1107,448 @@ namespace tx {
memwipe(plaintext.get(), keys_len);
}
@@ -145,36 +196,6 @@ index 0e59a16ba..4f146e3f1 100644
+ }
+ if (e.has_subaddr_minor())
+ o.AddMember("subaddr_minor", e.subaddr_minor(), a);
-+ if (e.has_multisig_klrki())
-+ {
-+ const auto &ms = e.multisig_klrki();
-+ const bool any = (ms.has_k() && !ms.k().empty()) || (ms.has_l() && !ms.l().empty()) || (ms.has_r() && !ms.r().empty()) || (ms.has_ki() && !ms.ki().empty());
-+ if (any)
-+ {
-+ rapidjson::Value mso(rapidjson::kObjectType);
-+ if (ms.has_k())
-+ {
-+ const std::string h = bin_to_hex_lower(ms.k());
-+ mso.AddMember("K", rapidjson::Value(h.c_str(), static_cast<rapidjson::SizeType>(h.size()), a), a);
-+ }
-+ if (ms.has_l())
-+ {
-+ const std::string h = bin_to_hex_lower(ms.l());
-+ mso.AddMember("L", rapidjson::Value(h.c_str(), static_cast<rapidjson::SizeType>(h.size()), a), a);
-+ }
-+ if (ms.has_r())
-+ {
-+ const std::string h = bin_to_hex_lower(ms.r());
-+ mso.AddMember("R", rapidjson::Value(h.c_str(), static_cast<rapidjson::SizeType>(h.size()), a), a);
-+ }
-+ if (ms.has_ki())
-+ {
-+ const std::string h = bin_to_hex_lower(ms.ki());
-+ mso.AddMember("ki", rapidjson::Value(h.c_str(), static_cast<rapidjson::SizeType>(h.size()), a), a);
-+ }
-+ o.AddMember("multisig_klrki", mso, a);
-+ }
-+ }
+ return o;
+ }
+
@@ -293,20 +314,262 @@ index 0e59a16ba..4f146e3f1 100644
+ return std::string(buffer.GetString(), buffer.GetSize());
+ }
+
++ namespace {
++
++ std::string hex_to_bin(const std::string &hex)
++ {
++ std::string stripped = hex;
++ if (stripped.size() >= 2 && stripped[0] == '0' && (stripped[1] == 'x' || stripped[1] == 'X'))
++ stripped = stripped.substr(2);
++ std::string bin;
++ if (!epee::string_tools::parse_hexstr_to_binbuff(stripped, bin))
++ throw std::invalid_argument("Invalid hex string");
++ return bin;
++ }
++
++ const rapidjson::Value & parse_connect_response(const std::string &response_json, rapidjson::Document &doc)
++ {
++ if (doc.Parse(response_json.c_str()).HasParseError())
++ throw std::invalid_argument("Trezor Connect response: JSON parse error");
++ if (!doc.IsObject())
++ throw std::invalid_argument("Trezor Connect response: expected JSON object");
++
++ if (doc.HasMember("payload") && doc["payload"].IsObject()) {
++ if (doc.HasMember("success") && doc["success"].IsBool() && !doc["success"].GetBool())
++ throw std::invalid_argument("Trezor Connect response: success is false");
++ return doc["payload"];
++ }
++
++ return doc;
++ }
++
++ std::string get_hex_field(const rapidjson::Value &obj, const char *name, bool required = true)
++ {
++ if (!obj.HasMember(name) || !obj[name].IsString()) {
++ if (required)
++ throw std::invalid_argument(std::string("Trezor Connect response: missing field ") + name);
++ return {};
++ }
++ return hex_to_bin(std::string(obj[name].GetString(), obj[name].GetStringLength()));
++ }
++
++ const rapidjson::Value * get_array_field(const rapidjson::Value &obj, const char *name)
++ {
++ if (!obj.HasMember(name) || !obj[name].IsArray())
++ return nullptr;
++ return &obj[name];
++ }
++
++ void prepare_connect_signing_state(
++ Signer &signer,
++ const rapidjson::Value &payload,
++ const cryptonote::transaction &prefix_tx)
++ {
++ TData &ct = signer.tdata();
++ signer.step_init();
++ ct.tx = prefix_tx;
++
++ CHECK_AND_ASSERT_THROW_MES(
++ payload.HasMember("tx_prefix_hash") && payload["tx_prefix_hash"].IsString(),
++ "Trezor Connect response: missing tx_prefix_hash");
++
++ ::crypto::hash computed{};
++ cryptonote::get_transaction_prefix_hash(ct.tx, computed);
++ const std::string expected = hex_to_bin(
++ std::string(payload["tx_prefix_hash"].GetString(), payload["tx_prefix_hash"].GetStringLength()));
++ if (expected.size() != sizeof(computed) ||
++ crypto_verify_32(
++ reinterpret_cast<const unsigned char *>(computed.data),
++ reinterpret_cast<const unsigned char *>(expected.data()))) {
++ throw exc::proto::SecurityException("Transaction prefix hash does not match");
++ }
++
++ ct.tx_prefix_hash = expected;
++
++ ct.rv = std::make_shared<rct::rctSig>();
++ if (prefix_tx.version > 1) {
++ const auto &src_rct = prefix_tx.rct_signatures;
++ ct.rv->type = src_rct.type;
++ ct.rv->txnFee = src_rct.txnFee;
++ ct.rv->message = src_rct.message;
++ ct.rv->outPk = src_rct.outPk;
++ ct.rv->ecdhInfo = src_rct.ecdhInfo;
++ if (rct::is_rct_bulletproof_plus(src_rct.type))
++ ct.rv->p.bulletproofs_plus = src_rct.p.bulletproofs_plus;
++ else if (rct::is_rct_bulletproof(src_rct.type))
++ ct.rv->p.bulletproofs = src_rct.p.bulletproofs;
++ }
++
++ if (payload.HasMember("rv") && payload["rv"].IsObject()) {
++ const auto &rvj = payload["rv"];
++ if (rvj.HasMember("txn_fee") && rvj["txn_fee"].IsUint64())
++ ct.rv->txnFee = rvj["txn_fee"].GetUint64();
++ if (rvj.HasMember("rv_type") && rvj["rv_type"].IsUint())
++ ct.rv->type = static_cast<uint8_t>(rvj["rv_type"].GetUint());
++ if (rvj.HasMember("message") && rvj["message"].IsString())
++ string_to_key(ct.rv->message, hex_to_bin(std::string(rvj["message"].GetString(), rvj["message"].GetStringLength())));
++ }
++
++ if (payload.HasMember("extra") && payload["extra"].IsString()) {
++ const std::string extra_bin = hex_to_bin(
++ std::string(payload["extra"].GetString(), payload["extra"].GetStringLength()));
++ ct.tx.extra.assign(extra_bin.begin(), extra_bin.end());
++ }
++
++ const rapidjson::Value *pseudo_outs = get_array_field(payload, "pseudo_outs");
++ if (pseudo_outs) {
++ ct.pseudo_outs.clear();
++ ct.rv->p.pseudoOuts.clear();
++ for (rapidjson::SizeType i = 0; i < pseudo_outs->Size(); ++i) {
++ if (!(*pseudo_outs)[i].IsString())
++ throw std::invalid_argument("Trezor Connect response: invalid pseudo_out entry");
++ const std::string po = hex_to_bin(
++ std::string((*pseudo_outs)[i].GetString(), (*pseudo_outs)[i].GetStringLength()));
++ ct.pseudo_outs.push_back(po);
++ rct::key k{};
++ string_to_key(k, po);
++ ct.rv->p.pseudoOuts.push_back(k);
++ }
++ }
++
++ ct.rv->mixRing.resize(signer.num_inputs());
++ }
++
++ tools::wallet2::pending_tx build_pending_tx_from_tdata(const TData &cdata)
++ {
++ tools::wallet2::pending_tx cpend;
++ cpend.tx = cdata.tx;
++ cpend.dust = 0;
++ cpend.fee = cpend.tx.rct_signatures.txnFee;
++ cpend.dust_added_to_fee = false;
++ cpend.change_dts = cdata.tx_data.change_dts;
++ cpend.selected_transfers = cdata.tx_data.selected_transfers;
++ cpend.key_images = "";
++ cpend.dests = cdata.tx_data.dests;
++ cpend.construction_data = cdata.tx_data;
++
++ std::string key_images;
++ const bool all_are_txin_to_key = std::all_of(cdata.tx.vin.begin(), cdata.tx.vin.end(), [&](const cryptonote::txin_v &s_e) -> bool {
++ CHECKED_GET_SPECIFIC_VARIANT(s_e, const cryptonote::txin_to_key, in, false);
++ key_images += boost::lexical_cast<std::string>(in.k_image) + " ";
++ return true;
++ });
++ if (!all_are_txin_to_key)
++ throw std::invalid_argument("Not all are txin_to_key");
++ cpend.key_images = key_images;
++
++ return cpend;
++ }
++
++ void fill_key_images_from_signed_tx(
++ std::vector<::crypto::key_image> &key_images,
++ const TData &cdata,
++ const unsigned_tx_set &utx)
++ {
++ key_images.clear();
++ key_images.resize(std::get<2>(utx.transfers).size());
++ for (size_t cidx = 0; cidx < key_images.size(); ++cidx)
++ key_images[cidx] = std::get<2>(utx.transfers)[cidx].m_key_image;
++
++ const size_t num_sources = cdata.tx_data.sources.size();
++ CHECK_AND_ASSERT_THROW_MES(num_sources == cdata.tx.vin.size(), "Invalid tx.vin size");
++
++ for (size_t src_idx = 0; src_idx < num_sources; ++src_idx) {
++ CHECK_AND_ASSERT_THROW_MES(src_idx < cdata.tx_data.selected_transfers.size(), "Invalid source index");
++ size_t idx_map_src = cdata.tx_data.selected_transfers[src_idx];
++ CHECK_AND_ASSERT_THROW_MES(idx_map_src >= std::get<0>(utx.transfers), "Invalid offset");
++ idx_map_src -= std::get<0>(utx.transfers);
++ CHECK_AND_ASSERT_THROW_MES(idx_map_src < key_images.size(), "Invalid key image index");
++
++ const auto vini = boost::get<cryptonote::txin_to_key>(cdata.tx.vin[src_idx]);
++ key_images[idx_map_src] = vini.k_image;
++ }
++ }
++
++ } // namespace
++
++ trezor_connect_signed_tx trezor_connect_monero_apply_sign_response(
++ wallet_shim *wallet,
++ const unsigned_tx_set *utx,
++ size_t tx_idx,
++ hw::tx_aux_data *aux_data,
++ const std::string &response_json,
++ const cryptonote::transaction *prefix_tx)
++ {
++ CHECK_AND_ASSERT_THROW_MES(wallet && utx && aux_data, "null argument");
++ CHECK_AND_ASSERT_THROW_MES(prefix_tx, "Trezor Connect: call commitTrezor first (unsigned transaction missing)");
++ CHECK_AND_ASSERT_THROW_MES(std::get<0>(utx->transfers) == 0, "Unsupported non zero offset");
++ CHECK_AND_ASSERT_THROW_MES(tx_idx < utx->txes.size(), "Invalid transaction index");
++
++ rapidjson::Document doc;
++ const rapidjson::Value &payload = parse_connect_response(response_json, doc);
++
++ Signer signer(wallet, utx, tx_idx, aux_data);
++ prepare_connect_signing_state(signer, payload, *prefix_tx);
++
++ const size_t num_sources = signer.num_inputs();
++
++ const rapidjson::Value *signatures = get_array_field(payload, "signatures");
++ if (!signatures)
++ throw std::invalid_argument("Trezor Connect response: missing signatures array");
++ if (signatures->Size() != num_sources)
++ throw std::invalid_argument("Trezor Connect response: signatures count mismatch");
++
++ const rapidjson::Value *pseudo_outs = get_array_field(payload, "pseudo_outs");
++
++ for (rapidjson::SizeType i = 0; i < signatures->Size(); ++i) {
++ if (!(*signatures)[i].IsString())
++ throw std::invalid_argument("Trezor Connect response: invalid signature entry");
++ auto ack = std::make_shared<messages::monero::MoneroTransactionSignInputAck>();
++ ack->set_signature(hex_to_bin(
++ std::string((*signatures)[i].GetString(), (*signatures)[i].GetStringLength())));
++ if (pseudo_outs && i < pseudo_outs->Size() && (*pseudo_outs)[i].IsString())
++ ack->set_pseudo_out(hex_to_bin(
++ std::string((*pseudo_outs)[i].GetString(), (*pseudo_outs)[i].GetStringLength())));
++ signer.step_sign_input_ack(ack);
++ }
++
++ auto final_ack = std::make_shared<messages::monero::MoneroTransactionFinalAck>();
++ final_ack->set_salt(get_hex_field(payload, "salt", false));
++ final_ack->set_rand_mult(get_hex_field(payload, "rand_mult", false));
++ final_ack->set_tx_enc_keys(get_hex_field(payload, "tx_enc_keys", false));
++ final_ack->set_cout_key(get_hex_field(payload, "cout_key", false));
++ final_ack->set_opening_key(get_hex_field(payload, "opening_key", true));
++
++ signer.step_final_ack(final_ack);
++
++ trezor_connect_signed_tx result;
++ result.ptx = build_pending_tx_from_tdata(signer.tdata());
++ result.tx_device_aux = signer.store_tx_aux_info();
++ fill_key_images_from_signed_tx(result.key_images, signer.tdata(), *utx);
++ return result;
++ }
++
}
}
}
diff --git a/src/device_trezor/trezor/protocol.hpp b/src/device_trezor/trezor/protocol.hpp
-index 7ffadd9aa..7899e60d0 100644
+index 7ffadd9aa..987c95eb5 100644
--- a/src/device_trezor/trezor/protocol.hpp
+++ b/src/device_trezor/trezor/protocol.hpp
-@@ -340,8 +340,16 @@ namespace tx {
+@@ -340,8 +340,35 @@ namespace tx {
const TData & tdata() const {
return m_ct;
}
++
++ TData & tdata() {
++ return m_ct;
++ }
++
+ void export_source_entry(MoneroTransactionSourceEntry *dst, size_t idx, bool need_ring_keys, bool need_ring_indices);
};
++ struct trezor_connect_signed_tx {
++ tools::wallet2::pending_tx ptx;
++ std::string tx_device_aux;
++ std::vector<::crypto::key_image> key_images;
++ };
++
+ std::string trezor_connect_monero_sign_transaction_to_json(
+ wallet_shim *wallet,
+ const unsigned_tx_set *utx,
@@ -314,24 +577,22 @@ index 7ffadd9aa..7899e60d0 100644
+ hw::tx_aux_data *aux_data,
+ cryptonote::network_type network_type);
+
++ trezor_connect_signed_tx trezor_connect_monero_apply_sign_response(
++ wallet_shim *wallet,
++ const unsigned_tx_set *utx,
++ size_t tx_idx,
++ hw::tx_aux_data *aux_data,
++ const std::string &response_json,
++ const cryptonote::transaction *prefix_tx);
++
// TX Key decryption
void load_tx_key_data(hw::device_cold::tx_key_data_t & res, const std::string & data);
-diff --git a/src/wallet/api/unsigned_transaction.cpp b/src/wallet/api/unsigned_transaction.cpp
-index fd03e959d..d45723bb5 100644
---- a/src/wallet/api/unsigned_transaction.cpp
-+++ b/src/wallet/api/unsigned_transaction.cpp
-@@ -34,14 +34,21 @@
-
- #include "cryptonote_basic/cryptonote_format_utils.h"
- #include "cryptonote_basic/cryptonote_basic_impl.h"
-+#include "cryptonote_config.h"
-
- #include <memory>
- #include <vector>
- #include <sstream>
- #include <boost/format.hpp>
-+#include <functional>
+diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp
+index 1f714d229..548e24352 100644
+--- a/src/wallet/api/pending_transaction.cpp
++++ b/src/wallet/api/pending_transaction.cpp
+@@ -44,6 +44,11 @@
#include "bc-ur/src/bc-ur.hpp"
@@ -343,11 +604,11 @@ index fd03e959d..d45723bb5 100644
using namespace std;
namespace Monero {
-@@ -138,6 +145,59 @@ std::string UnsignedTransactionImpl::signUR(int max_fragment_length)
- return "";
+@@ -210,6 +215,144 @@ std::string PendingTransactionImpl::commitUR(int max_fragment_length) {
+ }
}
-+std::string UnsignedTransactionImpl::commitTrezor(uint64_t tx_index)
++std::string PendingTransactionImpl::commitTrezor(uint64_t tx_index)
+{
+#if !defined(DEVICE_TREZOR_READY) || !DEVICE_TREZOR_READY
+ (void)tx_index;
@@ -355,21 +616,23 @@ index fd03e959d..d45723bb5 100644
+ m_status = Status_Error;
+ return "";
+#else
-+ if (tx_index >= m_unsigned_tx_set.txes.size())
++ if (tx_index >= m_pending_tx.size())
+ {
+ m_errorString = tr("Invalid transaction index");
+ m_status = Status_Error;
+ return "";
+ }
-+ if (std::get<0>(m_unsigned_tx_set.transfers) != 0)
-+ {
-+ m_errorString = tr("Unsupported unsigned transaction transfer offset");
-+ m_status = Status_Error;
-+ return "";
-+ }
+ try
+ {
+ tools::wallet2 *w = m_wallet.m_wallet.get();
++ tools::wallet2::unsigned_tx_set utx;
++ w->construct_unsigned_tx_set_for_signing(m_pending_tx, utx);
++ if (std::get<0>(utx.transfers) != 0)
++ {
++ m_errorString = tr("Unsupported unsigned transaction transfer offset");
++ m_status = Status_Error;
++ return "";
++ }
+ hw::tx_aux_data aux_data;
+ const int bpv = w->use_fork_rules(HF_VERSION_BULLETPROOF_PLUS, -10) ? 4
+ : (w->use_fork_rules(HF_VERSION_CLSAG, -10) ? 3
@@ -383,10 +646,18 @@ index fd03e959d..d45723bb5 100644
+
+ const std::string json = hw::trezor::protocol::tx::trezor_connect_monero_sign_transaction_to_json(
+ &shim,
-+ &m_unsigned_tx_set,
++ &utx,
+ static_cast<size_t>(tx_index),
+ &aux_data,
+ w->nettype());
++
++ m_trezor_connect_session = std::make_unique<TrezorConnectSession>();
++ m_trezor_connect_session->utx = std::move(utx);
++ m_trezor_connect_session->aux = aux_data;
++ m_trezor_connect_session->shim = shim;
++ m_trezor_connect_session->prefix_tx = m_pending_tx[tx_index].tx;
++ m_trezor_connect_session->tx_idx = tx_index;
++
+ m_errorString.clear();
+ m_status = Status_Ok;
+ return json;
@@ -400,20 +671,147 @@ index fd03e959d..d45723bb5 100644
+#endif
+}
+
- //----------------------------------------------------------------------------------------------------
- bool UnsignedTransactionImpl::checkLoadedTx(const std::function<size_t()> get_num_txes, const std::function<const tools::wallet2::tx_construction_data&(size_t)> &get_tx, const std::string &extra_message)
++bool PendingTransactionImpl::commitTrezorNext(const std::string &response_json, uint64_t tx_index)
++{
++#if !defined(DEVICE_TREZOR_READY) || !DEVICE_TREZOR_READY
++ (void)response_json;
++ (void)tx_index;
++ m_errorString = tr("This build was compiled without Trezor support");
++ m_status = Status_Error;
++ return false;
++#else
++ if (tx_index >= m_pending_tx.size())
++ {
++ m_errorString = tr("Invalid transaction index");
++ m_status = Status_Error;
++ return false;
++ }
++ try
++ {
++ tools::wallet2 *w = m_wallet.m_wallet.get();
++ std::unique_ptr<TrezorConnectSession> session = std::move(m_trezor_connect_session);
++ std::unique_ptr<TrezorConnectSession> fallback_session;
++
++ if (!session || session->tx_idx != tx_index)
++ {
++ fallback_session = std::make_unique<TrezorConnectSession>();
++ session = std::move(fallback_session);
++ w->construct_unsigned_tx_set_for_signing(m_pending_tx, session->utx);
++ if (std::get<0>(session->utx.transfers) != 0)
++ {
++ m_errorString = tr("Unsupported unsigned transaction transfer offset");
++ m_status = Status_Error;
++ return false;
++ }
++ const int bpv = w->use_fork_rules(HF_VERSION_BULLETPROOF_PLUS, -10) ? 4
++ : (w->use_fork_rules(HF_VERSION_CLSAG, -10) ? 3
++ : (w->use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1));
++ session->aux.bp_version = bpv;
++ session->aux.hard_fork = w->get_current_hard_fork();
++ session->aux.client_version = static_cast<unsigned>(bpv >= 4 ? 4u : 3u);
++ session->shim.get_tx_pub_key_from_received_outs = std::bind(
++ &tools::wallet2::get_tx_pub_key_from_received_outs, w, std::placeholders::_1);
++ session->prefix_tx = m_pending_tx[tx_index].tx;
++ session->tx_idx = tx_index;
++ }
++
++ const auto signed_tx = hw::trezor::protocol::tx::trezor_connect_monero_apply_sign_response(
++ &session->shim,
++ &session->utx,
++ static_cast<size_t>(tx_index),
++ &session->aux,
++ response_json,
++ &session->prefix_tx);
++
++ m_pending_tx[tx_index] = signed_tx.ptx;
++ m_key_images = signed_tx.key_images;
++
++ if (!signed_tx.tx_device_aux.empty())
++ {
++ if (m_tx_device_aux.size() <= tx_index)
++ m_tx_device_aux.resize(tx_index + 1);
++ m_tx_device_aux[tx_index] = signed_tx.tx_device_aux;
++ }
++
++ m_errorString.clear();
++ m_status = Status_Ok;
++ return commit();
++ }
++ catch (const std::exception &e)
++ {
++ m_errorString = e.what();
++ m_status = Status_Error;
++ return false;
++ }
++#endif
++}
++
+
+ uint64_t PendingTransactionImpl::amount() const
{
-diff --git a/src/wallet/api/unsigned_transaction.h b/src/wallet/api/unsigned_transaction.h
-index a94b23f75..e2f250565 100644
---- a/src/wallet/api/unsigned_transaction.h
-+++ b/src/wallet/api/unsigned_transaction.h
-@@ -54,6 +54,7 @@ public:
- // sign txs and save to file
- bool sign(const std::string &signedFileName) override;
- std::string signUR(int max_fragment_length = 130) override;
+diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h
+index 0cc6c58e9..32a7a296f 100644
+--- a/src/wallet/api/pending_transaction.h
++++ b/src/wallet/api/pending_transaction.h
+@@ -31,9 +31,14 @@
+ #include "wallet/api/wallet2_api.h"
+ #include "wallet/wallet2.h"
+
++#include <memory>
+ #include <string>
+ #include <vector>
+
++#if defined(DEVICE_TREZOR_READY) && DEVICE_TREZOR_READY
++#include "device/device_cold.hpp"
++#endif
++
+
+ namespace Monero {
+
+@@ -47,6 +52,8 @@ public:
+ std::string errorString() const override;
+ bool commit(const std::string &filename = "", bool overwrite = false) override;
+ std::string commitUR(int max_fragment_length = 130) override;
+ std::string commitTrezor(uint64_t tx_index = 0) override;
- std::string confirmationMessage() const override {return m_confirmationMessage;}
- uint64_t minMixinCount() const override;
++ bool commitTrezorNext(const std::string &response_json, uint64_t tx_index = 0) override;
+ uint64_t amount() const override;
+ uint64_t dust() const override;
+ uint64_t fee() const override;
+@@ -72,6 +79,17 @@ private:
+ std::unordered_set<crypto::public_key> m_signers;
+ std::vector<std::string> m_tx_device_aux;
+ std::vector<crypto::key_image> m_key_images;
++
++#if defined(DEVICE_TREZOR_READY) && DEVICE_TREZOR_READY
++ struct TrezorConnectSession {
++ tools::wallet2::unsigned_tx_set utx;
++ hw::tx_aux_data aux;
++ hw::wallet_shim shim;
++ cryptonote::transaction prefix_tx;
++ size_t tx_idx{0};
++ };
++ std::unique_ptr<TrezorConnectSession> m_trezor_connect_session;
++#endif
+ };
+
+
+diff --git a/src/wallet/api/unsigned_transaction.cpp b/src/wallet/api/unsigned_transaction.cpp
+index fd03e959d..7232e518f 100644
+--- a/src/wallet/api/unsigned_transaction.cpp
++++ b/src/wallet/api/unsigned_transaction.cpp
+@@ -34,11 +34,13 @@
+
+ #include "cryptonote_basic/cryptonote_format_utils.h"
+ #include "cryptonote_basic/cryptonote_basic_impl.h"
++#include "cryptonote_config.h"
+
+ #include <memory>
+ #include <vector>
+ #include <sstream>
+ #include <boost/format.hpp>
++#include <functional>
+
+ #include "bc-ur/src/bc-ur.hpp"
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
index c24b4a97d..cc85398ca 100644
@@ -476,18 +874,19 @@ index 98c03b9c1..5248badb8 100644
diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h
-index 3d11929f9..faea404dc 100644
+index 3d11929f9..760fc8859 100644
--- a/src/wallet/api/wallet2_api.h
+++ b/src/wallet/api/wallet2_api.h
-@@ -165,6 +165,7 @@ struct UnsignedTransaction
- */
- virtual bool sign(const std::string &signedFileName) = 0;
- virtual std::string signUR(int max_fragment_length = 130) = 0;
-+ virtual std::string commitTrezor(uint64_t tx_index = 0) = 0;
- };
-
- /**
-@@ -1220,6 +1221,19 @@ struct Wallet
+@@ -92,6 +92,8 @@ struct PendingTransaction
+ // commit transaction or save to file if filename is provided.
+ virtual bool commit(const std::string &filename = "", bool overwrite = false) = 0;
+ virtual std::string commitUR(int max_fragment_length = 130) = 0;
++ virtual std::string commitTrezor(uint64_t tx_index = 0) = 0;
++ virtual bool commitTrezorNext(const std::string &response_json, uint64_t tx_index = 0) = 0;
+ virtual uint64_t amount() const = 0;
+ virtual uint64_t dust() const = 0;
+ virtual uint64_t fee() const = 0;
+@@ -1220,6 +1222,19 @@ struct Wallet
//! serialize wallet cache to JSON
virtual std::string serializeCacheToJson() const = 0;
@@ -508,7 +907,7 @@ index 3d11929f9..faea404dc 100644
/**
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
-index a7532d7ec..7f931c4c9 100644
+index a7532d7ec..bb0ada04d 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -98,6 +98,7 @@ extern "C"
@@ -519,7 +918,28 @@ index a7532d7ec..7f931c4c9 100644
}
using namespace std;
using namespace crypto;
-@@ -16384,4 +16385,147 @@ std::pair<size_t, uint64_t> wallet2::estimate_tx_size_and_weight(bool use_rct, i
+@@ -7838,6 +7839,20 @@ std::string wallet2::dump_tx_to_str(const std::vector<pending_tx> &ptx_vector) c
+ return std::string(UNSIGNED_TX_PREFIX) + ciphertext;
+ }
+ //----------------------------------------------------------------------------------------------------
++void wallet2::construct_unsigned_tx_set_for_signing(const std::vector<pending_tx>& ptx_vector, unsigned_tx_set &utx) const
++{
++ utx.txes.clear();
++ utx.txes.reserve(ptx_vector.size());
++ for (const auto &tx : ptx_vector)
++ utx.txes.push_back(get_construction_data_with_decrypted_short_payment_id(tx, m_account.get_device()));
++
++ utx.new_transfers = std::make_tuple(static_cast<uint64_t>(0), static_cast<uint64_t>(0), std::vector<exported_transfer_details>());
++
++ transfer_container transfers_copy;
++ get_transfers(transfers_copy);
++ utx.transfers = std::make_tuple(static_cast<uint64_t>(0), static_cast<uint64_t>(transfers_copy.size()), std::move(transfers_copy));
++}
++//----------------------------------------------------------------------------------------------------
+ bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx_set &exported_txs) const
+ {
+ std::string s;
+@@ -16384,4 +16399,147 @@ std::pair<size_t, uint64_t> wallet2::estimate_tx_size_and_weight(bool use_rct, i
return std::make_pair(size, weight);
}
//----------------------------------------------------------------------------------------------------
@@ -668,10 +1088,19 @@ index a7532d7ec..7f931c4c9 100644
+//----------------------------------------------------------------------------------------------------
}
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
-index 37a2447d2..e2016945b 100644
+index 37a2447d2..a1ca49e90 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
-@@ -1717,7 +1717,8 @@ private:
+@@ -1207,6 +1207,8 @@ private:
+ void commit_tx(std::vector<pending_tx>& ptx_vector);
+ bool save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename) const;
+ std::string dump_tx_to_str(const std::vector<pending_tx> &ptx_vector) const;
++ //! Populate \p utx from pending txs for cold/device signing (uses live transfer indices).
++ void construct_unsigned_tx_set_for_signing(const std::vector<pending_tx>& ptx_vector, unsigned_tx_set &utx) const;
+ std::string save_multisig_tx(multisig_tx_set txs);
+ bool save_multisig_tx(const multisig_tx_set &txs, const std::string &filename);
+ std::string save_multisig_tx(const std::vector<pending_tx>& ptx_vector);
+@@ -1717,7 +1719,8 @@ private:
bool is_unattended() const { return m_unattended; }
std::pair<size_t, uint64_t> estimate_tx_size_and_weight(bool use_rct, int n_inputs, int ring_size, int n_outputs, size_t extra_size);