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

Commit 2884429

Browse files
committed
PR review fixes
1 parent bca01ea commit 2884429

File tree

6 files changed

+151
-71
lines changed

6 files changed

+151
-71
lines changed

src/hdkey.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import Wallet = require('./index')
1+
import Wallet from './index'
22

33
const HDKey = require('hdkey')
44

5-
class EthereumHDKey {
5+
export default class EthereumHDKey {
66
public static fromMasterSeed(seedBuffer: Buffer): EthereumHDKey {
77
return new EthereumHDKey(HDKey.fromMasterSeed(seedBuffer))
88
}
@@ -39,5 +39,3 @@ class EthereumHDKey {
3939
return Wallet.fromPublicKey(this._hdkey._publicKey, true)
4040
}
4141
}
42-
43-
export = EthereumHDKey

src/index.ts

Lines changed: 112 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -52,22 +52,48 @@ function mergeToV3ParamsWithDefaults(params?: Partial<V3Params>): V3Params {
5252
}
5353
}
5454

55-
interface KDFParams {
56-
c: number
57-
prf: string
55+
// KDF params
56+
57+
interface ScryptKDFParams {
5858
dklen: number
5959
n: number
60-
r: number
6160
p: number
61+
r: number
62+
salt: string
63+
}
64+
65+
interface PBKDFParams {
66+
c: number
67+
dklen: number
68+
prf: string
6269
salt: string
6370
}
6471

72+
// union of both the PBKDF2 and Scrypt KDF parameters representing all possible
73+
// parameters the user could supply
74+
type AllKDFParams = ScryptKDFParams & PBKDFParams
75+
76+
type KDFParams = ScryptKDFParams | PBKDFParams
77+
78+
function kdfParamsForPBKDF(params: AllKDFParams): PBKDFParams {
79+
delete params.n
80+
delete params.p
81+
delete params.r
82+
return params
83+
}
84+
85+
function kdfParamsForScrypt(params: AllKDFParams): ScryptKDFParams {
86+
delete params.c
87+
delete params.prf
88+
return params
89+
}
90+
6591
/**
6692
* Based on the parameter list passed to the Wallet.prototype.toV3() method this
6793
* returns a list of parameters for running the key derivation function.
6894
* @param params params passed into the .toV3() method
6995
*/
70-
function mergeKDFParamsWithDefaults(params: V3Params): KDFParams {
96+
function mergeKDFParamsWithDefaults(params: V3Params): AllKDFParams {
7197
const kdfDefaults = {
7298
c: 262144,
7399
prf: 'hmac-sha256',
@@ -84,24 +110,79 @@ function mergeKDFParamsWithDefaults(params: V3Params): KDFParams {
84110
prf: kdfDefaults.prf,
85111
n: params.n || kdfDefaults.n,
86112
r: params.r || kdfDefaults.r,
87-
p: params.p || kdfDefaults.c,
113+
p: params.p || kdfDefaults.p,
88114
}
89115
}
90116

91-
function stripUnusedKDFParamsForPBKDF2(params: KDFParams): Partial<KDFParams> {
92-
delete params.n
93-
delete params.r
94-
delete params.p
95-
return params
117+
// JSON keystore types
118+
119+
// https://github.com/ethereum/homestead-guide/blob/master/old-docs-for-reference/go-ethereum-wiki.rst/Passphrase-protected-key-store-spec.rst
120+
interface V1Keystore {
121+
Address: string
122+
Crypto: {
123+
CipherText: string
124+
IV: string
125+
KeyHeader: {
126+
Kdf: string
127+
KdfParams: {
128+
DkLen: number
129+
N: number
130+
P: number
131+
R: number
132+
SaltLen: number
133+
}
134+
Version: string
135+
}
136+
MAC: string
137+
Salt: string
138+
}
139+
Id: string
140+
Version: string
96141
}
97142

98-
function stripUnusedKDFParamsForScrypt(params: KDFParams): Partial<KDFParams> {
99-
delete params.c
100-
delete params.prf
101-
return params
143+
// https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
144+
interface V3Keystore {
145+
crypto: {
146+
cipher: string
147+
cipherparams: {
148+
iv: string
149+
}
150+
ciphertext: string
151+
kdf: string
152+
kdfparams: KDFParams
153+
mac: string
154+
}
155+
id: string
156+
version: number
157+
}
158+
159+
interface EthSaleKeystore {
160+
encseed: string
161+
ethaddr: string
162+
btcaddr: string
163+
email: string
102164
}
103165

104-
class Wallet {
166+
// wallet implementation
167+
168+
export default class Wallet {
169+
constructor(
170+
private readonly privateKey?: Buffer | undefined,
171+
private publicKey: Buffer | undefined = undefined,
172+
) {
173+
if (privateKey && publicKey) {
174+
throw new Error('Cannot supply both a private and a public key to the constructor')
175+
}
176+
177+
if (privateKey && !ethUtil.isValidPrivate(privateKey)) {
178+
throw new Error('Private key does not satisfy the curve requirements (ie. it is invalid)')
179+
}
180+
181+
if (publicKey && !ethUtil.isValidPublic(publicKey)) {
182+
throw new Error('Invalid public key')
183+
}
184+
}
185+
105186
// static methods
106187

107188
public static generate(icapDirect: boolean = false): Wallet {
@@ -164,10 +245,8 @@ class Wallet {
164245
return Wallet.fromPrivateKey(tmp.slice(46))
165246
}
166247

167-
// https://github.com/ethereum/go-ethereum/wiki/Passphrase-protected-key-store-spec
168-
public static fromV1(input: string | Object, password: string): Wallet {
169-
const json = typeof input === 'object' ? input : JSON.parse(input)
170-
248+
public static fromV1(input: string | V1Keystore, password: string): Wallet {
249+
const json: V1Keystore = typeof input === 'object' ? input : JSON.parse(input)
171250
if (json.Version !== '1') {
172251
throw new Error('Not a V1 Wallet')
173252
}
@@ -187,7 +266,6 @@ class Wallet {
187266

188267
const ciphertext = Buffer.from(json.Crypto.CipherText, 'hex')
189268
const mac = ethUtil.keccak256(Buffer.concat([derivedKey.slice(16, 32), ciphertext]))
190-
191269
if (mac.toString('hex') !== json.Crypto.MAC) {
192270
throw new Error('Key derivation failed - possibly wrong passphrase')
193271
}
@@ -198,16 +276,15 @@ class Wallet {
198276
Buffer.from(json.Crypto.IV, 'hex'),
199277
)
200278
const seed = runCipherBuffer(decipher, ciphertext)
201-
202279
return new Wallet(seed)
203280
}
204281

205282
public static fromV3(
206-
input: string | Object,
283+
input: string | V3Keystore,
207284
password: string,
208285
nonStrict: boolean = false,
209286
): Wallet {
210-
const json =
287+
const json: V3Keystore =
211288
typeof input === 'object' ? input : JSON.parse(nonStrict ? input.toLowerCase() : input)
212289

213290
if (json.version !== 3) {
@@ -264,8 +341,8 @@ class Wallet {
264341
* Based on https://github.com/ethereum/pyethsaletool/blob/master/pyethsaletool.py
265342
* JSON fields: encseed, ethaddr, btcaddr, email
266343
*/
267-
public static fromEthSale(input: string | Object, password: string): Wallet {
268-
const json = typeof input === 'object' ? input : JSON.parse(input)
344+
public static fromEthSale(input: string | EthSaleKeystore, password: string): Wallet {
345+
const json: EthSaleKeystore = typeof input === 'object' ? input : JSON.parse(input)
269346

270347
const encseed = Buffer.from(json.encseed, 'hex')
271348

@@ -301,26 +378,6 @@ class Wallet {
301378
return this.privateKey
302379
}
303380

304-
constructor(
305-
private readonly privateKey?: Buffer | undefined,
306-
private publicKey: Buffer | undefined = undefined,
307-
) {
308-
if (privateKey && publicKey) {
309-
throw new Error('Cannot supply both a private and a public key to the constructor')
310-
}
311-
312-
if (!privateKey && !publicKey) {
313-
}
314-
315-
if (privateKey && !ethUtil.isValidPrivate(privateKey)) {
316-
throw new Error('Private key does not satisfy the curve requirements (ie. it is invalid)')
317-
}
318-
319-
if (publicKey && !ethUtil.isValidPublic(publicKey)) {
320-
throw new Error('Invalid public key')
321-
}
322-
}
323-
324381
// public instance methods
325382

326383
public getPrivateKey(): Buffer {
@@ -351,15 +408,17 @@ class Wallet {
351408
return ethUtil.toChecksumAddress(this.getAddressString())
352409
}
353410

354-
public toV3(password: string, opts?: Partial<V3Params>) {
411+
public toV3(password: string, opts?: Partial<V3Params>): V3Keystore {
355412
if (!keyExists(this.privateKey)) {
356413
throw new Error('This is a public key only wallet')
357414
}
358415

359416
const params = mergeToV3ParamsWithDefaults(opts)
360417
const kdfParams = mergeKDFParamsWithDefaults(params)
361418

362-
let derivedKey: Buffer, finalKDFParams: Partial<KDFParams>
419+
let derivedKey: Buffer
420+
let finalKDFParams: KDFParams
421+
363422
if (params.kdf === 'pbkdf2') {
364423
derivedKey = crypto.pbkdf2Sync(
365424
Buffer.from(password),
@@ -368,7 +427,7 @@ class Wallet {
368427
kdfParams.dklen,
369428
'sha256',
370429
)
371-
finalKDFParams = stripUnusedKDFParamsForPBKDF2(kdfParams)
430+
finalKDFParams = kdfParamsForPBKDF(kdfParams)
372431
} else if (params.kdf === 'scrypt') {
373432
// FIXME: support progress reporting callback
374433
derivedKey = scryptsy(
@@ -379,7 +438,7 @@ class Wallet {
379438
kdfParams.p,
380439
kdfParams.dklen,
381440
)
382-
finalKDFParams = stripUnusedKDFParamsForScrypt(kdfParams)
441+
finalKDFParams = kdfParamsForScrypt(kdfParams)
383442
} else {
384443
throw new Error('Unsupported kdf')
385444
}
@@ -401,6 +460,7 @@ class Wallet {
401460
return {
402461
version: 3,
403462
id: uuidv4({ random: params.uuid }),
463+
// @ts-ignore FIXME: official V3 keystore spec omits the address key
404464
address: this.getAddress().toString('hex'),
405465
crypto: {
406466
ciphertext: ciphertext.toString('hex'),
@@ -442,8 +502,6 @@ function runCipherBuffer(cipher: crypto.Cipher | crypto.Decipher, data: Buffer):
442502
return Buffer.concat([cipher.update(data), cipher.final()])
443503
}
444504

445-
function keyExists(k: Buffer | undefined): k is Buffer {
446-
return k !== undefined
505+
function keyExists(k: Buffer | undefined | null): k is Buffer {
506+
return k !== undefined && k !== null
447507
}
448-
449-
export = Wallet

src/provider-engine.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import Wallet = require('./index')
1+
import Wallet from './index'
22

33
const HookedWalletEthTxSubprovider = require('web3-provider-engine/subproviders/hooked-wallet-ethtx')
44

5-
class WalletSubprovider extends HookedWalletEthTxSubprovider {
5+
export default class WalletSubprovider extends HookedWalletEthTxSubprovider {
66
constructor(wallet: Wallet, opts?: any) {
77
if (!opts) {
88
opts = {}
@@ -20,5 +20,3 @@ class WalletSubprovider extends HookedWalletEthTxSubprovider {
2020
super(opts)
2121
}
2222
}
23-
24-
export = WalletSubprovider

src/thirdparty.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as crypto from 'crypto'
22
import * as ethUtil from 'ethereumjs-util'
33

4-
import Wallet = require('./index')
4+
import Wallet from './index'
55

66
const scryptsy = require('scrypt.js')
77
const utf8 = require('utf8')
@@ -95,12 +95,30 @@ function decodeCryptojsSalt(input: string): { ciphertext: Buffer; salt?: Buffer
9595
return { ciphertext }
9696
}
9797

98+
// {
99+
// "address": "0x169aab499b549eac087035e640d3f7d882ef5e2d",
100+
// "encrypted": true,
101+
// "locked": true,
102+
// "hash": "342f636d174cc1caa49ce16e5b257877191b663e0af0271d2ea03ac7e139317d",
103+
// "private": "U2FsdGVkX19ZrornRBIfl1IDdcj6S9YywY8EgOeOtLj2DHybM/CHL4Jl0jcwjT+36kDnjj+qEfUBu6J1mGQF/fNcD/TsAUgGUTEUEOsP1CKDvNHfLmWLIfxqnYHhHsG5",
104+
// "public": "U2FsdGVkX19EaDNK52q7LEz3hL/VR3dYW5VcoP04tcVKNS0Q3JINpM4XzttRJCBtq4g22hNDrOR8RWyHuh3nPo0pRSe9r5AUfEiCLaMBAhI16kf2KqCA8ah4brkya9ZLECdIl0HDTMYfDASBnyNXd87qodt46U0vdRT3PppK+9hsyqP8yqm9kFcWqMHktqubBE937LIU0W22Rfw6cJRwIw=="
105+
// }
106+
107+
interface EtherWalletOptions {
108+
address: string
109+
encrypted: boolean
110+
locked: boolean
111+
hash: string
112+
private: string
113+
public: string
114+
}
115+
98116
/*
99117
* This wallet format is created by https://github.com/SilentCicero/ethereumjs-accounts
100118
* and used on https://www.myetherwallet.com/
101119
*/
102-
function fromEtherWallet(input: string | Object, password: string): Wallet {
103-
const json = typeof input === 'object' ? input : JSON.parse(input)
120+
function fromEtherWallet(input: string | EtherWalletOptions, password: string): Wallet {
121+
const json: EtherWalletOptions = typeof input === 'object' ? input : JSON.parse(input)
104122

105123
let privateKey: Buffer
106124
if (!json.locked) {
@@ -109,16 +127,20 @@ function fromEtherWallet(input: string | Object, password: string): Wallet {
109127
}
110128
privateKey = Buffer.from(json.private, 'hex')
111129
} else {
130+
if (typeof password !== 'string') {
131+
throw new Error('Password required')
132+
}
133+
112134
if (password.length < 7) {
113135
throw new Error('Password must be at least 7 characters')
114136
}
115137

116138
// the "encrypted" version has the low 4 bytes
117139
// of the hash of the address appended
118-
let cipher = json.encrypted ? json.private.slice(0, 128) : json.private
140+
const hash = json.encrypted ? json.private.slice(0, 128) : json.private
119141

120142
// decode openssl ciphertext + salt encoding
121-
cipher = decodeCryptojsSalt(cipher)
143+
const cipher = decodeCryptojsSalt(hash)
122144
if (!cipher.salt) {
123145
throw new Error('Unsupported EtherWallet key format')
124146
}
@@ -183,6 +205,10 @@ function fromKryptoKit(entropy: string, password: string): Wallet {
183205
if (type === 'd') {
184206
privateKey = ethUtil.sha256(entropy)
185207
} else if (type === 'q') {
208+
if (typeof password !== 'string') {
209+
throw new Error('Password required')
210+
}
211+
186212
const encryptedSeed = ethUtil.sha256(Buffer.from(entropy.slice(0, 30)))
187213
const checksum = entropy.slice(30, 46)
188214

@@ -246,4 +272,4 @@ const Thirdparty = {
246272
fromQuorumWallet,
247273
}
248274

249-
export = Thirdparty
275+
export default Thirdparty

0 commit comments

Comments
 (0)