Skip to content

Commit fd14d42

Browse files
committed
localized recovery phrases, user supplied phrases now verified against masterpubkey
1 parent 11fb1ca commit fd14d42

File tree

9 files changed

+8277
-36
lines changed

9 files changed

+8277
-36
lines changed

BreadWallet.xcodeproj/project.pbxproj

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
752E28EA19234DA200DB5A3C /* NSMutableData+Bitcoin.m in Sources */ = {isa = PBXBuildFile; fileRef = 752E28E419234DA200DB5A3C /* NSMutableData+Bitcoin.m */; };
3434
752E28EB19234DA200DB5A3C /* NSString+Bitcoin.m in Sources */ = {isa = PBXBuildFile; fileRef = 752E28E619234DA200DB5A3C /* NSString+Bitcoin.m */; };
3535
752E28F119234E8100DB5A3C /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 752E28F019234E8100DB5A3C /* Reachability.m */; settings = {COMPILER_FLAGS = "-w"; }; };
36-
752E28F619235B3000DB5A3C /* BIP39EnglishWords.plist in Resources */ = {isa = PBXBuildFile; fileRef = 752E28F419235B3000DB5A3C /* BIP39EnglishWords.plist */; };
3736
752E28F719235B3000DB5A3C /* FixedPeers.plist in Resources */ = {isa = PBXBuildFile; fileRef = 752E28F519235B3000DB5A3C /* FixedPeers.plist */; };
3837
7548CF3719556FD700EF827F /* BRCopyLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 7548CF3619556FD700EF827F /* BRCopyLabel.m */; };
3938
75767C421A0EC6F9003E7DE9 /* UIImage+Blur.m in Sources */ = {isa = PBXBuildFile; fileRef = 75767C411A0EC6F9003E7DE9 /* UIImage+Blur.m */; };
@@ -51,6 +50,7 @@
5150
759FBB36193290F600AB4465 /* BRRootViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 759FBB33193290F600AB4465 /* BRRootViewController.m */; };
5251
759FBB37193290F600AB4465 /* BRSendViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 759FBB35193290F600AB4465 /* BRSendViewController.m */; };
5352
75AA5F55194143D5003F23BD /* BRBouncyBurgerButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 75AA5F54194143D5003F23BD /* BRBouncyBurgerButton.m */; };
53+
75C0E3BF1B1BA294007BC531 /* BIP39Words.plist in Resources */ = {isa = PBXBuildFile; fileRef = 75C0E3C11B1BA294007BC531 /* BIP39Words.plist */; };
5454
75D5F3C2191EC270004AB296 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 75D5F3C1191EC270004AB296 /* Foundation.framework */; };
5555
75D5F3C4191EC270004AB296 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 75D5F3C3191EC270004AB296 /* CoreGraphics.framework */; };
5656
75D5F3C6191EC270004AB296 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 75D5F3C5191EC270004AB296 /* UIKit.framework */; };
@@ -182,7 +182,6 @@
182182
752E28E619234DA200DB5A3C /* NSString+Bitcoin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+Bitcoin.m"; sourceTree = "<group>"; };
183183
752E28EF19234E8100DB5A3C /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = SOURCE_ROOT; };
184184
752E28F019234E8100DB5A3C /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Reachability.m; sourceTree = SOURCE_ROOT; };
185-
752E28F419235B3000DB5A3C /* BIP39EnglishWords.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = BIP39EnglishWords.plist; sourceTree = "<group>"; };
186185
752E28F519235B3000DB5A3C /* FixedPeers.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = FixedPeers.plist; sourceTree = "<group>"; };
187186
7548CF3519556FD700EF827F /* BRCopyLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BRCopyLabel.h; sourceTree = "<group>"; };
188187
7548CF3619556FD700EF827F /* BRCopyLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BRCopyLabel.m; sourceTree = "<group>"; };
@@ -243,6 +242,11 @@
243242
75AA5F53194143D5003F23BD /* BRBouncyBurgerButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BRBouncyBurgerButton.h; sourceTree = "<group>"; };
244243
75AA5F54194143D5003F23BD /* BRBouncyBurgerButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BRBouncyBurgerButton.m; sourceTree = "<group>"; };
245244
75BCC59A1946520700EC1A64 /* BreadWalletTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BreadWalletTests.h; sourceTree = "<group>"; };
245+
75C0E3C71B1D333F007BC531 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = en; path = en.lproj/BIP39Words.plist; sourceTree = "<group>"; };
246+
75C0E3C21B1BA29D007BC531 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = fr; path = fr.lproj/BIP39Words.plist; sourceTree = "<group>"; };
247+
75C0E3C31B1BA29F007BC531 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = ja; path = ja.lproj/BIP39Words.plist; sourceTree = "<group>"; };
248+
75C0E3C41B1BA2A5007BC531 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = es; path = es.lproj/BIP39Words.plist; sourceTree = "<group>"; };
249+
75C0E3C51B1BA2AE007BC531 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "zh-Hans"; path = "zh-Hans.lproj/BIP39Words.plist"; sourceTree = "<group>"; };
246250
75D5F3BE191EC270004AB296 /* breadwallet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = breadwallet.app; sourceTree = BUILT_PRODUCTS_DIR; };
247251
75D5F3C1191EC270004AB296 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
248252
75D5F3C3191EC270004AB296 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
@@ -529,7 +533,7 @@
529533
75D5F3DF191EC270004AB296 /* Images.xcassets */,
530534
75D5F3DC191EC270004AB296 /* Main.storyboard */,
531535
752E2898192349CE00DB5A3C /* BreadWallet.xcdatamodeld */,
532-
752E28F419235B3000DB5A3C /* BIP39EnglishWords.plist */,
536+
75C0E3C11B1BA294007BC531 /* BIP39Words.plist */,
533537
752E28F519235B3000DB5A3C /* FixedPeers.plist */,
534538
7579F7FE19231D4600CA56F4 /* ThirdParty */,
535539
75D5F3C8191EC270004AB296 /* Supporting Files */,
@@ -696,7 +700,7 @@
696700
files = (
697701
75D5F3E0191EC270004AB296 /* Images.xcassets in Resources */,
698702
757E09991ADB8EEB006FD352 /* Localizable.strings in Resources */,
699-
752E28F619235B3000DB5A3C /* BIP39EnglishWords.plist in Resources */,
703+
75C0E3BF1B1BA294007BC531 /* BIP39Words.plist in Resources */,
700704
75D5F3CC191EC270004AB296 /* InfoPlist.strings in Resources */,
701705
752E28F719235B3000DB5A3C /* FixedPeers.plist in Resources */,
702706
75D5F3DE191EC270004AB296 /* Main.storyboard in Resources */,
@@ -812,6 +816,18 @@
812816
name = Localizable.strings;
813817
sourceTree = "<group>";
814818
};
819+
75C0E3C11B1BA294007BC531 /* BIP39Words.plist */ = {
820+
isa = PBXVariantGroup;
821+
children = (
822+
75C0E3C71B1D333F007BC531 /* en */,
823+
75C0E3C21B1BA29D007BC531 /* fr */,
824+
75C0E3C31B1BA29F007BC531 /* ja */,
825+
75C0E3C41B1BA2A5007BC531 /* es */,
826+
75C0E3C51B1BA2AE007BC531 /* zh-Hans */,
827+
);
828+
name = BIP39Words.plist;
829+
sourceTree = "<group>";
830+
};
815831
75D5F3CA191EC270004AB296 /* InfoPlist.strings */ = {
816832
isa = PBXVariantGroup;
817833
children = (

BreadWallet/BRBIP39Mnemonic.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
#import "ccMemory.h"
3030
#import <CommonCrypto/CommonKeyDerivation.h>
3131

32-
#define WORDS @"BIP39EnglishWords"
32+
#define WORDS @"BIP39Words"
3333

3434
// BIP39 is method for generating a deterministic wallet seed from a mnemonic phrase
3535
// https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki

BreadWallet/BRRestoreViewController.m

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,14 @@
3030
#import "NSMutableData+Bitcoin.h"
3131

3232
#define PHRASE_LENGTH 12
33-
#define WORDS @"BIP39EnglishWords"
33+
#define WORDS @"BIP39Words"
3434

3535
@interface BRRestoreViewController ()
3636

3737
@property (nonatomic, strong) IBOutlet UITextView *textView;
3838
@property (nonatomic, strong) IBOutlet NSLayoutConstraint *textViewYBottom;
3939
@property (nonatomic, strong) NSArray *words;
40+
@property (nonatomic, strong) NSMutableSet *allWords;
4041
@property (nonatomic, strong) id keyboardObserver;
4142

4243
@end
@@ -48,8 +49,14 @@ - (void)viewDidLoad
4849
[super viewDidLoad];
4950
// Do any additional setup after loading the view.
5051

51-
self.words = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:WORDS ofType:@"plist"]];
52-
52+
self.words = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:WORDS ofType:@"plist"]];
53+
self.allWords = [NSMutableSet set];
54+
55+
for (NSString *lang in [[NSBundle mainBundle] localizations]) {
56+
[self.allWords addObjectsFromArray:[NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]
57+
pathForResource:WORDS ofType:@"plist" inDirectory:nil forLocalization:lang]]];
58+
}
59+
5360
// TODO: create secure versions of keyboard and UILabel and use in place of UITextView
5461
// TODO: autocomplete based on 4 letter prefixes of mnemonic words
5562

