parent
d49ac9baf5
commit
0a43d48ee7
@ -1,2 +1,6 @@ |
|||||||
export * from './random'; |
export * from './random'; |
||||||
export * from './keyTool'; |
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