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:
Michael Bradley, Jr 2019-08-13 18:41:00 -05:00
parent de3a92e752
commit debbd4f4b1
5 changed files with 548 additions and 52 deletions

1
.npmrc Normal file
View File

@ -0,0 +1 @@
package-lock = false

View File

@ -1,6 +1,5 @@
language: node_js
node_js:
- "6"
- "8"
- "10"
addons:

View File

@ -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",

View File

@ -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'),
},
}

View File

@ -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$/)
})
})