Skip to content

Commit 85bb022

Browse files
[TxCompiler]: Support PreHash and Compile for Aptos & Sui (#3367)
1 parent 672f0ab commit 85bb022

File tree

13 files changed

+512
-59
lines changed

13 files changed

+512
-59
lines changed

src/Aptos/Entry.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,19 @@ void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::D
2323
signTemplate<Signer, Proto::SigningInput>(dataIn, dataOut);
2424
}
2525

26+
Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const {
27+
return txCompilerTemplate<Proto::SigningInput, TxCompiler::Proto::PreSigningOutput>(
28+
txInputData, [](const auto& input, auto& output) {
29+
output = Signer::preImageHashes(input);
30+
});
31+
}
32+
33+
void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector<Data>& signatures, const std::vector<PublicKey>& publicKeys, Data& dataOut) const {
34+
dataOut = txCompilerSingleTemplate<Proto::SigningInput, Proto::SigningOutput>(
35+
txInputData, signatures, publicKeys,
36+
[](const auto& input, auto& output, const auto& signature, const auto& publicKey) {
37+
output = Signer::compile(input, signature, publicKey);
38+
});
39+
}
40+
2641
} // namespace TW::Aptos

src/Aptos/Entry.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ namespace TW::Aptos {
1414
/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file
1515
class Entry final : public CoinEntry {
1616
public:
17-
bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const;
18-
std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const;
19-
void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const;
17+
bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override;
18+
std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override;
19+
void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override;
20+
Data preImageHashes(TWCoinType coin, const Data& txInputData) const override;
21+
void compile(TWCoinType coin, const Data& txInputData, const std::vector<Data>& signatures, const std::vector<PublicKey>& publicKeys, Data& dataOut) const override;
2022
};
2123

2224
} // namespace TW::Aptos

src/Aptos/Signer.cpp

Lines changed: 27 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -180,48 +180,29 @@ TransactionPayload registerTokenPayload(const Proto::SigningInput& input) {
180180
return payload;
181181
}
182182

183-
Proto::SigningOutput blindSign(const Proto::SigningInput& input) {
184-
auto output = Proto::SigningOutput();
185-
auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end()));
186-
auto pubKeyData = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes;
183+
TransactionBasePtr buildBlindTx(const Proto::SigningInput& input) {
187184
if (nlohmann::json j = nlohmann::json::parse(input.any_encoded(), nullptr, false); j.is_discarded()) {
188-
BCS::Serializer serializer;
189-
auto encodedCall = parse_hex(input.any_encoded());
190-
serializer.add_bytes(begin(encodedCall), end(encodedCall));
191-
auto signature = privateKey.sign(encodedCall, TWCurveED25519);
192-
output.set_raw_txn(encodedCall.data(), encodedCall.size());
193-
output.mutable_authenticator()->set_public_key(pubKeyData.data(), pubKeyData.size());
194-
output.mutable_authenticator()->set_signature(signature.data(), signature.size());
195-
serializer << BCS::uleb128{.value = 0} << pubKeyData << signature;
196-
output.set_encoded(serializer.bytes.data(), serializer.bytes.size());
197-
198-
// clang-format off
199-
nlohmann::json json = {
200-
{"type", "ed25519_signature"},
201-
{"public_key", hexEncoded(pubKeyData)},
202-
{"signature", hexEncoded(signature)}
203-
};
204-
// clang-format on
205-
output.set_json(json.dump());
185+
auto blindBuilder = std::make_unique<BlindBuilder>();
186+
blindBuilder->encodedCallHex(input.any_encoded());
187+
return blindBuilder;
206188
} else {
207-
TransactionBuilder::builder()
208-
.sender(Address(input.sender()))
189+
auto txBuilder = std::make_unique<TransactionBuilder>();
190+
txBuilder->sender(Address(input.sender()))
209191
.sequenceNumber(input.sequence_number())
210192
.payload(EntryFunction::from_json(j))
211193
.maxGasAmount(input.max_gas_amount())
212194
.gasUnitPrice(input.gas_unit_price())
213195
.expirationTimestampSecs(input.expiration_timestamp_secs())
214-
.chainId(static_cast<uint8_t>(input.chain_id()))
215-
.sign(input, output);
196+
.chainId(static_cast<uint8_t>(input.chain_id()));
197+
return txBuilder;
216198
}
217-
return output;
218199
}
219200

