天天看點

比特币源碼閱讀 —— 錢包位址 —— 比特币位址生成流程

一、比特币位址生成流程圖示

比特币源碼閱讀 —— 錢包位址 —— 比特币位址生成流程
比特币源碼閱讀 —— 錢包位址 —— 比特币位址生成流程

一個比特币錢包中包含一系列的密鑰對,每個密鑰對包括一個私鑰和一個公鑰。私鑰(k)是一個數字,通常是随

機選出的。有了私鑰,我們就可以使用橢圓曲線乘法這個單向加密函數産生一個公鑰(K)。有了公鑰(K),我們 就可以使用一個單向加密哈希函數生成比特币位址(A)。

二、具體步驟

1 生成私鑰

更準确地說,私鑰可以是1和n-1之間的任何數字,其中n是一個常數(n=1.158 * 10^77,略小于2^256),
并被定 義為由比特币所使用的橢圓曲線的階(見橢圓曲線密碼學解釋)。
1> 比特币核心首先 通過 OpenSSL's RNG 随機數生成器 和 作業系統 随機數生成器 生成一個随機數m。   		
2> 對m進行SHA256 哈希運算得到256位的值k
3> 對k進行驗證是否符合 secp256k1的 私鑰标準,符合則作為私鑰可以繼續,不符合傳回1>。
           

2 根據私鑰生成對應的公鑰

以上面生成的私鑰k為起點,我們将其與曲線上預定的生成點G相乘以獲得曲線上的另一點,也就是相應的
公鑰 K。生成點是secp256k1标準的一部分,比特币密鑰的生成點都是相同的: {K = k * G}
其中k 是私鑰,G是生成點,在該曲線上所得的點K是公鑰。因為所有比特币使用者的生成點是相同的,一個
私鑰k 乘以G 将得到相同的公鑰K。k和K之間的關系是固定的,但隻能單向運算,即從k得到K。這就是可以
把比特币位址 (K的衍生) 與任何人共享而不會洩露私鑰(k)的原因。
           

3 對公鑰進行處理得到比特币位址

1> 對公鑰K進行 Hash160 運算得到 K的hash。
2> 對K的hash 進行 如下操作
	1) 在頭部添加版本号。
	2) 對包含版本号的值再進行兩次SHA256運算,并将前四個位元組作為校驗和 添加到 值的尾部。
3> 對 (版本号 + K的hash + 校驗和) 整體進行Base58 編碼,得到最終的比特币位址。
           

三、對應代碼流程

1 生成私鑰

getnewaddress() -> CWallet::GetKeyFromPool() -> CWallet::GenerateNewKey() -> CKey::MakeNewKey() 
->GetStrongRandBytes() —— 該函數中 使用 OpenSSL's RNG 随機數生成器 和 作業系統 随機數生成器 得到一個
随機數(64個位元組)
           
1> 生成私鑰并檢查是否符合标準
           
void CKey::MakeNewKey(bool fCompressedIn) {
    do {
        GetStrongRandBytes(keydata.data(), keydata.size()); // 擷取随機數
    } while (!Check(keydata.data())); // 通過secp256k1 檢查私鑰是否符合标準
    fValid = true;
    fCompressed = fCompressedIn;
}
           
2> 生成随機數
           
void GetStrongRandBytes(unsigned char* out, int num)
{
    assert(num <= 32);
    CSHA512 hasher;
    unsigned char buf[64];

    // First source: OpenSSL's RNG
    RandAddSeedPerfmon();
    GetRandBytes(buf, 32);
    hasher.Write(buf, 32);

    // Second source: OS RNG
    GetOSRand(buf);
    hasher.Write(buf, 32);
    ...
}
           

2 根據私鑰生成公鑰

CPubKey CWallet::GenerateNewKey(CWalletDB &walletdb, bool internal)
{
    AssertLockHeld(cs_wallet); // mapKeyMetadata
    bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets

    CKey secret;

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

    // use HD key derivation if HD was enabled during wallet creation
    if (IsHDEnabled()) {
        DeriveNewChildKey(walletdb, metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));
    } else {
        secret.MakeNewKey(fCompressed);
    }

    // Compressed public keys were introduced in version 0.6.0
    if (fCompressed) {
        SetMinVersion(FEATURE_COMPRPUBKEY);
    }

	// 生成公鑰
    CPubKey pubkey = secret.GetPubKey();
    assert(secret.VerifyPubKey(pubkey));

    mapKeyMetadata[pubkey.GetID()] = metadata;
    UpdateTimeFirstKey(nCreationTime);

    if (!AddKeyPubKeyWithDB(walletdb, secret, pubkey)) {
        throw std::runtime_error(std::string(__func__) + ": AddKey failed");
    }
    return pubkey;
}
           
生成公鑰的過程其實是使用secp256k1 庫進行生成的
           
CPubKey CKey::GetPubKey() const {
    assert(fValid);
    secp256k1_pubkey pubkey;
    size_t clen = 65;
    CPubKey result;
    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);
	...
    return result;
}
           

3 對公鑰進行相應處理

UniValue getnewaddress(const JSONRPCRequest& request)
{
    CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
    if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
        return NullUniValue;
    }
    
    ...
    
    // Generate a new key that is added to wallet
    CPubKey newKey;
    if (!pwallet->GetKeyFromPool(newKey)) {
        throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
    }
    // 在 CPubKey::GetID 函數中對 公鑰K進行 Hash160 運算 得到keyID
    CKeyID keyID = newKey.GetID();
    pwallet->SetAddressBook(keyID, strAccount, "receive");
    return EncodeDestination(keyID);
}
           
1> GetID 函數是對公鑰進行 Hash160 運算,并傳回運算結果
           
CKeyID GetID() const {
        return CKeyID(Hash160(vch, vch + size()));
}
           
2> 在頭部添加版本号
           
std::string DestinationEncoder::operator()(const CKeyID& id) const {
        std::vector<unsigned char> data = m_params.Base58Prefix(CChainParams::PUBKEY_ADDRESS);
        data.insert(data.end(), id.begin(), id.end()); // 在頭部添加版本号
        return EncodeBase58Check(data);
}
           
3> 對添加好版本好的值進行兩次 SHA256運算,并取結果的前4個位元組添加到 值的尾部,然後進行Base58編碼
           
std::string EncodeBase58Check(const std::vector<unsigned char>& vchIn)
{
    // add 4-byte hash check to the end
    std::vector<unsigned char> vch(vchIn);
    uint256 hash = Hash(vch.begin(), vch.end());
    vch.insert(vch.end(), (unsigned char*)&hash, (unsigned char*)&hash + 4);
    return EncodeBase58(vch);
}
           

使用Base58進行編碼是為了使位址可讀,Base58其實就是 Base64 除去了幾個不太容易識别的字元,0 和 大寫的O容易混淆,1和l容易混淆等等。

參考書籍:<<精通比特币>>

*注:本文隻是個人筆記隻用,如有不妥,還望指正。