Skip to content

Instantly share code, notes, and snippets.

@oleganza
Created June 9, 2013 00:06
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oleganza/5737055 to your computer and use it in GitHub Desktop.
Save oleganza/5737055 to your computer and use it in GitHub Desktop.
// Oleg Andreev <oleganza@gmail.com>
#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
// Oleg Andreev <oleganza@gmail.com>
#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
// Oleg Andreev <oleganza@gmail.com>
#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
// Oleg Andreev <oleganza@gmail.com>
#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