const scrypt = require('scrypt-js')
const secp256k1 = require('secp256k1') // for elliptic operations
const createKeccakHash = require('keccak') // for hashing
const RLP = require('rlp') // for serialization
const version = 1
const ScryptN = 1 << 18
const ScryptP = 1
const scryptR = 8
const scryptDKLen = 32
/** @namespace*/
var keystore = {
/**
* @method
* @param {object} keyfile Keystore object in json format
* @param {string} pass Password to decrypt Keystore
* @return {object} privateKey and address pair from decryption
* @example
*
* keystore.open()
* // returns
* //
*/
open : async function decryptKeystore(keyfile, pass){
//check version
if ( keyfile.version!= version ) {
throw 'error: keyversion mistmatch'
}
return new Promise(function(resolve, reject) {
try {
const mac = keyfile.crypto.mac
const ciphertext = Buffer.from(keyfile.crypto.ciphertext,'hex')
const iv = Buffer.from(keyfile.crypto.iv,'hex')
const salt = Buffer.from(keyfile.crypto.salt,'hex')
const bpass = Buffer.from(pass,'utf8')
let scryptKey = []
scrypt(bpass, salt, ScryptN, scryptR, ScryptP, scryptDKLen, function(error, progress, key) {
if (error) {
console.log("Error: " + error);
} else if (key) {
const crypto = require('crypto');
const decipher = crypto.createDecipheriv('aes-128-ctr', Buffer.from(key.slice(0,16)), iv)
var res = decipher.update(ciphertext,'utf8','hex')
const oubuf = secp256k1.publicKeyCreate(Buffer.from(res,'hex'), false).slice(1);
var publicKey = createKeccakHash('keccak256').update(RLP.encode(oubuf)).digest().slice(12).toString('hex')
var addr = "0x"+publicKey.replace(/.$/i,"1")
var prik = "0x"+res.toString('hex')
const keypair = {
"privateKey":prik,
"address":addr
}
// console.log(keypair);
return resolve(keypair)
} else {
// update UI with progress complete
// console.log(progress.toFixed(2)*100,"%");
}
})
} catch(e) {
reject(e)
}
});
},
/**
* @method
* @param {string} privatekey PrivateKey to encrypt
* @param {string} pass Password used to encrypt privateKey
* @return {object} Keystore file
* @example
*
* keystore.lock()
* // returns
* //
*/
lock : async function encryptKeystore(privatekey, pass){
return new Promise(function(resolve, reject) {
const salt = randHex(64)
const iv = randHex(32)
const passb = Buffer.from(pass,'utf8')
const saltb = Buffer.from(salt, 'hex')
const ivb = Buffer.from(iv,"hex")
scrypt(passb, saltb, ScryptN, scryptR, ScryptP, scryptDKLen, function(error, progress, key) {
if (error) {
console.log("Error: " + error);
} else if (key) {
// console.log("Found: " + key);
const crypto = require('crypto');
prib = Buffer.from(privatekey.slice(2),'hex')
const cipher = crypto.createDecipheriv('aes-128-ctr', Buffer.from(key.slice(0,16)), ivb)
var ciphertx = cipher.update(prib,'utf8','hex')
const p1 = Buffer.from(key.slice(16,32))
const p2 = Buffer.from(ciphertx,'hex')
const mac = "0x"+createKeccakHash('keccak256').update(p1).update(p2).digest().toString('hex')
const oubuf = secp256k1.publicKeyCreate(prib, false).slice(1);
var publicKey = createKeccakHash('keccak256').update(RLP.encode(oubuf)).digest().slice(12).toString('hex')
const pubk = "0x"+publicKey.replace(/.$/i,"1")
const keyfile = {
"version": version,
"address": pubk,
"crypto": {
"ciphertext": ciphertx,
"iv": iv,
"salt": salt,
"mac": mac
}
}
// console.log(keyfile);
resolve(keyfile)
} else {
// update UI with progress complete
// console.log(parseInt(progress*100).toString(),"%");
}
})
});
}
}
function randHex(len){
var maxlen = 8,
min = Math.pow(16,Math.min(len,maxlen)-1)
max = Math.pow(16,Math.min(len,maxlen)) - 1,
n = Math.floor( Math.random() * (max-min+1) ) + min,
r = n.toString(16);
while ( r.length < len ) {
r = r + randHex( len - maxlen );
}
return r;
};
module.exports = keystore