@@ -87,14 +94,17 @@ - (void)dealloc
8794
- (void)wipeWithPhrase:(NSString *)phrase
8895
{
8996
@autoreleasepool {
90-
NSString *seedPhrase = [[BRWalletManager sharedInstance] seedPhrase];
97+
BRWalletManager *m = [BRWalletManager sharedInstance];
98+
99+
if ([phrase isEqual:@"wipe"]) phrase = [m seedPhraseWithPrompt:nil];
91100

92-
if (seedPhrase && ([phrase isEqual:seedPhrase] || [phrase isEqual:@"wipe"])) {
101+
if ([[m.sequence masterPublicKeyFromSeed:[m.mnemonic deriveKeyFromPhrase:phrase withPassphrase:nil]]
102+
isEqual:m.masterPublicKey]) {
93103
[[[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:NSLocalizedString(@"cancel", nil)
94104
destructiveButtonTitle:NSLocalizedString(@"wipe", nil) otherButtonTitles:nil]
95105
showInView:[[UIApplication sharedApplication] keyWindow]];
96106
}
97-
else if (seedPhrase) {
107+
else if (phrase) {
98108
[[[UIAlertView alloc] initWithTitle:@"" message:NSLocalizedString(@"recovery phrase doesn't match", nil)
99109
delegate:nil cancelButtonTitle:NSLocalizedString(@"ok", nil) otherButtonTitles:nil] show];
100110
}
@@ -152,19 +162,21 @@ - (void)textViewDidChange:(UITextView *)textView
152162

153163
if (! done) return;
154164

165+
BOOL isLocal = YES;
155166
NSString *phrase = [m.mnemonic normalizePhrase:s], *incorrect = nil;
156167
NSArray *a = CFBridgingRelease(CFStringCreateArrayBySeparatingStrings(SecureAllocator(), (CFStringRef)phrase,
157168
CFSTR(" ")));
158169

159170
for (NSString *word in a) {
160-
if ([self.words containsObject:word]) continue;
171+
if (! [self.words containsObject:word]) isLocal = NO;
172+
if ([self.allWords containsObject:word]) continue;
161173
incorrect = word;
162174
break;
163175
}
164176

165-
if ([s isEqual:@"wipe"]) { // shortcut word to force the wipe option to appear
177+
if ([phrase isEqual:@"wipe"]) { // shortcut word to force the wipe option to appear
166178
[self.textView resignFirstResponder];
167-
[self performSelector:@selector(wipeWithPhrase:) withObject:s afterDelay:0.0];
179+
[self performSelector:@selector(wipeWithPhrase:) withObject:phrase afterDelay:0.0];
168180
}
169181
else if (incorrect) {
170182
textView.selectedRange = [[textView.text lowercaseString] rangeOfString:incorrect];
@@ -180,7 +192,7 @@ - (void)textViewDidChange:(UITextView *)textView
180192
PHRASE_LENGTH] delegate:nil cancelButtonTitle:NSLocalizedString(@"ok", nil)
181193
otherButtonTitles:nil] show];
182194
}
183-
else if (! [m.mnemonic phraseIsValid:phrase]) {
195+
else if (isLocal && ! [m.mnemonic phraseIsValid:phrase]) {
184196
[[[UIAlertView alloc] initWithTitle:@"" message:NSLocalizedString(@"bad recovery phrase", nil) delegate:nil
185197
cancelButtonTitle:NSLocalizedString(@"ok", nil) otherButtonTitles:nil] show];
186198
}

BreadWallet/BRWalletManager.m

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -257,9 +257,14 @@ - (BRWallet *)wallet
257257
if (_wallet) return _wallet;
258258

259259
if (getKeychainData(SEED_KEY, nil)) { // upgrade from old keychain scheme
260-
NSLog(@"upgrading to authenticated keychain scheme");
261-
if (! setKeychainData([self.sequence masterPublicKeyFromSeed:self.seed], MASTER_PUBKEY_KEY, NO)) return _wallet;
262-
if (setKeychainData(getKeychainData(MNEMONIC_KEY, nil), MNEMONIC_KEY, YES)) setKeychainData(nil, SEED_KEY, NO);
260+
@autoreleasepool {
261+
NSString *seedPhrase = getKeychainString(MNEMONIC_KEY, nil);
262+
263+
NSLog(@"upgrading to authenticated keychain scheme");
264+
if (! setKeychainData([self.sequence masterPublicKeyFromSeed:[self.mnemonic deriveKeyFromPhrase:seedPhrase
265+
withPassphrase:nil]], MASTER_PUBKEY_KEY, NO)) return _wallet;
266+
if (setKeychainString(seedPhrase, MNEMONIC_KEY, YES)) setKeychainData(nil, SEED_KEY, NO);
267+
}
263268
}
264269

265270
if (! self.masterPublicKey) return _wallet;
@@ -323,16 +328,6 @@ - (NSData *)masterPublicKey
323328
return getKeychainData(MASTER_PUBKEY_KEY, nil);
324329
}
325330

326-
- (NSData *)seed
327-
{
328-
@autoreleasepool {
329-
NSString *phrase = getKeychainString(MNEMONIC_KEY, nil);
330-
331-
if (phrase.length == 0) return nil;
332-
return [self.mnemonic deriveKeyFromPhrase:phrase withPassphrase:nil];
333-
}
334-
}
335-
336331
// requesting seedPhrase will trigger authentication
337332
- (NSString *)seedPhrase
338333
{
@@ -342,7 +337,7 @@ - (NSString *)seedPhrase
342337
- (void)setSeedPhrase:(NSString *)seedPhrase
343338
{
344339
@autoreleasepool { // @autoreleasepool ensures sensitive data will be dealocated immediately
345-
if (seedPhrase) seedPhrase = [self.mnemonic encodePhrase:[self.mnemonic decodePhrase:seedPhrase]];
340+
if (seedPhrase) seedPhrase = [self.mnemonic normalizePhrase:seedPhrase];
346341

347342
[[NSManagedObject context] performBlockAndWait:^{
348343
[BRAddressEntity deleteObjects:[BRAddressEntity allObjects]];
@@ -449,7 +444,13 @@ - (NSData *)seedWithPrompt:(NSString *)authprompt forAmount:(uint64_t)amount
449444

450445
// BUG: if user manually chooses to enter pin, spending limit is reset without including the tx being authorized
451446
if (! touchid) setKeychainInt(self.wallet.totalSent + amount + self.spendingLimit, SPEND_LIMIT_KEY, NO);
452-
return self.seed;
447+
448+
@autoreleasepool {
449+
NSString *seedPhrase = getKeychainString(MNEMONIC_KEY, nil);
450+
451+
if (seedPhrase.length == 0) return nil;
452+
return [self.mnemonic deriveKeyFromPhrase:seedPhrase withPassphrase:nil];
453+
}
453454
}
454455

455456
// authenticates user and returns seedPhrase
@@ -1083,12 +1084,8 @@ - (void)textViewDidChange:(UITextView *)textView
10831084
if ([textView.text rangeOfString:@"\n"].location != NSNotFound) {
10841085
textView.text = [self.mnemonic normalizePhrase:textView.text];
10851086

1086-
if (! [self.mnemonic phraseIsValid:[self.mnemonic normalizePhrase:textView.text]]) {
1087-
self.alertView.title = NSLocalizedString(@"bad recovery phrase", nil);
1088-
[self.alertView performSelector:@selector(setTitle:)
1089-
withObject:NSLocalizedString(@"recovery phrase", nil) afterDelay:3.0];
1090-
}
1091-
else if (! [[self.mnemonic normalizePhrase:textView.text] isEqual:getKeychainString(MNEMONIC_KEY, nil)]) {
1087+
if (! [[self.sequence masterPublicKeyFromSeed:[self.mnemonic deriveKeyFromPhrase:textView.text
1088+
withPassphrase:nil]] isEqual:self.masterPublicKey]) {
10921089
self.alertView.title = NSLocalizedString(@"recovery phrase doesn't match", nil);
10931090
[self.alertView performSelector:@selector(setTitle:)
10941091
withObject:NSLocalizedString(@"recovery phrase", nil) afterDelay:3.0];

0 commit comments

Comments
 (0)