Hello , 我是09

此篇内容为我2018~2020年在【库神】工作时,对部分重要知识做的学习总结

随着比特币冲破10万美金,行业又再次兴起,希望相关从业者可以学习到你需要的内容

前言

  • 华特东方采用Secp256k1算法。

  • 华特东方根据分层确定性推算地址的方法和比特系列完全一致,也就是说可以和比特币一样根据扩展公钥来推出地址。

  • 华特东方根据公钥推导出地址的方法和BCH的新地址,以及BTM的地址推算及其相似。

  • 华特东方的地址前缀“htdf1”是最后得到地址后,手动拼接在最前面的。(并不是像其他的币种通过一个固定的前缀version编码后得到)

地址生成

先带大家看一下地址生成的方法:

+ (NSString )p_GetHTDFAddressWithPubKey:(NSData )publicKey slip44Code:(uint32_t)slip44_code
{
    // 1.首先是根据公钥直接生成control_programe
    NSData *publickey_hash160 = publicKey.SDK_SHA256_RMD160;
    
    // 2. 再将数据转换为字符串
    NSString *publickey_hash160_str = [NSString hexWithData:publickey_hash160];

    // 3. 再将control_programe转十进制Bytes数组
    NSMutableArray *bytes_array = [NSMutableArray arrayWithArray:publickey_hash160_str.hexStringToDecimalBytesArray];
    
    // 4. 移位转换
    NSArray *converted_array = [self o_GetPayLoadWithArray:bytes_array frombits:8 tobits:5 pad:YES];
    
    // 5. 获取有效载荷
    NSArray *payload_array = [self o_Bech32_Create_ChecksumWithPrefix:@"htdf" payload:converted_array];
    
    // 6. 移位转换拼接有效载荷
    NSArray *checksum = [converted_array arrayByAddingObjectsFromArray:payload_array];
    
    // 7. bech32转码
    NSString *bech_encode_str = [self p_GetBech32StrWithArray:checksum];
    
    // 8. 拼接前缀并返回
    return [NSString stringWithFormat:@"htdf1%@",bech_encode_str];
}

了解币种较多的伙伴可能会比较熟悉,这和BTM以及BCH的carsh地址生成方式很相似。几个关键点如下:

  1. 华特东方对公钥进行hash采用的是hash160)。(SHA256 + RMD16)

  2. 十六进制字符串转十进制数组。

  3. 移位,这个地方要注意是将8个bit变为5个bit,这个地方下面我会细讲。

  4. 有效载荷,这个大家可以理解为比特系列地址的4个byte的校验码,唯独不同的它是6个元素的数组。

十六进制字符串转十进制数组实现如下:

- (NSArray *)hexStringToDecimalBytesArray
{
    int j=0;
    Byte bytes[128];  ///3ds key的Byte 数组, 128位

    NSMutableArray *byteArray = [NSMutableArray array];
    for(int i=0;i<[self length];i++)
    {
        int int_ch;  /// 两位16进制数转化后的10进制数
        
        unichar hex_char1 = [self characterAtIndex:i]; ////两位16进制数中的第一位(高位*16)
        int int_ch1;
        if(hex_char1 >= '0' && hex_char1 <='9')
            int_ch1 = (hex_char1-48)*16;   //// 0 的Ascll - 48
        else if(hex_char1 >= 'A' && hex_char1 <='F')
            int_ch1 = (hex_char1-55)*16; //// A 的Ascll - 65
        else
            int_ch1 = (hex_char1-87)*16; //// a 的Ascll - 97
        i++;
        
        unichar hex_char2 = [self characterAtIndex:i]; ///两位16进制数中的第二位(低位)
        int int_ch2;

        if(hex_char2 >= '0' && hex_char2 <='9')
            int_ch2 = (hex_char2-48); //// 0 的Ascll - 48
        else if(hex_char1 >= 'A' && hex_char1 <='F')
            int_ch2 = hex_char2-55; //// A 的Ascll - 65
        else
            int_ch2 = hex_char2-87; //// a 的Ascll - 97
        
        int_ch = int_ch1+int_ch2;
        [byteArray addObject:[NSString stringWithFormat:@"%d",int_ch]];
        bytes[j] = int_ch;  ///将转化后的数放入Byte数组里
        j++;
    }
    return byteArray;
}

