diff --git a/README.md b/README.md
index 0b387b757b48739fc903b5442abcb898524c7687..6e4caa01f3feb919995fc0ab0324ee475aa79e2c 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,6 @@ If you wish to participate/debate on Duniter, you can:
 Duniter is using modules on different git repositories:
 - [Common](https://github.com/duniter/duniter-common): commons tools for Duniter core and modules.
 - [Crawler](https://github.com/duniter/duniter-crawler): network crawler.
-- [Prover](https://github.com/duniter/duniter-prover): handle Proof-of-Work.
 - [BMA API](https://github.com/duniter/duniter-bma): Basic Merkled API.
 - [Keypair](https://github.com/duniter/duniter-keypair): provide the cryptographic keypair.
 - [WotB](https://github.com/duniter/wotb): compute Web of Trust.
diff --git a/app/modules/prover/index.js b/app/modules/prover/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..f1079ab869e675ee8548d9ae64647bb82f3ce854
--- /dev/null
+++ b/app/modules/prover/index.js
@@ -0,0 +1,204 @@
+"use strict";
+
+const co = require('co');
+const async = require('async');
+const contacter = require('duniter-crawler').duniter.methods.contacter;
+const common = require('duniter-common');
+const constants = require('./lib/constants');
+const Prover = require('./lib/prover');
+const blockGenerator = require('./lib/blockGenerator');
+const blockProver = require('./lib/blockProver');
+
+const Peer = common.document.Peer
+
+module.exports = {
+
+  duniter: {
+
+    /*********** Permanent prover **************/
+    config: {
+      onLoading: (conf) => co(function*() {
+        if (conf.cpu === null || conf.cpu === undefined) {
+          conf.cpu = constants.DEFAULT_CPU;
+        }
+        conf.powSecurityRetryDelay = constants.POW_SECURITY_RETRY_DELAY;
+        conf.powMaxHandicap = constants.POW_MAXIMUM_ACCEPTABLE_HANDICAP;
+      }),
+      beforeSave: (conf) => co(function*() {
+        delete conf.powSecurityRetryDelay;
+        delete conf.powMaxHandicap;
+      })
+    },
+
+    service: {
+      output: (server, conf, logger) => {
+        const generator = blockGenerator(server);
+        server.generatorGetJoinData     = generator.getSinglePreJoinData.bind(generator)
+        server.generatorComputeNewCerts = generator.computeNewCerts.bind(generator)
+        server.generatorNewCertsToLinks = generator.newCertsToLinks.bind(generator)
+        return new Prover(server, conf, logger)
+      }
+    },
+
+    methods: {
+      hookServer: (server) => {
+        const generator = blockGenerator(server);
+        server.generatorGetJoinData     = generator.getSinglePreJoinData.bind(generator)
+        server.generatorComputeNewCerts = generator.computeNewCerts.bind(generator)
+        server.generatorNewCertsToLinks = generator.newCertsToLinks.bind(generator)
+      },
+      blockProver: blockProver,
+      prover: (server, conf, logger) => new Prover(server, conf, logger),
+      blockGenerator: (server, prover) => blockGenerator(server, prover),
+      generateTheNextBlock: (server, manualValues) => co(function*() {
+        const prover = blockProver(server);
+        const generator = blockGenerator(server, prover);
+        return generator.nextBlock(manualValues);
+      }),
+      generateAndProveTheNext: (server, block, trial, manualValues) => co(function*() {
+        const prover = blockProver(server);
+        const generator = blockGenerator(server, prover);
+        let res = yield generator.makeNextBlock(block, trial, manualValues);
+        return res
+      })
+    },
+
+    /*********** CLI gen-next + gen-root **************/
+
+    cliOptions: [
+      {value: '--show',  desc: 'With gen-next or gen-root commands, displays the generated block.'},
+      {value: '--check', desc: 'With gen-next: just check validity of generated block.'},
+      {value: '--at <medianTime>', desc: 'With gen-next --show --check: allows to try in a future time.', parser: parseInt }
+    ],
+
+    cli: [{
+      name: 'gen-next [host] [port] [difficulty]',
+      desc: 'Tries to generate the next block of the blockchain.',
+      onDatabaseExecute: (server, conf, program, params) => co(function*() {
+        const host = params[0];
+        const port = params[1];
+        const difficulty = params[2];
+        const generator = blockGenerator(server, null);
+        return generateAndSend(program, host, port, difficulty, server, () => generator.nextBlock);
+      })
+    }, {
+      name: 'gen-root [host] [port] [difficulty]',
+      desc: 'Tries to generate the next block of the blockchain.',
+      preventIfRunning: true,
+      onDatabaseExecute: (server, conf, program, params) => co(function*() {
+        const host = params[0];
+        const port = params[1];
+        const difficulty = params[2];
+        const generator = blockGenerator(server, null);
+        let toDelete, catched = true;
+        do {
+          try {
+            yield generateAndSend(program, host, port, difficulty, server, () => generator.nextBlock);
+            catched = false;
+          } catch (e) {
+            toDelete = yield server.dal.idtyDAL.query('SELECT * FROM idty i WHERE 5 > (SELECT count(*) from cert c where c.`to` = i.pubkey)');
+            console.log('Deleting', toDelete.map(i => i.pubkey));
+            yield server.dal.idtyDAL.exec('DELETE FROM idty WHERE pubkey IN ('  + toDelete.map(i => "'" + i.pubkey + "'").join(',') + ')');
+            yield server.dal.idtyDAL.exec('DELETE FROM cert WHERE `to` IN ('  + toDelete.map(i => "'" + i.pubkey + "'").join(',') + ')');
+            yield server.dal.idtyDAL.exec('DELETE FROM cert WHERE `from` IN ('  + toDelete.map(i => "'" + i.pubkey + "'").join(',') + ')');
+          }
+        } while (catched && toDelete.length);
+        console.log('Done');
+      })
+    }, {
+      name: 'gen-root-choose [host] [port] [difficulty]',
+      desc: 'Tries to generate root block, with choice of root members.',
+      preventIfRunning: true,
+      onDatabaseExecute: (server, conf, program, params, startServices, stopServices) => co(function*() {
+        const host = params[0];
+        const port = params[1];
+        const difficulty = params[2];
+        if (!host) {
+          throw 'Host is required.';
+        }
+        if (!port) {
+          throw 'Port is required.';
+        }
+        if (!difficulty) {
+          throw 'Difficulty is required.';
+        }
+        const generator = blockGenerator(server, null);
+        return generateAndSend(program, host, port, difficulty, server, () => generator.manualRoot);
+      })
+    }]
+  }
+}
+
+function generateAndSend(program, host, port, difficulty, server, getGenerationMethod) {
+  const logger = server.logger;
+  return new Promise((resolve, reject) => {
+    async.waterfall([
+      function (next) {
+        const method = getGenerationMethod(server);
+        co(function*(){
+          const simulationValues = {}
+          if (program.show && program.check) {
+            if (program.at && !isNaN(program.at)) {
+              simulationValues.medianTime = program.at
+            }
+          }
+          const block = yield method(null, simulationValues);
+          next(null, block);
+        });
+      },
+      function (block, next) {
+        if (program.check) {
+          block.time = block.medianTime;
+          program.show && console.log(block.getRawSigned());
+          co(function*(){
+            try {
+              const parsed = common.parsers.parseBlock.syncWrite(block.getRawSigned());
+              yield server.BlockchainService.checkBlock(parsed, false);
+              logger.info('Acceptable block');
+              next();
+            } catch (e) {
+              next(e);
+            }
+          });
+        }
+        else {
+          logger.debug('Block to be sent: %s', block.getRawInnerPart());
+          async.waterfall([
+            function (subNext) {
+              proveAndSend(program, server, block, server.conf.pair.pub, parseInt(difficulty), host, parseInt(port), subNext);
+            }
+          ], next);
+        }
+      }
+    ], (err, data) => {
+      err && reject(err);
+      !err && resolve(data);
+    });
+  });
+}
+
+function proveAndSend(program, server, block, issuer, difficulty, host, port, done) {
+  const logger = server.logger;
+  async.waterfall([
+    function (next) {
+      block.issuer = issuer;
+      program.show && console.log(block.getRawSigned());
+      co(function*(){
+        try {
+          const prover = blockProver(server);
+          const proven = yield prover.prove(block, difficulty);
+          const peer = Peer.fromJSON({
+            endpoints: [['BASIC_MERKLED_API', host, port].join(' ')]
+          });
+          program.show && console.log(proven.getRawSigned());
+          logger.info('Posted block ' + proven.getRawSigned());
+          const p = Peer.fromJSON(peer);
+          const contact = contacter(p.getHostPreferDNS(), p.getPort());
+          yield contact.postBlock(proven.getRawSigned());
+        } catch(e) {
+          next(e);
+        }
+      });
+    }
+  ], done);
+}
diff --git a/app/modules/prover/lib/blockGenerator.js b/app/modules/prover/lib/blockGenerator.js
new file mode 100644
index 0000000000000000000000000000000000000000..4549c2249115379acc54238904a43586c3a80979
--- /dev/null
+++ b/app/modules/prover/lib/blockGenerator.js
@@ -0,0 +1,757 @@
+"use strict";
+const _               = require('underscore');
+const co              = require('co');
+const moment          = require('moment');
+const inquirer        = require('inquirer');
+const common          = require('duniter-common');
+
+const keyring       = common.keyring;
+const hashf         = common.hashf;
+const rawer         = common.rawer;
+const Block         = common.document.Block;
+const Membership    = common.document.Membership;
+const Transaction   = common.document.Transaction;
+const Identity      = common.document.Identity;
+const Certification = common.document.Certification;
+const constants     = common.constants
+const indexer       = common.indexer
+const rules         = common.rules
+
+module.exports = (server, prover) => {
+  return new BlockGenerator(server, prover);
+};
+
+function BlockGenerator(server, prover) {
+
+  const that = this;
+  const conf = server.conf;
+  const dal = server.dal;
+  const mainContext = server.BlockchainService.getContext();
+  const selfPubkey = conf.pair.pub;
+  const logger = server.logger;
+
+  this.nextBlock = (manualValues, simulationValues) => generateNextBlock(new NextBlockGenerator(mainContext, conf, dal, logger), manualValues, simulationValues);
+
+  this.manualRoot = () => co(function *() {
+    let current = yield dal.getCurrentBlockOrNull();
+    if (current) {
+      throw 'Cannot generate root block: it already exists.';
+    }
+    return generateNextBlock(new ManualRootGenerator());
+  });
+
+  this.makeNextBlock = (block, trial, manualValues) => co(function *() {
+    const unsignedBlock = block || (yield that.nextBlock(manualValues));
+    const trialLevel = trial || (yield mainContext.getIssuerPersonalizedDifficulty(selfPubkey));
+    return prover.prove(unsignedBlock, trialLevel, (manualValues && manualValues.time) || null);
+  });
+
+  /**
+   * Generate next block, gathering both updates & newcomers
+   */
+  const generateNextBlock = (generator, manualValues, simulationValues) => co(function *() {
+    const vHEAD_1 = yield mainContext.getvHEAD_1()
+    if (simulationValues && simulationValues.medianTime) {
+      vHEAD_1.medianTime = simulationValues.medianTime
+    }
+    const current = yield dal.getCurrentBlockOrNull();
+    const revocations = yield dal.getRevocatingMembers();
+    const exclusions = yield dal.getToBeKickedPubkeys();
+    const newCertsFromWoT = yield generator.findNewCertsFromWoT(current);
+    const newcomersLeavers = yield findNewcomersAndLeavers(current, generator.filterJoiners);
+    const transactions = yield findTransactions(current);
+    const joinData = newcomersLeavers[2];
+    const leaveData = newcomersLeavers[3];
+    const newCertsFromNewcomers = newcomersLeavers[4];
+    const certifiersOfNewcomers = _.uniq(_.keys(joinData).reduce((theCertifiers, newcomer) => {
+      return theCertifiers.concat(_.pluck(joinData[newcomer].certs, 'from'));
+    }, []));
+    const certifiers = [].concat(certifiersOfNewcomers);
+    // Merges updates
+    _(newCertsFromWoT).keys().forEach(function(certified){
+      newCertsFromWoT[certified] = newCertsFromWoT[certified].filter((cert) => {
+        // Must not certify a newcomer, since it would mean multiple certifications at same time from one member
+        const isCertifier = certifiers.indexOf(cert.from) != -1;
+        if (!isCertifier) {
+          certifiers.push(cert.from);
+        }
+        return !isCertifier;
+      });
+    });
+    _(newCertsFromNewcomers).keys().forEach((certified) => {
+      newCertsFromWoT[certified] = (newCertsFromWoT[certified] || []).concat(newCertsFromNewcomers[certified]);
+    });
+    // Revocations
+    // Create the block
+    return createBlock(current, joinData, leaveData, newCertsFromWoT, revocations, exclusions, transactions, manualValues);
+  });
+
+  const findNewcomersAndLeavers  = (current, filteringFunc) => co(function*() {
+    const newcomers = yield findNewcomers(current, filteringFunc);
+    const leavers = yield findLeavers(current);
+
+    const cur = newcomers.current;
+    const newWoTMembers = newcomers.newWotMembers;
+    const finalJoinData = newcomers.finalJoinData;
+    const updates = newcomers.updates;
+
+    return [cur, newWoTMembers, finalJoinData, leavers, updates];
+  });
+
+  const findTransactions = (current) => co(function*() {
+    const versionMin = current ? Math.min(common.constants.LAST_VERSION_FOR_TX, current.version) : common.constants.DOCUMENTS_VERSION;
+    const txs = yield dal.getTransactionsPending(versionMin);
+    const transactions = [];
+    const passingTxs = [];
+    for (const obj of txs) {
+      obj.currency = conf.currency
+      const tx = Transaction.fromJSON(obj);
+      try {
+        yield new Promise((resolve, reject) => {
+          rules.HELPERS.checkBunchOfTransactions(passingTxs.concat(tx), (err, res) => {
+            if (err) return reject(err)
+            return resolve(res)
+          })
+        })
+        const nextBlockWithFakeTimeVariation = { medianTime: current.medianTime + 1 };
+        yield rules.HELPERS.checkSingleTransaction(tx, nextBlockWithFakeTimeVariation, conf, dal);
+        yield rules.HELPERS.checkTxBlockStamp(tx, dal);
+        transactions.push(tx);
+        passingTxs.push(tx);
+        logger.info('Transaction %s added to block', tx.hash);
+      } catch (err) {
+        logger.error(err);
+        const currentNumber = (current && current.number) || 0;
+        const blockstamp = tx.blockstamp || (currentNumber + '-');
+        const txBlockNumber = parseInt(blockstamp.split('-')[0]);
+        // 10 blocks before removing the transaction
+        if (currentNumber - txBlockNumber + 1 >= common.constants.TRANSACTION_MAX_TRIES) {
+          yield dal.removeTxByHash(tx.hash);
+        }
+      }
+    }
+    return transactions;
+  });
+
+  const findLeavers = (current) => co(function*() {
+    const leaveData = {};
+    const memberships = yield dal.findLeavers();
+    const leavers = [];
+    memberships.forEach((ms) => leavers.push(ms.issuer));
+    for (const ms of memberships) {
+      const leave = { identity: null, ms: ms, key: null, idHash: '' };
+      leave.idHash = (hashf(ms.userid + ms.certts + ms.issuer) + "").toUpperCase();
+      let block;
+      if (current) {
+        block = yield dal.getBlock(ms.number);
+      }
+      else {
+        block = {};
+      }
+      const identity = yield dal.getIdentityByHashOrNull(leave.idHash);
+      const currentMembership = yield dal.mindexDAL.getReducedMS(ms.issuer);
+      const currentMSN = currentMembership ? parseInt(currentMembership.created_on) : -1;
+      if (identity && block && currentMSN < leave.ms.number && identity.member) {
+        // MS + matching cert are found
+        leave.identity = identity;
+        leaveData[identity.pubkey] = leave;
+      }
+    }
+    return leaveData;
+  });
+
+  const findNewcomers = (current, filteringFunc) => co(function*() {
+    const updates = {};
+    const preJoinData = yield getPreJoinData(current);
+    const joinData = yield filteringFunc(preJoinData);
+    const members = yield dal.getMembers();
+    const wotMembers = _.pluck(members, 'pubkey');
+    // Checking step
+    const newcomers = _(joinData).keys();
+    const nextBlockNumber = current ? current.number + 1 : 0;
+    try {
+      const realNewcomers = yield iteratedChecking(newcomers, (someNewcomers) => co(function*() {
+        const nextBlock = {
+          number: nextBlockNumber,
+          joiners: someNewcomers,
+          identities: _.filter(newcomers.map((pub) => joinData[pub].identity), { wasMember: false }).map((idty) => idty.pubkey)
+        };
+        const theNewLinks = yield computeNewLinks(nextBlockNumber, someNewcomers, joinData, updates);
+        yield checkWoTConstraints(nextBlock, theNewLinks, current);
+      }));
+      const newLinks = yield computeNewLinks(nextBlockNumber, realNewcomers, joinData, updates);
+      const newWoT = wotMembers.concat(realNewcomers);
+      const finalJoinData = {};
+      realNewcomers.forEach((newcomer) => {
+        // Only keep membership of selected newcomers
+        finalJoinData[newcomer] = joinData[newcomer];
+        // Only keep certifications from final members
+        const keptCerts = [];
+        joinData[newcomer].certs.forEach((cert) => {
+          const issuer = cert.from;
+          if (~newWoT.indexOf(issuer) && ~newLinks[cert.to].indexOf(issuer)) {
+            keptCerts.push(cert);
+          }
+        });
+        joinData[newcomer].certs = keptCerts;
+      });
+      return {
+        current: current,
+        newWotMembers: wotMembers.concat(realNewcomers),
+        finalJoinData: finalJoinData,
+        updates: updates
+      }
+    } catch(err) {
+      logger.error(err);
+      throw err;
+    }
+  });
+
+  const checkWoTConstraints = (block, newLinks, current) => co(function*() {
+    if (block.number < 0) {
+      throw 'Cannot compute WoT constraint for negative block number';
+    }
+    const newcomers = block.joiners.map((inlineMS) => inlineMS.split(':')[0]);
+    const realNewcomers = block.identities;
+    for (const newcomer of newcomers) {
+      if (block.number > 0) {
+        try {
+          // Will throw an error if not enough links
+          yield mainContext.checkHaveEnoughLinks(newcomer, newLinks);
+          // This one does not throw but returns a boolean
+          const isOut = yield rules.HELPERS.isOver3Hops(newcomer, newLinks, realNewcomers, current, conf, dal);
+          if (isOut) {
+            throw 'Key ' + newcomer + ' is not recognized by the WoT for this block';
+          }
+        } catch (e) {
+          logger.debug(e);
+          throw e;
+        }
+      }
+    }
+  });
+
+  const iteratedChecking = (newcomers, checkWoTForNewcomers) => co(function*() {
+    const passingNewcomers = [];
+    let hadError = false;
+    for (const newcomer of newcomers) {
+      try {
+        yield checkWoTForNewcomers(passingNewcomers.concat(newcomer));
+        passingNewcomers.push(newcomer);
+      } catch (err) {
+        hadError = hadError || err;
+      }
+    }
+    if (hadError) {
+      return yield iteratedChecking(passingNewcomers, checkWoTForNewcomers);
+    } else {
+      return passingNewcomers;
+    }
+  });
+
+  const getPreJoinData = (current) => co(function*() {
+    const preJoinData = {};
+    const memberships = yield dal.findNewcomers(current && current.medianTime)
+    const joiners = [];
+    memberships.forEach((ms) =>joiners.push(ms.issuer));
+    for (const ms of memberships) {
+      try {
+        if (ms.block !== common.constants.SPECIAL_BLOCK) {
+          let msBasedBlock = yield dal.getBlockByBlockstampOrNull(ms.block);
+          if (!msBasedBlock) {
+            throw constants.ERRORS.BLOCKSTAMP_DOES_NOT_MATCH_A_BLOCK;
+          }
+          let age = current.medianTime - msBasedBlock.medianTime;
+          if (age > conf.msWindow) {
+            throw constants.ERRORS.TOO_OLD_MEMBERSHIP;
+          }
+        }
+        const idtyHash = (hashf(ms.userid + ms.certts + ms.issuer) + "").toUpperCase();
+        const join = yield that.getSinglePreJoinData(current, idtyHash, joiners);
+        join.ms = ms;
+        const currentMembership = yield dal.mindexDAL.getReducedMS(ms.issuer);
+        const currentMSN = currentMembership ? parseInt(currentMembership.created_on) : -1;
+        if (!join.identity.revoked && currentMSN < parseInt(join.ms.number)) {
+          preJoinData[join.identity.pubkey] = join;
+        }
+      } catch (err) {
+        if (err && !err.uerr) {
+          logger.warn(err);
+        }
+      }
+    }
+    return preJoinData;
+  });
+
+  const computeNewLinks = (forBlock, theNewcomers, joinData, updates) => co(function *() {
+    let newCerts = yield that.computeNewCerts(forBlock, theNewcomers, joinData);
+    return that.newCertsToLinks(newCerts, updates);
+  });
+
+  this.newCertsToLinks = (newCerts, updates) => {
+    let newLinks = {};
+    _.mapObject(newCerts, function(certs, pubkey) {
+      newLinks[pubkey] = _.pluck(certs, 'from');
+    });
+    _.mapObject(updates, function(certs, pubkey) {
+      newLinks[pubkey] = (newLinks[pubkey] || []).concat(_.pluck(certs, 'pubkey'));
+    });
+    return newLinks;
+  };
+
+  this.computeNewCerts = (forBlock, theNewcomers, joinData) => co(function *() {
+    const newCerts = {}, certifiers = [];
+    const certsByKey = _.mapObject(joinData, function(val){ return val.certs; });
+    for (const newcomer of theNewcomers) {
+      // New array of certifiers
+      newCerts[newcomer] = newCerts[newcomer] || [];
+      // Check wether each certification of the block is from valid newcomer/member
+      for (const cert of certsByKey[newcomer]) {
+        const isAlreadyCertifying = certifiers.indexOf(cert.from) !== -1;
+        if (!(isAlreadyCertifying && forBlock > 0)) {
+          if (~theNewcomers.indexOf(cert.from)) {
+            // Newcomer to newcomer => valid link
+            newCerts[newcomer].push(cert);
+            certifiers.push(cert.from);
+          } else {
+            let isMember = yield dal.isMember(cert.from);
+            // Member to newcomer => valid link
+            if (isMember) {
+              newCerts[newcomer].push(cert);
+              certifiers.push(cert.from);
+            }
+          }
+        }
+      }
+    }
+    return newCerts;
+  });
+
+  this.getSinglePreJoinData = (current, idHash, joiners) => co(function *() {
+    const identity = yield dal.getIdentityByHashOrNull(idHash);
+    let foundCerts = [];
+    const vHEAD_1 = yield mainContext.getvHEAD_1();
+    if (!identity) {
+      throw 'Identity with hash \'' + idHash + '\' not found';
+    }
+    if (current && identity.buid == common.constants.SPECIAL_BLOCK && !identity.wasMember) {
+      throw constants.ERRORS.TOO_OLD_IDENTITY;
+    }
+    else if (!identity.wasMember && identity.buid != common.constants.SPECIAL_BLOCK) {
+      const idtyBasedBlock = yield dal.getBlock(identity.buid);
+      const age = current.medianTime - idtyBasedBlock.medianTime;
+      if (age > conf.idtyWindow) {
+        throw constants.ERRORS.TOO_OLD_IDENTITY;
+      }
+    }
+    const idty = Identity.fromJSON(identity);
+    idty.currency = conf.currency;
+    const createIdentity = idty.rawWithoutSig();
+    const verified = keyring.verify(createIdentity, idty.sig, idty.pubkey);
+    if (!verified) {
+      throw constants.ERRORS.IDENTITY_WRONGLY_SIGNED;
+    }
+    const isIdentityLeaving = yield dal.isLeaving(idty.pubkey);
+    if (!isIdentityLeaving) {
+      if (!current) {
+        // Look for certifications from initial joiners
+        const certs = yield dal.certsNotLinkedToTarget(idHash);
+        foundCerts = _.filter(certs, function(cert){
+          // Add 'joiners && ': special case when block#0 not written ANd not joiner yet (avoid undefined error)
+          return joiners && ~joiners.indexOf(cert.from);
+        });
+      } else {
+        // Look for certifications from WoT members
+        let certs = yield dal.certsNotLinkedToTarget(idHash);
+        const certifiers = [];
+        for (const cert of certs) {
+          try {
+            const basedBlock = yield dal.getBlock(cert.block_number);
+            if (!basedBlock) {
+              throw 'Unknown timestamp block for identity';
+            }
+            if (current) {
+              const age = current.medianTime - basedBlock.medianTime;
+              if (age > conf.sigWindow || age > conf.sigValidity) {
+                throw 'Too old certification';
+              }
+            }
+            // Already exists a link not replayable yet?
+            let exists = yield dal.existsNonReplayableLink(cert.from, cert.to);
+            if (exists) {
+              throw 'It already exists a similar certification written, which is not replayable yet';
+            }
+            // Already exists a link not chainable yet?
+            exists = yield dal.existsNonChainableLink(cert.from, vHEAD_1, conf.sigStock);
+            if (exists) {
+              throw 'It already exists a written certification from ' + cert.from + ' which is not chainable yet';
+            }
+            const isMember = yield dal.isMember(cert.from);
+            const doubleSignature = ~certifiers.indexOf(cert.from) ? true : false;
+            if (isMember && !doubleSignature) {
+              const isValid = yield rules.HELPERS.checkCertificationIsValidForBlock(cert, { number: current.number + 1, currency: current.currency }, identity, conf, dal);
+              if (isValid) {
+                certifiers.push(cert.from);
+                foundCerts.push(cert);
+              }
+            }
+          } catch (e) {
+            logger.debug(e.stack || e.message || e);
+            // Go on
+          }
+        }
+      }
+    }
+    return {
+      identity: identity,
+      key: null,
+      idHash: idHash,
+      certs: foundCerts
+    };
+  });
+
+  const createBlock = (current, joinData, leaveData, updates, revocations, exclusions, transactions, manualValues) => {
+    return co(function *() {
+
+      if (manualValues && manualValues.excluded) {
+        exclusions = manualValues.excluded;
+      }
+      if (manualValues && manualValues.revoked) {
+        revocations = [];
+      }
+
+      const vHEAD = yield mainContext.getvHeadCopy();
+      const vHEAD_1 = yield mainContext.getvHEAD_1();
+      const maxLenOfBlock = indexer.DUP_HELPERS.getMaxBlockSize(vHEAD);
+      let blockLen = 0;
+      // Revocations have an impact on exclusions
+      revocations.forEach((idty) => exclusions.push(idty.pubkey));
+      // Prevent writing joins/updates for excluded members
+      exclusions = _.uniq(exclusions);
+      exclusions.forEach((excluded) => {
+        delete updates[excluded];
+        delete joinData[excluded];
+        delete leaveData[excluded];
+      });
+      _(leaveData).keys().forEach((leaver) => {
+        delete updates[leaver];
+        delete joinData[leaver];
+      });
+      const block = new Block();
+      block.number = current ? current.number + 1 : 0;
+      // Compute the new MedianTime
+      if (block.number == 0) {
+        block.medianTime = moment.utc().unix() - conf.rootoffset;
+      }
+      else {
+        block.medianTime = vHEAD.medianTime;
+      }
+      // Choose the version
+      block.version = (manualValues && manualValues.version) || (yield rules.HELPERS.getMaxPossibleVersionNumber(current));
+      block.currency = current ? current.currency : conf.currency;
+      block.nonce = 0;
+      if (!conf.dtReeval) {
+        conf.dtReeval = conf.dt;
+      }
+      if (!conf.udTime0) {
+        conf.udTime0 = block.medianTime + conf.dt;
+      }
+      if (!conf.udReevalTime0) {
+        conf.udReevalTime0 = block.medianTime + conf.dtReeval;
+      }
+      block.parameters = block.number > 0 ? '' : [
+        conf.c, conf.dt, conf.ud0,
+        conf.sigPeriod, conf.sigStock, conf.sigWindow, conf.sigValidity,
+        conf.sigQty, conf.idtyWindow, conf.msWindow, conf.xpercent, conf.msValidity,
+        conf.stepMax, conf.medianTimeBlocks, conf.avgGenTime, conf.dtDiffEval,
+        (conf.percentRot == 1 ? "1.0" : conf.percentRot),
+        conf.udTime0,
+        conf.udReevalTime0,
+        conf.dtReeval
+      ].join(':');
+      block.previousHash = current ? current.hash : "";
+      block.previousIssuer = current ? current.issuer : "";
+      if (selfPubkey)
+        block.issuer = selfPubkey;
+      // Members merkle
+      const joiners = _(joinData).keys();
+      joiners.sort()
+      const previousCount = current ? current.membersCount : 0;
+      if (joiners.length == 0 && !current) {
+        throw constants.ERRORS.CANNOT_ROOT_BLOCK_NO_MEMBERS;
+      }
+
+      // Kicked people
+      block.excluded = exclusions;
+
+      /*****
+       * Priority 1: keep the WoT sane
+       */
+      // Certifications from the WoT, to the WoT
+      _(updates).keys().forEach((certifiedMember) => {
+        const certs = updates[certifiedMember] || [];
+        certs.forEach((cert) => {
+          if (blockLen < maxLenOfBlock) {
+            block.certifications.push(Certification.fromJSON(cert).inline());
+            blockLen++;
+          }
+        });
+      });
+      // Renewed
+      joiners.forEach((joiner) => {
+        const data = joinData[joiner];
+        // Join only for non-members
+        if (data.identity.member) {
+          if (blockLen < maxLenOfBlock) {
+            block.actives.push(Membership.fromJSON(data.ms).inline());
+            blockLen++;
+          }
+        }
+      });
+      // Leavers
+      const leavers = _(leaveData).keys();
+      leavers.forEach((leaver) => {
+        const data = leaveData[leaver];
+        // Join only for non-members
+        if (data.identity.member) {
+          if (blockLen < maxLenOfBlock) {
+            block.leavers.push(Membership.fromJSON(data.ms).inline());
+            blockLen++;
+          }
+        }
+      });
+
+      /*****
+       * Priority 2: revoked identities
+       */
+      revocations.forEach((idty) => {
+        if (blockLen < maxLenOfBlock) {
+          block.revoked.push([idty.pubkey, idty.revocation_sig].join(':'));
+          blockLen++;
+        }
+      });
+
+      /*****
+       * Priority 3: newcomers/renewcomers
+       */
+      let countOfCertsToNewcomers = 0;
+      // Newcomers
+      // Newcomers + back people
+      joiners.forEach((joiner) => {
+        const data = joinData[joiner];
+        // Identities only for never-have-been members
+        if (!data.identity.member && !data.identity.wasMember) {
+          block.identities.push(Identity.fromJSON(data.identity).inline());
+        }
+        // Join only for non-members
+        if (!data.identity.member) {
+          block.joiners.push(Membership.fromJSON(data.ms).inline());
+        }
+      });
+      block.identities = _.sortBy(block.identities, (line) => {
+        const sp = line.split(':');
+        return sp[2] + sp[3];
+      });
+
+      // Certifications from the WoT, to newcomers
+      joiners.forEach((joiner) => {
+        const data = joinData[joiner] || [];
+        data.certs.forEach((cert) => {
+          countOfCertsToNewcomers++;
+          block.certifications.push(Certification.fromJSON(cert).inline());
+        });
+      });
+
+      // Eventually revert newcomers/renewcomer
+      if (block.number > 0 && Block.getLen(block) > maxLenOfBlock) {
+        for (let i = 0; i < block.identities.length; i++) {
+          block.identities.pop();
+          block.joiners.pop();
+        }
+        for (let i = 0; i < countOfCertsToNewcomers; i++) {
+          block.certifications.pop();
+        }
+      }
+
+      // Final number of members
+      block.membersCount = previousCount + block.joiners.length - block.excluded.length;
+
+      vHEAD.membersCount = block.membersCount;
+
+      /*****
+       * Priority 4: transactions
+       */
+      block.transactions = [];
+      blockLen = Block.getLen(block);
+      if (blockLen < maxLenOfBlock) {
+        transactions.forEach((tx) => {
+          const txLen = Transaction.getLen(tx);
+          if (txLen <= common.constants.MAXIMUM_LEN_OF_COMPACT_TX && blockLen + txLen <= maxLenOfBlock && tx.version == common.constants.TRANSACTION_VERSION) {
+            block.transactions.push({ raw: tx.compact() });
+          }
+          blockLen += txLen;
+        });
+      }
+
+      /**
+       * Finally handle the Universal Dividend
+       */
+      block.powMin = vHEAD.powMin;
+
+      // Universal Dividend
+      if (vHEAD.new_dividend) {
+
+        // BR_G13
+        // Recompute according to block.membersCount
+        indexer.prepareDividend(vHEAD, vHEAD_1, conf);
+        // BR_G14
+        indexer.prepareUnitBase(vHEAD, vHEAD_1, conf);
+
+        // Fix BR_G14 double call
+        vHEAD.unitBase = Math.min(vHEAD_1.unitBase + 1, vHEAD.unitBase);
+
+        block.dividend = vHEAD.dividend;
+        block.unitbase = vHEAD.unitBase;
+      } else {
+        block.unitbase = block.number == 0 ? 0 : current.unitbase;
+      }
+      // Rotation
+      block.issuersCount = vHEAD.issuersCount;
+      block.issuersFrame = vHEAD.issuersFrame;
+      block.issuersFrameVar = vHEAD.issuersFrameVar;
+      // Manual values before hashing
+      if (manualValues) {
+        _.extend(block, _.omit(manualValues, 'time'));
+      }
+      // InnerHash
+      block.time = block.medianTime;
+      block.inner_hash = hashf(rawer.getBlockInnerPart(block)).toUpperCase();
+      return block;
+    });
+  }
+}
+
+/**
+ * Class to implement strategy of automatic selection of incoming data for next block.
+ * @constructor
+ */
+function NextBlockGenerator(mainContext, conf, dal, logger) {
+
+  this.findNewCertsFromWoT = (current) => co(function *() {
+    const updates = {};
+    const updatesToFrom = {};
+    const certs = yield dal.certsFindNew();
+    const vHEAD_1 = yield mainContext.getvHEAD_1();
+    for (const cert of certs) {
+      const targetIdty = yield dal.getIdentityByHashOrNull(cert.target);
+      // The identity must be known
+      if (targetIdty) {
+        const certSig = cert.sig;
+        // Do not rely on certification block UID, prefer using the known hash of the block by its given number
+        const targetBlock = yield dal.getBlock(cert.block_number);
+        // Check if writable
+        let duration = current && targetBlock ? current.medianTime - parseInt(targetBlock.medianTime) : 0;
+        if (targetBlock && duration <= conf.sigWindow) {
+          cert.sig = '';
+          cert.currency = conf.currency;
+          cert.issuer = cert.from;
+          cert.idty_issuer = targetIdty.pubkey;
+          cert.idty_uid = targetIdty.uid;
+          cert.idty_buid = targetIdty.buid;
+          cert.idty_sig = targetIdty.sig;
+          cert.buid = current ? [cert.block_number, targetBlock.hash].join('-') : common.constants.SPECIAL_BLOCK;
+          const rawCert = Certification.fromJSON(cert).getRaw();
+          if (keyring.verify(rawCert, certSig, cert.from)) {
+            cert.sig = certSig;
+            let exists = false;
+            if (current) {
+              // Already exists a link not replayable yet?
+              exists = yield dal.existsNonReplayableLink(cert.from, cert.to);
+            }
+            if (!exists) {
+              // Already exists a link not chainable yet?
+              // No chainability block means absolutely nobody can issue certifications yet
+              exists = yield dal.existsNonChainableLink(cert.from, vHEAD_1, conf.sigStock);
+              if (!exists) {
+                // It does NOT already exists a similar certification written, which is not replayable yet
+                // Signatory must be a member
+                const isSignatoryAMember = yield dal.isMember(cert.from);
+                const isCertifiedANonLeavingMember = isSignatoryAMember && (yield dal.isMemberAndNonLeaver(cert.to));
+                // Certified must be a member and non-leaver
+                if (isSignatoryAMember && isCertifiedANonLeavingMember) {
+                  updatesToFrom[cert.to] = updatesToFrom[cert.to] || [];
+                  updates[cert.to] = updates[cert.to] || [];
+                  if (updatesToFrom[cert.to].indexOf(cert.from) == -1) {
+                    updates[cert.to].push(cert);
+                    updatesToFrom[cert.to].push(cert.from);
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+    return updates;
+  });
+
+  this.filterJoiners = (preJoinData) => co(function*() {
+    const filtered = {};
+    const filterings = [];
+    const filter = (pubkey) => co(function*() {
+      try {
+        // No manual filtering, takes all BUT already used UID or pubkey
+        let exists = yield rules.HELPERS.checkExistsUserID(preJoinData[pubkey].identity.uid, dal);
+        if (exists && !preJoinData[pubkey].identity.wasMember) {
+          throw 'UID already taken';
+        }
+        exists = yield rules.HELPERS.checkExistsPubkey(pubkey, dal);
+        if (exists && !preJoinData[pubkey].identity.wasMember) {
+          throw 'Pubkey already taken';
+        }
+        filtered[pubkey] = preJoinData[pubkey];
+      }
+      catch (err) {
+        logger.warn(err);
+      }
+    });
+    _.keys(preJoinData).forEach( (joinPubkey) => filterings.push(filter(joinPubkey)));
+    yield filterings;
+    return filtered;
+  });
+}
+
+/**
+ * Class to implement strategy of manual selection of root members for root block.
+ * @constructor
+ */
+function ManualRootGenerator() {
+
+  this.findNewCertsFromWoT = () => Promise.resolve({});
+
+  this.filterJoiners = (preJoinData) => co(function*() {
+    const filtered = {};
+    const newcomers = _(preJoinData).keys();
+    const uids = [];
+    newcomers.forEach((newcomer) => uids.push(preJoinData[newcomer].ms.userid));
+
+    if (newcomers.length > 0) {
+      const answers = yield inquirer.prompt([{
+          type: "checkbox",
+          name: "uids",
+          message: "Newcomers to add",
+          choices: uids,
+          default: uids[0]
+        }]);
+      newcomers.forEach((newcomer) => {
+        if (~answers.uids.indexOf(preJoinData[newcomer].ms.userid))
+          filtered[newcomer] = preJoinData[newcomer];
+      });
+      if (answers.uids.length == 0)
+        throw 'No newcomer selected';
+      return filtered
+    } else {
+      throw 'No newcomer found';
+    }
+  });
+}
diff --git a/app/modules/prover/lib/blockProver.js b/app/modules/prover/lib/blockProver.js
new file mode 100644
index 0000000000000000000000000000000000000000..c01019626590b4ac6f1eb28db4318a6f6966a016
--- /dev/null
+++ b/app/modules/prover/lib/blockProver.js
@@ -0,0 +1,176 @@
+"use strict";
+const co              = require('co');
+const engine          = require('./engine');
+const querablep       = require('querablep');
+const common          = require('duniter-common');
+const constants       = require('./constants');
+
+const Block = common.document.Block
+
+const POW_FOUND = true;
+const POW_NOT_FOUND_YET = false;
+
+module.exports = (server) => new BlockProver(server);
+
+function BlockProver(server) {
+
+  let conf = server.conf;
+  let pair = conf.pair;
+  let logger = server.logger;
+  let waitResolve;
+
+  let workerFarmPromise;
+
+  function getWorker() {
+    return (workerFarmPromise || (workerFarmPromise = co(function*() {
+      return new WorkerFarm();
+    })));
+  }
+
+  const debug = process.execArgv.toString().indexOf('--debug') !== -1;
+  if(debug) {
+    //Set an unused port number.
+    process.execArgv = [];
+  }
+
+  this.cancel = (gottenBlock) => co(function*() {
+    // If no farm was instanciated, there is nothing to do yet
+    if (workerFarmPromise) {
+      let farm = yield getWorker();
+      if (farm.isComputing() && !farm.isStopping()) {
+        yield farm.stopPoW(gottenBlock);
+      }
+      if (waitResolve) {
+        waitResolve();
+        waitResolve = null;
+      }
+    }
+  });
+
+  this.prove = function (block, difficulty, forcedTime) {
+
+    if (waitResolve) {
+      waitResolve();
+      waitResolve = null;
+    }
+
+    const remainder = difficulty % 16;
+    const nbZeros = (difficulty - remainder) / 16;
+    const highMark = common.constants.PROOF_OF_WORK.UPPER_BOUND[remainder];
+
+    return co(function*() {
+
+      let powFarm = yield getWorker();
+
+      if (block.number == 0) {
+        // On initial block, difficulty is the one given manually
+        block.powMin = difficulty;
+      }
+
+      // Start
+      powFarm.setOnAlmostPoW(function(pow, matches, aBlock, found) {
+        powEvent(found, pow);
+        if (matches && matches[1].length >= constants.MINIMAL_ZEROS_TO_SHOW_IN_LOGS) {
+          logger.info('Matched %s zeros %s with Nonce = %s for block#%s by %s', matches[1].length, pow, aBlock.nonce, aBlock.number, aBlock.issuer.slice(0,6));
+        }
+      });
+
+      block.nonce = 0;
+      logger.info('Generating proof-of-work with %s leading zeros followed by [0-' + highMark + ']... (CPU usage set to %s%) for block#%s', nbZeros, (conf.cpu * 100).toFixed(0), block.number, block.issuer.slice(0,6));
+      const start = Date.now();
+      let result = yield powFarm.askNewProof({
+        newPoW: { conf: conf, block: block, zeros: nbZeros, highMark: highMark, forcedTime: forcedTime, pair }
+      });
+      if (!result) {
+        logger.info('GIVEN proof-of-work for block#%s with %s leading zeros followed by [0-' + highMark + ']! stop PoW for %s', block.number, nbZeros, pair.pub.slice(0,6));
+        throw 'Proof-of-work computation canceled because block received';
+      } else {
+        const proof = result.block;
+        const testsCount = result.testsCount;
+        const duration = (Date.now() - start);
+        const testsPerSecond = (testsCount / (duration / 1000)).toFixed(2);
+        logger.info('Done: #%s, %s in %ss (%s tests, ~%s tests/s)', block.number, proof.hash, (duration / 1000).toFixed(2), testsCount, testsPerSecond);
+        logger.info('FOUND proof-of-work with %s leading zeros followed by [0-' + highMark + ']!', nbZeros);
+        return Block.fromJSON(proof);
+      }
+    });
+  };
+
+  this.changeCPU = (cpu) => co(function*() {
+    conf.cpu = cpu;
+    const farm = yield getWorker();
+    return farm.changeCPU(cpu);
+  });
+
+  this.changePoWPrefix = (prefix) => co(function*() {
+    const farm = yield getWorker();
+    return farm.changePoWPrefix(prefix);
+  });
+
+  function powEvent(found, hash) {
+    server && server.push({ pow: { found, hash } });
+  }
+
+  function WorkerFarm() {
+    // Create
+    const theEngine = engine(server.conf, server.logger)
+
+    let onAlmostPoW
+
+    // An utility method to filter the pow notifications
+    const checkPoWandNotify = (hash, block, found) => {
+      const matches = hash.match(/^(0{2,})[^0]/);
+      if (matches && onAlmostPoW) {
+        onAlmostPoW(hash, matches, block, found);
+      }
+    }
+
+    // Keep track of PoW advancement
+    theEngine.setOnInfoMessage((message) => {
+      if (message.error) {
+        logger.error('Error in engine#%s:', theEngine.id, message.error)
+      } else if (message.pow) {
+        // A message about the PoW
+        const msg = message.pow
+        checkPoWandNotify(msg.pow, msg.block, POW_NOT_FOUND_YET)
+      }
+    })
+
+    // We use as much cores as available, but not more than CORES_MAXIMUM_USE_IN_PARALLEL
+
+    let powPromise = null
+    let stopPromise = null
+
+    this.changeCPU = (cpu) => theEngine.setConf({ cpu })
+
+    this.changePoWPrefix = (prefix) => theEngine.setConf({ prefix })
+
+    this.isComputing = () => powPromise !== null && !powPromise.isResolved()
+
+    this.isStopping = () => stopPromise !== null && !stopPromise.isResolved()
+
+    /**
+     * Eventually stops the engine PoW if one was computing
+     */
+    this.stopPoW = (gottenBlock) => {
+      stopPromise = querablep(theEngine.cancel(gottenBlock))
+      return stopPromise;
+    };
+
+    /**
+     * Starts a new computation of PoW
+     * @param stuff The necessary data for computing the PoW
+     */
+    this.askNewProof = (stuff) => co(function*() {
+      // Starts the PoW
+      powPromise = querablep(theEngine.prove(stuff))
+      const res = yield powPromise
+      if (res) {
+        checkPoWandNotify(res.pow.pow, res.pow.block, POW_FOUND);
+      }
+      return res && res.pow
+    })
+
+    this.setOnAlmostPoW = (onPoW) => onAlmostPoW = onPoW
+  }
+}
diff --git a/app/modules/prover/lib/constants.js b/app/modules/prover/lib/constants.js
new file mode 100644
index 0000000000000000000000000000000000000000..4ed3b07654c3c470d8100596098b42bd419f41dc
--- /dev/null
+++ b/app/modules/prover/lib/constants.js
@@ -0,0 +1,20 @@
+"use strict";
+
+module.exports = {
+
+  PULLING_MAX_DURATION: 10 * 1000, // 10 seconds
+
+  CORES_MAXIMUM_USE_IN_PARALLEL: 8,
+
+  MINIMAL_ZEROS_TO_SHOW_IN_LOGS: 3,
+
+  POW_MINIMAL_TO_SHOW: 2,
+  DEFAULT_CPU: 0.6,
+
+  NONCE_RANGE: 1000 * 1000 * 1000 * 100,
+
+  POW_MAXIMUM_ACCEPTABLE_HANDICAP: 64,
+
+  // When to trigger the PoW process again if no PoW is triggered for a while. In milliseconds.
+  POW_SECURITY_RETRY_DELAY: 10 * 60 * 1000
+};
diff --git a/app/modules/prover/lib/engine.js b/app/modules/prover/lib/engine.js
new file mode 100644
index 0000000000000000000000000000000000000000..942ffba6555454ea6ccd811b611402ed73982326
--- /dev/null
+++ b/app/modules/prover/lib/engine.js
@@ -0,0 +1,47 @@
+"use strict";
+
+const os         = require('os')
+const co         = require('co')
+const querablep  = require('querablep')
+const powCluster = require('./powCluster')
+const constants  = require('./constants')
+
+module.exports = function (conf, logger) {
+  return new PowEngine(conf, logger);
+};
+
+function PowEngine(conf, logger) {
+
+  // Super important for Node.js debugging
+  const debug = process.execArgv.toString().indexOf('--debug') !== -1;
+  if(debug) {
+    //Set an unused port number.
+    process.execArgv = [];
+  }
+
+  const nbWorkers = require('os').cpus().slice(0, conf && conf.nbCores || constants.CORES_MAXIMUM_USE_IN_PARALLEL).length
+  const cluster = powCluster(nbWorkers, logger)
+
+  this.forceInit = () => cluster.initCluster()
+
+  this.id = cluster.clusterId
+
+  this.prove = (stuff) => co(function*() {
+
+    if (cluster.hasProofPending) {
+      yield cluster.cancelWork()
+    }
+
+    if (os.arch().match(/arm/)) {
+      stuff.conf.cpu /= 2; // Don't know exactly why is ARM so much saturated by PoW, so let's divide by 2
+    }
+    let res = yield cluster.proveByWorkers(stuff)
+    return res
+  })
+
+  this.cancel = () => cluster.cancelWork()
+
+  this.setConf = (value) => cluster.changeConf(value)
+
+  this.setOnInfoMessage = (callback) => cluster.onInfoMessage = callback
+}
diff --git a/app/modules/prover/lib/permanentProver.js b/app/modules/prover/lib/permanentProver.js
new file mode 100644
index 0000000000000000000000000000000000000000..62d45039a68ae0c5ac90f50d29894cffc8d40337
--- /dev/null
+++ b/app/modules/prover/lib/permanentProver.js
@@ -0,0 +1,209 @@
+"use strict";
+
+const co = require('co');
+const querablep = require('querablep');
+const common = require('duniter-common');
+const constants = require('./constants');
+const blockProver = require('./blockProver');
+const blockGenerator = require('./blockGenerator');
+
+module.exports = (server) => new PermanentProver(server);
+
+function PermanentProver(server) {
+
+  const dos2unix = common.dos2unix;
+  const parsers = common.parsers;
+  const logger = server.logger;
+  const conf = server.conf;
+  const prover = this.prover = blockProver(server);
+  const generator = blockGenerator(server, prover);
+  const that = this;
+
+  let blockchainChangedResolver = null,
+      promiseOfWaitingBetween2BlocksOfOurs = null,
+      lastComputedBlock = null;
+
+  // Promises triggering the prooving lopp
+  let resolveContinuePromise = null;
+  let continuePromise = new Promise((resolve) => resolveContinuePromise = resolve);
+
+  let pullingResolveCallback = null;
+  let timeoutPullingCallback = null, timeoutPulling;
+  let pullingFinishedPromise = querablep(Promise.resolve());
+
+  this.allowedToStart = () => {
+    resolveContinuePromise(true);
+  };
+
+  // When we detected a pulling, we stop the PoW loop
+  this.pullingDetected = () => {
+    if (pullingFinishedPromise.isResolved()) {
+      pullingFinishedPromise = querablep(Promise.race([
+        // We wait for end of pulling signal
+        new Promise((res) => pullingResolveCallback = res),
+        // Security: if the end of pulling signal is not emitted after some, we automatically trigger it
+        new Promise((res) => timeoutPullingCallback = () => {
+          logger.warn('Pulling not finished after %s ms, continue PoW', constants.PULLING_MAX_DURATION);
+          res();
+        })
+      ]));
+    }
+    // Delay the triggering of pulling timeout
+    if (timeoutPulling) {
+      clearTimeout(timeoutPulling);
+    }
+    timeoutPulling = setTimeout(timeoutPullingCallback, constants.PULLING_MAX_DURATION);
+  };
+
+  this.pullingFinished = () => pullingResolveCallback && pullingResolveCallback();
+
+  this.loops = 0;
+
+  /******************
+   * Main proof loop
+   *****************/
+  co(function*() {
+    while (yield continuePromise) {
+      try {
+        const waitingRaces = [];
+
+        // By default, we do not make a new proof
+        let doProof = false;
+
+        try {
+          const selfPubkey = server.keyPair.publicKey;
+          const dal = server.dal;
+          const theConf = server.conf;
+          if (!selfPubkey) {
+            throw 'No self pubkey found.';
+          }
+          let current;
+          const isMember = yield dal.isMember(selfPubkey);
+          if (!isMember) {
+            throw 'Local node is not a member. Waiting to be a member before computing a block.';
+          }
+          current = yield dal.getCurrentBlockOrNull();
+          if (!current) {
+            throw 'Waiting for a root block before computing new blocks';
+          }
+          const trial = yield server.getBcContext().getIssuerPersonalizedDifficulty(selfPubkey);
+          checkTrialIsNotTooHigh(trial, current, selfPubkey);
+          const lastIssuedByUs = current.issuer == selfPubkey;
+          if (pullingFinishedPromise && !pullingFinishedPromise.isFulfilled()) {
+            logger.warn('Waiting for the end of pulling...');
+            yield pullingFinishedPromise;
+            logger.warn('Pulling done. Continue proof-of-work loop.');
+          }
+          if (lastIssuedByUs && !promiseOfWaitingBetween2BlocksOfOurs) {
+            promiseOfWaitingBetween2BlocksOfOurs = new Promise((resolve) => setTimeout(resolve, theConf.powDelay));
+            logger.warn('Waiting ' + theConf.powDelay + 'ms before starting to compute next block...');
+          } else {
+            // We have waited enough
+            promiseOfWaitingBetween2BlocksOfOurs = null;
+            // But under some conditions, we can make one
+            doProof = true;
+          }
+        } catch (e) {
+          logger.warn(e);
+        }
+
+        if (doProof) {
+
+          /*******************
+           * COMPUTING A BLOCK
+           ******************/
+          yield Promise.race([
+
+            // We still listen at eventual blockchain change
+            co(function*() {
+              // If the blockchain changes
+              yield new Promise((resolve) => blockchainChangedResolver = resolve);
+              // Then cancel the generation
+              yield prover.cancel();
+            }),
+
+            // The generation
+            co(function*() {
+              try {
+                const current = yield server.dal.getCurrentBlockOrNull();
+                const selfPubkey = server.keyPair.publicKey;
+                const trial2 = yield server.getBcContext().getIssuerPersonalizedDifficulty(selfPubkey);
+                checkTrialIsNotTooHigh(trial2, current, selfPubkey);
+                lastComputedBlock = yield generator.makeNextBlock(null, trial2);
+                try {
+                  const obj = parsers.parseBlock.syncWrite(dos2unix(lastComputedBlock.getRawSigned()));
+                  yield server.singleWritePromise(obj);
+                } catch (err) {
+                  logger.warn('Proof-of-work self-submission: %s', err.message || err);
+                }
+              } catch (e) {
+                logger.warn('The proof-of-work generation was canceled: %s', (e && e.message) || e || 'unkonwn reason');
+              }
+            })
+          ]);
+        } else {
+
+          /*******************
+           * OR WAITING PHASE
+           ******************/
+          if (promiseOfWaitingBetween2BlocksOfOurs) {
+            waitingRaces.push(promiseOfWaitingBetween2BlocksOfOurs);
+          }
+
+          let raceDone = false;
+
+          yield Promise.race(waitingRaces.concat([
+
+            // The blockchain has changed! We or someone else found a proof, we must make a gnu one
+            new Promise((resolve) => blockchainChangedResolver = () => {
+              logger.warn('Blockchain changed!');
+              resolve();
+            }),
+
+            // Security: if nothing happens for a while, trigger the whole process again
+            new Promise((resolve) => setTimeout(() => {
+              if (!raceDone) {
+                logger.warn('Security trigger: proof-of-work process seems stuck');
+                resolve();
+              }
+            }, conf.powSecurityRetryDelay))
+          ]));
+
+          raceDone = true;
+        }
+      } catch (e) {
+        logger.warn(e);
+      }
+
+      that.loops++;
+      // Informative variable
+      logger.trace('PoW loops = %s', that.loops);
+    }
+  });
+
+  this.blockchainChanged = (gottenBlock) => co(function*() {
+    if (server && (!gottenBlock || !lastComputedBlock || gottenBlock.hash !== lastComputedBlock.hash)) {
+      // Cancel any processing proof
+      yield prover.cancel(gottenBlock);
+      // If we were waiting, stop it and process the continuous generation
+      blockchainChangedResolver && blockchainChangedResolver();
+    }
+  });
+
+  this.stopEveryting = () => co(function*() {
+    // First: avoid continuing the main loop
+    continuePromise = new Promise((resolve) => resolveContinuePromise = resolve);
+    // Second: stop any started proof
+    yield prover.cancel();
+    // If we were waiting, stop it and process the continuous generation
+    blockchainChangedResolver && blockchainChangedResolver();
+  });
+
+  function checkTrialIsNotTooHigh(trial, current, selfPubkey) {
+    if (trial > (current.powMin + conf.powMaxHandicap)) {
+      logger.debug('Trial = %s, powMin = %s, pubkey = %s', trial, current.powMin, selfPubkey.slice(0, 6));
+      throw 'Too high difficulty: waiting for other members to write next block';
+    }
+  }
+}
+
diff --git a/app/modules/prover/lib/powCluster.js b/app/modules/prover/lib/powCluster.js
new file mode 100644
index 0000000000000000000000000000000000000000..811e5e8c2a67cb5d21bf4f59f7fb8a9c6e45344f
--- /dev/null
+++ b/app/modules/prover/lib/powCluster.js
@@ -0,0 +1,230 @@
+"use strict";
+
+const co = require('co');
+const _ = require('underscore')
+const nuuid = require('node-uuid');
+const moment = require('moment');
+const cluster = require('cluster')
+const querablep = require('querablep')
+const constants = require('./constants')
+
+let clusterId = 0
+
+if (cluster.isMaster) {
+
+  // Super important for Node.js debugging
+  const debug = process.execArgv.toString().indexOf('--debug') !== -1;
+  if(debug) {
+    //Set an unused port number.
+    process.execArgv = [];
+  }
+
+  /**
+   * Cluster controller, handles the messages between the main program and the PoW cluster.
+   */
+  class Master {
+
+    constructor(nbCores, logger) {
+      this.clusterId = clusterId++
+      this.nbCores = nbCores
+      this.logger = logger || Master.defaultLogger()
+      this.currentPromise = null
+      this.slaves = []
+      this.slavesMap = {}
+      this.conf = {}
+      this.onInfoMessage = (message) => {
+        this.logger.info(`${message.pow.pow} nonce = ${message.pow.block.nonce}`)
+      }
+    }
+
+    get nbWorkers() {
+      return this.slaves.length
+    }
+
+    get hasProofPending() {
+      return !!this.currentPromise
+    }
+
+    set onInfoMessage(callback) {
+      this.onInfoCallback = callback
+    }
+
+    onWorkerMessage(worker, message) {
+      // this.logger.info(`worker#${this.slavesMap[worker.id].index} sent message:${message}`)
+      if (message.pow && message.pow.pow) {
+        this.onInfoCallback && this.onInfoCallback(message)
+      }
+      if (this.currentPromise && message.uuid === this.currentPromise.extras.uuid && !this.currentPromise.isResolved() && message.answer) {
+        this.logger.info(`ENGINE c#${this.clusterId}#${this.slavesMap[worker.id].index} HAS FOUND A PROOF #${message.answer.pow.pow}`)
+        this.currentPromise.extras.resolve(message.answer)
+        // Stop the slaves' current work
+        this.cancelWork()
+      }
+      this.logger.debug(`ENGINE c#${this.clusterId}#${this.slavesMap[worker.id].index}:`, message)
+    }
+
+    initCluster() {
+      // Setup master
+      cluster.setupMaster({
+        exec: __filename
+      })
+
+      this.slaves = Array.from({ length: this.nbCores }).map((value, index) => {
+        const worker = cluster.fork()
+        this.logger.info(`Creating worker c#${this.clusterId}#w#${worker.id}`)
+        this.slavesMap[worker.id] = {
+
+          // The Node.js worker
+          worker,
+
+          // Inner identifier
+          index,
+
+          // Worker ready
+          online: (function onlinePromise() {
+            let resolve
+            const p = querablep(new Promise(res => resolve = res))
+            p.extras = { resolve }
+            return p
+          })(),
+
+          // Each worker has his own chunk of possible nonces
+          nonceBeginning: this.nbCores === 1 ? 0 : (index + 1) * constants.NONCE_RANGE
+        }
+        return this.slavesMap[worker.id]
+      })
+
+      cluster.on('exit', (worker, code, signal) => {
+        this.logger.info(`worker ${worker.process.pid} died with code ${code} and signal ${signal}`)
+      })
+
+      cluster.on('online', (worker) => {
+        // We just listen to the workers of this Master
+        if (this.slavesMap[worker.id]) {
+          this.logger.info(`[online] worker c#${this.clusterId}#w#${worker.id}`)
+          this.slavesMap[worker.id].online.extras.resolve()
+          worker.send({
+            command: 'conf',
+            value: this.conf
+          })
+        }
+      })
+
+      cluster.on('message', (worker, msg) => {
+        // Message for this cluster
+        if (this.slavesMap[worker.id]) {
+          this.onWorkerMessage(worker, msg)
+        }
+      })
+
+      this.workersOnline = this.slaves.map(s => s.online)
+      return this.workersOnline
+    }
+
+    changeConf(conf) {
+      this.logger.info(`Changing conf to: ${JSON.stringify(conf)} on PoW cluster`)
+      this.conf.cpu = this.conf.cpu || conf.cpu
+      this.conf.prefix = this.conf.prefix || conf.prefix
+      this.slaves.forEach(s => {
+        s.worker.send({
+          command: 'conf',
+          value: this.conf
+        })
+      })
+      return Promise.resolve(_.clone(conf))
+    }
+
+    cancelWork() {
+      this.logger.info(`Cancelling the work on PoW cluster`)
+      this.slaves.forEach(s => {
+        s.worker.send({
+          command: 'cancel'
+        })
+      })
+
+      // Eventually force the end of current promise
+      if (this.currentPromise && !this.currentPromise.isFulfilled()) {
+        this.currentPromise.extras.resolve(null)
+      }
+
+      // Current promise is done
+      this.currentPromise = null
+
+      return Promise.resolve()
+    }
+
+    newPromise(uuid) {
+      let resolve
+      const p = querablep(new Promise(res => resolve = res))
+      p.extras = { resolve, uuid }
+      return p
+    }
+
+    proveByWorkers(stuff) {
+
+      // Eventually spawn the workers
+      if (this.slaves.length === 0) {
+        this.initCluster()
+      }
+
+      // Register the new proof uuid
+      const uuid = nuuid.v4()
+      this.currentPromise = this.newPromise(uuid)
+
+      const that = this
+
+      return co(function*() {
+        yield that.workersOnline
+
+        if (!that.currentPromise) {
+          that.logger.info(`Proof canceled during workers' initialization`)
+          return null
+        }
+
+        // Start the salves' job
+        that.slaves.forEach(s => {
+          s.worker.send({
+            uuid,
+            command: 'newPoW',
+            value: {
+              block: stuff.newPoW.block,
+              nonceBeginning: s.nonceBeginning,
+              zeros: stuff.newPoW.zeros,
+              highMark: stuff.newPoW.highMark,
+              pair: _.clone(stuff.newPoW.pair),
+              forcedTime: stuff.newPoW.forcedTime,
+              turnDuration: stuff.newPoW.turnDuration,
+              conf: {
+                medianTimeBlocks: stuff.newPoW.conf.medianTimeBlocks,
+                avgGenTime: stuff.newPoW.conf.avgGenTime,
+                cpu: stuff.newPoW.conf.cpu,
+                prefix: stuff.newPoW.conf.prefix
+              }
+            }
+          })
+        })
+
+        let res = yield that.currentPromise
+        return res
+      })
+    }
+
+    static defaultLogger() {
+      return {
+        info: (message) => {}
+      }
+    }
+  }
+
+  module.exports = (nbCores, logger) => new Master(nbCores, logger)
+
+} else {
+
+  process.on("SIGTERM", function() {
+    console.log(`SIGTERM received, closing worker ${process.pid}`);
+    process.exit(0)
+  });
+
+  require('./proof')
+}
+
diff --git a/app/modules/prover/lib/proof.js b/app/modules/prover/lib/proof.js
new file mode 100644
index 0000000000000000000000000000000000000000..64d68645dcb8eac0518566f33c63c683e4c3e33c
--- /dev/null
+++ b/app/modules/prover/lib/proof.js
@@ -0,0 +1,292 @@
+"use strict";
+const co = require('co');
+const moment = require('moment');
+const hashf = require('duniter-common').hashf;
+const dos2unix = require('duniter-common').dos2unix;
+const querablep = require('querablep');
+const constants = require('./constants');
+const keyring = require('duniter-common').keyring;
+const rawer = require('duniter-common').rawer;
+
+const PAUSES_PER_TURN = 5;
+
+// This value can be changed
+let TURN_DURATION_IN_MILLISEC = 100;
+
+let computing = querablep(Promise.resolve(null));
+let askedStop = false;
+
+// By default, we do not prefix the PoW by any number
+let prefix = 0;
+
+let signatureFunc, lastSecret, currentCPU = 1;
+
+process.on('uncaughtException', (err) => {
+  console.error(err.stack || Error(err));
+  process.send({error: err});
+});
+
+process.on('message', (message) => co(function*() {
+
+  switch (message.command) {
+
+    case 'newPoW':
+      co(function*() {
+        askedStop = true
+
+        // Very important: do not yield if the computation is already done, to keep the lock on JS engine
+        if (!computing.isFulfilled()) {
+          yield computing;
+        }
+
+        const res = yield beginNewProofOfWork(message.value);
+        answer(message, res);
+      });
+      break;
+
+    case 'cancel':
+      if (!computing.isFulfilled()) {
+        askedStop = true;
+      }
+      break;
+
+    case 'conf':
+      if (message.value.cpu !== undefined) {
+        currentCPU = message.value.cpu
+      }
+      if (message.value.prefix !== undefined) {
+        prefix = message.value.prefix
+      }
+      answer(message, { currentCPU, prefix });
+      break;
+  }
+
+}));
+
+function beginNewProofOfWork(stuff) {
+  askedStop = false;
+  computing = querablep(co(function*() {
+
+    /*****************
+     * PREPARE POW STUFF
+     ****************/
+
+    let nonce = 0;
+    const conf = stuff.conf;
+    const block = stuff.block;
+    const nonceBeginning = stuff.nonceBeginning;
+    const nbZeros = stuff.zeros;
+    const pair = stuff.pair;
+    const forcedTime = stuff.forcedTime;
+    currentCPU = conf.cpu || constants.DEFAULT_CPU;
+    prefix = parseInt(conf.prefix || prefix) * 10 * constants.NONCE_RANGE;
+    const highMark = stuff.highMark;
+    const turnDuration = stuff.turnDuration || TURN_DURATION_IN_MILLISEC
+    let sigFunc = null;
+    if (signatureFunc && lastSecret === pair.sec) {
+      sigFunc = signatureFunc;
+    }
+    else {
+      lastSecret = pair.sec;
+      sigFunc = keyring.Key(pair.pub, pair.sec).signSync;
+    }
+    signatureFunc = sigFunc;
+    let pow = "", sig = "", raw = "";
+
+    /*****************
+     * GO!
+     ****************/
+
+    let testsCount = 0;
+    let found = false;
+    let score = 0;
+    let turn = 0;
+
+    while (!found && !askedStop) {
+
+      /*****************
+       * A TURN
+       ****************/
+
+      yield Promise.race([
+
+        // I. Stop the turn if it exceeds `turnDuration` ms
+        countDown(turnDuration),
+
+        // II. Process the turn's PoW
+        co(function*() {
+
+          /*****************
+           * A TURN OF POW ~= 100ms by default
+           * --------------------
+           *
+           * The concept of "turn" is required to limit the CPU usage.
+           * We need a time reference to have the speed = nb tests / period of time.
+           * Here we have:
+           *
+           *   - speed = testsCount / turn
+           *
+           * We have taken 1 turn = 100ms to control the CPU usage after 100ms of PoW. This means that during the
+           * very first 100ms of the PoW, CPU usage = 100%. Then it becomes controlled to the %CPU set.
+           ****************/
+
+            // Prove
+          let i = 0;
+          const thisTurn = turn;
+          const pausePeriod = score ? score / PAUSES_PER_TURN : 10; // number of pauses per turn
+          // We limit the number of tests according to CPU usage
+          const testsPerRound = score ? Math.floor(score * currentCPU) : 1000 * 1000 * 1000
+
+          // Time is updated regularly during the proof
+          block.time = getBlockTime(block, conf, forcedTime)
+          if (block.number === 0) {
+            block.medianTime = block.time
+          }
+          block.inner_hash = getBlockInnerHash(block);
+
+          /*****************
+           * Iterations of a turn
+           ****************/
+
+          while(!found && i < testsPerRound && thisTurn === turn && !askedStop) {
+
+            // Nonce change (what makes the PoW change if the time field remains the same)
+            nonce++
+
+            /*****************
+             * A PROOF OF WORK
+             ****************/
+
+            // The final nonce is composed of 3 parts
+            block.nonce = prefix + nonceBeginning + nonce
+            raw = dos2unix("InnerHash: " + block.inner_hash + "\nNonce: " + block.nonce + "\n")
+            sig = dos2unix(sigFunc(raw))
+            pow = hashf("InnerHash: " + block.inner_hash + "\nNonce: " + block.nonce + "\n" + sig + "\n").toUpperCase()
+
+            /*****************
+             * Check the POW result
+             ****************/
+
+            let j = 0, charOK = true;
+            while (j < nbZeros && charOK) {
+              charOK = pow[j] === '0';
+              j++;
+            }
+            if (charOK) {
+              found = pow[nbZeros].match(new RegExp('[0-' + highMark + ']'));
+            }
+            if (!found && nbZeros > 0 && j - 1 >= constants.POW_MINIMAL_TO_SHOW) {
+              pSend({ pow: { pow: pow, block: block, nbZeros: nbZeros }});
+            }
+
+            /*****************
+             * - Update local vars
+             * - Allow to receive stop signal
+             ****************/
+
+            if (!found && !askedStop) {
+              i++;
+              testsCount++;
+              if (i % pausePeriod === 0) {
+                yield countDown(0); // Very low pause, just the time to process eventual end of the turn
+              }
+            }
+          }
+
+          /*****************
+           * Check the POW result
+           ****************/
+          if (!found) {
+
+            // CPU speed recording
+            if (turn > 0 && !score) {
+              score = testsCount;
+            }
+
+            /*****************
+             * UNLOAD CPU CHARGE
+             ****************/
+            // We wait for a maximum time of `turnDuration`.
+            // This will trigger the end of the turn by the concurrent race I. During that time, the proof.js script
+            // just does nothing: this gives of a bit of breath to the CPU. Tthe amount of "breath" depends on the "cpu"
+            // parameter.
+            yield countDown(turnDuration);
+          }
+        })
+      ]);
+
+      // Next turn
+      turn++
+    }
+
+    /*****************
+     * POW IS OVER
+     * -----------
+     *
+     * We either have found a valid POW or a stop event has been detected.
+     ****************/
+
+    if (askedStop) {
+
+      // PoW stopped
+      askedStop = false;
+      return null
+
+    } else {
+
+      // PoW success
+      block.hash = pow
+      block.signature = sig
+      return {
+        pow: {
+          block: block,
+          testsCount: testsCount,
+          pow: pow
+        }
+      }
+    }
+  }));
+
+  return computing;
+}
+
+function countDown(duration) {
+  return new Promise((resolve) => setTimeout(resolve, duration));
+}
+
+function getBlockInnerHash(block) {
+  const raw = rawer.getBlockInnerPart(block);
+  return hash(raw);
+}
+
+function hash(str) {
+  return hashf(str).toUpperCase();
+}
+
+function getBlockTime (block, conf, forcedTime) {
+  if (forcedTime) {
+    return forcedTime;
+  }
+  const now = moment.utc().unix();
+  const maxAcceleration = require('duniter-common').rules.HELPERS.maxAcceleration(conf);
+  const timeoffset = block.number >= conf.medianTimeBlocks ? 0 : conf.rootoffset || 0;
+  const medianTime = block.medianTime;
+  const upperBound = block.number === 0 ? medianTime : Math.min(medianTime + maxAcceleration, now - timeoffset);
+  return Math.max(medianTime, upperBound);
+}
+
+function answer(message, theAnswer) {
+  return pSend({
+    uuid: message.uuid,
+    answer: theAnswer
+  })
+}
+
+function pSend(stuff) {
+  return new Promise(function (resolve, reject) {
+    process.send(stuff, function (error) {
+      !error && resolve();
+      error && reject();
+    });
+  });
+}
diff --git a/app/modules/prover/lib/prover.js b/app/modules/prover/lib/prover.js
new file mode 100644
index 0000000000000000000000000000000000000000..504ceb1a4fabab712386830fa2ecffc66c23bb6d
--- /dev/null
+++ b/app/modules/prover/lib/prover.js
@@ -0,0 +1,44 @@
+"use strict";
+
+const co = require('co');
+const util = require('util');
+const stream = require('stream');
+const permanentProver = require('./permanentProver');
+
+module.exports = Prover;
+
+function Prover(server) {
+
+  const permaProver = this.permaProver = permanentProver(server);
+
+  stream.Transform.call(this, { objectMode: true });
+
+  this._write = function (obj, enc, done) {
+    // Never close the stream
+    if (obj && obj.membersCount) {
+      permaProver.blockchainChanged(obj);
+    } else if (obj.nodeIndexInPeers !== undefined) {
+      permaProver.prover.changePoWPrefix((obj.nodeIndexInPeers + 1) * 10); // We multiply by 10 to give room to computers with < 100 cores
+    } else if (obj.cpu !== undefined) {
+      permaProver.prover.changeCPU(obj.cpu); // We multiply by 10 to give room to computers with < 100 cores
+    } else if (obj.pulling !== undefined) {
+      if (obj.pulling === 'processing') {
+        permaProver.pullingDetected();
+      }
+      else if (obj.pulling === 'finished') {
+        permaProver.pullingFinished();
+      }
+    }
+    done && done();
+  };
+
+  this.startService = () => co(function*() {
+    permaProver.allowedToStart();
+  });
+
+  this.stopService = () => co(function*() {
+    permaProver.stopEveryting();
+  });
+}
+
+util.inherits(Prover, stream.Transform);
diff --git a/package.json b/package.json
index a40f8aa9758c323e24427592ca6f644be9b9a627..63387d7bb5026c81e8a91578e2fe3e3c1dd53434 100644
--- a/package.json
+++ b/package.json
@@ -80,7 +80,6 @@
     "duniter-bma": "1.3.x",
     "duniter-crawler": "1.3.x",
     "duniter-keypair": "1.3.X",
-    "duniter-prover": "1.4.x",
     "duniter-ui": "1.3.x",
     "eslint": "3.13.1",
     "eslint-plugin-mocha": "4.8.0",
@@ -97,7 +96,6 @@
     "duniter-bma": "1.3.x",
     "duniter-crawler": "1.3.x",
     "duniter-keypair": "1.3.X",
-    "duniter-prover": "1.4.x",
     "duniter-ui": "1.3.x"
   },
   "bin": {
diff --git a/release/arch/windows/build.bat b/release/arch/windows/build.bat
index 9be0fd426059b43ab917541ef7b78176b6b4873f..d4d9c042c8788be662c80ec4ffe5db42d1b0d33f 100644
--- a/release/arch/windows/build.bat
+++ b/release/arch/windows/build.bat
@@ -3,7 +3,6 @@ set DUNITER_BRANCH=1.3.x
 set VER_UI=%DUNITER_BRANCH%
 set VER_BMA=%DUNITER_BRANCH%
 set VER_CRAWLER=%DUNITER_BRANCH%
-set VER_PROVER=%DUNITER_BRANCH%
 set VER_KEYPAIR=%DUNITER_BRANCH%
 
 set ADDON_VERSION=48
@@ -51,15 +50,13 @@ call npm install --production
 REM call npm test
 echo "Retrait des modules 'dev'..."
 call npm prune --production
-echo "Ajout du module 1/5..."
+echo "Ajout du module 1/4..."
 call npm install duniter-bma@%VER_BMA% --save --production
-echo "Ajout du module 2/5..."
+echo "Ajout du module 2/4..."
 call npm install duniter-crawler@%VER_CRAWLER% --save --production
-echo "Ajout du module 3/5..."
+echo "Ajout du module 3/4..."
 call npm install duniter-keypair@%VER_KEYPAIR% --save --production
-echo "Ajout du module 4/5..."
-call npm install duniter-prover@%VER_PROVER% --save --production
-echo "Ajout du module 5/5..."
+echo "Ajout du module 4/4..."
 call npm install duniter-ui@%VER_UI% --save --production
 
 REM echo ">> VM: installing peerDependencies installer..."
diff --git a/test/fast/prover/pow-1-cluster.js b/test/fast/prover/pow-1-cluster.js
new file mode 100644
index 0000000000000000000000000000000000000000..9f1a20b63278ca6675fc5a7c3ab62a4d9b4c7667
--- /dev/null
+++ b/test/fast/prover/pow-1-cluster.js
@@ -0,0 +1,76 @@
+"use strict";
+
+const co = require('co')
+const should = require('should')
+const powCluster = require('../../../app/modules/prover/lib/powCluster')
+const logger = require('../../../app/lib/logger')()
+
+let master
+
+describe('PoW Cluster', () => {
+
+  before(() => {
+    master = powCluster(1, logger)
+  })
+
+  it('should have an empty cluster if no PoW was asked', () => {
+    master.nbWorkers.should.equal(0)
+  })
+
+  it('should answer for a basic PoW in more than 50ms (cold)', () => co(function*(){
+    const start = Date.now()
+    yield master.proveByWorkers({
+      newPoW: {
+        block: {
+          number: 0
+        },
+        zeros: 0,
+        highMark: 'F',
+        conf: {
+          medianTimeBlocks: 1,
+          avgGenTime: 100,
+          cpu: 0.8,
+          prefix: '8'
+        },
+        pair: {
+          pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd',
+          sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'
+        },
+        turnDuration: 10
+      }
+    })
+    const delay = Date.now() - start
+    delay.should.be.above(50)
+  }))
+
+  it('should have an non-empty cluster after a PoW was asked', () => {
+    master.nbWorkers.should.above(0)
+  })
+
+  it('should answer within 50ms for a basic PoW (warm)', () => co(function*(){
+    const start = Date.now()
+    yield master.proveByWorkers({
+      newPoW: {
+        block: {
+          number: 0
+        },
+        zeros: 0,
+        highMark: 'F',
+        conf: {
+          medianTimeBlocks: 1,
+          avgGenTime: 100,
+          cpu: 0.8,
+          prefix: '8'
+        },
+        pair: {
+          pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd',
+          sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'
+        },
+        turnDuration: 100
+      }
+    })
+    const delay = Date.now() - start
+    delay.should.be.below(50)
+  }))
+
+});
diff --git a/test/fast/prover/pow-2-engine.js b/test/fast/prover/pow-2-engine.js
new file mode 100644
index 0000000000000000000000000000000000000000..f9b189905576dd36576a70836d5fc6c789fb1640
--- /dev/null
+++ b/test/fast/prover/pow-2-engine.js
@@ -0,0 +1,89 @@
+"use strict";
+
+const co = require('co');
+const should = require('should');
+const engine = require('../../../app/modules/prover/lib/engine');
+const logger = require('../../../app/lib/logger')()
+
+describe('PoW Engine', () => {
+
+  it('should be configurable', () => co(function*(){
+    const e1 = engine({ nbCores: 1 }, logger);
+    (yield e1.setConf({ cpu: 0.2, prefix: '34' })).should.deepEqual({ cpu: 0.2, prefix: '34' });
+  }));
+
+  it('should be able to make a proof', () => co(function*(){
+    const e1 = engine({ nbCores: 1 }, logger);
+    const block = { number: 35 };
+    const zeros = 2;
+    const highMark = 'A';
+    const pair = {
+      pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd',
+      sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'
+    };
+    const forcedTime = 1;
+    const medianTimeBlocks = 20;
+    const avgGenTime = 5 * 60;
+    const proof = yield e1.prove({
+        newPoW: {
+          block,
+          zeros,
+          highMark,
+          pair,
+          forcedTime,
+          conf: {
+            medianTimeBlocks,
+            avgGenTime
+          }
+        }
+      }
+    )
+    proof.should.deepEqual({
+      pow: {
+        block: {
+          number: 35,
+          time: 1,
+          inner_hash: '51937F1192447A96537D10968689F4F48859E2DD6F8F9E8DE1006C9697C6C940',
+          nonce: 212,
+          hash: '009A52E6E2E4EA7DE950A2DA673114FA55B070EBE350D75FF0C62C6AAE9A37E5',
+          signature: 'bkmLGX7LNVkuOUMc+/HT6fXJajQtR5uk87fetIntMbGRZjychzu0whl5+AOOGlf+ilp/ara5UK6ppxyPcJIJAg=='
+        },
+        testsCount: 211,
+        pow: '009A52E6E2E4EA7DE950A2DA673114FA55B070EBE350D75FF0C62C6AAE9A37E5'
+      }
+    });
+  }));
+
+  it('should be able to stop a proof', () => co(function*(){
+    const e1 = engine({ nbCores: 1 }, logger);
+    yield e1.forceInit()
+    const block = { number: 26 };
+    const zeros = 10; // Requires hundreds of thousands of tries probably
+    const highMark = 'A';
+    const pair = {
+      pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd',
+      sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'
+    };
+    const forcedTime = 1;
+    const medianTimeBlocks = 20;
+    const avgGenTime = 5 * 60;
+    const proofPromise = e1.prove({
+        newPoW: {
+          block,
+          zeros,
+          highMark,
+          pair,
+          forcedTime,
+          conf: {
+            medianTimeBlocks,
+            avgGenTime
+          }
+        }
+      }
+    )
+    yield new Promise((res) => setTimeout(res, 10))
+    yield e1.cancel()
+    // const proof = yield proofPromise;
+    // should.not.exist(proof);
+  }));
+});
diff --git a/test/fast/prover/pow-3-prover.js b/test/fast/prover/pow-3-prover.js
new file mode 100644
index 0000000000000000000000000000000000000000..2568abd77efa518fb175326560ccbe5be9b909a5
--- /dev/null
+++ b/test/fast/prover/pow-3-prover.js
@@ -0,0 +1,92 @@
+"use strict";
+
+const co = require('co')
+const should = require('should')
+const moment = require('moment')
+const winston = require('winston')
+const blockProver = require('../../../app/modules/prover/lib/blockProver');
+
+// Mute logger
+winston.remove(winston.transports.Console)
+
+describe('PoW block prover', () => {
+
+  let prover
+
+  before(() => {
+    prover = blockProver({
+      conf: {
+        nbCores: 1,
+        medianTimeBlocks: 20,
+        avgGenTime: 5 * 60,
+        pair: {
+          pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd',
+          sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'
+        }
+      },
+      push: () => {},
+      logger: winston
+    })
+  })
+
+  it('should be configurable', () => co(function*(){
+    const res1 = yield prover.changeCPU(0.2)
+    res1.should.deepEqual({ cpu: 0.2 })
+    const res2 = yield prover.changePoWPrefix('34')
+    res2.should.deepEqual({ prefix: '34' })
+  }));
+
+  it('should be able to make a proof', () => co(function*(){
+    const block = {
+      number: 35,
+      issuer: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'
+    }
+    const forcedTime = 1;
+    const proof = yield prover.prove(block, 24, forcedTime)
+    proof.should.containEql({
+      version: 10,
+      nonce: 34000000000010,
+      number: 35,
+      time: 1,
+      currency: '',
+      issuer: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd',
+      signature: 'iG9XEEIoGvCuFLRXqXIcGKFeK88K/A0J9MfKWAGvkRHtf6+VtMR/VDtPP67UzfnVdJb4QfMqrNsPMH2+7bTTAA==',
+      hash: '07573FEA1248562F47B1FA7DABDAF93C93B7328AA528F470B488249D5806F66D',
+      parameters: '',
+      previousHash: null,
+      previousIssuer: null,
+      inner_hash: 'A31455535488AE74B819FD920CA0BDFEFB6E753BDF1EF17E1661A144A0D6B3EB',
+      dividend: null,
+      identities: [],
+      joiners: [],
+      actives: [],
+      leavers: [],
+      revoked: [],
+      excluded: [],
+      certifications: [],
+      transactions: []
+    });
+  }));
+
+  it('should be able to stop a proof', () => co(function*(){
+    const block = {
+      number: 35,
+      issuer: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd'
+    }
+    const forcedTime = 1;
+    const proofPromise = prover.prove(block, 70, forcedTime)
+    yield new Promise((res) => setTimeout(res, 20))
+    yield prover.cancel()
+    let err = ''
+    try {
+      yield proofPromise
+    } catch (e) {
+      err = e
+    } finally {
+      if (!err) {
+        throw "Should have thrown!"
+      }
+      err.should.equal('Proof-of-work computation canceled because block received')
+    }
+  }));
+});
diff --git a/test/integration/http_api.js b/test/integration/http_api.js
index 86171f3f524ce26ee72145dbd0c9f3d99b6da5de..28730321422d3b47d1a5d1ce7612cc230b057da9 100644
--- a/test/integration/http_api.js
+++ b/test/integration/http_api.js
@@ -58,7 +58,7 @@ describe("HTTP API", function() {
 
   function makeBlockAndPost(theServer) {
     return function() {
-      return require('duniter-prover').duniter.methods.generateAndProveTheNext(theServer)
+      return require('../../app/modules/prover').duniter.methods.generateAndProveTheNext(theServer)
         .then(postBlock(theServer));
     };
   }
diff --git a/test/integration/identity-expiry.js b/test/integration/identity-expiry.js
index b19e084af91fa9838cd8393157b2aa464a74eedd..ad5ca6e28891c8ce09ca32d369e29295531d6fe4 100644
--- a/test/integration/identity-expiry.js
+++ b/test/integration/identity-expiry.js
@@ -5,7 +5,7 @@ const co        = require('co');
 const should    = require('should');
 const duniter   = require('../../index');
 const bma       = require('duniter-bma').duniter.methods.bma;
-const prover    = require('duniter-prover').duniter.methods;
+const prover    = require('../../app/modules/prover').duniter.methods;
 const user      = require('./tools/user');
 const constants = require('../../app/lib/constants');
 const rp        = require('request-promise');
diff --git a/test/integration/identity-kicking.js b/test/integration/identity-kicking.js
index 7846cc522d0cfc61622cf09573a0c239eaa64666..00b76b845be2a0c9a4871bddc9a7d2b54259eaf6 100644
--- a/test/integration/identity-kicking.js
+++ b/test/integration/identity-kicking.js
@@ -50,7 +50,7 @@ describe("Identities kicking", function() {
 
       const now = Math.round(new Date().getTime() / 1000);
       yield s1.initWithDAL().then(bma).then((bmapi) => bmapi.openConnections());
-      require('duniter-prover').duniter.methods.hookServer(s1);
+      require('../../app/modules/prover').duniter.methods.hookServer(s1);
       yield cat.createIdentity();
       yield tac.createIdentity();
       yield cat.cert(tac);
diff --git a/test/integration/identity-test.js b/test/integration/identity-test.js
index 03bcdd091cbc38f2bcd01966f927ffc8ff2d560c..aa38506a516c9122402cf935b31a5e2179eb8fa6 100644
--- a/test/integration/identity-test.js
+++ b/test/integration/identity-test.js
@@ -54,7 +54,7 @@ describe("Identities collision", function() {
 
     return co(function *() {
       yield s1.initWithDAL().then(bma).then((bmapi) => bmapi.openConnections());
-      require('duniter-prover').duniter.methods.hookServer(s1);
+      require('../../app/modules/prover').duniter.methods.hookServer(s1);
       yield cat.createIdentity();
       yield tac.createIdentity();
       yield toc.createIdentity();
diff --git a/test/integration/proof-of-work.js b/test/integration/proof-of-work.js
index 078c8c36601d44b060c57ed98f312bcd2124cf35..9488ed3e18190e0ecaf146a132b8c3bab6a494d4 100644
--- a/test/integration/proof-of-work.js
+++ b/test/integration/proof-of-work.js
@@ -6,7 +6,7 @@ const toolbox   = require('./tools/toolbox');
 const Block = require('../../app/lib/entity/block');
 const constants = require('../../app/lib/constants');
 const logger = require('../../app/lib/logger')();
-const blockProver = require('duniter-prover').duniter.methods.blockProver;
+const blockProver = require('../../app/modules/prover').duniter.methods.blockProver;
 
 /***
 conf.medianTimeBlocks
diff --git a/test/integration/start_generate_blocks.js b/test/integration/start_generate_blocks.js
index f1a12056ab68058fa708f1d7071cb09fb6e022db..3b471cde1aa885e8fcaf7c5930846a0b4f8196fd 100644
--- a/test/integration/start_generate_blocks.js
+++ b/test/integration/start_generate_blocks.js
@@ -73,7 +73,7 @@ describe("Generation", function() {
         yield server.bma.openConnections();
         require('../../app/modules/router').duniter.methods.routeToNetwork(server);
         yield server.PeeringService.generateSelfPeer(server.conf, 0);
-        const prover = require('duniter-prover').duniter.methods.prover(server);
+        const prover = require('../../app/modules/prover').duniter.methods.prover(server);
         server.startBlockComputation = () => prover.startService();
         server.stopBlockComputation = () => prover.stopService();
       }
diff --git a/test/integration/tools/commit.js b/test/integration/tools/commit.js
index edd9a3799d6b1d284a1bffbbdfdb2608264c88b7..046e59756ffaa8ba891967538f56fa9ea8465c35 100644
--- a/test/integration/tools/commit.js
+++ b/test/integration/tools/commit.js
@@ -13,8 +13,8 @@ module.exports = function makeBlockAndPost(theServer, extraProps) {
     }
     return co(function *() {
       if (!theServer._utProver) {
-        theServer._utProver = require('duniter-prover').duniter.methods.blockProver(theServer)
-        theServer._utGenerator = require('duniter-prover').duniter.methods.blockGenerator(theServer, theServer._utProver)
+        theServer._utProver = require('../../../app/modules/prover').duniter.methods.blockProver(theServer)
+        theServer._utGenerator = require('../../../app/modules/prover').duniter.methods.blockGenerator(theServer, theServer._utProver)
       }
       let proven = yield theServer._utGenerator.makeNextBlock(null, null, manualValues)
       const block = yield postBlock(theServer)(proven);
diff --git a/test/integration/tools/node.js b/test/integration/tools/node.js
index f430bd39d46e21e8f64fb187ae4d3f65f488f030..fd25c02f4c8faead7ed3d0b04194f93ed6d17b93 100644
--- a/test/integration/tools/node.js
+++ b/test/integration/tools/node.js
@@ -79,9 +79,9 @@ function Node (dbName, options) {
             block: function(callback){
               co(function *() {
                 try {
-                  const block2 = yield require('duniter-prover').duniter.methods.generateTheNextBlock(that.server, params);
+                  const block2 = yield require('../../../app/modules/prover').duniter.methods.generateTheNextBlock(that.server, params);
                   const trial2 = yield that.server.getBcContext().getIssuerPersonalizedDifficulty(that.server.keyPair.publicKey);
-                  const block = yield require('duniter-prover').duniter.methods.generateAndProveTheNext(that.server, block2, trial2, params);
+                  const block = yield require('../../../app/modules/prover').duniter.methods.generateAndProveTheNext(that.server, block2, trial2, params);
                   callback(null, block);
                 } catch (e) {
                   callback(e);
diff --git a/test/integration/tools/toolbox.js b/test/integration/tools/toolbox.js
index aadc5d3c894cdfd63532406396524d4124660cc4..e9fbc3b92603aa7efb3d32160cd9ebf6629fa292 100644
--- a/test/integration/tools/toolbox.js
+++ b/test/integration/tools/toolbox.js
@@ -256,7 +256,7 @@ module.exports = {
     });
 
     server.makeNext = (overrideProps) => co(function*() {
-      const block = yield require('duniter-prover').duniter.methods.generateAndProveTheNext(server, null, null, overrideProps || {});
+      const block = yield require('../../../app/modules/prover').duniter.methods.generateAndProveTheNext(server, null, null, overrideProps || {});
       return Block.statics.fromJSON(block);
     });
 
@@ -302,13 +302,13 @@ module.exports = {
       server.bma = bmaAPI;
       require('../../../app/modules/router').duniter.methods.routeToNetwork(server);
       // Extra: for /wot/requirements URL
-      require('duniter-prover').duniter.methods.hookServer(server);
+      require('../../../app/modules/prover').duniter.methods.hookServer(server);
     });
 
     let prover;
     server.startBlockComputation = () => {
       if (!prover) {
-        prover = require('duniter-prover').duniter.methods.prover(server);
+        prover = require('../../../app/modules/prover').duniter.methods.prover(server);
         server.permaProver = prover.permaProver;
         server.pipe(prover);
       }
diff --git a/yarn.lock b/yarn.lock
index 593d666377ba1b89a7a7cd902af7135d694ab18d..d3c1fba6d29693793fed936ef98a20b169279cbe 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -78,6 +78,10 @@ ansi-regex@^2.0.0:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
 
+ansi-regex@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
+
 ansi-styles@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
@@ -793,20 +797,6 @@ duniter-keypair@1.3.X, duniter-keypair@1.3.x:
     tweetnacl "0.14.5"
     tweetnacl-util "0.15.0"
 
-duniter-prover@1.4.x:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/duniter-prover/-/duniter-prover-1.4.0.tgz#dabaa4408b4957366e7d7fea8144f217da65e79a"
-  dependencies:
-    async "2.2.0"
-    co "4.6.0"
-    duniter-common "1.3.x"
-    duniter-crawler "1.3.x"
-    inquirer "3.0.6"
-    moment "2.18.1"
-    node-uuid "1.4.8"
-    querablep "0.1.0"
-    underscore "1.8.3"
-
 duniter-ui@1.3.x:
   version "1.3.11"
   resolved "https://registry.yarnpkg.com/duniter-ui/-/duniter-ui-1.3.11.tgz#de22d5bff5b8313e4a563b6fa994c746f1908c39"
@@ -1771,13 +1761,20 @@ js-yaml@3.0.1:
     argparse "~ 0.1.11"
     esprima "~ 1.0.2"
 
-js-yaml@3.8.2, js-yaml@3.x, js-yaml@^3.2.5, js-yaml@^3.5.1:
+js-yaml@3.8.2:
   version "3.8.2"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.2.tgz#02d3e2c0f6beab20248d412c352203827d786721"
   dependencies:
     argparse "^1.0.7"
     esprima "^3.1.1"
 
+js-yaml@3.x, js-yaml@^3.2.5, js-yaml@^3.5.1:
+  version "3.8.4"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.4.tgz#520b4564f86573ba96662af85a8cafa7b4b5a6f6"
+  dependencies:
+    argparse "^1.0.7"
+    esprima "^3.1.1"
+
 jsbn@~0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
@@ -2179,7 +2176,7 @@ node-pre-gyp@0.6.34, node-pre-gyp@^0.6.34, node-pre-gyp@~0.6.28:
     tar "^2.2.1"
     tar-pack "^3.4.0"
 
-node-uuid@1.4.8, node-uuid@~1.4.0:
+node-uuid@~1.4.0:
   version "1.4.8"
   resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.8.tgz#b040eb0923968afabf8d32fb1f17f1167fdab907"
 
@@ -2210,8 +2207,8 @@ normalize-path@^2.0.0:
     remove-trailing-separator "^1.0.1"
 
 npmlog@^4.0.1, npmlog@^4.0.2:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.0.tgz#dc59bee85f64f00ed424efb2af0783df25d1c0b5"
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
   dependencies:
     are-we-there-yet "~1.1.2"
     console-control-strings "~1.1.0"
@@ -2935,11 +2932,11 @@ string-width@^1.0.1, string-width@^1.0.2:
     strip-ansi "^3.0.0"
 
 string-width@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.0.0.tgz#635c5436cc72a6e0c387ceca278d4e2eec52687e"
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.0.tgz#030664561fc146c9423ec7d978fe2457437fe6d0"
   dependencies:
     is-fullwidth-code-point "^2.0.0"
-    strip-ansi "^3.0.0"
+    strip-ansi "^4.0.0"
 
 string_decoder@~0.10.x:
   version "0.10.31"
@@ -2967,6 +2964,12 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1:
   dependencies:
     ansi-regex "^2.0.0"
 
+strip-ansi@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
+  dependencies:
+    ansi-regex "^3.0.0"
+
 strip-bom@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
@@ -3116,11 +3119,7 @@ tough-cookie@>=0.12.0, tough-cookie@~2.3.0:
   dependencies:
     punycode "^1.4.1"
 
-traverse@>=0.2.4:
-  version "0.6.6"
-  resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137"
-
-"traverse@>=0.3.0 <0.4":
+traverse@>=0.2.4, "traverse@>=0.3.0 <0.4":
   version "0.3.9"
   resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9"