From 0082f8a8be6a3eb2a4d33757a7742b43c953d19e Mon Sep 17 00:00:00 2001 From: Anton Paymyshev Date: Mon, 23 Dec 2024 12:55:33 +0700 Subject: [PATCH] HD Key cleanup --- components/brave_wallet/browser/BUILD.gn | 2 + .../bitcoin/bitcoin_discover_account_task.cc | 14 +- .../bitcoin/bitcoin_hardware_keyring.cc | 9 +- .../browser/bitcoin/bitcoin_hd_keyring.cc | 42 +- .../browser/bitcoin/bitcoin_import_keyring.cc | 9 +- .../browser/eip1559_transaction_unittest.cc | 6 +- .../browser/eip2930_transaction_unittest.cc | 6 +- .../browser/eth_transaction_unittest.cc | 6 +- .../brave_wallet/browser/ethereum_keyring.cc | 22 +- .../browser/ethereum_keyring_unittest.cc | 12 +- .../brave_wallet/browser/filecoin_keyring.cc | 34 +- .../brave_wallet/browser/internal/BUILD.gn | 2 + .../brave_wallet/browser/internal/hd_key.cc | 466 +++--------------- .../brave_wallet/browser/internal/hd_key.h | 52 +- .../browser/internal/hd_key_common.cc | 37 ++ .../browser/internal/hd_key_common.h | 38 ++ .../browser/internal/hd_key_ed25519.cc | 5 - .../browser/internal/hd_key_ed25519.h | 2 +- .../browser/internal/hd_key_unittest.cc | 271 ++++------ .../browser/json_keystore_parser.cc | 200 ++++++++ .../browser/json_keystore_parser.h | 23 + .../browser/json_keystore_parser_unittest.cc | 127 +++++ .../brave_wallet/browser/keyring_service.cc | 16 +- .../browser/secp256k1_hd_keyring.cc | 26 +- .../browser/secp256k1_hd_keyring.h | 9 +- .../browser/zcash/zcash_keyring.cc | 76 +-- .../browser/zcash/zcash_keyring.h | 4 +- 27 files changed, 790 insertions(+), 726 deletions(-) create mode 100644 components/brave_wallet/browser/internal/hd_key_common.cc create mode 100644 components/brave_wallet/browser/internal/hd_key_common.h create mode 100644 components/brave_wallet/browser/json_keystore_parser.cc create mode 100644 components/brave_wallet/browser/json_keystore_parser.h create mode 100644 components/brave_wallet/browser/json_keystore_parser_unittest.cc diff --git a/components/brave_wallet/browser/BUILD.gn b/components/brave_wallet/browser/BUILD.gn index 486626fe1165..dafa522e2a44 100644 --- a/components/brave_wallet/browser/BUILD.gn +++ b/components/brave_wallet/browser/BUILD.gn @@ -129,6 +129,8 @@ static_library("browser") { "fil_tx_meta.h", "fil_tx_state_manager.cc", "fil_tx_state_manager.h", + "json_keystore_parser.cc", + "json_keystore_parser.h", "json_rpc_requests_helper.cc", "json_rpc_requests_helper.h", "json_rpc_response_parser.cc", diff --git a/components/brave_wallet/browser/bitcoin/bitcoin_discover_account_task.cc b/components/brave_wallet/browser/bitcoin/bitcoin_discover_account_task.cc index 311d6acda507..b977cbcf4e34 100644 --- a/components/brave_wallet/browser/bitcoin/bitcoin_discover_account_task.cc +++ b/components/brave_wallet/browser/bitcoin/bitcoin_discover_account_task.cc @@ -7,6 +7,7 @@ #include +#include #include #include @@ -19,6 +20,7 @@ #include "brave/components/brave_wallet/browser/bitcoin/bitcoin_import_keyring.h" #include "brave/components/brave_wallet/browser/bitcoin/bitcoin_task_utils.h" #include "brave/components/brave_wallet/browser/bitcoin/bitcoin_wallet_service.h" +#include "brave/components/brave_wallet/browser/internal/hd_key_common.h" #include "brave/components/brave_wallet/browser/keyring_service.h" #include "brave/components/brave_wallet/common/bitcoin_utils.h" #include "brave/components/brave_wallet/common/common_utils.h" @@ -254,15 +256,9 @@ mojom::BitcoinAddressPtr DiscoverExtendedKeyAccountTask::GetAddressById( return nullptr; } - auto key = account_key_->DeriveNormalChild(key_id->change); - if (!key) { - return nullptr; - } - - key = key->DeriveNormalChild(key_id->index); - if (!key) { - return nullptr; - } + auto key = account_key_->DeriveChildFromPath( + std::array{DerivationIndex::Normal(key_id->change), + DerivationIndex::Normal(key_id->index)}); return mojom::BitcoinAddress::New( PubkeyToSegwitAddress(key->GetPublicKeyBytes(), testnet_), diff --git a/components/brave_wallet/browser/bitcoin/bitcoin_hardware_keyring.cc b/components/brave_wallet/browser/bitcoin/bitcoin_hardware_keyring.cc index 689a5de9e61a..87c083c0bf8b 100644 --- a/components/brave_wallet/browser/bitcoin/bitcoin_hardware_keyring.cc +++ b/components/brave_wallet/browser/bitcoin/bitcoin_hardware_keyring.cc @@ -95,12 +95,9 @@ std::unique_ptr BitcoinHardwareKeyring::DeriveKey( DCHECK(key_id.change == 0 || key_id.change == 1); - auto key = account_key->DeriveNormalChild(key_id.change); - if (!key) { - return nullptr; - } - - return key->DeriveNormalChild(key_id.index); + return account_key->DeriveChildFromPath( + std::array{DerivationIndex::Normal(key_id.change), + DerivationIndex::Normal(key_id.index)}); } } // namespace brave_wallet diff --git a/components/brave_wallet/browser/bitcoin/bitcoin_hd_keyring.cc b/components/brave_wallet/browser/bitcoin/bitcoin_hd_keyring.cc index 0a4805a7624f..7912c57b8112 100644 --- a/components/brave_wallet/browser/bitcoin/bitcoin_hd_keyring.cc +++ b/components/brave_wallet/browser/bitcoin/bitcoin_hd_keyring.cc @@ -12,16 +12,37 @@ #include "base/check.h" #include "base/containers/span.h" #include "base/notimplemented.h" +#include "brave/components/brave_wallet/browser/internal/hd_key_common.h" #include "brave/components/brave_wallet/common/bitcoin_utils.h" namespace brave_wallet { +namespace { + +std::unique_ptr ConstructAccountsRootKey(base::span seed, + bool testnet) { + auto result = HDKey::GenerateFromSeed(seed); + if (!result) { + return nullptr; + } + + if (testnet) { + // Testnet: m/84'/1' + return result->DeriveChildFromPath({DerivationIndex::Hardened(84), // + DerivationIndex::Hardened(1)}); + } else { + // Mainnet: m/84'/0' + return result->DeriveChildFromPath({DerivationIndex::Hardened(84), // + DerivationIndex::Hardened(0)}); + } +} + +} // namespace + BitcoinHDKeyring::BitcoinHDKeyring(base::span seed, bool testnet) - : Secp256k1HDKeyring( - seed, - GetRootPath(testnet ? mojom::KeyringId::kBitcoin84Testnet - : mojom::KeyringId::kBitcoin84)), - testnet_(testnet) {} + : testnet_(testnet) { + accounts_root_ = ConstructAccountsRootKey(seed, testnet); +} mojom::BitcoinAddressPtr BitcoinHDKeyring::GetAddress( uint32_t account, @@ -94,7 +115,7 @@ std::string BitcoinHDKeyring::GetAddressInternal(const HDKey& hd_key) const { std::unique_ptr BitcoinHDKeyring::DeriveAccount(uint32_t index) const { // Mainnet - m/84'/0'/{index}' // Testnet - m/84'/1'/{index}' - return root_->DeriveHardenedChild(index); + return accounts_root_->DeriveChild(DerivationIndex::Hardened(index)); } std::unique_ptr BitcoinHDKeyring::DeriveKey( @@ -109,14 +130,11 @@ std::unique_ptr BitcoinHDKeyring::DeriveKey( // TODO(apaymyshev): think if |key_id.change| should be a boolean. DCHECK(key_id.change == 0 || key_id.change == 1); - auto key = account_key->DeriveNormalChild(key_id.change); - if (!key) { - return nullptr; - } - // Mainnet - m/84'/0'/{account}'/{key_id.change}/{key_id.index} // Testnet - m/84'/1'/{account}'/{key_id.change}/{key_id.index} - return key->DeriveNormalChild(key_id.index); + return account_key->DeriveChildFromPath( + std::array{DerivationIndex::Normal(key_id.change), + DerivationIndex::Normal(key_id.index)}); } } // namespace brave_wallet diff --git a/components/brave_wallet/browser/bitcoin/bitcoin_import_keyring.cc b/components/brave_wallet/browser/bitcoin/bitcoin_import_keyring.cc index 732a9a314999..9468b06221bf 100644 --- a/components/brave_wallet/browser/bitcoin/bitcoin_import_keyring.cc +++ b/components/brave_wallet/browser/bitcoin/bitcoin_import_keyring.cc @@ -98,14 +98,11 @@ std::unique_ptr BitcoinImportKeyring::DeriveKey( DCHECK(key_id.change == 0 || key_id.change == 1); - auto key = account_key->DeriveNormalChild(key_id.change); - if (!key) { - return nullptr; - } - // Mainnet - m/84'/0'/{account}'/{key_id.change}/{key_id.index} // Testnet - m/84'/1'/{account}'/{key_id.change}/{key_id.index} - return key->DeriveNormalChild(key_id.index); + return account_key->DeriveChildFromPath( + std::array{DerivationIndex::Normal(key_id.change), + DerivationIndex::Normal(key_id.index)}); } } // namespace brave_wallet diff --git a/components/brave_wallet/browser/eip1559_transaction_unittest.cc b/components/brave_wallet/browser/eip1559_transaction_unittest.cc index 43c10e3d57ab..841f324cc4d5 100644 --- a/components/brave_wallet/browser/eip1559_transaction_unittest.cc +++ b/components/brave_wallet/browser/eip1559_transaction_unittest.cc @@ -125,10 +125,10 @@ TEST(Eip1559TransactionUnitTest, GetSignedTransactionAndHash) { "0x863c02549182b91f1764714b93d7e882f010539c0907adaf4de761f7b06a713c"}}; for (const auto& entry : cases) { SCOPED_TRACE(entry.signed_tx); - std::vector private_key; - EXPECT_TRUE(base::HexStringToBytes( + std::array private_key; + EXPECT_TRUE(base::HexStringToSpan( "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", - &private_key)); + private_key)); HDKey key; key.SetPrivateKey(private_key); diff --git a/components/brave_wallet/browser/eip2930_transaction_unittest.cc b/components/brave_wallet/browser/eip2930_transaction_unittest.cc index a7a18700007b..4237179fa429 100644 --- a/components/brave_wallet/browser/eip2930_transaction_unittest.cc +++ b/components/brave_wallet/browser/eip2930_transaction_unittest.cc @@ -117,10 +117,10 @@ TEST(Eip2930TransactionUnitTest, GetSignedTransactionAndHash) { access_list->push_back(item); - std::vector private_key; - EXPECT_TRUE(base::HexStringToBytes( + std::array private_key; + EXPECT_TRUE(base::HexStringToSpan( "fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19", - &private_key)); + private_key)); HDKey key; key.SetPrivateKey(private_key); diff --git a/components/brave_wallet/browser/eth_transaction_unittest.cc b/components/brave_wallet/browser/eth_transaction_unittest.cc index 2426e2d61636..c9a1da94a178 100644 --- a/components/brave_wallet/browser/eth_transaction_unittest.cc +++ b/components/brave_wallet/browser/eth_transaction_unittest.cc @@ -113,10 +113,10 @@ TEST(EthTransactionUnitTest, GetMessageToSign) { } TEST(EthTransactionUnitTest, GetSignedTransactionAndHash) { - std::vector private_key; - EXPECT_TRUE(base::HexStringToBytes( + std::array private_key; + EXPECT_TRUE(base::HexStringToSpan( "4646464646464646464646464646464646464646464646464646464646464646", - &private_key)); + private_key)); HDKey key; key.SetPrivateKey(private_key); diff --git a/components/brave_wallet/browser/ethereum_keyring.cc b/components/brave_wallet/browser/ethereum_keyring.cc index 15066d207cd5..44df448d3030 100644 --- a/components/brave_wallet/browser/ethereum_keyring.cc +++ b/components/brave_wallet/browser/ethereum_keyring.cc @@ -14,6 +14,7 @@ #include "base/strings/strcat.h" #include "base/strings/string_number_conversions.h" #include "brave/components/brave_wallet/browser/eth_transaction.h" +#include "brave/components/brave_wallet/browser/internal/hd_key_common.h" #include "brave/components/brave_wallet/common/eth_address.h" #include "brave/components/brave_wallet/common/hash_utils.h" @@ -30,10 +31,25 @@ std::vector GetMessageHash(base::span message) { return base::ToVector(KeccakHash(hash_input)); } +std::unique_ptr ConstructAccountsRootKey( + base::span seed) { + auto result = HDKey::GenerateFromSeed(seed); + if (!result) { + return nullptr; + } + + // m/44'/60'/0'/0 + return result->DeriveChildFromPath({DerivationIndex::Hardened(44), // + DerivationIndex::Hardened(60), + DerivationIndex::Hardened(0), + DerivationIndex::Normal(0)}); +} + } // namespace -EthereumKeyring::EthereumKeyring(base::span seed) - : Secp256k1HDKeyring(seed, GetRootPath(mojom::KeyringId::kDefault)) {} +EthereumKeyring::EthereumKeyring(base::span seed) { + accounts_root_ = ConstructAccountsRootKey(seed); +} // static std::optional EthereumKeyring::RecoverAddress( @@ -179,7 +195,7 @@ std::string EthereumKeyring::EncodePrivateKeyForExport( std::unique_ptr EthereumKeyring::DeriveAccount(uint32_t index) const { // m/44'/60'/0'/0/{index} - return root_->DeriveNormalChild(index); + return accounts_root_->DeriveChild(DerivationIndex::Normal(index)); } } // namespace brave_wallet diff --git a/components/brave_wallet/browser/ethereum_keyring_unittest.cc b/components/brave_wallet/browser/ethereum_keyring_unittest.cc index 808dee64c810..e5d6f3c09bc3 100644 --- a/components/brave_wallet/browser/ethereum_keyring_unittest.cc +++ b/components/brave_wallet/browser/ethereum_keyring_unittest.cc @@ -41,11 +41,13 @@ TEST(EthereumKeyringUnitTest, ConstructRootHDKey) { &seed)); EthereumKeyring keyring(seed); EXPECT_EQ( - keyring.root_.get()->GetPrivateExtendedKey(ExtendedKeyVersion::kXprv), + keyring.accounts_root_.get()->GetPrivateExtendedKey( + ExtendedKeyVersion::kXprv), "xprvA1YGbmYkUq9KMyPwADQehauc1vG7TSbNLc1dwYbvU7VzyAr7TPhj9VoJJoP2CV5kDmXX" "SZvbJ79ieLnD7Pt4rhbuaQjVr2JE3vcDBAvDoUg"); EXPECT_EQ( - keyring.root_.get()->GetPublicExtendedKey(ExtendedKeyVersion::kXpub), + keyring.accounts_root_.get()->GetPublicExtendedKey( + ExtendedKeyVersion::kXpub), "xpub6EXd1H5eKChcaTUQGEwf4irLZx6bruKDhpwEjw1Y2T2yqyBFzw1yhJ7nA5EeBKozqYKB" "8jHxmhe7bEqyBEdPNWyPgCm2aZfs9tbLVYujvL3"); } @@ -115,10 +117,10 @@ TEST(EthereumKeyringUnitTest, SignTransaction) { } TEST(EthereumKeyringUnitTest, SignMessage) { - std::vector private_key; - EXPECT_TRUE(base::HexStringToBytes( + std::array private_key; + EXPECT_TRUE(base::HexStringToSpan( "6969696969696969696969696969696969696969696969696969696969696969", - &private_key)); + private_key)); std::unique_ptr key = std::make_unique(); key->SetPrivateKey(private_key); diff --git a/components/brave_wallet/browser/filecoin_keyring.cc b/components/brave_wallet/browser/filecoin_keyring.cc index 0ddcdb6d4edf..4148314489f4 100644 --- a/components/brave_wallet/browser/filecoin_keyring.cc +++ b/components/brave_wallet/browser/filecoin_keyring.cc @@ -17,6 +17,7 @@ #include "base/strings/stringprintf.h" #include "brave/components/brave_wallet/browser/fil_transaction.h" #include "brave/components/brave_wallet/browser/internal/hd_key.h" +#include "brave/components/brave_wallet/browser/internal/hd_key_common.h" #include "brave/components/brave_wallet/common/brave_wallet.mojom.h" #include "brave/components/brave_wallet/common/fil_address.h" #include "brave/components/filecoin/rs/src/lib.rs.h" @@ -56,17 +57,36 @@ std::string GetExportEncodedJSON(base::span private_key_bytes, return base::ToLowerASCII(base::HexEncode(json)); } +std::unique_ptr ConstructAccountsRootKey(base::span seed, + bool testnet) { + auto result = HDKey::GenerateFromSeed(seed); + if (!result) { + return nullptr; + } + + if (testnet) { + // Testnet: m/44'/1'/0'/0 + return result->DeriveChildFromPath({DerivationIndex::Hardened(44), // + DerivationIndex::Hardened(1), + DerivationIndex::Hardened(0), + DerivationIndex::Normal(0)}); + } else { + // Mainnet: m/44'/461'/0'/0 + return result->DeriveChildFromPath({DerivationIndex::Hardened(44), // + DerivationIndex::Hardened(461), + DerivationIndex::Hardened(0), + DerivationIndex::Normal(0)}); + } +} + } // namespace FilecoinKeyring::~FilecoinKeyring() = default; FilecoinKeyring::FilecoinKeyring(base::span seed, - const std::string& chain_id) - : Secp256k1HDKeyring( - seed, - GetRootPath(chain_id == mojom::kFilecoinMainnet - ? mojom::KeyringId::kFilecoin - : mojom::KeyringId::kFilecoinTestnet)) { + const std::string& chain_id) { + accounts_root_ = + ConstructAccountsRootKey(seed, chain_id == mojom::kFilecoinTestnet); network_ = chain_id; DCHECK(network_ == mojom::kFilecoinMainnet || network_ == mojom::kFilecoinTestnet); @@ -219,7 +239,7 @@ std::optional FilecoinKeyring::SignTransaction( std::unique_ptr FilecoinKeyring::DeriveAccount(uint32_t index) const { // Mainnet m/44'/461'/0'/0/{index} // Testnet m/44'/1'/0'/0/{index} - return root_->DeriveNormalChild(index); + return accounts_root_->DeriveChild(DerivationIndex::Normal(index)); } } // namespace brave_wallet diff --git a/components/brave_wallet/browser/internal/BUILD.gn b/components/brave_wallet/browser/internal/BUILD.gn index 6f014fbb9bb5..980a57ecc2ef 100644 --- a/components/brave_wallet/browser/internal/BUILD.gn +++ b/components/brave_wallet/browser/internal/BUILD.gn @@ -9,6 +9,8 @@ source_set("hd_key") { sources = [ "hd_key.cc", "hd_key.h", + "hd_key_common.cc", + "hd_key_common.h", "hd_key_ed25519.cc", "hd_key_ed25519.h", ] diff --git a/components/brave_wallet/browser/internal/hd_key.cc b/components/brave_wallet/browser/internal/hd_key.cc index 9ff922674ba4..d0736d1ce7ea 100644 --- a/components/brave_wallet/browser/internal/hd_key.cc +++ b/components/brave_wallet/browser/internal/hd_key.cc @@ -11,46 +11,39 @@ #include "brave/components/brave_wallet/browser/internal/hd_key.h" +#include #include #include #include "base/check.h" #include "base/containers/span.h" +#include "base/containers/span_reader.h" +#include "base/containers/span_writer.h" #include "base/containers/to_vector.h" -#include "base/json/json_reader.h" #include "base/logging.h" #include "base/numerics/byte_conversions.h" -#include "base/strings/strcat.h" #include "base/strings/string_number_conversions.h" -#include "base/strings/string_split.h" #include "base/strings/string_util.h" +#include "brave/components/brave_wallet/browser/internal/hd_key_common.h" #include "brave/components/brave_wallet/common/bitcoin_utils.h" #include "brave/components/brave_wallet/common/hash_utils.h" #include "brave/components/brave_wallet/common/hex_utils.h" #include "brave/components/brave_wallet/common/zcash_utils.h" #include "brave/third_party/bitcoin-core/src/src/base58.h" #include "brave/vendor/bat-native-tweetnacl/tweetnacl.h" -#include "crypto/encryptor.h" -#include "crypto/kdf.h" #include "crypto/process_bound_string.h" #include "crypto/random.h" -#include "crypto/symmetric_key.h" #include "third_party/boringssl/src/include/openssl/hmac.h" #define SECP256K1_BUILD // This effectively turns off export attributes. #include "brave/third_party/bitcoin-core/src/src/secp256k1/include/secp256k1.h" #include "brave/third_party/bitcoin-core/src/src/secp256k1/include/secp256k1_recovery.h" -using crypto::Encryptor; -using crypto::SymmetricKey; - namespace brave_wallet { namespace { -constexpr char kMasterNode[] = "m"; constexpr char kMasterSecret[] = "Bitcoin seed"; constexpr size_t kSHA512Length = 64; -constexpr uint32_t kHardenedOffset = 0x80000000; constexpr size_t kSerializationLength = 78; constexpr size_t kMaxDerSignatureSize = 72; constexpr size_t kContextRandomizeSize = 32; @@ -71,59 +64,12 @@ const secp256k1_context* GetSecp256k1Ctx() { return kSecp256k1Ctx; } -bool UTCPasswordVerification(const std::string& derived_key, - base::span ciphertext, - const std::string& mac, - size_t dklen) { - std::vector mac_verification_input(derived_key.end() - dklen / 2, - derived_key.end()); - mac_verification_input.insert(mac_verification_input.end(), - ciphertext.begin(), ciphertext.end()); - // verify password - if (HexEncodeLower(KeccakHash(mac_verification_input)) != mac) { - VLOG(0) << __func__ << ": password does not match"; - return false; - } - return true; -} - -bool UTCDecryptPrivateKey(const std::string& derived_key, - base::span ciphertext, - base::span iv, - std::vector* private_key, - size_t dklen) { - if (!private_key) { - return false; - } - std::unique_ptr decryption_key = - SymmetricKey::Import(SymmetricKey::AES, derived_key.substr(0, dklen / 2)); - if (!decryption_key) { - VLOG(1) << __func__ << ": raw key has to be 16 or 32 bytes for AES import"; - return false; - } - Encryptor encryptor; - if (!encryptor.Init(decryption_key.get(), Encryptor::Mode::CTR, - std::vector())) { - VLOG(0) << __func__ << ": encryptor init failed"; - return false; - } - if (!encryptor.SetCounter(iv)) { - VLOG(0) << __func__ << ": encryptor set counter failed"; - return false; - } - - if (!encryptor.Decrypt(ciphertext, private_key)) { - VLOG(0) << __func__ << ": encryptor decrypt failed"; - return false; - } - - return true; -} - } // namespace -HDKey::HDKey() : identifier_(20), public_key_(33), chain_code_(32) {} +HDKey::HDKey() : public_key_(33), chain_code_(32) {} HDKey::~HDKey() = default; +HDKey& HDKey::operator=(const HDKey& other) = default; +HDKey::HDKey(const HDKey& other) = default; HDKey::ParsedExtendedKey::ParsedExtendedKey() = default; HDKey::ParsedExtendedKey::~ParsedExtendedKey() = default; @@ -147,11 +93,8 @@ std::unique_ptr HDKey::GenerateFromSeed(base::span seed) { std::unique_ptr hdkey = std::make_unique(); auto hmac_span = base::make_span(hmac); - auto IL = hmac_span.first(kSHA512Length / 2); - auto IR = hmac_span.last(kSHA512Length / 2); - hdkey->SetPrivateKey(IL); - hdkey->SetChainCode(IR); - hdkey->path_ = kMasterNode; + hdkey->SetPrivateKey(hmac_span.first()); + hdkey->SetChainCode(hmac_span.last()); return hdkey; } @@ -166,242 +109,44 @@ std::unique_ptr HDKey::GenerateFromExtendedKey( SecureVector buf(decoded_key.begin(), decoded_key.end()); crypto::internal::SecureZeroBuffer(base::as_writable_byte_span(decoded_key)); + // version(4) || depth(1) || parent_fingerprint(4) || index(4) || chain(32) || // key(33) - const uint8_t* ptr = buf.data(); - auto version = static_cast(ptr[0] << 24 | ptr[1] << 16 | - ptr[2] << 8 | ptr[3] << 0); - ptr += sizeof(version); - - uint8_t depth = *ptr; - ptr += sizeof(depth); + auto reader = base::SpanReader(base::as_byte_span(buf)); - int32_t parent_fingerprint = - ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3] << 0; - ptr += sizeof(parent_fingerprint); - - int32_t index = ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3] << 0; - ptr += sizeof(index); - - std::unique_ptr hdkey = std::make_unique(); - hdkey->depth_ = depth; - hdkey->parent_fingerprint_ = parent_fingerprint; - hdkey->index_ = index; + auto result = std::make_unique(); + result->hdkey = std::make_unique(); - std::vector chain_code(ptr, ptr + 32); - ptr += chain_code.size(); - hdkey->SetChainCode(chain_code); + CHECK(reader.ReadU32BigEndian(reinterpret_cast(result->version))); + CHECK(reader.ReadU8BigEndian(result->hdkey->depth_)); + CHECK(reader.ReadCopy(result->hdkey->parent_fingerprint_)); + CHECK(reader.ReadU32BigEndian(result->hdkey->index_)); + CHECK(reader.ReadCopy(result->hdkey->chain_code_)); - if (*ptr == 0x00) { + auto key_bytes = reader.Read(); + CHECK(key_bytes); + if (key_bytes->front() == 0x00) { // Skip first zero byte which is not part of private key. - hdkey->SetPrivateKey(base::make_span(ptr + 1, ptr + 33)); + result->hdkey->SetPrivateKey(key_bytes->subspan<1>()); } else { - hdkey->SetPublicKey(*base::span(ptr, ptr + kSecp256k1PubkeySize) - .to_fixed_extent()); + result->hdkey->SetPublicKey(*key_bytes); } - auto result = std::make_unique(); - result->hdkey = std::move(hdkey); - result->version = version; + return result; } // static std::unique_ptr HDKey::GenerateFromPrivateKey( - base::span private_key) { - if (private_key.size() != 32) { - return nullptr; - } + base::span private_key) { std::unique_ptr hd_key = std::make_unique(); hd_key->SetPrivateKey(private_key); return hd_key; } -// static -std::unique_ptr HDKey::GenerateFromV3UTC(const std::string& password, - const std::string& json) { - if (password.empty()) { - VLOG(0) << __func__ << "empty password"; - return nullptr; - } - auto parsed_json = base::JSONReader::ReadAndReturnValueWithError(json); - if (!parsed_json.has_value() || !parsed_json->is_dict()) { - VLOG(0) << __func__ << ": UTC v3 json parsed failed because " - << parsed_json.error().message; - return nullptr; - } - - auto& dict = parsed_json->GetDict(); - // check version - auto version = dict.FindInt("version"); - if (!version || *version != 3) { - VLOG(0) << __func__ << ": missing version or version is not 3"; - return nullptr; - } - - const auto* crypto = dict.FindDict("crypto"); - if (!crypto) { - VLOG(0) << __func__ << ": missing crypto"; - return nullptr; - } - const auto* kdf = crypto->FindString("kdf"); - if (!kdf) { - VLOG(0) << __func__ << ": missing kdf"; - return nullptr; - } - const auto* kdfparams = crypto->FindDict("kdfparams"); - if (!kdfparams) { - VLOG(0) << __func__ << ": missing kdfparams"; - return nullptr; - } - auto dklen = kdfparams->FindInt("dklen"); - if (!dklen) { - VLOG(0) << __func__ << ": missing dklen"; - return nullptr; - } - if (*dklen != 32) { - VLOG(0) << __func__ << ": dklen must be 32"; - return nullptr; - } - const auto* salt = kdfparams->FindString("salt"); - if (!salt) { - VLOG(0) << __func__ << ": missing salt"; - return nullptr; - } - std::vector salt_bytes; - if (!base::HexStringToBytes(*salt, &salt_bytes)) { - VLOG(1) << __func__ << ": invalid salt"; - return nullptr; - } - - std::vector key((size_t)*dklen); - - if (*kdf == "pbkdf2") { - auto c = kdfparams->FindInt("c"); - if (!c) { - VLOG(0) << __func__ << ": missing c"; - return nullptr; - } - const auto* prf = kdfparams->FindString("prf"); - if (!prf) { - VLOG(0) << __func__ << ": missing prf"; - return nullptr; - } - if (*prf != "hmac-sha256") { - VLOG(0) << __func__ << ": prf must be hmac-sha256 when using pbkdf2"; - return nullptr; - } - - crypto::kdf::Pbkdf2HmacSha256Params params = { - .iterations = base::checked_cast(*c), - }; - if (!crypto::kdf::DeriveKeyPbkdf2HmacSha256( - params, base::as_byte_span(password), - base::as_byte_span(salt_bytes), key)) { - VLOG(1) << __func__ << ": pbkdf2 derivation failed"; - return nullptr; - } - } else if (*kdf == "scrypt") { - auto n = kdfparams->FindInt("n"); - if (!n) { - VLOG(0) << __func__ << ": missing n"; - return nullptr; - } - auto r = kdfparams->FindInt("r"); - if (!r) { - VLOG(0) << __func__ << ": missing r"; - return nullptr; - } - auto p = kdfparams->FindInt("p"); - if (!p) { - VLOG(0) << __func__ << ": missing p"; - return nullptr; - } - crypto::kdf::ScryptParams params = { - .cost = (size_t)*n, - .block_size = (size_t)*r, - .parallelization = (size_t)*p, - .max_memory_bytes = 512 * 1024 * 1024, - }; - if (!crypto::kdf::DeriveKeyScryptNoCheck( - params, base::as_byte_span(password), - base::as_byte_span(salt_bytes), key)) { - VLOG(1) << __func__ << ": scrypt derivation failed"; - return nullptr; - } - } else { - VLOG(0) << __func__ - << ": kdf is not supported. (Only support pbkdf2 and scrypt)"; - return nullptr; - } - - const auto* mac = crypto->FindString("mac"); - if (!mac) { - VLOG(0) << __func__ << ": missing mac"; - return nullptr; - } - const auto* ciphertext = crypto->FindString("ciphertext"); - if (!ciphertext) { - VLOG(0) << __func__ << ": missing ciphertext"; - return nullptr; - } - std::vector ciphertext_bytes; - if (!base::HexStringToBytes(*ciphertext, &ciphertext_bytes)) { - VLOG(1) << __func__ << ": invalid ciphertext"; - return nullptr; - } - - auto derived_key = std::make_unique(key); - if (!UTCPasswordVerification(derived_key->key(), ciphertext_bytes, *mac, - *dklen)) { - return nullptr; - } - - const auto* cipher = crypto->FindString("cipher"); - if (!cipher) { - VLOG(0) << __func__ << ": missing cipher"; - return nullptr; - } - if (*cipher != "aes-128-ctr") { - VLOG(0) << __func__ - << ": AES-128-CTR is the minimal requirement of version 3"; - return nullptr; - } - - std::vector iv_bytes; - const auto* iv = crypto->FindStringByDottedPath("cipherparams.iv"); - if (!iv) { - VLOG(0) << __func__ << ": missing cipherparams.iv"; - return nullptr; - } - if (!base::HexStringToBytes(*iv, &iv_bytes)) { - VLOG(1) << __func__ << ": invalid iv"; - return nullptr; - } - - std::vector private_key; - if (!UTCDecryptPrivateKey(derived_key->key(), ciphertext_bytes, iv_bytes, - &private_key, *dklen)) { - return nullptr; - } - - return GenerateFromPrivateKey(private_key); -} - -std::string HDKey::GetPath() const { - return path_; -} - -void HDKey::SetPrivateKey(base::span value) { - if (value.size() != 32) { - LOG(ERROR) << __func__ << ": pivate key must be 32 bytes"; - return; - } +void HDKey::SetPrivateKey( + base::span value) { private_key_.assign(value.begin(), value.end()); GeneratePublicKey(); - identifier_ = base::ToVector(Hash160(public_key_)); - - const uint8_t* ptr = identifier_.data(); - fingerprint_ = ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3] << 0; } std::string HDKey::GetPrivateExtendedKey(ExtendedKeyVersion version) const { @@ -427,10 +172,6 @@ void HDKey::SetPublicKey( return; } public_key_ = base::ToVector(value); - identifier_ = base::ToVector(Hash160(public_key_)); - - const uint8_t* ptr = identifier_.data(); - fingerprint_ = ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3] << 0; } std::string HDKey::GetPublicExtendedKey(ExtendedKeyVersion version) const { @@ -509,31 +250,20 @@ HDKey::DecryptCipherFromX25519_XSalsa20_Poly1305( return plaintext; } -void HDKey::SetChainCode(base::span value) { +void HDKey::SetChainCode( + base::span value) { chain_code_.assign(value.begin(), value.end()); } -std::unique_ptr HDKey::DeriveNormalChild(uint32_t index) { - if (index >= kHardenedOffset) { - return nullptr; - } - - return DeriveChild(index); -} - -std::unique_ptr HDKey::DeriveHardenedChild(uint32_t index) { - if (index >= kHardenedOffset) { +std::unique_ptr HDKey::DeriveChild(const DerivationIndex& index) { + auto index_value = index.GetValue(); + if (!index_value) { return nullptr; } - return DeriveChild(kHardenedOffset + index); -} - -std::unique_ptr HDKey::DeriveChild(uint32_t index) { - bool is_hardened = index >= kHardenedOffset; SecureVector data; - if (is_hardened) { + if (index.is_hardened()) { // Hardened: data = 0x00 || ser256(kpar) || ser32(index) DCHECK(!private_key_.empty()); data.push_back(0x00); @@ -544,10 +274,10 @@ std::unique_ptr HDKey::DeriveChild(uint32_t index) { // serP(Kpar) is public key when point(kpar) is private key data.insert(data.end(), public_key_.begin(), public_key_.end()); } - data.push_back((index >> 24) & 0xFF); - data.push_back((index >> 16) & 0xFF); - data.push_back((index >> 8) & 0xFF); - data.push_back(index & 0xFF); + data.push_back((*index_value >> 24) & 0xFF); + data.push_back((*index_value >> 16) & 0xFF); + data.push_back((*index_value >> 8) & 0xFF); + data.push_back(*index_value & 0xFF); SecureVector hmac(kSHA512Length); unsigned int out_len; @@ -559,8 +289,8 @@ std::unique_ptr HDKey::DeriveChild(uint32_t index) { DCHECK(out_len == kSHA512Length); auto hmac_span = base::make_span(hmac); - auto IL = hmac_span.first(kSHA512Length / 2); - auto IR = hmac_span.last(kSHA512Length / 2); + auto IL = hmac_span.first(); + auto IR = hmac_span.last(); std::unique_ptr hdkey = std::make_unique(); hdkey->SetChainCode(IR); @@ -575,10 +305,11 @@ std::unique_ptr HDKey::DeriveChild(uint32_t index) { LOG(ERROR) << __func__ << ": secp256k1_ec_seckey_tweak_add failed"; return nullptr; } - hdkey->SetPrivateKey(private_key); + hdkey->SetPrivateKey( + *base::span(private_key).to_fixed_extent()); } else { // Public parent key -> public child key (Normal only) - DCHECK(!is_hardened); + DCHECK(!index.is_hardened()); secp256k1_pubkey pubkey; if (!secp256k1_ec_pubkey_parse(GetSecp256k1Ctx(), &pubkey, public_key_.data(), public_key_.size())) { @@ -601,75 +332,24 @@ std::unique_ptr HDKey::DeriveChild(uint32_t index) { hdkey->SetPublicKey(public_key); } - if (!path_.empty()) { - const std::string node = - is_hardened ? base::NumberToString(index - kHardenedOffset) + "'" - : base::NumberToString(index); - - hdkey->path_ = base::StrCat({path_, "/", node}); - } hdkey->depth_ = depth_ + 1; - hdkey->parent_fingerprint_ = fingerprint_; - hdkey->index_ = index; + hdkey->parent_fingerprint_ = GetFingerprint(); + hdkey->index_ = *index_value; return hdkey; } -std::unique_ptr HDKey::DeriveChildFromPath(const std::string& path) { - if (path_ != kMasterNode) { - LOG(ERROR) << __func__ << ": must derive only from master key"; - return nullptr; - } - if (private_key_.empty()) { - LOG(ERROR) << __func__ << ": master key must have private key"; - return nullptr; - } +std::unique_ptr HDKey::DeriveChildFromPath( + base::span path) { + auto hd_key = std::unique_ptr(new HDKey(*this)); - std::unique_ptr hd_key = std::make_unique(); - std::vector entries = - base::SplitString(path, "/", base::WhitespaceHandling::TRIM_WHITESPACE, - base::SplitResult::SPLIT_WANT_NONEMPTY); - if (entries.empty()) { - return nullptr; - } - - // Starting with 'm' node and effectively copying `*this` into `hd_key`. - if (entries[0] != kMasterNode) { - LOG(ERROR) << __func__ << ": path must start with \"m\""; - return nullptr; - } - hd_key->SetPrivateKey(private_key_); - hd_key->SetChainCode(chain_code_); - hd_key->path_ = path_; - - for (size_t i = 1; i < entries.size(); ++i) { - std::string entry = entries[i]; - - bool is_hardened = entry.length() > 1 && entry.back() == '\''; - if (is_hardened) { - entry.pop_back(); - } - unsigned child_index = 0; - if (!base::StringToUint(entry, &child_index)) { - LOG(ERROR) << __func__ << ": path must contain number or number'"; - return nullptr; - } - if (child_index >= kHardenedOffset) { - LOG(ERROR) << __func__ << ": index must be less than " << kHardenedOffset; - return nullptr; - } - if (is_hardened) { - child_index += kHardenedOffset; - } - - hd_key = hd_key->DeriveChild(child_index); + for (auto index : path) { + hd_key = hd_key->DeriveChild(index); if (!hd_key) { return nullptr; } } - DCHECK_EQ(path, hd_key->GetPath()); - return hd_key; } @@ -789,6 +469,18 @@ bool HDKey::VerifyForTesting(base::span msg, return true; } +std::array HDKey::GetIdentifier() const { + return Hash160(public_key_); +} + +std::array HDKey::GetFingerprint() const { + auto identifier = GetIdentifier(); + std::array result; + base::span(result).copy_from( + base::span(identifier).first()); + return result; +} + std::vector HDKey::RecoverCompact(bool compressed, base::span msg, base::span sig, @@ -849,45 +541,29 @@ std::string HDKey::Serialize(ExtendedKeyVersion version, base::span key) const { // version(4) || depth(1) || parent_fingerprint(4) || index(4) || chain(32) || // key(32 or 33) - SecureVector buf; - - buf.reserve(kSerializationLength); - - uint32_t version_uint32 = static_cast(version); - - buf.push_back((version_uint32 >> 24) & 0xFF); - buf.push_back((version_uint32 >> 16) & 0xFF); - buf.push_back((version_uint32 >> 8) & 0xFF); - buf.push_back((version_uint32 >> 0) & 0xFF); - - buf.push_back(depth_); - - buf.push_back((parent_fingerprint_ >> 24) & 0xFF); - buf.push_back((parent_fingerprint_ >> 16) & 0xFF); - buf.push_back((parent_fingerprint_ >> 8) & 0xFF); - buf.push_back(parent_fingerprint_ & 0xFF); - - buf.push_back((index_ >> 24) & 0xFF); - buf.push_back((index_ >> 16) & 0xFF); - buf.push_back((index_ >> 8) & 0xFF); - buf.push_back(index_ & 0xFF); + SecureVector buf(kSerializationLength); + auto span_writer = base::SpanWriter(base::span(buf)); + span_writer.WriteU32BigEndian(static_cast(version)); + span_writer.WriteU8BigEndian(depth_); + span_writer.Write(parent_fingerprint_); + span_writer.WriteU32BigEndian(index_); + span_writer.Write(chain_code_); - buf.insert(buf.end(), chain_code_.begin(), chain_code_.end()); if (key.size() == 32) { DCHECK(version == ExtendedKeyVersion::kXprv || version == ExtendedKeyVersion::kYprv || version == ExtendedKeyVersion::kZprv); // 32-bytes private key is padded with a zero byte. - buf.insert(buf.end(), 0); + span_writer.WriteU8BigEndian(0); } else { DCHECK(version == ExtendedKeyVersion::kXpub || version == ExtendedKeyVersion::kYpub || version == ExtendedKeyVersion::kZpub); DCHECK_EQ(key.size(), 33u); } - buf.insert(buf.end(), key.begin(), key.end()); + span_writer.Write(key); - DCHECK(buf.size() == kSerializationLength); + DCHECK_EQ(span_writer.remaining(), 0u); return EncodeBase58Check(buf); } diff --git a/components/brave_wallet/browser/internal/hd_key.h b/components/brave_wallet/browser/internal/hd_key.h index d665fe4467bd..700b05183ee3 100644 --- a/components/brave_wallet/browser/internal/hd_key.h +++ b/components/brave_wallet/browser/internal/hd_key.h @@ -13,12 +13,17 @@ #include "base/containers/span.h" #include "base/gtest_prod_util.h" +#include "brave/components/brave_wallet/browser/internal/hd_key_common.h" #include "crypto/process_bound_string.h" namespace brave_wallet { inline constexpr size_t kCompactSignatureSize = 64; +inline constexpr size_t kSecp256k1PrivateKeySize = 32; +inline constexpr size_t kSecp256k1ChainCodeSize = 32; inline constexpr size_t kSecp256k1PubkeySize = 33; +inline constexpr size_t kSecp256k1IdentifierSize = 20; +inline constexpr size_t kSecp256k1FingerprintSize = 4; using SecureVector = std::vector>; @@ -59,12 +64,7 @@ class HDKey { static std::unique_ptr GenerateFromExtendedKey( const std::string& key); static std::unique_ptr GenerateFromPrivateKey( - base::span private_key); - // https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition - static std::unique_ptr GenerateFromV3UTC(const std::string& password, - const std::string& json); - - std::string GetPath() const; + base::span private_key); std::string GetPrivateExtendedKey(ExtendedKeyVersion version) const; std::vector GetPrivateKeyBytes() const; @@ -79,19 +79,10 @@ class HDKey { base::span ephemeral_public_key, base::span ciphertext) const; - // index should be 0 to 2^31-1 - // If anything failed, nullptr will be returned. - std::unique_ptr DeriveNormalChild(uint32_t index); - // index should be 0 to 2^31-1 - // If anything failed, nullptr will be returned. - std::unique_ptr DeriveHardenedChild(uint32_t index); + std::unique_ptr DeriveChild(const DerivationIndex& index); - // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki - // path format: m/[n|n']*/[n|n']*... - // n: 0 to 2^31-1 (normal derivation) - // n': n + 2^31 (harden derivation) - // If path is invalid, nullptr will be returned - std::unique_ptr DeriveChildFromPath(const std::string& path); + std::unique_ptr DeriveChildFromPath( + base::span path); // TODO(apaymyshev): make arg and return types fixed size spans and arrays // where possible. @@ -119,6 +110,9 @@ class HDKey { base::span sig, int recid); + std::array GetIdentifier() const; + std::array GetFingerprint() const; + private: FRIEND_TEST_ALL_PREFIXES(Eip1559TransactionUnitTest, GetSignedTransactionAndHash); @@ -131,33 +125,23 @@ class HDKey { FRIEND_TEST_ALL_PREFIXES(HDKeyUnitTest, SetPublicKey); FRIEND_TEST_ALL_PREFIXES(HDKeyUnitTest, SignAndVerifyAndRecover); - // value must be 32 bytes - void SetPrivateKey(base::span value); - void SetChainCode(base::span value); - // value must be 33 bytes valid public key (compressed) - void SetPublicKey(base::span value); + HDKey& operator=(const HDKey& other); + HDKey(const HDKey& other); - // index should be 0 to 2^32 - // 0 to 2^31-1 is normal derivation and 2^31 to 2^32-1 is harden derivation - // If anything failed, nullptr will be returned - std::unique_ptr DeriveChild(uint32_t index); + void SetPrivateKey(base::span value); + void SetChainCode(base::span value); + void SetPublicKey(base::span value); void GeneratePublicKey(); std::string Serialize(ExtendedKeyVersion version, base::span key) const; - std::string path_; uint8_t depth_ = 0; - uint32_t fingerprint_ = 0; - uint32_t parent_fingerprint_ = 0; + std::array parent_fingerprint_ = {}; uint32_t index_ = 0; - std::vector identifier_; SecureVector private_key_; std::vector public_key_; SecureVector chain_code_; - - HDKey(const HDKey&) = delete; - HDKey& operator=(const HDKey&) = delete; }; } // namespace brave_wallet diff --git a/components/brave_wallet/browser/internal/hd_key_common.cc b/components/brave_wallet/browser/internal/hd_key_common.cc new file mode 100644 index 000000000000..0f6bad76119d --- /dev/null +++ b/components/brave_wallet/browser/internal/hd_key_common.cc @@ -0,0 +1,37 @@ +/* Copyright (c) 2025 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/brave_wallet/browser/internal/hd_key_common.h" + +#include +#include + +namespace brave_wallet { + +DerivationIndex::DerivationIndex(uint32_t index, bool is_hardened) + : index_(index), is_hardened_(is_hardened) {} + +// static +DerivationIndex DerivationIndex::Normal(uint32_t index) { + return DerivationIndex(index, false); +} + +// static +DerivationIndex DerivationIndex::Hardened(uint32_t index) { + return DerivationIndex(index, true); +} + +bool DerivationIndex::IsValid() const { + return index_ < kHardenedOffset; +} + +std::optional DerivationIndex::GetValue() const { + if (!IsValid()) { + return std::nullopt; + } + return index_ + (is_hardened_ ? kHardenedOffset : 0); +} + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/internal/hd_key_common.h b/components/brave_wallet/browser/internal/hd_key_common.h new file mode 100644 index 000000000000..b1f71957de3c --- /dev/null +++ b/components/brave_wallet/browser/internal/hd_key_common.h @@ -0,0 +1,38 @@ +/* Copyright (c) 2025 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_INTERNAL_HD_KEY_COMMON_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_INTERNAL_HD_KEY_COMMON_H_ + +#include +#include + +namespace brave_wallet { + +inline constexpr size_t kEd25519SignatureSize = 64; +inline constexpr size_t kEd25519PublicKeySize = 32; +inline constexpr uint32_t kHardenedOffset = 0x80000000; + +class DerivationIndex { + public: + static DerivationIndex Normal(uint32_t index); + static DerivationIndex Hardened(uint32_t index); + + bool IsValid() const; + + bool is_hardened() const { return is_hardened_; } + + std::optional GetValue() const; + + private: + DerivationIndex(uint32_t index, bool is_hardened); + + uint32_t index_ = 0; + bool is_hardened_ = false; +}; + +} // namespace brave_wallet + +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_INTERNAL_HD_KEY_COMMON_H_ diff --git a/components/brave_wallet/browser/internal/hd_key_ed25519.cc b/components/brave_wallet/browser/internal/hd_key_ed25519.cc index 40bfdd813733..e743af3986f7 100644 --- a/components/brave_wallet/browser/internal/hd_key_ed25519.cc +++ b/components/brave_wallet/browser/internal/hd_key_ed25519.cc @@ -23,11 +23,6 @@ namespace { inline constexpr char kMasterSecret[] = "ed25519 seed"; -// Equivalent to 2^31 to make sure we're not below this. -// If the index is below this we'll use the soft path -// which is susceptible to key attacks on the Ed25519 curve. -inline constexpr uint32_t kHardenedOffset = 0x80000000; - // OpenSSL has it's own definition of private key which in fact is key pair. static_assert(kEd25519KeyPairSize == ED25519_PRIVATE_KEY_LEN); static_assert(kEd25519PublicKeySize == ED25519_PUBLIC_KEY_LEN); diff --git a/components/brave_wallet/browser/internal/hd_key_ed25519.h b/components/brave_wallet/browser/internal/hd_key_ed25519.h index 3c4b74c43733..059bf85b9ecf 100644 --- a/components/brave_wallet/browser/internal/hd_key_ed25519.h +++ b/components/brave_wallet/browser/internal/hd_key_ed25519.h @@ -12,12 +12,12 @@ #include "base/containers/span.h" #include "base/gtest_prod_util.h" +#include "brave/components/brave_wallet/browser/internal/hd_key_common.h" namespace brave_wallet { // https://www.rfc-editor.org/rfc/rfc8032.html#section-5.1.5 inline constexpr size_t kEd25519PrivateKeySize = 32; -inline constexpr size_t kEd25519PublicKeySize = 32; inline constexpr size_t kEd25519KeyPairSize = kEd25519PrivateKeySize + kEd25519PublicKeySize; diff --git a/components/brave_wallet/browser/internal/hd_key_unittest.cc b/components/brave_wallet/browser/internal/hd_key_unittest.cc index 3c752dd88000..d9f6922230a9 100644 --- a/components/brave_wallet/browser/internal/hd_key_unittest.cc +++ b/components/brave_wallet/browser/internal/hd_key_unittest.cc @@ -8,10 +8,14 @@ #include #include "base/containers/span.h" +#include "base/containers/to_vector.h" +#include "base/notreached.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "brave/components/brave_wallet/browser/bitcoin/bitcoin_test_utils.h" #include "brave/components/brave_wallet/browser/brave_wallet_utils.h" +#include "brave/components/brave_wallet/browser/internal/hd_key_common.h" #include "brave/components/brave_wallet/browser/test_utils.h" #include "brave/components/brave_wallet/common/bitcoin_utils.h" #include "brave/components/brave_wallet/common/encoding_utils.h" @@ -32,16 +36,9 @@ bool IsPublicKeyEmpty(const std::vector& public_key) { return true; } -std::string GetHexAddr(const HDKey* key) { - const std::vector public_key = key->GetUncompressedPublicKey(); - // trim the header byte 0x04 - const std::vector pubkey_no_header(public_key.begin() + 1, - public_key.end()); - EthAddress addr = EthAddress::FromPublicKey(pubkey_no_header); - return addr.ToHex(); -} - -std::string GetWifPrivateKey(std::vector private_key, bool testnet) { +std::string GetWifPrivateKey(base::span private_key_bytes, + bool testnet) { + auto private_key = base::ToVector(private_key_bytes); private_key.insert(private_key.begin(), testnet ? 0xef : 0x80); // Version Byte. auto sha256hash = DoubleSHA256Hash(private_key); @@ -61,6 +58,43 @@ std::string GetWifCompressedPrivateKey(std::vector private_key, return Base58Encode(private_key); } +std::vector ParsePath(std::string_view path) { + auto entries = base::SplitStringPiece( + path, "/", base::WhitespaceHandling::TRIM_WHITESPACE, + base::SplitResult::SPLIT_WANT_NONEMPTY); + if (entries.empty()) { + NOTREACHED(); + } + + // Starting with 'm' node and effectively copying `*this` into `hd_key`. + if (entries[0] != "m") { + NOTREACHED(); + } + + std::vector result; + for (auto entry : base::span(entries).subspan(1)) { + bool is_hardened = entry.length() > 1 && entry.back() == '\''; + if (is_hardened) { + entry.remove_suffix(1); + } + uint32_t child_index = 0; + if (!base::StringToUint(entry, &child_index)) { + NOTREACHED(); + } + if (child_index >= kHardenedOffset) { + NOTREACHED(); + } + + if (is_hardened) { + result.push_back(DerivationIndex::Hardened(child_index)); + } else { + result.push_back(DerivationIndex::Normal(child_index)); + } + } + + return result; +} + } // namespace TEST(HDKeyUnitTest, GenerateFromSeed) { @@ -126,19 +160,20 @@ TEST(HDKeyUnitTest, TestVector1) { std::unique_ptr derived = HDKey::GenerateFromSeed(bytes); for (const auto& entry : cases) { - std::unique_ptr key = m_key->DeriveChildFromPath(entry.path); - EXPECT_EQ(key->GetPath(), entry.path); + auto key = m_key->DeriveChildFromPath(ParsePath(entry.path)); EXPECT_EQ(key->GetPublicExtendedKey(ExtendedKeyVersion::kXpub), entry.ext_pub); EXPECT_EQ(key->GetPrivateExtendedKey(ExtendedKeyVersion::kXprv), entry.ext_pri); if (entry.derive_normal) { - derived = derived->DeriveNormalChild(*entry.derive_normal); + derived = + derived->DeriveChild(DerivationIndex::Normal(*entry.derive_normal)); } else if (entry.derive_hardened) { - derived = derived->DeriveHardenedChild(*entry.derive_hardened); + derived = derived->DeriveChild( + DerivationIndex::Hardened(*entry.derive_hardened)); } - EXPECT_EQ(derived->GetPath(), entry.path); + EXPECT_EQ(derived->GetPublicExtendedKey(ExtendedKeyVersion::kXpub), entry.ext_pub); EXPECT_EQ(derived->GetPrivateExtendedKey(ExtendedKeyVersion::kXprv), @@ -202,19 +237,19 @@ TEST(HDKeyUnitTest, TestVector2) { std::unique_ptr derived = HDKey::GenerateFromSeed(bytes); for (const auto& entry : cases) { - std::unique_ptr key = m_key->DeriveChildFromPath(entry.path); - EXPECT_EQ(key->GetPath(), entry.path); + auto key = m_key->DeriveChildFromPath(ParsePath(entry.path)); EXPECT_EQ(key->GetPublicExtendedKey(ExtendedKeyVersion::kXpub), entry.ext_pub); EXPECT_EQ(key->GetPrivateExtendedKey(ExtendedKeyVersion::kXprv), entry.ext_pri); if (entry.derive_normal) { - derived = derived->DeriveNormalChild(*entry.derive_normal); + derived = + derived->DeriveChild(DerivationIndex::Normal(*entry.derive_normal)); } else if (entry.derive_hardened) { - derived = derived->DeriveHardenedChild(*entry.derive_hardened); + derived = derived->DeriveChild( + DerivationIndex::Hardened(*entry.derive_hardened)); } - EXPECT_EQ(derived->GetPath(), entry.path); EXPECT_EQ(derived->GetPublicExtendedKey(ExtendedKeyVersion::kXpub), entry.ext_pub); @@ -255,19 +290,19 @@ TEST(HDKeyUnitTest, TestVector3) { std::unique_ptr derived = HDKey::GenerateFromSeed(bytes); for (const auto& entry : cases) { - std::unique_ptr key = m_key->DeriveChildFromPath(entry.path); - EXPECT_EQ(key->GetPath(), entry.path); + auto key = m_key->DeriveChildFromPath(ParsePath(entry.path)); EXPECT_EQ(key->GetPublicExtendedKey(ExtendedKeyVersion::kXpub), entry.ext_pub); EXPECT_EQ(key->GetPrivateExtendedKey(ExtendedKeyVersion::kXprv), entry.ext_pri); if (entry.derive_normal) { - derived = derived->DeriveNormalChild(*entry.derive_normal); + derived = + derived->DeriveChild(DerivationIndex::Normal(*entry.derive_normal)); } else if (entry.derive_hardened) { - derived = derived->DeriveHardenedChild(*entry.derive_hardened); + derived = derived->DeriveChild( + DerivationIndex::Hardened(*entry.derive_hardened)); } - EXPECT_EQ(derived->GetPath(), entry.path); EXPECT_EQ(derived->GetPublicExtendedKey(ExtendedKeyVersion::kXpub), entry.ext_pub); @@ -284,7 +319,7 @@ TEST(HDKeyUnitTest, GenerateFromExtendedKey) { EXPECT_EQ(parsed_xprv->version, ExtendedKeyVersion::kXprv); auto* hdkey_from_pri = parsed_xprv->hdkey.get(); EXPECT_EQ(hdkey_from_pri->depth_, 5u); - EXPECT_EQ(hdkey_from_pri->parent_fingerprint_, 0x31a507b8u); + EXPECT_EQ(HexEncodeLower(hdkey_from_pri->parent_fingerprint_), "31a507b8"); EXPECT_EQ(hdkey_from_pri->index_, 2u); EXPECT_EQ(base::ToLowerASCII(base::HexEncode(hdkey_from_pri->chain_code_)), "9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271"); @@ -294,9 +329,8 @@ TEST(HDKeyUnitTest, GenerateFromExtendedKey) { EXPECT_EQ( base::ToLowerASCII(base::HexEncode(hdkey_from_pri->public_key_)), "024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c"); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(hdkey_from_pri->identifier_)), + EXPECT_EQ(HexEncodeLower(hdkey_from_pri->GetIdentifier()), "26132fdbe7bf89cbc64cf8dafa3f9f88b8666220"); - EXPECT_EQ(hdkey_from_pri->GetPath(), ""); // m/0/2147483647'/1/2147483646'/2 auto parsed_xpub = HDKey::GenerateFromExtendedKey( @@ -305,7 +339,7 @@ TEST(HDKeyUnitTest, GenerateFromExtendedKey) { EXPECT_EQ(parsed_xpub->version, ExtendedKeyVersion::kXpub); auto* hdkey_from_pub = parsed_xpub->hdkey.get(); EXPECT_EQ(hdkey_from_pub->depth_, 5u); - EXPECT_EQ(hdkey_from_pub->parent_fingerprint_, 0x31a507b8u); + EXPECT_EQ(HexEncodeLower(hdkey_from_pub->parent_fingerprint_), "31a507b8"); EXPECT_EQ(hdkey_from_pub->index_, 2u); EXPECT_EQ(base::ToLowerASCII(base::HexEncode(hdkey_from_pub->chain_code_)), "9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271"); @@ -313,15 +347,14 @@ TEST(HDKeyUnitTest, GenerateFromExtendedKey) { EXPECT_EQ( base::ToLowerASCII(base::HexEncode(hdkey_from_pub->public_key_)), "024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c"); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(hdkey_from_pub->identifier_)), + EXPECT_EQ(HexEncodeLower(hdkey_from_pub->GetIdentifier()), "26132fdbe7bf89cbc64cf8dafa3f9f88b8666220"); - EXPECT_EQ(hdkey_from_pub->GetPath(), ""); auto parsed_zprv = HDKey::GenerateFromExtendedKey(kBtcMainnetImportAccount0); EXPECT_EQ(parsed_zprv->version, ExtendedKeyVersion::kZprv); auto* hdkey_from_zprv = parsed_zprv->hdkey.get(); EXPECT_EQ(hdkey_from_zprv->depth_, 3u); - EXPECT_EQ(hdkey_from_zprv->parent_fingerprint_, 0x7ef32bdbu); + EXPECT_EQ(HexEncodeLower(hdkey_from_zprv->parent_fingerprint_), "7ef32bdb"); EXPECT_EQ(hdkey_from_zprv->index_, 2147483648u); EXPECT_EQ(base::ToLowerASCII(base::HexEncode(hdkey_from_zprv->chain_code_)), "4a53a0ab21b9dc95869c4e92a161194e03c0ef3ff5014ac692f433c4765490fc"); @@ -331,15 +364,14 @@ TEST(HDKeyUnitTest, GenerateFromExtendedKey) { EXPECT_EQ( base::ToLowerASCII(base::HexEncode(hdkey_from_zprv->public_key_)), "02707a62fdacc26ea9b63b1c197906f56ee0180d0bcf1966e1a2da34f5f3a09a9b"); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(hdkey_from_zprv->identifier_)), + EXPECT_EQ(HexEncodeLower(hdkey_from_zprv->GetIdentifier()), "fd13aac9a294188cdfe1331a8d94880bccbef8c1"); - EXPECT_EQ(hdkey_from_zprv->GetPath(), ""); auto parsed_vprv = HDKey::GenerateFromExtendedKey(kBtcTestnetImportAccount0); EXPECT_EQ(parsed_vprv->version, ExtendedKeyVersion::kVprv); auto* hdkey_from_vprv = parsed_vprv->hdkey.get(); EXPECT_EQ(hdkey_from_vprv->depth_, 3u); - EXPECT_EQ(hdkey_from_vprv->parent_fingerprint_, 0x0ef4b1afu); + EXPECT_EQ(HexEncodeLower(hdkey_from_vprv->parent_fingerprint_), "0ef4b1af"); EXPECT_EQ(hdkey_from_vprv->index_, 2147483648u); EXPECT_EQ(base::ToLowerASCII(base::HexEncode(hdkey_from_vprv->chain_code_)), "3c8c2037ee4c1621da0d348db51163709a622d0d2838dde6d8419c51f6301c62"); @@ -349,19 +381,18 @@ TEST(HDKeyUnitTest, GenerateFromExtendedKey) { EXPECT_EQ( base::ToLowerASCII(base::HexEncode(hdkey_from_vprv->public_key_)), "03b88e0fbe3f646337ed93bc0c0f3b843fcf7d2589e5ec884754e6402027a890b4"); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(hdkey_from_vprv->identifier_)), + EXPECT_EQ(HexEncodeLower(hdkey_from_vprv->GetIdentifier()), "e99b862826a40a32c24c79785d06b19de3fb076f"); - EXPECT_EQ(hdkey_from_vprv->GetPath(), ""); } TEST(HDKeyUnitTest, GenerateFromPrivateKey) { - std::vector private_key; - ASSERT_TRUE(base::HexStringToBytes( + std::array private_key; + EXPECT_TRUE(base::HexStringToSpan( "bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23", - &private_key)); + private_key)); std::unique_ptr key = HDKey::GenerateFromPrivateKey(private_key); EXPECT_NE(key, nullptr); - EXPECT_EQ(key->GetPath(), ""); + const std::vector msg_a(32, 0x00); const std::vector msg_b(32, 0x08); int recid_a = -1; @@ -378,9 +409,6 @@ TEST(HDKeyUnitTest, GenerateFromPrivateKey) { "532e5c0ae2a25392d97f5e55ab1288ef1e08d5c034bad3b0956fbbab73b381"); EXPECT_TRUE(key->VerifyForTesting(msg_a, sig_a)); EXPECT_TRUE(key->VerifyForTesting(msg_b, sig_b)); - - EXPECT_EQ(HDKey::GenerateFromPrivateKey(std::vector(33)), nullptr); - EXPECT_EQ(HDKey::GenerateFromPrivateKey(std::vector(31)), nullptr); } TEST(HDKeyUnitTest, SignAndVerifyAndRecover) { @@ -447,11 +475,7 @@ TEST(HDKeyUnitTest, SignAndVerifyAndRecover) { TEST(HDKeyUnitTest, SetPrivateKey) { HDKey key; - key.SetPrivateKey(std::vector(31)); - ASSERT_TRUE(key.GetPrivateKeyBytes().empty()); - key.SetPrivateKey(std::vector(33)); - ASSERT_TRUE(key.GetPrivateKeyBytes().empty()); - key.SetPrivateKey(std::vector(32, 0x1)); + key.SetPrivateKey(std::array{1, 2, 3}); EXPECT_FALSE(key.GetPrivateKeyBytes().empty()); EXPECT_TRUE(!IsPublicKeyEmpty(key.public_key_)); } @@ -471,29 +495,19 @@ TEST(HDKeyUnitTest, DeriveChildFromPath) { std::unique_ptr m_key = HDKey::GenerateFromSeed(std::vector(32)); - const char* cases[] = { - "1/2/3/4", "a/b/1/2", "////", "m1234", - "m'/1/2/3'", "m/1'''/12", "m/1/a'/3", "m/-4", - "m/2147483648", "m/2147483648'", "m/2/2147483649", - }; - - for (auto* entry : cases) { - std::unique_ptr key = m_key->DeriveChildFromPath(entry); - EXPECT_EQ(key, nullptr); - } - { // public parent derives public child auto parsed_xpub = HDKey::GenerateFromExtendedKey( "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJo" "Cu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"); auto* key = parsed_xpub->hdkey.get(); - std::unique_ptr derived_key = key->DeriveNormalChild(3353535) - ->DeriveNormalChild(2223) - ->DeriveNormalChild(0) - ->DeriveNormalChild(99424) - ->DeriveNormalChild(4) - ->DeriveNormalChild(33); + std::unique_ptr derived_key = + key->DeriveChild(DerivationIndex::Normal(3353535)) + ->DeriveChild(DerivationIndex::Normal(2223)) + ->DeriveChild(DerivationIndex::Normal(0)) + ->DeriveChild(DerivationIndex::Normal(99424)) + ->DeriveChild(DerivationIndex::Normal(4)) + ->DeriveChild(DerivationIndex::Normal(33)); EXPECT_EQ( derived_key->GetPublicExtendedKey(ExtendedKeyVersion::kXpub), "xpub6JdKdVJtdx6sC3nh87pDvnGhotXuU5Kz6Qy7Piy84vUAwWSYShsUGULE8u6gCi" @@ -507,7 +521,7 @@ TEST(HDKeyUnitTest, DeriveChildFromPath) { std::unique_ptr key = HDKey::GenerateFromSeed(bytes); std::unique_ptr derived_key = - key->DeriveChildFromPath("m/44'/6'/4'"); + key->DeriveChildFromPath(ParsePath("m/44'/6'/4'")); EXPECT_EQ(derived_key->GetPrivateExtendedKey(ExtendedKeyVersion::kXprv), "xprv9ymoag6W7cR6KBcJzhCM6qqTrb3rRVVwXKzwNqp1tDWcwierEv3BA9if3ARH" "MhMPh9u2jNoutcgpUBLMfq3kADDo7LzfoCnhhXMRGX3PXDx"); @@ -521,11 +535,12 @@ TEST(HDKeyUnitTest, DeriveChildFromPath) { EXPECT_EQ( base::ToLowerASCII(base::HexEncode(key->GetPrivateKeyBytes())), "00000055378cf5fafb56c711c674143f9b0ee82ab0ba2924f19b64f5ae7cdbfd"); - std::unique_ptr derived_key = key->DeriveHardenedChild(44) - ->DeriveHardenedChild(0) - ->DeriveHardenedChild(0) - ->DeriveNormalChild(0) - ->DeriveHardenedChild(0); + std::unique_ptr derived_key = + key->DeriveChild(DerivationIndex::Hardened(44)) + ->DeriveChild(DerivationIndex::Hardened(0)) + ->DeriveChild(DerivationIndex::Hardened(0)) + ->DeriveChild(DerivationIndex::Normal(0)) + ->DeriveChild(DerivationIndex::Hardened(0)); EXPECT_EQ( base::ToLowerASCII(base::HexEncode(derived_key->GetPrivateKeyBytes())), "3348069561d2a0fb925e74bf198762acc47dce7db27372257d2d959a9e6f8aeb"); @@ -544,99 +559,9 @@ TEST(HDKeyUnitTest, EncodePrivateKeyForExport) { "00000055378CF5FAFB56C711C674143F9B0EE82AB0BA2924F19B64F5AE7CDBFD"); } -TEST(HDKeyUnitTest, GenerateFromV3UTC) { - const std::string json( - R"({ - "address":"b14ab53e38da1c172f877dbc6d65e4a1b0474c3c", - "crypto" : { - "cipher" : "aes-128-ctr", - "cipherparams" : { - "iv" : "cecacd85e9cb89788b5aab2f93361233" - }, - "ciphertext" : "c52682025b1e5d5c06b816791921dbf439afe7a053abb9fac19f38a57499652c", - "kdf" : "scrypt", - "kdfparams" : { - "dklen" : 32, - "n" : 262144, - "p" : 1, - "r" : 8, - "salt" : "dc9e4a98886738bd8aae134a1f89aaa5a502c3fbd10e336136d4d5fe47448ad6" - }, - "mac" : "27b98c8676dc6619d077453b38db645a4c7c17a3e686ee5adaf53c11ac1b890e" - }, - "id" : "7e59dc02-8d42-409d-b29a-a8a0f862cc81", - "version" : 3 - })"); - std::unique_ptr hd_key = HDKey::GenerateFromV3UTC("testtest", json); - ASSERT_TRUE(hd_key); - EXPECT_EQ(hd_key->GetPath(), ""); - EXPECT_EQ(GetHexAddr(hd_key.get()), - "0xb14ab53e38da1c172f877dbc6d65e4a1b0474c3c"); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(hd_key->GetPrivateKeyBytes())), - "efca4cdd31923b50f4214af5d2ae10e7ac45a5019e9431cc195482d707485378"); - - // wrong password - EXPECT_FALSE(HDKey::GenerateFromV3UTC("brave1234", json)); - EXPECT_FALSE(HDKey::GenerateFromV3UTC("", json)); - EXPECT_FALSE(HDKey::GenerateFromV3UTC("testtest", R"({{})")); - - // |N| > 2^(128 * |r| / 8) - const std::string invalid_r( - R"({ - "crypto" : { - "cipher" : "aes-128-ctr", - "cipherparams" : { - "iv" : "83dbcc02d8ccb40e466191a123791e0e" - }, - "ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", - "kdf" : "scrypt", - "kdfparams" : { - "dklen" : 32, - "n" : 262144, - "p" : 8, - "r" : 1, - "salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19" - }, - "mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097" - }, - "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6", - "version" : 3 - })"); - - EXPECT_FALSE(HDKey::GenerateFromV3UTC("testtest", invalid_r)); - - const std::string pbkdf2_json( - R"({ - "address":"b14ab53e38da1c172f877dbc6d65e4a1b0474c3c", - "crypto" : { - "cipher" : "aes-128-ctr", - "cipherparams" : { - "iv" : "cecacd85e9cb89788b5aab2f93361233" - }, - "ciphertext" : "01ee7f1a3c8d187ea244c92eea9e332ab0bb2b4c902d89bdd71f80dc384da1be", - "kdf" : "pbkdf2", - "kdfparams" : { - "c" : 262144, - "dklen" : 32, - "prf" : "hmac-sha256", - "salt" : "dc9e4a98886738bd8aae134a1f89aaa5a502c3fbd10e336136d4d5fe47448ad6" - }, - "mac" : "0c02cd0badfebd5e783e0cf41448f84086a96365fc3456716c33641a86ebc7cc" - }, - "id" : "7e59dc02-8d42-409d-b29a-a8a0f862cc81", - "version" : 3 - })"); - - std::unique_ptr hd_key2 = - HDKey::GenerateFromV3UTC("testtest", pbkdf2_json); - ASSERT_TRUE(hd_key2); - EXPECT_EQ(GetHexAddr(hd_key2.get()), - "0xb14ab53e38da1c172f877dbc6d65e4a1b0474c3c"); -} - // https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#examples TEST(HDKeyUnitTest, GetSegwitAddress) { - std::vector private_key_bytes(32, 0); + std::array private_key_bytes = {}; private_key_bytes.back() = 1; std::unique_ptr hdkey = HDKey::GenerateFromPrivateKey(private_key_bytes); @@ -652,10 +577,10 @@ TEST(HDKeyUnitTest, GetSegwitAddress) { // TODO(apaymyshev): Consider more tests. Also test R grinding. TEST(HDKeyUnitTest, SignDer) { - std::vector private_key_bytes; - ASSERT_TRUE(base::HexStringToBytes( + std::array private_key_bytes; + EXPECT_TRUE(base::HexStringToSpan( "12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747", - &private_key_bytes)); + private_key_bytes)); // https://github.com/bitcoin/bitcoin/blob/v24.0/src/test/key_tests.cpp#L20 ASSERT_EQ(GetWifPrivateKey(private_key_bytes, false), "5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHfmFiWtmAbrj"); @@ -688,7 +613,7 @@ TEST(HDKeyUnitTest, Bip84TestVectors) { "zpub6jftahH18ngZxLmXaKw3GSZzZsszmt9WqedkyZdezFtWRFBZqsQH5hyUmb4pCE" "eZGmVfQuP5bedXTB8is6fTv19U1GQRyQUKQGUTzyHACMF"); - auto base = m_key->DeriveChildFromPath("m/84'/0'/0'"); + auto base = m_key->DeriveChildFromPath(ParsePath("m/84'/0'/0'")); EXPECT_EQ(base->GetPrivateExtendedKey(ExtendedKeyVersion::kZprv), "zprvAdG4iTXWBoARxkkzNpNh8r6Qag3irQB8PzEMkAFeTRXxHpbF9z4QgEvBRmfvqW" "vGp42t42nvgGpNgYSJA9iefm1yYNZKEm7z6qUWCroSQnE"); @@ -696,7 +621,7 @@ TEST(HDKeyUnitTest, Bip84TestVectors) { "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1AD" "qtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs"); - base = m_key->DeriveChildFromPath("m/84'/0'/0'/0/0"); + base = m_key->DeriveChildFromPath(ParsePath("m/84'/0'/0'/0/0")); EXPECT_EQ(GetWifCompressedPrivateKey(base->GetPrivateKeyBytes(), false), "KyZpNDKnfs94vbrwhJneDi77V6jF64PWPF8x5cdJb8ifgg2DUc9d"); EXPECT_EQ( @@ -705,7 +630,7 @@ TEST(HDKeyUnitTest, Bip84TestVectors) { EXPECT_EQ(PubkeyToSegwitAddress(base->GetPublicKeyBytes(), false), "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu"); - base = m_key->DeriveChildFromPath("m/84'/0'/0'/0/1"); + base = m_key->DeriveChildFromPath(ParsePath("m/84'/0'/0'/0/1")); EXPECT_EQ(GetWifCompressedPrivateKey(base->GetPrivateKeyBytes(), false), "Kxpf5b8p3qX56DKEe5NqWbNUP9MnqoRFzZwHRtsFqhzuvUJsYZCy"); EXPECT_EQ( @@ -714,7 +639,7 @@ TEST(HDKeyUnitTest, Bip84TestVectors) { EXPECT_EQ(PubkeyToSegwitAddress(base->GetPublicKeyBytes(), false), "bc1qnjg0jd8228aq7egyzacy8cys3knf9xvrerkf9g"); - base = m_key->DeriveChildFromPath("m/84'/0'/0'/1/0"); + base = m_key->DeriveChildFromPath(ParsePath("m/84'/0'/0'/1/0")); EXPECT_EQ(GetWifCompressedPrivateKey(base->GetPrivateKeyBytes(), false), "KxuoxufJL5csa1Wieb2kp29VNdn92Us8CoaUG3aGtPtcF3AzeXvF"); EXPECT_EQ( @@ -732,19 +657,19 @@ TEST(HDKeyUnitTest, GetZCashTransparentAddress) { std::unique_ptr m_key = HDKey::GenerateFromSeed(*seed); { - auto base = m_key->DeriveChildFromPath("m/44'/133'/1'/0/0"); + auto base = m_key->DeriveChildFromPath(ParsePath("m/44'/133'/1'/0/0")); EXPECT_EQ(base->GetZCashTransparentAddress(false), "t1Hxm2pmTLYuKhyLeZoSPjsHPFLWePSTDka"); } { - auto base = m_key->DeriveChildFromPath("m/44'/133'/1'/1/1"); + auto base = m_key->DeriveChildFromPath(ParsePath("m/44'/133'/1'/1/1")); EXPECT_EQ(base->GetZCashTransparentAddress(false), "t1MhfG9BdcchMh1R1THE6yGUgopfEp7hSAy"); } { - auto base = m_key->DeriveChildFromPath("m/44'/133'/1'/1/2"); + auto base = m_key->DeriveChildFromPath(ParsePath("m/44'/133'/1'/1/2")); EXPECT_EQ(base->GetZCashTransparentAddress(false), "t1KD4D7F7Ur89pVox3CZi5LvAcsGV3xXFuX"); } diff --git a/components/brave_wallet/browser/json_keystore_parser.cc b/components/brave_wallet/browser/json_keystore_parser.cc new file mode 100644 index 000000000000..b3a54b5c33e3 --- /dev/null +++ b/components/brave_wallet/browser/json_keystore_parser.cc @@ -0,0 +1,200 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/brave_wallet/browser/json_keystore_parser.h" + +#include "base/containers/extend.h" +#include "brave/components/brave_wallet/common/hash_utils.h" +#include "crypto/encryptor.h" +#include "crypto/kdf.h" +#include "crypto/symmetric_key.h" + +namespace brave_wallet { + +inline constexpr size_t kPrivateKeySize = 32; +inline constexpr size_t kDerivedKeySize = 32; + +namespace { + +bool UTCPasswordVerification( + base::span derived_key, + base::span ciphertext, + base::span mac) { + std::vector mac_verification_input; + mac_verification_input.reserve(kDerivedKeySize / 2 + ciphertext.size()); + + base::Extend(mac_verification_input, derived_key.last(kDerivedKeySize / 2)); + base::Extend(mac_verification_input, ciphertext); + + return KeccakHash(mac_verification_input) == mac; +} + +std::optional> UTCDecryptPrivateKey( + base::span derived_key, + base::span ciphertext, + base::span iv) { + crypto::SymmetricKey decryption_key(derived_key.first(kDerivedKeySize / 2)); + crypto::Encryptor encryptor; + if (!encryptor.Init(&decryption_key, crypto::Encryptor::Mode::CTR, + std::vector())) { + return std::nullopt; + } + if (!encryptor.SetCounter(iv)) { + return std::nullopt; + } + + std::vector private_key; + if (!encryptor.Decrypt(ciphertext, &private_key)) { + return std::nullopt; + } + + return private_key; +} + +} // namespace + +std::optional> DecryptPrivateKeyFromJsonKeystore( + const std::string& password, + const base::Value::Dict& dict) { + if (password.empty()) { + return std::nullopt; + } + + // check version + auto version = dict.FindInt("version"); + if (!version || *version != 3) { + return std::nullopt; + } + + const auto* crypto = dict.FindDict("crypto"); + if (!crypto) { + return std::nullopt; + } + const auto* kdf = crypto->FindString("kdf"); + if (!kdf) { + return std::nullopt; + } + const auto* kdfparams = crypto->FindDict("kdfparams"); + if (!kdfparams) { + return std::nullopt; + } + auto dklen = kdfparams->FindInt("dklen"); + if (!dklen) { + return std::nullopt; + } + // TODO(apaymyshev): web3.js parser allows more bytes for `dklen`, but uses + // only first 32 + // https://github.com/web3/web3.js/blob/4.x/packages/web3-eth-accounts/src/account.ts#L857-L868 + if (*dklen != 32) { + return std::nullopt; + } + const auto* salt = kdfparams->FindString("salt"); + if (!salt) { + return std::nullopt; + } + std::vector salt_bytes; + if (!base::HexStringToBytes(*salt, &salt_bytes)) { + return std::nullopt; + } + + std::array derived_key = {}; + + if (*kdf == "pbkdf2") { + auto c = kdfparams->FindInt("c"); + if (!c) { + return std::nullopt; + } + const auto* prf = kdfparams->FindString("prf"); + if (!prf) { + return std::nullopt; + } + if (*prf != "hmac-sha256") { + return std::nullopt; + } + + crypto::kdf::Pbkdf2HmacSha256Params params = { + .iterations = base::checked_cast(*c), + }; + if (!crypto::kdf::DeriveKeyPbkdf2HmacSha256( + params, base::as_byte_span(password), + base::as_byte_span(salt_bytes), derived_key)) { + return std::nullopt; + } + } else if (*kdf == "scrypt") { + auto n = kdfparams->FindInt("n"); + if (!n) { + return std::nullopt; + } + auto r = kdfparams->FindInt("r"); + if (!r) { + return std::nullopt; + } + auto p = kdfparams->FindInt("p"); + if (!p) { + return std::nullopt; + } + crypto::kdf::ScryptParams params = { + .cost = (size_t)*n, + .block_size = (size_t)*r, + .parallelization = (size_t)*p, + .max_memory_bytes = 512 * 1024 * 1024, + }; + if (!crypto::kdf::DeriveKeyScryptNoCheck( + params, base::as_byte_span(password), + base::as_byte_span(salt_bytes), derived_key)) { + return std::nullopt; + } + } else { + return std::nullopt; + } + + const auto* mac = crypto->FindString("mac"); + if (!mac) { + return std::nullopt; + } + std::vector mac_bytes; + if (!base::HexStringToBytes(*mac, &mac_bytes)) { + return std::nullopt; + } + const auto* ciphertext = crypto->FindString("ciphertext"); + if (!ciphertext) { + return std::nullopt; + } + std::vector ciphertext_bytes; + if (!base::HexStringToBytes(*ciphertext, &ciphertext_bytes)) { + return std::nullopt; + } + + if (!UTCPasswordVerification(derived_key, ciphertext_bytes, mac_bytes)) { + return std::nullopt; + } + + const auto* cipher = crypto->FindString("cipher"); + if (!cipher) { + return std::nullopt; + } + if (*cipher != "aes-128-ctr") { + return std::nullopt; + } + + std::vector iv_bytes; + const auto* iv = crypto->FindStringByDottedPath("cipherparams.iv"); + if (!iv) { + return std::nullopt; + } + if (!base::HexStringToBytes(*iv, &iv_bytes)) { + return std::nullopt; + } + + auto private_key = + UTCDecryptPrivateKey(derived_key, ciphertext_bytes, iv_bytes); + if (!private_key || private_key->size() != kPrivateKeySize) { + return std::nullopt; + } + + return private_key; +} + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/json_keystore_parser.h b/components/brave_wallet/browser/json_keystore_parser.h new file mode 100644 index 000000000000..c794dff3eb98 --- /dev/null +++ b/components/brave_wallet/browser/json_keystore_parser.h @@ -0,0 +1,23 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_JSON_KEYSTORE_PARSER_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_JSON_KEYSTORE_PARSER_H_ + +#include +#include + +#include "base/values.h" + +namespace brave_wallet { + +// https://ethereum.org/en/developers/docs/data-structures-and-encoding/web3-secret-storage/ +// https://github.com/web3/web3.js/blob/ae994346a656688b8e9b907e1ab4731be8c5736e/packages/web3-eth-accounts/src/account.ts#L769 +std::optional> DecryptPrivateKeyFromJsonKeystore( + const std::string& password, + const base::Value::Dict& json); + +} // namespace brave_wallet +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_JSON_KEYSTORE_PARSER_H_ diff --git a/components/brave_wallet/browser/json_keystore_parser_unittest.cc b/components/brave_wallet/browser/json_keystore_parser_unittest.cc new file mode 100644 index 000000000000..335b902d9aac --- /dev/null +++ b/components/brave_wallet/browser/json_keystore_parser_unittest.cc @@ -0,0 +1,127 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/brave_wallet/browser/json_keystore_parser.h" + +#include "base/test/values_test_util.h" +#include "brave/components/brave_wallet/browser/internal/hd_key.h" +#include "brave/components/brave_wallet/common/eth_address.h" +#include "brave/components/brave_wallet/common/hex_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::test::ParseJsonDict; + +namespace brave_wallet { + +namespace { + +std::string GetHexAddr(const HDKey* key) { + const std::vector public_key = key->GetUncompressedPublicKey(); + // trim the header byte 0x04 + const std::vector pubkey_no_header(public_key.begin() + 1, + public_key.end()); + EthAddress addr = EthAddress::FromPublicKey(pubkey_no_header); + return addr.ToHex(); +} + +} // namespace + +TEST(HDKeyUnitTest, DecryptPrivateKeyFromJsonKeystore) { + const std::string json( + R"({ + "address":"b14ab53e38da1c172f877dbc6d65e4a1b0474c3c", + "crypto" : { + "cipher" : "aes-128-ctr", + "cipherparams" : { + "iv" : "cecacd85e9cb89788b5aab2f93361233" + }, + "ciphertext" : "c52682025b1e5d5c06b816791921dbf439afe7a053abb9fac19f38a57499652c", + "kdf" : "scrypt", + "kdfparams" : { + "dklen" : 32, + "n" : 262144, + "p" : 1, + "r" : 8, + "salt" : "dc9e4a98886738bd8aae134a1f89aaa5a502c3fbd10e336136d4d5fe47448ad6" + }, + "mac" : "27b98c8676dc6619d077453b38db645a4c7c17a3e686ee5adaf53c11ac1b890e" + }, + "id" : "7e59dc02-8d42-409d-b29a-a8a0f862cc81", + "version" : 3 + })"); + auto private_key = + DecryptPrivateKeyFromJsonKeystore("testtest", ParseJsonDict(json)); + ASSERT_TRUE(private_key); + + auto hd_key = HDKey::GenerateFromPrivateKey( + *base::span(*private_key).to_fixed_extent<32>()); + EXPECT_EQ(GetHexAddr(hd_key.get()), + "0xb14ab53e38da1c172f877dbc6d65e4a1b0474c3c"); + EXPECT_EQ(HexEncodeLower(*private_key), + "efca4cdd31923b50f4214af5d2ae10e7ac45a5019e9431cc195482d707485378"); + + // wrong password + EXPECT_FALSE( + DecryptPrivateKeyFromJsonKeystore("brave1234", ParseJsonDict(json))); + EXPECT_FALSE(DecryptPrivateKeyFromJsonKeystore("", ParseJsonDict(json))); + + // |N| > 2^(128 * |r| / 8) + const std::string invalid_r( + R"({ + "crypto" : { + "cipher" : "aes-128-ctr", + "cipherparams" : { + "iv" : "83dbcc02d8ccb40e466191a123791e0e" + }, + "ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c", + "kdf" : "scrypt", + "kdfparams" : { + "dklen" : 32, + "n" : 262144, + "p" : 8, + "r" : 1, + "salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19" + }, + "mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097" + }, + "id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6", + "version" : 3 + })"); + + EXPECT_FALSE( + DecryptPrivateKeyFromJsonKeystore("testtest", ParseJsonDict(invalid_r))); + + const std::string pbkdf2_json( + R"({ + "address":"b14ab53e38da1c172f877dbc6d65e4a1b0474c3c", + "crypto" : { + "cipher" : "aes-128-ctr", + "cipherparams" : { + "iv" : "cecacd85e9cb89788b5aab2f93361233" + }, + "ciphertext" : "01ee7f1a3c8d187ea244c92eea9e332ab0bb2b4c902d89bdd71f80dc384da1be", + "kdf" : "pbkdf2", + "kdfparams" : { + "c" : 262144, + "dklen" : 32, + "prf" : "hmac-sha256", + "salt" : "dc9e4a98886738bd8aae134a1f89aaa5a502c3fbd10e336136d4d5fe47448ad6" + }, + "mac" : "0c02cd0badfebd5e783e0cf41448f84086a96365fc3456716c33641a86ebc7cc" + }, + "id" : "7e59dc02-8d42-409d-b29a-a8a0f862cc81", + "version" : 3 + })"); + + auto private_key2 = + DecryptPrivateKeyFromJsonKeystore("testtest", ParseJsonDict(pbkdf2_json)); + auto hd_key2 = HDKey::GenerateFromPrivateKey( + *base::span(*private_key2).to_fixed_extent<32>()); + ASSERT_TRUE(hd_key2); + EXPECT_EQ(GetHexAddr(hd_key2.get()), + "0xb14ab53e38da1c172f877dbc6d65e4a1b0474c3c"); +} + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/keyring_service.cc b/components/brave_wallet/browser/keyring_service.cc index 40aaf5e22761..6023a7778aa0 100644 --- a/components/brave_wallet/browser/keyring_service.cc +++ b/components/brave_wallet/browser/keyring_service.cc @@ -16,6 +16,7 @@ #include "base/command_line.h" #include "base/containers/span.h" #include "base/functional/callback_helpers.h" +#include "base/json/json_reader.h" #include "base/logging.h" #include "base/notreached.h" #include "base/numerics/safe_conversions.h" @@ -34,6 +35,7 @@ #include "brave/components/brave_wallet/browser/ethereum_keyring.h" #include "brave/components/brave_wallet/browser/filecoin_keyring.h" #include "brave/components/brave_wallet/browser/hd_keyring.h" +#include "brave/components/brave_wallet/browser/json_keystore_parser.h" #include "brave/components/brave_wallet/browser/json_rpc_service.h" #include "brave/components/brave_wallet/browser/keyring_service_migrations.h" #include "brave/components/brave_wallet/browser/keyring_service_prefs.h" @@ -1107,16 +1109,24 @@ void KeyringService::ImportAccountFromJson(const std::string& account_name, std::move(callback).Run({}); return; } + + auto parsed_json = base::JSONReader::ReadAndReturnValueWithError(json); + if (!parsed_json.has_value() || !parsed_json->is_dict()) { + std::move(callback).Run({}); + return; + } + CHECK(encryptor_); - std::unique_ptr hd_key = HDKey::GenerateFromV3UTC(password, json); - if (!hd_key) { + auto private_key = + DecryptPrivateKeyFromJsonKeystore(password, parsed_json->GetDict()); + if (!private_key) { std::move(callback).Run({}); return; } auto account = ImportAccountForKeyring(mojom::CoinType::ETH, mojom::kDefaultKeyringId, - account_name, hd_key->GetPrivateKeyBytes()); + account_name, *private_key); std::move(callback).Run(std::move(account)); } diff --git a/components/brave_wallet/browser/secp256k1_hd_keyring.cc b/components/brave_wallet/browser/secp256k1_hd_keyring.cc index 2d79ad6aff59..34ad7822633c 100644 --- a/components/brave_wallet/browser/secp256k1_hd_keyring.cc +++ b/components/brave_wallet/browser/secp256k1_hd_keyring.cc @@ -11,24 +11,10 @@ namespace brave_wallet { -Secp256k1HDKeyring::Secp256k1HDKeyring(base::span seed, - const std::string& hd_path) - : root_(ConstructRootHDKey(seed, hd_path)) {} +Secp256k1HDKeyring::Secp256k1HDKeyring() = default; Secp256k1HDKeyring::~Secp256k1HDKeyring() = default; -// static -std::unique_ptr Secp256k1HDKeyring::ConstructRootHDKey( - base::span seed, - const std::string& hd_path) { - if (!seed.empty()) { - if (auto master_key = HDKey::GenerateFromSeed(seed)) { - return master_key->DeriveChildFromPath(hd_path); - } - } - return nullptr; -} - std::string Secp256k1HDKeyring::GetDiscoveryAddress(size_t index) const { if (auto key = DeriveAccount(index)) { return GetAddressInternal(*key); @@ -58,7 +44,7 @@ bool Secp256k1HDKeyring::RemoveImportedAccount(const std::string& address) { } std::optional Secp256k1HDKeyring::AddNewHDAccount() { - if (!root_) { + if (!accounts_root_) { return std::nullopt; } @@ -78,7 +64,13 @@ void Secp256k1HDKeyring::RemoveLastHDAccount() { std::string Secp256k1HDKeyring::ImportAccount( base::span private_key) { - std::unique_ptr hd_key = HDKey::GenerateFromPrivateKey(private_key); + auto private_key_fixed_size = + private_key.to_fixed_extent(); + if (!private_key_fixed_size) { + return std::string(); + } + std::unique_ptr hd_key = + HDKey::GenerateFromPrivateKey(*private_key_fixed_size); if (!hd_key) { return std::string(); } diff --git a/components/brave_wallet/browser/secp256k1_hd_keyring.h b/components/brave_wallet/browser/secp256k1_hd_keyring.h index fe7a280f461a..1e6d0106ab8b 100644 --- a/components/brave_wallet/browser/secp256k1_hd_keyring.h +++ b/components/brave_wallet/browser/secp256k1_hd_keyring.h @@ -22,14 +22,9 @@ namespace brave_wallet { // Base class for ECDSA over the Secp256k1 types of HD keyrings. class Secp256k1HDKeyring : public HDKeyring { public: - Secp256k1HDKeyring(base::span seed, - const std::string& hd_path); + Secp256k1HDKeyring(); ~Secp256k1HDKeyring() override; - static std::unique_ptr ConstructRootHDKey( - base::span seed, - const std::string& hd_path); - std::optional AddNewHDAccount() override; void RemoveLastHDAccount() override; @@ -49,7 +44,7 @@ class Secp256k1HDKeyring : public HDKeyring { HDKey* GetHDKeyFromAddress(const std::string& address); - std::unique_ptr root_; + std::unique_ptr accounts_root_; std::vector> accounts_; // TODO(apaymyshev): make separate abstraction for imported keys as they are diff --git a/components/brave_wallet/browser/zcash/zcash_keyring.cc b/components/brave_wallet/browser/zcash/zcash_keyring.cc index 81fc7508a983..b2c6a1bdcea2 100644 --- a/components/brave_wallet/browser/zcash/zcash_keyring.cc +++ b/components/brave_wallet/browser/zcash/zcash_keyring.cc @@ -16,12 +16,32 @@ namespace brave_wallet { +namespace { + +std::unique_ptr ConstructAccountsRootKey(base::span seed, + bool testnet) { + auto result = HDKey::GenerateFromSeed(seed); + if (!result) { + return nullptr; + } + + if (testnet) { + // Testnet: m/44'/1' + return result->DeriveChildFromPath({DerivationIndex::Hardened(44), // + DerivationIndex::Hardened(1)}); + } else { + // Mainnet: m/44'/133' + return result->DeriveChildFromPath({DerivationIndex::Hardened(44), // + DerivationIndex::Hardened(133)}); + } +} + +} // namespace + ZCashKeyring::ZCashKeyring(base::span seed, bool testnet) - : Secp256k1HDKeyring( - seed, - GetRootPath(testnet ? mojom::KeyringId::kZCashTestnet - : mojom::KeyringId::kZCashMainnet)), - testnet_(testnet) { + : testnet_(testnet) { + accounts_root_ = ConstructAccountsRootKey(seed, testnet); + #if BUILDFLAG(ENABLE_ORCHARD) if (!seed.empty() && IsZCashShieldedTransactionsEnabled()) { auto orchard_key = HDKeyZip32::GenerateFromSeed(seed); @@ -32,7 +52,7 @@ ZCashKeyring::ZCashKeyring(base::span seed, bool testnet) if (!orchard_key) { return; } - orchard_key_ = orchard_key->DeriveHardenedChild( + orchard_accounts_root_ = orchard_key->DeriveHardenedChild( testnet_ ? kTestnetCoinType : static_cast(mojom::CoinType::ZEC)); } @@ -77,14 +97,19 @@ std::optional> ZCashKeyring::GetPubkeyHash( } #if BUILDFLAG(ENABLE_ORCHARD) +std::unique_ptr ZCashKeyring::DeriveOrchardAccount( + uint32_t index) const { + if (!orchard_accounts_root_) { + return nullptr; + } + + return orchard_accounts_root_->DeriveHardenedChild(index); +} + std::optional ZCashKeyring::GetUnifiedAddress( const mojom::ZCashKeyId& transparent_key_id, const mojom::ZCashKeyId& orchard_key_id) { - if (!orchard_key_) { - return std::nullopt; - } - - auto esk = orchard_key_->DeriveHardenedChild(orchard_key_id.account); + auto esk = DeriveOrchardAccount(orchard_key_id.account); if (!esk) { return std::nullopt; } @@ -115,11 +140,7 @@ std::optional ZCashKeyring::GetUnifiedAddress( mojom::ZCashAddressPtr ZCashKeyring::GetShieldedAddress( const mojom::ZCashKeyId& key_id) { - if (!orchard_key_) { - return nullptr; - } - - auto esk = orchard_key_->DeriveHardenedChild(key_id.account); + auto esk = DeriveOrchardAccount(key_id.account); if (!esk) { return nullptr; } @@ -142,11 +163,7 @@ mojom::ZCashAddressPtr ZCashKeyring::GetShieldedAddress( std::optional ZCashKeyring::GetOrchardRawBytes( const mojom::ZCashKeyId& key_id) { - if (!orchard_key_) { - return std::nullopt; - } - - auto esk = orchard_key_->DeriveHardenedChild(key_id.account); + auto esk = DeriveOrchardAccount(key_id.account); if (!esk) { return std::nullopt; } @@ -161,11 +178,7 @@ std::optional ZCashKeyring::GetOrchardRawBytes( std::optional ZCashKeyring::GetOrchardFullViewKey( const uint32_t& account_id) { - if (!orchard_key_) { - return std::nullopt; - } - - auto esk = orchard_key_->DeriveHardenedChild(account_id); + auto esk = DeriveOrchardAccount(account_id); if (!esk) { return std::nullopt; } @@ -178,7 +191,7 @@ std::optional ZCashKeyring::GetOrchardFullViewKey( std::unique_ptr ZCashKeyring::DeriveAccount(uint32_t index) const { // Mainnet - m/44'/133'/{index}' // Testnet - m/44'/1'/{index}' - return root_->DeriveHardenedChild(index); + return accounts_root_->DeriveChild(DerivationIndex::Hardened(index)); } std::unique_ptr ZCashKeyring::DeriveKey( @@ -190,14 +203,11 @@ std::unique_ptr ZCashKeyring::DeriveKey( DCHECK(key_id.change == 0 || key_id.change == 1); - auto key = account_key->DeriveNormalChild(key_id.change); - if (!key) { - return nullptr; - } - // Mainnet - m/44'/133'/{address.account}'/{address.change}/{address.index} // Testnet - m/44'/1'/{address.account}'/{address.change}/{address.index} - return key->DeriveNormalChild(key_id.index); + return account_key->DeriveChildFromPath( + std::array{DerivationIndex::Normal(key_id.change), + DerivationIndex::Normal(key_id.index)}); } std::optional> ZCashKeyring::SignMessage( diff --git a/components/brave_wallet/browser/zcash/zcash_keyring.h b/components/brave_wallet/browser/zcash/zcash_keyring.h index d38dce0b035e..7ce69d1ab3b4 100644 --- a/components/brave_wallet/browser/zcash/zcash_keyring.h +++ b/components/brave_wallet/browser/zcash/zcash_keyring.h @@ -37,6 +37,7 @@ class ZCashKeyring : public Secp256k1HDKeyring { // TODO(cypt4): move Orchard to the separate keyring #if BUILDFLAG(ENABLE_ORCHARD) + std::unique_ptr DeriveOrchardAccount(uint32_t index) const; std::optional GetUnifiedAddress( const mojom::ZCashKeyId& transparent_key_id, const mojom::ZCashKeyId& orchard_key_id); @@ -52,13 +53,14 @@ class ZCashKeyring : public Secp256k1HDKeyring { base::span message); std::string EncodePrivateKeyForExport(const std::string& address) override; + private: std::string GetAddressInternal(const HDKey& hd_key) const override; std::unique_ptr DeriveAccount(uint32_t index) const override; std::unique_ptr DeriveKey(const mojom::ZCashKeyId& key_id); #if BUILDFLAG(ENABLE_ORCHARD) - std::unique_ptr orchard_key_; + std::unique_ptr orchard_accounts_root_; #endif bool testnet_ = false;