From 0bb67217685c0b58b341605eb4a29e1368604a1b Mon Sep 17 00:00:00 2001
From: Niels Andriesse <andriesseniels@gmail.com>
Date: Tue, 24 Sep 2019 14:20:22 +1000
Subject: [PATCH] Create DeviceLinkingMessage

---
 Signal.xcodeproj/project.pbxproj              |  8 ++--
 .../xcshareddata/xcschemes/Signal.xcscheme    | 39 +++++++++---------
 ...ntroller.swift => NewConversationVC.swift} |  4 +-
 Signal/src/Loki/Onboarding/SeedVC.swift       |  9 +++--
 .../Loki/Settings/DeviceLinkingModal.swift    |  2 +-
 .../Settings/DeviceLinkingModalDelegate.swift |  3 +-
 Signal/src/Signal-Bridging-Header.h           |  1 +
 .../ConversationViewController.m              |  2 +-
 .../HomeView/HomeViewController.m             |  4 +-
 .../OnboardingPermissionsViewController.swift |  1 +
 SignalMessaging/utils/ThreadUtil.h            |  3 +-
 SignalMessaging/utils/ThreadUtil.m            | 17 ++++++--
 .../Multi Device/DeviceLinkingSession.swift   |  4 --
 .../Loki/Messaging/LKDeviceLinkingMessage.h   |  8 ++++
 .../Loki/Messaging/LKDeviceLinkingMessage.m   | 40 +++++++++++++++++++
 .../Messages/Interactions/TSOutgoingMessage.m |  2 +-
 .../src/Messages/OWSMessageManager.m          |  3 +-
 .../src/TestUtils/FakeContactsManager.swift   |  1 +
 18 files changed, 105 insertions(+), 46 deletions(-)
 rename Signal/src/Loki/{NewConversationViewController.swift => NewConversationVC.swift} (98%)
 create mode 100644 SignalServiceKit/src/Loki/Messaging/LKDeviceLinkingMessage.h
 create mode 100644 SignalServiceKit/src/Loki/Messaging/LKDeviceLinkingMessage.m

diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj
index cbc2c678c..cd0322bd8 100644
--- a/Signal.xcodeproj/project.pbxproj
+++ b/Signal.xcodeproj/project.pbxproj
@@ -576,7 +576,7 @@
 		B891105C2320872800F15FCC /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B891105B2320872800F15FCC /* GoogleService-Info.plist */; };
 		B891105E2320872800F15FCC /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B891105B2320872800F15FCC /* GoogleService-Info.plist */; };
 		B891105F2320872800F15FCC /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B891105B2320872800F15FCC /* GoogleService-Info.plist */; };
-		B89841E322B7579F00B1BDC6 /* NewConversationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B89841E222B7579F00B1BDC6 /* NewConversationViewController.swift */; };
+		B89841E322B7579F00B1BDC6 /* NewConversationVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B89841E222B7579F00B1BDC6 /* NewConversationVC.swift */; };
 		B90418E6183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; };
 		B9EB5ABD1884C002007CBB57 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9EB5ABC1884C002007CBB57 /* MessageUI.framework */; };
 		BFF3FB9730634F37D25903F4 /* Pods_Signal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D17BB5C25D615AB49813100C /* Pods_Signal.framework */; };
@@ -1382,7 +1382,7 @@
 		B885D5F3233491AB00EE0D8E /* DeviceLinkingModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceLinkingModal.swift; sourceTree = "<group>"; };
 		B885D5F52334A32100EE0D8E /* UIView+Constraint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Constraint.swift"; sourceTree = "<group>"; };
 		B891105B2320872800F15FCC /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
-		B89841E222B7579F00B1BDC6 /* NewConversationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationViewController.swift; sourceTree = "<group>"; };
+		B89841E222B7579F00B1BDC6 /* NewConversationVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationVC.swift; sourceTree = "<group>"; };
 		B90418E4183E9DD40038554A /* DateUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateUtil.h; sourceTree = "<group>"; };
 		B90418E5183E9DD40038554A /* DateUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DateUtil.m; sourceTree = "<group>"; };
 		B97940251832BD2400BD66CB /* UIUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIUtil.h; sourceTree = "<group>"; };
@@ -2646,7 +2646,7 @@
 				B8162F0222891AD600D46544 /* FriendRequestView.swift */,
 				B8162F0422892C5F00D46544 /* FriendRequestViewDelegate.swift */,
 				24A830A12293CD0100F4CAC0 /* LokiP2PServer.swift */,
