@@ -10614,7 +10614,11 @@ var hmac = require('ethers-utils/hmac');
1061410614var pbkdf2 = require ( 'ethers-utils/pbkdf2' ) ;
1061510615var utils = require ( 'ethers-utils' ) ;
1061610616
10617- var SigningKey = require ( './signing-key.js' ) ;
10617+ var SigningKey = require ( './signing-key' ) ;
10618+ var HDNode = require ( './hdnode' ) ;
10619+
10620+ // @TODO : Maybe move this to HDNode?
10621+ var defaultPath = "m/44'/60'/0'/0/0" ;
1061810622
1061910623function arrayify ( hexString ) {
1062010624 if ( typeof ( hexString ) === 'string' && hexString . substring ( 0 , 2 ) !== '0x' ) {
@@ -10623,6 +10627,12 @@ function arrayify(hexString) {
1062310627 return utils . arrayify ( hexString ) ;
1062410628}
1062510629
10630+ function zpad ( value , length ) {
10631+ value = String ( value ) ;
10632+ while ( value . length < length ) { value = '0' + value ; }
10633+ return value ;
10634+ }
10635+
1062610636function getPassword ( password ) {
1062710637 if ( typeof ( password ) === 'string' ) {
1062810638 return utils . toUtf8Bytes ( password , 'NFKC' ) ;
@@ -10739,7 +10749,7 @@ utils.defineProperty(secretStorage, 'decrypt', function(json, password, progress
1073910749
1074010750 var aesCtr = new aes . ModeOfOperation . ctr ( key , counter ) ;
1074110751
10742- return new arrayify ( aesCtr . decrypt ( ciphertext ) ) ;
10752+ return arrayify ( aesCtr . decrypt ( ciphertext ) ) ;
1074310753 }
1074410754
1074510755 return null ;
@@ -10759,6 +10769,7 @@ utils.defineProperty(secretStorage, 'decrypt', function(json, password, progress
1075910769 }
1076010770
1076110771 var privateKey = decrypt ( key . slice ( 0 , 16 ) , ciphertext ) ;
10772+ var mnemonicKey = key . slice ( 32 , 64 ) ;
1076210773
1076310774 if ( ! privateKey ) {
1076410775 reject ( new Error ( 'unsupported cipher' ) ) ;
@@ -10771,6 +10782,28 @@ utils.defineProperty(secretStorage, 'decrypt', function(json, password, progress
1077110782 return null ;
1077210783 }
1077310784
10785+ // Version 0.1 x-ethers metadata must contain an encrypted mnemonic phrase
10786+ if ( searchPath ( data , 'x-ethers/version' ) === '0.1' ) {
10787+ var mnemonicCiphertext = arrayify ( searchPath ( data , 'x-ethers/mnemonicCiphertext' ) , 'x-ethers/mnemonicCiphertext' ) ;
10788+ var mnemonicIv = arrayify ( searchPath ( data , 'x-ethers/mnemonicCounter' ) , 'x-ethers/mnemonicCounter' ) ;
10789+
10790+ var mnemonicCounter = new aes . Counter ( mnemonicIv ) ;
10791+ var mnemonicAesCtr = new aes . ModeOfOperation . ctr ( mnemonicKey , mnemonicCounter ) ;
10792+
10793+ var path = searchPath ( data , 'x-ethers/path' ) || defaultPath ;
10794+
10795+ var entropy = arrayify ( mnemonicAesCtr . decrypt ( mnemonicCiphertext ) ) ;
10796+ var mnemonic = HDNode . entropyToMnemonic ( entropy ) ;
10797+
10798+ if ( HDNode . fromMnemonic ( mnemonic ) . derivePath ( path ) . privateKey != utils . hexlify ( privateKey ) ) {
10799+ reject ( new Error ( 'mnemonic mismatch' ) ) ;
10800+ return null ;
10801+ }
10802+
10803+ signingKey . mnemonic = mnemonic ;
10804+ signingKey . path = path ;
10805+ }
10806+
1077410807 return signingKey ;
1077510808 }
1077610809
@@ -10800,7 +10833,7 @@ utils.defineProperty(secretStorage, 'decrypt', function(json, password, progress
1080010833 return ;
1080110834 }
1080210835
10803- scrypt ( password , salt , N , r , p , dkLen , function ( error , progress , key ) {
10836+ scrypt ( password , salt , N , r , p , 64 , function ( error , progress , key ) {
1080410837 if ( error ) {
1080510838 error . progress = progress ;
1080610839 reject ( error ) ;
@@ -10871,11 +10904,33 @@ utils.defineProperty(secretStorage, 'encrypt', function(privateKey, password, op
1087110904 if ( privateKey instanceof SigningKey ) {
1087210905 privateKey = privateKey . privateKey ;
1087310906 }
10874- privateKey = utils . arrayify ( privateKey , 'private key' ) ;
10907+ privateKey = arrayify ( privateKey , 'private key' ) ;
1087510908 if ( privateKey . length !== 32 ) { throw new Error ( 'invalid private key' ) ; }
1087610909
1087710910 password = getPassword ( password ) ;
1087810911
10912+ var entropy = options . entropy ;
10913+ if ( options . mnemonic ) {
10914+ if ( entropy ) {
10915+ if ( HDNode . entropyToMnemonic ( entropy ) !== options . mnemonic ) {
10916+ throw new Error ( 'entropy and mnemonic mismatch' ) ;
10917+ }
10918+ } else {
10919+ entropy = HDNode . mnemonicToEntropy ( options . mnemonic ) ;
10920+ }
10921+ }
10922+ if ( entropy ) {
10923+ entropy = arrayify ( entropy , 'entropy' ) ;
10924+ }
10925+
10926+ var path = options . path ;
10927+ if ( entropy && ! path ) {
10928+ path = defaultPath ;
10929+ }
10930+
10931+ var client = options . client ;
10932+ if ( ! client ) { client = "ethers.js" ; }
10933+
1087910934 // Check/generate the salt
1088010935 var salt = options . salt ;
1088110936 if ( salt ) {
@@ -10889,13 +10944,17 @@ utils.defineProperty(secretStorage, 'encrypt', function(privateKey, password, op
1088910944 if ( options . iv ) {
1089010945 iv = arrayify ( options . iv , 'iv' ) ;
1089110946 if ( iv . length !== 16 ) { throw new Error ( 'invalid iv' ) ; }
10947+ } else {
10948+ iv = utils . randomBytes ( 16 ) ;
1089210949 }
1089310950
1089410951 // Override the uuid
1089510952 var uuidRandom = options . uuid ;
1089610953 if ( uuidRandom ) {
10897- uuidRandom = utils . arrayify ( uuidRandom , 'uuid' ) ;
10954+ uuidRandom = arrayify ( uuidRandom , 'uuid' ) ;
1089810955 if ( uuidRandom . length !== 16 ) { throw new Error ( 'invalid uuid' ) ; }
10956+ } else {
10957+ uuidRandom = utils . randomBytes ( 16 ) ;
1089910958 }
1090010959
1090110960 // Override the scrypt password-based key derivation function parameters
@@ -10910,8 +10969,7 @@ utils.defineProperty(secretStorage, 'encrypt', function(privateKey, password, op
1091010969
1091110970 // We take 64 bytes:
1091210971 // - 32 bytes As normal for the Web3 secret storage (derivedKey, macPrefix)
10913- // - 16 bytes The initialization vector
10914- // - 16 bytes The UUID random bytes
10972+ // - 32 bytes AES key to encrypt mnemonic with (required here to be Ethers Wallet)
1091510973 scrypt ( password , salt , N , r , p , 64 , function ( error , progress , key ) {
1091610974 if ( error ) {
1091710975 error . progress = progress ;
@@ -10920,15 +10978,12 @@ utils.defineProperty(secretStorage, 'encrypt', function(privateKey, password, op
1092010978 } else if ( key ) {
1092110979 key = arrayify ( key ) ;
1092210980
10923- // These will be used to encrypt the wallet (as per Web3 secret storage)
10981+ // This will be used to encrypt the wallet (as per Web3 secret storage)
1092410982 var derivedKey = key . slice ( 0 , 16 ) ;
1092510983 var macPrefix = key . slice ( 16 , 32 ) ;
1092610984
10927- // Get the initialization vector
10928- if ( ! iv ) { iv = key . slice ( 32 , 48 ) ; }
10929-
10930- // Get the UUID random data
10931- if ( ! uuidRandom ) { uuidRandom = key . slice ( 48 , 64 ) ; }
10985+ // This will be used to encrypt the mnemonic phrase (if any)
10986+ var mnemonicKey = key . slice ( 32 , 64 ) ;
1093210987
1093310988 // Get the address for this private key
1093410989 var address = ( new SigningKey ( privateKey ) ) . address ;
@@ -10944,7 +10999,7 @@ utils.defineProperty(secretStorage, 'encrypt', function(privateKey, password, op
1094410999 // See: https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
1094511000 var data = {
1094611001 address : address . substring ( 2 ) . toLowerCase ( ) ,
10947- id : uuid . v4 ( { random : uuidRandom } ) ,
11002+ id : uuid . v4 ( { random : uuidRandom } ) ,
1094811003 version : 3 ,
1094911004 Crypto : {
1095011005 cipher : 'aes-128-ctr' ,
@@ -10964,6 +11019,29 @@ utils.defineProperty(secretStorage, 'encrypt', function(privateKey, password, op
1096411019 }
1096511020 } ;
1096611021
11022+ // If we have a mnemonic, encrypt it into the JSON wallet
11023+ if ( entropy ) {
11024+ var mnemonicIv = utils . randomBytes ( 16 ) ;
11025+ var mnemonicCounter = new aes . Counter ( mnemonicIv ) ;
11026+ var mnemonicAesCtr = new aes . ModeOfOperation . ctr ( mnemonicKey , mnemonicCounter ) ;
11027+ var mnemonicCiphertext = utils . arrayify ( mnemonicAesCtr . encrypt ( entropy ) ) ;
11028+ var now = new Date ( ) ;
11029+ var timestamp = ( now . getUTCFullYear ( ) + '-' +
11030+ zpad ( now . getUTCMonth ( ) + 1 , 2 ) + '-' +
11031+ zpad ( now . getUTCDate ( ) , 2 ) + 'T' +
11032+ zpad ( now . getUTCHours ( ) , 2 ) + '-' +
11033+ zpad ( now . getUTCMinutes ( ) , 2 ) + '-' +
11034+ zpad ( now . getUTCSeconds ( ) , 2 ) + '.0Z'
11035+ ) ;
11036+ data [ 'x-ethers' ] = {
11037+ client : client ,
11038+ gethFilename : ( 'UTC--' + timestamp + '--' + data . address ) ,
11039+ mnemonicCounter : utils . hexlify ( mnemonicIv ) . substring ( 2 ) ,
11040+ mnemonicCiphertext : utils . hexlify ( mnemonicCiphertext ) . substring ( 2 ) ,
11041+ version : "0.1"
11042+ } ;
11043+ }
11044+
1096711045 if ( progressCallback ) { progressCallback ( 1 ) ; }
1096811046 resolve ( JSON . stringify ( data ) ) ;
1096911047
@@ -10976,7 +11054,7 @@ utils.defineProperty(secretStorage, 'encrypt', function(privateKey, password, op
1097611054
1097711055module . exports = secretStorage ;
1097811056
10979- } , { "./signing-key.js " :62 , "aes-js" :1 , "ethers-utils" :27 , "ethers-utils/hmac" :25 , "ethers-utils/pbkdf2" :30 , "scrypt-js" :55 , "uuid" :58 } ] , 62 :[ function ( require , module , exports ) {
11057+ } , { "./hdnode" : 59 , "./ signing-key" :62 , "aes-js" :1 , "ethers-utils" :27 , "ethers-utils/hmac" :25 , "ethers-utils/pbkdf2" :30 , "scrypt-js" :55 , "uuid" :58 } ] , 62 :[ function ( require , module , exports ) {
1098011058'use strict' ;
1098111059
1098211060/**
@@ -11387,6 +11465,17 @@ utils.defineProperty(Wallet.prototype, 'encrypt', function(password, options, pr
1138711465
1138811466 if ( ! options ) { options = { } ; }
1138911467
11468+ if ( this . mnemonic ) {
11469+ // Make sure we don't accidentally bubble the mnemonic up the call-stack
11470+ var safeOptions = { } ;
11471+ for ( var key in options ) { safeOptions [ key ] = options [ key ] ; }
11472+ options = safeOptions ;
11473+
11474+ // Set the mnemonic and path
11475+ options . mnemonic = this . mnemonic ;
11476+ options . path = this . path
11477+ }
11478+
1139011479 return secretStorage . encrypt ( this . privateKey , password , options , progressCallback ) ;
1139111480} ) ;
1139211481
@@ -11405,6 +11494,7 @@ utils.defineProperty(Wallet, 'createRandom', function(options) {
1140511494 if ( options . extraEntropy ) {
1140611495 entropy = utils . keccak256 ( utils . concat ( [ entropy , options . extraEntropy ] ) ) . substring ( 0 , 34 ) ;
1140711496 }
11497+
1140811498 var mnemonic = HDNode . entropyToMnemonic ( entropy ) ;
1140911499 return Wallet . fromMnemonic ( mnemonic , options . path ) ;
1141011500} ) ;
@@ -11428,7 +11518,12 @@ utils.defineProperty(Wallet, 'fromEncryptedWallet', function(json, password, pro
1142811518 } else if ( secretStorage . isValidWallet ( json ) ) {
1142911519
1143011520 secretStorage . decrypt ( json , password , progressCallback ) . then ( function ( signingKey ) {
11431- resolve ( new Wallet ( signingKey ) ) ;
11521+ var wallet = new Wallet ( signingKey ) ;
11522+ if ( signingKey . mnemonic && signingKey . path ) {
11523+ utils . defineProperty ( wallet , 'mnemonic' , signingKey . mnemonic ) ;
11524+ utils . defineProperty ( wallet , 'path' , signingKey . path ) ;
11525+ }
11526+ resolve ( wallet ) ;
1143211527 } , function ( error ) {
1143311528 reject ( error ) ;
1143411529 } ) ;
0 commit comments