Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 57 additions & 18 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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."
Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/bip32/arg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/derive/arg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/diagram/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ impl crate::Execute for DiagramCommand<SimpleDiagram> {

// 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
Expand All @@ -36,7 +36,7 @@ impl crate::Execute for DiagramCommand<ComplexDiagram> {

// 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
Expand Down
30 changes: 25 additions & 5 deletions src/encrypt/arg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use artimonist::bitcoin;

#[derive(clap::Parser)]
pub struct EncryptCommand<const ENCRYPT: bool> {
/// 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
Expand All @@ -14,9 +14,11 @@ pub struct EncryptCommand<const ENCRYPT: bool> {
/// 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),
}

Expand All @@ -26,6 +28,8 @@ impl std::str::FromStr for EncryptSource {
fn from_str(s: &str) -> Result<Self, Self::Err> {
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 {
Expand All @@ -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:
/// <https://github.com/bitcoin/bips/blob/master/bip-0038.mediawiki>
/// > 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()
}
13 changes: 11 additions & 2 deletions src/encrypt/bip38.rs → src/encrypt/execute.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -18,11 +18,20 @@ impl<const ENCRYPT: bool> Execute for EncryptCommand<ENCRYPT> {
};

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) => {
Expand Down
2 changes: 1 addition & 1 deletion src/encrypt/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
mod arg;
mod bip38;
mod execute;

pub use arg::EncryptCommand;
2 changes: 1 addition & 1 deletion tests/encrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ fn test_encrypt_error() {
"6PYPVwvgux4mN96iwj1RGvbiGmmPWpkiQimpkP1fvFGGhT38XxZed6Kdth"
);
cli_test_error!(
"Error: Invalid BIP38 encrypted key",
"Error: Invalid encrypted key",
"decrypt",
"KyyXeMvCn36KuedmVX727NYQ35YEeF4z1ZjXGyqgFpmZM4AcY8ay"
);
Expand Down