iOS开发-BIP39的实现

今天来说一下区块链-比特币中的一个概念,并且在iOS中将它实现。

首先介绍一下什么是BIP,BIP全称是Bitcoin Improvement Proposals,是用来提出Bitcoin的新功能或改进措施的文件。可以由任何人提出,经过审核后会公布在bitcoin/bips中。

其中BIP32、BIP39和BID44又是HD wallet中使用的核心概念。

BIP-39 介绍

HD wallet具有管理多个密钥和地址的机制,我们可以使用一个随机字符串seed通过BIP32或BIP44协议创建一个HD wallet,但是一串字符串的记忆成本太高,而且摘抄下来也会很麻烦。所以BIP39协议应运而生,他是可以使用12-24单字(可以是英文、中文、日文等等语言)来帮助用户更好的保存seed。一般我们使用长度为12个的英文单词来生成BIP39的内容,这串单词被称为mnemonic code,中文名叫助记词。

BIP-39 词典

可以在bitcoin/bips下的bip-0039中来获取助记词字典,现在已经支持简体中文、繁体中文、英文、法文、意大利文、日文、韩文、西班牙文每种2048个词来生成助记词。

BIP-39 生成助记词流程

助记词钱包是通过BIP-39中定义的标准化过程自动生成的,钱包从熵源开始,增加校验和,然后将熵映射到字典列表中。

  • 1.创建一个128-256位的随机序列(熵)
  • 2.提出SHA256哈希的前几位(熵长/32),就可以创建一个随机序列的校验和。
  • 3.将校验和添加到随机序列的末尾。
  • 4.将序列划分为包含11位的不同部分。
  • 5.将每个包含11位部分的值与一个预先定义的2048个单词的字典做对应。
  • 6.生成的有顺序的单词组就是助记词。
logo
熵(bits) 校验和(bits) 熵+校验和(bits) 助记词长度(单词个数)
128 4 132 12
160 5 165 15
192 6 198 18
224 7 231 21
256 8 264 24

使用iOS代码生成BIP-39 助记词

引入库

首先肯定是引入iOS中哈希需要用到的库

1
2
#import <CommonCrypto/CommonHMAC.h>
#import <CommonCrypto/CommonKeyDerivation.h>

生成随机熵

我们可以使用SecRandomCopyBytes()函数来生成一个随机的NSData比特组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (NSString *)generateMnemonicString:(NSNumber *)strlength language:(NSString *)language
{
//输入长度必须为128、160、192、224、256
if([strlength integerValue] % 32 != 0)
{
[NSException raise:@"Strength must be divisible by 32" format:@"Strength Was: %@",strlength];
}
//创建比特数组
NSMutableData *bytes = [NSMutableData dataWithLength:([strlength integerValue]/8)];
//生成随机data
int status = SecRandomCopyBytes(kSecRandomDefault, bytes.length, bytes.mutableBytes);
//如果生成成功
if(status == 0)
{
NSString *hexString = [bytes my_hexString];
return [self mnemonicStringFromRandomHexString:hexString language:language];
}
else
{
[NSException raise:@"Unable to get random data!" format:@"Unable to get random data!"];
}
return nil;
}

生成助记词

生成好随机的熵之后,我们就可以通过随机熵的内容根据256哈希计算需要使用的单词列,然后在单词表中查找出需要的单词来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
- (NSString *)mnemonicStringFromRandomHexString:(NSString *)seed language:(NSString *)language
{
//将16进制转换为NSData
NSData *seedData = [seed my_dataFromHexString];
//计算 sha256 哈希
NSMutableData *hash = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
CC_SHA256(seedData.bytes, (int)seedData.length, hash.mutableBytes);
NSMutableArray *checkSumBits = [NSMutableArray arrayWithArray:[[NSData dataWithData:hash] my_hexToBitArray]];
NSMutableArray *seedBits = [NSMutableArray arrayWithArray:[seedData my_hexToBitArray]];
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]];
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:@" "];
}

需要的支持

其中我们需要几个辅助性操作:

字符串转换为16进制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//将字符串转换为16进制
- (NSString *)my_hexString
{
const unsigned char *dataBuffer = (const unsigned char *)[self bytes];
//如果buffer不存在
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];
}

16进制转换为比特数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
- (NSArray *)my_hexToBitArray
{
NSMutableArray *bitArray = [NSMutableArray arrayWithCapacity:(int)self.length * 8];
NSString *hexStr = [self my_hexString];
for(NSUInteger i = 0 ; i < [hexStr length] ; i++)
{
NSString *bin = [self my_hexToBinary:[hexStr characterAtIndex:i]];
for(NSUInteger j = 0 ; j < bin.length ; j++)
{
[bitArray addObject:@([[NSString stringWithFormat:@"%C",[bin characterAtIndex:j]] intValue])];
}
}
return [NSArray arrayWithArray:bitArray];
}
- (NSString *)my_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";
}

16进制字符串转换为NSData

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//将16进制字符串转换为NSData
- (NSData *)my_dataFromHexString
{
const char *chars = [self UTF8String];
int i = 0, len = (int)self.length;
NSMutableData *data = [NSMutableData dataWithCapacity:len/2.0];
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;
}

调用

OK,这些关键的内容都写好之后,只需要调用最开始的生成随机字符的函数,指定好长度和助记词语言就可以根据BIP-39生成助记词了。

1
NSString *mnemonicString = [self generateMnemonicString:@128 language:@"english"];

Demo

Demo地址在这里

参考文档

bip-0044.mediawiki

BIP32, BIP39, BIP44

Mnemonic Code Converter