diff --git a/Cargo.lock b/Cargo.lock
index 420b4841ec2a2751b8df910f8465843d464ec740..3ad7041f45acf746279c31f58bd6a20da90c49f5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,6 +2,220 @@
 # It is not intended for manual editing.
 version = 3
 
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "base64ct"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b"
+
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bs58"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cipher"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crypto-mac"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
+dependencies = [
+ "generic-array",
+ "subtle",
+]
+
+[[package]]
+name = "cryptoxide"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8c4fdc86023bc33b265f256ce8205329125b86c38a8a96e243a6a705b7230ec"
+
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array",
+]
+
 [[package]]
 name = "duniter-mini-client"
 version = "0.1.0"
+dependencies = [
+ "base64",
+ "bs58",
+ "cryptoxide",
+ "scrypt",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "hmac"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
+dependencies = [
+ "crypto-mac",
+ "digest",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
+[[package]]
+name = "password-hash"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77e0b28ace46c5a396546bcf443bf422b57049617433d8854227352a4a9b24e7"
+dependencies = [
+ "base64ct",
+ "rand_core",
+ "subtle",
+]
+
+[[package]]
+name = "pbkdf2"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa"
+dependencies = [
+ "crypto-mac",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "salsa20"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecbd2eb639fd7cab5804a0837fe373cc2172d15437e804c054a9fb885cb923b0"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
+name = "scrypt"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "879588d8f90906e73302547e20fffefdd240eb3e0e744e142321f5d49dea0518"
+dependencies = [
+ "base64ct",
+ "hmac",
+ "password-hash",
+ "pbkdf2",
+ "salsa20",
+ "sha2",
+]
+
+[[package]]
+name = "sha2"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
+dependencies = [
+ "block-buffer",
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+ "opaque-debug",
+]
+
+[[package]]
+name = "subtle"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
+
+[[package]]
+name = "typenum"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
+
+[[package]]
+name = "version_check"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
diff --git a/Cargo.toml b/Cargo.toml
index 582e632761bedbe6a1ad365b479f075ec6e6639d..23fe1ea1b4453339116503fc4a79e1ac30b49082 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,3 +6,7 @@ edition = "2018"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
+scrypt = "0.7.0"
+base64 = "0.13.0"
+bs58 = "0.4.0"
+cryptoxide = "0.3.3"
\ No newline at end of file
diff --git a/src/cli/mod.rs b/src/cli/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e2b2546be4c6c5a70cde25bf1ecc55bc55350722
--- /dev/null
+++ b/src/cli/mod.rs
@@ -0,0 +1,42 @@
+use std::env::Args;
+
+use crate::cli::Command::*;
+
+pub enum Command {
+    /// Compute the public key from salt/passwd and displays it
+    PUB(String, String),
+    /// Compute the secret key from salt/passwd and displays it
+    SEC(String, String),
+    /// Compute the keyring from salt/passwd and displays it
+    KEYRING(String, String),
+    /// Unknown command
+    UNKNOWN(String)
+}
+
+impl Command {
+
+    pub fn from(mut args: Args) -> Command {
+        args.next();
+        let command = args.next().expect("Command must be provided");
+
+        let command = command.as_str();
+        match command {
+            "pub" => {
+                let salt = args.next().expect("Salt must be provided");
+                let passwd = args.next().expect("Password must be provided");
+                PUB(salt, passwd)
+            },
+            "sec" => {
+                let salt = args.next().expect("Salt must be provided");
+                let passwd = args.next().expect("Password must be provided");
+                SEC(salt, passwd)
+            },
+            "keyring" => {
+                let salt = args.next().expect("Salt must be provided");
+                let passwd = args.next().expect("Password must be provided");
+                KEYRING(salt, passwd)
+            },
+            _ => UNKNOWN(String::from(command)),
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/crypto/duniter_key.rs b/src/crypto/duniter_key.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4b3aba2e98fb0e90d8494f5037b09e22a5c96972
--- /dev/null
+++ b/src/crypto/duniter_key.rs
@@ -0,0 +1,65 @@
+use std::error::Error;
+use std::fmt::Formatter;
+
+use bs58;
+use cryptoxide::ed25519;
+use scrypt::{Params, Scrypt};
+use scrypt::password_hash::{Output, PasswordHasher, SaltString};
+
+use crate::crypto::duniter_signature::DuniterSignature;
+
+/// The main functional class for handle Duniter crypto.
+pub struct DuniterKey {
+    pub public: [u8; 32],
+    pub secret: [u8; 64],
+}
+
+/// How to display a duniter keyring: "pub_base58:sec_base58" format.
+impl std::fmt::Display for DuniterKey {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}:{}", bs58::encode(&self.public).into_string(), bs58::encode(&self.secret).into_string())
+    }
+}
+
+impl DuniterKey {
+
+    pub fn from_scrypt(salt: &str, passwd: &str) -> Result<DuniterKey, Box<dyn Error>> {
+        let seed = derive_seed(salt, passwd)?;
+        let (secret, public) = ed25519::keypair(seed.as_bytes());
+        Ok(DuniterKey { public, secret })
+    }
+
+    pub fn from_base58(public: &str, secret: &str) -> Result<DuniterKey, Box<dyn Error>> {
+        let public_vec = bs58::decode(public).into_vec().unwrap();
+        let secret_vec = bs58::decode(secret).into_vec().unwrap();
+        let mut public = [0u8; 32];
+        for (i, v) in public_vec.iter().enumerate() {
+            if i < 32 {
+                public[i] = *v;
+            }
+        }
+        let mut secret = [0u8; 64];
+        for (i, v) in secret_vec.iter().enumerate() {
+            if i < 64 {
+                secret[i] = *v;
+            }
+        }
+        Ok(DuniterKey { public, secret })
+    }
+
+    pub fn sign(&self, message: &str) -> DuniterSignature {
+        DuniterSignature::new(ed25519::signature(message.as_bytes(), &self.secret[..]))
+    }
+
+    pub fn verify(&self, message: &str, signature: &DuniterSignature) -> bool {
+        ed25519::verify(message.as_bytes(), &self.public, &signature.as_bytes())
+    }
+}
+
+fn derive_seed(salt: &str, passwd: &str) -> Result<Output, Box<dyn Error>> {
+    let saltb64 = base64::encode(salt); // convert utf8 to base64
+    let salt = SaltString::new(saltb64.as_str())?; // expectes base64-encoded str
+    let params = Params::new(12, 16, 1)?;
+    let password_hash = Scrypt.hash_password(passwd.as_bytes(), None, params, &salt)?;
+    Ok(password_hash.hash.expect("Seed should have been generated"))
+}
\ No newline at end of file
diff --git a/src/crypto/duniter_signature.rs b/src/crypto/duniter_signature.rs
new file mode 100644
index 0000000000000000000000000000000000000000..770fecb26a3e3c47e3b1656ca54ba5977486c691
--- /dev/null
+++ b/src/crypto/duniter_signature.rs
@@ -0,0 +1,21 @@
+use std::fmt::Formatter;
+
+const SIGNATURE_LENGTH: usize = 64;
+
+pub struct DuniterSignature([u8; SIGNATURE_LENGTH]);
+
+/// A DuniterSignature wraps an actuel signature (64 bytes data)
+impl DuniterSignature {
+    pub fn new(signature: [u8; SIGNATURE_LENGTH]) -> DuniterSignature {
+        DuniterSignature(signature)
+    }
+    pub fn as_bytes(&self) -> [u8; SIGNATURE_LENGTH] {
+        self.0
+    }
+}
+
+impl std::fmt::Display for DuniterSignature {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", base64::encode(self.0))
+    }
+}
\ No newline at end of file
diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e838147252916b52ea0d1d65b9b31b871cd853a1
--- /dev/null
+++ b/src/crypto/mod.rs
@@ -0,0 +1,4 @@
+mod duniter_signature;
+
+/// The API of crypto module.
+pub mod duniter_key;
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..85f9fdbf919000a5cd94224ba4d1df7ed82976c2
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,64 @@
+mod crypto;
+
+use crate::crypto::duniter_key::DuniterKey;
+
+pub fn compute_pub(salt: &str, passwd: &str) -> String {
+    let key = DuniterKey::from_scrypt(salt, passwd).expect("Error generating key from scrypt");
+    String::from(format!("{}", key).split(":").next().unwrap())
+}
+
+pub fn compute_sec(salt: &str, passwd: &str) -> String {
+    let key = DuniterKey::from_scrypt(salt, passwd).expect("Error generating key from scrypt");
+    let key_str = key.to_string();
+    let mut split = key_str.split(":");
+    split.next();
+    String::from(split.next().unwrap())
+}
+
+pub fn compute_key(salt: &str, passwd: &str) -> String {
+    let key = DuniterKey::from_scrypt(salt, passwd).expect("Error generating key from scrypt");
+    format!("{}", key)
+}
+
+#[cfg(test)]
+mod tests {
+
+    use crate::*;
+
+    const SALT: &str = "test_salt";
+    const PASSWD: &str = "test_passwd";
+    const PUBKEY: &str = "953SE7QJoDC2vFFwkSDojGKjUFU6BCu1kH6s4MnoyQCw";
+    const SECKEY: &str = "s3G7v2FPuTSWh8f9BSrU8EurwcM214x4fMLWFDLYmH7doqfyCqM96Ai5q4xB8y4GBKTCUvHoR3Et8AJKf7XTTZR";
+
+    #[test]
+    fn should_compute_pub() {
+        assert_eq!(compute_pub(SALT, PASSWD), PUBKEY);
+    }
+
+    #[test]
+    fn should_compute_sec() {
+        assert_eq!(compute_sec(SALT, PASSWD), SECKEY);
+    }
+
+    #[test]
+    fn should_compute_keyring() {
+        assert_eq!(compute_key(SALT, PASSWD), format!("{}:{}", PUBKEY, SECKEY));
+    }
+
+    #[test]
+    fn base58_or_scrypt_is_the_same() {
+        let scrypt_key = DuniterKey::from_scrypt(SALT, PASSWD).expect("Error generating key from scrypt");
+        let bs58_key = DuniterKey::from_base58(PUBKEY, SECKEY).expect("Wrong public/secret key");
+        assert_eq!(bs58_key.public, scrypt_key.public);
+        assert_eq!(bs58_key.secret, scrypt_key.secret);
+    }
+
+    #[test]
+    fn should_verify_or_reject_sig() {
+        let bs58_key = DuniterKey::from_base58(PUBKEY, SECKEY).expect("Wrong public/secret key");
+        let key = bs58_key;
+        let signature = key.sign("GOOD MESSAGE");
+        assert_eq!(true, key.verify("GOOD MESSAGE", &signature));
+        assert_eq!(false, key.verify("WRONG MESSAGE", &signature));
+    }
+}
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index e7a11a969c037e00a796aafeff6258501ec15e9a..368af760ffd9e434d676e3c1d5e581c40ee011ba 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,20 @@
+use std::env;
+
+use duniter_mini_client::{compute_pub, compute_sec, compute_key};
+
+use crate::cli::Command;
+pub use crate::cli::Command::{PUB, UNKNOWN};
+use crate::cli::Command::{SEC, KEYRING};
+
+mod cli;
+
 fn main() {
-    println!("Hello, world!");
-}
+    let command = Command::from(env::args());
+
+    match command {
+        PUB(salt, passwd) => println!("{}", compute_pub(salt.as_str(), passwd.as_str())),
+        SEC(salt, passwd) => println!("{}", compute_sec(salt.as_str(), passwd.as_str())),
+        KEYRING(salt, passwd) => println!("{}", compute_key(salt.as_str(), passwd.as_str())),
+        UNKNOWN(cmd) => eprintln!("Unknown command {}", cmd),
+    };
+}
\ No newline at end of file