Merge pull request #6 from loki-project/message-proof-of-work

Hooked up proof of work to message sending.
pull/7/head
Niels Andriesse 7 years ago committed by GitHub
commit 8b7e5003d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -43,7 +43,7 @@ private extension MutableCollection where Element == UInt8, Index == Int {
* This was copied from the desktop messenger.
* Ref: libloki/proof-of-work.js
*/
public enum ProofOfWork {
@objc public class ProofOfWork : NSObject {
// If this changes then we also have to use something other than UInt64 to support the new length
private static let nonceLength = 8
@ -54,37 +54,22 @@ public enum ProofOfWork {
case .production: return 100
}
}()
public struct Configuration {
var pubKey: String
var data: String
var timestamp: Date
var ttl: Int
var payload: [UInt8] {
let timestampString = String(Int(timestamp.timeIntervalSince1970))
let ttlString = String(ttl)
let payloadString = timestampString + ttlString + pubKey + data
return payloadString.bytes
}
public init(pubKey: String, data: String, timestamp: Date, ttl: Int) {
self.pubKey = pubKey
self.data = data
self.timestamp = timestamp
self.ttl = ttl
}
}
private override init() { }
/// Calculate a proof of work with the given configuration
///
/// Ref: https://bitmessage.org/wiki/Proof_of_work
///
/// - Parameter config: The configuration
/// - Parameters:
/// - data: The message data
/// - pubKey: The message recipient
/// - timestamp: The timestamp
/// - ttl: The message time to live
/// - Returns: A nonce string or nil if it failed
public static func calculate(with config: Configuration) -> String? {
let payload = config.payload
let target = calcTarget(ttl: config.ttl, payloadLength: payload.count, nonceTrials: nonceTrialCount)
@objc public static func calculate(data: String, pubKey: String, timestamp: UInt64, ttl: Int) -> String? {
let payload = getPayload(pubKey: pubKey, data: data, timestamp: timestamp, ttl: ttl)
let target = calcTarget(ttl: ttl, payloadLength: payload.count, nonceTrials: nonceTrialCount)
// Start with the max value
var trialValue = UInt64.max
@ -105,6 +90,14 @@ public enum ProofOfWork {
return nonce.toBase64()
}
/// Get the proof of work payload
private static func getPayload(pubKey: String, data: String, timestamp: UInt64, ttl: Int) -> [UInt8] {
let timestampString = String(timestamp)
let ttlString = String(ttl)
let payloadString = timestampString + ttlString + pubKey + data
return payloadString.bytes
}
/// Calculate the target we need to reach
private static func calcTarget(ttl: Int, payloadLength: Int, nonceTrials: Int) -> UInt64 {
let two16 = UInt64(pow(2, 16) - 1)

@ -915,6 +915,38 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
return deviceMessages;
}
- (AnyPromise *)calculateProofOfWorkForDeviceMessages:(NSArray<NSDictionary *> *)deviceMessages
ttl:(NSNumber *)ttl
{
// Loki: Calculate the proof of work for each device message
NSMutableArray *promises = [NSMutableArray new];
for (NSDictionary<NSString *, id> *deviceMessage in deviceMessages) {
AnyPromise *promise = [AnyPromise promiseWithValue:deviceMessage]
.thenOn(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(NSDictionary<NSString *, id> *message) {
NSTimeInterval timestampInterval = [[NSDate new] timeIntervalSince1970];
NSNumber *timestamp = [NSNumber numberWithDouble:timestampInterval];
NSString *destination = message[@"destination"];
NSString *data = message[@"content"];
NSString *_Nullable nonce = [ProofOfWork calculateWithData:data pubKey:destination timestamp:timestamp.unsignedIntegerValue ttl:ttl.integerValue];
// Return our timestamp along with the nonce
// These will help us identify which nonce belongs to which message
return @{
@"destination" : destination,
@"deviceId" : message[@"destinationDeviceId"],
@"timestamp" : timestamp,
@"nonce" : nonce
};
});
[promises addObject:promise];
}
// Wait for all the PoW Calculations to finish
return PMKWhen(promises);
}
- (void)sendMessageToRecipient:(OWSMessageSend *)messageSend
{
OWSAssertDebug(messageSend);
@ -1086,66 +1118,91 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
// so that we can learn from the service whether or not there are
// linked devices that we don't know about.
OWSLogWarn(@"Sending a message with no device messages.");
// Loki: We don't handle linked devices yet so it's better to just exit early
// This would only occur if we're sending a message to ourself
NSError *error = OWSErrorMakeFailedToSendOutgoingMessageError();
[error setIsRetryable:NO];
return messageSend.failure(error);
}
OWSRequestMaker *requestMaker = [[OWSRequestMaker alloc] initWithLabel:@"Message Send"
requestFactoryBlock:^(SMKUDAccessKey *_Nullable udAccessKey) {
return [OWSRequestFactory submitMessageRequestWithRecipient:recipient.recipientId
messages:deviceMessages
timeStamp:message.timestamp
udAccessKey:udAccessKey];
}
udAuthFailureBlock:^{
// Note the UD auth failure so subsequent retries
// to this recipient also use basic auth.
[messageSend setHasUDAuthFailed];
}
websocketFailureBlock:^{
// Note the websocket failure so subsequent retries
// to this recipient also use REST.
messageSend.hasWebsocketSendFailed = YES;
}
recipientId:recipient.recipientId
udAccess:messageSend.udAccess
canFailoverUDAuth:NO];
[[requestMaker makeRequestObjc]
.then(^(OWSRequestMakerResult *result) {
dispatch_async([OWSDispatch sendingQueue], ^{
[self messageSendDidSucceed:messageSend
deviceMessages:deviceMessages
wasSentByUD:result.wasSentByUD
wasSentByWebsocket:result.wasSentByWebsocket];
});
})
.catch(^(NSError *error) {
dispatch_async([OWSDispatch sendingQueue], ^{
NSUInteger statusCode = 0;
NSData *_Nullable responseData = nil;
if ([error.domain isEqualToString:@"SignalServiceKit.RequestMakerUDAuthError"]) {
// Try again.
OWSLogInfo(@"UD request auth failed; failing over to non-UD request.");
[error setIsRetryable:YES];
} else if ([error.domain isEqualToString:TSNetworkManagerErrorDomain]) {
statusCode = error.code;
NSError *_Nullable underlyingError = error.userInfo[NSUnderlyingErrorKey];
if (underlyingError) {
responseData
= underlyingError.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
} else {
OWSFailDebug(@"Missing underlying error: %@", error);
}
// TODO: Update message here to show the pow cog icon
// Loki: Calculate the proof of work for each device message
NSNumber *ttl = [NSNumber numberWithInteger:@(4 * 24 * 60 * 60)];
AnyPromise *powPromise = [self calculateProofOfWorkForDeviceMessages:deviceMessages ttl:ttl];
[powPromise
.thenOn([OWSDispatch sendingQueue], ^(NSArray *nonceArray) {
OWSRequestMaker *requestMaker = [[OWSRequestMaker alloc] initWithLabel:@"Message Send"
requestFactoryBlock:^(SMKUDAccessKey *_Nullable udAccessKey) {
// Loki
// ========
return [OWSRequestFactory submitLokiMessageRequestWithRecipient:recipient.recipientId
messages:deviceMessages
nonceArray:nonceArray
ttl:ttl];
// ========
/* Original code:
return [OWSRequestFactory submitMessageRequestWithRecipient:recipient.recipientId
messages:deviceMessages
timeStamp:message.timestamp
udAccessKey:udAccessKey];
*/
}
udAuthFailureBlock:^{
// Note the UD auth failure so subsequent retries
// to this recipient also use basic auth.
[messageSend setHasUDAuthFailed];
}
websocketFailureBlock:^{
// Note the websocket failure so subsequent retries
// to this recipient also use REST.
messageSend.hasWebsocketSendFailed = YES;
}
recipientId:recipient.recipientId
udAccess:messageSend.udAccess
canFailoverUDAuth:NO];
return requestMaker;
})
.thenOn([OWSDispatch sendingQueue], ^(OWSRequestMaker *requestMaker) {
return [requestMaker makeRequestObjc];
}).then(^(OWSRequestMakerResult *result) {
dispatch_async([OWSDispatch sendingQueue], ^{
[self messageSendDidSucceed:messageSend
deviceMessages:deviceMessages
wasSentByUD:result.wasSentByUD
wasSentByWebsocket:result.wasSentByWebsocket];
});
})
.catch(^(NSError *error) {
dispatch_async([OWSDispatch sendingQueue], ^{
NSUInteger statusCode = 0;
NSData *_Nullable responseData = nil;
if ([error.domain isEqualToString:@"SignalServiceKit.RequestMakerUDAuthError"]) {
// Try again.
OWSLogInfo(@"UD request auth failed; failing over to non-UD request.");
[error setIsRetryable:YES];
} else if ([error.domain isEqualToString:TSNetworkManagerErrorDomain]) {
statusCode = error.code;
NSError *_Nullable underlyingError = error.userInfo[NSUnderlyingErrorKey];
if (underlyingError) {
responseData
= underlyingError.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
} else {
OWSFailDebug(@"Unexpected error: %@", error);
OWSFailDebug(@"Missing underlying error: %@", error);
}
[self messageSendDidFail:messageSend
deviceMessages:deviceMessages
statusCode:statusCode
error:error
responseData:responseData];
});
}) retainUntilComplete];
} else {
OWSFailDebug(@"Unexpected error: %@", error);
}
[self messageSendDidFail:messageSend
deviceMessages:deviceMessages
statusCode:statusCode
error:error
responseData:responseData];
});
}) retainUntilComplete];
}
- (void)messageSendDidSucceed:(OWSMessageSend *)messageSend
@ -1465,7 +1522,13 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
messageSend.isLocalNumber,
messageSend.isUDSend);
// Loki: Since we don't support multi-device sending yet, just send it to the primary device
NSMutableArray<NSNumber *> *deviceIds = @[@(OWSDevicePrimaryDeviceId)];
/* Original code:
NSMutableArray<NSNumber *> *deviceIds = [recipient.devices mutableCopy];
*/
OWSAssertDebug(deviceIds);
if (messageSend.isLocalNumber) {

@ -59,6 +59,11 @@ typedef NS_ENUM(NSUInteger, TSVerificationTransport) { TSVerificationTransportVo
captchaToken:(nullable NSString *)captchaToken
transport:(TSVerificationTransport)transport;
+ (TSRequest *)submitLokiMessageRequestWithRecipient:(NSString *)recipientId
messages:(NSArray *)messages
nonceArray:(NSArray *)nonceArray
ttl:(NSNumber *)ttl;
+ (TSRequest *)submitMessageRequestWithRecipient:(NSString *)recipientId
messages:(NSArray *)messages
timeStamp:(uint64_t)timeStamp

@ -354,6 +354,62 @@ NS_ASSUME_NONNULL_BEGIN
return [accountAttributes copy];
}
// Loki: Convert Signal JSON messages to Loki messages
// Refer to OWSMessageServiceParams for the Signal JSON params
+ (NSArray *)lokiMessagesFromMessages:(NSArray *)messages
nonceArray:(NSArray *)nonceArray
ttl:(NSNumber *)ttl {
NSMutableArray *modifiedMessages = [NSMutableArray new];
for (NSDictionary *message in messages) {
NSMutableDictionary *lokiMessage = [NSMutableDictionary new];
// Params for our message server
lokiMessage[@"pubKey"] = message[@"destination"];
lokiMessage[@"data"] = message[@"content"];
lokiMessage[@"ttl"] = ttl;
NSDictionary *_Nullable nonce = [self getNonceFromArray:nonceArray forMessage:message];
if (nonce) {
lokiMessage[@"timestamp"] = nonce[@"timestmap"];
lokiMessage[@"nonce"] = nonce[@"nonce"];
}
[modifiedMessages addObject:lokiMessage];
}
return modifiedMessages;
}
+ (NSDictionary *_Nullable)getNonceFromArray:(NSArray *)nonceArray forMessage:(NSDictionary *)message {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"destination == %@ AND deviceId == %d", message[@"destination"], message[@"destinationDeviceId"]];
NSArray *filtered = [nonceArray filteredArrayUsingPredicate:predicate];
return filtered.count > 0 ? [filtered objectAtIndex:0] : nil;
}
// Loki: This is the function below with our changes
+ (TSRequest *)submitLokiMessageRequestWithRecipient:(NSString *)recipientId
messages:(NSArray *)messages
nonceArray:(NSArray *)nonceArray
ttl:(NSNumber *)ttl
{
// Messages may be empty; see comments in OWSDeviceManager.
// This doesn't apply to Loki since we don't have linked device support.
OWSAssertDebug(recipientId.length > 0);
OWSAssertDebug(messages.count > 0);
// Convert to Loki JSON format
NSArray *lokiMessages = [self lokiMessagesFromMessages:messages nonceArray:nonceArray ttl:ttl];
OWSAssertDebug(lokiMessages.count > 0);
// Loki: Just send the first message
NSString *path = [textSecureMessagesAPI stringByAppendingString:recipientId];
NSDictionary *parameters = [lokiMessages objectAtIndex:0];
TSRequest *request = [TSRequest requestWithUrl:[NSURL URLWithString:path] method:@"PUT" parameters:parameters];
return request;
}
+ (TSRequest *)submitMessageRequestWithRecipient:(NSString *)recipientId
messages:(NSArray *)messages
timeStamp:(uint64_t)timeStamp

Loading…
Cancel
Save