1. 程式人生 > >【區塊鏈】比特幣原始碼

【區塊鏈】比特幣原始碼

比特幣原始碼 - 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為係數,圖示為大致形狀 y2=x3+ax+b y^2 = x^3 + ax + b

生成公鑰

公鑰K = 私鑰k * 生成點G => 得到的公鑰K為橢圓曲線上一個點(x,y)。

根據運演算法則,二倍運算定義為:將橢圓曲線在G點的切線,與橢圓曲線的交點,交點關於x軸對稱位置的點,定義為G + G,即2G,如此重複k次則得到kG

由於根據以下公式,可以通過x計算出y,公鑰分為兩種: y2modP=(x3+7)modp 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;
}