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

[Cardano] Support utxo with legacy(byron) address #3284

Merged
merged 11 commits into from
Jul 7, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import org.junit.Assert.assertEquals
import org.junit.Test
import wallet.core.java.AnySigner
import wallet.core.jni.*
import wallet.core.jni.Cardano.getByronAddress
import wallet.core.jni.Cardano.getStakingAddress
import wallet.core.jni.Cardano.outputMinAdaAmount
import wallet.core.jni.CoinType.CARDANO
Expand Down Expand Up @@ -75,6 +76,60 @@ class TestCardanoSigning {
assertEquals(Numeric.toHexString(txid.toByteArray()), "0x9b5b15e133cd73ccaa85307d2986aebc846505118a2eb4e6111e6b4b67d1f389");
}

/// Successfully broadcasted:
/// https://cardanoscan.io/transaction/0203ce2c91f59f169a26e9ef91254639d2b7911afac9c7c0ae64539f88ba46a5
@Test
fun testSignTransferFromLegacy() {
val privateKey = PrivateKey("98f266d1aac660179bc2f456033941238ee6b2beb8ed0f9f34c9902816781f5a9903d1d395d6ab887b65ea5e344ef09b449507c21a75f0ce8c59d0ed1c6764eba7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f4e890ca4eb6bec44bf751b5a843174534af64d6ad1f44e0613db78a7018781f5aa151d2997f52059466b715d8eefab30a78b874ae6ef4931fa58bb21ef8ce2423d46f19d0fbf75afb0b9a24e31d533f4fd74cee3b56e162568e8defe37123afc4".toHexByteArray())
var publicKey = privateKey.publicKeyEd25519Cardano
var byronAddress = wallet.core.jni.Cardano.getByronAddress(publicKey)

assertEquals(byronAddress, "Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8")

val message = Cardano.Transfer.newBuilder()
.setToAddress("addr1q90uh2eawrdc9vaemftgd50l28yrh9lqxtjjh4z6dnn0u7ggasexxdyyk9f05atygnjlccsjsggtc87hhqjna32fpv5qeq96ls")
.setChangeAddress("addr1qx55ymlqemndq8gluv40v58pu76a2tp4mzjnyx8n6zrp2vtzrs43a0057y0edkn8lh9su8vh5lnhs4npv6l9tuvncv8swc7t08")
.setAmount(3_000_000)
.build()
val input = Cardano.SigningInput.newBuilder()
.setTransferMessage(message)
.setTtl(190000000)

input.addPrivateKey(ByteString.copyFrom(privateKey.data()))

val outpoint1 = Cardano.OutPoint.newBuilder()
.setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("8316e5007d61fb90652cabb41141972a38b5bc60954d602cf843476aa3f67f63")))
.setOutputIndex(0)
.build()
val utxo1 = Cardano.TxInput.newBuilder()
.setOutPoint(outpoint1)
.setAddress("Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8")
.setAmount(2_500_000)
.build()
input.addUtxos(utxo1)

val outpoint2 = Cardano.OutPoint.newBuilder()
.setTxHash(ByteString.copyFrom(Numeric.hexStringToByteArray("e29392c59c903fefb905730587d22cae8bda30bd8d9aeec3eca082ae77675946")))
.setOutputIndex(0)
.build()
val utxo2 = Cardano.TxInput.newBuilder()
.setOutPoint(outpoint2)
.setAddress("Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8")
.setAmount(1_700_000)
.build()
input.addUtxos(utxo2)

val output = AnySigner.sign(input.build(), CARDANO, Cardano.SigningOutput.parser())
assertEquals(output.error, SigningError.OK)

