Hello , 我是09

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

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

前提

瑞波币市面上能见到的私钥大概分为两种。

        (1). 根据助记词,通过分层确定性一步一步通过Secp256K1生成的32个Byte64个长度的私钥。

                   例: 2a6b9bf9a391f0055dbfe70c0ebc7393a81513377c53d276d63f3d243a079dca

        (2). 瑞波币官方使用的私钥最后通过base58得到的长度为29的私钥。

                   例:shtrUE17GiDF27C9yan4E55vfCsR。

本文主要讲解瑞波币官方使用的私钥格式。

私钥解析

解析场景:

种子:shtrUE17GiDF27CV9yan4E55vfCsR

算法:Secp256k1

特殊:超级长的大整数的和以及模运算。

如果大家用的Secp256k1,在分层确定性中,就有通过(a + b) % c的方法,如果没有那只能自己写了,作者这写过一套大数加法,如果需要可以拿去。

#pragma mark  --  超级大的两个数相加(两个长度在100位以内的数字测试没有问题)  --
- (NSString )addSuperBigNumber:(NSString )number
{
    // 1. 初始化部分参数
    BOOL isCarry = 0;      // 是否需要进位
    NSInteger total_len = self.length > number.length ? self.length : number.length;
    NSString *sum_str = [NSString string];
    
    // 2. 循环计算
    for (NSInteger i = 1; i < total_len + 1; i++) {
        
        @autoreleasepool {
            // 2.1 取出两个加数的对应位置上的数字
            int a = self.length >= i ? [[self substringWithRange:NSMakeRange(self.length - i, 1)] intValue] : 0;
            int b = number.length >= i ? [[number substringWithRange:NSMakeRange(number.length - i, 1)] intValue] : 0;
            
            // 2.2 计算a + b + 进位
            int sum = a + b + (int)isCarry;
            
            // 2.3 拼接和
            sum_str = [NSString stringWithFormat:@"%d%@",sum % 10,sum_str];
            
            // 2.4 计算是否有进位
            isCarry = sum > 9 ? YES : NO;
            
            if (i == total_len && isCarry) {
                sum_str = [NSString stringWithFormat:@"1%@",sum_str];
            }
        }
    }
    return sum_str;
}

大家可以根据自己的需求来添加判断条件,网络上也可以搜索到C函数的方法大家自行选择。

解析重点

这个是XRP解析的hash散列的计算方式,作者写的时候参考的官方JAVA代码,作者OC的代码如下:

#define SECP256K1_ORDER @“115792089237316195423570985008687907852837564279074904382605163141518161494337"
+ (NSData )p_SpacingAndHash:(NSData )data discriminator:(int)discriminator
{
    // 1. 实例化可变的要进行拼接的data,并赋值
    NSMutableData *spacing_data = [NSMutableData dataWithData:data];
    
    // 2. 实例化一个返回Data
    NSData *result_data = [NSData data];
    for (long i = 0L; i <= 4294967295L; i++) {
        
        // 3. 判别器,判别是否需要进行拼接数据
        if (discriminator >= 0) {
            [spacing_data appendUInt32:discriminator];
        }
        
        // 4. 拼接i
        [spacing_data appendUInt32:(int)i];
        
        // 5. 进行SHA512操作,并截取前32个byte
        result_data = [spacing_data.SDK_SHA512 subdataWithRange:NSMakeRange(0, 32)];
        
        // 6. 将结果转换为10机制
        NSString *decimalString = [KSHotWalletBigNumber bigNumberWithData:result_data].decimalString;
        
        // 7. 判断结果是否大于算法的N值,这个值是固定的,要求:1 < 结果要求必须 < N值,如果满足就Break,不满足就继续循环
        if ([[SECP256K1_SORT minusSuperBigNumber:decimalString] isEqualToString:@"0"]) {
            continue;
        }else if ([[decimalString minusSuperBigNumber:@"1"] isEqualToString:@"0"]) {
            continue;
        }else{
            break;
        }
    }
    return result_data;
}

