Skip to content

Commit 91ee958

Browse files
authored
Support compiling tron raw json from DApp (trustwallet#4449)
* Support compiling tron raw json from DApp * Support raw json in method sign * chore: change code comments * Fix signature structure in compiling output
1 parent 6e9567b commit 91ee958

File tree

6 files changed

+190
-4
lines changed

6 files changed

+190
-4
lines changed

src/Tron/Entry.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInput
3131
txInputData, [](const auto& input, auto& output) {
3232
const auto signer = Signer(input);
3333
auto preImage = signer.signaturePreimage();
34-
auto preImageHash = Hash::sha256(preImage);
34+
auto preImageHash = signer.signaturePreimageHash();
3535
output.set_data_hash(preImageHash.data(), preImageHash.size());
3636
output.set_data(preImage.data(), preImage.size());
3737
});

src/Tron/Signer.cpp

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "../BinaryCoding.h"
1212
#include "../HexCoding.h"
1313

14+
#include <nlohmann/json.hpp>
1415
#include <cassert>
1516
#include <chrono>
1617

@@ -405,17 +406,39 @@ Data serialize(const protocol::Transaction& tx) noexcept {
405406

406407
Proto::SigningOutput signDirect(const Proto::SigningInput& input) {
407408
const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1);
408-
auto hash = parse_hex(input.txid());
409-
const auto signature = key.sign(hash);
410409
auto output = Proto::SigningOutput();
410+
411+
Data hash;
412+
if (!input.txid().empty()) {
413+
hash = parse_hex(input.txid());
414+
} else if (!input.raw_json().empty()) {
415+
try {
416+
auto parsed = nlohmann::json::parse(input.raw_json());
417+
if (parsed.contains("txID") && parsed["txID"].is_string()) {
418+
hash = parse_hex(parsed["txID"].get<std::string>());
419+
} else {
420+
// If txID is not present, return an error
421+
output.set_error(Common::Proto::Error_invalid_params);
422+
output.set_error_message("No txID found in raw JSON");
423+
return output;
424+
}
425+
} catch (const std::exception& e) {
426+
// If parsing fails, return an error
427+
output.set_error(Common::Proto::Error_invalid_params);
428+
output.set_error_message(e.what());
429+
return output;
430+
}
431+
}
432+
433+
const auto signature = key.sign(hash);
411434
output.set_signature(signature.data(), signature.size());
412435
output.set_id(input.txid());
413436
output.set_id(hash.data(), hash.size());
414437
return output;
415438
}
416439

