比特币源码学习-钱包标准(一)-BIP32

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/m0_37847176/article/details/82011876

钱包分类

钱包,参考书籍《精通比特币(第二版)》第五章钱包
钱包的一些标准有:

助记码,基于 BIP-39
HD 钱包,基于 BIP-32
多用途 HD 钱包结构,基于 BIP-43
多币种和多帐户钱包,基于 BIP-44

比特币钱包只含有密钥而不是钱币,每个用户有包含多个密钥的钱包。有两种主要类型非确定性钱包(其中每个密钥都是从随 机数独立生成的)和确定性钱包(其中所有的密钥都是从一个主 密钥派生出来,这个主密钥即为种子(seed))。
1.非确定性钱包
这里写图片描述
2.确定性(种子)钱包
这里写图片描述
3.分层确定性钱包(HD Wallets (BIP-32/BIP-44))
确定性钱包的最高级 形式是通过 BIP0032 标准定义的 HD 钱包。
这里写图片描述
结合代码部分来看这个标准

BIP 32

https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
比特币改进协议32 https://blog.csdn.net/pony_maggie/article/details/76178228

The specification consists of two parts. In a first part, a system for deriving a tree of keypairs from a single seed is presented. The second part demonstrates how to build a wallet structure on top of such a tree.
该规范由两部分组成。 在第一部分中,提出了一种用于从单个种子导出密钥对树的系统。 第二部分演示了如何在这样的树顶部构建钱包结构。

part I 密钥派生

这里写图片描述
在下面,会定义一个从父密钥派生多个子密钥的函数,为了防止这些子密钥仅依赖密钥本省,我们首先使用额外的256位熵扩展私钥和公钥,这个被称为链码的扩展对于相应的私钥和公钥是相同的,由32个字节组成。
我们将扩展私钥表示为(k,c),k表示普通私钥,c表示链码;扩展公钥表示为(K,c)K表示point(k)

point(p): returns the coordinate pair resulting from EC point multiplication (repeated application of the EC group operation) of the secp256k1 base point with the integer p.

每个扩展密钥有231个普通子密钥和231个硬化子密钥,这些子密钥都有一个索引。普通子密钥使用索引0-231,硬化子密钥使用231-232-1,为简化硬化子密钥的索引,用数字iH代表i+231
来看这个函数
给定父扩展密钥和索引,可以计算相应的子密钥。这个算法的实现取决于子密钥是否是一个硬化的密钥(或者,等价与i>=231),以及我们是否在讨论私钥或公钥。

Child key derivation (CKD) functions
Given a parent extended key and an index i, it is possible to compute the corresponding child extended key. The algorithm to do so depends on whether the child is a hardened key or not (or, equivalently, whether i ≥ 231), and whether we’re talking about private or public keys.
Private parent key → private child key//父私钥到子私钥
The function CKDpriv((kpar, cpar), i) → (ki, ci) computes a child extended private key from the parent extended private key:
1.Check whether i ≥ 231 (whether the child is a hardened key).
If so (hardened child): let I = HMAC-SHA512(Key = cpar, Data = 0x00 || ser256(kpar) || ser32(i)). (Note: The 0x00 pads the private key to make it 33 bytes long.)
If not (normal child): let I = HMAC-SHA512(Key = cpar, Data = serP(point(kpar)) || ser32(i)).
2.Split I into two 32-byte sequences, IL and IR.
The returned child key ki is parse256(IL) + kpar (mod n).
The returned chain code ci is IR.
3.In case parse256(IL) ≥ n or ki = 0, the resulting key is invalid, and one should proceed with the next value for i. (Note: this has probability lower than 1 in 2127.)
The HMAC-SHA512 function is specified in RFC 4231.
Public parent key → public child key//父公钥->子公钥
步骤与上述类似
Private parent key → public child key//父私钥->子公钥
The function N((k, c)) → (K, c) computes the extended public key corresponding to an extended private key (the “neutered” version, as it removes the ability to sign transactions).
The returned key K is point(k).
The returned chain code c is just the passed chain code.
To compute the public child key of a parent private key:
N(CKDpriv((kpar, cpar), i)) (works always).
CKDpub(N(kpar, cpar), i) (works only for non-hardened child keys).
Public parent key → private child key//不存在

