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