diff --git a/.gitignore b/.gitignore index c2a95905c..7dc16c794 100644 --- a/.gitignore +++ b/.gitignore @@ -18,5 +18,3 @@ DerivedData .idea/ openssl openssl-* -boost -boost_* diff --git a/ZincWallet/ZNPayViewController.m b/ZincWallet/ZNPayViewController.m index 1b841445d..ef8c5acb7 100644 --- a/ZincWallet/ZNPayViewController.m +++ b/ZincWallet/ZNPayViewController.m @@ -27,6 +27,7 @@ #import "ZNAmountViewController.h" #import "ZNReceiveViewController.h" #import "ZNWallet.h" +#import "ZNWallet+WebSocket.h" #import "ZNPaymentRequest.h" #import "ZNKey.h" #import "ZNTransaction.h" @@ -127,6 +128,8 @@ - (void)viewDidLoad [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:nil usingBlock:^(NSNotification *note) { if (w.timeSinceLastSync > DEFAULT_SYNC_INTERVAL) [self refresh:nil]; + else [w openSocket]; + [self layoutButtonsAnimated:YES]; // check the clipboard for changes }]; @@ -134,6 +137,7 @@ - (void)viewDidLoad [[NSNotificationCenter defaultCenter] addObserverForName:kReachabilityChangedNotification object:nil queue:nil usingBlock:^(NSNotification *note) { if (w.timeSinceLastSync > DEFAULT_SYNC_INTERVAL) [self refresh:nil]; + else [w openSocket]; }]; self.balanceObserver = @@ -161,6 +165,8 @@ - (void)viewDidLoad self.navigationItem.title = [NSString stringWithFormat:@"%@ (%@)", [w stringForAmount:w.balance], [w localCurrencyStringForAmount:w.balance]]; + + [w openSocket]; }]; self.syncFailedObserver = @@ -272,6 +278,7 @@ - (void)viewDidAppear:(BOOL)animated [super viewDidAppear:animated]; if ([[ZNWallet sharedInstance] timeSinceLastSync] > DEFAULT_SYNC_INTERVAL) [self refresh:nil]; + else [[ZNWallet sharedInstance] openSocket]; } - (void)viewWillDisappear:(BOOL)animated @@ -504,9 +511,7 @@ - (IBAction)refresh:(id)sender if (sender || [self.reachability currentReachabilityStatus] != NotReachable) { self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.spinner]; [self.spinner startAnimating]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [[ZNWallet sharedInstance] synchronize]; - }); + [[ZNWallet sharedInstance] synchronize]; } } diff --git a/ZincWallet/ZNWallet.h b/ZincWallet/ZNWallet.h index 3e80c0d45..3e00b7144 100644 --- a/ZincWallet/ZNWallet.h +++ b/ZincWallet/ZNWallet.h @@ -25,7 +25,7 @@ #import -#define WALLET_BIP32 1 +//#define WALLET_BIP32 1 //XXXX switch mnemonic to bip39 //#define WALLET_BIP39 1 diff --git a/ZincWallet/ZNWallet.m b/ZincWallet/ZNWallet.m index 2306edcaa..c039ab9fc 100644 --- a/ZincWallet/ZNWallet.m +++ b/ZincWallet/ZNWallet.m @@ -187,17 +187,10 @@ - (instancetype)init self.localFormat.numberStyle = NSNumberFormatterCurrencyStyle; self.localFormat.negativeFormat = [self.localFormat.positiveFormat stringByReplacingOccurrencesOfString:@"¤" withString:@"¤-"]; - - [self openSocket]; return self; } -- (void)dealloc -{ - [self closeSocket]; -} - - (instancetype)initWithSeedPhrase:(NSString *)phrase { if (! (self = [self init])) return nil; @@ -349,95 +342,88 @@ - (void)synchronize _synchronizing = YES; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:walletSyncStartedNotification object:self]; - [self openSocket]; }); - self.updatedTransactions = [NSMutableSet set]; + NSMutableArray *newAddresses = [NSMutableArray arrayWithCapacity:GAP_LIMIT_EXTERNAL + GAP_LIMIT_INTERNAL + + self.fundedAddresses.count + self.spentAddresses.count]; - //XXXX refactor this to optimize for fewest network reqeusts (should only make two) + [newAddresses addObjectsFromArray:[self addressesWithGapLimit:GAP_LIMIT_EXTERNAL internal:NO]]; + [newAddresses addObjectsFromArray:[self addressesWithGapLimit:GAP_LIMIT_INTERNAL internal:YES]]; + [newAddresses addObjectsFromArray:self.fundedAddresses]; + [newAddresses addObjectsFromArray:self.spentAddresses]; - [self synchronizeWithGapLimit:GAP_LIMIT_EXTERNAL internal:NO completion:^(NSError *error) { + // An ARC retain loop when using block recursion is avoided here by passing the block to itself as an argument + void (^completion)(NSError *, id) = ^(NSError *error, id completion) { if (error) { _synchronizing = NO; [_defs synchronize]; - + dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:walletSyncFailedNotification object:self userInfo:@{@"error":error}]; }); return; } + + [newAddresses removeObjectsAtIndexes:[newAddresses + indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { + return [self.spentAddresses containsObject:obj] || [self.fundedAddresses containsObject:obj]; + }]]; + + if (newAddresses.count < GAP_LIMIT_EXTERNAL + GAP_LIMIT_INTERNAL) { + [newAddresses setArray:[self addressesWithGapLimit:GAP_LIMIT_EXTERNAL internal:NO]]; + [newAddresses addObjectsFromArray:[self addressesWithGapLimit:GAP_LIMIT_INTERNAL internal:YES]]; + if (newAddresses.count) { + [self queryAddresses:newAddresses completion:^(NSError *error) { + ((void (^)(NSError *, id))completion)(error, completion); + }]; + } + return; + } - [self synchronizeWithGapLimit:GAP_LIMIT_INTERNAL internal:YES completion:^(NSError *error) { + @synchronized(self) { + // remove unconfirmed transactions that no longer appear in query results + //XXX we should keep a seprate list of failed transactions to display along with the successful ones + [self.transactions + removeObjectsForKeys:[self.transactions keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) { + return ! obj[@"block_height"] && ! [self.updatedTransactions containsObject:obj[@"hash"]]; + }].allObjects]; + + [_defs setObject:self.transactions forKey:TRANSACTIONS_KEY]; + } + + [self queryUnspentOutputs:self.outdatedAddresses.allObjects completion:^(NSError *error) { + _synchronizing = NO; + if (error) { - _synchronizing = NO; [_defs synchronize]; - + dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:walletSyncFailedNotification object:self userInfo:@{@"error":error}]; }); return; } - - // check funded and spent addresses for new transactions - [self queryAddresses:[self.fundedAddresses arrayByAddingObjectsFromArray:self.spentAddresses] - completion:^(NSError *error) { - if (error) { - _synchronizing = NO; - [_defs synchronize]; - - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:walletSyncFailedNotification - object:self userInfo:@{@"error":error}]; - }); - return; - } + + [self cleanUnconfirmed]; + + [_defs setDouble:[NSDate timeIntervalSinceReferenceDate] forKey:LAST_SYNC_TIME_KEY]; + [_defs synchronize]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:walletSyncFinishedNotification object:self]; - @synchronized(self) { - // remove unconfirmed transactions that no longer appear in query results - //XXX we should keep a seprate list of failed transactions to display along with the successful ones - [self.transactions removeObjectsForKeys:[self.transactions - keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) { - return ! obj[@"block_height"] && ! [self.updatedTransactions containsObject:obj[@"hash"]]; - }].allObjects]; - - [_defs setObject:self.transactions forKey:TRANSACTIONS_KEY]; - } - - [self queryUnspentOutputs:self.outdatedAddresses.allObjects completion:^(NSError *error) { - if (error) { - _synchronizing = NO; - [_defs synchronize]; - - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:walletSyncFailedNotification - object:self userInfo:@{@"error":error}]; - }); - return; - } - - _synchronizing = NO; - - [self cleanUnconfirmed]; - - [_defs setDouble:[NSDate timeIntervalSinceReferenceDate] forKey:LAST_SYNC_TIME_KEY]; - [_defs synchronize]; - - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:walletSyncFinishedNotification - object:self]; - - // need to send balance notification every time since exchnage rates might be different - //if (self.outdatedAddresses.count) { - [[NSNotificationCenter defaultCenter] postNotificationName:walletBalanceNotification - object:self]; - //} - }); - }]; - }]; + // send balance notification every time since exchnage rates might have changed + [[NSNotificationCenter defaultCenter] postNotificationName:walletBalanceNotification object:self]; + }); }]; - }]; + }; + + self.updatedTransactions = [NSMutableSet set]; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [self queryAddresses:newAddresses completion:^(NSError *error) { completion(error, completion); }]; + }); } - (NSArray *)addressesWithGapLimit:(NSUInteger)gapLimit internal:(BOOL)internal @@ -482,36 +468,6 @@ - (NSArray *)addressesWithGapLimit:(NSUInteger)gapLimit internal:(BOOL)internal return addresses; } -- (void)synchronizeWithGapLimit:(NSUInteger)gapLimit internal:(BOOL)internal -completion:(void (^)(NSError *error))completion -{ - NSMutableArray *newAddresses = [[self addressesWithGapLimit:gapLimit internal:internal] mutableCopy]; - - if (! newAddresses) { - if (completion) { - completion(nil); - //completion([NSError errorWithDomain:@"ZincWallet" code:500 - // userInfo:@{NSLocalizedDescriptionKey:@"error generating keys"}]); - } - return; - } - - [self queryAddresses:newAddresses completion:^(NSError *error) { - [newAddresses removeObjectsAtIndexes:[newAddresses - indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { - return [self.spentAddresses containsObject:obj] || [self.fundedAddresses containsObject:obj]; - }]]; - - if (newAddresses.count < gapLimit) { - [self synchronizeWithGapLimit:gapLimit internal:internal completion:completion]; - } - else if (self.outdatedAddresses.count) { - [self queryUnspentOutputs:self.outdatedAddresses.allObjects completion:completion]; - } - else if (completion) completion(error); - }]; -} - // query blockchain for the given addresses - (void)queryAddresses:(NSArray *)addresses completion:(void (^)(NSError *error))completion { @@ -537,7 +493,6 @@ - (void)queryAddresses:(NSArray *)addresses completion:(void (^)(NSError *error) NSURL *url = [NSURL URLWithString:[ADDRESS_URL stringByAppendingString:[[addresses componentsJoinedByString:@"|"] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]; - __block dispatch_queue_t q = dispatch_get_current_queue(); __block AFJSONRequestOperation *requestOp = [AFJSONRequestOperation JSONRequestOperationWithRequest:[NSURLRequest requestWithURL:url] @@ -601,10 +556,6 @@ - (void)queryAddresses:(NSArray *)addresses completion:(void (^)(NSError *error) if (price > DBL_EPSILON) [_defs setDouble:price forKey:LOCAL_CURRENCY_PRICE_KEY]; } - if (self.outdatedAddresses.count) { - [[NSNotificationCenter defaultCenter] postNotificationName:walletBalanceNotification object:self]; - } - if (completion) dispatch_async(q, ^{ completion(nil); }); } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) { NSLog(@"%@", error.localizedDescription); @@ -612,6 +563,7 @@ - (void)queryAddresses:(NSArray *)addresses completion:(void (^)(NSError *error) if (completion) dispatch_async(q, ^{ completion(error); }); }]; + NSLog(@"%@", url.absoluteString); requestOp.successCallbackQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); requestOp.failureCallbackQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); [requestOp start]; @@ -711,6 +663,7 @@ - (void)queryUnspentOutputs:(NSArray *)addresses completion:(void (^)(NSError *e if (completion) dispatch_async(q, ^{ completion(nil); }); }]; + NSLog(@"%@", url.absoluteString); requestOp.successCallbackQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); requestOp.failureCallbackQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); [requestOp start]; diff --git a/ZincWalletTests/ZincWalletTests.m b/ZincWalletTests/ZincWalletTests.m index fbd58a911..992a7f18f 100644 --- a/ZincWalletTests/ZincWalletTests.m +++ b/ZincWalletTests/ZincWalletTests.m @@ -104,7 +104,64 @@ - (void)testTransactionHeightUntilFree #pragma mark - testBIP39Mnemonic -//XXXX testBIP39Mnemonic +//XXXX test bip39 +- (void)testBIP39MnemonicDecodePhrase +{ + id mnemonic = [ZNBIP39Mnemonic sharedInstance]; + + NSData *d = [mnemonic decodePhrase:@"like just love know never want time out there make look eye"]; + + NSLog(@"like just love know never want time out there make look eye = 0x%@", [NSString hexWithData:d]); + + //STAssertEqualObjects(d, @"00285dfe00285e0100285e0400285e07".hexToData, @"[ZNWallet decodePhrase:]"); + + d = [mnemonic decodePhrase:@"kick chair mask master passion quick raise smooth unless wander actually broke"]; + + NSLog(@"kick chair mask master passion quick raise smooth unless wander actually broke = 0x%@", + [NSString hexWithData:d]); + + //STAssertEqualObjects(d, @"fea983ac0028608e0028609100286094".hexToData, @"[ZNWallet decodePhrase:]"); + + // test of phrase with trailing space + d = [mnemonic + decodePhrase:@"kick quiet student ignore cruel danger describe accident eager darkness embrace suppose "]; + + NSLog(@"kick quiet student ignore cruel danger describe accident eager darkness embrace suppose = 0x%@", + [NSString hexWithData:d]); + + //STAssertEqualObjects(d, @"8d02be487e1953ce2dd6c186fcc97e65".hexToData, @"[ZNWallet decodePhrase:]"); +} + +- (void)testBIP39MnemonicEncodePhrase +{ + id mnemonic = [ZNBIP39Mnemonic sharedInstance]; + + NSString *s = [mnemonic encodePhrase:@"00285dfe00285e0100285e0400285e07".hexToData]; + + NSLog(@"0x00285dfe00285e0100285e0400285e07 = %@", s); + + //STAssertEqualObjects(s, @"like just love know never want time out there make look eye", + // @"[ZNWallet encodePhrase:]"); + + s = [mnemonic encodePhrase:@"00000000000000000000000000000000".hexToData]; + + NSLog(@"0x00285dfe00285e0100285e0400285e07 = %@", s); + + s = [mnemonic encodePhrase:@"fea983ac0028608e0028609100286094".hexToData]; + + NSLog(@"0x00285dfe00285e0100285e0400285e07 = %@", s); + + //STAssertEqualObjects(s, @"kick chair mask master passion quick raise smooth unless wander actually broke", + // @"[ZNWallet encodePhrase:]"); + + s = [mnemonic encodePhrase:@"8d02be487e1953ce2dd6c186fcc97e65".hexToData]; + + NSLog(@"0x8d02be487e1953ce2dd6c186fcc97e65 = %@", s); + + //STAssertEqualObjects(s, @"kick quiet student ignore cruel danger describe accident eager darkness embrace suppose", + // @"[ZNWallet encodePhrase:]"); +} + #pragma mark - testElectrumMnemonic