Skip to content
Merged
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
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
82 changes: 79 additions & 3 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 @@ -405,17 +406,39 @@ Data serialize(const protocol::Transaction& tx) noexcept {

Proto::SigningOutput signDirect(const Proto::SigningInput& input) {
const auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end()), TWCurveSECP256k1);
auto hash = parse_hex(input.txid());
const auto signature = key.sign(hash);
auto output = Proto::SigningOutput();

Data hash;
if (!input.txid().empty()) {
hash = parse_hex(input.txid());
} else if (!input.raw_json().empty()) {
try {
auto parsed = nlohmann::json::parse(input.raw_json());
if (parsed.contains("txID") && parsed["txID"].is_string()) {
hash = parse_hex(parsed["txID"].get<std::string>());
} else {
// If txID is not present, return an error
output.set_error(Common::Proto::Error_invalid_params);
output.set_error_message("No txID found in raw JSON");
return output;
}
} catch (const std::exception& e) {
// If parsing fails, return an error
output.set_error(Common::Proto::Error_invalid_params);
output.set_error_message(e.what());
return output;
}
}

const auto signature = key.sign(hash);
output.set_signature(signature.data(), signature.size());
output.set_id(input.txid());
output.set_id(hash.data(), hash.size());
return output;
}

Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept {
if (!input.txid().empty()) {
if (!input.txid().empty() || !input.raw_json().empty()) {
return signDirect(input);
}

Expand Down Expand Up @@ -455,6 +478,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"] = nlohmann::json::array({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 +511,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
32 changes: 32 additions & 0 deletions tests/chains/Tron/SignerTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,38 @@ TEST(TronSigner, SignDirectTransferAsset) {
ASSERT_EQ(hex(output.signature()), "77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00");
}

TEST(TronSigner, SignDirectRawJsonTransferAsset) {
auto input = Proto::SigningInput();
const auto privateKey = PrivateKey(parse_hex("2d8f68944bdbfbc0769542fba8fc2d2a3de67393334471624364c7006da2aa54"));
input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size());
auto rawJson = 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(rawJson);
const auto output = Signer::sign(input);
ASSERT_EQ(hex(output.id()), "546a3d07164c624809cf4e564a083a7a7974bb3c4eff6bb3e278b0ca21083fcb");
ASSERT_EQ(hex(output.signature()), "77f5eabde31e739d34a66914540f1756981dc7d782c9656f5e14e53b59a15371603a183aa12124adeee7991bf55acc8e488a6ca04fb393b1a8ac16610eeafdfc00");
}

TEST(TronSigner, SignTransferAsset) {
auto input = Proto::SigningInput();
auto& transaction = *input.mutable_transaction();
Expand Down
73 changes: 73 additions & 0 deletions tests/chains/Tron/TransactionCompilerTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,76 @@ 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 rawJson = 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(rawJson);

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 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})";
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(), expectedTx);
}

{ // 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);
}
}
Loading