laksa-extend-keystore
Version:
Extend keystore for laksa-core-crypto
145 lines (130 loc) • 5.97 kB
JavaScript
/**
* This source code is being disclosed to you solely for the purpose of your participation in
* testing Zilliqa and Laksa. You may view, compile and run the code for that purpose and pursuant to
* the protocols and algorithms that are programmed into, and intended by, the code. You may
* not do anything else with the code without express permission from Zilliqa Research Pte. Ltd.,
* including modifying or publishing the code (or any part of it), and developing or forming
* another public or private blockchain network. This source code is provided ‘as is’ and no
* warranties are given as to title or non-infringement, merchantability or fitness for purpose
* and, to the extent permitted by law, all liability for your use of the code is disclaimed.
* Some programs in this code are governed by the GNU General Public License v3.0 (available at
* https://www.gnu.org/licenses/gpl-3.0.en.html) (‘GPLv3’). The programs that are governed by
* GPLv3.0 are those programs that are located in the folders src/depends and tests/depends
* and which include a reference to GPLv3 in their program files.
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('aes-js'), require('pbkdf2'), require('scrypt.js'), require('uuid'), require('laksa-core-crypto')) :
typeof define === 'function' && define.amd ? define(['exports', 'aes-js', 'pbkdf2', 'scrypt.js', 'uuid', 'laksa-core-crypto'], factory) :
(factory((global.Laksa = {}),global.aes,global.pbkdf2,global.scrypt,global.uuid,global.laksaCoreCrypto));
}(this, (function (exports,aes,pbkdf2,scrypt,uuid,laksaCoreCrypto) { 'use strict';
aes = aes && aes.hasOwnProperty('default') ? aes['default'] : aes;
scrypt = scrypt && scrypt.hasOwnProperty('default') ? scrypt['default'] : scrypt;
uuid = uuid && uuid.hasOwnProperty('default') ? uuid['default'] : uuid;
const ALGO_IDENTIFIER = 'aes-128-ctr';
/**
* getDerivedKey
*
* NOTE: only scrypt and pbkdf2 are supported.
*
* @param {Buffer} key - the passphrase
* @param {KDF} kdf - the key derivation function to be used
* @param {KDFParams} params - params for the kdf
*
* @returns {Promise<Buffer>}
*/
const getDerivedKey = (key, kdf, params) => {
return new Promise((resolve, reject) => {
const {
n,
r,
p,
dklen
} = params;
const salt = Buffer.from(params.salt);
if (kdf !== 'pbkdf2' && kdf !== 'scrypt') {
reject(new Error('Only pbkdf2 and scrypt are supported'));
}
const derivedKey = kdf === 'scrypt' ? scrypt(key, salt, n, r, p, dklen) : pbkdf2.pbkdf2Sync(key, salt, n, dklen, 'sha256');
resolve(derivedKey);
});
};
/**
* encryptPrivateKey
*
* Encodes and encrypts an account in the format specified by
* https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition.
* However, note that, in keeping with the hash function used by Zilliqa's
* core protocol, the MAC is generated using sha256 instead of keccak.
*
* NOTE: only scrypt and pbkdf2 are supported.
*
* @param {KDF} kdf - the key derivation function to be used
* @param {string} privateKey - hex-encoded private key
* @param {string} passphrase - a passphrase used for encryption
*
* @returns {Promise<string>}
*/
const encrypt = async (privateKey, passphrase, options) => {
const salt = laksaCoreCrypto.randomBytes(32);
const iv = Buffer.from(laksaCoreCrypto.randomBytes(16), 'hex');
const kdf = options !== undefined ? options.kdf ? options.kdf : 'scrypt' : 'scrypt';
const level = options !== undefined ? options.level ? options.level : 8192 : 8192;
const n = kdf === 'pbkdf2' ? 262144 : level;
const kdfparams = {
salt,
n,
r: 8,
p: 1,
dklen: 32
};
const derivedKey = await getDerivedKey(Buffer.from(passphrase), kdf, kdfparams);
const CTR = aes.ModeOfOperation.ctr;
const cipher = new CTR(derivedKey.slice(0, 16), new aes.Counter(iv));
const ciphertext = Buffer.from(cipher.encrypt(Buffer.from(privateKey, 'hex')));
return {
crypto: {
cipher: ALGO_IDENTIFIER,
cipherparams: {
iv: iv.toString('hex')
},
ciphertext: ciphertext.toString('hex'),
kdf,
kdfparams,
mac: laksaCoreCrypto.hashjs.hmac(laksaCoreCrypto.hashjs.sha256, derivedKey, 'hex').update(Buffer.concat([derivedKey.slice(16, 32), ciphertext, iv, Buffer.from(ALGO_IDENTIFIER)]), 'hex').digest('hex')
},
id: uuid.v4({
random: laksaCoreCrypto.randomBytes(16)
}),
version: 3
};
};
/**
* decryptPrivateKey
*
* Recovers the private key from a keystore file using the given passphrase.
*
* @param {string} passphrase
* @param {KeystoreV3} keystore
* @returns {Promise<string>}
*/
const decrypt = async (keystore, passphrase) => {
const ciphertext = Buffer.from(keystore.crypto.ciphertext, 'hex');
const iv = Buffer.from(keystore.crypto.cipherparams.iv, 'hex');
const {
kdfparams
} = keystore.crypto;
const derivedKey = await getDerivedKey(Buffer.from(passphrase), keystore.crypto.kdf, kdfparams);
const mac = laksaCoreCrypto.hashjs.hmac(laksaCoreCrypto.hashjs.sha256, derivedKey, 'hex').update(Buffer.concat([derivedKey.slice(16, 32), ciphertext, iv, Buffer.from(ALGO_IDENTIFIER)]), 'hex').digest('hex');
if (mac.toUpperCase() !== keystore.crypto.mac.toUpperCase()) {
return Promise.reject(new Error('Failed to decrypt.'));
}
const CTR = aes.ModeOfOperation.ctr;
const cipher = new CTR(derivedKey.slice(0, 16), new aes.Counter(iv));
const decrypted = Buffer.from(cipher.decrypt(ciphertext)).toString('hex');
return decrypted;
};
exports.uuid = uuid;
exports.encrypt = encrypt;
exports.decrypt = decrypt;
Object.defineProperty(exports, '__esModule', { value: true });
})));