2014-04-16 17:50:10 -03:00
'use strict' ;
2014-10-25 19:57:12 -03:00
var _ = require ( 'lodash' ) ;
2014-11-30 00:31:17 -03:00
var preconditions = require ( 'preconditions' ) . singleton ( ) ;
var inherits = require ( 'inherits' ) ;
var events = require ( 'events' ) ;
2014-10-24 12:24:44 -03:00
var async = require ( 'async' ) ;
2014-10-10 12:02:46 -03:00
2014-11-30 00:31:17 -03:00
var bitcore = require ( 'bitcore' ) ;
2014-08-01 01:09:46 -03:00
var TxProposals = require ( './TxProposals' ) ;
2014-04-16 17:50:10 -03:00
var PublicKeyRing = require ( './PublicKeyRing' ) ;
var PrivateKey = require ( './PrivateKey' ) ;
var Wallet = require ( './Wallet' ) ;
2014-09-03 01:25:08 -03:00
var PluginManager = require ( './PluginManager' ) ;
2014-11-30 00:31:17 -03:00
var Async = require ( './Async' ) ;
var cryptoUtil = require ( '../util/crypto' ) ;
2014-12-02 11:20:47 -03:00
var log = require ( '../util/log' ) ;
var version = require ( '../../version' ) . version ;
2014-06-15 20:55:23 -07:00
2014-09-04 01:33:12 -03:00
/ * *
* @ desc
2014-09-26 05:00:43 -03:00
* Identity - stores the state for a wallet in creation
2014-09-04 01:33:12 -03:00
*
2014-10-24 12:24:44 -03:00
* @ param { Object } opts - configuration for this wallet
* @ param { string } opts . fullName
* @ param { string } opts . email
* @ param { string } opts . password
* @ param { string } opts . storage
* @ param { string } opts . pluginManager
* @ param { Object } opts . walletDefaults
* @ param { string } opts . version
* @ param { Object } opts . wallets
* @ param { Object } opts . network
* @ param { string } opts . network . testnet
* @ param { string } opts . network . livenet
2014-09-08 15:42:55 -03:00
* @ constructor
2014-04-16 17:50:10 -03:00
* /
2014-10-24 12:24:44 -03:00
function Identity ( opts ) {
2014-09-28 18:38:06 -03:00
preconditions . checkArgument ( opts ) ;
2014-06-15 20:55:23 -07:00
2014-10-22 00:14:48 -03:00
opts = _ . extend ( { } , opts ) ;
2014-10-07 18:33:55 -03:00
this . networkOpts = {
'livenet' : opts . network . livenet ,
'testnet' : opts . network . testnet ,
2014-09-10 11:12:28 -07:00
} ;
2014-10-07 18:33:55 -03:00
this . blockchainOpts = {
'livenet' : opts . network . livenet ,
'testnet' : opts . network . testnet ,
2014-09-10 11:12:28 -07:00
} ;
2014-04-16 17:50:10 -03:00
2014-10-24 12:24:44 -03:00
this . fullName = opts . fullName || opts . email ;
this . email = opts . email ;
this . password = opts . password ;
2014-09-28 18:38:06 -03:00
2014-10-24 12:24:44 -03:00
this . storage = opts . storage || opts . pluginManager . get ( 'DB' ) ;
this . storage . setCredentials ( this . email , this . password , { } ) ;
2014-09-27 18:53:34 -03:00
2014-10-24 12:24:44 -03:00
this . walletDefaults = opts . walletDefaults || { } ;
this . version = opts . version || version ;
2014-09-29 12:11:10 -03:00
2014-12-18 00:38:00 -03:00
this . walletIds = opts . walletIds || [ ] ;
2014-10-24 12:24:44 -03:00
this . wallets = opts . wallets || { } ;
2014-11-30 00:31:17 -03:00
this . focusedTimestamps = opts . focusedTimestamps || { } ;
2014-11-20 18:13:11 -03:00
this . backupNeeded = opts . backupNeeded || false ;
2014-09-29 12:11:10 -03:00
} ;
2014-11-30 00:31:17 -03:00
inherits ( Identity , events . EventEmitter ) ;
2014-10-29 16:21:44 -03:00
Identity . getStoragePrefix = function ( ) {
return 'profile::' ;
} ;
2014-10-24 12:24:44 -03:00
Identity . getKeyForEmail = function ( email ) {
2014-10-29 16:21:44 -03:00
return Identity . getStoragePrefix ( ) + bitcore . util . sha256ripe160 ( email ) . toString ( 'hex' ) ;
2014-09-29 19:58:00 -03:00
} ;
2014-12-30 15:12:01 -03:00
Identity . prototype . getChecksumForStorage = function ( ) {
2014-12-30 15:49:19 -03:00
return JSON . stringify ( _ . sortBy ( this . walletIds ) ) ;
2014-12-30 15:12:01 -03:00
} ;
2014-10-24 12:24:44 -03:00
Identity . prototype . getId = function ( ) {
return Identity . getKeyForEmail ( this . email ) ;
2014-10-01 08:35:17 -03:00
} ;
2014-10-24 12:24:44 -03:00
Identity . prototype . getName = function ( ) {
return this . fullName || this . email ;
2014-10-07 18:33:55 -03:00
} ;
2014-10-24 12:24:44 -03:00
/ * *
* Creates an Identity
*
* @ param opts
* @ param cb
* @ return { undefined }
* /
2014-10-30 18:08:50 -03:00
Identity . create = function ( opts , cb ) {
2014-11-20 18:13:11 -03:00
opts = _ . extend ( {
backupNeeded : true
} , opts ) ;
2014-10-11 08:43:51 -03:00
2014-10-30 18:08:50 -03:00
var iden = new Identity ( opts ) ;
2014-11-05 22:05:09 -03:00
iden . store ( _ . extend ( opts , {
failIfExists : true
} ) , function ( err ) {
2014-10-30 18:08:50 -03:00
if ( err ) return cb ( err ) ;
return cb ( null , iden ) ;
} ) ;
2014-10-11 08:43:51 -03:00
} ;
2014-12-18 18:23:17 -03:00
Identity . prototype . resendVerificationEmail = function ( cb ) {
2014-12-18 15:50:42 -03:00
var self = this ;
preconditions . checkArgument ( _ . isFunction ( cb ) ) ;
preconditions . checkState ( _ . isFunction ( self . storage . resendVerificationEmail ) ) ;
self . storage . resendVerificationEmail ( cb ) ;
} ;
2014-10-01 08:35:17 -03:00
2014-10-11 14:33:45 -03:00
/ * *
2014-11-30 00:31:17 -03:00
* Open an Identity from the given storage .
*
* After opening a profile , and setting its wallet event handlers ,
* the client must run . netStart on each
2014-11-30 04:42:39 -03:00
* ( probably on iden ' s newWallet handler
2014-10-11 14:33:45 -03:00
*
2014-10-24 12:24:44 -03:00
* @ param { Object } opts
* @ param { Object } opts . storage
2014-10-25 20:10:54 -03:00
* @ param { string } opts . email
* @ param { string } opts . password
2014-10-24 12:24:44 -03:00
* @ param { Function } cb
2014-10-11 14:33:45 -03:00
* /
2014-10-25 20:10:54 -03:00
Identity . open = function ( opts , cb ) {
2014-11-30 00:31:17 -03:00
preconditions . checkArgument ( _ . isObject ( opts ) ) ;
preconditions . checkArgument ( _ . isFunction ( cb ) ) ;
2014-10-24 12:24:44 -03:00
var storage = opts . storage || opts . pluginManager . get ( 'DB' ) ;
2014-10-25 20:10:54 -03:00
storage . setCredentials ( opts . email , opts . password , opts ) ;
2014-12-12 14:37:40 -03:00
storage . getItem ( Identity . getKeyForEmail ( opts . email ) , function ( err , data , headers ) {
2014-11-30 00:31:17 -03:00
var exported ;
2014-10-24 12:24:44 -03:00
if ( err ) {
return cb ( err ) ;
}
2014-11-30 00:31:17 -03:00
try {
exported = JSON . parse ( data ) ;
} catch ( e ) {
return cb ( e ) ;
}
2014-12-12 14:37:40 -03:00
return cb ( null , new Identity ( _ . extend ( opts , exported ) ) , headers ) ;
2014-10-24 09:36:28 -03:00
} ) ;
2014-10-11 14:33:45 -03:00
} ;
2014-12-18 18:23:17 -03:00
Identity . prototype . verifyChecksum = function ( cb ) {
2014-12-30 15:12:01 -03:00
var self = this ;
self . storage . getItem ( Identity . getKeyForEmail ( self . email ) , function ( err , data , headers ) {
var iden ;
if ( err ) return cb ( err ) ;
try {
iden = JSON . parse ( data ) ;
} catch ( e ) {
return cb ( e ) ;
}
return cb ( null , self . getChecksumForStorage ( ) == self . getChecksumForStorage . call ( iden ) ) ;
2014-12-30 15:49:19 -03:00
} ) ;
2014-12-30 15:12:01 -03:00
} ;
2014-12-18 00:38:00 -03:00
/ * *
* @ param { string } walletId
* @ returns { Wallet }
* /
Identity . prototype . getWalletById = function ( walletId ) {
return this . wallets [ walletId ] ;
} ;
/ * *
* @ returns { Wallet [ ] }
* /
Identity . prototype . getWallets = function ( ) {
return _ . values ( this . wallets ) ;
} ;
/ * *
* addWallet
*
* @ param w
* /
Identity . prototype . addWallet = function ( w ) {
this . wallets [ w . getId ( ) ] = w ;
this . walletIds = _ . union ( this . walletIds , [ w . getId ( ) ] ) ;
} ;
/ * *
* @ desc Deletes a wallet . This involves removing it from the storage instance
*
* @ param { string } walletId
* @ callback cb
* @ return { err }
* /
Identity . prototype . deleteWallet = function ( walletId , cb ) {
preconditions . checkArgument ( _ . isString ( walletId ) ) ;
var self = this ;
2014-12-18 18:23:17 -03:00
self . verifyChecksum ( function ( err , match ) {
2014-12-30 15:49:19 -03:00
if ( err ) return cb ( err ) ;
2014-12-30 15:50:30 -03:00
if ( ! match ) return cb ( 'The profile is out of sync. Please re-login to get the latest changes.' ) ;
2014-12-18 00:38:00 -03:00
2014-12-30 15:49:19 -03:00
var w = self . getWalletById ( walletId ) ;
w . close ( ) ;
2014-12-18 00:38:00 -03:00
2014-12-30 15:49:19 -03:00
delete self . wallets [ walletId ] ;
delete self . focusedTimestamps [ walletId ] ;
self . walletIds = _ . without ( self . walletIds , walletId ) ;
2014-12-18 00:38:00 -03:00
2014-12-30 15:49:19 -03:00
self . storage . removeItem ( Wallet . getStorageKey ( walletId ) , function ( err ) {
if ( err ) return cb ( err ) ;
self . emitAndKeepAlive ( 'walletDeleted' , walletId ) ;
2015-01-05 11:31:25 -03:00
self . store ( {
noWallets : true
} , cb ) ;
2014-12-30 15:49:19 -03:00
} ) ;
2014-12-18 00:38:00 -03:00
} ) ;
} ;
2014-11-30 00:31:17 -03:00
2014-09-28 18:38:06 -03:00
/ * *
2014-11-30 00:31:17 -03:00
* readAndBindWallet
*
* @ param { string } wid walletId to be readed
* @ param { function } cb
2014-09-28 18:38:06 -03:00
*
* /
2014-11-30 00:31:17 -03:00
Identity . prototype . readAndBindWallet = function ( walletId , cb ) {
var self = this ;
self . retrieveWalletFromStorage ( walletId , { } , function ( error , wallet ) {
if ( ! error ) {
2014-12-18 00:38:00 -03:00
self . addWallet ( wallet ) ;
2014-11-30 00:31:17 -03:00
self . bindWallet ( wallet ) ;
}
return cb ( error ) ;
} ) ;
} ;
Identity . prototype . emitAndKeepAlive = function ( args ) {
var args = Array . prototype . slice . call ( arguments ) ;
log . debug ( 'Ident Emitting:' , args ) ;
//this.keepAlive(); // TODO
this . emit . apply ( this , arguments ) ;
} ;
/ * *
* @ desc open profile ' s wallets . Call it AFTER setting
2014-11-30 00:44:25 -03:00
* the proper even listeners . no callback .
2014-11-30 00:31:17 -03:00
*
* /
2014-11-30 00:44:25 -03:00
Identity . prototype . openWallets = function ( ) {
2014-11-30 00:31:17 -03:00
var self = this ;
if ( _ . isEmpty ( self . walletIds ) ) {
self . emitAndKeepAlive ( 'noWallets' )
2014-11-30 00:44:25 -03:00
return ;
2014-10-24 12:24:44 -03:00
}
2014-11-30 00:31:17 -03:00
// First read the lastFocused wallet
self . walletIds . sort ( function ( a , b ) {
var va = self . focusedTimestamps [ a ] || 0 ;
var vb = self . focusedTimestamps [ b ] || 0 ;
return va < vb ? 1 : ( va === vb ? 0 : - 1 ) ;
2014-09-28 18:38:06 -03:00
} ) ;
2014-11-30 00:31:17 -03:00
2014-11-30 00:44:25 -03:00
// opens the wallets, in the order they were last accessed. Emits open events (newWallet)
2014-11-30 00:31:17 -03:00
async . eachSeries ( self . walletIds , function ( walletId , a _cb ) {
self . readAndBindWallet ( walletId , a _cb ) ;
2014-11-30 00:44:25 -03:00
} ) ;
2014-09-28 18:38:06 -03:00
} ;
/ * *
2014-10-24 12:24:44 -03:00
* @ param { string } walletId
2014-10-27 14:53:40 -03:00
* @ param { } opts
* opts . importWallet
2014-11-30 00:31:17 -03:00
* @ param { Function } cb
2014-09-28 18:38:06 -03:00
* /
2014-11-30 00:31:17 -03:00
Identity . prototype . retrieveWalletFromStorage = function ( walletId , opts , cb ) {
2014-10-24 12:24:44 -03:00
var self = this ;
2014-10-27 14:53:40 -03:00
var importFunction = opts . importWallet || Wallet . fromUntrustedObj ;
2014-10-24 12:24:44 -03:00
this . storage . getItem ( Wallet . getStorageKey ( walletId ) , function ( error , walletData ) {
if ( error ) {
2014-11-30 00:31:17 -03:00
return cb ( error ) ;
2014-10-24 12:24:44 -03:00
}
try {
2014-10-31 18:17:44 -03:00
log . info ( '## OPENING Wallet:' , walletId ) ;
2014-10-24 12:24:44 -03:00
if ( _ . isString ( walletData ) ) {
walletData = JSON . parse ( walletData ) ;
}
var readOpts = {
networkOpts : self . networkOpts ,
blockchainOpts : self . blockchainOpts ,
skipFields : [ ]
} ;
} catch ( e ) {
log . debug ( "ERROR: " , e . message ) ;
if ( e && e . message && e . message . indexOf ( 'MISSOPTS' ) !== - 1 ) {
2014-11-30 00:31:17 -03:00
return cb ( new Error ( 'WERROR: Could not read: ' + walletId + ': ' + e . message ) ) ;
2014-10-24 12:24:44 -03:00
} else {
2014-11-30 00:31:17 -03:00
return cb ( e ) ;
2014-10-22 00:14:48 -03:00
}
}
2014-11-30 00:31:17 -03:00
return cb ( null , importFunction ( walletData , readOpts ) ) ;
2014-09-28 18:38:06 -03:00
} ) ;
} ;
2014-10-24 12:24:44 -03:00
/ * *
* @ param { Wallet } wallet
* @ param { Function } cb
* /
Identity . prototype . storeWallet = function ( wallet , cb ) {
2014-12-04 17:36:31 -03:00
var self = this ;
2014-10-27 11:57:13 -03:00
preconditions . checkArgument ( wallet && _ . isObject ( wallet ) ) ;
2014-10-24 09:36:28 -03:00
2014-12-02 17:24:37 -03:00
wallet . setVersion ( this . version ) ;
2014-10-24 12:24:44 -03:00
var val = wallet . toObj ( ) ;
var key = wallet . getStorageKey ( ) ;
2014-10-31 15:49:56 -03:00
log . debug ( 'Storing wallet:' + wallet . getName ( ) ) ;
2014-10-24 09:36:28 -03:00
2014-10-24 12:24:44 -03:00
this . storage . setItem ( key , val , function ( err ) {
if ( err ) {
2014-12-04 17:36:31 -03:00
log . error ( 'Wallet:' + wallet . getName ( ) + ' could not be stored:' , err ) ;
2014-11-25 16:41:56 -03:00
log . error ( 'Wallet:' + wallet . getName ( ) + ' Size:' , JSON . stringify ( wallet . sizes ( ) ) ) ;
2014-12-04 15:55:50 -03:00
if ( err . match ( 'OVERQUOTA' ) ) {
2014-12-04 17:36:31 -03:00
self . emitAndKeepAlive ( 'walletStorageError' , wallet . getId ( ) , 'Storage limits on remote server exceeded' ) ;
2014-12-04 15:55:50 -03:00
} else {
2014-12-04 17:36:31 -03:00
self . emitAndKeepAlive ( 'walletStorageError' , wallet . getId ( ) , err ) ;
2014-12-04 15:55:50 -03:00
}
2014-10-24 09:36:28 -03:00
}
2014-10-27 14:53:40 -03:00
if ( cb )
return cb ( err ) ;
2014-10-24 09:36:28 -03:00
} ) ;
} ;
2014-09-28 18:38:06 -03:00
2014-10-31 15:49:56 -03:00
/ * *
* @ param { Identity } identity
* @ param { Wallet } wallet
* @ param { Function } cb
* /
Identity . storeWalletDebounced = _ . debounce ( function ( identity , wallet , cb ) {
2014-10-31 18:53:50 -03:00
identity . storeWallet ( wallet , cb ) ;
2014-10-31 15:49:56 -03:00
} , 3000 ) ;
2014-10-24 12:24:44 -03:00
Identity . prototype . toObj = function ( ) {
2014-12-18 00:38:00 -03:00
return _ . pick ( this , 'walletIds' , 'version' , 'fullName' , 'password' , 'email' , 'backupNeeded' , 'focusedTimestamps' ) ;
2014-10-24 08:37:40 -03:00
} ;
2014-10-28 00:31:30 -03:00
Identity . prototype . exportEncryptedWithWalletInfo = function ( opts ) {
var crypto = opts . cryptoUtil || cryptoUtil ;
2014-11-26 12:08:26 -03:00
2014-11-06 10:30:46 -03:00
return crypto . encrypt ( this . password , this . exportWithWalletInfo ( opts ) ) ;
2014-10-28 00:31:30 -03:00
} ;
2014-12-30 15:49:19 -03:00
Identity . prototype . setBackupNeeded = function ( backupNeeded ) {
var self = this ;
2014-11-26 17:16:46 -03:00
2014-12-30 15:49:19 -03:00
self . backupNeeded = ! ! backupNeeded ;
2014-12-18 18:23:17 -03:00
self . verifyChecksum ( function ( err , match ) {
2014-12-30 15:49:19 -03:00
if ( err ) return cb ( err ) ;
2014-12-30 15:50:30 -03:00
if ( ! match ) return cb ( 'The profile is out of sync. Please re-login to get the latest changes.' ) ;
2014-12-30 15:49:19 -03:00
self . store ( {
noWallets : true
} , function ( ) { } ) ;
} ) ;
2014-11-26 12:08:26 -03:00
}
2014-10-28 00:31:30 -03:00
Identity . prototype . exportWithWalletInfo = function ( opts ) {
2014-10-27 10:49:25 -03:00
return _ . extend ( {
2014-12-18 00:38:00 -03:00
wallets : _ . map ( this . getWallets ( ) , function ( wallet ) {
2014-10-27 10:49:25 -03:00
return wallet . toObj ( ) ;
} )
} ,
2014-11-20 18:13:11 -03:00
_ . pick ( this , 'version' , 'fullName' , 'password' , 'email' , 'backupNeeded' )
2014-10-28 00:31:30 -03:00
) ;
2014-10-24 12:24:44 -03:00
} ;
2014-10-24 08:37:40 -03:00
2014-09-29 10:18:47 -03:00
/ * *
2014-10-24 12:24:44 -03:00
* @ param { Object } opts
* @ param { Function } cb
2014-09-29 10:18:47 -03:00
* /
2014-09-28 18:38:06 -03:00
Identity . prototype . store = function ( opts , cb ) {
2014-09-28 20:50:37 -03:00
var self = this ;
2014-10-27 17:23:01 -03:00
opts = opts || { } ;
2014-10-28 17:00:01 -03:00
var storeFunction = opts . failIfExists ? self . storage . createItem : self . storage . setItem ;
storeFunction . call ( self . storage , this . getId ( ) , this . toObj ( ) , function ( err ) {
2014-12-17 17:16:05 -03:00
if ( err ) return cb ( err ) ;
2014-10-27 14:53:40 -03:00
2014-12-17 17:16:05 -03:00
if ( opts . noWallets ) return cb ( ) ;
2014-10-27 14:53:40 -03:00
2014-12-18 00:38:00 -03:00
async . each ( self . getWallets ( ) , function ( wallet , in _cb ) {
2014-10-31 18:53:50 -03:00
self . storeWallet ( wallet , in _cb ) ;
} , cb ) ;
2014-09-28 20:50:37 -03:00
} ) ;
2014-09-28 18:38:06 -03:00
} ;
2014-12-01 14:44:36 -03:00
/ * *
* @ param { Object } opts
* @ param { Function } cb
* /
Identity . prototype . remove = function ( opts , cb ) {
log . debug ( 'Deleting profile' ) ;
var self = this ;
opts = opts || { } ;
2014-12-18 00:38:00 -03:00
async . each ( self . getWallets ( ) , function ( w , cb ) {
2014-12-01 15:37:28 -03:00
w . close ( ) ;
self . storage . removeItem ( Wallet . getStorageKey ( w . getId ( ) ) , function ( err ) {
if ( err ) return cb ( err ) ;
cb ( ) ;
} ) ;
2014-11-20 18:13:11 -03:00
} , function ( err ) {
2014-12-01 14:44:36 -03:00
if ( err ) return cb ( err ) ;
2014-11-20 18:13:11 -03:00
2014-12-01 14:44:36 -03:00
self . storage . removeItem ( self . getId ( ) , function ( err ) {
if ( err ) return cb ( err ) ;
2014-12-10 10:49:25 -03:00
2014-12-18 00:38:00 -03:00
self . storage . clear ( function ( err ) {
2014-12-10 10:49:25 -03:00
if ( err ) return cb ( err ) ;
2014-12-18 00:38:00 -03:00
2014-12-10 10:49:25 -03:00
self . emitAndKeepAlive ( 'closed' ) ;
return cb ( ) ;
} ) ;
2014-12-01 14:44:36 -03:00
} ) ;
} ) ;
} ;
2014-10-05 15:59:41 -03:00
Identity . prototype . _cleanUp = function ( ) {
2014-12-18 18:23:17 -03:00
var self = this ;
2014-12-18 00:38:00 -03:00
_ . each ( this . getWallets ( ) , function ( w ) {
2014-12-03 00:30:44 -03:00
w . close ( ) ;
2014-12-18 18:23:17 -03:00
delete self . wallets [ w . getId ( ) ] ;
2014-12-03 00:30:44 -03:00
} ) ;
2014-10-05 15:59:41 -03:00
} ;
2014-09-30 21:16:46 -03:00
/ * *
* @ desc Closes the wallet and disconnects all services
* /
2015-01-07 19:59:35 -03:00
Identity . prototype . close = function ( cb ) {
2015-01-08 16:48:12 -03:00
console . log ( 'close!!!!!' ) ;
2015-01-07 19:59:35 -03:00
var self = this ;
function doClose ( ) {
self . _cleanUp ( ) ;
self . emitAndKeepAlive ( 'closed' ) ;
if ( cb ) return cb ( ) ;
} ;
self . verifyChecksum ( function ( err , match ) {
if ( ! err && match ) {
self . store ( {
noWallets : true ,
} , function ( err ) {
return doClose ( ) ;
} ) ;
} else {
return doClose ( ) ;
}
} ) ;
2014-09-30 21:16:46 -03:00
} ;
2014-11-14 16:00:08 -03:00
2014-10-28 00:31:30 -03:00
Identity . prototype . importWalletFromObj = function ( obj , opts , cb ) {
2014-10-16 17:39:22 -03:00
var self = this ;
2014-09-29 16:55:45 -03:00
preconditions . checkArgument ( cb ) ;
2014-09-29 11:35:04 -03:00
2015-01-05 10:58:02 -03:00
self . verifyChecksum ( function ( err , match ) {
if ( err ) return cb ( err ) ;
if ( ! match ) return cb ( 'The profile is out of sync. Please re-login to get the latest changes.' ) ;
var importFunction = opts . importWallet || Wallet . fromUntrustedObj ;
var readOpts = {
networkOpts : self . networkOpts ,
blockchainOpts : self . blockchainOpts ,
skipFields : opts . skipFields ,
} ;
2014-10-16 17:39:22 -03:00
2015-01-05 10:58:02 -03:00
var w = importFunction ( obj , readOpts ) ;
if ( ! w ) return cb ( new Error ( 'Could not decrypt' ) ) ;
log . debug ( 'Wallet decrypted:' + w . getName ( ) ) ;
self . _checkVersion ( w . version ) ;
log . debug ( 'Updating Indexes for wallet:' + w . getName ( ) ) ;
w . updateIndexes ( function ( err ) {
log . debug ( 'Adding wallet to profile:' + w . getName ( ) ) ;
2015-01-08 16:48:12 -03:00
self . storeWallet ( w , function ( err ) {
2015-01-05 10:58:02 -03:00
if ( err ) return cb ( err ) ;
self . addWallet ( w ) ;
self . updateFocusedTimestamp ( w . getId ( ) ) ;
self . bindWallet ( w ) ;
self . backupNeeded = true ;
self . store ( {
noWallets : true ,
} , function ( err ) {
return cb ( err ) ;
} ) ;
} ) ;
} ) ;
2014-09-29 11:35:04 -03:00
} ) ;
2014-05-01 18:32:22 -03:00
} ;
2014-10-05 15:59:41 -03:00
2014-11-30 04:42:39 -03:00
Identity . prototype . importMultipleWalletsFromObj = function ( objs , opts ) {
var self = this ;
opts = opts || { } ;
async . eachSeries ( objs , function ( walletData , cb ) {
if ( ! walletData )
return cb ( ) ;
self . importWalletFromObj ( walletData , opts , cb ) ;
} ) ;
} ;
2014-10-24 12:24:44 -03:00
/ * *
* @ param { Wallet } wallet
* @ param { Function } cb
* /
Identity . prototype . closeWallet = function ( wallet , cb ) {
preconditions . checkState ( wallet , 'Wallet not found' ) ;
2014-10-05 15:59:41 -03:00
2014-11-30 00:31:17 -03:00
var self = this ;
2014-10-24 12:24:44 -03:00
wallet . close ( function ( err ) {
2014-10-05 15:59:41 -03:00
return cb ( err ) ;
} ) ;
} ;
2014-11-11 14:51:01 -03:00
Identity . importFromEncryptedFullJson = function ( ejson , password , opts , cb ) {
2014-10-28 00:31:30 -03:00
var crypto = opts . cryptoUtil || cryptoUtil ;
2014-11-07 17:35:32 -03:00
2014-11-11 14:51:01 -03:00
var str = crypto . decrypt ( password , ejson ) ;
2014-11-03 14:34:38 -03:00
if ( ! str ) {
2014-11-11 14:51:01 -03:00
// 0.7.3 broken KDF
log . debug ( 'Trying legacy encryption...' ) ;
var passphrase = crypto . kdf ( password , 'mjuBtGybi/4=' , 100 ) ;
str = crypto . decrypt ( passphrase , ejson ) ;
2014-11-03 14:34:38 -03:00
}
2014-11-11 14:51:01 -03:00
if ( ! str )
return cb ( 'BADSTR' ) ;
2014-11-03 14:34:38 -03:00
return Identity . importFromFullJson ( str , password , opts , cb ) ;
2014-10-28 00:31:30 -03:00
} ;
2014-10-24 12:24:44 -03:00
Identity . importFromFullJson = function ( str , password , opts , cb ) {
2014-10-21 09:57:54 -03:00
preconditions . checkArgument ( str ) ;
2014-10-22 00:14:48 -03:00
var json ;
try {
json = JSON . parse ( str ) ;
} catch ( e ) {
2014-11-03 14:34:38 -03:00
return cb ( 'BADSTR: Unable to retrieve json from string' , str ) ;
2014-10-22 00:14:48 -03:00
}
2014-10-21 09:57:54 -03:00
2014-10-24 12:24:44 -03:00
var email = json . email ;
2014-10-31 19:39:01 -03:00
opts . email = email ;
opts . password = password ;
2014-11-14 16:00:08 -03:00
if ( ! email )
return cb ( 'BADSTR' ) ;
2014-10-31 19:39:01 -03:00
var iden = new Identity ( opts ) ;
2014-10-21 09:57:54 -03:00
2014-11-18 12:12:09 -03:00
opts . failIfExists = true ;
2014-10-21 09:57:54 -03:00
json . wallets = json . wallets || { } ;
2014-10-31 19:39:01 -03:00
2014-11-18 12:12:09 -03:00
iden . store ( opts , function ( err ) {
2014-11-19 11:15:09 -03:00
if ( err ) return cb ( err ) ; //profile already exists
2014-11-18 12:12:09 -03:00
2014-11-30 04:42:39 -03:00
return cb ( null , iden , json . wallets ) ;
2014-10-15 16:24:21 -03:00
} ) ;
2014-10-15 15:02:14 -03:00
} ;
2014-11-30 04:42:39 -03:00
2014-11-30 00:31:17 -03:00
/ * *
* @ desc binds a wallet 's events and emits ' newWallet '
* @ param { string } walletId Wallet id to be binded
* @ emits newWallet ( walletId )
* /
2014-10-24 08:37:40 -03:00
Identity . prototype . bindWallet = function ( w ) {
2014-12-18 00:38:00 -03:00
preconditions . checkArgument ( w && this . getWalletById ( w . getId ( ) ) ) ;
2014-12-02 17:32:32 -03:00
2014-11-30 01:11:00 -03:00
var self = this ;
2014-10-31 18:14:14 -03:00
log . debug ( 'Binding wallet:' + w . getName ( ) ) ;
2014-10-24 08:37:40 -03:00
2014-10-27 13:13:08 -03:00
w . on ( 'txProposalsUpdated' , function ( ) {
2014-10-31 15:49:56 -03:00
Identity . storeWalletDebounced ( self , w ) ;
2014-10-27 13:13:08 -03:00
} ) ;
2014-11-23 19:19:05 -03:00
w . on ( 'paymentAck' , function ( ) {
Identity . storeWalletDebounced ( self , w ) ;
} ) ;
2014-10-27 13:13:08 -03:00
w . on ( 'newAddresses' , function ( ) {
2014-10-31 15:49:56 -03:00
Identity . storeWalletDebounced ( self , w ) ;
2014-10-27 13:13:08 -03:00
} ) ;
w . on ( 'settingsUpdated' , function ( ) {
2014-10-31 15:49:56 -03:00
Identity . storeWalletDebounced ( self , w ) ;
2014-10-27 13:13:08 -03:00
} ) ;
w . on ( 'txProposalEvent' , function ( ) {
2014-10-31 15:49:56 -03:00
Identity . storeWalletDebounced ( self , w ) ;
2014-10-27 13:13:08 -03:00
} ) ;
w . on ( 'addressBookUpdated' , function ( ) {
2014-10-31 15:49:56 -03:00
Identity . storeWalletDebounced ( self , w ) ;
2014-10-24 08:37:40 -03:00
} ) ;
2014-10-29 14:53:52 -03:00
w . on ( 'publicKeyRingUpdated' , function ( ) {
2014-10-31 15:49:56 -03:00
Identity . storeWalletDebounced ( self , w ) ;
2014-10-29 14:53:52 -03:00
} ) ;
2014-12-09 11:52:01 -03:00
w . on ( 'ready' , function ( ) {
Identity . storeWalletDebounced ( self , w ) ;
} ) ;
2014-11-30 00:31:17 -03:00
this . emitAndKeepAlive ( 'newWallet' , w . getId ( ) ) ;
2014-10-24 08:37:40 -03:00
} ;
2014-09-04 01:33:12 -03:00
/ * *
2014-09-14 13:52:43 -03:00
* @ desc This method prepares options for a new Wallet
2014-09-04 01:33:12 -03:00
*
* @ param { Object } opts
* @ param { string } opts . id
* @ param { PrivateKey = } opts . privateKey
* @ param { string = } opts . privateKeyHex
* @ param { number } opts . requiredCopayers
* @ param { number } opts . totalCopayers
* @ param { PublicKeyRing = } opts . publicKeyRing
* @ param { string } opts . nickname
2014-09-30 21:16:46 -03:00
* @ param { string } opts . password
2014-10-28 17:00:01 -03:00
* @ param { boolean } opts . spendUnconfirmed this . walletDefaults . spendUnconfirmed
* @ param { number } opts . reconnectDelay time in milliseconds
2014-09-04 01:33:12 -03:00
* @ param { number = } opts . version
2014-09-03 15:43:27 -03:00
* @ param { callback } opts . version
2014-09-04 01:33:12 -03:00
* @ return { Wallet }
* /
2014-09-27 18:56:25 -03:00
Identity . prototype . createWallet = function ( opts , cb ) {
2014-09-08 10:46:57 -03:00
preconditions . checkArgument ( cb ) ;
2014-12-18 18:23:17 -03:00
2014-12-30 15:49:19 -03:00
var self = this ;
2014-09-10 13:56:49 -07:00
2014-12-18 18:23:17 -03:00
self . verifyChecksum ( function ( err , match ) {
2014-12-30 15:49:19 -03:00
if ( err ) return cb ( err ) ;
2014-12-30 15:50:30 -03:00
if ( ! match ) return cb ( 'The profile is out of sync. Please re-login to get the latest changes.' ) ;
2014-09-10 11:12:28 -07:00
2014-12-30 15:49:19 -03:00
opts = opts || { } ;
opts . networkName = opts . networkName || 'testnet' ;
2014-04-16 17:50:10 -03:00
2014-12-30 15:49:19 -03:00
log . debug ( '### CREATING NEW WALLET.' + ( opts . id ? ' USING ID: ' + opts . id : ' NEW ID' ) + ( opts . privateKey ? ' USING PrivateKey: ' + opts . privateKey . getId ( ) : ' NEW PrivateKey' ) ) ;
2014-08-21 14:54:36 -04:00
2014-12-30 15:49:19 -03:00
var privOpts = {
networkName : opts . networkName ,
} ;
2014-08-21 14:54:36 -04:00
2014-12-30 15:49:19 -03:00
if ( opts . privateKeyHex && opts . privateKeyHex . length > 1 ) {
privOpts . extendedPrivateKeyString = opts . privateKeyHex ;
}
2014-04-20 12:41:28 -03:00
2014-12-30 15:49:19 -03:00
opts . privateKey = opts . privateKey || new PrivateKey ( privOpts ) ;
2014-04-16 17:50:10 -03:00
2014-12-30 15:49:19 -03:00
var requiredCopayers = opts . requiredCopayers || self . walletDefaults . requiredCopayers ;
var totalCopayers = opts . totalCopayers || self . walletDefaults . totalCopayers ;
opts . lockTimeoutMin = self . walletDefaults . idleDurationMin ;
2014-04-16 17:50:10 -03:00
2014-12-30 15:49:19 -03:00
opts . publicKeyRing = opts . publicKeyRing || new PublicKeyRing ( {
networkName : opts . networkName ,
requiredCopayers : requiredCopayers ,
totalCopayers : totalCopayers ,
} ) ;
opts . publicKeyRing . addCopayer (
opts . privateKey . deriveBIP45Branch ( ) . extendedPublicKeyString ( ) ,
opts . nickname || self . getName ( )
) ;
log . debug ( '\t### PublicKeyRing Initialized' ) ;
opts . txProposals = opts . txProposals || new TxProposals ( {
networkName : opts . networkName ,
} ) ;
var walletClass = opts . walletClass || Wallet ;
2014-10-27 13:13:08 -03:00
2014-12-30 15:49:19 -03:00
log . debug ( '\t### TxProposals Initialized' ) ;
2014-04-16 17:50:10 -03:00
2014-05-01 10:03:58 -03:00
2014-12-30 15:49:19 -03:00
opts . networkOpts = self . networkOpts ;
opts . blockchainOpts = self . blockchainOpts ;
2014-04-16 20:58:57 -03:00
2014-12-30 15:49:19 -03:00
opts . spendUnconfirmed = opts . spendUnconfirmed || self . walletDefaults . spendUnconfirmed ;
opts . reconnectDelay = opts . reconnectDelay || self . walletDefaults . reconnectDelay ;
opts . requiredCopayers = requiredCopayers ;
opts . totalCopayers = totalCopayers ;
opts . version = opts . version || self . version ;
2014-08-14 18:46:42 -04:00
2014-12-30 15:49:19 -03:00
var w = new walletClass ( opts ) ;
2014-12-03 19:20:51 -03:00
2014-12-30 15:49:19 -03:00
if ( self . getWalletById ( w . getId ( ) ) ) {
return cb ( 'walletAlreadyExists' ) ;
}
2015-01-05 10:15:51 -03:00
2014-12-30 15:49:19 -03:00
self . storeWallet ( w , function ( err ) {
if ( err ) return cb ( err ) ;
2014-11-20 18:13:11 -03:00
2015-01-05 10:15:51 -03:00
self . addWallet ( w ) ;
self . updateFocusedTimestamp ( w . getId ( ) ) ;
self . bindWallet ( w ) ;
2014-12-30 15:49:19 -03:00
self . backupNeeded = true ;
self . store ( {
noWallets : true ,
} , function ( err ) {
return cb ( err , w ) ;
} ) ;
2014-10-31 18:53:50 -03:00
} ) ;
2014-10-22 00:14:48 -03:00
} ) ;
} ;
2014-09-04 01:33:12 -03:00
/ * *
* @ desc Checks if a version is compatible with the current version
* @ param { string } inVersion - a version , with major , minor , and revision , period - separated ( x . y . z )
* @ throws { Error } if there ' s a major version difference
* /
2014-09-26 05:00:43 -03:00
Identity . prototype . _checkVersion = function ( inVersion ) {
2014-09-29 12:11:10 -03:00
if ( inVersion ) {
var thisV = this . version . split ( '.' ) ;
var thisV0 = parseInt ( thisV [ 0 ] ) ;
var inV = inVersion . split ( '.' ) ;
var inV0 = parseInt ( inV [ 0 ] ) ;
}
2014-05-14 21:02:01 -03:00
//We only check for major version differences
2014-06-16 15:51:19 -03:00
if ( thisV0 < inV0 ) {
2014-05-14 21:02:01 -03:00
throw new Error ( 'Major difference in software versions' +
2014-09-04 16:13:30 -03:00
'. Received:' + inVersion +
'. Current version:' + this . version +
'. Aborting.' ) ;
2014-05-14 21:02:01 -03:00
}
} ;
2014-04-16 17:50:10 -03:00
2014-09-04 01:33:12 -03:00
/ * *
* @ desc Pass through to { @ link Wallet # secret }
* /
2014-09-26 05:00:43 -03:00
Identity . prototype . decodeSecret = function ( secret ) {
2014-05-14 14:24:24 -07:00
try {
return Wallet . decodeSecret ( secret ) ;
} catch ( e ) {
return false ;
}
2014-06-09 18:01:15 -03:00
} ;
2014-04-16 20:58:57 -03:00
2014-11-30 00:31:17 -03:00
/ * *
* getLastFocusedWalletId
*
* @ return { string } walletId
* /
Identity . prototype . getLastFocusedWalletId = function ( ) {
2014-12-02 14:33:46 -03:00
if ( this . walletIds . length == 0 ) return undefined ;
var max = _ . max ( this . focusedTimestamps ) ;
2014-11-30 00:31:17 -03:00
if ( ! max )
2014-12-02 14:33:46 -03:00
return this . walletIds [ 0 ] ;
2014-11-30 00:31:17 -03:00
return _ . findKey ( this . focusedTimestamps , function ( ts ) {
return ts == max ;
2014-12-02 14:33:46 -03:00
} ) || this . walletIds [ 0 ] ;
2014-11-30 00:31:17 -03:00
} ;
Identity . prototype . updateFocusedTimestamp = function ( wid ) {
2014-12-18 00:38:00 -03:00
preconditions . checkArgument ( wid && this . getWalletById ( wid ) ) ;
2014-11-30 00:31:17 -03:00
this . focusedTimestamps [ wid ] = Date . now ( ) ;
2014-10-24 12:24:44 -03:00
} ;
2014-09-04 01:33:12 -03:00
/ * *
* @ callback walletCreationCallback
2014-09-08 15:42:55 -03:00
* @ param { ? } err - an error , if any , that happened during the wallet creation
2014-09-04 01:33:12 -03:00
* @ param { Wallet = } wallet - the wallet created
* /
2014-04-20 12:41:28 -03:00
2014-09-04 01:33:12 -03:00
/ * *
* @ desc Start the network functionality .
*
* Start up the Network instance and try to join a wallet defined by the
* parameter < tt > secret < / t t > u s i n g t h e p a r a m e t e r < t t > n i c k n a m e < / t t > . E n c o d e
* information locally using < tt > passphrase < / t t > . < t t > p r i v a t e H e x < / t t > i s t h e
* private extended master key . < tt > cb < / t t > h a s t w o p a r a m s : e r r o r a n d w a l l e t .
*
2014-09-14 13:52:43 -03:00
* @ param { object } opts
* @ param { string } opts . secret - the wallet secret
* @ param { string } opts . nickname - a nickname for the current user
* @ param { string } opts . privateHex - the private extended master key
2014-09-04 01:33:12 -03:00
* @ param { walletCreationCallback } cb - a callback
* /
2014-09-27 18:56:25 -03:00
Identity . prototype . joinWallet = function ( opts , cb ) {
2014-09-14 13:52:43 -03:00
preconditions . checkArgument ( opts ) ;
preconditions . checkArgument ( opts . secret ) ;
2014-09-08 10:46:57 -03:00
preconditions . checkArgument ( cb ) ;
2014-08-20 18:00:33 -04:00
var self = this ;
2014-09-14 13:52:43 -03:00
var decodedSecret = this . decodeSecret ( opts . secret ) ;
2014-09-10 14:01:10 -07:00
if ( ! decodedSecret || ! decodedSecret . networkName || ! decodedSecret . pubKey ) {
2014-09-10 11:12:28 -07:00
return cb ( 'badSecret' ) ;
}
2014-06-16 15:51:19 -03:00
2014-08-20 18:00:33 -04:00
var privOpts = {
2014-09-09 15:30:49 -07:00
networkName : decodedSecret . networkName ,
2014-08-20 18:00:33 -04:00
} ;
2014-09-14 13:52:43 -03:00
if ( opts . privateHex && opts . privateHex . length > 1 ) {
2014-10-04 11:30:08 -03:00
privOpts . extendedPrivateKeyString = opts . privateHex ;
2014-08-20 18:00:33 -04:00
}
2014-04-20 12:41:28 -03:00
//Create our PrivateK
2014-08-20 18:00:33 -04:00
var privateKey = new PrivateKey ( privOpts ) ;
2014-09-01 12:35:40 -03:00
log . debug ( '\t### PrivateKey Initialized' ) ;
2014-09-18 18:29:00 -03:00
var joinOpts = {
2014-04-24 23:13:55 -03:00
copayerId : privateKey . getId ( ) ,
2014-06-26 08:49:22 -07:00
privkey : privateKey . getIdPriv ( ) ,
2014-09-03 16:51:02 -03:00
key : privateKey . getIdKey ( ) ,
2014-09-09 15:30:49 -07:00
secretNumber : decodedSecret . secretNumber ,
2014-04-24 23:13:55 -03:00
} ;
2014-09-10 11:12:28 -07:00
2014-10-07 18:33:55 -03:00
2014-10-24 12:24:44 -03:00
var joinNetwork = opts . Async || new Async ( this . networkOpts [ decodedSecret . networkName ] ) ;
2014-07-08 15:25:12 -03:00
// This is a hack to reconize if the connection was rejected or the peer wasn't there.
var connectedOnce = false ;
2014-09-10 11:12:28 -07:00
joinNetwork . on ( 'connected' , function ( sender , data ) {
2014-07-08 15:25:12 -03:00
connectedOnce = true ;
} ) ;
2014-09-19 11:15:45 -03:00
joinNetwork . on ( 'connect_error' , function ( ) {
return cb ( 'connectionError' ) ;
} ) ;
2014-09-10 11:12:28 -07:00
joinNetwork . on ( 'serverError' , function ( ) {
2014-07-08 15:25:12 -03:00
return cb ( 'joinError' ) ;
} ) ;
2014-09-18 18:29:00 -03:00
joinNetwork . start ( joinOpts , function ( ) {
joinNetwork . greet ( decodedSecret . pubKey , joinOpts . secretNumber ) ;
2014-09-10 11:12:28 -07:00
joinNetwork . on ( 'data' , function ( sender , data ) {
2014-09-15 14:25:09 -03:00
if ( data . type === 'walletId' && data . opts ) {
2014-10-04 19:52:43 -03:00
if ( ! data . networkName || data . networkName !== decodedSecret . networkName ) {
2014-06-09 21:00:28 -03:00
return cb ( 'badNetwork' ) ;
}
2014-10-04 19:52:43 -03:00
data . opts . networkName = data . networkName ;
2014-06-09 21:00:28 -03:00
2014-09-23 15:24:57 -03:00
var walletOpts = _ . clone ( data . opts ) ;
2014-09-18 18:29:00 -03:00
walletOpts . id = data . walletId ;
2014-10-14 19:49:29 -03:00
walletOpts . network = joinNetwork ;
2014-09-18 18:29:00 -03:00
walletOpts . privateKey = privateKey ;
2014-10-24 12:24:44 -03:00
walletOpts . nickname = opts . nickname || self . getName ( ) ;
2014-10-05 15:59:41 -03:00
if ( opts . password )
walletOpts . password = opts . password ;
2014-09-18 18:29:00 -03:00
2014-09-28 18:38:06 -03:00
self . createWallet ( walletOpts , function ( err , w ) {
2014-09-18 18:29:00 -03:00
if ( w ) {
w . sendWalletReady ( decodedSecret . pubKey ) ;
2014-09-15 14:25:09 -03:00
} else {
2014-10-26 21:06:43 -03:00
if ( ! err ) {
err = 'walletFull' ;
}
2014-09-15 14:25:09 -03:00
}
2014-10-31 18:53:50 -03:00
return cb ( err , w ) ;
2014-09-15 14:25:09 -03:00
} ) ;
2014-04-20 16:16:09 -03:00
}
2014-04-16 20:58:57 -03:00
} ) ;
} ) ;
} ;
2014-09-27 17:14:49 -03:00
2014-09-26 05:00:43 -03:00
module . exports = Identity ;