PR review fixes

This commit is contained in:
Jack Cook 2019-07-02 11:56:56 -07:00
parent bca01ea69f
commit 288442924e
6 changed files with 151 additions and 71 deletions

View File

@ -1,8 +1,8 @@
import Wallet = require('./index')
import Wallet from './index'
const HDKey = require('hdkey')
class EthereumHDKey {
export default class EthereumHDKey {
public static fromMasterSeed(seedBuffer: Buffer): EthereumHDKey {
return new EthereumHDKey(HDKey.fromMasterSeed(seedBuffer))
}
@ -39,5 +39,3 @@ class EthereumHDKey {
return Wallet.fromPublicKey(this._hdkey._publicKey, true)
}
}
export = EthereumHDKey

View File

@ -52,22 +52,48 @@ function mergeToV3ParamsWithDefaults(params?: Partial<V3Params>): V3Params {
}
}
interface KDFParams {
c: number
prf: string
// KDF params
interface ScryptKDFParams {
dklen: number
n: number
r: number
p: number
r: number
salt: string
}
interface PBKDFParams {
c: number
dklen: number
prf: string
salt: string
}
// union of both the PBKDF2 and Scrypt KDF parameters representing all possible
// parameters the user could supply
type AllKDFParams = ScryptKDFParams & PBKDFParams
type KDFParams = ScryptKDFParams | PBKDFParams
function kdfParamsForPBKDF(params: AllKDFParams): PBKDFParams {
delete params.n
delete params.p
delete params.r
return params
}
function kdfParamsForScrypt(params: AllKDFParams): ScryptKDFParams {
delete params.c
delete params.prf
return params
}
/**
* Based on the parameter list passed to the Wallet.prototype.toV3() method this
* returns a list of parameters for running the key derivation function.
* @param params params passed into the .toV3() method
*/
function mergeKDFParamsWithDefaults(params: V3Params): KDFParams {
function mergeKDFParamsWithDefaults(params: V3Params): AllKDFParams {
const kdfDefaults = {
c: 262144,
prf: 'hmac-sha256',
@ -84,24 +110,79 @@ function mergeKDFParamsWithDefaults(params: V3Params): KDFParams {
prf: kdfDefaults.prf,
n: params.n || kdfDefaults.n,
r: params.r || kdfDefaults.r,
p: params.p || kdfDefaults.c,
p: params.p || kdfDefaults.p,
}
}
function stripUnusedKDFParamsForPBKDF2(params: KDFParams): Partial<KDFParams> {
delete params.n
delete params.r
delete params.p
return params
// JSON keystore types
// https://github.com/ethereum/homestead-guide/blob/master/old-docs-for-reference/go-ethereum-wiki.rst/Passphrase-protected-key-store-spec.rst
interface V1Keystore {
Address: string
Crypto: {
CipherText: string
IV: string
KeyHeader: {
Kdf: string
KdfParams: {
DkLen: number
N: number
P: number
R: number
SaltLen: number
}
Version: string
}
MAC: string
Salt: string
}
Id: string
Version: string
}
function stripUnusedKDFParamsForScrypt(params: KDFParams): Partial<KDFParams> {
delete params.c
delete params.prf
return params
// https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
interface V3Keystore {
crypto: {
cipher: string
cipherparams: {
iv: string
}
ciphertext: string
kdf: string
kdfparams: KDFParams
mac: string
}
id: string
version: number
}
interface EthSaleKeystore {
encseed: string
ethaddr: string
btcaddr: string
email: string
}
// wallet implementation
export default class Wallet {
constructor(
private readonly privateKey?: Buffer | undefined,
private publicKey: Buffer | undefined = undefined,
) {
if (privateKey && publicKey) {
throw new Error('Cannot supply both a private and a public key to the constructor')
}
if (privateKey && !ethUtil.isValidPrivate(privateKey)) {
throw new Error('Private key does not satisfy the curve requirements (ie. it is invalid)')
}
if (publicKey && !ethUtil.isValidPublic(publicKey)) {
throw new Error('Invalid public key')
}
}
class Wallet {
// static methods
public static generate(icapDirect: boolean = false): Wallet {
@ -164,10 +245,8 @@ class Wallet {
return Wallet.fromPrivateKey(tmp.slice(46))
}
// https://github.com/ethereum/go-ethereum/wiki/Passphrase-protected-key-store-spec
public static fromV1(input: string | Object, password: string): Wallet {
const json = typeof input === 'object' ? input : JSON.parse(input)
public static fromV1(input: string | V1Keystore, password: string): Wallet {
const json: V1Keystore = typeof input === 'object' ? input : JSON.parse(input)
if (json.Version !== '1') {
throw new Error('Not a V1 Wallet')
}
@ -187,7 +266,6 @@ class Wallet {
const ciphertext = Buffer.from(json.Crypto.CipherText, 'hex')
const mac = ethUtil.keccak256(Buffer.concat([derivedKey.slice(16, 32), ciphertext]))
if (mac.toString('hex') !== json.Crypto.MAC) {
throw new Error('Key derivation failed - possibly wrong passphrase')
}
@ -198,16 +276,15 @@ class Wallet {
Buffer.from(json.Crypto.IV, 'hex'),
)
const seed = runCipherBuffer(decipher, ciphertext)
return new Wallet(seed)
}
public static fromV3(
input: string | Object,
input: string | V3Keystore,
password: string,
nonStrict: boolean = false,
): Wallet {
const json =
const json: V3Keystore =
typeof input === 'object' ? input : JSON.parse(nonStrict ? input.toLowerCase() : input)
if (json.version !== 3) {
@ -264,8 +341,8 @@ class Wallet {
* Based on https://github.com/ethereum/pyethsaletool/blob/master/pyethsaletool.py
* JSON fields: encseed, ethaddr, btcaddr, email
*/
public static fromEthSale(input: string | Object, password: string): Wallet {
const json = typeof input === 'object' ? input : JSON.parse(input)
public static fromEthSale(input: string | EthSaleKeystore, password: string): Wallet {
const json: EthSaleKeystore = typeof input === 'object' ? input : JSON.parse(input)
const encseed = Buffer.from(json.encseed, 'hex')
@ -301,26 +378,6 @@ class Wallet {
return this.privateKey
}
constructor(
private readonly privateKey?: Buffer | undefined,
private publicKey: Buffer | undefined = undefined,
) {
if (privateKey && publicKey) {
throw new Error('Cannot supply both a private and a public key to the constructor')
}
if (!privateKey && !publicKey) {
}
if (privateKey && !ethUtil.isValidPrivate(privateKey)) {
throw new Error('Private key does not satisfy the curve requirements (ie. it is invalid)')
}
if (publicKey && !ethUtil.isValidPublic(publicKey)) {
throw new Error('Invalid public key')
}
}
// public instance methods
public getPrivateKey(): Buffer {
@ -351,7 +408,7 @@ class Wallet {
return ethUtil.toChecksumAddress(this.getAddressString())
}
public toV3(password: string, opts?: Partial<V3Params>) {
public toV3(password: string, opts?: Partial<V3Params>): V3Keystore {
if (!keyExists(this.privateKey)) {
throw new Error('This is a public key only wallet')
}
@ -359,7 +416,9 @@ class Wallet {
const params = mergeToV3ParamsWithDefaults(opts)
const kdfParams = mergeKDFParamsWithDefaults(params)
let derivedKey: Buffer, finalKDFParams: Partial<KDFParams>
let derivedKey: Buffer
let finalKDFParams: KDFParams
if (params.kdf === 'pbkdf2') {
derivedKey = crypto.pbkdf2Sync(
Buffer.from(password),
@ -368,7 +427,7 @@ class Wallet {
kdfParams.dklen,
'sha256',
)
finalKDFParams = stripUnusedKDFParamsForPBKDF2(kdfParams)
finalKDFParams = kdfParamsForPBKDF(kdfParams)
} else if (params.kdf === 'scrypt') {
// FIXME: support progress reporting callback
derivedKey = scryptsy(
@ -379,7 +438,7 @@ class Wallet {
kdfParams.p,
kdfParams.dklen,
)
finalKDFParams = stripUnusedKDFParamsForScrypt(kdfParams)
finalKDFParams = kdfParamsForScrypt(kdfParams)
} else {
throw new Error('Unsupported kdf')
}
@ -401,6 +460,7 @@ class Wallet {
return {
version: 3,
id: uuidv4({ random: params.uuid }),
// @ts-ignore FIXME: official V3 keystore spec omits the address key
address: this.getAddress().toString('hex'),
crypto: {
ciphertext: ciphertext.toString('hex'),
@ -442,8 +502,6 @@ function runCipherBuffer(cipher: crypto.Cipher | crypto.Decipher, data: Buffer):
return Buffer.concat([cipher.update(data), cipher.final()])
}
function keyExists(k: Buffer | undefined): k is Buffer {
return k !== undefined
function keyExists(k: Buffer | undefined | null): k is Buffer {
return k !== undefined && k !== null
}
export = Wallet

View File

@ -1,8 +1,8 @@
import Wallet = require('./index')
import Wallet from './index'
const HookedWalletEthTxSubprovider = require('web3-provider-engine/subproviders/hooked-wallet-ethtx')
class WalletSubprovider extends HookedWalletEthTxSubprovider {
export default class WalletSubprovider extends HookedWalletEthTxSubprovider {
constructor(wallet: Wallet, opts?: any) {
if (!opts) {
opts = {}
@ -20,5 +20,3 @@ class WalletSubprovider extends HookedWalletEthTxSubprovider {
super(opts)
}
}
export = WalletSubprovider

View File

@ -1,7 +1,7 @@
import * as crypto from 'crypto'
import * as ethUtil from 'ethereumjs-util'
import Wallet = require('./index')
import Wallet from './index'
const scryptsy = require('scrypt.js')
const utf8 = require('utf8')
@ -95,12 +95,30 @@ function decodeCryptojsSalt(input: string): { ciphertext: Buffer; salt?: Buffer
return { ciphertext }
}
// {
// "address": "0x169aab499b549eac087035e640d3f7d882ef5e2d",
// "encrypted": true,
// "locked": true,
// "hash": "342f636d174cc1caa49ce16e5b257877191b663e0af0271d2ea03ac7e139317d",
// "private": "U2FsdGVkX19ZrornRBIfl1IDdcj6S9YywY8EgOeOtLj2DHybM/CHL4Jl0jcwjT+36kDnjj+qEfUBu6J1mGQF/fNcD/TsAUgGUTEUEOsP1CKDvNHfLmWLIfxqnYHhHsG5",
// "public": "U2FsdGVkX19EaDNK52q7LEz3hL/VR3dYW5VcoP04tcVKNS0Q3JINpM4XzttRJCBtq4g22hNDrOR8RWyHuh3nPo0pRSe9r5AUfEiCLaMBAhI16kf2KqCA8ah4brkya9ZLECdIl0HDTMYfDASBnyNXd87qodt46U0vdRT3PppK+9hsyqP8yqm9kFcWqMHktqubBE937LIU0W22Rfw6cJRwIw=="
// }
interface EtherWalletOptions {
address: string
encrypted: boolean
locked: boolean
hash: string
private: string
public: string
}
/*
* This wallet format is created by https://github.com/SilentCicero/ethereumjs-accounts
* and used on https://www.myetherwallet.com/
*/
function fromEtherWallet(input: string | Object, password: string): Wallet {
const json = typeof input === 'object' ? input : JSON.parse(input)
function fromEtherWallet(input: string | EtherWalletOptions, password: string): Wallet {
const json: EtherWalletOptions = typeof input === 'object' ? input : JSON.parse(input)
let privateKey: Buffer
if (!json.locked) {
@ -109,16 +127,20 @@ function fromEtherWallet(input: string | Object, password: string): Wallet {
}
privateKey = Buffer.from(json.private, 'hex')
} else {
if (typeof password !== 'string') {
throw new Error('Password required')
}
if (password.length < 7) {
throw new Error('Password must be at least 7 characters')
}
// the "encrypted" version has the low 4 bytes
// of the hash of the address appended
let cipher = json.encrypted ? json.private.slice(0, 128) : json.private
const hash = json.encrypted ? json.private.slice(0, 128) : json.private
// decode openssl ciphertext + salt encoding
cipher = decodeCryptojsSalt(cipher)
const cipher = decodeCryptojsSalt(hash)
if (!cipher.salt) {
throw new Error('Unsupported EtherWallet key format')
}
@ -183,6 +205,10 @@ function fromKryptoKit(entropy: string, password: string): Wallet {
if (type === 'd') {
privateKey = ethUtil.sha256(entropy)
} else if (type === 'q') {
if (typeof password !== 'string') {
throw new Error('Password required')
}
const encryptedSeed = ethUtil.sha256(Buffer.from(entropy.slice(0, 30)))
const checksum = entropy.slice(30, 46)
@ -246,4 +272,4 @@ const Thirdparty = {
fromQuorumWallet,
}
export = Thirdparty
export default Thirdparty

View File

@ -1,5 +1,5 @@
import * as assert from 'assert'
import EthereumHDKey = require('../src/hdkey')
import EthereumHDKey from '../src/hdkey'
// from BIP39 mnemonic: awake book subject inch gentle blur grant damage process float month clown
const fixtureseed = Buffer.from(

View File

@ -2,8 +2,8 @@
import * as assert from 'assert'
import * as ethUtil from 'ethereumjs-util'
import Wallet = require('../src')
import Thirdparty = require('../src/thirdparty')
import Wallet from '../src'
import Thirdparty from '../src/thirdparty'
const fixturePrivateKey = 'efca4cdd31923b50f4214af5d2ae10e7ac45a5019e9431cc195482d707485378'
const fixturePrivateKeyStr = '0x' + fixturePrivateKey