diff --git a/Cargo.lock b/Cargo.lock index 30eaffe..e2360e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,6 +78,18 @@ version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + [[package]] name = "arrayvec" version = "0.7.6" @@ -86,11 +98,12 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "artimonist" -version = "1.5.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7413d6d7cab3380a8f2796944c2634338417814670902cdee87f3f465b70e0e1" +checksum = "32f0ee57eb43caa7d5a672bf6addb6b51edf6b28b5f0a3367a9d10515a4cd97a" dependencies = [ "aes", + "argon2", "bitcoin", "pbkdf2", "rand", @@ -103,10 +116,10 @@ dependencies = [ [[package]] name = "artimonist" -version = "1.7.0" +version = "1.8.1" dependencies = [ "anyhow", - "artimonist 1.5.0", + "artimonist 1.7.1", "assert_cmd", "clap", "comfy-table", @@ -148,6 +161,12 @@ dependencies = [ "bitcoin_hashes", ] +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + [[package]] name = "bech32" version = "0.11.0" @@ -210,9 +229,18 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" + +[[package]] +name = "blake2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] [[package]] name = "block-buffer" @@ -242,9 +270,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.32" +version = "1.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" +checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" dependencies = [ "shlex", ] @@ -478,7 +506,7 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", "crossterm", "dyn-clone", "fxhash", @@ -593,6 +621,17 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + [[package]] name = "pbkdf2" version = "0.12.2" @@ -644,9 +683,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.97" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -696,7 +735,7 @@ version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.2", ] [[package]] @@ -860,9 +899,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.105" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -877,18 +916,18 @@ checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" [[package]] name = "thiserror" -version = "2.0.14" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" +checksum = "80d76d3f064b981389ecb4b6b7f45a0bf9fdac1d5b9204c7bd6714fecc302850" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.14" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" +checksum = "44d29feb33e986b6ea906bd9c3559a856983f92371b3eaa5e83782a351623de0" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index af8790e..af0d6be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "artimonist" -version = "1.7.0" +version = "1.8.1" edition = "2024" description = "A tool for generating mnemonics and wallets." @@ -21,19 +21,19 @@ testnet = ["artimonist/testnet"] [dependencies] -artimonist = { version = "1.5" } +artimonist = { version = "1.7" } clap = { version = "^4.5", features = ["derive"] } inquire = { version = "^0.7", default-features = false, features = ["crossterm"] } -comfy-table = { version = "^7.1", default-features = false } +comfy-table = { version = "^7.1", default-features = false} +unicode-normalization = "0.1" thiserror = "2" anyhow = "1" -unicode-normalization = "0.1" [profile.release] -codegen-units = 1 +# codegen-units = 1 lto = true -opt-level = "z" strip = true +opt-level = 3 [dev-dependencies] assert_cmd = "2" diff --git a/src/bip32/arg.rs b/src/bip32/arg.rs index 280ea00..5e13d8b 100644 --- a/src/bip32/arg.rs +++ b/src/bip32/arg.rs @@ -4,7 +4,7 @@ use artimonist::{Mnemonic, Xpriv, Xpub}; #[derive(clap::Parser, Debug)] pub struct Bip32Command { /// Mnemonic phrase or Master key - #[clap(name = "MNEMONIC|MASTER_KEY")] + #[clap(name = "MNEMONIC|MASTER KEY")] pub key: MasterKey, /// Derivation path diff --git a/src/derive/arg.rs b/src/derive/arg.rs index 28485b7..ea753f4 100644 --- a/src/derive/arg.rs +++ b/src/derive/arg.rs @@ -3,7 +3,7 @@ use artimonist::{Mnemonic, Xpriv}; #[derive(clap::Parser, Debug)] pub struct DeriveCommand { /// Mnemonic phrase or Master key - #[clap(name = "MNEMONIC|MASTER_KEY")] + #[clap(name = "MNEMONIC|MASTER KEY")] pub key: MasterKey, /// Account start index diff --git a/src/diagram/execute.rs b/src/diagram/execute.rs index f39b40b..73564cc 100644 --- a/src/diagram/execute.rs +++ b/src/diagram/execute.rs @@ -12,7 +12,7 @@ impl crate::Execute for DiagramCommand { // choose a mnemonic language if needed if self.has_mnemonic() && self.language.is_none() { - self.language = Some(select_language(&Language::all())?); + self.language = Some(select_language(Language::all())?); } // inquire the encryption password as salt @@ -36,7 +36,7 @@ impl crate::Execute for DiagramCommand { // choose a mnemonic language if needed if self.has_mnemonic() && self.language.is_none() { - self.language = Some(select_language(&Language::all())?); + self.language = Some(select_language(Language::all())?); } // inquire the encryption password as salt diff --git a/src/encrypt/arg.rs b/src/encrypt/arg.rs index 3c1d0de..ea1548f 100644 --- a/src/encrypt/arg.rs +++ b/src/encrypt/arg.rs @@ -2,8 +2,8 @@ use artimonist::bitcoin; #[derive(clap::Parser)] pub struct EncryptCommand { - /// Private key in WIF format or encrypted key, or a file containing keys - #[clap(name = "KEY|FILE_NAME")] + /// Mnemonic or private key + #[clap(name = "MNEMONIC|PRIVATE KEY|FILE NAME")] pub source: EncryptSource, /// Password @@ -14,9 +14,11 @@ pub struct EncryptCommand { /// Source of encryption/decryption #[derive(Clone, Debug)] pub enum EncryptSource { - /// Private key in WIF format or encrypted key + /// Mnemonic or encrypted mnemonic string. + Mnemonic(String), + /// Private key in WIF format or encrypted key. Key(String), - /// Text file containing private keys or encrypted keys + /// Text file containing private keys or encrypted keys. File(String), } @@ -26,6 +28,8 @@ impl std::str::FromStr for EncryptSource { fn from_str(s: &str) -> Result { if is_private_key(s) || is_encrypted_key(s) { Ok(EncryptSource::Key(s.to_string())) + } else if is_mnemonic(s) { + Ok(EncryptSource::Mnemonic(s.to_string())) } else if std::path::Path::new(s).exists() { Ok(EncryptSource::File(s.to_string())) } else { @@ -34,12 +38,28 @@ impl std::str::FromStr for EncryptSource { } } +/// "menmonic", "mnemonic; verify" or "mnemonic; count" +#[inline(always)] +fn is_mnemonic(s: &str) -> bool { + let count = s.split_whitespace().count(); + matches!(count, 12 | 15 | 18 | 21 | 24 | 13 | 16 | 19 | 22 | 25) +} + #[inline(always)] fn is_private_key(s: &str) -> bool { s.starts_with(['K', 'L', '5']) && s.len() == 52 && bitcoin::base58::decode(s).is_ok() } +/// # Reference: +/// +/// > non-EC-multiplied keys without compression (prefix 6PR) +/// > non-EC-multiplied keys with compression (prefix 6PY) +/// > EC-multiplied keys without compression (prefix 6Pf) +/// > EC-multiplied keys with compression (prefix 6Pn) #[inline(always)] fn is_encrypted_key(s: &str) -> bool { - s.starts_with("6P") && s.len() == 58 && bitcoin::base58::decode(s).is_ok() + s.starts_with("6P") + && matches!(s.as_bytes()[2], b'R' | b'Y' | b'f' | b'n') + && s.len() == 58 + && bitcoin::base58::decode(s).is_ok() } diff --git a/src/encrypt/bip38.rs b/src/encrypt/execute.rs similarity index 81% rename from src/encrypt/bip38.rs rename to src/encrypt/execute.rs index 5c1aeb6..6ee94f6 100644 --- a/src/encrypt/bip38.rs +++ b/src/encrypt/execute.rs @@ -1,7 +1,7 @@ use super::{EncryptCommand, arg::EncryptSource}; use crate::{Execute, utils::inquire_password}; use anyhow::anyhow; -use artimonist::BIP38; +use artimonist::{BIP38, MnemonicEncryption}; use std::fs::File; use std::io::{BufRead, BufReader, BufWriter, Write}; @@ -18,11 +18,20 @@ impl Execute for EncryptCommand { }; match &self.source { + EncryptSource::Mnemonic(str) => { + if ENCRYPT { + let mnemonic = str.mnemonic_encrypt(&password)?; + println!("Encrypted mnemonic: \"{mnemonic}\""); + } else { + let original = str.mnemonic_decrypt(&password)?; + println!("Original mnemonic: \"{original}\""); + } + } EncryptSource::Key(key) => { if ENCRYPT { println!("Encrypted private key: {}", key.bip38_encrypt(&password)?); } else { - println!("Decrypted private key: {}", key.bip38_decrypt(&password)?); + println!("Original private key: {}", key.bip38_decrypt(&password)?); } } EncryptSource::File(file) => { diff --git a/src/encrypt/mod.rs b/src/encrypt/mod.rs index ad316fa..5688f46 100644 --- a/src/encrypt/mod.rs +++ b/src/encrypt/mod.rs @@ -1,4 +1,4 @@ mod arg; -mod bip38; +mod execute; pub use arg::EncryptCommand; diff --git a/tests/encrypt.rs b/tests/encrypt.rs index 4d3a74b..9ddb8d8 100644 --- a/tests/encrypt.rs +++ b/tests/encrypt.rs @@ -59,7 +59,7 @@ fn test_encrypt_error() { "6PYPVwvgux4mN96iwj1RGvbiGmmPWpkiQimpkP1fvFGGhT38XxZed6Kdth" ); cli_test_error!( - "Error: Invalid BIP38 encrypted key", + "Error: Invalid encrypted key", "decrypt", "KyyXeMvCn36KuedmVX727NYQ35YEeF4z1ZjXGyqgFpmZM4AcY8ay" );