比特幣學習 - 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爲係數,圖示爲大致形狀
生成公鑰:
公鑰K = 私鑰k * 生成點G => 得到的公鑰K爲橢圓曲線上一個點(x,y)。
根據運算法則,二倍運算定義爲:將橢圓曲線在G點的切線,與橢圓曲線的交點,交點關於x軸對稱位置的點,定義爲G + G,即2G,如此重複k次則得到kG
由於根據以下公式,可以通過x計算出y,公鑰分爲兩種:
- 壓縮公鑰:當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;
}