六、初始化和啟動模塊(5)

(十一)繼續(xù)看bitcoind.cpp中的142-146行

 if (!AppInitSanityChecks())
    {
      //InitError將被調用,并有詳細的錯誤,最終將在控制臺結束
      exit(EXIT_FAILURE);
     }

這個部分最重要的是AppInitSanityChecks()函數。對這個函數名的定義中我找出了幾個關鍵詞:“appinit”,“sanity”和“checks”,我猜測這個函數是關于初始化健全性檢查的。那么就來分析這個函數。
AppInitSanityChecks()函數的聲明在init.h的第45行:

/**
*初始化健全性檢查:ecc初始化、健全性檢查、目錄鎖檢查。
*注意:這可以在daemonization之前完成。如果此函數失敗,請不要調用Shutdown()。 
*前提:參數解析和配置文件應該已經被讀過,AppInitParameterInteraction()函數應該已經被調用過。
*/
bool AppInitSanityChecks();

該函數的實現(xiàn)在init.cpp中1190-1209,其中的有效代碼只有8行:

bool AppInitSanityChecks()
{
    // *************第四步:健全性檢查

    // 初始化橢圓曲線密碼(ECC)
    std::string sha256_algo = SHA256AutoDetect();
    LogPrintf("Using the '%s' SHA256 implementation\n", sha256_algo);
    RandomInit();
    ECC_Start();
    globalVerifyHandle.reset(new ECCVerifyHandle());

    // 健全性檢查
    if (!InitSanityCheck())
        return InitError(strprintf(_("Initialization sanity check failed. %s is shutting down."), _(PACKAGE_NAME)));

    // 如果可能,探測數據目錄鎖以提供早期錯誤消息。
    // 我們不能將數據目錄鎖定在這里,因為deamon()尚未調用forking指令,
    // 并且fork指令將會對它產生一個奇怪的行為。
    return LockDataDirectory(true);
}

由此可以看出,這個函數完成的是初始化的第四步,在該函數中主要完成了三個內容:

  1. 初始化橢圓曲線密碼(ECC);
  2. 健全性檢查;
  3. 目錄鎖檢查。

下面對這三個內容分別說明:

1. 初始化橢圓曲線密碼(ECC)

對于橢圓曲線密碼的概念想必對比特幣的加密知識了解的都很清楚:比特幣使用橢圓曲線算法生成公鑰和私鑰,選擇的是secp256k1曲線。所以在此也再強調一點:比特幣中的橢圓曲線算法只在生成公鑰和私鑰的時候才用到,其他大部分的加密都是用SHA256算法。橢圓曲線算法牽涉到一些密碼學知識和許多數學原理,感興趣的可以參考:

https://www.cnblogs.com/Kalafinaian/p/7392505.html

那么比特幣中應用的是現(xiàn)有的SHA256算法和橢圓曲線加密算法,那么我們對這兩種算法在源碼中的定義和實現(xiàn)邏輯不詳細分析,感興趣的可以在網絡上自行更深入的了解,我們只是對應用它們之后的效果進行分析。
回到AppInitSanityChecks()函數的第一部分:

std::string sha256_algo = SHA256AutoDetect();
    LogPrintf("Using the '%s' SHA256 implementation\n", sha256_algo);
    RandomInit();
    ECC_Start();
    globalVerifyHandle.reset(new ECCVerifyHandle());

這個部分調用了5個函數:
(1)SHA256AutoDetect():該函數是SHA256算法相關的函數,函數聲明在sha256.h中32行,函數實現(xiàn)在sha256.cpp的179-192行。由聲明可以知道該函數的功能是:

//自動選擇最佳有效的SHA256實現(xiàn)。
//返回實現(xiàn)的名稱。

即這個函數選擇了最合適的實現(xiàn)SHA256的方式,并在日志中打印該消息。
(2)RandomInit():該函數聲明在random.h的第144行,實現(xiàn)在random.cpp中的464-467行。由聲明知道該函數是:

初始化RNG

