Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Evmos]: Fix signing, prehash, compile for Native Evmos #3406

Merged
merged 4 commits into from
Sep 1, 2023
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 samples/kmp/shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("com.trustwallet:wallet-core-kotlin:3.2.13")
implementation("com.trustwallet:wallet-core-kotlin:3.2.17")
}
}
val commonTest by getting {
Expand Down
21 changes: 16 additions & 5 deletions src/Cosmos/ProtobufSerialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,21 @@
#include "Base64.h"
#include "uint256.h"

#include <google/protobuf/util/json_util.h>

using namespace TW;

namespace TW::Cosmos::Protobuf {

namespace internal {

// Some of the Cosmos blockchains use different public key types for address deriving and transaction signing.
// `registry.json` contains the public key required to derive an address,
// while this function prepares the given public key to use it for transaction signing/compiling.
inline PublicKey preparePublicKey(const PublicKey& publicKey, TWCoinType coin) {
return coin == TWCoinTypeNativeEvmos ? publicKey.compressed() : publicKey;
}

} // namespace internal

using json = nlohmann::json;
using string = std::string;
const auto ProtobufAnyNamespacePrefix = ""; // to override default 'type.googleapis.com'
Expand Down Expand Up @@ -407,6 +416,8 @@ std::string buildAuthInfo(const Proto::SigningInput& input, TWCoinType coin) {
}

std::string buildAuthInfo(const Proto::SigningInput& input, const PublicKey& publicKey, TWCoinType coin) {
const auto pbk = internal::preparePublicKey(publicKey, coin);

if (input.messages_size() >= 1 && input.messages(0).has_sign_direct_message()) {
return input.messages(0).sign_direct_message().auth_info_bytes();
}
Expand All @@ -419,19 +430,19 @@ std::string buildAuthInfo(const Proto::SigningInput& input, const PublicKey& pub
switch(coin) {
case TWCoinTypeNativeEvmos: {
auto pubKey = ethermint::crypto::v1::ethsecp256k1::PubKey();
pubKey.set_key(publicKey.bytes.data(), publicKey.bytes.size());
pubKey.set_key(pbk.bytes.data(), pbk.bytes.size());
signerInfo->mutable_public_key()->PackFrom(pubKey, ProtobufAnyNamespacePrefix);
break;
}
case TWCoinTypeNativeInjective: {
auto pubKey = injective::crypto::v1beta1::ethsecp256k1::PubKey();
pubKey.set_key(publicKey.bytes.data(), publicKey.bytes.size());
pubKey.set_key(pbk.bytes.data(), pbk.bytes.size());
signerInfo->mutable_public_key()->PackFrom(pubKey, ProtobufAnyNamespacePrefix);
break;
}
default: {
auto pubKey = cosmos::crypto::secp256k1::PubKey();
pubKey.set_key(publicKey.bytes.data(), publicKey.bytes.size());
pubKey.set_key(pbk.bytes.data(), pbk.bytes.size());
signerInfo->mutable_public_key()->PackFrom(pubKey, ProtobufAnyNamespacePrefix);
}
}
Expand Down
5 changes: 1 addition & 4 deletions src/Cosmos/Signer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,13 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input, TWCoinType c
}

std::string Signer::signaturePreimage(const Proto::SigningInput& input, const Data& publicKey, TWCoinType coin) const {
auto isEvmCosmosChain = [coin]() {
return coin == TWCoinTypeNativeInjective || coin == TWCoinTypeNativeEvmos || coin == TWCoinTypeNativeCanto;
};
switch (input.signing_mode()) {
case Proto::JSON:
return Json::signaturePreimageJSON(input).dump();

case Proto::Protobuf:
default:
auto pbk = isEvmCosmosChain() ? PublicKey(publicKey, TWPublicKeyTypeSECP256k1Extended) : PublicKey(publicKey, TWPublicKeyTypeSECP256k1);
auto pbk = PublicKey(publicKey, TWCoinTypePublicKeyType(coin));
return Protobuf::signaturePreimageProto(input, pbk, coin);
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/Greenfield/ProtobufSerialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ static SigningResult<Any> convertMessage(const Proto::Message& msg) {
any.PackFrom(msgTransferOut, ProtobufAnyNamespacePrefix);
break;
}
default: {
return SigningResult<Any>::failure(Common::Proto::SigningError::Error_invalid_params);
}
}

return SigningResult<Any>::success(std::move(any));
Expand Down
2 changes: 1 addition & 1 deletion swift/Tests/Blockchains/EvmosTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class EvmosTests: XCTestCase {
let output: CosmosSigningOutput = AnySigner.sign(input: input, coin: .nativeEvmos)
// https://www.mintscan.io/evmos/txs/B05D2047086B158665EC552879270AEF40AEAAFEE7D275B63E9674E3CC4C4E55
let expected = """
{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpoBCpcBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEncKLGV2bW9zMXJrMzlkazN3ZmY1bnBzN2VtdWh2M250a24zbnN6NnoyZXJxZnIwEixldm1vczEwazlscnJydWFwOW51OTZteHd3eWUyZjZhNXdhemVoMzNrcTY3ehoZCgZhZXZtb3MSDzIwMDAwMDAwMDAwMDAwMBKbAQp3Cm8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSQwpBBJR1yfoj7Gk2Z7qnbE2mm0nMz98FjE3LJ7pnz7yQgtntkHR4ZWCqaYsZu5cpUmscdZNPPUp4975xnkOGt0mzYxASBAoCCAESIAoaCgZhZXZtb3MSEDE0MDAwMDAwMDAwMDAwMDAQ4MUIGkDBSFkYlIx1/6ZvRtEHyq6r0EEFDb/5T2Cb9zVYWtYZE1e7YteG2GuYMX546PvBQ7CYAFZEkr+rU6okC0nAT0UK"}
{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpoBCpcBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEncKLGV2bW9zMXJrMzlkazN3ZmY1bnBzN2VtdWh2M250a24zbnN6NnoyZXJxZnIwEixldm1vczEwazlscnJydWFwOW51OTZteHd3eWUyZjZhNXdhemVoMzNrcTY3ehoZCgZhZXZtb3MSDzIwMDAwMDAwMDAwMDAwMBJ7ClcKTwooL2V0aGVybWludC5jcnlwdG8udjEuZXRoc2VjcDI1NmsxLlB1YktleRIjCiEClHXJ+iPsaTZnuqdsTaabSczP3wWMTcsnumfPvJCC2e0SBAoCCAESIAoaCgZhZXZtb3MSEDE0MDAwMDAwMDAwMDAwMDAQ4MUIGkAz9vh1EutbLrLZmRA4eK72bA6bhfMX0YnhtRl5jeaL3AYmk0qdrwG9XzzleBsZ++IokJIk47cgOOyvEjl92Jhj"}
"""
XCTAssertJSONEqual(output.serialized, expected)
XCTAssertEqual(output.errorMessage, "")
Expand Down
103 changes: 103 additions & 0 deletions tests/chains/Cosmos/NativeInjective/TransactionCompilerTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright © 2017-2023 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

#include "Base64.h"
#include "Cosmos/Signer.h"
#include "HexCoding.h"
#include "proto/Cosmos.pb.h"
#include "proto/TransactionCompiler.pb.h"
#include "TrustWalletCore/TWAnySigner.h"
#include "TestUtilities.h"
#include "TransactionCompiler.h"

namespace TW::Cosmos::nativeInjective::tests {

TEST(NativeInjectiveCompiler, CompileWithSignatures) {
// Successfully broadcasted: https://www.mintscan.io/injective/transactions/B77D61590353C4AEDEAE2BBFF9E406DCF90E8D3A1A723BF22860F1E0A2617058

const auto coin = TWCoinTypeNativeInjective;
TW::Cosmos::Proto::SigningInput input;

PrivateKey privateKey =
PrivateKey(parse_hex("727513ec3c54eb6fae24f2ff756bbc4c89b82945c6538bbd173613ae3de719d3"));
input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size());

/// Step 1: Prepare transaction input (protobuf)
input.set_account_number(88701);
input.set_chain_id("injective-1");
input.set_memo("");
input.set_sequence(0);

PublicKey publicKey = privateKey.getPublicKey(TWCoinTypePublicKeyType(coin));
const auto pubKeyBz = publicKey.bytes;
ASSERT_EQ(hex(pubKeyBz), "04088ac2919987d927368cb2be2ade44cd0ed3616745a9699cae264b3fc5a7c3607d99f441b8340990ee990cb3eaf560f1f0bafe600c7e94a4be8392166984f728");
input.set_public_key(pubKeyBz.data(), pubKeyBz.size());

auto msg = input.add_messages();
auto& message = *msg->mutable_send_coins_message();
message.set_from_address("inj1d0jkrsd09c7pule43y3ylrul43lwwcqaky8w8c");
message.set_to_address("inj1xmpkmxr4as00em23tc2zgmuyy2gr4h3wgcl6vd");
auto amountOfTx = message.add_amounts();
amountOfTx->set_denom("inj");
amountOfTx->set_amount("10000000000");

auto& fee = *input.mutable_fee();
fee.set_gas(110000);
auto amountOfFee = fee.add_amounts();
amountOfFee->set_denom("inj");
amountOfFee->set_amount("100000000000000");

/// Step 2: Obtain protobuf preimage hash
input.set_signing_mode(TW::Cosmos::Proto::Protobuf);
auto protoInputString = input.SerializeAsString();
auto protoInputData = TW::Data(protoInputString.begin(), protoInputString.end());

const auto preImageHashData = TransactionCompiler::preImageHashes(coin, protoInputData);
auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput();
ASSERT_TRUE(
preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size()));
ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK);
auto preImage = preSigningOutput.data();
auto preImageHash = preSigningOutput.data_hash();

EXPECT_EQ(
hex(preImage),
"0a8f010a8c010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e64126c0a2a696e6a3164306a6b7273643039633770756c6534337933796c72756c34336c77776371616b7938773863122a696e6a31786d706b6d78723461733030656d32337463327a676d7579793267723468337767636c3676641a120a03696e6a120b3130303030303030303030129c010a7c0a740a2d2f696e6a6563746976652e63727970746f2e763162657461312e657468736563703235366b312e5075624b657912430a4104088ac2919987d927368cb2be2ade44cd0ed3616745a9699cae264b3fc5a7c3607d99f441b8340990ee990cb3eaf560f1f0bafe600c7e94a4be8392166984f72812040a020801121c0a160a03696e6a120f31303030303030303030303030303010b0db061a0b696e6a6563746976652d3120fdb405");
EXPECT_EQ(hex(preImageHash),
"57dc10c3d1893ff16e1f5a47fa4b2e96f37b9c57d98a42d88c971debb4947ec9");


auto expectedTx = R"({"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"Co8BCowBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmwKKmluajFkMGprcnNkMDljN3B1bGU0M3kzeWxydWw0M2x3d2NxYWt5OHc4YxIqaW5qMXhtcGtteHI0YXMwMGVtMjN0YzJ6Z211eXkyZ3I0aDN3Z2NsNnZkGhIKA2luahILMTAwMDAwMDAwMDASnAEKfAp0Ci0vaW5qZWN0aXZlLmNyeXB0by52MWJldGExLmV0aHNlY3AyNTZrMS5QdWJLZXkSQwpBBAiKwpGZh9knNoyyvireRM0O02FnRalpnK4mSz/Fp8NgfZn0Qbg0CZDumQyz6vVg8fC6/mAMfpSkvoOSFmmE9ygSBAoCCAESHAoWCgNpbmoSDzEwMDAwMDAwMDAwMDAwMBCw2wYaQPep7ApSEXC7VWbKlz08c6G2mxYtmc4CIFkYmZHsRAY3MzOU/xyedfrYTrEUOTlp8gmJsDbx3+0olJ6QbcAHdCE="})";
Data signature;

{
TW::Cosmos::Proto::SigningOutput output;
ANY_SIGN(input, coin);
assertJSONEqual(
output.serialized(),
expectedTx);

signature = data(output.signature());
EXPECT_EQ(hex(signature),
"f7a9ec0a521170bb5566ca973d3c73a1b69b162d99ce022059189991ec440637333394ff1c9e75fad84eb114393969f20989b036f1dfed28949e906dc0077421");

ASSERT_TRUE(publicKey.verify(signature, data(preImageHash.data())));
}

{
const Data outputData = TransactionCompiler::compileWithSignatures(
coin, protoInputData, {signature}, {publicKey.bytes});
Cosmos::Proto::SigningOutput output;
ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size()));

EXPECT_EQ(output.error(), Common::Proto::OK);
EXPECT_EQ(output.serialized(), expectedTx);
EXPECT_EQ(output.signature(), "");
EXPECT_EQ(hex(output.signature()), "");
}
}

}
2 changes: 1 addition & 1 deletion tests/chains/Evmos/SignerTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ TEST(EvmosSigner, CompoundingAuthz) {
auto output = Signer::sign(input, TWCoinTypeNativeEvmos);
auto expected = R"(
{
"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5EjUKM2V2bW9zdmFsb3BlcjF1bWs0MDdlZWQ3YWY2YW52dXQ2bGxnMnpldm5mMGRuMGZlcXFueSABEgYI4LD6pgYSnQEKeQpvCigvZXRoZXJtaW50LmNyeXB0by52MS5ldGhzZWNwMjU2azEuUHViS2V5EkMKQQSAdlh24+rB/xlhO8/2FuT0Z12BRmhkQKL+4GWSNhb9p0uO6cUkpw8dpkYb5Wx+YsQgPvyUNSmiaO75siYg72ylEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkArFy2yWnZbP9aqxyx9KN8xlTVyWT5jolnrY5fbMtXeijVOGZIrFCtEg+xgjv6XpMTKK9A3cMMMcBAcuv2S+nN8"
"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5EjUKM2V2bW9zdmFsb3BlcjF1bWs0MDdlZWQ3YWY2YW52dXQ2bGxnMnpldm5mMGRuMGZlcXFueSABEgYI4LD6pgYSfQpZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohA4B2WHbj6sH/GWE7z/YW5PRnXYFGaGRAov7gZZI2Fv2nEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkAm17CZgB7m+CPVlITnrHosklMTL9zrUeGRs8FL8N0GcRami9zdJ+e3xuXOtJmwP7G6QNh85CRYjFj8a8lpmmJM"
})";
assertJSONEqual(output.serialized(), expected);
}
Expand Down
103 changes: 103 additions & 0 deletions tests/chains/Evmos/TransactionCompilerTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright © 2017-2023 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