220-
Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) {
221-
auto protoOutput = Proto::SigningOutput();
201+
TransactionBasePtr buildTx(const Proto::SigningInput& input) {
222202
if (!input.any_encoded().empty()) {
223-
return blindSign(input);
203+
return buildBlindTx(input);
224204
}
205+
225206
auto nftPayloadFunctor = [](const Proto::NftMessage& nftMessage) {
226207
switch (nftMessage.nft_transaction_payload_case()) {
227208
case Proto::NftMessage::kOfferNft:
@@ -273,16 +254,27 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) {
273254
throw std::runtime_error("Transaction payload should be set");
274255
}
275256
};
276-
TransactionBuilder::builder()
277-
.sender(Address(input.sender()))
257+
auto txBuilder = std::make_unique<TransactionBuilder>();
258+
txBuilder->sender(Address(input.sender()))
278259
.sequenceNumber(input.sequence_number())
279260
.payload(payloadFunctor())
280261
.maxGasAmount(input.max_gas_amount())
281262
.gasUnitPrice(input.gas_unit_price())
282263
.expirationTimestampSecs(input.expiration_timestamp_secs())
283-
.chainId(static_cast<uint8_t>(input.chain_id()))
284-
.sign(input, protoOutput);
285-
return protoOutput;
264+
.chainId(static_cast<uint8_t>(input.chain_id()));
265+
return txBuilder;
266+
}
267+
268+
Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) {
269+
return buildTx(input)->sign(input);
270+
}
271+
272+
TxCompiler::Proto::PreSigningOutput Signer::preImageHashes(const Proto::SigningInput& input) {
273+
return buildTx(input)->preImage();
274+
}
275+
276+
Proto::SigningOutput Signer::compile(const Proto::SigningInput& input, const Data& signature, const PublicKey& publicKey) {
277+
return buildTx(input)->compile(signature, publicKey);
286278
}
287279

288280
} // namespace TW::Aptos

src/Aptos/Signer.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "Data.h"
1010
#include "../PrivateKey.h"
1111
#include "../proto/Aptos.pb.h"
12+
#include "../proto/TransactionCompiler.pb.h"
1213

1314
namespace TW::Aptos {
1415

@@ -22,6 +23,10 @@ class Signer {
2223

2324
/// Signs a Proto::SigningInput transaction
2425
static Proto::SigningOutput sign(const Proto::SigningInput& input);
26+
27+
static TxCompiler::Proto::PreSigningOutput preImageHashes(const Proto::SigningInput& input);
28+
29+
static Proto::SigningOutput compile(const Proto::SigningInput& input, const Data& signature, const PublicKey& publicKey);
2530
};
2631

2732
} // namespace TW::Aptos

src/Aptos/TransactionBuilder.h

Lines changed: 105 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,82 @@
88

99
#include "HexCoding.h"
1010
#include "TransactionPayload.h"
11+
1112
#include <nlohmann/json.hpp>
13+
#include <memory>
1214

1315
namespace TW::Aptos {
1416

15-
class TransactionBuilder {
17+
struct TransactionBase;
18+
19+
using TransactionBasePtr = std::unique_ptr<TransactionBase>;
20+
21+
struct TransactionBase {
22+
virtual ~TransactionBase() = default;
23+
24+
virtual TxCompiler::Proto::PreSigningOutput preImage() noexcept = 0;
25+
26+
virtual Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) = 0;
27+
28+
virtual Proto::SigningOutput sign(const Proto::SigningInput& input) = 0;
29+
};
30+
31+
class BlindBuilder final : public TransactionBase {
1632
public:
17-
TransactionBuilder() noexcept = default;
33+
BlindBuilder() noexcept = default;
34+
35+
BlindBuilder& encodedCallHex(const std::string& encodedCallHex) {
36+
mEncodedCall = parse_hex(encodedCallHex);
37+
return *this;
38+
}
39+
40+
TxCompiler::Proto::PreSigningOutput preImage() noexcept override {
41+
TxCompiler::Proto::PreSigningOutput output;
42+
// Aptos has no preImageHash.
43+
output.set_data(mEncodedCall.data(), mEncodedCall.size());
44+
return output;
45+
}
46+
47+
Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) override {
48+
Proto::SigningOutput output;
49+
const auto& pubKeyData = publicKey.bytes;
50+
51+
BCS::Serializer serializer;
52+
serializer.add_bytes(begin(mEncodedCall), end(mEncodedCall));
53+
54+
output.set_raw_txn(mEncodedCall.data(), mEncodedCall.size());
55+
output.mutable_authenticator()->set_public_key(pubKeyData.data(), pubKeyData.size());
56+
output.mutable_authenticator()->set_signature(signature.data(), signature.size());
57+
serializer << BCS::uleb128{.value = 0} << pubKeyData << signature;
58+
output.set_encoded(serializer.bytes.data(), serializer.bytes.size());
59+
60+
// clang-format off
61+
nlohmann::json json = {
62+
{"type", "ed25519_signature"},
63+
{"public_key", hexEncoded(pubKeyData)},
64+
{"signature", hexEncoded(signature)}
65+
};
66+
// clang-format on
67+
output.set_json(json.dump());
1868

19-
static TransactionBuilder builder() noexcept { return {}; }
69+
return output;
70+
}
71+
72+
Proto::SigningOutput sign(const Proto::SigningInput& input) override {
73+
auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end()));
74+
auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519);
75+
auto signature = privateKey.sign(mEncodedCall, TWCurveED25519);
76+
return compile(signature, publicKey);
77+
}
78+
79+
private:
80+
Data mEncodedCall;
81+
};
82+
83+
// Standard transaction builder.
84+
class TransactionBuilder final : public TransactionBase {
85+
public:
86+
TransactionBuilder() noexcept = default;
2087

2188
TransactionBuilder& sender(Address sender) noexcept {
2289
mSender = sender;
@@ -53,17 +120,37 @@ class TransactionBuilder {
53120
return *this;
54121
}
55122

56-
TransactionBuilder& sign(const Proto::SigningInput& input, Proto::SigningOutput& output) noexcept {
123+
BCS::Serializer prepareSerializer() noexcept {
57124
BCS::Serializer serializer;
58125
serializer << mSender << mSequenceNumber << mPayload << mMaxGasAmount << mGasUnitPrice << mExpirationTimestampSecs << mChainId;
59-
auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end()));
126+
return serializer;
127+
}
128+
129+
Data msgToSign() noexcept {
130+
auto serialized = prepareSerializer().bytes;
131+
auto preImageOutput = TW::Hash::sha3_256(gAptosSalt.data(), gAptosSalt.size());
132+
append(preImageOutput, serialized);
133+
return preImageOutput;
134+
}
135+
136+
TxCompiler::Proto::PreSigningOutput preImage() noexcept override {
137+
TxCompiler::Proto::PreSigningOutput output;
138+
auto signingMsg = msgToSign();
139+
// Aptos has no preImageHash.
140+
output.set_data(signingMsg.data(), signingMsg.size());
141+
return output;
142+
}
143+
144+
Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) noexcept override {
145+
Proto::SigningOutput output;
146+
const auto& pubKeyData = publicKey.bytes;
147+
148+
auto serializer = prepareSerializer();
149+
60150
output.set_raw_txn(serializer.bytes.data(), serializer.bytes.size());
61-
auto msgToSign = TW::Hash::sha3_256(gAptosSalt.data(), gAptosSalt.size());
62-
append(msgToSign, serializer.bytes);
63-
auto signature = privateKey.sign(msgToSign, TWCurveED25519);
64-
auto pubKeyData = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes;
65151
output.mutable_authenticator()->set_public_key(pubKeyData.data(), pubKeyData.size());
66152
output.mutable_authenticator()->set_signature(signature.data(), signature.size());
153+
67154
serializer << BCS::uleb128{.value = 0} << pubKeyData << signature;
68155
output.set_encoded(serializer.bytes.data(), serializer.bytes.size());
69156

@@ -84,7 +171,15 @@ class TransactionBuilder {
84171
};
85172
// clang-format on
86173
output.set_json(json.dump());
87-
return *this;
174+
return output;
175+
}
176+
177+
Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept override {
178+
auto signingMsg = msgToSign();
179+
auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end()));
180+
auto signature = privateKey.sign(signingMsg, TWCurveED25519);
181+
auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519);
182+
return compile(signature, publicKey);
88183
}
89184

90185
private:

src/Sui/Entry.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include "Address.h"
1010
#include "Signer.h"
1111

