Merge pull request #2 from loki-project/niels

Implement Mnemonic Based Key Pair Restoration
pull/4/head
Niels Andriesse 5 years ago committed by GitHub
commit 7a6b87bc63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -77,6 +77,7 @@
#import <SignalServiceKit/Contact.h>
#import <SignalServiceKit/ContactsUpdater.h>
#import <SignalServiceKit/DataSource.h>
#import <SignalServiceKit/ECKeyPair.h>
#import <SignalServiceKit/MIMETypeUtil.h>
#import <SignalServiceKit/NSData+Image.h>
#import <SignalServiceKit/NSNotificationCenter+OWS.h>

@ -1,9 +1,24 @@
final class OnboardingKeyPairViewController : OnboardingBaseViewController {
private var mode: Mode = .register { didSet { if mode != oldValue { handleModeChanged() } } }
private var keyPair: ECKeyPair! { didSet { updateMnemonic() } }
private var mnemonic: String! { didSet { mnemonicLabel.text = mnemonic } }
private var mnemonic: String! { didSet { handleMnemonicChanged() } }
private var userName: String?
// MARK: Components
private lazy var registerStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ explanationLabel, UIView.spacer(withHeight: 32), mnemonicLabel, UIView.spacer(withHeight: 24), copyButton, UIView.spacer(withHeight: 8), restoreButton ])
result.accessibilityIdentifier = "onboarding.keyPairStep.registerStackView"
result.axis = .vertical
return result
}()
private lazy var explanationLabel: UILabel = {
let result = createExplanationLabel(text: NSLocalizedString("Please save the seed below in a safe location. It can be used to restore your account if you lose access, or to migrate to a new device.", comment: ""))
result.accessibilityIdentifier = "onboarding.keyPairStep.explanationLabel"
return result
}()
private lazy var mnemonicLabel: UILabel = {
let result = createExplanationLabel(text: "")
result.accessibilityIdentifier = "onboarding.keyPairStep.mnemonicLabel"
@ -20,6 +35,43 @@ final class OnboardingKeyPairViewController : OnboardingBaseViewController {
return result
}()
private lazy var restoreButton: OWSFlatButton = {
let result = createLinkButton(title: NSLocalizedString("Restore Using Mnemonic", comment: ""), selector: #selector(switchMode))
result.accessibilityIdentifier = "onboarding.keyPairStep.restoreButton"
return result
}()
private lazy var restoreStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ mnemonicTextField, UIView.spacer(withHeight: 24), registerButton ])
result.accessibilityIdentifier = "onboarding.keyPairStep.restoreStackView"
result.axis = .vertical
return result
}()
private lazy var mnemonicTextField: UITextField = {
let result = UITextField(frame: CGRect.zero)
result.accessibilityIdentifier = "onboarding.keyPairStep.mnemonicTextField"
result.textAlignment = .center
result.placeholder = NSLocalizedString("Enter Your Mnemonic", comment: "")
return result
}()
private lazy var registerButton: OWSFlatButton = {
let result = createLinkButton(title: NSLocalizedString("Register a New Account", comment: ""), selector: #selector(switchMode))
result.accessibilityIdentifier = "onboarding.keyPairStep.registerButton"
return result
}()
private lazy var registerOrRestoreButton: OWSFlatButton = {
let result = createButton(title: "", selector: #selector(registerOrRestore))
result.accessibilityIdentifier = "onboarding.keyPairStep.registerOrRestoreButton"
return result
}()
// MARK: Types
enum Mode { case register, restore }
// MARK: Lifecycle
init(onboardingController: OnboardingController, userName: String?) {
super.init(onboardingController: onboardingController)
self.userName = userName
@ -28,6 +80,7 @@ final class OnboardingKeyPairViewController : OnboardingBaseViewController {
override public func viewDidLoad() {
super.loadView()
setUpViewHierarchy()
handleModeChanged() // Perform initial update
updateKeyPair()
// Test
// ================
@ -43,36 +96,50 @@ final class OnboardingKeyPairViewController : OnboardingBaseViewController {
}
private func setUpViewHierarchy() {
// Prepare
view.backgroundColor = Theme.backgroundColor
view.layoutMargins = .zero
// Set up view hierarchy
let titleLabel = createTitleLabel(text: NSLocalizedString("Create Your Loki Messenger Account", comment: ""))
titleLabel.accessibilityIdentifier = "onboarding.keyPairStep.titleLabel"
let topSpacer = UIView.vStretchingSpacer()
let explanationLabel = createExplanationLabel(text: NSLocalizedString("Please save the seed below in a safe location. It can be used to restore your account if you lose access, or to migrate to a new device.", comment: ""))
explanationLabel.accessibilityIdentifier = "onboarding.keyPairStep.explanationLabel"
let bottomSpacer = UIView.vStretchingSpacer()
let registerButton = createButton(title: NSLocalizedString("Register", comment: ""), selector: #selector(register))
registerButton.accessibilityIdentifier = "onboarding.keyPairStep.registerButton"
let stackView = UIStackView(arrangedSubviews: [
titleLabel,
topSpacer,
explanationLabel,
UIView.spacer(withHeight: 32),
mnemonicLabel,
UIView.spacer(withHeight: 24),
copyButton,
bottomSpacer,
registerButton
])
stackView.axis = .vertical
stackView.alignment = .fill
stackView.layoutMargins = UIEdgeInsets(top: 32, left: 32, bottom: 32, right: 32)
stackView.isLayoutMarginsRelativeArrangement = true
view.addSubview(stackView)
stackView.autoPinWidthToSuperview()
stackView.autoPin(toTopLayoutGuideOf: self, withInset: 0)
autoPinView(toBottomOfViewControllerOrKeyboard: stackView, avoidNotch: true)
topSpacer.autoMatch(.height, to: .height, of: bottomSpacer)
titleLabel.setContentHuggingPriority(.required, for: NSLayoutConstraint.Axis.vertical)
let mainView = UIView(frame: CGRect.zero)
mainView.addSubview(restoreStackView)
mainView.addSubview(registerStackView)
let mainStackView = UIStackView(arrangedSubviews: [ titleLabel, mainView, registerOrRestoreButton ])
mainStackView.axis = .vertical
mainStackView.layoutMargins = UIEdgeInsets(top: 32, left: 32, bottom: 32, right: 32)
mainStackView.isLayoutMarginsRelativeArrangement = true
view.addSubview(mainStackView)
// Set up constraints
mainStackView.autoPinWidthToSuperview()
mainStackView.autoPin(toTopLayoutGuideOf: self, withInset: 0)
autoPinView(toBottomOfViewControllerOrKeyboard: mainStackView, avoidNotch: true)
registerStackView.autoPinWidthToSuperview()
registerStackView.autoVCenterInSuperview()
restoreStackView.autoPinWidthToSuperview()
restoreStackView.autoVCenterInSuperview()
}
// MARK: General
@objc private func enableCopyButton() {
copyButton.isUserInteractionEnabled = true
copyButton.setTitle(NSLocalizedString("Copy", comment: ""))
}
// MARK: Updating
private func handleModeChanged() {
let registerOrRestoreButtonTitle: String = {
switch mode {
case .register: return NSLocalizedString("Register", comment: "")
case .restore: return NSLocalizedString("Restore", comment: "")
}
}()
UIView.animate(withDuration: 0.25) {
self.registerStackView.alpha = (self.mode == .register ? 1 : 0)
self.restoreStackView.alpha = (self.mode == .restore ? 1 : 0)
}
self.registerOrRestoreButton.setTitle(registerOrRestoreButtonTitle)
}
private func updateKeyPair() {
@ -84,33 +151,50 @@ final class OnboardingKeyPairViewController : OnboardingBaseViewController {
private func updateMnemonic() {
mnemonic = Mnemonic.encode(hexEncodedString: keyPair.hexEncodedPrivateKey)
}
private func handleMnemonicChanged() {
mnemonicLabel.text = mnemonic
}
// MARK: Interaction
@objc private func copyMnemonic() {
UIPasteboard.general.string = mnemonic
copyButton.isUserInteractionEnabled = false
copyButton.setTitle(NSLocalizedString("Copied ✓", comment: ""))
Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(enableCopyButton), userInfo: nil, repeats: false)
}
@objc private func enableCopyButton() {
copyButton.isUserInteractionEnabled = true
copyButton.setTitle(NSLocalizedString("Copy", comment: ""))
@objc private func switchMode() {
switch mode {
case .register: mode = .restore
case .restore: mode = .register
}
}
@objc private func register() {
@objc private func registerOrRestore() {
let hexEncodedPublicKey: String
switch mode {
case .register: hexEncodedPublicKey = keyPair.hexEncodedPublicKey
case .restore:
let mnemonic = mnemonicTextField.text!
do {
let hexEncodedPrivateKey = try Mnemonic.decode(mnemonic: mnemonic)
let keyPair = ECKeyPair.generate(withHexEncodedPrivateKey: hexEncodedPrivateKey)
// TODO: Store key pair
hexEncodedPublicKey = keyPair.hexEncodedPublicKey
} catch let error {
fatalError(error.localizedDescription) // TODO: Handle
}
}
let accountManager = TSAccountManager.sharedInstance()
accountManager.phoneNumberAwaitingVerification = keyPair.hexEncodedPublicKey
accountManager.phoneNumberAwaitingVerification = hexEncodedPublicKey
accountManager.didRegister()
let onSuccess: () -> Void = { [weak self] in
let onSuccess = { [weak self] in
guard let strongSelf = self else { return }
strongSelf.onboardingController.verificationDidComplete(fromView: strongSelf)
}
if let userName = userName {
// Try to save the user name
OWSProfileManager.shared().updateLocalProfileName(userName, avatarImage: nil, success: onSuccess, failure: {
Logger.warn("Failed to set user name")
onSuccess()
})
OWSProfileManager.shared().updateLocalProfileName(userName, avatarImage: nil, success: onSuccess, failure: onSuccess) // Try to save the user name but ignore the result
} else {
onSuccess()
}

@ -2556,3 +2556,6 @@
"Copy" = "Copy";
"Copied ✓" = "Copied ✓";
"Register" = "Register";
"Restore" = "Restore";
"Enter Your Mnemonic" = "Enter Your Mnemonic";
"Restore Using Mnemonic" = "Restore Using Mnemonic";

@ -0,0 +1,7 @@
@import Curve25519Kit;
@interface ECKeyPair (ECKeyPairExtension)
+ (nonnull ECKeyPair *)generateKeyPairWithHexEncodedPrivateKey:(nonnull NSString *)hexEncodedPrivateKey;
@end

@ -0,0 +1,27 @@
#import "ECKeyPair.h"
extern void curve25519_donna(unsigned char *output, const unsigned char *a, const unsigned char *b);
@implementation ECKeyPair (ECKeyPairExtension)
+ (nonnull ECKeyPair *)generateKeyPairWithHexEncodedPrivateKey:(nonnull NSString *)hexEncodedPrivateKey {
NSMutableData *privateKey = [NSMutableData new];
for (int i = 0; i < hexEncodedPrivateKey.length; i += 2) {
char buffer[3];
buffer[0] = [hexEncodedPrivateKey characterAtIndex:i];
buffer[1] = [hexEncodedPrivateKey characterAtIndex:i + 1];
buffer[2] = '\0';
unsigned char byte = strtol(buffer, NULL, 16);
[privateKey appendBytes:&byte length:1];
}
static const uint8_t basepoint[ECCKeyLength] = { 9 };
NSMutableData *publicKey = [NSMutableData dataWithLength:ECCKeyLength];
if (!publicKey) { OWSFail(@"Could not allocate buffer"); }
curve25519_donna(publicKey.mutableBytes, privateKey.mutableBytes, basepoint);
ECKeyPair *result = [ECKeyPair new];
[result setValue:[privateKey copy] forKey:@"privateKey"];
[result setValue:[publicKey copy] forKey:@"publicKey"];
return result;
}
@end
Loading…
Cancel
Save