Created
June 9, 2013 00:06
-
-
Save oleganza/5737055 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Oleg Andreev <[email protected]> | |
#import <Foundation/Foundation.h> | |
// Base58 is used for compact human-friendly representation of Bitcoin addresses and private keys. | |
// Typically Base58-encoded text also contains a checksum. | |
// Addresses look like 19FGfswVqxNubJbh1NW8A4t51T9x9RDVWQ | |
// Private keys look like 5KQntKuhYWSRXNqp2yhdXzjekYAR7US3MT1715Mbv5CyUKV6hVe | |
// | |
// Here is what Satoshi said about Base58: | |
// Why base-58 instead of standard base-64 encoding? | |
// - Don't want 0OIl characters that look the same in some fonts and | |
// could be used to create visually identical looking account numbers. | |
// - A string with non-alphanumeric characters is not as easily accepted as an account number. | |
// - E-mail usually won't line-break if there's no punctuation to break at. | |
// - Double-clicking selects the whole number as one word if it's all alphanumeric. | |
@interface NSString (Base58) | |
// Returns data for Base58 string without checksum | |
// Data is mutable so you can clear sensitive information as soon as possible. | |
- (NSMutableData*) dataFromBase58; | |
// Returns data for Base58 string with checksum | |
- (NSMutableData*) dataFromBase58Check; | |
@end | |
@interface NSData (Base58) | |
// String in Base58 without checksum | |
- (NSString*) base58; | |
// String in Base58 with checksum | |
- (NSString*) base58Check; | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Oleg Andreev <[email protected]> | |
#import "NSData+Base58.h" | |
#import "NSData+BTC.h" | |
#import <openssl/bn.h> | |
static const char* BTCBase58Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; | |
@implementation NSString (Base58) | |
- (NSMutableData*) dataFromBase58 | |
{ | |
NSMutableData* result = nil; | |
BN_CTX* pctx = BN_CTX_new(); | |
__block BIGNUM bn58; BN_init(&bn58); BN_set_word(&bn58, 58); | |
__block BIGNUM bn; BN_init(&bn); BN_zero(&bn); | |
__block BIGNUM bnChar; BN_init(&bnChar); | |
void(^finish)() = ^{ | |
if (pctx) BN_CTX_free(pctx); | |
BN_clear_free(&bn58); | |
BN_clear_free(&bn); | |
BN_clear_free(&bnChar); | |
}; | |
const char* psz = [self cStringUsingEncoding:NSUTF8StringEncoding]; | |
while (isspace(*psz)) psz++; | |
// Convert big endian string to bignum | |
for (const char* p = psz; *p; p++) | |
{ | |
const char* p1 = strchr(BTCBase58Alphabet, *p); | |
if (p1 == NULL) | |
{ | |
while (isspace(*p)) | |
p++; | |
if (*p != '\0') | |
{ | |
finish(); | |
return nil; | |
} | |
break; | |
} | |
BN_set_word(&bnChar, p1 - BTCBase58Alphabet); | |
if (!BN_mul(&bn, &bn, &bn58, pctx)) | |
@throw [NSException exceptionWithName:@"NSString+Base58 Exception" | |
reason:@"Failed to execute BN_mul." userInfo:nil]; | |
if (!BN_add(&bn, &bn, &bnChar)) | |
@throw [NSException exceptionWithName:@"NSString+Base58 Exception" | |
reason:@"Failed to execute BN_add." userInfo:nil]; | |
} | |
// Get bignum as little endian data | |
size_t bnsize = BN_bn2mpi(&bn, NULL); | |
if (bnsize <= 4) | |
{ | |
@throw [NSException exceptionWithName:@"NSString+Base58 Exception" | |
reason:@"Failed to execute BN_bn2mpi." userInfo:nil]; | |
} | |
NSMutableData* bndata = [NSMutableData dataWithLength:bnsize]; | |
BN_bn2mpi(&bn, bndata.mutableBytes); | |
[bndata replaceBytesInRange:NSMakeRange(0, 4) withBytes:NULL length:0]; | |
[bndata reverse]; | |
// Trim off sign byte if present | |
bnsize = bndata.length; | |
if (bnsize >= 2 | |
&& ((unsigned char*)bndata.bytes)[bnsize - 1] == 0 | |
&& ((unsigned char*)bndata.bytes)[bnsize - 2] >= 0x80) | |
{ | |
bnsize -= 1; | |
[bndata setLength:bnsize]; | |
} | |
// Restore leading zeros | |
int nLeadingZeros = 0; | |
for (const char* p = psz; *p == BTCBase58Alphabet[0]; p++) | |
nLeadingZeros++; | |
result = [NSMutableData dataWithLength:nLeadingZeros + bnsize]; | |
// Copy the bignum to the beginning of array. We'll reverse it then and zeros will become leading zeros. | |
[result replaceBytesInRange:NSMakeRange(0, bnsize) withBytes:bndata.bytes length:bnsize]; | |
// Convert little endian data to big endian | |
[result reverse]; | |
finish(); | |
return result; | |
} | |
- (NSMutableData*) dataFromBase58Check | |
{ | |
NSMutableData* result = [self dataFromBase58]; | |
size_t length = result.length; | |
if (length < 4) | |
{ | |
return nil; | |
} | |
NSData* hash = [[result subdataWithRange:NSMakeRange(0, length - 4)] doubleSHA256]; | |
// Last 4 bytes should be equal first 4 bytes of the hash. | |
if (memcmp(hash.bytes, result.bytes + length - 4, 4) != 0) | |
{ | |
return nil; | |
} | |
[result setLength:length - 4]; | |
return result; | |
} | |
@end | |
@implementation NSData (Base58) | |
// String in Base58 without checksum | |
- (NSString*) base58 | |
{ | |
BN_CTX* pctx = BN_CTX_new(); | |
__block BIGNUM bn58; BN_init(&bn58); BN_set_word(&bn58, 58); | |
__block BIGNUM bn0; BN_init(&bn0); BN_zero(&bn0); | |
__block BIGNUM bn; BN_init(&bn); BN_zero(&bn); | |
__block BIGNUM dv; BN_init(&dv); BN_zero(&dv); | |
__block BIGNUM rem; BN_init(&rem); BN_zero(&rem); | |
void(^finish)() = ^{ | |
if (pctx) BN_CTX_free(pctx); | |
BN_clear_free(&bn58); | |
BN_clear_free(&bn0); | |
BN_clear_free(&bn); | |
BN_clear_free(&dv); | |
BN_clear_free(&rem); | |
}; | |
// Convert big endian data to little endian. | |
// Extra zero at the end make sure bignum will interpret as a positive number. | |
NSMutableData* tmp = [self reversedMutableData]; | |
tmp.length += 1; | |
// Convert little endian data to bignum | |
{ | |
NSUInteger size = tmp.length; | |
NSMutableData* mdata = [tmp mutableCopy]; | |
// Reverse to convert to OpenSSL bignum endianess | |
[mdata reverse]; | |
// BIGNUM's byte stream format expects 4 bytes of | |
// big endian size data info at the front | |
[mdata replaceBytesInRange:NSMakeRange(0, 0) withBytes:"\0\0\0\0" length:4]; | |
unsigned char* bytes = mdata.mutableBytes; | |
bytes[0] = (size >> 24) & 0xff; | |
bytes[1] = (size >> 16) & 0xff; | |
bytes[2] = (size >> 8) & 0xff; | |
bytes[3] = (size >> 0) & 0xff; | |
BN_mpi2bn(bytes, (int)mdata.length, &bn); | |
} | |
// Expected size increase from base58 conversion is approximately 137% | |
// use 138% to be safe | |
NSMutableData* stringData = [NSMutableData dataWithCapacity:self.length*138/100 + 1]; | |
while (BN_cmp(&bn, &bn0) > 0) | |
{ | |
if (!BN_div(&dv, &rem, &bn, &bn58, pctx)) | |
{ | |
@throw [NSException exceptionWithName:@"NSData+Base58" | |
reason:@"Failed to execute BN_div." userInfo:nil]; | |
} | |
BN_copy(&bn, &dv); | |
unsigned long c = BN_get_word(&rem); | |
[stringData appendBytes:BTCBase58Alphabet + c length:1]; | |
} | |
finish(); | |
// Leading zeroes encoded as base58 ones | |
const unsigned char* pbegin = self.bytes; | |
const unsigned char* pend = self.bytes + self.length; | |
for (const unsigned char* p = pbegin; p < pend && *p == 0; p++) | |
{ | |
[stringData appendBytes:BTCBase58Alphabet + 0 length:1]; | |
} | |
// Convert little endian std::string to big endian | |
[stringData reverse]; | |
return [[NSString alloc] initWithData:stringData encoding:NSUTF8StringEncoding]; | |
} | |
// String in Base58 with checksum | |
- (NSString*) base58Check | |
{ | |
// add 4-byte hash check to the end | |
NSMutableData* data = [self mutableCopy]; | |
NSData* checksum = [data doubleSHA256]; | |
[data appendBytes:checksum.bytes length:4]; | |
return [data base58]; | |
} | |
@end | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Oleg Andreev <[email protected]> | |
#import <Foundation/Foundation.h> | |
@interface NSData (BTC) | |
// Init with securely random byte string from /dev/random | |
- (id) initRandomWithLength:(NSUInteger)length; | |
// Init with zero-terminated string in UTF-8 encoding. | |
- (id) initWithUTF8String:(const char*)utf8string; | |
// Init with hex string (lower- or uppercase, with optional 0x prefix) | |
- (id) initWithHexString:(NSString*)hexString; | |
// Init with zero-terminated hex raw string (lower- or uppercase, with optional 0x prefix) | |
- (id) initWithHexCString:(const char*)hexCString; | |
// Returns a copy of data with reversed byte order | |
- (NSData*) reversedData; | |
// Returns a reversed mutable copy so you wouldn't need to make another mutable copy | |
- (NSMutableData*) reversedMutableData; | |
// Core hash functions that we need | |
- (NSData*) SHA256; | |
- (NSData*) RIPEMD160; | |
- (NSData*) doubleSHA256; // aka Hash in Bitcoin, more efficient than .SHA256.SHA256 | |
- (NSData*) SHA256RIPEMD160; // aka Hash160 in Bitcoin, more efficient than .SHA256.RIPEMD160 | |
// Formats data as a lowercase hex string | |
- (NSString*) hexString; | |
- (NSString*) hexUppercaseString; | |
// Encrypts/decrypts data using the key. IV is null (you should use random or salted key). | |
+ (NSMutableData*) encryptData:(NSData*)data key:(NSData*)key; | |
+ (NSMutableData*) decryptData:(NSData*)data key:(NSData*)key; | |
@end | |
@interface NSMutableData (BTC) | |
// Reversed bytes in place. | |
- (void) reverse; | |
// Clears contents of the data to prevent leaks through swapping or buffer-overflow attacks. | |
- (void) clear; | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Oleg Andreev <[email protected]> | |
#import "NSData+BTC.h" | |
#import <CommonCrypto/CommonCrypto.h> | |
#include <openssl/ripemd.h> | |
// This is designed to be not optimized out by compiler like memset | |
void *NSData_BTCSecureMemset(void *v, int c, size_t n) | |
{ | |
if (!v) return v; | |
volatile unsigned char *p = v; | |
while (n--) | |
*p++ = c; | |
return v; | |
} | |
void *NSData_BTCRandomData(size_t length) | |
{ | |
FILE *fp = fopen("/dev/random", "r"); | |
if (!fp) | |
{ | |
NSLog(@"NSData+BTC: cannot fopen /dev/random"); | |
exit(-1); | |
return NULL; | |
} | |
char* bytes = (char*)malloc(length); | |
for (int i = 0; i < length; i++) | |
{ | |
char c = fgetc(fp); | |
bytes[i] = c; | |
} | |
fclose(fp); | |
return bytes; | |
} | |
@implementation NSData (BTC) | |
- (id) initRandomWithLength:(NSUInteger)length | |
{ | |
void *bytes = NSData_BTCRandomData(length); | |
if (!bytes) return nil; | |
return [self initWithBytesNoCopy:bytes length:length]; | |
} | |
- (id) initWithUTF8String:(const char*)utf8string | |
{ | |
return [self initWithBytes:utf8string length:strlen(utf8string)]; | |
} | |
- (id) initWithHexString:(NSString*)hexString | |
{ | |
return [self initWithHexCString:[hexString cStringUsingEncoding:NSUTF8StringEncoding]]; | |
} | |
- (id) initWithHexCString:(const char *)hexCString | |
{ | |
if (!hexCString) return [self init]; | |
const unsigned char *psz = (const unsigned char*)hexCString; | |
while (isspace(*psz)) psz++; | |
// Skip optional 0x prefix | |
if (psz[0] == '0' && tolower(psz[1]) == 'x') psz += 2; | |
while (isspace(*psz)) psz++; | |
size_t len = strlen((const char*)psz); | |
// If the string is not full number of bytes (each byte 2 hex characters), return nil. | |
if (len % 2 != 0) return nil; | |
unsigned char* buf = (unsigned char*)malloc(len/2); | |
static const signed char digits[256] = { | |
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, | |
-1,0xa,0xb,0xc,0xd,0xe,0xf, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
-1,0xa,0xb,0xc,0xd,0xe,0xf, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 | |
}; | |
unsigned char* bufpointer = buf; | |
while (1) | |
{ | |
unsigned char c1 = (unsigned char)*psz++; | |
signed char n1 = digits[c1]; | |
if (n1 == (signed char)-1) break; // break when null-terminator is hit | |
unsigned char c2 = (unsigned char)*psz++; | |
signed char n2 = digits[c2]; | |
if (n2 == (signed char)-1) break; // break when null-terminator is hit | |
*bufpointer = (unsigned char)((n1 << 4) | n2); | |
bufpointer++; | |
} | |
return [self initWithBytesNoCopy:buf length:len/2]; | |
} | |
- (NSData*) reversedData | |
{ | |
return [self reversedMutableData]; | |
} | |
- (NSMutableData*) reversedMutableData | |
{ | |
NSMutableData* md = [self mutableCopy]; | |
[md reverse]; | |
return md; | |
} | |
+ (NSMutableData*) encryptData:(NSData*)data key:(NSData*)key | |
{ | |
return [self cryptData:data key:key operation:kCCEncrypt]; | |
} | |
+ (NSMutableData*) decryptData:(NSData*)data key:(NSData*)key | |
{ | |
return [self cryptData:data key:key operation:kCCDecrypt]; | |
} | |
+ (NSMutableData*) cryptData:(NSData*)data key:(NSData*)key operation:(CCOperation)operation | |
{ | |
if (!data || !key) return nil; | |
int encryptedDataCapacity = (int)(data.length / kCCBlockSizeAES128 + 1) * kCCBlockSizeAES128; | |
NSMutableData* encryptedData = [[NSMutableData alloc] initWithLength:encryptedDataCapacity]; | |
size_t dataOutMoved = 0; | |
CCCryptorStatus cryptstatus = CCCrypt( | |
operation, // CCOperation op, /* kCCEncrypt, kCCDecrypt */ | |
kCCAlgorithmAES128, // CCAlgorithm alg, /* kCCAlgorithmAES128, etc. */ | |
kCCOptionPKCS7Padding, // CCOptions options, /* kCCOptionPKCS7Padding, etc. */ | |
key.bytes, // const void *key, | |
key.length, // size_t keyLength, | |
0, // const void *iv, /* optional initialization vector; we'll be using different salt on each login */ | |
data.bytes, // const void *dataIn, /* optional per op and alg */ | |
data.length, // size_t dataInLength, | |
encryptedData.mutableBytes, // void *dataOut, /* data RETURNED here */ | |
encryptedData.length, // size_t dataOutAvailable, | |
&dataOutMoved // size_t *dataOutMoved | |
); | |
if (cryptstatus == kCCSuccess) | |
{ | |
// Resize the result key to the correct size. | |
encryptedData.length = dataOutMoved; | |
return encryptedData; | |
} | |
else | |
{ | |
//kCCSuccess = 0, | |
//kCCParamError = -4300, | |
//kCCBufferTooSmall = -4301, | |
//kCCMemoryFailure = -4302, | |
//kCCAlignmentError = -4303, | |
//kCCDecodeError = -4304, | |
//kCCUnimplemented = -4305, | |
//kCCOverflow = -4306 | |
@throw [NSException exceptionWithName:@"NSData+BTC CCCrypt failed" | |
reason:[NSString stringWithFormat:@"error: %d", cryptstatus] userInfo:nil]; | |
return nil; | |
} | |
} | |
- (NSData*) SHA256 | |
{ | |
unsigned char digest[CC_SHA256_DIGEST_LENGTH]; | |
CC_SHA256([self bytes], (CC_LONG)[self length], digest); | |
return [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH]; | |
} | |
- (NSData*) doubleSHA256 | |
{ | |
unsigned char digest1[CC_SHA256_DIGEST_LENGTH]; | |
unsigned char digest2[CC_SHA256_DIGEST_LENGTH]; | |
CC_SHA256([self bytes], (CC_LONG)[self length], digest1); | |
CC_SHA256(digest1, CC_SHA256_DIGEST_LENGTH, digest2); | |
return [NSData dataWithBytes:digest2 length:CC_SHA256_DIGEST_LENGTH]; | |
} | |
- (NSData*) SHA256RIPEMD160 | |
{ | |
unsigned char digest1[CC_SHA256_DIGEST_LENGTH]; | |
unsigned char digest2[RIPEMD160_DIGEST_LENGTH]; | |
CC_SHA256([self bytes], (CC_LONG)[self length], digest1); | |
RIPEMD160(digest1, CC_SHA256_DIGEST_LENGTH, digest2); | |
return [NSData dataWithBytes:digest2 length:RIPEMD160_DIGEST_LENGTH]; | |
} | |
- (NSData*) RIPEMD160 | |
{ | |
unsigned char digest[RIPEMD160_DIGEST_LENGTH]; | |
RIPEMD160([self bytes], (size_t)[self length], digest); | |
return [NSData dataWithBytes:digest length:RIPEMD160_DIGEST_LENGTH]; | |
} | |
- (NSString*) hexString | |
{ | |
return [self hexStringWithFormat:"%02x"]; | |
} | |
- (NSString*) hexUppercaseString | |
{ | |
return [self hexStringWithFormat:"%02X"]; | |
} | |
- (NSString*) hexStringWithFormat:(const char*)format | |
{ | |
if (self.length == 0) return @""; | |
NSUInteger length = self.length; | |
NSMutableData* data = [NSMutableData dataWithLength:length * 2]; | |
char *dest = data.mutableBytes; | |
unsigned const char *src = self.bytes; | |
for (int i = 0; i < length; ++i) | |
{ | |
sprintf(dest + i*2, format, (unsigned int)(src[i])); | |
} | |
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; | |
} | |
@end | |
@implementation NSMutableData (BTC) | |
- (void) reverse | |
{ | |
// K&R | |
NSUInteger length = self.length; | |
if (length <= 1) return; | |
unsigned char* buf = self.mutableBytes; | |
unsigned char byte; | |
NSUInteger i, j; | |
for (i = 0, j = length - 1; i < j; i++, j--) | |
{ | |
byte = buf[i]; | |
buf[i] = buf[j]; | |
buf[j] = byte; | |
} | |
} | |
- (void) clear | |
{ | |
[self resetBytesInRange:NSMakeRange(0, self.length)]; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment