Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wallet openssl ed25519 #26770

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions components/brave_wallet/browser/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -431,11 +431,10 @@ source_set("hd_keyring") {
"//brave/components/brave_wallet/common:buildflags",
"//brave/components/brave_wallet/common:common_constants",
"//brave/components/brave_wallet/common:mojom",
"//brave/components/brave_wallet/rust:rust_lib",
"//brave/components/filecoin/rs:rust_lib",
"//crypto",
]

public_deps = [ "//brave/components/brave_wallet/rust:rust_lib" ]
}

source_set("transaction") {
Expand Down
1 change: 0 additions & 1 deletion components/brave_wallet/browser/internal/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,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
151 changes: 78 additions & 73 deletions components/brave_wallet/browser/internal/hd_key_ed25519.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,55 +5,73 @@

#include "brave/components/brave_wallet/browser/internal/hd_key_ed25519.h"

#include <memory>
#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/containers/to_vector.h"
#include "base/strings/string_number_conversions.h"
#include "brave/third_party/bitcoin-core/src/src/base58.h"
#include "crypto/hmac.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());
inline constexpr char kMasterSecret[] = "ed25519 seed";
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);

std::array<uint8_t, kEd25519KeyPairSize> DeriveKeyPairFromPrivateKey(
base::span<const uint8_t, kEd25519PrivateKeySize> private_key) {
std::array<uint8_t, kEd25519KeyPairSize> key_pair;
std::array<uint8_t, kEd25519PublicKeySize> public_key_ignore;
ED25519_keypair_from_seed(public_key_ignore.data(), key_pair.data(),
private_key.data());
DCHECK_EQ(private_key, base::span(key_pair).first<kEd25519PrivateKeySize>());
return key_pair;
}

} // namespace

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

// Child key derivation.
// https://github.com/satoshilabs/slips/blob/master/slip-0010.md#private-parent-key--private-child-key
// 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));
std::unique_ptr<HDKeyEd25519> HDKeyEd25519::DeriveFromHmacPayload(
base::span<const uint8_t> key,
base::span<const uint8_t> data) {
auto hmac = crypto::hmac::SignSha512(key, data);
auto hmac_span = base::span(hmac);

auto result = std::make_unique<HDKeyEd25519>();
result->key_pair_ =
DeriveKeyPairFromPrivateKey(hmac_span.first<kEd25519PrivateKeySize>());
base::span(result->chain_code_)
.copy_from(hmac_span.last<kSlip10ChainCodeSize>());
return result;
}

// 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());
auto private_key_fixed_size =
private_key.to_fixed_extent<kEd25519PrivateKeySize>();
if (!private_key_fixed_size) {
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>();
result->key_pair_ = DeriveKeyPairFromPrivateKey(*private_key_fixed_size);
return result;
}

// index should be 0 to 2^31-1
Expand All @@ -62,74 +80,61 @@ 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));
}
std::array<uint8_t, 37> hmac_payload = {};

std::unique_ptr<HDKeyEd25519> HDKeyEd25519::DeriveChildFromPath(
const std::string& path) {
if (path_ != kMasterNode) {
VLOG(0) << "must derive only from master key";
return nullptr;
}
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.WriteU8BigEndian(0);
span_writer.Write(GetPrivateKeyAsSpan());
span_writer.WriteU32BigEndian(index + kHardenedOffset);
DCHECK_EQ(span_writer.remaining(), 0u);

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;
}
return DeriveFromHmacPayload(chain_code_, hmac_payload);
}

return std::make_unique<HDKeyEd25519>(path, std::move(child_private_key));
// static
std::unique_ptr<HDKeyEd25519> HDKeyEd25519::GenerateFromSeed(
base::span<const uint8_t> seed) {
return DeriveFromHmacPayload(base::byte_span_from_cstring(kMasterSecret),
seed);
}

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>();
std::vector<uint8_t> signature(ED25519_SIGNATURE_LEN);
if (!ED25519_sign(signature.data(), msg.data(), msg.size(),
key_pair_.data())) {
return {};
}
auto signature_bytes = signature_result->unwrap().to_bytes();
return std::vector<uint8_t>(signature_bytes.begin(), signature_bytes.end());
return 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;
base::span<const uint8_t, kEd25519PrivateKeySize>
Copy link
Collaborator

