@ -54,10 +54,13 @@ export type SendParams = {
ttl : string ;
timestamp : string ;
data : string ;
isSyncMessage? : boolean ;
messageId? : string ;
} ;
// get snodes for pubkey from random snode. Uses an existing snode
/ * *
* get snodes for pubkey from random snode . Uses an existing snode
* /
async function requestSnodesForPubkeyWithTargetNodeRetryable (
pubKey : string ,
targetNode : Snode
@ -407,7 +410,10 @@ export async function TEST_getSnodePoolFromSnode(targetNode: Snode): Promise<Arr
}
}
export async function storeOnNode ( targetNode : Snode , params : SendParams ) : Promise < boolean > {
export async function storeOnNode (
targetNode : Snode ,
params : SendParams
) : Promise < string | null | boolean > {
try {
// no retry here. If an issue is with the path this is handled in lokiOnionFetch
// if there is an issue with the targetNode, we still send a few times this request to a few snodes in // already so it's handled
@ -426,6 +432,12 @@ export async function storeOnNode(targetNode: Snode, params: SendParams): Promis
try {
const parsed = JSON . parse ( result . body ) ;
handleTimestampOffset ( 'store' , parsed . t ) ;
const messageHash = parsed . hash ;
if ( messageHash ) {
return messageHash ;
}
return true ;
} catch ( e ) {
window ? . log ? . warn ( 'Failed to parse "store" result: ' , e . msg ) ;
@ -666,3 +678,165 @@ export const forceNetworkDeletion = async (): Promise<Array<string> | null> => {
// tslint:disable-next-line: variable-name
export const TEST_getMinTimeout = ( ) = > 500 ;
/ * *
* Locally deletes message and deletes message on the network ( all nodes that contain the message )
* /
// tslint:disable-next-line: max-func-body-length
export const networkDeleteMessages = async ( hashes : Array < string > ) : Promise < any > = > {
const sodium = await getSodium ( ) ;
const userX25519PublicKey = UserUtils . getOurPubKeyStrFromCache ( ) ;
const userED25519KeyPair = await UserUtils . getUserED25519KeyPair ( ) ;
if ( ! userED25519KeyPair ) {
window ? . log ? . warn ( 'Cannot networkDeleteMessages, did not find user ed25519 key.' ) ;
return null ;
}
const edKeyPriv = userED25519KeyPair . privKey ;
try {
const maliciousSnodes = await pRetry (
async ( ) = > {
const userSwarm = await getSwarmFor ( userX25519PublicKey ) ;
const snodeToMakeRequestTo : Snode | undefined = _ . sample ( userSwarm ) ;
const edKeyPrivBytes = fromHexToArray ( edKeyPriv ) ;
if ( ! snodeToMakeRequestTo ) {
window ? . log ? . warn ( 'Cannot networkDeleteMessages, without a valid swarm node.' ) ;
return null ;
}
return pRetry (
async ( ) = > {
const verificationData = StringUtils . encode ( ` delete ${ hashes . join ( '' ) } ` , 'utf8' ) ;
const message = new Uint8Array ( verificationData ) ;
const signature = sodium . crypto_sign_detached ( message , edKeyPrivBytes ) ;
const signatureBase64 = fromUInt8ArrayToBase64 ( signature ) ;
const deleteMessageParams = {
pubkey : userX25519PublicKey ,
pubkey_ed25519 : userED25519KeyPair.pubKey.toUpperCase ( ) ,
messages : hashes ,
signature : signatureBase64 ,
} ;
const ret = await snodeRpc ( {
method : 'delete' ,
params : deleteMessageParams ,
targetNode : snodeToMakeRequestTo ,
associatedWith : userX25519PublicKey ,
} ) ;
if ( ! ret ) {
throw new Error (
` Empty response got for delete on snode ${ ed25519Str (
snodeToMakeRequestTo . pubkey_ed25519
) } `
) ;
}
try {
const parsedResponse = JSON . parse ( ret . body ) ;
const { swarm } = parsedResponse ;
if ( ! swarm ) {
throw new Error (
` Invalid JSON swarm response got for delete on snode ${ ed25519Str (
snodeToMakeRequestTo . pubkey_ed25519
) } , $ { ret ? . body } `
) ;
}
const swarmAsArray = Object . entries ( swarm ) as Array < Array < any > > ;
if ( ! swarmAsArray . length ) {
throw new Error (
` Invalid JSON swarmAsArray response got for delete on snode ${ ed25519Str (
snodeToMakeRequestTo . pubkey_ed25519
) } , $ { ret ? . body } `
) ;
}
// results will only contains the snode pubkeys which returned invalid/empty results
const results : Array < string > = _ . compact (
swarmAsArray . map ( snode = > {
const snodePubkey = snode [ 0 ] ;
const snodeJson = snode [ 1 ] ;
//#region failure handling
const isFailed = snodeJson . failed || false ;
if ( isFailed ) {
const reason = snodeJson . reason ;
const statusCode = snodeJson . code ;
if ( reason && statusCode ) {
window ? . log ? . warn (
` Could not delete data from ${ ed25519Str (
snodeToMakeRequestTo . pubkey_ed25519
) } due to error : $ { reason } : $ { statusCode } `
) ;
} else {
window ? . log ? . warn (
` Could not delete data from ${ ed25519Str (
snodeToMakeRequestTo . pubkey_ed25519
) } `
) ;
}
return snodePubkey ;
}
//#endregion
//#region verification
const responseHashes = snodeJson . deleted as Array < string > ;
const signatureSnode = snodeJson . signature as string ;
// The signature looks like ( PUBKEY_HEX || RMSG[0] || ... || RMSG[N] || DMSG[0] || ... || DMSG[M] )
const dataToVerify = ` ${ userX25519PublicKey } ${ hashes . join (
''
) } $ { responseHashes . join ( '' ) } ` ;
const dataToVerifyUtf8 = StringUtils . encode ( dataToVerify , 'utf8' ) ;
const isValid = sodium . crypto_sign_verify_detached (
fromBase64ToArray ( signatureSnode ) ,
new Uint8Array ( dataToVerifyUtf8 ) ,
fromHexToArray ( snodePubkey )
) ;
if ( ! isValid ) {
return snodePubkey ;
}
return null ;
//#endregion
} )
) ;
return results ;
} catch ( e ) {
throw new Error (
` Invalid JSON response got for delete on snode ${ ed25519Str (
snodeToMakeRequestTo . pubkey_ed25519
) } , $ { ret ? . body } `
) ;
}
} ,
{
retries : 3 ,
minTimeout : exports.TEST_getMinTimeout ( ) ,
onFailedAttempt : e = > {
window ? . log ? . warn (
` delete INNER request attempt # ${ e . attemptNumber } failed. ${ e . retriesLeft } retries left... `
) ;
} ,
}
) ;
} ,
{
retries : 3 ,
minTimeout : exports.TEST_getMinTimeout ( ) ,
onFailedAttempt : e = > {
window ? . log ? . warn (
` delete OUTER request attempt # ${ e . attemptNumber } failed. ${ e . retriesLeft } retries left... `
) ;
} ,
}
) ;
return maliciousSnodes ;
} catch ( e ) {
window ? . log ? . warn ( 'failed to delete message on network:' , e ) ;
return null ;
}
} ;