Skip to content

Commit

Permalink
Use OpenSSL for ed25519 keys
Browse files Browse the repository at this point in the history
  • Loading branch information
supermassive committed Dec 2, 2024
1 parent 968ebf4 commit f39f005
Show file tree
Hide file tree
Showing 17 changed files with 395 additions and 292 deletions.
2 changes: 0 additions & 2 deletions components/brave_wallet/browser/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down
3 changes: 2 additions & 1 deletion components/brave_wallet/browser/internal/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand All @@ -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",
Expand Down
68 changes: 10 additions & 58 deletions components/brave_wallet/browser/internal/hd_key.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand All @@ -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;
Expand Down Expand Up @@ -136,19 +133,10 @@ std::unique_ptr<HDKey> HDKey::GenerateFromSeed(base::span<const uint8_t> 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> hdkey = std::make_unique<HDKey>();
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;
Expand Down Expand Up @@ -549,18 +537,8 @@ std::unique_ptr<HDKey> 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> hdkey = std::make_unique<HDKey>();
hdkey->SetChainCode(IR);
Expand Down Expand Up @@ -625,44 +603,18 @@ std::unique_ptr<HDKey> HDKey::DeriveChildFromPath(const std::string& path) {
return nullptr;
}

std::unique_ptr<HDKey> hd_key = std::make_unique<HDKey>();
std::vector<std::string> 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<HDKey> hd_key = std::make_unique<HDKey>();
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;
}
Expand Down
165 changes: 88 additions & 77 deletions components/brave_wallet/browser/internal/hd_key_ed25519.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,52 +8,63 @@
#include <utility>

#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<Ed25519DalekExtendedSecretKeyResult> 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<const uint8_t, kEd25519KeypairSize> key_pair) {
std::array<uint8_t, kEd25519SignatureSize> 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<kEd25519PublicKeySize>().data());
}

} // namespace

HDKeyEd25519::HDKeyEd25519() = default;
HDKeyEd25519::~HDKeyEd25519() = default;

// static
std::unique_ptr<HDKeyEd25519> HDKeyEd25519::GenerateFromSeed(
base::span<const uint8_t> seed) {
auto master_private_key = generate_ed25519_extended_secret_key_from_seed(
rust::Slice<const uint8_t>{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<HDKeyEd25519>(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<const uint8_t> key,
base::span<const uint8_t> 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<uint8_t, ED25519_PUBLIC_KEY_LEN> 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> HDKeyEd25519::GenerateFromPrivateKey(
base::span<const uint8_t> private_key) {
auto master_private_key = generate_ed25519_extended_secret_key_from_bytes(
rust::Slice<const uint8_t>{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> HDKeyEd25519::GenerateFromKeyPair(
base::span<const uint8_t, kEd25519KeypairSize> key_pair) {
if (!ValidateKeypair(key_pair)) {
return nullptr;
}
return std::make_unique<HDKeyEd25519>("", std::move(master_private_key));
}

std::string HDKeyEd25519::GetPath() const {
return path_;
auto result = std::make_unique<HDKeyEd25519>();
base::span(result->key_pair_).copy_from(key_pair);
return result;
}

// index should be 0 to 2^31-1
Expand All @@ -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> 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<HDKeyEd25519>(std::move(child_path),
std::move(child_private_key));
return DeriveChild(kHardenedOffset + index);
}

std::unique_ptr<HDKeyEd25519> HDKeyEd25519::DeriveChild(uint32_t index) {
std::vector<uint8_t> 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> HDKeyEd25519::DeriveChildFromPath(
const std::string& path) {
if (path_ != kMasterNode) {
VLOG(0) << "must derive only from master key";
// static
std::unique_ptr<HDKeyEd25519> HDKeyEd25519::GenerateFromSeedAndPath(
base::span<const uint8_t> 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<HDKeyEd25519>(path, std::move(child_private_key));
return hd_key;
}

std::vector<uint8_t> HDKeyEd25519::Sign(base::span<const uint8_t> msg) {
auto signature_result = private_key_->unwrap().sign(
rust::Slice<const uint8_t>{msg.data(), msg.size()});
if (!signature_result->is_ok()) {
VLOG(0) << std::string(signature_result->error_message());
return std::vector<uint8_t>();
}
auto signature_bytes = signature_result->unwrap().to_bytes();
return std::vector<uint8_t>(signature_bytes.begin(), signature_bytes.end());
}
std::array<uint8_t, kEd25519SignatureSize> HDKeyEd25519::Sign(
base::span<const uint8_t> msg) {
std::array<uint8_t, kEd25519SignatureSize> signature = {};

bool HDKeyEd25519::VerifyForTesting(base::span<const uint8_t> msg,
base::span<const uint8_t> sig) {
auto verification_result = private_key_->unwrap().verify(
rust::Slice<const uint8_t>{msg.data(), msg.size()},
rust::Slice<const uint8_t>{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<uint8_t> HDKeyEd25519::GetPrivateKeyBytes() const {
auto secret_key = private_key_->unwrap().secret_key_raw();
return {secret_key.begin(), secret_key.end()};
base::span<const uint8_t, kEd25519SecretKeySize>
HDKeyEd25519::GetPrivateKeyAsSpan() const {
return base::span(key_pair_).first<kEd25519SecretKeySize>();
}

std::vector<uint8_t> HDKeyEd25519::GetPublicKeyBytes() const {
auto public_key = private_key_->unwrap().public_key_raw();
return {public_key.begin(), public_key.end()};
base::span<const uint8_t, kEd25519PublicKeySize>
HDKeyEd25519::GetPublicKeyAsSpan() const {
return base::span(key_pair_).last<kEd25519PublicKeySize>();
}

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
Loading

0 comments on commit f39f005

Please sign in to comment.