-
Notifications
You must be signed in to change notification settings - Fork 900
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
13d806a
commit 172a49e
Showing
53 changed files
with
728 additions
and
803 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/* 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/bip39.h" | ||
|
||
#include "base/strings/strcat.h" | ||
#include "brave/third_party/bip39wally-core-native/include/wally_bip39.h" | ||
#include "openssl/digest.h" | ||
#include "openssl/evp.h" | ||
|
||
namespace brave_wallet::bip39 { | ||
|
||
namespace { | ||
|
||
// https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#generating-the-mnemonic | ||
inline constexpr uint32_t kMaxSupportedEntropySize = 32; | ||
|
||
// https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#from-mnemonic-to-seed | ||
inline constexpr uint32_t kPBKDF2Iterations = 2048; | ||
inline constexpr uint32_t kSeedSize = 64; | ||
|
||
std::optional<std::string> GenerateMnemonicInternal( | ||
base::span<const uint8_t> entropy) { | ||
char* words = nullptr; | ||
if (bip39_mnemonic_from_bytes(nullptr, entropy.data(), entropy.size(), | ||
&words) != WALLY_OK) { | ||
return std::nullopt; | ||
} | ||
std::string result(words); | ||
wally_free_string(words); | ||
return result; | ||
} | ||
|
||
bool IsValidEntropySize(size_t entropy_size) { | ||
// entropy size should be 128, 160, 192, 224, 256 bits | ||
if (entropy_size < 16 || entropy_size > 32 || entropy_size % 4 != 0) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
} // namespace | ||
|
||
std::optional<std::string> GenerateMnemonic(base::span<const uint8_t> entropy) { | ||
if (!IsValidEntropySize(entropy.size())) { | ||
return std::nullopt; | ||
} | ||
return GenerateMnemonicInternal(entropy); | ||
} | ||
|
||
std::optional<std::vector<uint8_t>> MnemonicToSeed( | ||
std::string_view mnemonic, | ||
std::string_view passphrase) { | ||
if (!IsValidMnemonic(mnemonic)) { | ||
return std::nullopt; | ||
} | ||
|
||
std::vector<uint8_t> seed(kSeedSize, 0); | ||
const std::string salt = base::StrCat({"mnemonic", passphrase}); | ||
if (PKCS5_PBKDF2_HMAC(mnemonic.data(), mnemonic.length(), | ||
reinterpret_cast<const uint8_t*>(salt.data()), | ||
salt.length(), kPBKDF2Iterations, EVP_sha512(), | ||
seed.size(), seed.data())) { | ||
return seed; | ||
} | ||
|
||
return std::nullopt; | ||
} | ||
|
||
std::optional<std::vector<uint8_t>> MnemonicToEntropy( | ||
std::string_view mnemonic) { | ||
if (!IsValidMnemonic(mnemonic)) { | ||
return std::nullopt; | ||
} | ||
|
||
std::vector<uint8_t> entropy(kMaxSupportedEntropySize, 0); | ||
size_t written = 0; | ||
if (bip39_mnemonic_to_bytes(nullptr, std::string(mnemonic).c_str(), | ||
entropy.data(), entropy.size(), | ||
&written) != WALLY_OK) { | ||
return std::nullopt; | ||
} | ||
entropy.resize(written); | ||
return entropy; | ||
} | ||
|
||
bool IsValidMnemonic(std::string_view mnemonic) { | ||
if (bip39_mnemonic_validate(nullptr, std::string(mnemonic).c_str()) != | ||
WALLY_OK) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
} // namespace brave_wallet::bip39 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* 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_BIP39_H_ | ||
#define BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_BIP39_H_ | ||
|
||
#include <optional> | ||
#include <string> | ||
#include <string_view> | ||
#include <vector> | ||
|
||
#include "base/containers/span.h" | ||
|
||
// Utility functions for BIP39 mnemonics support. | ||
// https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki | ||
namespace brave_wallet::bip39 { | ||
|
||
inline constexpr uint32_t kDefaultEntropySize = 16; | ||
inline constexpr uint32_t kLegacyEthEntropySize = 32; | ||
|
||
// Generate mnemonic from entropy bytes following BIP39. | ||
// If |entropy.size()| is not in 16, 20, 24, 28, 32 range or | ||
// allocation failure, the std::nullopt will be returned. | ||
std::optional<std::string> GenerateMnemonic(base::span<const uint8_t> entropy); | ||
|
||
// Generate 64 bytes seed from mnemonic following BIP39. | ||
std::optional<std::vector<uint8_t>> MnemonicToSeed( | ||
std::string_view mnemonic, | ||
std::string_view passphrase = ""); | ||
|
||
// This is mainly used for restoring legacy brave crypto wallet | ||
std::optional<std::vector<uint8_t>> MnemonicToEntropy( | ||
std::string_view mnemonic); | ||
|
||
bool IsValidMnemonic(std::string_view mnemonic); | ||
|
||
} // namespace brave_wallet::bip39 | ||
|
||
#endif // BRAVE_COMPONENTS_BRAVE_WALLET_BROWSER_BIP39_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
/* 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/bip39.h" | ||
|
||
#include <optional> | ||
|
||
#include "base/rand_util.h" | ||
#include "base/strings/string_number_conversions.h" | ||
#include "base/strings/string_split.h" | ||
#include "base/strings/string_util.h" | ||
#include "base/test/values_test_util.h" | ||
#include "brave/components/brave_wallet/browser/test_utils.h" | ||
#include "testing/gtest/include/gtest/gtest.h" | ||
|
||
using base::test::ParseJsonDict; | ||
using testing::Contains; | ||
using testing::ElementsAreArray; | ||
using testing::Eq; | ||
using testing::Not; | ||
|
||
namespace brave_wallet::bip39 { | ||
|
||
TEST(BraveWalletUtilsUnitTest, Mnemonic) { | ||
const struct { | ||
std::string_view entropy; | ||
std::string_view mnemonic; | ||
std::string_view seed; | ||
} cases[] = { | ||
{"00000000000000000000000000000000", kMnemonicAbandonAbandon, | ||
"c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a69" | ||
"87" | ||
"599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04"}, | ||
{"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", | ||
"legal winner thank year wave sausage worth useful legal winner thank " | ||
"yellow", | ||
"2e8905819b8723fe2c1d161860e5ee1830318dbf49a83bd451cfb8440c28bd6fa457fe1" | ||
"29" | ||
"6106559a3c80937a1c1069be3a3a5bd381ee6260e8d9739fce1f607"}, | ||
{"80808080808080808080808080808080", | ||
"letter advice cage absurd amount doctor acoustic avoid letter advice " | ||
"cage above", | ||
"d71de856f81a8acc65e6fc851a38d4d7ec216fd0796d0a6827a3ad6ed5511a30fa280f1" | ||
"2eb2e47ed2ac03b5c462a0358d18d69fe4f985ec81778c1b370b652a8"}, | ||
{"ffffffffffffffffffffffffffffffff", | ||
"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong", | ||
"ac27495480225222079d7be181583751e86f571027b0497b5b5d11218e0a8a133325729" | ||
"17f0f8e5a589620c6f15b11c61dee327651a14c34e18231052e48c069"}, | ||
{"000000000000000000000000000000000000000000000000", | ||
"abandon abandon abandon abandon abandon abandon abandon abandon " | ||
"abandon abandon abandon abandon abandon abandon abandon abandon " | ||
"abandon agent", | ||
"035895f2f481b1b0f01fcf8c289c794660b289981a78f8106447707fdd9666ca06da5a9" | ||
"a565181599b79f53b844d8a71dd9f439c52a3d7b3e8a79c906ac845fa"}, | ||
{"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", | ||
"legal winner thank year wave sausage worth useful legal winner thank " | ||
"year wave sausage worth useful legal will", | ||
"f2b94508732bcbacbcc020faefecfc89feafa6649a5491b8c952cede496c214a0c7b3c3" | ||
"92d168748f2d4a612bada0753b52a1c7ac53c1e93abd5c6320b9e95dd"}, | ||
{"808080808080808080808080808080808080808080808080", | ||
"letter advice cage absurd amount doctor acoustic avoid letter advice " | ||
"cage absurd amount doctor acoustic avoid letter always", | ||
"107d7c02a5aa6f38c58083ff74f04c607c2d2c0ecc55501dadd72d025b751bc27fe913f" | ||
"fb796f841c49b1d33b610cf0e91d3aa239027f5e99fe4ce9e5088cd65"}, | ||
{"ffffffffffffffffffffffffffffffffffffffffffffffff", | ||
"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo " | ||
"when", | ||
"0cd6e5d827bb62eb8fc1e262254223817fd068a74b5b449cc2f667c3f1f985a76379b43" | ||
"348d952e2265b4cd129090758b3e3c2c49103b5051aac2eaeb890a528"}, | ||
{"0000000000000000000000000000000000000000000000000000000000000000", | ||
"abandon abandon abandon abandon abandon abandon abandon abandon " | ||
"abandon abandon abandon abandon abandon abandon abandon abandon " | ||
"abandon abandon abandon abandon abandon abandon abandon art", | ||
"bda85446c68413707090a52022edd26a1c9462295029f2e60cd7c4f2bbd3097170af7a4" | ||
"d73245cafa9c3cca8d561a7c3de6f5d4a10be8ed2a5e608d68f92fcc8"}, | ||
{"7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f", | ||
"legal winner thank year wave sausage worth useful legal winner thank " | ||
"year wave sausage worth useful legal winner thank year wave sausage " | ||
"worth title", | ||
"bc09fca1804f7e69da93c2f2028eb238c227f2e9dda30cd63699232578480a4021b146a" | ||
"d717fbb7e451ce9eb835f43620bf5c514db0f8add49f5d121449d3e87"}, | ||
{"8080808080808080808080808080808080808080808080808080808080808080", | ||
"letter advice cage absurd amount doctor acoustic avoid letter advice " | ||
"cage absurd amount doctor acoustic avoid letter advice cage absurd " | ||
"amount doctor acoustic bless", | ||
"c0c519bd0e91a2ed54357d9d1ebef6f5af218a153624cf4f2da911a0ed8f7a09e2ef61a" | ||
"f0aca007096df430022f7a2b6fb91661a9589097069720d015e4e982f"}, | ||
{"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", | ||
"zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo " | ||
"zoo zoo zoo zoo zoo zoo vote", | ||
"dd48c104698c30cfe2b6142103248622fb7bb0ff692eebb00089b32d22484e1613912f0" | ||
"a5b694407be899ffd31ed3992c456cdf60f5d4564b8ba3f05a69890ad"}, | ||
{"77c2b00716cec7213839159e404db50d", | ||
"jelly better achieve collect unaware mountain thought cargo oxygen act " | ||
"hood bridge", | ||
"b5b6d0127db1a9d2226af0c3346031d77af31e918dba64287a1b44b8ebf63cdd52676f6" | ||
"72a290aae502472cf2d602c051f3e6f18055e84e4c43897fc4e51a6ff"}, | ||
{"b63a9c59a6e641f288ebc103017f1da9f8290b3da6bdef7b", | ||
"renew stay biology evidence goat welcome casual join adapt armor " | ||
"shuffle fault little machine walk stumble urge swap", | ||
"9248d83e06f4cd98debf5b6f010542760df925ce46cf38a1bdb4e4de7d21f5c39366941" | ||
"c69e1bdbf2966e0f6e6dbece898a0e2f0a4c2b3e640953dfe8b7bbdc5"}, | ||
{"3e141609b97933b66a060dcddc71fad1d91677db872031e85f4c015c5e7e8982", | ||
"dignity pass list indicate nasty swamp pool script soccer toe leaf " | ||
"photo multiply desk host tomato cradle drill spread actor shine " | ||
"dismiss champion exotic", | ||
"ff7f3184df8696d8bef94b6c03114dbee0ef89ff938712301d27ed8336ca89ef9635da2" | ||
"0af07d4175f2bf5f3de130f39c9d9e8dd0472489c19b1a020a940da67"}, | ||
{"0460ef47585604c5660618db2e6a7e7f", | ||
"afford alter spike radar gate glance object seek swamp infant panel " | ||
"yellow", | ||
"65f93a9f36b6c85cbe634ffc1f99f2b82cbb10b31edc7f087b4f6cb9e976e9faf76ff41" | ||
"f8f27c99afdf38f7a303ba1136ee48a4c1e7fcd3dba7aa876113a36e4"}, | ||
{"72f60ebac5dd8add8d2a25a797102c3ce21bc029c200076f", | ||
"indicate race push merry suffer human cruise dwarf pole review arch " | ||
"keep canvas theme poem divorce alter left", | ||
"3bbf9daa0dfad8229786ace5ddb4e00fa98a044ae4c4975ffd5e094dba9e0bb289349db" | ||
"e2091761f30f382d4e35c4a670ee8ab50758d2c55881be69e327117ba"}, | ||
{"2c85efc7f24ee4573d2b81a6ec66cee209b2dcbd09d8eddc51e0215b0b68e416", | ||
"clutch control vehicle tonight unusual clog visa ice plunge glimpse " | ||
"recipe series open hour vintage deposit universe tip job dress radar " | ||
"refuse motion taste", | ||
"fe908f96f46668b2d5b37d82f558c77ed0d69dd0e7e043a5b0511c48c2f1064694a956f" | ||
"86360c93dd04052a8899497ce9e985ebe0c8c52b955e6ae86d4ff4449"}, | ||
{"eaebabb2383351fd31d703840b32e9e2", | ||
"turtle front uncle idea crush write shrug there lottery flower risk " | ||
"shell", | ||
"bdfb76a0759f301b0b899a1e3985227e53b3f51e67e3f2a65363caedf3e32fde42a66c4" | ||
"04f18d7b05818c95ef3ca1e5146646856c461c073169467511680876c"}, | ||
{"7ac45cfe7722ee6c7ba84fbc2d5bd61b45cb2fe5eb65aa78", | ||
"kiss carry display unusual confirm curtain upgrade antique rotate " | ||
"hello void custom frequent obey nut hole price segment", | ||
"ed56ff6c833c07982eb7119a8f48fd363c4a9b1601cd2de736b01045c5eb8ab4f57b079" | ||
"403485d1c4924f0790dc10a971763337cb9f9c62226f64fff26397c79"}, | ||
{"4fa1a8bc3e6d80ee1316050e862c1812031493212b7ec3f3bb1b08f168cabeef", | ||
"exile ask congress lamp submit jacket era scheme attend cousin alcohol " | ||
"catch course end lucky hurt sentence oven short ball bird grab wing " | ||
"top", | ||
"095ee6f817b4c2cb30a5a797360a81a40ab0f9a4e25ecd672a3f58a0b5ba0687c096a6b" | ||
"14d2c0deb3bdefce4f61d01ae07417d502429352e27695163f7447a8c"}, | ||
{"18ab19a9f54a9274f03e5209a2ac8a91", | ||
"board flee heavy tunnel powder denial science ski answer betray cargo " | ||
"cat", | ||
"6eff1bb21562918509c73cb990260db07c0ce34ff0e3cc4a8cb3276129fbcb300bddfe0" | ||
"05831350efd633909f476c45c88253276d9fd0df6ef48609e8bb7dca8"}, | ||
{"18a2e1d81b8ecfb2a333adcb0c17a5b9eb76cc5d05db91a4", | ||
"board blade invite damage undo sun mimic interest slam gaze truly " | ||
"inherit resist great inject rocket museum chief", | ||
"f84521c777a13b61564234bf8f8b62b3afce27fc4062b51bb5e62bdfecb23864ee6ecf0" | ||
"7c1d5a97c0834307c5c852d8ceb88e7c97923c0a3b496bedd4e5f88a9"}, | ||
{"15da872c95a13dd738fbf50e427583ad61f18fd99f628c417a61cf8343c90419", | ||
"beyond stage sleep clip because twist token leaf atom beauty genius " | ||
"food business side grid unable middle armed observe pair crouch " | ||
"tonight away coconut", | ||
"b15509eaa2d09d3efd3e006ef42151b30367dc6e3aa5e44caba3fe4d3e352e65101fbdb" | ||
"86a96776b91946ff06f8eac594dc6ee1d3e82a42dfe1b40fef6bcc3fd"}, | ||
}; | ||
|
||
for (const auto& entry : cases) { | ||
std::vector<uint8_t> bytes; | ||
EXPECT_TRUE(base::HexStringToBytes(entry.entropy, &bytes)); | ||
auto entropy = MnemonicToEntropy(entry.mnemonic); | ||
EXPECT_EQ(base::ToLowerASCII(base::HexEncode(*entropy)), entry.entropy); | ||
|
||
EXPECT_EQ(GenerateMnemonic(bytes), entry.mnemonic); | ||
auto seed = MnemonicToSeed(entry.mnemonic, "TREZOR"); | ||
EXPECT_EQ(base::ToLowerASCII(base::HexEncode(*seed)), entry.seed); | ||
} | ||
|
||
for (size_t i = 15; i <= 33; i += 2) { | ||
EXPECT_FALSE(GenerateMnemonic(base::RandBytesAsVector(i))); | ||
} | ||
for (size_t i = 16; i <= 32; i += 4) { | ||
auto result = GenerateMnemonic(base::RandBytesAsVector(i)); | ||
ASSERT_TRUE(result); | ||
auto words = base::SplitStringPiece(*result, " ", base::KEEP_WHITESPACE, | ||
base::SPLIT_WANT_ALL); | ||
// words count should be 12, 15, 18, 21, 24 | ||
EXPECT_EQ(words.size(), (i / 4) * 3); | ||
// Random generated entropy | ||
EXPECT_NE(GenerateMnemonic(base::RandBytesAsVector(i)), | ||
GenerateMnemonic(base::RandBytesAsVector(i))); | ||
} | ||
} | ||
|
||
TEST(BraveWalletUtilsUnitTest, MnemonicToSeedAndEntropy) { | ||
const char valid_mnemonic[] = | ||
"kingdom possible coast island six arrow fluid spell chunk loud glue " | ||
"street"; | ||
const char invalid_mnemonic1[] = | ||
"lingdom possible coast island six arrow fluid spell chunk loud glue " | ||
"street"; | ||
const char invalid_mnemonic2[] = | ||
"kingdom possible coast island six arrow fluid spell chunk loud glue"; | ||
EXPECT_NE(MnemonicToSeed(valid_mnemonic, ""), std::nullopt); | ||
EXPECT_NE(MnemonicToEntropy(valid_mnemonic), std::nullopt); | ||
EXPECT_EQ(MnemonicToSeed(invalid_mnemonic1, ""), std::nullopt); | ||
EXPECT_EQ(MnemonicToEntropy(invalid_mnemonic1), std::nullopt); | ||
EXPECT_EQ(MnemonicToSeed(invalid_mnemonic2, ""), std::nullopt); | ||
EXPECT_EQ(MnemonicToEntropy(invalid_mnemonic2), std::nullopt); | ||
EXPECT_EQ(MnemonicToSeed("", ""), std::nullopt); | ||
EXPECT_EQ(MnemonicToEntropy(""), std::nullopt); | ||
} | ||
|
||
TEST(BraveWalletUtilsUnitTest, IsValidMnemonic) { | ||
EXPECT_TRUE( | ||
IsValidMnemonic("kingdom possible coast island six arrow fluid " | ||
"spell chunk loud glue street")); | ||
EXPECT_FALSE( | ||
IsValidMnemonic("lingdom possible coast island six arrow fluid " | ||
"spell chunk loud glue street")); | ||
EXPECT_FALSE(IsValidMnemonic("kingdom possible coast island six arrow")); | ||
EXPECT_FALSE(IsValidMnemonic("")); | ||
} | ||
|
||
} // namespace brave_wallet::bip39 |
Oops, something went wrong.