Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions include/keepkey/firmware/tron.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,67 @@

#include "messages-tron.pb.h"

#include <stdbool.h>
#include <stddef.h>
#include <stdint.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

// Raw 21-byte TRON address: 0x41 prefix + 20-byte keccak hash tail
#define TRON_RAW_ADDRESS_SIZE 21

/**
* On-device classification of a TronSignTx raw_data payload.
*
* The device signs sha256(raw_data), so anything shown to the user MUST be
* decoded from raw_data itself — never from side-channel proto fields.
* Unless every field of the payload is understood, the transaction is
* TRON_TX_UNVERIFIED and only the blind-sign path may be offered.
*/
typedef enum {
TRON_TX_UNVERIFIED = 0, // not fully understood — blind-sign only
TRON_TX_TRANSFER, // single TransferContract (native TRX send)
TRON_TX_TRC20_TRANSFER, // single TriggerSmartContract:
// transfer(address,uint256)
} TronTxType;

typedef struct {
TronTxType type;
uint8_t owner[TRON_RAW_ADDRESS_SIZE]; // spending account
uint8_t to[TRON_RAW_ADDRESS_SIZE]; // TRX or token recipient
uint8_t contract[TRON_RAW_ADDRESS_SIZE]; // TRC-20 token contract
uint64_t amount; // SUN, TransferContract only
uint8_t trc20_amount[32]; // big-endian uint256 token base units
bool has_fee_limit;
uint64_t fee_limit; // SUN
const uint8_t* memo; // points into caller's raw_data
uint16_t memo_len;
} TronParsedTx;

/**
* Parse a TRON raw_data protobuf for on-device display.
* Fail-closed: any unrecognized top-level field, contract type, extra
* contract, or unexpected parameter field yields TRON_TX_UNVERIFIED.
* out->memo points into raw — valid only while raw is alive.
*/
TronTxType tron_parseRawTx(const uint8_t* raw, size_t len, TronParsedTx* out);

/**
* Base58Check-encode a raw 21-byte TRON address for display.
*/
bool tron_addressFromBytes(const uint8_t addr[TRON_RAW_ADDRESS_SIZE], char* out,
size_t out_len);

/**
* Format a TRC-20 uint256 amount (big-endian) as a decimal string of token
* base units. Token decimals are unknown on-device, so no scaling is done.
*/
bool tron_formatTrc20Amount(const uint8_t amount_be[32], char* buf, size_t len);

/**
* Generate TRON address from secp256k1 public key
* @param public_key secp256k1 public key (33 bytes compressed)
Expand Down
87 changes: 32 additions & 55 deletions lib/firmware/fsm_msg_ton.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,20 @@ void fsm_msgTonSignTx(TonSignTx* msg) {
return;
}

/* AdvancedMode gate: to_address/amount are display-only, so this is
* length-only blind signing of raw bytes. Same fence as TonSignMessage
* and Solana/TRON opaque signing until the displayed fields are parsed
* from and bound to raw_tx. */
if (!storage_isPolicyEnabled("AdvancedMode")) {
(void)review(ButtonRequestType_ButtonRequest_Other, "Blocked",
"TON transaction signing is blind-only. "
"Enable AdvancedMode in device settings.");
fsm_sendFailure(FailureType_Failure_ActionCancelled,
_("Transaction signing disabled by policy"));
layoutHome();
return;
}

// Derive node using Ed25519 curve
HDNode* node = fsm_getDerivedNode(ED25519_NAME, msg->address_n,
msg->address_n_count, NULL);
Expand All @@ -112,24 +126,14 @@ void fsm_msgTonSignTx(TonSignTx* msg) {
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?")) {
/* to_address and amount are display-only fields not bound to raw_tx bytes.
* A malicious host could show one recipient while getting a different
* transaction signed. Show only the raw_tx size. */
char blind_msg[48];
snprintf(blind_msg, sizeof(blind_msg), "Sign %u-byte TON transaction?",
(unsigned)msg->raw_tx.size);
if (!confirm(ButtonRequestType_ButtonRequest_SignTx, "TON Blind Sign", "%s",
blind_msg)) {
memzero(node, sizeof(*node));
fsm_sendFailure(FailureType_Failure_ActionCancelled, "Signing cancelled");
layoutHome();
Expand Down Expand Up @@ -190,43 +194,16 @@ void fsm_msgTonSignMessage(const TonSignMessage* msg) {
if (!node) return;
hdnode_fill_public_key(node);

/* Always require on-device confirmation. Display message content if
* printable, hex preview otherwise. */
{
char msgBuf[129] = {0};
const char* typeLabel;
bool printable = true;
for (unsigned i = 0; i < msg->message.size; i++) {
if (msg->message.bytes[i] < 0x20 || msg->message.bytes[i] > 0x7e) {
printable = false;
break;
}
}
if (printable && msg->message.size <= sizeof(msgBuf) - 1) {
typeLabel = "Sign TON Message";
memcpy(msgBuf, msg->message.bytes, msg->message.size);
msgBuf[msg->message.size] = '\0';
} else {
typeLabel = "Sign TON Bytes";
unsigned show = msg->message.size;
if (show > 32) show = 32;
for (unsigned i = 0; i < show; i++) {
snprintf(&msgBuf[2 * i], 3, "%02x", msg->message.bytes[i]);
}
msgBuf[2 * show] = '\0';
if (msg->message.size > 32) {
snprintf(&msgBuf[64], sizeof(msgBuf) - 64, "... (%u bytes)",
(unsigned)msg->message.size);
}
}
if (!confirm(ButtonRequestType_ButtonRequest_ProtectCall, _(typeLabel),
"%s", msgBuf)) {
memzero(node, sizeof(*node));
fsm_sendFailure(FailureType_Failure_ActionCancelled,
_("Signing cancelled"));
layoutHome();
return;
}
/* AdvancedMode permits the opaque primitive, but never permits a hidden
* suffix: review every signed byte using renderer-measured pages. */
if (!confirm_bytes(ButtonRequestType_ButtonRequest_ProtectCall,
"Sign TON Message", msg->message.bytes,
msg->message.size)) {
memzero(node, sizeof(*node));
fsm_sendFailure(FailureType_Failure_ActionCancelled,
_("Signing cancelled"));
layoutHome();
return;
}

if (!ton_message_sign(node, msg, resp)) {
Expand Down
Loading
Loading