@bridiver bridiver Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these changes to the method signature necessary for something in the new implementation? Please don't change anything that doesn't strictly need to be changed. Any other changes should be follow-ups

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed

HDKeyEd25519::GetPrivateKeyAsSpan() const {
return base::span(key_pair_).first<kEd25519PrivateKeySize>();
}

base::span<const uint8_t, kEd25519PublicKeySize>
HDKeyEd25519::GetPublicKeyAsSpan() const {
return base::span(key_pair_).last<kEd25519PublicKeySize>();
}

std::vector<uint8_t> HDKeyEd25519::GetPrivateKeyBytes() const {
auto secret_key = private_key_->unwrap().secret_key_raw();
return {secret_key.begin(), secret_key.end()};
return base::ToVector(GetPrivateKeyAsSpan());
}

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

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
36 changes: 25 additions & 11 deletions components/brave_wallet/browser/internal/hd_key_ed25519.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,26 @@
#include <vector>

#include "base/containers/span.h"
#include "brave/components/brave_wallet/rust/lib.rs.h"
#include "base/gtest_prod_util.h"

namespace brave_wallet {

// This class implement basic EdDSA over ed25519 functionality of bip32-ed25519
// https://www.rfc-editor.org/rfc/rfc8032.html#section-5.1.5
inline constexpr size_t kEd25519PrivateKeySize = 32;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ED25519_PRIVATE_KEY_LEN, ED25519_PUBLIC_KEY_LEN
also it looks like kEd25519PrivateKeySize is incorrect and should be 64?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

32 bytes is private key size per spec.

64 bytes is what OpenSSL names as private_key which is actually a pair of private and public keys.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had left a comment elsewhere about this, but I guess both me and Brian were confused, so I guess maybe we need some better naming, the comment above could also better explain this distinction, besides the link to the rfc.

inline constexpr size_t kEd25519PublicKeySize = 32;
inline constexpr size_t kEd25519KeyPairSize =
kEd25519PrivateKeySize + kEd25519PublicKeySize;

// https://github.com/satoshilabs/slips/blob/de7f963959ccfc80256fb5e001f64ce9ada9fba1/slip-0010.md?plain=1#L116-L117
inline constexpr size_t kSlip10ChainCodeSize = 32;

// 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<Ed25519DalekExtendedSecretKeyResult>);
HDKeyEd25519();
~HDKeyEd25519();
HDKeyEd25519(const HDKeyEd25519&) = delete;
HDKeyEd25519& operator=(const HDKeyEd25519&) = delete;
Expand All @@ -31,14 +40,9 @@ class HDKeyEd25519 {
static std::unique_ptr<HDKeyEd25519> GenerateFromPrivateKey(
base::span<const uint8_t> private_key);

std::string GetPath() const;
std::unique_ptr<HDKeyEd25519> DeriveHardenedChild(uint32_t index);

// If path contains normal index, nullptr will be returned
std::unique_ptr<HDKeyEd25519> DeriveChildFromPath(const std::string& path);
std::vector<uint8_t> Sign(base::span<const uint8_t> msg);
bool VerifyForTesting(base::span<const uint8_t> msg,
base::span<const uint8_t> sig);

std::vector<uint8_t> GetPrivateKeyBytes() const;
std::vector<uint8_t> GetPublicKeyBytes() const;
Expand All @@ -47,8 +51,18 @@ class HDKeyEd25519 {
std::string GetBase58EncodedKeypair() const;

private:
std::string path_;
rust::Box<Ed25519DalekExtendedSecretKeyResult> private_key_;
FRIEND_TEST_ALL_PREFIXES(HDKeyEd25519UnitTest, TestVector1);
FRIEND_TEST_ALL_PREFIXES(HDKeyEd25519UnitTest, TestVector2);

base::span<const uint8_t, kEd25519PrivateKeySize> GetPrivateKeyAsSpan() const;
base::span<const uint8_t, kEd25519PublicKeySize> GetPublicKeyAsSpan() const;

static std::unique_ptr<HDKeyEd25519> DeriveFromHmacPayload(
base::span<const uint8_t> key,
base::span<const uint8_t> data);

std::array<uint8_t, kEd25519KeyPairSize> key_pair_ = {};
std::array<uint8_t, kSlip10ChainCodeSize> chain_code_ = {};
};

} // namespace brave_wallet
Expand Down
Loading
Loading