diff --git a/CHANGELOG.md b/CHANGELOG.md
index b59fa8cef9579dcc515777811221f7932ca43eda..66db7a96212ffa37ee47460d15a6ba7ee40a21fa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased] - ReleaseDate
 
+### Added
+
+- Private message encryption/decryption with authentification
+
 ## [0.12.1] - 2020-03-03
 
 ### Fixed
diff --git a/Cargo.lock b/Cargo.lock
index dc2ddc1d85049a3195c2f5604624cb267eaad4f5..8cb891672cf117f91e666f9568a06091804b47a6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -285,7 +285,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
 dependencies = [
  "generic-array",
- "subtle",
+ "subtle 1.0.0",
 ]
 
 [[package]]
@@ -310,6 +310,19 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "curve25519-dalek"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26778518a7f6cffa1d25a44b602b62b979bd88adb9e99ffec546998cf3404839"
+dependencies = [
+ "byteorder",
+ "digest",
+ "rand_core",
+ "subtle 2.2.2",
+ "zeroize",
+]
+
 [[package]]
 name = "digest"
 version = "0.8.1"
@@ -330,6 +343,7 @@ dependencies = [
  "bs58",
  "byteorder",
  "criterion",
+ "curve25519-dalek",
  "ring",
  "scrypt",
  "serde",
@@ -372,6 +386,17 @@ dependencies = [
  "typenum",
 ]
 
+[[package]]
+name = "getrandom"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
 [[package]]
 name = "heck"
 version = "0.3.1"
@@ -579,6 +604,15 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom",
+]
+
 [[package]]
 name = "rayon"
 version = "1.3.0"
@@ -784,6 +818,12 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
 
+[[package]]
+name = "subtle"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941"
+
 [[package]]
 name = "syn"
 version = "1.0.14"
@@ -923,6 +963,12 @@ dependencies = [
  "winapi-util",
 ]
 
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
 [[package]]
 name = "wasm-bindgen"
 version = "0.2.58"
diff --git a/Cargo.toml b/Cargo.toml
index b145e0e983c343d35c92ff5c2a62cde23bdd4003..826600e11d6f4b59cd8ef3f8f41ba84abc8fe5de 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -39,10 +39,11 @@ harness = false
 required-features = ["criterion"]
 
 [features]
-default = ["dewif", "rand", "ser"]
+default = ["dewif", "private_message", "rand", "ser"]
 
 aes256 = ["aes"]
 dewif = ["aes256", "arrayvec"]
 x25519 = []
+private_message = ["arrayvec", "x25519", "rand"]
 rand = []
 ser = ["serde"]
diff --git a/src/keys/ed25519.rs b/src/keys/ed25519.rs
index e7d178b23ed8d90a9386c38bec4a7ab7923136d4..f13ee32519c6f01f70dbdfafd9ef382fb3394377 100644
--- a/src/keys/ed25519.rs
+++ b/src/keys/ed25519.rs
@@ -719,6 +719,7 @@ Timestamp: 0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855
         pubkey_bytes.copy_from_slice(&bytes[32..64]);
 
         let keypair = KeyPairFromSeed32Generator::generate(Seed32::new(seed));
