创建公钥
写在前面
创建钱包和交易是比特币最重要的两方面,涉及到很多很多的内容,远非一篇文章能概括的完。上一篇从整体上讲解了钱包的创建流程,虽然已经很详细了,但还是漏掉了几个重要的方法,从本篇开始我们讲解这几个特别重要的方法。对钱包感兴趣的朋友一定不要错误。
GenerateNewSeed 生成新的私钥/公钥
这个方法主要用来生成私钥/公钥,在生成公钥后,调用钱包对象的 SetHDSeed 方法,根据公钥生成一个 CHDChain 对象,把公钥经过 SHA256、RIPEMD160 两次哈希返回后的 20个字节 180 位字符串作为它的 seed_id 属性,从而以后可以衍生更的扩展公钥。
void CWallet::SetHDSeed(const CPubKey& seed)
{
LOCK(cs_wallet);
CHDChain newHdChain;
newHdChain.nVersion = CanSupportFeature(FEATURE_HD_SPLIT) ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE;
newHdChain.seed_id = seed.GetID();
SetHDChain(newHdChain, false);
}在创建钱包的过程中,本方法及下面的 TopUpKeyPool 方法,可能会被调用两次,一次在第一次创建钱包时,另一次在用户明确升级,且钱包支持 HD,但 HD 没启用的情况下。
下面,我们开始正式讲解这个方法。
首先,生成并调用私钥的
MakeNewKey来初始化私钥。CKey key; key.MakeNewKey(true);然后,调用
DeriveNewSeed方法,生成并返回私钥对应的公钥。return DeriveNewSeed(key);
我们先来看 MakeNewKey 这个方法。这个方法比较简单,但是非常重要,因为正是这个方法,生成了真正意义上的私钥。
GetStrongRandBytes 方法,正是生成私钥的过程。这个方法的执行逻辑如下:
生成一个 SHA512对象和一个无符号字符数组。
首先,调用
RandAddSeedPerfmon方法,通过 OpenSSL's RNG 生成随机数,并进行 SHA512 哈希。然后,调用
GetOSRand方法,通过 OS RNG 生成随机数,并进行 SHA512 哈希。最后,如果 HW 可用,通过 HW 生成随机数,并进行 SHA512 哈希。
接下来,合并并更新状态。
然后,把
buf数组中的内容拷贝到输出参数中,并清空数组中的内容。
Check 方法内部通过调用 secp256k1_ec_seckey_verify 方法,检查私钥的数据是否满足要求,后者正是椭圆曲线算法相关的。
接下来,我们来看 DeriveNewSeed 方法是如何生成公钥的。方法执行逻辑如下:
生成当前时间,并用当前时间初始化蜜钥元数据。
调用私钥的
GetPubKey方法,生成公钥。方法内部正是通过椭圆曲线算法来生成私钥对应的公钥。代码如下,有兴趣的读者可以自行研究。
执行
assert(key.VerifyPubKey(seed));方法,验证公钥确实有私钥相匹配。设置蜜钥元数据的两个属性。
GetID方法,用公钥的数据经过 SHA256、RIPEMD160 两次哈希后生成的字符串来生成一个CKeyID对象。把密钥元数据保存在
mapKeyMetadata集合中。调用
AddKeyPubKey方法,把私钥/公钥及元数据保存到数据库。如果出错,抛出一个异常。AddKeyPubKey方法生成一个访问数据库的对象,然后调用钱包对象的AddKeyPubKeyWithDB方法来保存密钥数据。后者的执行流程如下:如果变量
encrypted_batch为空,那么设置变量needsDB为真。如果后者为真,那么设置encrypted_batch属性为参数指定的对象。调用
CCryptoKeyStore::AddKeyPubKey方法,保存私钥和公钥。如果不成功,进一步,如果变量needsDB为真,则设置变量encrypted_batch为假。CCryptoKeyStore::AddKeyPubKey方法内部执行逻辑如下:如果没有加密,那么调用
CBasicKeyStore::AddKeyPubKey来保存私钥和公钥,然后返回。IsCrypted方法,初始化时是没有加密的,只有在执行加锁、解锁、添加加密密钥的情况下才会是加密的。当前情景是没有加密的,所以执行基类的方法来保存私钥和公钥。CBasicKeyStore::AddKeyPubKey方法,首先把私钥保存在mapKeys集合中,然后调用ImplicitlyLearnRelatedKeyScripts方法来处理脚本,因为公钥默认是压缩的,所以在方法内会生成脚本,并把脚本保存在mapScripts集合中。如果是锁定的,那么返回假。
IsLocked方法,首先调用IsCrypted方法,确定是否是加密的,如果不是加密的,则直接返回假;否则,把vMasterKey集合清空。接下来,生成加密私钥。
最后,调用
AddCryptedKey方法,保存加密私钥,并返回AddCryptedKey方法,首先确定是否是加密的,如果没有加密则返回假。接下来,把私钥保存在mapCryptedKeys集合中,然后调用ImplicitlyLearnRelatedKeyScripts方法来处理脚本,因为公钥默认是压缩的,所以在方法内会生成脚本,并把脚本保存在mapScripts集合中。
如果变量
needsDB为真,则设置变量encrypted_batch为空指针。调用
GetScriptForDestination方法,获得公钥对应的脚本。调用
HaveWatchOnly方法,检查setWatchOnly集合中是否有这个脚本。如果有,则调用RemoveWatchOnly方法,清除公钥对应的脚本。RemoveWatchOnly方法执行逻辑如下:调用
CCryptoKeyStore::RemoveWatchOnly方法来移除脚本。方法内部执行逻辑如下:从setWatchOnly集合中移除对应的脚本;然后,调用ExtractPubKey方法,通过脚本来解析出公钥,如果可以得到公钥,则从mapWatchKeys集合中移除对应的公钥;最后,返回真。调用数据库访问对象的
EraseWatchOnly方法,从数据库中移除watchmeta和watchs对应的数据。返回真。
调用
GetScriptForRawPubKey方法,从原始公钥获得脚本。调用
HaveWatchOnly方法,检查setWatchOnly集合中是否有这个脚本。如果有,则调用RemoveWatchOnly方法,清除公钥对应的脚本。方法执行逻辑如上所述,这里不讲。
如果不是加密的,则调用数据库的访问对象的
WriteKey方法,把私钥和公钥及密钥元数据写入数据库。在这个方法中,首先,以
keymeta为键,保存对应的元数据;然后,把公钥/私钥保存到向量中,以key为键,保存对应的公钥/私钥。最后,返回真。
后记
由于本人水平所限,文中错误在所难免,欢迎您踊跃指出错误,在下感激不尽。我的微信联系方式:joepeak。
原创不易,尤其寒冬,欢迎赞助我一杯咖啡。

比特币 
微信 
支付宝
Last updated
Was this helpful?