From 5f8c0f1abd8895afb8eafa65fb483eb686114a46 Mon Sep 17 00:00:00 2001
From: cgeek <cem.moreau@gmail.com>
Date: Thu, 20 Jul 2017 09:49:12 +0200
Subject: [PATCH] [fix] #1037 Migrate back duniter-keypair

---
 .eslintignore                            |   2 +
 app/lib/common/crypto/base58.ts          |   5 +
 app/lib/common/crypto/keyring.ts         |  87 +++++++++++++
 app/lib/common/crypto/nacl-util.ts       |  34 +++++
 app/lib/dto/ConfDTO.ts                   |   8 ++
 app/lib/indexer.ts                       |   8 +-
 app/lib/rules/global_rules.ts            |   4 +-
 app/lib/rules/local_rules.ts             |  10 +-
 app/modules/keypair/index.ts             | 155 +++++++++++++++++++++++
 app/modules/keypair/lib/scrypt.ts        |  36 ++++++
 app/modules/prover/lib/blockGenerator.ts |   6 +-
 app/modules/prover/lib/proof.ts          |   4 +-
 app/service/IdentityService.ts           |   6 +-
 app/service/PeeringService.ts            |   4 +-
 index.ts                                 |   6 +-
 package.json                             |   6 +-
 server.ts                                |   8 +-
 test/fast/common/crypto.js               |  81 ++++++++++++
 test/fast/common/randomKey.js            |  35 +++++
 test/fast/modules/keypair/crypto-test.js |  25 ++++
 test/fast/modules/keypair/module-test.js |  33 +++++
 test/integration/continuous-proof.js     |   1 -
 yarn.lock                                |  26 ++--
 23 files changed, 547 insertions(+), 43 deletions(-)
 create mode 100644 app/lib/common/crypto/base58.ts
 create mode 100644 app/lib/common/crypto/keyring.ts
 create mode 100644 app/lib/common/crypto/nacl-util.ts
 create mode 100644 app/modules/keypair/index.ts
 create mode 100644 app/modules/keypair/lib/scrypt.ts
 create mode 100644 test/fast/common/crypto.js
 create mode 100644 test/fast/common/randomKey.js
 create mode 100644 test/fast/modules/keypair/crypto-test.js
 create mode 100644 test/fast/modules/keypair/module-test.js

diff --git a/.eslintignore b/.eslintignore
index 6fc760c16..8604f057d 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -30,5 +30,7 @@ app/modules/check-config.js
 app/modules/config.js
 app/modules/prover/*.js
 app/modules/prover/lib/*.js
+app/modules/keypair/*.js
+app/modules/keypair/lib/*.js
 test/*.js
 test/**/*.js
\ No newline at end of file
diff --git a/app/lib/common/crypto/base58.ts b/app/lib/common/crypto/base58.ts
new file mode 100644
index 000000000..61e710cc3
--- /dev/null
+++ b/app/lib/common/crypto/base58.ts
@@ -0,0 +1,5 @@
+const bs58 = require('bs58')
+
+export const Base58encode = (bytes:any) => bs58.encode(bytes)
+
+export const Base58decode = (data:any) => new Uint8Array(bs58.decode(data))
diff --git a/app/lib/common/crypto/keyring.ts b/app/lib/common/crypto/keyring.ts
new file mode 100644
index 000000000..24cb7a1a1
--- /dev/null
+++ b/app/lib/common/crypto/keyring.ts
@@ -0,0 +1,87 @@
+import {Base58decode, Base58encode} from "./base58"
+import {decodeBase64, decodeUTF8, encodeBase64} from "./nacl-util"
+
+const nacl        = require('tweetnacl');
+const seedrandom  = require('seedrandom');
+const naclBinding = require('naclb');
+
+const crypto_sign_BYTES = 64;
+
+class Key {
+
+  constructor(readonly pub:string, readonly sec:string) {
+  }
+
+  /*****************************
+  *
+  *      GENERAL CRYPTO
+  *
+  *****************************/
+
+  get publicKey() {
+    return this.pub
+  }
+
+  get secretKey() {
+    return this.sec
+  }
+
+  private rawSec() {
+    return Base58decode(this.secretKey)
+  }
+
+  json() {
+    return {
+      pub: this.publicKey,
+      sec: this.secretKey
+    }
+  }
+
+  sign(msg:string) {
+    return Promise.resolve(this.signSync(msg))
+  }
+
+  signSync(msg:string) {
+    const m = decodeUTF8(msg);
+    const signedMsg = naclBinding.sign(m, this.rawSec());
+    const sig = new Uint8Array(crypto_sign_BYTES);
+    for (let i = 0; i < sig.length; i++) {
+      sig[i] = signedMsg[i];
+    }
+    return encodeBase64(sig)
+  };
+}
+
+export function randomKey() {
+  const byteseed = new Uint8Array(32)
+  for (let i = 0; i < 32; i++) {
+    byteseed[i] = Math.floor(seedrandom()() *  255) + 1
+  }
+  const keypair = nacl.sign.keyPair.fromSeed(byteseed)
+  return new Key(
+    Base58encode(keypair.publicKey),
+    Base58encode(keypair.secretKey)
+  )
+}
+
+export function KeyGen(pub:string, sec:string) {
+  return new Key(pub, sec)
+}
+
+/**
+ * Verify a signature against data & public key.
+ * Return true of false as callback argument.
+ */
+export function verify(rawMsg:string, rawSig:string, rawPub:string) {
+  const msg = decodeUTF8(rawMsg);
+  const sig = decodeBase64(rawSig);
+  const pub = Base58decode(rawPub);
+  const m = new Uint8Array(crypto_sign_BYTES + msg.length);
+  const sm = new Uint8Array(crypto_sign_BYTES + msg.length);
+  let i;
+  for (i = 0; i < crypto_sign_BYTES; i++) sm[i] = sig[i];
+  for (i = 0; i < msg.length; i++) sm[i+crypto_sign_BYTES] = msg[i];
+
+  // Call to verification lib...
+  return naclBinding.verify(m, sm, pub);
+}
diff --git a/app/lib/common/crypto/nacl-util.ts b/app/lib/common/crypto/nacl-util.ts
new file mode 100644
index 000000000..6ecb795f4
--- /dev/null
+++ b/app/lib/common/crypto/nacl-util.ts
@@ -0,0 +1,34 @@
+declare function escape(s:string): string;
+declare function unescape(s:string): string;
+
+export const decodeUTF8 = function(s:string) {
+  let i, d = unescape(encodeURIComponent(s)), b = new Uint8Array(d.length);
+  for (i = 0; i < d.length; i++) b[i] = d.charCodeAt(i);
+  return b;
+}
+
+export const encodeUTF8 = function(arr:any[]) {
+  let i, s = [];
+  for (i = 0; i < arr.length; i++) s.push(String.fromCharCode(arr[i]));
+  return decodeURIComponent(escape(s.join('')))
+}
+
+export const encodeBase64 = function(arr:Uint8Array) {
+  if (typeof btoa === 'undefined' || !window) {
+    return (new Buffer(arr)).toString('base64');
+  } else {
+    let i, s = [], len = arr.length;
+    for (i = 0; i < len; i++) s.push(String.fromCharCode(arr[i]));
+    return btoa(s.join(''));
+  }
+}
+
+export const decodeBase64 = function(s:string) {
+  if (typeof atob === 'undefined' || !window) {
+    return new Uint8Array(Array.prototype.slice.call(new Buffer(s, 'base64'), 0));
+  } else {
+    let i, d = atob(s), b = new Uint8Array(d.length);
+    for (i = 0; i < d.length; i++) b[i] = d.charCodeAt(i);
+    return b;
+  }
+}
\ No newline at end of file
diff --git a/app/lib/dto/ConfDTO.ts b/app/lib/dto/ConfDTO.ts
index 37ee8e5c8..0144f692b 100644
--- a/app/lib/dto/ConfDTO.ts
+++ b/app/lib/dto/ConfDTO.ts
@@ -29,6 +29,14 @@ export interface CurrencyConfDTO {
   udReevalTime0: number
   dtReeval: number
 }
