Skip to content

Commit

Permalink
wallet-core: Generate seed from mnemonic
Browse files Browse the repository at this point in the history
Resolves #2231
  • Loading branch information
moCello committed Sep 1, 2024
1 parent 573406d commit ead8e09
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 16 deletions.
5 changes: 5 additions & 0 deletions wallet-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ ff = { version = "0.13", default-features = false }
poseidon-merkle = { version = "0.7", features = ["rkyv-impl"] }
execution-core = { version = "0.1", path = "../execution-core/" }

# std dependencies
tiny-bip39 = {version = "1", optional = true }

[dev-dependencies]
rand = "0.8"

[features]
default = ["std"]
std = ["tiny-bip39"]
1 change: 1 addition & 0 deletions wallet-core/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ wasm: ## Build the WASM files
CARGO_TARGET_DIR=$(TARGET_DIR) \
cargo +dusk build \
--release \
--no-default-features \
--color=always \
-Z build-std=core,alloc \
--target wasm64-unknown-unknown
Expand Down
22 changes: 22 additions & 0 deletions wallet-core/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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.

//! Error-type for wallet-core.
use core::fmt;

/// The execution-core error type.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
/// Recovery phrase is not valid
InvalidMnemonicPhrase,
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Wallet-Core Error: {:?}", &self)
}
}
50 changes: 39 additions & 11 deletions wallet-core/src/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
use alloc::vec::Vec;
use core::ops::Range;

#[cfg(feature = "std")]
use bip39::{Language, Mnemonic};

use rand_chacha::{rand_core::SeedableRng, ChaCha12Rng};
use sha2::{Digest, Sha256};
use zeroize::Zeroize;
Expand All @@ -21,13 +24,17 @@ use execution_core::{
},
};

use crate::RNG_SEED;
/// Length of the seed of the generated rng.
const RNG_SEED: usize = 64;

/// The seed used to generate the keys.
pub type Seed = [u8; RNG_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: &[u8; RNG_SEED], index: u8) -> BlsSecretKey {
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
Expand All @@ -38,7 +45,7 @@ pub fn derive_bls_sk(seed: &[u8; RNG_SEED], index: u8) -> BlsSecretKey {
///
/// The randomness is generated using [`rng_with_index`].
#[must_use]
pub fn derive_phoenix_sk(seed: &[u8; RNG_SEED], index: u8) -> PhoenixSecretKey {
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
Expand All @@ -50,7 +57,7 @@ pub fn derive_phoenix_sk(seed: &[u8; RNG_SEED], index: u8) -> PhoenixSecretKey {
/// The randomness is generated using [`rng_with_index`].
#[must_use]
pub fn derive_multiple_phoenix_sk(
seed: &[u8; RNG_SEED],
seed: &Seed,
index_range: Range<u8>,
) -> Vec<PhoenixSecretKey> {
index_range
Expand All @@ -64,7 +71,7 @@ pub fn derive_multiple_phoenix_sk(
/// the public key is generated from it and the secret key is erased from
/// memory.
#[must_use]
pub fn derive_phoenix_pk(seed: &[u8; RNG_SEED], index: u8) -> PhoenixPublicKey {
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();
Expand All @@ -77,7 +84,7 @@ pub fn derive_phoenix_pk(seed: &[u8; RNG_SEED], index: u8) -> PhoenixPublicKey {
/// 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: &[u8; RNG_SEED], index: u8) -> PhoenixViewKey {
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();
Expand All @@ -93,11 +100,7 @@ pub fn derive_phoenix_vk(seed: &[u8; RNG_SEED], index: u8) -> PhoenixViewKey {
/// 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: &[u8; RNG_SEED],
index: u8,
termination: &[u8],
) -> ChaCha12Rng {
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.
Expand All @@ -111,3 +114,28 @@ pub fn rng_with_index(
let hash = hash.finalize().into();
ChaCha12Rng::from_seed(hash)
}

/// Creates a new seed derived from a valid BIP39 mnemonic phrase.
#[cfg(feature = "std")]
pub fn seed_from_mnemonic<P>(phrase: P) -> Result<Seed, crate::Error>
where
P: Into<String>,
{
// generate mnemonic
let phrase: String = phrase.into();
let try_mnem = Mnemonic::from_phrase(&phrase, Language::English);

if let Ok(mnemonic) = try_mnem {
// derive the mnemonic seed
let bip39_seed = bip39::Seed::new(&mnemonic, "");

// Generate a Seed type from the mnemonic Seed bytes
let mut seed = [0u8; RNG_SEED];
seed.copy_from_slice(bip39_seed.as_bytes());

// return new wallet instance
Ok(seed)
} else {
Err(crate::Error::InvalidMnemonicPhrase)
}
}
4 changes: 2 additions & 2 deletions wallet-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ extern crate alloc;
pub mod keys;
pub mod transaction;

/// Length of the seed of the generated rng.
pub const RNG_SEED: usize = 64;
mod error;
pub use error::Error;

// The maximum amount of input notes that can be spend in one
// phoenix-transaction
Expand Down
37 changes: 34 additions & 3 deletions wallet-core/tests/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@

use dusk_bytes::Serializable;

use wallet_core::keys::{
derive_bls_sk, derive_multiple_phoenix_sk, derive_phoenix_pk,
derive_phoenix_sk, derive_phoenix_vk,
use wallet_core::{
keys::{
derive_bls_sk, derive_multiple_phoenix_sk, derive_phoenix_pk,
derive_phoenix_sk, derive_phoenix_vk, seed_from_mnemonic,
},
Error,
};

const SEED: [u8; 64] = [0; 64];
Expand Down Expand Up @@ -86,3 +89,31 @@ fn test_derive_bls_sk() {
];
assert_eq!(derive_bls_sk(&SEED, INDEX).to_bytes(), sk_bytes);
}

#[test]
fn test_seed_from_mnemonic() -> Result<(), Error> {
let phrase = "park remain person kitchen mule spell knee armed position rail grid ankle";
let seed = seed_from_mnemonic(phrase)?;

let expected = [
104, 198, 132, 173, 250, 171, 90, 57, 70, 89, 159, 147, 207, 142, 133,
101, 48, 182, 157, 50, 109, 9, 34, 245, 93, 239, 191, 96, 248, 217,
146, 204, 136, 50, 182, 115, 192, 152, 85, 232, 62, 33, 90, 52, 137,
80, 206, 214, 90, 82, 64, 61, 252, 21, 236, 26, 124, 114, 93, 91, 137,
19, 255, 112,
];
assert_eq!(seed, expected);

let invalid_phrase = "Now I need a drink alcoholic of course after the heavy chapters involving quantum mechanics";
assert_eq!(
seed_from_mnemonic(invalid_phrase).unwrap_err(),
Error::InvalidMnemonicPhrase
);

assert_eq!(
seed_from_mnemonic("").unwrap_err(),
Error::InvalidMnemonicPhrase
);

Ok(())
}

0 comments on commit ead8e09

Please sign in to comment.