Skip to content
This repository was archived by the owner on Oct 30, 2024. It is now read-only.

Commit debbd4f

Browse files
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.
1 parent de3a92e commit debbd4f

File tree

5 files changed

+549
-53
lines changed

5 files changed

+549
-53
lines changed

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package-lock = false

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
language: node_js
22
node_js:
3-
- "6"
43
- "8"
54
- "10"
65
addons:

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,11 @@
6060
"@types/bn.js": "^4.11.5",
6161
"@types/mocha": "^5.2.7",
6262
"@types/node": "^12.0.10",
63+
"@types/lodash.zip": "^4.2.6",
6364
"coveralls": "^3.0.0",
65+
"ethers": "^4.0.33",
6466
"husky": "^2.1.0",
67+
"lodash.zip": "^4.2.0",
6568
"mocha": "^5.2.0",
6669
"nyc": "^14.1.1",
6770
"prettier": "^1.15.3",

src/index.ts

Lines changed: 104 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@ const uuidv4 = require('uuid/v4')
99
// parameters for the toV3() method
1010

1111
interface V3Params {
12+
kdf: string
13+
cipher: string
14+
salt: string | Buffer
15+
iv: string | Buffer
16+
uuid: string | Buffer
17+
dklen: number
18+
c: number
19+
n: number
20+
r: number
21+
p: number
22+
}
23+
24+
interface V3ParamsStrict {
1225
kdf: string
1326
cipher: string
1427
salt: Buffer
@@ -21,8 +34,43 @@ interface V3Params {
2134
p: number
2235
}
2336

24-
function mergeToV3ParamsWithDefaults(params?: Partial<V3Params>): V3Params {
25-
const v3Defaults: V3Params = {
37+
function validateHexString(paramName: string, str: string, length?: number) {
38+
if (str.toLowerCase().startsWith('0x')) {
39+
str = str.slice(2)
40+
}
41+
if (!str && !length) {
42+
return str
43+
}
44+
if ((length as number) % 2) {
45+
throw new Error(`Invalid length argument, must be an even number`)
46+
}
47+
if (typeof length === 'number' && str.length !== length) {
48+
throw new Error(`Invalid ${paramName}, string must be ${length} hex characters`)
49+
}
50+
if (!/^([0-9a-f]{2})+$/i.test(str)) {
51+
const howMany = typeof length === 'number' ? length : 'empty or a non-zero even number of'
52+
throw new Error(`Invalid ${paramName}, string must be ${howMany} hex characters`)
53+
}
54+
return str
55+
}
56+
57+
function validateBuffer(paramName: string, buff: Buffer, length?: number) {
58+
if (!Buffer.isBuffer(buff)) {
59+
const howManyHex =
60+
typeof length === 'number' ? `${length * 2}` : 'empty or a non-zero even number of'
61+
const howManyBytes = typeof length === 'number' ? ` (${length} bytes)` : ''
62+
throw new Error(
63+
`Invalid ${paramName}, must be a string (${howManyHex} hex characters) or buffer${howManyBytes}`,
64+
)
65+
}
66+
if (typeof length === 'number' && buff.length !== length) {
67+
throw new Error(`Invalid ${paramName}, buffer must be ${length} bytes`)
68+
}
69+
return buff
70+
}
71+
72+
function mergeToV3ParamsWithDefaults(params?: Partial<V3Params>): V3ParamsStrict {
73+
const v3Defaults: V3ParamsStrict = {
2674
cipher: 'aes-128-ctr',
2775
kdf: 'scrypt',
2876
salt: randomBytes(32),
@@ -38,17 +86,30 @@ function mergeToV3ParamsWithDefaults(params?: Partial<V3Params>): V3Params {
3886
if (!params) {
3987
return v3Defaults
4088
}
89+
90+
if (typeof params.salt === 'string') {
91+
params.salt = Buffer.from(validateHexString('salt', params.salt), 'hex')
92+
}
93+
if (typeof params.iv === 'string') {
94+
params.iv = Buffer.from(validateHexString('iv', params.iv, 32), 'hex')
95+
}
96+
if (typeof params.uuid === 'string') {
97+
params.uuid = Buffer.from(validateHexString('uuid', params.uuid, 32), 'hex')
98+
}
99+
100+
if (params.salt) {
101+
validateBuffer('salt', params.salt)
102+
}
103+
if (params.iv) {
104+
validateBuffer('iv', params.iv, 16)
105+
}
106+
if (params.uuid) {
107+
validateBuffer('uuid', params.uuid, 16)
108+
}
109+
41110
return {
42-
cipher: params.cipher || 'aes-128-ctr',
43-
kdf: params.kdf || 'scrypt',
44-
salt: params.salt || randomBytes(32),
45-
iv: params.iv || randomBytes(16),
46-
uuid: params.uuid || randomBytes(16),
47-
dklen: params.dklen || 32,
48-
c: params.c || 262144,
49-
n: params.n || 262144,
50-
r: params.r || 8,
51-
p: params.p || 1,
111+
...v3Defaults,
112+
...(params as V3ParamsStrict),
52113
}
53114
}
54115

@@ -60,6 +121,14 @@ const enum KDFFunctions {
60121
}
61122

62123
interface ScryptKDFParams {
124+
dklen: number
125+
n: number
126+
p: number
127+
r: number
128+
salt: Buffer
129+
}
130+
131+
interface ScryptKDFParamsOut {
63132
dklen: number
64133
n: number
65134
p: number
@@ -68,27 +137,35 @@ interface ScryptKDFParams {
68137
}
69138

70139
interface PBKDFParams {
140+
c: number
141+
dklen: number
142+
prf: string
143+
salt: Buffer
144+
}
145+
146+
interface PBKDFParamsOut {
71147
c: number
72148
dklen: number
73149
prf: string
74150
salt: string
75151
}
76152

77153
type KDFParams = ScryptKDFParams | PBKDFParams
154+
type KDFParamsOut = ScryptKDFParamsOut | PBKDFParamsOut
78155

79-
function kdfParamsForPBKDF(opts: V3Params): PBKDFParams {
156+
function kdfParamsForPBKDF(opts: V3ParamsStrict): PBKDFParams {
80157
return {
81158
dklen: opts.dklen,
82-
salt: opts.salt.toString('hex'),
159+
salt: opts.salt,
83160
c: opts.c,
84161
prf: 'hmac-sha256',
85162
}
86163
}
87164

88-
function kdfParamsForScrypt(opts: V3Params): ScryptKDFParams {
165+
function kdfParamsForScrypt(opts: V3ParamsStrict): ScryptKDFParams {
89166
return {
90167
dklen: opts.dklen,
91-
salt: opts.salt.toString('hex'),
168+
salt: opts.salt,
92169
n: opts.n,
93170
r: opts.r,
94171
p: opts.p,
@@ -130,7 +207,7 @@ interface V3Keystore {
130207
}
131208
ciphertext: string
132209
kdf: string
133-
kdfparams: KDFParams
210+
kdfparams: KDFParamsOut
134211
mac: string
135212
}
136213
id: string
@@ -361,6 +438,7 @@ export default class Wallet {
361438

362439
// public instance methods
363440

441+
// tslint:disable-next-line
364442
public getPrivateKey(): Buffer {
365443
return this.privKey
366444
}
@@ -369,6 +447,7 @@ export default class Wallet {
369447
return ethUtil.bufferToHex(this.privKey)
370448
}
371449

450+
// tslint:disable-next-line
372451
public getPublicKey(): Buffer {
373452
return this.pubKey
374453
}
@@ -394,16 +473,16 @@ export default class Wallet {
394473
throw new Error('This is a public key only wallet')
395474
}
396475

397-
const v3Params: V3Params = mergeToV3ParamsWithDefaults(opts)
476+
const v3Params: V3ParamsStrict = mergeToV3ParamsWithDefaults(opts)
398477

399-
let kdfParams: PBKDFParams | ScryptKDFParams
478+
let kdfParams: KDFParams
400479
let derivedKey: Buffer
401480
switch (v3Params.kdf) {
402481
case KDFFunctions.PBKDF:
403482
kdfParams = kdfParamsForPBKDF(v3Params)
404483
derivedKey = crypto.pbkdf2Sync(
405484
Buffer.from(password),
406-
v3Params.salt,
485+
kdfParams.salt,
407486
kdfParams.c,
408487
kdfParams.dklen,
409488
'sha256',
@@ -414,7 +493,7 @@ export default class Wallet {
414493
// FIXME: support progress reporting callback
415494
derivedKey = scryptsy(
416495
Buffer.from(password),
417-
v3Params.salt,
496+
kdfParams.salt,
418497
kdfParams.n,
419498
kdfParams.r,
420499
kdfParams.p,
@@ -449,7 +528,10 @@ export default class Wallet {
449528
cipherparams: { iv: v3Params.iv.toString('hex') },
450529
cipher: v3Params.cipher,
451530
kdf: v3Params.kdf,
452-
kdfparams: kdfParams,
531+
kdfparams: {
532+
...kdfParams,
533+
salt: kdfParams.salt.toString('hex'),
534+
},
453535
mac: mac.toString('hex'),
454536
},
455537
}

0 commit comments

Comments
 (0)