From c08a40707ab12812fd6ed3e30698e5c81763bf7c Mon Sep 17 00:00:00 2001 From: David Kim <63450340+PowerStream3604@users.noreply.github.com> Date: Thu, 8 Feb 2024 18:29:21 +0900 Subject: [PATCH] [Barz] Add prefixedMsgHash and diamondCut Encoder (#3680) * Add declaration to Barz header * Add Bytes32 type to ETH ABI Proto * Add DiamondCut input to Barz Proto * Add functions for Barz * Add Barz interface * Add Barz test * Update comments * Update header comments * Remove comments * Update hardcode text to constants * Update initData encoding --------- Co-authored-by: Sztergbaum Roman --- include/TrustWalletCore/TWBarz.h | 16 ++++ src/Ethereum/ABI/ProtoParam.h | 20 +++++ src/Ethereum/Barz.cpp | 133 ++++++++++++++++++++++++++++ src/Ethereum/Barz.h | 2 + src/interface/TWBarz.cpp | 21 +++++ src/proto/Barz.proto | 22 +++++ tests/chains/Ethereum/BarzTests.cpp | 102 +++++++++++++++++++++ 7 files changed, 316 insertions(+) diff --git a/include/TrustWalletCore/TWBarz.h b/include/TrustWalletCore/TWBarz.h index bc2d0615ba0..1454e219b5d 100644 --- a/include/TrustWalletCore/TWBarz.h +++ b/include/TrustWalletCore/TWBarz.h @@ -39,4 +39,20 @@ TWData *_Nonnull TWBarzGetInitCode(TWString* _Nonnull factory, struct TWPublicKe /// \return Bytes of the formatted signature TW_EXPORT_STATIC_METHOD TWData *_Nonnull TWBarzGetFormattedSignature(TWData* _Nonnull signature, TWData* _Nonnull challenge, TWData* _Nonnull authenticatorData, TWString* _Nonnull clientDataJSON); + +/// Returns the final hash to be signed by Barz for signing messages & typed data +/// +/// \param msgHash Original msgHash +/// \param barzAddress The address of Barz wallet signing the message +/// \param chainId The chainId of the network the verification will happen +/// \return The final hash to be signed +TW_EXPORT_STATIC_METHOD +TWData *_Nonnull TWBarzGetPrefixedMsgHash(TWData* _Nonnull msgHash, TWString* _Nonnull barzAddress, uint32_t chainId); + +/// Returns the encoded diamondCut function call for Barz contract upgrades +/// +/// \param input The serialized data of DiamondCutInput +/// \return The encoded bytes of diamondCut function call +TW_EXPORT_STATIC_METHOD +TWData *_Nonnull TWBarzGetDiamondCutCode(TWData *_Nonnull input); TW_EXTERN_C_END diff --git a/src/Ethereum/ABI/ProtoParam.h b/src/Ethereum/ABI/ProtoParam.h index c9d6c2f1c80..8391ee94a26 100644 --- a/src/Ethereum/ABI/ProtoParam.h +++ b/src/Ethereum/ABI/ProtoParam.h @@ -74,6 +74,26 @@ class ProtoByteArray final: public BaseProtoParam { Data m_data; }; +class ProtoBytes32 final: public BaseProtoParam { +public: + explicit ProtoBytes32(const Data& data): m_data(data) { + if (data.size() != 32) { + throw std::invalid_argument("Data must be exactly 32 bytes long"); + } + } + + ~ProtoBytes32() override = default; + + AbiProto::Token toToken() const override { + AbiProto::Token proto; + proto.set_byte_array_fix(m_data.data(), m_data.size()); + return proto; + } + +private: + Data m_data; +}; + class ProtoString final: public BaseProtoParam { public: explicit ProtoString(std::string str): m_string(std::move(str)) { diff --git a/src/Ethereum/Barz.cpp b/src/Ethereum/Barz.cpp index 2cd9a0da071..6002af3692b 100644 --- a/src/Ethereum/Barz.cpp +++ b/src/Ethereum/Barz.cpp @@ -2,6 +2,8 @@ // // Copyright © 2017 Trust Wallet. +#include +#include "ABI/ValueEncoder.h" #include "ABI/Function.h" #include "AddressChecksum.h" #include "EIP1014.h" @@ -85,4 +87,135 @@ Data getFormattedSignature(const Data& signature, const Data challenge, const Da return {}; } +Data getPrefixedMsgHash(const Data msgHash, const std::string& barzAddress, const uint32_t chainId) { + // keccak256("EIP712Domain(uint256 chainId,address verifyingContract)"); + const Data& domainSeparatorTypeHashData = parse_hex("0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218"); + // keccak256("BarzMessage(bytes message)") + const Data& barzMsgHashData = parse_hex("0xb1bcb804a4a3a1af3ee7920d949bdfd417ea1b736c3552c8d6563a229a619100"); + const auto signedDataPrefix = "0x1901"; + + auto encodedDomainSeparatorData = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { + std::make_shared(domainSeparatorTypeHashData), + std::make_shared(chainId), + std::make_shared(barzAddress) + }); + if (!encodedDomainSeparatorData.has_value()) { + return {}; + } + + Data domainSeparator = encodedDomainSeparatorData.value(); + const Data domainSeparatorHash = Hash::keccak256(domainSeparator); + + auto encodedRawMessageData = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { + std::make_shared(barzMsgHashData), + std::make_shared(Hash::keccak256(msgHash)), + }); + + Data rawMessageData = encodedRawMessageData.value(); + + auto encodedMsg = Ethereum::ABI::Function::encodeParams(Ethereum::ABI::BaseParams { + std::make_shared(domainSeparatorHash), + std::make_shared(Hash::keccak256(rawMessageData)) + }); + auto encodedMsgData = signedDataPrefix + hex(encodedMsg.value()); + + Data finalEncodedMsgData = parse_hex(encodedMsgData); + + const Data encodedMsgHash = Hash::keccak256(finalEncodedMsgData); + + Data envelope; + append(envelope, encodedMsgHash); + + return envelope; +} + +// Function to encode the diamondCut function call using protobuf message as input +Data getDiamondCutCode(const Proto::DiamondCutInput& input) { + const auto diamondCutSelector = "1f931c1c"; + const auto dataLocationChunk = "60"; + const char defaultPadding = '0'; + Data encoded; + + // function diamondCut( + // FacetCut[] calldata diamondCut, + // address init, + // bytes calldata _calldata // Note that Barz does not use the _calldata for initialization. + // ) + Data encodedSignature = parse_hex(diamondCutSelector); // diamondCut() function selector + encoded.insert(encoded.end(), encodedSignature.begin(), encodedSignature.end()); + + // First argument Data Location `diamondCut` + Data dataLocation = parse_hex(dataLocationChunk); + pad_left(dataLocation, 32); + append(encoded, dataLocation); + + // Encode second Parameter `init` + Data initAddress = parse_hex(input.init_address()); + pad_left(initAddress, 32); + append(encoded, initAddress); + + // Third Argument Data location `_calldata` + auto callDataDataLocation = int(hex(encoded).size()) / 2; + + Ethereum::ABI::ValueEncoder::encodeUInt256(input.facet_cuts_size(), encoded); + + // Prepend the function selector for the diamondCut function + int instructChunk = 0; + int totalInstructChunk = 0; + int prevDataPosition = 0; + const auto encodingChunk = 32; + const auto bytesChunkLine = 5; + int chunkLocation; + Data dataPosition; + // Encode each FacetCut from the input + for (const auto& facetCut : input.facet_cuts()) { + if (instructChunk == 0) { + prevDataPosition = input.facet_cuts_size() * encodingChunk; + Ethereum::ABI::ValueEncoder::encodeUInt256(prevDataPosition, encoded); + chunkLocation = int(hex(encoded).size()) / 2; + } else { + prevDataPosition = prevDataPosition + (instructChunk * encodingChunk); + Ethereum::ABI::ValueEncoder::encodeUInt256(prevDataPosition, dataPosition); + instructChunk = 0; + + encoded.insert(encoded.begin() + chunkLocation, dataPosition.begin(), dataPosition.end()); + ++instructChunk; + } + Ethereum::ABI::ValueEncoder::encodeAddress(parse_hex(facetCut.facet_address()), encoded); // facet address + ++instructChunk; + Ethereum::ABI::ValueEncoder::encodeUInt256(facetCut.action(), encoded); // FacetAction enum + ++instructChunk; + append(encoded, dataLocation); // adding 0x60 DataStorage position + ++instructChunk; + Ethereum::ABI::ValueEncoder::encodeUInt256(facetCut.function_selectors_size(), encoded); // Number of FacetSelector + ++instructChunk; + // Encode and append function selectors + for (const auto& selector : facetCut.function_selectors()) { + Ethereum::ABI::ValueEncoder::encodeBytes(parse_hex(hex(selector)), encoded); + ++instructChunk; + } + totalInstructChunk += instructChunk; + } + + Data calldataLength; + Ethereum::ABI::ValueEncoder::encodeUInt256((totalInstructChunk * encodingChunk) + (bytesChunkLine * encodingChunk), calldataLength); + + encoded.insert(encoded.begin() + callDataDataLocation, calldataLength.begin(), calldataLength.end()); + + auto initDataSize = int(hex(parse_hex(input.init_data())).size()); + if (initDataSize == 0 || initDataSize % 2 != 0) + return {}; + + auto initDataLength = initDataSize / 2; // 1 byte is encoded into 2 char + Ethereum::ABI::ValueEncoder::encodeUInt256(initDataLength, encoded); + + append(encoded, parse_hex(input.init_data())); + + const int paddingLength = (encodingChunk * 2) - (initDataSize % (encodingChunk * 2)); + const std::string padding(paddingLength, defaultPadding); + append(encoded, parse_hex(padding)); + + return encoded; +} + } // namespace TW::Barz diff --git a/src/Ethereum/Barz.h b/src/Ethereum/Barz.h index 9ea1d0c0ccd..f29afc4eeed 100644 --- a/src/Ethereum/Barz.h +++ b/src/Ethereum/Barz.h @@ -14,5 +14,7 @@ namespace TW::Barz { std::string getCounterfactualAddress(const Proto::ContractAddressInput input); Data getInitCode(const std::string& factoryAddress, const PublicKey& publicKey, const std::string& verificationFacet, const uint32_t salt); Data getFormattedSignature(const Data& signature, const Data challenge, const Data& authenticatorData, const std::string& clientDataJSON); +Data getPrefixedMsgHash(const Data msgHash, const std::string& barzAddress, const uint32_t chainId); +Data getDiamondCutCode(const Proto::DiamondCutInput& input); // action should be one of 0, 1, 2. 0 = Add, 1 = Remove, 2 = Replace } diff --git a/src/interface/TWBarz.cpp b/src/interface/TWBarz.cpp index cbfdea41700..60da9690279 100644 --- a/src/interface/TWBarz.cpp +++ b/src/interface/TWBarz.cpp @@ -36,4 +36,25 @@ TWData *_Nonnull TWBarzGetFormattedSignature(TWData* _Nonnull signature, TWData* const auto initCode = TW::Barz::getFormattedSignature(signatureData, challengeData, authenticatorDataConverted, clientDataJSONStr); return TWDataCreateWithData(&initCode); +} + +TWData *_Nonnull TWBarzGetPrefixedMsgHash(TWData* _Nonnull msgHash, TWString* _Nonnull barzAddress, uint32_t chainId) { + const auto& msgHashData = *reinterpret_cast(msgHash); + const auto& barzAddressData = *reinterpret_cast(barzAddress); + + const auto prefixedMsgHash = TW::Barz::getPrefixedMsgHash(msgHashData, barzAddressData, chainId); + return TWDataCreateWithData(&prefixedMsgHash); +} + +TWData *_Nonnull TWBarzGetDiamondCutCode(TWData *_Nonnull input) { + TW::Barz::Proto::DiamondCutInput inputProto; + + const auto bytes = TWDataBytes(input); + const auto size = static_cast(TWDataSize(input)); + if (!inputProto.ParseFromArray(bytes, size)) { + return ""; + } + + const auto diamondCutCode = TW::Barz::getDiamondCutCode(inputProto); + return TWDataCreateWithData(&diamondCutCode); } \ No newline at end of file diff --git a/src/proto/Barz.proto b/src/proto/Barz.proto index b0be650c0f3..6c8342054e7 100644 --- a/src/proto/Barz.proto +++ b/src/proto/Barz.proto @@ -28,3 +28,25 @@ message ContractAddressInput { // Salt is used to derive multiple account from the same public key uint32 salt = 9; } + +// FacetCutAction represents the action to be performed for a FacetCut +enum FacetCutAction { + ADD = 0; + REPLACE = 1; + REMOVE = 2; +} + +// FacetCut represents a single operation to be performed on a facet +message FacetCut { + string facet_address = 1; // The address of the facet + FacetCutAction action = 2; // The action to perform + repeated bytes function_selectors = 3; // List of function selectors, each is bytes4 +} + +// DiamondCutInput represents the input parameters for a diamondCut operation +message DiamondCutInput { + repeated FacetCut facet_cuts = 1; // List of facet cuts to apply + string init_address = 2; // Address to call with `init` data after applying cuts + bytes init_data = 3; // Data to pass to `init` function call +} + diff --git a/tests/chains/Ethereum/BarzTests.cpp b/tests/chains/Ethereum/BarzTests.cpp index 6d4bc6dfc45..479726d7393 100644 --- a/tests/chains/Ethereum/BarzTests.cpp +++ b/tests/chains/Ethereum/BarzTests.cpp @@ -313,4 +313,106 @@ TEST(Barz, SignR1BatchedTransferAccountDeployed) { TWEthereumAbiFunctionDelete(transferFunc); } +TEST(Barz, GetPrefixedMsgHash) { + { + const Data& msgHash = parse_hex("0xa6ebe22d8c1ec7edbd7f5776e49a161f67ab97161d7b8c648d80abf365765cf2"); + const std::string& barzAddress = "0x913233BfC283ffe89a5E70ADC39c0926d240bbD9"; + const auto chainId = 3604; + + const auto& prefixedMsgHash = Barz::getPrefixedMsgHash(msgHash, barzAddress, chainId); + ASSERT_EQ(hexEncoded(prefixedMsgHash), "0x0488fb3e4fdaa890bf55532fc9840fb9edef9c38244f431c9430a78a86d89157"); + } +} + +TEST(Barz, GetPrefixedMsgHashWithZeroChainId) { + { + const Data& msgHash = parse_hex("0xcf267a78c5adaf96f341a696eb576824284c572f3e61be619694d539db1925f9"); + const std::string& barzAddress = "0xB91aaa96B138A1B1D94c9df4628187132c5F2bf1"; + const auto chainId = 0; + + const auto& prefixedMsgHash = Barz::getPrefixedMsgHash(msgHash, barzAddress, chainId); + ASSERT_EQ(hexEncoded(prefixedMsgHash), "0xc74e78634261222af51530703048f98a1b7b995a606a624f0a008e7aaba7a21b"); + } +} + +TEST(Barz, GetDiamondCutCode) { + { + TW::Barz::Proto::DiamondCutInput input; + + input.set_init_address("0x0000000000000000000000000000000000000000"); + input.set_init_data("0x00"); + + auto* facetCutAdd = input.add_facet_cuts(); + facetCutAdd->set_facet_address("0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"); + facetCutAdd->set_action(TW::Barz::Proto::FacetCutAction::ADD); + + auto functionSelectorAdd = parse_hex("0xfdd8a83c"); + facetCutAdd->add_function_selectors(functionSelectorAdd.data(), functionSelectorAdd.size()); + + const auto& diamondCutCode = Barz::getDiamondCutCode(input); + ASSERT_EQ(hexEncoded(diamondCutCode), "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001fdd8a83c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000"); + } } + +TEST(Barz, GetDiamondCutCodeWithMultipleCut) { + { + TW::Barz::Proto::DiamondCutInput input; + + input.set_init_address("0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"); + input.set_init_data("0x12341234"); + + auto* facetCutMigrationFacet = input.add_facet_cuts(); + facetCutMigrationFacet->set_facet_address("0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"); + facetCutMigrationFacet->set_action(TW::Barz::Proto::FacetCutAction::ADD); + + auto migrationSignature = parse_hex("0xfdd8a83c"); + + facetCutMigrationFacet->add_function_selectors(migrationSignature.data(), migrationSignature.size()); + facetCutMigrationFacet->add_function_selectors(migrationSignature.data(), migrationSignature.size()); + facetCutMigrationFacet->add_function_selectors(migrationSignature.data(), migrationSignature.size()); + + auto* facetCutTestFacet = input.add_facet_cuts(); + facetCutTestFacet->set_facet_address("0x6e3c94d74af6227aEeF75b54a679e969189a6aEC"); + facetCutTestFacet->set_action(TW::Barz::Proto::FacetCutAction::ADD); + + auto testSignature = parse_hex("0x12345678"); + facetCutTestFacet->add_function_selectors(testSignature.data(), testSignature.size()); + + + const auto& diamondCutCode = Barz::getDiamondCutCode(input); + ASSERT_EQ(hexEncoded(diamondCutCode), "0x1f931c1c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000003fdd8a83c00000000000000000000000000000000000000000000000000000000fdd8a83c00000000000000000000000000000000000000000000000000000000fdd8a83c000000000000000000000000000000000000000000000000000000000000000000000000000000006e3c94d74af6227aeef75b54a679e969189a6aec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000001123456780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041234123400000000000000000000000000000000000000000000000000000000"); + } +} + +TEST(Barz, GetDiamondCutCodeWithZeroSelector) { + { + TW::Barz::Proto::DiamondCutInput input; + + input.set_init_address("0x0000000000000000000000000000000000000000"); + input.set_init_data("0x00"); + auto* facetCutAdd = input.add_facet_cuts(); + facetCutAdd->set_facet_address("0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"); + facetCutAdd->set_action(TW::Barz::Proto::FacetCutAction::ADD); + + const auto& diamondCutCode = Barz::getDiamondCutCode(input); + ASSERT_EQ(hexEncoded(diamondCutCode), "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000"); + } +} + +TEST(Barz, GetDiamondCutCodeWithLongInitData) { + { + TW::Barz::Proto::DiamondCutInput input; + + input.set_init_address("0x0000000000000000000000000000000000000000"); + input.set_init_data("0xb61d27f6000000000000000000000000c2ce171d25837cd43e496719f5355a847edc679b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024a526d83b00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b90600000000000000000000000000000000000000000000000000000000"); + auto* facetCutAdd = input.add_facet_cuts(); + facetCutAdd->set_facet_address("0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"); + facetCutAdd->set_action(TW::Barz::Proto::FacetCutAction::ADD); + + const auto& diamondCutCode = Barz::getDiamondCutCode(input); + ASSERT_EQ(hexEncoded(diamondCutCode), "0x1f931c1c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000002279b7a0a67db372996a5fab50d91eaa73d2ebe600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c4b61d27f6000000000000000000000000c2ce171d25837cd43e496719f5355a847edc679b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024a526d83b00000000000000000000000090f79bf6eb2c4f870365e785982e1f101e93b9060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + } +} + +} +