diff --git a/src/Aptos/Entry.cpp b/src/Aptos/Entry.cpp index 5d56f793861..8c7fafced8e 100644 --- a/src/Aptos/Entry.cpp +++ b/src/Aptos/Entry.cpp @@ -23,4 +23,19 @@ void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D signTemplate(dataIn, dataOut); } +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + output = Signer::preImageHashes(input); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, const auto& publicKey) { + output = Signer::compile(input, signature, publicKey); + }); +} + } // namespace TW::Aptos diff --git a/src/Aptos/Entry.h b/src/Aptos/Entry.h index 390e8bbc7e3..2219499f5ac 100644 --- a/src/Aptos/Entry.h +++ b/src/Aptos/Entry.h @@ -14,9 +14,11 @@ namespace TW::Aptos { /// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file class Entry final : public CoinEntry { public: - bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; }; } // namespace TW::Aptos diff --git a/src/Aptos/Signer.cpp b/src/Aptos/Signer.cpp index 5c4b676583b..992d91dd6f6 100644 --- a/src/Aptos/Signer.cpp +++ b/src/Aptos/Signer.cpp @@ -180,48 +180,29 @@ TransactionPayload registerTokenPayload(const Proto::SigningInput& input) { return payload; } -Proto::SigningOutput blindSign(const Proto::SigningInput& input) { - auto output = Proto::SigningOutput(); - auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - auto pubKeyData = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; +TransactionBasePtr buildBlindTx(const Proto::SigningInput& input) { if (nlohmann::json j = nlohmann::json::parse(input.any_encoded(), nullptr, false); j.is_discarded()) { - BCS::Serializer serializer; - auto encodedCall = parse_hex(input.any_encoded()); - serializer.add_bytes(begin(encodedCall), end(encodedCall)); - auto signature = privateKey.sign(encodedCall, TWCurveED25519); - output.set_raw_txn(encodedCall.data(), encodedCall.size()); - output.mutable_authenticator()->set_public_key(pubKeyData.data(), pubKeyData.size()); - output.mutable_authenticator()->set_signature(signature.data(), signature.size()); - serializer << BCS::uleb128{.value = 0} << pubKeyData << signature; - output.set_encoded(serializer.bytes.data(), serializer.bytes.size()); - - // clang-format off - nlohmann::json json = { - {"type", "ed25519_signature"}, - {"public_key", hexEncoded(pubKeyData)}, - {"signature", hexEncoded(signature)} - }; - // clang-format on - output.set_json(json.dump()); + auto blindBuilder = std::make_unique(); + blindBuilder->encodedCallHex(input.any_encoded()); + return blindBuilder; } else { - TransactionBuilder::builder() - .sender(Address(input.sender())) + auto txBuilder = std::make_unique(); + txBuilder->sender(Address(input.sender())) .sequenceNumber(input.sequence_number()) .payload(EntryFunction::from_json(j)) .maxGasAmount(input.max_gas_amount()) .gasUnitPrice(input.gas_unit_price()) .expirationTimestampSecs(input.expiration_timestamp_secs()) - .chainId(static_cast(input.chain_id())) - .sign(input, output); + .chainId(static_cast(input.chain_id())); + return txBuilder; } - return output; } -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) { - auto protoOutput = Proto::SigningOutput(); +TransactionBasePtr buildTx(const Proto::SigningInput& input) { if (!input.any_encoded().empty()) { - return blindSign(input); + return buildBlindTx(input); } + auto nftPayloadFunctor = [](const Proto::NftMessage& nftMessage) { switch (nftMessage.nft_transaction_payload_case()) { case Proto::NftMessage::kOfferNft: @@ -273,16 +254,27 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) { throw std::runtime_error("Transaction payload should be set"); } }; - TransactionBuilder::builder() - .sender(Address(input.sender())) + auto txBuilder = std::make_unique(); + txBuilder->sender(Address(input.sender())) .sequenceNumber(input.sequence_number()) .payload(payloadFunctor()) .maxGasAmount(input.max_gas_amount()) .gasUnitPrice(input.gas_unit_price()) .expirationTimestampSecs(input.expiration_timestamp_secs()) - .chainId(static_cast(input.chain_id())) - .sign(input, protoOutput); - return protoOutput; + .chainId(static_cast(input.chain_id())); + return txBuilder; +} + +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) { + return buildTx(input)->sign(input); +} + +TxCompiler::Proto::PreSigningOutput Signer::preImageHashes(const Proto::SigningInput& input) { + return buildTx(input)->preImage(); +} + +Proto::SigningOutput Signer::compile(const Proto::SigningInput& input, const Data& signature, const PublicKey& publicKey) { + return buildTx(input)->compile(signature, publicKey); } } // namespace TW::Aptos diff --git a/src/Aptos/Signer.h b/src/Aptos/Signer.h index 239941c4347..e0222515ca0 100644 --- a/src/Aptos/Signer.h +++ b/src/Aptos/Signer.h @@ -9,6 +9,7 @@ #include "Data.h" #include "../PrivateKey.h" #include "../proto/Aptos.pb.h" +#include "../proto/TransactionCompiler.pb.h" namespace TW::Aptos { @@ -22,6 +23,10 @@ class Signer { /// Signs a Proto::SigningInput transaction static Proto::SigningOutput sign(const Proto::SigningInput& input); + + static TxCompiler::Proto::PreSigningOutput preImageHashes(const Proto::SigningInput& input); + + static Proto::SigningOutput compile(const Proto::SigningInput& input, const Data& signature, const PublicKey& publicKey); }; } // namespace TW::Aptos diff --git a/src/Aptos/TransactionBuilder.h b/src/Aptos/TransactionBuilder.h index cc839a0e7fa..46bf3fbaa12 100644 --- a/src/Aptos/TransactionBuilder.h +++ b/src/Aptos/TransactionBuilder.h @@ -8,15 +8,82 @@ #include "HexCoding.h" #include "TransactionPayload.h" + #include +#include namespace TW::Aptos { -class TransactionBuilder { +struct TransactionBase; + +using TransactionBasePtr = std::unique_ptr; + +struct TransactionBase { + virtual ~TransactionBase() = default; + + virtual TxCompiler::Proto::PreSigningOutput preImage() noexcept = 0; + + virtual Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) = 0; + + virtual Proto::SigningOutput sign(const Proto::SigningInput& input) = 0; +}; + +class BlindBuilder final : public TransactionBase { public: - TransactionBuilder() noexcept = default; + BlindBuilder() noexcept = default; + + BlindBuilder& encodedCallHex(const std::string& encodedCallHex) { + mEncodedCall = parse_hex(encodedCallHex); + return *this; + } + + TxCompiler::Proto::PreSigningOutput preImage() noexcept override { + TxCompiler::Proto::PreSigningOutput output; + // Aptos has no preImageHash. + output.set_data(mEncodedCall.data(), mEncodedCall.size()); + return output; + } + + Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) override { + Proto::SigningOutput output; + const auto& pubKeyData = publicKey.bytes; + + BCS::Serializer serializer; + serializer.add_bytes(begin(mEncodedCall), end(mEncodedCall)); + + output.set_raw_txn(mEncodedCall.data(), mEncodedCall.size()); + output.mutable_authenticator()->set_public_key(pubKeyData.data(), pubKeyData.size()); + output.mutable_authenticator()->set_signature(signature.data(), signature.size()); + serializer << BCS::uleb128{.value = 0} << pubKeyData << signature; + output.set_encoded(serializer.bytes.data(), serializer.bytes.size()); + + // clang-format off + nlohmann::json json = { + {"type", "ed25519_signature"}, + {"public_key", hexEncoded(pubKeyData)}, + {"signature", hexEncoded(signature)} + }; + // clang-format on + output.set_json(json.dump()); - static TransactionBuilder builder() noexcept { return {}; } + return output; + } + + Proto::SigningOutput sign(const Proto::SigningInput& input) override { + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + auto signature = privateKey.sign(mEncodedCall, TWCurveED25519); + return compile(signature, publicKey); + } + +private: + Data mEncodedCall; +}; + +// Standard transaction builder. +class TransactionBuilder final : public TransactionBase { +public: + TransactionBuilder() noexcept = default; TransactionBuilder& sender(Address sender) noexcept { mSender = sender; @@ -53,17 +120,37 @@ class TransactionBuilder { return *this; } - TransactionBuilder& sign(const Proto::SigningInput& input, Proto::SigningOutput& output) noexcept { + BCS::Serializer prepareSerializer() noexcept { BCS::Serializer serializer; serializer << mSender << mSequenceNumber << mPayload << mMaxGasAmount << mGasUnitPrice << mExpirationTimestampSecs << mChainId; - auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + return serializer; + } + + Data msgToSign() noexcept { + auto serialized = prepareSerializer().bytes; + auto preImageOutput = TW::Hash::sha3_256(gAptosSalt.data(), gAptosSalt.size()); + append(preImageOutput, serialized); + return preImageOutput; + } + + TxCompiler::Proto::PreSigningOutput preImage() noexcept override { + TxCompiler::Proto::PreSigningOutput output; + auto signingMsg = msgToSign(); + // Aptos has no preImageHash. + output.set_data(signingMsg.data(), signingMsg.size()); + return output; + } + + Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) noexcept override { + Proto::SigningOutput output; + const auto& pubKeyData = publicKey.bytes; + + auto serializer = prepareSerializer(); + output.set_raw_txn(serializer.bytes.data(), serializer.bytes.size()); - auto msgToSign = TW::Hash::sha3_256(gAptosSalt.data(), gAptosSalt.size()); - append(msgToSign, serializer.bytes); - auto signature = privateKey.sign(msgToSign, TWCurveED25519); - auto pubKeyData = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; output.mutable_authenticator()->set_public_key(pubKeyData.data(), pubKeyData.size()); output.mutable_authenticator()->set_signature(signature.data(), signature.size()); + serializer << BCS::uleb128{.value = 0} << pubKeyData << signature; output.set_encoded(serializer.bytes.data(), serializer.bytes.size()); @@ -84,7 +171,15 @@ class TransactionBuilder { }; // clang-format on output.set_json(json.dump()); - return *this; + return output; + } + + Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept override { + auto signingMsg = msgToSign(); + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto signature = privateKey.sign(signingMsg, TWCurveED25519); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + return compile(signature, publicKey); } private: diff --git a/src/Sui/Entry.cpp b/src/Sui/Entry.cpp index 8df448495ca..f745a5e87e2 100644 --- a/src/Sui/Entry.cpp +++ b/src/Sui/Entry.cpp @@ -9,6 +9,8 @@ #include "Address.h" #include "Signer.h" +#include "proto/TransactionCompiler.pb.h" + namespace TW::Sui { bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { @@ -23,4 +25,21 @@ void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D signTemplate(dataIn, dataOut); } +Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { + return txCompilerTemplate( + txInputData, [](const auto& input, auto& output) { + output = Signer::preImageHashes(input); + }); +} + +void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { + dataOut = txCompilerSingleTemplate( + txInputData, signatures, publicKeys, + [](const auto& input, auto& output, const auto& signature, const auto& publicKey) { + auto txSignatureScheme = Signer::signatureScheme(signature, publicKey); + output.set_unsigned_tx(input.sign_direct_message().unsigned_tx_msg()); + output.set_signature(txSignatureScheme); + }); +} + } // namespace TW::Sui diff --git a/src/Sui/Entry.h b/src/Sui/Entry.h index 75cfde95062..e2bb9959b91 100644 --- a/src/Sui/Entry.h +++ b/src/Sui/Entry.h @@ -12,9 +12,11 @@ namespace TW::Sui { class Entry final : public CoinEntry { public: - bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const; - std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const; - void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const; + bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override; + std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override; + void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override; + Data preImageHashes(TWCoinType coin, const Data& txInputData) const override; + void compile(TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const override; }; } // namespace TW::Sui diff --git a/src/Sui/Signer.cpp b/src/Sui/Signer.cpp index 52b82485aed..875b23b860b 100644 --- a/src/Sui/Signer.cpp +++ b/src/Sui/Signer.cpp @@ -27,19 +27,45 @@ enum IntentAppId { namespace TW::Sui { -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { +Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) { auto protoOutput = Proto::SigningOutput(); + auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + + auto toSign = transactionPreimage(input); + auto signature = privateKey.sign(TW::Hash::blake2b(toSign, 32), TWCurveED25519); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + auto txSignatureScheme = signatureScheme(signature, publicKey); + + auto unsignedTx = input.sign_direct_message().unsigned_tx_msg(); + protoOutput.set_unsigned_tx(unsignedTx); + protoOutput.set_signature(txSignatureScheme); + return protoOutput; +} + +TxCompiler::Proto::PreSigningOutput Signer::preImageHashes(const Proto::SigningInput& input) { + TxCompiler::Proto::PreSigningOutput output; + auto preImage = Signer::transactionPreimage(input); + auto preImageHash = TW::Hash::blake2b(preImage, 32); + output.set_data(preImage.data(), preImage.size()); + output.set_data_hash(preImageHash.data(), preImageHash.size()); + return output; +} + +Data Signer::transactionPreimage(const Proto::SigningInput& input) { auto unsignedTx = input.sign_direct_message().unsigned_tx_msg(); auto unsignedTxData = TW::Base64::decode(unsignedTx); Data toSign{TransactionData, V0, IntentAppId::Sui}; append(toSign, unsignedTxData); - auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + return toSign; +} + +std::string Signer::signatureScheme(const Data& signature, const PublicKey& publicKey) { Data signatureScheme{0x00}; - append(signatureScheme, privateKey.sign(TW::Hash::blake2b(toSign, 32), TWCurveED25519)); - append(signatureScheme, privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes); - protoOutput.set_unsigned_tx(unsignedTx); - protoOutput.set_signature(TW::Base64::encode(signatureScheme)); - return protoOutput; + append(signatureScheme, signature); + append(signatureScheme, publicKey.bytes); + return TW::Base64::encode(signatureScheme); } +// Data + } // namespace TW::Sui diff --git a/src/Sui/Signer.h b/src/Sui/Signer.h index 2b158464c88..83573cb054a 100644 --- a/src/Sui/Signer.h +++ b/src/Sui/Signer.h @@ -9,6 +9,7 @@ #include "Data.h" #include "PrivateKey.h" #include "proto/Sui.pb.h" +#include "proto/TransactionCompiler.pb.h" namespace TW::Sui { @@ -19,7 +20,14 @@ class Signer { Signer() = delete; /// Signs a Proto::SigningInput transaction - static Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept; + static Proto::SigningOutput sign(const Proto::SigningInput& input); + + static TxCompiler::Proto::PreSigningOutput preImageHashes(const Proto::SigningInput& input); + + /// Get transaction data to be signed (with a type tag). + static Data transactionPreimage(const Proto::SigningInput& input); + + static std::string signatureScheme(const Data& signature, const PublicKey& publicKey); }; } // namespace TW::Sui diff --git a/src/proto/Aptos.proto b/src/proto/Aptos.proto index 6f124b47423..774bce9f2fd 100644 --- a/src/proto/Aptos.proto +++ b/src/proto/Aptos.proto @@ -9,6 +9,8 @@ syntax = "proto3"; package TW.Aptos.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Necessary fields to process a TransferMessage message TransferMessage { // Destination Account address (string) @@ -189,4 +191,10 @@ message SigningOutput { // Transaction json format for api broadcasting (string) string json = 4; + + // Error code, 0 is ok, other codes will be treated as errors. + Common.Proto.SigningError error = 5; + + // Error description. + string error_message = 6; } diff --git a/src/proto/Sui.proto b/src/proto/Sui.proto index dea0161e005..8c033a8bf85 100644 --- a/src/proto/Sui.proto +++ b/src/proto/Sui.proto @@ -9,6 +9,8 @@ syntax = "proto3"; package TW.Sui.Proto; option java_package = "wallet.core.jni.proto"; +import "Common.proto"; + // Base64 encoded msg to sign (string) message SignDirect { // Obtain by calling any write RpcJson on SUI @@ -29,6 +31,13 @@ message SigningInput { message SigningOutput { /// The raw transaction without indent in base64 string unsigned_tx = 1; + /// The signature encoded in base64 string signature = 2; + + // Error code, 0 is ok, other codes will be treated as errors. + Common.Proto.SigningError error = 3; + + // Error description. + string error_message = 4; } diff --git a/tests/chains/Aptos/CompilerTests.cpp b/tests/chains/Aptos/CompilerTests.cpp new file mode 100644 index 00000000000..6f731cfbfe5 --- /dev/null +++ b/tests/chains/Aptos/CompilerTests.cpp @@ -0,0 +1,224 @@ +// 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 "Aptos/Signer.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TestUtilities.h" +#include "TransactionCompiler.h" + +#include +#include + +namespace TW::Aptos::tests { + +TEST(AptosCompiler, StandardTransaction) { + // Set up a signing input. + + Proto::SigningInput input; + input.set_sender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + input.set_sequence_number(99); + auto& tf = *input.mutable_transfer(); + tf.set_to("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + tf.set_amount(1000); + input.set_max_gas_amount(3296766); + input.set_gas_unit_price(100); + input.set_expiration_timestamp_secs(3664390082); + input.set_chain_id(33); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + // Pre-hash the transaction. + + auto preImageHashesData = TransactionCompiler::preImageHashes(TWCoinTypeAptos, inputStrData); + TxCompiler::Proto::PreSigningOutput preSigningOutput; + preSigningOutput.ParseFromArray(preImageHashesData.data(), static_cast(preImageHashesData.size())); + auto actualDataToSign = data(preSigningOutput.data()); + + EXPECT_EQ(preSigningOutput.error(), Common::Proto::OK); + EXPECT_EQ(hex(actualDataToSign), "b5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada0000000021"); + + // Sign the pre-hash data. + + auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; + auto signature = privateKey.sign(actualDataToSign, TWCurveED25519); + EXPECT_EQ(hex(signature), "5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"); + + // Compile the transaction. + + auto outputData = TransactionCompiler::compileWithSignatures(TWCoinTypeAptos, inputStrData, {signature}, {publicKey}); + Proto::SigningOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); + + EXPECT_EQ(output.error(), Common::Proto::OK); + ASSERT_EQ(hex(output.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada0000000021"); + ASSERT_EQ(hex(output.authenticator().signature()), "5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"); + ASSERT_EQ(hex(output.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c405707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"); + nlohmann::json expectedJson = R"( + { + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": ["0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","1000"], + "function": "0x1::aptos_account::transfer", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "99", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01", + "type": "ed25519_signature" + } + } + )"_json; + nlohmann::json parsedJson = nlohmann::json::parse(output.json()); + assertJSONEqual(expectedJson, parsedJson); +} + +TEST(AptosCompiler, BlindTransactionJson) { + // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x7efd69e7f9462774b932ce500ab51c0d0dcc004cf272e09f8ffd5804c2a84e33?network=mainnet + + auto payloadJson = R"( + { + "function": "0x16fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c::AnimeSwapPoolV1::swap_exact_coins_for_coins_3_pair_entry", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin", + "0x881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f4::coin::MOJO", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDT", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC" + ], + "arguments": [ + "1000000", + "49329" + ], + "type": "entry_function_payload" + })"_json; + Proto::SigningInput input; + input.set_sequence_number(42); + input.set_sender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + input.set_gas_unit_price(100); + input.set_max_gas_amount(100011); + input.set_expiration_timestamp_secs(3664390082); + input.set_any_encoded(payloadJson.dump()); + input.set_chain_id(1); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + // Pre-hash the transaction. + + auto preImageHashesData = TransactionCompiler::preImageHashes(TWCoinTypeAptos, inputStrData); + TxCompiler::Proto::PreSigningOutput preSigningOutput; + preSigningOutput.ParseFromArray(preImageHashesData.data(), static_cast(preImageHashesData.size())); + auto actualDataToSign = data(preSigningOutput.data()); + + EXPECT_EQ(preSigningOutput.error(), Common::Proto::OK); + EXPECT_EQ(hex(actualDataToSign), "b5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f302a000000000000000216fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c0f416e696d6553776170506f6f6c563127737761705f65786163745f636f696e735f666f725f636f696e735f335f706169725f656e747279040700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e0007881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f404636f696e044d4f4a4f0007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa05617373657404555344540007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa056173736574045553444300020840420f000000000008b1c0000000000000ab860100000000006400000000000000c2276ada0000000001"); + + // Sign the pre-hash data. + + auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; + auto signature = privateKey.sign(actualDataToSign, TWCurveED25519); + EXPECT_EQ(hex(signature), "42cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b"); + + // Compile the transaction. + + auto outputData = TransactionCompiler::compileWithSignatures(TWCoinTypeAptos, inputStrData, {signature}, {publicKey}); + Proto::SigningOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); + + EXPECT_EQ(output.error(), Common::Proto::OK); + ASSERT_EQ(hex(output.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f302a000000000000000216fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c0f416e696d6553776170506f6f6c563127737761705f65786163745f636f696e735f666f725f636f696e735f335f706169725f656e747279040700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e0007881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f404636f696e044d4f4a4f0007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa05617373657404555344540007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa056173736574045553444300020840420f000000000008b1c0000000000000ab860100000000006400000000000000c2276ada0000000001"); + ASSERT_EQ(hex(output.authenticator().signature()), "42cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b"); + ASSERT_EQ(hex(output.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f302a000000000000000216fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c0f416e696d6553776170506f6f6c563127737761705f65786163745f636f696e735f666f725f636f696e735f335f706169725f656e747279040700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e0007881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f404636f696e044d4f4a4f0007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa05617373657404555344540007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa056173736574045553444300020840420f000000000008b1c0000000000000ab860100000000006400000000000000c2276ada00000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c4042cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b"); + nlohmann::json expectedJson = R"( +{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "100011", + "payload": { + "function": "0x16fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c::AnimeSwapPoolV1::swap_exact_coins_for_coins_3_pair_entry", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin", + "0x881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f4::coin::MOJO", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDT", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC" + ], + "arguments": [ + "1000000", + "49329" + ], + "type": "entry_function_payload" + }, + "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "42", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x42cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b", + "type": "ed25519_signature" + } +} + )"_json; + nlohmann::json parsedJson = nlohmann::json::parse(output.json()); + assertJSONEqual(expectedJson, parsedJson); +} + +TEST(AptosCompiler, BlindTransactionHex) { + // successfully broadcasted https://explorer.aptoslabs.com/txn/0xd95857a9e644528708778a3a0a6e13986751944fca30eaac98853c1655de0422?network=Devnet + auto anyEncoded = "0xb5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f300200000000000000024633134869a61c41ad42eaca028d71c5b8b4109ffd69e1aa99c35a621b29883704706f6f6c0b737761705f795f746f5f780207deae46f81671e76f444e2ce5a299d9e1ea06a8fa26e81dfd49aa7fa5a5a60e010c6465766e65745f636f696e730a4465766e657455534454000700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00020800e1f50500000000080000000000000000fe4d3200000000006400000000000000c2276ada0000000021"; + + Proto::SigningInput input; + input.set_any_encoded(anyEncoded); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + // Pre-hash the transaction. + + auto preImageHashesData = TransactionCompiler::preImageHashes(TWCoinTypeAptos, inputStrData); + TxCompiler::Proto::PreSigningOutput preSigningOutput; + preSigningOutput.ParseFromArray(preImageHashesData.data(), static_cast(preImageHashesData.size())); + auto actualDataToSign = data(preSigningOutput.data()); + + EXPECT_EQ(preSigningOutput.error(), Common::Proto::OK); + EXPECT_EQ(actualDataToSign, parse_hex(anyEncoded)); + + // Sign the pre-hash data. + + auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; + auto signature = privateKey.sign(actualDataToSign, TWCurveED25519); + EXPECT_EQ(hex(signature), "9e81026fdd43986f4d5588afdab875cd18b64dc15b3489fcc00ed46fc361915b27e23e0cefe6d23698ee76a562915fe85e99185dbc1dd29ba720f7fad144af0b"); + + // Compile the transaction. + + auto outputData = TransactionCompiler::compileWithSignatures(TWCoinTypeAptos, inputStrData, {signature}, {publicKey}); + Proto::SigningOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); + + EXPECT_EQ(output.error(), Common::Proto::OK); + ASSERT_EQ(hex(output.raw_txn()), "b5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f300200000000000000024633134869a61c41ad42eaca028d71c5b8b4109ffd69e1aa99c35a621b29883704706f6f6c0b737761705f795f746f5f780207deae46f81671e76f444e2ce5a299d9e1ea06a8fa26e81dfd49aa7fa5a5a60e010c6465766e65745f636f696e730a4465766e657455534454000700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00020800e1f50500000000080000000000000000fe4d3200000000006400000000000000c2276ada0000000021"); + ASSERT_EQ(hex(output.authenticator().signature()), "9e81026fdd43986f4d5588afdab875cd18b64dc15b3489fcc00ed46fc361915b27e23e0cefe6d23698ee76a562915fe85e99185dbc1dd29ba720f7fad144af0b"); + ASSERT_EQ(hex(output.encoded()), "b5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f300200000000000000024633134869a61c41ad42eaca028d71c5b8b4109ffd69e1aa99c35a621b29883704706f6f6c0b737761705f795f746f5f780207deae46f81671e76f444e2ce5a299d9e1ea06a8fa26e81dfd49aa7fa5a5a60e010c6465766e65745f636f696e730a4465766e657455534454000700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00020800e1f50500000000080000000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c409e81026fdd43986f4d5588afdab875cd18b64dc15b3489fcc00ed46fc361915b27e23e0cefe6d23698ee76a562915fe85e99185dbc1dd29ba720f7fad144af0b"); + nlohmann::json expectedJson = R"( + { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x9e81026fdd43986f4d5588afdab875cd18b64dc15b3489fcc00ed46fc361915b27e23e0cefe6d23698ee76a562915fe85e99185dbc1dd29ba720f7fad144af0b", + "type": "ed25519_signature" + } + )"_json; + nlohmann::json parsedJson = nlohmann::json::parse(output.json()); + assertJSONEqual(expectedJson, parsedJson); +} + +} diff --git a/tests/chains/Sui/CompilerTests.cpp b/tests/chains/Sui/CompilerTests.cpp new file mode 100644 index 00000000000..0b94cd53781 --- /dev/null +++ b/tests/chains/Sui/CompilerTests.cpp @@ -0,0 +1,48 @@ +// 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 "Sui/Signer.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "TransactionCompiler.h" + +#include + +namespace TW::Sui::tests { + +TEST(SuiCompiler, PreHashAndCompile) { + // Successfully broadcasted https://explorer.sui.io/txblock/HkPo6rYPyDY53x1MBszvSZVZyixVN7CHvCJGX381czAh?network=devnet + auto expectedData = parse_hex("000000000002000810270000000000000020259ff8074ab425cbb489f236e18e08f03f1a7856bdf7c7a2877bd64f738b50150202000101000001010300000000010100d575ad7f18e948462a5cf698f564ef394a752a71fec62493af8a055c012c0d500106f2c2c8c1d8964df1019d6616e9705719bebabd931da2755cb948ceb7e68964ec020000000000002060456ec667f5cd10467680ebf950ed329205175dacd946bb236aeed57c8617cfd575ad7f18e948462a5cf698f564ef394a752a71fec62493af8a055c012c0d500100000000000000d00700000000000000"); + auto expectedHash = TW::Hash::blake2b(expectedData, 32); + + Proto::SigningInput input; + auto txMsg = "AAACAAgQJwAAAAAAAAAgJZ/4B0q0Jcu0ifI24Y4I8D8aeFa998eih3vWT3OLUBUCAgABAQAAAQEDAAAAAAEBANV1rX8Y6UhGKlz2mPVk7zlKdSpx/sYkk6+KBVwBLA1QAQbywsjB2JZN8QGdZhbpcFcZvrq9kx2idVy5SM635olk7AIAAAAAAAAgYEVuxmf1zRBGdoDr+VDtMpIFF12s2Ua7I2ru1XyGF8/Vda1/GOlIRipc9pj1ZO85SnUqcf7GJJOvigVcASwNUAEAAAAAAAAA0AcAAAAAAAAA"; + input.mutable_sign_direct_message()->set_unsigned_tx_msg(txMsg); + + auto inputString = input.SerializeAsString(); + auto inputStrData = TW::Data(inputString.begin(), inputString.end()); + + auto preImageHashesData = TransactionCompiler::preImageHashes(TWCoinTypeSui, inputStrData); + TxCompiler::Proto::PreSigningOutput preSigningOutput; + preSigningOutput.ParseFromArray(preImageHashesData.data(), static_cast(preImageHashesData.size())); + + EXPECT_EQ(data(preSigningOutput.data()), expectedData); + EXPECT_EQ(data(preSigningOutput.data_hash()), expectedHash); + + auto privateKey = PrivateKey(parse_hex("3823dce5288ab55dd1c00d97e91933c613417fdb282a0b8b01a7f5f5a533b266")); + auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; + auto signature = privateKey.sign(expectedHash, TWCurveED25519); + + auto outputData = TransactionCompiler::compileWithSignatures(TWCoinTypeSui, inputStrData, {signature}, {publicKey}); + Proto::SigningOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); + + EXPECT_EQ(output.error(), Common::Proto::OK); + EXPECT_EQ(output.signature(), "APxPduNVvHj2CcRcHOtiP2aBR9qP3vO2Cb0g12PI64QofDB6ks33oqe/i/iCTLcop2rBrkczwrayZuJOdi7gvwNqfN7sFqdcD/Z4e8I1YQlGkDMCK7EOgmydRDqfH8C9jg=="); +} + +}