diff --git a/Cargo.lock b/Cargo.lock index c38270e588..c1e82152b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -348,7 +348,7 @@ version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e" dependencies = [ - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -466,9 +466,9 @@ checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" [[package]] name = "bytes" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" dependencies = [ "serde", ] @@ -545,9 +545,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.76" +version = "1.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" +checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" [[package]] name = "cexpr" @@ -998,22 +998,22 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" +checksum = "96bf8df95e795db1a4aca2957ad884a2df35413b24bbeb3114422f3cc21498e8" dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", "crossbeam-utils", - "memoffset", + "memoffset 0.7.1", "scopeguard", ] [[package]] name = "crossbeam-queue" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd42583b04998a5363558e5f9291ee5a5ff6b49944332103f251e7479a82aa7" +checksum = "ebb3d1683412e9be6a15533314f00ec223c0762c522a3f77f048b265aab4470c" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -1021,9 +1021,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.12" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +checksum = "422f23e724af1240ec469ea1e834d87a4b59ce2efe2c6a96256b0c47e2fd86aa" dependencies = [ "cfg-if 1.0.0", ] @@ -1189,7 +1189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12dc3116fe595d7847c701796ac1b189bd86b81f4f593c6f775f9d80fb2e29f4" dependencies = [ "byteorder", - "digest 0.10.5", + "digest 0.10.6", "rand_core 0.6.4", "subtle", "zeroize", @@ -1197,9 +1197,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888" +checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453" dependencies = [ "cc", "cxxbridge-flags", @@ -1209,9 +1209,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3" +checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0" dependencies = [ "cc", "codespan-reporting", @@ -1224,15 +1224,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f" +checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71" [[package]] name = "cxxbridge-macro" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" +checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470" dependencies = [ "proc-macro2", "quote", @@ -1415,9 +1415,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer 0.10.3", "crypto-common", @@ -1563,9 +1563,9 @@ dependencies = [ [[package]] name = "ethnum" -version = "1.3.0" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eac3c0b9fa6eb75255ebb42c0ba3e2210d102a66d2795afef6fed668f373311" +checksum = "0198b9d0078e0f30dedc7acbb21c974e838fc8fae3ee170128658a98cb2c1c04" [[package]] name = "fast-float" @@ -1865,7 +1865,7 @@ version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "fnv", "futures-core", "futures-sink", @@ -1911,7 +1911,7 @@ checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ "base64 0.13.1", "bitflags 1.3.2", - "bytes 1.2.1", + "bytes 1.3.0", "headers-core", "http", "httpdate", @@ -1970,7 +1970,7 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "fnv", "itoa 1.0.4", ] @@ -1981,7 +1981,7 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "http", "pin-project-lite", ] @@ -2019,7 +2019,7 @@ version = "0.14.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "futures-channel", "futures-core", "futures-util", @@ -2055,7 +2055,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "hyper", "native-tls", "tokio", @@ -2140,9 +2140,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg 1.1.0", "hashbrown", @@ -2519,6 +2519,15 @@ dependencies = [ "autocfg 1.1.0", ] +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg 1.1.0", +] + [[package]] name = "merlin" version = "2.0.1" @@ -2738,7 +2747,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "libc", - "memoffset", + "memoffset 0.6.5", ] [[package]] @@ -2982,9 +2991,9 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.4.0" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5bf27447411e9ee3ff51186bf7a08e16c341efdde93f4d823e8844429bed7e" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] name = "packed_simd_2" @@ -3389,7 +3398,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "prost-derive", ] @@ -3399,7 +3408,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "heck 0.3.3", "itertools 0.10.5", "lazy_static", @@ -3432,7 +3441,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "prost", ] @@ -3610,11 +3619,10 @@ dependencies = [ [[package]] name = "rayon" -version = "1.5.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +checksum = "1e060280438193c554f654141c9ea9417886713b7acd75974c85b18a69a88e0b" dependencies = [ - "autocfg 1.1.0", "crossbeam-deque", "either", "rayon-core", @@ -3622,9 +3630,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.3" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -3695,12 +3703,12 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.12" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" +checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" dependencies = [ "base64 0.13.1", - "bytes 1.2.1", + "bytes 1.3.0", "encoding_rs", "futures-core", "futures-util", @@ -3832,9 +3840,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.1" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812a2ec2043c4d6bc6482f5be2ab8244613cac2493d128d36c0759e52a626ab3" +checksum = "203974af07ea769452490ee8de3e5947971efc3a090dca8a779dd432d3fa46a7" dependencies = [ "bitflags 1.3.2", "errno", @@ -4079,9 +4087,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "8e8b3801309262e8184d9687fb697586833e939767aea0dda89f5a8e650e8bd7" dependencies = [ "itoa 1.0.4", "ryu", @@ -4150,7 +4158,7 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -4174,7 +4182,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.5", + "digest 0.10.6", ] [[package]] @@ -4611,7 +4619,7 @@ dependencies = [ "async-trait", "bitflags 1.3.2", "blake2 0.10.5", - "bytes 1.2.1", + "bytes 1.3.0", "chrono", "cidr", "data-encoding", @@ -4758,6 +4766,7 @@ dependencies = [ "tui", "unicode-segmentation", "unicode-width", + "zeroize", ] [[package]] @@ -4904,7 +4913,7 @@ version = "0.40.2" dependencies = [ "anyhow", "bincode", - "bytes 1.2.1", + "bytes 1.3.0", "chrono", "clap 3.2.23", "config", @@ -5376,12 +5385,12 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.21.2" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" dependencies = [ "autocfg 1.1.0", - "bytes 1.2.1", + "bytes 1.3.0", "libc", "memchr", "mio 0.8.5", @@ -5453,7 +5462,7 @@ version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "futures-core", "futures-io", "futures-sink", @@ -5468,7 +5477,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "futures-core", "futures-sink", "pin-project-lite", @@ -5494,7 +5503,7 @@ dependencies = [ "async-stream", "async-trait", "base64 0.13.1", - "bytes 1.2.1", + "bytes 1.3.0", "futures-core", "futures-util", "h2", @@ -5900,7 +5909,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed7b8be92646fc3d18b06147664ebc5f48d222686cb11a8755e561a735aacc6d" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "futures-channel", "futures-util", "headers", diff --git a/applications/tari_console_wallet/Cargo.toml b/applications/tari_console_wallet/Cargo.toml index 1aba3a720f..91d1d542b4 100644 --- a/applications/tari_console_wallet/Cargo.toml +++ b/applications/tari_console_wallet/Cargo.toml @@ -50,6 +50,7 @@ tonic = "0.6.2" tracing = "0.1.26" unicode-segmentation = "1.6.0" unicode-width = "0.1" +zeroize = "1.3.0" [dependencies.tari_core] path = "../../base_layer/core" diff --git a/applications/tari_console_wallet/src/init/mod.rs b/applications/tari_console_wallet/src/init/mod.rs index ed2187b158..5fab553e61 100644 --- a/applications/tari_console_wallet/src/init/mod.rs +++ b/applications/tari_console_wallet/src/init/mod.rs @@ -401,7 +401,7 @@ pub async fn init_wallet( } if let Some(file_name) = seed_words_file_name { let seed_words = wallet.get_seed_words(&MnemonicLanguage::English)?.join(" "); - let _result = fs::write(file_name, seed_words).map_err(|e| { + let _result = fs::write(file_name, seed_words.reveal()).map_err(|e| { ExitError::new( ExitCode::WalletError, &format!("Problem writing seed words to file: {}", e), @@ -549,7 +549,7 @@ fn confirm_seed_words(wallet: &mut WalletSqlite) -> Result<(), ExitError> { println!("WRITE THEM DOWN OR COPY THEM NOW. THIS IS YOUR ONLY CHANCE TO DO SO."); println!(); println!("========================="); - println!("{}", seed_words.join(" ")); + println!("{}", seed_words.join(" ").reveal()); println!("========================="); println!("\x07"); // beep! diff --git a/applications/tari_console_wallet/src/lib.rs b/applications/tari_console_wallet/src/lib.rs index 9de5b00972..5203305558 100644 --- a/applications/tari_console_wallet/src/lib.rs +++ b/applications/tari_console_wallet/src/lib.rs @@ -48,7 +48,8 @@ use tari_common::{ configuration::bootstrap::ApplicationType, exit_codes::{ExitCode, ExitError}, }; -use tari_key_manager::cipher_seed::CipherSeed; +use tari_crypto::tari_utilities::Hidden; +use tari_key_manager::{cipher_seed::CipherSeed, SeedWords}; #[cfg(all(unix, feature = "libtor"))] use tari_libtor::tor::Tor; use tari_shutdown::Shutdown; @@ -213,13 +214,15 @@ fn get_password(config: &ApplicationConfig, cli: &Cli) -> Option { fn get_recovery_seed(boot_mode: WalletBoot, cli: &Cli) -> Result, ExitError> { if matches!(boot_mode, WalletBoot::Recovery) { let seed = if cli.seed_words.is_some() { - let seed_words: Vec = cli - .seed_words - .clone() - .unwrap() - .split_whitespace() - .map(|v| v.to_string()) - .collect(); + // need to zeroize first, to clean up memory of cli.seed_words clone + let seed_words: SeedWords = SeedWords::new( + cli.seed_words + .as_ref() + .unwrap() + .split_whitespace() + .map(|s| Hidden::hide(s.to_string())) + .collect(), + ); get_seed_from_seed_words(seed_words)? } else { prompt_private_key_from_seed_words()? diff --git a/applications/tari_console_wallet/src/recovery.rs b/applications/tari_console_wallet/src/recovery.rs index 35e2df9ca4..3fdf55edfa 100644 --- a/applications/tari_console_wallet/src/recovery.rs +++ b/applications/tari_console_wallet/src/recovery.rs @@ -27,7 +27,8 @@ use futures::FutureExt; use log::*; use rustyline::Editor; use tari_common::exit_codes::{ExitCode, ExitError}; -use tari_key_manager::{cipher_seed::CipherSeed, mnemonic::Mnemonic}; +use tari_crypto::tari_utilities::Hidden; +use tari_key_manager::{cipher_seed::CipherSeed, mnemonic::Mnemonic, SeedWords}; use tari_shutdown::Shutdown; use tari_utilities::hex::Hex; use tari_wallet::{ @@ -37,6 +38,7 @@ use tari_wallet::{ WalletSqlite, }; use tokio::sync::broadcast; +use zeroize::Zeroizing; use crate::wallet_modes::PeerConfig; @@ -51,8 +53,9 @@ pub fn prompt_private_key_from_seed_words() -> Result { println!("Recovery Mode"); println!(); println!("Type or paste all of your seed words on one line, only separated by spaces."); - let input = rl.readline(">> ").map_err(|e| ExitError::new(ExitCode::IOError, e))?; - let seed_words: Vec = input.split_whitespace().map(str::to_string).collect(); + let input = Zeroizing::new(rl.readline(">> ").map_err(|e| ExitError::new(ExitCode::IOError, e))?); + let seed_words: SeedWords = + SeedWords::new(input.split_whitespace().map(|s| Hidden::hide(s.to_string())).collect()); match CipherSeed::from_mnemonic(&seed_words, None) { Ok(seed) => break Ok(seed), @@ -66,7 +69,7 @@ pub fn prompt_private_key_from_seed_words() -> Result { } /// Return seed matching the seed words. -pub fn get_seed_from_seed_words(seed_words: Vec) -> Result { +pub fn get_seed_from_seed_words(seed_words: SeedWords) -> Result { debug!(target: LOG_TARGET, "Return seed derived from the provided seed words"); match CipherSeed::from_mnemonic(&seed_words, None) { Ok(seed) => Ok(seed), diff --git a/base_layer/key_manager/src/cipher_seed.rs b/base_layer/key_manager/src/cipher_seed.rs index 57bf8e1214..71c1dbce0f 100644 --- a/base_layer/key_manager/src/cipher_seed.rs +++ b/base_layer/key_manager/src/cipher_seed.rs @@ -20,7 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::{convert::TryFrom, mem::size_of}; +use std::{convert::TryFrom, mem::size_of, str::FromStr}; use argon2; use chacha20::{ @@ -34,12 +34,16 @@ use rand::{rngs::OsRng, RngCore}; use serde::{Deserialize, Serialize}; use subtle::ConstantTimeEq; use tari_crypto::hash::blake2::Blake256; +use tari_utilities::{hidden::Hidden, safe_array::SafeArray, SafePassword}; use zeroize::{Zeroize, Zeroizing}; use crate::{ error::KeyManagerError, mac_domain_hasher, mnemonic::{from_bytes, to_bytes, to_bytes_with_language, Mnemonic, MnemonicLanguage}, + CipherSeedEncryptionKey, + CipherSeedMacKey, + SeedWords, LABEL_ARGON_ENCODING, LABEL_CHACHA20_ENCODING, LABEL_MAC_GENERATION, @@ -121,7 +125,7 @@ pub struct CipherSeed { } // This is a separate type to make the linter happy -type DerivedCipherSeedKeys = Result<(Zeroizing>, Zeroizing>), KeyManagerError>; +type DerivedCipherSeedKeys = Result<(CipherSeedEncryptionKey, CipherSeedMacKey), KeyManagerError>; impl CipherSeed { #[cfg(not(target_arch = "wasm32"))] @@ -165,9 +169,12 @@ impl CipherSeed { } /// Generate an encrypted seed from a passphrase - pub fn encipher(&self, passphrase: Option) -> Result, KeyManagerError> { + pub fn encipher(&self, passphrase: Option) -> Result, KeyManagerError> { // Derive encryption and MAC keys from passphrase and main salt - let passphrase = Zeroizing::new(passphrase.unwrap_or_else(|| DEFAULT_CIPHER_SEED_PASSPHRASE.to_string())); + let passphrase = passphrase.unwrap_or_else(|| { + SafePassword::from_str(DEFAULT_CIPHER_SEED_PASSPHRASE) + .expect("Failed to parse default cipher seed passphrase to SafePassword") + }); let (encryption_key, mac_key) = Self::derive_keys(&passphrase, self.salt.as_ref())?; // Generate the MAC @@ -176,7 +183,7 @@ impl CipherSeed { self.entropy.as_ref(), CIPHER_SEED_VERSION, self.salt.as_ref(), - mac_key.as_ref(), + &mac_key, )?; // Assemble the secret data to be encrypted: birthday, entropy, MAC @@ -188,7 +195,7 @@ impl CipherSeed { secret_data.extend(&mac); // Encrypt the secret data - Self::apply_stream_cipher(&mut secret_data, encryption_key.as_ref(), self.salt.as_ref())?; + Self::apply_stream_cipher(&mut secret_data, &encryption_key, self.salt.as_ref())?; // Assemble the final seed: version, main salt, secret data, checksum let mut encrypted_seed = @@ -206,7 +213,10 @@ impl CipherSeed { } /// Recover a seed from encrypted data and a passphrase - pub fn from_enciphered_bytes(encrypted_seed: &[u8], passphrase: Option) -> Result { + pub fn from_enciphered_bytes( + encrypted_seed: &[u8], + passphrase: Option, + ) -> Result { // Check the length: version, birthday, entropy, MAC, salt, checksum if encrypted_seed.len() != 1 + CIPHER_SEED_BIRTHDAY_BYTES + @@ -241,7 +251,10 @@ impl CipherSeed { } // Derive encryption and MAC keys from passphrase and main salt - let passphrase = Zeroizing::new(passphrase.unwrap_or_else(|| DEFAULT_CIPHER_SEED_PASSPHRASE.to_string())); + let passphrase = passphrase.unwrap_or_else(|| { + SafePassword::from_str(DEFAULT_CIPHER_SEED_PASSPHRASE) + .expect("Failed to parse default cipher seed passphrase to SafePassword") + }); let salt: Box<[u8; CIPHER_SEED_MAIN_SALT_BYTES]> = encrypted_seed .split_off(1 + CIPHER_SEED_BIRTHDAY_BYTES + CIPHER_SEED_ENTROPY_BYTES + CIPHER_SEED_MAC_BYTES) .into_boxed_slice() @@ -251,7 +264,7 @@ impl CipherSeed { // Decrypt the secret data: birthday, entropy, MAC let mut secret_data = Zeroizing::new(encrypted_seed.split_off(1)); - Self::apply_stream_cipher(&mut secret_data, encryption_key.as_ref(), salt.as_ref())?; + Self::apply_stream_cipher(&mut secret_data, &encryption_key, salt.as_ref())?; // Parse secret data let mac = secret_data.split_off(CIPHER_SEED_BIRTHDAY_BYTES + CIPHER_SEED_ENTROPY_BYTES); @@ -266,13 +279,7 @@ impl CipherSeed { let birthday = u16::from_le_bytes(birthday_bytes); // Generate the MAC - let expected_mac = Self::generate_mac( - &birthday_bytes, - entropy.as_ref(), - version, - salt.as_ref(), - mac_key.as_ref(), - )?; + let expected_mac = Self::generate_mac(&birthday_bytes, entropy.as_ref(), version, salt.as_ref(), &mac_key)?; // Verify the MAC in constant time to avoid leaking data if mac.ct_eq(&expected_mac).unwrap_u8() == 0 { @@ -288,7 +295,11 @@ impl CipherSeed { } /// Encrypt or decrypt data using ChaCha20 - fn apply_stream_cipher(data: &mut [u8], encryption_key: &[u8], salt: &[u8]) -> Result<(), KeyManagerError> { + fn apply_stream_cipher( + data: &mut [u8], + encryption_key: &CipherSeedEncryptionKey, + salt: &[u8], + ) -> Result<(), KeyManagerError> { // The ChaCha20 nonce is derived from the main salt let encryption_nonce = mac_domain_hasher::(LABEL_CHACHA20_ENCODING) .chain(salt) @@ -296,7 +307,10 @@ impl CipherSeed { let encryption_nonce = &encryption_nonce.as_ref()[..size_of::()]; // Encrypt/decrypt the data - let mut cipher = ChaCha20::new(Key::from_slice(encryption_key), Nonce::from_slice(encryption_nonce)); + let mut cipher = ChaCha20::new( + Key::from_slice(encryption_key.reveal()), + Nonce::from_slice(encryption_nonce), + ); cipher.apply_keystream(data); Ok(()) @@ -318,7 +332,7 @@ impl CipherSeed { entropy: &[u8], cipher_seed_version: u8, salt: &[u8], - mac_key: &[u8], + mac_key: &CipherSeedMacKey, ) -> Result, KeyManagerError> { // Check all lengths are valid if birthday.len() != CIPHER_SEED_BIRTHDAY_BYTES { @@ -336,14 +350,14 @@ impl CipherSeed { .chain(entropy) .chain(&[cipher_seed_version]) .chain(salt) - .chain(mac_key) + .chain(mac_key.reveal()) .finalize() .as_ref()[..CIPHER_SEED_MAC_BYTES] .to_vec()) } /// Use Argon2 to derive encryption and MAC keys from a passphrase and main salt - fn derive_keys(passphrase: &str, salt: &[u8]) -> DerivedCipherSeedKeys { + fn derive_keys(passphrase: &SafePassword, salt: &[u8]) -> DerivedCipherSeedKeys { // The Argon2 salt is derived from the main salt let argon2_salt = mac_domain_hasher::(LABEL_ARGON_ENCODING) .chain(salt) @@ -362,15 +376,23 @@ impl CipherSeed { .map_err(|_| KeyManagerError::CryptographicError("Problem generating Argon2 parameters".to_string()))?; // Derive the main key from the password in place - let mut main_key = Zeroizing::new([0u8; CIPHER_SEED_ENCRYPTION_KEY_BYTES + CIPHER_SEED_MAC_KEY_BYTES]); + let mut main_key = Hidden::hide([0u8; CIPHER_SEED_ENCRYPTION_KEY_BYTES + CIPHER_SEED_MAC_KEY_BYTES]); let hasher = argon2::Argon2::new(argon2::Algorithm::Argon2d, argon2::Version::V0x13, params); hasher - .hash_password_into(passphrase.as_bytes(), argon2_salt, main_key.as_mut()) + .hash_password_into(passphrase.reveal(), argon2_salt, main_key.reveal_mut()) .map_err(|_| KeyManagerError::CryptographicError("Problem generating Argon2 password hash".to_string()))?; // Split off the keys - let encryption_key = Zeroizing::new(main_key.as_ref()[..CIPHER_SEED_ENCRYPTION_KEY_BYTES].to_vec()); - let mac_key = Zeroizing::new(main_key.as_ref()[CIPHER_SEED_ENCRYPTION_KEY_BYTES..].to_vec()); + let mut encryption_key = CipherSeedEncryptionKey::from(SafeArray::default()); + encryption_key + .reveal_mut() + .copy_from_slice(&main_key.reveal()[..CIPHER_SEED_ENCRYPTION_KEY_BYTES]); + + let mut mac_key = CipherSeedMacKey::from(SafeArray::default()); + mac_key + .reveal_mut() + .copy_from_slice(&main_key.reveal()[CIPHER_SEED_ENCRYPTION_KEY_BYTES..]); + Ok((encryption_key, mac_key)) } } @@ -384,34 +406,40 @@ impl Default for CipherSeed { impl Mnemonic for CipherSeed { /// Generates a CipherSeed that represent the provided mnemonic sequence of words, the language of the mnemonic /// sequence is autodetected - fn from_mnemonic(mnemonic_seq: &[String], passphrase: Option) -> Result { + fn from_mnemonic( + mnemonic_seq: &SeedWords, + passphrase: Option, + ) -> Result { let bytes = to_bytes(mnemonic_seq)?; - CipherSeed::from_enciphered_bytes(&bytes, passphrase) + CipherSeed::from_enciphered_bytes(bytes.reveal(), passphrase) } /// Generates a SecretKey that represent the provided mnemonic sequence of words using the specified language fn from_mnemonic_with_language( - mnemonic_seq: &[String], + mnemonic_seq: &SeedWords, language: MnemonicLanguage, - passphrase: Option, + passphrase: Option, ) -> Result { let bytes = to_bytes_with_language(mnemonic_seq, &language)?; - CipherSeed::from_enciphered_bytes(&bytes, passphrase) + CipherSeed::from_enciphered_bytes(bytes.reveal(), passphrase) } /// Generates a mnemonic sequence of words from the provided secret key fn to_mnemonic( &self, language: MnemonicLanguage, - passphrase: Option, - ) -> Result, KeyManagerError> { + passphrase: Option, + ) -> Result { Ok(from_bytes(&self.encipher(passphrase)?, language)?) } } #[cfg(test)] mod test { + use std::str::FromStr; + use crc32fast::Hasher as CrcHasher; + use tari_utilities::{Hidden, SafePassword}; use super::BIRTHDAY_GENESIS_FROM_UNIX_EPOCH; use crate::{ @@ -419,27 +447,34 @@ mod test { error::KeyManagerError, get_birthday_from_unix_epoch_in_seconds, mnemonic::{Mnemonic, MnemonicLanguage}, + SeedWords, }; #[test] fn test_cipher_seed_generation_and_deciphering() { let seed = CipherSeed::new(); - let mut enciphered_seed = seed.encipher(Some("Passphrase".to_string())).unwrap(); + let mut enciphered_seed = seed + .encipher(Some(SafePassword::from_str("Passphrase").unwrap())) + .unwrap(); let deciphered_seed = - CipherSeed::from_enciphered_bytes(&enciphered_seed, Some("Passphrase".to_string())).unwrap(); + CipherSeed::from_enciphered_bytes(&enciphered_seed, Some(SafePassword::from_str("Passphrase").unwrap())) + .unwrap(); assert_eq!(seed, deciphered_seed); - match CipherSeed::from_enciphered_bytes(&enciphered_seed, Some("WrongPassphrase".to_string())) { + match CipherSeed::from_enciphered_bytes( + &enciphered_seed, + Some(SafePassword::from_str("WrongPassphrase").unwrap()), + ) { Err(KeyManagerError::DecryptionFailed) => (), _ => panic!("Version should not match"), } enciphered_seed[0] = CIPHER_SEED_VERSION + 1; // this is an unsupported version - match CipherSeed::from_enciphered_bytes(&enciphered_seed, Some("Passphrase".to_string())) { + match CipherSeed::from_enciphered_bytes(&enciphered_seed, Some(SafePassword::from_str("Passphrase").unwrap())) { Err(KeyManagerError::VersionMismatch) => (), _ => panic!("Version should not match"), } @@ -449,7 +484,7 @@ mod test { // flip some bits enciphered_seed[1] = !enciphered_seed[1]; - match CipherSeed::from_enciphered_bytes(&enciphered_seed, Some("Passphrase".to_string())) { + match CipherSeed::from_enciphered_bytes(&enciphered_seed, Some(SafePassword::from_str("Passphrase").unwrap())) { Err(KeyManagerError::CrcError) => (), _ => panic!("Crc should not match"), } @@ -475,7 +510,7 @@ mod test { enciphered_seed[(n - 4)..].copy_from_slice(&calculated_checksum); // the MAC decryption should fail in this case - match CipherSeed::from_enciphered_bytes(&enciphered_seed, Some("passphrase".to_string())) { + match CipherSeed::from_enciphered_bytes(&enciphered_seed, Some(SafePassword::from_str("passphrase").unwrap())) { Err(KeyManagerError::DecryptionFailed) => (), _ => panic!("Decryption should fail"), } @@ -502,7 +537,7 @@ mod test { enciphered_seed[(n - 4)..].copy_from_slice(&calculated_checksum); // the MAC decryption should fail in this case - match CipherSeed::from_enciphered_bytes(&enciphered_seed, Some("passphrase".to_string())) { + match CipherSeed::from_enciphered_bytes(&enciphered_seed, Some(SafePassword::from_str("passphrase").unwrap())) { Err(KeyManagerError::DecryptionFailed) => (), _ => panic!("Decryption should fail"), } @@ -528,7 +563,7 @@ mod test { enciphered_seed[(n - 4)..].copy_from_slice(&calculated_checksum); // the MAC decryption should fail in this case - match CipherSeed::from_enciphered_bytes(&enciphered_seed, Some("passphrase".to_string())) { + match CipherSeed::from_enciphered_bytes(&enciphered_seed, Some(SafePassword::from_str("passphrase").unwrap())) { Err(KeyManagerError::DecryptionFailed) => (), _ => panic!("Decryption should fail"), } @@ -559,8 +594,9 @@ mod test { "cover", "vote", "federal", "husband", "cave", "alone", "dynamic", "reopen", "visa", "young", "gas", ] .iter() - .map(|x| x.to_string()) - .collect::>(); + .map(|x| Hidden::hide(x.to_string())) + .collect::>>(); + let mnemonic_seq = SeedWords::new(mnemonic_seq); // Language not known match CipherSeed::from_mnemonic(&mnemonic_seq, None) { Ok(_k) => panic!(), @@ -577,18 +613,24 @@ mod test { fn cipher_seed_to_and_from_mnemonic_with_passphrase() { let seed = CipherSeed::new(); let mnemonic_seq = seed - .to_mnemonic(MnemonicLanguage::Spanish, Some("Passphrase".to_string())) + .to_mnemonic( + MnemonicLanguage::Spanish, + Some(SafePassword::from_str("Passphrase").unwrap()), + ) .expect("Couldn't convert CipherSeed to Mnemonic"); - match CipherSeed::from_mnemonic(&mnemonic_seq, Some("Passphrase".to_string())) { + match CipherSeed::from_mnemonic(&mnemonic_seq, Some(SafePassword::from_str("Passphrase").unwrap())) { Ok(mnemonic_seed) => assert_eq!(seed, mnemonic_seed), Err(e) => panic!("Couldn't create CipherSeed from Mnemonic: {}", e), } let mnemonic_seq = seed - .to_mnemonic(MnemonicLanguage::Spanish, Some("Passphrase".to_string())) + .to_mnemonic( + MnemonicLanguage::Spanish, + Some(SafePassword::from_str("Passphrase").unwrap()), + ) .expect("Couldn't convert CipherSeed to Mnemonic"); assert!( - CipherSeed::from_mnemonic(&mnemonic_seq, Some("WrongPassphrase".to_string())).is_err(), + CipherSeed::from_mnemonic(&mnemonic_seq, Some(SafePassword::from_str("WrongPassphrase").unwrap())).is_err(), "Should not be able to derive seed with wrong passphrase" ); } diff --git a/base_layer/key_manager/src/diacritics.rs b/base_layer/key_manager/src/diacritics.rs index 2ed5c30e32..1bb6164f5e 100644 --- a/base_layer/key_manager/src/diacritics.rs +++ b/base_layer/key_manager/src/diacritics.rs @@ -1,13 +1,13 @@ // Copyright 2022 The Tari Project // SPDX-License-Identifier: BSD-3-Clause +use tari_utilities::Hidden; + /// Remove diacritic marks, points and accents on lowercase characters -pub fn remove_diacritics(word: &str) -> String { +pub fn remove_diacritics(word: &str) -> Hidden { // Replace diacritics accents - let clean_string: String = - word.to_lowercase() - .as_str() - .chars() + let clean_string: Hidden = Hidden::hide( + word.chars() .map(|x| match x { 'a' | '\u{24D0}' | '\u{FF41}' | '\u{1E9A}' | '\u{00E0}' | '\u{00E1}' | '\u{00E2}' | '\u{1EA7}' | '\u{1EA5}' | '\u{1EAB}' | '\u{1EA9}' | '\u{00E3}' | '\u{0101}' | '\u{0103}' | '\u{1EB1}' | @@ -77,9 +77,10 @@ pub fn remove_diacritics(word: &str) -> String { '\u{1E95}' | '\u{01B6}' | '\u{0225}' | '\u{0240}' | '\u{2C6C}' | '\u{A763}' => 'z', _ => x, }) - .collect(); + .collect(), + ); // Remove any remaining non-ascii characters - clean_string.replace(|c: char| !c.is_ascii(), "") + Hidden::hide(clean_string.reveal().replace(|c: char| !c.is_ascii(), "")) } #[cfg(test)] @@ -89,17 +90,17 @@ mod test { #[test] fn test_temp() { // Words with Diacretics - assert_eq!(remove_diacritics("ábaco"), "abaco".to_string()); - assert_eq!(remove_diacritics("cúpula"), "cupula".to_string()); - assert_eq!(remove_diacritics("legión"), "legion".to_string()); - assert_eq!(remove_diacritics("sureño"), "sureno".to_string()); - assert_eq!(remove_diacritics("chimère"), "chimere".to_string()); - assert_eq!(remove_diacritics("élève"), "eleve".to_string()); + assert_eq!(remove_diacritics("ábaco").reveal(), "abaco"); + assert_eq!(remove_diacritics("cúpula").reveal(), "cupula"); + assert_eq!(remove_diacritics("legión").reveal(), "legion"); + assert_eq!(remove_diacritics("sureño").reveal(), "sureno"); + assert_eq!(remove_diacritics("chimère").reveal(), "chimere"); + assert_eq!(remove_diacritics("élève").reveal(), "eleve"); // Words without Diacretics - assert_eq!(remove_diacritics("observe"), "observe".to_string()); - assert_eq!(remove_diacritics("response"), "response".to_string()); - assert_eq!(remove_diacritics("bizzarro"), "bizzarro".to_string()); - assert_eq!(remove_diacritics("materasso"), "materasso".to_string()); + assert_eq!(remove_diacritics("observe").reveal(), "observe"); + assert_eq!(remove_diacritics("response").reveal(), "response"); + assert_eq!(remove_diacritics("bizzarro").reveal(), "bizzarro"); + assert_eq!(remove_diacritics("materasso").reveal(), "materasso"); } } diff --git a/base_layer/key_manager/src/key_manager.rs b/base_layer/key_manager/src/key_manager.rs index e66f282d7f..1d24e14376 100644 --- a/base_layer/key_manager/src/key_manager.rs +++ b/base_layer/key_manager/src/key_manager.rs @@ -30,10 +30,11 @@ use tari_crypto::{ keys::SecretKey, tari_utilities::byte_array::ByteArrayError, }; +use zeroize::Zeroize; use crate::{cipher_seed::CipherSeed, mac_domain_hasher, LABEL_DERIVE_KEY}; -#[derive(Clone, Derivative, Serialize, Deserialize)] +#[derive(Clone, Derivative, Serialize, Deserialize, Zeroize)] #[derivative(Debug)] pub struct DerivedKey where K: SecretKey @@ -44,7 +45,7 @@ where K: SecretKey pub key_index: u64, } -#[derive(Clone, Derivative, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Derivative, PartialEq, Serialize, Deserialize, Zeroize)] #[derivative(Debug)] pub struct KeyManager { #[derivative(Debug = "ignore")] diff --git a/base_layer/key_manager/src/lib.rs b/base_layer/key_manager/src/lib.rs index 98c15329d0..33ac743b1d 100644 --- a/base_layer/key_manager/src/lib.rs +++ b/base_layer/key_manager/src/lib.rs @@ -7,6 +7,13 @@ use tari_crypto::{ hash_domain, hashing::{DomainSeparatedHasher, LengthExtensionAttackResistant}, }; +use tari_utilities::{hidden::Hidden, hidden_type, safe_array::SafeArray}; +use zeroize::Zeroize; + +use crate::{ + cipher_seed::{CIPHER_SEED_ENCRYPTION_KEY_BYTES, CIPHER_SEED_MAC_KEY_BYTES}, + error::MnemonicError, +}; pub mod cipher_seed; pub mod diacritics; @@ -32,6 +39,9 @@ pub(crate) fn mac_domain_hasher( DomainSeparatedHasher::::new_with_label(label) } +hidden_type!(CipherSeedEncryptionKey, SafeArray); +hidden_type!(CipherSeedMacKey, SafeArray< u8, CIPHER_SEED_MAC_KEY_BYTES>); + /// Computes the birthday duration, in seconds, from the unix epoch. Currently, birthday is stored /// on the wallet as days since 2022-01-01, mainly to preserve space regarding u16 type. That said, /// for wallet synchronization, it is necessary we are compatible with block timestamps (calculated @@ -39,3 +49,182 @@ pub(crate) fn mac_domain_hasher( pub fn get_birthday_from_unix_epoch_in_seconds(birthday: u16, to_days: u16) -> u64 { u64::from(birthday.saturating_sub(to_days)) * 24 * 60 * 60 + BIRTHDAY_GENESIS_FROM_UNIX_EPOCH } + +#[derive(Debug, Clone)] +pub struct SeedWords { + words: Vec>, +} + +impl PartialEq for SeedWords { + fn eq(&self, other: &Self) -> bool { + (other.len() == self.len()) && + (0..self.len()) + .into_iter() + .all(|i| self.get_word(i).unwrap() == other.get_word(i).unwrap()) + } +} + +impl SeedWords { + pub fn new(words: Vec>) -> Self { + Self { words } + } + + pub fn len(&self) -> usize { + self.words.len() + } + + pub fn get_word(&self, index: usize) -> Result<&String, MnemonicError> { + if index > self.len() - 1 { + return Err(MnemonicError::IndexOutOfBounds); + } + + Ok(self.words[index].reveal()) + } + + pub fn is_empty(&self) -> bool { + self.words.is_empty() + } + + pub fn push(&mut self, word: String) { + let word = Hidden::hide(word); + self.words.push(word); + } + + pub fn join(&self, sep: &str) -> Hidden { + Hidden::hide( + self.words + .iter() + .map(|s| s.reveal().as_str()) + .collect::>() + .join(sep), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + pub fn test_seed_words_len() { + let seed_words = SeedWords::new(vec![]); + assert_eq!(seed_words.len(), 0_usize); + + let seed_words = SeedWords::new(vec![ + Hidden::hide("hi".to_string()), + Hidden::hide("niao".to_string()), + Hidden::hide("hola".to_string()), + Hidden::hide("bonjour".to_string()), + Hidden::hide("olá".to_string()), + ]); + + assert_eq!(seed_words.len(), 5_usize); + } + + #[test] + pub fn test_seed_words_get_word_at_index() { + let seed_words = SeedWords::new(vec![ + Hidden::hide("hi".to_string()), + Hidden::hide("niao".to_string()), + Hidden::hide("hola".to_string()), + Hidden::hide("bonjour".to_string()), + Hidden::hide("olá".to_string()), + ]); + + let vec_words = vec![ + "hi".to_string(), + "niao".to_string(), + "hola".to_string(), + "bonjour".to_string(), + "olá".to_string(), + ]; + + for (index, word) in vec_words.iter().enumerate().take(5_usize) { + // should not derefence, in practice. We do it here, for testing purposes + assert_eq!(*seed_words.get_word(index).unwrap(), *word); + } + } + + #[test] + pub fn test_seed_words_is_empty() { + let seed_words = SeedWords::new(vec![]); + assert!(seed_words.is_empty()); + + let seed_words = SeedWords::new(vec![ + Hidden::hide("hi".to_string()), + Hidden::hide("niao".to_string()), + Hidden::hide("hola".to_string()), + Hidden::hide("bonjour".to_string()), + Hidden::hide("olá".to_string()), + ]); + + assert!(!seed_words.is_empty()); + } + + #[test] + pub fn test_seed_words_push() { + let mut seed_words = SeedWords::new(vec![ + Hidden::hide("hi".to_string()), + Hidden::hide("niao".to_string()), + Hidden::hide("hola".to_string()), + Hidden::hide("bonjour".to_string()), + Hidden::hide("olá".to_string()), + ]); + + seed_words.push("ciao".to_string()); + assert_eq!(seed_words.len(), 6_usize); + assert_eq!(seed_words.get_word(5).unwrap(), "ciao") + } + + #[test] + pub fn test_seed_words_join() { + let seed_words = SeedWords::new(vec![ + Hidden::hide("hi".to_string()), + Hidden::hide("niao".to_string()), + Hidden::hide("hola".to_string()), + Hidden::hide("bonjour".to_string()), + Hidden::hide("olá".to_string()), + ]); + + let joined = seed_words.join(", "); + assert_eq!(joined.reveal(), "hi, niao, hola, bonjour, olá"); + } + + #[test] + pub fn test_seed_words_partial_eq() { + let seed_words = SeedWords::new(vec![ + Hidden::hide("hi".to_string()), + Hidden::hide("niao".to_string()), + Hidden::hide("hola".to_string()), + Hidden::hide("bonjour".to_string()), + Hidden::hide("olá".to_string()), + ]); + + let other_seed_words = SeedWords::new(vec![ + Hidden::hide("hi".to_string()), + Hidden::hide("niao".to_string()), + Hidden::hide("hola".to_string()), + Hidden::hide("bonjour".to_string()), + Hidden::hide("olá".to_string()), + ]); + + // equality should hold, in this case + assert_eq!(seed_words, other_seed_words); + + let other_seed_words = SeedWords::new(vec![]); + + // equality fails, in case of distinguished len + assert_ne!(seed_words, other_seed_words); + + let other_seed_words = SeedWords::new(vec![ + Hidden::hide("hi".to_string()), + Hidden::hide("niao".to_string()), + Hidden::hide("hola".to_string()), + Hidden::hide("bonjour".to_string()), + Hidden::hide("ciao".to_string()), + ]); + + // equality fails, in case of same len but distinct words + assert_ne!(seed_words, other_seed_words); + } +} diff --git a/base_layer/key_manager/src/mnemonic.rs b/base_layer/key_manager/src/mnemonic.rs index 4a5093316d..3d852da313 100644 --- a/base_layer/key_manager/src/mnemonic.rs +++ b/base_layer/key_manager/src/mnemonic.rs @@ -24,12 +24,17 @@ use std::{cmp::Ordering, slice::Iter}; use serde::{Deserialize, Serialize}; use strum_macros::{Display, EnumString}; -use tari_utilities::bit::{bytes_to_bits, checked_bits_to_uint}; +use tari_utilities::{ + bit::{bytes_to_bits, checked_bits_to_uint}, + Hidden, + SafePassword, +}; use crate::{ diacritics::*, error::{KeyManagerError, MnemonicError}, mnemonic_wordlists::*, + SeedWords, }; /// The Mnemonic system simplifies the encoding and decoding of a secret key into and from a Mnemonic word sequence @@ -52,7 +57,7 @@ pub enum MnemonicLanguage { impl MnemonicLanguage { /// Detects the mnemonic language of a specific word by searching all defined mnemonic word lists pub fn from(mnemonic_word: &str) -> Result { - let words = vec![mnemonic_word.to_string()]; + let words = SeedWords::new(vec![Hidden::hide(mnemonic_word.to_string())]); MnemonicLanguage::detect_language(&words) } @@ -84,14 +89,14 @@ impl MnemonicLanguage { } /// Detects the language of a list of words - pub fn detect_language(words: &[String]) -> Result { - let count = words.iter().len(); + pub fn detect_language(words: &SeedWords) -> Result { + let count = words.len(); match count.cmp(&1) { Ordering::Less => { return Err(MnemonicError::UnknownLanguage); }, Ordering::Equal => { - let word = words.get(0).ok_or(MnemonicError::EncodeInvalidLength)?; + let word = words.get_word(0)?; for language in MnemonicLanguage::iterator() { if find_mnemonic_index_from_word(word, *language).is_ok() { return Ok(*language); @@ -100,7 +105,8 @@ impl MnemonicLanguage { return Err(MnemonicError::UnknownLanguage); }, Ordering::Greater => { - for word in words { + for word_ind in 0..words.len() { + let word = words.get_word(word_ind)?; let mut languages = Vec::with_capacity(MnemonicLanguage::iterator().len()); // detect all languages in which a word falls into for language in MnemonicLanguage::iterator() { @@ -112,7 +118,8 @@ impl MnemonicLanguage { // yielded from the initial word for this iteration for language in languages { let mut consistent = true; - for compare in words { + for compare_ind in 0..words.len() { + let compare = words.get_word(compare_ind)?; if compare != word && find_mnemonic_index_from_word(compare, language).is_err() { consistent = false; } @@ -131,19 +138,27 @@ impl MnemonicLanguage { /// Finds and returns the index of a specific word in a mnemonic word list defined by the specified language fn find_mnemonic_index_from_word(word: &str, language: MnemonicLanguage) -> Result { - let lowercase_word = word.to_lowercase(); + let lowercase_word = Hidden::hide(word.to_lowercase()); let search_result = match language { // Search through languages are ordered according to the predominance (number of speakers in the world) of that // language MnemonicLanguage::ChineseSimplified => { - MNEMONIC_CHINESE_SIMPLIFIED_WORDS.binary_search(&lowercase_word.as_str()) + MNEMONIC_CHINESE_SIMPLIFIED_WORDS.binary_search(&lowercase_word.reveal().as_str()) + }, + MnemonicLanguage::English => { + MNEMONIC_ENGLISH_WORDS.binary_search(&remove_diacritics(lowercase_word.reveal()).reveal().as_str()) + }, + MnemonicLanguage::French => { + MNEMONIC_FRENCH_WORDS.binary_search(&remove_diacritics(lowercase_word.reveal()).reveal().as_str()) + }, + MnemonicLanguage::Italian => { + MNEMONIC_ITALIAN_WORDS.binary_search(&remove_diacritics(lowercase_word.reveal()).reveal().as_str()) + }, + MnemonicLanguage::Japanese => MNEMONIC_JAPANESE_WORDS.binary_search(&lowercase_word.reveal().as_str()), + MnemonicLanguage::Korean => MNEMONIC_KOREAN_WORDS.binary_search(&lowercase_word.reveal().as_str()), + MnemonicLanguage::Spanish => { + MNEMONIC_SPANISH_WORDS.binary_search(&remove_diacritics(lowercase_word.reveal()).reveal().as_str()) }, - MnemonicLanguage::English => MNEMONIC_ENGLISH_WORDS.binary_search(&remove_diacritics(&lowercase_word).as_str()), - MnemonicLanguage::French => MNEMONIC_FRENCH_WORDS.binary_search(&remove_diacritics(&lowercase_word).as_str()), - MnemonicLanguage::Italian => MNEMONIC_ITALIAN_WORDS.binary_search(&remove_diacritics(&lowercase_word).as_str()), - MnemonicLanguage::Japanese => MNEMONIC_JAPANESE_WORDS.binary_search(&lowercase_word.as_str()), - MnemonicLanguage::Korean => MNEMONIC_KOREAN_WORDS.binary_search(&lowercase_word.as_str()), - MnemonicLanguage::Spanish => MNEMONIC_SPANISH_WORDS.binary_search(&remove_diacritics(&lowercase_word).as_str()), }; match search_result { Ok(v) => Ok(v), @@ -152,54 +167,53 @@ fn find_mnemonic_index_from_word(word: &str, language: MnemonicLanguage) -> Resu } /// Finds and returns the word for a specific index in a mnemonic word list defined by the specified language -fn find_mnemonic_word_from_index(index: usize, language: MnemonicLanguage) -> Result { +fn find_mnemonic_word_from_index(index: usize, language: MnemonicLanguage) -> Result, MnemonicError> { if index < MNEMONIC_ENGLISH_WORDS.len() { Ok(match language { // Select word according to specified language - MnemonicLanguage::ChineseSimplified => MNEMONIC_CHINESE_SIMPLIFIED_WORDS[index], - MnemonicLanguage::English => MNEMONIC_ENGLISH_WORDS[index], - MnemonicLanguage::French => MNEMONIC_FRENCH_WORDS[index], - MnemonicLanguage::Italian => MNEMONIC_ITALIAN_WORDS[index], - MnemonicLanguage::Japanese => MNEMONIC_JAPANESE_WORDS[index], - MnemonicLanguage::Korean => MNEMONIC_KOREAN_WORDS[index], - MnemonicLanguage::Spanish => MNEMONIC_SPANISH_WORDS[index], - } - .to_string()) + MnemonicLanguage::ChineseSimplified => Hidden::hide(MNEMONIC_CHINESE_SIMPLIFIED_WORDS[index].to_string()), + MnemonicLanguage::English => Hidden::hide(MNEMONIC_ENGLISH_WORDS[index].to_string()), + MnemonicLanguage::French => Hidden::hide(MNEMONIC_FRENCH_WORDS[index].to_string()), + MnemonicLanguage::Italian => Hidden::hide(MNEMONIC_ITALIAN_WORDS[index].to_string()), + MnemonicLanguage::Japanese => Hidden::hide(MNEMONIC_JAPANESE_WORDS[index].to_string()), + MnemonicLanguage::Korean => Hidden::hide(MNEMONIC_KOREAN_WORDS[index].to_string()), + MnemonicLanguage::Spanish => Hidden::hide(MNEMONIC_SPANISH_WORDS[index].to_string()), + }) } else { Err(MnemonicError::IndexOutOfBounds) } } /// Converts a vector of bytes to a sequence of mnemonic words using the specified language -pub fn from_bytes(bytes: &[u8], language: MnemonicLanguage) -> Result, MnemonicError> { - let mut bits = bytes_to_bits(bytes); +pub fn from_bytes(bytes: &[u8], language: MnemonicLanguage) -> Result { + let mut bits = Hidden::hide(bytes_to_bits(bytes)); // Pad with zeros if length not divisible by 11 let group_bit_count = 11; - let mut padded_size = bits.len() / group_bit_count; - if bits.len() % group_bit_count > 0 { + let mut padded_size = bits.reveal().len() / group_bit_count; + if bits.reveal().len() % group_bit_count > 0 { padded_size += 1; } padded_size *= group_bit_count; - bits.resize(padded_size, false); + bits.reveal_mut().resize(padded_size, false); // Group each set of 11 bits to form one mnemonic word - let mut mnemonic_sequence: Vec = Vec::new(); - for i in 0..bits.len() / group_bit_count { + let mut mnemonic_sequence: Vec> = Vec::new(); + for i in 0..bits.reveal().len() / group_bit_count { let start_index = i * group_bit_count; let stop_index = start_index + group_bit_count; - let sub_v = &bits[start_index..stop_index]; + let sub_v = &bits.reveal()[start_index..stop_index]; let word_index = checked_bits_to_uint(sub_v).ok_or(MnemonicError::BitsToIntConversion)?; let mnemonic_word = find_mnemonic_word_from_index(word_index, language)?; mnemonic_sequence.push(mnemonic_word); } - Ok(mnemonic_sequence) + Ok(SeedWords::new(mnemonic_sequence)) } /// Generates a vector of bytes that represent the provided mnemonic sequence of words, the language of the mnemonic /// sequence is detected -pub fn to_bytes(mnemonic_seq: &[String]) -> Result, MnemonicError> { +pub fn to_bytes(mnemonic_seq: &SeedWords) -> Result>, MnemonicError> { let language = MnemonicLanguage::detect_language(mnemonic_seq)?; to_bytes_with_language(mnemonic_seq, &language) } @@ -219,43 +233,51 @@ pub fn to_bytes(mnemonic_seq: &[String]) -> Result, MnemonicError> { /// 1) the first output 'a' is last 8 bits from input 'A', we have leftover 3 bits from 'A' /// 2) We add 5 bits from 'B' to generate 'b', the leftover is 6 bits from 'B' /// 3) We add 2 bits from 'C to generate 'c', now we have 8 bits needed to generate 'd' and we have 1 bit leftover. -pub fn to_bytes_with_language(mnemonic_seq: &[String], language: &MnemonicLanguage) -> Result, MnemonicError> { +pub fn to_bytes_with_language( + mnemonic_seq: &SeedWords, + language: &MnemonicLanguage, +) -> Result>, MnemonicError> { const MASK: u64 = (1u64 << 8) - 1; - let mut bytes = Vec::new(); + let mut bytes = Hidden::hide(Vec::new()); let mut rest = 0u64; let mut rest_bits: u8 = 0; - for curr_word in mnemonic_seq { - let index = find_mnemonic_index_from_word(curr_word, *language)? as u64; + for curr_ind in 0..mnemonic_seq.len() { + let index = find_mnemonic_index_from_word( + mnemonic_seq + .get_word(curr_ind) + .map_err(|_| MnemonicError::IndexOutOfBounds)?, + *language, + )? as u64; // Add 11 bits to the front rest += index << rest_bits; rest_bits += 11; while rest_bits >= 8 { // Get last 8 bits and shift it - bytes.push((rest & MASK) as u8); + bytes.reveal_mut().push((rest & MASK) as u8); rest >>= 8; rest_bits -= 8; } } // If we have any leftover, we write it. if rest > 0 { - bytes.push((rest & MASK) as u8); + bytes.reveal_mut().push((rest & MASK) as u8); } Ok(bytes) } pub trait Mnemonic { - fn from_mnemonic(mnemonic_seq: &[String], passphrase: Option) -> Result; + fn from_mnemonic(mnemonic_seq: &SeedWords, passphrase: Option) -> Result; fn from_mnemonic_with_language( - mnemonic_seq: &[String], + mnemonic_seq: &SeedWords, language: MnemonicLanguage, - passphrase: Option, + passphrase: Option, ) -> Result; fn to_mnemonic( &self, language: MnemonicLanguage, - passphrase: Option, - ) -> Result, KeyManagerError>; + passphrase: Option, + ) -> Result; } #[cfg(test)] @@ -335,45 +357,54 @@ mod test { assert!(MnemonicLanguage::from("desvelado").is_err()); // Invalid Mnemonic Spanish word // English/Spanish + English/French -> English - let words1 = vec![ - "album".to_string(), - "area".to_string(), - "opera".to_string(), - "abandon".to_string(), - ]; + let words1 = SeedWords::new(vec![ + Hidden::hide("album".to_string()), + Hidden::hide("area".to_string()), + Hidden::hide("opera".to_string()), + Hidden::hide("abandon".to_string()), + ]); assert_eq!( MnemonicLanguage::detect_language(&words1), Ok(MnemonicLanguage::English) ); // English/Spanish + English/French + Italian/Spanish - let words2 = vec![ - "album".to_string(), - "area".to_string(), - "opera".to_string(), - "abandon".to_string(), - "tipico".to_string(), - ]; + let words2 = SeedWords::new(vec![ + Hidden::hide("album".to_string()), + Hidden::hide("area".to_string()), + Hidden::hide("opera".to_string()), + Hidden::hide("abandon".to_string()), + Hidden::hide("tipico".to_string()), + ]); assert!(MnemonicLanguage::detect_language(&words2).is_err()); // bounds check (last word is invalid) - let words3 = vec![ - "album".to_string(), - "area".to_string(), - "opera".to_string(), - "abandon".to_string(), - "topazio".to_string(), - ]; + let words3 = SeedWords::new(vec![ + Hidden::hide("album".to_string()), + Hidden::hide("area".to_string()), + Hidden::hide("opera".to_string()), + Hidden::hide("abandon".to_string()), + Hidden::hide("topazio".to_string()), + ]); assert!(MnemonicLanguage::detect_language(&words3).is_err()); // building up a word list: English/French + French -> French let mut words = Vec::with_capacity(3); - words.push("concert".to_string()); - assert_eq!(MnemonicLanguage::detect_language(&words), Ok(MnemonicLanguage::English)); - words.push("abandon".to_string()); - assert_eq!(MnemonicLanguage::detect_language(&words), Ok(MnemonicLanguage::English)); - words.push("barbier".to_string()); - assert_eq!(MnemonicLanguage::detect_language(&words), Ok(MnemonicLanguage::French)); + words.push(Hidden::hide("concert".to_string())); + assert_eq!( + MnemonicLanguage::detect_language(&SeedWords::new(words.clone())), + Ok(MnemonicLanguage::English) + ); + words.push(Hidden::hide("abandon".to_string())); + assert_eq!( + MnemonicLanguage::detect_language(&SeedWords::new(words.clone())), + Ok(MnemonicLanguage::English) + ); + words.push(Hidden::hide("barbier".to_string())); + assert_eq!( + MnemonicLanguage::detect_language(&SeedWords::new(words)), + Ok(MnemonicLanguage::French) + ); } #[test] @@ -384,7 +415,7 @@ mod test { let index = find_mnemonic_index_from_word(&desired_word, MnemonicLanguage::ChineseSimplified).expect(""); assert_eq!(desired_index, index); let word = find_mnemonic_word_from_index(desired_index, MnemonicLanguage::ChineseSimplified).expect(""); - assert_eq!(desired_word, word); + assert_eq!(&desired_word, word.reveal()); // Encoding and Decoding using English Simplified let desired_index = 1717; @@ -392,7 +423,7 @@ mod test { let index = find_mnemonic_index_from_word(&desired_word, MnemonicLanguage::English).expect(""); assert_eq!(desired_index, index); let word = find_mnemonic_word_from_index(desired_index, MnemonicLanguage::English).expect(""); - assert_eq!(desired_word, word); + assert_eq!(&desired_word, word.reveal()); // Encoding and Decoding using French Simplified let desired_index = 824; @@ -400,7 +431,7 @@ mod test { let index = find_mnemonic_index_from_word(&desired_word, MnemonicLanguage::French).expect(""); assert_eq!(desired_index, index); let word = find_mnemonic_word_from_index(desired_index, MnemonicLanguage::French).expect(""); - assert_eq!(desired_word, word); + assert_eq!(&desired_word, word.reveal()); // Encoding and Decoding using Italian Simplified let desired_index = 1123; @@ -408,7 +439,7 @@ mod test { let index = find_mnemonic_index_from_word(&desired_word, MnemonicLanguage::Italian).expect(""); assert_eq!(desired_index, index); let word = find_mnemonic_word_from_index(desired_index, MnemonicLanguage::Italian).expect(""); - assert_eq!(desired_word, word); + assert_eq!(&desired_word, word.reveal()); // Encoding and Decoding using Japanese Simplified let desired_index = 1856; @@ -416,7 +447,7 @@ mod test { let index = find_mnemonic_index_from_word(&desired_word, MnemonicLanguage::Japanese).expect(""); assert_eq!(desired_index, index); let word = find_mnemonic_word_from_index(desired_index, MnemonicLanguage::Japanese).expect(""); - assert_eq!(desired_word, word); + assert_eq!(&desired_word, word.reveal()); // Encoding and Decoding using Korean Simplified let desired_index = 345; @@ -424,7 +455,7 @@ mod test { let index = find_mnemonic_index_from_word(&desired_word, MnemonicLanguage::Korean).expect(""); assert_eq!(desired_index, index); let word = find_mnemonic_word_from_index(desired_index, MnemonicLanguage::Korean).expect(""); - assert_eq!(desired_word, word); + assert_eq!(&desired_word, word.reveal()); // Encoding and Decoding using Spanish Simplified let desired_index = 345; @@ -432,7 +463,7 @@ mod test { let index = find_mnemonic_index_from_word(&desired_word, MnemonicLanguage::Spanish).expect(""); assert_eq!(desired_index, index); let word = find_mnemonic_word_from_index(desired_index, MnemonicLanguage::Spanish).expect(""); - assert_eq!(desired_word, word); + assert_eq!(&desired_word, word.reveal()); } #[test] @@ -442,7 +473,7 @@ mod test { let mnemonic_bytes = mnemonic::to_bytes(&mnemonic_seq).expect(""); let mismatched_bytes = secretkey_bytes .iter() - .zip(mnemonic_bytes.iter()) + .zip(mnemonic_bytes.reveal().iter()) .filter(|&(a, b)| a != b) .count(); assert_eq!(mismatched_bytes, 0); @@ -458,7 +489,7 @@ mod test { OsRng.fill_bytes(&mut secretkey_bytes); let mnemonic_seq = mnemonic::from_bytes(&secretkey_bytes, MnemonicLanguage::English).unwrap(); let mnemonic_bytes = mnemonic::to_bytes(&mnemonic_seq).unwrap(); - assert_eq!(secretkey_bytes, mnemonic_bytes, "failed len = {}", len); + assert_eq!(&secretkey_bytes, mnemonic_bytes.reveal(), "failed len = {}", len); } } } diff --git a/base_layer/wallet/src/wallet.rs b/base_layer/wallet/src/wallet.rs index e56d69e1f7..5d7cc74a01 100644 --- a/base_layer/wallet/src/wallet.rs +++ b/base_layer/wallet/src/wallet.rs @@ -57,6 +57,7 @@ use tari_key_manager::{ cipher_seed::CipherSeed, key_manager::KeyManager, mnemonic::{Mnemonic, MnemonicLanguage}, + SeedWords, }; use tari_p2p::{ auto_update::{AutoUpdateConfig, SoftwareUpdaterHandle, SoftwareUpdaterService}, @@ -694,7 +695,7 @@ where Ok(self.db.get_client_key_value(RECOVERY_KEY.to_string())?.is_some()) } - pub fn get_seed_words(&self, language: &MnemonicLanguage) -> Result, WalletError> { + pub fn get_seed_words(&self, language: &MnemonicLanguage) -> Result { let master_seed = self.db.get_master_seed()?.ok_or_else(|| { WalletError::WalletStorageError(WalletStorageError::RecoverySeedError( "Cipher Seed not found".to_string(), diff --git a/base_layer/wallet/tests/output_manager_service_tests/service.rs b/base_layer/wallet/tests/output_manager_service_tests/service.rs index 642b0890dc..8c4981b4c7 100644 --- a/base_layer/wallet/tests/output_manager_service_tests/service.rs +++ b/base_layer/wallet/tests/output_manager_service_tests/service.rs @@ -52,10 +52,11 @@ use tari_crypto::{ commitment::HomomorphicCommitmentFactory, keys::{PublicKey as PublicKeyTrait, SecretKey}, }; -use tari_key_manager::{cipher_seed::CipherSeed, mnemonic::Mnemonic}; +use tari_key_manager::{cipher_seed::CipherSeed, mnemonic::Mnemonic, SeedWords}; use tari_script::{inputs, script, TariScript}; use tari_service_framework::reply_channel; use tari_shutdown::Shutdown; +use tari_utilities::Hidden; use tari_wallet::{ base_node_service::{ handle::{BaseNodeEvent, BaseNodeServiceHandle}, @@ -180,18 +181,14 @@ async fn setup_output_manager_service>(), - None, - ) - .unwrap(); + let words = [ + "scan", "train", "success", "hover", "prepare", "donor", "upgrade", "attitude", "debate", "emotion", "myself", + "ladder", "display", "athlete", "welcome", "artist", "home", "punch", "sense", "park", "midnight", "quantum", + "bright", "carbon", + ]; + let seed_words = SeedWords::new(words.iter().map(|s| Hidden::hide(s.to_string())).collect::>()); + + let cipher_seed = CipherSeed::from_mnemonic(&seed_words, None).unwrap(); let key_manager = KeyManagerHandle::new(cipher_seed.clone(), KeyManagerDatabase::new(ks_backend)); let output_manager_service = OutputManagerService::new( diff --git a/base_layer/wallet/tests/wallet.rs b/base_layer/wallet/tests/wallet.rs index 182bb80d56..a343355624 100644 --- a/base_layer/wallet/tests/wallet.rs +++ b/base_layer/wallet/tests/wallet.rs @@ -47,7 +47,7 @@ use tari_core::{ }, }; use tari_crypto::keys::{PublicKey as PublicKeyTrait, SecretKey}; -use tari_key_manager::{cipher_seed::CipherSeed, mnemonic::Mnemonic}; +use tari_key_manager::{cipher_seed::CipherSeed, mnemonic::Mnemonic, SeedWords}; use tari_p2p::{ auto_update::AutoUpdateConfig, comms_connector::InboundDomainConnector, @@ -62,7 +62,7 @@ use tari_p2p::{ use tari_script::{inputs, script}; use tari_shutdown::{Shutdown, ShutdownSignal}; use tari_test_utils::{collect_recv, random}; -use tari_utilities::SafePassword; +use tari_utilities::{Hidden, SafePassword}; use tari_wallet::{ contacts_service::{ handle::ContactsLivenessEvent, @@ -795,16 +795,17 @@ async fn test_recovery_birthday() { // .expect("Couldn't convert CipherSeed to Mnemonic"); // println!("{:?}", mnemonic_seq); - let seed_words: Vec = [ + let vec_words: Vec> = [ "octubre", "rinon", "ameno", "rigido", "verbo", "dosis", "ocaso", "fallo", "tez", "ladron", "entrar", "pedal", "fortuna", "ahogo", "llanto", "mascara", "intuir", "buey", "cubrir", "anillo", "cajon", "entrar", "clase", "latir", ] .iter() - .map(|w| w.to_string()) + .map(|w| Hidden::hide(w.to_string())) .collect(); + let seed_words = SeedWords::new(vec_words); - let recovery_seed = CipherSeed::from_mnemonic(seed_words.as_slice(), None).unwrap(); + let recovery_seed = CipherSeed::from_mnemonic(&seed_words, None).unwrap(); let birthday = recovery_seed.birthday(); let wallet = create_wallet( diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index d7a98124a6..2d23938188 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -106,9 +106,9 @@ use tari_core::transactions::{ }; use tari_crypto::{ keys::{PublicKey as PublicKeyTrait, SecretKey}, - tari_utilities::ByteArray, + tari_utilities::{ByteArray, Hidden}, }; -use tari_key_manager::{cipher_seed::CipherSeed, mnemonic::MnemonicLanguage}; +use tari_key_manager::{cipher_seed::CipherSeed, mnemonic::MnemonicLanguage, SeedWords}; use tari_p2p::{ auto_update::AutoUpdateConfig, transport::MemoryTransportConfig, @@ -215,7 +215,7 @@ pub struct ByteVector(Vec); // declared like this so that it can be exp pub struct EmojiSet(Vec); #[derive(Debug, PartialEq)] -pub struct TariSeedWords(Vec); +pub struct TariSeedWords(SeedWords); #[derive(Debug, PartialEq)] pub struct TariPublicKeys(Vec); @@ -1662,7 +1662,8 @@ pub unsafe extern "C" fn output_features_destroy(output_features: *mut TariOutpu /// None #[no_mangle] pub unsafe extern "C" fn seed_words_create() -> *mut TariSeedWords { - Box::into_raw(Box::new(TariSeedWords(Vec::new()))) + let seed_words = SeedWords::new(vec![]); + Box::into_raw(Box::new(TariSeedWords(seed_words))) } /// Create a TariSeedWords instance containing the entire mnemonic wordlist for the requested language @@ -1689,7 +1690,7 @@ pub unsafe extern "C" fn seed_words_get_mnemonic_word_list_for_language( let mut error = 0; ptr::swap(error_out, &mut error as *mut c_int); - let mut mnemonic_word_list_vec = Vec::new(); + let mut mnemonic_word_list_vec = SeedWords::new(vec![]); if language.is_null() { error = LibWalletError::from(InterfaceError::NullError("mnemonic wordlist".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); @@ -1730,7 +1731,8 @@ pub unsafe extern "C" fn seed_words_get_mnemonic_word_list_for_language( target: LOG_TARGET, "Retrieved mnemonic wordlist for'{}'", language_string ); - mnemonic_word_list_vec = mnemonic_word_list.to_vec().iter().map(|s| s.to_string()).collect(); + mnemonic_word_list_vec = + SeedWords::new(mnemonic_word_list.iter().map(|s| Hidden::hide(s.to_string())).collect()); } Box::into_raw(Box::new(TariSeedWords(mnemonic_word_list_vec))) @@ -1793,16 +1795,17 @@ pub unsafe extern "C" fn seed_words_get_at( if position > len as u32 { error = LibWalletError::from(InterfaceError::PositionInvalidError).code; ptr::swap(error_out, &mut error as *mut c_int); + } else if let Ok(v) = CString::new( + (*seed_words) + .0 + .get_word(position as usize) + .expect("Seed Words position is in bounds") + .as_str(), + ) { + word = v; } else { - match CString::new((*seed_words).0[position as usize].clone()) { - Ok(v) => { - word = v; - }, - _ => { - error = LibWalletError::from(InterfaceError::PointerError("seed_words".to_string())).code; - ptr::swap(error_out, &mut error as *mut c_int); - }, - } + error = LibWalletError::from(InterfaceError::PointerError("seed_words".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); } } CString::into_raw(word) @@ -1889,15 +1892,12 @@ pub unsafe extern "C" fn seed_words_push_word( } // Try push to a temporary copy first to prevent existing object becoming invalid - let mut temp = (*seed_words).0.clone(); - - if let Ok(language) = MnemonicLanguage::detect_language(&temp) { - temp.push(word_string.clone()); + if let Ok(language) = MnemonicLanguage::detect_language(&(*seed_words).0) { // Check words in temp are still consistent for a language, note that detected language can change // depending on word added - if MnemonicLanguage::detect_language(&temp).is_ok() { - if temp.len() >= 24 { - if let Err(e) = CipherSeed::from_mnemonic(&temp, None) { + if MnemonicLanguage::detect_language(&(*seed_words).0).is_ok() { + if (*seed_words).0.len() >= 24 { + if let Err(e) = CipherSeed::from_mnemonic(&(*seed_words).0, None) { log::error!( target: LOG_TARGET, "Problem building valid private seed from seed phrase: {:?}", @@ -8951,11 +8951,13 @@ mod test { // Compare from Rust's perspective assert_eq!( (*mnemonic_wordlist_ffi).0, - mnemonic_wordlist - .to_vec() - .iter() - .map(|s| s.to_string()) - .collect::>() + SeedWords::new( + mnemonic_wordlist + .to_vec() + .iter() + .map(|s| Hidden::hide(s.to_string())) + .collect::>>() + ) ); // Compare from C's perspective let count = seed_words_get_length(mnemonic_wordlist_ffi, error_ptr);