From 1c9725de10a383b03152ac7912f9013567832386 Mon Sep 17 00:00:00 2001 From: cypt4 <106654560+cypt4@users.noreply.github.com> Date: Tue, 24 Dec 2024 17:09:50 +0400 Subject: [PATCH] [ZCash] Fetch consensus branch id from the backend (#27089) * [ZCash] Fetch consensus branch id from the backend Resolves https://github.com/brave/brave-browser/issues/42951 --- .../brave_wallet/browser/zcash/zcash_rpc.cc | 62 +++++++++++++++++++ .../brave_wallet/browser/zcash/zcash_rpc.h | 11 ++++ .../browser/zcash/zcash_serializer.cc | 24 ++++--- .../zcash/zcash_serializer_unittest.cc | 7 ++- .../browser/zcash/zcash_transaction.h | 6 ++ .../zcash_transaction_complete_manager.cc | 29 ++++++++- .../zcash_transaction_complete_manager.h | 7 +++ .../zcash/zcash_wallet_service_unittest.cc | 16 ++++- .../public/mojom/zcash_decoder.mojom | 7 +++ .../public/proto/zcash_grpc_data.proto | 18 ++++++ .../brave_wallet/zcash/zcash_decoder.cc | 14 +++++ .../brave_wallet/zcash/zcash_decoder.h | 2 + .../zcash/zcash_decoder_unittest.cc | 45 ++++++++++++++ 13 files changed, 234 insertions(+), 14 deletions(-) diff --git a/components/brave_wallet/browser/zcash/zcash_rpc.cc b/components/brave_wallet/browser/zcash/zcash_rpc.cc index 44420fe07dd8..bb9746bd6fd2 100644 --- a/components/brave_wallet/browser/zcash/zcash_rpc.cc +++ b/components/brave_wallet/browser/zcash/zcash_rpc.cc @@ -225,6 +225,23 @@ const GURL MakeGetLatestBlockHeightURL(const GURL& base_url) { return base_url.ReplaceComponents(replacements); } +const GURL MakeGetLightdInfoURL(const GURL& base_url) { + if (!base_url.is_valid()) { + return GURL(); + } + if (!UrlPathEndsWithSlash(base_url)) { + return GURL(); + } + + GURL::Replacements replacements; + std::string path = + base::StrCat({base_url.path(), + "cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLightdInfo"}); + replacements.SetPathStr(path); + + return base_url.ReplaceComponents(replacements); +} + const GURL MakeGetTransactionURL(const GURL& base_url) { if (!base_url.is_valid()) { return GURL(); @@ -285,6 +302,11 @@ std::string MakeGetLatestBlockHeightParams() { return GetPrefixedProtobuf(request.SerializeAsString()); } +std::string MakeGetLightdInfoParams() { + ::zcash::Empty request; + return GetPrefixedProtobuf(request.SerializeAsString()); +} + std::string MakeGetTransactionParams(const std::string& tx_hash) { ::zcash::TxFilter request; std::string as_bytes; @@ -517,6 +539,28 @@ void ZCashRpc::GetCompactBlocks(const std::string& chain_id, (*it)->DownloadAsStream(url_loader_factory_.get(), handler_it->get()); } +void ZCashRpc::GetLightdInfo(const std::string& chain_id, + GetLightdInfoCallback callback) { + GURL request_url = MakeGetLightdInfoURL(GetNetworkURL(chain_id)); + + if (!request_url.is_valid()) { + std::move(callback).Run( + base::unexpected(l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR))); + return; + } + + auto url_loader = MakeGRPCLoader(request_url, MakeGetLightdInfoParams()); + + UrlLoadersList::iterator it = url_loaders_list_.insert( + url_loaders_list_.begin(), std::move(url_loader)); + + (*it)->DownloadToString( + url_loader_factory_.get(), + base::BindOnce(&ZCashRpc::OnGetLightdInfoResponse, + weak_ptr_factory_.GetWeakPtr(), std::move(callback), it), + kMaxBodySize); +} + void ZCashRpc::OnGetCompactBlocksResponse( ZCashRpc::GetCompactBlocksCallback callback, UrlLoadersList::iterator it, @@ -751,6 +795,24 @@ void ZCashRpc::OnGetAddressTxResponse( std::move(callback).Run(result.value()); } +void ZCashRpc::OnGetLightdInfoResponse( + GetLightdInfoCallback callback, + UrlLoadersList::iterator it, + std::unique_ptr response_body) { + url_loaders_list_.erase(it); + + if (!response_body) { + std::move(callback).Run( + base::unexpected(l10n_util::GetStringUTF8(IDS_WALLET_INTERNAL_ERROR))); + return; + } + + GetDecoder()->ParseLightdInfo( + *response_body, + base::BindOnce(&ZCashRpc::OnParseResult, + weak_ptr_factory_.GetWeakPtr(), std::move(callback))); +} + mojo::AssociatedRemote& ZCashRpc::GetDecoder() { if (zcash_decoder_.is_bound()) { return zcash_decoder_; diff --git a/components/brave_wallet/browser/zcash/zcash_rpc.h b/components/brave_wallet/browser/zcash/zcash_rpc.h index 3234c255ad00..2b8b40fd7024 100644 --- a/components/brave_wallet/browser/zcash/zcash_rpc.h +++ b/components/brave_wallet/browser/zcash/zcash_rpc.h @@ -44,6 +44,10 @@ class ZCashRpc { base::expected)>; using GetCompactBlocksCallback = base::OnceCallback, std::string>)>; + using GetSubtreeRootsCallback = base::OnceCallback, std::string>)>; + using GetLightdInfoCallback = base::OnceCallback)>; ZCashRpc(NetworkManager* network_manager, scoped_refptr url_loader_factory); @@ -81,6 +85,9 @@ class ZCashRpc { uint32_t to, GetCompactBlocksCallback callback); + virtual void GetLightdInfo(const std::string& chain_id, + GetLightdInfoCallback callback); + private: friend class base::RefCountedThreadSafe; @@ -119,6 +126,10 @@ class ZCashRpc { StreamHandlersList::iterator handler_it, base::expected, std::string> result); + void OnGetLightdInfoResponse(GetLightdInfoCallback callback, + UrlLoadersList::iterator it, + std::unique_ptr response_body); + template void OnParseResult(base::OnceCallback)>, T value); diff --git a/components/brave_wallet/browser/zcash/zcash_serializer.cc b/components/brave_wallet/browser/zcash/zcash_serializer.cc index 75e03c030788..86f4dd09d017 100644 --- a/components/brave_wallet/browser/zcash/zcash_serializer.cc +++ b/components/brave_wallet/browser/zcash/zcash_serializer.cc @@ -10,6 +10,7 @@ #include #include "base/containers/span.h" +#include "base/containers/span_writer.h" #include "base/numerics/byte_conversions.h" #include "brave/components/brave_wallet/common/btc_like_serializer_stream.h" #include "brave/components/brave_wallet/common/hex_utils.h" @@ -25,10 +26,7 @@ constexpr char kSaplingHashPersonalizer[] = "ZTxIdSaplingHash"; constexpr char kOrchardHashPersonalizer[] = "ZTxIdOrchardHash"; // https://zips.z.cash/zip-0244#txid-digest-1 -constexpr uint32_t kConsensusBranchId = 0xC2D6D0B4; -constexpr char kTxHashPersonalizer[] = - "ZcashTxHash_" - "\xB4\xD0\xD6\xC2"; +constexpr char kTxHashPersonalizerPrefix[] = "ZcashTxHash_"; constexpr uint32_t kV5TxVersion = 5 | 1 << 31 /* overwintered bit */; // https://zips.z.cash/protocol/protocol.pdf#txnconsensus @@ -56,7 +54,7 @@ std::array blake2b256( void PushHeader(const ZCashTransaction& tx, BtcLikeSerializerStream& stream) { stream.Push32(kV5TxVersion); stream.Push32(kV5VersionGroupId); - stream.Push32(kConsensusBranchId); + stream.Push32(tx.consensus_brach_id()); stream.Push32(tx.locktime()); stream.Push32(tx.expiry_height()); } @@ -93,6 +91,16 @@ std::array HashScriptPubKeys(const ZCashTransaction& tx) { return blake2b256(data, base::byte_span_from_cstring("ZTxTrScriptsHash")); } +std::array GetHashPersonalizer( + const ZCashTransaction& tx) { + std::array result; + auto span_writer = base::SpanWriter(base::span(result)); + span_writer.Write(base::byte_span_from_cstring(kTxHashPersonalizerPrefix)); + span_writer.WriteU32LittleEndian(tx.consensus_brach_id()); + DCHECK_EQ(span_writer.remaining(), 0u); + return result; +} + } // namespace // static @@ -260,8 +268,7 @@ std::array ZCashSerializer::CalculateTxIdDigest( stream.PushBytes(sapling_hash); stream.PushBytes(orchard_hash); - digest_hash = - blake2b256(data, base::byte_span_from_cstring(kTxHashPersonalizer)); + digest_hash = blake2b256(data, GetHashPersonalizer(zcash_transaction)); } std::reverse(digest_hash.begin(), digest_hash.end()); @@ -315,8 +322,7 @@ std::array ZCashSerializer::CalculateSignatureDigest( stream.PushBytes(sapling_hash); stream.PushBytes(orchard_hash); - digest_hash = - blake2b256(data, base::byte_span_from_cstring(kTxHashPersonalizer)); + digest_hash = blake2b256(data, GetHashPersonalizer(zcash_transaction)); } return digest_hash; diff --git a/components/brave_wallet/browser/zcash/zcash_serializer_unittest.cc b/components/brave_wallet/browser/zcash/zcash_serializer_unittest.cc index 273242c5d207..5f6cf99b372a 100644 --- a/components/brave_wallet/browser/zcash/zcash_serializer_unittest.cc +++ b/components/brave_wallet/browser/zcash/zcash_serializer_unittest.cc @@ -22,6 +22,7 @@ namespace brave_wallet { TEST(ZCashSerializerTest, HashPrevouts) { ZCashTransaction zcash_transaciton; + zcash_transaciton.set_consensus_brach_id(0xc2d6d0b4); { ZCashTransaction::TxInput tx_input; @@ -60,6 +61,7 @@ TEST(ZCashSerializerTest, HashPrevouts) { TEST(ZCashSerializerTest, HashOutputs) { ZCashTransaction zcash_transaciton; + zcash_transaciton.set_consensus_brach_id(0xc2d6d0b4); { ZCashTransaction::TxOutput tx_output; @@ -84,6 +86,7 @@ TEST(ZCashSerializerTest, HashOutputs) { TEST(ZCashSerializerTest, HashSequences) { ZCashTransaction zcash_transaciton; + zcash_transaciton.set_consensus_brach_id(0xc2d6d0b4); { ZCashTransaction::TxInput tx_input; @@ -110,6 +113,7 @@ TEST(ZCashSerializerTest, HashSequences) { TEST(ZCashSerializerTest, HashHeader) { ZCashTransaction zcash_transaciton; + zcash_transaciton.set_consensus_brach_id(0xc2d6d0b4); zcash_transaciton.set_expiry_height(10000); zcash_transaciton.set_locktime(1); EXPECT_EQ( @@ -140,7 +144,7 @@ TEST(ZCashSerializerTest, HashTxIn) { // https://zcashblockexplorer.com/transactions/360d056309669faf0d7937f41581418be5e46b04e2cea0a7b14261d7bff1d825/raw TEST(ZCashSerializerTest, TxId_TransparentOnly) { ZCashTransaction tx; - + tx.set_consensus_brach_id(0xc2d6d0b4); tx.set_expiry_height(2283846); tx.set_locktime(2283826); @@ -203,6 +207,7 @@ TEST(ZCashSerializerTest, OrchardBundle) { auto key_id = mojom::ZCashKeyId::New(0, 0, 0); auto address = keyring.GetTransparentAddress(*key_id)->address_string; ZCashTransaction tx; + tx.set_consensus_brach_id(0xc2d6d0b4); tx.set_expiry_height(1687144); tx.set_locktime(0); diff --git a/components/brave_wallet/browser/zcash/zcash_transaction.h b/components/brave_wallet/browser/zcash/zcash_transaction.h index 7749a558073a..7a7ae696d12b 100644 --- a/components/brave_wallet/browser/zcash/zcash_transaction.h +++ b/components/brave_wallet/browser/zcash/zcash_transaction.h @@ -159,6 +159,11 @@ class ZCashTransaction { expiry_height_ = expiry_height; } + uint32_t consensus_brach_id() const { return consensus_brach_id_; } + void set_consensus_brach_id(uint32_t consensus_brach_id) { + consensus_brach_id_ = consensus_brach_id; + } + private: TransparentPart transparent_part_; OrchardPart orchard_part_; @@ -169,6 +174,7 @@ class ZCashTransaction { std::optional memo_; uint64_t amount_ = 0; uint64_t fee_ = 0; + uint32_t consensus_brach_id_ = 0; }; } // namespace brave_wallet diff --git a/components/brave_wallet/browser/zcash/zcash_transaction_complete_manager.cc b/components/brave_wallet/browser/zcash/zcash_transaction_complete_manager.cc index 6fd78916bf17..e69e01f05b44 100644 --- a/components/brave_wallet/browser/zcash/zcash_transaction_complete_manager.cc +++ b/components/brave_wallet/browser/zcash/zcash_transaction_complete_manager.cc @@ -54,9 +54,9 @@ void ZCashTransactionCompleteManager::CompleteTransaction( const ZCashTransaction& transaction, const mojom::AccountIdPtr& account_id, CompleteTransactionCallback callback) { - zcash_wallet_service_->zcash_rpc().GetLatestBlock( + zcash_wallet_service_->zcash_rpc().GetLightdInfo( chain_id, - base::BindOnce(&ZCashTransactionCompleteManager::OnGetLatestBlockHeight, + base::BindOnce(&ZCashTransactionCompleteManager::OnGetLightdInfo, weak_ptr_factory_.GetWeakPtr(), ParamsBundle{chain_id, transaction, account_id.Clone(), std::move(callback)})); @@ -155,6 +155,31 @@ void ZCashTransactionCompleteManager::OnSignOrchardPartComplete( #endif // BUILDFLAG(ENABLE_ORCHARD) +void ZCashTransactionCompleteManager::OnGetLightdInfo( + ParamsBundle params, + base::expected result) { + if (!result.has_value()) { + std::move(params.callback).Run(base::unexpected("get lightd info error")); + return; + } + + uint32_t consensus_branch_id; + if (!base::HexStringToUInt(result.value()->consensusBranchId, + &consensus_branch_id)) { + std::move(params.callback) + .Run(base::unexpected("wrong consensus branch format")); + return; + } + + params.transaction.set_consensus_brach_id(consensus_branch_id); + std::string chain_id = params.chain_id; + + zcash_wallet_service_->zcash_rpc().GetLatestBlock( + chain_id, + base::BindOnce(&ZCashTransactionCompleteManager::OnGetLatestBlockHeight, + weak_ptr_factory_.GetWeakPtr(), std::move(params))); +} + void ZCashTransactionCompleteManager::SignTransparentPart(ParamsBundle params) { // Sign transparent part if (!ZCashSerializer::SignTransparentPart( diff --git a/components/brave_wallet/browser/zcash/zcash_transaction_complete_manager.h b/components/brave_wallet/browser/zcash/zcash_transaction_complete_manager.h index 27c036debc18..2b7e3c888764 100644 --- a/components/brave_wallet/browser/zcash/zcash_transaction_complete_manager.h +++ b/components/brave_wallet/browser/zcash/zcash_transaction_complete_manager.h @@ -53,6 +53,10 @@ class ZCashTransactionCompleteManager { void OnGetLatestBlockHeight( ParamsBundle params, base::expected result); + void GetLightdInfo(ParamsBundle params); + void OnGetLightdInfo( + ParamsBundle params, + base::expected result); #if BUILDFLAG(ENABLE_ORCHARD) void OnGetTreeState( ParamsBundle params, @@ -63,6 +67,9 @@ class ZCashTransactionCompleteManager { #endif // BUILDFLAG(ENABLE_ORCHARD) raw_ref zcash_wallet_service_; // Owns `this`. + + std::optional lightd_info_; + base::WeakPtrFactory weak_ptr_factory_{this}; }; diff --git a/components/brave_wallet/browser/zcash/zcash_wallet_service_unittest.cc b/components/brave_wallet/browser/zcash/zcash_wallet_service_unittest.cc index ef7bc9c58e79..e3d15b7f1c45 100644 --- a/components/brave_wallet/browser/zcash/zcash_wallet_service_unittest.cc +++ b/components/brave_wallet/browser/zcash/zcash_wallet_service_unittest.cc @@ -98,6 +98,10 @@ class MockZCashRPC : public ZCashRpc { void(const std::string& chain_id, zcash::mojom::BlockIDPtr block_id, GetTreeStateCallback callback)); + + MOCK_METHOD2(GetLightdInfo, + void(const std::string& chain_id, + GetLightdInfoCallback callback)); }; } // namespace @@ -119,13 +123,21 @@ class ZCashWalletServiceUnitTest : public testing::Test { brave_wallet::RegisterLocalStatePrefs(local_state_.registry()); keyring_service_ = std::make_unique(nullptr, &prefs_, &local_state_); - auto zcash_rpc = std::make_unique>(); zcash_wallet_service_ = std::make_unique( - db_path, *keyring_service_, std::move(zcash_rpc)); + db_path, *keyring_service_, + std::make_unique>()); GetAccountUtils().CreateWallet(kMnemonicDivideCruise, kTestWalletPassword); zcash_account_ = GetAccountUtils().EnsureAccount(mojom::KeyringId::kZCashMainnet, 0); ASSERT_TRUE(zcash_account_); + + ON_CALL(zcash_rpc(), GetLightdInfo(_, _)) + .WillByDefault( + ::testing::Invoke([&](const std::string& chain_id, + ZCashRpc::GetLightdInfoCallback callback) { + auto response = zcash::mojom::LightdInfo::New("c2d6d0b4"); + std::move(callback).Run(std::move(response)); + })); } AccountUtils GetAccountUtils() { diff --git a/components/services/brave_wallet/public/mojom/zcash_decoder.mojom b/components/services/brave_wallet/public/mojom/zcash_decoder.mojom index 2dfa39ef247b..0ba76c4d548d 100644 --- a/components/services/brave_wallet/public/mojom/zcash_decoder.mojom +++ b/components/services/brave_wallet/public/mojom/zcash_decoder.mojom @@ -77,6 +77,12 @@ struct SubtreeRoot { uint32 complete_block_height; }; +struct LightdInfo { + string consensusBranchId; + // Other fields mentioned in https://github.com/zcash/lightwalletd/blob/1e63bee7614d8fd2be79c0ee13008f0f4aaaebbd/walletrpc/service.proto#L75C1-L93C1 + // are skipped for now. +}; + interface ZCashDecoder { ParseBlockID(string data) => (BlockID? value); ParseGetAddressUtxos(string data) => (GetAddressUtxosResponse? value); @@ -84,5 +90,6 @@ interface ZCashDecoder { ParseRawTransaction(string data) => (RawTransaction? tx); ParseTreeState(string data) => (TreeState? tree_state); ParseCompactBlocks(array data) => (array? compact_blocks); + ParseLightdInfo(string data) => (LightdInfo? lightd_info); }; diff --git a/components/services/brave_wallet/public/proto/zcash_grpc_data.proto b/components/services/brave_wallet/public/proto/zcash_grpc_data.proto index 89ae1120db39..4c826cd3156b 100644 --- a/components/services/brave_wallet/public/proto/zcash_grpc_data.proto +++ b/components/services/brave_wallet/public/proto/zcash_grpc_data.proto @@ -110,3 +110,21 @@ message CompactBlock { repeated CompactTx vtx = 7; ChainMetadata chainMetadata = 8; } + +message LightdInfo { + string version = 1; + string vendor = 2; + bool taddrSupport = 3; // true + string chainName = 4; // either "main" or "test" + uint64 saplingActivationHeight = 5; // depends on mainnet or testnet + string consensusBranchId = + 6; // protocol identifier, see consensus/upgrades.cpp + uint64 blockHeight = 7; // latest block on the best chain + string gitCommit = 8; + string branch = 9; + string buildDate = 10; + string buildUser = 11; + uint64 estimatedHeight = 12; // less than tip height if zcashd is syncing + string zcashdBuild = 13; // example: "v4.1.1-877212414" + string zcashdSubversion = 14; // example: "/MagicBean:4.1.1/" +}; diff --git a/components/services/brave_wallet/zcash/zcash_decoder.cc b/components/services/brave_wallet/zcash/zcash_decoder.cc index e51cfa4857c2..ad689b3fcdd1 100644 --- a/components/services/brave_wallet/zcash/zcash_decoder.cc +++ b/components/services/brave_wallet/zcash/zcash_decoder.cc @@ -138,4 +138,18 @@ void ZCashDecoder::ParseCompactBlocks(const std::vector& data, std::move(callback).Run(std::move(parsed_blocks)); } +void ZCashDecoder::ParseLightdInfo(const std::string& data, + ParseLightdInfoCallback callback) { + ::zcash::LightdInfo result; + auto serialized_message = ResolveSerializedMessage(data); + if (!serialized_message || serialized_message->empty() || + !result.ParseFromString(serialized_message.value())) { + std::move(callback).Run(nullptr); + return; + } + + std::move(callback).Run( + zcash::mojom::LightdInfo::New(result.consensusbranchid())); +} + } // namespace brave_wallet diff --git a/components/services/brave_wallet/zcash/zcash_decoder.h b/components/services/brave_wallet/zcash/zcash_decoder.h index b7136ccb0e50..4dc1f34d55c9 100644 --- a/components/services/brave_wallet/zcash/zcash_decoder.h +++ b/components/services/brave_wallet/zcash/zcash_decoder.h @@ -36,6 +36,8 @@ class ZCashDecoder : public zcash::mojom::ZCashDecoder { ParseTreeStateCallback callback) override; void ParseCompactBlocks(const std::vector& data, ParseCompactBlocksCallback callback) override; + void ParseLightdInfo(const std::string& data, + ParseLightdInfoCallback callback) override; }; } // namespace brave_wallet diff --git a/components/services/brave_wallet/zcash/zcash_decoder_unittest.cc b/components/services/brave_wallet/zcash/zcash_decoder_unittest.cc index 6cfd237a31a9..2bc19f9a0040 100644 --- a/components/services/brave_wallet/zcash/zcash_decoder_unittest.cc +++ b/components/services/brave_wallet/zcash/zcash_decoder_unittest.cc @@ -493,4 +493,49 @@ TEST_F(ZCashDecoderUnitTest, ParseCompactBlock) { } } +TEST_F(ZCashDecoderUnitTest, ParseLightdInfo) { + ::zcash::LightdInfo response; + response.set_consensusbranchid("5"); + + // Correct input + { + base::MockCallback callback; + EXPECT_CALL(callback, Run(EqualsMojo(zcash::mojom::LightdInfo::New("5")))); + decoder()->ParseLightdInfo( + GetPrefixedProtobuf(response.SerializeAsString()), callback.Get()); + } + // Missed protobuf prefix is incorrect + { + base::MockCallback callback; + EXPECT_CALL(callback, Run(EqualsMojo(zcash::mojom::LightdInfoPtr()))); + decoder()->ParseLightdInfo(response.SerializeAsString(), callback.Get()); + } + // Protobuf prefix exists but data format is wrong + { + base::MockCallback callback; + EXPECT_CALL(callback, Run(EqualsMojo(zcash::mojom::LightdInfoPtr()))); + decoder()->ParseLightdInfo(GetPrefixedProtobuf(""), callback.Get()); + } + // Corrupted input + { + base::MockCallback callback; + EXPECT_CALL(callback, Run(EqualsMojo(zcash::mojom::LightdInfoPtr()))); + decoder()->ParseLightdInfo( + GetPrefixedProtobuf(response.SerializeAsString()).substr(0, 5), + callback.Get()); + } + // Random string as input + { + base::MockCallback callback; + EXPECT_CALL(callback, Run(EqualsMojo(zcash::mojom::LightdInfoPtr()))); + decoder()->ParseLightdInfo("123", callback.Get()); + } + // Empty input + { + base::MockCallback callback; + EXPECT_CALL(callback, Run(EqualsMojo(zcash::mojom::LightdInfoPtr()))); + decoder()->ParseLightdInfo("", callback.Get()); + } +} + } // namespace brave_wallet