PR review fixes
This commit is contained in:
parent
bca01ea69f
commit
288442924e
|
@ -1,8 +1,8 @@
|
||||||
import Wallet = require('./index')
|
import Wallet from './index'
|
||||||
|
|
||||||
const HDKey = require('hdkey')
|
const HDKey = require('hdkey')
|
||||||
|
|
||||||
class EthereumHDKey {
|
export default class EthereumHDKey {
|
||||||
public static fromMasterSeed(seedBuffer: Buffer): EthereumHDKey {
|
public static fromMasterSeed(seedBuffer: Buffer): EthereumHDKey {
|
||||||
return new EthereumHDKey(HDKey.fromMasterSeed(seedBuffer))
|
return new EthereumHDKey(HDKey.fromMasterSeed(seedBuffer))
|
||||||
}
|
}
|
||||||
|
@ -39,5 +39,3 @@ class EthereumHDKey {
|
||||||
return Wallet.fromPublicKey(this._hdkey._publicKey, true)
|
return Wallet.fromPublicKey(this._hdkey._publicKey, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = EthereumHDKey
|
|
||||||
|
|
166
src/index.ts
166
src/index.ts
|
@ -52,22 +52,48 @@ function mergeToV3ParamsWithDefaults(params?: Partial<V3Params>): V3Params {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface KDFParams {
|
// KDF params
|
||||||
c: number
|
|
||||||
prf: string
|
interface ScryptKDFParams {
|
||||||
dklen: number
|
dklen: number
|
||||||
n: number
|
n: number
|
||||||
r: number
|
|
||||||
p: number
|
p: number
|
||||||
|
r: number
|
||||||
salt: string
|
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
|
* Based on the parameter list passed to the Wallet.prototype.toV3() method this
|
||||||
* returns a list of parameters for running the key derivation function.
|
* returns a list of parameters for running the key derivation function.
|
||||||
* @param params params passed into the .toV3() method
|
* @param params params passed into the .toV3() method
|
||||||
*/
|
*/
|
||||||
function mergeKDFParamsWithDefaults(params: V3Params): KDFParams {
|
function mergeKDFParamsWithDefaults(params: V3Params): AllKDFParams {
|
||||||
const kdfDefaults = {
|
const kdfDefaults = {
|
||||||
c: 262144,
|
c: 262144,
|
||||||
prf: 'hmac-sha256',
|
prf: 'hmac-sha256',
|
||||||
|
@ -84,24 +110,79 @@ function mergeKDFParamsWithDefaults(params: V3Params): KDFParams {
|
||||||
prf: kdfDefaults.prf,
|
prf: kdfDefaults.prf,
|
||||||
n: params.n || kdfDefaults.n,
|
n: params.n || kdfDefaults.n,
|
||||||
r: params.r || kdfDefaults.r,
|
r: params.r || kdfDefaults.r,
|
||||||
p: params.p || kdfDefaults.c,
|
p: params.p || kdfDefaults.p,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stripUnusedKDFParamsForPBKDF2(params: KDFParams): Partial<KDFParams> {
|
// JSON keystore types
|
||||||
delete params.n
|
|
||||||
delete params.r
|
// https://github.com/ethereum/homestead-guide/blob/master/old-docs-for-reference/go-ethereum-wiki.rst/Passphrase-protected-key-store-spec.rst
|
||||||
delete params.p
|
interface V1Keystore {
|
||||||
return params
|
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> {
|
// https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
|
||||||
delete params.c
|
interface V3Keystore {
|
||||||
delete params.prf
|
crypto: {
|
||||||
return params
|
cipher: string
|
||||||
|
cipherparams: {
|
||||||
|
iv: string
|
||||||
|
}
|
||||||
|
ciphertext: string
|
||||||
|
kdf: string
|
||||||
|
kdfparams: KDFParams
|
||||||
|
mac: string
|
||||||
|
}
|
||||||
|
id: string
|
||||||
|
version: number
|
||||||
}
|
}
|
||||||
|
|
||||||
class Wallet {
|
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')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// static methods
|
// static methods
|
||||||
|
|
||||||
public static generate(icapDirect: boolean = false): Wallet {
|
public static generate(icapDirect: boolean = false): Wallet {
|
||||||
|
@ -164,10 +245,8 @@ class Wallet {
|
||||||
return Wallet.fromPrivateKey(tmp.slice(46))
|
return Wallet.fromPrivateKey(tmp.slice(46))
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/ethereum/go-ethereum/wiki/Passphrase-protected-key-store-spec
|
public static fromV1(input: string | V1Keystore, password: string): Wallet {
|
||||||
public static fromV1(input: string | Object, password: string): Wallet {
|
const json: V1Keystore = typeof input === 'object' ? input : JSON.parse(input)
|
||||||
const json = typeof input === 'object' ? input : JSON.parse(input)
|
|
||||||
|
|
||||||
if (json.Version !== '1') {
|
if (json.Version !== '1') {
|
||||||
throw new Error('Not a V1 Wallet')
|
throw new Error('Not a V1 Wallet')
|
||||||
}
|
}
|
||||||
|
@ -187,7 +266,6 @@ class Wallet {
|
||||||
|
|
||||||
const ciphertext = Buffer.from(json.Crypto.CipherText, 'hex')
|
const ciphertext = Buffer.from(json.Crypto.CipherText, 'hex')
|
||||||
const mac = ethUtil.keccak256(Buffer.concat([derivedKey.slice(16, 32), ciphertext]))
|
const mac = ethUtil.keccak256(Buffer.concat([derivedKey.slice(16, 32), ciphertext]))
|
||||||
|
|
||||||
if (mac.toString('hex') !== json.Crypto.MAC) {
|
if (mac.toString('hex') !== json.Crypto.MAC) {
|
||||||
throw new Error('Key derivation failed - possibly wrong passphrase')
|
throw new Error('Key derivation failed - possibly wrong passphrase')
|
||||||
}
|
}
|
||||||
|
@ -198,16 +276,15 @@ class Wallet {
|
||||||
Buffer.from(json.Crypto.IV, 'hex'),
|
Buffer.from(json.Crypto.IV, 'hex'),
|
||||||
)
|
)
|
||||||
const seed = runCipherBuffer(decipher, ciphertext)
|
const seed = runCipherBuffer(decipher, ciphertext)
|
||||||
|
|
||||||
return new Wallet(seed)
|
return new Wallet(seed)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static fromV3(
|
public static fromV3(
|
||||||
input: string | Object,
|
input: string | V3Keystore,
|
||||||
password: string,
|
password: string,
|
||||||
nonStrict: boolean = false,
|
nonStrict: boolean = false,
|
||||||
): Wallet {
|
): Wallet {
|
||||||
const json =
|
const json: V3Keystore =
|
||||||
typeof input === 'object' ? input : JSON.parse(nonStrict ? input.toLowerCase() : input)
|
typeof input === 'object' ? input : JSON.parse(nonStrict ? input.toLowerCase() : input)
|
||||||
|
|
||||||
if (json.version !== 3) {
|
if (json.version !== 3) {
|
||||||
|
@ -264,8 +341,8 @@ class Wallet {
|
||||||
* Based on https://github.com/ethereum/pyethsaletool/blob/master/pyethsaletool.py
|
* Based on https://github.com/ethereum/pyethsaletool/blob/master/pyethsaletool.py
|
||||||
* JSON fields: encseed, ethaddr, btcaddr, email
|
* JSON fields: encseed, ethaddr, btcaddr, email
|
||||||
*/
|
*/
|
||||||
public static fromEthSale(input: string | Object, password: string): Wallet {
|
public static fromEthSale(input: string | EthSaleKeystore, password: string): Wallet {
|
||||||
const json = typeof input === 'object' ? input : JSON.parse(input)
|
const json: EthSaleKeystore = typeof input === 'object' ? input : JSON.parse(input)
|
||||||
|
|
||||||
const encseed = Buffer.from(json.encseed, 'hex')
|
const encseed = Buffer.from(json.encseed, 'hex')
|
||||||
|
|
||||||
|
@ -301,26 +378,6 @@ class Wallet {
|
||||||
return this.privateKey
|
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 instance methods
|
||||||
|
|
||||||
public getPrivateKey(): Buffer {
|
public getPrivateKey(): Buffer {
|
||||||
|
@ -351,7 +408,7 @@ class Wallet {
|
||||||
return ethUtil.toChecksumAddress(this.getAddressString())
|
return ethUtil.toChecksumAddress(this.getAddressString())
|
||||||
}
|
}
|
||||||
|
|
||||||
public toV3(password: string, opts?: Partial<V3Params>) {
|
public toV3(password: string, opts?: Partial<V3Params>): V3Keystore {
|
||||||
if (!keyExists(this.privateKey)) {
|
if (!keyExists(this.privateKey)) {
|
||||||
throw new Error('This is a public key only wallet')
|
throw new Error('This is a public key only wallet')
|
||||||
}
|
}
|
||||||
|
@ -359,7 +416,9 @@ class Wallet {
|
||||||
const params = mergeToV3ParamsWithDefaults(opts)
|
const params = mergeToV3ParamsWithDefaults(opts)
|
||||||
const kdfParams = mergeKDFParamsWithDefaults(params)
|
const kdfParams = mergeKDFParamsWithDefaults(params)
|
||||||
|
|
||||||
let derivedKey: Buffer, finalKDFParams: Partial<KDFParams>
|
let derivedKey: Buffer
|
||||||
|
let finalKDFParams: KDFParams
|
||||||
|
|
||||||
if (params.kdf === 'pbkdf2') {
|
if (params.kdf === 'pbkdf2') {
|
||||||
derivedKey = crypto.pbkdf2Sync(
|
derivedKey = crypto.pbkdf2Sync(
|
||||||
Buffer.from(password),
|
Buffer.from(password),
|
||||||
|
@ -368,7 +427,7 @@ class Wallet {
|
||||||
kdfParams.dklen,
|
kdfParams.dklen,
|
||||||
'sha256',
|
'sha256',
|
||||||
)
|
)
|
||||||
finalKDFParams = stripUnusedKDFParamsForPBKDF2(kdfParams)
|
finalKDFParams = kdfParamsForPBKDF(kdfParams)
|
||||||
} else if (params.kdf === 'scrypt') {
|
} else if (params.kdf === 'scrypt') {
|
||||||
// FIXME: support progress reporting callback
|
// FIXME: support progress reporting callback
|
||||||
derivedKey = scryptsy(
|
derivedKey = scryptsy(
|
||||||
|
@ -379,7 +438,7 @@ class Wallet {
|
||||||
kdfParams.p,
|
kdfParams.p,
|
||||||
kdfParams.dklen,
|
kdfParams.dklen,
|
||||||
)
|
)
|
||||||
finalKDFParams = stripUnusedKDFParamsForScrypt(kdfParams)
|
finalKDFParams = kdfParamsForScrypt(kdfParams)
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unsupported kdf')
|
throw new Error('Unsupported kdf')
|
||||||
}
|
}
|
||||||
|
@ -401,6 +460,7 @@ class Wallet {
|
||||||
return {
|
return {
|
||||||
version: 3,
|
version: 3,
|
||||||
id: uuidv4({ random: params.uuid }),
|
id: uuidv4({ random: params.uuid }),
|
||||||
|
// @ts-ignore FIXME: official V3 keystore spec omits the address key
|
||||||
address: this.getAddress().toString('hex'),
|
address: this.getAddress().toString('hex'),
|
||||||
crypto: {
|
crypto: {
|
||||||
ciphertext: ciphertext.toString('hex'),
|
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()])
|
return Buffer.concat([cipher.update(data), cipher.final()])
|
||||||
}
|
}
|
||||||
|
|
||||||
function keyExists(k: Buffer | undefined): k is Buffer {
|
function keyExists(k: Buffer | undefined | null): k is Buffer {
|
||||||
return k !== undefined
|
return k !== undefined && k !== null
|
||||||
}
|
}
|
||||||
|
|
||||||
export = Wallet
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import Wallet = require('./index')
|
import Wallet from './index'
|
||||||
|
|
||||||
const HookedWalletEthTxSubprovider = require('web3-provider-engine/subproviders/hooked-wallet-ethtx')
|
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) {
|
constructor(wallet: Wallet, opts?: any) {
|
||||||
if (!opts) {
|
if (!opts) {
|
||||||
opts = {}
|
opts = {}
|
||||||
|
@ -20,5 +20,3 @@ class WalletSubprovider extends HookedWalletEthTxSubprovider {
|
||||||
super(opts)
|
super(opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export = WalletSubprovider
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as crypto from 'crypto'
|
import * as crypto from 'crypto'
|
||||||
import * as ethUtil from 'ethereumjs-util'
|
import * as ethUtil from 'ethereumjs-util'
|
||||||
|
|
||||||
import Wallet = require('./index')
|
import Wallet from './index'
|
||||||
|
|
||||||
const scryptsy = require('scrypt.js')
|
const scryptsy = require('scrypt.js')
|
||||||
const utf8 = require('utf8')
|
const utf8 = require('utf8')
|
||||||
|
@ -95,12 +95,30 @@ function decodeCryptojsSalt(input: string): { ciphertext: Buffer; salt?: Buffer
|
||||||
return { ciphertext }
|
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
|
* This wallet format is created by https://github.com/SilentCicero/ethereumjs-accounts
|
||||||
* and used on https://www.myetherwallet.com/
|
* and used on https://www.myetherwallet.com/
|
||||||
*/
|
*/
|
||||||
function fromEtherWallet(input: string | Object, password: string): Wallet {
|
function fromEtherWallet(input: string | EtherWalletOptions, password: string): Wallet {
|
||||||
const json = typeof input === 'object' ? input : JSON.parse(input)
|
const json: EtherWalletOptions = typeof input === 'object' ? input : JSON.parse(input)
|
||||||
|
|
||||||
let privateKey: Buffer
|
let privateKey: Buffer
|
||||||
if (!json.locked) {
|
if (!json.locked) {
|
||||||
|
@ -109,16 +127,20 @@ function fromEtherWallet(input: string | Object, password: string): Wallet {
|
||||||
}
|
}
|
||||||
privateKey = Buffer.from(json.private, 'hex')
|
privateKey = Buffer.from(json.private, 'hex')
|
||||||
} else {
|
} else {
|
||||||
|
if (typeof password !== 'string') {
|
||||||
|
throw new Error('Password required')
|
||||||
|
}
|
||||||
|
|
||||||
if (password.length < 7) {
|
if (password.length < 7) {
|
||||||
throw new Error('Password must be at least 7 characters')
|
throw new Error('Password must be at least 7 characters')
|
||||||
}
|
}
|
||||||
|
|
||||||
// the "encrypted" version has the low 4 bytes
|
// the "encrypted" version has the low 4 bytes
|
||||||
// of the hash of the address appended
|
// 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
|
// decode openssl ciphertext + salt encoding
|
||||||
cipher = decodeCryptojsSalt(cipher)
|
const cipher = decodeCryptojsSalt(hash)
|
||||||
if (!cipher.salt) {
|
if (!cipher.salt) {
|
||||||
throw new Error('Unsupported EtherWallet key format')
|
throw new Error('Unsupported EtherWallet key format')
|
||||||
}
|
}
|
||||||
|
@ -183,6 +205,10 @@ function fromKryptoKit(entropy: string, password: string): Wallet {
|
||||||
if (type === 'd') {
|
if (type === 'd') {
|
||||||
privateKey = ethUtil.sha256(entropy)
|
privateKey = ethUtil.sha256(entropy)
|
||||||
} else if (type === 'q') {
|
} else if (type === 'q') {
|
||||||
|
if (typeof password !== 'string') {
|
||||||
|
throw new Error('Password required')
|
||||||
|
}
|
||||||
|
|
||||||
const encryptedSeed = ethUtil.sha256(Buffer.from(entropy.slice(0, 30)))
|
const encryptedSeed = ethUtil.sha256(Buffer.from(entropy.slice(0, 30)))
|
||||||
const checksum = entropy.slice(30, 46)
|
const checksum = entropy.slice(30, 46)
|
||||||
|
|
||||||
|
@ -246,4 +272,4 @@ const Thirdparty = {
|
||||||
fromQuorumWallet,
|
fromQuorumWallet,
|
||||||
}
|
}
|
||||||
|
|
||||||
export = Thirdparty
|
export default Thirdparty
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as assert from 'assert'
|
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
|
// from BIP39 mnemonic: awake book subject inch gentle blur grant damage process float month clown
|
||||||
const fixtureseed = Buffer.from(
|
const fixtureseed = Buffer.from(
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
import * as assert from 'assert'
|
import * as assert from 'assert'
|
||||||
import * as ethUtil from 'ethereumjs-util'
|
import * as ethUtil from 'ethereumjs-util'
|
||||||
|
|
||||||
import Wallet = require('../src')
|
import Wallet from '../src'
|
||||||
import Thirdparty = require('../src/thirdparty')
|
import Thirdparty from '../src/thirdparty'
|
||||||
|
|
||||||
const fixturePrivateKey = 'efca4cdd31923b50f4214af5d2ae10e7ac45a5019e9431cc195482d707485378'
|
const fixturePrivateKey = 'efca4cdd31923b50f4214af5d2ae10e7ac45a5019e9431cc195482d707485378'
|
||||||
const fixturePrivateKeyStr = '0x' + fixturePrivateKey
|
const fixturePrivateKeyStr = '0x' + fixturePrivateKey
|
||||||
|
|
Loading…
Reference in New Issue