Skip to content

Commit

Permalink
[TxCompiler]: Support PreHash and Compile for Aptos & Sui (#3367)
Browse files Browse the repository at this point in the history
  • Loading branch information
satoshiotomakan authored Aug 15, 2023
1 parent 672f0ab commit 85bb022
Show file tree
Hide file tree
Showing 13 changed files with 512 additions and 59 deletions.
15 changes: 15 additions & 0 deletions src/Aptos/Entry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,19 @@ void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D
signTemplate<Signer, Proto::SigningInput>(dataIn, dataOut);
}

Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const {
return txCompilerTemplate<Proto::SigningInput, TxCompiler::Proto::PreSigningOutput>(
txInputData, [](const auto& input, auto& output) {
output = Signer::preImageHashes(input);
});
}

void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector<Data>& signatures, const std::vector<PublicKey>& publicKeys, Data& dataOut) const {
dataOut = txCompilerSingleTemplate<Proto::SigningInput, Proto::SigningOutput>(
txInputData, signatures, publicKeys,
[](const auto& input, auto& output, const auto& signature, const auto& publicKey) {
output = Signer::compile(input, signature, publicKey);
});
}

} // namespace TW::Aptos
8 changes: 5 additions & 3 deletions src/Aptos/Entry.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Data>& signatures, const std::vector<PublicKey>& publicKeys, Data& dataOut) const override;
};

} // namespace TW::Aptos
62 changes: 27 additions & 35 deletions src/Aptos/Signer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>();
blindBuilder->encodedCallHex(input.any_encoded());
return blindBuilder;
} else {
TransactionBuilder::builder()
.sender(Address(input.sender()))
auto txBuilder = std::make_unique<TransactionBuilder>();
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<uint8_t>(input.chain_id()))
.sign(input, output);
.chainId(static_cast<uint8_t>(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:
Expand Down Expand Up @@ -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<TransactionBuilder>();
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<uint8_t>(input.chain_id()))
.sign(input, protoOutput);
return protoOutput;
.chainId(static_cast<uint8_t>(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
5 changes: 5 additions & 0 deletions src/Aptos/Signer.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "Data.h"
#include "../PrivateKey.h"
#include "../proto/Aptos.pb.h"
#include "../proto/TransactionCompiler.pb.h"

namespace TW::Aptos {

Expand All @@ -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
115 changes: 105 additions & 10 deletions src/Aptos/TransactionBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,82 @@

#include "HexCoding.h"
#include "TransactionPayload.h"

#include <nlohmann/json.hpp>
#include <memory>

namespace TW::Aptos {

class TransactionBuilder {
struct TransactionBase;

using TransactionBasePtr = std::unique_ptr<TransactionBase>;

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;
Expand Down Expand Up @@ -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());

Expand All @@ -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:
Expand Down
19 changes: 19 additions & 0 deletions src/Sui/Entry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -23,4 +25,21 @@ void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D
signTemplate<Signer, Proto::SigningInput>(dataIn, dataOut);
}

Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const {
return txCompilerTemplate<Proto::SigningInput, TxCompiler::Proto::PreSigningOutput>(
txInputData, [](const auto& input, auto& output) {
output = Signer::preImageHashes(input);
});
}

void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector<Data>& signatures, const std::vector<PublicKey>& publicKeys, Data& dataOut) const {
dataOut = txCompilerSingleTemplate<Proto::SigningInput, Proto::SigningOutput>(
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
8 changes: 5 additions & 3 deletions src/Sui/Entry.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Data>& signatures, const std::vector<PublicKey>& publicKeys, Data& dataOut) const override;
};

} // namespace TW::Sui
40 changes: 33 additions & 7 deletions src/Sui/Signer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading

0 comments on commit 85bb022

Please sign in to comment.