diff --git a/packages/neuron-wallet/src/models/keys/keystore.ts b/packages/neuron-wallet/src/models/keys/keystore.ts index 97fe3f4ed4..018ffe0288 100644 --- a/packages/neuron-wallet/src/models/keys/keystore.ts +++ b/packages/neuron-wallet/src/models/keys/keystore.ts @@ -1,5 +1,5 @@ import crypto from 'crypto' -import SHA3 from 'sha3' +import { Keccak } from 'sha3' import { v4 as uuid } from 'uuid' import { UnsupportedCipher, IncorrectPassword, InvalidKeystore } from 'exceptions' @@ -48,24 +48,21 @@ export default class Keystore { } } - static create = (extendedPrivateKey: ExtendedPrivateKey, password: string) => { - const salt = crypto.randomBytes(32) - const iv = crypto.randomBytes(16) - const params = { - n: 8192, - r: 8, - p: 1, - } + static create = ( + extendedPrivateKey: ExtendedPrivateKey, + password: string, + options: { salt?: Buffer; iv?: Buffer } = {} + ) => { + const salt = options.salt || crypto.randomBytes(32) + const iv = options.iv || crypto.randomBytes(16) const kdfparams: KdfParams = { dklen: 32, salt: salt.toString('hex'), - ...params, + n: 2 ** 18, + r: 8, + p: 1, } - const derivedKey: Buffer = crypto.scryptSync(Buffer.from(password), salt, kdfparams.dklen, { - N: kdfparams.n, - r: kdfparams.r, - p: kdfparams.p, - }) + const derivedKey = crypto.scryptSync(password, salt, kdfparams.dklen, Keystore.scryptOptions(kdfparams)) const cipher = crypto.createCipheriv(CIPHER, derivedKey.slice(0, 16), iv) if (!cipher) { @@ -75,11 +72,6 @@ export default class Keystore { cipher.update(Buffer.from(extendedPrivateKey.serialize(), 'hex')), cipher.final(), ]) - const hash = new SHA3(256) - const mac = hash - .update(Buffer.concat([derivedKey.slice(16, 32), ciphertext])) - .digest() - .toString('hex') return new Keystore( { @@ -90,7 +82,7 @@ export default class Keystore { cipher: CIPHER, kdf: 'scrypt', kdfparams, - mac, + mac: Keystore.mac(derivedKey, ciphertext), }, uuid() ) @@ -98,23 +90,9 @@ export default class Keystore { // Decrypt and return serialized extended private key. decrypt(password: string): string { - const { kdfparams } = this.crypto - const derivedKey: Buffer = crypto.scryptSync( - Buffer.from(password), - Buffer.from(kdfparams.salt, 'hex'), - kdfparams.dklen, - { - N: kdfparams.n, - r: kdfparams.r, - p: kdfparams.p, - } - ) + const derivedKey = this.derivedKey(password) const ciphertext = Buffer.from(this.crypto.ciphertext, 'hex') - const mac = new SHA3(256) - .update(Buffer.concat([derivedKey.slice(16, 32), ciphertext])) - .digest() - .toString('hex') - if (mac !== this.crypto.mac) { + if (Keystore.mac(derivedKey, ciphertext) !== this.crypto.mac) { throw new IncorrectPassword() } const decipher = crypto.createDecipheriv( @@ -130,22 +108,31 @@ export default class Keystore { } checkPassword = (password: string) => { + const derivedKey = this.derivedKey(password) + const ciphertext = Buffer.from(this.crypto.ciphertext, 'hex') + return Keystore.mac(derivedKey, ciphertext) === this.crypto.mac + } + + derivedKey = (password: string) => { const { kdfparams } = this.crypto - const derivedKey: Buffer = crypto.scryptSync( - Buffer.from(password), + return crypto.scryptSync( + password, Buffer.from(kdfparams.salt, 'hex'), kdfparams.dklen, - { - N: kdfparams.n, - r: kdfparams.r, - p: kdfparams.p, - } + Keystore.scryptOptions(kdfparams) ) - const ciphertext = Buffer.from(this.crypto.ciphertext, 'hex') - const mac = new SHA3(256) - .update(Buffer.concat([derivedKey.slice(16, 32), ciphertext])) - .digest() - .toString('hex') - return mac === this.crypto.mac + } + + static mac = (derivedKey: Buffer, ciphertext: Buffer) => { + return new Keccak(256).update(Buffer.concat([derivedKey.slice(16, 32), ciphertext])).digest('hex') + } + + static scryptOptions = (kdfparams: KdfParams) => { + return { + N: kdfparams.n, + r: kdfparams.r, + p: kdfparams.p, + maxmem: 128 * (kdfparams.n + kdfparams.p + 2) * kdfparams.r, + } } } diff --git a/packages/neuron-wallet/tests/models/keys/keystore.test.ts b/packages/neuron-wallet/tests/models/keys/keystore.test.ts index a0b9fc2367..5611af6a1e 100644 --- a/packages/neuron-wallet/tests/models/keys/keystore.test.ts +++ b/packages/neuron-wallet/tests/models/keys/keystore.test.ts @@ -19,7 +19,7 @@ describe('load and check password', () => { expect(keystore.checkPassword(password)).toBe(true) }) - it('descrypt', () => { + it('decrypts', () => { expect(keystore.decrypt(password)).toEqual( new ExtendedPrivateKey(fixture.privateKey, fixture.chainCode).serialize() ) @@ -31,3 +31,31 @@ describe('load and check password', () => { expect(extendedPrivateKey.chainCode).toEqual(fixture.chainCode) }) }) + +describe('load ckb cli light keystore', () => { + const password = '123' + const keystoreString = + '{"crypto":{"cipher": "aes-128-ctr", "ciphertext": "253397209cae86474e368720f9baa30f448767047d2cc5a7672ef121861974ed", "cipherparams": {"iv": "8bd8523e0048db3a4ae2534aec6d303a"}, "kdf": "scrypt", "kdfparams": {"dklen": 32, "n": 4096, "p": 6, "r": 8, "salt": "be3d86c99f4895f99d1a0048afb61a34153fa83d5edd033fc914de2c502f57e7"}, "mac": "4453cf5d4f6ec43d0664c3895c4ab9b1c9bcd2d02c7abb190c84375a42739099" },"id": "id", "version": 3}' + const keystore = Keystore.fromJson(keystoreString) + + it('checks correct password', () => { + expect(keystore.checkPassword(password)).toBe(true) + }) +}) + +describe('load ckb cli standard keystore', () => { + const password = '123' + const keystoreString = + '{"address":"ea22142fa5be326e834681144ca30326f99a6d5a","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"29304e5bcbb1885ef5cdcb40b5312b58"},"ciphertext":"93054530a8fbe5b11995acda856585d7362ac7d2b1e4f268c633d997be2d6532c4962501d0835bf52a4693ae7a091ac9bac9297793f4116ef7c123edb00dbc85","kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"724327e67ca321ccf15035bb78a0a05c816bebbe218a0840abdc26da8453c1f4"},"mac":"1d0e5660ffbfc1f9ff4da97aefcfc2153c0ec1b411e35ffee26ee92815cc06f9"},"id":"43c1116e-efd5-4c9e-a86a-3ec0ab163122","version":3}' + const keystore = Keystore.fromJson(keystoreString) + + it('checks correct password', () => { + expect(keystore.checkPassword(password)).toBe(true) + }) + + it('loads private key', () => { + const extendedPrivateKey = keystore.extendedPrivateKey(password) + expect(extendedPrivateKey.privateKey).toEqual('8af124598932440269a81771ad662642e83a38b323b2f70223b8ae0b6c5e0779') + expect(extendedPrivateKey.chainCode).toEqual('615302e2c93151a55c29121dd02ad554e47908a6df6d7374f357092cec11675b') + }) +})