#include "Base64.h"
#include "Cosmos/Signer.h"
#include "HexCoding.h"
#include "proto/Cosmos.pb.h"
#include "proto/TransactionCompiler.pb.h"
#include "TrustWalletCore/TWAnySigner.h"
#include "TestUtilities.h"
#include "TransactionCompiler.h"

namespace TW::Cosmos::evmos::tests {

TEST(EvmosCompiler, CompileWithSignatures) {
// Successfully broadcasted: https://www.mintscan.io/evmos/transactions/02105B186FCA473C9F467B2D3BF487F6CE5DB26EE54BCD1667DDB7A2DA0E2489

const auto coin = TWCoinTypeNativeEvmos;
TW::Cosmos::Proto::SigningInput input;

PrivateKey privateKey =
PrivateKey(parse_hex("727513ec3c54eb6fae24f2ff756bbc4c89b82945c6538bbd173613ae3de719d3"));
input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size());

/// Step 1: Prepare transaction input (protobuf)
input.set_account_number(106619981);
input.set_chain_id("evmos_9001-2");
input.set_memo("");
input.set_sequence(0);

PublicKey publicKey = privateKey.getPublicKey(TWCoinTypePublicKeyType(coin));
const auto pubKeyBz = publicKey.bytes;
ASSERT_EQ(hex(pubKeyBz), "04088ac2919987d927368cb2be2ade44cd0ed3616745a9699cae264b3fc5a7c3607d99f441b8340990ee990cb3eaf560f1f0bafe600c7e94a4be8392166984f728");
input.set_public_key(pubKeyBz.data(), pubKeyBz.size());

auto msg = input.add_messages();
auto& message = *msg->mutable_send_coins_message();
message.set_from_address("evmos1d0jkrsd09c7pule43y3ylrul43lwwcqa7vpy0g");
message.set_to_address("evmos17dh3frt0m6kdd3m9lr6e6sr5zz0rz8cvxd7u5t");
auto amountOfTx = message.add_amounts();
amountOfTx->set_denom("aevmos");
amountOfTx->set_amount("10000000000000000");

auto& fee = *input.mutable_fee();
fee.set_gas(137840);
auto amountOfFee = fee.add_amounts();
amountOfFee->set_denom("aevmos");
amountOfFee->set_amount("5513600000000000");

/// Step 2: Obtain protobuf preimage hash
input.set_signing_mode(TW::Cosmos::Proto::Protobuf);
auto protoInputString = input.SerializeAsString();
auto protoInputData = TW::Data(protoInputString.begin(), protoInputString.end());

const auto preImageHashData = TransactionCompiler::preImageHashes(coin, protoInputData);
auto preSigningOutput = TW::TxCompiler::Proto::PreSigningOutput();
ASSERT_TRUE(
preSigningOutput.ParseFromArray(preImageHashData.data(), (int)preImageHashData.size()));
ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK);
auto preImage = preSigningOutput.data();
auto preImageHash = preSigningOutput.data_hash();

