From ead8e09a056fef23683f55cd2d8636f756e06a91 Mon Sep 17 00:00:00 2001 From: moana Date: Sat, 31 Aug 2024 18:13:30 +0200 Subject: [PATCH] wallet-core: Generate seed from mnemonic Resolves #2231 --- wallet-core/Cargo.toml | 5 ++++ wallet-core/Makefile | 1 + wallet-core/src/error.rs | 22 +++++++++++++++++ wallet-core/src/keys.rs | 50 ++++++++++++++++++++++++++++++--------- wallet-core/src/lib.rs | 4 ++-- wallet-core/tests/keys.rs | 37 ++++++++++++++++++++++++++--- 6 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 wallet-core/src/error.rs diff --git a/wallet-core/Cargo.toml b/wallet-core/Cargo.toml index 9007a178ef..111d29eadc 100644 --- a/wallet-core/Cargo.toml +++ b/wallet-core/Cargo.toml @@ -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"] diff --git a/wallet-core/Makefile b/wallet-core/Makefile index fffa5c6726..d56b5ecfd4 100644 --- a/wallet-core/Makefile +++ b/wallet-core/Makefile @@ -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 diff --git a/wallet-core/src/error.rs b/wallet-core/src/error.rs new file mode 100644 index 0000000000..e905c03ea7 --- /dev/null +++ b/wallet-core/src/error.rs @@ -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) + } +} diff --git a/wallet-core/src/keys.rs b/wallet-core/src/keys.rs index 357e08dc49..00eed2ba78 100644 --- a/wallet-core/src/keys.rs +++ b/wallet-core/src/keys.rs @@ -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; @@ -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 @@ -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 @@ -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, ) -> Vec { index_range @@ -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(); @@ -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(); @@ -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. @@ -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

(phrase: P) -> Result +where + P: Into, +{ + // 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) + } +} diff --git a/wallet-core/src/lib.rs b/wallet-core/src/lib.rs index 98e64c467b..86ed57e583 100644 --- a/wallet-core/src/lib.rs +++ b/wallet-core/src/lib.rs @@ -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 diff --git a/wallet-core/tests/keys.rs b/wallet-core/tests/keys.rs index 2e1cfb8752..c11358863d 100644 --- a/wallet-core/tests/keys.rs +++ b/wallet-core/tests/keys.rs @@ -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]; @@ -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(()) +}