import { delay , inject , singleton } from "tsyringe" ; import { TYPES } from "../types" ; import { Key , KeyRing , KeyRingStatus , MultiKeyStoreInfoWithSelected , } from "./keyring" ; import { Bech32Address , checkAndValidateADR36AminoSignDoc , makeADR36AminoSignDoc , verifyADR36AminoSignDoc , } from "@keplr-wallet/cosmos" ; import { BIP44HDPath , CommonCrypto , ExportKeyRingData } from "./types" ; import { KVStore } from "@keplr-wallet/common" ; import { ChainsService } from "../chains" ; import { LedgerService } from "../ledger" ; import { BIP44 , ChainInfo , KeplrSignOptions } from "@keplr-wallet/types" ; import { APP_PORT , Env , WEBPAGE_PORT } from "@keplr-wallet/router" ; import { InteractionService } from "../interaction" ; import { PermissionService } from "../permission" ; import { encodeSecp256k1Signature , serializeSignDoc , AminoSignResponse , StdSignDoc , StdSignature , } from "@cosmjs/launchpad" ; import { DirectSignResponse , makeSignBytes } from "@cosmjs/proto-signing" ; import { RNG } from "@keplr-wallet/crypto" ; import { cosmos } from "@keplr-wallet/cosmos" ; import { Buffer } from "buffer/" ; @ singleton ( ) export class KeyRingService { private readonly keyRing : KeyRing ; constructor ( @ inject ( TYPES . KeyRingStore ) kvStore , @ inject ( TYPES . ChainsEmbedChainInfos ) embedChainInfos , @ inject ( delay ( ( ) => InteractionService ) ) protected readonly interactionService , @ inject ( delay ( ( ) => ChainsService ) ) public readonly chainsService , @ inject ( delay ( ( ) => PermissionService ) ) public readonly permissionService , @ inject ( LedgerService ) ledgerService , @ inject ( TYPES . RNG ) protected readonly rng , @ inject ( TYPES . CommonCrypto ) protected readonly crypto ) { this . keyRing = new KeyRing ( embedChainInfos , kvStore , ledgerService , rng , crypto ) ; } async restore ( ) { await this . keyRing . restore ( ) ; return { status : this . keyRing . status , multiKeyStoreInfo : this . keyRing . getMultiKeyStoreInfo ( ) , } ; } async enable ( env ) { if ( this . keyRing . status === KeyRingStatus . EMPTY ) { throw new Error ( "key doesn't exist" ) ; } if ( this . keyRing . status === KeyRingStatus . NOTLOADED ) { await this . keyRing . restore ( ) ; } if ( this . keyRing . status === KeyRingStatus . LOCKED ) { await this . interactionService . waitApprove ( env , "/unlock" , "unlock" , { } ) ; return this . keyRing . status ; } return this . keyRing . status ; } get keyRingStatus ( ) : KeyRingStatus { return this . keyRing . status ; } async deleteKeyRing ( index , password ) { let keyStoreChanged = false ; try { const result = await this . keyRing . deleteKeyRing ( index , password ) ; keyStoreChanged = result . keyStoreChanged ; return { multiKeyStoreInfo : result . multiKeyStoreInfo , status : this . keyRing . status , } ; } finally { if ( keyStoreChanged ) { this . interactionService . dispatchEvent ( WEBPAGE_PORT , "keystore-changed" , { } ) ; } } } async updateNameKeyRing ( index , name ) { const multiKeyStoreInfo = await this . keyRing . updateNameKeyRing ( index , name ) ; return { multiKeyStoreInfo , } ; } async showKeyRing ( index , password ) { return await this . keyRing . showKeyRing ( index , password ) ; } async createMnemonicKey ( kdf , mnemonic , password , meta , bip44HDPath ) { return await this . keyRing . createMnemonicKey ( kdf , mnemonic , password , meta , bip44HDPath ) ; } async createPrivateKey ( kdf , privateKey , password , meta ) { return await this . keyRing . createPrivateKey ( kdf , privateKey , password , meta ) ; } async createLedgerKey ( env , kdf , password , meta , bip44HDPath ) { return await this . keyRing . createLedgerKey ( env , kdf , password , meta , bip44HDPath ) ; } lock ( ) { this . keyRing . lock ( ) ; return this . keyRing . status ; } async unlock ( password ) { await this . keyRing . unlock ( password ) ; return this . keyRing . status ; } async getKey ( chainId ) { return this . keyRing . getKey ( chainId , await this . chainsService . getChainCoinType ( chainId ) ) ; } getKeyStoreMeta ( key ) { return this . keyRing . getKeyStoreMeta ( key ) ; } getKeyRingType ( ) { return this . keyRing . type ; } async requestSignAmino ( env , msgOrigin , chainId , signer , signDoc , signOptions ) { const coinType = await this . chainsService . getChainCoinType ( chainId ) ; const key = await this . keyRing . getKey ( chainId , coinType ) ; const bech32Prefix = ( await this . chainsService . getChainInfo ( chainId ) ) . bech32Config . bech32PrefixAccAddr ; const bech32Address = new Bech32Address ( key . address ) . toBech32 ( bech32Prefix ) ; if ( signer !== bech32Address ) { throw new Error ( "Signer mismatched" ) ; } const isADR36SignDoc = checkAndValidateADR36AminoSignDoc ( signDoc , bech32Prefix ) ; if ( isADR36SignDoc ) { if ( signDoc . msgs [ 0 ] . value . signer !== signer ) { throw new Error ( "Unmatched signer in sign doc" ) ; } } if ( signOptions . isADR36WithString != null && ! isADR36SignDoc ) { throw new Error ( 'Sign doc is not for ADR-36. But, "isADR36WithString" option is defined' ) ; } const newSignDoc = ( await this . interactionService . waitApprove ( env , "/sign" , "request-sign" , { msgOrigin , chainId , mode : "amino" , signDoc , signer , signOptions , isADR36SignDoc , isADR36WithString : signOptions . isADR36WithString , } ) ) as StdSignDoc ; if ( isADR36SignDoc ) { if ( checkAndValidateADR36AminoSignDoc ( signDoc , bech32Prefix ) ) { if ( signDoc . msgs [ 0 ] . value . signer !== signer ) { throw new Error ( "Unmatched signer in new sign doc" ) ; } } else { throw new Error ( "Signing request was for ADR-36. But, accidentally, new sign doc is not for ADR-36" ) ; } } try { const signature = await this . keyRing . sign ( env , chainId , coinType , serializeSignDoc ( newSignDoc ) ) ; return { signed : newSignDoc , signature : encodeSecp256k1Signature ( key . pubKey , signature ) , } ; } finally { this . interactionService . dispatchEvent ( APP_PORT , "request-sign-end" , { } ) ; } } async requestSignDirect ( env , msgOrigin , chainId , signer , signDoc , signOptions ) { const coinType = await this . chainsService . getChainCoinType ( chainId ) ; const key = await this . keyRing . getKey ( chainId , coinType ) ; const bech32Address = new Bech32Address ( key . address ) . toBech32 ( ( await this . chainsService . getChainInfo ( chainId ) ) . bech32Config . bech32PrefixAccAddr ) ; if ( signer !== bech32Address ) { throw new Error ( "Signer mismatched" ) ; } const newSignDocBytes = ( await this . interactionService . waitApprove ( env , "/sign" , "request-sign" , { msgOrigin , chainId , mode : "direct" , signDocBytes : cosmos . tx . v1beta1 . SignDoc . encode ( signDoc ) . finish ( ) , signer , signOptions , } ) ) as Uint8Array ; const newSignDoc = cosmos . tx . v1beta1 . SignDoc . decode ( newSignDocBytes ) ; try { const signature = await this . keyRing . sign ( env , chainId , coinType , makeSignBytes ( newSignDoc ) ) ; return { signed : newSignDoc , signature : encodeSecp256k1Signature ( key . pubKey , signature ) , } ; } finally { this . interactionService . dispatchEvent ( APP_PORT , "request-sign-end" , { } ) ; } } async verifyADR36AminoSignDoc ( chainId , signer , data , signature ) { const coinType = await this . chainsService . getChainCoinType ( chainId ) ; const key = await this . keyRing . getKey ( chainId , coinType ) ; const bech32Prefix = ( await this . chainsService . getChainInfo ( chainId ) ) . bech32Config . bech32PrefixAccAddr ; const bech32Address = new Bech32Address ( key . address ) . toBech32 ( bech32Prefix ) ; if ( signer !== bech32Address ) { throw new Error ( "Signer mismatched" ) ; } if ( signature . pub_key . type !== "tendermint/PubKeySecp256k1" ) { throw new Error ( `Unsupported type of pub key: ${ signature . pub_key . type }` ) ; } if ( Buffer . from ( key . pubKey ) . toString ( "base64" ) !== signature . pub_key . value ) { throw new Error ( "Pub key unmatched" ) ; } const signDoc = makeADR36AminoSignDoc ( signer , data ) ; return verifyADR36AminoSignDoc ( bech32Prefix , signDoc , Buffer . from ( signature . pub_key . value , "base64" ) , Buffer . from ( signature . signature , "base64" ) ) ; } async sign ( env , chainId , message ) { return this . keyRing . sign ( env , chainId , await this . chainsService . getChainCoinType ( chainId ) , message ) ; } async addMnemonicKey ( kdf , mnemonic , meta , bip44HDPath ) { return this . keyRing . addMnemonicKey ( kdf , mnemonic , meta , bip44HDPath ) ; } async addPrivateKey ( kdf , privateKey , meta ) { return this . keyRing . addPrivateKey ( kdf , privateKey , meta ) ; } async addLedgerKey ( env , kdf , meta , bip44HDPath ) { return this . keyRing . addLedgerKey ( env , kdf , meta , bip44HDPath ) ; } public async changeKeyStoreFromMultiKeyStore ( index ) { try { return await this . keyRing . changeKeyStoreFromMultiKeyStore ( index ) ; } finally { this . interactionService . dispatchEvent ( WEBPAGE_PORT , "keystore-changed" , { } ) ; } } public checkPassword ( password ) { return this . keyRing . checkPassword ( password ) ; } getMultiKeyStoreInfo ( ) { return this . keyRing . getMultiKeyStoreInfo ( ) ; } isKeyStoreCoinTypeSet ( chainId ) { return this . keyRing . isKeyStoreCoinTypeSet ( chainId ) ; } async setKeyStoreCoinType ( chainId , coinType ) { const prevCoinType = this . keyRing . computeKeyStoreCoinType ( chainId , await this . chainsService . getChainCoinType ( chainId ) ) ; await this . keyRing . setKeyStoreCoinType ( chainId , coinType ) ; if ( prevCoinType !== coinType ) { this . interactionService . dispatchEvent ( WEBPAGE_PORT , "keystore-changed" , { } ) ; } } async getKeyStoreBIP44Selectables ( chainId , paths ) { if ( this . isKeyStoreCoinTypeSet ( chainId ) ) { return [ ] ; } const result = [ ] ; const chainInfo = await this . chainsService . getChainInfo ( chainId ) ; for ( const path of paths ) { const key = await this . keyRing . getKeyFromCoinType ( path . coinType ) ; const bech32Address = new Bech32Address ( key . address ) . toBech32 ( chainInfo . bech32Config . bech32PrefixAccAddr ) ; result . push ( { path , bech32Address , } ) ; } return result ; } async exportKeyRingDatas ( password ) { return await this . keyRing . exportKeyRingDatas ( password ) ; } }