移位:大家应该知道  1个Byte = 8个bit,这个方法就是将8个bit转换为5个bit,那么原来的数据转换后就会变长。例如:20个Byte,每个Byte变为5个bit后就是32个Byte。方法如下:

+ (NSArray )o_GetPayLoadWithArray:(NSArray )array frombits:(int)frombits tobits:(int)tobits pad:(BOOL)pad
{
    //General power-of-2 base conversion.
    int acc = 0;
    int bits = 0;

    NSMutableArray *ret = [NSMutableArray array];
    int maxv = (1 << tobits) - 1;
    int max_acc = (1 << (frombits + tobits - 1)) - 1;

    for (NSString *byte in array) {

        int value = [byte intValue];
        if (value < 0 || (value >> frombits)){
            return nil;
        }

        acc = ((acc << frombits) | value) & max_acc;
        bits += frombits;

        while (bits >= tobits) {
            bits -= tobits;
            int know = (acc >> bits) & maxv;
            [ret addObject:[NSString stringWithFormat:@"%d",know]];
        }
    }

    if (pad) {
        if (bits){
            int know = (acc << (tobits - bits)) & maxv;
            [ret addObject:[NSString stringWithFormat:@"%d",know]];
        }
    }

    else if (bits >= frombits || ((acc << (tobits - bits)) & maxv)){
        return nil;
    }
    return ret;
}

获取有效载荷,其实就是华特东方地址的校验码,代码如下:

+ (NSArray )o_Bech32_Create_ChecksumWithPrefix:(NSString )prefix payload:(NSArray *)payLoadArray
{
    payLoadArray = [[self p_Bech32_Prefix_Expand:prefix] arrayByAddingObjectsFromArray:payLoadArray];
    payLoadArray = [payLoadArray arrayByAddingObjectsFromArray:@[@"0",@"0",@"0",@"0",@"0",@"0"]];
    long long poly = [self p_Bech32_PolymodWithValue:payLoadArray];
    NSMutableArray *tempArray = [NSMutableArray array];

    for (int i = 0; i < 6; i++) {
        long long num = ((poly >> 5 * (5 - i)) & 31);
        [tempArray addObject:[NSString stringWithFormat:@"%lld",num]];
    }
    return tempArray;
}

+ (NSArray )p_Bech32_Prefix_Expand:(NSString )prefix
{
    NSString *tempStr;
    NSMutableArray *tempArray = [NSMutableArray array];
    
    for (int i = 0; i < [prefix length]; i++) {
        tempStr = [prefix substringWithRange:NSMakeRange(i, 1)];
        int code = [tempStr characterAtIndex:0];
        code = code >> 5;
        [tempArray addObject:[NSString stringWithFormat:@"%d",code]];
    }

    tempArray = [NSMutableArray arrayWithArray:[tempArray arrayByAddingObjectsFromArray:@[@"0"]]];
    
    for (int i = 0; i < [prefix length]; i++) {
        tempStr = [prefix substringWithRange:NSMakeRange(i, 1)];
        int code = [tempStr characterAtIndex:0];
        code = code & 31;
        [tempArray addObject:[NSString stringWithFormat:@"%d",code]];
    }
    return tempArray;
}

