Hello , 我是09

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

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

前言

  • IOST通过助记词推导出的IOST路径和其他的币种不同,它和新经币一样只保留增强的路径:  m/44’/291’/0’。

  • IOST的私钥与公钥的可视化展示都是经过base58编码后得到的。

  • IOST的分层确定性推算方法和比特系列是不同的,它用的标准的ED25519的推算方法。

场景

Seed   : super copper normal bronze almost habit razor bottom trip dutch unusual order

Slip44 : 291

Path   : m/44'/291'/0'

算法   : ED25519

重点

标准ED25519分层确定性:

#pragma mark  --  这个是标准的ED25519分层确定性  --
void hdnode_private_ckd_ed25519(UInt256 k, UInt256 c, uint32_t i)
{
    uint8_t buf[sizeof(BRECPoint) + sizeof(i)];
    UInt512 I;
    
    if (i & BIP32_HARD) {
        buf[0] = 0;
        (UInt256 )&buf[1] = *k;
    }else {
        NSLog(@"数据有误");
    }
    
    (uint32_t )&buf[sizeof(BRECPoint)] = CFSwapInt32HostToBig(i);
    
    CCHmac( kCCHmacAlgSHA512, c, sizeof(*c), buf, sizeof(buf), &I );
    k = (UInt256 *)&I;
    c = (UInt256 *)&I.u8[sizeof(UInt256)];
    memset(buf, 0, sizeof(buf));
    memset(&I, 0, sizeof(I));
}

私钥生成

通过助记词进行经过PBKDF算法后推导出助记符代码,推导的相关代码请查看基础支持模块。

NSString *masterSeed = [KSHotWalletImplement_Mnemonic o_deriveKeyFromPhrase:seed withPassphrase:nil];

// 我们得到的助记符代码
74fe2e4c74e09b654bf6e9cdb043deb05548e95b22cd4cf34423401401c96e1c320c9e1f55546d2e530399cf047608bcd6a64129662fe1178f8c8698699316ed

我们讲助记符代码拆分为秘钥和链码,(这里有些同学称呼为 私钥和链码,都可以,但作者分析过很多国外的项目,觉得称为秘钥和链码最为合适)

#define BIP32_SEED_KEY_IOST @"ed25519 seed"

// 1. 将助记符代码进行HMac运算,运算中运用hash512,并加入key。
UInt512 hmac_data = (UInt512 )[masterSeed SDK_HMACWithAlgorithm:kCCHmacAlgSHA512 key:BIP32_SEED_KEY_IOST].bytes;

// 2. SHA512的结果为64个Byte,我们将结果进行拆分,前32个Byte就是秘钥、后32个Byte就是链码
UInt256 secret = (UInt256 )&hmac_data;
UInt256 chain = (UInt256 )&hmac_data.u8[sizeof(UInt256)];

// 打印数据
hmac_data结果:cae09bc9de80adecab04a47fd758d48f84c3654d7aed924ef22e018774db94937b300b536fbef8e101b04f7763a1c49b529c2c582dd6267e521997eceb8bbd03
secret :cae09bc9de80adecab04a47fd758d48f84c3654d7aed924ef22e018774db9493
chain  :7b300b536fbef8e101b04f7763a1c49b529c2c582dd6267e521997eceb8bbd03

得到最上层的秘钥和链码后,我们就可以通过封层确定性来推出指定路径的秘钥和链码。

+ (NSString )p_GetIOSTXEMPrivateKeyWithMasterSeed:(NSData )masterSeed path:(NSString *)path slip44Code:(uint32_t)slip44_code
{
    // 1. 将助记符代码进行HMac运算,运算中运用hash512,并加入key。
    UInt512 hmac_data = (UInt512 )[masterSeed SDK_HMACWithAlgorithm:kCCHmacAlgSHA512 key:BIP32_SEED_KEY_IOST].bytes;
    UInt256 secret = (UInt256 )&hmac_data;
    UInt256 chain = (UInt256 )&hmac_data.u8[sizeof(UInt256)];
    
    // 2. SHA512的结果为64个Byte,我们将结果进行拆分,前32个Byte就是秘钥、后32个Byte就是链码
    NSArray *pathArray = [path componentsSeparatedByString:@"/"];
    for (NSString *path_info in pathArray) {
        
        if ([path_info isEqualToString:@"m"]) {
            continue;
        }else if ([path_info rangeOfString:@"'"].location != NSNotFound){
            
            // 2.5 将路径的某一个增强层级转换为索引
            unsigned int index = [[path_info stringByReplacingOccurrencesOfString:@"'" withString:@""] intValue];
            
            // 2.6 执行分层确定性进行分层运算
            hdnode_private_ckd_ed25519(&secret, &chain, index | BIP32_HARD);
        }
    }
}

