import { delay , inject , singleton } from "tsyringe" ; import { TYPES } from "../types" ; import { EnigmaUtils } from "secretjs" ; import { KeyRingService } from "../keyring" ; import { ChainsService } from "../chains" ; import { PermissionService } from "../permission" ; import { Hash } from "@keplr-wallet/crypto" ; import { KVStore , Debouncer } from "@keplr-wallet/common" ; import { ChainInfo } from "@keplr-wallet/types" ; import { Bech32Address } from "@keplr-wallet/cosmos" ; import { Env } from "@keplr-wallet/router" ; import { Buffer } from "buffer/" ; @ singleton ( ) export class SecretWasmService { protected debouncerMap : Map < string , ( env : Env , chainInfo : ChainInfo , bech32Address : string ) => Promise < Uint8Array > > = new Map ( ) ; protected cacheEnigmaUtils : Map < string , EnigmaUtils > = new Map ( ) ; constructor ( @ inject ( TYPES . SecretWasmStore ) protected readonly kvStore , @ inject ( ChainsService ) protected readonly chainsService , @ inject ( delay ( ( ) => KeyRingService ) ) protected readonly keyRingService , @ inject ( delay ( ( ) => PermissionService ) ) public readonly permissionService ) { this . chainsService . addChainRemovedHandler ( this . onChainRemoved ) ; } protected readonly onChainRemoved = ( ) => { this . cacheEnigmaUtils = new Map ( ) ; } ; async getPubkey ( env , chainId ) { const chainInfo = await this . chainsService . getChainInfo ( chainId ) ; const keyRingType = await this . keyRingService . getKeyRingType ( ) ; if ( keyRingType === "none" ) { throw new Error ( "Key ring is not initialized" ) ; } const seed = await this . getSeed ( env , chainInfo ) ; const utils = this . getEnigmaUtils ( chainInfo , seed ) ; return utils . pubkey ; } async getTxEncryptionKey ( env , chainId , nonce ) { const chainInfo = await this . chainsService . getChainInfo ( chainId ) ; const keyRingType = await this . keyRingService . getKeyRingType ( ) ; if ( keyRingType === "none" ) { throw new Error ( "Key ring is not initialized" ) ; } const seed = await this . getSeed ( env , chainInfo ) ; const utils = this . getEnigmaUtils ( chainInfo , seed ) ; return utils . getTxEncryptionKey ( nonce ) ; } async encrypt ( env , chainId , contractCodeHash , msg ) { const chainInfo = await this . chainsService . getChainInfo ( chainId ) ; const keyRingType = await this . keyRingService . getKeyRingType ( ) ; if ( keyRingType === "none" ) { throw new Error ( "Key ring is not initialized" ) ; } const seed = await this . getSeed ( env , chainInfo ) ; const utils = this . getEnigmaUtils ( chainInfo , seed ) ; return await utils . encrypt ( contractCodeHash , msg ) ; } async decrypt ( env , chainId , ciphertext , nonce ) { const chainInfo = await this . chainsService . getChainInfo ( chainId ) ; const keyRingType = await this . keyRingService . getKeyRingType ( ) ; if ( keyRingType === "none" ) { throw new Error ( "Key ring is not initialized" ) ; } const seed = await this . getSeed ( env , chainInfo ) ; const utils = this . getEnigmaUtils ( chainInfo , seed ) ; return await utils . decrypt ( ciphertext , nonce ) ; } private getEnigmaUtils ( chainInfo , seed ) { const key = `${ chainInfo . chainId }-${ Buffer . from ( seed ) . toString ( "hex" ) }` ; if ( this . cacheEnigmaUtils . has ( key ) ) { return this . cacheEnigmaUtils . get ( key ) ! ; } const utils = new EnigmaUtils ( chainInfo . rest , seed ) ; this . cacheEnigmaUtils . set ( key , utils ) ; return utils ; } protected async getSeed ( env , chainInfo ) { const key = await this . keyRingService . getKey ( chainInfo . chainId ) ; const bech32Address = new Bech32Address ( key . address ) . toBech32 ( chainInfo . bech32Config . bech32PrefixAccAddr ) ; const debouncerKey = `${ env . isInternalMsg }/${ chainInfo . chainId }/${ bech32Address }` ; if ( ! this . debouncerMap . has ( debouncerKey ) ) { this . debouncerMap . set ( debouncerKey , Debouncer . promise ( this . getSeedInner . bind ( this ) ) ) ; } const debouncedFn = this . debouncerMap . get ( debouncerKey ) ! ; return await debouncedFn ( env , chainInfo , bech32Address ) ; } protected async getSeedInner ( env , chainInfo , bech32Address ) { const storeKey = `seed-${ chainInfo . chainId }-${ bech32Address }` ; const cached = await this . kvStore . get < string > ( storeKey ) ; if ( cached ) { return Buffer . from ( cached , "hex" ) ; } const seed = Hash . sha256 ( Buffer . from ( await this . keyRingService . sign ( env , chainInfo . chainId , Buffer . from ( JSON . stringify ( { account_number : 0 , chain_id : chainInfo . chainId , fee : [ ] , memo : "Create Keplr Secret encryption key. Only approve requests by Keplr." , msgs : [ ] , sequence : 0 , } ) ) ) ) ) ; await this . kvStore . set ( storeKey , Buffer . from ( seed ) . toString ( "hex" ) ) ; return seed ; } }