diff --git a/components/brave_wallet/browser/BUILD.gn b/components/brave_wallet/browser/BUILD.gn index c322e93d8661..d35b9f5379dd 100644 --- a/components/brave_wallet/browser/BUILD.gn +++ b/components/brave_wallet/browser/BUILD.gn @@ -435,8 +435,6 @@ source_set("hd_keyring") { "//brave/components/filecoin/rs:rust_lib", "//crypto", ] - - public_deps = [ "//brave/components/brave_wallet/rust:rust_lib" ] } source_set("transaction") { diff --git a/components/brave_wallet/browser/internal/BUILD.gn b/components/brave_wallet/browser/internal/BUILD.gn index c957e9fbd3b8..f242783cad6a 100644 --- a/components/brave_wallet/browser/internal/BUILD.gn +++ b/components/brave_wallet/browser/internal/BUILD.gn @@ -11,6 +11,8 @@ source_set("hd_key") { "hd_key.h", "hd_key_ed25519.cc", "hd_key_ed25519.h", + "hd_key_utils.cc", + "hd_key_utils.h", ] visibility = [ @@ -28,7 +30,6 @@ source_set("hd_key") { "//third_party/boringssl", ] - public_deps = [ "//brave/components/brave_wallet/rust:rust_lib" ] if (enable_orchard) { sources += [ "hd_key_zip32.cc", diff --git a/components/brave_wallet/browser/internal/hd_key.cc b/components/brave_wallet/browser/internal/hd_key.cc index 9ff922674ba4..aea339ad3142 100644 --- a/components/brave_wallet/browser/internal/hd_key.cc +++ b/components/brave_wallet/browser/internal/hd_key.cc @@ -24,6 +24,7 @@ #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_utils.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" @@ -35,7 +36,6 @@ #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" @@ -47,10 +47,7 @@ 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; @@ -136,19 +133,10 @@ std::unique_ptr HDKey::GenerateFromSeed(base::span seed) { return nullptr; } - SecureVector hmac(kSHA512Length); - unsigned int out_len; - if (!HMAC(EVP_sha512(), kMasterSecret, sizeof(kMasterSecret), seed.data(), - seed.size(), hmac.data(), &out_len)) { - LOG(ERROR) << __func__ << ": HMAC_SHA512 failed"; - return nullptr; - } - DCHECK(out_len == kSHA512Length); + auto hmac = HmacSha512(base::byte_span_from_cstring(kMasterSecret), seed); + auto [IL, IR] = base::span(hmac).split_at(kSHA512HashLength / 2); 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; @@ -549,18 +537,8 @@ std::unique_ptr HDKey::DeriveChild(uint32_t index) { data.push_back((index >> 8) & 0xFF); data.push_back(index & 0xFF); - SecureVector hmac(kSHA512Length); - unsigned int out_len; - if (!HMAC(EVP_sha512(), chain_code_.data(), chain_code_.size(), data.data(), - data.size(), hmac.data(), &out_len)) { - LOG(ERROR) << __func__ << ": HMAC_SHA512 failed"; - return nullptr; - } - 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 hmac = HmacSha512(chain_code_, data); + auto [IL, IR] = base::span(hmac).split_at(kSHA512HashLength / 2); std::unique_ptr hdkey = std::make_unique(); hdkey->SetChainCode(IR); @@ -625,44 +603,18 @@ std::unique_ptr HDKey::DeriveChildFromPath(const std::string& path) { return nullptr; } - 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()) { + const auto hd_path = ParseFullHDPath(path); + if (!hd_path) { 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; - } + std::unique_ptr hd_key = std::make_unique(); 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 node : *hd_path) { + hd_key = hd_key->DeriveChild(node); if (!hd_key) { return nullptr; } diff --git a/components/brave_wallet/browser/internal/hd_key_ed25519.cc b/components/brave_wallet/browser/internal/hd_key_ed25519.cc index a10752f68f9f..475e161801c6 100644 --- a/components/brave_wallet/browser/internal/hd_key_ed25519.cc +++ b/components/brave_wallet/browser/internal/hd_key_ed25519.cc @@ -8,52 +8,63 @@ #include #include "base/check.h" -#include "base/logging.h" -#include "base/notreached.h" -#include "base/strings/strcat.h" +#include "base/containers/span.h" +#include "base/containers/span_writer.h" +#include "base/memory/ptr_util.h" +#include "base/numerics/byte_conversions.h" #include "base/strings/string_number_conversions.h" +#include "brave/components/brave_wallet/browser/internal/hd_key_utils.h" +#include "brave/components/brave_wallet/common/hash_utils.h" #include "brave/third_party/bitcoin-core/src/src/base58.h" +#include "third_party/boringssl/src/include/openssl/curve25519.h" namespace brave_wallet { namespace { -constexpr char kMasterNode[] = "m"; -} -HDKeyEd25519::HDKeyEd25519( - std::string path, - rust::Box private_key) - : path_(std::move(path)), private_key_(std::move(private_key)) { - CHECK(private_key_->is_ok()); +constexpr char kMasterSecret[] = "ed25519 seed"; + +// Validate keypair by signing and verifying signature to ensure included +// private key matches public key. +bool ValidateKeypair(base::span key_pair) { + std::array signature = {}; + auto msg = base::byte_span_from_cstring("brave"); + + CHECK( + ED25519_sign(signature.data(), msg.data(), msg.size(), key_pair.data())); + + return !!ED25519_verify(msg.data(), msg.size(), signature.data(), + key_pair.last().data()); } + +} // namespace + +HDKeyEd25519::HDKeyEd25519() = default; HDKeyEd25519::~HDKeyEd25519() = default; -// static -std::unique_ptr HDKeyEd25519::GenerateFromSeed( - base::span seed) { - auto master_private_key = generate_ed25519_extended_secret_key_from_seed( - rust::Slice{seed.data(), seed.size()}); - if (!master_private_key->is_ok()) { - VLOG(0) << std::string(master_private_key->error_message()); - return nullptr; - } - return std::make_unique(kMasterNode, - std::move(master_private_key)); +// Child key derivation constructor. +// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#private-parent-key--private-child-key +HDKeyEd25519::HDKeyEd25519(base::span key, + base::span data) { + auto hmac = HmacSha512(key, data); + auto [il, ir] = base::span(hmac).split_at<32>(); + + // `public_key` is not used, we use key pair instead. + std::array public_key; + ED25519_keypair_from_seed(public_key.data(), key_pair_.data(), il.data()); + + base::span(chain_code_).copy_from(ir); } // static -std::unique_ptr HDKeyEd25519::GenerateFromPrivateKey( - base::span private_key) { - auto master_private_key = generate_ed25519_extended_secret_key_from_bytes( - rust::Slice{private_key.data(), private_key.size()}); - if (!master_private_key->is_ok()) { - VLOG(0) << std::string(master_private_key->error_message()); +std::unique_ptr HDKeyEd25519::GenerateFromKeyPair( + base::span key_pair) { + if (!ValidateKeypair(key_pair)) { return nullptr; } - return std::make_unique("", std::move(master_private_key)); -} -std::string HDKeyEd25519::GetPath() const { - return path_; + auto result = std::make_unique(); + base::span(result->key_pair_).copy_from(key_pair); + return result; } // index should be 0 to 2^31-1 @@ -62,74 +73,74 @@ std::string HDKeyEd25519::GetPath() const { // https://github.com/satoshilabs/slips/blob/master/slip-0010.md#private-parent-key--private-child-key std::unique_ptr HDKeyEd25519::DeriveHardenedChild( uint32_t index) { - auto child_private_key = private_key_->unwrap().derive_hardened_child(index); - if (!child_private_key->is_ok()) { - VLOG(0) << std::string(child_private_key->error_message()); + if (index >= kHardenedOffset) { return nullptr; } - auto child_path = - base::StrCat({path_, "/", base::NumberToString(index), "'"}); - return std::make_unique(std::move(child_path), - std::move(child_private_key)); + return DeriveChild(kHardenedOffset + index); +} + +std::unique_ptr HDKeyEd25519::DeriveChild(uint32_t index) { + std::vector hmac_payload(37); + + auto span_writer = base::SpanWriter(base::span(hmac_payload)); + // https://github.com/satoshilabs/slips/blob/master/slip-0010.md#private-parent-key--private-child-key + span_writer.Write(base::U8ToBigEndian(0)); + span_writer.Write(GetPrivateKeyAsSpan()); + span_writer.Write(base::U32ToBigEndian(index)); + + return base::WrapUnique(new HDKeyEd25519(chain_code_, hmac_payload)); } -std::unique_ptr HDKeyEd25519::DeriveChildFromPath( - const std::string& path) { - if (path_ != kMasterNode) { - VLOG(0) << "must derive only from master key"; +// static +std::unique_ptr HDKeyEd25519::GenerateFromSeedAndPath( + base::span seed, + std::string_view hd_path) { + auto hd_key = base::WrapUnique( + new HDKeyEd25519(base::byte_span_from_cstring(kMasterSecret), seed)); + + auto nodes = ParseFullHDPath(hd_path); + if (!nodes) { return nullptr; } - auto child_private_key = private_key_->unwrap().derive(path); - if (!child_private_key->is_ok()) { - VLOG(0) << std::string(child_private_key->error_message()); - return nullptr; + for (auto index : *nodes) { + if (index < kHardenedOffset) { + return nullptr; + } + hd_key = hd_key->DeriveChild(index); + if (!hd_key) { + return nullptr; + } } - return std::make_unique(path, std::move(child_private_key)); + return hd_key; } -std::vector HDKeyEd25519::Sign(base::span msg) { - auto signature_result = private_key_->unwrap().sign( - rust::Slice{msg.data(), msg.size()}); - if (!signature_result->is_ok()) { - VLOG(0) << std::string(signature_result->error_message()); - return std::vector(); - } - auto signature_bytes = signature_result->unwrap().to_bytes(); - return std::vector(signature_bytes.begin(), signature_bytes.end()); -} +std::array HDKeyEd25519::Sign( + base::span msg) { + std::array signature = {}; -bool HDKeyEd25519::VerifyForTesting(base::span msg, - base::span sig) { - auto verification_result = private_key_->unwrap().verify( - rust::Slice{msg.data(), msg.size()}, - rust::Slice{sig.data(), sig.size()}); - if (!verification_result->is_ok()) { - VLOG(0) << std::string(verification_result->error_message()); - return false; - } - return true; + CHECK( + ED25519_sign(signature.data(), msg.data(), msg.size(), key_pair_.data())); + return signature; } -std::vector HDKeyEd25519::GetPrivateKeyBytes() const { - auto secret_key = private_key_->unwrap().secret_key_raw(); - return {secret_key.begin(), secret_key.end()}; +base::span +HDKeyEd25519::GetPrivateKeyAsSpan() const { + return base::span(key_pair_).first(); } -std::vector HDKeyEd25519::GetPublicKeyBytes() const { - auto public_key = private_key_->unwrap().public_key_raw(); - return {public_key.begin(), public_key.end()}; +base::span +HDKeyEd25519::GetPublicKeyAsSpan() const { + return base::span(key_pair_).last(); } std::string HDKeyEd25519::GetBase58EncodedPublicKey() const { - auto public_key = private_key_->unwrap().public_key_raw(); - return EncodeBase58(public_key); + return EncodeBase58(GetPublicKeyAsSpan()); } std::string HDKeyEd25519::GetBase58EncodedKeypair() const { - auto keypair = private_key_->unwrap().keypair_raw(); - return EncodeBase58(keypair); + return EncodeBase58(key_pair_); } } // namespace brave_wallet diff --git a/components/brave_wallet/browser/internal/hd_key_ed25519.h b/components/brave_wallet/browser/internal/hd_key_ed25519.h index 9a02be8a0b49..c7f48dbed4c0 100644 --- a/components/brave_wallet/browser/internal/hd_key_ed25519.h +++ b/components/brave_wallet/browser/internal/hd_key_ed25519.h @@ -8,47 +8,55 @@ #include #include -#include #include "base/containers/span.h" -#include "brave/components/brave_wallet/rust/lib.rs.h" namespace brave_wallet { -// This class implement basic EdDSA over ed25519 functionality of bip32-ed25519 +constexpr size_t kEd25519SecretKeySize = 32; +constexpr size_t kEd25519PublicKeySize = 32; +constexpr size_t kEd25519KeypairSize = + kEd25519SecretKeySize + kEd25519PublicKeySize; +constexpr size_t kEd25519ChainCodeSize = 32; +constexpr size_t kEd25519SignatureSize = 64; + +// This class implements basic EdDSA over ed25519 functionality of SLIP-0010 // spec with 32 bytes private key and only allows private key derivation with // hardened index. +// https://github.com/satoshilabs/slips/blob/master/slip-0010.md class HDKeyEd25519 { public: - HDKeyEd25519(std::string path, - rust::Box); + HDKeyEd25519(); ~HDKeyEd25519(); HDKeyEd25519(const HDKeyEd25519&) = delete; HDKeyEd25519& operator=(const HDKeyEd25519&) = delete; - static std::unique_ptr GenerateFromSeed( - base::span seed); - static std::unique_ptr GenerateFromPrivateKey( - base::span private_key); + static std::unique_ptr GenerateFromSeedAndPath( + base::span seed, + std::string_view hd_path); + static std::unique_ptr GenerateFromKeyPair( + base::span key_pair); - std::string GetPath() const; std::unique_ptr DeriveHardenedChild(uint32_t index); - // If path contains normal index, nullptr will be returned - std::unique_ptr DeriveChildFromPath(const std::string& path); - std::vector Sign(base::span msg); - bool VerifyForTesting(base::span msg, - base::span sig); + std::array Sign( + base::span msg); - std::vector GetPrivateKeyBytes() const; - std::vector GetPublicKeyBytes() const; + base::span GetPrivateKeyAsSpan() const + LIFETIME_BOUND; + base::span GetPublicKeyAsSpan() const + LIFETIME_BOUND; std::string GetBase58EncodedPublicKey() const; std::string GetBase58EncodedKeypair() const; private: - std::string path_; - rust::Box private_key_; + HDKeyEd25519(base::span key, base::span data); + + std::unique_ptr DeriveChild(uint32_t index); + + std::array key_pair_ = {}; + std::array chain_code_ = {}; }; } // namespace brave_wallet diff --git a/components/brave_wallet/browser/internal/hd_key_ed25519_unittest.cc b/components/brave_wallet/browser/internal/hd_key_ed25519_unittest.cc index da32fc5a53af..ff2035200887 100644 --- a/components/brave_wallet/browser/internal/hd_key_ed25519_unittest.cc +++ b/components/brave_wallet/browser/internal/hd_key_ed25519_unittest.cc @@ -4,12 +4,26 @@ * You can obtain one at https://mozilla.org/MPL/2.0/. */ #include "brave/components/brave_wallet/browser/internal/hd_key_ed25519.h" + #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" +#include "brave/components/brave_wallet/common/hex_utils.h" #include "testing/gtest/include/gtest/gtest.h" +#include "third_party/boringssl/src/include/openssl/curve25519.h" namespace brave_wallet { +namespace { + +bool VerifySignature(const HDKeyEd25519& key, + base::span msg, + base::span sig) { + return !!ED25519_verify(msg.data(), msg.size(), sig.data(), + key.GetPublicKeyAsSpan().data()); +} + +} // namespace + // https://github.com/satoshilabs/slips/blob/master/slip-0010.md#test-vector-1-for-ed25519 TEST(HDKeyEd25519UnitTest, TestVector1) { std::vector bytes; @@ -17,15 +31,11 @@ TEST(HDKeyEd25519UnitTest, TestVector1) { base::HexStringToBytes("000102030405060708090a0b0c0d0e0f", &bytes)); // m - auto master_key_base = HDKeyEd25519::GenerateFromSeed(bytes); - EXPECT_EQ(master_key_base->GetPath(), "m"); - HDKeyEd25519* master_key = static_cast(master_key_base.get()); - EXPECT_EQ( - base::ToLowerASCII(base::HexEncode(master_key->GetPrivateKeyBytes())), - "2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19e7"); - EXPECT_EQ( - base::ToLowerASCII(base::HexEncode(master_key->GetPublicKeyBytes())), - "a4b2856bfec510abab89753fac1ac0e1112364e7d250545963f135f2a33188ed"); + auto master_key = HDKeyEd25519::GenerateFromSeedAndPath(bytes, "m"); + EXPECT_EQ(HexEncodeLower(master_key->GetPrivateKeyAsSpan()), + "2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19e7"); + EXPECT_EQ(HexEncodeLower(master_key->GetPublicKeyAsSpan()), + "a4b2856bfec510abab89753fac1ac0e1112364e7d250545963f135f2a33188ed"); EXPECT_EQ(master_key->GetBase58EncodedPublicKey(), "C5ukMV73nk32h52MjxtnZXTrrr7rupD9CTDDRnYYDRYQ"); EXPECT_EQ(master_key->GetBase58EncodedKeypair(), @@ -33,13 +43,11 @@ TEST(HDKeyEd25519UnitTest, TestVector1) { "YbQtaJQKLXET9jVjepWXe"); // m/0'/1'/2'/2'/1000000000' - auto child_base = - master_key->DeriveChildFromPath("m/0'/1'/2'/2'/1000000000'"); - EXPECT_EQ(child_base->GetPath(), "m/0'/1'/2'/2'/1000000000'"); - HDKeyEd25519* child = static_cast(child_base.get()); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPrivateKeyBytes())), + auto child = + HDKeyEd25519::GenerateFromSeedAndPath(bytes, "m/0'/1'/2'/2'/1000000000'"); + EXPECT_EQ(HexEncodeLower(child->GetPrivateKeyAsSpan()), "8f94d394a8e8fd6b1bc2f3f49f5c47e385281d5c17e65324b0f62483e37e8793"); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPublicKeyBytes())), + EXPECT_EQ(HexEncodeLower(child->GetPublicKeyAsSpan()), "3c24da049451555d51a7014a37337aa4e12d41e485abccfa46b47dfb2af54b7a"); EXPECT_EQ(child->GetBase58EncodedPublicKey(), "53n47S4RT9ozx5KrpH6uYfdnAjrTBJri8qZJBvRfw1Bf"); @@ -47,12 +55,10 @@ TEST(HDKeyEd25519UnitTest, TestVector1) { "3sVsV9myuRDg4rio4n3ftoP3NsUDzjVk6i8WiTg9veDsiALQjt9QEfUckJkutYUgzm" "wwz55D49JUDFic5Fu2gDjX"); // m/0' - child_base = master_key->DeriveHardenedChild(0); - EXPECT_EQ(child_base->GetPath(), "m/0'"); - child = static_cast(child_base.get()); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPrivateKeyBytes())), + child = master_key->DeriveHardenedChild(0); + EXPECT_EQ(HexEncodeLower(child->GetPrivateKeyAsSpan()), "68e0fe46dfb67e368c75379acec591dad19df3cde26e63b93a8e704f1dade7a3"); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPublicKeyBytes())), + EXPECT_EQ(HexEncodeLower(child->GetPublicKeyAsSpan()), "8c8a13df77a28f3445213a0f432fde644acaa215fc72dcdf300d5efaa85d350c"); EXPECT_EQ(child->GetBase58EncodedPublicKey(), "ATcCGRoY87cSJESCXbHXEX6CDWQxepAViUvVnNsELhRu"); @@ -60,12 +66,10 @@ TEST(HDKeyEd25519UnitTest, TestVector1) { "36crUN2YvuPXEpRXNmdtv5W1veeXHZvMqSe4Egqu4Ski9FHtbdizagf9Kfj8e7sD4S" "e5YCqQQ2vpUuKGycM8WhF9"); // m/0'/1' - child_base = child->DeriveHardenedChild(1); - EXPECT_EQ(child_base->GetPath(), "m/0'/1'"); - child = static_cast(child_base.get()); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPrivateKeyBytes())), + child = child->DeriveHardenedChild(1); + EXPECT_EQ(HexEncodeLower(child->GetPrivateKeyAsSpan()), "b1d0bad404bf35da785a64ca1ac54b2617211d2777696fbffaf208f746ae84f2"); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPublicKeyBytes())), + EXPECT_EQ(HexEncodeLower(child->GetPublicKeyAsSpan()), "1932a5270f335bed617d5b935c80aedb1a35bd9fc1e31acafd5372c30f5c1187"); EXPECT_EQ(child->GetBase58EncodedPublicKey(), "2hMz2f8WbLw5m2icKR2WVrcizvnguw8xaAnXjaeohuHQ"); @@ -73,12 +77,10 @@ TEST(HDKeyEd25519UnitTest, TestVector1) { "4ZCMMnibQjY732c95g1bK5aWzZpR3H1HAqGMeh1B4xpcUWkpxJyUVfwqVBjft1bpRA" "WjiJTaUUPWFJEqKWn6cVZp"); // m/0'/1'/2' - child_base = child->DeriveHardenedChild(2); - EXPECT_EQ(child_base->GetPath(), "m/0'/1'/2'"); - child = static_cast(child_base.get()); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPrivateKeyBytes())), + child = child->DeriveHardenedChild(2); + EXPECT_EQ(HexEncodeLower(child->GetPrivateKeyAsSpan()), "92a5b23c0b8a99e37d07df3fb9966917f5d06e02ddbd909c7e184371463e9fc9"); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPublicKeyBytes())), + EXPECT_EQ(HexEncodeLower(child->GetPublicKeyAsSpan()), "ae98736566d30ed0e9d2f4486a64bc95740d89c7db33f52121f8ea8f76ff0fc1"); EXPECT_EQ(child->GetBase58EncodedPublicKey(), "CkYmXLvWehLXBzUAJ3g3wsfc5QjoCtWtSydquF7HDxXS"); @@ -86,12 +88,10 @@ TEST(HDKeyEd25519UnitTest, TestVector1) { "3w45HeUP7x8DhVFxmUwsww19XUdxNZeTuMQQBFJCXAaGtYLvjUVvWovNX7aKpjp5pa" "YERPr1jgWEvGeemRm2bCBJ"); // m/0'/1'/2'/2' - child_base = child->DeriveHardenedChild(2); - EXPECT_EQ(child_base->GetPath(), "m/0'/1'/2'/2'"); - child = static_cast(child_base.get()); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPrivateKeyBytes())), + child = child->DeriveHardenedChild(2); + EXPECT_EQ(HexEncodeLower(child->GetPrivateKeyAsSpan()), "30d1dc7e5fc04c31219ab25a27ae00b50f6fd66622f6e9c913253d6511d1e662"); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPublicKeyBytes())), + EXPECT_EQ(HexEncodeLower(child->GetPublicKeyAsSpan()), "8abae2d66361c879b900d204ad2cc4984fa2aa344dd7ddc46007329ac76c429c"); EXPECT_EQ(child->GetBase58EncodedPublicKey(), "ALYYdMp2jVV4HGsZZPfLy1BQLMHL2CQG5XHpzr2XiHCw"); @@ -99,12 +99,10 @@ TEST(HDKeyEd25519UnitTest, TestVector1) { "ycUieXQauHN9msp7beGkDcUPwF4g3YhzqUXwVihv8PJbF96Eyeh1PFTxhzP4AaXt5U" "QCR3mVsrs8AiPCKMCLs2s"); // m/0'/1'/2'/2'/1000000000' - child_base = child->DeriveHardenedChild(1000000000); - EXPECT_EQ(child_base->GetPath(), "m/0'/1'/2'/2'/1000000000'"); - child = static_cast(child_base.get()); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPrivateKeyBytes())), + child = child->DeriveHardenedChild(1000000000); + EXPECT_EQ(HexEncodeLower(child->GetPrivateKeyAsSpan()), "8f94d394a8e8fd6b1bc2f3f49f5c47e385281d5c17e65324b0f62483e37e8793"); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPublicKeyBytes())), + EXPECT_EQ(HexEncodeLower(child->GetPublicKeyAsSpan()), "3c24da049451555d51a7014a37337aa4e12d41e485abccfa46b47dfb2af54b7a"); EXPECT_EQ(child->GetBase58EncodedPublicKey(), "53n47S4RT9ozx5KrpH6uYfdnAjrTBJri8qZJBvRfw1Bf"); @@ -122,28 +120,23 @@ TEST(HDKeyEd25519UnitTest, TestVector2) { &bytes)); // m - auto master_key_base = HDKeyEd25519::GenerateFromSeed(bytes); - EXPECT_EQ(master_key_base->GetPath(), "m"); + auto master_key_base = HDKeyEd25519::GenerateFromSeedAndPath(bytes, "m"); HDKeyEd25519* master_key = static_cast(master_key_base.get()); - EXPECT_EQ( - base::ToLowerASCII(base::HexEncode(master_key->GetPrivateKeyBytes())), - "171cb88b1b3c1db25add599712e36245d75bc65a1a5c9e18d76f9f2b1eab4012"); - EXPECT_EQ( - base::ToLowerASCII(base::HexEncode(master_key->GetPublicKeyBytes())), - "8fe9693f8fa62a4305a140b9764c5ee01e455963744fe18204b4fb948249308a"); + EXPECT_EQ(HexEncodeLower(master_key->GetPrivateKeyAsSpan()), + "171cb88b1b3c1db25add599712e36245d75bc65a1a5c9e18d76f9f2b1eab4012"); + EXPECT_EQ(HexEncodeLower(master_key->GetPublicKeyAsSpan()), + "8fe9693f8fa62a4305a140b9764c5ee01e455963744fe18204b4fb948249308a"); EXPECT_EQ(master_key->GetBase58EncodedPublicKey(), "AgmjPHe5Qs4VakvXHGnd6NsYjaxt4suMUtf39TayrSfb"); EXPECT_EQ(master_key->GetBase58EncodedKeypair(), "ToTfZTGTYncQcR7P7PduNLKDd8sNHMKsB7td24qCZzwzzZ65fA8y7Ht3o7nwojMzoV" "rD9M6Y7vPKznLJPjpwgLZ"); // m/0'/2147483647'/1'/2147483646'/2' - auto child_base = - master_key->DeriveChildFromPath("m/0'/2147483647'/1'/2147483646'/2'"); - EXPECT_EQ(child_base->GetPath(), "m/0'/2147483647'/1'/2147483646'/2'"); - HDKeyEd25519* child = static_cast(child_base.get()); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPrivateKeyBytes())), + auto child = master_key->GenerateFromSeedAndPath( + bytes, "m/0'/2147483647'/1'/2147483646'/2'"); + EXPECT_EQ(HexEncodeLower(child->GetPrivateKeyAsSpan()), "551d333177df541ad876a60ea71f00447931c0a9da16f227c11ea080d7391b8d"); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPublicKeyBytes())), + EXPECT_EQ(HexEncodeLower(child->GetPublicKeyAsSpan()), "47150c75db263559a70d5778bf36abbab30fb061ad69f69ece61a72b0cfa4fc0"); EXPECT_EQ(child->GetBase58EncodedPublicKey(), "5nUZbtNefYa7tWHdpQApxsjPLtTZpKuZYnKDsd2dXADu"); @@ -151,12 +144,10 @@ TEST(HDKeyEd25519UnitTest, TestVector2) { "2hhXd52y2dVVJGUkr6kikm3LcMQcPSwhWaB1GDU7nAMRWXbjAuG1G9mjdSETpAEAJ1" "vV9nQrvhARxQDc6iEEbpU7"); // m/0' - child_base = master_key->DeriveHardenedChild(0); - EXPECT_EQ(child_base->GetPath(), "m/0'"); - child = static_cast(child_base.get()); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPrivateKeyBytes())), + child = master_key->DeriveHardenedChild(0); + EXPECT_EQ(HexEncodeLower(child->GetPrivateKeyAsSpan()), "1559eb2bbec5790b0c65d8693e4d0875b1747f4970ae8b650486ed7470845635"); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPublicKeyBytes())), + EXPECT_EQ(HexEncodeLower(child->GetPublicKeyAsSpan()), "86fab68dcb57aa196c77c5f264f215a112c22a912c10d123b0d03c3c28ef1037"); EXPECT_EQ(child->GetBase58EncodedPublicKey(), "A5uN5c31sqKK4x82gXeHzsBFpBTTusPDHBZT111V3u4i"); @@ -164,12 +155,10 @@ TEST(HDKeyEd25519UnitTest, TestVector2) { "Rm2NBwPiLaJoWaetGVz9Jy1T477CS2FfM4Q5JmWgCLRhX54T8zHX57RH6LgR2kRXTc" "DwPVMAQi4nxFVH2DJiXkA"); // m/0'/2147483647' - child_base = child->DeriveHardenedChild(2147483647); - EXPECT_EQ(child_base->GetPath(), "m/0'/2147483647'"); - child = static_cast(child_base.get()); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPrivateKeyBytes())), + child = child->DeriveHardenedChild(2147483647); + EXPECT_EQ(HexEncodeLower(child->GetPrivateKeyAsSpan()), "ea4f5bfe8694d8bb74b7b59404632fd5968b774ed545e810de9c32a4fb4192f4"); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPublicKeyBytes())), + EXPECT_EQ(HexEncodeLower(child->GetPublicKeyAsSpan()), "5ba3b9ac6e90e83effcd25ac4e58a1365a9e35a3d3ae5eb07b9e4d90bcf7506d"); EXPECT_EQ(child->GetBase58EncodedPublicKey(), "7AiuCW2Mg2vRAHsrVmsM3uFky4XRaXHqqcemSp6Bract"); @@ -177,12 +166,10 @@ TEST(HDKeyEd25519UnitTest, TestVector2) { "5gi27AKyRrB5rvX9yPT39WpRak9B5QAXSZLvFDoqb7nQGhKLTqhTLeUgax4FVGGurZ" "PQNjRX6N9sn4o7f5rSAeWG"); // m/0'/2147483647'/1' - child_base = child->DeriveHardenedChild(1); - EXPECT_EQ(child_base->GetPath(), "m/0'/2147483647'/1'"); - child = static_cast(child_base.get()); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPrivateKeyBytes())), + child = child->DeriveHardenedChild(1); + EXPECT_EQ(HexEncodeLower(child->GetPrivateKeyAsSpan()), "3757c7577170179c7868353ada796c839135b3d30554bbb74a4b1e4a5a58505c"); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPublicKeyBytes())), + EXPECT_EQ(HexEncodeLower(child->GetPublicKeyAsSpan()), "2e66aa57069c86cc18249aecf5cb5a9cebbfd6fadeab056254763874a9352b45"); EXPECT_EQ(child->GetBase58EncodedPublicKey(), "488Z1Z7moahUL7Np2JMrApWbWwdUEBzSfEioz9vj7vCc"); @@ -190,12 +177,10 @@ TEST(HDKeyEd25519UnitTest, TestVector2) { "27BCpwH2qcy7ANSVAisHjBN3CQyfzKyV4qcSet2YP1X5aCsoKS9kwcxqvJdVNcBWN3" "xuKFviozGBrUsbhXumYa9z"); // m/0'/2147483647'/1'/2147483646' - child_base = child->DeriveHardenedChild(2147483646); - EXPECT_EQ(child_base->GetPath(), "m/0'/2147483647'/1'/2147483646'"); - child = static_cast(child_base.get()); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPrivateKeyBytes())), + child = child->DeriveHardenedChild(2147483646); + EXPECT_EQ(HexEncodeLower(child->GetPrivateKeyAsSpan()), "5837736c89570de861ebc173b1086da4f505d4adb387c6a1b1342d5e4ac9ec72"); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPublicKeyBytes())), + EXPECT_EQ(HexEncodeLower(child->GetPublicKeyAsSpan()), "e33c0f7d81d843c572275f287498e8d408654fdf0d1e065b84e2e6f157aab09b"); EXPECT_EQ(child->GetBase58EncodedPublicKey(), "GJ2famWaTaWgT5oYvi1dqA7cvtoKMzyje1Pcx1bL9Nsc"); @@ -203,12 +188,10 @@ TEST(HDKeyEd25519UnitTest, TestVector2) { "2mJCNeA9JefF3B2gikqrR22BWa2ETCZNwijZvDn7XktHRVYj7sXhTt93sr7SqkBUp8" "h2pLb6V3nzpYN4mB9paeDQ"); // m/0'/2147483647'/1'/2147483646'/2' - child_base = child->DeriveHardenedChild(2); - EXPECT_EQ(child_base->GetPath(), "m/0'/2147483647'/1'/2147483646'/2'"); - child = static_cast(child_base.get()); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPrivateKeyBytes())), + child = child->DeriveHardenedChild(2); + EXPECT_EQ(HexEncodeLower(child->GetPrivateKeyAsSpan()), "551d333177df541ad876a60ea71f00447931c0a9da16f227c11ea080d7391b8d"); - EXPECT_EQ(base::ToLowerASCII(base::HexEncode(child->GetPublicKeyBytes())), + EXPECT_EQ(HexEncodeLower(child->GetPublicKeyAsSpan()), "47150c75db263559a70d5778bf36abbab30fb061ad69f69ece61a72b0cfa4fc0"); EXPECT_EQ(child->GetBase58EncodedPublicKey(), "5nUZbtNefYa7tWHdpQApxsjPLtTZpKuZYnKDsd2dXADu"); @@ -221,14 +204,14 @@ TEST(HDKeyEd25519UnitTest, Errors) { std::vector bytes; EXPECT_TRUE( base::HexStringToBytes("000102030405060708090a0b0c0d0e0f", &bytes)); - auto master_key = HDKeyEd25519::GenerateFromSeed(bytes); + auto master_key = HDKeyEd25519::GenerateFromSeedAndPath(bytes, "m"); // path contains normal index - auto child1 = master_key->DeriveChildFromPath("m/0'/1'/2'/3'/4"); + auto child1 = HDKeyEd25519::GenerateFromSeedAndPath(bytes, "m/0'/1'/2'/3'/4"); EXPECT_FALSE(child1); // invalid path - auto child2 = master_key->DeriveChildFromPath("BRAVE0'1'2'3'4'"); + auto child2 = HDKeyEd25519::GenerateFromSeedAndPath(bytes, "BRAVE0'1'2'3'4'"); EXPECT_FALSE(child2); // index is too big for hardened index @@ -240,7 +223,7 @@ TEST(HDKeyEd25519UnitTest, EncodePrivateKeyForExport) { std::vector bytes; EXPECT_TRUE( base::HexStringToBytes("000102030405060708090a0b0c0d0e0f", &bytes)); - auto master_key = HDKeyEd25519::GenerateFromSeed(bytes); + auto master_key = HDKeyEd25519::GenerateFromSeedAndPath(bytes, "m"); EXPECT_EQ(master_key->GetBase58EncodedKeypair(), "sCzwsBKmKtk5Hgb4YUJAduQ5nmJq4GTyzCXhrKonAGaexa83MgSZuTSMS6TSZTndnC" "YbQtaJQKLXET9jVjepWXe"); @@ -250,56 +233,40 @@ TEST(HDKeyEd25519UnitTest, SignAndVerify) { std::vector bytes; EXPECT_TRUE( base::HexStringToBytes("000102030405060708090a0b0c0d0e0f", &bytes)); - auto key = HDKeyEd25519::GenerateFromSeed(bytes); + auto key = HDKeyEd25519::GenerateFromSeedAndPath(bytes, "m"); const std::vector msg_a(32, 0x00); const std::vector msg_b(32, 0x08); - const std::vector sig_a = key->Sign(msg_a); - const std::vector sig_b = key->Sign(msg_b); + const auto sig_a = key->Sign(msg_a); + const auto sig_b = key->Sign(msg_b); - EXPECT_TRUE(key->VerifyForTesting(msg_a, sig_a)); - EXPECT_TRUE(key->VerifyForTesting(msg_b, sig_b)); + EXPECT_TRUE(VerifySignature(*key, msg_a, sig_a)); + EXPECT_TRUE(VerifySignature(*key, msg_b, sig_b)); // wrong signature - EXPECT_FALSE(key->VerifyForTesting(msg_a, sig_b)); - EXPECT_FALSE(key->VerifyForTesting(msg_b, sig_a)); - - // signature size != 64 - EXPECT_FALSE(key->VerifyForTesting(msg_a, std::vector(128, 0xff))); - EXPECT_FALSE(key->VerifyForTesting(msg_a, std::vector(32, 0xff))); - EXPECT_FALSE(key->VerifyForTesting(msg_b, std::vector(128, 0xff))); - EXPECT_FALSE(key->VerifyForTesting(msg_b, std::vector(32, 0xff))); + EXPECT_FALSE(VerifySignature(*key, msg_a, sig_b)); + EXPECT_FALSE(VerifySignature(*key, msg_b, sig_a)); } TEST(HDKeyEd25519UnitTest, GenerateFromPrivateKey) { - std::vector private_key; - ASSERT_TRUE(base::HexStringToBytes( - "2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19e7", - &private_key)); - auto master_key = HDKeyEd25519::GenerateFromPrivateKey(private_key); + std::array key_pair; + ASSERT_TRUE(base::HexStringToSpan( + "2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19e7" + "a4b2856bfec510abab89753fac1ac0e1112364e7d250545963f135f2a33188ed", + base::span(key_pair))); + auto master_key = HDKeyEd25519::GenerateFromKeyPair(key_pair); EXPECT_EQ(master_key->GetBase58EncodedKeypair(), "sCzwsBKmKtk5Hgb4YUJAduQ5nmJq4GTyzCXhrKonAGaexa83MgSZuTSMS6TSZTndnC" "YbQtaJQKLXET9jVjepWXe"); EXPECT_EQ(master_key->GetBase58EncodedPublicKey(), "C5ukMV73nk32h52MjxtnZXTrrr7rupD9CTDDRnYYDRYQ"); - EXPECT_EQ(master_key->GetPath(), ""); - - // 31 bytes - private_key.clear(); - ASSERT_TRUE(base::HexStringToBytes( - "2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19", - &private_key)); - EXPECT_FALSE(HDKeyEd25519::GenerateFromPrivateKey(private_key)); - // 33 bytes - private_key.clear(); - ASSERT_TRUE(base::HexStringToBytes( - "2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19e7ff", - &private_key)); - EXPECT_FALSE(HDKeyEd25519::GenerateFromPrivateKey(private_key)); + EXPECT_EQ( + "6260C446B2BA429541722F6BAABBEEAF3D1B5981DA326A2DB66804B5EACE770D" + "58CFBA0E0D409A3054E80C00505215C7CCD7A040F23364005A47CDE7FCED1400", + base::HexEncode(master_key->Sign(base::byte_span_from_cstring("hello")))); - // 0 bytes - private_key.clear(); - EXPECT_FALSE(HDKeyEd25519::GenerateFromPrivateKey(private_key)); + key_pair.back() = 0; + EXPECT_FALSE(HDKeyEd25519::GenerateFromKeyPair(key_pair)); } } // namespace brave_wallet diff --git a/components/brave_wallet/browser/internal/hd_key_utils.cc b/components/brave_wallet/browser/internal/hd_key_utils.cc new file mode 100644 index 000000000000..04af08342922 --- /dev/null +++ b/components/brave_wallet/browser/internal/hd_key_utils.cc @@ -0,0 +1,50 @@ +/* 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/internal/hd_key_utils.h" + +#include + +#include "base/containers/span.h" +#include "base/numerics/checked_math.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" + +namespace brave_wallet { + +std::optional> ParseFullHDPath(std::string_view path) { + auto entries = base::SplitStringPiece( + path, "/", base::WhitespaceHandling::TRIM_WHITESPACE, + base::SplitResult::SPLIT_WANT_NONEMPTY); + + if (entries.empty()) { + return std::nullopt; + } + if (entries.front() != kMasterNode) { + return std::nullopt; + } + + std::vector result; + result.reserve(entries.size() - 1); + for (auto node : base::span(entries).subspan(1)) { + uint32_t offset = 0; + if (node.ends_with('\'')) { + node.remove_suffix(1); + offset = kHardenedOffset; + } + uint32_t value = 0; + if (!base::StringToUint(node, &value)) { + return std::nullopt; + } + if (value >= kHardenedOffset) { + return std::nullopt; + } + result.push_back(base::CheckAdd(value, offset).ValueOrDie()); + } + + return result; +} + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/internal/hd_key_utils.h b/components/brave_wallet/browser/internal/hd_key_utils.h new file mode 100644 index 000000000000..66b86d1b1bd7 --- /dev/null +++ b/components/brave_wallet/browser/internal/hd_key_utils.h @@ -0,0 +1,25 @@ +/* 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_INTERNAL_HD_KEY_UTILS_H_ +#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_INTERNAL_HD_KEY_UTILS_H_ + +#include +#include +#include + +namespace brave_wallet { + +inline constexpr char kMasterNode[] = "m"; +inline constexpr uint32_t kHardenedOffset = 0x80000000; + +// Parses BIP-32 full derivation path into a vector of indexes. Hardened indexes +// expected to end with a single quote per BIP-44 style. +// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki +// https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki +std::optional> ParseFullHDPath(std::string_view path); + +} // namespace brave_wallet +#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_INTERNAL_HD_KEY_UTILS_H_ diff --git a/components/brave_wallet/browser/internal/hd_key_utils_unittest.cc b/components/brave_wallet/browser/internal/hd_key_utils_unittest.cc new file mode 100644 index 000000000000..e12689cbb84d --- /dev/null +++ b/components/brave_wallet/browser/internal/hd_key_utils_unittest.cc @@ -0,0 +1,50 @@ +/* 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/internal/hd_key_utils.h" + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::ElementsAre; +using testing::IsEmpty; + +namespace brave_wallet { + +TEST(HDKeyUtilsUnitTest, ParseFullHDPath) { + // Success cases. + EXPECT_THAT(*ParseFullHDPath("m"), IsEmpty()); + + EXPECT_THAT(*ParseFullHDPath("m/0"), ElementsAre(0)); + EXPECT_THAT(*ParseFullHDPath("m/1"), ElementsAre(1)); + + EXPECT_THAT(*ParseFullHDPath("m/0'"), ElementsAre(kHardenedOffset)); + EXPECT_THAT(*ParseFullHDPath("m/2'"), ElementsAre(kHardenedOffset + 2)); + + EXPECT_THAT(*ParseFullHDPath("m/0/1/2/3/4"), ElementsAre(0, 1, 2, 3, 4)); + EXPECT_THAT(*ParseFullHDPath("m/2'/3/4'/5"), + ElementsAre(kHardenedOffset + 2, 3, kHardenedOffset + 4, 5)); + + // Index overflows. + EXPECT_THAT(*ParseFullHDPath("m/2147483647"), ElementsAre(2147483647)); + EXPECT_FALSE(ParseFullHDPath("m/2147483648")); + EXPECT_THAT(*ParseFullHDPath("m/2147483647'"), + ElementsAre(kHardenedOffset + 2147483647)); + EXPECT_FALSE(ParseFullHDPath("m/2147483648'")); + + // Incorrect format. + EXPECT_FALSE(ParseFullHDPath("")); + EXPECT_FALSE(ParseFullHDPath("a")); + EXPECT_FALSE(ParseFullHDPath("/0/1/2/3/4")); + EXPECT_FALSE(ParseFullHDPath("0/1/2/3/4")); + EXPECT_FALSE(ParseFullHDPath("m/0//1")); + EXPECT_FALSE(ParseFullHDPath("m/0/1/")); + EXPECT_FALSE(ParseFullHDPath("m/-1")); + EXPECT_FALSE(ParseFullHDPath("m/1/a")); + EXPECT_FALSE(ParseFullHDPath("m/1''")); + EXPECT_FALSE(ParseFullHDPath("m/1'1")); +} + +} // namespace brave_wallet diff --git a/components/brave_wallet/browser/solana_keyring.cc b/components/brave_wallet/browser/solana_keyring.cc index 9e80cbd9715d..7f7c2b4ade33 100644 --- a/components/brave_wallet/browser/solana_keyring.cc +++ b/components/brave_wallet/browser/solana_keyring.cc @@ -11,6 +11,7 @@ #include "base/containers/contains.h" #include "base/containers/span.h" +#include "base/containers/to_vector.h" #include "base/ranges/algorithm.h" #include "brave/components/brave_wallet/browser/brave_wallet_utils.h" #include "brave/components/brave_wallet/common/brave_wallet.mojom.h" @@ -38,9 +39,7 @@ std::unique_ptr SolanaKeyring::ConstructRootHDKey( base::span seed, const std::string& hd_path) { if (!seed.empty()) { - if (auto master_key = HDKeyEd25519::GenerateFromSeed(seed)) { - return master_key->DeriveChildFromPath(hd_path); - } + return HDKeyEd25519::GenerateFromSeedAndPath(seed, hd_path); } return nullptr; @@ -65,12 +64,13 @@ void SolanaKeyring::RemoveLastHDAccount() { accounts_.pop_back(); } -std::string SolanaKeyring::ImportAccount(base::span keypair) { - // extract private key from keypair - std::vector private_key = std::vector( - keypair.begin(), keypair.begin() + kSolanaPrikeySize); +std::string SolanaKeyring::ImportAccount(base::span payload) { + auto key_pair = payload.to_fixed_extent(); + if (!key_pair) { + return std::string(); + } std::unique_ptr hd_key = - HDKeyEd25519::GenerateFromPrivateKey(private_key); + HDKeyEd25519::GenerateFromKeyPair(*key_pair); if (!hd_key) { return std::string(); } @@ -108,13 +108,12 @@ std::string SolanaKeyring::EncodePrivateKeyForExport( std::vector SolanaKeyring::SignMessage( const std::string& address, base::span message) { - HDKeyEd25519* hd_key = - static_cast(GetHDKeyFromAddress(address)); + HDKeyEd25519* hd_key = GetHDKeyFromAddress(address); if (!hd_key) { return std::vector(); } - return hd_key->Sign(message); + return base::ToVector(hd_key->Sign(message)); } std::vector SolanaKeyring::GetHDAccountsForTesting() const { @@ -177,15 +176,14 @@ std::optional SolanaKeyring::CreateProgramDerivedAddress( buffer.insert(buffer.end(), pda_marker.begin(), pda_marker.end()); auto hash_array = crypto::SHA256Hash(buffer); - std::vector hash_vec(hash_array.begin(), hash_array.end()); // Invalid because program derived addresses have to be off-curve. if (bytes_are_curve25519_point( - rust::Slice{hash_vec.data(), hash_vec.size()})) { + rust::Slice{hash_array.data(), hash_array.size()})) { return std::nullopt; } - return Base58Encode(hash_vec); + return Base58Encode(hash_array); } // Find a valid program derived address and its corresponding bump seed. diff --git a/components/brave_wallet/browser/solana_keyring.h b/components/brave_wallet/browser/solana_keyring.h index 519679275706..b97dfad0b0a5 100644 --- a/components/brave_wallet/browser/solana_keyring.h +++ b/components/brave_wallet/browser/solana_keyring.h @@ -31,7 +31,7 @@ class SolanaKeyring : public HDKeyring { std::optional AddNewHDAccount() override; void RemoveLastHDAccount() override; - std::string ImportAccount(base::span keypair) override; + std::string ImportAccount(base::span payload) override; bool RemoveImportedAccount(const std::string& address) override; std::string EncodePrivateKeyForExport(const std::string& address) override; diff --git a/components/brave_wallet/browser/solana_keyring_unittest.cc b/components/brave_wallet/browser/solana_keyring_unittest.cc index dd604364ba12..cf11f5484e17 100644 --- a/components/brave_wallet/browser/solana_keyring_unittest.cc +++ b/components/brave_wallet/browser/solana_keyring_unittest.cc @@ -95,7 +95,8 @@ TEST(SolanaKeyringUnitTest, ImportAccount) { std::vector private_key; ASSERT_TRUE(base::HexStringToBytes( - "2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19e7", + "2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19e7" + "a4b2856bfec510abab89753fac1ac0e1112364e7d250545963f135f2a33188ed", &private_key)); keyring.ImportAccount(private_key); EXPECT_EQ(keyring.GetImportedAccountsForTesting().size(), 1u); diff --git a/components/brave_wallet/browser/test/BUILD.gn b/components/brave_wallet/browser/test/BUILD.gn index 1cb94f2a57e1..4bc0657cac25 100644 --- a/components/brave_wallet/browser/test/BUILD.gn +++ b/components/brave_wallet/browser/test/BUILD.gn @@ -58,6 +58,7 @@ source_set("brave_wallet_unit_tests") { "//brave/components/brave_wallet/browser/filecoin_keyring_unittest.cc", "//brave/components/brave_wallet/browser/internal/hd_key_ed25519_unittest.cc", "//brave/components/brave_wallet/browser/internal/hd_key_unittest.cc", + "//brave/components/brave_wallet/browser/internal/hd_key_utils_unittest.cc", "//brave/components/brave_wallet/browser/json_rpc_requests_helper_unittest.cc", "//brave/components/brave_wallet/browser/json_rpc_response_parser_unittest.cc", "//brave/components/brave_wallet/browser/json_rpc_service_test_utils_unittest.cc", diff --git a/components/brave_wallet/common/BUILD.gn b/components/brave_wallet/common/BUILD.gn index 77ebce97ad4d..c03313270678 100644 --- a/components/brave_wallet/common/BUILD.gn +++ b/components/brave_wallet/common/BUILD.gn @@ -75,7 +75,6 @@ static_library("common") { ":generated_eth_requests", ":generated_json_rpc_requests", ":pref_names", - "//brave/components/brave_wallet/rust:rust_lib", "//brave/net/base:utils", "//brave/third_party/argon2", "//brave/third_party/bitcoin-core", diff --git a/components/brave_wallet/common/hash_utils.cc b/components/brave_wallet/common/hash_utils.cc index 5d1e89639dbc..1cbeebcbe031 100644 --- a/components/brave_wallet/common/hash_utils.cc +++ b/components/brave_wallet/common/hash_utils.cc @@ -17,6 +17,7 @@ #include "brave/third_party/bitcoin-core/src/src/crypto/ripemd160.h" #include "brave/third_party/ethash/src/include/ethash/keccak.h" #include "crypto/sha2.h" +#include "third_party/boringssl/src/include/openssl/hmac.h" namespace brave_wallet { namespace { @@ -74,4 +75,14 @@ Ripemd160HashArray Hash160(base::span input) { return result; } +SHA512HashArray HmacSha512(base::span key, + base::span data) { + SHA512HashArray hmac; + unsigned int out_len = 0; + CHECK(HMAC(EVP_sha512(), key.data(), key.size(), data.data(), data.size(), + hmac.data(), &out_len)); + CHECK_EQ(out_len, hmac.size()); + return hmac; +} + } // namespace brave_wallet diff --git a/components/brave_wallet/common/hash_utils.h b/components/brave_wallet/common/hash_utils.h index f445edca8a3b..c56bb4dfd702 100644 --- a/components/brave_wallet/common/hash_utils.h +++ b/components/brave_wallet/common/hash_utils.h @@ -17,9 +17,11 @@ namespace brave_wallet { inline constexpr size_t kKeccakHashLength = 32; inline constexpr size_t kRipemd160HashLength = 20; +inline constexpr size_t kSHA512HashLength = 64; using KeccakHashArray = std::array; using SHA256HashArray = std::array; +using SHA512HashArray = std::array; using Ripemd160HashArray = std::array; KeccakHashArray KeccakHash(base::span input); @@ -40,6 +42,9 @@ SHA256HashArray DoubleSHA256Hash(base::span input); // ripemd160(sha256(input)) Ripemd160HashArray Hash160(base::span input); +SHA512HashArray HmacSha512(base::span key, + base::span data); + } // namespace brave_wallet #endif // BRAVE_COMPONENTS_BRAVE_WALLET_COMMON_HASH_UTILS_H_ diff --git a/components/brave_wallet/common/hash_utils_unittest.cc b/components/brave_wallet/common/hash_utils_unittest.cc index 587b4740a162..b57e3ff46bc1 100644 --- a/components/brave_wallet/common/hash_utils_unittest.cc +++ b/components/brave_wallet/common/hash_utils_unittest.cc @@ -79,4 +79,30 @@ TEST(HashUtilsUnitTest, Hash160) { "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"); } +TEST(HashUtilsUnitTest, HmacSha512) { + // Empty vectors test. + EXPECT_EQ(HexEncodeLower(HmacSha512({}, {})), + "b936cee86c9f87aa5d3c6f2e84cb5a4239a5fe50480a6ec66b70ab5b1f4ac673" + "0c6c515421b327ec1d69402e53dfb49ad7381eb067b338fd7b0cb22247225d47"); + + // Large vectors test. + EXPECT_EQ(HexEncodeLower(HmacSha512(std::vector(1000, 0xee), + std::vector(2000, 0x45))), + "5d6a801cf32c7d5edb17f5287653c86323599de6e8ab76819b3530494e144ec6" + "3a40f6e541d6cc8a7db3d0560349d74ca52c1e370c9a70a96096e28761d017fc"); + + // https://datatracker.ietf.org/doc/html/rfc4231#section-4.2 + EXPECT_EQ( + HexEncodeLower(HmacSha512(std::vector(20, 0x0b), + base::byte_span_from_cstring("Hi There"))), + "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cde" + "daa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"); + + // https://datatracker.ietf.org/doc/html/rfc4231#section-4.4 + EXPECT_EQ(HexEncodeLower(HmacSha512(std::vector(20, 0xaa), + std::vector(50, 0xdd))), + "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39" + "bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb"); +} + } // namespace brave_wallet