Skip to content

Commit

Permalink
Slip-0023 implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
supermassive committed Jan 10, 2025
1 parent df7d7cf commit 8d42cbd
Show file tree
Hide file tree
Showing 9 changed files with 590 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* 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 "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

// Produces pubkey form scalar.
// `scalar` must be pruned. https://www.rfc-editor.org/rfc/rfc8032.html#section-5.1.5
// See `ED25519_keypair_from_seed` as origin.
void ED25519_pubkey_from_scalar(uint8_t out_public_key[32],
const uint8_t scalar[32]) {
ge_p3 A;

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

CONSTTIME_DECLASSIFY(out_public_key, 32);
}

// Same as `ED25519_sign` but without hashing private key. `scalar` and `prefix`
// come from ED25519_BIP32 algorithm.
// `scalar` must be 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]) {
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) 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 "src/third_party/boringssl/src/include/openssl/curve25519.h" // IWYU pragma: export

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

OPENSSL_EXPORT void 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
5 changes: 5 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,11 @@ DerivationIndex DerivationIndex::Hardened(uint32_t index) {
return DerivationIndex(index, true);
}

// static
DerivationIndex DerivationIndex::FromRawValueForTesting(uint32_t index) {
return DerivationIndex(index % kHardenedOffset, index / kHardenedOffset);
}

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
235 changes: 235 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,235 @@
/* 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_ed25519_slip23.h"

#include <array>
#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 "brave/components/brave_wallet/common/hash_utils.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 {

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

// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-eddsa-05#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 expands
// 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/draft-irtf-cfrg-eddsa-05#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::array<uint8_t, 32> PubkeyFromScalar(base::span<const uint8_t, 32> scalar) {
DCHECK(IsValidEd25519Scalar(scalar));
std::array<uint8_t, 32> public_key;
ED25519_pubkey_from_scalar(public_key.data(), scalar.data());
return public_key;
}

DerivedHmacs DeriveHardenedHmacs(
uint32_t index,
base::span<const uint8_t, kSlip23ScalarSize> scalar,
base::span<const uint8_t, kSlip23PrefixSize> prefix,
base::span<const uint8_t> cc) {
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(index);

DerivedHmacs result;

data[0] = 0x00;
result.z_hmac = crypto::hmac::SignSha512(cc, data);

data[0] = 0x01;
result.cc_hmac = crypto::hmac::SignSha512(cc, data);

return result;
}

DerivedHmacs DeriveNormalHmacs(
uint32_t index,
base::span<const uint8_t, kSlip23ScalarSize> scalar,
base::span<const uint8_t, kEd25519PublicKeySize> public_key,
base::span<const uint8_t> cc) {
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(index);

DerivedHmacs result;

data[0] = 0x02;
result.z_hmac = crypto::hmac::SignSha512(cc, data);

data[0] = 0x03;
result.cc_hmac = crypto::hmac::SignSha512(cc, data);

return result;
}

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(scalar_).copy_from(scalar);
base::span(prefix_).copy_from(prefix);
base::span(chain_code_).copy_from(chain_code);
public_key_ = PubkeyFromScalar(scalar_);
}

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

auto derived =
index.is_hardened()
? DeriveHardenedHmacs(*raw_index_value, scalar_, prefix_, chain_code_)
: DeriveNormalHmacs(*raw_index_value, scalar_, public_key_,
chain_code_);

return base::WrapUnique(new HDKeyEd25519Slip23(
CalculateDerivedScalar(
scalar_,
base::span(derived.z_hmac).first<kSlip23DerivationScalarSize>()),
CalculateDerivedPrefix(
prefix_, base::span(derived.z_hmac).last<kSlip23PrefixSize>()),
CalculateDerivedChainCode(derived.cc_hmac)));
}

// 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);

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

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, kEd25519PublicKeySize>
HDKeyEd25519Slip23::GetPublicKeyAsSpan() const {
return base::span(public_key_);
}

} // namespace brave_wallet
Loading

0 comments on commit 8d42cbd

Please sign in to comment.