Skip to content
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
2 changes: 2 additions & 0 deletions wallet-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [1.4.0] - 2025-11-06

- Add EIP2334 deterministic account hierarchy derivation support [#3572]
- Added support for generic TransactionData into FFI [#3750]

### Changed
Expand Down Expand Up @@ -42,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- First `dusk-wallet-core` release

<!-- Issues -->
[#3572]: https://github.com/dusk-network/rusk/issues/3572
[#3750]: https://github.com/dusk-network/rusk/issues/3750
[#3681]: https://github.com/dusk-network/rusk/issues/3681
[#3476]: https://github.com/dusk-network/rusk/issues/3476
Expand Down
117 changes: 117 additions & 0 deletions wallet-core/src/keys/eip2334.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// 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 http://mozilla.org/MPL/2.0/.
//
// Copyright (c) DUSK NETWORK. All rights reserved.

//! This module defines the EIP2334 Deterministic Account Hierarchy path for BLS
//! keys as defined at <https://eips.ethereum.org/EIPS/eip-2334>

use alloc::string::{String, ToString};
use alloc::vec::Vec;

use dusk_core::signatures::bls::{
PublicKey as BlsPublicKey, SecretKey as BlsSecretKey,
};

use crate::keys::eip2333;

/// The base derivation path for Dusk EIP-2333 BLS12-381 derivation.
/// `m / purpose / coin_type / account / use`
///
/// `purpose` is set to 12381 as per EIP-2334, which is the name of the curve.
/// `coin_type` type is set to 744. The number that Dusk uses.
/// `account` is incremented when the user wants to create a new account i.e.,
/// get a new address.
/// `use` is **always** set to 0 for moonlight. It is set to 1 for staking keys
/// to separate them from moonlight keys.
pub const EIP_2334_BASE_PATH: &str = "m/12381/744/0/0";

/// Converts a given index nummber to the corresponding derivation path of
/// moonlight EIP-2333 BLS12-381 derivation.
pub(crate) fn index_to_path(index: usize) -> String {
let index_str = index.to_string();

// put the index at the correct position (account)
// m/12381/744/index/0
let mut path_parts: Vec<&str> = EIP_2334_BASE_PATH.split('/').collect();
path_parts[3] = &index_str;

path_parts.join("/")
}

/// Generates a [`BlsSecretKey`] from the master secret key and index.
///
/// The key is generated through EIP-2333.
///
/// When generating a new key pair, the [`derive_bls_key_pair`] function is
/// preferred to be used, as it generates both the secret and public key at
/// once.
///
/// # Panics
///
/// This function panics when invariants are violated, which should never
/// happen.
#[must_use]
pub fn derive_bls_sk(master_sk: &BlsSecretKey, index: usize) -> BlsSecretKey {
let path = index_to_path(index);

eip2333::derive_bls_sk(master_sk, &path).expect("Should always succeed")
}

/// Generates the [`BlsSecretKey`] & [`BlsPublicKey`] pair from the given master
/// secret key and index.
///
/// The key is generated through EIP-2333.
///
/// # Panics
///
/// This function panics when invariants are violated, which should never
/// happen.
#[must_use]
pub fn derive_bls_key_pair(
master_sk: &BlsSecretKey,
index: usize,
) -> (BlsSecretKey, BlsPublicKey) {
let path = index_to_path(index);

let sk = eip2333::derive_bls_sk(master_sk, &path)
.expect("Should always succeed");
let pk = BlsPublicKey::from(&sk);

(sk, pk)
}

/// Generates a [`BlsPublicKey`] from the given [`BlsSecretKey`]
///
/// When generating a new key pair, the [`derive_bls_key_pair`] function is
/// preferred to be used, as it generates both the secret and public key at
/// once.
#[must_use]
pub fn derive_bls_pk(sk: &BlsSecretKey) -> BlsPublicKey {
BlsPublicKey::from(sk)
}

#[cfg(test)]
mod tests {
use super::*;

// Test path to index conversion
#[test]
fn test_index_to_path_conversion() {
let path = "m/12381/744/0/0";

let indexes = index_to_path(0);
assert_eq!(indexes, path);

let path = "m/12381/744/1/0";

let indexes = index_to_path(1);
assert_eq!(indexes, path);

let path = "m/12381/744/150/0";

let indexes = index_to_path(150);
assert_eq!(indexes, path);
}
}
128 changes: 128 additions & 0 deletions wallet-core/src/keys/legacy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// 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 http://mozilla.org/MPL/2.0/.
//
// Copyright (c) DUSK NETWORK. All rights reserved.

//! Module to generate phoenix and moonlight keys from a seed and index based on
//! the legacy key derivation method.

use alloc::vec::Vec;
use core::ops::Range;

use dusk_core::signatures::bls::{
PublicKey as BlsPublicKey, SecretKey as BlsSecretKey,
};
use dusk_core::transfer::phoenix::{
PublicKey as PhoenixPublicKey, SecretKey as PhoenixSecretKey,
ViewKey as PhoenixViewKey,
};
use rand_chacha::rand_core::SeedableRng;
use rand_chacha::ChaCha12Rng;
use sha2::{Digest, Sha256};
use zeroize::Zeroize;

use crate::Seed;

/// Generates a [`BlsSecretKey`] from a seed and index.
///
/// The randomness is generated using [`rng_with_index`].
#[must_use]
pub fn derive_bls_sk(seed: &Seed, index: u8) -> BlsSecretKey {
// note that if we change the string used for the rng, all previously
// generated keys will become invalid
// NOTE: When breaking the keys, we will want to change the string too
BlsSecretKey::random(&mut rng_with_index(seed, index, b"SK"))
}

/// Generates a [`BlsPublicKey`] from a seed and index.
///
/// The randomness is generated using [`rng_with_index`].
#[must_use]
pub fn derive_bls_pk(seed: &Seed, index: u8) -> BlsPublicKey {
let mut sk = derive_bls_sk(seed, index);
let pk = BlsPublicKey::from(&sk);
sk.zeroize();

pk
}

/// Generates a [`PhoenixSecretKey`] from a seed and index.
///
/// The randomness is generated using [`rng_with_index`].
#[must_use]
pub fn derive_phoenix_sk(seed: &Seed, index: u8) -> PhoenixSecretKey {
// note that if we change the string used for the rng, all previously
// generated keys will become invalid
// NOTE: When breaking the keys, we will want to change the string too
PhoenixSecretKey::random(&mut rng_with_index(seed, index, b"SSK"))
}

/// Generates multiple [`PhoenixSecretKey`] from a seed and a range of
/// indices.
///
/// The randomness is generated using [`rng_with_index`].
#[must_use]
pub fn derive_multiple_phoenix_sk(
seed: &Seed,
index_range: Range<u8>,
) -> Vec<PhoenixSecretKey> {
index_range
.map(|index| derive_phoenix_sk(seed, index))
.collect()
}

/// Generates a [`PhoenixPublicKey`] from its seed and index.
///
/// First the [`PhoenixSecretKey`] is derived with [`derive_phoenix_sk`],
/// then the public key is generated from it and the secret key is
/// erased from memory.
#[must_use]
pub fn derive_phoenix_pk(seed: &Seed, index: u8) -> PhoenixPublicKey {
let mut sk = derive_phoenix_sk(seed, index);
let pk = PhoenixPublicKey::from(&sk);
sk.zeroize();

pk
}

/// Generates a [`PhoenixViewKey`] from its seed and index.
///
/// First the [`PhoenixSecretKey`] is derived with [`derive_phoenix_sk`],
/// then the view key is generated from it and the secret key is erased
/// from memory.
#[must_use]
pub fn derive_phoenix_vk(seed: &Seed, index: u8) -> PhoenixViewKey {
let mut sk = derive_phoenix_sk(seed, index);
let vk = PhoenixViewKey::from(&sk);
sk.zeroize();

vk
}

/// Creates a secure RNG from a seed with embedded index and termination
/// constant.
///
/// First the `seed` and then the little-endian representation of the key's
/// `index` are passed through SHA-256. A constant is then mixed in and the
/// resulting hash is then used to seed a `ChaCha12` CSPRNG, which is
/// subsequently used to generate the key.
#[must_use]
pub fn rng_with_index(
seed: &Seed,
index: u8,
termination: &[u8],
) -> ChaCha12Rng {
// NOTE: to not break the test-keys, we cast to a u64 here. Once we are
// ready to use the new keys, the index should not be cast to a u64
// anymore.
let index = u64::from(index);
let mut hash = Sha256::new();

hash.update(seed);
hash.update(index.to_le_bytes());
hash.update(termination);

let hash = hash.finalize().into();
ChaCha12Rng::from_seed(hash)
}
126 changes: 9 additions & 117 deletions wallet-core/src/keys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,121 +7,13 @@
//! Utilities to derive keys from the seed.

pub mod eip2333;

use alloc::vec::Vec;
use core::ops::Range;

use dusk_core::signatures::bls::{
PublicKey as BlsPublicKey, SecretKey as BlsSecretKey,
pub mod eip2334;
pub mod legacy;

// Re-export all phoenix functions, as they are not influenced by EIP-2333
// Temporarily Re-export bls functions as well, until we migrate consuming apps
// to using EIP-2334
pub use legacy::{
derive_bls_pk, derive_bls_sk, derive_multiple_phoenix_sk,
derive_phoenix_pk, derive_phoenix_sk, derive_phoenix_vk,
};
use dusk_core::transfer::phoenix::{
PublicKey as PhoenixPublicKey, SecretKey as PhoenixSecretKey,
ViewKey as PhoenixViewKey,
};
use rand_chacha::rand_core::SeedableRng;
use rand_chacha::ChaCha12Rng;
use sha2::{Digest, Sha256};
use zeroize::Zeroize;

use crate::Seed;

/// Generates a [`BlsSecretKey`] from a seed and index.
///
/// The randomness is generated using [`rng_with_index`].
#[must_use]
pub fn derive_bls_sk(seed: &Seed, index: u8) -> BlsSecretKey {
// note that if we change the string used for the rng, all previously
// generated keys will become invalid
// NOTE: When breaking the keys, we will want to change the string too
BlsSecretKey::random(&mut rng_with_index(seed, index, b"SK"))
}

/// Generates a [`BlsPublicKey`] from a seed and index.
///
/// The randomness is generated using [`rng_with_index`].
#[must_use]
pub fn derive_bls_pk(seed: &Seed, index: u8) -> BlsPublicKey {
let mut sk = derive_bls_sk(seed, index);
let pk = BlsPublicKey::from(&sk);
sk.zeroize();

pk
}

/// Generates a [`PhoenixSecretKey`] from a seed and index.
///
/// The randomness is generated using [`rng_with_index`].
#[must_use]
pub fn derive_phoenix_sk(seed: &Seed, index: u8) -> PhoenixSecretKey {
// note that if we change the string used for the rng, all previously
// generated keys will become invalid
// NOTE: When breaking the keys, we will want to change the string too
PhoenixSecretKey::random(&mut rng_with_index(seed, index, b"SSK"))
}

/// Generates multiple [`PhoenixSecretKey`] from a seed and a range of indices.
///
/// The randomness is generated using [`rng_with_index`].
#[must_use]
pub fn derive_multiple_phoenix_sk(
seed: &Seed,
index_range: Range<u8>,
) -> Vec<PhoenixSecretKey> {
index_range
.map(|index| derive_phoenix_sk(seed, index))
.collect()
}

/// Generates a [`PhoenixPublicKey`] from its seed and index.
///
/// First the [`PhoenixSecretKey`] is derived with [`derive_phoenix_sk`], then
/// the public key is generated from it and the secret key is erased from
/// memory.
#[must_use]
pub fn derive_phoenix_pk(seed: &Seed, index: u8) -> PhoenixPublicKey {
let mut sk = derive_phoenix_sk(seed, index);
let pk = PhoenixPublicKey::from(&sk);
sk.zeroize();

pk
}

/// Generates a [`PhoenixViewKey`] from its seed and index.
///
/// First the [`PhoenixSecretKey`] is derived with [`derive_phoenix_sk`], then
/// the view key is generated from it and the secret key is erased from memory.
#[must_use]
pub fn derive_phoenix_vk(seed: &Seed, index: u8) -> PhoenixViewKey {
let mut sk = derive_phoenix_sk(seed, index);
let vk = PhoenixViewKey::from(&sk);
sk.zeroize();

vk
}

/// Creates a secure RNG from a seed with embedded index and termination
/// constant.
///
/// First the `seed` and then the little-endian representation of the key's
/// `index` are passed through SHA-256. A constant is then mixed in and the
/// resulting hash is then used to seed a `ChaCha12` CSPRNG, which is
/// subsequently used to generate the key.
#[must_use]
pub fn rng_with_index(
seed: &Seed,
index: u8,
termination: &[u8],
) -> ChaCha12Rng {
// NOTE: to not break the test-keys, we cast to a u64 here. Once we are
// ready to use the new keys, the index should not be cast to a u64
// anymore.
let index = u64::from(index);
let mut hash = Sha256::new();

hash.update(seed);
hash.update(index.to_le_bytes());
hash.update(termination);

let hash = hash.finalize().into();
ChaCha12Rng::from_seed(hash)
}
Loading
Loading