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
3 changes: 2 additions & 1 deletion include/keepkey/firmware/solana_tx.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/*
* This file is part of the KeepKey project.
*
Expand Down Expand Up @@ -60,7 +60,8 @@
uint16_t num_accounts;
uint8_t account_keys[16][32]; // 16 accounts (512 bytes)
uint8_t recent_blockhash[32];
uint8_t num_instructions;
uint8_t num_instructions; // min(actual, 8) — parsed instructions
uint16_t total_instructions; // actual count from TX (for display)
SolanaInstruction instructions[8]; // 8 instructions
} SolanaParsedTransaction;

Expand Down
18 changes: 18 additions & 0 deletions include/keepkey/firmware/tron.h
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/*
* This file is part of the KeepKey project.
*
Expand Down Expand Up @@ -57,4 +57,22 @@
bool tron_signTx(const HDNode *node, const TronSignTx *msg,
TronSignedTx *resp);

/**
* Parsed TRON TransferContract fields extracted from raw_data.
* Used to verify on-device display against the actual signed payload.
*/
typedef struct {
uint8_t to_address[21]; // 0x41 prefix + 20 bytes
int64_t amount; // SUN
bool valid; // true if parsing succeeded
} TronParsedTransfer;

/**
* Parse a TRON Transaction.raw protobuf to extract TransferContract fields.
* Only succeeds for simple TRX transfers (contract type 1).
* @return true if a TransferContract was found and parsed
*/
bool tron_parseTransfer(const uint8_t *raw_data, size_t raw_data_len,
TronParsedTransfer *out);