+
+export interface KeypairConfDTO {
+  pair: Keypair
+  oldPair: Keypair
+  salt: string
+  passwd: string
+}
+
 export class ConfDTO implements CurrencyConfDTO {
 
   constructor(
diff --git a/app/lib/indexer.ts b/app/lib/indexer.ts
index 71446f302..e0d3d52dc 100644
--- a/app/lib/indexer.ts
+++ b/app/lib/indexer.ts
@@ -7,6 +7,7 @@ import {CertificationDTO} from "./dto/CertificationDTO"
 import {TransactionDTO} from "./dto/TransactionDTO"
 import {DBHead} from "./db/DBHead"
 import {LOCAL_RULES_HELPERS} from "./rules/local_rules"
+import {verify} from "./common/crypto/keyring"
 
 const co              = require('co');
 const _               = require('underscore');
@@ -15,7 +16,6 @@ const common          = require('duniter-common');
 const constants       = common.constants
 const rawer           = common.rawer
 const unlock          = common.txunlock
-const keyring         = common.keyring
 const Block           = common.document.Block
 const Membership      = common.document.Membership
 
@@ -1897,7 +1897,7 @@ async function getNodeIDfromPubkey(nodesCache: any, pubkey: string, dal: any) {
 
 async function sigCheckRevoke(entry: MindexEntry, dal: any, currency: string) {
   try {
-    let pubkey = entry.pub, sig = entry.revocation;
+    let pubkey = entry.pub, sig = entry.revocation || "";
     let idty = await dal.getWrittenIdtyByPubkey(pubkey);
     if (!idty) {
       throw Error("A pubkey who was never a member cannot be revoked");
@@ -1913,7 +1913,7 @@ async function sigCheckRevoke(entry: MindexEntry, dal: any, currency: string) {
       sig: idty.sig,
       revocation: ''
     });
-    let sigOK = keyring.verify(rawRevocation, sig, pubkey);
+    let sigOK = verify(rawRevocation, sig, pubkey);
     if (!sigOK) {
       throw Error("Revocation signature must match");
     }
@@ -1962,7 +1962,7 @@ async function checkCertificationIsValid (block: BlockDTO, cert: CindexEntry, fi
           buid: buid,
           sig: ''
         }));
-        const verified = keyring.verify(raw, cert.sig, cert.issuer);
+        const verified = verify(raw, cert.sig, cert.issuer);
         if (!verified) {
           throw constants.ERRORS.WRONG_SIGNATURE_FOR_CERT
         }
diff --git a/app/lib/rules/global_rules.ts b/app/lib/rules/global_rules.ts
index c4b3d39ed..4a6acb1b5 100644
--- a/app/lib/rules/global_rules.ts
+++ b/app/lib/rules/global_rules.ts
@@ -5,13 +5,13 @@ import {DBBlock} from "../db/DBBlock"
 import {TransactionDTO} from "../dto/TransactionDTO"
 import * as local_rules from "./local_rules"
 import {BlockDTO} from "../dto/BlockDTO"
+import {verify} from "../common/crypto/keyring"
 
 const _              = require('underscore');
 const common         = require('duniter-common');
 const indexer        = require('../indexer').Indexer
 
 const constants      = common.constants
-const keyring        = common.keyring
 const rawer          = common.rawer
 const Identity       = common.document.Identity
 const Transaction    = common.document.Transaction
@@ -276,7 +276,7 @@ async function checkCertificationIsValid (block:{ number:number, currency:string
         buid: buid,
         sig: ''
       }));
-      const verified = keyring.verify(raw, cert.sig, cert.from);
+      const verified = verify(raw, cert.sig, cert.from);
       if (!verified) {
         throw constants.ERRORS.WRONG_SIGNATURE_FOR_CERT
       }
diff --git a/app/lib/rules/local_rules.ts b/app/lib/rules/local_rules.ts
index 0494fab79..1bf8139c8 100644
--- a/app/lib/rules/local_rules.ts
+++ b/app/lib/rules/local_rules.ts
@@ -4,13 +4,13 @@ import {ConfDTO} from "../dto/ConfDTO"
 import {CindexEntry, IndexEntry, Indexer, MindexEntry, SindexEntry} from "../indexer"
 import {BaseDTO, TransactionDTO} from "../dto/TransactionDTO"
 import {DBBlock} from "../db/DBBlock"
+import {verify} from "../common/crypto/keyring"
 
 const _          = require('underscore');
 const common     = require('duniter-common');
 
 const constants       = common.constants
 const hashf           = common.hashf
-const keyring         = common.keyring
 const Block           = common.document.Block
 const Identity        = common.document.Identity
 const Membership      = common.document.Membership
@@ -73,7 +73,7 @@ export const LOCAL_RULES_FUNCTIONS = {
   },
 
   checkBlockSignature: async (block:BlockDTO) => {
-    if (!keyring.verify(block.getSignedPart(), block.signature, block.issuer))
+    if (!verify(block.getSignedPart(), block.signature, block.issuer))
       throw Error('Block\'s signature must match');
     return true;
   },
@@ -94,7 +94,7 @@ export const LOCAL_RULES_FUNCTIONS = {
     while (!wrongSig && i < block.identities.length) {
       const idty = Identity.fromInline(block.identities[i]);
       idty.currency = block.currency;
-      wrongSig = !keyring.verify(idty.rawWithoutSig(), idty.sig, idty.pubkey);
+      wrongSig = !verify(idty.rawWithoutSig(), idty.sig, idty.pubkey);
       if (wrongSig) {
         throw Error('Identity\'s signature must match');
       }
@@ -382,7 +382,7 @@ export const LOCAL_RULES_FUNCTIONS = {
 }
 
 function checkSingleMembershipSignature(ms:any) {
-  return keyring.verify(ms.getRaw(), ms.signature, ms.issuer);
+  return verify(ms.getRaw(), ms.signature, ms.issuer);
 }
 
 function getSigResult(tx:any) {
@@ -401,7 +401,7 @@ function getSigResult(tx:any) {
   while (signaturesMatching && i < tx.signatures.length) {
     const sig = tx.signatures[i];
     const pub = tx.issuers[i];
-    signaturesMatching = keyring.verify(raw, sig, pub);
+    signaturesMatching = verify(raw, sig, pub);
     sigResult.sigs[pub] = {
       matching: signaturesMatching,
       index: i
diff --git a/app/modules/keypair/index.ts b/app/modules/keypair/index.ts
new file mode 100644
index 000000000..0b8f71816
--- /dev/null
+++ b/app/modules/keypair/index.ts
@@ -0,0 +1,155 @@
+import {randomKey} from "../../lib/common/crypto/keyring"
+import {ConfDTO, KeypairConfDTO} from "../../lib/dto/ConfDTO"
+import {Scrypt} from "./lib/scrypt"
+
+const inquirer = require('inquirer');
+const fs = require('fs');
+const yaml = require('js-yaml');
+
+export const KeypairDependency = {
+
+  duniter: {
+
+    methods: {
+      scrypt: Scrypt
+    },
+
+    cliOptions: [
+      { value: '--salt <salt>', desc: 'Salt to generate the keypair' },
+      { value: '--passwd <password>', desc: 'Password to generate the keypair' },
+      { value: '--keyN <N>', desc: 'Scrypt `N` parameter. Defaults to 4096.', parser: parseInt },
+      { value: '--keyr <r>', desc: 'Scrypt `N` parameter. Defaults to 16.', parser: parseInt },
+      { value: '--keyp <p>', desc: 'Scrypt `N` parameter. Defaults to 1.', parser: parseInt },
+      { value: '--keyprompt', desc: 'Force to use the keypair given by user prompt.' },
+      { value: '--keyfile <filepath>', desc: 'Force to use the keypair of the given YAML file. File must contain `pub:` and `sec:` fields.' }
+    ],
+
+    wizard: {
+
+      'key': promptKey
+
+    },
+
+    onReset: {
+      config: (conf:ConfDTO, program:any, logger:any, confDAL:any) => confDAL.coreFS.remove('keyring.yml')
+    },
+
+    config: {
+
+      /*****
+       * Tries to load a specific parameter `conf.pair`
+       */
+      onLoading: async (conf:KeypairConfDTO, program:any, logger:any, confDAL:any) => {
+
+        if ((program.keyN || program.keyr || program.keyp) && !(program.salt && program.passwd)) {
+          throw Error('Missing --salt and --passwd options along with --keyN|keyr|keyp option');
+        }
+
+        // If we have salt and password, convert it to keypair
+        if (program.salt || program.passwd) {
+          const salt = program.salt || '';
+          const key  = program.passwd || '';
+          conf.pair = await Scrypt(salt, key);
+        }
+
+        // If no keypair has been loaded, try the default .yml file
+        if (!conf.pair || !conf.pair.pub || !conf.pair.sec) {
+          const ymlContent = await confDAL.coreFS.read('keyring.yml')
+          conf.pair = yaml.safeLoad(ymlContent);
+        }
+
+        // If no keypair has been loaded or derived from salt/key, generate a random one
+        if (!conf.pair || !conf.pair.pub || !conf.pair.sec) {
+          conf.pair = randomKey().json()
+        }
+
+        // With the --keyprompt option, temporarily use a keypair given from CLI prompt (it won't be stored)
+        if (program.keyprompt) {
+          // Backup of the current pair
+          conf.oldPair = {
+            pub: conf.pair.pub,
+            sec: conf.pair.sec
+          };
+          // Ask the for the session key
+          await promptKey(conf, program);
+        }
+
+        // With the --keyfile option, temporarily use a keypair given from file system (content won't be stored)
+        if (program.keyfile) {
+          // Backup of the current pair
+          conf.oldPair = {
+            pub: conf.pair.pub,
+            sec: conf.pair.sec
+          };
+          // Load file content
+          const doc = yaml.safeLoad(fs.readFileSync(program.keyfile, 'utf8'));
+          if (!doc || !doc.pub || !doc.sec) {
+            throw 'Could not load full keyring from file';
+          }
+          conf.pair = {
+            pub: doc.pub,
+            sec: doc.sec
+          }
+        }
+
+      },
+
+      beforeSave: async (conf:KeypairConfDTO, program:any, logger:any, confDAL:any) => {
+
+        if (program.keyprompt || program.keyfile) {
+          // Don't store the given key, but only the default/saved one
+          conf.pair = {
+            pub: conf.oldPair.pub,
+            sec: conf.oldPair.sec
+          };
+        }
+        delete conf.oldPair;
+
+        // We save the key in a separate file
+        const keyring = 'pub: "' + conf.pair.pub + '"\n' +
+          'sec: "' + conf.pair.sec + '"'
+        await confDAL.coreFS.write('keyring.yml', keyring)
+
+        // We never want to store salt, password or keypair in the conf.json file
+        delete conf.salt;
+        delete conf.passwd;
+        delete conf.pair;
+      }
+    }
+  }
+};
+
+async function promptKey (conf:KeypairConfDTO, program:any) {
+
+  const changeKeypair = !conf.pair || !conf.pair.pub || !conf.pair.sec;
+
+  const answersWantToChange = await inquirer.prompt([{
+    type: "confirm",
+    name: "change",
+    message: "Modify you keypair?",
+    default: changeKeypair
+  }]);
+
+  if (answersWantToChange.change) {
+    const obfuscatedSalt = (program.salt || "").replace(/./g, '*');
+    const answersSalt = await inquirer.prompt([{
+      type: "password",
+      name: "salt",
+      message: "Key's salt",
+      default: obfuscatedSalt || undefined
+    }]);
+    const obfuscatedPasswd = (program.passwd || "").replace(/./g, '*');
+    const answersPasswd = await inquirer.prompt([{
+      type: "password",
+      name: "passwd",
+      message: "Key\'s password",
+      default: obfuscatedPasswd || undefined
+    }]);
+
+    const keepOldSalt = obfuscatedSalt.length > 0 && obfuscatedSalt == answersSalt.salt;
+    const keepOldPasswd = obfuscatedPasswd.length > 0 && obfuscatedPasswd == answersPasswd.passwd;
+    const salt   = keepOldSalt ? program.salt : answersSalt.salt;
+    const passwd = keepOldPasswd ? program.passwd : answersPasswd.passwd;
+    conf.pair = await Scrypt(salt, passwd)
+  }
+}
diff --git a/app/modules/keypair/lib/scrypt.ts b/app/modules/keypair/lib/scrypt.ts
new file mode 100644
index 000000000..2e292eaad
--- /dev/null
+++ b/app/modules/keypair/lib/scrypt.ts
@@ -0,0 +1,36 @@
+"use strict";
+import {Base58encode} from "../../../lib/common/crypto/base58"
+import {decodeBase64} from "../../../lib/common/crypto/nacl-util"
+
+const nacl     = require('tweetnacl');
+const scrypt   = require('scryptb');
+
+const SEED_LENGTH = 32; // Length of the key
+
+/**
+ * Generates a new keypair object from salt + password strings.
+ * @param salt
+ * @param key
+ * @param N Scrypt parameter N. Defaults to 4096.
+ * @param r Scrypt parameter r. Defaults to 16.
+ * @param p Scrypt parameter p. Defaults to 1.
+ * @return keyPair An object containing the public and private keys, base58 encoded.
+ */
+export const Scrypt = async (salt:string, key:string, N = 4096, r = 16, p = 1) => {
+  const keyBytes = await getScryptKey(key, salt, N, r, p)
+  const pair = nacl.sign.keyPair.fromSeed(keyBytes);
+  return {
+    pub: Base58encode(new Buffer(pair.publicKey, 'hex')),
+    sec: Base58encode(new Buffer(pair.secretKey, 'hex'))
+  };
+}
+
+const getScryptKey = async (key:string, salt:string, N:number, r:number, p:number) => {
+  const res:any = await new Promise((resolve, reject) => {
+    scrypt.hash(key, { N, r, p }, SEED_LENGTH, salt, (err:any, res:Buffer) => {
+      if (err) return reject(err)
+      resolve(res)
+    })
+  })
+  return decodeBase64(res.toString("base64"))
+}
diff --git a/app/modules/prover/lib/blockGenerator.ts b/app/modules/prover/lib/blockGenerator.ts
index e0f1f44ac..d7a3b4032 100644
--- a/app/modules/prover/lib/blockGenerator.ts
+++ b/app/modules/prover/lib/blockGenerator.ts
@@ -7,13 +7,13 @@ import {LOCAL_RULES_HELPERS} from "../../../lib/rules/local_rules"
 import {Indexer} from "../../../lib/indexer"
 import {FileDAL} from "../../../lib/dal/fileDAL"
 import {DBBlock} from "../../../lib/db/DBBlock"
+import {verify} from "../../../lib/common/crypto/keyring"
 
 const _               = require('underscore');
 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;
@@ -352,7 +352,7 @@ export class BlockGenerator {
     const idty = Identity.fromJSON(identity);
     idty.currency = this.conf.currency;
     const createIdentity = idty.rawWithoutSig();
-    const verified = keyring.verify(createIdentity, idty.sig, idty.pubkey);
+    const verified = verify(createIdentity, idty.sig, idty.pubkey);
     if (!verified) {
       throw constants.ERRORS.IDENTITY_WRONGLY_SIGNED;
     }
@@ -693,7 +693,7 @@ class NextBlockGenerator implements BlockGeneratorInterface {
           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)) {
+          if (verify(rawCert, certSig, cert.from)) {
             cert.sig = certSig;
             let exists = false;
             if (current) {
diff --git a/app/modules/prover/lib/proof.ts b/app/modules/prover/lib/proof.ts
index 3bd033ad5..ccb196ca4 100644
--- a/app/modules/prover/lib/proof.ts
+++ b/app/modules/prover/lib/proof.ts
@@ -3,11 +3,11 @@ import {hashf} from "../../../lib/common"
 import {DBBlock} from "../../../lib/db/DBBlock"
 import {ConfDTO} from "../../../lib/dto/ConfDTO"
 import {Constants} from "./constants"
+import {KeyGen} from "../../../lib/common/crypto/keyring"
 
 const moment = require('moment');
 const dos2unix = require('duniter-common').dos2unix;
 const querablep = require('querablep');
-const keyring = require('duniter-common').keyring;
 const rawer = require('duniter-common').rawer;
 
 const PAUSES_PER_TURN = 5;
@@ -94,7 +94,7 @@ function beginNewProofOfWork(stuff:any) {
     }
     else {
       lastSecret = pair.sec;
-      sigFunc = keyring.Key(pair.pub, pair.sec).signSync;
+      sigFunc = (msg:string) => KeyGen(pair.pub, pair.sec).signSync(msg)
     }
     signatureFunc = sigFunc;
     let pow = "", sig = "", raw = "";
diff --git a/app/service/IdentityService.ts b/app/service/IdentityService.ts
index 2290d3e8c..1bf368b01 100644
--- a/app/service/IdentityService.ts
+++ b/app/service/IdentityService.ts
@@ -8,9 +8,9 @@ import {RevocationDTO} from "../lib/dto/RevocationDTO"
 import {BasicIdentity, IdentityDTO} from "../lib/dto/IdentityDTO"
 import {CertificationDTO} from "../lib/dto/CertificationDTO"
 import {DBCert} from "../lib/dal/sqliteDAL/CertDAL"
+import {verify} from "../lib/common/crypto/keyring"
 
 "use strict";
-const keyring          = require('duniter-common').keyring;
 const constants       = require('../lib/constants');
 
 const BY_ABSORPTION = true;
@@ -80,7 +80,7 @@ export class IdentityService {
     return GlobalFifoPromise.pushFIFO(async () => {
       this.logger.info('⬇ IDTY %s %s', idty.pubkey, idty.uid);
       // Check signature's validity
-      let verified = keyring.verify(createIdentity, idty.sig, idty.pubkey);
+      let verified = verify(createIdentity, idty.sig, idty.pubkey);
       if (!verified) {
         throw constants.ERRORS.SIGNATURE_DOES_NOT_MATCH;
       }
@@ -210,7 +210,7 @@ export class IdentityService {
     return GlobalFifoPromise.pushFIFO(async () => {
       try {
         this.logger.info('⬇ REVOCATION %s %s', revoc.pubkey, revoc.idty_uid);
-        let verified = keyring.verify(raw, revoc.revocation, revoc.pubkey);
+        let verified = verify(raw, revoc.revocation, revoc.pubkey);
         if (!verified) {
           throw 'Wrong signature for revocation';
         }
diff --git a/app/service/PeeringService.ts b/app/service/PeeringService.ts
index 85fd86039..32d5851f0 100644
--- a/app/service/PeeringService.ts
+++ b/app/service/PeeringService.ts
@@ -5,12 +5,12 @@ import {DBPeer} from "../lib/dal/sqliteDAL/PeerDAL"
 import {DBBlock} from "../lib/db/DBBlock"
 import {Multicaster} from "../lib/streams/multicaster"
 import {PeerDTO} from "../lib/dto/PeerDTO"
+import {verify} from "../lib/common/crypto/keyring"
 
 const util           = require('util');
 const _              = require('underscore');
 const events         = require('events');
 const rp             = require('request-promise');
-const keyring        = require('duniter-common').keyring;
 const logger         = require('../lib/logger').NewLogger('peering');
 const dos2unix       = require('duniter-common').dos2unix;
 const hashf          = require('duniter-common').hashf;
@@ -64,7 +64,7 @@ export class PeeringService {
     const raw = rawer.getPeerWithoutSignature(p);
     const sig = p.signature;
     const pub = p.pubkey;
-    const signaturesMatching = keyring.verify(raw, sig, pub);
+    const signaturesMatching = verify(raw, sig, pub);
     return !!signaturesMatching;
   };
 
diff --git a/index.ts b/index.ts
index 8ac403be5..8946c887a 100644
--- a/index.ts
+++ b/index.ts
@@ -2,6 +2,8 @@ import {ExecuteCommand} from "./app/cli"
 import * as stream from "stream"
 import {Server} from "./server"
 import {ConfDTO} from "./app/lib/dto/ConfDTO"
+import {ProverDependency} from "./app/modules/prover/index"
+import {KeypairDependency} from "./app/modules/keypair/index"
 
 const path = require('path');
 const _ = require('underscore');
@@ -20,7 +22,6 @@ const daemonDependency    = require('./app/modules/daemon');
 const pSignalDependency   = require('./app/modules/peersignal');
 const routerDependency    = require('./app/modules/router');
 const pluginDependency    = require('./app/modules/plugin');
-const proverDependency    = require('./app/modules/prover').ProverDependency;
 
 class Stacks {
 
@@ -98,7 +99,8 @@ const DEFAULT_DEPENDENCIES = MINIMAL_DEPENDENCIES.concat([
   { name: 'duniter-psignal',   required: pSignalDependency },
   { name: 'duniter-router',    required: routerDependency },
   { name: 'duniter-plugin',    required: pluginDependency },
-  { name: 'duniter-prover',    required: proverDependency }
+  { name: 'duniter-prover',    required: ProverDependency },
+  { name: 'duniter-keypair',   required: KeypairDependency }
 ]);
 
 const PRODUCTION_DEPENDENCIES = DEFAULT_DEPENDENCIES.concat([
diff --git a/package.json b/package.json
index 837b1ef45..da61ed01f 100644
--- a/package.json
+++ b/package.json
@@ -49,6 +49,7 @@
     "archiver": "1.3.0",
     "async": "2.2.0",
     "bindings": "1.2.1",
+    "bs58": "^4.0.1",
     "co": "4.6.0",
     "colors": "1.1.2",
     "commander": "2.9.0",
@@ -59,17 +60,20 @@
     "inquirer": "3.0.6",
     "merkle": "0.5.1",
     "moment": "2.18.1",
+    "naclb": "1.3.9",
     "node-pre-gyp": "0.6.34",
     "optimist": "0.6.1",
     "q-io": "1.13.2",
     "querablep": "^0.1.0",
     "request": "2.81.0",
     "request-promise": "4.2.0",
+    "seedrandom": "^2.4.3",
     "sha1": "1.1.1",
     "spawn-sync": "^1.0.15",
     "sqlite3": "3.1.4",
     "superagent": "3.5.2",
     "tail": "^1.2.1",
+    "tweetnacl": "0.14.3",
     "underscore": "1.8.3",
     "unzip": "0.1.11",
     "unzip2": "0.2.5",
@@ -83,7 +87,6 @@
     "coveralls": "2.11.4",
     "duniter-bma": "1.3.x",
     "duniter-crawler": "1.3.x",
-    "duniter-keypair": "1.3.X",
     "duniter-ui": "1.3.x",
     "eslint": "3.13.1",
     "eslint-plugin-mocha": "4.8.0",
@@ -100,7 +103,6 @@
   "peerDependencies": {
     "duniter-bma": "1.3.x",
     "duniter-crawler": "1.3.x",
-    "duniter-keypair": "1.3.X",
     "duniter-ui": "1.3.x"
   },
   "bin": {
diff --git a/server.ts b/server.ts
index dd3150588..1aa0c167c 100644
--- a/server.ts
+++ b/server.ts
@@ -8,6 +8,7 @@ import {FileDAL} from "./app/lib/dal/fileDAL"
 import {DuniterBlockchain} from "./app/lib/blockchain/DuniterBlockchain"
 import {SQLBlockchain} from "./app/lib/blockchain/SqlBlockchain"
 import * as stream from "stream"
+import {KeyGen, randomKey} from "./app/lib/common/crypto/keyring"
 
 interface HookableServer {
   getMainEndpoint: (...args:any[]) => Promise<any>
@@ -28,7 +29,6 @@ const daemonize   = require("daemonize2")
 const parsers     = require('duniter-common').parsers;
 const constants   = require('./app/lib/constants');
 const jsonpckg    = require('./package.json');
-const keyring      = require('duniter-common').keyring;
 const directory   = require('./app/lib/system/directory');
 const rawer       = require('duniter-common').rawer;
 const logger      = require('./app/lib/logger').NewLogger('server');
@@ -150,11 +150,11 @@ export class Server extends stream.Duplex implements HookableServer {
     // Default keypair
     if (!this.conf.pair || !this.conf.pair.pub || !this.conf.pair.sec) {
       // Create a random key
-      this.conf.pair = keyring.randomKey().json()
+      this.conf.pair = randomKey().json()
     }
     // Extract key pair
-    this.keyPair = keyring.Key(this.conf.pair.pub, this.conf.pair.sec);
-    this.sign = this.keyPair.sign;
+    this.keyPair = KeyGen(this.conf.pair.pub, this.conf.pair.sec);
+    this.sign = (msg:string) => this.keyPair.sign(msg)
     // Blockchain object
     this.blockchain = new DuniterBlockchain(new SQLBlockchain(this.dal), this.dal);
     // Update services
diff --git a/test/fast/common/crypto.js b/test/fast/common/crypto.js
new file mode 100644
index 000000000..477a46f42
--- /dev/null
+++ b/test/fast/common/crypto.js
@@ -0,0 +1,81 @@
+"use strict";
+const should = require('should');
+const co  = require('co');
+const nacl   = require('tweetnacl');
+const base58 = require('../../../app/lib/common/crypto/base58')
+const naclUtil = require('../../../app/lib/common/crypto/nacl-util')
+const keyring      = require('../../../app/lib/common/crypto/keyring')
+
+const Base58decode = base58.Base58decode
+const Base58encode = base58.Base58encode
+
+const enc = naclUtil.encodeBase64
+const dec = naclUtil.decodeBase64
+
+let pub, sec, rawPub, rawSec;
+
+describe('ed25519 tests:', function(){
+
+  before(() => co(function*() {
+    // Generate the keypair
+    const keyPair = keyring.KeyGen('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP');
+    pub = Base58decode(keyPair.publicKey);
+    sec = Base58decode(keyPair.secretKey);
+    rawPub = Base58encode(pub);
+    rawSec = Base58encode(sec);
+  }));
+
+  //it('good signature from existing secret key should be verified', function(done){
+  //  const keys = nacl.sign.scryptKeyPair.fromSecretKey(dec("TM0Imyj/ltqdtsNG7BFOD1uKMZ81q6Yk2oz27U+4pvs9QBfD6EOJWpK3CqdNG368nJgszy7ElozAzVXxKvRmDA=="));
+  //  const msg = "cg==";
+  //  const goodSig = dec("52Hh9omo9rxklulAE7gvVeYvAq0GgXYoZE2NB/gzehpCYIT04bMcGIs5bhYLaH93oib34jsVMWs9Udadr1B+AQ==");
+  //  const sig = crypto.signSync(msg, keys.secretKey);
+  //  sig.should.equal(enc(goodSig));
+  //  crypto.verify(msg, sig, enc(keys.publicKey)).should.be.true;
+  //  done();
+  //});
+
+  it('good signature from generated key should be verified', function(done){
+    const msg = "Some message to be signed";
+    const sig = keyring.KeyGen(rawPub, rawSec).signSync(msg);
+    const verified = keyring.verify(msg, sig, rawPub);
+    verified.should.equal(true);
+    done();
+  });
+
+  it('wrong signature from generated key should NOT be verified', function(done){
+    const msg = "Some message to be signed";
+    const cor = dec(enc(msg) + 'delta');
+    const sig = keyring.KeyGen(rawPub, rawSec).signSync(msg);
+    const verified = keyring.verify(cor, sig, rawPub);
+    verified.should.equal(false);
+    done();
+  });
+
+  it('good signature on a Peer document with just BMA should be verified', function(done){
+    const msg = "Version: 10\n" +
+      "Type: Peer\n" +
+      "Currency: g1\n" +
+      "PublicKey: 3AF7bhGQRt6ymcBZgZTBMoDsEtSwruSarjNG8kDnaueX\n" +
+      "Block: 33291-0000088375C232A4DDAE171BB3D3C51347CB6DC8B7AA8BE4CD4DAEEADF26FEB8\n" +
+      "Endpoints:\n" +
+      "BASIC_MERKLED_API g1.duniter.org 10901\n"
+    const verified = keyring.verify(msg, "u8t1IoWrB/C7T+2rS0rKYJfjPG4FN/HkKGFiUO5tILIzjFDvxxQiVC+0o/Vaz805SMmqJvXqornI71U7//+wCg==", "3AF7bhGQRt6ymcBZgZTBMoDsEtSwruSarjNG8kDnaueX");
+    verified.should.equal(true);
+    done();
+  });
+
+  it('good signature on a Peer document with just BMA + BMAS should be verified', function(done){
+    const msg = "Version: 10\n" +
+      "Type: Peer\n" +
+      "Currency: g1\n" +
+      "PublicKey: Com8rJukCozHZyFao6AheSsfDQdPApxQRnz7QYFf64mm\n" +
+      "Block: 33291-0000088375C232A4DDAE171BB3D3C51347CB6DC8B7AA8BE4CD4DAEEADF26FEB8\n" +
+      "Endpoints:\n" +
+      "BASIC_MERKLED_API g1.duniter.tednet.fr 37.187.0.204 8999\n" +
+      "BMAS g1.duniter.tednet.fr 9000\n"
+    const verified = keyring.verify(msg, "ImvQDdpGv2M6CxSnBuseM/azJhBUGzWVgQhIvb5L2oGLm2GyLk/Sbi5wkb4IjbjbQfdRPdlcx5zxaHhvZCiWAA==", "Com8rJukCozHZyFao6AheSsfDQdPApxQRnz7QYFf64mm");
+    verified.should.equal(true);
+    done();
+  });
+});
diff --git a/test/fast/common/randomKey.js b/test/fast/common/randomKey.js
new file mode 100644
index 000000000..5c924fe65
--- /dev/null
+++ b/test/fast/common/randomKey.js
@@ -0,0 +1,35 @@
+"use strict";
+const co = require('co')
+const should = require('should');
+const keyring  = require('../../../app/lib/common/crypto/keyring')
+const naclUtil = require('../../../app/lib/common/crypto/nacl-util')
+
+const enc = naclUtil.encodeBase64
+const dec = naclUtil.decodeBase64
+
+let key;
+
+describe('Random keypair', function(){
+
+  before(() => co(function*() {
+    // Generate the keypair
+    key = keyring.randomKey()
+  }));
+
+  it('good signature from generated key should be verified', function(done){
+    const msg = "Some message to be signed";
+    const sig = keyring.KeyGen(key.publicKey, key.secretKey).signSync(msg);
+    const verified = keyring.verify(msg, sig, key.publicKey);
+    verified.should.equal(true);
+    done();
+  });
+
+  it('wrong signature from generated key should NOT be verified', function(done){
+    const msg = "Some message to be signed";
+    const cor = dec(enc(msg) + 'delta');
+    const sig = keyring.KeyGen(key.publicKey, key.secretKey).signSync(msg);
+    const verified = keyring.verify(cor, sig, key.publicKey);
+    verified.should.equal(false);
+    done();
+  });
+});
diff --git a/test/fast/modules/keypair/crypto-test.js b/test/fast/modules/keypair/crypto-test.js
new file mode 100644
index 000000000..f9a496564
--- /dev/null
+++ b/test/fast/modules/keypair/crypto-test.js
@@ -0,0 +1,25 @@
+"use strict";
+const should = require('should');
+const co  = require('co');
+const scrypt = require('../../../../app/modules/keypair/lib/scrypt').Scrypt
+
+describe('Scrypt salt // key', () => {
+
+  it('abc // abc', () => co(function*() {
+    const pair = yield scrypt('abc', 'abc');
+    pair.should.have.property('pub').equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd');
+    pair.should.have.property('sec').equal('51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP');
+  }));
+
+  it('abc // def', () => co(function*() {
+    const pair = yield scrypt('abc', 'def');
+    pair.should.have.property('pub').equal('G2CBgZBPLe6FSFUgpx2Jf1Aqsgta6iib3vmDRA1yLiqU');
+    pair.should.have.property('sec').equal('58LDg8QLmF5pv6Dn9h7X4yFKfMTdP8fdAiWVcyDoTRJu454fwRihCLULH4MW37zncsg4ruoTGJPZneWk22QmG1w4');
+  }));
+
+  it('azerty // def', () => co(function*() {
+    const pair = yield scrypt('azerty', 'def');
+    pair.should.have.property('pub').equal('3dbw4NYVEm5mwTH6bFrqBhan1k39qNHubkQWdrw2C5AD');
+    pair.should.have.property('sec').equal('4kemdi17CPkkBPnjXiPFf6oBhdGiiqhCL3R4Tuafe9THK8mzBs1evHw5r9u3f8xts2zn6VCBJYVrRMzdaEaWn5Ch');
+  }));
+});
diff --git a/test/fast/modules/keypair/module-test.js b/test/fast/modules/keypair/module-test.js
new file mode 100644
index 000000000..4e7145cac
--- /dev/null
+++ b/test/fast/modules/keypair/module-test.js
@@ -0,0 +1,33 @@
+"use strict";
+const should = require('should');
+const co  = require('co');
+const keypair = require('../../../../app/modules/keypair/index').KeypairDependency
+const assert = require('assert');
+const duniter = require('../../../../index')
+
+duniter.statics.logger.mute()
+
+describe('Module usage', () => {
+
+  it('wrong options should throw', () => co(function*() {
+    let errMessage;
+    try {
+      const stack = duniter.statics.minimalStack();
+      stack.registerDependency(keypair, 'duniter-keypair');
+      yield stack.executeStack(['node', 'index.js', 'config', '--memory', '--keyN', '2048']);
+    } catch (e) {
+      errMessage = e.message;
+    }
+    should.exist(errMessage);
+    should.equal(errMessage, 'Missing --salt and --passwd options along with --keyN|keyr|keyp option');
+  }));
+
+  it('no options on brand new node should generate random key', () => co(function*() {
+    const stack = duniter.statics.minimalStack();
+    stack.registerDependency(keypair, 'duniter-keypair');
+    const res = yield stack.executeStack(['node', 'index.js', 'config', '--memory']);
+    // This is extremely very unlikely to happen
+    res.pair.should.have.property('pub').not.equal('HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd');
+    res.pair.should.have.property('sec').not.equal('51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP');
+  }));
+});
diff --git a/test/integration/continuous-proof.js b/test/integration/continuous-proof.js
index c523b995a..c373ddc1c 100644
--- a/test/integration/continuous-proof.js
+++ b/test/integration/continuous-proof.js
@@ -5,7 +5,6 @@ const should    = require('should');
 const user      = require('./tools/user');
 const toolbox   = require('./tools/toolbox');
 const constants = require('../../app/lib/constants');
-const keyring   = require('duniter-common').keyring;
 
 // Trace these errors
 process.on('unhandledRejection', (reason) => {
diff --git a/yarn.lock b/yarn.lock
index f74509fd7..4888f1d8e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -193,9 +193,9 @@ async@0.1.22:
   version "0.1.22"
   resolved "https://registry.yarnpkg.com/async/-/async-0.1.22.tgz#0fc1aaa088a0e3ef0ebe2d8831bab0dcf8845061"
 
-async@1.x, async@~1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/async/-/async-1.0.0.tgz#f8fc04ca3a13784ade9e1641af98578cfbd647a9"
+async@1.x, async@^1.4.0:
+  version "1.5.2"
+  resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
 
 async@2.2.0, async@^2.0.0:
   version "2.2.0"
@@ -203,14 +203,14 @@ async@2.2.0, async@^2.0.0:
   dependencies:
     lodash "^4.14.0"
 
-async@^1.4.0:
-  version "1.5.2"
-  resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
-
 async@~0.9.0:
   version "0.9.2"
   resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
 
+async@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/async/-/async-1.0.0.tgz#f8fc04ca3a13784ade9e1641af98578cfbd647a9"
+
 asynckit@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -340,7 +340,7 @@ bs58@4.0.0:
   dependencies:
     base-x "^2.0.1"
 
-bs58@^4.0.0:
+bs58@^4.0.0, bs58@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a"
   dependencies:
@@ -806,7 +806,7 @@ duniter-crawler@1.3.x:
     request-promise "4.2.0"
     underscore "1.8.3"
 
-duniter-keypair@1.3.X, duniter-keypair@1.3.x:
+duniter-keypair@1.3.x:
   version "1.3.4"
   resolved "https://registry.yarnpkg.com/duniter-keypair/-/duniter-keypair-1.3.4.tgz#0f13cbf3130ad3720bb853d4440b78a6b7106c6b"
   dependencies:
@@ -2960,8 +2960,8 @@ string-width@^1.0.1, string-width@^1.0.2:
     strip-ansi "^3.0.0"
 
 string-width@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.0.tgz#030664561fc146c9423ec7d978fe2457437fe6d0"
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
   dependencies:
     is-fullwidth-code-point "^2.0.0"
     strip-ansi "^4.0.0"
@@ -3195,8 +3195,8 @@ typedarray@^0.0.6:
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
 
 typescript@^2.4.1:
-  version "2.4.1"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.1.tgz#c3ccb16ddaa0b2314de031e7e6fee89e5ba346bc"
+  version "2.4.2"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.2.tgz#f8395f85d459276067c988aa41837a8f82870844"
 
 uglify-js@^2.6:
   version "2.8.29"
-- 
GitLab