那么RNG是什么呢?通過查找資料知道RNG(The Random Number Generator)是隨機數發(fā)生器,它是被嵌入到計算機硬件中的隨機數生成器,生成的是偽隨機數。對于普通用戶使用的筆記本和臺式機來說是本身就含有的一部分,和有沒有比特幣客戶端無關,這里可以理解成比特幣客戶端需要用到這個隨機數生成器,給他在客戶端初始化了,方便軟件的使用。我們讀過《精通比特幣》這本書都知道:在生成私鑰的時候,我們是需要有一個256位的隨機數的,而比特幣軟件使用操作系統(tǒng)底層的隨機數生成器來產生256位的熵(隨機性)。那么就明白這個函數對私鑰生成的重要作用了。
(3)ECC_Start():該函數的聲明在key.h的183行,實現(xiàn)在key.cpp中291-306行。由它的聲明知道該函數的功能:

初始化橢圓曲線支持。如果在調用了第一次之后不首先調用ECC_Stop,就不能第二次調用這個函數。

由該函數的所在位置(key.cpp)可以知道該函數和私鑰與公鑰的操作有關。而且它的聲明文件說的很清楚,是初始化橢圓曲線支持,該函數定義中也多次出現(xiàn)了secp256k1,說明這個函數是開啟橢圓曲線算法功能。
(4)ECCVerifyHandle():該函數是ECCVerifyHandle類的一個方法,聲明在pubkey.h中的241-248行,實現(xiàn)在pubkey.cpp中282-290行。由ECCVerifyHandle類的聲明:

此模塊的用戶必須持有ECCVerifyHandle。不過,它們的構造函數和析構函數不允許并行運行。

說明該函數是實例化ECC的核實處理,這個函數在后面用來驗證基于橢圓曲線創(chuàng)建的密鑰。
(5)globalVerifyHandle.reset():這個主要是reset()函數,這個是重置函數,主要清除上面的核實處理內存。

總之,這個部分是對橢圓曲線密碼(ECC)的初始化。選擇SHA256準備、隨機數生成器準備、橢圓曲線功能開啟、驗證橢圓曲線開啟和重置內存,都是為ECC的正常運行提供的條件。

2. 健全性檢查

這個部分只有兩行代碼:

    if (!InitSanityCheck())
        return InitError(strprintf(_("Initialization sanity check failed. %s is shutting down."), _(PACKAGE_NAME)));

主要是判斷InitSanityCheck()函數。
該函數實現(xiàn)位于init.cpp的707-723行:

/** 健全性檢查
 *  確保比特幣在一個可用的環(huán)境中運行,并提供所有必要的庫支持。
*/
bool InitSanityCheck(void)
{
    if(!ECC_InitSanityCheck()) {
        InitError("Elliptic curve cryptography sanity check failure. Aborting.");
        return false;
    }

    if (!glibc_sanity_test() || !glibcxx_sanity_test())
        return false;

    if (!Random_SanityCheck()) {
        InitError("OS cryptographic RNG sanity check failure. Aborting.");
        return false;
    }

    return true;
}

該部分又有三個判斷函數,ECC_InitSanityCheck()為橢圓曲線加密結果的完整性驗證,glibc_sanity_test()glibcxx_sanity_test()驗證當前運行環(huán)境是否支持C/C++運行環(huán)境,Random_SanityCheck()驗證系統(tǒng)的隨機數生成器是否可用。
(1)ECC_InitSanityCheck():橢圓曲線加密結果驗證。
這個函數聲明在key.h中189行:

/** 驗證橢圓曲線算法的計算功能是否實時有效 */
bool ECC_InitSanityCheck(void);

它的實現(xiàn)在key.cpp中的284-289行:

bool ECC_InitSanityCheck() {
    CKey key;
    key.MakeNewKey(true);
    CPubKey pubkey = key.GetPubKey();
    return key.VerifyPubKey(pubkey);
}

現(xiàn)在我們對這段代碼進行分析:
CKey
這個類在key.h中第35行定義,對這個類解釋為:一個封裝的私鑰。則可以知道key為私鑰對象,它是一個無符號字符串類型的數組,在CKey的構造函數中對該參數進行了初始化,定義其字節(jié)大小為32字節(jié),并且是必須為32字節(jié)。
這個類中還有兩個變量:fValidfCompressed
fValid:參數用于表示私鑰是否有效,該參數是在私鑰值發(fā)生變化時進行相應修改,即私鑰值有效時,其為true,反之則為false。
fCompressed:參數代表的是公鑰是否為壓縮公鑰,true為壓縮公鑰,false為非壓縮公鑰。
MakeNewKey()函數:
該函數在key.h中98行聲明:

    //使用加密PRNG(偽隨機數)來生成私鑰

    void MakeNewKey(bool fCompressed);

該函數的實現(xiàn)在key.cpp的126-132行:

void CKey::MakeNewKey(bool fCompressedIn) {
    do {
        GetStrongRandBytes(keydata.data(), keydata.size());
    } while (!Check(keydata.data()));
    fValid = true;
    fCompressed = fCompressedIn;
}

可以看出,在MakeNewKey()函數中是通過GetStrongRandBytes()函數循環(huán)獲取私鑰,直到獲取的私鑰滿足Check()函數驗證條件時才停止。
其實MakeNewKey()函數的作用可以這樣理解:我們在前面知道了私鑰是機器內部的隨機數生成器生成的,但是也不是任意生成的隨機數就能滿足成為私鑰的條件,還是要規(guī)定能成為私鑰的條件的,MakeNewKey()函數就是找到符合要求的私鑰的。其中GetStrongRandBytes()函數是生成一個未驗證的私鑰的,而這個過程也不簡單:

a. 首先會通過Open SSL的RNG獲取隨機數;
b. 然后通過操作系統(tǒng)的RNG獲取隨機數;
c. 再然后獲得私鑰的哈希值;
d. 最后清除隨機數的內存。

Check()函數就是檢查該隨機數是否滿足要求的。其實在Check()函數的有個函數secp256k1_ec_seckey_verify()就是通過調用libsecp256k1庫實現(xiàn)隨機數的驗證的:如果返回值如果為1,密鑰有效;如果為0則無效。傳入的兩個參數ctx與seckey均不能為NULL,都需要有值。
獲得私鑰并檢查符合作為私鑰的條件后,將私鑰有效性標志fValid設置為true,同時將傳入的fCompressedIn值賦值給fCompressed,用于標識是否使用壓縮公鑰。
CPubKey類:
CKey類相似,這個就是公鑰類,pubkey就是公鑰對象。chHeader值為2或3,為壓縮公鑰,長度為33;chHeader值為4或6或7,為非壓縮公鑰,長度為65;都不是時其長度為空,也可說明該公鑰值無效。
key.GetPubKey()函數:
那么由私鑰怎么怎么到公鑰呢?其實就是用這個函數來實現(xiàn)。這個函數在key.cpp的147-158行,其實包括很多加密相關函數的調用,主要調用了secp256k1_ec_pubkey_create()函數創(chuàng)建公鑰值,然后調用secp256k1_ec_pubkey_serialize()函數實現(xiàn)壓縮或非壓縮公鑰序列值的計算。

注意
我們都知道橢圓曲線這種非對稱加密方式,使比特幣中的私鑰得到公鑰是很簡單的,但是要想從公鑰反推出私鑰基本是不可能的,在這里就是用這兩個橢圓曲線算法函數來實現(xiàn)這個特點。

VerifyPubKey()函數:
這個是驗證公鑰的函數,這個函數在key.h的134行聲明,在key.cpp中的175-187行實現(xiàn)。由它的聲明中的注釋可以知道,這個函數是用來徹底檢查私鑰和公鑰的匹配,這個過程是用另一個完全不同的機制來完成的。我們知道橢圓曲線加密方式的不可逆性,我們得到公鑰的機制就用了這個加密算法,那么驗證時就不能再根據這個公鑰反推出私鑰和私鑰比較來驗證,那么在比特幣中是用什么機制來驗證的呢?其實可以把它的驗證機制看作是模擬交易的簽名檢驗機制:

a. 通過OpenSSL的GetRandBytes()函數實現(xiàn)隨機數的生成;
b. 根據"Bitcoin key verification\n"字符串與剛生成的隨機數共同作用計算隨機哈希值;
c. 在Sign函數通過該隨機哈希值基于ECDSA算法實現(xiàn)簽名值的計算;
d. 利用該簽名信息驗證獲取的公鑰的有效性,驗證函數位于CPubKey的Verify()函數中。

(2)glibc_sanity_test()glibcxx_sanity_test():C與C++運行環(huán)境驗證。
這兩個函數主要是為了驗證運行環(huán)境中的C/C++運行庫的有效性,即比特幣核心軟件能否在當前環(huán)境中正常運行,其所需的運行庫是否能夠支撐比特幣核心的正常運行。
(3)Random_SanityCheck()函數:驗證系統(tǒng)的隨機數生成器。
該函數的聲明在random.h的第141行:

/**檢查操作系統(tǒng)的隨機性是否可用,
 * 并返回所請求的字節(jié)數。
 */
bool Random_SanityCheck();