// 聚模运算
+ (long int)p_Bech32_PolymodWithValue:(NSArray *)values
{
    long long chk = 1;
    NSArray *generator = @[@(0x3b6a57b2),@(0x26508e6d),@(0x1ea119fa),@(0x3d4233dd),@(0x2a1462b3)];
    for (NSString *value in values){
        long long top = chk >> 25;
        chk = ((chk & 0x1ffffff) << 5) ^ [value longLongValue];
        for (int i = 0; i < generator.count; i++) {
            if ((top >> i) & 1) {
                chk ^= [generator[i] longLongValue];
            }
        }
    }
    return chk ^ 1;
}





bech32加密方法,这个和BCH的Crash地址以及BTM是通用的,很好理解,不解释,代码如下:
+ (NSString )p_GetBech32StrWithArray:(NSArray )payLoad
{
    
    @try{
        NSString *base32Str = [NSString string];
        for (NSString *code in payLoad) {
            base32Str = [base32Str stringByAppendingString:[@"qpzry9x8gf2tvdw0s3jn54khce6mua7l" substringWithRange:NSMakeRange([code integerValue], 1)]];
        }
        return [NSString stringWithFormat:@"%@",base32Str];
    }@catch (NSException *exception) {
        //NSLog(@"Request the gaceUsed get an error:%@",exception);
    } @finally {
        
    }
}

数据校验

为了方便大家核对自己的数据是否正确,作者这里将每一步对应的结果PO上来提供给大家参考:

参数:

传入的公钥:0259d492265cded5c38aa5dc98bb9b9cacf0b7c1e8651efeb2bfbc4fd8545e4a05

Slip44_code : 346

+ (NSString )p_GetHTDFAddressWithPubKey:(NSData )publicKey slip44Code:(uint32_t)slip44_code
{
    // 1.首先是根据公钥直接生成control_programe
    NSData *publickey_hash160 = publicKey.SDK_SHA256_RMD160;
    结果:<6bb01a0c 680f6c20 a9c6419e d276cc2c 8291830b>
    
    // 2. 再将数据转换为字符串
    NSString *publickey_hash160_str = [NSString hexWithData:publickey_hash160];
    结果:6bb01a0c680f6c20a9c6419ed276cc2c8291830b

    // 3. 再将control_programe转十进制Bytes数组
    NSMutableArray *bytes_array = [NSMutableArray arrayWithArray:publickey_hash160_str.hexStringToDecimalBytesArray];
    结果:[107,176,26,12,104,15,108,32,169,198,65,158,210,118,204,44,130,145,131,11]

    // 4. 移位转换
    NSArray *converted_array = [self o_GetPayLoadWithArray:bytes_array frombits:8 tobits:5 pad:YES];
    结果:[13,14,24,1,20,3,3,8,1,29,22,2,1,10,14,6,8,6,15,13,4,29,22,12,5,18,1,9,3,0,24,11]
    
    // 5. 获取有效载荷
    NSArray *payload_array = [self o_Bech32_Create_ChecksumWithPrefix:@"htdf" payload:converted_array];
    结果:[14,23,24,21,12,15]
    
    // 6. 移位转换拼接有效载荷
    NSArray *checksum = [converted_array arrayByAddingObjectsFromArray:payload_array];
    结果:[13,14,24,1,20,3,3,8,1,29,22,2,1,10,14,6,8,6,15,13,4,29,22,12,5,18,1,9,3,0,24,11,14,23,24,21,12,15]
    
    // 7. bech32转码
    NSString *bech_encode_str = [self p_GetBech32StrWithArray:checksum];
    结果:dwcp5rrgpakzp2wxgx0dyakv9jpfrqctwhc4v0
    
    // 8. 拼接前缀并返回
    return [NSString stringWithFormat:@"htdf1%@",bech_encode_str];
    结果:htdf1dwcp5rrgpakzp2wxgx0dyakv9jpfrqctwhc4v0
}

总结

  • 看似用到的方法很多,但是仔细理解一下并不难。

  • 真正的华特东方地址就是 htdf1 + bech32 ( 移位 ( 公钥hash ) + 有效载荷 )