Commit 5f8c0f1a authored by Cédric Moreau's avatar Cédric Moreau

[fix] #1037 Migrate back duniter-keypair

parent 2272b78d
......@@ -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
const bs58 = require('bs58')
export const Base58encode = (bytes:any) => bs58.encode(bytes)
export const Base58decode = (data:any) => new Uint8Array(bs58.decode(data))
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);
}
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
......@@ -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(
......
......@@ -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
}
......
......@@ -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
}
......
......@@ -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
......
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)
}
}
"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"))
}
......@@ -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) {
......
......@@ -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 = "";
......
......@@ -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';
}
......
......@@ -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;
};
......
......@@ -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([
......
......@@ -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",