-				B89841E222B7579F00B1BDC6 /* NewConversationViewController.swift */,
+				B89841E222B7579F00B1BDC6 /* NewConversationVC.swift */,
 				B8258491230FA5DA001B41CB /* ScanQRCodeViewController.h */,
 				B8258492230FA5E9001B41CB /* ScanQRCodeViewController.m */,
 			);
@@ -3859,7 +3859,7 @@
 				45AE48511E0732D6004D96C2 /* TurnServerInfo.swift in Sources */,
 				34B3F8771E8DF1700035BE1A /* ContactsPicker.swift in Sources */,
 				45C0DC1B1E68FE9000E04C47 /* UIApplication+OWS.swift in Sources */,
-				B89841E322B7579F00B1BDC6 /* NewConversationViewController.swift in Sources */,
+				B89841E322B7579F00B1BDC6 /* NewConversationVC.swift in Sources */,
 				45FBC5C81DF8575700E9B410 /* CallKitCallManager.swift in Sources */,
 				4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */,
 				45FBC5D11DF8592E00E9B410 /* SignalCall.swift in Sources */,
diff --git a/Signal.xcodeproj/xcshareddata/xcschemes/Signal.xcscheme b/Signal.xcodeproj/xcshareddata/xcschemes/Signal.xcscheme
index 9503ced76..6b9dae11e 100644
--- a/Signal.xcodeproj/xcshareddata/xcschemes/Signal.xcscheme
+++ b/Signal.xcodeproj/xcshareddata/xcschemes/Signal.xcscheme
@@ -28,7 +28,7 @@
             buildForAnalyzing = "YES">
             <BuildableReference
                BuildableIdentifier = "primary"
-               BlueprintIdentifier = "BF1AA1A1F4180E0BF6CA9FD699844BF7"
+               BlueprintIdentifier = "51E5126436E74C817013DE9DE3C88131"
                BuildableName = "SignalServiceKit.framework"
                BlueprintName = "SignalServiceKit"
                ReferencedContainer = "container:Pods/Pods.xcodeproj">
@@ -41,6 +41,22 @@
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       shouldUseLaunchSchemeArgsEnv = "NO">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "D221A088169C9E5E00537ABF"
+            BuildableName = "Loki Messenger.app"
+            BlueprintName = "Signal"
+            ReferencedContainer = "container:Signal.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <EnvironmentVariables>
+         <EnvironmentVariable
+            key = "runningTests_dontStartApp"
+            value = "YES"
+            isEnabled = "YES">
+         </EnvironmentVariable>
+      </EnvironmentVariables>
       <Testables>
          <TestableReference
             skipped = "NO">
@@ -113,24 +129,6 @@
             </BuildableReference>
          </TestableReference>
       </Testables>
-      <MacroExpansion>
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "D221A088169C9E5E00537ABF"
-            BuildableName = "Loki Messenger.app"
-            BlueprintName = "Signal"
-            ReferencedContainer = "container:Signal.xcodeproj">
-         </BuildableReference>
-      </MacroExpansion>
-      <EnvironmentVariables>
-         <EnvironmentVariable
-            key = "runningTests_dontStartApp"
-            value = "YES"
-            isEnabled = "YES">
-         </EnvironmentVariable>
-      </EnvironmentVariables>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"
@@ -144,6 +142,7 @@
       debugDocumentVersioning = "YES"
       stopOnEveryThreadSanitizerIssue = "YES"
       stopOnEveryUBSanitizerIssue = "YES"
+      migratedStopOnEveryIssue = "YES"
       debugServiceExtension = "internal"
       allowLocationSimulation = "YES"
       showNonLocalizedStrings = "YES">
@@ -179,8 +178,6 @@
             isEnabled = "YES">
          </EnvironmentVariable>
       </EnvironmentVariables>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "App Store Release"
diff --git a/Signal/src/Loki/NewConversationViewController.swift b/Signal/src/Loki/NewConversationVC.swift
similarity index 98%
rename from Signal/src/Loki/NewConversationViewController.swift
rename to Signal/src/Loki/NewConversationVC.swift
index be5a0dd32..5430ab13b 100644
--- a/Signal/src/Loki/NewConversationViewController.swift
+++ b/Signal/src/Loki/NewConversationVC.swift
@@ -1,6 +1,6 @@
 
