【区块链】比特币源码 - 2 - 密钥和地址

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

比特币源码 - 2 - 密钥和地址

一、基本概念

这里摘抄一下《精通比特币》里面的描述:

比特币的所有权是通过数字密钥、比特币地址和数字签名来确立的。数字密钥实际上并不是存储在网络中,而是由用户生成并存储在一个文件或简单的数据库中,称为钱包。

每笔比特币交易都需要一个有效的签名才会被存储在区块链。只有有效的数字密钥才能产生有效的数字签名,因此拥有比特币的密钥副本就拥有了该帐户的比特币控制权。密钥是成对出现的,由一个私钥和一个公钥所组成。

非对称加密

百度百科:非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法。

简单来说,A和B都有自己的公钥和对应的私钥,公钥都是公开的,私钥自己保管。当A给B发送信息时,采用B的公钥加密,这样B能够用B的私钥进行解密,获取信息。比特币中采用这种方式:

二、密钥

接下来我们从左到右根据源代码,进行学习

1. 私钥

私钥(k)是一个数字,通常是随机选出的。

一个比特币地址中的所有资金的控制取决于相应私钥的所有权和控制权。在比特币交易中,私钥用于生成支付比特币所必需的签名以证明资金的所有权。私钥必须始终保持机密,因为一旦被泄露给第三方,相当于该私钥保护之下的比特币也拱手相让了。

数据结构:

class CKey
{
public:
    // secp256k1
    static const unsigned int PRIVATE_KEY_SIZE            = 279;
    static const unsigned int COMPRESSED_PRIVATE_KEY_SIZE = 214;
private:
    // 私钥是否有效
    bool fValid;
    // 与此私钥对应的是否为压缩公钥
    bool fCompressed;
    // 真实 byte 数据
    std::vector<unsigned char, secure_allocator<unsigned char> > keydata;
    // 被vch指向的32-byte 数据是否有效
    bool static Check(const unsigned char* vch);
 ...
};

创建私钥:

void CKey::MakeNewKey(bool fCompressedIn) {
    // 首先获得一个强的随机字节数;然后通过椭圆曲线验证私钥,直到有效为止。
    do {
        // 获得随机数,1~ 2^256,256位的二进制数,通过伪随机数发生器生成
        GetStrongRandBytes(keydata.data(), keydata.size());
    } while (!Check(keydata.data()));
    fValid = true;
    fCompressed = fCompressedIn;
    ...
}

2. 公钥

私钥经过椭圆曲线加密算法产生公钥,此过程不可逆

椭圆曲线密码学:简称ECC,是一种建立公开密钥加密的算法,也就是非对称加密。[这边需要再进行仔细学习]

一般,椭圆曲线可以用如下二元三阶方程表示:a,b为系数,图示为大致形状
y 2 = x 3 + a x + b y^2 = x^3 + ax + b

生成公钥

公钥K = 私钥k * 生成点G => 得到的公钥K为椭圆曲线上一个点(x,y)。

根据运算法则,二倍运算定义为:将椭圆曲线在G点的切线,与椭圆曲线的交点,交点关于x轴对称位置的点,定义为G + G,即2G,如此重复k次则得到kG

由于根据以下公式,可以通过x计算出y,公钥分为两种:
y 2 m o d   P = ( x 3 + 7 ) m o d   p y^2 mod\ P = (x^3 + 7) mod\ p

  • 压缩公钥:当y是偶数时为 02 + x;当y是奇数时为 03 + x
  • 非压缩:04 + x + y

数据结构:

class CPubKey
{
public:
    // secp256k1
    static constexpr unsigned int PUBLIC_KEY_SIZE             = 65;
    static constexpr unsigned int COMPRESSED_PUBLIC_KEY_SIZE  = 33;
    static constexpr unsigned int SIGNATURE_SIZE              = 72;
    static constexpr unsigned int COMPACT_SIGNATURE_SIZE      = 65;

private:

    // 该参数主要用于存储公钥值,该值为序列化的十六进制数,我们可以通过vch[0]获得公钥的长度,也就是通过该值判断其值为2和3,还是4,6,7,如果为2和3则为压缩公钥,长度为33,反之则为非压缩公钥,长度为65。
    unsigned char vch[PUBLIC_KEY_SIZE];
    ...

创建公钥:

CPubKey CKey::GetPubKey() const {
	...
    // 创建公钥值
    int ret = secp256k1_ec_pubkey_create(secp256k1_context_sign, &pubkey, begin());
    assert(ret);
    // 实现压缩或非压缩公钥序列值的计算
    secp256k1_ec_pubkey_serialize(secp256k1_context_sign, (unsigned char*)result.begin(), &clen, &pubkey, fCompressed ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED);
    ...
}

3. 公钥哈希

公钥通过双哈希SHA256 + RIPEMD160得到20字节的公钥哈希

4. 地址

公钥哈希通过Base58Check编码得到比特币地址

比特币地址 = Base58编码[ 0x00 + 公钥哈希 + SHA256(SHA256(0x00+公钥哈希))的前四位个字节 ]

首先介绍一下几种编码:

编码名称 方式
Base64 使用26个小写字母,大写字母,10个数字,两个符号(+ /)
Base58 在Base64基础上删去了数字0,大写字母O,小写字母l,大写字母I, +, /
Base58Check Base58编码(版本 +数据 + 校验)

版本号:比特币地址为0x00

校验码:版本+数据进行哈希算法,公式为 checksum = SHA256(SHA256(prefix+data)),然后取前四个字节.

5. 整体流程

CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal)
{
    ...
	// 创建CKey类型对象
    CKey secret;

    // Create new metadata
    int64_t nCreationTime = GetTime();
    CKeyMetadata metadata(nCreationTime);

    // 如果使用了分层确定性钱包HD,则使用HD key生成
    if (IsHDEnabled()) {
        DeriveNewChildKey(batch, metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));
    } else {
        // 创建私钥
        secret.MakeNewKey(fCompressed);
    }
	...
	// 创建公钥
    CPubKey pubkey = secret.GetPubKey();
    assert(secret.VerifyPubKey(pubkey));

    mapKeyMetadata[pubkey.GetID()] = metadata;
    UpdateTimeFirstKey(nCreationTime);
	// 存储到钱包数据库
    if (!AddKeyPubKeyWithDB(batch, secret, pubkey)) {
        throw std::runtime_error(std::string(__func__) + ": AddKey failed");
    }
    return pubkey;
}

猜你喜欢

转载自blog.csdn.net/loy_184548/article/details/82900112