417440
Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept {
418-
if (!input.txid().empty()) {
441+
if (!input.txid().empty() || !input.raw_json().empty()) {
419442
return signDirect(input);
420443
}
421444

@@ -455,6 +478,26 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept {
455478

456479
Proto::SigningOutput Signer::compile(const Data& signature) const {
457480
Proto::SigningOutput output;
481+
if (!input.raw_json().empty()) {
482+
// If raw JSON is provided, we use it directly
483+
try {
484+
auto parsed = nlohmann::json::parse(input.raw_json());
485+
// Add signature to JSON and set to output
486+
parsed["signature"] = nlohmann::json::array({hex(signature)});
487+
output.set_json(parsed.dump());
488+
output.set_signature(signature.data(), signature.size());
489+
// Extract txID and set to output
490+
if (parsed.contains("txID") && parsed["txID"].is_string()) {
491+
auto txID = parse_hex(parsed["txID"].get<std::string>());
492+
output.set_id(txID.data(), txID.size());
493+
}
494+
return output;
495+
} catch (const std::exception& e) {
496+
output.set_error(Common::Proto::Error_invalid_params);
497+
output.set_error_message(e.what());
498+
return output;
499+
}
500+
}
458501
auto preImage = signaturePreimage();
459502
auto hash = Hash::sha256(preImage);
460503
auto transaction = buildTransaction(input);
@@ -468,7 +511,40 @@ Proto::SigningOutput Signer::compile(const Data& signature) const {
468511
}
469512

470513
Data Signer::signaturePreimage() const {
514+
if (!input.raw_json().empty()) {
515+
// If raw JSON is provided, we use raw_data_hex directly
516+
try {
517+
auto parsed = nlohmann::json::parse(input.raw_json());
518+
if (parsed.contains("raw_data_hex") && parsed["raw_data_hex"].is_string()) {
519+
return parse_hex(parsed["raw_data_hex"].get<std::string>());
520+
}
521+
// If raw_data_hex is not present, return an empty Data
522+
return {};
523+
} catch (...) {
524+
// Ignore parsing errors, return an empty Data
525+
return {};
526+
}
527+
}
471528
return serialize(buildTransaction(input));
472529
}
473530

531+
Data Signer::signaturePreimageHash() const {
532+
if (!input.raw_json().empty()) {
533+
// If raw JSON is provided, we use txID directly
534+
try {
535+
auto parsed = nlohmann::json::parse(input.raw_json());
536+
if (parsed.contains("txID") && parsed["txID"].is_string()) {
537+
return parse_hex(parsed["txID"].get<std::string>());
538+
}
539+
// If txID is not present, return an empty Data
540+
return {};
541+
} catch (...) {
542+
// Ignore parsing errors, return an empty Data
543+
return {};
544+
}
545+
}
546+
auto preImage = signaturePreimage();
547+
return Hash::sha256(preImage);
548+
}
549+
474550
} // namespace TW::Tron

src/Tron/Signer.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class Signer {
2121
static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept;
2222
Proto::SigningOutput compile(const Data& signature) const;
2323
Data signaturePreimage() const;
24+
Data signaturePreimageHash() const;
2425
};
2526

2627
} // namespace TW::Tron

src/proto/Tron.proto

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,11 @@ message SigningInput {
263263
bytes private_key = 2;
264264

265265
// For direct sign in Tron, we just have to sign the txId returned by the DApp json payload.
266+
// TODO: This field can be removed in the future, as we can use raw_json.txID instead.
266267
string txId = 3;
268+
269+
// Raw JSON data from the DApp, which contains fields 'txID', 'raw_data' and 'raw_data_hex' normally.
270+
string raw_json = 4;
267271
}
268272

269273
// Result containing the signed and encoded transaction.

tests/chains/Tron/SignerTests.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,38 @@ TEST(TronSigner, SignDirectTransferAsset) {
2424
ASSERT_EQ(hex(output.signature()), "77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00");
2525
}
2626

27+
TEST(TronSigner, SignDirectRawJsonTransferAsset) {
28+
auto input = Proto::SigningInput();
29+
const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"));
30+
input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size());
31+
auto rawJson = R"({
32+
"raw_data": {
33+
"contract": [{
34+
"parameter": {
35+
"type_url": "type.googleapis.com/protocol.TransferAssetContract",
36+
"value": {
37+
"amount": 4,
38+
"asset_name": "31303030393539",
39+
"owner_address": "415cd0fb0ab3ce40f3051414c604b27756e69e43db",
40+
"to_address": "41521ea197907927725ef36d70f25f850d1659c7c7"
41+
}
42+
},
43+
"type": "TransferAssetContract"
44+
}],
45+
"expiration": 1541926116000,
46+
"ref_block_bytes": "b801",
47+
"ref_block_hash": "0e2bc08d550f5f58",
48+
"timestamp": 1539295479000
49+
},
50+
"visible":false,
51+
"txID": "546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb"
52+
})";
53+
input.set_raw_json(rawJson);
54+
const auto output = Signer::sign(input);
55+
ASSERT_EQ(hex(output.id()), "546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb");
56+
ASSERT_EQ(hex(output.signature()), "77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00");
57+
}
58+
2759
TEST(TronSigner, SignTransferAsset) {
2860
auto input = Proto::SigningInput();
2961
auto& transaction = *input.mutable_transaction();

tests/chains/Tron/TransactionCompilerTests.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,76 @@ TEST(TronCompiler, CompileWithSignatures) {
108108
EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n);
109109
}
110110
}
111+
112+
TEST(TronCompiler, CompileWithSignaturesRawJson) {
113+
const auto privateKey =
114+
PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"));
115+
const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended);
116+
constexpr auto coin = TWCoinTypeTron;
117+
/// Step 1: Prepare transaction input (protobuf)
118+
auto input = TW::Tron::Proto::SigningInput();
119+
auto rawJson = R"({
120+
"raw_data": {
121+
"contract": [{
122+
"parameter": {
123+
"type_url": "type.googleapis.com/protocol.TransferAssetContract",
124+
"value": {
125+
"amount": 4,
126+
"asset_name": "31303030393539",
127+
"owner_address": "415cd0fb0ab3ce40f3051414c604b27756e69e43db",
128+
"to_address": "41521ea197907927725ef36d70f25f850d1659c7c7"
129+
}
130+
},
131+
"type": "TransferAssetContract"
132+
}],
133+
"expiration": 1541926116000,
134+
"ref_block_bytes": "b801",
135+
"ref_block_hash": "0e2bc08d550f5f58",
136+
"timestamp": 1539295479000
137+
},
138+
"visible":false,
139+
"txID": "546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb"
140+
})";
141+
input.set_raw_json(rawJson);
142+
143+
auto inputString = input.SerializeAsString();
144+
auto inputStrData = TW::Data(inputString.begin(), inputString.end());
145+
146+
/// Step 2: Obtain preimage hash
147+
const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData);
148+
auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput();
149+
preSigningOutput.ParseFromArray(preImageHashesData.data(), static_cast<int>(preImageHashesData.size()));
150+
ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK);
151+
auto preImageHash = preSigningOutput.data_hash();
152+
EXPECT_EQ(hex(preImageHash), "546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb");
153+
auto signature = parse_hex("77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603"
154+
"a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00");
155+
156+
// Verify signature (pubkey & hash & signature)
157+
EXPECT_TRUE(publicKey.verify(signature, TW::data(preSigningOutput.data_hash())));
158+
/// Step 3: Compile transaction info
159+
const auto expectedTx = R"({"raw_data":{"contract":[{"parameter":{"type_url":"type.googleapis.com/protocol.TransferAssetContract","value":{"amount":4,"asset_name":"31303030393539","owner_address":"415cd0fb0ab3ce40f3051414c604b27756e69e43db","to_address":"41521ea197907927725ef36d70f25f850d1659c7c7"}},"type":"TransferAssetContract"}],"expiration":1541926116000,"ref_block_bytes":"b801","ref_block_hash":"0e2bc08d550f5f58","timestamp":1539295479000},"signature":["77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00"],"txID":"546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb","visible":false})";
160+
auto outputData =
161+
TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKey.bytes});
162+
163+
{
164+
TW::Tron::Proto::SigningOutput output;
165+
ASSERT_TRUE(output.ParseFromArray(outputData.data(), static_cast<int>(outputData.size())));
166+
EXPECT_EQ(output.json(), expectedTx);
167+
}
168+
169+
{ // Negative: invalid raw json
170+
auto input = TW::Tron::Proto::SigningInput();
171+
auto invalidRawJson = "not valid json";
172+
input.set_raw_json(invalidRawJson);
173+
auto inputString = input.SerializeAsString();
174+
auto inputStrData = TW::Data(inputString.begin(), inputString.end());
175+
176+
outputData = TransactionCompiler::compileWithSignatures(
177+
coin, inputStrData, {signature}, {publicKey.bytes});
178+
Tron::Proto::SigningOutput output;
179+
ASSERT_TRUE(output.ParseFromArray(outputData.data(), static_cast<int>(outputData.size())));
180+
EXPECT_EQ(output.json().size(), 0ul);
181+
EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params);
182+
}
183+
}

0 commit comments

Comments
 (0)