开始解析

  1. 第一步:将私钥进行Base58解码。

NSData *private_base58_encode = [privateKey dataUsingEncoding:NSUTF8StringEncoding];
NSMutableData *private_base58_decode = [NSMutableData secureDataWithLength:21];
xrp_decode_base58(private_base58_encode.bytes, private_base58_encode.length, private_base58_decode.mutableBytes);

// 解码后的结果
po [NSString hexWithData:private_base58_decode]
结果:219ccd8eff8f7a84600a8f6fe5ca2ba4b8268ac004
  1. 第二步,截取私钥,就是去掉第一步得到的私钥中的Version(版本号)以及CheckCode(校验码)。

私钥结构:
219ccd8eff8f7a84600a8f6fe5ca2ba4b8268ac004
version + private + hash校验码

// 截取私钥
NSData *private_data = [private_base58_decode subdataWithRange:NSMakeRange(1, private_base58_decode.length - 5)];

// 我们得到截取后的私钥
po [NSString hexWithData:private_data]
结果:9ccd8eff8f7a84600a8f6fe5ca2ba4b8
  1. 第三步,将私钥进行hash散列运算,方法上面已经po上了。

NSData *private_hash_data = [self p_SpacingAndHash:private_data discriminator:-1];

// 私钥散列结果
po [NSString hexWithData:private_hash_data]
结果:f938695eae97422459372885edabe031a635c52447858f9051f5ed98c27ab185
  1. 第四步,我们通过第第三步得到的散列结果推算出公钥,这里用到的Secp256k1,算法不做过多介绍。

NSData pub_data = [KSHotWalletImplement_PublicKey o_GetPublicKeyWithSeckey:(const UInt256 *)data_in_0_sha512.bytes slip44:144 compressed:YES];

// 十六进制公钥
po [NSString hexWithData:pub_data]
结果:02f9e0fa55dc0d9111b84da7688215c72dbd7eea6aeee063240fdbf020d194707f
  1. 第五步,将公钥进行hash散列运算,结果就是我们终签名秘钥的第一部分。

NSData *public_hash_data = [self p_SpacingAndHash:pub_data discriminator:0];

// 公钥散列结果
po [NSString hexWithData:public_hash_data]
结果:3133329af4faade10488be8621109360bc8e2af9e416e322441bae1847c32d86
  1. 第六步,计算秘钥     秘钥 = (私钥散列 + 公钥散列)mod secp256k1 order

UInt256 result_256 = (const UInt256 )private_hash_data.bytes;
UInt256 result_256_2 = (const UInt256 )public_hash_data.bytes;
KSBRSecp256k1ModAdd(&result_256, &result_256_2);

// adds 256bit big endian ints a and b (mod secp256k1 order) and stores the result in a
// returns true on success
int KSBRSecp256k1ModAdd(UInt256 a, const UInt256 b)

// 最终秘钥的十六进制
结果:2a6b9bf9a391f0055dbfe70c0ebc7393a81513377c53d276d63f3d243a079dca
  • 到这里我们就成功的解出来正确的瑞波币秘钥了!

  • 作者举例用的私钥是随机生成的,里面没有币,大家不要白费力气在这个上面哦。

注意点

  1. 瑞波币私钥解析的散列计算函数的时候,一定要注意作者po的代码,hash出一个正确的私钥必须要小于secp256k1 order。

  2. 调用瑞波币散列计算函数,私钥散列和公钥三笠传入的判别器的值是不一样的,一定要注意!

由于OC没有相关的资源,大部分项目为了实现功能都是直接继承JavaScript,通过交互实现交易等功能,这导致圈里为数不多的开发者根本不知道解析都做了什么,如果没有资料根本无法实现功能。所以作者极力不推荐这种方式。很多做过以太坊等钱包的小伙伴来面试,一问三不知,希望大家既然选择做这一块,就一定要做到懂这一块!

以上为纯OC原生开发的总结,如果有问题欢迎大家留言提出!