子密钥的派生函数,这段可以结合源码来看,对应Derive函数


SetMaster

seed->masterkey,主密钥生成

Generate a seed byte sequence S of a chosen length (between 128 and 512 bits; 256 bits is advised) from a (P)RNG.
Calculate I = HMAC-SHA512(Key = “Bitcoin seed”, Data = S)
Split I into two 32-byte sequences, IL and IR.
Use parse256(IL) as master secret key, and IR as master chain code

void CExtKey::SetMaster(const unsigned char *seed, unsigned int nSeedLen) {
    static const unsigned char hashkey[] = {'B','i','t','c','o','i','n',' ','s','e','e','d'};
    unsigned char out[64];
    LockObject(out);
    CHMAC_SHA512(hashkey, sizeof(hashkey)).Write(seed, nSeedLen).Finalize(out);
    key.Set(&out[0], &out[32], true);
    memcpy(chaincode.begin(), &out[32], 32);
    UnlockObject(out);
    nDepth = 0;//表示master node
    nChild = 0;//还没有子密钥
    memset(vchFingerprint, 0, sizeof(vchFingerprint));//指纹初始化为0
}

memcpy:memcpy指的是c和c++使用的内存拷贝函数,memcpy函数的功能是从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中。
void *memcpy(void *dest, const void *src, size_t n)
memset:memset是计算机中C/C++语言初始化函数。将s所指向的某一块内存中的后n个 字节的内容全部设置为ch指定的ASCII值, 第一个值为指定的内存地址,块的大小由第三个参数指定,这个函数通常为新申请的内存做初始化工作, 其返回值为s。

首先调用CHMAC_SHA512,

HMAC-SHA-512 is the realization of the HMAC message authentication code using the SHA-512 hash function.
HMAC-SHA-512是使用SHA-512哈希函数实现HMAC消息认证码。

CHMAC_SHA512::CHMAC_SHA512(const unsigned char* key, size_t keylen)
{
    unsigned char rkey[128];
    if (keylen <= 128) {
        memcpy(rkey, key, keylen);
        memset(rkey + keylen, 0, 128 - keylen);//其余位初始化为0
    } else {
        CSHA512().Write(key, keylen).Finalize(rkey);
        memset(rkey + 64, 0, 64);
    }
//每个字符做异或操作
    for (int n = 0; n < 128; n++)
        rkey[n] ^= 0x5c;
    outer.Write(rkey, 128);//CSHA512 outer

    for (int n = 0; n < 128; n++)
        rkey[n] ^= 0x5c ^ 0x36;//抵消0x5c,等于原 rkey[n]^0x36
    inner.Write(rkey, 128);//CSHA512 inner
}

这里调用的write是CSHA512::Write,具体步骤不介绍了

//src/crypto/sha512.cpp
CSHA512& CSHA512::Write(const unsigned char* data, size_t len)
{
    const unsigned char* end = data + len;
    size_t bufsize = bytes % 128;
    if (bufsize && bufsize + len >= 128) {
        // Fill the buffer, and process it.
        memcpy(buf + bufsize, data, 128 - bufsize);
        bytes += 128 - bufsize;
        data += 128 - bufsize;
        sha512::Transform(s, buf);
        bufsize = 0;
    }
    while (end >= data + 128) {
        // Process full chunks directly from the source.
        sha512::Transform(s, data);
        data += 128;
        bytes += 128;
    }
    if (end > data) {
        // Fill the buffer with what remains.
        memcpy(buf + bufsize, data, end - data);
        bytes += end - data;
    }
    return *this;
}

Derive

Key identifiers
Extended keys can be identified by the Hash160 (RIPEMD160 after SHA256) of the serialized ECDSA public key K, ignoring the chain code. This corresponds exactly to the data used in traditional Bitcoin addresses. It is not advised to represent this data in base58 format though, as it may be interpreted as an address that way (and wallet software is not required to accept payment to the chain key itself).

The first 32 bits of the identifier are called the key fingerprint.

