import { decrypt , encrypt , getPubKeyECC , getPubKeyPoint , IModule , ITKeyApi , ITkeyError , ShareStore , ShareStoreMap , ShareTransferStorePointerArgs , toPrivKeyECC , } from "@tkey/common-types" ; import { generatePrivate } from "@toruslabs/eccrypto" ; import BN from "bn.js" ; import ShareTransferError from "./errors" ; import ShareRequest from "./ShareRequest" ; import ShareTransferStorePointer from "./ShareTransferStorePointer" ; import { getClientIp } from "./utils" ; export type ShareTransferStore = { [ encPubKeyX ] : ShareRequest ; } ; export const SHARE_TRANSFER_MODULE_NAME = "shareTransfer" ; class ShareTransferModule implements IModule { moduleName : string ; tbSDK : ITKeyApi ; currentEncKey : BN ; requestStatusCheckId : number ; requestStatusCheckInterval : number ; constructor ( ) { this . moduleName = SHARE_TRANSFER_MODULE_NAME ; this . requestStatusCheckInterval = 1000 ; } static refreshShareTransferMiddleware ( generalStore , oldShareStores , newShareStores ) { const numberOfOldShares = Object . keys ( oldShareStores ) . length ; const numberOfNewShares = Object . keys ( newShareStores ) . length ; if ( numberOfNewShares <= numberOfOldShares ) { const shareTransferStorePointer = { pointer : new BN ( generatePrivate ( ) ) } ; return shareTransferStorePointer ; } return generalStore as ShareTransferStorePointer ; } setModuleReferences ( tbSDK ) { this . tbSDK = tbSDK ; this . tbSDK . _addRefreshMiddleware ( this . moduleName , ShareTransferModule . refreshShareTransferMiddleware ) ; } setRequestStatusCheckInterval ( interval ) { this . requestStatusCheckInterval = interval ; } async initialize ( ) { const metadata = this . tbSDK . getMetadata ( ) ; const rawShareTransferStorePointer = metadata . getGeneralStoreDomain ( this . moduleName ) as ShareTransferStorePointerArgs ; let shareTransferStorePointer ; if ( ! rawShareTransferStorePointer ) { shareTransferStorePointer = { pointer : new BN ( generatePrivate ( ) ) } ; metadata . setGeneralStoreDomain ( this . moduleName , shareTransferStorePointer ) ; } else { shareTransferStorePointer = new ShareTransferStorePointer ( rawShareTransferStorePointer ) ; } } async requestNewShare ( userAgent , availableShareIndexes , callback ? ) { if ( this . currentEncKey ) throw ShareTransferError . requestExists ( `${ this . currentEncKey . toString ( "hex" ) }` ) ; this . currentEncKey = new BN ( generatePrivate ( ) ) ; const [ newShareTransferStore , userIp ] = await Promise . all ( [ this . getShareTransferStore ( ) , getClientIp ( ) ] ) ; const encPubKeyX = getPubKeyPoint ( this . currentEncKey ) . x . toString ( "hex" ) ; newShareTransferStore [ encPubKeyX ] = new ShareRequest ( { encPubKey : getPubKeyECC ( this . currentEncKey ) , encShareInTransit : undefined , availableShareIndexes , userAgent , userIp , timestamp : Date . now ( ) , } ) ; await this . setShareTransferStore ( newShareTransferStore ) ; if ( callback ) { this . requestStatusCheckId = Number ( setInterval ( async ( ) => { try { const latestShareTransferStore = await this . getShareTransferStore ( ) ; if ( ! this . currentEncKey ) throw ShareTransferError . missingEncryptionKey ( ) ; if ( latestShareTransferStore [ encPubKeyX ] . encShareInTransit ) { const shareStoreBuf = await decrypt ( toPrivKeyECC ( this . currentEncKey ) , latestShareTransferStore [ encPubKeyX ] . encShareInTransit ) ; const receivedShare = ShareStore . fromJSON ( JSON . parse ( shareStoreBuf . toString ( ) ) ) ; await this . tbSDK . inputShareStoreSafe ( receivedShare , true ) ; this . _cleanUpCurrentRequest ( ) ; callback ( null , receivedShare ) ; } else if ( ! latestShareTransferStore [ encPubKeyX ] ) { this . _cleanUpCurrentRequest ( ) ; callback ( ShareTransferError . userCancelledRequest ( ) ) ; } } catch ( error ) { this . _cleanUpCurrentRequest ( ) ; callback ( error ) ; } } , this . requestStatusCheckInterval ) ) ; } return encPubKeyX ; } async addCustomInfoToShareRequest ( encPubKeyX , customInfo ) { const shareTransferStore = await this . getShareTransferStore ( ) ; if ( ! shareTransferStore [ encPubKeyX ] ) throw ShareTransferError . missingEncryptionKey ( ) ; shareTransferStore [ encPubKeyX ] . customInfo = customInfo ; await this . setShareTransferStore ( shareTransferStore ) ; } async lookForRequests ( ) { const shareTransferStore = await this . getShareTransferStore ( ) ; return Object . keys ( shareTransferStore ) ; } async approveRequest ( encPubKeyX , shareStore ? ) { const shareTransferStore = await this . getShareTransferStore ( ) ; if ( ! shareTransferStore [ encPubKeyX ] ) throw ShareTransferError . missingEncryptionKey ( ) ; let bufferedShare ; if ( shareStore ) { bufferedShare = Buffer . from ( JSON . stringify ( shareStore ) ) ; } else { const store = new ShareRequest ( shareTransferStore [ encPubKeyX ] ) ; const { availableShareIndexes } = store ; const metadata = this . tbSDK . getMetadata ( ) ; const latestPolynomial = metadata . getLatestPublicPolynomial ( ) ; const latestPolynomialId = latestPolynomial . getPolynomialID ( ) ; const indexes = metadata . getShareIndexesForPolynomial ( latestPolynomialId ) ; const filtered = indexes . filter ( ( el ) => ! availableShareIndexes . includes ( el ) ) ; const share = this . tbSDK . outputShareStore ( filtered [ 0 ] ) ; bufferedShare = Buffer . from ( JSON . stringify ( share ) ) ; } const shareRequest = new ShareRequest ( shareTransferStore [ encPubKeyX ] ) ; shareTransferStore [ encPubKeyX ] . encShareInTransit = await encrypt ( shareRequest . encPubKey , bufferedShare ) ; await this . setShareTransferStore ( shareTransferStore ) ; this . currentEncKey = undefined ; } async approveRequestWithShareIndex ( encPubKeyX , shareIndex ) { const deviceShare = this . tbSDK . outputShareStore ( shareIndex ) ; return this . approveRequest ( encPubKeyX , deviceShare ) ; } async getShareTransferStore ( ) { const metadata = this . tbSDK . getMetadata ( ) ; const shareTransferStorePointer = new ShareTransferStorePointer ( metadata . getGeneralStoreDomain ( this . moduleName ) as ShareTransferStorePointerArgs ) ; const storageLayer = this . tbSDK . getStorageLayer ( ) ; return storageLayer . getMetadata < ShareTransferStore > ( { privKey : shareTransferStorePointer . pointer } ) ; } async setShareTransferStore ( shareTransferStore ) { const metadata = this . tbSDK . getMetadata ( ) ; const shareTransferStorePointer = new ShareTransferStorePointer ( metadata . getGeneralStoreDomain ( this . moduleName ) as ShareTransferStorePointerArgs ) ; const storageLayer = this . tbSDK . getStorageLayer ( ) ; await storageLayer . setMetadata ( { input : shareTransferStore , privKey : shareTransferStorePointer . pointer } ) ; } async startRequestStatusCheck ( encPubKeyX , deleteRequestAfterCompletion ) { return new Promise ( ( resolve , reject ) => { this . requestStatusCheckId = Number ( setInterval ( async ( ) => { try { const latestShareTransferStore = await this . getShareTransferStore ( ) ; if ( ! this . currentEncKey ) throw ShareTransferError . missingEncryptionKey ( ) ; if ( ! latestShareTransferStore [ encPubKeyX ] ) { this . _cleanUpCurrentRequest ( ) ; reject ( ShareTransferError . userCancelledRequest ( ) ) ; } else if ( latestShareTransferStore [ encPubKeyX ] . encShareInTransit ) { const shareStoreBuf = await decrypt ( toPrivKeyECC ( this . currentEncKey ) , latestShareTransferStore [ encPubKeyX ] . encShareInTransit ) ; const receivedShare = ShareStore . fromJSON ( JSON . parse ( shareStoreBuf . toString ( ) ) ) ; await this . tbSDK . inputShareStoreSafe ( receivedShare , true ) ; if ( deleteRequestAfterCompletion ) { await this . deleteShareTransferStore ( encPubKeyX ) ; } this . _cleanUpCurrentRequest ( ) ; resolve ( receivedShare ) ; } } catch ( err ) { this . _cleanUpCurrentRequest ( ) ; reject ( err ) ; } } , this . requestStatusCheckInterval ) ) ; } ) ; } async cancelRequestStatusCheck ( ) { clearInterval ( this . requestStatusCheckId ) ; } async deleteShareTransferStore ( encPubKey ) { const currentShareTransferStore = await this . getShareTransferStore ( ) ; delete currentShareTransferStore [ encPubKey ] ; await this . setShareTransferStore ( currentShareTransferStore ) ; } async resetShareTransferStore ( ) { const metadata = this . tbSDK . getMetadata ( ) ; const shareTransferStorePointer = { pointer : new BN ( generatePrivate ( ) ) } ; metadata . setGeneralStoreDomain ( this . moduleName , shareTransferStorePointer ) ; await this . tbSDK . _syncShareMetadata ( ) ; } private _cleanUpCurrentRequest ( ) { this . currentEncKey = undefined ; clearInterval ( this . requestStatusCheckId ) ; } } export default ShareTransferModule ;