+        println!("seed={}", keypair.seed());
         assert_eq!(
             "8hgzaeFnjkNCsemcaL4rmhB2999B79BydtE8xow4etB7",
             &keypair.public_key().to_base58()
diff --git a/src/lib.rs b/src/lib.rs
index c1f0f3bff4abbb3b8a5cf645214bcf64a7e3d6a4..06d8472f9d4e22bf1edea46b5a3592123fca87ca 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -26,6 +26,9 @@
 //!   * [Compute Sha256 hash](./hashs/index.html#compute-sha256-hash)
 //! * [Ed25519](./keys/index.html)
 //!   * [generate and use ed25519 key-pair](./keys/index.html#generate-and-use-ed25519-key-pair)
+//! * [Private message encryption with authentification](./private_message/index.html)
+//!   * [Encrypt a private message (sender side)](./private_message/index.html#encrypt-a-private-message-sender-side)
+//!   * [Decrypt a private message (receiver side)](./private_message/index.html#decrypt-a-private-message-receiver-side)
 //!
 
 #![deny(
@@ -48,6 +51,8 @@ pub mod bases;
 pub mod dewif;
 pub mod hashs;
 pub mod keys;
+#[cfg(feature = "private_message")]
+pub mod private_message;
 #[cfg(feature = "rand")]
 pub mod rand;
 pub mod seeds;
diff --git a/src/private_message.rs b/src/private_message.rs
new file mode 100644
index 0000000000000000000000000000000000000000..1655c7aaa07fc95f06938cc1be0fb11ca0e05829
--- /dev/null
+++ b/src/private_message.rs
@@ -0,0 +1,520 @@
+//  Copyright (C) 2020  Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! Private message encryption/decryption
+//!
+//! ## Encrypt a private message (sender side)
+//!
+//! **Warning**: Take the time to study which is the authentication policy adapted to **your specific use case**.
+//! Choosing an unsuitable authentication policy can be **dramatic for your end users**.
+//! Read the documentation of [AuthenticationPolicy](./enum.AuthenticationPolicy.html).
+//!
+//! ```;
+//! use dup_crypto::keys::{
+//!     KeyPair, PublicKey,
+//!     ed25519::{KeyPairFromSaltedPasswordGenerator, PublicKey as Ed25519PublicKey, SaltedPassword}
+//! };
+//! use dup_crypto::private_message::{Aad, Algorithm, AuthenticationPolicy, METADATA_LEN};
+//! use dup_crypto::seeds::Seed32;
+//!
+//! // Take the time to study which is the authentication policy adapted
+//! // to your specific use case.
+//! // Read `dup_crypto::private_message::AuthenticationPolicy` documentation.
+//! let authentication_policy = AuthenticationPolicy::PrivateAuthentication;
+//!
+//! // Regardless of the authentication policy chosen, the sender's key-pair is required.
+//! let sender_key_pair = KeyPairFromSaltedPasswordGenerator::with_default_parameters()
+//!     .generate(SaltedPassword::new("sender salt".to_owned(), "sender password".to_owned()));
+//!
+//! // Choose an encryption algorithm adapted to your specific use case.
+//! // Read `dup_crypto::private_message::Algorithm` documentation.
+//! let encryption_algo = Algorithm::Chacha20Poly1305;
+//!
+//! // Aad value must be known by the software that will decipher the message, it can be the
+//! // name of the service followed by the name of the network (name of the currency for example).
+//! // This field is only used to ensure that there is no interference between different services
+//! // and/or networks.
+//! let aad = Aad::from(b"service name - currency name");
+//!
+//! // Define receiver and message content
+//! // The message must be mutable because for performance reasons the encryption is applied
+//! // directly to the bytes of the message (the message is never copied).
+//! let receiver_public_key = Ed25519PublicKey::from_base58(
+//!     "8hgzaeFnjkNCsemcaL4rmhB2999B79BydtE8xow4etB7"
+//! ).expect("invalid public key");
+//! let message = b"This is a secret message, which can only be read by the recipient.";
+//!
+//! // It is up to you to create the buffer that will contain the encrypted message.
+//! // This gives you the freedom to choose how to allocate the memory space and in
+//! // which type of "container" to store the bytes of the encrypted message.
+//! // Metadata needed for decryption and authentication will be added to your message,
+//! // you must make sure that your buffer has enough capacity to hold this metadata.
+//! let mut buffer: Vec<u8> = Vec::with_capacity(message.len() + METADATA_LEN);
+//! buffer.extend(&message[..]);
+//!
+//! // Finally, authenticate and encrypt the message.
+//! dup_crypto::private_message::encrypt_private_message(
+//!     aad,
+//!     encryption_algo,
+//!     authentication_policy,
+//!     &mut buffer,
+//!     &receiver_public_key,
+//!     &sender_key_pair,
+//! )?;
+//!
+//! // Send message to the recipient by any way..
+//!
+//! # Ok::<(), dup_crypto::private_message::PrivateMessageError>(())
+//! ```
+//!
+//! ## Decrypt a private message (receiver side)
+//!
+//! ```
+//! use dup_crypto::keys::{KeyPair, ed25519::KeyPairFromSeed32Generator};
+//! use dup_crypto::private_message::{Aad, Algorithm};
+//! use dup_crypto::seeds::Seed32;
+//!
+//! let receiver_key_pair = KeyPairFromSeed32Generator::generate(
+//!     Seed32::from_base58("7nY1fYmCXL1vF86ptneeg8r7M6C7G93M8MCfzBCaCtiJ").expect("invalid seed")
+//! );
+//!
+//! let mut encrypted_message = vec![221u8, 252, 176, 127, 197, // ... several bytes hidden
+//! # 20, 191, 154, 245, 206, 154, 71, 71,
+//! # 169, 240, 50, 142, 231, 143, 239, 55, 31, 117, 197, 66, 90, 232, 14, 108, 203, 188, 70, 123, 75,
+//! # 216, 55, 5, 57, 60, 35, 185, 99, 147, 23, 51, 57, 93, 213, 149, 101, 24, 195, 18, 168, 37, 71, 182,
+//! # 220, 198, 250, 72, 199, 21, 66, 15, 57, 144, 247, 54, 19, 30, 134, 210, 227, 205, 113, 142, 15, 77,
+//! # 76, 223, 132, 38, 237, 100, 139, 227, 115, 49, 216, 102, 120, 124, 84, 208, 85, 242, 141, 216, 145,
+//! # 10, 17, 168, 219, 129, 199, 149, 188, 210, 123, 79, 128, 76, 159, 133, 251, 95, 29, 238, 43, 225,
+//! # 211, 43, 197, 237, 93, 79, 243, 120, 227, 153, 79, 57, 1, 23, 233, 167, 110, 210, 16, 52, 16, 73, 13,
+//! # 214, 16, 223, 17, 175, 228, 211, 151, 79, 227, 14, 56, 135, 77, 73, 36, 22, 115, 77, 201, 114, 38,
+//! # 206, 240, 212, 129, 247, 111, 165, 182, 98, 176, 247, 69, 198, 34, 71, 26, 176, 147, 205, 173, 50,
+//! # 247, 151, 148, 197, 162, 88, 254, 185, 149, 108, 2, 137, 139, 66, 82, 168, 213, 118, 218, 188, 238,
+//! # 147, 89, 156];
+//!
+//! let (message, signature_opt) = dup_crypto::private_message::decrypt_private_message(
+//!     Aad::from(b"service name - currency name"),
+//!     Algorithm::Chacha20Poly1305,
+//!     &mut encrypted_message,
+//!     &receiver_key_pair,
+//! )?;
+//!
+//! assert_eq!(
+//!     message,
+//!     &b"This is a secret message, which can only be read by the recipient."[..],
+//! );
+//! assert_eq!(
+//!     signature_opt,
+//!     None
+//! );
+//!
+//! # Ok::<(), dup_crypto::private_message::PrivateMessageError>(())
+//! ```
+//!
+
+mod authentication;
+
+pub use self::authentication::AuthenticationPolicy;
+pub use ring::aead::Aad;
+
+use self::authentication::{
+    generate_authentication_proof, verify_authentication_proof, write_anthentication_datas,
+};
+use crate::keys::ed25519::{
+    Ed25519KeyPair, KeyPairFromSeed32Generator, PublicKey as Ed25519PublicKey, Signature,
+};
+use crate::keys::x25519::{diffie_hellman, X25519PublicKey, X25519SecretKey};
+use crate::keys::{KeyPair, PubkeyFromBytesError};
+use crate::rand::UnspecifiedRandError;
+use crate::seeds::Seed32;
+use ring::aead::{LessSafeKey, Nonce, Tag, UnboundKey};
+use ring::pbkdf2;
+use std::convert::TryFrom;
+use std::num::NonZeroU32;
+use zeroize::Zeroize;
+
+/// Metadata length
+pub const METADATA_LEN: usize = 129; // EPHEMERAL_PUBLIC_KEY_LEN + AUTHENTICATION_DATAS_LEN
+
+const ITERATIONS: u32 = 3;
+const SENDER_PUBLIC_KEY_LEN: usize = 32;
+const EPHEMERAL_PUBLIC_KEY_LEN: usize = 32;
+const AUTHENTICATION_DATAS_LEN: usize = 97;
+
+/// Private message encryption algorithm
+/// If your program is susceptible to running on machines that do not provide hardware
+/// acceleration for AES (some phones, embedded devices, old computers, etc) then you
+/// should choose `Chacha20Poly1305`. Even on devices with hardware acceleration for AES,
+/// the performance of `Chacha20Poly1305` is often equivalent to `Aes256Gcm`, so only choose
+/// `Aes256Gcm` if you have strong reasons to do so.
+#[derive(Clone, Copy, Debug)]
+pub enum Algorithm {
+    /// AES-256 in GCM mode with 128-bit tags and 96 bit nonces.
+    Aes256Gcm,
+    /// ChaCha20-Poly1305 as described in [RFC 7539](https://tools.ietf.org/html/rfc7539).
+    Chacha20Poly1305,
+}
+
+impl Algorithm {
+    fn to_ring_algo(self) -> &'static ring::aead::Algorithm {
+        match self {
+            Self::Aes256Gcm => &ring::aead::AES_256_GCM,
+            Self::Chacha20Poly1305 => &ring::aead::CHACHA20_POLY1305,
+        }
+    }
+}
+
+/// Error at encryption/decryption of a private message
+#[derive(Debug)]
+pub enum PrivateMessageError {
+    /// I/O error
+    IoError(std::io::Error),
+    /// Invalid ephemeral pubkey
+    InvalidEphemeralPubkey(PubkeyFromBytesError),
+    /// Invalid sender pubkey
+    InvalidSenderPubkey(PubkeyFromBytesError),
+    /// Invalid authentication proof : invalid signature
+    InvalidAuthenticationProof,
+    /// Unspecified errror
+    Unspecified,
+}
+
+impl From<std::io::Error> for PrivateMessageError {
+    fn from(e: std::io::Error) -> Self {
+        PrivateMessageError::IoError(e)
+    }
+}
+
+impl From<UnspecifiedRandError> for PrivateMessageError {
+    fn from(_: UnspecifiedRandError) -> Self {
+        PrivateMessageError::Unspecified
+    }
+}
+
+impl From<ring::error::Unspecified> for PrivateMessageError {
+    fn from(_: ring::error::Unspecified) -> Self {
+        PrivateMessageError::Unspecified
+    }
+}
+
+#[derive(Zeroize)]
+#[zeroize(drop)]
+struct SharedSecret([u8; 48]);
+
+impl Default for SharedSecret {
+    fn default() -> Self {
+        SharedSecret([0u8; 48])
+    }
+}
+
+impl AsRef<[u8]> for SharedSecret {
+    fn as_ref(&self) -> &[u8] {
+        &self.0
+    }
+}
+
+impl AsMut<[u8]> for SharedSecret {
+    fn as_mut(&mut self) -> &mut [u8] {
+        &mut self.0
+    }
+}
+
+/// Encrypt private message
+pub fn encrypt_private_message<A, M>(
+    additionally_authenticated_data: Aad<A>,
+    algorithm: Algorithm,
+    authentication_policy: AuthenticationPolicy,
+    message: &mut M,
+    receiver_public_key: &Ed25519PublicKey,
+    sender_keypair: &Ed25519KeyPair,
+) -> Result<(), PrivateMessageError>
+where
+    A: AsRef<[u8]>,
+    M: AsRef<[u8]> + AsMut<[u8]> + Extend<u8>,
+{
+    // Generate ephemeral ed25519 keypair
+    let ephemeral_keypair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
+
+    // Compute DH exchange (ephemeral_secret_key, receiver_public_key)
+    // and derive symmetric_key and nonce from shared secret
+    let (symmetric_key, nonce) = generate_symmetric_key_and_nonce(
+        algorithm,
+        ephemeral_keypair.public_key().as_ref(),
+        ephemeral_keypair.seed(),
+        &receiver_public_key,
+    )?;
+
+    // Write encrypted footer (=authentication datas)
+    let encrypted_footer = write_anthentication_datas(
+        &sender_keypair.public_key(),
+        generate_authentication_proof(
+            authentication_policy,
+            sender_keypair,
+            receiver_public_key,
+            message.as_ref(),
+        ),
+        authentication_policy,
+    );
+    message.extend(encrypted_footer);
+
+    // Encrypt message
+    let tag = encrypt::<A>(
+        symmetric_key,
+        nonce,
+        additionally_authenticated_data,
+        message.as_mut(),
+    )?;
+
+    // write clear footer (tag and ephemeral_public_key)
+    let mut clear_footer = arrayvec::ArrayVec::<[u8; 64]>::new();
+    clear_footer
+        .try_extend_from_slice(tag.as_ref())
+        .expect("too long tag");
+    clear_footer
+        .try_extend_from_slice(ephemeral_keypair.public_key().as_ref())
+        .expect("too long ephemeral_public_key");
+    message.extend(clear_footer.into_iter());
+
+    Ok(())
+}
+
+/// Decrypt private message.
+/// Return a reference to decrypted bytes and an optional signature.
+/// If the authentication method chosen by the sender is `Signature`,
+/// then the signature is necessarily returned. The signature is returned
+/// to allow subsequent publication of proof that this particular message was sent by the sender.
+pub fn decrypt_private_message<'m, A: AsRef<[u8]>>(
+    additionally_authenticated_data: Aad<A>,
+    algorithm: Algorithm,
+    encrypted_message: &'m mut [u8],
+    receiver_key_pair: &Ed25519KeyPair,
+) -> Result<(&'m [u8], Option<Signature>), PrivateMessageError> {
+    // Get ephemeral public key
+    let len = encrypted_message.len();
+    let ephemeral_public_key = &encrypted_message[(len - EPHEMERAL_PUBLIC_KEY_LEN)..];
+
+    // Compute DH exchange (receiver_secret_key, ephemeral_public_key)
+    // and derive symmetric_key and nonce from shared secret
+    let (symmetric_key, nonce) = generate_symmetric_key_and_nonce(
+        algorithm,
+        &ephemeral_public_key,
+        &receiver_key_pair.seed(),
+        &Ed25519PublicKey::try_from(ephemeral_public_key)
+            .map_err(PrivateMessageError::InvalidEphemeralPubkey)?,
+    )?;
+
+    // Decrypt message
+    decrypt::<A>(
+        symmetric_key,
+        nonce,
+        additionally_authenticated_data,
+        &mut encrypted_message[..(len - EPHEMERAL_PUBLIC_KEY_LEN)],
+    )?;
+
+    // Verify authentication proof
+    let tag_len = algorithm.to_ring_algo().tag_len();
+    let authent_end = len - EPHEMERAL_PUBLIC_KEY_LEN - tag_len;
+    let authent_begin = authent_end - AUTHENTICATION_DATAS_LEN;
+    let sig_opt = verify_authentication_proof(
+        receiver_key_pair,
+        &encrypted_message[..authent_begin],
+        &encrypted_message[authent_begin..authent_end],
+    )?;
+
+    Ok((&encrypted_message[..authent_begin], sig_opt))
+}
+
+fn generate_symmetric_key_and_nonce(
+    algorithm: Algorithm,
+    ephemeral_public_key: &[u8],
+    exchange_secret_key: &Seed32,
+    exchange_public_key: &Ed25519PublicKey,
+) -> Result<(UnboundKey, Nonce), PrivateMessageError> {
+    let shared_secret = diffie_hellman(
+        X25519SecretKey::from(exchange_secret_key),
+        X25519PublicKey::from(exchange_public_key),
+        |key_material| derive(key_material, ephemeral_public_key),
+    );
+
+    let symmetric_key = UnboundKey::new(algorithm.to_ring_algo(), &shared_secret.as_ref()[..32])?;
+    let nonce = Nonce::try_assume_unique_for_key(&shared_secret.as_ref()[32..44])?;
+
+    Ok((symmetric_key, nonce))
+}
+
+fn derive(seed: &[u8], salt: &[u8]) -> SharedSecret {
+    let mut shared_secret = SharedSecret::default();
+    pbkdf2::derive(
+        pbkdf2::PBKDF2_HMAC_SHA384,
+        NonZeroU32::new(ITERATIONS).expect("ITERATIONS must be > 0"),
+        salt,
+        seed,
+        shared_secret.as_mut(),
+    );
+    shared_secret
+}
+
+fn encrypt<A: AsRef<[u8]>>(
+    key: UnboundKey,
+    nonce: Nonce,
+    aad: Aad<A>,
+    message: &mut [u8],
+) -> Result<Tag, PrivateMessageError> {
+    let key = LessSafeKey::new(key);
+    Ok(key.seal_in_place_separate_tag(nonce, aad, message.as_mut())?)
+}
+
+fn decrypt<A: AsRef<[u8]>>(
+    key: UnboundKey,
+    nonce: Nonce,
+    aad: Aad<A>,
+    encrypted_message: &mut [u8],
+) -> Result<(), PrivateMessageError> {
+    let key = LessSafeKey::new(key);
+    key.open_in_place(nonce, aad, encrypted_message)?;
+
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+    use crate::keys::ed25519::KeyPairFromSeed32Generator;
+    use crate::keys::KeyPair;
+
+    const AAD: &[u8] = b"service name - currency name";
+    const MESSAGE: &[u8] =
+        b"Hello, this is a secret message, which can only be read by the recipient.";
+
+    #[test]
+    fn encrypt_same_message_must_be_different() -> Result<(), PrivateMessageError> {
+        let sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
+        let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
+
+        let message = MESSAGE;
+
+        let encrypted_message1 =
+            test_encrypt(message, &receiver_key_pair.public_key(), &sender_key_pair)?;
+
+        let encrypted_message2 =
+            test_encrypt(message, &receiver_key_pair.public_key(), &sender_key_pair)?;
+
+        assert_ne!(encrypted_message1, encrypted_message2);
+        assert_ne!(encrypted_message1[32..37], encrypted_message2[32..37]);
+
+        Ok(())
+    }
+
+    #[test]
+    fn encrypt_then_decrypt_with_invalid_aad() -> Result<(), PrivateMessageError> {
+        let sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
+        let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
+
+        let message = MESSAGE;
+
+        let mut encrypted_message =
+            test_encrypt(message, &receiver_key_pair.public_key(), &sender_key_pair)?;
+
+        println!("encrypted message={:?}", encrypted_message);
+
+        match decrypt_private_message(
+            Aad::from(b"invalid aad"),
+            Algorithm::Chacha20Poly1305,
+            &mut encrypted_message,
+            &receiver_key_pair,
+        ) {
+            Ok(_) => panic!("Expected error rivateMessageError::Unspecified, found: Ok(())."),
+            Err(PrivateMessageError::Unspecified) => Ok(()),
+            Err(e) => panic!(
+                "Expected error rivateMessageError::Unspecified, found: {:?}.",
+                e
+            ),
+        }
+    }
+
+    #[test]
+    fn encrypt_then_decrypt_with_invalid_algorithm() -> Result<(), PrivateMessageError> {
+        let sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
+        let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
+
+        let message = MESSAGE;
+
+        let mut encrypted_message =
+            test_encrypt(message, &receiver_key_pair.public_key(), &sender_key_pair)?;
+
+        println!("encrypted message={:?}", encrypted_message);
+
+        match decrypt_private_message(
+            Aad::from(AAD),
+            Algorithm::Aes256Gcm,
+            &mut encrypted_message,
+            &receiver_key_pair,
+        ) {
+            Ok(_) => panic!("Expected error rivateMessageError::Unspecified, found: Ok(())."),
+            Err(PrivateMessageError::Unspecified) => Ok(()),
+            Err(e) => panic!(
+                "Expected error rivateMessageError::Unspecified, found: {:?}.",
+                e
+            ),
+        }
+    }
+
+    #[test]
+    fn encrypt_and_decrypt_ok() -> Result<(), PrivateMessageError> {
+        let sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
+        let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
+
+        let message = MESSAGE;
+
+        let mut encrypted_message =
+            test_encrypt(message, &receiver_key_pair.public_key(), &sender_key_pair)?;
+
+        println!("encrypted message={:?}", encrypted_message);
+
+        let (decrypted_message, sig_opt) = decrypt_private_message(
+            Aad::from(AAD),
+            Algorithm::Chacha20Poly1305,
+            &mut encrypted_message,
+            &receiver_key_pair,
+        )?;
+
+        println!("decrypted message={:?}", decrypted_message);
+
+        assert_eq!(decrypted_message, message);
+        assert_eq!(sig_opt, None);
+
+        Ok(())
+    }
+
+    fn test_encrypt(
+        message: &[u8],
+        receiver_public_key: &Ed25519PublicKey,
+        sender_keypair: &Ed25519KeyPair,
+    ) -> Result<Vec<u8>, PrivateMessageError> {
+        let mut encrypted_message = Vec::new();
+        encrypted_message.extend(message);
+
+        encrypt_private_message(
+            Aad::from(AAD),
+            Algorithm::Chacha20Poly1305,
+            AuthenticationPolicy::PrivateAuthentication,
+            &mut encrypted_message,
+            receiver_public_key,
+            sender_keypair,
+        )?;
+
+        Ok(encrypted_message)
+    }
+}
diff --git a/src/private_message/authentication.rs b/src/private_message/authentication.rs
new file mode 100644
index 0000000000000000000000000000000000000000..52f626f3554a71adc4e3e3787b4e8582c2e15b24
--- /dev/null
+++ b/src/private_message/authentication.rs
@@ -0,0 +1,311 @@
+//  Copyright (C) 2020  Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! Handle private message authentication policy
+
+use super::{PrivateMessageError, AUTHENTICATION_DATAS_LEN, SENDER_PUBLIC_KEY_LEN};
+use crate::keys::ed25519::{Ed25519KeyPair, PublicKey as Ed25519PublicKey, Signature};
+use crate::keys::x25519::{diffie_hellman, X25519PublicKey, X25519SecretKey};
+use crate::keys::{KeyPair, PublicKey, Signator};
+use ring::digest;
+use std::convert::TryFrom;
+
+#[derive(Clone, Copy, Debug)]
+/// Authentication policy.
+///
+/// **Warning**: Take the time to study which is the authentication policy adapted to **your specific use case**.
+/// Choosing an unsuitable authentication policy can be **dramatic for your end users**.
+pub enum AuthenticationPolicy {
+    /// Only the sender and the recipient have proof that the message was written by one of them.
+    /// The recipient knows that he is not the author of the message so he has proof that the message was necessarily written by the sender.
+    /// If your use case is the encrypted correspondence between machines in a decentralized network,
+    /// and you sometimes need to prove that a machine has sent this or that message (to prove for example that it has not honored a commitment),
+    /// then choose policy `Signature` instead.
+    PrivateAuthentication,
+    /// The sender proves that he is the author of the message.
+    /// If the message is publicly disclosed, everyone will have proof that the sender is indeed the one who wrote the message.
+    /// In certain uses this can be harmful to the sender: in case of conflict with the recipient,
+    /// the latter may threaten to disclose their private correspondence to blackmail the sender.
+    /// If your use case is private messaging between humans, choose method `PrivateAuthentication` instead.
+    Signature,
+}
+
+impl Into<u8> for AuthenticationPolicy {
+    fn into(self) -> u8 {
+        match self {
+            Self::PrivateAuthentication => 0,
+            Self::Signature => 1,
+        }
+    }
+}
+
+impl From<u8> for AuthenticationPolicy {
+    fn from(source: u8) -> Self {
+        match source {
+            0 => Self::PrivateAuthentication,
+            _ => Self::Signature,
+        }
+    }
+}
+
+pub(crate) struct AuthenticationProof([u8; 64]);
+
+impl AsRef<[u8]> for AuthenticationProof {
+    fn as_ref(&self) -> &[u8] {
+        &self.0
+    }
+}
+
+pub(crate) fn write_anthentication_datas(
+    sender_public_key: &Ed25519PublicKey,
+    authent_proof: AuthenticationProof,
+    authent_policy: AuthenticationPolicy,
+) -> impl AsRef<[u8]> + IntoIterator<Item = u8> {
+    let mut authent_datas = arrayvec::ArrayVec::<[u8; 128]>::new();
+    authent_datas
+        .try_extend_from_slice(sender_public_key.as_ref())
+        .expect("too long sender public key");
+    authent_datas
+        .try_extend_from_slice(authent_proof.as_ref())
+        .expect("too long authent_proof");
+    authent_datas.push(authent_policy.into());
+    authent_datas
+}
+
+pub(crate) fn generate_authentication_proof(
+    authentication_policy: AuthenticationPolicy,
+    sender_keypair: &Ed25519KeyPair,
+    receiver_public_key: &Ed25519PublicKey,
+    message: &[u8],
+) -> AuthenticationProof {
+    AuthenticationProof(match authentication_policy {
+        AuthenticationPolicy::PrivateAuthentication => diffie_hellman(
+            X25519SecretKey::from(sender_keypair.seed()),
+            X25519PublicKey::from(receiver_public_key),
+            |key_material| {
+                let mut hash = [0u8; 64];
+                let mut ctx = digest::Context::new(&digest::SHA512);
+                ctx.update(message.as_ref());
+                ctx.update(key_material);
+                let digest = ctx.finish();
+                hash.copy_from_slice(digest.as_ref());
+                hash
+            },
+        ),
+        AuthenticationPolicy::Signature => {
+            sender_keypair.generate_signator().sign(message.as_ref()).0
+        }
+    })
+}
+
+pub(crate) fn verify_authentication_proof(
+    receiver_key_pair: &Ed25519KeyPair,
+    message: &[u8],
+    authentication_datas: &[u8],
+) -> Result<Option<Signature>, PrivateMessageError> {
+    let sender_public_key =
+        Ed25519PublicKey::try_from(&authentication_datas[..SENDER_PUBLIC_KEY_LEN])
+            .map_err(PrivateMessageError::InvalidSenderPubkey)?;
+    let mut authent_proof = AuthenticationProof([0u8; 64]);
+    authent_proof.0.copy_from_slice(
+        &authentication_datas[SENDER_PUBLIC_KEY_LEN..(AUTHENTICATION_DATAS_LEN - 1)],
+    );
+    let mut signature_opt = None;
+    match AuthenticationPolicy::from(authentication_datas[AUTHENTICATION_DATAS_LEN - 1]) {
+        AuthenticationPolicy::PrivateAuthentication => {
+            let expected_proof = AuthenticationProof(diffie_hellman(
+                X25519SecretKey::from(receiver_key_pair.seed()),
+                X25519PublicKey::from(&sender_public_key),
+                |key_material| {
+                    let mut hash = [0u8; 64];
+                    let mut ctx = digest::Context::new(&digest::SHA512);
+                    ctx.update(message.as_ref());
+                    ctx.update(key_material);
+                    let digest = ctx.finish();
+                    hash.copy_from_slice(digest.as_ref());
+                    hash
+                },
+            ));
+            for i in 0..64 {
+                if expected_proof.0[i] != authent_proof.0[i] {
+                    return Err(PrivateMessageError::InvalidAuthenticationProof);
+                }
+            }
+        }
+        AuthenticationPolicy::Signature => {
+            signature_opt = Some(Signature(authent_proof.0));
+            sender_public_key
+                .verify(message, &Signature(authent_proof.0))
+                .map_err(|_| PrivateMessageError::InvalidAuthenticationProof)?;
+        }
+    }
+    Ok(signature_opt)
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+    use crate::keys::ed25519::KeyPairFromSeed32Generator;
+    use crate::seeds::Seed32;
+
+    const MESSAGE: &[u8] = b"message";
+
+    #[test]
+    fn private_authent_ok() -> Result<(), PrivateMessageError> {
+        let sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
+        let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
+
+        let authent_policy = AuthenticationPolicy::PrivateAuthentication;
+
+        let authent_proof = generate_authentication_proof(
+            authent_policy,
+            &sender_key_pair,
+            &receiver_key_pair.public_key(),
+            MESSAGE,
+        );
+
+        let authent_datas = write_anthentication_datas(
+            &sender_key_pair.public_key(),
+            authent_proof,
+            authent_policy,
+        );
+
+        verify_authentication_proof(&receiver_key_pair, MESSAGE, authent_datas.as_ref())?;
+
+        Ok(())
+    }
+
+    #[test]
+    fn invalid_sender_pubkey() -> Result<(), PrivateMessageError> {
+        let sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
+        let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
+
+        let authent_policy = AuthenticationPolicy::PrivateAuthentication;
+
+        let authent_proof = generate_authentication_proof(
+            authent_policy,
+            &sender_key_pair,
+            &receiver_key_pair.public_key(),
+            MESSAGE,
+        );
+
+        let mut authent_datas: Vec<u8> = write_anthentication_datas(
+            &sender_key_pair.public_key(),
+            authent_proof,
+            authent_policy,
+        )
+        .as_ref()
+        .to_vec();
+
+        let invalid_pubkey_bytes = [
+            206u8, 58, 67, 221, 20, 133, 0, 225, 86, 115, 26, 104, 142, 116, 140, 132, 119, 51,
+            175, 45, 82, 225, 14, 195, 7, 107, 43, 212, 8, 37, 234, 23,
+        ];
+
+        authent_datas[..32].copy_from_slice(&invalid_pubkey_bytes);
+
+        if let Err(PrivateMessageError::InvalidSenderPubkey(_)) =
+            verify_authentication_proof(&receiver_key_pair, MESSAGE, authent_datas.as_ref())
+        {
+            Ok(())
+        } else {
+            panic!("Expected PrivateMessageError::InvalidSenderPubkey.")
+        }
+    }
+
+    #[test]
+    fn invalid_private_authent_proof() -> Result<(), PrivateMessageError> {
+        let sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
+        let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
+
+        let authent_policy = AuthenticationPolicy::PrivateAuthentication;
+
+        let authent_proof = generate_authentication_proof(
+            authent_policy,
+            &receiver_key_pair, // invalid key pair
+            &receiver_key_pair.public_key(),
+            MESSAGE,
+        );
+
+        let authent_datas = write_anthentication_datas(
+            &sender_key_pair.public_key(),
+            authent_proof,
+            authent_policy,
+        );
+
+        if let Err(PrivateMessageError::InvalidAuthenticationProof) =
+            verify_authentication_proof(&receiver_key_pair, MESSAGE, authent_datas.as_ref())
+        {
+            Ok(())
+        } else {
+            panic!("Expected PrivateMessageError::InvalidSenderPubkey.")
+        }
+    }
+
+    #[test]
+    fn invalid_sig_authent_proof() -> Result<(), PrivateMessageError> {
+        let sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
+        let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
+
+        let authent_policy = AuthenticationPolicy::Signature;
+
+        let authent_proof = generate_authentication_proof(
+            authent_policy,
+            &receiver_key_pair, // invalid key pair
+            &receiver_key_pair.public_key(),
+            MESSAGE,
+        );
+
+        let authent_datas = write_anthentication_datas(
+            &sender_key_pair.public_key(),
+            authent_proof,
+            authent_policy,
+        );
+
+        if let Err(PrivateMessageError::InvalidAuthenticationProof) =
+            verify_authentication_proof(&receiver_key_pair, MESSAGE, authent_datas.as_ref())
+        {
+            Ok(())
+        } else {
+            panic!("Expected PrivateMessageError::InvalidSenderPubkey.")
+        }
+    }
+
+    #[test]
+    fn sig_authent_ok() -> Result<(), PrivateMessageError> {
+        let sender_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
+        let receiver_key_pair = KeyPairFromSeed32Generator::generate(Seed32::random()?);
+
+        let authent_policy = AuthenticationPolicy::Signature;
+
+        let authent_proof = generate_authentication_proof(
+            authent_policy,
+            &sender_key_pair,
+            &receiver_key_pair.public_key(),
+            MESSAGE,
+        );
+
+        let authent_datas = write_anthentication_datas(
+            &sender_key_pair.public_key(),
+            authent_proof,
+            authent_policy,
+        );
+
+        verify_authentication_proof(&receiver_key_pair, MESSAGE, authent_datas.as_ref())?;
+
+        Ok(())
+    }
+}