What changed, and why it matters
This commit adds support for including a text 'memo' field on Tron cryptocurrency swap transactions. The memo is read from an incoming transaction and later copied into the raw transaction data when building a new transaction. There is no obvious security bug in the diff itself, but the change touches how transaction data is parsed and reconstructed, which is a sensitive area in hardware wallet firmware.
Review the full Tron swap flow to ensure memo length is bounded before signing, that memo bytes are included in the transaction hash the user approves on device, and that `String::from_utf8_lossy` fallback cannot cause a mismatch between displayed memo and signed raw data. Consider adding unit tests for non-UTF-8 and oversized memo inputs.
Security signals we found
New user-controlled data field (memo) is copied into transaction raw_data
Use of String::from_utf8_lossy on raw blockchain bytes may silently mutate data
Transaction parsing/building logic in hardware wallet signing path is modified
No input length validation for memo is visible in the diff
Evidence from the diff
The patch extends the WrappedTron struct with a memo: String field. When parsing an existing Tron transaction, it now copies raw.data (arbitrary bytes) into instance.memo using String::from_utf8_lossy, which replaces invalid UTF-8 sequences with the Unicode replacement character. When constructing a new transaction from a swap request, if tx.memo is non-empty it writes tx.memo.as_bytes() into raw.data. The change is small and appears to be a feature addition for swap memos. It does not, on its own, introduce a clear vulnerability such as buffer overflow, injection, or unchecked parsing, but it does add a new data path that could affect transaction hashing and signing if the memo bytes influence the serialized transaction.
Changed components
rust/apps/tron/src/transaction/wrapped_tron.rsTron transaction parsing and constructionKeystone 3 firmware Tron swap flowInspect captured patch +14 / −1
diff --git a/rust/apps/tron/src/transaction/wrapped_tron.rs b/rust/apps/tron/src/transaction/wrapped_tron.rs
index c4cd465..dc00cb7 100644
--- a/rust/apps/tron/src/transaction/wrapped_tron.rs
+++ b/rust/apps/tron/src/transaction/wrapped_tron.rs
@@ -37,6 +37,7 @@ pub struct WrappedTron {
pub(crate) token_short_name: Option<String>,
pub(crate) divider: f64,
pub(crate) fee_limit: u64,
+ pub(crate) memo: String,
}
#[macro_export]
@@ -72,10 +73,12 @@ impl WrappedTron {
divider: DIVIDER,
token_short_name: None,
fee_limit: 0,
+ memo: String::new(),
};
if let Some(raw) = &instance.tron_tx.raw_data {
instance.fee_limit = raw.fee_limit as u64;
+ instance.memo = String::from_utf8_lossy(&raw.data).to_string();
if let Some(contract) = raw.contract.get(0) {
use crate::pb::protocol::transaction::contract::ContractType;
let c_type = ContractType::from_i32(contract.r#type)
@@ -387,7 +390,7 @@ impl WrappedTron {
token_short_name = Some(value.token_short_name);
divider = 10u64.pow(value.decimals as u32) as f64;
}
- let tron_tx = if tx.contract_address.is_empty() {
+ let mut tron_tx = if tx.contract_address.is_empty() {
Self::build_transfer_tx(tx)
} else {
Self::generate_trc20_tx(tx)
@@ -397,6 +400,13 @@ impl WrappedTron {
} else {
0
};
+
+ if !tx.memo.is_empty() {
+ if let Some(ref mut raw) = tron_tx.raw_data {
+ raw.data = tx.memo.as_bytes().to_vec();
+ }
+ }
+
Ok(Self {
hd_path: content.hd_path,
extended_pubkey: context.extended_public_key.to_string(),
@@ -410,6 +420,7 @@ impl WrappedTron {
divider,
token_short_name,
fee_limit,
+ memo: tx.memo.clone(),
})
}
_ => Err(TronError::InvalidRawTxCryptoBytes(
@@ -541,6 +552,7 @@ mod tests {
token_short_name: None,
divider: 1.0,
fee_limit: 0,
+ memo: String::new(),
};
assert!(tx_no_raw.signature_hash().is_err());
}
@@ -592,6 +604,7 @@ mod tests {
token_short_name: None,
divider: 1.0,
fee_limit: 0,
+ memo: String::new(),
};
let result = tx.signature_hash();
assert!(result.is_err());
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.