Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion src/Tron/Entry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInput
txInputData, [](const auto& input, auto& output) {
const auto signer = Signer(input);
auto preImage = signer.signaturePreimage();
auto preImageHash = Hash::sha256(preImage);
auto preImageHash = signer.signaturePreimageHash();
output.set_data_hash(preImageHash.data(), preImageHash.size());
output.set_data(preImage.data(), preImage.size());
});
Expand Down
54 changes: 54 additions & 0 deletions src/Tron/Signer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "../BinaryCoding.h"
#include "../HexCoding.h"

#include <nlohmann/json.hpp>
#include <cassert>
#include <chrono>

Expand Down Expand Up @@ -455,6 +456,26 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept {

Proto::SigningOutput Signer::compile(const Data& signature) const {
Proto::SigningOutput output;
if (!input.raw_json().empty()) {
// If raw JSON is provided, we use it directly
try {
auto parsed = nlohmann::json::parse(input.raw_json());
// Add signature to JSON and set to output
parsed["signature"] = hex(signature);
output.set_json(parsed.dump());
output.set_signature(signature.data(), signature.size());
// Extract txID and set to output
if (parsed.contains("txID") && parsed["txID"].is_string()) {
auto txID = parse_hex(parsed["txID"].get<std::string>());
output.set_id(txID.data(), txID.size());
}
return output;
} catch (const std::exception& e) {
output.set_error(Common::Proto::Error_invalid_params);
output.set_error_message(e.what());
return output;
}
}
auto preImage = signaturePreimage();
auto hash = Hash::sha256(preImage);
auto transaction = buildTransaction(input);
Expand All @@ -468,7 +489,40 @@ Proto::SigningOutput Signer::compile(const Data& signature) const {
}

Data Signer::signaturePreimage() const {
if (!input.raw_json().empty()) {
// If raw JSON is provided, we use raw_data_hex directly
try {
auto parsed = nlohmann::json::parse(input.raw_json());
if (parsed.contains("raw_data_hex") && parsed["raw_data_hex"].is_string()) {
return parse_hex(parsed["raw_data_hex"].get<std::string>());
}
// If raw_data_hex is not present, return an empty Data
return {};
} catch (...) {
// Ignore parsing errors, return an empty Data
return {};
}
}
return serialize(buildTransaction(input));
}

Data Signer::signaturePreimageHash() const {
if (!input.raw_json().empty()) {
// If raw JSON is provided, we use txID directly
try {
auto parsed = nlohmann::json::parse(input.raw_json());
if (parsed.contains("txID") && parsed["txID"].is_string()) {
return parse_hex(parsed["txID"].get<std::string>());
}
// If txID is not present, return an empty Data
return {};
} catch (...) {
// Ignore parsing errors, return an empty Data
return {};
}
}
auto preImage = signaturePreimage();
return Hash::sha256(preImage);
}

} // namespace TW::Tron
1 change: 1 addition & 0 deletions src/Tron/Signer.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class Signer {
static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept;
Proto::SigningOutput compile(const Data& signature) const;
Data signaturePreimage() const;
Data signaturePreimageHash() const;
};

} // namespace TW::Tron
4 changes: 4 additions & 0 deletions src/proto/Tron.proto
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,11 @@ message SigningInput {
bytes private_key = 2;

// For direct sign in Tron, we just have to sign the txId returned by the DApp json payload.
// TODO: This field can be removed in the future, as we can use raw_json.txID instead.
string txId = 3;

// Raw JSON data from the DApp, which contains fields 'txID', 'raw_data' and 'raw_data_hex' normally.
string raw_json = 4;
}

// Result containing the signed and encoded transaction.
Expand Down
74 changes: 74 additions & 0 deletions tests/chains/Tron/TransactionCompilerTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,77 @@ TEST(TronCompiler, CompileWithSignatures) {
EXPECT_EQ(output.error(), Common::Proto::Error_no_support_n2n);
}
}

TEST(TronCompiler, CompileWithSignaturesRawJson) {
const auto privateKey =
PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"));
const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeSECP256k1Extended);
constexpr auto coin = TWCoinTypeTron;
/// Step 1: Prepare transaction input (protobuf)
auto input = TW::Tron::Proto::SigningInput();
auto raw_json = 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
},
"visible":false,
"txID": "546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb"
})";
input.set_raw_json(raw_json);
input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size());

auto inputString = input.SerializeAsString();
auto inputStrData = TW::Data(inputString.begin(), inputString.end());

/// Step 2: Obtain preimage hash
const auto preImageHashesData = TransactionCompiler::preImageHashes(coin, inputStrData);
auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput();
preSigningOutput.ParseFromArray(preImageHashesData.data(), static_cast<int>(preImageHashesData.size()));
ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK);
auto preImageHash = preSigningOutput.data_hash();
EXPECT_EQ(hex(preImageHash), "546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb");
auto signature = parse_hex("77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603"
"a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00");

// Verify signature (pubkey & hash & signature)
EXPECT_TRUE(publicKey.verify(signature, TW::data(preSigningOutput.data_hash())));
/// Step 3: Compile transaction info
const auto expected_tx = 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})";
auto outputData =
TransactionCompiler::compileWithSignatures(coin, inputStrData, {signature}, {publicKey.bytes});

{
TW::Tron::Proto::SigningOutput output;
ASSERT_TRUE(output.ParseFromArray(outputData.data(), static_cast<int>(outputData.size())));
EXPECT_EQ(output.json(), expected_tx);
}

{ // Negative: invalid raw json
auto input = TW::Tron::Proto::SigningInput();
auto invalidRawJson = "not valid json";
input.set_raw_json(invalidRawJson);
auto inputString = input.SerializeAsString();
auto inputStrData = TW::Data(inputString.begin(), inputString.end());

outputData = TransactionCompiler::compileWithSignatures(
coin, inputStrData, {signature}, {publicKey.bytes});
Tron::Proto::SigningOutput output;
ASSERT_TRUE(output.ParseFromArray(outputData.data(), static_cast<int>(outputData.size())));
EXPECT_EQ(output.json().size(), 0ul);
EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params);
}
}