// 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