//key.cpp
bool CExtKey::Derive(CExtKey &out, unsigned int nChild) const {
    out.nDepth = nDepth + 1;//深度加一
    CKeyID id = key.GetPubKey().GetID();//密钥标识符,GetID函数就是取Hash160
    memcpy(&out.vchFingerprint[0], &id, 4);//标识符的前32位称为密钥指纹
    out.nChild = nChild;
    return key.Derive(out.key, out.chaincode, nChild, chaincode);
}
bool CKey::Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const {
    assert(IsValid());
    assert(IsCompressed());
    unsigned char out[64];
    LockObject(out);
    //1.Check whether i ≥ 2<sup>31 </sup>(whether the child is a hardened key).
    if ((nChild >> 31) == 0) {
        CPubKey pubkey = GetPubKey();
        assert(pubkey.begin() + 33 == pubkey.end());
        BIP32Hash(cc, nChild, *pubkey.begin(), pubkey.begin()+1, out);
    } else {
        assert(begin() + 32 == end());
        BIP32Hash(cc, nChild, 0, begin(), out);
    }
   //2. Split I into two 32-byte sequences, IL and IR.
    memcpy(ccChild.begin(), out+32, 32);
    memcpy((unsigned char*)keyChild.begin(), begin(), 32);
    //3.
    bool ret = secp256k1_ec_privkey_tweak_add(secp256k1_context_sign, (unsigned char*)keyChild.begin(), out);
    UnlockObject(out);
    keyChild.fCompressed = true;
    keyChild.fValid = ret;
    return ret;
}

序列化格式

扩展的私钥

//key.cpp
struct CExtKey {
    unsigned char nDepth;//深度
    unsigned char vchFingerprint[4];//父密钥的指纹
    unsigned int nChild;
    ChainCode chaincode;//链码
    CKey key;//私钥
    ...

4 byte: version bytes (mainnet: 0x0488B21E public, 0x0488ADE4 private; testnet: 0x043587CF public, 0x04358394 private)
1 byte: depth: 0x00 for master nodes, 0x01 for level-1 derived keys, ….
4 bytes: the fingerprint of the parent’s key (0x00000000 if master key)
4 bytes: child number. This is ser32(i) for i in xi = xpar/i, with xi the key being serialized. (0x00000000 if master key)
32 bytes: the chain code
33 bytes: the public key or private key data (serP(K) for public keys, 0x00 || ser256(k) for private keys)

这种78字节结构可以像Base58中的其他比特币数据一样进行编码,首先添加32个校验和位(从双SHA-256校验和中导出),然后转换为Base58表示。 这导致Base58编码的字符串最多112个字符。 由于版本字节的选择,Base58表示将在主网上以“xprv”或“xpub”开头,在testnet上以“tprv”或“tpub”开头。
扩展的公钥结构也是类似的。


part II 钱包结构

客户端要求具备兼容性,即使不支持所有功能。

缺省的钱包结构

HDW被组织为几个“帐户”。 帐户已编号,默认帐户(“”)为数字0.客户端不需要支持多个帐户 - 如果不支持,则仅使用默认帐户。
每个帐户由两个密钥对链组成:内部和外部链。 外部钥匙串用于生成新的公共地址,而内部钥匙串用于所有其他操作(更改地址,生成地址,……,任何不需要通信的内容)。 不支持单独的钥匙串的客户端应该使用外部钥匙链。
m / iH / 0 / k对应于从主m导出的HDW的帐号i的外链的第k个密钥对。
m / iH/ 1 / k对应于从主m导出的HDW的帐号i的内部链的第k个密钥对。

兼容性

为了符合此标准,客户端必须至少能够导入扩展的公钥或私钥,以便将其直接后代作为钱包密钥进行访问。 在规范的第二部分中提供的钱包结构(主/帐户/链/子链)仅供参考,但建议作为最小结构以便于兼容 - 即使没有单独的帐户或内部链和外部链之间的区别。 但是,实现可能会因特定需求而偏离它; 更复杂的应用程序可能需要更复杂的树结构。

猜你喜欢

转载自blog.csdn.net/m0_37847176/article/details/82011876