import { EventEmitter } from 'events' ; import log from 'loglevel' ; import encryptor from 'browser-passworder' ; import * as ethUtil from 'ethereumjs-util' ; import * as bip39 from 'bip39' ; import { ObservableStore } from '@metamask/obs-store' ; import { normalizeAddress , setPageStateCacheWhenPopupClose , hasWalletConnectPageStateCache , } from 'background/utils' ; import BitBox02Keyring from './eth-bitbox02-keyring' ; import LedgerBridgeKeyring from './eth-ledger-bridge-keyring' ; import SimpleKeyring from '@rabby-wallet/eth-simple-keyring' ; import HdKeyring from '@rabby-wallet/eth-hd-keyring' ; import TrezorKeyring from '@rabby-wallet/eth-trezor-keyring' ; import OnekeyKeyring from './eth-onekey-keyring' ; import LatticeKeyring from '@rabby-wallet/eth-lattice-keyring' ; import WatchKeyring from '@rabby-wallet/eth-watch-keyring' ; import WalletConnectKeyring from '@rabby-wallet/eth-walletconnect-keyring' ; import GnosisKeyring , { TransactionBuiltEvent , TransactionConfirmedEvent , } from './eth-gnosis-keyring' ; import preference from '../preference' ; import i18n from '../i18n' ; import { KEYRING_TYPE , HARDWARE_KEYRING_TYPES , EVENTS } from 'consts' ; import DisplayKeyring from './display' ; import eventBus from '@/eventBus' ; export const KEYRING_SDK_TYPES = { SimpleKeyring , HdKeyring , BitBox02Keyring , TrezorKeyring , LedgerBridgeKeyring , OnekeyKeyring , WatchKeyring , WalletConnectKeyring , GnosisKeyring , LatticeKeyring , } ; export const KEYRING_CLASS = { PRIVATE_KEY : SimpleKeyring . type , MNEMONIC : HdKeyring . type , HARDWARE : { BITBOX02 : BitBox02Keyring . type , TREZOR : TrezorKeyring . type , LEDGER : LedgerBridgeKeyring . type , ONEKEY : OnekeyKeyring . type , GRIDPLUS : LatticeKeyring . type , } , WATCH : WatchKeyring . type , WALLETCONNECT : WalletConnectKeyring . type , GNOSIS : GnosisKeyring . type , } ; interface MemStoreState { isUnlocked : boolean ; keyringTypes : any [ ] ; keyrings : any [ ] ; preMnemonics : string ; } export interface DisplayedKeryring { type : string ; accounts : { address : string ; brandName : string ; type ? : string ; keyring ? : DisplayKeyring ; alianName ? : string ; } [ ] ; keyring : DisplayKeyring ; } class KeyringService extends EventEmitter { keyringTypes : any [ ] ; store ! : ObservableStore < any > ; memStore : ObservableStore < MemStoreState > ; keyrings : any [ ] ; encryptor : typeof encryptor = encryptor ; password : string | null = null ; constructor ( ) { super ( ) ; this . keyringTypes = Object . values ( KEYRING_SDK_TYPES ) ; this . memStore = new ObservableStore ( { isUnlocked : false , keyringTypes : this . keyringTypes . map ( ( krt ) => krt . type ) , keyrings : [ ] , preMnemonics : '' , } ) ; this . keyrings = [ ] ; } loadStore ( initState ) { this . store = new ObservableStore ( initState ) ; } async boot ( password ) { this . password = password ; const encryptBooted = await this . encryptor . encrypt ( password , 'true' ) ; this . store . updateState ( { booted : encryptBooted } ) ; this . memStore . updateState ( { isUnlocked : true } ) ; } isBooted ( ) { return ! ! this . store . getState ( ) . booted ; } hasVault ( ) { return ! ! this . store . getState ( ) . vault ; } returns { Object } fullUpdate ( ) { this . emit ( 'update' , this . memStore . getState ( ) ) ; return this . memStore . getState ( ) ; } emits param { string } privateKey returns { Promise < Object > } importPrivateKey ( privateKey ) { let keyring ; return this . persistAllKeyrings ( ) . then ( this . addNewKeyring . bind ( this , 'Simple Key Pair' , [ privateKey ] ) ) . then ( ( _keyring ) => { keyring = _keyring ; return this . persistAllKeyrings . bind ( this ) ; } ) . then ( this . setUnlocked . bind ( this ) ) . then ( this . fullUpdate . bind ( this ) ) . then ( ( ) => keyring ) ; } generateMnemonic ( ) { return bip39 . generateMnemonic ( ) ; } async generatePreMnemonic ( ) { if ( ! this . password ) { throw new Error ( i18n . t ( 'you need to unlock wallet first' ) ) ; } const mnemonic = this . generateMnemonic ( ) ; const preMnemonics = await this . encryptor . encrypt ( this . password , mnemonic ) ; this . memStore . updateState ( { preMnemonics } ) ; return mnemonic ; } getKeyringByType ( type ) { const keyring = this . keyrings . find ( ( keyring ) => keyring . type === type ) ; return keyring ; } removePreMnemonics ( ) { this . memStore . updateState ( { preMnemonics : '' } ) ; } async getPreMnemonics ( ) { if ( ! this . memStore . getState ( ) . preMnemonics ) { return '' ; } if ( ! this . password ) { throw new Error ( i18n . t ( 'you need to unlock wallet first' ) ) ; } return await this . encryptor . decrypt ( this . password , this . memStore . getState ( ) . preMnemonics ) ; } emits param { string } seed returns { Promise < Object > } createKeyringWithMnemonics ( seed ) { if ( ! bip39 . validateMnemonic ( seed ) ) { return Promise . reject ( new Error ( i18n . t ( 'mnemonic phrase is invalid' ) ) ) ; } let keyring ; return this . persistAllKeyrings ( ) . then ( ( ) => { return this . addNewKeyring ( 'HD Key Tree' , { mnemonic : seed , activeIndexes : [ 0 ] , } ) ; } ) . then ( ( firstKeyring ) => { keyring = firstKeyring ; return firstKeyring . getAccounts ( ) ; } ) . then ( ( [ firstAccount ] ) => { if ( ! firstAccount ) { throw new Error ( 'KeyringController - First Account not found.' ) ; } return null ; } ) . then ( this . persistAllKeyrings . bind ( this ) ) . then ( this . setUnlocked . bind ( this ) ) . then ( this . fullUpdate . bind ( this ) ) . then ( ( ) => keyring ) ; } addKeyring ( keyring ) { return keyring . getAccounts ( ) . then ( ( accounts ) => { return this . checkForDuplicate ( keyring . type , accounts ) ; } ) . then ( ( ) => { this . keyrings . push ( keyring ) ; return this . persistAllKeyrings ( ) ; } ) . then ( ( ) => this . _updateMemStoreKeyrings ( ) ) . then ( ( ) => this . fullUpdate ( ) ) . then ( ( ) => { return keyring ; } ) ; } emits returns { Promise < Object > } async setLocked ( ) { this . password = null ; this . memStore . updateState ( { isUnlocked : false } ) ; this . keyrings = [ ] ; await this . _updateMemStoreKeyrings ( ) ; this . emit ( 'lock' ) ; return this . fullUpdate ( ) ; } emits param { string } password returns { Promise < Object > } async submitPassword ( password ) { await this . verifyPassword ( password ) ; this . password = password ; try { this . keyrings = await this . unlockKeyrings ( password ) ; } catch { } finally { this . setUnlocked ( ) ; } return this . fullUpdate ( ) ; } param { string } password async verifyPassword ( password ) { const encryptedBooted = this . store . getState ( ) . booted ; if ( ! encryptedBooted ) { throw new Error ( i18n . t ( 'Cannot unlock without a previous vault' ) ) ; } await this . encryptor . decrypt ( password , encryptedBooted ) ; } param { string } type param { Object } opts returns { Promise < Keyring > } addNewKeyring ( type , opts ? ) { const Keyring = this . getKeyringClassForType ( type ) ; const keyring = new Keyring ( opts ) ; return this . addKeyring ( keyring ) ; } /**
     * Remove Empty Keyrings
     *
     * Loops through the keyrings and removes the ones with empty accounts
     * (usually after removing the last / only account) from a keyring
     */ async removeEmptyKeyrings ( ) { const validKeyrings = [ ] ; await Promise . all ( this . keyrings . map ( async ( keyring ) => { const accounts = await keyring . getAccounts ( ) ; if ( accounts . length > 0 ) { validKeyrings . push ( keyring ) ; } } ) ) ; this . keyrings = validKeyrings ; return ; } param { string } type param { Array < string > } newAccountArray returns { Promise < Array < string > > } async checkForDuplicate ( type , newAccountArray ) { const keyrings = this . getKeyringsByType ( type ) ; const _accounts = await Promise . all ( keyrings . map ( ( keyring ) => keyring . getAccounts ( ) ) ) ; const accounts = _accounts . reduce ( ( m , n ) => m . concat ( n ) , [ ] as string [ ] ) . map ( ( address ) => normalizeAddress ( address ) . toLowerCase ( ) ) ; const isIncluded = newAccountArray . some ( ( account ) => { return accounts . find ( ( key ) => key === account . toLowerCase ( ) || key === ethUtil . stripHexPrefix ( account ) ) ; } ) ; return isIncluded ? Promise . reject ( new Error ( i18n . t ( 'duplicateAccount' ) ) ) : Promise . resolve ( newAccountArray ) ; } param { Keyring } selectedKeyring returns { Promise < Object > } addNewAccount ( selectedKeyring ) { let _accounts ; return selectedKeyring . addAccounts ( 1 ) . then ( ( accounts ) => { accounts . forEach ( ( hexAccount ) => { this . emit ( 'newAccount' , hexAccount ) ; } ) ; _accounts = accounts ; } ) . then ( this . persistAllKeyrings . bind ( this ) ) . then ( this . _updateMemStoreKeyrings . bind ( this ) ) . then ( this . fullUpdate . bind ( this ) ) . then ( ( ) => _accounts ) ; } param { string } address returns { Promise < string > } exportAccount ( address ) { try { return this . getKeyringForAccount ( address ) . then ( ( keyring ) => { return keyring . exportAccount ( normalizeAddress ( address ) ) ; } ) ; } catch ( e ) { return Promise . reject ( e ) ; } } param { string } address returns { Promise < void > } removeAccount ( address , type , brand ? ) { return this . getKeyringForAccount ( address , type ) . then ( ( keyring ) => { if ( typeof keyring . removeAccount === 'function' ) { keyring . removeAccount ( address , brand ) ; this . emit ( 'removedAccount' , address ) ; return keyring . getAccounts ( ) ; } return Promise . reject ( new Error ( `Keyring ${ keyring . type } doesn't support account removal operations` ) ) ; } ) . then ( ( accounts ) => { if ( accounts . length === 0 ) { return this . removeEmptyKeyrings ( ) ; } return undefined ; } ) . then ( this . persistAllKeyrings . bind ( this ) ) . then ( this . _updateMemStoreKeyrings . bind ( this ) ) . then ( this . fullUpdate . bind ( this ) ) . catch ( ( e ) => { return Promise . reject ( e ) ; } ) ; } param { Object } ethTx param { string } _fromAddress param { Object } opts returns { Promise < Object > } signTransaction ( keyring , ethTx , _fromAddress , opts = { } ) { const fromAddress = normalizeAddress ( _fromAddress ) ; return keyring . signTransaction ( fromAddress , ethTx , opts ) ; } param { Object } msgParams returns { Promise < Buffer > } signMessage ( msgParams , opts = { } ) { const address = normalizeAddress ( msgParams . from ) ; return this . getKeyringForAccount ( address ) . then ( ( keyring ) => { return keyring . signMessage ( address , msgParams . data , opts ) ; } ) ; } param { Object } msgParams returns { Promise < Buffer > } signPersonalMessage ( keyring , msgParams , opts = { } ) { const address = normalizeAddress ( msgParams . from ) ; return keyring . signPersonalMessage ( address , msgParams . data , opts ) ; } param { Object } msgParams returns { Promise < Buffer > } signTypedMessage ( keyring , msgParams , opts = { version : 'V1' } ) { const address = normalizeAddress ( msgParams . from ) ; return keyring . signTypedData ( address , msgParams . data , opts ) ; } param { Object } address returns { Promise < Buffer > } getEncryptionPublicKey ( _address , opts = { } ) { const address = normalizeAddress ( _address ) ; return this . getKeyringForAccount ( address ) . then ( ( keyring ) => { return keyring . getEncryptionPublicKey ( address , opts ) ; } ) ; } param { Object } msgParams returns { Promise < Buffer > } decryptMessage ( msgParams , opts = { } ) { const address = normalizeAddress ( msgParams . from ) ; return this . getKeyringForAccount ( address ) . then ( ( keyring ) => { return keyring . decryptMessage ( address , msgParams . data , opts ) ; } ) ; } param { string } _address param { string } origin returns { string } async getAppKeyAddress ( _address , origin ) { const address = normalizeAddress ( _address ) ; const keyring = await this . getKeyringForAccount ( address ) ; return keyring . getAppKeyAddress ( address , origin ) ; } param { string } _address param { string } origin returns { string } async exportAppKeyForAddress ( _address , origin ) { const address = normalizeAddress ( _address ) ; const keyring = await this . getKeyringForAccount ( address ) ; if ( ! ( 'exportAccount' in keyring ) ) { throw new Error ( `The keyring for address ${ _address } does not support exporting.` ) ; } return keyring . exportAccount ( address , { withAppKeyOrigin : origin } ) ; } returns { Promise < void > } createFirstKeyTree ( ) { this . clearKeyrings ( ) ; return this . addNewKeyring ( 'HD Key Tree' , { activeIndexes : [ 0 ] } ) . then ( ( keyring ) => { return keyring . getAccounts ( ) ; } ) . then ( ( [ firstAccount ] ) => { if ( ! firstAccount ) { throw new Error ( 'KeyringController - No account found on keychain.' ) ; } const hexAccount = normalizeAddress ( firstAccount ) ; this . emit ( 'newVault' , hexAccount ) ; return null ; } ) ; } param { string } password returns { Promise < boolean > } persistAllKeyrings ( ) { if ( ! this . password || typeof this . password !== 'string' ) { return Promise . reject ( new Error ( 'KeyringController - password is not a string' ) ) ; } return Promise . all ( this . keyrings . map ( ( keyring ) => { return Promise . all ( [ keyring . type , keyring . serialize ( ) ] ) . then ( ( serializedKeyringArray ) => { return { type : serializedKeyringArray [ 0 ] , data : serializedKeyringArray [ 1 ] , } ; } ) ; } ) ) . then ( ( serializedKeyrings ) => { return this . encryptor . encrypt ( this . password as string , ( serializedKeyrings as unknown ) as Buffer ) ; } ) . then ( ( encryptedString ) => { this . store . updateState ( { vault : encryptedString } ) ; return true ; } ) ; } param { string } password returns { Promise < Array < Keyring > > } async unlockKeyrings ( password ) { const encryptedVault = this . store . getState ( ) . vault ; if ( ! encryptedVault ) { throw new Error ( i18n . t ( 'Cannot unlock without a previous vault' ) ) ; } await this . clearKeyrings ( ) ; const vault = await this . encryptor . decrypt ( password , encryptedVault ) ; this . password = password ; await Promise . all ( Array . from ( vault ) . map ( this . _restoreKeyring . bind ( this ) ) ) ; await this . _updateMemStoreKeyrings ( ) ; return this . keyrings ; } param { Object } serialized returns { Promise < Keyring > } async restoreKeyring ( serialized ) { const keyring = await this . _restoreKeyring ( serialized ) ; await this . _updateMemStoreKeyrings ( ) ; return keyring ; } param { Object } serialized returns { Promise < Keyring > } async _restoreKeyring ( serialized ) { const { type , data } = serialized ; const Keyring = this . getKeyringClassForType ( type ) ; const keyring = new Keyring ( ) ; await keyring . deserialize ( data ) ; if ( keyring . type === HARDWARE_KEYRING_TYPES . Ledger . type && preference . store . useLedgerLive ) { await keyring . updateTransportMethod ( true ) ; } if ( keyring . type === KEYRING_CLASS . WALLETCONNECT ) { eventBus . addEventListener ( EVENTS . WALLETCONNECT . INIT , ( { address , brandName } ) => { ( keyring as WalletConnectKeyring ) . init ( address , brandName ) ; } ) ; ( keyring as WalletConnectKeyring ) . on ( 'inited' , ( uri ) => { eventBus . emit ( EVENTS . broadcastToUI , { method : EVENTS . WALLETCONNECT . INITED , params : { uri } , } ) ; } ) ; keyring . on ( 'statusChange' , ( data ) => { if ( ! preference . getPopupOpen ( ) && hasWalletConnectPageStateCache ( ) ) { setPageStateCacheWhenPopupClose ( data ) ; } eventBus . emit ( EVENTS . broadcastToUI , { method : EVENTS . WALLETCONNECT . STATUS_CHANGED , params : data , } ) ; } ) ; } if ( keyring . type === KEYRING_CLASS . GNOSIS ) { ( keyring as GnosisKeyring ) . on ( TransactionBuiltEvent , ( data ) => { eventBus . emit ( EVENTS . broadcastToUI , { method : TransactionBuiltEvent , params : data , } ) ; ( keyring as GnosisKeyring ) . on ( TransactionConfirmedEvent , ( data ) => { eventBus . emit ( EVENTS . broadcastToUI , { method : TransactionConfirmedEvent , params : data , } ) ; } ) ; } ) ; } await keyring . getAccounts ( ) ; this . keyrings . push ( keyring ) ; return keyring ; } param { string } type returns { Keyring | undefined } getKeyringClassForType ( type ) { return this . keyringTypes . find ( ( kr ) => kr . type === type ) ; } param { string } type returns { Array < Keyring > } getKeyringsByType ( type ) { return this . keyrings . filter ( ( keyring ) => keyring . type === type ) ; } returns { Promise < Array < string > > } async getAccounts ( ) { const keyrings = this . keyrings || [ ] ; const addrs = await Promise . all ( keyrings . map ( ( kr ) => kr . getAccounts ( ) ) ) . then ( ( keyringArrays ) => { return keyringArrays . reduce ( ( res , arr ) => { return res . concat ( arr ) ; } , [ ] ) ; } ) ; return addrs . map ( normalizeAddress ) ; } param { string } address returns { Promise < Keyring > } getKeyringForAccount ( address , type ? , start ? , end ? , includeWatchKeyring = true ) { const hexed = normalizeAddress ( address ) . toLowerCase ( ) ; log . debug ( `KeyringController - getKeyringForAccount: ${ hexed }` ) ; let keyrings = type ? this . keyrings . filter ( ( keyring ) => keyring . type === type ) : this . keyrings ; if ( ! includeWatchKeyring ) { keyrings = keyrings . filter ( ( keyring ) => keyring . type !== KEYRING_TYPE . WatchAddressKeyring ) ; } return Promise . all ( keyrings . map ( ( keyring ) => { return Promise . all ( [ keyring , keyring . getAccounts ( ) ] ) ; } ) ) . then ( ( candidates ) => { const winners = candidates . filter ( ( candidate ) => { const accounts = candidate [ 1 ] . map ( ( addr ) => { return normalizeAddress ( addr ) . toLowerCase ( ) ; } ) ; return accounts . includes ( hexed ) ; } ) ; if ( winners && winners . length > 0 ) { return winners [ 0 ] [ 0 ] ; } throw new Error ( 'No keyring found for the requested account.' ) ; } ) ; } param { Keyring } keyring returns { Promise < Object > } displayForKeyring ( keyring , includeHidden = true ) { const hiddenAddresses = preference . getHiddenAddresses ( ) ; const accounts = keyring . getAccountsWithBrand ? keyring . getAccountsWithBrand ( ) : keyring . getAccounts ( ) ; return accounts . then ( ( accounts ) => { const allAccounts = accounts . map ( ( account ) => ( { address : normalizeAddress ( typeof account === 'string' ? account : account . address ) , brandName : typeof account === 'string' ? keyring . type : account . brandName , } ) ) ; return { type : keyring . type , accounts : includeHidden ? allAccounts : allAccounts . filter ( ( account ) => ! hiddenAddresses . find ( ( item ) => item . type === keyring . type && item . address . toLowerCase ( ) === account . address . toLowerCase ( ) ) ) , keyring , } ; } ) ; } getAllTypedAccounts ( ) { return Promise . all ( this . keyrings . map ( ( keyring ) => this . displayForKeyring ( keyring ) ) ) ; } async getAllTypedVisibleAccounts ( ) { const keyrings = await Promise . all ( this . keyrings . map ( ( keyring ) => this . displayForKeyring ( keyring , false ) ) ) ; return keyrings . filter ( ( keyring ) => keyring . accounts . length > 0 ) ; } async getAllVisibleAccountsArray ( ) { const typedAccounts = await this . getAllTypedVisibleAccounts ( ) ; const result = [ ] ; typedAccounts . forEach ( ( accountGroup ) => { result . push ( ... accountGroup . accounts . map ( ( account ) => ( { address : account . address , brandName : account . brandName , type : accountGroup . type , } ) ) ) ; } ) ; return result ; } /**
     * Clear Keyrings
     *
     * Deallocates all currently managed keyrings and accounts.
     * Used before initializing a new vault.
     */ async clearKeyrings ( ) { this . keyrings = [ ] ; this . memStore . updateState ( { keyrings : [ ] , } ) ; } /**
     * Update Memstore Keyrings
     *
     * Updates the in-memory keyrings, without persisting.
     */ async _updateMemStoreKeyrings ( ) { const keyrings = await Promise . all ( this . keyrings . map ( ( keyring ) => this . displayForKeyring ( keyring ) ) ) ; return this . memStore . updateState ( { keyrings } ) ; } emits setUnlocked ( ) { this . memStore . updateState ( { isUnlocked : true } ) ; this . emit ( 'unlock' ) ; } } export default new KeyringService ( ) ;