diff options
| -rwxr-xr-x | build_single.sh | 2 | ||||
| -rw-r--r-- | contrib/depends/Makefile | 4 | ||||
| -rwxr-xr-x | contrib/depends/gen_toolchain.cmake.sh | 4 | ||||
| -rw-r--r-- | impls/monero.dart/lib/monero.dart | 20 | ||||
| -rw-r--r-- | impls/monero.dart/lib/src/generated_bindings_monero.g.dart | 18 | ||||
| -rw-r--r-- | impls/monero.dart/lib/src/monero.dart | 5 | ||||
| -rw-r--r-- | impls/monero.dart/lib/src/wallet2.dart | 1 | ||||
| -rw-r--r-- | impls/monero.dart/lib/src/wownero.dart | 3 | ||||
| -rw-r--r-- | monero_libwallet2_api_c/src/main/cpp/monero_wallet2_api_c.cpp | 9 | ||||
| -rw-r--r-- | monero_libwallet2_api_c/src/main/cpp/monero_wallet2_api_c.h | 1 | ||||
| -rw-r--r-- | patches/monero/0021-trezor-import-export-sign-functions.patch | 686 |
11 files changed, 748 insertions, 5 deletions
diff --git a/build_single.sh b/build_single.sh index ebc30e3..ec2d760 100755 --- a/build_single.sh +++ b/build_single.sh @@ -78,7 +78,7 @@ do EXTRA_CMAKE_FLAGS="-DCAKEWALLET=ON" fi pushd build/${HOST_ABI}_${OUTPUT_MODE} - cmake -DCMAKE_TOOLCHAIN_FILE=$PWD/../../../contrib/depends/${HOST_ABI}/share/toolchain.cmake $EXTRA_CMAKE_FLAGS -DUSE_DEVICE_TREZOR=OFF -DMONERO_FLAVOR=$repo -DCMAKE_BUILD_TYPE=Debug -DHOST_ABI=${HOST_ABI} -DOUTPUT_MODE=${OUTPUT_MODE} ../.. + cmake -DCMAKE_TOOLCHAIN_FILE=$PWD/../../../contrib/depends/${HOST_ABI}/share/toolchain.cmake $EXTRA_CMAKE_FLAGS -DMONERO_FLAVOR=$repo -DCMAKE_BUILD_TYPE=Debug -DHOST_ABI=${HOST_ABI} -DOUTPUT_MODE=${OUTPUT_MODE} ../.. make $NPROC popd popd diff --git a/contrib/depends/Makefile b/contrib/depends/Makefile index 92e9036..1a7ad47 100644 --- a/contrib/depends/Makefile +++ b/contrib/depends/Makefile @@ -3,8 +3,8 @@ HOST ?= BOOST_VERSION ?= 1_90_0 -PACKAGES = native/cmake,native/python@3.14,native/git,native/_,native/make,native/cmake-toolchain,icu4c,boost@$(BOOST_VERSION),zeromq,unbound,sodium,openssl,libiconv,zlib -SIMPLYBS_HASH = 0ea74cb120b06156ce16118f86dc5b7d23a93c46 +PACKAGES = native/protobuf,native/cmake,native/python@3.14,native/git,native/_,native/make,native/cmake-toolchain,icu4c,boost@$(BOOST_VERSION),zeromq,unbound,sodium,openssl,libiconv,zlib,protobuf +SIMPLYBS_HASH = 8621f898aada59d15095a63e6b6746c69c2c5b76 all: simplybs $(if $(HOST),sbs-build-$(HOST),) @if [ -z "$(HOST)" ]; then \ diff --git a/contrib/depends/gen_toolchain.cmake.sh b/contrib/depends/gen_toolchain.cmake.sh index 9ed2621..5d48edd 100755 --- a/contrib/depends/gen_toolchain.cmake.sh +++ b/contrib/depends/gen_toolchain.cmake.sh @@ -155,7 +155,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") SET(LLVM_ENABLE_PIC OFF) SET(LLVM_ENABLE_PIE OFF) elseif(CMAKE_SYSTEM_NAME STREQUAL "Android") - add_definitions(-DUSE_DEVICE_TREZOR=OFF) + add_definitions(-DUSE_DEVICE_TREZOR=ON) SET(ANDROID TRUE) if(ARCHITECTURE STREQUAL "armv7a") SET(CMAKE_ANDROID_ARCH_ABI "armeabi-v7a") @@ -181,7 +181,7 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Android") SET(CMAKE_C_COMPILER @CC@) SET(CMAKE_CXX_COMPILER @CXX@) elseif(CMAKE_SYSTEM_NAME STREQUAL "iOS") - set(USE_DEVICE_TREZOR OFF) + set(USE_DEVICE_TREZOR ON) add_definitions(-DUSE_DEVICE_LEDGER=ON) SET(CMAKE_C_COMPILER @CC@) SET(CMAKE_CXX_COMPILER @CXX@) diff --git a/impls/monero.dart/lib/monero.dart b/impls/monero.dart/lib/monero.dart index a7f026e..9057f18 100644 --- a/impls/monero.dart/lib/monero.dart +++ b/impls/monero.dart/lib/monero.dart @@ -569,6 +569,26 @@ String UnsignedTransaction_signUR( } } +@Deprecated("TODO") +String UnsignedTransaction_commitTrezor( + PendingTransaction ptr, int tx_index) { + debugStart?.call('MONERO_UnsignedTransaction_commitTrezor'); + lib ??= MoneroC(DynamicLibrary.open(libPath)); + final txid = lib!.MONERO_UnsignedTransaction_commitTrezor(ptr, tx_index); + debugEnd?.call('MONERO_UnsignedTransaction_commitTrezor'); + try { + final strPtr = txid.cast<Utf8>(); + final str = strPtr.toDartString(); + MONERO_free(strPtr.cast()); + debugEnd?.call('MONERO_UnsignedTransaction_commitTrezor'); + return str; + } catch (e) { + errorHandler?.call('MONERO_UnsignedTransaction_commitTrezor', e); + debugEnd?.call('MONERO_UnsignedTransaction_commitTrezor'); + return ""; + } +} + // TransactionInfo typedef TransactionInfo = Pointer<Void>; diff --git a/impls/monero.dart/lib/src/generated_bindings_monero.g.dart b/impls/monero.dart/lib/src/generated_bindings_monero.g.dart index 0cd6bb5..adf04da 100644 --- a/impls/monero.dart/lib/src/generated_bindings_monero.g.dart +++ b/impls/monero.dart/lib/src/generated_bindings_monero.g.dart @@ -484,6 +484,24 @@ class MoneroC { _MONERO_UnsignedTransaction_signURPtr.asFunction< ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Void>, int)>(); + ffi.Pointer<ffi.Char> MONERO_UnsignedTransaction_commitTrezor( + ffi.Pointer<ffi.Void> unsignedTx_ptr, + int tx_index, + ) { + return _MONERO_UnsignedTransaction_commitTrezor( + unsignedTx_ptr, + tx_index, + ); + } + + late final _MONERO_UnsignedTransaction_commitTrezorPtr = _lookup< + ffi.NativeFunction< + ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Void>, + ffi.Int)>>('MONERO_UnsignedTransaction_commitTrezor'); + late final _MONERO_UnsignedTransaction_commitTrezor = + _MONERO_UnsignedTransaction_commitTrezorPtr.asFunction< + ffi.Pointer<ffi.Char> Function(ffi.Pointer<ffi.Void>, int)>(); + int MONERO_TransactionInfo_direction( ffi.Pointer<ffi.Void> txInfo_ptr, ) { diff --git a/impls/monero.dart/lib/src/monero.dart b/impls/monero.dart/lib/src/monero.dart index abecee0..717d59f 100644 --- a/impls/monero.dart/lib/src/monero.dart +++ b/impls/monero.dart/lib/src/monero.dart @@ -807,6 +807,11 @@ class MoneroUnsignedTransaction implements Wallet2UnsignedTransaction { String signUR(int max_fragment_length) { return monero.UnsignedTransaction_signUR(unsignedTransactionPtr, max_fragment_length); } + + @override + String commitTrezor(int tx_index) { + return monero.UnsignedTransaction_commitTrezor(unsignedTransactionPtr, tx_index); + } @override int status() { diff --git a/impls/monero.dart/lib/src/wallet2.dart b/impls/monero.dart/lib/src/wallet2.dart index 8c41ffa..655862d 100644 --- a/impls/monero.dart/lib/src/wallet2.dart +++ b/impls/monero.dart/lib/src/wallet2.dart @@ -232,6 +232,7 @@ abstract class Wallet2UnsignedTransaction { int txCount(); bool sign(String signedFileName); String signUR(int max_fragment_length); + String commitTrezor(int tx_index); } abstract class Wallet2Wallet { diff --git a/impls/monero.dart/lib/src/wownero.dart b/impls/monero.dart/lib/src/wownero.dart index c1ef9eb..ebf576b 100644 --- a/impls/monero.dart/lib/src/wownero.dart +++ b/impls/monero.dart/lib/src/wownero.dart @@ -819,6 +819,9 @@ class WowneroUnsignedTransaction implements Wallet2UnsignedTransaction { @override int ffiAddress() => unsignedTransactionPtr.address; + + @override + String commitTrezor(int tx_index) => throw UnimplementedError(); } class WowneroWallet implements Wallet2Wallet { diff --git a/monero_libwallet2_api_c/src/main/cpp/monero_wallet2_api_c.cpp b/monero_libwallet2_api_c/src/main/cpp/monero_wallet2_api_c.cpp index db56643..a800ad5 100644 --- a/monero_libwallet2_api_c/src/main/cpp/monero_wallet2_api_c.cpp +++ b/monero_libwallet2_api_c/src/main/cpp/monero_wallet2_api_c.cpp @@ -101,6 +101,7 @@ const char* MONERO_PendingTransaction_commitUR(void* pendingTx_ptr, int max_frag return strdup(str.c_str()); DEBUG_END() } + uint64_t MONERO_PendingTransaction_amount(void* pendingTx_ptr) { DEBUG_START() Monero::PendingTransaction *pendingTx = reinterpret_cast<Monero::PendingTransaction*>(pendingTx_ptr); @@ -264,6 +265,14 @@ const char* MONERO_UnsignedTransaction_signUR(void* unsignedTx_ptr, int max_frag return strdup(str.c_str()); DEBUG_END() } + +const char* MONERO_UnsignedTransaction_commitTrezor(void* unsignedTx_ptr, int tx_index) { + DEBUG_START() + Monero::UnsignedTransaction *unsignedTx = reinterpret_cast<Monero::UnsignedTransaction*>(unsignedTx_ptr); + std::string str = unsignedTx->commitTrezor(tx_index); + return strdup(str.c_str()); + DEBUG_END() +} // TransactionInfo const int MONERO_TransactionInfoDirection_In = 0; diff --git a/monero_libwallet2_api_c/src/main/cpp/monero_wallet2_api_c.h b/monero_libwallet2_api_c/src/main/cpp/monero_wallet2_api_c.h index f3cfdee..fdaf228 100644 --- a/monero_libwallet2_api_c/src/main/cpp/monero_wallet2_api_c.h +++ b/monero_libwallet2_api_c/src/main/cpp/monero_wallet2_api_c.h @@ -144,6 +144,7 @@ extern ADDAPI uint64_t MONERO_UnsignedTransaction_txCount(void* unsignedTx_ptr); // virtual bool sign(const std::string &signedFileName) = 0; extern ADDAPI bool MONERO_UnsignedTransaction_sign(void* unsignedTx_ptr, const char* signedFileName); extern ADDAPI const char* MONERO_UnsignedTransaction_signUR(void* unsignedTx_ptr, int max_fragment_length); +extern ADDAPI const char* MONERO_UnsignedTransaction_commitTrezor(void* unsignedTx_ptr, int tx_index); // }; // struct TransactionInfo // { diff --git a/patches/monero/0021-trezor-import-export-sign-functions.patch b/patches/monero/0021-trezor-import-export-sign-functions.patch new file mode 100644 index 0000000..6129263 --- /dev/null +++ b/patches/monero/0021-trezor-import-export-sign-functions.patch @@ -0,0 +1,686 @@ +From 8a56c766201269bd06319b30bd7400fdb4610a27 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 + +--- + 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 ++- + 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(-) + +diff --git a/src/device_trezor/trezor/protocol.cpp b/src/device_trezor/trezor/protocol.cpp +index 0e59a16ba..4f146e3f1 100644 +--- a/src/device_trezor/trezor/protocol.cpp ++++ b/src/device_trezor/trezor/protocol.cpp +@@ -29,12 +29,17 @@ + + #include "version.h" + #include "protocol.hpp" ++#include "string_tools.h" + #include <unordered_map> + #include <set> ++#include <sstream> + #include <utility> + #include <boost/endian/conversion.hpp> + #include <common/apply_permutation.h> + #include <common/json_util.h> ++#include <rapidjson/document.h> ++#include <rapidjson/stringbuffer.h> ++#include <rapidjson/writer.h> + #include <crypto/hmac-keccak.h> + #include <ringct/rctSigs.h> + #include <ringct/bulletproofs.h> +@@ -450,6 +455,10 @@ namespace tx { + } + } + ++ void Signer::export_source_entry(MoneroTransactionSourceEntry *dst, size_t idx, bool need_ring_keys, bool need_ring_indices){ ++ set_tx_input(dst, idx, need_ring_keys, need_ring_indices); ++ } ++ + 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 { + memwipe(plaintext.get(), keys_len); + } + ++ namespace { ++ ++ std::string bin_to_hex_lower(const std::string &bin) ++ { ++ return epee::string_tools::buff_to_hex_nodelimer(bin); ++ } ++ ++ rapidjson::Value dest_entry_to_json(const messages::monero::MoneroTransactionDestinationEntry &e, rapidjson::Document::AllocatorType &a) ++ { ++ rapidjson::Value o(rapidjson::kObjectType); ++ if (e.has_amount()) ++ o.AddMember("amount", e.amount(), a); ++ if (e.has_addr()) ++ { ++ rapidjson::Value addr(rapidjson::kObjectType); ++ const auto &ad = e.addr(); ++ if (ad.has_spend_public_key()) ++ { ++ const std::string h = bin_to_hex_lower(ad.spend_public_key()); ++ addr.AddMember("spend_public_key", rapidjson::Value(h.c_str(), static_cast<rapidjson::SizeType>(h.size()), a), a); ++ } ++ if (ad.has_view_public_key()) ++ { ++ const std::string h = bin_to_hex_lower(ad.view_public_key()); ++ addr.AddMember("view_public_key", rapidjson::Value(h.c_str(), static_cast<rapidjson::SizeType>(h.size()), a), a); ++ } ++ o.AddMember("addr", addr, a); ++ } ++ if (e.has_is_subaddress()) ++ o.AddMember("is_subaddress", e.is_subaddress(), a); ++ if (e.has_original()) ++ o.AddMember("original", rapidjson::Value(e.original().c_str(), static_cast<rapidjson::SizeType>(e.original().size()), a), a); ++ if (e.has_is_integrated()) ++ o.AddMember("is_integrated", e.is_integrated(), a); ++ return o; ++ } ++ ++ rapidjson::Value source_entry_to_json(const messages::monero::MoneroTransactionSourceEntry &e, rapidjson::Document::AllocatorType &a) ++ { ++ rapidjson::Value o(rapidjson::kObjectType); ++ rapidjson::Value ring(rapidjson::kArrayType); ++ for (int i = 0; i < e.outputs_size(); ++i) ++ { ++ const auto &out = e.outputs(i); ++ rapidjson::Value ring_m(rapidjson::kObjectType); ++ if (out.has_idx()) ++ ring_m.AddMember("idx", out.idx(), a); ++ if (out.has_key()) ++ { ++ rapidjson::Value key(rapidjson::kObjectType); ++ const auto &k = out.key(); ++ if (k.has_dest()) ++ { ++ const std::string h = bin_to_hex_lower(k.dest()); ++ key.AddMember("dest", rapidjson::Value(h.c_str(), static_cast<rapidjson::SizeType>(h.size()), a), a); ++ } ++ if (k.has_commitment()) ++ { ++ const std::string h = bin_to_hex_lower(k.commitment()); ++ key.AddMember("commitment", rapidjson::Value(h.c_str(), static_cast<rapidjson::SizeType>(h.size()), a), a); ++ } ++ ring_m.AddMember("key", key, a); ++ } ++ ring.PushBack(ring_m, a); ++ } ++ o.AddMember("outputs", ring, a); ++ if (e.has_real_output()) ++ o.AddMember("real_output", e.real_output(), a); ++ if (e.has_real_out_tx_key()) ++ { ++ const std::string h = bin_to_hex_lower(e.real_out_tx_key()); ++ o.AddMember("real_out_tx_key", rapidjson::Value(h.c_str(), static_cast<rapidjson::SizeType>(h.size()), a), a); ++ } ++ rapidjson::Value add_keys(rapidjson::kArrayType); ++ for (int i = 0; i < e.real_out_additional_tx_keys_size(); ++i) ++ { ++ const std::string h = bin_to_hex_lower(e.real_out_additional_tx_keys(i)); ++ add_keys.PushBack(rapidjson::Value(h.c_str(), static_cast<rapidjson::SizeType>(h.size()), a), a); ++ } ++ o.AddMember("real_out_additional_tx_keys", add_keys, a); ++ if (e.has_real_output_in_tx_index()) ++ o.AddMember("real_output_in_tx_index", e.real_output_in_tx_index(), a); ++ if (e.has_amount()) ++ o.AddMember("amount", e.amount(), a); ++ if (e.has_rct()) ++ o.AddMember("rct", e.rct(), a); ++ if (e.has_mask()) ++ { ++ const std::string h = bin_to_hex_lower(e.mask()); ++ o.AddMember("mask", rapidjson::Value(h.c_str(), static_cast<rapidjson::SizeType>(h.size()), a), a); ++ } ++ 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; ++ } ++ ++ std::string monero_default_bip44_path(uint32_t subaddr_account) ++ { ++ std::ostringstream oss; ++ oss << "m/44'/128'/" << subaddr_account << "'"; ++ return oss.str(); ++ } ++ ++ } // namespace ++ ++ ++ std::string trezor_connect_monero_sign_transaction_to_json( ++ wallet_shim *wallet, ++ const unsigned_tx_set *utx, ++ size_t tx_idx, ++ hw::tx_aux_data *aux_data, ++ cryptonote::network_type network_type) ++ { ++ CHECK_AND_ASSERT_THROW_MES(utx && aux_data, "null argument"); ++ 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"); ++ ++ Signer signer(wallet, utx, tx_idx, aux_data); ++ auto init_req = signer.step_init(); ++ const auto &tsx = init_req->tsx_data(); ++ ++ rapidjson::Document doc; ++ doc.SetObject(); ++ auto &alloc = doc.GetAllocator(); ++ ++ const std::string path_str = monero_default_bip44_path(utx->txes[tx_idx].subaddr_account); ++ doc.AddMember("path", rapidjson::Value(path_str.c_str(), static_cast<rapidjson::SizeType>(path_str.size()), alloc), alloc); ++ doc.AddMember("networkType", static_cast<uint32_t>(network_type), alloc); ++ ++ rapidjson::Value tsx_data(rapidjson::kObjectType); ++ tsx_data.AddMember("version", tsx.version(), alloc); ++ if (tsx.has_payment_id() && !tsx.payment_id().empty()) ++ { ++ const std::string h = bin_to_hex_lower(tsx.payment_id()); ++ tsx_data.AddMember("payment_id", rapidjson::Value(h.c_str(), static_cast<rapidjson::SizeType>(h.size()), alloc), alloc); ++ } ++ tsx_data.AddMember("unlock_time", static_cast<uint64_t>(tsx.unlock_time()), alloc); ++ ++ rapidjson::Value outputs(rapidjson::kArrayType); ++ for (int i = 0; i < tsx.outputs_size(); ++i) ++ outputs.PushBack(dest_entry_to_json(tsx.outputs(i), alloc), alloc); ++ tsx_data.AddMember("outputs", outputs, alloc); ++ ++ if (tsx.has_change_dts()) ++ tsx_data.AddMember("change_dts", dest_entry_to_json(tsx.change_dts(), alloc), alloc); ++ ++ if (tsx.has_num_inputs()) ++ tsx_data.AddMember("num_inputs", tsx.num_inputs(), alloc); ++ if (tsx.has_mixin()) ++ tsx_data.AddMember("mixin", tsx.mixin(), alloc); ++ if (tsx.has_fee()) ++ tsx_data.AddMember("fee", tsx.fee(), alloc); ++ if (tsx.has_account()) ++ tsx_data.AddMember("account", tsx.account(), alloc); ++ ++ if (tsx.minor_indices_size() > 0) ++ { ++ rapidjson::Value minors(rapidjson::kArrayType); ++ for (int i = 0; i < tsx.minor_indices_size(); ++i) ++ minors.PushBack(tsx.minor_indices(i), alloc); ++ tsx_data.AddMember("minor_indices", minors, alloc); ++ } ++ ++ if (tsx.has_rsig_data()) ++ { ++ const auto &rd = tsx.rsig_data(); ++ rapidjson::Value rj(rapidjson::kObjectType); ++ if (rd.has_rsig_type()) ++ rj.AddMember("rsig_type", rd.rsig_type(), alloc); ++ if (rd.has_bp_version()) ++ rj.AddMember("bp_version", rd.bp_version(), alloc); ++ rapidjson::Value grp(rapidjson::kArrayType); ++ for (int i = 0; i < rd.grouping_size(); ++i) ++ grp.PushBack(static_cast<uint64_t>(rd.grouping(i)), alloc); ++ rj.AddMember("grouping", grp, alloc); ++ tsx_data.AddMember("rsig_data", rj, alloc); ++ } ++ ++ if (tsx.integrated_indices_size() > 0) ++ { ++ rapidjson::Value ii(rapidjson::kArrayType); ++ for (int i = 0; i < tsx.integrated_indices_size(); ++i) ++ ii.PushBack(tsx.integrated_indices(i), alloc); ++ tsx_data.AddMember("integrated_indices", ii, alloc); ++ } ++ ++ if (tsx.has_client_version()) ++ tsx_data.AddMember("client_version", tsx.client_version(), alloc); ++ if (tsx.has_hard_fork()) ++ tsx_data.AddMember("hard_fork", tsx.hard_fork(), alloc); ++ if (tsx.has_monero_version()) ++ tsx_data.AddMember("monero_version", rapidjson::Value(tsx.monero_version().c_str(), static_cast<rapidjson::SizeType>(tsx.monero_version().size()), alloc), alloc); ++ ++ doc.AddMember("tsx_data", tsx_data, alloc); ++ ++ rapidjson::Value inputs(rapidjson::kArrayType); ++ const size_t n_in = utx->txes[tx_idx].sources.size(); ++ for (size_t i = 0; i < n_in; ++i) ++ { ++ messages::monero::MoneroTransactionSourceEntry src_pb; ++ signer.export_source_entry(&src_pb, i, true, true); ++ inputs.PushBack(source_entry_to_json(src_pb, alloc), alloc); ++ } ++ doc.AddMember("inputs", inputs, alloc); ++ ++ rapidjson::StringBuffer buffer; ++ rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); ++ doc.Accept(writer); ++ return std::string(buffer.GetString(), buffer.GetSize()); ++ } ++ + } + } + } +diff --git a/src/device_trezor/trezor/protocol.hpp b/src/device_trezor/trezor/protocol.hpp +index 7ffadd9aa..7899e60d0 100644 +--- a/src/device_trezor/trezor/protocol.hpp ++++ b/src/device_trezor/trezor/protocol.hpp +@@ -340,8 +340,16 @@ namespace tx { + const TData & tdata() const { + return m_ct; + } ++ void export_source_entry(MoneroTransactionSourceEntry *dst, size_t idx, bool need_ring_keys, bool need_ring_indices); + }; + ++ std::string trezor_connect_monero_sign_transaction_to_json( ++ wallet_shim *wallet, ++ const unsigned_tx_set *utx, ++ size_t tx_idx, ++ hw::tx_aux_data *aux_data, ++ cryptonote::network_type network_type); ++ + // 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> + + #include "bc-ur/src/bc-ur.hpp" + ++#if defined(DEVICE_TREZOR_READY) && DEVICE_TREZOR_READY ++#include "device/device_cold.hpp" ++#include "device_trezor/trezor/protocol.hpp" ++#endif ++ + using namespace std; + + namespace Monero { +@@ -138,6 +145,59 @@ std::string UnsignedTransactionImpl::signUR(int max_fragment_length) + return ""; + } + ++std::string UnsignedTransactionImpl::commitTrezor(uint64_t tx_index) ++{ ++#if !defined(DEVICE_TREZOR_READY) || !DEVICE_TREZOR_READY ++ (void)tx_index; ++ m_errorString = tr("This build was compiled without Trezor support"); ++ m_status = Status_Error; ++ return ""; ++#else ++ if (tx_index >= m_unsigned_tx_set.txes.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(); ++ 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 ++ : (w->use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1)); ++ aux_data.bp_version = bpv; ++ aux_data.hard_fork = w->get_current_hard_fork(); ++ aux_data.client_version = static_cast<unsigned>(bpv >= 4 ? 4u : 3u); ++ ++ hw::wallet_shim shim; ++ shim.get_tx_pub_key_from_received_outs = std::bind(&tools::wallet2::get_tx_pub_key_from_received_outs, w, std::placeholders::_1); ++ ++ const std::string json = hw::trezor::protocol::tx::trezor_connect_monero_sign_transaction_to_json( ++ &shim, ++ &m_unsigned_tx_set, ++ static_cast<size_t>(tx_index), ++ &aux_data, ++ w->nettype()); ++ m_errorString.clear(); ++ m_status = Status_Ok; ++ return json; ++ } ++ catch (const std::exception &e) ++ { ++ m_errorString = e.what(); ++ m_status = Status_Error; ++ return ""; ++ } ++#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) + { +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; ++ std::string commitTrezor(uint64_t tx_index = 0) override; + std::string confirmationMessage() const override {return m_confirmationMessage;} + uint64_t minMixinCount() const override; + +diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp +index c24b4a97d..cc85398ca 100644 +--- a/src/wallet/api/wallet.cpp ++++ b/src/wallet/api/wallet.cpp +@@ -1545,7 +1545,7 @@ bool WalletImpl::importKeyImages(const string &filename) + return false; + } + +- return true; ++ return true; + } + + +@@ -3479,4 +3479,33 @@ std::string WalletImpl::serializeCacheToJson() const + return std::string(m_wallet->serialize_cache_to_json()); + } + ++std::string WalletImpl::exportTrezorTdis() const ++{ ++ return m_wallet->export_trezor_tdis(); ++} ++ ++bool WalletImpl::importTrezorEncryptedKeyImagesJson(const string &json) ++{ ++ if (checkBackgroundSync("cannot import key images")) ++ return false; ++ if (!trustedDaemon()) { ++ setStatusError(tr("Key images can only be imported with a trusted daemon")); ++ return false; ++ } ++ try ++ { ++ uint64_t spent = 0, unspent = 0; ++ uint64_t height = m_wallet->import_trezor_encrypted_key_images_json(json, spent, unspent); ++ LOG_PRINT_L2("Trezor encrypted key images imported to height " << height << ", " ++ << print_money(spent) << " spent, " << print_money(unspent) << " unspent"); ++ } ++ catch (const std::exception &e) ++ { ++ LOG_ERROR("Error importing Trezor encrypted key images: " << e.what()); ++ setStatusError(string(tr("Failed to import key images: ")) + e.what()); ++ return false; ++ } ++ return true; ++} ++ + } // namespace +diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h +index 98c03b9c1..5248badb8 100644 +--- a/src/wallet/api/wallet.h ++++ b/src/wallet/api/wallet.h +@@ -337,6 +337,8 @@ private: + bool getWaitsForDeviceReceive(); + + virtual std::string serializeCacheToJson() const override; ++ virtual std::string exportTrezorTdis() const override; ++ bool importTrezorEncryptedKeyImagesJson(const std::string &json) override; + }; + + +diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h +index 3d11929f9..faea404dc 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 + + //! serialize wallet cache to JSON + virtual std::string serializeCacheToJson() const = 0; ++ ++ ++ /*! ++ * \brief exportTrezorTdis — export transfer details for Trezor cold key-image sync as JSON ++ * ++ * Returns an object `{ "tdis": [ ... ] }` with hex pubkeys and indices per output. ++ */ ++ virtual std::string exportTrezorTdis() const = 0; ++ ++ /*! ++ * \brief importTrezorEncryptedKeyImagesJson — import key images from Trezor-style encrypted JSON ++ */ ++ virtual bool importTrezorEncryptedKeyImagesJson(const std::string &json) = 0; + }; + + /** +diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp +index a7532d7ec..7f931c4c9 100644 +--- a/src/wallet/wallet2.cpp ++++ b/src/wallet/wallet2.cpp +@@ -98,6 +98,7 @@ extern "C" + { + #include "crypto/keccak.h" + #include "crypto/crypto-ops.h" ++#include <sodium/crypto_aead_chacha20poly1305.h> + } + 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 + return std::make_pair(size, weight); + } + //---------------------------------------------------------------------------------------------------- ++// Returns JSON: { "tdis": [ { "out_key", "tx_pub_key", optional "additional_tx_pub_keys", indices }, ... ] } ++// Hex fields are 64-char lower-case pubkeys; additional_tx_pub_keys matches protocol::ki::key_image_data ++// (subset of txn additional pubkeys for this transfer — currently 0 or 1 entry). ++std::string wallet2::export_trezor_tdis() const ++{ ++ rapidjson::Document doc; ++ doc.SetObject(); ++ auto &alloc = doc.GetAllocator(); ++ ++ rapidjson::Value tdis(rapidjson::kArrayType); ++ tdis.Reserve(static_cast<rapidjson::SizeType>(m_transfers.size()), alloc); ++ ++ for (const auto &td : m_transfers) ++ { ++ const crypto::public_key out_key = td.get_public_key(); ++ const crypto::public_key tx_pub_key = get_tx_pub_key_from_received_outs(td); ++ const std::vector<crypto::public_key> additional_tx_pub_keys = cryptonote::get_additional_tx_pub_keys_from_extra(td.m_tx); ++ ++ std::vector<crypto::public_key> additional_for_tdi; ++ if (!additional_tx_pub_keys.empty() && additional_tx_pub_keys.size() > td.m_internal_output_index) ++ additional_for_tdi.push_back(additional_tx_pub_keys[td.m_internal_output_index]); ++ ++ rapidjson::Value obj(rapidjson::kObjectType); ++ const std::string out_hex = epee::string_tools::pod_to_hex(out_key); ++ const std::string tx_pub_hex = epee::string_tools::pod_to_hex(tx_pub_key); ++ obj.AddMember("out_key", rapidjson::Value(out_hex.c_str(), static_cast<rapidjson::SizeType>(out_hex.size()), alloc), alloc); ++ obj.AddMember("tx_pub_key", rapidjson::Value(tx_pub_hex.c_str(), static_cast<rapidjson::SizeType>(tx_pub_hex.size()), alloc), alloc); ++ if (!additional_for_tdi.empty()) ++ { ++ rapidjson::Value aux(rapidjson::kArrayType); ++ aux.Reserve(static_cast<rapidjson::SizeType>(additional_for_tdi.size()), alloc); ++ for (const auto &apk : additional_for_tdi) ++ { ++ const std::string ah = epee::string_tools::pod_to_hex(apk); ++ aux.PushBack(rapidjson::Value(ah.c_str(), static_cast<rapidjson::SizeType>(ah.size()), alloc), alloc); ++ } ++ obj.AddMember("additional_tx_pub_keys", aux, alloc); ++ } ++ obj.AddMember("internal_output_index", rapidjson::Value(static_cast<uint64_t>(td.m_internal_output_index)), alloc); ++ obj.AddMember("sub_addr_major", rapidjson::Value(td.m_subaddr_index.major), alloc); ++ obj.AddMember("sub_addr_minor", rapidjson::Value(td.m_subaddr_index.minor), alloc); ++ tdis.PushBack(obj, alloc); ++ } ++ ++ doc.AddMember("tdis", tdis, alloc); ++ ++ rapidjson::StringBuffer buffer; ++ rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); ++ doc.Accept(writer); ++ return std::string(buffer.GetString(), buffer.GetSize()); ++} ++//---------------------------------------------------------------------------------------------------- ++namespace ++{ ++ static void decrypt_trezor_exported_ki_blob(const std::string &cipher, const std::string &iv, const std::string &key32, crypto::key_image &ki, crypto::signature &sig) ++ { ++ THROW_WALLET_EXCEPTION_IF(iv.size() != crypto_aead_chacha20poly1305_ietf_NPUBBYTES, ++ error::wallet_internal_error, "Trezor KI JSON: IV must be 12 bytes"); ++ THROW_WALLET_EXCEPTION_IF(key32.size() != 32, ++ error::wallet_internal_error, "Trezor KI JSON: encryption key must be 32 bytes"); ++ THROW_WALLET_EXCEPTION_IF(cipher.size() < crypto_aead_chacha20poly1305_ietf_ABYTES, ++ error::wallet_internal_error, "Trezor KI JSON: key_image ciphertext too short"); ++ char buf[96]; ++ unsigned long long out_len = 0; ++ const int r = crypto_aead_chacha20poly1305_ietf_decrypt( ++ reinterpret_cast<unsigned char *>(buf), &out_len, nullptr, ++ reinterpret_cast<const unsigned char *>(cipher.data()), cipher.size(), ++ nullptr, 0, ++ reinterpret_cast<const unsigned char *>(iv.data()), ++ reinterpret_cast<const unsigned char *>(key32.data())); ++ THROW_WALLET_EXCEPTION_IF(r != 0, ++ error::wallet_internal_error, "Trezor KI JSON: decryption failed (wrong key or corrupt ciphertext)"); ++ THROW_WALLET_EXCEPTION_IF(out_len != 96, ++ error::wallet_internal_error, "Trezor KI JSON: unexpected plaintext length"); ++ memcpy(ki.data, buf, 32); ++ memcpy(sig.c.data, buf + 32, 32); ++ memcpy(sig.r.data, buf + 64, 32); ++ memwipe(buf, sizeof(buf)); ++ } ++} ++ ++//---------------------------------------------------------------------------------------------------- ++uint64_t wallet2::import_trezor_encrypted_key_images_json(const std::string &json, uint64_t &spent, uint64_t &unspent, bool check_spent) ++{ ++ rapidjson::Document doc; ++ doc.Parse(json.c_str()); ++ THROW_WALLET_EXCEPTION_IF(doc.HasParseError(), error::wallet_internal_error, ++ std::string("Trezor KI JSON: parse error at offset ") + std::to_string(doc.GetErrorOffset())); ++ ++ const rapidjson::Value *proot = &doc; ++ if (doc.IsObject() && doc.HasMember("payload") && doc["payload"].IsObject()) ++ proot = &doc["payload"]; ++ ++ THROW_WALLET_EXCEPTION_IF(!proot->IsObject(), error::wallet_internal_error, "Trezor KI JSON: expected JSON object"); ++ const rapidjson::Value &root = *proot; ++ ++ THROW_WALLET_EXCEPTION_IF(!root.HasMember("key_images") || !root["key_images"].IsArray(), ++ error::wallet_internal_error, "Trezor KI JSON: missing or invalid key_images array"); ++ ++ std::string enc_key_bin; ++ if (root.HasMember("signature") && root["signature"].IsString()) ++ { ++ THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(root["signature"].GetString(), enc_key_bin), ++ error::wallet_internal_error, "Trezor KI JSON: invalid hex in signature"); ++ } ++ else if (root.HasMember("enc_key") && root["enc_key"].IsString()) ++ { ++ THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(root["enc_key"].GetString(), enc_key_bin), ++ error::wallet_internal_error, "Trezor KI JSON: invalid hex in enc_key"); ++ } ++ else ++ { ++ THROW_WALLET_EXCEPTION(error::wallet_internal_error, "Trezor KI JSON: need signature or enc_key (32-byte hex)"); ++ } ++ THROW_WALLET_EXCEPTION_IF(enc_key_bin.size() != 32, ++ error::wallet_internal_error, "Trezor KI JSON: encryption key must decode to 32 bytes"); ++ ++ const rapidjson::Value &arr = root["key_images"]; ++ std::vector<std::pair<crypto::key_image, crypto::signature>> ski; ++ ski.reserve(arr.Size()); ++ ++ for (rapidjson::SizeType i = 0; i < arr.Size(); ++i) ++ { ++ const rapidjson::Value &el = arr[i]; ++ THROW_WALLET_EXCEPTION_IF(!el.IsObject(), error::wallet_internal_error, "Trezor KI JSON: key_images entry must be object"); ++ THROW_WALLET_EXCEPTION_IF(!el.HasMember("iv") || !el["iv"].IsString(), error::wallet_internal_error, "Trezor KI JSON: missing iv"); ++ THROW_WALLET_EXCEPTION_IF(!el.HasMember("key_image") || !el["key_image"].IsString(), error::wallet_internal_error, "Trezor KI JSON: missing key_image"); ++ ++ std::string iv_bin, blob_bin; ++ THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(el["iv"].GetString(), iv_bin), ++ error::wallet_internal_error, "Trezor KI JSON: invalid hex in iv"); ++ THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(el["key_image"].GetString(), blob_bin), ++ error::wallet_internal_error, "Trezor KI JSON: invalid hex in key_image"); ++ ++ crypto::key_image ki{}; ++ crypto::signature sig{}; ++ decrypt_trezor_exported_ki_blob(blob_bin, iv_bin, enc_key_bin, ki, sig); ++ ski.emplace_back(std::move(ki), std::move(sig)); ++ } ++ ++ return import_key_images(ski, 0, spent, unspent, check_spent); ++} ++//---------------------------------------------------------------------------------------------------- + } +diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h +index 37a2447d2..e2016945b 100644 +--- a/src/wallet/wallet2.h ++++ b/src/wallet/wallet2.h +@@ -1717,7 +1717,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); +- ++ std::string export_trezor_tdis() const; ++ uint64_t import_trezor_encrypted_key_images_json(const std::string &json, uint64_t &spent, uint64_t &unspent, bool check_spent = true); + bool get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &hashing_blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie); + bool daemon_requires_payment(); + bool make_rpc_payment(uint32_t nonce, uint32_t cookie, uint64_t &credits, uint64_t &balance); +-- +2.50.1 (Apple Git-155) + |