該函數的實現(xiàn)在random.cpp的411-453行,由實現(xiàn)函數代碼中的注釋我們可以知道:

這并不能度量隨機性的質量,但它確實測試了OSRandom()在給定最大嘗試次數的情況下覆蓋了輸出的所有32個字節(jié)。

????????????????????????
對于這部分的源碼我有了點困惑
首先我知道了RNG是隨機數生成器,猜測這個函數是檢測生成的隨機數是否可用的。那么如果是這樣的話為什么不把它放在生成私鑰之前,即ECC_InitSanityCheck()函數的前面?像源碼中那樣把該驗證放在公鑰都已經生成了之后,那么如果檢測到的結果不符合條件,那么按理說私鑰和公鑰都有問題,那樣私鑰和公鑰生成和檢測的工作算是白做了,何不把它放在私鑰和公鑰生成之前呢?
然后我就懷疑我對這個函數的理解不對,但注釋中對它的解釋不夠全面,代碼有些又看不太明白,就先把這個問題留著,等詢問大牛和在網上查找相關問題后再補上!
????????????????????????

3. 目錄鎖檢查

這個是AppInitSanityChecks()函數的最后一步,通過調用LockDataDirectory()函數來完成,其中LockDataDirectory()函數實現(xiàn)位于init.cpp的1167-1188行。該函數主要是確保只有一個比特幣進程正在使用數據目錄,因為要證數據目錄在同一臺機器中僅被一個比特幣核心核心程序所使用,否則如果多個比特幣核心程序同時使用同一數據目錄,將會造成該程序數據內容產生不一致的情況。
在LockDataDirectory中,首先獲取數據目錄值,然后打開數據目錄下的.lock文件,判斷其是否存在。該文件在ubuntu中的$HOME/.bitcoin/文件夾下是存在的,其內容為空。.lock文件的作用是:該文件將通過lock.try_lock()被鎖定,但是如果已被其它先啟動的比特幣程序鎖定了的話,本次鎖定將失效,同時提示錯誤信息,并返回false,整個程序將退出。

********************************************
第四步總結:
這一步在函數AppInitSanityChecks()中實現(xiàn),主要分為三個部分:首先是初始化橢圓曲線密碼(ECC),為一系列的準備工作——選擇SHA256準備、隨機數生成器準備、橢圓曲線功能開啟、驗證橢圓曲線開啟和重置內存,都是為了下一步做準備工作;然后是InitSanityCheck()函數進行的健全性檢查——包括橢圓曲線加密結果的完整性驗證、驗證當前運行環(huán)境是否支持C/C++運行環(huán)境和驗證系統(tǒng)的隨機數生成器是否可用,是此步驟的核心;最后是AppInitSanityChecks()函數控制的目錄鎖檢查——確保只有一個比特幣進程正在使用數據目錄,也是確保只有一個bitcoind運行。
********************************************


(十二)繼續(xù)看bitcoind.cpp中的147-161行

if (gArgs.GetBoolArg("-daemon", false))
        {
#if HAVE_DECL_DAEMON
            fprintf(stdout, "Bitcoin server starting\n");

            // 后臺運行
            if (daemon(1, 0)) { // don't chdir (1), do close FDs (0)
                fprintf(stderr, "Error: daemon() failed: %s\n", strerror(errno));
                return false;
            }
#else
            fprintf(stderr, "Error: -daemon is not supported on this operating system\n");
            return false;
#endif // HAVE_DECL_DAEMON
        }

大概可以知道這一部分主要是-deamon參數的解析,該參數應用于比特幣核心的bitcond.exe控制臺程序,該程序為比特幣核心的后臺服務程序。
這段代碼的實現(xiàn)流程是:

①首先if條件語句判斷是否在啟動時設置了守護進程 -daemon參數,如果設置了則該參數使用設置的代碼,并且繼續(xù)下面的代碼;否則返回false。
②用條件編譯判斷是否包含HAVE_DECL_DAEMON宏定義,包含則繼續(xù)下面的代碼;不包含則將在控制臺中輸出當前系統(tǒng)不支持守護進程的錯誤提示。

HAVE_DECL_DAEMON宏定義在未經編譯的源碼中是不包含的,需經過./configure配置后才會出現(xiàn),該定義位于config/bitcoin-config.h的106行,默認為1,表示定義了daemon;如果為0則為不定義daemon。

