diff --git a/src/Tron/Entry.cpp b/src/Tron/Entry.cpp index 7cf7f756ca5..64c15d6d75c 100644 --- a/src/Tron/Entry.cpp +++ b/src/Tron/Entry.cpp @@ -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()); }); diff --git a/src/Tron/Signer.cpp b/src/Tron/Signer.cpp index d0acf6706cd..3e126cdea3b 100644 --- a/src/Tron/Signer.cpp +++ b/src/Tron/Signer.cpp @@ -11,6 +11,7 @@ #include "../BinaryCoding.h" #include "../HexCoding.h" +#include #include #include @@ -405,9 +406,31 @@ 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()); + } 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()); @@ -415,7 +438,7 @@ Proto::SigningOutput signDirect(const Proto::SigningInput& input) { } Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - if (!input.txid().empty()) { + if (!input.txid().empty() || !input.raw_json().empty()) { return signDirect(input); } @@ -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()); + 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); @@ -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()); + } + // 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()); + } + // 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 diff --git a/src/Tron/Signer.h b/src/Tron/Signer.h index 1d3063378af..0765cbf6626 100644 --- a/src/Tron/Signer.h +++ b/src/Tron/Signer.h @@ -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 diff --git a/src/proto/Tron.proto b/src/proto/Tron.proto index 89e6fd6529d..3130965dc82 100644 --- a/src/proto/Tron.proto +++ b/src/proto/Tron.proto @@ -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. diff --git a/tests/chains/Tron/SignerTests.cpp b/tests/chains/Tron/SignerTests.cpp index b62ac222b73..43e58849c4e 100644 --- a/tests/chains/Tron/SignerTests.cpp +++ b/tests/chains/Tron/SignerTests.cpp @@ -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(); diff --git a/tests/chains/Tron/TransactionCompilerTests.cpp b/tests/chains/Tron/TransactionCompilerTests.cpp index 0962f4bda07..df5fddcdc9d 100644 --- a/tests/chains/Tron/TransactionCompilerTests.cpp +++ b/tests/chains/Tron/TransactionCompilerTests.cpp @@ -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(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(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(outputData.size()))); + EXPECT_EQ(output.json().size(), 0ul); + EXPECT_EQ(output.error(), Common::Proto::Error_invalid_params); + } +}