-@objc(LKNewConversationViewController)
-final class NewConversationViewController : OWSViewController, OWSQRScannerDelegate {
+@objc(LKNewConversationVC)
+final class NewConversationVC : OWSViewController, OWSQRScannerDelegate {
 
     // MARK: Components
     private lazy var publicKeyTextField: UITextField = {
diff --git a/Signal/src/Loki/Onboarding/SeedVC.swift b/Signal/src/Loki/Onboarding/SeedVC.swift
index d5df7123a..46e7c0dd3 100644
--- a/Signal/src/Loki/Onboarding/SeedVC.swift
+++ b/Signal/src/Loki/Onboarding/SeedVC.swift
@@ -315,8 +315,8 @@ final class SeedVC : OnboardingBaseViewController, DeviceLinkingModalDelegate {
             }
         case .link:
             seed = self.seed
-            let hexEncodedPublicKey = masterHexEncodedPublicKeyTextField.text!.trimmingCharacters(in: CharacterSet.whitespaces)
-            if !ECKeyPair.isValidHexEncodedPublicKey(candidate: hexEncodedPublicKey) {
+            let masterHexEncodedPublicKey = masterHexEncodedPublicKeyTextField.text!.trimmingCharacters(in: CharacterSet.whitespaces)
+            if !ECKeyPair.isValidHexEncodedPublicKey(candidate: masterHexEncodedPublicKey) {
                 errorLabel2Spacer.isHidden = false
                 return errorLabel2.text = NSLocalizedString("Invalid public key", comment: "")
             }
@@ -337,15 +337,18 @@ final class SeedVC : OnboardingBaseViewController, DeviceLinkingModalDelegate {
         case .link: Analytics.shared.track("Device Linked")
         }
         if mode == .link {
+            let masterHexEncodedPublicKey = masterHexEncodedPublicKeyTextField.text!.trimmingCharacters(in: CharacterSet.whitespaces)
             let deviceLinkingModal = DeviceLinkingModal(mode: .slave, delegate: self)
             deviceLinkingModal.modalPresentationStyle = .overFullScreen
             present(deviceLinkingModal, animated: true, completion: nil)
+            let thread = TSContactThread.getOrCreateThread(contactId: masterHexEncodedPublicKey)
+            ThreadUtil.enqueueDeviceLinkingMessage(in: thread)
         } else {
             onboardingController.pushDisplayNameVC(from: self)
         }
     }
     
-    func handleDeviceLinked() {
+    func handleDeviceLinkingRequestAuthorized() {
         TSAccountManager.sharedInstance().didRegister()
         UserDefaults.standard.set(true, forKey: "didUpdateForMainnet")
         onboardingController.verificationDidComplete(fromView: self)
diff --git a/Signal/src/Loki/Settings/DeviceLinkingModal.swift b/Signal/src/Loki/Settings/DeviceLinkingModal.swift
index cc11f317e..760251133 100644
--- a/Signal/src/Loki/Settings/DeviceLinkingModal.swift
+++ b/Signal/src/Loki/Settings/DeviceLinkingModal.swift
@@ -122,8 +122,8 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
     @objc private func authorizeDeviceLink() {
         let deviceLink = self.deviceLink!
         let session = DeviceLinkingSession.current!
-        session.authorizeDeviceLink(deviceLink)
         session.stopListeningForLinkingRequests()
+        // TODO: Send device link authorized message
         dismiss(animated: true, completion: nil)
     }
 }
diff --git a/Signal/src/Loki/Settings/DeviceLinkingModalDelegate.swift b/Signal/src/Loki/Settings/DeviceLinkingModalDelegate.swift
index 210becab1..984daf70b 100644
--- a/Signal/src/Loki/Settings/DeviceLinkingModalDelegate.swift
+++ b/Signal/src/Loki/Settings/DeviceLinkingModalDelegate.swift
@@ -2,5 +2,6 @@
 @objc(LKDeviceLinkingModalDelegate)
 protocol DeviceLinkingModalDelegate {
     
-    func handleDeviceLinked()
+    /// Provides an opportunity for the slave device to update its UI after the master device has authorized the device linking request.
+    func handleDeviceLinkingRequestAuthorized()
 }
diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h
index 31e65d573..dec8661f9 100644
--- a/Signal/src/Signal-Bridging-Header.h
+++ b/Signal/src/Signal-Bridging-Header.h
@@ -91,6 +91,7 @@
 #import <SignalServiceKit/OWSDispatch.h>
 #import <SignalServiceKit/OWSEndSessionMessage.h>
 #import <SignalServiceKit/LKEphemeralMessage.h>
+#import <SignalServiceKit/LKDeviceLinkingMessage.h>
 #import <SignalServiceKit/OWSError.h>
 #import <SignalServiceKit/OWSFileSystem.h>
 #import <SignalServiceKit/LKFriendRequestMessage.h>
diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m
index af507073b..fa5b147e8 100644
--- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m
+++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m
@@ -4348,7 +4348,7 @@ typedef enum : NSUInteger {
     // Update the thread's friend request status
     [self.thread saveFriendRequestStatus:LKThreadFriendRequestStatusFriends withTransaction:nil];
     // Send a friend request accepted message
-    [ThreadUtil enqueueAcceptFriendRequestMessageInThread:self.thread];
+    [ThreadUtil enqueueFriendRequestAcceptanceMessageInThread:self.thread];
 }
 
 - (void)declineFriendRequest:(TSIncomingMessage *)friendRequest
diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m
index 4a3162af9..ca513eda8 100644
--- a/Signal/src/ViewControllers/HomeView/HomeViewController.m
+++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m
@@ -835,8 +835,8 @@ typedef NS_ENUM(NSInteger, HomeViewControllerSection) {
 
 - (void)showNewConversationView
 {
-    LKNewConversationViewController *viewController = [LKNewConversationViewController new];
-    OWSNavigationController *navigationController = [[OWSNavigationController alloc] initWithRootViewController:viewController];
+    LKNewConversationVC *newConversationVC = [LKNewConversationVC new];
+    OWSNavigationController *navigationController = [[OWSNavigationController alloc] initWithRootViewController:newConversationVC];
     [self.navigationController presentViewController:navigationController animated:YES completion:nil];
     
     /**
diff --git a/Signal/src/ViewControllers/Registration/OnboardingPermissionsViewController.swift b/Signal/src/ViewControllers/Registration/OnboardingPermissionsViewController.swift
index 82f21c825..cfb6da989 100644
--- a/Signal/src/ViewControllers/Registration/OnboardingPermissionsViewController.swift
+++ b/Signal/src/ViewControllers/Registration/OnboardingPermissionsViewController.swift
@@ -4,6 +4,7 @@
 
 import UIKit
 import PromiseKit
+import Contacts
 
 @objc
 public class OnboardingPermissionsViewController: OnboardingBaseViewController {
diff --git a/SignalMessaging/utils/ThreadUtil.h b/SignalMessaging/utils/ThreadUtil.h
index 229ccdc25..1f0e92c3a 100644
--- a/SignalMessaging/utils/ThreadUtil.h
+++ b/SignalMessaging/utils/ThreadUtil.h
@@ -44,7 +44,8 @@ NS_ASSUME_NONNULL_BEGIN
 
 #pragma mark - Durable Message Enqueue
 
-+ (TSOutgoingMessage *)enqueueAcceptFriendRequestMessageInThread:(TSThread *)thread;
++ (TSOutgoingMessage *)enqueueFriendRequestAcceptanceMessageInThread:(TSThread *)thread;
++ (TSOutgoingMessage *)enqueueDeviceLinkingMessageInThread:(TSThread *)thread;
 
 + (TSOutgoingMessage *)enqueueMessageWithText:(NSString *)fullMessageText
                                      inThread:(TSThread *)thread
diff --git a/SignalMessaging/utils/ThreadUtil.m b/SignalMessaging/utils/ThreadUtil.m
index c4197aea3..5daaee1a4 100644
--- a/SignalMessaging/utils/ThreadUtil.m
+++ b/SignalMessaging/utils/ThreadUtil.m
@@ -85,13 +85,22 @@ typedef void (^BuildOutgoingMessageCompletionBlock)(TSOutgoingMessage *savedMess
 
 #pragma mark - Durable Message Enqueue
 
-+ (LKEphemeralMessage *)enqueueAcceptFriendRequestMessageInThread:(TSThread *)thread
++ (LKEphemeralMessage *)enqueueFriendRequestAcceptanceMessageInThread:(TSThread *)thread
 {
-    LKEphemeralMessage *emptyMessage = [[LKEphemeralMessage alloc] initInThread:thread];
+    LKEphemeralMessage *message = [[LKEphemeralMessage alloc] initInThread:thread];
     [self.dbConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
-        [self.messageSenderJobQueue addMessage:emptyMessage transaction:transaction];
+        [self.messageSenderJobQueue addMessage:message transaction:transaction];
     }];
-    return emptyMessage;
+    return message;
+}
+
++ (LKDeviceLinkingMessage *)enqueueDeviceLinkingMessageInThread:(TSThread *)thread
+{
+    LKDeviceLinkingMessage *message = [[LKDeviceLinkingMessage alloc] initInThread:thread];
+    [self.dbConnection asyncReadWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
+        [self.messageSenderJobQueue addMessage:message transaction:transaction];
+    }];
+    return message;
 }
 
 + (TSOutgoingMessage *)enqueueMessageWithText:(NSString *)fullMessageText
diff --git a/SignalServiceKit/src/Loki/API/Multi Device/DeviceLinkingSession.swift b/SignalServiceKit/src/Loki/API/Multi Device/DeviceLinkingSession.swift
index c0b9f701c..af8b5bf1a 100644
--- a/SignalServiceKit/src/Loki/API/Multi Device/DeviceLinkingSession.swift	
+++ b/SignalServiceKit/src/Loki/API/Multi Device/DeviceLinkingSession.swift	
@@ -36,10 +36,6 @@ public final class DeviceLinkingSession : NSObject {
         isListeningForLinkingRequests = false
     }
     
-    public func authorizeDeviceLink(_ deviceLink: DeviceLink) {
-        // TODO: Send a device link authorized message
-    }
-    
     // MARK: Private API
     private func isValidLinkingRequest(_ deviceLink: DeviceLink) -> Bool {
         // When requesting a device link, the slave device signs the master device's public key. When authorizing
diff --git a/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkingMessage.h b/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkingMessage.h
new file mode 100644
index 000000000..edb82900c
--- /dev/null
+++ b/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkingMessage.h
@@ -0,0 +1,8 @@
+#import "TSOutgoingMessage.h"
+
+NS_SWIFT_NAME(DeviceLinkingMessage)
+@interface LKDeviceLinkingMessage : TSOutgoingMessage
+
+- (instancetype)initInThread:(TSThread *)thread;
+
+@end
diff --git a/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkingMessage.m b/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkingMessage.m
new file mode 100644
index 000000000..0c61ee7fa
--- /dev/null
+++ b/SignalServiceKit/src/Loki/Messaging/LKDeviceLinkingMessage.m
@@ -0,0 +1,40 @@
+#import "LKDeviceLinkingMessage.h"
+#import "OWSIdentityManager.h"
+#import "SignalRecipient.h"
+#import <SignalCoreKit/NSData+OWS.h>
+#import <SignalCoreKit/NSDate+OWS.h>
+#import <SignalServiceKit/SignalServiceKit-Swift.h>
+
+@implementation LKDeviceLinkingMessage
+
+- (instancetype)initInThread:(nullable TSThread *)thread {
+    return [self initOutgoingMessageWithTimestamp:NSDate.ows_millisecondTimeStamp inThread:thread messageBody:@"" attachmentIds:[NSMutableArray<NSString *> new]
+        expiresInSeconds:0 expireStartedAt:0 isVoiceMessage:NO groupMetaMessage:TSGroupMetaMessageUnspecified quotedMessage:nil contactShare:nil linkPreview:nil];
+}
+
+- (SSKProtoContentBuilder *)contentBuilder:(SignalRecipient *)recipient {
+    SSKProtoContentBuilder *contentBuilder = [super contentBuilder:recipient];
+    // When authorizing a device link, the master device signs the slave device's public key. When requesting
+    // a device link, the slave device signs the master device's public key.
+    SSKProtoLokiDeviceLinkingMessageBuilder *deviceLinkingMessageBuilder = [SSKProtoLokiDeviceLinkingMessage builder];
+    NSString *masterHexEncodedPublicKey = recipient.recipientId;
+    NSData *masterPublicKey = [NSData dataFromHexString:masterHexEncodedPublicKey];
+    [deviceLinkingMessageBuilder setMasterHexEncodedPublicKey:masterHexEncodedPublicKey];
+    ECKeyPair *slaveKeyPair = OWSIdentityManager.sharedManager.identityKeyPair;
+    NSString *slaveHexEncodedPublicKey = slaveKeyPair.hexEncodedPublicKey;
+    [deviceLinkingMessageBuilder setSlaveHexEncodedPublicKey:slaveHexEncodedPublicKey];
+    NSData *slaveSignature = [Ed25519 sign:masterPublicKey withKeyPair:slaveKeyPair error:nil];
+    [deviceLinkingMessageBuilder setSlaveSignature:slaveSignature];
+    NSError *error;
+    SSKProtoLokiDeviceLinkingMessage *deviceLinkingMessage = [deviceLinkingMessageBuilder buildAndReturnError:&error];
+    if (error || deviceLinkingMessage == nil) {
+        OWSFailDebug(@"Failed to build device linking message for: %@ due to error: %@", masterHexEncodedPublicKey, error);
+    }
+    [contentBuilder setLokiDeviceLinkingMessage:deviceLinkingMessage];
+    return contentBuilder;
+}
+
+- (BOOL)shouldSyncTranscript { return NO; }
+- (BOOL)shouldBeSaved { return NO; }
+
+@end
diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m
index bb68e62a1..b48001550 100644
--- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m
+++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m
@@ -1103,12 +1103,12 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt
 
     [ProtoUtils addLocalProfileKeyIfNecessary:self.thread recipientId:recipientId dataMessageBuilder:builder];
 
+    // Loki: Set display name if needed
     id<ProfileManagerProtocol> profileManager = SSKEnvironment.shared.profileManager;
     NSString *displayName = profileManager.localProfileName;
     if (displayName != nil) {
         SSKProtoDataMessageLokiProfileBuilder *profileBuilder = [SSKProtoDataMessageLokiProfile builder];
         [profileBuilder setDisplayName:displayName];
-        
         SSKProtoDataMessageLokiProfile *profile = [profileBuilder buildAndReturnError:nil];
         [builder setProfile:profile];
     }
diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m
index 7c37f4948..c1f17b9c7 100644
--- a/SignalServiceKit/src/Messages/OWSMessageManager.m
+++ b/SignalServiceKit/src/Messages/OWSMessageManager.m
@@ -20,6 +20,7 @@
 #import "OWSDisappearingMessagesConfiguration.h"
 #import "OWSDisappearingMessagesJob.h"
 #import "LKEphemeralMessage.h"
+#import "LKDeviceLinkingMessage.h"
 #import "OWSIdentityManager.h"
 #import "OWSIncomingMessageFinder.h"
 #import "OWSIncomingSentMessageTranscript.h"
@@ -1546,7 +1547,7 @@ NS_ASSUME_NONNULL_BEGIN
         if (existingFriendRequestMessage != nil && existingFriendRequestMessage.isFriendRequest) {
             [existingFriendRequestMessage saveFriendRequestStatus:LKMessageFriendRequestStatusAccepted withTransaction:transaction];
         }
-        // The two lines below are equivalent to calling [ThreadUtil enqueueAcceptFriendRequestMessageInThread:thread]
+        // The two lines below are equivalent to calling [ThreadUtil enqueueFriendRequestAcceptanceMessageInThread:thread]
         LKEphemeralMessage *backgroundMessage = [[LKEphemeralMessage alloc] initInThread:thread];
         [self.messageSenderJobQueue addMessage:backgroundMessage transaction:transaction];
     } else if (!thread.isContactFriend) {
diff --git a/SignalServiceKit/src/TestUtils/FakeContactsManager.swift b/SignalServiceKit/src/TestUtils/FakeContactsManager.swift
index 94c3eb437..47abf2479 100644
--- a/SignalServiceKit/src/TestUtils/FakeContactsManager.swift
+++ b/SignalServiceKit/src/TestUtils/FakeContactsManager.swift
@@ -1,6 +1,7 @@
 //
 //  Copyright (c) 2018 Open Whisper Systems. All rights reserved.
 //
+import Contacts
 
 @objc(OWSFakeContactsManager)
 public class FakeContactsManager: NSObject, ContactsManagerProtocol {