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

Commit 7904ac8

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 7904ac8

File tree

5 files changed

+494
-51
lines changed

5 files changed

+494
-51
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: 107 additions & 20 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,40 @@ 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 && !length) {
39+
return str
40+
}
41+
if (str.toLowerCase().startsWith('0x')) {
42+
str = str.slice(2)
43+
}
44+
if (!/^[0-9a-f]{2}([0-9a-f]{2})*$/i.test(str)) {
45+
const howMany = typeof length === 'number' ? length : 'empty or a non-zero even number of'
46+
throw new Error(`Invalid ${paramName}, string must be ${howMany} hex characters`)
47+
}
48+
if (typeof length === 'number' && str.length !== length) {
49+
throw new Error(`Invalid ${paramName}, string must be ${length} hex characters`)
50+
}
51+
return str
52+
}
53+
54+
function validateBuffer(paramName: string, buff: Buffer, length?: number) {
55+
if (!Buffer.isBuffer(buff)) {
56+
const howManyHex =
57+
typeof length === 'number' ? `${length * 2}` : 'empty or a non-zero even number of'
58+
const howManyBytes = typeof length === 'number' ? ` (${length} bytes)` : ''
59+
throw new Error(
60+
`Invalid ${paramName}, must be a string (${howManyHex} hex characters) or buffer${howManyBytes}`,
61+
)
62+
}
63+
if (typeof length === 'number' && buff.length !== length) {
64+
throw new Error(`Invalid ${paramName}, buffer must be ${length} bytes`)
65+
}
66+
return buff
67+
}
68+
69+
function mergeToV3ParamsWithDefaults(params?: Partial<V3Params>): V3ParamsStrict {
70+
const v3Defaults: V3ParamsStrict = {
2671
cipher: 'aes-128-ctr',
2772
kdf: 'scrypt',
2873
salt: randomBytes(32),
@@ -38,17 +83,38 @@ function mergeToV3ParamsWithDefaults(params?: Partial<V3Params>): V3Params {
3883
if (!params) {
3984
return v3Defaults
4085
}
86+
87+
if (typeof params.salt === 'string') {
88+
params.salt = Buffer.from(validateHexString('salt', params.salt), 'hex')
89+
}
90+
if (typeof params.iv === 'string') {
91+
params.iv = Buffer.from(validateHexString('iv', params.iv, 32), 'hex')
92+
}
93+
if (typeof params.uuid === 'string') {
94+
params.uuid = Buffer.from(validateHexString('uuid', params.uuid, 32), 'hex')
95+
}
96+
97+
if (params.salt) {
98+
validateBuffer('salt', params.salt)
99+
}
100+
if (params.iv) {
101+
validateBuffer('iv', params.iv, 16)
102+
}
103+
if (params.uuid) {
104+
validateBuffer('uuid', params.uuid, 16)
105+
}
106+
41107
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,
108+
cipher: params.cipher || v3Defaults.cipher,
109+
kdf: params.kdf || v3Defaults.kdf,
110+
salt: params.salt || v3Defaults.salt,
111+
iv: params.iv || v3Defaults.iv,
112+
uuid: params.uuid || v3Defaults.uuid,
113+
dklen: params.dklen || v3Defaults.dklen,
114+
c: params.c || v3Defaults.c,
115+
n: params.n || v3Defaults.n,
116+
r: params.r || v3Defaults.r,
117+
p: params.p || v3Defaults.p,
52118
}
53119
}
54120

@@ -60,6 +126,14 @@ const enum KDFFunctions {
60126
}
61127

62128
interface ScryptKDFParams {
129+
dklen: number
130+
n: number
131+
p: number
132+
r: number
133+
salt: Buffer
134+
}
135+
136+
interface ScryptKDFParamsOut {
63137
dklen: number
64138
n: number
65139
p: number
@@ -68,27 +142,35 @@ interface ScryptKDFParams {
68142
}
69143

70144
interface PBKDFParams {
145+
c: number
146+
dklen: number
147+
prf: string
148+
salt: Buffer
149+
}
150+
151+
interface PBKDFParamsOut {
71152
c: number
72153
dklen: number
73154
prf: string
74155
salt: string
75156
}
76157

77158
type KDFParams = ScryptKDFParams | PBKDFParams
159+
type KDFParamsOut = ScryptKDFParamsOut | PBKDFParamsOut
78160

79-
function kdfParamsForPBKDF(opts: V3Params): PBKDFParams {
161+
function kdfParamsForPBKDF(opts: V3ParamsStrict): PBKDFParams {
80162
return {
81163
dklen: opts.dklen,
82-
salt: opts.salt.toString('hex'),
164+
salt: opts.salt,
83165
c: opts.c,
84166
prf: 'hmac-sha256',
85167
}
86168
}
87169

88-
function kdfParamsForScrypt(opts: V3Params): ScryptKDFParams {
170+
function kdfParamsForScrypt(opts: V3ParamsStrict): ScryptKDFParams {
89171
return {
90172
dklen: opts.dklen,
91-
salt: opts.salt.toString('hex'),
173+
salt: opts.salt,
92174
n: opts.n,
93175
r: opts.r,
94176
p: opts.p,
@@ -130,7 +212,7 @@ interface V3Keystore {
130212
}
131213
ciphertext: string
132214
kdf: string
133-
kdfparams: KDFParams
215+
kdfparams: KDFParamsOut
134216
mac: string
135217
}
136218
id: string
@@ -361,6 +443,7 @@ export default class Wallet {
361443

362444
// public instance methods
363445

446+
// tslint:disable-next-line
364447
public getPrivateKey(): Buffer {
365448
return this.privKey
366449
}
@@ -369,6 +452,7 @@ export default class Wallet {
369452
return ethUtil.bufferToHex(this.privKey)
370453
}
371454

455+
// tslint:disable-next-line
372456
public getPublicKey(): Buffer {
373457
return this.pubKey
374458
}
@@ -394,9 +478,9 @@ export default class Wallet {
394478
throw new Error('This is a public key only wallet')
395479
}
396480

397-
const v3Params: V3Params = mergeToV3ParamsWithDefaults(opts)
481+
const v3Params: V3ParamsStrict = mergeToV3ParamsWithDefaults(opts)
398482

399-
let kdfParams: PBKDFParams | ScryptKDFParams
483+
let kdfParams: KDFParams
400484
let derivedKey: Buffer
401485
switch (v3Params.kdf) {
402486
case KDFFunctions.PBKDF:
@@ -449,7 +533,10 @@ export default class Wallet {
449533
cipherparams: { iv: v3Params.iv.toString('hex') },
450534
cipher: v3Params.cipher,
451535
kdf: v3Params.kdf,
452-
kdfparams: kdfParams,
536+
kdfparams: {
537+
...kdfParams,
538+
salt: kdfParams.salt.toString('hex'),
539+
},
453540
mac: mac.toString('hex'),
454541
},
455542
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy