@ -719,6 +719,99 @@ public final class SnodeAPI {
return promise
return promise
}
}
// MARK: E d i t
public static func updateExpiry (
publicKey : String ,
edKeyPair : Box . KeyPair ,
updatedExpiryMs : UInt64 ,
serverHashes : [ String ]
) -> Promise < [ String : ( hashes : [ String ] , expiry : UInt64 ) ] > {
let publicKey = ( Features . useTestnet ? publicKey . removingIdPrefixIfNeeded ( ) : publicKey )
return attempt ( maxRetryCount : maxRetryCount , recoveringOn : Threading . workQueue ) {
getSwarm ( for : publicKey )
. then2 { swarm -> Promise < [ String : ( hashes : [ String ] , expiry : UInt64 ) ] > in
// " e x p i r e " | | e x p i r y | | m e s s a g e s [ 0 ] | | . . . | | m e s s a g e s [ N ]
let verificationBytes = SnodeAPIEndpoint . expire . rawValue . bytes
. appending ( contentsOf : " \( updatedExpiryMs ) " . data ( using : . ascii ) ? . bytes )
. appending ( contentsOf : serverHashes . joined ( ) . bytes )
guard
let snode = swarm . randomElement ( ) ,
let signature = sodium . sign . signature (
message : verificationBytes ,
secretKey : edKeyPair . secretKey
)
else {
throw SnodeAPIError . signingFailed
}
let parameters : JSON = [
" pubkey " : publicKey ,
" pubkey_ed25519 " : edKeyPair . publicKey . toHexString ( ) ,
" expiry " : updatedExpiryMs ,
" messages " : serverHashes ,
" signature " : signature . toBase64 ( )
]
return attempt ( maxRetryCount : maxRetryCount , recoveringOn : Threading . workQueue ) {
invoke ( . expire , on : snode , associatedWith : publicKey , parameters : parameters )
. map2 { responseData -> [ String : ( hashes : [ String ] , expiry : UInt64 ) ] in
guard let responseJson : JSON = try ? JSONSerialization . jsonObject ( with : responseData , options : [ . fragmentsAllowed ] ) as ? JSON else {
throw HTTP . Error . invalidJSON
}
guard let swarm = responseJson [ " swarm " ] as ? JSON else { throw HTTP . Error . invalidJSON }
var result : [ String : ( hashes : [ String ] , expiry : UInt64 ) ] = [ : ]
for ( snodePublicKey , rawJSON ) in swarm {
guard let json = rawJSON as ? JSON else { throw HTTP . Error . invalidJSON }
guard ( json [ " failed " ] as ? Bool ? ? false ) = = false else {
if let reason = json [ " reason " ] as ? String , let statusCode = json [ " code " ] as ? String {
SNLog ( " Couldn't delete data from: \( snodePublicKey ) due to error: \( reason ) ( \( statusCode ) ). " )
}
else {
SNLog ( " Couldn't delete data from: \( snodePublicKey ) . " )
}
result [ snodePublicKey ] = ( [ ] , 0 )
continue
}
guard
let hashes : [ String ] = json [ " updated " ] as ? [ String ] ,
let expiryApplied : UInt64 = json [ " expiry " ] as ? UInt64 ,
let signature : String = json [ " signature " ] as ? String
else {
throw HTTP . Error . invalidJSON
}
// T h e s i g n a t u r e f o r m a t i s ( P U B K E Y _ H E X | | E X P I R Y | | R M S G [ 0 ] | | . . . | | R M S G [ N ] | | U M S G [ 0 ] | | . . . | | U M S G [ M ] )
let verificationBytes = publicKey . bytes
. appending ( contentsOf : " \( expiryApplied ) " . data ( using : . ascii ) ? . bytes )
. appending ( contentsOf : serverHashes . joined ( ) . bytes )
. appending ( contentsOf : hashes . joined ( ) . bytes )
let isValid = sodium . sign . verify (
message : verificationBytes ,
publicKey : Bytes ( Data ( hex : snodePublicKey ) ) ,
signature : Bytes ( Data ( base64Encoded : signature ) ! )
)
// E n s u r e t h e s i g n a t u r e i s v a l i d
guard isValid else {
throw SnodeAPIError . signatureVerificationFailed
}
result [ snodePublicKey ] = ( hashes , expiryApplied )
}
return result
}
}
}
}
}
// MARK: D e l e t e
// MARK: D e l e t e
public static func deleteMessage ( publicKey : String , serverHashes : [ String ] ) -> Promise < [ String : Bool ] > {
public static func deleteMessage ( publicKey : String , serverHashes : [ String ] ) -> Promise < [ String : Bool ] > {
@ -732,10 +825,16 @@ public final class SnodeAPI {
return attempt ( maxRetryCount : maxRetryCount , recoveringOn : Threading . workQueue ) {
return attempt ( maxRetryCount : maxRetryCount , recoveringOn : Threading . workQueue ) {
getSwarm ( for : publicKey )
getSwarm ( for : publicKey )
. then2 { swarm -> Promise < [ String : Bool ] > in
. then2 { swarm -> Promise < [ String : Bool ] > in
// " d e l e t e " | | m e s s a g e s . . .
let verificationBytes = SnodeAPIEndpoint . deleteMessage . rawValue . bytes
. appending ( contentsOf : serverHashes . joined ( ) . bytes )
guard
guard
let snode = swarm . randomElement ( ) ,
let snode = swarm . randomElement ( ) ,
let verificationData = ( SnodeAPIEndpoint . deleteMessage . rawValue + serverHashes . joined ( ) ) . data ( using : String . Encoding . utf8 ) ,
let signature = sodium . sign . signature (
let signature = sodium . sign . signature ( message : Bytes ( verificationData ) , secretKey : userED25519KeyPair . secretKey )
message : verificationBytes ,
secretKey : userED25519KeyPair . secretKey
)
else {
else {
throw SnodeAPIError . signingFailed
throw SnodeAPIError . signingFailed
}
}
@ -771,15 +870,11 @@ public final class SnodeAPI {
}
}
// T h e s i g n a t u r e f o r m a t i s ( P U B K E Y _ H E X | | R M S G [ 0 ] | | . . . | | R M S G [ N ] | | D M S G [ 0 ] | | . . . | | D M S G [ M ] )
// T h e s i g n a t u r e f o r m a t i s ( P U B K E Y _ H E X | | R M S G [ 0 ] | | . . . | | R M S G [ N ] | | D M S G [ 0 ] | | . . . | | D M S G [ M ] )
let verificationData = [
let verificationBytes = userX25519PublicKey . bytes
userX25519PublicKey ,
. appending ( contentsOf : serverHashes . joined ( ) . bytes )
serverHashes . joined ( ) ,
. appending ( contentsOf : hashes . joined ( ) . bytes )
hashes . joined ( )
]
. joined ( )
. data ( using : String . Encoding . utf8 ) !
let isValid = sodium . sign . verify (
let isValid = sodium . sign . verify (
message : Bytes( verificationData ) ,
message : verificationBytes ,
publicKey : Bytes ( Data ( hex : snodePublicKey ) ) ,
publicKey : Bytes ( Data ( hex : snodePublicKey ) ) ,
signature : Bytes ( Data ( base64Encoded : signature ) ! )
signature : Bytes ( Data ( base64Encoded : signature ) ! )
)
)