12+
#include "proto/TransactionCompiler.pb.h"
13+
1214
namespace TW::Sui {
1315

1416
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
2325
signTemplate<Signer, Proto::SigningInput>(dataIn, dataOut);
2426
}
2527

28+
Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const {
29+
return txCompilerTemplate<Proto::SigningInput, TxCompiler::Proto::PreSigningOutput>(
30+
txInputData, [](const auto& input, auto& output) {
31+
output = Signer::preImageHashes(input);
32+
});
33+
}
34+
35+
void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector<Data>& signatures, const std::vector<PublicKey>& publicKeys, Data& dataOut) const {
36+
dataOut = txCompilerSingleTemplate<Proto::SigningInput, Proto::SigningOutput>(
37+
txInputData, signatures, publicKeys,
38+
[](const auto& input, auto& output, const auto& signature, const auto& publicKey) {
39+
auto txSignatureScheme = Signer::signatureScheme(signature, publicKey);
40+
output.set_unsigned_tx(input.sign_direct_message().unsigned_tx_msg());
41+
output.set_signature(txSignatureScheme);
42+
});
43+
}
44+
2645
} // namespace TW::Sui

src/Sui/Entry.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ namespace TW::Sui {
1212

1313
class Entry final : public CoinEntry {
1414
public:
15-
bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const;
16-
std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const;
17-
void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const;
15+
bool validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const override;
16+
std::string deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const override;
17+
void sign(TWCoinType coin, const Data& dataIn, Data& dataOut) const override;
18+
Data preImageHashes(TWCoinType coin, const Data& txInputData) const override;
19+
void compile(TWCoinType coin, const Data& txInputData, const std::vector<Data>& signatures, const std::vector<PublicKey>& publicKeys, Data& dataOut) const override;
1820
};
1921

2022
} // namespace TW::Sui

src/Sui/Signer.cpp

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,45 @@ enum IntentAppId {
2727

2828
namespace TW::Sui {
2929

30-
Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept {
30+
Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) {
3131
auto protoOutput = Proto::SigningOutput();
32+
auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end()));
33+
34+
auto toSign = transactionPreimage(input);
35+
auto signature = privateKey.sign(TW::Hash::blake2b(toSign, 32), TWCurveED25519);
36+
auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519);
37+
auto txSignatureScheme = signatureScheme(signature, publicKey);
38+
39+
auto unsignedTx = input.sign_direct_message().unsigned_tx_msg();
40+
protoOutput.set_unsigned_tx(unsignedTx);
41+
protoOutput.set_signature(txSignatureScheme);
42+
return protoOutput;
43+
}
44+
45+
TxCompiler::Proto::PreSigningOutput Signer::preImageHashes(const Proto::SigningInput& input) {
46+
TxCompiler::Proto::PreSigningOutput output;
47+
auto preImage = Signer::transactionPreimage(input);
48+
auto preImageHash = TW::Hash::blake2b(preImage, 32);
49+
output.set_data(preImage.data(), preImage.size());
50+
output.set_data_hash(preImageHash.data(), preImageHash.size());
51+
return output;
52+
}
53+
54+
Data Signer::transactionPreimage(const Proto::SigningInput& input) {
3255
auto unsignedTx = input.sign_direct_message().unsigned_tx_msg();
3356
auto unsignedTxData = TW::Base64::decode(unsignedTx);
3457
Data toSign{TransactionData, V0, IntentAppId::Sui};
3558
append(toSign, unsignedTxData);
36-
auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end()));
59+
return toSign;
60+
}
61+
62+
std::string Signer::signatureScheme(const Data& signature, const PublicKey& publicKey) {
3763
Data signatureScheme{0x00};
38-
append(signatureScheme, privateKey.sign(TW::Hash::blake2b(toSign, 32), TWCurveED25519));
39-
append(signatureScheme, privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes);
40-
protoOutput.set_unsigned_tx(unsignedTx);
41-
protoOutput.set_signature(TW::Base64::encode(signatureScheme));
42-
return protoOutput;
64+
append(signatureScheme, signature);
65+
append(signatureScheme, publicKey.bytes);
66+
return TW::Base64::encode(signatureScheme);
4367
}
4468

69+
// Data
70+
4571
} // namespace TW::Sui

0 commit comments

Comments
 (0)