比特币源码研读之十八_SanityChecks

作者: 菜菜子_forest | 来源:发表于2017-11-26 00:37 被阅读1021次

    比特币源码研读记录已有一段时间没有更新了,最近除了工作比较忙意外,还有就是最近一直在我们研习社的千聊平台中开讲《比特币编程》系列课程,能开讲这课程源于之前的源码研读以及《精通比特币》的研读工作,让我能有一定的知识储备和心得体会,将自己学到的东西分享出来,在分享的同时也能更深入地理解之前学到的东西,可以说是做到了教与学的相互促进了。

    今天我将继续第十八篇源码研读,这篇文章我们将开始AppInitSanityChecks函数源码的研读与解析。

    本文主要涉及的源码文件包括:

    src/bitcond.cpp、src/init.h、src/init.cpp、src/util.h、src/key.h、src/key.cpp、src/random.h、src/random.cpp、src/pubkey.h、src/pubkey.cpp、src/secp256k1、src/crypto/sha512.h、src/crypto/sha512.cpp、src/compat/sanity.h、src/compat/glibc_sanity.cpp、src/compat/glibcxx_sanity.cpp

    我们可以看到在InitSanityCheck函数中只有短短的8行代码,其注释是该函数是用于比特币核心的一个可用性测试,初步验证比特币核心的功能完整性,验证运行环境中包含了所需依赖库。

    下图是可用性验证的一个具体流程:

    其中,ECC_InitSanityCheck为椭圆曲线加密结果的完整性验证,后面的glibc_sanity_test与glibcxx_sanity_test验证当前运行环境是否支持C/C++运行环境,具体验证过程,我们将在本文的后续章节中详细描述。

    本文的也将按照流程图中的3个部分进行逐一分析:

    一、椭圆曲线加密结果验证(ECCSanityCheck)

    ECC_InitSanityCheck函数执行过程如下:

    通过源码研读我们会发现ECC_InitSanityCheck函数作为全局函数定义于key.h中,并在key.cpp中得以实现。其在key.cpp中的实现代码也很简短,具体如下:

    bool ECC_InitSanityCheck() {

    CKey key;

    key.MakeNewKey(true);

    CPubKeypubkey = key.GetPubKey();

    returnkey.VerifyPubKey(pubkey);

    }

    在key.h的定义中包含了ECC_InitSanityCheck的功能注释:

    /** Check that required EC support is available atruntime. */

    验证椭圆曲线算法的计算功能是否实时有效,从其代码我们可以看出其主要是验证是否能正确计算公钥。

    (1)定义私钥对象(CKey key)

    在ECC_InitSanityCheck函数中首先定义了Ckey类型的私钥对象key。Ckey类在key.h中定义,在其类中使用keydata参数存储私钥对象,其定义如下:

    //! The actual byte data

    std::vector<char, secure_allocator<unsigned char> > keydata;

    我们可以看到其为无符号字符串类型的数组,并且在CKey的构造函数中对该参数进行了初始化,定义其字节大小为32字节,并且是必须为32字节:

    //! Construct an invalid private key.

    CKey() : fValid(false), fCompressed(false)

    {

    //Important: vch must be 32 bytes in length to not break serialization

    keydata.resize(32);

    }

    大家应该知道私钥长度为256位,256除以8的结果是32,所以,其字节为32字节是正确的。

    同时在Ckey类中还定义了2个参数fValid和fCompressed,它们的详细定义分别是:

    //! Whether this private key is valid. We check forcorrectness when modifying the key data, so fValid should always correspond tothe actual state.

    bool fValid;

    //! Whether the public key corresponding to thisprivate key is (to be) compressed.

    boolfCompressed;

    其中,

    fValid:参数用于表示私钥是否有效,该参数是在私钥值发生变化时进行相应修改,即私钥值有效时,其为true,反之则为false。

    fCompressed:参数代表的是公钥是否为压缩公钥,true为压缩公钥,false为非压缩公钥。

    (2)创建私钥(MakeNewKey)

    上面是对Ckey类的一个解析,我们接着ECC_InitSanityCheck函数代码的分析,在定义了key参数之后,执行了其MakeNewKey()函数,执行代码为:

    key.MakeNewKey(true);

    我们先来看key.h中MakeNewKey的函数定义:

    //! Generate a new private key using a cryptographicPRNG.

    void MakeNewKey(bool fCompressed);

    通过其注释我们可以看到该函数通过使用加密PRNG(伪随机数)生成私钥。

    在MakeNewKey函数中通过GetStrongRandBytes函数循环获取私钥,直到获取的私钥满足Check函数验证条件时才停止。

    我们首先来看GetStrongRandBytes函数,该函数定义于src/random.h中,并在random.cpp中实现:

    /**

    * Function togather random data from multiple sources, failing whenever any of those sourcefail to provide a result.

    */

    void GetStrongRandBytes(unsigned char* buf, int num);

    通过其注释我们可以知道,随机数是通过多渠道共同作用获得的,但获得的私钥在某个渠道出错时,生成的随机数将无效。该函数的实现过程如图所示:

    在通过GetStrongRandBytes函数获取随机数后,程序将把随机数放入Check中进行验证,其验证代码为:

    bool CKey::Check(const unsigned char *vch) {

    returnsecp256k1_ec_seckey_verify(secp256k1_context_sign, vch);

    }

    该函数实现很简单,其主要是通过调用libsecp256k1库实现随机数的验证的。libsecp256k1库的源码已经包含至比特币源码中,其源码位于src/secp256k1文件夹中。其README.md文件中的描述如下:

    通过该描述信息我们可以知道libsecp256k1为标准C动态库,其主要功能是通过ECDSA(椭圆曲线加密算法)实现签名验证、公私钥生成、公私钥添加等能力。

    此处的验证函数为secp256k1_ec_seckey_verify,该函数定义于src\secp256k1\include\secp256k1.h中,其定义如下:

    我们通过其注释与参数说明可以看到该函数的功能是验证基于椭圆曲线创建的密钥。其返回值如果为1,密钥有效;如果为0则无效。传入的两个参数ctx与seckey均不能为NULL,都需要有值。

    在找到了正确的私钥后,程序此时将私钥有效性标志fValid设置为true,同时将传入的fCompressedIn值赋值给fCompressed,用于标识是否使用压缩公钥。

    (1)私钥创建公钥(GetPubKey)

    在完成了私钥的创建后,我们可以通过私钥创建公钥了。

    其获取代码为CPubKey pubkey = key.GetPubKey();

    其中CPubKey类定义于src/pubkey.h中,实现与src/pubkey.cpp中,其包含了一个参数vch[65],该参数主要用于存储公钥值,该值为序列化的十六进制数,通过vch的注释我们可以通过vch[0]获得公钥的长度,也就是通过该值判断其值为2和3,还是4,,6,7,如果为2和3则为压缩公钥,长度为33,反之则为非压缩公钥,长度为65。其长度获取代码就在CPubKey类的GetLen函数中,实现代码如下:

    //! Compute the length of a pubkey with a given firstbyte.

    unsigned int static GetLen(unsigned char chHeader)

    {

    if (chHeader== 2 || chHeader == 3)

    return33;

    if (chHeader ==4 || chHeader == 6 || chHeader == 7)

    return65;

    return 0;

    }

    同时,我们可以看到,如果vch[0]如果不是2,3, 4,,6,7中的任何一个值的话,其长度为空,也可说明该公钥值无效。

    在完成了CPubKey的说明后,我们再来看下我们本小节关注的GetPubKey函数。通过其源代码我们发现该函数通过secp256pk1库提供的函数实现了压缩或非压缩公钥的计算。其首先通过secp256k1_ec_pubkey_create函数创建公钥值,随后通过secp256k1_ec_pubkey_serialize函数实现压缩或非压缩公钥序列值的计算。

    (2)验证公钥(VerifyPubKey)

    验证公钥代码在CKey的VerifyPubKey函数中实现。该函数的实现流程为:

    1、通过OpenSSL的GetRandBytes函数实现随机数的生成;

    2、根据"Bitcoin key verification\n"字符串与刚生成的随机数共同作用计算随机哈希值;

    3、在Sign函数通过该随机哈希值基于ECDSA算法实现签名值的计算;

    4、利用该签名信息验证获取的公钥的有效性,验证函数位于CPubKey的Verify函数中。

    二、C与C++运行环境验证

    本节我们将分析glibc_sanity_test与glibcxx_sanity_test两个函数验证的相关内容。glibc_sanity_test与glibcxx_sanity_test在src\compat\sanity.h中定义,分别在src\compat\glibc_sanity.cpp与src\compat\glibcxx_sanity.cpp中实现。通过这两个函数的实现代码我们可以了解到它们主要是为了验证运行环境中的C/C++运行库的有效性,即比特币核心软件能否在当前环境中正常运行,其所需的运行库是否能够支撑比特币核心的正常运行。

    以上就是InitSanityCheck函数的研读记录,本文主要对其验证过程与部分细节进行了分析,对于secp256k1库使用的函数未进行详细描述,对该库的详细分析与研读将在后续研读记录中进行,敬请期待!

    三、小结

    通过对InitSanityCheck函数的研读,让我对私钥、公钥、压缩或非压缩公钥、椭圆曲线加密算法有了更深入的理解,这也是我坚持读源码的原因,可以让自己从技术角度更好地理解比特币,更好地理解区块链。所以,鼓励有编程基础的,对比特币源码感兴趣的朋友一起来参加我们的比特币源码研读。参与方式见《比特币源码研读班第三期招募令》 一文。

    比特币源码研读班班长 菜菜子

    相关文章

      网友评论

      • 19de97f51ded:请问SC官网钱包怎么导入?是不是输入那句长长的英文句子就可以了?
      • powershot1:我扫码付费加入比特币源码研读班,怎么没有任何回复?

      本文标题:比特币源码研读之十八_SanityChecks

      本文链接:https://www.haomeiwen.com/subject/kgmhbxtx.html