mirror of
https://github.com/embarklabs/ethereumjs-wallet.git
synced 2025-01-11 19:24:30 +00:00
Normalize salt, iv, uuid params of .toV3() before encrypting
Previously, if `salt`, `iv` and/or `uuid` options were supplied as strings to `.toV3()` they would be passed to `pbkdf2Sync`/`scrypt` as strings. That could result in errors during encryption. Also, during decryption these options were always converted to Buffer instances such that supplying strings during encryption could result in output that could not be decrypted. This commit fixes the inconsistencies, guards against bad inputs, and also makes encrypted output match up with the output of other wallet libraries, e.g. `ethers`, whenever the equivalent encryption options are used consistently across libraries.
This commit is contained in:
parent
de3a92e752
commit
debbd4f4b1
@ -1,6 +1,5 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "6"
|
||||
- "8"
|
||||
- "10"
|
||||
addons:
|
||||
|
@ -60,8 +60,11 @@
|
||||
"@types/bn.js": "^4.11.5",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "^12.0.10",
|
||||
"@types/lodash.zip": "^4.2.6",
|
||||
"coveralls": "^3.0.0",
|
||||
"ethers": "^4.0.33",
|
||||
"husky": "^2.1.0",
|
||||
"lodash.zip": "^4.2.0",
|
||||
"mocha": "^5.2.0",
|
||||
"nyc": "^14.1.1",
|
||||
"prettier": "^1.15.3",
|
||||
|
126
src/index.ts
126
src/index.ts
@ -9,6 +9,19 @@ const uuidv4 = require('uuid/v4')
|
||||
// parameters for the toV3() method
|
||||
|
||||
interface V3Params {
|
||||
kdf: string
|
||||
cipher: string
|
||||
salt: string | Buffer
|
||||
iv: string | Buffer
|
||||
uuid: string | Buffer
|
||||
dklen: number
|
||||
c: number
|
||||
n: number
|
||||
r: number
|
||||
p: number
|
||||
}
|
||||
|
||||
interface V3ParamsStrict {
|
||||
kdf: string
|
||||
cipher: string
|
||||
salt: Buffer
|
||||
@ -21,8 +34,43 @@ interface V3Params {
|
||||
p: number
|
||||
}
|
||||
|
||||
function mergeToV3ParamsWithDefaults(params?: Partial<V3Params>): V3Params {
|
||||
const v3Defaults: V3Params = {
|
||||
function validateHexString(paramName: string, str: string, length?: number) {
|
||||
if (str.toLowerCase().startsWith('0x')) {
|
||||
str = str.slice(2)
|
||||
}
|
||||
if (!str && !length) {
|
||||
return str
|
||||
}
|
||||
if ((length as number) % 2) {
|
||||
throw new Error(`Invalid length argument, must be an even number`)
|
||||
}
|
||||
if (typeof length === 'number' && str.length !== length) {
|
||||
throw new Error(`Invalid ${paramName}, string must be ${length} hex characters`)
|
||||
}
|
||||
if (!/^([0-9a-f]{2})+$/i.test(str)) {
|
||||
const howMany = typeof length === 'number' ? length : 'empty or a non-zero even number of'
|
||||
throw new Error(`Invalid ${paramName}, string must be ${howMany} hex characters`)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
function validateBuffer(paramName: string, buff: Buffer, length?: number) {
|
||||
if (!Buffer.isBuffer(buff)) {
|
||||
const howManyHex =
|
||||
typeof length === 'number' ? `${length * 2}` : 'empty or a non-zero even number of'
|
||||
const howManyBytes = typeof length === 'number' ? ` (${length} bytes)` : ''
|
||||
throw new Error(
|
||||
`Invalid ${paramName}, must be a string (${howManyHex} hex characters) or buffer${howManyBytes}`,
|
||||
)
|
||||
}
|
||||
if (typeof length === 'number' && buff.length !== length) {
|
||||
throw new Error(`Invalid ${paramName}, buffer must be ${length} bytes`)
|
||||
}
|
||||
return buff
|
||||
}
|
||||
|
||||
function mergeToV3ParamsWithDefaults(params?: Partial<V3Params>): V3ParamsStrict {
|
||||
const v3Defaults: V3ParamsStrict = {
|
||||
cipher: 'aes-128-ctr',
|
||||
kdf: 'scrypt',
|
||||
salt: randomBytes(32),
|
||||
@ -38,17 +86,30 @@ function mergeToV3ParamsWithDefaults(params?: Partial<V3Params>): V3Params {
|
||||
if (!params) {
|
||||
return v3Defaults
|
||||
}
|
||||
|
||||
if (typeof params.salt === 'string') {
|
||||
params.salt = Buffer.from(validateHexString('salt', params.salt), 'hex')
|
||||
}
|
||||
if (typeof params.iv === 'string') {
|
||||
params.iv = Buffer.from(validateHexString('iv', params.iv, 32), 'hex')
|
||||
}
|
||||
if (typeof params.uuid === 'string') {
|
||||
params.uuid = Buffer.from(validateHexString('uuid', params.uuid, 32), 'hex')
|
||||
}
|
||||
|
||||
if (params.salt) {
|
||||
validateBuffer('salt', params.salt)
|
||||
}
|
||||
if (params.iv) {
|
||||
validateBuffer('iv', params.iv, 16)
|
||||
}
|
||||
if (params.uuid) {
|
||||
validateBuffer('uuid', params.uuid, 16)
|
||||
}
|
||||
|
||||
return {
|
||||
cipher: params.cipher || 'aes-128-ctr',
|
||||
kdf: params.kdf || 'scrypt',
|
||||
salt: params.salt || randomBytes(32),
|
||||
iv: params.iv || randomBytes(16),
|
||||
uuid: params.uuid || randomBytes(16),
|
||||
dklen: params.dklen || 32,
|
||||
c: params.c || 262144,
|
||||
n: params.n || 262144,
|
||||
r: params.r || 8,
|
||||
p: params.p || 1,
|
||||
...v3Defaults,
|
||||
...(params as V3ParamsStrict),
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,6 +121,14 @@ const enum KDFFunctions {
|
||||
}
|
||||
|
||||
interface ScryptKDFParams {
|
||||
dklen: number
|
||||
n: number
|
||||
p: number
|
||||
r: number
|
||||
salt: Buffer
|
||||
}
|
||||
|
||||
interface ScryptKDFParamsOut {
|
||||
dklen: number
|
||||
n: number
|
||||
p: number
|
||||
@ -68,6 +137,13 @@ interface ScryptKDFParams {
|
||||
}
|
||||
|
||||
interface PBKDFParams {
|
||||
c: number
|
||||
dklen: number
|
||||
prf: string
|
||||
salt: Buffer
|
||||
}
|
||||
|
||||
interface PBKDFParamsOut {
|
||||
c: number
|
||||
dklen: number
|
||||
prf: string
|
||||
@ -75,20 +151,21 @@ interface PBKDFParams {
|
||||
}
|
||||
|
||||
type KDFParams = ScryptKDFParams | PBKDFParams
|
||||
type KDFParamsOut = ScryptKDFParamsOut | PBKDFParamsOut
|
||||
|
||||
function kdfParamsForPBKDF(opts: V3Params): PBKDFParams {
|
||||
function kdfParamsForPBKDF(opts: V3ParamsStrict): PBKDFParams {
|
||||
return {
|
||||
dklen: opts.dklen,
|
||||
salt: opts.salt.toString('hex'),
|
||||
salt: opts.salt,
|
||||
c: opts.c,
|
||||
prf: 'hmac-sha256',
|
||||
}
|
||||
}
|
||||
|
||||
function kdfParamsForScrypt(opts: V3Params): ScryptKDFParams {
|
||||
function kdfParamsForScrypt(opts: V3ParamsStrict): ScryptKDFParams {
|
||||
return {
|
||||
dklen: opts.dklen,
|
||||
salt: opts.salt.toString('hex'),
|
||||
salt: opts.salt,
|
||||
n: opts.n,
|
||||
r: opts.r,
|
||||
p: opts.p,
|
||||
@ -130,7 +207,7 @@ interface V3Keystore {
|
||||
}
|
||||
ciphertext: string
|
||||
kdf: string
|
||||
kdfparams: KDFParams
|
||||
kdfparams: KDFParamsOut
|
||||
mac: string
|
||||
}
|
||||
id: string
|
||||
@ -361,6 +438,7 @@ export default class Wallet {
|
||||
|
||||
// public instance methods
|
||||
|
||||
// tslint:disable-next-line
|
||||
public getPrivateKey(): Buffer {
|
||||
return this.privKey
|
||||
}
|
||||
@ -369,6 +447,7 @@ export default class Wallet {
|
||||
return ethUtil.bufferToHex(this.privKey)
|
||||
}
|
||||
|
||||
// tslint:disable-next-line
|
||||
public getPublicKey(): Buffer {
|
||||
return this.pubKey
|
||||
}
|
||||
@ -394,16 +473,16 @@ export default class Wallet {
|
||||
throw new Error('This is a public key only wallet')
|
||||
}
|
||||
|
||||
const v3Params: V3Params = mergeToV3ParamsWithDefaults(opts)
|
||||
const v3Params: V3ParamsStrict = mergeToV3ParamsWithDefaults(opts)
|
||||
|
||||
let kdfParams: PBKDFParams | ScryptKDFParams
|
||||
let kdfParams: KDFParams
|
||||
let derivedKey: Buffer
|
||||
switch (v3Params.kdf) {
|
||||
case KDFFunctions.PBKDF:
|
||||
kdfParams = kdfParamsForPBKDF(v3Params)
|
||||
derivedKey = crypto.pbkdf2Sync(
|
||||
Buffer.from(password),
|
||||
v3Params.salt,
|
||||
kdfParams.salt,
|
||||
kdfParams.c,
|
||||
kdfParams.dklen,
|
||||
'sha256',
|
||||
@ -414,7 +493,7 @@ export default class Wallet {
|
||||
// FIXME: support progress reporting callback
|
||||
derivedKey = scryptsy(
|
||||
Buffer.from(password),
|
||||
v3Params.salt,
|
||||
kdfParams.salt,
|
||||
kdfParams.n,
|
||||
kdfParams.r,
|
||||
kdfParams.p,
|
||||
@ -449,7 +528,10 @@ export default class Wallet {
|
||||
cipherparams: { iv: v3Params.iv.toString('hex') },
|
||||
cipher: v3Params.cipher,
|
||||
kdf: v3Params.kdf,
|
||||
kdfparams: kdfParams,
|
||||
kdfparams: {
|
||||
...kdfParams,
|
||||
salt: kdfParams.salt.toString('hex'),
|
||||
},
|
||||
mac: mac.toString('hex'),
|
||||
},
|
||||
}
|
||||
|
469
test/index.ts
469
test/index.ts
@ -1,10 +1,17 @@
|
||||
/* tslint:disable no-invalid-this */
|
||||
import * as assert from 'assert'
|
||||
import * as ethUtil from 'ethereumjs-util'
|
||||
import { Wallet as ethersWallet } from 'ethers'
|
||||
|
||||
const zip = require('lodash.zip')
|
||||
|
||||
import Wallet from '../src'
|
||||
import Thirdparty from '../src/thirdparty'
|
||||
|
||||
const n = 262144
|
||||
const r = 8
|
||||
const p = 1
|
||||
|
||||
const fixturePrivateKey = 'efca4cdd31923b50f4214af5d2ae10e7ac45a5019e9431cc195482d707485378'
|
||||
const fixturePrivateKeyStr = '0x' + fixturePrivateKey
|
||||
const fixturePrivateKeyBuffer = Buffer.from(fixturePrivateKey, 'hex')
|
||||
@ -15,6 +22,7 @@ const fixturePublicKeyStr = '0x' + fixturePublicKey
|
||||
const fixturePublicKeyBuffer = Buffer.from(fixturePublicKey, 'hex')
|
||||
|
||||
const fixtureWallet = Wallet.fromPrivateKey(fixturePrivateKeyBuffer)
|
||||
const fixtureEthersWallet = new ethersWallet(fixtureWallet.getPrivateKeyString())
|
||||
|
||||
describe('.getPrivateKey()', function() {
|
||||
it('should work', function() {
|
||||
@ -157,14 +165,14 @@ describe('.generate()', function() {
|
||||
|
||||
describe('.generateVanityAddress()', function() {
|
||||
it('should generate an account with 000 prefix (object)', function() {
|
||||
this.timeout(180000) // 3minutes
|
||||
this.timeout(0) // never
|
||||
const wallet = Wallet.generateVanityAddress(/^000/)
|
||||
assert.strictEqual(wallet.getPrivateKey().length, 32)
|
||||
assert.strictEqual(wallet.getAddress()[0], 0)
|
||||
assert.strictEqual(wallet.getAddress()[1] >>> 4, 0)
|
||||
})
|
||||
it('should generate an account with 000 prefix (string)', function() {
|
||||
this.timeout(180000) // 3minutes
|
||||
this.timeout(0) // never
|
||||
const wallet = Wallet.generateVanityAddress('^000')
|
||||
assert.strictEqual(wallet.getPrivateKey().length, 32)
|
||||
assert.strictEqual(wallet.getAddress()[0], 0)
|
||||
@ -182,42 +190,416 @@ describe('.getV3Filename()', function() {
|
||||
})
|
||||
|
||||
describe('.toV3()', function() {
|
||||
const salt = Buffer.from(
|
||||
'dc9e4a98886738bd8aae134a1f89aaa5a502c3fbd10e336136d4d5fe47448ad6',
|
||||
'hex',
|
||||
)
|
||||
const iv = Buffer.from('cecacd85e9cb89788b5aab2f93361233', 'hex')
|
||||
const uuid = Buffer.from('7e59dc028d42d09db29aa8a0f862cc81', 'hex')
|
||||
const pw = 'testtest'
|
||||
const salt = 'dc9e4a98886738bd8aae134a1f89aaa5a502c3fbd10e336136d4d5fe47448ad6'
|
||||
const iv = 'cecacd85e9cb89788b5aab2f93361233'
|
||||
const uuid = '7e59dc028d42d09db29aa8a0f862cc81'
|
||||
|
||||
it('should work with PBKDF2', function() {
|
||||
const strKdfOptions = { iv, salt, uuid }
|
||||
const buffKdfOptions = {
|
||||
salt: Buffer.from(salt, 'hex'),
|
||||
iv: Buffer.from(iv, 'hex'),
|
||||
uuid: Buffer.from(uuid, 'hex'),
|
||||
}
|
||||
|
||||
// generate all possible combinations of salt, iv, uuid properties, e.g.
|
||||
// {salt: [string], iv: [buffer], uuid: [string]}
|
||||
// the number of objects is naturally a radix for selecting one of the
|
||||
// input values for a given property; example, three objects and two keys:
|
||||
// [{a: 0, b: 0},
|
||||
// {a: 1, b: 1},
|
||||
// {a: 2, b: 2}]
|
||||
const makePermutations = (...objs: Array<object>): Array<object> => {
|
||||
const permus = []
|
||||
const keys = Array.from(
|
||||
objs.reduce((acc: Set<string>, curr: object) => {
|
||||
Object.keys(curr).forEach(key => {
|
||||
acc.add(key)
|
||||
})
|
||||
return acc
|
||||
}, new Set()),
|
||||
)
|
||||
const radix = objs.length
|
||||
const numPermus = radix ** keys.length
|
||||
for (let permuIdx = 0; permuIdx < numPermus; permuIdx++) {
|
||||
const selectors = permuIdx
|
||||
.toString(radix)
|
||||
.padStart(keys.length, '0')
|
||||
.split('')
|
||||
.map(v => parseInt(v, 10))
|
||||
const obj: any = {}
|
||||
zip(selectors, keys).forEach(([sel, k]: [number, string]) => {
|
||||
if ((objs as any)[sel].hasOwnProperty(k)) {
|
||||
obj[k] = (objs as any)[sel][k]
|
||||
}
|
||||
})
|
||||
permus.push(obj)
|
||||
}
|
||||
return permus
|
||||
}
|
||||
|
||||
const makeEthersOptions = (opts: object) => {
|
||||
const obj: any = {}
|
||||
Object.entries(opts).forEach(([key, val]: [string, string | Buffer]) => {
|
||||
obj[key] = typeof val === 'string' ? '0x' + val : val
|
||||
})
|
||||
return obj
|
||||
}
|
||||
|
||||
const permutations = makePermutations(strKdfOptions, buffKdfOptions)
|
||||
|
||||
it('should work with PBKDF2', async function() {
|
||||
this.timeout(0) // never
|
||||
const w =
|
||||
'{"version":3,"id":"7e59dc02-8d42-409d-b29a-a8a0f862cc81","address":"b14ab53e38da1c172f877dbc6d65e4a1b0474c3c","crypto":{"ciphertext":"01ee7f1a3c8d187ea244c92eea9e332ab0bb2b4c902d89bdd71f80dc384da1be","cipherparams":{"iv":"cecacd85e9cb89788b5aab2f93361233"},"cipher":"aes-128-ctr","kdf":"pbkdf2","kdfparams":{"dklen":32,"salt":"dc9e4a98886738bd8aae134a1f89aaa5a502c3fbd10e336136d4d5fe47448ad6","c":262144,"prf":"hmac-sha256"},"mac":"0c02cd0badfebd5e783e0cf41448f84086a96365fc3456716c33641a86ebc7cc"}}'
|
||||
// FIXME: just test for ciphertext and mac?
|
||||
assert.strictEqual(
|
||||
fixtureWallet.toV3String('testtest', { kdf: 'pbkdf2', uuid: uuid, salt: salt, iv: iv }),
|
||||
w,
|
||||
|
||||
await Promise.all(
|
||||
(permutations as Array<{
|
||||
salt: string | Buffer
|
||||
iv: string | Buffer
|
||||
uuid: string | Buffer
|
||||
}>).map(async function({ salt, iv, uuid }) {
|
||||
const encFixtureWallet = fixtureWallet.toV3String(pw, {
|
||||
kdf: 'pbkdf2',
|
||||
c: n,
|
||||
uuid: uuid,
|
||||
salt: salt,
|
||||
iv: iv,
|
||||
})
|
||||
|
||||
assert.deepStrictEqual(JSON.parse(w), JSON.parse(encFixtureWallet))
|
||||
// ethers doesn't support encrypting with PBKDF2
|
||||
}),
|
||||
)
|
||||
})
|
||||
it('should work with Scrypt', function() {
|
||||
const w =
|
||||
it('should work with Scrypt', async function() {
|
||||
this.timeout(0) // never
|
||||
const wStatic =
|
||||
'{"version":3,"id":"7e59dc02-8d42-409d-b29a-a8a0f862cc81","address":"b14ab53e38da1c172f877dbc6d65e4a1b0474c3c","crypto":{"ciphertext":"c52682025b1e5d5c06b816791921dbf439afe7a053abb9fac19f38a57499652c","cipherparams":{"iv":"cecacd85e9cb89788b5aab2f93361233"},"cipher":"aes-128-ctr","kdf":"scrypt","kdfparams":{"dklen":32,"salt":"dc9e4a98886738bd8aae134a1f89aaa5a502c3fbd10e336136d4d5fe47448ad6","n":262144,"r":8,"p":1},"mac":"27b98c8676dc6619d077453b38db645a4c7c17a3e686ee5adaf53c11ac1b890e"}}'
|
||||
this.timeout(180000) // 3minutes
|
||||
// FIXME: just test for ciphertext and mac?
|
||||
assert.strictEqual(
|
||||
fixtureWallet.toV3String('testtest', { kdf: 'scrypt', uuid: uuid, salt: salt, iv: iv }),
|
||||
w,
|
||||
const wRandom = Wallet.generate()
|
||||
const wEthers = new ethersWallet(wRandom.getPrivateKeyString())
|
||||
|
||||
await Promise.all(
|
||||
(permutations as Array<{
|
||||
salt: string | Buffer
|
||||
iv: string | Buffer
|
||||
uuid: string | Buffer
|
||||
}>).map(async function({ salt, iv, uuid }) {
|
||||
const ethersOpts = makeEthersOptions({ salt, iv, uuid })
|
||||
|
||||
const encFixtureWallet = fixtureWallet.toV3String(pw, {
|
||||
kdf: 'scrypt',
|
||||
uuid: uuid,
|
||||
salt: salt,
|
||||
iv: iv,
|
||||
n: n,
|
||||
r: r,
|
||||
p: p,
|
||||
})
|
||||
|
||||
const encFixtureEthersWallet = (await fixtureEthersWallet.encrypt(pw, {
|
||||
scrypt: { N: n, r: r, p: p },
|
||||
salt: ethersOpts.salt,
|
||||
iv: ethersOpts.iv,
|
||||
uuid: ethersOpts.uuid,
|
||||
})).toLowerCase()
|
||||
|
||||
const encRandomWallet = wRandom.toV3String(pw, {
|
||||
kdf: 'scrypt',
|
||||
uuid: uuid,
|
||||
salt: salt,
|
||||
iv: iv,
|
||||
n: n,
|
||||
r: r,
|
||||
p: p,
|
||||
})
|
||||
|
||||
const encEthersWallet = (await wEthers.encrypt(pw, {
|
||||
scrypt: { N: n, r: r, p: p },
|
||||
salt: ethersOpts.salt,
|
||||
iv: ethersOpts.iv,
|
||||
uuid: ethersOpts.uuid,
|
||||
})).toLowerCase()
|
||||
|
||||
assert.deepStrictEqual(JSON.parse(wStatic), JSON.parse(encFixtureWallet))
|
||||
assert.deepStrictEqual(JSON.parse(wStatic), JSON.parse(encFixtureEthersWallet))
|
||||
assert.deepStrictEqual(JSON.parse(encRandomWallet), JSON.parse(encEthersWallet))
|
||||
}),
|
||||
)
|
||||
})
|
||||
it('should work without providing options', function() {
|
||||
this.timeout(180000) // 3minutes
|
||||
this.timeout(0) // never
|
||||
assert.strictEqual(fixtureWallet.toV3('testtest')['version'], 3)
|
||||
})
|
||||
it('should fail for unsupported kdf', function() {
|
||||
this.timeout(180000) // 3minutes
|
||||
this.timeout(0) // never
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3('testtest', { kdf: 'superkey' })
|
||||
}, /^Error: Unsupported kdf$/)
|
||||
})
|
||||
it('should fail for bad salt', function() {
|
||||
const pw = 'test'
|
||||
const errStr = /^Error: Invalid salt, string must be empty or a non-zero even number of hex characters$/
|
||||
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { salt: 'f' })
|
||||
}, errStr)
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { salt: 'fff' })
|
||||
}, errStr)
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { salt: 'xfff' })
|
||||
}, errStr)
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { salt: 'fffx' })
|
||||
}, errStr)
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { salt: 'fffxff' })
|
||||
}, errStr)
|
||||
assert.throws(function() {
|
||||
// @ts-ignore
|
||||
fixtureWallet.toV3(pw, { salt: {} })
|
||||
}, /^Error: Invalid salt, must be a string \(empty or a non-zero even number of hex characters\) or buffer$/)
|
||||
})
|
||||
it('should work with empty salt', async function() {
|
||||
this.timeout(0) // never
|
||||
const pw = 'test'
|
||||
let salt: any = ''
|
||||
let w = fixtureWallet.toV3(pw, { salt: salt, kdf: 'pbkdf2' })
|
||||
|
||||
assert.strictEqual(salt, w.crypto.kdfparams.salt)
|
||||
assert.strictEqual(
|
||||
fixtureWallet.getPrivateKeyString(),
|
||||
Wallet.fromV3(w, pw).getPrivateKeyString(),
|
||||
)
|
||||
|
||||
salt = '0x'
|
||||
w = fixtureWallet.toV3(pw, { salt: salt, kdf: 'pbkdf2' })
|
||||
|
||||
assert.strictEqual('', w.crypto.kdfparams.salt)
|
||||
assert.strictEqual(
|
||||
fixtureWallet.getPrivateKeyString(),
|
||||
Wallet.fromV3(w, pw).getPrivateKeyString(),
|
||||
)
|
||||
|
||||
salt = Buffer.from('', 'hex')
|
||||
w = fixtureWallet.toV3(pw, { salt: salt, kdf: 'pbkdf2' })
|
||||
|
||||
assert.strictEqual('', w.crypto.kdfparams.salt)
|
||||
assert.strictEqual(
|
||||
fixtureWallet.getPrivateKeyString(),
|
||||
Wallet.fromV3(w, pw).getPrivateKeyString(),
|
||||
)
|
||||
|
||||
salt = ''
|
||||
let iv = 'ffffffffffffffffffffffffffffffff'
|
||||
let uuid = 'ffffffffffffffffffffffffffffffff'
|
||||
let wStr = fixtureWallet.toV3String(pw, {
|
||||
salt: salt,
|
||||
iv: iv,
|
||||
uuid: uuid,
|
||||
kdf: 'scrypt',
|
||||
n: n,
|
||||
r: r,
|
||||
p: p,
|
||||
})
|
||||
let wEthersStr = await new ethersWallet(fixtureWallet.getPrivateKeyString()).encrypt(pw, {
|
||||
scrypt: { N: n, r: r, p: p },
|
||||
salt: '0x' + (salt as string),
|
||||
iv: '0x' + iv,
|
||||
uuid: '0x' + uuid,
|
||||
})
|
||||
|
||||
assert.strictEqual(salt, JSON.parse(wStr).crypto.kdfparams.salt)
|
||||
assert.deepStrictEqual(JSON.parse(wStr), JSON.parse(wEthersStr.toLowerCase()))
|
||||
assert.strictEqual(
|
||||
fixtureWallet.getPrivateKeyString(),
|
||||
Wallet.fromV3(JSON.parse(wStr), pw).getPrivateKeyString(),
|
||||
)
|
||||
assert.strictEqual(
|
||||
fixtureWallet.getPrivateKeyString(),
|
||||
(await ethersWallet.fromEncryptedJson(wEthersStr, pw)).privateKey,
|
||||
)
|
||||
|
||||
salt = '0x'
|
||||
iv = '0x' + iv
|
||||
uuid = '0x' + uuid
|
||||
wStr = fixtureWallet.toV3String(pw, {
|
||||
salt: salt,
|
||||
iv: iv,
|
||||
uuid: uuid,
|
||||
kdf: 'scrypt',
|
||||
n: n,
|
||||
r: r,
|
||||
p: p,
|
||||
})
|
||||
wEthersStr = await new ethersWallet(fixtureWallet.getPrivateKeyString()).encrypt(pw, {
|
||||
scrypt: { N: n, r: r, p: p },
|
||||
salt: salt,
|
||||
iv: iv,
|
||||
uuid: uuid,
|
||||
})
|
||||
|
||||
assert.strictEqual('', JSON.parse(wStr).crypto.kdfparams.salt)
|
||||
assert.deepStrictEqual(JSON.parse(wStr), JSON.parse(wEthersStr.toLowerCase()))
|
||||
assert.strictEqual(
|
||||
fixtureWallet.getPrivateKeyString(),
|
||||
Wallet.fromV3(JSON.parse(wStr), pw).getPrivateKeyString(),
|
||||
)
|
||||
assert.strictEqual(
|
||||
fixtureWallet.getPrivateKeyString(),
|
||||
(await ethersWallet.fromEncryptedJson(wEthersStr, pw)).privateKey,
|
||||
)
|
||||
|
||||
salt = Buffer.from('', 'hex')
|
||||
wStr = fixtureWallet.toV3String(pw, {
|
||||
salt: salt,
|
||||
iv: iv,
|
||||
uuid: uuid,
|
||||
kdf: 'scrypt',
|
||||
n: n,
|
||||
r: r,
|
||||
p: p,
|
||||
})
|
||||
wEthersStr = await new ethersWallet(fixtureWallet.getPrivateKeyString()).encrypt(pw, {
|
||||
scrypt: { N: n, r: r, p: p },
|
||||
salt: salt,
|
||||
iv: iv,
|
||||
uuid: uuid,
|
||||
})
|
||||
|
||||
assert.strictEqual('', JSON.parse(wStr).crypto.kdfparams.salt)
|
||||
assert.deepStrictEqual(JSON.parse(wStr), JSON.parse(wEthersStr.toLowerCase()))
|
||||
assert.strictEqual(
|
||||
fixtureWallet.getPrivateKeyString(),
|
||||
Wallet.fromV3(JSON.parse(wStr), pw).getPrivateKeyString(),
|
||||
)
|
||||
assert.strictEqual(
|
||||
fixtureWallet.getPrivateKeyString(),
|
||||
(await ethersWallet.fromEncryptedJson(wEthersStr, pw)).privateKey,
|
||||
)
|
||||
})
|
||||
it('should fail for bad iv', function() {
|
||||
const pw = 'test'
|
||||
const errStrLength = /^Error: Invalid iv, string must be 32 hex characters$/
|
||||
const errBuffLength = /^Error: Invalid iv, buffer must be 16 bytes$/
|
||||
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { iv: '' })
|
||||
}, errStrLength)
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { iv: 'ff' })
|
||||
}, errStrLength)
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { iv: 'ffffffffffffffffffffffffffffffffff' })
|
||||
}, errStrLength)
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { iv: 'xfffffffffffffffffffffffffffffff' })
|
||||
}, errStrLength)
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { iv: 'fffffffffffffffffffffffffffffffx' })
|
||||
}, errStrLength)
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { iv: 'fffffffffffffffxffffffffffffffff' })
|
||||
}, errStrLength)
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { iv: Buffer.from('', 'hex') })
|
||||
}, errBuffLength)
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { iv: Buffer.from('ff', 'hex') })
|
||||
}, errBuffLength)
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { iv: Buffer.from('ffffffffffffffffffffffffffffffffff', 'hex') })
|
||||
}, errBuffLength)
|
||||
assert.throws(function() {
|
||||
// @ts-ignore
|
||||
fixtureWallet.toV3(pw, { iv: {} })
|
||||
}, /^Error: Invalid iv, must be a string \(32 hex characters\) or buffer \(16 bytes\)$/)
|
||||
})
|
||||
it('should fail for bad uuid', function() {
|
||||
const pw = 'test'
|
||||
const errStrLength = /^Error: Invalid uuid, string must be 32 hex characters$/
|
||||
const errBuffLength = /^Error: Invalid uuid, buffer must be 16 bytes$/
|
||||
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { uuid: '' })
|
||||
}, errStrLength)
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { uuid: 'ff' })
|
||||
}, errStrLength)
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { uuid: 'ffffffffffffffffffffffffffffffffff' })
|
||||
}, errStrLength)
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { uuid: 'xfffffffffffffffffffffffffffffff' })
|
||||
}, errStrLength)
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { uuid: 'fffffffffffffffffffffffffffffffx' })
|
||||
}, errStrLength)
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { uuid: 'fffffffffffffffxffffffffffffffff' })
|
||||
}, errStrLength)
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { uuid: Buffer.from('', 'hex') })
|
||||
}, errBuffLength)
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { uuid: Buffer.from('ff', 'hex') })
|
||||
}, errBuffLength)
|
||||
assert.throws(function() {
|
||||
fixtureWallet.toV3(pw, { uuid: Buffer.from('ffffffffffffffffffffffffffffffffff', 'hex') })
|
||||
}, errBuffLength)
|
||||
assert.throws(function() {
|
||||
// @ts-ignore
|
||||
fixtureWallet.toV3(pw, { uuid: {} })
|
||||
}, /^Error: Invalid uuid, must be a string \(32 hex characters\) or buffer \(16 bytes\)$/)
|
||||
})
|
||||
it('should strip leading "0x" from salt, iv, uuid', function() {
|
||||
this.timeout(0) // never
|
||||
const pw = 'test'
|
||||
const salt =
|
||||
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||
const iv = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
|
||||
const uuid = 'cccccccccccccccccccccccccccccccc'
|
||||
let w = fixtureWallet.toV3(pw, {
|
||||
salt: '0x' + salt,
|
||||
iv: '0X' + iv,
|
||||
uuid: '0x' + uuid,
|
||||
kdf: 'pbkdf2',
|
||||
})
|
||||
let w2 = fixtureWallet.toV3(pw, { salt: '0x' + salt, iv: '0X' + iv, uuid: uuid, kdf: 'pbkdf2' })
|
||||
|
||||
assert.strictEqual(salt, w.crypto.kdfparams.salt)
|
||||
assert.strictEqual(iv, w.crypto.cipherparams.iv)
|
||||
assert.strictEqual(w.id, w2.id)
|
||||
assert.strictEqual(
|
||||
fixtureWallet.getPrivateKeyString(),
|
||||
Wallet.fromV3(w, pw).getPrivateKeyString(),
|
||||
)
|
||||
assert.strictEqual(
|
||||
fixtureWallet.getPrivateKeyString(),
|
||||
Wallet.fromV3(w2, pw).getPrivateKeyString(),
|
||||
)
|
||||
|
||||
w = fixtureWallet.toV3(pw, {
|
||||
salt: '0x' + salt,
|
||||
iv: '0X' + iv,
|
||||
uuid: '0x' + uuid,
|
||||
kdf: 'scrypt',
|
||||
})
|
||||
w2 = fixtureWallet.toV3(pw, { salt: '0x' + salt, iv: '0X' + iv, uuid: uuid, kdf: 'scrypt' })
|
||||
|
||||
assert.strictEqual(salt, w.crypto.kdfparams.salt)
|
||||
assert.strictEqual(iv, w.crypto.cipherparams.iv)
|
||||
assert.strictEqual(w.id, w2.id)
|
||||
assert.strictEqual(
|
||||
fixtureWallet.getPrivateKeyString(),
|
||||
Wallet.fromV3(w, pw).getPrivateKeyString(),
|
||||
)
|
||||
assert.strictEqual(
|
||||
fixtureWallet.getPrivateKeyString(),
|
||||
Wallet.fromV3(w2, pw).getPrivateKeyString(),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
/*
|
||||
@ -231,24 +613,53 @@ describe('.fromV1()', function () {
|
||||
*/
|
||||
|
||||
describe('.fromV3()', function() {
|
||||
it('should work with PBKDF2', function() {
|
||||
it('should work with PBKDF2', async function() {
|
||||
const w =
|
||||
'{"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"6087dab2f9fdbbfaddc31a909735c1e6"},"ciphertext":"5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46","kdf":"pbkdf2","kdfparams":{"c":262144,"dklen":32,"prf":"hmac-sha256","salt":"ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"},"mac":"517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2"},"id":"3198bc9c-6672-5ab3-d995-4942343ae5b6","version":3}'
|
||||
const wallet = Wallet.fromV3(w, 'testpassword')
|
||||
let wEthersCompat = JSON.parse(w)
|
||||
// see: https://github.com/ethers-io/ethers.js/issues/582
|
||||
wEthersCompat.address = '0x008aeeda4d805471df9b2a5b0f38a0c3bcba786b'
|
||||
wEthersCompat = JSON.stringify(wEthersCompat)
|
||||
const pw = 'testpassword'
|
||||
const wallet = Wallet.fromV3(w, pw)
|
||||
const wRandom = Wallet.generate().toV3String(pw, { kdf: 'pbkdf2' })
|
||||
const walletRandom = Wallet.fromV3(wRandom, pw)
|
||||
|
||||
this.timeout(0) // never
|
||||
assert.strictEqual(wallet.getAddressString(), '0x008aeeda4d805471df9b2a5b0f38a0c3bcba786b')
|
||||
assert.strictEqual(
|
||||
wallet.getAddressString(),
|
||||
(await ethersWallet.fromEncryptedJson(wEthersCompat, pw)).address.toLowerCase(),
|
||||
)
|
||||
assert.strictEqual(
|
||||
walletRandom.getAddressString(),
|
||||
(await ethersWallet.fromEncryptedJson(wRandom, pw)).address.toLowerCase(),
|
||||
)
|
||||
})
|
||||
it('should work with Scrypt', function() {
|
||||
it('should work with Scrypt', async function() {
|
||||
const sample =
|
||||
'{"address":"2f91eb73a6cd5620d7abb50889f24eea7a6a4feb","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"a2bc4f71e8445d64ceebd1247079fbd8"},"ciphertext":"6b9ab7954c9066fa1e54e04e2c527c7d78a77611d5f84fede1bd61ab13c51e3e","kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"r":1,"p":8,"salt":"caf551e2b7ec12d93007e528093697a4c68e8a50e663b2a929754a8085d9ede4"},"mac":"506cace9c5c32544d39558025cb3bf23ed94ba2626e5338c82e50726917e1a15"},"id":"1b3cad9b-fa7b-4817-9022-d5e598eb5fe3","version":3}'
|
||||
const wallet = Wallet.fromV3(sample, 'testtest')
|
||||
this.timeout(180000) // 3minutes
|
||||
const pw = 'testtest'
|
||||
const wallet = Wallet.fromV3(sample, pw)
|
||||
const sampleRandom = Wallet.generate().toV3String(pw)
|
||||
const walletRandom = Wallet.fromV3(sampleRandom, pw)
|
||||
|
||||
this.timeout(0) // never
|
||||
assert.strictEqual(wallet.getAddressString(), '0x2f91eb73a6cd5620d7abb50889f24eea7a6a4feb')
|
||||
assert.strictEqual(
|
||||
wallet.getAddressString(),
|
||||
(await ethersWallet.fromEncryptedJson(sample, pw)).address.toLowerCase(),
|
||||
)
|
||||
assert.strictEqual(
|
||||
walletRandom.getAddressString(),
|
||||
(await ethersWallet.fromEncryptedJson(sampleRandom, pw)).address.toLowerCase(),
|
||||
)
|
||||
})
|
||||
it("should work with 'unencrypted' wallets", function() {
|
||||
const w =
|
||||
'{"address":"a9886ac7489ecbcbd79268a79ef00d940e5fe1f2","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"c542cf883299b5b0a29155091054028d"},"ciphertext":"0a83c77235840cffcfcc5afe5908f2d7f89d7d54c4a796dfe2f193e90413ee9d","kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"r":1,"p":8,"salt":"699f7bf5f6985068dfaaff9db3b06aea8fe3dd3140b3addb4e60620ee97a0316"},"mac":"613fed2605240a2ff08b8d93ccc48c5b3d5023b7088189515d70df41d65f44de"},"id":"0edf817a-ee0e-4e25-8314-1f9e88a60811","version":3}'
|
||||
const wallet = Wallet.fromV3(w, '')
|
||||
this.timeout(180000) // 3minutes
|
||||
this.timeout(0) // never
|
||||
assert.strictEqual(wallet.getAddressString(), '0xa9886ac7489ecbcbd79268a79ef00d940e5fe1f2')
|
||||
})
|
||||
it('should fail with invalid password', function() {
|
||||
@ -359,7 +770,7 @@ describe('.fromQuorumWallet()', function() {
|
||||
describe('raw new Wallet() init', function() {
|
||||
it('should fail when both priv and pub key provided', function() {
|
||||
assert.throws(function() {
|
||||
new Wallet(fixturePrivateKeyBuffer, fixturePublicKeyBuffer) // eslint-disable-line
|
||||
new Wallet(fixturePrivateKeyBuffer, fixturePublicKeyBuffer)
|
||||
}, /^Error: Cannot supply both a private and a public key to the constructor$/)
|
||||
})
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user