feat(nufi-tron): update nufi-tron support
What changed, and why it matters
This commit adds a new way for the Keystone hardware wallet to handle Tron (TRX) transactions sent as plain JSON sign requests, alongside the older Keystone-specific format. It introduces code that parses JSON, derives the sender address from the seed, checks it matches the 'from' field, and signs. The change is a feature addition, not a clearly labeled security fix. There are no obvious catastrophic bugs in the diff, but the new path adds complexity and a few places where input validation could be tightened.
Treat as a feature commit requiring normal security review rather than an emergency patch. Review the new JSON parsing path for malformed input handling, ensure base58check decoding failures cannot be confused with valid addresses, verify the address-derivation check cannot be bypassed, and confirm the legacy Keystone Tron handler remains safe if still reachable via test commands or other code paths.
Security signals we found
New transaction parsing path from untrusted JSON input (serde_json::Value then protoc::TronTx)
Address-ownership check added before signing (derived address vs. JSON 'from' field)
Use of keccak256 with slicing `digest[12..]` to form 20-byte TRON address
Default derivation path fallback to `m/44'/195'/0'/0/0` when request omits path
Legacy Keystone Tron path left intact but no longer used by UI
No explicit bounds/length checks visible on JSON string fields or base58 inputs in the diff
No commit message or vendor reference indicating this is a security fix
Evidence from the diff
The patch implements TronSignRequest support (UR type tron-sign-request) in the Keystone 3 firmware. It adds from_json_bytes to WrappedTron, three new Rust FFI entry points (tron_check_sign_request, tron_parse_sign_request, tron_sign_request), and wires them into the C UI for TRX. The signing function derives the TRON address from seed + BIP32 path using keccak256 over the uncompressed public key (minus 0x04 prefix) and compares it to base58check-decoded tx.from, returning TronError::NoMyInputs on mismatch. The UI switches from the legacy tron_*_keystone APIs to the new standard-request APIs. The commit also adds serde/serde_json dependencies and unit tests.
Changed components
rust/apps/tron/src/lib.rsrust/apps/tron/src/transaction/wrapped_tron.rsrust/apps/tron/src/utils.rsrust/rust_c/src/tron/mod.rsrust/rust_c/src/common/ur.rsrust/rust_c/src/common/ur_ext.rssrc/ui/gui_chain/multi/web3/gui_trx.csrc/ui/gui_chain/gui_chain.cInspect captured patch +571 / −17
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index c35203c..49caba2 100644
--- a/rust/Cargo.lock
+++ b/rust/Cargo.lock
@@ -415,6 +415,8 @@ dependencies = [
"prost",
"prost-build",
"prost-types",
+ "serde",
+ "serde_json",
"thiserror-core",
"ur-registry",
]
diff --git a/rust/apps/tron/Cargo.toml b/rust/apps/tron/Cargo.toml
index 5274608..beb1503 100644
--- a/rust/apps/tron/Cargo.toml
+++ b/rust/apps/tron/Cargo.toml
@@ -18,6 +18,8 @@ bitcoin = { workspace = true }
cryptoxide = { workspace = true }
ur-registry = { workspace = true }
thiserror = { workspace = true }
+serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] }
+serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
[dev-dependencies]
keystore = { workspace = true, features = ["multi_coins"] }
diff --git a/rust/apps/tron/src/lib.rs b/rust/apps/tron/src/lib.rs
index 7f0f023..232e747 100644
--- a/rust/apps/tron/src/lib.rs
+++ b/rust/apps/tron/src/lib.rs
@@ -6,8 +6,10 @@ extern crate core;
#[macro_use]
extern crate std;
-use crate::errors::Result;
+use crate::errors::{Result, TronError};
+use crate::utils::base58check_to_u8_slice;
use alloc::string::String;
+use alloc::vec;
use ur_registry::pb::protoc;
@@ -20,7 +22,11 @@ mod utils;
pub use crate::address::get_address;
pub use crate::transaction::parser::{DetailTx, OverviewTx, ParsedTx, TxParser};
use crate::transaction::wrapped_tron::WrappedTron;
+use crate::utils::keccak256;
+use alloc::string::ToString;
use app_utils::keystone;
+use core::str::FromStr;
+use keystore::algorithms::secp256k1;
use transaction::checker::TxChecker;
use transaction::signer::Signer;
@@ -43,6 +49,55 @@ pub fn check_raw_tx(raw_tx: protoc::Payload, context: keystone::ParseContext) ->
tx_data.check(&context)
}
+pub fn sign_tx_request(json_bytes: &[u8], hd_path: &String, seed: &[u8]) -> errors::Result<String> {
+ let tx = WrappedTron::from_json_bytes(json_bytes, hd_path.clone())?;
+
+ let pubkey = secp256k1::get_public_key_by_seed(seed, hd_path)
+ .map_err(|e| TronError::KeystoreError(e.to_string()))?;
+
+ let derived_raw_address = {
+ // Get uncompressed public key (65 bytes), remove 0x04 prefix, keep 64 bytes
+ let uncompressed = pubkey.serialize_uncompressed();
+ let pubkey_hash_input = &uncompressed[1..65];
+
+ let digest = keccak256(pubkey_hash_input);
+
+ // Construct TRON address bytes: 0x41 + last 20 bytes of Keccak256 hash
+ let mut raw = vec![0x41u8];
+ raw.extend_from_slice(&digest[12..]);
+ raw
+ };
+ let json_from_bytes = base58check_to_u8_slice(tx.from.clone())?;
+
+ if derived_raw_address != json_from_bytes {
+ return Err(TronError::NoMyInputs);
+ }
+
+ let (signed_hex, _) = tx.sign(seed)?;
+ Ok(signed_hex)
+}
+
+pub fn parse_tx_request(json_bytes: &[u8], path: &String) -> errors::Result<ParsedTx> {
+ let tx = WrappedTron::from_json_bytes(json_bytes, path.clone())?;
+ let parsed_tx = tx.parse()?;
+ Ok(parsed_tx)
+}
+
+pub fn check_tx_request(
+ sign_data: &[u8],
+ path: &str,
+ master_fingerprint: bitcoin::bip32::Fingerprint,
+ xpub: &str,
+) -> Result<()> {
+ let mut tx_data = WrappedTron::from_json_bytes(sign_data, path.to_string())?;
+ tx_data.extended_pubkey = xpub.to_string();
+
+ let extended_pubkey = bitcoin::bip32::Xpub::from_str(xpub)
+ .map_err(|_| errors::TronError::InvalidParseContext(String::from("invalid xpub")))?;
+ let context = keystone::ParseContext::new(master_fingerprint, extended_pubkey);
+ tx_data.check(&context)
+}
+
#[cfg(test)]
mod test {
use super::*;
@@ -100,4 +155,148 @@ mod test {
let result = check_raw_tx(payload, context);
assert!(result.is_ok());
}
+
+ // Helper function: compute TRON address from seed and path, used for test data construction
+ fn compute_address_from_seed(seed: &[u8], path: &String) -> String {
+ let pubkey = secp256k1::get_public_key_by_seed(seed, path).unwrap();
+ let uncompressed = pubkey.serialize_uncompressed();
+ let hash = keccak256(&uncompressed[1..65]);
+ let mut address_bytes = [0u8; 21];
+ address_bytes[0] = 0x41;
+ address_bytes[1..].copy_from_slice(&hash[12..]);
+ bitcoin::base58::encode_check(&address_bytes)
+ }
+
+ #[test]
+ fn test_sign_tx_request_success() {
+ let path = "m/44'/195'/0'/0/0".to_string();
+ let seed = hex::decode("5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4").unwrap();
+
+ let pubkey = secp256k1::get_public_key_by_seed(&seed, &path).unwrap();
+ let uncompressed = pubkey.serialize_uncompressed();
+ let digest = keccak256(&uncompressed[1..65]);
+ let mut raw = vec![0x41u8];
+ raw.extend_from_slice(&digest[12..]);
+ let correct_address = compute_address_from_seed(&seed, &path);
+
+ let json_str = format!(
+ r#"{{
+ "token": "TRX",
+ "contract_address": "",
+ "from": "{}",
+ "to": "{}",
+ "memo": "Test Transaction",
+ "value": "1000000",
+ "latest_block": {{
+ "hash": "000000000001e240dec2860d5e1687299b8f269d09ceea82e7b96408dab58bd2",
+ "number": 123456,
+ "timestamp": 1670000000
+ }},
+ "override": {{
+ "token_short_name": "TRX",
+ "token_full_name": "tron",
+ "decimals": 6
+ }},
+ "fee": 1000000
+ }}"#,
+ correct_address, correct_address
+ );
+
+ let result = sign_tx_request(json_str.as_bytes(), &path, &seed);
+ if let Err(ref e) = result {
+ std::println!("Sign Error Details: {:?}", e);
+ }
+
+ assert!(
+ result.is_ok(),
+ "Sign should succeed when address matches: {:?}",
+ result.err()
+ );
+ }
+
+ #[test]
+ fn test_sign_tx_request_address_mismatch() {
+ let path = "m/44'/195'/0'/0/0".to_string();
+ let seed = hex::decode("5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4").unwrap();
+
+ let json_str = r#"{"to":"TKCsXtfKfH2d6aEaQCctybDC9uaA3MSj2h","from":"TWrongAddressHash12345678901234567890","value":"1000000"}"#;
+
+ let result = sign_tx_request(json_str.as_bytes(), &path, &seed);
+
+ assert!(result.is_err());
+ assert!(matches!(result.unwrap_err(), TronError::NoMyInputs));
+ }
+
+ #[test]
+ fn test_parse_tx_request() {
+ let path = "m/44'/195'/0'/0/0".to_string();
+ let json_str = r#"{
+ "token": "TRX",
+ "contract_address": "",
+ "from": "TXhtYr8nmgiSp3dY3cSfiKBjed3zN8teHS",
+ "to": "TKCsXtfKfH2d6aEaQCctybDC9uaA3MSj2h",
+ "memo": "Test Transaction",
+ "value": "1000000",
+ "latest_block": {
+ "hash": "000000000001e240dec2860d5e1687299b8f269d09ceea82e7b96408dab58bd2",
+ "number": 123456,
+ "timestamp": 1670000000
+ },
+ "override": {
+ "token_short_name": "TRX",
+ "token_full_name": "tron",
+ "decimals": 6
+ },
+ "fee": 1000000
+}"#;
+
+ let result = parse_tx_request(json_str.as_bytes(), &path);
+ match result {
+ Ok(_) => (),
+ Err(e) => panic!("Transaction parse Failed with error: {:?}", e),
+ }
+ assert!(result.is_ok());
+ let parsed = result.unwrap();
+ assert_eq!(parsed.overview.value, "1 TRX");
+ }
+
+ #[test]
+ fn test_check_tx_request() {
+ let path = "m/44'/195'/0'/0/0";
+ let xpub = "xpub6C3ndD75jvoARyqUBTvrsMZaprs2ZRF84kRTt5r9oxKQXn5oFChRRgrP2J8QhykhKACBLF2HxwAh4wccFqFsuJUBBcwyvkyqfzJU5gfn5pY";
+ let master_fingerprint = bitcoin::bip32::Fingerprint::from_str("73c5da0a").unwrap();
+
+ let correct_address = get_address(path.to_string(), &xpub.to_string()).unwrap();
+
+ let json_str = format!(
+ r#"{{
+ "token": "TRX",
+ "contract_address": "",
+ "xfp": "73c5da0a",
+ "memo": "Test Transaction",
+ "from": "{}",
+ "to": "TKCsXtfKfH2d6aEaQCctybDC9uaA3MSj2h",
+ "value": "1000000",
+ "latest_block": {{
+ "hash": "000000000001e240dec2860d5e1687299b8f269d09ceea82e7b96408dab58bd2",
+ "number": 123456,
+ "timestamp": 1670000000
+ }},
+ "override": {{
+ "token_short_name": "TRX",
+ "token_full_name": "tron",
+ "decimals": 6
+ }},
+ "fee": 1000000
+ }}"#,
+ correct_address
+ );
+
+ let result = check_tx_request(json_str.as_bytes(), path, master_fingerprint, xpub);
+
+ match result {
+ Ok(_) => (),
+ Err(e) => panic!("Transaction Check Failed with error: {:?}", e),
+ }
+ }
}
diff --git a/rust/apps/tron/src/transaction/wrapped_tron.rs b/rust/apps/tron/src/transaction/wrapped_tron.rs
index 7efd930..a0eb1c9 100644
--- a/rust/apps/tron/src/transaction/wrapped_tron.rs
+++ b/rust/apps/tron/src/transaction/wrapped_tron.rs
@@ -55,6 +55,47 @@ macro_rules! derivation_account_path {
}
impl WrappedTron {
+ pub fn from_json_bytes(json_bytes: &[u8], path: String) -> Result<Self> {
+ let json_str = core::str::from_utf8(json_bytes)
+ .map_err(|_| TronError::InvalidRawTxCryptoBytes("Invalid UTF-8".to_string()))?;
+
+ let temp_val: serde_json::Value = serde_json::from_str(json_str)
+ .map_err(|e| TronError::InvalidRawTxCryptoBytes(e.to_string()))?;
+
+ let xfp = temp_val["xfp"].as_str().unwrap_or("").to_string();
+
+ let tx_data: protoc::TronTx = serde_json::from_str(json_str)
+ .map_err(|e| TronError::InvalidRawTxCryptoBytes(e.to_string()))?;
+
+ let mut token_short_name = None;
+ let mut divider = DIVIDER;
+ if let Some(ov) = &tx_data.r#override {
+ token_short_name = Some(ov.token_short_name.clone());
+ divider = 10u64.pow(ov.decimals as u32) as f64;
+ }
+
+ // use the existing transaction construction logic
+ let tron_tx: Transaction = if tx_data.contract_address.is_empty() {
+ Self::build_transfer_tx(&tx_data)?
+ } else {
+ Self::generate_trc20_tx(&tx_data)?
+ };
+
+ Ok(Self {
+ hd_path: path,
+ extended_pubkey: "".to_string(), // leave empty when no context
+ tron_tx,
+ xfp,
+ token: tx_data.token,
+ contract_address: tx_data.contract_address,
+ from: tx_data.from,
+ to: tx_data.to,
+ value: tx_data.value,
+ divider,
+ token_short_name,
+ })
+ }
+
pub fn check_input(&self, context: &keystone::ParseContext) -> Result<()> {
// check master fingerprint
if self.xfp.to_uppercase() != hex::encode(context.master_fingerprint).to_uppercase() {
@@ -345,6 +386,60 @@ mod tests {
use alloc::string::ToString;
use bitcoin::bip32::Fingerprint;
use core::str::FromStr;
+ use serde_json::json;
+
+ #[test]
+ fn test_from_json_bytes_success() {
+ let tron_tx_json = json!({
+ "token": "TRX",
+ "contract_address": "",
+ "from": "TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH",
+ "to": "TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH",
+ "memo": "Test Transaction",
+ "value": "1000000",
+ "latest_block": {
+ "hash": "000000000001e240dec2860d5e1687299b8f269d09ceea82e7b96408dab58bd2",
+ "number": 123456,
+ "timestamp": 1670000000
+ },
+ "override": {
+ "token_short_name": "TRX",
+ "token_full_name": "tron",
+ "decimals": 6
+ },
+ "fee": 1000000
+ });
+ let json_bytes = serde_json::to_vec(&tron_tx_json).unwrap();
+ let path = "m/44'/195'/0'/0/0".to_string();
+ let result = WrappedTron::from_json_bytes(&json_bytes, path.clone());
+ // println!("RAW ADDRESS FROM JSON: [{}]", result.as_ref().map(|tx| tx.from.clone()).unwrap_or("None".to_string()));
+ if let Err(e) = &result {
+ std::eprintln!("Error detail: {:?}", e);
+ }
+ assert!(result.is_ok());
+ let tx = result.unwrap();
+ assert_eq!(tx.hd_path, path);
+ assert_eq!(tx.token, "TRX");
+ assert_eq!(tx.value, "1000000");
+ assert_eq!(tx.from, "TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH");
+ assert_eq!(tx.to, "TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH");
+ }
+
+ #[test]
+ fn test_from_json_bytes_invalid_utf8() {
+ let invalid_bytes = vec![0xFF, 0xFF, 0xFF];
+ let path = "m/44'/195'/0'/0/0".to_string();
+ let result = WrappedTron::from_json_bytes(&invalid_bytes, path);
+ assert!(result.is_err());
+ }
+
+ #[test]
+ fn test_from_json_bytes_invalid_json() {
+ let invalid_json = b"not a json";
+ let path = "m/44'/195'/0'/0/0".to_string();
+ let result = WrappedTron::from_json_bytes(invalid_json, path);
+ assert!(result.is_err());
+ }
#[test]
fn test_signature_hash() {
@@ -353,6 +448,7 @@ mod tests {
let payload = prepare_payload(hex);
let context = prepare_parse_context(pubkey_str);
let tx = WrappedTron::from_payload(payload, &context).unwrap();
+ println!("Valid Address from Payload: {}", tx.from);
let hash = tx.signature_hash().unwrap();
assert_eq!(32, hash.len());
}
diff --git a/rust/apps/tron/src/utils.rs b/rust/apps/tron/src/utils.rs
index 1d3f67c..5d4199a 100644
--- a/rust/apps/tron/src/utils.rs
+++ b/rust/apps/tron/src/utils.rs
@@ -2,8 +2,19 @@ use crate::errors::Result;
use alloc::string::String;
use alloc::vec::Vec;
use bitcoin::base58;
+use cryptoxide::digest::Digest;
+use cryptoxide::sha3::Keccak256;
pub fn base58check_to_u8_slice(input: String) -> Result<Vec<u8>> {
let result = base58::decode_check(input.as_str())?;
Ok(result)
}
+
+pub fn keccak256(input: &[u8]) -> [u8; 32] {
+ let mut hasher = Keccak256::new();
+ hasher.input(input);
+ let mut output = [0u8; 32];
+ hasher.result(&mut output);
+ output
+}
+
diff --git a/rust/rust_c/src/common/ur.rs b/rust/rust_c/src/common/ur.rs
index b964fe5..92b01ea 100644
--- a/rust/rust_c/src/common/ur.rs
+++ b/rust/rust_c/src/common/ur.rs
@@ -71,6 +71,10 @@ use ur_registry::sui::sui_sign_request::SuiSignRequest;
use ur_registry::ton::ton_sign_request::TonSignRequest;
#[cfg(feature = "zcash")]
use ur_registry::zcash::zcash_pczt::ZcashPczt;
+#[cfg(feature = "tron")]
+use ur_registry::tron::tron_sign_request::TronSignRequest;
+#[cfg(feature = "tron")]
+use ur_registry::tron::tron_signature::TronSignature;
use super::errors::{ErrorCodes, RustCError};
use super::free::Free;
@@ -314,6 +318,8 @@ pub enum QRCodeType {
EthBatchSignRequest,
#[cfg(feature = "solana")]
SolSignRequest,
+ #[cfg(feature = "tron")]
+ TronSignRequest,
#[cfg(feature = "near")]
NearSignRequest,
#[cfg(feature = "cardano")]
@@ -383,6 +389,8 @@ impl QRCodeType {
InnerURType::EthBatchSignRequest(_) => Ok(QRCodeType::EthBatchSignRequest),
#[cfg(feature = "solana")]
InnerURType::SolSignRequest(_) => Ok(QRCodeType::SolSignRequest),
+ #[cfg(feature = "tron")]
+ InnerURType::TronSignRequest(_) => Ok(QRCodeType::TronSignRequest),
#[cfg(feature = "near")]
InnerURType::NearSignRequest(_) => Ok(QRCodeType::NearSignRequest),
#[cfg(feature = "cosmos")]
@@ -524,6 +532,10 @@ unsafe fn free_ur(ur_type: &QRCodeType, data: PtrUR) {
QRCodeType::SolSignRequest => {
free_ptr_with_type!(data, SolSignRequest);
}
+ #[cfg(feature = "tron")]
+ QRCodeType::TronSignRequest => {
+ free_ptr_with_type!(data, TronSignRequest);
+ }
#[cfg(feature = "near")]
QRCodeType::NearSignRequest => {
free_ptr_with_type!(data, NearSignRequest);
@@ -738,6 +750,8 @@ pub fn decode_ur(ur: String) -> URParseResult {
QRCodeType::EthBatchSignRequest => _decode_ur::<EthBatchSignRequest>(ur, ur_type),
#[cfg(feature = "solana")]
QRCodeType::SolSignRequest => _decode_ur::<SolSignRequest>(ur, ur_type),
+ #[cfg(feature = "tron")]
+ QRCodeType::TronSignRequest => _decode_ur::<TronSignRequest>(ur, ur_type),
#[cfg(feature = "near")]
QRCodeType::NearSignRequest => _decode_ur::<NearSignRequest>(ur, ur_type),
#[cfg(feature = "cardano")]
@@ -842,6 +856,8 @@ fn receive_ur(ur: String, decoder: &mut KeystoneURDecoder) -> URParseMultiResult
QRCodeType::EthBatchSignRequest => _receive_ur::<EthBatchSignRequest>(ur, ur_type, decoder),
#[cfg(feature = "solana")]
QRCodeType::SolSignRequest => _receive_ur::<SolSignRequest>(ur, ur_type, decoder),
+ #[cfg(feature = "tron")]
+ QRCodeType::TronSignRequest => _receive_ur::<TronSignRequest>(ur, ur_type, decoder),
#[cfg(feature = "near")]
QRCodeType::NearSignRequest => _receive_ur::<NearSignRequest>(ur, ur_type, decoder),
#[cfg(feature = "cardano")]
diff --git a/rust/rust_c/src/common/ur_ext.rs b/rust/rust_c/src/common/ur_ext.rs
index 75d5af4..d825f86 100644
--- a/rust/rust_c/src/common/ur_ext.rs
+++ b/rust/rust_c/src/common/ur_ext.rs
@@ -55,6 +55,8 @@ use ur_registry::pb::protoc;
use ur_registry::pb::protoc::Base;
#[cfg(feature = "solana")]
use ur_registry::solana::sol_sign_request::SolSignRequest;
+#[cfg(feature = "tron")]
+use ur_registry::tron::tron_sign_request::TronSignRequest;
#[cfg(feature = "stellar")]
use ur_registry::stellar::stellar_sign_request::{SignType as StellarSignType, StellarSignRequest};
#[cfg(feature = "sui")]
@@ -228,6 +230,13 @@ impl InferViewType for AvaxSignRequest {
}
}
+#[cfg(feature = "tron")]
+impl InferViewType for TronSignRequest {
+ fn infer(&self) -> Result<ViewType, URError> {
+ Ok(ViewType::TronTx)
+ }
+}
+
fn get_view_type_from_keystone(bytes: Vec<u8>) -> Result<ViewType, URError> {
let unzip_data = unzip(bytes)
.map_err(|_| URError::NotSupportURTypeError("bytes can not unzip".to_string()))?;
diff --git a/rust/rust_c/src/test_cmd/general_test_cmd.rs b/rust/rust_c/src/test_cmd/general_test_cmd.rs
index e1b6208..9d787a9 100644
--- a/rust/rust_c/src/test_cmd/general_test_cmd.rs
+++ b/rust/rust_c/src/test_cmd/general_test_cmd.rs
@@ -62,6 +62,19 @@ pub unsafe extern "C" fn test_get_tron_keystone_bytes() -> *mut URParseResult {
URParseResult::single(ViewType::TronTx, QRCodeType::Bytes, bytes).c_ptr()
}
+#[no_mangle]
+pub unsafe extern "C" fn test_get_tron_standard_request_bytes() -> *mut URParseResult {
+ let hex_str = "a30258d47b2266726f6d223a22545868745972386e6d6769537033645933635366694b426a6564337a4e3874654853222c22746f223a22544b43735874664b6648326436614561514363747962444339756141334d536a3268222c2276616c7565223a2231303030303030227d03d90130a1018a182cf518c3f51800f51800f51800f5";
+
+ let bytes = Bytes::new(hex::decode(hex_str).unwrap());
+
+ URParseResult::single(
+ ViewType::TronTx,
+ QRCodeType::TronSignRequest,
+ bytes
+ ).c_ptr()
+}
+
#[no_mangle]
pub unsafe extern "C" fn test_get_tron_check_failed_keystone_bytes() -> *mut URParseResult {
let bytes = Bytes::new(hex::decode("1f8b08000000000000030dcfbd4ac34000c071220ea58bdaa9742a41a84bc87d27270e9ab61890c4268d54bb5dee2e26607b508b4a9fa26fe01bf8b128f812be82b383b8161703ffe9bffd1a5bad9d64d1374a77470bb334d2dc7436567d1b1e96540920ec6fabb99da5e7716b5f4a4e58ae91e36b221d8272ed088ca04399a058f8b2a09075f62297909e0b39edb9a0ce05dde79faf8f0d3868048f56c7ce2e86d3b13abb35833089f4f4be2a97ca04554cd8eaa13c9d5ca9d0b6b3315d8d4c9f5c0e83597837884fe6f309ba0e719494328d5995ce90050fe3e671c17c0ab9d2bc904011a031a502f202e414032e19c60c78be209e409aab1cfa9041e603c204821ad588ddd7f5baddfefd7c7aff03e1cbdbd13f2aab0f710f010000").unwrap());
diff --git a/rust/rust_c/src/tron/mod.rs b/rust/rust_c/src/tron/mod.rs
index a8cce68..402da3d 100644
--- a/rust/rust_c/src/tron/mod.rs
+++ b/rust/rust_c/src/tron/mod.rs
@@ -1,16 +1,118 @@
pub mod structs;
-use crate::common::errors::RustCError;
+use crate::common::errors::{KeystoneError, RustCError};
use crate::common::keystone;
use crate::common::structs::{SimpleResponse, TransactionCheckResult, TransactionParseResult};
use crate::common::types::{PtrBytes, PtrString, PtrT, PtrUR};
-use crate::common::ur::{QRCodeType, UREncodeResult};
+use crate::common::ur::{QRCodeType, UREncodeResult, FRAGMENT_MAX_LENGTH_DEFAULT};
use crate::common::utils::{convert_c_char, recover_c_char};
use crate::extract_array;
use alloc::boxed::Box;
use alloc::slice;
use cty::c_char;
use structs::DisplayTron;
+use alloc::vec::Vec;
+use alloc::string::{ToString, String};
+
+use crate::extract_ptr_with_type;
+use ur_registry::traits::{RegistryItem, To};
+use ur_registry::tron::tron_sign_request::TronSignRequest;
+use ur_registry::tron::tron_signature::TronSignature;
+
+use app_tron::TxParser;
+
+const TRON_DEFAULT_PATH: &str = "m/44'/195'/0'/0/0";
+
+#[no_mangle]
+pub unsafe extern "C" fn tron_check_sign_request(
+ ptr: PtrUR,
+ x_pub: PtrString,
+ master_fingerprint: PtrBytes,
+ length: u32,
+) -> PtrT<TransactionCheckResult> {
+ if length != 4 {
+ return TransactionCheckResult::from(RustCError::InvalidMasterFingerprint).c_ptr();
+ }
+
+ let req = extract_ptr_with_type!(ptr, TronSignRequest);
+ let json_bytes = req.get_sign_data();
+ let path = req
+ .get_derivation_path()
+ .get_path()
+ .unwrap_or_else(|| String::from(TRON_DEFAULT_PATH));
+
+ let xpub = recover_c_char(x_pub);
+
+ let xfp_bytes = extract_array!(master_fingerprint, u8, 4);
+ let mut array = [0u8; 4];
+ array.copy_from_slice(&xfp_bytes);
+ let xfp = bitcoin::bip32::Fingerprint::from(array);
+
+ match app_tron::check_tx_request(&json_bytes, &path, xfp, &xpub) {
+ Ok(_) => TransactionCheckResult::new().c_ptr(),
+ Err(e) => TransactionCheckResult::from(e).c_ptr(),
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn tron_parse_sign_request(
+ ptr: PtrUR,
+) -> *mut TransactionParseResult<DisplayTron> {
+ let req = extract_ptr_with_type!(ptr, TronSignRequest);
+ let json_bytes = req.get_sign_data();
+ let path = req
+ .get_derivation_path()
+ .get_path()
+ .unwrap_or_else(|| String::from(TRON_DEFAULT_PATH));
+
+ app_tron::parse_tx_request(&json_bytes, &path).map_or_else(
+ |e| TransactionParseResult::from(e).c_ptr(),
+ |parsed_tx| {
+ let display_tx = DisplayTron::from(parsed_tx);
+
+ TransactionParseResult::success(Box::into_raw(Box::new(display_tx))).c_ptr()
+ },
+ )
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn tron_sign_request(
+ ptr: PtrUR,
+ seed: PtrBytes,
+ seed_len: u32,
+) -> *mut UREncodeResult {
+ let req = extract_ptr_with_type!(ptr, TronSignRequest);
+ let seed_slice = extract_array!(seed, u8, seed_len as usize);
+
+ let sign_res = (|| -> Result<Vec<u8>, KeystoneError> {
+ let json_bytes = req.get_sign_data();
+ let request_id = req.get_request_id();
+ let path = req
+ .get_derivation_path()
+ .get_path()
+ .unwrap_or_else(|| String::from(TRON_DEFAULT_PATH));
+
+ let signed_tx_hex = app_tron::sign_tx_request(&json_bytes, &path, seed_slice)
+ .map_err(|e| KeystoneError::SignTxFailed(e.to_string()))?;
+
+ let signed_tx_bytes = hex::decode(signed_tx_hex)
+ .map_err(|_| KeystoneError::SignTxFailed("Invalid Hex output".to_string()))?;
+ let sig_obj = TronSignature::new(request_id, signed_tx_bytes);
+ sig_obj
+ .to_bytes()
+ .map_err(|e| KeystoneError::SignTxFailed(e.to_string()))
+ })();
+
+ match sign_res {
+ Ok(data) => UREncodeResult::encode(
+ data,
+ TronSignature::get_registry_type().get_type(),
+ FRAGMENT_MAX_LENGTH_DEFAULT,
+ )
+ .c_ptr(),
+ Err(e) => UREncodeResult::from(e).c_ptr(),
+ }
+}
#[no_mangle]
pub unsafe extern "C" fn tron_check_keystone(
diff --git a/src/ui/gui_chain/gui_chain.c b/src/ui/gui_chain/gui_chain.c
index ad79f99..8d35d3e 100644
--- a/src/ui/gui_chain/gui_chain.c
+++ b/src/ui/gui_chain/gui_chain.c
@@ -36,6 +36,7 @@ bool CheckViewTypeIsAllow(uint8_t viewType)
case REMAPVIEW_ADA_CATALYST:
case REMAPVIEW_APT:
case REMAPVIEW_AVAX:
+ case REMAPVIEW_TRX:
return true;
default:
return false;
diff --git a/src/ui/gui_chain/multi/web3/gui_trx.c b/src/ui/gui_chain/multi/web3/gui_trx.c
index 25b1096..ff6fc2d 100644
--- a/src/ui/gui_chain/multi/web3/gui_trx.c
+++ b/src/ui/gui_chain/multi/web3/gui_trx.c
@@ -27,31 +27,54 @@ void GuiSetTrxUrData(URParseResult *urResult, URParseMultiResult *urMultiResult,
result = NULL; \
}
+// void *GuiGetTrxData(void)
+// {
+// CHECK_FREE_PARSE_RESULT(g_parseResult);
+// uint8_t mfp[4];
+// void *data = g_isMulti ? g_urMultiResult->data : g_urResult->data;
+// QRCodeType urType = g_isMulti ? g_urMultiResult->ur_type : g_urResult->ur_type;
+// char *trxXpub = GetCurrentAccountPublicKey(XPUB_TYPE_TRX);
+// GetMasterFingerPrint(mfp);
+// do {
+// PtrT_TransactionParseResult_DisplayTron parseResult = tron_parse_keystone(data, urType, mfp, sizeof(mfp), trxXpub);
+// CHECK_CHAIN_BREAK(parseResult);
+// g_parseResult = (void *)parseResult;
+// } while (0);
+// return g_parseResult;
+// }
void *GuiGetTrxData(void)
{
CHECK_FREE_PARSE_RESULT(g_parseResult);
- uint8_t mfp[4];
void *data = g_isMulti ? g_urMultiResult->data : g_urResult->data;
- QRCodeType urType = g_isMulti ? g_urMultiResult->ur_type : g_urResult->ur_type;
- char *trxXpub = GetCurrentAccountPublicKey(XPUB_TYPE_TRX);
- GetMasterFingerPrint(mfp);
+
do {
- PtrT_TransactionParseResult_DisplayTron parseResult = tron_parse_keystone(data, urType, mfp, sizeof(mfp), trxXpub);
+ PtrT_TransactionParseResult_DisplayTron parseResult = tron_parse_sign_request(data);
+
CHECK_CHAIN_BREAK(parseResult);
g_parseResult = (void *)parseResult;
} while (0);
+
return g_parseResult;
}
+// PtrT_TransactionCheckResult GuiGetTrxCheckResult(void)
+// {
+// uint8_t mfp[4];
+// void *data = g_isMulti ? g_urMultiResult->data : g_urResult->data;
+// QRCodeType urType = g_isMulti ? g_urMultiResult->ur_type : g_urResult->ur_type;
+// char *trxXpub = GetCurrentAccountPublicKey(XPUB_TYPE_TRX);
+// GetMasterFingerPrint(mfp);
+// return tron_check_keystone(data, urType, mfp, sizeof(mfp), trxXpub);
+// }
PtrT_TransactionCheckResult GuiGetTrxCheckResult(void)
{
uint8_t mfp[4];
void *data = g_isMulti ? g_urMultiResult->data : g_urResult->data;
- QRCodeType urType = g_isMulti ? g_urMultiResult->ur_type : g_urResult->ur_type;
char *trxXpub = GetCurrentAccountPublicKey(XPUB_TYPE_TRX);
GetMasterFingerPrint(mfp);
- return tron_check_keystone(data, urType, mfp, sizeof(mfp), trxXpub);
+ return tron_check_sign_request(data, trxXpub, mfp, sizeof(mfp));
}
+
void FreeTrxMemory(void)
{
CHECK_FREE_UR_RESULT(g_urResult, false);
@@ -107,27 +130,53 @@ void GetTrxToken(void *indata, void *param, uint32_t maxLen)
strcpy_s((char *)indata, maxLen, trx->detail->token);
}
+// UREncodeResult *GuiGetTrxSignQrCodeData(void)
+// {
+// bool enable = IsPreviousLockScreenEnable();
+// SetLockScreen(false);
+// UREncodeResult *encodeResult = NULL;
+// void *data = g_isMulti ? g_urMultiResult->data : g_urResult->data;
+// QRCodeType urType = g_isMulti ? g_urMultiResult->ur_type : g_urResult->ur_type;
+// uint8_t mfp[4];
+// GetMasterFingerPrint(mfp);
+// uint8_t seed[SEED_LEN];
+// do {
+// int ret = GetAccountSeed(GetCurrentAccountIndex(), seed, SecretCacheGetPassword());
+// if (ret != 0) {
+// break;
+// }
+// encodeResult = tron_sign_keystone(data, urType, mfp, sizeof(mfp), GetCurrentAccountPublicKey(XPUB_TYPE_TRX),
+// SOFTWARE_VERSION, seed, GetCurrentAccountSeedLen());
+// CHECK_CHAIN_BREAK(encodeResult);
+// } while (0);
+// memset_s(seed, sizeof(seed), 0, sizeof(seed));
+// ClearSecretCache();
+// SetLockScreen(enable);
+// return encodeResult;
+// }
UREncodeResult *GuiGetTrxSignQrCodeData(void)
{
bool enable = IsPreviousLockScreenEnable();
SetLockScreen(false);
UREncodeResult *encodeResult = NULL;
+
void *data = g_isMulti ? g_urMultiResult->data : g_urResult->data;
- QRCodeType urType = g_isMulti ? g_urMultiResult->ur_type : g_urResult->ur_type;
- uint8_t mfp[4];
- GetMasterFingerPrint(mfp);
- uint8_t seed[SEED_LEN];
+ uint8_t seed[64];
+
do {
int ret = GetAccountSeed(GetCurrentAccountIndex(), seed, SecretCacheGetPassword());
if (ret != 0) {
break;
}
- encodeResult = tron_sign_keystone(data, urType, mfp, sizeof(mfp), GetCurrentAccountPublicKey(XPUB_TYPE_TRX),
- SOFTWARE_VERSION, seed, GetCurrentAccountSeedLen());
+
+ encodeResult = tron_sign_request(data, seed, GetCurrentAccountSeedLen());
+
CHECK_CHAIN_BREAK(encodeResult);
} while (0);
+
memset_s(seed, sizeof(seed), 0, sizeof(seed));
ClearSecretCache();
SetLockScreen(enable);
+
return encodeResult;
}
\ No newline at end of file
diff --git a/test/test_cmd.c b/test/test_cmd.c
index d1b3c4b..a26ee9f 100644
--- a/test/test_cmd.c
+++ b/test/test_cmd.c
@@ -132,6 +132,8 @@ static void RustTestParseBTCKeystone(int argc, char *argv[]);
static void RustTestCheckFailedBTCKeystone(int argc, char *argv[]);
static void RustTestCheckSucceedBCHKeystone(int argc, char *argv[]);
static void RustTestParseLTCKeystone(int argc, char *argv[]);
+static void RustTestParseTronStandard(int argc, char *argv[]);
+static void RustTestSignTronStandard(int argc, char *argv[]);
static void RustTestParseTronKeystone(int argc, char *argv[]);
static void RustTestCheckTronKeystoneSucceed(int argc, char *argv[]);
static void RustTestCheckTronKeystoneFailed(int argc, char *argv[]);
@@ -176,6 +178,8 @@ static void RustTestCheckFailedBTCKeystone(int argc, char *argv[]);
static void RustTestCheckSucceedBCHKeystone(int argc, char *argv[]);
static void RustTestParseLTCKeystone(int argc, char *argv[]);
static void RustTestParseTronKeystone(int argc, char *argv[]);
+static void RustTestParseTronStandard(int argc, char *argv[]);
+static void RustTestSignTronStandard(int argc, char *argv[]);
static void RustTestCheckTronKeystoneSucceed(int argc, char *argv[]);
static void RustTestCheckTronKeystoneFailed(int argc, char *argv[]);
static void RustTestSignTronKeystone(int argc, char *argv[]);
@@ -295,6 +299,8 @@ const static UartTestCmdItem_t g_uartTestCmdTable[] = {
{"rust test check bch succeed", RustTestCheckSucceedBCHKeystone},
{"rust test parse ltc", RustTestParseLTCKeystone},
{"rust test parse tron keystone", RustTestParseTronKeystone},
+ {"rust test parse tron standard request", RustTestParseTronStandard},
+ {"rust test sign tron standard request", RustTestSignTronStandard},
{"rust test check tron keystone succeed:", RustTestCheckTronKeystoneSucceed},
{"rust test check tron keystone failed", RustTestCheckTronKeystoneFailed},
{"rust test sign tron keystone:", RustTestSignTronKeystone},
@@ -1315,6 +1321,54 @@ static void RustTestParseLTCKeystone(int argc, char *argv[])
printf("FreeHeapSize = %d\n", xPortGetFreeHeapSize());
}
+void RustTestParseTronStandard(int argc, char *argv[])
+{
+ printf("--- Test Tron Standard Parse Start ---\r\n");
+
+ URParseResult *ur = test_get_tron_standard_request_bytes();
+ void *ur_ptr = ur->data;
+
+ TransactionParseResult_DisplayTron *result = tron_parse_sign_request(ur_ptr);
+
+ printf("Error Code: %d\r\n", result->error_code);
+ if (result->error_code == 0) {
+ printf("From: %s\r\n", result->data->overview->from);
+ printf("To: %s\r\n", result->data->overview->to);
+ printf("Value: %s\r\n", result->data->overview->value);
+ printf("Method: %s\r\n", result->data->overview->method);
+ } else {
+ printf("Error Message: %s\r\n", result->error_message);
+ }
+
+ free_ur_parse_result(ur);
+ free_TransactionParseResult_DisplayTron(result);
+ printf("--- Test Tron Standard Parse End ---\r\n");
+}
+
+void RustTestSignTronStandard(int argc, char *argv[])
+{
+ printf("--- Test Tron Standard Sign Start ---\r\n");
+ int32_t index = 0;
+
+ URParseResult *ur = test_get_tron_standard_request_bytes();
+ void *ur_ptr = ur->data;
+
+ uint8_t seed[64];
+ GetAccountSeed(index, seed, "123456");
+
+ UREncodeResult *result = tron_sign_request(ur_ptr, seed, sizeof(seed));
+
+ if (result->error_code == 0) {
+ printf("Signature UR: %s\r\n", result->data);
+ } else {
+ printf("Sign Failed: %s\r\n", result->error_message);
+ }
+
+ free_ur_parse_result(ur);
+ free_ur_encode_result(result);
+ printf("--- Test Tron Standard Sign End ---\r\n");
+}
+
static void RustTestParseTronKeystone(int argc, char *argv[])
{
printf("RustTestParseTronKeystone 11\r\n");
Why this scored 35/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.