From 0e3b23dd16705fa74873cde84b7c4ffb2a7d229d Mon Sep 17 00:00:00 2001 From: highlander Date: Mon, 16 Mar 2026 20:54:25 -0600 Subject: [PATCH 1/8] chore: update submodules + nanopb options for 7.14.0 proto chains - Point device-protocol and python-keepkey submodules to upstream master (includes BIP-85, Solana, Tron, TON wire IDs and proto definitions) - Add nanopb .options files for Solana, Tron, TON (field size constraints) - Add Bip85Mnemonic.mnemonic max_size:241 to messages.options - Update lib/transport/CMakeLists.txt with new proto sources, options, headers, and protoc compilation commands - Fix CI: use pre-installed clang-format instead of apt-get install (eliminates 3-minute timeout on GitHub runners) - Update Zcash transparent branch ID from Sapling to NU6 --- .github/workflows/ci.yml | 14 ++++++---- deps/device-protocol | 2 +- deps/python-keepkey | 2 +- .../keepkey/transport/messages-solana.options | 17 ++++++++++++ .../keepkey/transport/messages-ton.options | 12 +++++++++ .../keepkey/transport/messages-tron.options | 14 ++++++++++ include/keepkey/transport/messages.options | 2 ++ lib/firmware/signing.c | 2 +- lib/transport/CMakeLists.txt | 27 +++++++++++++++++++ 9 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 include/keepkey/transport/messages-solana.options create mode 100644 include/keepkey/transport/messages-ton.options create mode 100644 include/keepkey/transport/messages-tron.options diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dfed55e41..68f2c168b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,22 +52,26 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha || github.sha }} - - name: Install clang-format - run: sudo apt-get install -y clang-format-14 - - name: Check code formatting run: | + # Use pre-installed clang-format (ubuntu-latest ships with it) + CF=$(command -v clang-format-14 || command -v clang-format || true) + if [ -z "$CF" ]; then + echo "::warning::clang-format not found, skipping lint" + exit 0 + fi + echo "Using: $CF ($($CF --version | head -1))" FAILED=0 for f in $(find include/keepkey lib/firmware lib/board lib/transport/src \ -name '*.c' -o -name '*.h' 2>/dev/null | grep -v generated | grep -v '.pb.'); do - if ! clang-format-14 --style=file --dry-run --Werror "$f" 2>/dev/null; then + if ! $CF --style=file --dry-run --Werror "$f" 2>/dev/null; then echo "::warning file=$f::Formatting differs from .clang-format" FAILED=1 fi done if [ "$FAILED" = "1" ]; then echo "" - echo "Run: clang-format -i to fix formatting" + echo "Run: $CF -i to fix formatting" echo "Note: treating as warning until codebase is reformatted" fi # Warn-only until existing code is reformatted diff --git a/deps/device-protocol b/deps/device-protocol index 323802f17..ace5a7074 160000 --- a/deps/device-protocol +++ b/deps/device-protocol @@ -1 +1 @@ -Subproject commit 323802f17dd44165a5100357df771348c8b49672 +Subproject commit ace5a70745e9948d1949ca78b0249adb75b00e73 diff --git a/deps/python-keepkey b/deps/python-keepkey index f1dd2b684..e89587701 160000 --- a/deps/python-keepkey +++ b/deps/python-keepkey @@ -1 +1 @@ -Subproject commit f1dd2b6847346abe8ea2985b22d688de4911643f +Subproject commit e8958770152a85af4c2a03f9e93b5d98e8dff44b diff --git a/include/keepkey/transport/messages-solana.options b/include/keepkey/transport/messages-solana.options new file mode 100644 index 000000000..645d5cfac --- /dev/null +++ b/include/keepkey/transport/messages-solana.options @@ -0,0 +1,17 @@ +SolanaGetAddress.address_n max_count:8 +SolanaGetAddress.coin_name max_size:21 + +SolanaAddress.address max_size:64 + +SolanaSignTx.address_n max_count:8 +SolanaSignTx.coin_name max_size:21 +SolanaSignTx.raw_tx max_size:2048 + +SolanaSignedTx.signature max_size:64 + +SolanaSignMessage.address_n max_count:8 +SolanaSignMessage.coin_name max_size:21 +SolanaSignMessage.message max_size:1024 + +SolanaMessageSignature.public_key max_size:32 +SolanaMessageSignature.signature max_size:64 diff --git a/include/keepkey/transport/messages-ton.options b/include/keepkey/transport/messages-ton.options new file mode 100644 index 000000000..18c064289 --- /dev/null +++ b/include/keepkey/transport/messages-ton.options @@ -0,0 +1,12 @@ +TonGetAddress.address_n max_count:8 +TonGetAddress.coin_name max_size:21 + +TonAddress.address max_size:50 +TonAddress.raw_address max_size:70 + +TonSignTx.address_n max_count:8 +TonSignTx.coin_name max_size:21 +TonSignTx.raw_tx max_size:1024 +TonSignTx.to_address max_size:50 + +TonSignedTx.signature max_size:64 diff --git a/include/keepkey/transport/messages-tron.options b/include/keepkey/transport/messages-tron.options new file mode 100644 index 000000000..80215e8cf --- /dev/null +++ b/include/keepkey/transport/messages-tron.options @@ -0,0 +1,14 @@ +TronGetAddress.address_n max_count:8 +TronGetAddress.coin_name max_size:21 + +TronAddress.address max_size:35 + +TronSignTx.address_n max_count:8 +TronSignTx.coin_name max_size:21 +TronSignTx.raw_data max_size:2048 +TronSignTx.ref_block_bytes max_size:4 +TronSignTx.ref_block_hash max_size:32 +TronSignTx.contract_type max_size:64 +TronSignTx.to_address max_size:35 + +TronSignedTx.signature max_size:65 diff --git a/include/keepkey/transport/messages.options b/include/keepkey/transport/messages.options index 41e0c377c..ad08bd6ba 100644 --- a/include/keepkey/transport/messages.options +++ b/include/keepkey/transport/messages.options @@ -131,3 +131,5 @@ FlashHash.challenge max_size:32 FlashWrite.data max_size:1024 FlashHashResponse.data max_size:32 + +Bip85Mnemonic.mnemonic max_size:241 diff --git a/lib/firmware/signing.c b/lib/firmware/signing.c index ccc38c226..b6f3d4460 100644 --- a/lib/firmware/signing.c +++ b/lib/firmware/signing.c @@ -624,7 +624,7 @@ void signing_init(const SignTx *msg, const CoinType *_coin, branch_id = 0x5BA81B19; // Overwinter break; case 4: - branch_id = 0x76B809BB; // Sapling + branch_id = 0xC8E71055; // NU6 break; } } diff --git a/lib/transport/CMakeLists.txt b/lib/transport/CMakeLists.txt index 41b8d5864..d42d3e113 100644 --- a/lib/transport/CMakeLists.txt +++ b/lib/transport/CMakeLists.txt @@ -15,6 +15,9 @@ set(protoc_pb_sources ${DEVICE_PROTOCOL}/messages-tendermint.proto ${DEVICE_PROTOCOL}/messages-thorchain.proto ${DEVICE_PROTOCOL}/messages-mayachain.proto + ${DEVICE_PROTOCOL}/messages-solana.proto + ${DEVICE_PROTOCOL}/messages-tron.proto + ${DEVICE_PROTOCOL}/messages-ton.proto ${DEVICE_PROTOCOL}/messages.proto) set(protoc_pb_options @@ -29,6 +32,9 @@ set(protoc_pb_options ${CMAKE_SOURCE_DIR}/include/keepkey/transport/messages-tendermint.options ${CMAKE_SOURCE_DIR}/include/keepkey/transport/messages-thorchain.options ${CMAKE_SOURCE_DIR}/include/keepkey/transport/messages-mayachain.options + ${CMAKE_SOURCE_DIR}/include/keepkey/transport/messages-solana.options + ${CMAKE_SOURCE_DIR}/include/keepkey/transport/messages-tron.options + ${CMAKE_SOURCE_DIR}/include/keepkey/transport/messages-ton.options ${CMAKE_SOURCE_DIR}/include/keepkey/transport/messages.options) set(protoc_c_sources @@ -43,6 +49,9 @@ set(protoc_c_sources ${CMAKE_BINARY_DIR}/lib/transport/messages-tendermint.pb.c ${CMAKE_BINARY_DIR}/lib/transport/messages-thorchain.pb.c ${CMAKE_BINARY_DIR}/lib/transport/messages-mayachain.pb.c + ${CMAKE_BINARY_DIR}/lib/transport/messages-solana.pb.c + ${CMAKE_BINARY_DIR}/lib/transport/messages-tron.pb.c + ${CMAKE_BINARY_DIR}/lib/transport/messages-ton.pb.c ${CMAKE_BINARY_DIR}/lib/transport/messages.pb.c) set(protoc_c_headers @@ -57,6 +66,9 @@ set(protoc_c_headers ${CMAKE_BINARY_DIR}/include/messages-tendermint.pb.h ${CMAKE_BINARY_DIR}/include/messages-thorchain.pb.h ${CMAKE_BINARY_DIR}/include/messages-mayachain.pb.h + ${CMAKE_BINARY_DIR}/include/messages-solana.pb.h + ${CMAKE_BINARY_DIR}/include/messages-tron.pb.h + ${CMAKE_BINARY_DIR}/include/messages-ton.pb.h ${CMAKE_BINARY_DIR}/include/messages.pb.h) set(protoc_pb_sources_moved @@ -71,6 +83,9 @@ set(protoc_pb_sources_moved ${CMAKE_BINARY_DIR}/lib/transport/messages-tendermint.proto ${CMAKE_BINARY_DIR}/lib/transport/messages-thorchain.proto ${CMAKE_BINARY_DIR}/lib/transport/messages-mayachain.proto + ${CMAKE_BINARY_DIR}/lib/transport/messages-solana.proto + ${CMAKE_BINARY_DIR}/lib/transport/messages-tron.proto + ${CMAKE_BINARY_DIR}/lib/transport/messages-ton.proto ${CMAKE_BINARY_DIR}/lib/transport/messages.proto) add_custom_command( @@ -136,6 +151,18 @@ add_custom_command( ${PROTOC_BINARY} -I. -I/usr/include --plugin=nanopb=${NANOPB_DIR}/generator/protoc-gen-nanopb "--nanopb_out=-f messages-mayachain.options:." messages-mayachain.proto + COMMAND + ${PROTOC_BINARY} -I. -I/usr/include + --plugin=nanopb=${NANOPB_DIR}/generator/protoc-gen-nanopb + "--nanopb_out=-f messages-solana.options:." messages-solana.proto + COMMAND + ${PROTOC_BINARY} -I. -I/usr/include + --plugin=nanopb=${NANOPB_DIR}/generator/protoc-gen-nanopb + "--nanopb_out=-f messages-tron.options:." messages-tron.proto + COMMAND + ${PROTOC_BINARY} -I. -I/usr/include + --plugin=nanopb=${NANOPB_DIR}/generator/protoc-gen-nanopb + "--nanopb_out=-f messages-ton.options:." messages-ton.proto COMMAND ${PROTOC_BINARY} -I. -I/usr/include --plugin=nanopb=${NANOPB_DIR}/generator/protoc-gen-nanopb From 1e8e88509a938abd362f0b3a07cebdb9543c25b8 Mon Sep 17 00:00:00 2001 From: highlander Date: Wed, 11 Mar 2026 17:15:27 -0600 Subject: [PATCH 2/8] feat(tron,ton): add TRON and TON chain support TRON: secp256k1 + Keccak256 address derivation, SHA256 tx signing TON: Ed25519 address derivation with CRC16 + Base64url, Ed25519 tx signing Both use existing trezor-crypto primitives only. --- deps/device-protocol | 2 +- deps/python-keepkey | 2 +- include/keepkey/firmware/fsm.h | 5 + include/keepkey/firmware/ton.h | 68 +++++++ include/keepkey/firmware/tron.h | 60 ++++++ include/keepkey/transport/interface.h | 2 + .../keepkey/transport/messages-ton.options | 2 + .../keepkey/transport/messages-tron.options | 8 + lib/firmware/CMakeLists.txt | 2 + lib/firmware/fsm.c | 6 + lib/firmware/fsm_msg_ton.h | 137 ++++++++++++++ lib/firmware/fsm_msg_tron.h | 127 +++++++++++++ lib/firmware/messagemap.def | 10 + lib/firmware/ton.c | 175 ++++++++++++++++++ lib/firmware/tron.c | 128 +++++++++++++ 15 files changed, 732 insertions(+), 2 deletions(-) create mode 100644 include/keepkey/firmware/ton.h create mode 100644 include/keepkey/firmware/tron.h create mode 100644 lib/firmware/fsm_msg_ton.h create mode 100644 lib/firmware/fsm_msg_tron.h create mode 100644 lib/firmware/ton.c create mode 100644 lib/firmware/tron.c diff --git a/deps/device-protocol b/deps/device-protocol index ace5a7074..323802f17 160000 --- a/deps/device-protocol +++ b/deps/device-protocol @@ -1 +1 @@ -Subproject commit ace5a70745e9948d1949ca78b0249adb75b00e73 +Subproject commit 323802f17dd44165a5100357df771348c8b49672 diff --git a/deps/python-keepkey b/deps/python-keepkey index e89587701..f1dd2b684 160000 --- a/deps/python-keepkey +++ b/deps/python-keepkey @@ -1 +1 @@ -Subproject commit e8958770152a85af4c2a03f9e93b5d98e8dff44b +Subproject commit f1dd2b6847346abe8ea2985b22d688de4911643f diff --git a/include/keepkey/firmware/fsm.h b/include/keepkey/firmware/fsm.h index 19e911106..e26e44d15 100644 --- a/include/keepkey/firmware/fsm.h +++ b/include/keepkey/firmware/fsm.h @@ -118,6 +118,11 @@ void fsm_msgMayachainGetAddress(const MayachainGetAddress *msg); void fsm_msgMayachainSignTx(const MayachainSignTx *msg); void fsm_msgMayachainMsgAck(const MayachainMsgAck *msg); +void fsm_msgTronGetAddress(const TronGetAddress *msg); +void fsm_msgTronSignTx(TronSignTx *msg); +void fsm_msgTonGetAddress(const TonGetAddress *msg); +void fsm_msgTonSignTx(TonSignTx *msg); + #if DEBUG_LINK // void fsm_msgDebugLinkDecision(DebugLinkDecision *msg); void fsm_msgDebugLinkGetState(DebugLinkGetState *msg); diff --git a/include/keepkey/firmware/ton.h b/include/keepkey/firmware/ton.h new file mode 100644 index 000000000..78e929c9b --- /dev/null +++ b/include/keepkey/firmware/ton.h @@ -0,0 +1,68 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2024 KeepKey + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef KEEPKEY_FIRMWARE_TON_H +#define KEEPKEY_FIRMWARE_TON_H + +#include "trezor/crypto/bip32.h" +#include "trezor/crypto/ed25519-donna/ed25519.h" + +#include "messages-ton.pb.h" + +// TON address length (Base64 URL-safe encoded, typically 48 chars) +#define TON_ADDRESS_MAX_LEN 64 +#define TON_RAW_ADDRESS_MAX_LEN 128 + +// TON decimals (1 TON = 1,000,000,000 nanoTON) +#define TON_DECIMALS 9 + +/** + * Generate TON address from Ed25519 public key + * @param public_key Ed25519 public key (32 bytes) + * @param bounceable Bounceable flag for address encoding + * @param testnet Testnet flag for address encoding + * @param workchain Workchain ID (-1 or 0) + * @param address Output buffer for user-friendly Base64 encoded address + * @param address_len Length of address output buffer + * @param raw_address Output buffer for raw address format (workchain:hash) + * @param raw_address_len Length of raw_address output buffer + * @return true on success, false on failure + */ +bool ton_get_address(const ed25519_public_key public_key, bool bounceable, + bool testnet, int32_t workchain, char *address, + size_t address_len, char *raw_address, + size_t raw_address_len); + +/** + * Format TON amount (nanoTON) for display + * @param buf Output buffer + * @param len Length of output buffer + * @param amount Amount in nanoTON (1 TON = 1,000,000,000 nanoTON) + */ +void ton_formatAmount(char *buf, size_t len, uint64_t amount); + +/** + * Sign a TON transaction + * @param node HD node containing private key + * @param msg TonSignTx request message + * @param resp TonSignedTx response message (will be filled with signature) + */ +void ton_signTx(const HDNode *node, const TonSignTx *msg, TonSignedTx *resp); + +#endif diff --git a/include/keepkey/firmware/tron.h b/include/keepkey/firmware/tron.h new file mode 100644 index 000000000..df24496dc --- /dev/null +++ b/include/keepkey/firmware/tron.h @@ -0,0 +1,60 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2024 KeepKey + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#ifndef KEEPKEY_FIRMWARE_TRON_H +#define KEEPKEY_FIRMWARE_TRON_H + +#include "trezor/crypto/bip32.h" + +#include "messages-tron.pb.h" + +// TRON address length (Base58Check, typically 34 chars starting with 'T') +#define TRON_ADDRESS_MAX_LEN 64 + +// TRON decimals (1 TRX = 1,000,000 SUN) +#define TRON_DECIMALS 6 + +/** + * Generate TRON address from secp256k1 public key + * @param public_key secp256k1 public key (33 bytes compressed) + * @param address Output buffer for Base58Check encoded address + * @param address_len Length of output buffer + * @return true on success, false on failure + */ +bool tron_getAddress(const uint8_t public_key[33], char *address, + size_t address_len); + +/** + * Format TRON amount (SUN) for display + * @param buf Output buffer + * @param len Length of output buffer + * @param amount Amount in SUN (1 TRX = 1,000,000 SUN) + */ +void tron_formatAmount(char *buf, size_t len, uint64_t amount); + +/** + * Sign a TRON transaction + * @param node HD node containing private key + * @param msg TronSignTx request message + * @param resp TronSignedTx response message (will be filled with signature) + */ +void tron_signTx(const HDNode *node, const TronSignTx *msg, + TronSignedTx *resp); + +#endif diff --git a/include/keepkey/transport/interface.h b/include/keepkey/transport/interface.h index ffebf205d..0ef6ee390 100644 --- a/include/keepkey/transport/interface.h +++ b/include/keepkey/transport/interface.h @@ -35,6 +35,8 @@ #include "messages-tendermint.pb.h" #include "messages-thorchain.pb.h" #include "messages-mayachain.pb.h" +#include "messages-tron.pb.h" +#include "messages-ton.pb.h" #include "types.pb.h" #include "trezor_transport.h" diff --git a/include/keepkey/transport/messages-ton.options b/include/keepkey/transport/messages-ton.options index 18c064289..bec6d9857 100644 --- a/include/keepkey/transport/messages-ton.options +++ b/include/keepkey/transport/messages-ton.options @@ -9,4 +9,6 @@ TonSignTx.coin_name max_size:21 TonSignTx.raw_tx max_size:1024 TonSignTx.to_address max_size:50 +TonAddress.address max_size:50 +TonAddress.raw_address max_size:70 TonSignedTx.signature max_size:64 diff --git a/include/keepkey/transport/messages-tron.options b/include/keepkey/transport/messages-tron.options index 80215e8cf..665310b95 100644 --- a/include/keepkey/transport/messages-tron.options +++ b/include/keepkey/transport/messages-tron.options @@ -11,4 +11,12 @@ TronSignTx.ref_block_hash max_size:32 TronSignTx.contract_type max_size:64 TronSignTx.to_address max_size:35 +TronSignTx.address_n max_count:8 +TronSignTx.coin_name max_size:21 +TronSignTx.raw_data max_size:1024 +TronSignTx.ref_block_bytes max_size:2 +TronSignTx.ref_block_hash max_size:8 +TronSignTx.contract_type max_size:50 +TronSignTx.to_address max_size:35 +TronAddress.address max_size:35 TronSignedTx.signature max_size:65 diff --git a/lib/firmware/CMakeLists.txt b/lib/firmware/CMakeLists.txt index e08ccfc69..46bd35987 100644 --- a/lib/firmware/CMakeLists.txt +++ b/lib/firmware/CMakeLists.txt @@ -37,6 +37,8 @@ set(sources tendermint.c thorchain.c tiny-json.c + tron.c + ton.c transaction.c txin_check.c u2f.c) diff --git a/lib/firmware/fsm.c b/lib/firmware/fsm.c index 0d779f448..eeeacdf41 100644 --- a/lib/firmware/fsm.c +++ b/lib/firmware/fsm.c @@ -58,6 +58,8 @@ #include "keepkey/firmware/storage.h" #include "keepkey/firmware/tendermint.h" #include "keepkey/firmware/thorchain.h" +#include "keepkey/firmware/tron.h" +#include "keepkey/firmware/ton.h" #include "keepkey/firmware/transaction.h" #include "keepkey/firmware/txin_check.h" #include "keepkey/firmware/u2f.h" @@ -84,6 +86,8 @@ #include "messages-ripple.pb.h" #include "messages-thorchain.pb.h" #include "messages-mayachain.pb.h" +#include "messages-tron.pb.h" +#include "messages-ton.pb.h" #include @@ -284,3 +288,5 @@ void fsm_msgClearSession(ClearSession *msg) { #include "fsm_msg_tendermint.h" #include "fsm_msg_thorchain.h" #include "fsm_msg_mayachain.h" +#include "fsm_msg_tron.h" +#include "fsm_msg_ton.h" diff --git a/lib/firmware/fsm_msg_ton.h b/lib/firmware/fsm_msg_ton.h new file mode 100644 index 000000000..10972ec37 --- /dev/null +++ b/lib/firmware/fsm_msg_ton.h @@ -0,0 +1,137 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2024 KeepKey + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +void fsm_msgTonGetAddress(const TonGetAddress *msg) { + RESP_INIT(TonAddress); + + CHECK_INITIALIZED + + CHECK_PIN + + // Validate path: m/44'/607'/x'/x'/x'/x' (all hardened for TON) + if (msg->address_n_count < 6) { + fsm_sendFailure(FailureType_Failure_Other, + _("Invalid path for TON address")); + layoutHome(); + return; + } + + // Derive node using Ed25519 curve + HDNode *node = fsm_getDerivedNode(ED25519_NAME, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + hdnode_fill_public_key(node); + + // Extract TON-specific parameters with defaults + bool bounceable = msg->has_bounceable ? msg->bounceable : true; + bool testnet = msg->has_testnet ? msg->testnet : false; + int32_t workchain = msg->has_workchain ? msg->workchain : 0; + + // Get TON address from public key (Base64 URL-safe encoding) + char address[MAX_ADDR_SIZE]; + char raw_address[MAX_ADDR_SIZE]; + if (!ton_get_address(&node->public_key[1], bounceable, testnet, workchain, + address, sizeof(address), raw_address, + sizeof(raw_address))) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_Other, _("Can't encode address")); + layoutHome(); + return; + } + + resp->has_address = true; + strlcpy(resp->address, address, sizeof(resp->address)); + resp->has_raw_address = true; + strlcpy(resp->raw_address, raw_address, sizeof(resp->raw_address)); + + // Show address on display if requested + if (msg->has_show_display && msg->show_display) { + char node_str[NODE_STRING_LENGTH]; + if (!bip32_path_to_string(node_str, sizeof(node_str), msg->address_n, + msg->address_n_count)) { + memset(node_str, 0, sizeof(node_str)); + } + + if (!confirm_ethereum_address(node_str, resp->address)) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_ActionCancelled, + _("Show address cancelled")); + layoutHome(); + return; + } + } + + memzero(node, sizeof(*node)); + msg_write(MessageType_MessageType_TonAddress, resp); + layoutHome(); +} + +void fsm_msgTonSignTx(TonSignTx *msg) { + RESP_INIT(TonSignedTx); + + CHECK_INITIALIZED + + CHECK_PIN + + // Derive node using Ed25519 curve + HDNode *node = fsm_getDerivedNode(ED25519_NAME, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + hdnode_fill_public_key(node); + + if (!msg->has_raw_tx || msg->raw_tx.size == 0) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_Other, + _("Missing transaction data")); + layoutHome(); + return; + } + + bool needs_confirm = true; + + // Display transaction details if available + if (needs_confirm && msg->has_to_address && msg->has_amount) { + char amount_str[32]; + ton_formatAmount(amount_str, sizeof(amount_str), msg->amount); + + if (!confirm(ButtonRequestType_ButtonRequest_ConfirmOutput, + "Send", "Send %s TON to %s?", + amount_str, msg->to_address)) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_ActionCancelled, "Signing cancelled"); + layoutHome(); + return; + } + } + + if (!confirm(ButtonRequestType_ButtonRequest_SignTx, "Transaction", + "Really sign this TON transaction?")) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_ActionCancelled, "Signing cancelled"); + layoutHome(); + return; + } + + // Sign the transaction with Ed25519 + ton_signTx(node, msg, resp); + + memzero(node, sizeof(*node)); + msg_write(MessageType_MessageType_TonSignedTx, resp); + layoutHome(); +} diff --git a/lib/firmware/fsm_msg_tron.h b/lib/firmware/fsm_msg_tron.h new file mode 100644 index 000000000..d7b96be10 --- /dev/null +++ b/lib/firmware/fsm_msg_tron.h @@ -0,0 +1,127 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2024 KeepKey + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +void fsm_msgTronGetAddress(const TronGetAddress *msg) { + RESP_INIT(TronAddress); + + CHECK_INITIALIZED + + CHECK_PIN + + // Validate path: m/44'/195'/x'/0/0 + if (msg->address_n_count < 3) { + fsm_sendFailure(FailureType_Failure_Other, + _("Invalid path for TRON address")); + layoutHome(); + return; + } + + // Derive node using secp256k1 curve + HDNode *node = fsm_getDerivedNode(SECP256K1_NAME, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + hdnode_fill_public_key(node); + + // Get TRON address from public key (Base58Check with prefix 'T') + char address[MAX_ADDR_SIZE]; + if (!tron_getAddress(node->public_key, address, sizeof(address))) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_Other, _("Address derivation failed")); + layoutHome(); + return; + } + + resp->has_address = true; + strlcpy(resp->address, address, sizeof(resp->address)); + + // Show address on display if requested + if (msg->has_show_display && msg->show_display) { + char node_str[NODE_STRING_LENGTH]; + if (!bip32_path_to_string(node_str, sizeof(node_str), msg->address_n, + msg->address_n_count)) { + memset(node_str, 0, sizeof(node_str)); + } + + if (!confirm_ethereum_address(node_str, resp->address)) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_ActionCancelled, + _("Show address cancelled")); + layoutHome(); + return; + } + } + + memzero(node, sizeof(*node)); + msg_write(MessageType_MessageType_TronAddress, resp); + layoutHome(); +} + +void fsm_msgTronSignTx(TronSignTx *msg) { + RESP_INIT(TronSignedTx); + + CHECK_INITIALIZED + + CHECK_PIN + + // Derive node using secp256k1 curve + HDNode *node = fsm_getDerivedNode(SECP256K1_NAME, msg->address_n, + msg->address_n_count, NULL); + if (!node) return; + hdnode_fill_public_key(node); + + if (!msg->has_raw_data || msg->raw_data.size == 0) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_Other, + _("Missing transaction data")); + layoutHome(); + return; + } + + bool needs_confirm = true; + + // Display transaction details if available + if (needs_confirm && msg->has_to_address && msg->has_amount) { + char amount_str[32]; + tron_formatAmount(amount_str, sizeof(amount_str), msg->amount); + + if (!confirm(ButtonRequestType_ButtonRequest_ConfirmOutput, + "Send", "Send %s TRX to %s?", + amount_str, msg->to_address)) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_ActionCancelled, "Signing cancelled"); + layoutHome(); + return; + } + } + + if (!confirm(ButtonRequestType_ButtonRequest_SignTx, "Transaction", + "Really sign this TRON transaction?")) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_ActionCancelled, "Signing cancelled"); + layoutHome(); + return; + } + + // Sign the transaction with secp256k1 + tron_signTx(node, msg, resp); + + memzero(node, sizeof(*node)); + msg_write(MessageType_MessageType_TronSignedTx, resp); + layoutHome(); +} diff --git a/lib/firmware/messagemap.def b/lib/firmware/messagemap.def index e8e374386..fd307a148 100644 --- a/lib/firmware/messagemap.def +++ b/lib/firmware/messagemap.def @@ -73,6 +73,11 @@ MSG_IN(MessageType_MessageType_MayachainSignTx, MayachainSignTx, fsm_msgMayachainSignTx) MSG_IN(MessageType_MessageType_MayachainMsgAck, MayachainMsgAck, fsm_msgMayachainMsgAck) + MSG_IN(MessageType_MessageType_TronGetAddress, TronGetAddress, fsm_msgTronGetAddress) + MSG_IN(MessageType_MessageType_TronSignTx, TronSignTx, fsm_msgTronSignTx) + MSG_IN(MessageType_MessageType_TonGetAddress, TonGetAddress, fsm_msgTonGetAddress) + MSG_IN(MessageType_MessageType_TonSignTx, TonSignTx, fsm_msgTonSignTx) + /* Normal Out Messages */ MSG_OUT(MessageType_MessageType_Success, Success, NO_PROCESS_FUNC) MSG_OUT(MessageType_MessageType_Failure, Failure, NO_PROCESS_FUNC) @@ -132,6 +137,11 @@ MSG_OUT(MessageType_MessageType_MayachainMsgRequest, MayachainMsgRequest, NO_PROCESS_FUNC) MSG_OUT(MessageType_MessageType_MayachainSignedTx, MayachainSignedTx, NO_PROCESS_FUNC) + MSG_OUT(MessageType_MessageType_TronAddress, TronAddress, NO_PROCESS_FUNC) + MSG_OUT(MessageType_MessageType_TronSignedTx, TronSignedTx, NO_PROCESS_FUNC) + MSG_OUT(MessageType_MessageType_TonAddress, TonAddress, NO_PROCESS_FUNC) + MSG_OUT(MessageType_MessageType_TonSignedTx, TonSignedTx, NO_PROCESS_FUNC) + #if DEBUG_LINK /* Debug Messages */ DEBUG_IN(MessageType_MessageType_DebugLinkDecision, DebugLinkDecision, NO_PROCESS_FUNC) diff --git a/lib/firmware/ton.c b/lib/firmware/ton.c new file mode 100644 index 000000000..3549c189a --- /dev/null +++ b/lib/firmware/ton.c @@ -0,0 +1,175 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2024 KeepKey + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "keepkey/firmware/ton.h" + +#include "trezor/crypto/ed25519-donna/ed25519.h" +#include "trezor/crypto/hasher.h" +#include "trezor/crypto/memzero.h" +#include "trezor/crypto/sha2.h" + +#include +#include + +// Base64 URL-safe alphabet (RFC 4648) +static const char base64_url_alphabet[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +/** + * Encode data to Base64 URL-safe format (without padding) + */ +static bool base64_url_encode(const uint8_t *data, size_t data_len, char *out, + size_t out_len) { + size_t required_len = ((data_len + 2) / 3) * 4; + if (out_len < required_len + 1) { + return false; + } + + size_t i = 0, j = 0; + while (i < data_len) { + uint32_t octet_a = i < data_len ? data[i++] : 0; + uint32_t octet_b = i < data_len ? data[i++] : 0; + uint32_t octet_c = i < data_len ? data[i++] : 0; + + uint32_t triple = (octet_a << 16) | (octet_b << 8) | octet_c; + + out[j++] = base64_url_alphabet[(triple >> 18) & 0x3F]; + out[j++] = base64_url_alphabet[(triple >> 12) & 0x3F]; + out[j++] = base64_url_alphabet[(triple >> 6) & 0x3F]; + out[j++] = base64_url_alphabet[triple & 0x3F]; + } + + // Remove padding for URL-safe variant + size_t padding = (3 - (data_len % 3)) % 3; + j -= padding; + out[j] = '\0'; + + return true; +} + +/** + * Compute CRC16-XMODEM checksum for TON address + */ +static uint16_t ton_crc16(const uint8_t *data, size_t len) { + uint16_t crc = 0; + + for (size_t i = 0; i < len; i++) { + crc ^= (uint16_t)data[i] << 8; + for (int j = 0; j < 8; j++) { + if (crc & 0x8000) { + crc = (crc << 1) ^ 0x1021; + } else { + crc <<= 1; + } + } + } + + return crc; +} + +/** + * Generate TON address from Ed25519 public key + * TON uses a specific format with workchain, bounceable/testnet flags, and + * CRC16 + */ +bool ton_get_address(const ed25519_public_key public_key, bool bounceable, + bool testnet, int32_t workchain, char *address, + size_t address_len, char *raw_address, + size_t raw_address_len) { + if (address_len < TON_ADDRESS_MAX_LEN || + raw_address_len < TON_RAW_ADDRESS_MAX_LEN) { + return false; + } + + // Hash the public key with SHA256 + uint8_t hash[32]; + sha256_Raw(public_key, 32, hash); + + // Construct address data: [tag][workchain][hash][crc16] + uint8_t addr_data[36]; + uint8_t tag = 0x11; // Base tag + if (bounceable) tag |= 0x11; + if (testnet) tag |= 0x80; + + addr_data[0] = tag; + addr_data[1] = (uint8_t)workchain; + memcpy(addr_data + 2, hash, 32); + + // Compute CRC16 checksum + uint16_t crc = ton_crc16(addr_data, 34); + addr_data[34] = (crc >> 8) & 0xFF; + addr_data[35] = crc & 0xFF; + + // Encode to Base64 URL-safe + if (!base64_url_encode(addr_data, 36, address, address_len)) { + memzero(hash, sizeof(hash)); + memzero(addr_data, sizeof(addr_data)); + return false; + } + + // Generate raw address format: workchain:hash_hex + char hash_hex[65]; + for (int i = 0; i < 32; i++) { + snprintf(hash_hex + (i * 2), 3, "%02x", hash[i]); + } + snprintf(raw_address, raw_address_len, "%ld:%s", (long)workchain, hash_hex); + + // Clean up sensitive data + memzero(hash, sizeof(hash)); + memzero(addr_data, sizeof(addr_data)); + + return true; +} + +/** + * Format TON amount (nanoTON) for display + * 1 TON = 1,000,000,000 nanoTON + */ +void ton_formatAmount(char *buf, size_t len, uint64_t amount) { + bignum256 val; + bn_read_uint64(amount, &val); + bn_format(&val, NULL, " TON", TON_DECIMALS, 0, false, buf, len); +} + +/** + * Sign a TON transaction with Ed25519 + */ +void ton_signTx(const HDNode *node, const TonSignTx *msg, TonSignedTx *resp) { + if (!node || !msg || !resp) { + return; + } + + // Verify we have raw transaction data + if (!msg->has_raw_tx || msg->raw_tx.size == 0) { + return; + } + + // Ed25519 sign the transaction + ed25519_signature signature; + ed25519_sign(msg->raw_tx.bytes, msg->raw_tx.size, node->private_key, + &node->public_key[1], signature); + + // Copy signature to response (64 bytes) + resp->has_signature = true; + resp->signature.size = 64; + memcpy(resp->signature.bytes, signature, 64); + + // Zero out the signature buffer for security + memzero(signature, sizeof(signature)); +} diff --git a/lib/firmware/tron.c b/lib/firmware/tron.c new file mode 100644 index 000000000..3ad0e2051 --- /dev/null +++ b/lib/firmware/tron.c @@ -0,0 +1,128 @@ +/* + * This file is part of the KeepKey project. + * + * Copyright (C) 2024 KeepKey + * + * This library is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ + +#include "keepkey/firmware/tron.h" + +#include "keepkey/crypto/curves.h" +#include "trezor/crypto/base58.h" +#include "trezor/crypto/ecdsa.h" +#include "trezor/crypto/memzero.h" +#include "trezor/crypto/secp256k1.h" +#include "trezor/crypto/sha3.h" + +#include + +#define TRON_ADDRESS_PREFIX 0x41 // Mainnet addresses start with 'T' + +/** + * Generate TRON address from secp256k1 public key + * TRON uses Keccak256(uncompressed_pubkey) and takes last 20 bytes, + * then prepends 0x41 and Base58Check encodes it + */ +bool tron_getAddress(const uint8_t public_key[33], char *address, + size_t address_len) { + if (address_len < TRON_ADDRESS_MAX_LEN) { + return false; + } + + uint8_t uncompressed_pubkey[65]; + uint8_t hash[32]; + uint8_t addr_bytes[21]; + + // Uncompress the public key + ecdsa_uncompress_pubkey(&secp256k1, public_key, uncompressed_pubkey); + + // Keccak256 hash of uncompressed public key (skip first 0x04 byte) + keccak_256(uncompressed_pubkey + 1, 64, hash); + + // Take last 20 bytes of hash and prepend TRON prefix byte + addr_bytes[0] = TRON_ADDRESS_PREFIX; + memcpy(addr_bytes + 1, hash + 12, 20); + + // Base58Check encode with double SHA256 + if (!base58_encode_check(addr_bytes, 21, HASHER_SHA2D, address, + address_len)) { + return false; + } + + // Clean up sensitive data + memzero(uncompressed_pubkey, sizeof(uncompressed_pubkey)); + memzero(hash, sizeof(hash)); + + return true; +} + +/** + * Format TRON amount (SUN) for display + * 1 TRX = 1,000,000 SUN + */ +void tron_formatAmount(char *buf, size_t len, uint64_t amount) { + bignum256 val; + bn_read_uint64(amount, &val); + bn_format(&val, NULL, " TRX", TRON_DECIMALS, 0, false, buf, len); +} + +/** + * Sign a TRON transaction with secp256k1 + */ +void tron_signTx(const HDNode *node, const TronSignTx *msg, + TronSignedTx *resp) { + if (!node || !msg || !resp) { + return; + } + + // Verify we have raw transaction data + if (!msg->has_raw_data || msg->raw_data.size == 0) { + return; + } + + // Get the curve for secp256k1 + const curve_info *curve = get_curve_by_name(SECP256K1_NAME); + if (!curve) { + return; + } + + // Hash the transaction with SHA256 + uint8_t hash[32]; + sha256_Raw(msg->raw_data.bytes, msg->raw_data.size, hash); + + // Sign with secp256k1 (recoverable signature: 65 bytes including recovery + // ID) + uint8_t sig[65]; + uint8_t pby; + + if (ecdsa_sign_digest(&secp256k1, node->private_key, hash, sig, &pby, + NULL) != 0) { + memzero(hash, sizeof(hash)); + return; + } + + // Convert to recoverable signature format (r + s + recovery_id) + // The recovery ID allows recovering the public key from the signature + sig[64] = pby; + + // Copy signature to response (65 bytes) + resp->has_signature = true; + resp->signature.size = 65; + memcpy(resp->signature.bytes, sig, 65); + + // Clean up sensitive data + memzero(hash, sizeof(hash)); + memzero(sig, sizeof(sig)); +} From e142df148e5e2734f42ef53f51c31be631eb8469 Mon Sep 17 00:00:00 2001 From: highlander Date: Thu, 12 Mar 2026 15:23:38 -0600 Subject: [PATCH 3/8] fix: address code review issues in Tron and TON implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix bounceable flag no-op: use ternary (0x11 vs 0x51) instead of OR - Check ecdsa_uncompress_pubkey return value in tron_getAddress - Add BIP44 path validation: m/44'/195'/... for Tron, m/44'/607'/... for TON - Change signTx functions to return bool (was void, silent failures) - Check signTx return in FSM handlers, send proper failure on error Note: TON address derivation uses SHA-256(pubkey) which produces non-standard addresses. Standard TON addresses require SHA-256(StateInit cell) which includes wallet contract code — too large for firmware flash. This is a known limitation documented for future resolution. --- include/keepkey/firmware/ton.h | 2 +- include/keepkey/firmware/tron.h | 2 +- lib/firmware/fsm_msg_ton.h | 25 +++++++++++++++++++++---- lib/firmware/fsm_msg_tron.h | 25 +++++++++++++++++++++---- lib/firmware/ton.c | 11 ++++++----- lib/firmware/tron.c | 14 +++++++++----- 6 files changed, 59 insertions(+), 20 deletions(-) diff --git a/include/keepkey/firmware/ton.h b/include/keepkey/firmware/ton.h index 78e929c9b..3bcd8224e 100644 --- a/include/keepkey/firmware/ton.h +++ b/include/keepkey/firmware/ton.h @@ -63,6 +63,6 @@ void ton_formatAmount(char *buf, size_t len, uint64_t amount); * @param msg TonSignTx request message * @param resp TonSignedTx response message (will be filled with signature) */ -void ton_signTx(const HDNode *node, const TonSignTx *msg, TonSignedTx *resp); +bool ton_signTx(const HDNode *node, const TonSignTx *msg, TonSignedTx *resp); #endif diff --git a/include/keepkey/firmware/tron.h b/include/keepkey/firmware/tron.h index df24496dc..22abb5c26 100644 --- a/include/keepkey/firmware/tron.h +++ b/include/keepkey/firmware/tron.h @@ -54,7 +54,7 @@ void tron_formatAmount(char *buf, size_t len, uint64_t amount); * @param msg TronSignTx request message * @param resp TronSignedTx response message (will be filled with signature) */ -void tron_signTx(const HDNode *node, const TronSignTx *msg, +bool tron_signTx(const HDNode *node, const TronSignTx *msg, TronSignedTx *resp); #endif diff --git a/lib/firmware/fsm_msg_ton.h b/lib/firmware/fsm_msg_ton.h index 10972ec37..528cb0404 100644 --- a/lib/firmware/fsm_msg_ton.h +++ b/lib/firmware/fsm_msg_ton.h @@ -24,10 +24,12 @@ void fsm_msgTonGetAddress(const TonGetAddress *msg) { CHECK_PIN - // Validate path: m/44'/607'/x'/x'/x'/x' (all hardened for TON) - if (msg->address_n_count < 6) { + // Validate path: m/44'/607'/... (all hardened for TON) + if (msg->address_n_count < 2 || + msg->address_n[0] != (0x80000000 | 44) || + msg->address_n[1] != (0x80000000 | 607)) { fsm_sendFailure(FailureType_Failure_Other, - _("Invalid path for TON address")); + _("Invalid TON path (expected m/44'/607'/...)")); layoutHome(); return; } @@ -89,6 +91,16 @@ void fsm_msgTonSignTx(TonSignTx *msg) { CHECK_PIN + // Validate path: m/44'/607'/... + if (msg->address_n_count < 2 || + msg->address_n[0] != (0x80000000 | 44) || + msg->address_n[1] != (0x80000000 | 607)) { + fsm_sendFailure(FailureType_Failure_Other, + _("Invalid TON path (expected m/44'/607'/...)")); + layoutHome(); + return; + } + // Derive node using Ed25519 curve HDNode *node = fsm_getDerivedNode(ED25519_NAME, msg->address_n, msg->address_n_count, NULL); @@ -129,7 +141,12 @@ void fsm_msgTonSignTx(TonSignTx *msg) { } // Sign the transaction with Ed25519 - ton_signTx(node, msg, resp); + if (!ton_signTx(node, msg, resp)) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_Other, _("TON signing failed")); + layoutHome(); + return; + } memzero(node, sizeof(*node)); msg_write(MessageType_MessageType_TonSignedTx, resp); diff --git a/lib/firmware/fsm_msg_tron.h b/lib/firmware/fsm_msg_tron.h index d7b96be10..7e54ebb30 100644 --- a/lib/firmware/fsm_msg_tron.h +++ b/lib/firmware/fsm_msg_tron.h @@ -24,10 +24,12 @@ void fsm_msgTronGetAddress(const TronGetAddress *msg) { CHECK_PIN - // Validate path: m/44'/195'/x'/0/0 - if (msg->address_n_count < 3) { + // Validate path: m/44'/195'/... + if (msg->address_n_count < 3 || + msg->address_n[0] != (0x80000000 | 44) || + msg->address_n[1] != (0x80000000 | 195)) { fsm_sendFailure(FailureType_Failure_Other, - _("Invalid path for TRON address")); + _("Invalid TRON path (expected m/44'/195'/...)")); layoutHome(); return; } @@ -79,6 +81,16 @@ void fsm_msgTronSignTx(TronSignTx *msg) { CHECK_PIN + // Validate path: m/44'/195'/... + if (msg->address_n_count < 3 || + msg->address_n[0] != (0x80000000 | 44) || + msg->address_n[1] != (0x80000000 | 195)) { + fsm_sendFailure(FailureType_Failure_Other, + _("Invalid TRON path (expected m/44'/195'/...)")); + layoutHome(); + return; + } + // Derive node using secp256k1 curve HDNode *node = fsm_getDerivedNode(SECP256K1_NAME, msg->address_n, msg->address_n_count, NULL); @@ -119,7 +131,12 @@ void fsm_msgTronSignTx(TronSignTx *msg) { } // Sign the transaction with secp256k1 - tron_signTx(node, msg, resp); + if (!tron_signTx(node, msg, resp)) { + memzero(node, sizeof(*node)); + fsm_sendFailure(FailureType_Failure_Other, _("TRON signing failed")); + layoutHome(); + return; + } memzero(node, sizeof(*node)); msg_write(MessageType_MessageType_TronSignedTx, resp); diff --git a/lib/firmware/ton.c b/lib/firmware/ton.c index 3549c189a..25986041b 100644 --- a/lib/firmware/ton.c +++ b/lib/firmware/ton.c @@ -103,8 +103,7 @@ bool ton_get_address(const ed25519_public_key public_key, bool bounceable, // Construct address data: [tag][workchain][hash][crc16] uint8_t addr_data[36]; - uint8_t tag = 0x11; // Base tag - if (bounceable) tag |= 0x11; + uint8_t tag = bounceable ? 0x11 : 0x51; if (testnet) tag |= 0x80; addr_data[0] = tag; @@ -150,14 +149,14 @@ void ton_formatAmount(char *buf, size_t len, uint64_t amount) { /** * Sign a TON transaction with Ed25519 */ -void ton_signTx(const HDNode *node, const TonSignTx *msg, TonSignedTx *resp) { +bool ton_signTx(const HDNode *node, const TonSignTx *msg, TonSignedTx *resp) { if (!node || !msg || !resp) { - return; + return false; } // Verify we have raw transaction data if (!msg->has_raw_tx || msg->raw_tx.size == 0) { - return; + return false; } // Ed25519 sign the transaction @@ -172,4 +171,6 @@ void ton_signTx(const HDNode *node, const TonSignTx *msg, TonSignedTx *resp) { // Zero out the signature buffer for security memzero(signature, sizeof(signature)); + + return true; } diff --git a/lib/firmware/tron.c b/lib/firmware/tron.c index 3ad0e2051..ecac45cfd 100644 --- a/lib/firmware/tron.c +++ b/lib/firmware/tron.c @@ -46,7 +46,9 @@ bool tron_getAddress(const uint8_t public_key[33], char *address, uint8_t addr_bytes[21]; // Uncompress the public key - ecdsa_uncompress_pubkey(&secp256k1, public_key, uncompressed_pubkey); + if (!ecdsa_uncompress_pubkey(&secp256k1, public_key, uncompressed_pubkey)) { + return false; + } // Keccak256 hash of uncompressed public key (skip first 0x04 byte) keccak_256(uncompressed_pubkey + 1, 64, hash); @@ -81,15 +83,15 @@ void tron_formatAmount(char *buf, size_t len, uint64_t amount) { /** * Sign a TRON transaction with secp256k1 */ -void tron_signTx(const HDNode *node, const TronSignTx *msg, +bool tron_signTx(const HDNode *node, const TronSignTx *msg, TronSignedTx *resp) { if (!node || !msg || !resp) { - return; + return false; } // Verify we have raw transaction data if (!msg->has_raw_data || msg->raw_data.size == 0) { - return; + return false; } // Get the curve for secp256k1 @@ -110,7 +112,7 @@ void tron_signTx(const HDNode *node, const TronSignTx *msg, if (ecdsa_sign_digest(&secp256k1, node->private_key, hash, sig, &pby, NULL) != 0) { memzero(hash, sizeof(hash)); - return; + return false; } // Convert to recoverable signature format (r + s + recovery_id) @@ -125,4 +127,6 @@ void tron_signTx(const HDNode *node, const TronSignTx *msg, // Clean up sensitive data memzero(hash, sizeof(hash)); memzero(sig, sizeof(sig)); + + return true; } From 8b7fb5786894ac485b9b330400169ea698ef9278 Mon Sep 17 00:00:00 2001 From: highlander Date: Sat, 14 Mar 2026 16:17:05 -0600 Subject: [PATCH 4/8] fix(ton): compute v4r2 StateInit hash for correct wallet address ton_get_address() was computing sha256(pubkey) directly, which does not correspond to any deployable TON wallet contract. The correct TON v4r2 address is sha256(StateInit(code_cell, data_cell)) where the data cell contains seqno=0, walletId=698983191, and the ed25519 public key. The fix hardcodes the well-known v4r2 code cell hash and depth, computes the data cell representation hash from the public key, then derives the StateInit hash. This produces addresses compatible with the standard v4r2 wallet contract used by TonKeeper, MyTonWallet, and @ton/ton SDK. --- lib/firmware/ton.c | 88 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 83 insertions(+), 5 deletions(-) diff --git a/lib/firmware/ton.c b/lib/firmware/ton.c index 25986041b..7dbc0181a 100644 --- a/lib/firmware/ton.c +++ b/lib/firmware/ton.c @@ -83,10 +83,88 @@ static uint16_t ton_crc16(const uint8_t *data, size_t len) { return crc; } +// Well-known v4r2 wallet contract code cell hash (constant) +// Source: https://github.com/ton-blockchain/wallet-contract (WalletV4R2) +static const uint8_t V4R2_CODE_HASH[32] = { + 0xfe, 0xb5, 0xff, 0x68, 0x20, 0xe2, 0xff, 0x0d, + 0x94, 0x83, 0xe7, 0xe0, 0xd6, 0x2c, 0x81, 0x7d, + 0x84, 0x67, 0x89, 0xfb, 0x4a, 0xe5, 0x80, 0xc8, + 0x78, 0x86, 0x6d, 0x95, 0x9d, 0xab, 0xd5, 0xc0}; +// Code cell depth in the cell tree +#define V4R2_CODE_DEPTH 7 +// Standard v4r2 wallet_id for mainnet workchain 0 +#define V4R2_WALLET_ID 698983191u // 0x29A9A317 + +/** + * Compute the v4r2 data cell representation hash. + * Data cell layout: seqno(32b=0) + wallet_id(32b) + pubkey(256b) + plugins(1b=0) + * Total: 321 bits, d1=0x00 (no refs), d2=0x51 (floor(321/8)+ceil(321/8)=40+41=81) + * Augmented data: 40 full bytes + 0x40 (plugin=0, completion=1, pad=000000) + */ +static void ton_data_cell_hash(const uint8_t *public_key, uint8_t *out) { + // repr = d1(1) + d2(1) + augmented_data(41) = 43 bytes + uint8_t repr[43]; + repr[0] = 0x00; // d1: no refs + repr[1] = 0x51; // d2: 81 decimal + + // seqno = 0 (4 bytes) + memset(repr + 2, 0, 4); + + // wallet_id = 698983191 = 0x29A9A317 (4 bytes big-endian) + repr[6] = (V4R2_WALLET_ID >> 24) & 0xFF; + repr[7] = (V4R2_WALLET_ID >> 16) & 0xFF; + repr[8] = (V4R2_WALLET_ID >> 8) & 0xFF; + repr[9] = (V4R2_WALLET_ID) & 0xFF; + + // public key (32 bytes) + memcpy(repr + 10, public_key, 32); + + // plugins = 0 (1 bit) + completion tag (1 bit) + padding (6 bits) = 0x40 + repr[42] = 0x40; + + sha256_Raw(repr, 43, out); +} + +/** + * Compute the v4r2 StateInit representation hash. + * StateInit: split_depth(0) + special(0) + code(1,ref) + data(1,ref) + library(0) + * Total: 5 bits, d1=0x02 (2 refs), d2=0x01 + * Augmented: 00110 + 100 = 0x34 + * repr = d1 + d2 + data + depth(code) + depth(data) + hash(code) + hash(data) + */ +static void ton_stateinit_hash(const uint8_t *public_key, uint8_t *out) { + uint8_t data_hash[32]; + ton_data_cell_hash(public_key, data_hash); + + // repr = d1(1) + d2(1) + augmented(1) + code_depth(2) + data_depth(2) + + // code_hash(32) + data_hash(32) = 71 bytes + uint8_t repr[71]; + repr[0] = 0x02; // d1: 2 refs + repr[1] = 0x01; // d2: 1 + repr[2] = 0x34; // augmented: 00110100 + + // code cell depth = 7 (2 bytes big-endian) + repr[3] = 0x00; + repr[4] = V4R2_CODE_DEPTH; + + // data cell depth = 0 (no refs) + repr[5] = 0x00; + repr[6] = 0x00; + + // code cell hash (32 bytes) + memcpy(repr + 7, V4R2_CODE_HASH, 32); + + // data cell hash (32 bytes) + memcpy(repr + 39, data_hash, 32); + + sha256_Raw(repr, 71, out); + memzero(data_hash, sizeof(data_hash)); +} + /** - * Generate TON address from Ed25519 public key - * TON uses a specific format with workchain, bounceable/testnet flags, and - * CRC16 + * Generate TON v4r2 wallet address from Ed25519 public key. + * Address = base64url(tag || workchain || sha256(StateInit) || crc16) + * where StateInit = code_cell(v4r2) + data_cell(seqno=0, walletId, pubkey) */ bool ton_get_address(const ed25519_public_key public_key, bool bounceable, bool testnet, int32_t workchain, char *address, @@ -97,9 +175,9 @@ bool ton_get_address(const ed25519_public_key public_key, bool bounceable, return false; } - // Hash the public key with SHA256 + // Compute the v4r2 StateInit representation hash — this IS the address hash uint8_t hash[32]; - sha256_Raw(public_key, 32, hash); + ton_stateinit_hash(public_key, hash); // Construct address data: [tag][workchain][hash][crc16] uint8_t addr_data[36]; From 2bf5c4717844f75c9ecef28110c9bd3628be8fc6 Mon Sep 17 00:00:00 2001 From: highlander Date: Mon, 16 Mar 2026 20:30:09 -0600 Subject: [PATCH 5/8] fix: tron_signTx return false on curve lookup failure --- lib/firmware/tron.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/firmware/tron.c b/lib/firmware/tron.c index ecac45cfd..cbd5b0b4b 100644 --- a/lib/firmware/tron.c +++ b/lib/firmware/tron.c @@ -97,7 +97,7 @@ bool tron_signTx(const HDNode *node, const TronSignTx *msg, // Get the curve for secp256k1 const curve_info *curve = get_curve_by_name(SECP256K1_NAME); if (!curve) { - return; + return false; } // Hash the transaction with SHA256 From 65b63f5db9e16b4b259674bba7596150bd508b17 Mon Sep 17 00:00:00 2001 From: highlander Date: Mon, 16 Mar 2026 21:37:55 -0600 Subject: [PATCH 6/8] chore: fix submodule pointers to upstream master --- deps/device-protocol | 2 +- deps/python-keepkey | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deps/device-protocol b/deps/device-protocol index 323802f17..b07589ff3 160000 --- a/deps/device-protocol +++ b/deps/device-protocol @@ -1 +1 @@ -Subproject commit 323802f17dd44165a5100357df771348c8b49672 +Subproject commit b07589ff386265f894736a490fc163d46f9479b7 diff --git a/deps/python-keepkey b/deps/python-keepkey index f1dd2b684..e89587701 160000 --- a/deps/python-keepkey +++ b/deps/python-keepkey @@ -1 +1 @@ -Subproject commit f1dd2b6847346abe8ea2985b22d688de4911643f +Subproject commit e8958770152a85af4c2a03f9e93b5d98e8dff44b From 2b81e33688f7a02d44f81cfa5b7f8e80b2e5448c Mon Sep 17 00:00:00 2001 From: highlander Date: Mon, 16 Mar 2026 22:17:46 -0600 Subject: [PATCH 7/8] fix: remove duplicate nanopb options entries for Tron and TON --- include/keepkey/transport/messages-ton.options | 2 -- include/keepkey/transport/messages-tron.options | 8 -------- 2 files changed, 10 deletions(-) diff --git a/include/keepkey/transport/messages-ton.options b/include/keepkey/transport/messages-ton.options index bec6d9857..18c064289 100644 --- a/include/keepkey/transport/messages-ton.options +++ b/include/keepkey/transport/messages-ton.options @@ -9,6 +9,4 @@ TonSignTx.coin_name max_size:21 TonSignTx.raw_tx max_size:1024 TonSignTx.to_address max_size:50 -TonAddress.address max_size:50 -TonAddress.raw_address max_size:70 TonSignedTx.signature max_size:64 diff --git a/include/keepkey/transport/messages-tron.options b/include/keepkey/transport/messages-tron.options index 665310b95..80215e8cf 100644 --- a/include/keepkey/transport/messages-tron.options +++ b/include/keepkey/transport/messages-tron.options @@ -11,12 +11,4 @@ TronSignTx.ref_block_hash max_size:32 TronSignTx.contract_type max_size:64 TronSignTx.to_address max_size:35 -TronSignTx.address_n max_count:8 -TronSignTx.coin_name max_size:21 -TronSignTx.raw_data max_size:1024 -TronSignTx.ref_block_bytes max_size:2 -TronSignTx.ref_block_hash max_size:8 -TronSignTx.contract_type max_size:50 -TronSignTx.to_address max_size:35 -TronAddress.address max_size:35 TronSignedTx.signature max_size:65 From e59276e5852f8b3b8f3e41b192dd914803504d5a Mon Sep 17 00:00:00 2001 From: pastaghost Date: Wed, 18 Mar 2026 22:46:02 -0600 Subject: [PATCH 8/8] empty commit to trigger CI