|
@@ -0,0 +1,199 @@
|
|
|
+// NYMnemonic.m
|
|
|
+//
|
|
|
+// Copyright (c) 2014 Nybex, Inc. (https://nybex.com)
|
|
|
+//
|
|
|
+// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
+// of this software and associated documentation files (the "Software"), to deal
|
|
|
+// in the Software without restriction, including without limitation the rights
|
|
|
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
+// copies of the Software, and to permit persons to whom the Software is
|
|
|
+// furnished to do so, subject to the following conditions:
|
|
|
+//
|
|
|
+// The above copyright notice and this permission notice shall be included in
|
|
|
+// all copies or substantial portions of the Software.
|
|
|
+//
|
|
|
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
+// THE SOFTWARE.
|
|
|
+#import "NYMnemonic.h"
|
|
|
+
|
|
|
+@implementation NYMnemonic
|
|
|
++ (NSString *)mnemonicStringFromRandomHexString:(NSString *)seed language:(NSString *)language {
|
|
|
+
|
|
|
+ // Convert our hex string to NSData
|
|
|
+ NSData *seedData = [seed ny_dataFromHexString];
|
|
|
+
|
|
|
+ // Calculate the sha256 hash to use with a checksum
|
|
|
+ NSMutableData *hash = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
|
|
|
+ CC_SHA256(seedData.bytes, (int)seedData.length, hash.mutableBytes);
|
|
|
+
|
|
|
+ NSMutableArray *checksumBits = [NSMutableArray
|
|
|
+ arrayWithArray:[[NSData dataWithData:hash] ny_hexToBitArray]];
|
|
|
+ NSMutableArray *seedBits =
|
|
|
+ [NSMutableArray arrayWithArray:[seedData ny_hexToBitArray]];
|
|
|
+
|
|
|
+ // Append the appropriate checksum bits to the seed
|
|
|
+ for (int i = 0; i < (int)seedBits.count / 32; i++) {
|
|
|
+ [seedBits addObject: checksumBits[i]];
|
|
|
+ }
|
|
|
+ NSString *path = [NSString stringWithFormat:@"%@/%@.txt", [[NSBundle mainBundle] bundlePath], language];
|
|
|
+ NSString *fileText = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
|
|
|
+ NSArray *lines = [fileText componentsSeparatedByCharactersInSet: [NSCharacterSet newlineCharacterSet]];
|
|
|
+
|
|
|
+ // Split into groups of 11, and change to numbers
|
|
|
+ NSMutableArray *words = [NSMutableArray arrayWithCapacity:(int)seedBits.count / 11];
|
|
|
+ for (int i = 0; i < (int)seedBits.count / 11; i++) {
|
|
|
+ NSUInteger wordNumber =
|
|
|
+ strtol(
|
|
|
+ [[[seedBits subarrayWithRange: NSMakeRange(i * 11, 11)] componentsJoinedByString: @""] UTF8String],
|
|
|
+ NULL,
|
|
|
+ 2);
|
|
|
+
|
|
|
+ [words addObject: lines[wordNumber]];
|
|
|
+ }
|
|
|
+
|
|
|
+ return [words componentsJoinedByString:@" "];
|
|
|
+}
|
|
|
+
|
|
|
++ (NSString *)deterministicSeedStringFromMnemonicString:(NSString *)mnemonic
|
|
|
+ passphrase:(NSString *)passphrase
|
|
|
+ language:(NSString *)language {
|
|
|
+
|
|
|
+ NSData *data = [mnemonic dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
|
|
|
+ NSString *dataString = [[NSString alloc] initWithData: data encoding: NSASCIIStringEncoding];
|
|
|
+ NSData *normalized = [dataString dataUsingEncoding: NSASCIIStringEncoding allowLossyConversion: NO];
|
|
|
+
|
|
|
+ NSData *saltData =
|
|
|
+ [[@"mnemonic" stringByAppendingString: [[NSString alloc] initWithData:[passphrase dataUsingEncoding: NSASCIIStringEncoding
|
|
|
+ allowLossyConversion:YES]
|
|
|
+ encoding:NSASCIIStringEncoding]]
|
|
|
+ dataUsingEncoding: NSASCIIStringEncoding
|
|
|
+ allowLossyConversion: NO];
|
|
|
+
|
|
|
+ NSMutableData *hashKeyData =
|
|
|
+ [NSMutableData dataWithLength:CC_SHA512_DIGEST_LENGTH];
|
|
|
+
|
|
|
+ CCKeyDerivationPBKDF(kCCPBKDF2, normalized.bytes, normalized.length,
|
|
|
+ saltData.bytes, saltData.length, kCCPRFHmacAlgSHA512,
|
|
|
+ 2048, hashKeyData.mutableBytes, hashKeyData.length);
|
|
|
+
|
|
|
+ return [[NSData dataWithData:hashKeyData] ny_hexString];
|
|
|
+}
|
|
|
+
|
|
|
++ (NSString *)generateMnemonicString:(NSNumber *)strength
|
|
|
+ language:(NSString *)language {
|
|
|
+
|
|
|
+ // Check that the strength is divisible by 32
|
|
|
+ if ([strength intValue] % 32 != 0) {
|
|
|
+ [NSException raise:@"Strength must be divisible by 32"
|
|
|
+ format:@"Strength Was: %@", strength];
|
|
|
+ }
|
|
|
+
|
|
|
+ // Create an array of bytes
|
|
|
+ NSMutableData *bytes = [NSMutableData dataWithLength: ([strength integerValue]/8)];
|
|
|
+ // Generate the random data
|
|
|
+ int status = SecRandomCopyBytes(kSecRandomDefault, bytes.length, bytes.mutableBytes);
|
|
|
+ // Make sure we were successful
|
|
|
+ if (status != -1) {
|
|
|
+ return [self mnemonicStringFromRandomHexString:[bytes ny_hexString] language:language];
|
|
|
+ } else {
|
|
|
+ [NSException raise:@"Unable to get random data!"
|
|
|
+ format:@"Unable to get random data!"];
|
|
|
+ }
|
|
|
+ return nil;
|
|
|
+}
|
|
|
+@end
|
|
|
+
|
|
|
+@implementation NSData (NYMnemonic)
|
|
|
+- (NSString *)ny_hexString {
|
|
|
+ const unsigned char *dataBuffer = (const unsigned char *)[self bytes];
|
|
|
+
|
|
|
+ if (!dataBuffer) {
|
|
|
+ return [NSString string];
|
|
|
+ }
|
|
|
+
|
|
|
+ NSUInteger dataLength = [self length];
|
|
|
+ NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)];
|
|
|
+ for (int i = 0; i < dataLength; ++i) {
|
|
|
+ [hexString appendString:[NSString stringWithFormat:@"%02lx", (unsigned long)dataBuffer[i]]];
|
|
|
+ }
|
|
|
+
|
|
|
+ return [NSString stringWithString:hexString];
|
|
|
+}
|
|
|
+
|
|
|
+- (NSArray *)ny_hexToBitArray {
|
|
|
+ NSMutableArray *bitArray = [NSMutableArray arrayWithCapacity:(int)self.length * 8];
|
|
|
+ NSString *hexStr = [self ny_hexString];
|
|
|
+ // Loop over the string and convert each char
|
|
|
+ for (NSUInteger i = 0; i < [hexStr length]; i++) {
|
|
|
+ NSString *bin = [self _hexToBinary:[hexStr characterAtIndex:i]];
|
|
|
+ // Each character will return a string representation of the binary
|
|
|
+ // Create NSNumbers from each and append to the array.
|
|
|
+ for (NSInteger j = 0; j < bin.length; j++) {
|
|
|
+ [bitArray addObject:
|
|
|
+ @([[NSString stringWithFormat: @"%C", [bin characterAtIndex: j]] intValue])];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return [NSArray arrayWithArray:bitArray];
|
|
|
+}
|
|
|
+
|
|
|
+- (NSString *)_hexToBinary:(unichar)value {
|
|
|
+ switch (value) {
|
|
|
+ case '0': return @"0000";
|
|
|
+ case '1': return @"0001";
|
|
|
+ case '2': return @"0010";
|
|
|
+ case '3': return @"0011";
|
|
|
+ case '4': return @"0100";
|
|
|
+ case '5': return @"0101";
|
|
|
+ case '6': return @"0110";
|
|
|
+ case '7': return @"0111";
|
|
|
+ case '8': return @"1000";
|
|
|
+ case '9': return @"1001";
|
|
|
+
|
|
|
+ case 'a':
|
|
|
+ case 'A': return @"1010";
|
|
|
+
|
|
|
+ case 'b':
|
|
|
+ case 'B': return @"1011";
|
|
|
+
|
|
|
+ case 'c':
|
|
|
+ case 'C': return @"1100";
|
|
|
+
|
|
|
+ case 'd':
|
|
|
+ case 'D': return @"1101";
|
|
|
+
|
|
|
+ case 'e':
|
|
|
+ case 'E': return @"1110";
|
|
|
+
|
|
|
+ case 'f':
|
|
|
+ case 'F': return @"1111";
|
|
|
+ }
|
|
|
+ return @"-1";
|
|
|
+}
|
|
|
+
|
|
|
+@end
|
|
|
+
|
|
|
+@implementation NSString (NYMnemonic)
|
|
|
+- (NSData *)ny_dataFromHexString {
|
|
|
+ const char *chars = [self UTF8String];
|
|
|
+ int i = 0, len = (int)self.length;
|
|
|
+
|
|
|
+ NSMutableData *data = [NSMutableData dataWithCapacity:len / 2];
|
|
|
+ char byteChars[3] = { '\0', '\0', '\0' };
|
|
|
+ unsigned long wholeByte;
|
|
|
+
|
|
|
+ while (i < len) {
|
|
|
+ byteChars[0] = chars[i++];
|
|
|
+ byteChars[1] = chars[i++];
|
|
|
+ wholeByte = strtoul(byteChars, NULL, 16);
|
|
|
+ [data appendBytes:&wholeByte length:1];
|
|
|
+ }
|
|
|
+
|
|
|
+ return data;
|
|
|
+}
|
|
|
+
|
|
|
+@end
|