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..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 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..3bcd8224e --- /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) + */ +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 new file mode 100644 index 000000000..22abb5c26 --- /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) + */ +bool 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-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/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..528cb0404 --- /dev/null +++ b/lib/firmware/fsm_msg_ton.h @@ -0,0 +1,154 @@ +/* + * 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'/... (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 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); + 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 + + // 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); + 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 + 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); + layoutHome(); +} diff --git a/lib/firmware/fsm_msg_tron.h b/lib/firmware/fsm_msg_tron.h new file mode 100644 index 000000000..7e54ebb30 --- /dev/null +++ b/lib/firmware/fsm_msg_tron.h @@ -0,0 +1,144 @@ +/* + * 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'/... + 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); + 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 + + // 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); + 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 + 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); + 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/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/firmware/ton.c b/lib/firmware/ton.c new file mode 100644 index 000000000..7dbc0181a --- /dev/null +++ b/lib/firmware/ton.c @@ -0,0 +1,254 @@ +/* + * 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; +} + +// 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 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, + 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; + } + + // Compute the v4r2 StateInit representation hash — this IS the address hash + uint8_t hash[32]; + ton_stateinit_hash(public_key, hash); + + // Construct address data: [tag][workchain][hash][crc16] + uint8_t addr_data[36]; + uint8_t tag = bounceable ? 0x11 : 0x51; + 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 + */ +bool ton_signTx(const HDNode *node, const TonSignTx *msg, TonSignedTx *resp) { + if (!node || !msg || !resp) { + return false; + } + + // Verify we have raw transaction data + if (!msg->has_raw_tx || msg->raw_tx.size == 0) { + return false; + } + + // 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)); + + return true; +} diff --git a/lib/firmware/tron.c b/lib/firmware/tron.c new file mode 100644 index 000000000..cbd5b0b4b --- /dev/null +++ b/lib/firmware/tron.c @@ -0,0 +1,132 @@ +/* + * 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 + 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); + + // 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 + */ +bool tron_signTx(const HDNode *node, const TronSignTx *msg, + TronSignedTx *resp) { + if (!node || !msg || !resp) { + return false; + } + + // Verify we have raw transaction data + if (!msg->has_raw_data || msg->raw_data.size == 0) { + return false; + } + + // Get the curve for secp256k1 + const curve_info *curve = get_curve_by_name(SECP256K1_NAME); + if (!curve) { + return false; + } + + // 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 false; + } + + // 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)); + + return true; +} 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