EXPECT_EQ(
hex(preImage),
"0a9c010a99010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412790a2c65766d6f733164306a6b7273643039633770756c6534337933796c72756c34336c7777637161377670793067122c65766d6f733137646833667274306d366b6464336d396c723665367372357a7a30727a3863767864377535741a1b0a066165766d6f7312113130303030303030303030303030303030127b0a570a4f0a282f65746865726d696e742e63727970746f2e76312e657468736563703235366b312e5075624b657912230a2102088ac2919987d927368cb2be2ade44cd0ed3616745a9699cae264b3fc5a7c36012040a02080112200a1a0a066165766d6f7312103535313336303030303030303030303010f0b4081a0c65766d6f735f393030312d3220cdc8eb32");
EXPECT_EQ(hex(preImageHash),
"9912eb629e215027b8d587939b1af72a9f70ae326bcaf48dfe77a729fc4ac632");


auto expectedTx = R"({"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpwBCpkBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnkKLGV2bW9zMWQwamtyc2QwOWM3cHVsZTQzeTN5bHJ1bDQzbHd3Y3FhN3ZweTBnEixldm1vczE3ZGgzZnJ0MG02a2RkM205bHI2ZTZzcjV6ejByejhjdnhkN3U1dBobCgZhZXZtb3MSETEwMDAwMDAwMDAwMDAwMDAwEnsKVwpPCigvZXRoZXJtaW50LmNyeXB0by52MS5ldGhzZWNwMjU2azEuUHViS2V5EiMKIQIIisKRmYfZJzaMsr4q3kTNDtNhZ0WpaZyuJks/xafDYBIECgIIARIgChoKBmFldm1vcxIQNTUxMzYwMDAwMDAwMDAwMBDwtAgaQKrmMaaSKnohf3ahyCOYdRJKBKJjr4WkkA/cbn6FRdF0Gd6FHSzBP8S4v4VNiy3KC47TD0C+sUBO413gCzjo8/U="})";
Data signature;

{
TW::Cosmos::Proto::SigningOutput output;
ANY_SIGN(input, coin);
assertJSONEqual(
output.serialized(),
expectedTx);

signature = data(output.signature());
EXPECT_EQ(hex(signature),
"aae631a6922a7a217f76a1c8239875124a04a263af85a4900fdc6e7e8545d17419de851d2cc13fc4b8bf854d8b2dca0b8ed30f40beb1404ee35de00b38e8f3f5");

ASSERT_TRUE(publicKey.verify(signature, data(preImageHash.data())));
}

{
const Data outputData = TransactionCompiler::compileWithSignatures(
coin, protoInputData, {signature}, {publicKey.bytes});
Cosmos::Proto::SigningOutput output;
ASSERT_TRUE(output.ParseFromArray(outputData.data(), (int)outputData.size()));

EXPECT_EQ(output.error(), Common::Proto::OK);
EXPECT_EQ(output.serialized(), expectedTx);
EXPECT_EQ(output.signature(), "");
EXPECT_EQ(hex(output.signature()), "");
}
}

}