③如果包含HAVE_DECL_DAEMON宏定義,且該值為1,則在控制臺中輸出"Bitcoin server starting"信息,表明比特幣后臺守護進程在運行。
④然后再判斷daemon(1, 0)函數的返回值,如果返回值為非零,則輸出errorno對應的錯誤提示,并返回false,程序退出;如果daemon()函數返回為0時則正常運行。

daemon()函數是成功時返回0,否則返回-1并設置errno。daemon()函數的用法可以參考:http://www.cppblog.com/cjz/archive/2011/12/29/163123.html

daemon(1, 0)可知:當前設置的參數為1和0,根據其注釋我們可以得知,該守護進程將當前目錄設為根目錄,即程序中的相對目錄是從該目錄開始的,第二個參數設置為0則表示不輸出任何信息。

********************************************

-deamon參數設置總結:

該參數應用于比特幣核心的bitcond.exe控制臺程序,該程序為比特幣核心的后臺服務程序。一般在啟動時會設置此參數,表示啟動bitcoin的后臺守護進程,但是如果判斷沒有在啟動時設置此參數會報錯,提示當前系統(tǒng)不支持守護進程的錯誤消息。并且就算是啟動了該參數,如果設置的參數不對,也會報錯。總之就是檢測在程序啟動時是否有-deamon參數并是否是正確設置的值,保證程序正常啟動后臺服務。
********************************************


(十三)繼續(xù)看bitcoind.cpp中的163-167行

 // 在守護進程后鎖定數據目錄。
        if (!AppInitLockDataDirectory())
        {
            // 如果鎖定數據目錄失敗,立即退出
            exit(EXIT_FAILURE);
        }

對于這部分我們可以在注釋中了解它主要是完成鎖定數據目錄的。這部分主要調用了AppInitLockDataDirectory()函數,該函數的聲明在init.h的51行:

/**
 * 鎖定比特幣核心數據目錄。
 * 注意: 這只能在守護進程之后完成。 如果此功能失敗,請勿調用Shutdown()。
 * 前提: 應該解析參數并讀取配置文件,應該調用過AppInitSanityChecks。
 */
bool AppInitLockDataDirectory();

從該注釋中我們知道這個步驟只能在守護進程完成之后,而且要是調用過AppInitSanityChecks()函數之后。AppInitSanityChecks()函數我們前面已經學習過了,這個是目標鎖檢查函數,其中它主要是調用LockDataDirectory() 函數來完成的。我們再看看它的實現(xiàn)文件,在init.cpp中的1211-1221行:

bool AppInitLockDataDirectory()
{
    // 在守護進程之后,再次獲取數據目錄鎖定并保持它直到退出;
    // 這為競爭條件創(chuàng)造了一個小小的窗口,但是這個條件是無害的:它最多會讓我們退出而不打印消息給控制臺。
    if (!LockDataDirectory(false)) {
        // 在LockDataDirectory內部打印詳細的錯誤
        return false;
    }
    return true;
}

不出所料,該函數的實現(xiàn)真的調用了LockDataDirectory() 函數,該函數實現(xiàn)位于init.cpp的1167-1188行,它是目錄鎖檢查的主要實現(xiàn)函數,主要是確保只有一個bitcoind運行。這里是再次獲取數據目錄鎖定并一直保持它目錄鎖鎖定狀態(tài),直到程序的退出。


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 一、快速術語檢索 比特幣地址:(例如:1DSrfJdB2AnWaFNgSbv3MZC2m74996JafV)由一串...
    不如假如閱讀 16,566評論 4 87
  • 在比特幣中,經常出現(xiàn)三個詞:私鑰,公鑰和地址。他們是什么意思呢?他們之間又有什么樣的關系呢?搞清楚他們之間的關系和...
    0xSen閱讀 45,947評論 3 48
  • 前些日子買了一些花草,我將它輕輕放在陽光下的窗臺。新發(fā)的綠色讓我欣喜,但枯萎的漸漸枯萎。綠色的歸宿是枯萎,那枯萎的...
    199x20xx閱讀 335評論 0 0
  • npm config list查看可配置項,npm設置代理 proxynpm config set proxy="...
    LongfeiSong閱讀 988評論 0 0
  • 這深秋的雪啊 是透明的 夾雜著些許淚 落到地上 身上 這深秋的天哪 冷的瘆人 像穿堂的風 掠過...
    一顆半糖閱讀 400評論 9 8

友情鏈接更多精彩內容