// 第一层结果打印
Path   : m/44'/
secret :2ead62c97341eb99f81ed7bca36d640785a5a61f9d78d00d8f5f72e9ad1f166f
chain  :325ebb3488faf46d5832e001d7d825cf8ea75c38ba3ddf6357ff0f34f448625b

// 第二层结果打印
Path   : m/44'/291’
secret :76edf06cfad75c3a88864b5d0905a59de504536109a7b4f2ffc6190d1f392783
chain  :ece82ad47e8483dc55880d1061854721871a57b041c6927486031d50a05a8153

// 第三层结果打印
Path   : m/44'/291'/0'
secret :4486c6bed9c43cf1bf95ab793117d270c9747b6076b22fcfeb5dfb01499db37a
chain  :8cb3b546f08ee763946252ee612cb93bc89c22961611ef5d98831e0f922d71d6

通过最后一层的秘钥和链码推导私钥

// 1. 将秘钥和链码合并转换为Data,非OC语言可以理解为转换为Byte
NSMutableData *secret_data = [NSMutableData data];
[secret_data appendData:[NSData dataWithBytes:&secret length:sizeof(secret)]];
[secret_data appendData:[NSData dataWithBytes:&chain length:sizeof(chain)]];

// 打印
secret_data结果:4486c6bed9c43cf1bf95ab793117d270c9747b6076b22fcfeb5dfb01499db37a8cb3b546f08ee763946252ee612cb93bc89c22961611ef5d98831e0f922d71d6

// 2. 将结合后的结果进行base58得到可视化私钥
size_t len = 100;
char base[100];
eos_encode_base58(base, &len, secret_data.bytes,64);

// 打印
Po [NSString stringWithUTF8String:base]
结果:2NTtrXqourN1ULTNxk6uW1H8W58ZZN5K9TzvrVnYVpb4cRwtDFqs9ZUuW9Cswo5zUJptHe7qXSqCH324dLocPvdT
  • 上面的代码中,将秘钥和链码结合后其实就是我们要获取的私钥。

  • 市面上部分钱包时这么做的,也有部分钱包后面拼接的是各种运算公钥得到的结果,并不是很统一,没有一个统一的格式,但是这并不影响我们签名。

  • 在IOST的签名中,其实我们真正用到的就是前32个Byte,后32个Byte根本就没有用到,至于为什么要这样,作者有时间找官方的人问一下再回来解答。

  • 最后进行base58是将私钥转换为可视化私钥的一种方法,不同的私钥base58编码后长度lenght有可能是88和87。

公钥(Onwerkey)生成

我们先将私钥的原因就在于需要通过私钥来计算公钥,用到的算法为Ed25519。

// 1. 第一步我们获取私钥,只需要前32个Byte也就是分层确定性后的secret(秘钥)
NSMutableData *private_data = [NSMutableData dataWithBytes:&seckey length:sizeof(seckey)];

// 打印
private_data结果 :4486c6bed9c43cf1bf95ab793117d270c9747b6076b22fcfeb5dfb01499db37a

// 2. ED25519算法推算公钥
NSMutableData *pub_data = [NSMutableData dataWithLength:32];
ed25519_publickey(private_data.bytes, pub_data.mutableBytes);

// 打印
pub_data结果 :b1eaab369b795519fe52201f1f00cfe894d7127cf3a2f6ae1020c6759af5e852

// 3. 进行base58编码
size_t len = 100;
char base[100];
eos_encode_base58(base, &len, pub_data.bytes,32);

// 打印
Po [NSString stringWithUTF8String:base]
结果:CyWhstPxdauhZcM9xFKMWWSGAHHQkHhPtnyZsgXdhsQM

公钥的Base58编码其实就是真正注册新账户用到的Onwerkey,以及我们查询一个公钥下有哪些账户用到的也是公钥的Base58编码后的结果。

总结

  • 大家要知道Base58编码只是为了让编码的内容更方便大家来看而已,真正的签名用到的秘钥是将编码后的数据解码后的数据。

  • 要支持IOST的小伙伴需要注意,私钥Base58解码后得到的长度时64个Byte,但签名的时候只会取前32个Byte,后面32个Byte没有用到。官方也没有统一标准,所以私钥导入的钱包,最好保存用户输入的完整私钥,不要截取,否则导出的时候直接呈现给用户后用户会一脸懵逼。

  • 分层确定性不懂得小伙伴可以看一下基础知识模块内容,谢谢支持!