import { decrypt , ecCurve , encrypt , EncryptedMessage , getPubKeyECC , getPubKeyPoint , IServiceProvider , IStorageLayer , KEY_NOT_FOUND , ONE_KEY_DELETE_NONCE , ONE_KEY_NAMESPACE , StringifiedType , stripHexPrefix , toPrivKeyEC , toPrivKeyECC , TorusStorageLayerAPIParams , TorusStorageLayerArgs , } from "@tkey/common-types" ; import { post } from "@toruslabs/http-helpers" ; import BN from "bn.js" ; import stringify from "json-stable-stringify" ; import { keccak256 } from "web3-utils" ; function signDataWithPrivKey ( data , privKey ) { const sig = ecCurve . sign ( stripHexPrefix ( keccak256 ( stringify ( data ) ) ) , toPrivKeyECC ( privKey ) , "utf-8" ) ; return sig . toDER ( "hex" ) ; } class TorusStorageLayer implements IStorageLayer { enableLogging : boolean ; hostUrl : string ; storageLayerName : string ; serverTimeOffset : number ; constructor ( { enableLogging = false , hostUrl = "http://localhost:5051" , serverTimeOffset = 0 } ) { this . enableLogging = enableLogging ; this . hostUrl = hostUrl ; this . storageLayerName = "TorusStorageLayer" ; this . serverTimeOffset = serverTimeOffset ; } static async serializeMetadataParamsInput ( el , serviceProvider , privKey ) { if ( typeof el === "object" ) { const obj = el as Record < string , unknown > ; const isCommandMessage = obj . message === ONE_KEY_DELETE_NONCE ; if ( isCommandMessage ) return obj . message ; } const bufferMetadata = Buffer . from ( stringify ( el ) ) ; let encryptedDetails ; if ( privKey ) { encryptedDetails = await encrypt ( getPubKeyECC ( privKey ) , bufferMetadata ) ; } else { encryptedDetails = await serviceProvider . encrypt ( bufferMetadata ) ; } const serializedEncryptedDetails = btoa ( stringify ( encryptedDetails ) ) ; return serializedEncryptedDetails ; } static fromJSON ( value ) { const { enableLogging , hostUrl , storageLayerName , serverTimeOffset = 0 } = value ; if ( storageLayerName !== "TorusStorageLayer" ) return undefined ; return new TorusStorageLayer ( { enableLogging , hostUrl , serverTimeOffset } ) ; } param privKey async getMetadata < T > ( params ) { const { serviceProvider , privKey } = params ; const keyDetails = this . generateMetadataParams ( { } , serviceProvider , privKey ) ; const metadataResponse = await post < { message : string ; } > ( `${ this . hostUrl }/get` , keyDetails ) ; if ( metadataResponse . message === "" ) { return Object . create ( { message : KEY_NOT_FOUND } ) as T ; } const encryptedMessage = JSON . parse ( atob ( metadataResponse . message ) ) ; let decrypted ; if ( privKey ) { decrypted = await decrypt ( toPrivKeyECC ( privKey ) , encryptedMessage ) ; } else { decrypted = await serviceProvider . decrypt ( encryptedMessage ) ; } return JSON . parse ( decrypted . toString ( ) ) as T ; } param input param privKey async setMetadata < T > ( params ) { const { serviceProvider , privKey , input } = params ; const metadataParams = this . generateMetadataParams ( await TorusStorageLayer . serializeMetadataParamsInput ( input , serviceProvider , privKey ) , serviceProvider , privKey ) ; return post < { message : string ; } > ( `${ this . hostUrl }/set` , metadataParams ) ; } async setMetadataStream < T > ( params ) { const { serviceProvider , privKey , input } = params ; const newInput = input ; const finalMetadataParams = await Promise . all ( newInput . map ( async ( el , i ) => this . generateMetadataParams ( await TorusStorageLayer . serializeMetadataParamsInput ( el , serviceProvider , privKey [ i ] ) , serviceProvider , privKey [ i ] ) ) ) ; const FD = new FormData ( ) ; finalMetadataParams . forEach ( ( el , index ) => { FD . append ( index . toString ( ) , JSON . stringify ( el ) ) ; } ) ; const options = { mode : "cors" , method : "POST" , headers : { "Content-Type" : undefined , } , } ; const customOptions = { isUrlEncodedData : true , } ; return post < { message : string ; } > ( `${ this . hostUrl }/bulk_set_stream` , FD , options , customOptions ) ; } generateMetadataParams ( message , serviceProvider ? , privKey ? ) { let sig ; let pubX ; let pubY ; let namespace = "tkey" ; const setTKeyStore = { data : message , timestamp : new BN ( ~ ~ ( ( this . serverTimeOffset + Date . now ( ) ) / 1000 ) ) . toString ( 16 ) , } ; if ( message === ONE_KEY_DELETE_NONCE ) { namespace = ONE_KEY_NAMESPACE ; setTKeyStore . data = "<deleted>" ; } const hash = keccak256 ( stringify ( setTKeyStore ) ) . slice ( 2 ) ; if ( privKey ) { const unparsedSig = toPrivKeyEC ( privKey ) . sign ( hash ) ; sig = Buffer . from ( unparsedSig . r . toString ( 16 , 64 ) + unparsedSig . s . toString ( 16 , 64 ) + new BN ( 0 ) . toString ( 16 , 2 ) , "hex" ) . toString ( "base64" ) ; const pubK = getPubKeyPoint ( privKey ) ; pubX = pubK . x . toString ( "hex" ) ; pubY = pubK . y . toString ( "hex" ) ; } else { const point = serviceProvider . retrievePubKeyPoint ( ) ; sig = serviceProvider . sign ( hash ) ; pubX = point . getX ( ) . toString ( "hex" ) ; pubY = point . getY ( ) . toString ( "hex" ) ; } return { pub_key_X : pubX , pub_key_Y : pubY , set_data : setTKeyStore , signature : sig , namespace , } ; } async acquireWriteLock ( params ) { const { serviceProvider , privKey } = params ; const data = { timestamp : Math . floor ( ( this . serverTimeOffset + Date . now ( ) ) / 1000 ) , } ; let signature ; if ( privKey ) { signature = signDataWithPrivKey ( data , privKey ) ; } else { signature = serviceProvider . sign ( stripHexPrefix ( keccak256 ( stringify ( data ) ) ) ) ; } const metadataParams = { key : toPrivKeyEC ( privKey ) . getPublic ( "hex" ) , data , signature , } ; return post < { status : number ; id ? : string ; } > ( `${ this . hostUrl }/acquireLock` , metadataParams ) ; } async releaseWriteLock ( params ) { const { serviceProvider , privKey , id } = params ; const data = { timestamp : Math . floor ( ( this . serverTimeOffset + Date . now ( ) ) / 1000 ) , } ; let signature ; if ( privKey ) { signature = signDataWithPrivKey ( data , privKey ) ; } else { signature = serviceProvider . sign ( stripHexPrefix ( keccak256 ( stringify ( data ) ) ) ) ; } const metadataParams = { key : toPrivKeyEC ( privKey ) . getPublic ( "hex" ) , data , signature , id , } ; return post < { status : number ; id ? : string ; } > ( `${ this . hostUrl }/releaseLock` , metadataParams ) ; } toJSON ( ) { return { enableLogging : this . enableLogging , hostUrl : this . hostUrl , storageLayerName : this . storageLayerName , } ; } } export default TorusStorageLayer ;