Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use scrypt-js #125

Merged
merged 2 commits into from
May 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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