parent
d49ac9baf5
commit
0a43d48ee7
@ -1,2 +1,6 @@ |
||||
export * from './random'; |
||||
export * from './keyTool'; |
||||
export * from './keystore'; |
||||
|
||||
// export types
|
||||
export * from './types'; |
||||
|
@ -0,0 +1,171 @@ |
||||
import aes from 'aes-js'; |
||||
import scrypt from 'scrypt.js'; |
||||
import { pbkdf2Sync } from 'pbkdf2'; |
||||
import uuid from 'uuid'; |
||||
import { isPrivateKey } from '@harmony/utils'; |
||||
import { randomBytes } from './random'; |
||||
import { getAddressFromPrivateKey } from './keyTool'; |
||||
import { |
||||
concat, |
||||
// arrayify,
|
||||
// hexDataLength,
|
||||
// hexToByteArray,
|
||||
hexToIntArray, |
||||
} from './bytes'; |
||||
import { keccak256 } from './keccak256'; |
||||
import { |
||||
KDF, |
||||
KDFParams, |
||||
EncryptOptions, |
||||
PBKDF2Params, |
||||
ScryptParams, |
||||
Keystore, |
||||
} from './types'; |
||||
|
||||
const DEFAULT_ALGORITHM = '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>} |
||||
*/ |
||||
async function getDerivedKey( |
||||
key: Buffer, |
||||
kdf: KDF, |
||||
params: KDFParams, |
||||
): Promise<Buffer> { |
||||
const salt = Buffer.from(params.salt, 'hex'); |
||||
|
||||
if (kdf === 'pbkdf2') { |
||||
const { c, dklen } = params as PBKDF2Params; |
||||
return pbkdf2Sync(key, salt, c, dklen, 'sha256'); |
||||
} |
||||
|
||||
if (kdf === 'scrypt') { |
||||
const { n, r, p, dklen } = params as ScryptParams; |
||||
return scrypt(key, salt, n, r, p, dklen); |
||||
} |
||||
|
||||
throw new Error('Only pbkdf2 and scrypt are supported'); |
||||
} |
||||
|
||||
/** |
||||
* This method will map the current Account object to V3Keystore object. |
||||
* |
||||
* @method encrypt |
||||
* |
||||
* @param {string} privateKey |
||||
* @param {string} password |
||||
* @param {object} options |
||||
* |
||||
* @return {{version, id, address, crypto}} |
||||
*/ |
||||
export const encrypt = async ( |
||||
privateKey: string, |
||||
password: string, |
||||
options?: EncryptOptions, |
||||
): Promise<string> => { |
||||
if (!isPrivateKey(privateKey)) { |
||||
throw new Error('privateKey is not correct'); |
||||
} |
||||
// TODO: should use isString() to implement this
|
||||
|
||||
if (!password) { |
||||
throw new Error('password is not found'); |
||||
} |
||||
const address = getAddressFromPrivateKey(privateKey); |
||||
|
||||
const salt = randomBytes(32); |
||||
const iv = Buffer.from(randomBytes(16), 'hex'); |
||||
const kdf = |
||||
options !== undefined ? (options.kdf ? options.kdf : 'scrypt') : 'scrypt'; |
||||
const level = |
||||
options !== undefined ? (options.level ? options.level : 8192) : 8192; |
||||
|
||||
const uuidRandom = options !== undefined ? options.uuid : undefined; |
||||
|
||||
const n = kdf === 'pbkdf2' ? 262144 : level; |
||||
const kdfparams = { |
||||
salt, |
||||
n, |
||||
r: 8, |
||||
p: 1, |
||||
dklen: 32, |
||||
}; |
||||
|
||||
const derivedKey = await getDerivedKey(Buffer.from(password), kdf, kdfparams); |
||||
const cipher = new aes.ModeOfOperation.ctr( |
||||
derivedKey.slice(0, 16), |
||||
new aes.Counter(iv), |
||||
); |
||||
|
||||
if (!cipher) { |
||||
throw new Error('Unsupported cipher'); |
||||
} |
||||
|
||||
const ciphertext = Buffer.from( |
||||
cipher.encrypt(Buffer.from(privateKey.replace('0x', ''), 'hex')), |
||||
); |
||||
|
||||
const mac = keccak256(concat([derivedKey.slice(16, 32), ciphertext])); |
||||
|
||||
return JSON.stringify({ |
||||
version: 3, |
||||
id: uuid.v4({ random: uuidRandom || hexToIntArray(randomBytes(16)) }), |
||||
address: address.toLowerCase().replace('0x', ''), |
||||
crypto: { |
||||
ciphertext: ciphertext.toString('hex'), |
||||
cipherparams: { |
||||
iv: iv.toString('hex'), |
||||
}, |
||||
cipher: DEFAULT_ALGORITHM, |
||||
kdf, |
||||
kdfparams, |
||||
mac: mac.replace('0x', ''), |
||||
}, |
||||
}); |
||||
}; |
||||
|
||||
/** |
||||
* @function decrypt |
||||
* @param {Keystore} keystore - Keystore file |
||||
* @param {string} password - password string |
||||
* @return {string} privateKey |
||||
*/ |
||||
export const decrypt = async ( |
||||
keystore: Keystore, |
||||
password: string, |
||||
): Promise<string> => { |
||||
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(password), |
||||
keystore.crypto.kdf, |
||||
kdfparams, |
||||
); |
||||
|
||||
const mac = keccak256(concat([derivedKey.slice(16, 32), ciphertext])).replace( |
||||
'0x', |
||||
'', |
||||
); |
||||
|
||||
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 = |
||||
'0x' + Buffer.from(cipher.decrypt(ciphertext)).toString('hex'); |
||||
return decrypted; |
||||
}; |
@ -0,0 +1,39 @@ |
||||
export type KDF = 'pbkdf2' | 'scrypt'; |
||||
|
||||
export interface PBKDF2Params { |
||||
salt: string; |
||||
dklen: number; |
||||
c: number; |
||||
} |
||||
|
||||
export interface ScryptParams { |
||||
salt: string; |
||||
dklen: number; |
||||
n: number; |
||||
r: number; |
||||
p: number; |
||||
} |
||||
|
||||
export type KDFParams = PBKDF2Params | ScryptParams; |
||||
|
||||
export interface EncryptOptions { |
||||
kdf?: KDF; |
||||
level?: number; |
||||
uuid?: number[]; |
||||
} |
||||
|
||||
export interface Keystore { |
||||
address: string; |
||||
crypto: { |
||||
cipher: string; |
||||
cipherparams: { |
||||
iv: string; |
||||
}; |
||||
ciphertext: string; |
||||
kdf: KDF; |
||||
kdfparams: KDFParams; |
||||
mac: string; |
||||
}; |
||||
id: string; |
||||
version: 3; |
||||
} |
@ -0,0 +1,8 @@ |
||||
{ |
||||
"extends": "../../tsconfig.base.json", |
||||
"compilerOptions": { |
||||
"outDir": "lib", |
||||
"rootDir": "src" |
||||
}, |
||||
"include": ["src", "../../typings/**/*.d.ts"] |
||||
} |
@ -0,0 +1,16 @@ |
||||
declare module 'aes-js' { |
||||
export class Counter { |
||||
constructor(iv: Buffer); |
||||
setValue(value: number): void; |
||||
setBytes(bytes: Array<number> | Buffer | string): void; |
||||
increment(): void; |
||||
} |
||||
|
||||
class CTR { |
||||
constructor(derivedKey: Buffer, iv: Counter); |
||||
encrypt(bytes: Buffer): Uint8Array; |
||||
decrypt(bytes: Buffer): Uint8Array; |
||||
} |
||||
|
||||
export const ModeOfOperation: { ctr: typeof CTR }; |
||||
} |
@ -0,0 +1,11 @@ |
||||
declare module 'scrypt.js' { |
||||
export default function scrypt( |
||||
key: Buffer, |
||||
salt: Buffer, |
||||
n: number, |
||||
r: number, |
||||
p: number, |
||||
dklen: number, |
||||
progressCB?: (prog: any) => void, |
||||
): Buffer; |
||||
} |
Loading…
Reference in new issue