val encoded = output.encoded
assertEquals(Numeric.toHexString(encoded.toByteArray()),
"0x83a400828258208316e5007d61fb90652cabb41141972a38b5bc60954d602cf843476aa3f67f6300825820e29392c59c903fefb905730587d22cae8bda30bd8d9aeec3eca082ae77675946000182825839015fcbab3d70db82b3b9da5686d1ff51c83b97e032e52bd45a6ce6fe7908ec32633484b152fa756444e5fc62128210bc1fd7b8253ec5490b281a002dc6c082583901a9426fe0cee6d01d1fe32af650e1e7b5d52c35d8a53218f3d0861531621c2b1ebdf4f11f96da67fdcb0e1d97a7e778566166be55f193c30f1a000f9ec1021a0002b0bf031a0b532b80a20081825820d163c8c4f0be7c22cd3a1152abb013c855ea614b92201497a568c5d93ceeb41e58406a23ab9267867fbf021c1cb2232bc83d2cdd663d651d22d59b6cddbca5cb106d4db99da50672f69a2309ca8a329a3f9576438afe4538b013de4591a6dfcd4d090281845820d163c8c4f0be7c22cd3a1152abb013c855ea614b92201497a568c5d93ceeb41e58406a23ab9267867fbf021c1cb2232bc83d2cdd663d651d22d59b6cddbca5cb106d4db99da50672f69a2309ca8a329a3f9576438afe4538b013de4591a6dfcd4d095820a7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f441a0f6");

val txid = output.txId
assertEquals(Numeric.toHexString(txid.toByteArray()), "0x0203ce2c91f59f169a26e9ef91254639d2b7911afac9c7c0ae64539f88ba46a5");
}

