Skip to content
Open
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
7 changes: 6 additions & 1 deletion crates/gem_tron/src/models/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ impl TriggerSmartContractData {
let Ok(payload) = serde_json::from_slice::<TronPayload>(data) else {
return Ok(None);
};
let Some(raw_data_value) = payload.transaction.other.get("raw_data") else {
let raw_data_value = payload
.transaction
.other
.get("raw_data")
.or_else(|| payload.transaction.other.get("transaction").and_then(|t| t.get("raw_data")));
let Some(raw_data_value) = raw_data_value else {
return Ok(None);
};
let Ok(raw_data) = serde_json::from_value::<TransactionData>(raw_data_value.clone()) else {
Expand Down
112 changes: 62 additions & 50 deletions crates/gem_tron/src/signer/chain_signer.rs
Original file line number Diff line number Diff line change
@@ -1,69 +1,81 @@
use super::transaction::{TronPayload, TronTransaction};
use gem_hash::sha2::sha256;
use primitives::{ChainSigner, SignerError, TransactionLoadInput, TransferDataOutputAction, TransferDataOutputType, hex::decode_hex};
use primitives::{ChainSigner, SignerError, TransactionLoadInput, TransferDataOutputType, hex::decode_hex};
use serde_json::Value;
use signer::{SignatureScheme, Signer};

struct PayloadMetadata {
enum PayloadFormat {
V1,
Legacy,
}

struct TronPayload {
payload: Value,
format: PayloadFormat,
output_type: TransferDataOutputType,
output_action: TransferDataOutputAction,
}

pub struct TronChainSigner;
impl TronPayload {
fn parse(input: &TransactionLoadInput) -> Result<Self, SignerError> {
let extra = input.get_data_extra().map_err(SignerError::invalid_input)?;
let data = extra.data.as_ref().ok_or_else(|| SignerError::invalid_input("Missing transaction data"))?;
let payload: Value = serde_json::from_slice(data)?;

impl ChainSigner for TronChainSigner {
fn sign_data(&self, input: &TransactionLoadInput, private_key: &[u8]) -> Result<String, SignerError> {
sign_data(input, private_key)
}
}
let transaction = payload
.get("transaction")
.ok_or_else(|| SignerError::invalid_input("Missing transaction in Tron payload"))?;

fn sign_data(input: &TransactionLoadInput, private_key: &[u8]) -> Result<String, SignerError> {
let (transaction, metadata) = get_transaction(input)?;
let raw_data_hex = transaction
.raw_data_hex
.as_deref()
.ok_or_else(|| SignerError::invalid_input("Missing raw_data_hex in Tron transaction payload"))?;
let raw_bytes = decode_hex(raw_data_hex)?;
let digest = sha256(&raw_bytes);
let signature = Signer::sign_digest(SignatureScheme::Secp256k1, digest.to_vec(), private_key.to_vec()).map_err(|e| SignerError::signing_error(e.to_string()))?;
let signature_hex = hex::encode(signature);
let format = if transaction.get("raw_data_hex").is_some() {
PayloadFormat::V1
} else if transaction.get("transaction").and_then(|t| t.get("raw_data_hex")).is_some() {
PayloadFormat::Legacy
} else {
return Err(SignerError::invalid_input("Missing raw_data_hex in Tron transaction payload"));
};

match metadata.output_type {
TransferDataOutputType::Signature => Ok(signature_hex),
TransferDataOutputType::EncodedTransaction => {
let payload = apply_signature(metadata.payload, &signature_hex)?;
let result_payload = match metadata.output_action {
TransferDataOutputAction::Send => payload
.get("transaction")
.cloned()
.ok_or_else(|| SignerError::invalid_input("Missing transaction object for Tron broadcast"))?,
TransferDataOutputAction::Sign => payload,
};
Ok(Self {
payload,
format,
output_type: extra.output_type.clone(),
})
}

Ok(serde_json::to_string(&result_payload)?)
fn transaction(&self) -> &Value {
let transaction = &self.payload["transaction"];
match self.format {
PayloadFormat::V1 => transaction,
PayloadFormat::Legacy => &transaction["transaction"],
}
}
}

fn get_transaction(input: &TransactionLoadInput) -> Result<(TronTransaction, PayloadMetadata), SignerError> {
let extra = input.get_data_extra().map_err(SignerError::invalid_input)?;
let data = extra.data.as_ref().ok_or_else(|| SignerError::invalid_input("Missing transaction data"))?;
fn raw_data_hex(&self) -> Result<&str, SignerError> {
self.transaction()["raw_data_hex"]
.as_str()
.ok_or_else(|| SignerError::invalid_input("raw_data_hex must be a string"))
}

let payload: TronPayload = serde_json::from_slice(data).map_err(|_| SignerError::invalid_input("Invalid Tron transaction payload"))?;
let transaction = payload.transaction.clone();
let payload_value = serde_json::to_value(&payload).map_err(|_| SignerError::invalid_input("Invalid Tron transaction payload"))?;
let metadata = PayloadMetadata {
payload: payload_value,
output_type: extra.output_type.clone(),
output_action: extra.output_action.clone(),
};
Ok((transaction, metadata))
fn into_signed(self, signature_hex: &str) -> Result<String, SignerError> {
let mut transaction = self.transaction().clone();
transaction
.as_object_mut()
.ok_or_else(|| SignerError::invalid_input("Transaction is not an object"))?
.insert("signature".to_string(), serde_json::json!([signature_hex]));
serde_json::to_string(&transaction).map_err(Into::into)
}
}

fn apply_signature(payload: Value, signature_hex: &str) -> Result<Value, SignerError> {
let mut payload: TronPayload = serde_json::from_value(payload).map_err(|_| SignerError::invalid_input("Invalid Tron transaction payload"))?;
payload.transaction.signature = Some(vec![signature_hex.to_string()]);
payload.signature = Some(signature_hex.to_string());
Ok(serde_json::to_value(payload)?)
pub struct TronChainSigner;

impl ChainSigner for TronChainSigner {
fn sign_data(&self, input: &TransactionLoadInput, private_key: &[u8]) -> Result<String, SignerError> {
let payload = TronPayload::parse(input)?;
let raw_bytes = decode_hex(payload.raw_data_hex()?)?;
let digest = sha256(&raw_bytes);
let signature = Signer::sign_digest(SignatureScheme::Secp256k1, digest.to_vec(), private_key.to_vec()).map_err(|e| SignerError::signing_error(e.to_string()))?;
let signature_hex = hex::encode(signature);

match payload.output_type {
TransferDataOutputType::Signature => Ok(signature_hex),
TransferDataOutputType::EncodedTransaction => payload.into_signed(&signature_hex),
}
}
}
6 changes: 3 additions & 3 deletions gemstone/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ integration-test:
build-integration-tests:
cargo test --test integration_test --no-run --features reqwest_provider

export ANDROID_HOME := env_var_or_default("ANDROID_HOME", "~/Library/Android/sdk")
export ANDROID_HOME := env("ANDROID_HOME", "~/Library/Android/sdk")
export HOST_ARCH := arch()

install-ndk:
Expand Down Expand Up @@ -54,7 +54,7 @@ export FW_NAME := "Gemstone"
export STATIC_LIB_NAME := "lib" + LIB_NAME + ".a"
export DY_LIB_NAME := if os() == "macos" { "libgemstone.dylib" } else { "libgemstone.so" }

export BUILD_MODE_ENV := env_var_or_default("BUILD_MODE", "debug")
export BUILD_MODE_ENV := env("BUILD_MODE", "debug")
export BUILD_MODE_TARGET := if BUILD_MODE_ENV == "release" { "release" } else { "debug" }
export BUILD_FLAG := if BUILD_MODE_TARGET == "release" { "--release" } else { "" }

Expand All @@ -67,7 +67,7 @@ export TARGET_XC_FW_FOLDER := "target/spm"
export FW_FFI_NAME := FW_NAME + "FFI"
export FW_FFI_FILE := FW_FFI_NAME + ".framework"
export XC_FW_FFI_NAME := FW_FFI_NAME + ".xcframework"
export DEPLOYMENT_TARGET := env_var_or_default("IPHONEOS_DEPLOYMENT_TARGET", "17.0")
export DEPLOYMENT_TARGET := env("IPHONEOS_DEPLOYMENT_TARGET", "17.0")
export LIB_OUTPUT_PATH := TARGET_DIR + "/" + BUILD_MODE_TARGET + "/" + DY_LIB_NAME
build-ios: build-targets bindgen-swift assemble-frameworks xcframework cp-xcframework-source

Expand Down
45 changes: 37 additions & 8 deletions gemstone/src/wallet_connect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,24 +217,53 @@ mod tests {

let signature_payload = TronChainSigner.sign_data(&input, &TEST_PRIVATE_KEY).unwrap();
let value: serde_json::Value = serde_json::from_str(&signature_payload).unwrap();
let signature = value
.get("transaction")
.and_then(|v| v.get("signature"))
.and_then(|v| v.get(0))
.and_then(|v| v.as_str())
.unwrap_or_default()
.to_string();

assert_eq!(
signature,
value["signature"][0].as_str().unwrap(),
"943d286dfd1fb6a2cd31c9af7a6cfd23ee062ec2e0abcf82c7daa0c7bb43ab04458e0e88ebe3a94060122cccc8fb4395e5eb922720327df04ae840139c729a1f00"
);
assert!(value["txID"].as_str().is_some());
assert!(value["raw_data_hex"].as_str().is_some());
assert_eq!(value["visible"], serde_json::Value::Bool(false));

let response = wallet_connect.encode_sign_transaction(Chain::Tron, signature_payload.clone());
let expected: serde_json::Value = serde_json::from_str(include_str!("./test/tron_sign_transaction_response.json")).unwrap();
assert_eq!(response_json(&response), expected);
}

#[test]
fn parse_tron_sign_transaction_nested_and_sign() {
let params = include_str!("./test/tron_sign_transaction_nested.json");
let expected_data: serde_json::Value = serde_json::from_str(params.trim()).unwrap();
let expected_data = expected_data.to_string();

let input = TransactionLoadInput {
input_type: TransactionInputType::Generic(
Asset::from_chain(Chain::Tron),
WalletConnectionSessionAppMetadata::mock(),
TransferDataExtra::mock_encoded_transaction(expected_data.as_bytes().to_vec()),
),
sender_address: "TJoSEwEqt7cT3TUwmEoUYnYs5cZR3xSukM".to_string(),
destination_address: "".to_string(),
value: "0".to_string(),
gas_price: GasPriceType::regular(0),
memo: None,
is_max_value: false,
metadata: TransactionLoadMetadata::mock_tron(),
};

let signature_payload = TronChainSigner.sign_data(&input, &TEST_PRIVATE_KEY).unwrap();
let value: serde_json::Value = serde_json::from_str(&signature_payload).unwrap();

assert_eq!(
value["signature"][0].as_str().unwrap(),
"943d286dfd1fb6a2cd31c9af7a6cfd23ee062ec2e0abcf82c7daa0c7bb43ab04458e0e88ebe3a94060122cccc8fb4395e5eb922720327df04ae840139c729a1f00"
);
assert!(value["txID"].as_str().is_some());
assert!(value["raw_data_hex"].as_str().is_some());
assert_eq!(value["visible"], serde_json::Value::Bool(false));
}

#[test]
fn parse_tron_send_transaction() {
let params = include_str!("./test/tron_send_transaction.json");
Expand Down
30 changes: 30 additions & 0 deletions gemstone/src/wallet_connect/test/tron_sign_transaction_nested.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"address": "TJoSEwEqt7cT3TUwmEoUYnYs5cZR3xSukM",
"transaction": {
"transaction": {
"raw_data": {
"contract": [
{
"parameter": {
"type_url": "type.googleapis.com/protocol.TriggerSmartContract",
"value": {
"contract_address": "41a614f803b6fd780986a42c78ec9c7f77e6ded13c",
"data": "095ea7b300000000000000000000000060e00625a95cbc180f290e2611c826f90eeba56f0000000000000000000000000000000000000000000000000000000000000000",
"owner_address": "4160e00625a95cbc180f290e2611c826f90eeba56f"
}
},
"type": "TriggerSmartContract"
}
],
"expiration": 1770267837000,
"fee_limit": 200000000,
"ref_block_bytes": "af5b",
"ref_block_hash": "64a0e8e5926b22fc",
"timestamp": 1770267778282
},
"raw_data_hex": "0a02af5b220864a0e8e5926b22fc40c884bee1c2335aae01081f12a9010a31747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e54726967676572536d617274436f6e747261637412740a154160e00625a95cbc180f290e2611c826f90eeba56f121541a614f803b6fd780986a42c78ec9c7f77e6ded13c2244095ea7b300000000000000000000000060e00625a95cbc180f290e2611c826f90eeba56f000000000000000000000000000000000000000000000000000000000000000070eab9bae1c23390018084af5f",
"txID": "fb21f360363fcf23f80bbf33f28c1d9972f0bf5e13f20e48430000c88ce88205",
"visible": false
}
}
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,28 @@
{
"address": "TJoSEwEqt7cT3TUwmEoUYnYs5cZR3xSukM",
"transaction": {
"raw_data": {
"contract": [
{
"parameter": {
"type_url": "type.googleapis.com/protocol.TriggerSmartContract",
"value": {
"contract_address": "41a614f803b6fd780986a42c78ec9c7f77e6ded13c",
"data": "095ea7b300000000000000000000000060e00625a95cbc180f290e2611c826f90eeba56f0000000000000000000000000000000000000000000000000000000000000000",
"owner_address": "4160e00625a95cbc180f290e2611c826f90eeba56f"
}
},
"type": "TriggerSmartContract"
}
],
"expiration": 1770267837000,
"fee_limit": 200000000,
"ref_block_bytes": "af5b",
"ref_block_hash": "64a0e8e5926b22fc",
"timestamp": 1770267778282
},
"raw_data_hex": "0a02af5b220864a0e8e5926b22fc40c884bee1c2335aae01081f12a9010a31747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e54726967676572536d617274436f6e747261637412740a154160e00625a95cbc180f290e2611c826f90eeba56f121541a614f803b6fd780986a42c78ec9c7f77e6ded13c2244095ea7b300000000000000000000000060e00625a95cbc180f290e2611c826f90eeba56f000000000000000000000000000000000000000000000000000000000000000070eab9bae1c23390018084af5f",
"signature": [
"943d286dfd1fb6a2cd31c9af7a6cfd23ee062ec2e0abcf82c7daa0c7bb43ab04458e0e88ebe3a94060122cccc8fb4395e5eb922720327df04ae840139c729a1f00"
"raw_data": {
"contract": [
{
"parameter": {
"type_url": "type.googleapis.com/protocol.TriggerSmartContract",
"value": {
"contract_address": "41a614f803b6fd780986a42c78ec9c7f77e6ded13c",
"data": "095ea7b300000000000000000000000060e00625a95cbc180f290e2611c826f90eeba56f0000000000000000000000000000000000000000000000000000000000000000",
"owner_address": "4160e00625a95cbc180f290e2611c826f90eeba56f"
}
},
"type": "TriggerSmartContract"
}
],
"txID": "fb21f360363fcf23f80bbf33f28c1d9972f0bf5e13f20e48430000c88ce88205",
"visible": false
"expiration": 1770267837000,
"fee_limit": 200000000,
"ref_block_bytes": "af5b",
"ref_block_hash": "64a0e8e5926b22fc",
"timestamp": 1770267778282
},
"signature": "943d286dfd1fb6a2cd31c9af7a6cfd23ee062ec2e0abcf82c7daa0c7bb43ab04458e0e88ebe3a94060122cccc8fb4395e5eb922720327df04ae840139c729a1f00"
"raw_data_hex": "0a02af5b220864a0e8e5926b22fc40c884bee1c2335aae01081f12a9010a31747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e54726967676572536d617274436f6e747261637412740a154160e00625a95cbc180f290e2611c826f90eeba56f121541a614f803b6fd780986a42c78ec9c7f77e6ded13c2244095ea7b300000000000000000000000060e00625a95cbc180f290e2611c826f90eeba56f000000000000000000000000000000000000000000000000000000000000000070eab9bae1c23390018084af5f",
"signature": [
"943d286dfd1fb6a2cd31c9af7a6cfd23ee062ec2e0abcf82c7daa0c7bb43ab04458e0e88ebe3a94060122cccc8fb4395e5eb922720327df04ae840139c729a1f00"
],
"txID": "fb21f360363fcf23f80bbf33f28c1d9972f0bf5e13f20e48430000c88ce88205",
"visible": false
}
Loading