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

Implement SLIP-0023 for Cardano #27152

Open
wants to merge 3 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* 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 "third_party/boringssl/src/include/openssl/curve25519.h"

#include "src/third_party/boringssl/src/crypto/curve25519/curve25519.c"

#ifdef UNSAFE_BUFFERS_BUILD
#pragma allow_unsafe_buffers
#endif

static int IsScalarPruned(const uint8_t scalar[32]) {
return (scalar[0] & 0b00000111) == 0b00000000 &&
(scalar[31] & 0b11000000) == 0b01000000;
}

// Produces pubkey form scalar.
// Function fails if `scalar` is not pruned. https://www.rfc-editor.org/rfc/rfc8032.html#section-5.1.5
// See `ED25519_keypair_from_seed` as origin.
int ED25519_pubkey_from_scalar(uint8_t out_public_key[32],
const uint8_t scalar[32]) {
if (!IsScalarPruned(scalar)) {
return 0;
}

ge_p3 A;

x25519_ge_scalarmult_base(&A, scalar);
ge_p3_tobytes(out_public_key, &A);

CONSTTIME_DECLASSIFY(out_public_key, 32);

return 1;
}

// Same as `ED25519_sign` but without hashing private key. `scalar` and `prefix`
// come from ED25519_BIP32 algorithm.
// Function fails if `scalar` is not pruned. https://www.rfc-editor.org/rfc/rfc8032.html#section-5.1.5
int ED25519_sign_with_scalar_and_prefix(uint8_t out_sig[64],
const uint8_t* message,
size_t message_len,
const uint8_t scalar[32],
const uint8_t prefix[32],
const uint8_t public_key[32]) {
if (!IsScalarPruned(scalar)) {
return 0;
}

SHA512_CTX hash_ctx;
SHA512_Init(&hash_ctx);
SHA512_Update(&hash_ctx, prefix, 32);
SHA512_Update(&hash_ctx, message, message_len);
uint8_t nonce[SHA512_DIGEST_LENGTH];
SHA512_Final(nonce, &hash_ctx);

x25519_sc_reduce(nonce);
ge_p3 R;
x25519_ge_scalarmult_base(&R, nonce);
ge_p3_tobytes(out_sig, &R);

SHA512_Init(&hash_ctx);
SHA512_Update(&hash_ctx, out_sig, 32);
SHA512_Update(&hash_ctx, public_key, 32);
SHA512_Update(&hash_ctx, message, message_len);
uint8_t hram[SHA512_DIGEST_LENGTH];
SHA512_Final(hram, &hash_ctx);

x25519_sc_reduce(hram);
sc_muladd(out_sig + 32, hram, scalar, nonce);

// The signature is computed from the private key, but is public.
CONSTTIME_DECLASSIFY(out_sig, 64);
return 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* 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 "src/third_party/boringssl/src/include/openssl/curve25519.h" // IWYU pragma: export

#if defined(__cplusplus)
extern "C" {
#endif

OPENSSL_EXPORT int ED25519_pubkey_from_scalar(uint8_t out_public_key[32],
const uint8_t scalar[32]);
OPENSSL_EXPORT int ED25519_sign_with_scalar_and_prefix(
uint8_t out_sig[64],
const uint8_t* message,
size_t message_len,
const uint8_t scalar[32],
const uint8_t prefix[32],
const uint8_t public_key[32]);

#if defined(__cplusplus)
} // extern C
#endif
2 changes: 2 additions & 0 deletions components/brave_wallet/browser/internal/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ source_set("hd_key") {
"hd_key.h",
"hd_key_ed25519.cc",
"hd_key_ed25519.h",
"hd_key_ed25519_slip23.cc",
"hd_key_ed25519_slip23.h",
]

visibility = [
Expand Down
9 changes: 9 additions & 0 deletions components/brave_wallet/browser/internal/hd_key_common.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ DerivationIndex DerivationIndex::Hardened(uint32_t index) {
return DerivationIndex(index, true);
}

// static
DerivationIndex DerivationIndex::FromRawValueForTesting(uint32_t index) {
if (index >= kHardenedOffset) {
return DerivationIndex(index - kHardenedOffset, true);
} else {
return DerivationIndex(index, false);
}
}

bool DerivationIndex::IsValid() const {
return index_ < kHardenedOffset;
}
Expand Down
1 change: 1 addition & 0 deletions components/brave_wallet/browser/internal/hd_key_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class DerivationIndex {
public:
static DerivationIndex Normal(uint32_t index);
static DerivationIndex Hardened(uint32_t index);
static DerivationIndex FromRawValueForTesting(uint32_t index);

bool IsValid() const;

Expand Down
237 changes: 237 additions & 0 deletions components/brave_wallet/browser/internal/hd_key_ed25519_slip23.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
/* Copyright (c) 2025 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at https://mozilla.org/MPL/2.0/. */

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

#include <array>
#include <string>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/containers/span.h"
#include "base/containers/span_writer.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "crypto/hmac.h"
#include "third_party/boringssl/src/include/openssl/curve25519.h"
#include "third_party/boringssl/src/include/openssl/evp.h"

namespace brave_wallet {

namespace {

// https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.5
// requires scalar to follow this requirements 'The lowest 3 bits of the first
// octet are cleared, the highest bit of the last octet is cleared, and the
// second highest bit of the last octet is set'.
// https://input-output-hk.github.io/adrestia/static/Ed25519_BIP.pdf extends
// this requirement to `We admit only those k such that the third highest bit
// of the last byte of k is zero`
base::span<uint8_t, kSlip23ScalarSize> ClampScalarEd25519Bip32(
base::span<uint8_t, kSlip23ScalarSize> scalar) {
scalar[0] &= 0b1111'1000; // The lowest 3 bits of the first
// octet are cleared

scalar[31] &= 0b0111'1111; // highest bit of the last octet is cleared and
scalar[31] &= 0b1101'1111; // third highest bit of the last byte of k is zero

scalar[31] |= 0b0100'0000; // the second highest bit of the last octet is set
return scalar;
}

// https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.5
// requires scalar to follow this requirements 'The lowest 3 bits of the first
// octet are cleared, the highest bit of the last octet is cleared, and the
// second highest bit of the last octet is set'.
bool IsValidEd25519Scalar(base::span<const uint8_t, kSlip23ScalarSize> scalar) {
return (scalar[0] & 0b0000'0111) == 0b0000'0000 &&
(scalar[31] & 0b1100'0000) == 0b0100'0000;
}

std::optional<std::array<uint8_t, 32>> PubkeyFromScalar(
base::span<const uint8_t, 32> scalar) {
DCHECK(IsValidEd25519Scalar(scalar));
std::array<uint8_t, 32> public_key;
if (!ED25519_pubkey_from_scalar(public_key.data(), scalar.data())) {
return std::nullopt;
}
return public_key;
}

std::array<uint8_t, kSlip23ScalarSize> CalculateDerivedScalar(
base::span<const uint8_t, kSlip23ScalarSize> parent_scalar,
base::span<const uint8_t, kSlip23DerivationScalarSize> zl) {
std::array<uint8_t, kSlip23ScalarSize> out = {};

uint8_t carry = 0;
for (auto i = 0u; i < kSlip23DerivationScalarSize; i++) {
uint32_t r = parent_scalar[i] + (zl[i] << 3) + carry;
out[i] = r & 0xff;
carry = r >> 8;
}
for (auto i = kSlip23DerivationScalarSize; i < kSlip23ScalarSize; i++) {
uint32_t r = parent_scalar[i] + carry;
out[i] = r & 0xff;
carry = r >> 8;
}
return out;
}

std::array<uint8_t, kSlip23PrefixSize> CalculateDerivedPrefix(
base::span<const uint8_t, kSlip23PrefixSize> parent_prefix,
base::span<const uint8_t, kSlip23PrefixSize> zr) {
std::array<uint8_t, kSlip23PrefixSize> out = {};

uint8_t carry = 0;
for (auto i = 0u; i < kSlip23PrefixSize; i++) {
uint32_t r = parent_prefix[i] + zr[i] + carry;
out[i] = r;
carry = r >> 8;
}

return out;
}

std::array<uint8_t, kSlip23ChainCodeSize> CalculateDerivedChainCode(
base::span<const uint8_t, crypto::hash::kSha512Size> z_hmac) {
std::array<uint8_t, kSlip23ChainCodeSize> chain_code;
base::span(chain_code)
.copy_from(base::span(z_hmac).last<kSlip23ChainCodeSize>());
return chain_code;
}

} // namespace

HDKeyEd25519Slip23::HDKeyEd25519Slip23() = default;
HDKeyEd25519Slip23::~HDKeyEd25519Slip23() = default;

// Child key derivation constructor.
HDKeyEd25519Slip23::HDKeyEd25519Slip23(
base::span<const uint8_t, kSlip23ScalarSize> scalar,
base::span<const uint8_t, kSlip23PrefixSize> prefix,
base::span<const uint8_t, kSlip23ChainCodeSize> chain_code,
base::span<const uint8_t, kEd25519PublicKeySize> public_key) {
base::span(scalar_).copy_from(scalar);
base::span(prefix_).copy_from(prefix);
base::span(chain_code_).copy_from(chain_code);
base::span(public_key_).copy_from(public_key);
}

std::unique_ptr<HDKeyEd25519Slip23> HDKeyEd25519Slip23::DeriveChild(
DerivationIndex index) {
auto raw_index_value = index.GetValue();
if (!raw_index_value) {
return nullptr;
}

std::array<uint8_t, crypto::hash::kSha512Size> z_hmac = {};
std::array<uint8_t, crypto::hash::kSha512Size> cc_hmac = {};

if (index.is_hardened()) {
std::array<uint8_t, 1 + 32 + 32 + 4> data;
auto span_writer = base::SpanWriter(base::span(data));
span_writer.Skip(1u);
span_writer.Write(scalar_);
span_writer.Write(prefix_);
span_writer.WriteU32LittleEndian(*raw_index_value);

data[0] = 0x00;
z_hmac = crypto::hmac::SignSha512(chain_code_, data);
data[0] = 0x01;
cc_hmac = crypto::hmac::SignSha512(chain_code_, data);
} else {
std::array<uint8_t, 1 + 32 + 4> data;
auto span_writer = base::SpanWriter(base::span(data));
span_writer.Skip(1u);
span_writer.Write(public_key_);
span_writer.WriteU32LittleEndian(*raw_index_value);

data[0] = 0x02;
z_hmac = crypto::hmac::SignSha512(chain_code_, data);
data[0] = 0x03;
cc_hmac = crypto::hmac::SignSha512(chain_code_, data);
}

auto derived_scalar = CalculateDerivedScalar(
scalar_, base::span(z_hmac).first<kSlip23DerivationScalarSize>());

auto pubkey = PubkeyFromScalar(derived_scalar);
if (!pubkey) {
return nullptr;
}

return base::WrapUnique(new HDKeyEd25519Slip23(
derived_scalar,
CalculateDerivedPrefix(prefix_,
base::span(z_hmac).last<kSlip23PrefixSize>()),
CalculateDerivedChainCode(cc_hmac), *pubkey));
}

// static
std::unique_ptr<HDKeyEd25519Slip23>
HDKeyEd25519Slip23::GenerateMasterKeyFromBip39Entropy(
base::span<const uint8_t> entropy) {
// https://github.com/cardano-foundation/CIPs/blob/master/CIP-0003/Icarus.md
std::array<uint8_t,
kSlip23ScalarSize + kSlip23PrefixSize + kSlip23ChainCodeSize>
xprv;
std::string passphrase;
if (PKCS5_PBKDF2_HMAC(passphrase.data(), passphrase.size(), entropy.data(),
entropy.size(), 4096, EVP_sha512(), xprv.size(),
xprv.data())) {
} else {
return nullptr;
}

auto xprv_span = base::span(xprv);
auto scalar = ClampScalarEd25519Bip32(xprv_span.first<kSlip23ScalarSize>());
auto pubkey = PubkeyFromScalar(scalar);
if (!pubkey) {
return nullptr;
}

return base::WrapUnique(new HDKeyEd25519Slip23(
scalar, xprv_span.subspan<kSlip23ScalarSize, kSlip23PrefixSize>(),
xprv_span.last<kSlip23ChainCodeSize>(), *pubkey));
}

std::optional<std::array<uint8_t, kEd25519SignatureSize>>
HDKeyEd25519Slip23::Sign(base::span<const uint8_t> msg) {
if (!IsValidEd25519Scalar(scalar_)) {
return std::nullopt;
}

std::array<uint8_t, kEd25519SignatureSize> signature = {};

if (!ED25519_sign_with_scalar_and_prefix(
signature.data(), msg.data(), msg.size(), scalar_.data(),
prefix_.data(), public_key_.data())) {
return std::nullopt;
}
return signature;
}

base::span<const uint8_t, kSlip23ScalarSize>
HDKeyEd25519Slip23::GetScalarAsSpanForTesting() const {
return base::span(scalar_);
}

base::span<const uint8_t, kSlip23PrefixSize>
HDKeyEd25519Slip23::GetPrefixAsSpanForTesting() const {
return base::span(prefix_);
}

base::span<const uint8_t, kSlip23ChainCodeSize>
HDKeyEd25519Slip23::GetChainCodeAsSpanForTesting() const {
return base::span(chain_code_);
}

base::span<const uint8_t, kEd25519PublicKeySize>
HDKeyEd25519Slip23::GetPublicKeyAsSpan() const {
return base::span(public_key_);
}

} // namespace brave_wallet
Loading
Loading