Skip to content

Commit

Permalink
Merge pull request #125 from ethereumjs/use-scrypt-js
Browse files Browse the repository at this point in the history
Use scrypt-js
  • Loading branch information
holgerd77 authored May 27, 2020
2 parents 59eb071 + 110c3b5 commit 626e720
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 151 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# ethereumjs-wallet

[![NPM Package](https://img.shields.io/npm/v/ethereumjs-wallet.svg?style=flat-square)](https://www.npmjs.org/package/ethereumjs-wallet)
[![NPM Package](https://img.shields.io/npm/v/ethereumjs-wallet.svg)](https://www.npmjs.org/package/ethereumjs-wallet)
[![Actions Status](https://github.com/ethereumjs/ethereumjs-wallet/workflows/Build/badge.svg)](https://github.com/ethereumjs/ethereumjs-wallet/actions)
[![Coverage Status](https://img.shields.io/coveralls/ethereumjs/ethereumjs-wallet.svg?style=flat-square)](https://coveralls.io/r/ethereumjs/ethereumjs-wallet)
[![Gitter](https://img.shields.io/gitter/room/ethereum/ethereumjs-lib.svg?style=flat-square)](https://gitter.im/ethereum/ethereumjs-lib) or #ethereumjs on freenode
[![Coverage Status](https://img.shields.io/coveralls/ethereumjs/ethereumjs-wallet.svg)](https://coveralls.io/r/ethereumjs/ethereumjs-wallet)
[![Gitter](https://img.shields.io/gitter/room/ethereum/ethereumjs-lib.svg)](https://gitter.im/ethereum/ethereumjs-lib)

A lightweight wallet implementation. At the moment it supports key creation and conversion between various formats.

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"format:fix": "ethereumjs-config-format-fix",
"lint": "ethereumjs-config-lint",
"lint:fix": "ethereumjs-config-lint-fix",
"test": "npm run build && nyc --reporter=lcov mocha ./test/**/*.ts",
"test": "npm run build && nyc --reporter=lcov mocha --require ts-node/register --require source-map-support/register ./test/**/*.ts",
"test:browser": "karma start karma.conf.js",
"tsc": "ethereumjs-config-tsc",
"tslint": "ethereumjs-config-tslint",
Expand All @@ -43,12 +43,12 @@
},
"homepage": "https://github.com/ethereumjs/ethereumjs-wallet",
"dependencies": {
"@web3-js/scrypt-shim": "^0.1.0",
"aes-js": "^3.1.1",
"bs58check": "^2.1.2",
"ethereumjs-util": "^7.0.1",
"hdkey": "^1.1.1",
"randombytes": "^2.0.6",
"scrypt-js": "^3.0.1",
"utf8": "^3.0.0",
"uuid": "^3.3.2"
},
Expand Down
79 changes: 44 additions & 35 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import * as crypto from 'crypto'
import * as ethUtil from 'ethereumjs-util'
import {
BN,
keccak256,
bufferToHex,
privateToAddress,
publicToAddress,
toChecksumAddress,
privateToPublic,
importPublic,
isValidPrivate,
isValidPublic,
} from 'ethereumjs-util'
import { scrypt } from 'scrypt-js'

export { default as hdkey } from './hdkey'
export { default as thirdparty } from './thirdparty'

const bs58check = require('bs58check')
const randomBytes = require('randombytes')
const scryptsy = require('@web3-js/scrypt-shim')
const uuidv4 = require('uuid/v4')

// parameters for the toV3() method
Expand Down Expand Up @@ -235,11 +246,11 @@ export default class Wallet {
throw new Error('Cannot supply both a private and a public key to the constructor')
}

if (privateKey && !ethUtil.isValidPrivate(privateKey)) {
if (privateKey && !isValidPrivate(privateKey)) {
throw new Error('Private key does not satisfy the curve requirements (ie. it is invalid)')
}

if (publicKey && !ethUtil.isValidPublic(publicKey)) {
if (publicKey && !isValidPublic(publicKey)) {
throw new Error('Invalid public key')
}
}
Expand All @@ -253,10 +264,10 @@ export default class Wallet {
*/
public static generate(icapDirect: boolean = false): Wallet {
if (icapDirect) {
const max = new ethUtil.BN('088f924eeceeda7fe92e1f5b0fffffffffffffff', 16)
const max = new BN('088f924eeceeda7fe92e1f5b0fffffffffffffff', 16)
while (true) {
const privateKey = randomBytes(32) as Buffer
if (new ethUtil.BN(ethUtil.privateToAddress(privateKey)).lte(max)) {
if (new BN(privateToAddress(privateKey)).lte(max)) {
return new Wallet(privateKey)
}
}
Expand All @@ -275,7 +286,7 @@ export default class Wallet {

while (true) {
const privateKey = randomBytes(32) as Buffer
const address = ethUtil.privateToAddress(privateKey)
const address = privateToAddress(privateKey)

if (pattern.test(address.toString('hex'))) {
return new Wallet(privateKey)
Expand All @@ -291,7 +302,7 @@ export default class Wallet {
*/
public static fromPublicKey(publicKey: Buffer, nonStrict: boolean = false): Wallet {
if (nonStrict) {
publicKey = ethUtil.importPublic(publicKey)
publicKey = importPublic(publicKey)
}
return new Wallet(undefined, publicKey)
}
Expand Down Expand Up @@ -335,7 +346,7 @@ export default class Wallet {
* @param input A JSON serialized string, or an object representing V1 Keystore.
* @param password The keystore password.
*/
public static fromV1(input: string | V1Keystore, password: string): Wallet {
public static async fromV1(input: string | V1Keystore, password: string): Promise<Wallet> {
const json: V1Keystore = typeof input === 'object' ? input : JSON.parse(input)
if (json.Version !== '1') {
throw new Error('Not a V1 Wallet')
Expand All @@ -345,24 +356,24 @@ export default class Wallet {
}

const kdfparams = json.Crypto.KeyHeader.KdfParams
const derivedKey = scryptsy(
const derivedKey = await scrypt(
Buffer.from(password),
Buffer.from(json.Crypto.Salt, 'hex'),
kdfparams.N,
kdfparams.R,
kdfparams.P,
kdfparams.DkLen,
) as Buffer
)

const ciphertext = Buffer.from(json.Crypto.CipherText, 'hex')
const mac = ethUtil.keccak256(Buffer.concat([derivedKey.slice(16, 32), ciphertext]))
const mac = keccak256(Buffer.concat([derivedKey.slice(16, 32), ciphertext]))
if (mac.toString('hex') !== json.Crypto.MAC) {
throw new Error('Key derivation failed - possibly wrong passphrase')
}

const decipher = crypto.createDecipheriv(
'aes-128-cbc',
ethUtil.keccak256(derivedKey.slice(0, 16)).slice(0, 16),
keccak256(derivedKey.slice(0, 16) as Buffer).slice(0, 16),
Buffer.from(json.Crypto.IV, 'hex'),
)
const seed = runCipherBuffer(decipher, ciphertext)
Expand All @@ -375,31 +386,31 @@ export default class Wallet {
* @param input A JSON serialized string, or an object representing V3 Keystore.
* @param password The keystore password.
*/
public static fromV3(
public static async fromV3(
input: string | V3Keystore,
password: string,
nonStrict: boolean = false,
): Wallet {
): Promise<Wallet> {
const json: V3Keystore =
typeof input === 'object' ? input : JSON.parse(nonStrict ? input.toLowerCase() : input)

if (json.version !== 3) {
throw new Error('Not a V3 wallet')
}

let derivedKey: Buffer, kdfparams: any
let derivedKey: Uint8Array, kdfparams: any
if (json.crypto.kdf === 'scrypt') {
kdfparams = json.crypto.kdfparams

// FIXME: support progress reporting callback
derivedKey = scryptsy(
derivedKey = await scrypt(
Buffer.from(password),
Buffer.from(kdfparams.salt, 'hex'),
kdfparams.n,
kdfparams.r,
kdfparams.p,
kdfparams.dklen,
) as Buffer
)
} else if (json.crypto.kdf === 'pbkdf2') {
kdfparams = json.crypto.kdfparams

Expand All @@ -419,7 +430,7 @@ export default class Wallet {
}

const ciphertext = Buffer.from(json.crypto.ciphertext, 'hex')
const mac = ethUtil.keccak256(Buffer.concat([derivedKey.slice(16, 32), ciphertext]))
const mac = keccak256(Buffer.concat([derivedKey.slice(16, 32), ciphertext]))
if (mac.toString('hex') !== json.crypto.mac) {
throw new Error('Key derivation failed - possibly wrong passphrase')
}
Expand Down Expand Up @@ -455,7 +466,7 @@ export default class Wallet {
const decipher = crypto.createDecipheriv('aes-128-cbc', derivedKey, encseed.slice(0, 16))
const seed = runCipherBuffer(decipher, encseed.slice(16))

const wallet = new Wallet(ethUtil.keccak256(seed))
const wallet = new Wallet(keccak256(seed))
if (wallet.getAddress().toString('hex') !== json.ethaddr) {
throw new Error('Decoded key mismatch - possibly wrong passphrase')
}
Expand All @@ -469,7 +480,7 @@ export default class Wallet {
*/
private get pubKey(): Buffer {
if (!keyExists(this.publicKey)) {
this.publicKey = ethUtil.privateToPublic(this.privateKey as Buffer)
this.publicKey = privateToPublic(this.privateKey as Buffer)
}
return this.publicKey
}
Expand All @@ -496,7 +507,7 @@ export default class Wallet {
}

public getPrivateKeyString(): string {
return ethUtil.bufferToHex(this.privKey)
return bufferToHex(this.privKey)
}

/**
Expand All @@ -511,29 +522,29 @@ export default class Wallet {
* Returns the wallet's public key as a "0x" prefixed hex string
*/
public getPublicKeyString(): string {
return ethUtil.bufferToHex(this.getPublicKey())
return bufferToHex(this.getPublicKey())
}

/**
* Returns the wallet's address.
*/
public getAddress(): Buffer {
return ethUtil.publicToAddress(this.pubKey)
return publicToAddress(this.pubKey)
}

/**
* Returns the wallet's address as a "0x" prefixed hex string
*/
public getAddressString(): string {
return ethUtil.bufferToHex(this.getAddress())
return bufferToHex(this.getAddress())
}

/**
* Returns the wallet's private key as a "0x" prefixed hex string checksummed
* according to [EIP 55](https://github.com/ethereum/EIPs/issues/55).
*/
public getChecksumAddressString(): string {
return ethUtil.toChecksumAddress(this.getAddressString())
return toChecksumAddress(this.getAddressString())
}

/**
Expand All @@ -542,15 +553,15 @@ export default class Wallet {
* @param password The password used to encrypt the Keystore.
* @param opts The options for the keystore. See [its spec](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) for more info.
*/
public toV3(password: string, opts?: Partial<V3Params>): V3Keystore {
public async toV3(password: string, opts?: Partial<V3Params>): Promise<V3Keystore> {
if (!keyExists(this.privateKey)) {
throw new Error('This is a public key only wallet')
}

const v3Params: V3ParamsStrict = mergeToV3ParamsWithDefaults(opts)

let kdfParams: KDFParams
let derivedKey: Buffer
let derivedKey: Uint8Array
switch (v3Params.kdf) {
case KDFFunctions.PBKDF:
kdfParams = kdfParamsForPBKDF(v3Params)
Expand All @@ -565,14 +576,14 @@ export default class Wallet {
case KDFFunctions.Scrypt:
kdfParams = kdfParamsForScrypt(v3Params)
// FIXME: support progress reporting callback
derivedKey = scryptsy(
derivedKey = await scrypt(
Buffer.from(password),
kdfParams.salt,
kdfParams.n,
kdfParams.r,
kdfParams.p,
kdfParams.dklen,
) as Buffer
)
break
default:
throw new Error('Unsupported kdf')
Expand All @@ -588,9 +599,7 @@ export default class Wallet {
}

const ciphertext = runCipherBuffer(cipher, this.privKey)
const mac = ethUtil.keccak256(
Buffer.concat([derivedKey.slice(16, 32), Buffer.from(ciphertext)]),
)
const mac = keccak256(Buffer.concat([derivedKey.slice(16, 32), Buffer.from(ciphertext)]))

return {
version: 3,
Expand Down Expand Up @@ -632,8 +641,8 @@ export default class Wallet {
)
}

public toV3String(password: string, opts?: Partial<V3Params>): string {
return JSON.stringify(this.toV3(password, opts))
public async toV3String(password: string, opts?: Partial<V3Params>): Promise<string> {
return JSON.stringify(await this.toV3(password, opts))
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/thirdparty.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as crypto from 'crypto'
import { keccak256, sha256, toBuffer } from 'ethereumjs-util'
import { scrypt } from 'scrypt-js'

import Wallet from './index'

const scryptsy = require('@web3-js/scrypt-shim')
const utf8 = require('utf8')
const aesjs = require('aes-js')

Expand Down Expand Up @@ -173,7 +173,7 @@ export function fromEtherCamp(passphrase: string): Wallet {
/**
* Third Party API: Import a wallet from a KryptoKit seed
*/
export function fromKryptoKit(entropy: string, password: string): Wallet {
export async function fromKryptoKit(entropy: string, password: string): Promise<Wallet> {
function kryptoKitBrokenScryptSeed(buf: Buffer) {
// js-scrypt calls `Buffer.from(String(salt), 'utf8')` on the seed even though it is a buffer
//
Expand Down Expand Up @@ -220,7 +220,7 @@ export function fromKryptoKit(entropy: string, password: string): Wallet {
const checksum = entropy.slice(30, 46)

const salt = kryptoKitBrokenScryptSeed(encryptedSeed)
const aesKey = scryptsy(Buffer.from(password, 'utf8'), salt, 16384, 8, 1, 32)
const aesKey = await scrypt(Buffer.from(password, 'utf8'), salt, 16384, 8, 1, 32)

/* FIXME: try to use `crypto` instead of `aesjs`
Expand Down
Loading

0 comments on commit 626e720

Please sign in to comment.