@Test
fun testSignTransferToken1() {
val toToken = Cardano.TokenAmount.newBuilder()
Expand Down
6 changes: 6 additions & 0 deletions include/TrustWalletCore/TWCardano.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,10 @@ TWString *_Nullable TWCardanoOutputMinAdaAmount(TWString *_Nonnull toAddress, TW
TW_EXPORT_STATIC_METHOD
TWString *_Nonnull TWCardanoGetStakingAddress(TWString *_Nonnull baseAddress) TW_VISIBILITY_DEFAULT;

/// Return the legacy(byron) address.
/// \param publicKey A valid public key with TWPublicKeyTypeED25519Cardano type.
/// \return the legacy(byron) address, as string, or empty string on error.
TW_EXPORT_STATIC_METHOD
TWString *_Nonnull TWCardanoGetByronAddress(struct TWPublicKey *_Nonnull publicKey);

TW_EXTERN_C_END
75 changes: 59 additions & 16 deletions src/Cardano/Signer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ Common::Proto::SigningError Signer::assembleSignatures(std::vector<std::pair<Dat
const auto address = AddressV3(publicKey);
privateKeys[address.string()] = privateKeyData;

const auto legacyAddress = AddressV2(publicKey);
privateKeys[legacyAddress.string()] = privateKeyData;

// Also add the derived staking private key (the 2nd half) and associated address; because staking keys also need signature
const auto stakingPrivKeyData = deriveStakingPrivateKey(privateKeyData);
if (!stakingPrivKeyData.empty()) {
Expand All @@ -131,7 +134,7 @@ Common::Proto::SigningError Signer::assembleSignatures(std::vector<std::pair<Dat
// collect every unique input UTXO address, preserving order
std::vector<std::string> addresses;
for (auto& u : plan.utxos) {
if (!AddressV3::isValid(u.address)) {
if (!AddressV3::isValidLegacy(u.address)) {
return Common::Proto::Error_invalid_address;
}
addresses.emplace_back(u.address);
Expand Down Expand Up @@ -174,31 +177,53 @@ Common::Proto::SigningError Signer::assembleSignatures(std::vector<std::pair<Dat
const auto privateKey = PrivateKey(privateKeyData);
const auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519Cardano);
const auto signature = privateKey.sign(txId, TWCurveED25519ExtendedCardano);
// public key (first 32 bytes) and signature (64 bytes)
signatures.emplace_back(subData(publicKey.bytes, 0, 32), signature);
signatures.emplace_back(publicKey.bytes, signature);
}

return Common::Proto::OK;
}

Cbor::Encode cborizeSignatures(const std::vector<std::pair<Data, Data>>& signatures) {
Cbor::Encode cborizeSignatures(const std::vector<std::pair<Data, Data>>& signatures, const bool addByronSignatures) {
std::map<Cbor::Encode, Cbor::Encode> cborizeSigs;
// signatures as Cbor
// clang-format off
std::vector<Cbor::Encode> sigsCbor;
std::vector<Cbor::Encode> sigsShelly;
std::vector<Cbor::Encode> sigsByron;

for (auto& s : signatures) {
sigsCbor.emplace_back(Cbor::Encode::array({
Cbor::Encode::bytes(s.first),
sigsShelly.emplace_back(Cbor::Encode::array({
// public key (first 32 bytes)
Cbor::Encode::bytes(subData(s.first, 0, 32)),
Cbor::Encode::bytes(s.second)
}));

if (addByronSignatures) {
sigsByron.emplace_back(Cbor::Encode::array({
// skey - public key (first 32 bytes)
Cbor::Encode::bytes(subData(s.first, 0, 32)),
Cbor::Encode::bytes(s.second),
// vkey - public key (second 32 bytes started from 32)
Cbor::Encode::bytes(subData(s.first, 32, 32)),
// payload
Cbor::Encode::bytes(parse_hex("A0"))
}));
}
}

cborizeSigs.emplace(
Cbor::Encode::uint(0),
Cbor::Encode::array(sigsShelly)
);

if (!sigsByron.empty()) {
cborizeSigs.emplace(
Cbor::Encode::uint(2),
Cbor::Encode::array(sigsByron)
);
}

// Cbor-encode txAux & signatures
return Cbor::Encode::map({
std::make_pair(
Cbor::Encode::uint(0),
Cbor::Encode::array(sigsCbor)
)
});
return Cbor::Encode::map(cborizeSigs);
// clang-format on
}

Expand Down Expand Up @@ -242,7 +267,16 @@ Common::Proto::SigningError Signer::encodeTransaction(Data& encoded, Data& txId,
if (sigError != Common::Proto::OK) {
return sigError;
}
const auto sigsCbor = cborizeSignatures(signatures);

bool hasLegacyUtxos = false;
for (const auto& utxo : input.utxos()) {
if (AddressV2::isValid(utxo.address())) {
hasLegacyUtxos = true;
break;
}
}

const auto sigsCbor = cborizeSignatures(signatures, hasLegacyUtxos);

// Cbor-encode txAux & signatures
const auto cbor = Cbor::Encode::array({
Expand Down Expand Up @@ -557,8 +591,17 @@ Data Signer::encodeTransactionWithSig(const Proto::SigningInput &input, const Pu
}

std::vector<std::pair<Data, Data>> signatures;
signatures.emplace_back(subData(publicKey.bytes, 0, 32), signature);
const auto sigsCbor = cborizeSignatures(signatures);
signatures.emplace_back(publicKey.bytes, signature);

bool hasLegacyUtxos = false;
for (const auto& utxo : input.utxos()) {
if (AddressV2::isValid(utxo.address())) {
hasLegacyUtxos = true;
break;
}
}

const auto sigsCbor = cborizeSignatures(signatures, hasLegacyUtxos);

// Cbor-encode txAux & signatures
const auto cbor = Cbor::Encode::array({
Expand Down
8 changes: 8 additions & 0 deletions src/interface/TWCardano.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,11 @@ TWString *_Nonnull TWCardanoGetStakingAddress(TWString *_Nonnull baseAddress) {
return TWStringCreateWithUTF8Bytes("");
}
}

TWString *_Nonnull TWCardanoGetByronAddress(struct TWPublicKey *_Nonnull publicKey) {
try {
return TWStringCreateWithUTF8Bytes(TW::Cardano::AddressV2({ publicKey->impl }).string().c_str());
} catch (...) {
return TWStringCreateWithUTF8Bytes("");
}
}
50 changes: 50 additions & 0 deletions swift/Tests/Blockchains/CardanoTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,56 @@ class CardanoTests: XCTestCase {
let txid = output.txID
XCTAssertEqual(txid.hexString, "9b5b15e133cd73ccaa85307d2986aebc846505118a2eb4e6111e6b4b67d1f389")
}

/// Successfully broadcasted:
/// https://cardanoscan.io/transaction/0203ce2c91f59f169a26e9ef91254639d2b7911afac9c7c0ae64539f88ba46a5
func testSignTransferFromLegacy() throws {
let privateKey = PrivateKey(data: Data(hexString: "98f266d1aac660179bc2f456033941238ee6b2beb8ed0f9f34c9902816781f5a9903d1d395d6ab887b65ea5e344ef09b449507c21a75f0ce8c59d0ed1c6764eba7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f4e890ca4eb6bec44bf751b5a843174534af64d6ad1f44e0613db78a7018781f5aa151d2997f52059466b715d8eefab30a78b874ae6ef4931fa58bb21ef8ce2423d46f19d0fbf75afb0b9a24e31d533f4fd74cee3b56e162568e8defe37123afc4")!)!
let publicKey = privateKey.getPublicKeyEd25519Cardano()
let byronAddress = Cardano.getByronAddress(publicKey: publicKey)

XCTAssertEqual(
publicKey.data.hexString,
"d163c8c4f0be7c22cd3a1152abb013c855ea614b92201497a568c5d93ceeb41ea7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f40b5aaa6103dc10842894a1eeefc5447b9bcb9bcf227d77e57be195d17bc03263d46f19d0fbf75afb0b9a24e31d533f4fd74cee3b56e162568e8defe37123afc4"
)

XCTAssertEqual(
byronAddress,
"Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8"
)
var input = CardanoSigningInput.with {
$0.transferMessage.toAddress = "addr1q90uh2eawrdc9vaemftgd50l28yrh9lqxtjjh4z6dnn0u7ggasexxdyyk9f05atygnjlccsjsggtc87hhqjna32fpv5qeq96ls"
$0.transferMessage.changeAddress = "addr1qx55ymlqemndq8gluv40v58pu76a2tp4mzjnyx8n6zrp2vtzrs43a0057y0edkn8lh9su8vh5lnhs4npv6l9tuvncv8swc7t08"
$0.transferMessage.amount = 3000000
$0.ttl = 190000000
}

input.privateKey.append(privateKey.data)

let utxo1 = CardanoTxInput.with {
$0.outPoint.txHash = Data(hexString: "8316e5007d61fb90652cabb41141972a38b5bc60954d602cf843476aa3f67f63")!
$0.outPoint.outputIndex = 0
$0.address = "Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8"
$0.amount = 2500000
}
input.utxos.append(utxo1)

let utxo2 = CardanoTxInput.with {
$0.outPoint.txHash = Data(hexString: "e29392c59c903fefb905730587d22cae8bda30bd8d9aeec3eca082ae77675946")!
$0.outPoint.outputIndex = 0
$0.address = "Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8"
$0.amount = 1700000
}
input.utxos.append(utxo2)

// Sign
let output: CardanoSigningOutput = AnySigner.sign(input: input, coin: .cardano)
XCTAssertEqual(output.error, TW_Common_Proto_SigningError.ok)

XCTAssertEqual(output.encoded.hexString, "83a400828258208316e5007d61fb90652cabb41141972a38b5bc60954d602cf843476aa3f67f6300825820e29392c59c903fefb905730587d22cae8bda30bd8d9aeec3eca082ae77675946000182825839015fcbab3d70db82b3b9da5686d1ff51c83b97e032e52bd45a6ce6fe7908ec32633484b152fa756444e5fc62128210bc1fd7b8253ec5490b281a002dc6c082583901a9426fe0cee6d01d1fe32af650e1e7b5d52c35d8a53218f3d0861531621c2b1ebdf4f11f96da67fdcb0e1d97a7e778566166be55f193c30f1a000f9ec1021a0002b0bf031a0b532b80a20081825820d163c8c4f0be7c22cd3a1152abb013c855ea614b92201497a568c5d93ceeb41e58406a23ab9267867fbf021c1cb2232bc83d2cdd663d651d22d59b6cddbca5cb106d4db99da50672f69a2309ca8a329a3f9576438afe4538b013de4591a6dfcd4d090281845820d163c8c4f0be7c22cd3a1152abb013c855ea614b92201497a568c5d93ceeb41e58406a23ab9267867fbf021c1cb2232bc83d2cdd663d651d22d59b6cddbca5cb106d4db99da50672f69a2309ca8a329a3f9576438afe4538b013de4591a6dfcd4d095820a7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f441a0f6")

XCTAssertEqual(output.txID.hexString, "0203ce2c91f59f169a26e9ef91254639d2b7911afac9c7c0ae64539f88ba46a5")
}

func testSignTransferToken1() throws {
let toToken = CardanoTokenAmount.with {
Expand Down
33 changes: 18 additions & 15 deletions tests/chains/Cardano/SigningTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -598,40 +598,43 @@ TEST(CardanoSigning, SignTransfer_0db1ea) {
EXPECT_EQ(hex(txid), "0db1ea8c5c5828bbd027fcef3da02a63b86899db670ad7bb0630cefbe35944fa");
}

/// Successfully broadcasted:
/// https://cardanoscan.io/transaction/0203ce2c91f59f169a26e9ef91254639d2b7911afac9c7c0ae64539f88ba46a5
TEST(CardanoSigning, SignTransferFromLegacy) {
Proto::SigningInput input;
auto* utxo1 = input.add_utxos();
const auto txHash1 = parse_hex("f074134aabbfb13b8aec7cf5465b1e5a862bde5cb88532cc7e64619179b3e767");
const auto txHash1 = parse_hex("8316e5007d61fb90652cabb41141972a38b5bc60954d602cf843476aa3f67f63");
utxo1->mutable_out_point()->set_tx_hash(txHash1.data(), txHash1.size());
utxo1->mutable_out_point()->set_output_index(1);
utxo1->set_address("Ae2tdPwUPEZMRgecV9jV2e9RdbrmnWu7YgRie4de16xLdkWhy6q7ypmRhgn");
utxo1->set_amount(1500000);
utxo1->mutable_out_point()->set_output_index(0);
utxo1->set_address("Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8");
utxo1->set_amount(2500000);
auto* utxo2 = input.add_utxos();
const auto txHash2 = parse_hex("554f2fd942a23d06835d26bbd78f0106fa94c8a551114a0bef81927f66467af0");
const auto txHash2 = parse_hex("e29392c59c903fefb905730587d22cae8bda30bd8d9aeec3eca082ae77675946");
utxo2->mutable_out_point()->set_tx_hash(txHash2.data(), txHash2.size());
utxo2->mutable_out_point()->set_output_index(0);
utxo2->set_address("Ae2tdPwUPEZMRgecV9jV2e9RdbrmnWu7YgRie4de16xLdkWhy6q7ypmRhgn");
utxo2->set_amount(6500000);
utxo2->set_address("Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8");
utxo2->set_amount(1700000);

const auto privateKeyData = parse_hex("c031e942f6bf2b2864700e7da20964ee6bb6d716345ce2e24d8c00e6500b574411111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111");
const auto privateKeyData = parse_hex("98f266d1aac660179bc2f456033941238ee6b2beb8ed0f9f34c9902816781f5a9903d1d395d6ab887b65ea5e344ef09b449507c21a75f0ce8c59d0ed1c6764eba7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f4e890ca4eb6bec44bf751b5a843174534af64d6ad1f44e0613db78a7018781f5aa151d2997f52059466b715d8eefab30a78b874ae6ef4931fa58bb21ef8ce2423d46f19d0fbf75afb0b9a24e31d533f4fd74cee3b56e162568e8defe37123afc4");
{
const auto privKey = PrivateKey(privateKeyData);
const auto pubKey = privKey.getPublicKey(TWPublicKeyTypeED25519Cardano);
const auto addr = AddressV2(pubKey);
EXPECT_EQ(addr.string(), "Ae2tdPwUPEZMRgecV9jV2e9RdbrmnWu7YgRie4de16xLdkWhy6q7ypmRhgn");
EXPECT_EQ(addr.string(), "Ae2tdPwUPEZ6vkqxSjJxaQYmDxHf5DTnxtZ67pFLJGTb9LTnCGkDP6ca3f8");
}
input.add_private_key(privateKeyData.data(), privateKeyData.size());
input.mutable_transfer_message()->set_to_address("addr1q92cmkgzv9h4e5q7mnrzsuxtgayvg4qr7y3gyx97ukmz3dfx7r9fu73vqn25377ke6r0xk97zw07dqr9y5myxlgadl2s0dgke5");
input.mutable_transfer_message()->set_change_address(ownAddress1);
input.mutable_transfer_message()->set_amount(7000000);
input.mutable_transfer_message()->set_to_address("addr1q90uh2eawrdc9vaemftgd50l28yrh9lqxtjjh4z6dnn0u7ggasexxdyyk9f05atygnjlccsjsggtc87hhqjna32fpv5qeq96ls");
input.mutable_transfer_message()->set_change_address("addr1qx55ymlqemndq8gluv40v58pu76a2tp4mzjnyx8n6zrp2vtzrs43a0057y0edkn8lh9su8vh5lnhs4npv6l9tuvncv8swc7t08");
input.mutable_transfer_message()->set_amount(3000000);
input.mutable_transfer_message()->set_use_max_amount(false);
input.set_ttl(53333333);
input.set_ttl(190000000);

auto signer = Signer(input);
const auto output = signer.sign();

EXPECT_EQ(output.error(), Common::Proto::Error_invalid_address);
EXPECT_EQ(hex(output.encoded()), "");
EXPECT_EQ(output.error(), Common::Proto::OK);
EXPECT_EQ(hex(output.encoded()), "83a400828258208316e5007d61fb90652cabb41141972a38b5bc60954d602cf843476aa3f67f6300825820e29392c59c903fefb905730587d22cae8bda30bd8d9aeec3eca082ae77675946000182825839015fcbab3d70db82b3b9da5686d1ff51c83b97e032e52bd45a6ce6fe7908ec32633484b152fa756444e5fc62128210bc1fd7b8253ec5490b281a002dc6c082583901a9426fe0cee6d01d1fe32af650e1e7b5d52c35d8a53218f3d0861531621c2b1ebdf4f11f96da67fdcb0e1d97a7e778566166be55f193c30f1a000f9ec1021a0002b0bf031a0b532b80a20081825820d163c8c4f0be7c22cd3a1152abb013c855ea614b92201497a568c5d93ceeb41e58406a23ab9267867fbf021c1cb2232bc83d2cdd663d651d22d59b6cddbca5cb106d4db99da50672f69a2309ca8a329a3f9576438afe4538b013de4591a6dfcd4d090281845820d163c8c4f0be7c22cd3a1152abb013c855ea614b92201497a568c5d93ceeb41e58406a23ab9267867fbf021c1cb2232bc83d2cdd663d651d22d59b6cddbca5cb106d4db99da50672f69a2309ca8a329a3f9576438afe4538b013de4591a6dfcd4d095820a7f484aa383806735c46fd769c679ee41f8952952036a6e2338ada940b8a91f441a0f6");
EXPECT_EQ(hex(data(output.tx_id())), "0203ce2c91f59f169a26e9ef91254639d2b7911afac9c7c0ae64539f88ba46a5");
}

TEST(CardanoSigning, SignTransferToLegacy) {
Expand Down