diff --git a/Cargo.lock b/Cargo.lock
index 4231a086a501edfab6b11677e8bca2bfd4a74a73..ba62ddef50eefaf84463c67904569113bcbc42f2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -15,6 +15,15 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "ansi_term"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+dependencies = [
+ "winapi 0.3.8",
+]
+
 [[package]]
 name = "autocfg"
 version = "1.0.0"
@@ -37,6 +46,12 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
 [[package]]
 name = "bs58"
 version = "0.3.1"
@@ -67,6 +82,18 @@ version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
 
+[[package]]
+name = "core_affinity"
+version = "0.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f8a03115cc34fb0d7c321dd154a3914b3ca082ccc5c11d91bf7117dbbe7171f"
+dependencies = [
+ "kernel32-sys",
+ "libc",
+ "num_cpus",
+ "winapi 0.2.8",
+]
+
 [[package]]
 name = "crc32fast"
 version = "1.2.0"
@@ -129,6 +156,16 @@ version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "697c714f50560202b1f4e2e09cd50a421881c83e9025db75d15f276616f04f40"
 
+[[package]]
+name = "ctor"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf6b25ee9ac1995c54d7adb2eff8cfffb7260bc774fb63c601ec65467f43cd9d"
+dependencies = [
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "curve25519-dalek"
 version = "2.0.0"
@@ -142,6 +179,12 @@ dependencies = [
  "zeroize",
 ]
 
+[[package]]
+name = "difference"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
+
 [[package]]
 name = "digest"
 version = "0.8.1"
@@ -151,6 +194,18 @@ dependencies = [
  "generic-array",
 ]
 
+[[package]]
+name = "dubp-pow"
+version = "0.1.0"
+dependencies = [
+ "core_affinity",
+ "duniter-common-tools",
+ "dup-crypto",
+ "log",
+ "rand",
+ "thiserror",
+]
+
 [[package]]
 name = "dubp-wot"
 version = "0.11.0"
@@ -162,6 +217,16 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "duniter-common-tools"
+version = "0.1.0"
+dependencies = [
+ "pretty_assertions",
+ "serde",
+ "shrinkwraprs",
+ "thiserror",
+]
+
 [[package]]
 name = "duniteroxyde"
 version = "0.2.9"
@@ -238,6 +303,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "js-sys"
 version = "0.3.37"
@@ -247,6 +321,16 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "kernel32-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
+dependencies = [
+ "winapi 0.2.8",
+ "winapi-build",
+]
+
 [[package]]
 name = "lazy_static"
 version = "1.4.0"
@@ -350,6 +434,33 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "output_vt100"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
+dependencies = [
+ "winapi 0.3.8",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
+
+[[package]]
+name = "pretty_assertions"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427"
+dependencies = [
+ "ansi_term",
+ "ctor",
+ "difference",
+ "output_vt100",
+]
+
 [[package]]
 name = "proc-macro2"
 version = "1.0.10"
@@ -368,6 +479,29 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom",
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
 [[package]]
 name = "rand_core"
 version = "0.5.1"
@@ -377,6 +511,15 @@ dependencies = [
  "getrandom",
 ]
 
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core",
+]
+
 [[package]]
 name = "rayon"
 version = "1.3.0"
@@ -431,7 +574,7 @@ dependencies = [
  "spin",
  "untrusted",
  "web-sys",
- "winapi",
+ "winapi 0.3.8",
 ]
 
 [[package]]
@@ -475,6 +618,19 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "shrinkwraprs"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e63e6744142336dfb606fe2b068afa2e1cca1ee6a5d8377277a92945d81fa331"
+dependencies = [
+ "bitflags",
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "spin"
 version = "0.5.2"
@@ -633,6 +789,12 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "winapi"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
+
 [[package]]
 name = "winapi"
 version = "0.3.8"
@@ -643,6 +805,12 @@ dependencies = [
  "winapi-x86_64-pc-windows-gnu",
 ]
 
+[[package]]
+name = "winapi-build"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
+
 [[package]]
 name = "winapi-i686-pc-windows-gnu"
 version = "0.4.0"
diff --git a/Cargo.toml b/Cargo.toml
index 551b803b8e95fa86068ce5cce35a3c0d1ba91003..8403939e17451b3dd860b765f41b95d46ddef650 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,2 +1,6 @@
 [workspace]
-members = ["neon/native"]
+members = [
+    "neon/native",
+    "rust-libs/pow",
+    "rust-libs/common-tools"
+]
diff --git a/rust-libs/common-tools/Cargo.toml b/rust-libs/common-tools/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..023d9db8b9ba5afcc4c7cb940acc13c0b9ecf8e4
--- /dev/null
+++ b/rust-libs/common-tools/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "duniter-common-tools"
+version = "0.1.0"
+authors = ["elois <elois@duniter.org>"]
+description = "Common rust tools for Duniter project."
+keywords = ["duniter", "tools"]
+license = "AGPL-3.0"
+edition = "2018"
+
+[lib]
+path = "src/lib.rs"
+
+[dependencies]
+shrinkwraprs = "0.3.*"
+serde = { version = "1.0.*", features = ["derive"] }
+thiserror = "1.0.11"
+
+[dev-dependencies]
+pretty_assertions = "0.6.1"
diff --git a/rust-libs/common-tools/src/lib.rs b/rust-libs/common-tools/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..772035c354767798ec1951e4045a02e4601b0cee
--- /dev/null
+++ b/rust-libs/common-tools/src/lib.rs
@@ -0,0 +1,82 @@
+//  Copyright (C) 2019  É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/>.
+
+//! Common rust tools for Duniter project.
+
+#![deny(
+    clippy::option_unwrap_used,
+    clippy::result_unwrap_used,
+    missing_docs,
+    missing_debug_implementations,
+    missing_copy_implementations,
+    trivial_casts,
+    trivial_numeric_casts,
+    unsafe_code,
+    unstable_features,
+    unused_import_braces
+)]
+
+//pub mod fns;
+//pub mod macros;
+pub mod traits;
+
+use thiserror::Error;
+
+/// Percent
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub struct Percent(u8);
+
+/// Percent error
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Error, Hash)]
+pub enum PercentError {
+    /// Percent too large (greater than 100)
+    #[error("percent too large (greater than 100)")]
+    TooLarge(u8),
+}
+
+impl Percent {
+    /// Max percent (= 100%)
+    pub const MAX: Percent = Percent(100u8);
+
+    /// New percent
+    pub fn new(percent: u8) -> Result<Percent, PercentError> {
+        if percent <= 100 {
+            Ok(Percent(percent))
+        } else {
+            Err(PercentError::TooLarge(percent))
+        }
+    }
+}
+
+impl Into<u8> for Percent {
+    fn into(self) -> u8 {
+        self.0
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+
+    #[test]
+    fn test_percent() {
+        assert_eq!(Percent::new(101), Err(PercentError::TooLarge(101)));
+
+        let percent = Percent::new(100).expect("wrong percent");
+        let percent_value: u8 = percent.into();
+        assert_eq!(percent_value, 100u8);
+    }
+}
diff --git a/rust-libs/common-tools/src/traits.rs b/rust-libs/common-tools/src/traits.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2bee8814ed961c4ef97389a97a213dacdf509455
--- /dev/null
+++ b/rust-libs/common-tools/src/traits.rs
@@ -0,0 +1,23 @@
+//  Copyright (C) 2019  É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/>.
+
+//! Common rust traits for Duniter project.
+
+pub mod bool_ext;
+pub mod merge;
+
+/// Allows to mark the real structure in order to differentiate it from the mocked structure,
+/// is essential in some special cases
+pub trait NotMock {}
diff --git a/rust-libs/common-tools/src/traits/bool_ext.rs b/rust-libs/common-tools/src/traits/bool_ext.rs
new file mode 100644
index 0000000000000000000000000000000000000000..612bab9caf00248df973ba6d56be7360303de60a
--- /dev/null
+++ b/rust-libs/common-tools/src/traits/bool_ext.rs
@@ -0,0 +1,49 @@
+//  Copyright (C) 2019  É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/>.
+
+//! Trait BoolExt.
+
+/// Transform any type to Result
+pub trait BoolExt {
+    /// Transform any type to result<(), E>
+    fn or_err<E>(self, err: E) -> Result<(), E>;
+    /// Transform any type to result<T, E>
+    fn as_result<T, E>(self, ok: T, err: E) -> Result<T, E>;
+    /// Reverse bool
+    fn not(&self) -> bool;
+}
+
+impl BoolExt for bool {
+    #[inline]
+    fn or_err<E>(self, err: E) -> Result<(), E> {
+        if self {
+            Ok(())
+        } else {
+            Err(err)
+        }
+    }
+    #[inline]
+    fn as_result<T, E>(self, ok: T, err: E) -> Result<T, E> {
+        if self {
+            Ok(ok)
+        } else {
+            Err(err)
+        }
+    }
+    #[inline]
+    fn not(&self) -> bool {
+        !self
+    }
+}
diff --git a/rust-libs/common-tools/src/traits/merge.rs b/rust-libs/common-tools/src/traits/merge.rs
new file mode 100644
index 0000000000000000000000000000000000000000..31ac75376d6775f46d6c5c415d3642a6d346cf2c
--- /dev/null
+++ b/rust-libs/common-tools/src/traits/merge.rs
@@ -0,0 +1,22 @@
+//  Copyright (C) 2019  É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/>.
+
+//! Trait Merge.
+
+/// Merge two instances of same type
+pub trait Merge {
+    /// Merge two instances of same type
+    fn merge(self, other: Self) -> Self;
+}
diff --git a/rust-libs/pow/Cargo.toml b/rust-libs/pow/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..8dcd7c51c9409f476875c314a264a7748e058d06
--- /dev/null
+++ b/rust-libs/pow/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "dubp-pow"
+version = "0.1.0"
+authors = ["elois <elois@duniter.org>"]
+description = "Makes Proof of Work computations for the DUBP protocol."
+repository = "https://git.duniter.org/libs/dubp-rs-libs"
+readme = "README.md"
+keywords = ["dubp", "dunitrust", "pow"]
+license = "AGPL-3.0"
+edition = "2018"
+
+[lib]
+path = "src/lib.rs"
+
+[dependencies]
+core_affinity = "0.5.10"
+dup-crypto = { version = "0.15.0", default-features = false, features = ["rand"] }
+duniter-common-tools = { path = "../common-tools" }
+log = "0.4.8"
+rand = "0.7.3"
+thiserror = "1.0.11"
+
+[dev-dependencies]
+
+[features]
diff --git a/rust-libs/pow/src/calculator.rs b/rust-libs/pow/src/calculator.rs
new file mode 100644
index 0000000000000000000000000000000000000000..aab4e35555fa3225486da81771ebb713e5f15ffc
--- /dev/null
+++ b/rust-libs/pow/src/calculator.rs
@@ -0,0 +1,187 @@
+//  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/>.
+
+//! Makes Proof of Work computation for the DUBP protocol.
+
+use super::{ClusterMsg, PowConf, PowQuery, ValidProof, WorkerMsg, WorkerNonceArea};
+use crate::fast_check_hash_pattern::{PowDiff, PowDiffiV10};
+use duniter_common_tools::Percent;
+use dup_crypto::hashs::Hash;
+use dup_crypto::keys::{ed25519::Signator as Ed25519Signator, KeyPair, Signator};
+use std::sync::mpsc::{Receiver, RecvTimeoutError, Sender};
+use std::time::{Duration, SystemTime};
+
+#[cfg(not(target_arch = "arm"))]
+const NUMBER_OF_ITERATIONS_WITHOUT_SLEEP: usize = 500;
+#[cfg(target_arch = "arm")]
+const NUMBER_OF_ITERATIONS_WITHOUT_SLEEP: usize = 200;
+
+pub enum PowCalculatorResult {
+    FoundValidProof(ValidProof),
+    PowStoppedByCluster,
+    PowStoppedByClusterAndNeedNotification,
+    Quit,
+}
+
+pub struct PowCalculator<'a> {
+    begin_nonce: u64,
+    block_inner_hash: Hash,
+    cpu_percent: Percent,
+    difficulty: PowDiff,
+    marker: usize,
+    signator: Ed25519Signator,
+    sleep_factor: f64,
+    worker_nonce_area: WorkerNonceArea,
+    worker_receiver: &'a Receiver<WorkerMsg>,
+}
+
+impl<'a> PowCalculator<'a> {
+    pub(crate) fn new(
+        pow_params: &PowQuery,
+        worker_nonce_area: WorkerNonceArea,
+        worker_receiver: &'a Receiver<WorkerMsg>,
+    ) -> Self {
+        // Init nonce
+        let marker = std::cmp::min(pow_params.marker, PowQuery::MARKER_MAX);
+        let begin_nonce = (pow_params.conf.prefix.get() as u64 * 10_000_000_000_000u64)
+            + (worker_nonce_area.0 as u64 * 100_000_000_000u64)
+            + (marker as u64 * 100_000_000u64);
+
+        // Precompute sleep factor ((100 - cpu_percent) / cpu_percent)
+        let cpu_percent_u8: u8 = pow_params.conf.cpu.into();
+        let sleep_factor: f64 = f64::from(100u8 - cpu_percent_u8) / f64::from(cpu_percent_u8);
+
+        PowCalculator {
+            begin_nonce,
+            block_inner_hash: pow_params.block_inner_hash,
+            cpu_percent: pow_params.conf.cpu,
+            difficulty: PowDiff::V10(PowDiffiV10::from(pow_params.diff)),
+            marker: std::cmp::min(pow_params.marker, PowQuery::MARKER_MAX),
+            sleep_factor,
+            signator: pow_params.conf.keypair.generate_signator(),
+            worker_nonce_area,
+            worker_receiver,
+        }
+    }
+
+    fn update(&mut self, new_pow_conf: &PowConf) {
+        // Recompute begin nonce
+        self.begin_nonce = (new_pow_conf.prefix.get() as u64 * 10_000_000_000_000u64)
+            + (self.worker_nonce_area.0 as u64 * 100_000_000_000u64)
+            + (self.marker as u64 * 100_000_000u64);
+
+        // Update cup_percent
+        self.cpu_percent = new_pow_conf.cpu;
+
+        // Update signator
+        self.signator = new_pow_conf.keypair.generate_signator();
+
+        // Recompute sleep factor ((100-cpu_percent) / cpu_percent)
+        let cpu_percent_u8: u8 = new_pow_conf.cpu.into();
+        self.sleep_factor = f64::from(100u8 - cpu_percent_u8) / f64::from(cpu_percent_u8);
+    }
+
+    pub(crate) fn start_computation_main_loop(
+        mut self,
+        cluster_sender: &Sender<ClusterMsg>,
+    ) -> PowCalculatorResult {
+        // Notify the cluster that the computation has started
+        if cluster_sender.send(ClusterMsg::PowStarted).is_err() {
+            return PowCalculatorResult::Quit;
+        }
+
+        // Start computation main loop
+        let mut nonce = self.begin_nonce;
+        let mut iters_count = 0;
+        let mut new_pow_conf_opt = None;
+        loop {
+            let computation_start_time = SystemTime::now();
+            for _ in 0..NUMBER_OF_ITERATIONS_WITHOUT_SLEEP {
+                let block_sig = self.signator.sign(
+                    format!("InnerHash: {}\nNonce: {}\n", self.block_inner_hash, nonce).as_bytes(),
+                );
+                let block_hash = Hash::compute(
+                    format!(
+                        "InnerHash: {}\nNonce: {}\n{}\n",
+                        self.block_inner_hash, nonce, block_sig
+                    )
+                    .as_bytes(),
+                );
+
+                if self.difficulty.check_hash(block_hash) {
+                    return PowCalculatorResult::FoundValidProof(ValidProof {
+                        iters_count: iters_count + 1,
+                        nonce,
+                        sig: block_sig,
+                        hash: block_hash,
+                        worker_nonce_area: self.worker_nonce_area,
+                    });
+                } else {
+                    nonce += 1;
+                    iters_count += 1;
+                }
+            }
+            // Compute sleep time needed to respect cpu percent
+            // This sleep time is achieved by a maximum time to listen incoming messages.
+            let timeout = if self.cpu_percent == Percent::MAX {
+                Duration::from_secs(0)
+            } else {
+                let computation_duration = SystemTime::now()
+                    .duration_since(computation_start_time)
+                    .expect("Duration error !")
+                    .as_millis();
+                let sleep_time = f64::from(computation_duration as u32) * self.sleep_factor;
+                Duration::from_millis(sleep_time.trunc() as u64)
+            };
+
+            // Listen incoming messages
+            match self.worker_receiver.recv_timeout(timeout) {
+                Ok(msg) => match msg {
+                    WorkerMsg::ChangeConf(new_pow_conf) => {
+                        new_pow_conf_opt = Some(new_pow_conf);
+                    }
+                    WorkerMsg::StartPoW { .. } => {}
+                    WorkerMsg::StopPow => {
+                        if cluster_sender.send(ClusterMsg::PowStopped).is_err() {
+                            return PowCalculatorResult::Quit;
+                        } else {
+                            return PowCalculatorResult::PowStoppedByCluster;
+                        }
+                    }
+                    WorkerMsg::StopPowAndNotifyRequestor => {
+                        if cluster_sender.send(ClusterMsg::PowStopped).is_err() {
+                            return PowCalculatorResult::Quit;
+                        } else {
+                            return PowCalculatorResult::PowStoppedByClusterAndNeedNotification;
+                        }
+                    }
+                    WorkerMsg::Quit => return PowCalculatorResult::Quit,
+                },
+                Err(RecvTimeoutError::Disconnected) => return PowCalculatorResult::Quit,
+                Err(RecvTimeoutError::Timeout) => continue,
+            }
+
+            // If you receive a new conf, update calculator data and reset nonce
+            if let Some(ref new_pow_conf) = new_pow_conf_opt {
+                self.update(new_pow_conf);
+                new_pow_conf_opt = None;
+                nonce = self.begin_nonce + iters_count;
+                if cluster_sender.send(ClusterMsg::PowConfUpdated).is_err() {
+                    return PowCalculatorResult::Quit;
+                }
+            }
+        }
+    }
+}
diff --git a/rust-libs/pow/src/fast_check_hash_pattern.rs b/rust-libs/pow/src/fast_check_hash_pattern.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2fa6add185c040181696d400137755de0c09807a
--- /dev/null
+++ b/rust-libs/pow/src/fast_check_hash_pattern.rs
@@ -0,0 +1,147 @@
+//  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/>.
+
+//! Fast check hash pattern for DUBP Proof of Work.
+
+use dup_crypto::hashs::Hash;
+use log::info;
+
+pub enum PowDiff {
+    V10(PowDiffiV10),
+}
+
+impl PowDiff {
+    #[inline]
+    pub fn check_hash(&self, hash: Hash) -> bool {
+        match self {
+            Self::V10(pow_diff_v10) => pow_diff_v10.check_hash(hash),
+        }
+    }
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub struct PowDiffiV10 {
+    pub zeros_bytes: usize,
+    pub remaining_byte_max: u8,
+    pub shift_remaining_byte: bool,
+}
+
+impl From<usize> for PowDiffiV10 {
+    fn from(diff: usize) -> Self {
+        let zeros = diff / 16;
+
+        PowDiffiV10 {
+            zeros_bytes: zeros / 2,
+            remaining_byte_max: 15u8 - (diff % 16) as u8,
+            shift_remaining_byte: zeros % 2 == 0,
+        }
+    }
+}
+
+impl PowDiffiV10 {
+    #[inline]
+    #[allow(clippy::comparison_chain, clippy::needless_range_loop)]
+    pub fn check_hash(&self, hash: Hash) -> bool {
+        let bytes = hash.0;
+        for i in 0..32 {
+            if i < self.zeros_bytes {
+                if bytes[i] != 0u8 {
+                    return false;
+                } else if i >= 3 {
+                    info!("POW: Matched {} zeros with hash = {}", i, hash);
+                } else {
+                    continue;
+                }
+            } else if i == self.zeros_bytes {
+                let remaining_byte = if self.shift_remaining_byte {
+                    bytes[i] >> 4
+                } else {
+                    bytes[i]
+                };
+                return remaining_byte <= self.remaining_byte_max;
+            } else {
+                return false;
+            }
+        }
+
+        false
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+
+    #[test]
+    fn fast_check_hash_pattern_v10() {
+        let hash =
+            Hash::from_hex("B2ECA8B21738301F647C3DC960421992EC87ED9139F6FDDB07F8D495B4F0A6F1")
+                .expect("invalid hash");
+
+        assert_eq!(
+            PowDiffiV10 {
+                zeros_bytes: 0,
+                remaining_byte_max: 11,
+                shift_remaining_byte: true,
+            },
+            PowDiffiV10::from(4),
+        );
+
+        assert!(PowDiffiV10::from(4).check_hash(hash));
+        assert!(!PowDiffiV10::from(5).check_hash(hash));
+
+        let hash2 =
+            Hash::from_hex("000000F31738301F647C3DC960421992EC87ED9139F6FDDB07F8D495B4F0A6F1")
+                .expect("invalid hash");
+
+        assert_eq!(
+            PowDiffiV10 {
+                zeros_bytes: 2,
+                remaining_byte_max: 0,
+                shift_remaining_byte: false,
+            },
+            PowDiffiV10::from(95),
+        );
+        assert_eq!(
+            PowDiffiV10 {
+                zeros_bytes: 3,
+                remaining_byte_max: 15,
+                shift_remaining_byte: true,
+            },
+            PowDiffiV10::from(96),
+        );
+
+        assert!(PowDiffiV10::from(95).check_hash(hash2));
+        assert!(PowDiffiV10::from(96).check_hash(hash2));
+        assert!(!PowDiffiV10::from(97).check_hash(hash2));
+
+        let hash3 =
+            Hash::from_hex("00000FB21738301F647C3DC960421992EC87ED9139F6FDDB07F8D495B4F0A6F1")
+                .expect("invalid hash");
+
+        assert_eq!(
+            PowDiffiV10 {
+                zeros_bytes: 2,
+                remaining_byte_max: 14,
+                shift_remaining_byte: false,
+            },
+            PowDiffiV10::from(81),
+        );
+
+        assert!(PowDiffiV10::from(80).check_hash(hash3));
+        assert!(!PowDiffiV10::from(81).check_hash(hash3));
+    }
+}
diff --git a/rust-libs/pow/src/lib.rs b/rust-libs/pow/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e46dd510a8dce81b7c784772849e0416544592f9
--- /dev/null
+++ b/rust-libs/pow/src/lib.rs
@@ -0,0 +1,577 @@
+//  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 Proof of Work cluster for the DUBP protocol.
+
+#![deny(
+    clippy::option_unwrap_used,
+    clippy::result_unwrap_used,
+    missing_docs,
+    missing_debug_implementations,
+    missing_copy_implementations,
+    trivial_casts,
+    trivial_numeric_casts,
+    unstable_features,
+    unused_import_braces,
+    unused_qualifications
+)]
+
+mod calculator;
+mod fast_check_hash_pattern;
+mod query;
+mod verify;
+
+pub use query::{PowConf, PowQuery};
+pub use verify::{verify_hash_pattern, InvalidHashPattern};
+
+use crate::calculator::{PowCalculator, PowCalculatorResult};
+use crate::query::InvalidPowQuery;
+use dup_crypto::hashs::Hash;
+use dup_crypto::keys::ed25519::Signature;
+use rand::seq::SliceRandom;
+use rand::thread_rng;
+use std::sync::mpsc::{channel, Receiver, Sender};
+use std::thread::{self, JoinHandle};
+use std::time::Duration;
+use thiserror::Error;
+
+#[derive(Clone, Copy, Debug)]
+/// Valid proof
+pub struct ValidProof {
+    /// Number of iterations performed by the worker who found the proof
+    pub iters_count: u64,
+    /// Block nonce
+    pub nonce: u64,
+    /// Block signature
+    pub sig: Signature,
+    /// Block hash
+    pub hash: Hash,
+    /// Worker that found proof
+    pub worker_nonce_area: WorkerNonceArea,
+}
+
+#[derive(Clone, Copy, Debug)]
+/// Message for pow requestor
+pub enum RequestorMsg {
+    /// Found valid proof
+    FoundValidProof(ValidProof),
+    /// Pow stopped
+    PowStoppedByCluster,
+}
+
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+/// Worker id
+pub struct WorkerId(pub usize);
+
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+/// Nonce area "explored" by the worker
+pub struct WorkerNonceArea(pub usize);
+
+#[derive(Clone, Debug)]
+enum ClusterMsg {
+    Initialized(Sender<WorkerMsg>),
+    PowStarted,
+    PowConfUpdated,
+    PowStopped,
+}
+
+#[derive(Clone, Debug)]
+enum WorkerMsg {
+    StartPoW {
+        worker_nonce_area: WorkerNonceArea,
+        pow_query: PowQuery,
+    },
+    ChangeConf(PowConf),
+    StopPowAndNotifyRequestor,
+    StopPow,
+    Quit,
+}
+
+#[derive(Clone, Copy, Debug, Error)]
+/// Pow cluster error
+pub enum PowClusterError {
+    /// No available core
+    #[error("No available core.")]
+    NoAvailableCore,
+    /// Fail to init cluster
+    #[error("Fail to init cluster.")]
+    FailToInitCluster,
+    /// Fail to start pow
+    #[error("Fail to start pow.")]
+    FailToStartPow,
+    /// Fail to stop pow
+    #[error("Fail to stop pow.")]
+    FailToStopPow,
+    /// Invalid pow parameters
+    #[error("Invalid pow parameters: {}", _0)]
+    InvalidPowQuery(InvalidPowQuery),
+    /// A worker is unreachable
+    #[error("A worker is unreachable.")]
+    WorkerUnreachable,
+    /// A worker panicked
+    #[error("A worker panicked.")]
+    WorkerPanic,
+}
+
+/// Pow cluster
+#[derive(Debug)]
+pub struct PowCluster {
+    cluster_receiver: Receiver<ClusterMsg>,
+    workers_handles: Vec<JoinHandle<()>>,
+    workers_ids_computing: Vec<WorkerId>,
+    workers_senders: Vec<Sender<WorkerMsg>>,
+}
+
+impl PowCluster {
+    /// Get the number of workers
+    #[inline]
+    pub fn get_workers_count(&self) -> usize {
+        self.workers_handles.len()
+    }
+    /// Initialize pow cluster
+    pub fn init(max_cores_opt: Option<usize>, workers_name: &str) -> Result<Self, PowClusterError> {
+        if let Some(mut core_ids) = core_affinity::get_core_ids() {
+            core_ids.shuffle(&mut thread_rng());
+            if let Some(max_cores) = max_cores_opt {
+                while core_ids.len() > max_cores {
+                    core_ids.pop();
+                }
+            }
+
+            let count_cores = core_ids.len();
+
+            let (cluster_sender, cluster_receiver) = channel();
+
+            let workers_handles = core_ids
+                .into_iter()
+                .map(|core_id| {
+                    let cluster_sender_clone = cluster_sender.clone();
+                    thread::Builder::new()
+                        .name(format!("{}{}", workers_name, core_id.id + 1))
+                        .spawn(move || {
+                            // Pin this thread to a single CPU core.
+                            core_affinity::set_for_current(core_id);
+
+                            // Create worker channel and send worker sender to cluster
+                            let (worker_sender, worker_receiver) = channel();
+                            let _ =
+                                cluster_sender_clone.send(ClusterMsg::Initialized(worker_sender));
+
+                            // Start worker main loop
+                            worker_main_loop(cluster_sender_clone, &worker_receiver);
+                        })
+                })
+                .collect::<std::io::Result<Vec<_>>>()
+                .map_err(|_| PowClusterError::FailToInitCluster)?;
+
+            let mut workers_senders = Vec::with_capacity(count_cores);
+            for _ in 0..count_cores {
+                match cluster_receiver.recv_timeout(Duration::from_millis(1000)) {
+                    Ok(msg) => match msg {
+                        ClusterMsg::Initialized(worker_sender) => {
+                            workers_senders.push(worker_sender);
+                        }
+                        _ => return Err(PowClusterError::FailToInitCluster),
+                    },
+                    Err(_) => return Err(PowClusterError::FailToInitCluster),
+                }
+            }
+
+            Ok(PowCluster {
+                cluster_receiver,
+                workers_handles,
+                workers_ids_computing: Vec::with_capacity(count_cores),
+                workers_senders,
+            })
+        } else {
+            Err(PowClusterError::NoAvailableCore)
+        }
+    }
+    fn select_workers_ids(&mut self, number_of_workers_solicited_opt: Option<usize>) {
+        let workers_count = self.get_workers_count();
+        let number_of_workers_solicited =
+            if let Some(number_of_workers_solicited) = number_of_workers_solicited_opt {
+                number_of_workers_solicited
+            } else {
+                workers_count
+            };
+        let mut workers_ids: Vec<WorkerId> = (0..workers_count).map(WorkerId).collect();
+        workers_ids.shuffle(&mut thread_rng());
+        self.workers_ids_computing = workers_ids
+            .into_iter()
+            .take(number_of_workers_solicited)
+            .collect();
+    }
+    /// Start PoW
+    pub fn start_pow(
+        &mut self,
+        pow_query: &PowQuery,
+        number_of_workers_solicited_opt: Option<usize>,
+    ) -> Result<(), PowClusterError> {
+        // Check pow query validity
+        pow_query
+            .verify()
+            .map_err(PowClusterError::InvalidPowQuery)?;
+
+        // We must stop the pow because the new request won't be sent to all workers,
+        // so it's possible that some workers currently calculating are not selected to process this new request.
+        self.stop_pow(false)?;
+
+        // Select workers
+        self.select_workers_ids(number_of_workers_solicited_opt);
+
+        for (worker_nonce_area, worker_id) in self.workers_ids_computing.iter().enumerate() {
+            self.workers_senders[worker_id.0]
+                .send(WorkerMsg::StartPoW {
+                    worker_nonce_area: WorkerNonceArea(worker_nonce_area),
+                    pow_query: pow_query.clone(),
+                })
+                .map_err(|_| PowClusterError::WorkerUnreachable)?;
+        }
+        for _ in 0..self.workers_ids_computing.len() {
+            match self
+                .cluster_receiver
+                .recv_timeout(Duration::from_millis(10_000))
+            {
+                Ok(msg) => match msg {
+                    ClusterMsg::PowStarted => continue,
+                    _other => {
+                        //println!("other={:?}", _other);
+                        return Err(PowClusterError::FailToStartPow);
+                    }
+                },
+                Err(_e) => {
+                    //println!("err={:?}", _e);
+                    return Err(PowClusterError::FailToStartPow);
+                }
+            }
+        }
+
+        Ok(())
+    }
+    /// Update pow conf
+    pub fn change_pow_conf(&self, new_pow_conf: &PowConf) -> Result<(), PowClusterError> {
+        for worker_sender in &self.workers_senders {
+            worker_sender
+                .send(WorkerMsg::ChangeConf(new_pow_conf.clone()))
+                .map_err(|_| PowClusterError::WorkerUnreachable)?;
+        }
+        Ok(())
+    }
+    /// Stop PoW
+    pub fn stop_pow(&self, notify_requestor: bool) -> Result<(), PowClusterError> {
+        let mut first_worker = true;
+        for worker_sender in &self.workers_senders {
+            if first_worker && notify_requestor {
+                worker_sender
+                    .send(WorkerMsg::StopPowAndNotifyRequestor)
+                    .map_err(|_| PowClusterError::WorkerUnreachable)?;
+            } else {
+                worker_sender
+                    .send(WorkerMsg::StopPow)
+                    .map_err(|_| PowClusterError::WorkerUnreachable)?;
+            }
+            first_worker = false;
+        }
+        for _ in 0..self.workers_senders.len() {
+            match self
+                .cluster_receiver
+                .recv_timeout(Duration::from_millis(1000))
+            {
+                Ok(msg) => match msg {
+                    ClusterMsg::PowStopped => continue,
+                    _ => return Err(PowClusterError::FailToStopPow),
+                },
+                Err(_) => return Err(PowClusterError::FailToStopPow),
+            }
+        }
+        Ok(())
+    }
+    /// Destroy cluster
+    pub fn quit(self) {
+        for worker_sender in &self.workers_senders {
+            let _ = worker_sender.send(WorkerMsg::Quit);
+        }
+        for worker_handle in self.workers_handles {
+            let _ = worker_handle.join();
+        }
+    }
+    /// Orders the workers to stop but does not check if they have actually stopped.
+    /// This method is unfortunately necessary for NodeJs binding because Neon does not allow methods that take the ownership on self.
+    /// Do not use this method if you can do otherwise.
+    #[doc(hidden)]
+    pub fn quit_unchecked(&mut self) {
+        for worker_sender in &self.workers_senders {
+            let _ = worker_sender.send(WorkerMsg::Quit);
+        }
+        while let Ok(_) = self.cluster_receiver.recv() {}
+        self.workers_handles = Vec::with_capacity(0);
+    }
+}
+
+fn worker_main_loop(cluster_sender: Sender<ClusterMsg>, worker_receiver: &Receiver<WorkerMsg>) {
+    while let Ok(msg) = worker_receiver.recv() {
+        match msg {
+            WorkerMsg::StartPoW {
+                worker_nonce_area,
+                pow_query,
+            } => {
+                let calculator = PowCalculator::new(&pow_query, worker_nonce_area, worker_receiver);
+                match calculator.start_computation_main_loop(&cluster_sender) {
+                    PowCalculatorResult::FoundValidProof(valid_proof) => {
+                        if pow_query
+                            .send_msg_to_requestor(RequestorMsg::FoundValidProof(valid_proof))
+                            .is_err()
+                        {
+                            break;
+                        } else {
+                            continue;
+                        }
+                    }
+                    PowCalculatorResult::PowStoppedByCluster => continue,
+                    PowCalculatorResult::PowStoppedByClusterAndNeedNotification => {
+                        if pow_query
+                            .send_msg_to_requestor(RequestorMsg::PowStoppedByCluster)
+                            .is_err()
+                        {
+                            break;
+                        }
+                    }
+                    PowCalculatorResult::Quit => break,
+                }
+            }
+            WorkerMsg::ChangeConf(_) => {}
+            WorkerMsg::StopPow | WorkerMsg::StopPowAndNotifyRequestor => {
+                if cluster_sender.send(ClusterMsg::PowStopped).is_err() {
+                    break;
+                }
+            }
+            WorkerMsg::Quit => break,
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+    use duniter_common_tools::Percent;
+    use dup_crypto::hashs::Hash;
+    use dup_crypto::keys::ed25519::Ed25519KeyPair;
+    use std::num::NonZeroUsize;
+    use std::sync::mpsc::{channel, Receiver};
+
+    const TESTS_WORKERS_NAME: &str = "pow_worker_";
+
+    fn gen_pow_query(
+        cpu_percent: u8,
+        diff: usize,
+        marker: usize,
+        requestor_sender: Sender<RequestorMsg>,
+    ) -> PowQuery {
+        PowQuery {
+            block_inner_hash: Hash::random().expect("fail to gen random hash"),
+            conf: PowConf {
+                cpu: Percent::new(cpu_percent).expect("invalid percent"),
+                keypair: Ed25519KeyPair::generate_random().expect("fail to gen random keypair"),
+                prefix: unsafe { NonZeroUsize::new_unchecked(2) },
+            },
+            diff,
+            marker,
+            requestor_sender,
+        }
+    }
+
+    fn proof_found_before_timeout(
+        receiver: &Receiver<RequestorMsg>,
+        timeout: u64,
+    ) -> Result<bool, PowClusterError> {
+        if let Ok(RequestorMsg::FoundValidProof(proof)) =
+            receiver.recv_timeout(Duration::from_millis(timeout))
+        {
+            println!(
+                "Find valid proof '{}' with nonce '{}'",
+                proof.hash, proof.nonce
+            );
+            Ok(true)
+        } else {
+            Ok(false)
+        }
+    }
+
+    #[test]
+    fn test_diff_zero() -> Result<(), PowClusterError> {
+        let mut pow_cluster = PowCluster::init(None, TESTS_WORKERS_NAME)?;
+        let (sender, receiver) = channel();
+        let pow_query = gen_pow_query(100, 0, 0, sender);
+        pow_cluster.start_pow(&pow_query, None)?;
+
+        if proof_found_before_timeout(&receiver, 10)? {
+            pow_cluster.quit();
+            Ok(())
+        } else {
+            pow_cluster.quit();
+            panic!("must be found valid proof under 10 ms")
+        }
+    }
+
+    #[test]
+    fn test_fast_pow() -> Result<(), PowClusterError> {
+        let mut pow_cluster = PowCluster::init(None, TESTS_WORKERS_NAME)?;
+        let (sender, receiver) = channel();
+        let pow_query = gen_pow_query(100, 16, 0, sender);
+        pow_cluster.start_pow(&pow_query, None)?;
+
+        if proof_found_before_timeout(&receiver, 100)? {
+            pow_cluster.quit();
+            Ok(())
+        } else {
+            pow_cluster.quit();
+            panic!("must be found valid proof under 100 ms")
+        }
+    }
+
+    #[test]
+    fn test_soliciting_a_single_worker() -> Result<(), PowClusterError> {
+        let mut pow_cluster = PowCluster::init(None, TESTS_WORKERS_NAME)?;
+        let (sender, receiver) = channel();
+        let pow_query = gen_pow_query(100, 16, 0, sender);
+        pow_cluster.start_pow(&pow_query, Some(1))?;
+
+        if let Ok(RequestorMsg::FoundValidProof(proof)) =
+            receiver.recv_timeout(Duration::from_millis(500))
+        {
+            assert_eq!(proof.worker_nonce_area, WorkerNonceArea(0));
+        } else {
+            pow_cluster.quit();
+            panic!("must be found valid proof under 500 ms")
+        }
+
+        pow_cluster.quit();
+        Ok(())
+    }
+
+    #[test]
+    fn test_start_and_quit_unchecked() -> Result<(), PowClusterError> {
+        let mut pow_cluster = PowCluster::init(None, TESTS_WORKERS_NAME)?;
+        let (sender, _receiver) = channel();
+        let pow_query = gen_pow_query(1, 150, 0, sender);
+        pow_cluster.start_pow(&pow_query, None)?;
+        assert!(pow_cluster.get_workers_count() > 0);
+        pow_cluster.quit_unchecked();
+        assert!(pow_cluster.get_workers_count() == 0);
+        Ok(())
+    }
+
+    #[test]
+    fn test_stop_and_restart_pow() -> Result<(), PowClusterError> {
+        let mut pow_cluster = PowCluster::init(Some(2), TESTS_WORKERS_NAME)?;
+        let (sender, receiver) = channel();
+        let pow_query = gen_pow_query(100, 120, 999, sender.clone());
+        pow_cluster.start_pow(&pow_query, None)?;
+        assert!(pow_cluster.get_workers_count() > 0);
+
+        if proof_found_before_timeout(&receiver, 1_000)? {
+            pow_cluster.quit();
+            panic!("Must not found proof");
+        }
+
+        pow_cluster.stop_pow(true)?;
+        pow_cluster.stop_pow(true)?; // Pow should be stopped several times
+
+        // Must be receive None message from first worker  only
+        if let Ok(RequestorMsg::PowStoppedByCluster) =
+            receiver.recv_timeout(Duration::from_millis(3_000))
+        {
+            // We receive pow cancellation signal from first worker
+        } else {
+            panic!("Must be not receive any message")
+        }
+
+        let pow_query = gen_pow_query(100, 30, 999, sender);
+        pow_cluster.start_pow(&pow_query, None)?;
+        if proof_found_before_timeout(&receiver, 5_000)? {
+            // Workers found proof OK
+        } else {
+            panic!("must be found valid proof under 5 secs")
+        }
+
+        pow_cluster.stop_pow(false)?;
+
+        // Must be not receive any message from workers
+        if let Ok(RequestorMsg::PowStoppedByCluster) =
+            receiver.recv_timeout(Duration::from_millis(500))
+        {
+            pow_cluster.quit();
+            panic!("Must be not receive any message from workers");
+        } else {
+            // We not receive any message OK
+            pow_cluster.quit();
+            Ok(())
+        }
+    }
+
+    #[test]
+    fn test_hot_change_of_conf() -> Result<(), PowClusterError> {
+        let mut pow_cluster = PowCluster::init(Some(4), TESTS_WORKERS_NAME)?;
+        let (sender, receiver) = channel();
+        let pow_query = gen_pow_query(1, 48, 999, sender);
+        pow_cluster.start_pow(&pow_query, None)?;
+
+        if proof_found_before_timeout(&receiver, 1_000)? {
+            pow_cluster.quit();
+            return Ok(());
+        }
+
+        println!("No found proof, change prefix and cpu");
+        pow_cluster.change_pow_conf(&PowConf {
+            cpu: Percent::new(100).expect("invalid percent"),
+            keypair: pow_query.conf.keypair,
+            prefix: unsafe { NonZeroUsize::new_unchecked(3) },
+        })?;
+
+        if !proof_found_before_timeout(&receiver, 5_000)? {
+            pow_cluster.quit();
+            panic!("must be found valid proof under 5 secs");
+        } else {
+            pow_cluster.quit();
+            Ok(())
+        }
+    }
+    #[test]
+    fn test_hot_change_pow_query() -> Result<(), PowClusterError> {
+        let mut pow_cluster = PowCluster::init(Some(4), TESTS_WORKERS_NAME)?;
+        let (sender, receiver) = channel();
+        let pow_query = gen_pow_query(1, 120, 999, sender.clone());
+        pow_cluster.start_pow(&pow_query, None)?;
+
+        if proof_found_before_timeout(&receiver, 1_000)? {
+            pow_cluster.quit();
+            return Ok(());
+        }
+
+        println!("blockchain HEAD change, start new pow query");
+        let pow_query_2 = gen_pow_query(50, 32, 0, sender);
+        pow_cluster.start_pow(&pow_query_2, None)?;
+
+        if !proof_found_before_timeout(&receiver, 3_000)? {
+            pow_cluster.quit();
+            panic!("must be found valid proof under 3 secs");
+        } else {
+            pow_cluster.quit();
+            Ok(())
+        }
+    }
+}
diff --git a/rust-libs/pow/src/query.rs b/rust-libs/pow/src/query.rs
new file mode 100644
index 0000000000000000000000000000000000000000..39e865a5a640acd88cf55bec4f664a04340d026e
--- /dev/null
+++ b/rust-libs/pow/src/query.rs
@@ -0,0 +1,88 @@
+//  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/>.
+
+//! Define Proof of Work request.
+
+use super::RequestorMsg;
+use duniter_common_tools::Percent;
+use dup_crypto::hashs::Hash;
+use dup_crypto::keys::ed25519::Ed25519KeyPair;
+use std::num::NonZeroUsize;
+use std::sync::mpsc::{SendError, Sender};
+use thiserror::Error;
+
+/// PoW conf
+#[derive(Clone, Debug)]
+pub struct PowConf {
+    /// Cpu usage in %
+    pub cpu: Percent,
+    /// Leypair used to sign block
+    pub keypair: Ed25519KeyPair,
+    /// Nonce prefix
+    pub prefix: NonZeroUsize,
+}
+
+impl PowConf {
+    const PREFIX_MAX: usize = 899;
+}
+
+#[derive(Clone, Debug)]
+/// PoW request
+pub struct PowQuery {
+    /// Block inner hash
+    pub block_inner_hash: Hash,
+    /// PoW configuration
+    pub conf: PowConf,
+    /// PoW Difficulty
+    pub diff: usize,
+    /// None marker
+    pub marker: usize,
+    /// Proof sender
+    pub requestor_sender: Sender<RequestorMsg>,
+}
+
+#[derive(Clone, Copy, Debug, Error)]
+/// Invalid pow parameters
+pub enum InvalidPowQuery {
+    /// Too large marker
+    #[error("Too large marker.")]
+    TooLargeMarker,
+    /// Too large prefix
+    #[error("Too large prefix.")]
+    TooLargePrefix,
+}
+
+impl PowQuery {
+    /// Maximum value for nonce marker
+    pub const MARKER_MAX: usize = 999;
+
+    pub(crate) fn verify(&self) -> Result<(), InvalidPowQuery> {
+        if self.marker > Self::MARKER_MAX {
+            Err(InvalidPowQuery::TooLargeMarker)
+        } else if self.conf.prefix.get() > PowConf::PREFIX_MAX {
+            Err(InvalidPowQuery::TooLargePrefix)
+        } else {
+            Ok(())
+        }
+    }
+
+    #[inline]
+    pub(crate) fn send_msg_to_requestor(
+        &self,
+        msg: RequestorMsg,
+    ) -> Result<(), SendError<RequestorMsg>> {
+        self.requestor_sender.send(msg)
+    }
+}
diff --git a/rust-libs/pow/src/verify.rs b/rust-libs/pow/src/verify.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d89eb4da20eeebca50c85009f58bbac44f07cc78
--- /dev/null
+++ b/rust-libs/pow/src/verify.rs
@@ -0,0 +1,71 @@
+//  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/>.
+
+//! Makes Proof of Work computations for the DUBP protocol.
+
+use duniter_common_tools::traits::bool_ext::BoolExt;
+use dup_crypto::hashs::Hash;
+
+static ZERO_STRING: &str = "0";
+
+/// Invalid PoW pattern
+#[derive(Debug, PartialEq)]
+pub struct InvalidHashPattern {
+    expected_pattern: String,
+    actual_hash: String,
+}
+
+#[inline]
+/// Verify hash pattern
+pub fn verify_hash_pattern(hash: Hash, diffi: usize) -> Result<(), InvalidHashPattern> {
+    let hash_string = hash.to_hex();
+    let nb_zeros = diffi / 16;
+    let expected_pattern_last_hex_digit = 16 - (diffi % 16);
+    let repeated_zero_string = ZERO_STRING.repeat(nb_zeros);
+    let expected_pattern = if expected_pattern_last_hex_digit < 15 && nb_zeros < 64 {
+        let expected_pattern_last_char =
+            std::char::from_digit(expected_pattern_last_hex_digit as u32, 16)
+                .expect("expected_pattern_last_hex_digit is necessarily less than 16");
+        let expected_pattern = format!(
+            "{}[0-{}]*",
+            repeated_zero_string, expected_pattern_last_char
+        );
+        let actual_pattern_last_hex_digit = usize::from_str_radix(
+            hash_string
+                .get(nb_zeros..=nb_zeros)
+                .expect("Hash string is necessary greater than nb_zeros + 1."),
+            16,
+        )
+        .expect("Hash type guarantees a valid hexadecimal string.");
+        // remainder must be less than or equal to expected_end_pattern
+        (actual_pattern_last_hex_digit <= expected_pattern_last_hex_digit).or_err(
+            InvalidHashPattern {
+                expected_pattern: expected_pattern.clone(),
+                actual_hash: hash_string.clone(),
+            },
+        )?;
+        expected_pattern
+    } else {
+        repeated_zero_string.clone()
+    };
+    hash_string
+        .starts_with(&repeated_zero_string)
+        .or_err(InvalidHashPattern {
+            expected_pattern,
+            actual_hash: hash_string,
+        })?;
+
+    Ok(())
+}