#endif
18 changes: 8 additions & 10 deletions lib/firmware/fsm_msg_solana.h
Original file line number Diff line number Diff line change
Expand Up @@ -192,16 +192,14 @@ void fsm_msgSolanaSignMessage(const SolanaSignMessage *msg) {
uint8_t public_key[32];
ed25519_publickey(node->private_key, public_key);

// Confirm message signing with user (shows message preview)
// Default to showing confirmation when client doesn't set the field
if (!msg->has_show_display || msg->show_display) {
if (!solana_confirmMessage(msg->message.bytes, msg->message.size)) {
memzero(node, sizeof(*node));
fsm_sendFailure(FailureType_Failure_ActionCancelled,
_("Message signing cancelled"));
layoutHome();
return;
}
// Always require user confirmation — signed messages can authorize
// on-chain actions. Never allow host to bypass via show_display=false.
if (!solana_confirmMessage(msg->message.bytes, msg->message.size)) {
memzero(node, sizeof(*node));
fsm_sendFailure(FailureType_Failure_ActionCancelled,
_("Message signing cancelled"));
layoutHome();
return;
}

// Sign the message
Expand Down
14 changes: 7 additions & 7 deletions lib/firmware/fsm_msg_ton.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,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) {
// TON uses Cell/BoC encoding which cannot be parsed on-device.
// Display host-supplied fields with explicit blind-sign warning.
if (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?",
"TON Transfer", "Send %s to\n%s?",
amount_str, msg->to_address)) {
memzero(node, sizeof(*node));
fsm_sendFailure(FailureType_Failure_ActionCancelled, "Signing cancelled");
Expand All @@ -132,8 +131,9 @@ void fsm_msgTonSignTx(TonSignTx *msg) {
}
}

if (!confirm(ButtonRequestType_ButtonRequest_SignTx, "Transaction",
"Really sign this TON transaction?")) {
if (!confirm(ButtonRequestType_ButtonRequest_SignTx, "Blind Signature",
"TON TX details cannot be verified on device. "
"Sign only if you trust the sending app.")) {
memzero(node, sizeof(*node));
fsm_sendFailure(FailureType_Failure_ActionCancelled, "Signing cancelled");
layoutHome();
Expand Down
36 changes: 28 additions & 8 deletions lib/firmware/fsm_msg_tron.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,25 +105,45 @@ void fsm_msgTronSignTx(TronSignTx *msg) {
return;
}

bool needs_confirm = true;
// Try to parse a TransferContract from the signed payload so the device
// can show verified transfer details instead of host-asserted fields.
TronParsedTransfer parsed;
if (tron_parseTransfer(msg->raw_data.bytes, msg->raw_data.size, &parsed)) {
// Simple TRX transfer — show on-device-verified details
char to_addr[TRON_ADDRESS_MAX_LEN];
if (!base58_encode_check(parsed.to_address, 21, HASHER_SHA2D,
to_addr, sizeof(to_addr))) {
memzero(node, sizeof(*node));
fsm_sendFailure(FailureType_Failure_Other, _("Bad to_address in TX"));
layoutHome();
return;
}

// 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);
tron_formatAmount(amount_str, sizeof(amount_str), (uint64_t)parsed.amount);

if (!confirm(ButtonRequestType_ButtonRequest_ConfirmOutput,
"Send", "Send %s TRX to %s?",
amount_str, msg->to_address)) {
"TRON Transfer", "Send %s to\n%s?",
amount_str, to_addr)) {
memzero(node, sizeof(*node));
fsm_sendFailure(FailureType_Failure_ActionCancelled, "Signing cancelled");
layoutHome();
return;
}
} else {
// Token / contract / complex TX — blind sign with explicit warning
if (!confirm(ButtonRequestType_ButtonRequest_SignTx, "Blind Signature",
"Cannot verify TX details on device. "
"Sign only if you trust the sending app.")) {
memzero(node, sizeof(*node));
fsm_sendFailure(FailureType_Failure_ActionCancelled, "Signing cancelled");
layoutHome();
return;
}
}

if (!confirm(ButtonRequestType_ButtonRequest_SignTx, "Transaction",
"Really sign this TRON transaction?")) {
if (!confirm(ButtonRequestType_ButtonRequest_SignTx, "Confirm",
"Sign this TRON transaction?")) {
memzero(node, sizeof(*node));
fsm_sendFailure(FailureType_Failure_ActionCancelled, "Signing cancelled");
layoutHome();
Expand Down
19 changes: 12 additions & 7 deletions lib/firmware/solana_tx.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ bool solana_parseTransaction(const uint8_t *raw_tx, size_t tx_size,
uint16_t num_instructions;
if (!read_compact_u16(&data, &remaining, &num_instructions)) return false;
parsed->num_instructions = MIN(num_instructions, 8);
parsed->total_instructions = num_instructions;

for (int i = 0; i < num_instructions; i++) {
// Use a stack variable for instructions beyond our storage limit
Expand Down Expand Up @@ -313,16 +314,18 @@ bool solana_confirmTransaction(const SolanaParsedTransaction *tx,
return false;
}

// For now, handle simple single-instruction transactions
const SolanaInstruction *instr = &tx->instructions[0];

if (instr->type == SOLANA_INSTRUCTION_SYSTEM_TRANSFER) {
// Only show verified transfer details for single-instruction system
// transfers. Multi-instruction TXs could hide malicious instructions
// behind a benign first one — the user must be warned.
if (tx->total_instructions == 1 &&
instr->type == SOLANA_INSTRUCTION_SYSTEM_TRANSFER) {
SolanaSystemTransfer transfer;
if (!solana_parseSystemTransfer(instr->data, instr->data_len, &transfer)) {
return false;
}

// Get recipient address (account index 1 for System Transfer)
if (instr->num_accounts < 2) return false;
uint8_t to_idx = instr->account_indices[1];
if (to_idx >= tx->num_accounts) return false;
Expand All @@ -342,9 +345,11 @@ bool solana_confirmTransaction(const SolanaParsedTransaction *tx,
amount_str, to_address);
}

// Unknown or complex transaction - show warning
// Multi-instruction or unknown program — blind sign with warning
return confirm(ButtonRequestType_ButtonRequest_SignTx,
"Solana Transaction",
"Sign transaction with %d instruction(s)?",
tx->num_instructions);
"Blind Signature",
"TX has %d instruction(s) that cannot be "
"fully verified on device. Sign only if "
"you trust the sending app.",
tx->total_instructions);
}
162 changes: 162 additions & 0 deletions lib/firmware/tron.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,168 @@ void tron_formatAmount(char *buf, size_t len, uint64_t amount) {
bn_format(&val, NULL, " TRX", TRON_DECIMALS, 0, false, buf, len);
}

/*
* Minimal protobuf wire-format parser for TRON TransferContract.
*
* TRON Transaction.raw layout (protobuf):
* field 11 (contract, repeated message) → Contract {
* field 1 (type, enum): ContractType (1 = TransferContract)
* field 2 (parameter, Any message) → google.protobuf.Any {
* field 1 (type_url, string)
* field 2 (value, bytes) → serialized TransferContract {
* field 1 (owner_address, bytes): 21 bytes
* field 2 (to_address, bytes): 21 bytes
* field 3 (amount, int64): varint
* }
* }
* }
*/

// Read a protobuf varint, return bytes consumed (0 on error)
static size_t pb_read_varint(const uint8_t *buf, size_t len, uint64_t *val) {
*val = 0;
size_t i = 0;
unsigned shift = 0;
while (i < len && shift < 64) {
uint64_t byte = buf[i];
*val |= (byte & 0x7F) << shift;
shift += 7;
i++;
if (!(byte & 0x80)) return i;
}
return 0; // malformed
}

// Skip a protobuf field given its wire type (0=varint, 2=length-delimited, etc)
static size_t pb_skip_field(const uint8_t *buf, size_t len, unsigned wire_type) {
if (wire_type == 0) { // varint
uint64_t dummy;
return pb_read_varint(buf, len, &dummy);
} else if (wire_type == 2) { // length-delimited
uint64_t flen;
size_t n = pb_read_varint(buf, len, &flen);
if (n == 0 || n + flen > len) return 0;
return n + (size_t)flen;
} else if (wire_type == 5) { // 32-bit
return len >= 4 ? 4 : 0;
} else if (wire_type == 1) { // 64-bit
return len >= 8 ? 8 : 0;
}
return 0;
}

// Find a length-delimited field by field number in a protobuf message.
// Returns pointer to the field data and sets *out_len. NULL if not found.
static const uint8_t *pb_find_bytes(const uint8_t *buf, size_t len,
unsigned field_num, size_t *out_len) {
size_t pos = 0;
while (pos < len) {
uint64_t tag;
size_t n = pb_read_varint(buf + pos, len - pos, &tag);
if (n == 0) return NULL;
pos += n;
unsigned fn = (unsigned)(tag >> 3);
unsigned wt = (unsigned)(tag & 7);
if (fn == field_num && wt == 2) {
uint64_t flen;
n = pb_read_varint(buf + pos, len - pos, &flen);
if (n == 0 || pos + n + flen > len) return NULL;
*out_len = (size_t)flen;
return buf + pos + n;
}
n = pb_skip_field(buf + pos, len - pos, wt);
if (n == 0) return NULL;
pos += n;
}
return NULL;
}

// Find a varint field by field number. Returns true if found.
static bool pb_find_varint(const uint8_t *buf, size_t len,
unsigned field_num, uint64_t *val) {
size_t pos = 0;
while (pos < len) {
uint64_t tag;
size_t n = pb_read_varint(buf + pos, len - pos, &tag);
if (n == 0) return false;
pos += n;
unsigned fn = (unsigned)(tag >> 3);
unsigned wt = (unsigned)(tag & 7);
if (fn == field_num && wt == 0) {
n = pb_read_varint(buf + pos, len - pos, val);
return n > 0;
}
n = pb_skip_field(buf + pos, len - pos, wt);
if (n == 0) return false;
pos += n;
}
return false;
}

// Count occurrences of a length-delimited field in a protobuf message.
static unsigned pb_count_field(const uint8_t *buf, size_t len,
unsigned field_num) {
unsigned count = 0;
size_t pos = 0;
while (pos < len) {
uint64_t tag;
size_t n = pb_read_varint(buf + pos, len - pos, &tag);
if (n == 0) break;
pos += n;
unsigned fn = (unsigned)(tag >> 3);
unsigned wt = (unsigned)(tag & 7);
if (fn == field_num && wt == 2) count++;
n = pb_skip_field(buf + pos, len - pos, wt);
if (n == 0) break;
pos += n;
}
return count;
}

bool tron_parseTransfer(const uint8_t *raw_data, size_t raw_data_len,
TronParsedTransfer *out) {
memset(out, 0, sizeof(*out));

// Reject if there are multiple contract entries (repeated field 11).
// A malicious payload could hide extra contracts after a benign transfer.
if (pb_count_field(raw_data, raw_data_len, 11) != 1) return false;

// Find the single contract entry
size_t contract_len = 0;
const uint8_t *contract = pb_find_bytes(raw_data, raw_data_len, 11,
&contract_len);
if (!contract) return false;

// Check contract type (field 1): must be 1 (TransferContract)
uint64_t contract_type = 0;
if (!pb_find_varint(contract, contract_len, 1, &contract_type)) return false;
if (contract_type != 1) return false; // not a simple transfer

// Find parameter (field 2) = google.protobuf.Any
size_t any_len = 0;
const uint8_t *any = pb_find_bytes(contract, contract_len, 2, &any_len);
if (!any) return false;

// Find value (field 2 inside Any) = serialized TransferContract
size_t tc_len = 0;
const uint8_t *tc = pb_find_bytes(any, any_len, 2, &tc_len);
if (!tc) return false;

// Parse TransferContract: field 2 = to_address (bytes, 21)
size_t addr_len = 0;
const uint8_t *to_addr = pb_find_bytes(tc, tc_len, 2, &addr_len);
if (!to_addr || addr_len != 21) return false;
memcpy(out->to_address, to_addr, 21);

// Parse TransferContract: field 3 = amount (varint, signed zigzag or plain)
uint64_t amount_raw = 0;
if (!pb_find_varint(tc, tc_len, 3, &amount_raw)) return false;
out->amount = (int64_t)amount_raw;

out->valid = true;
return true;
}

/**
* Sign a TRON transaction with secp256k1
*/
Expand Down
Loading