std数字货币(创建数字货币钱包重要方法之填充密钥池)
写在前面
创建钱包和交易是比特币最重要的两方面,涉及到很多很多的内容,远非一篇文章能概括的完。前面两篇分别从整体上讲解了钱包的创建流程和创建私钥/公钥的流程,本篇讲解填充密钥池生成 HD 钱包的过程。呕血之作,吐血推荐,对钱包感兴趣的朋友一定不要错误。
TopUpKeyPool 填充密钥池
本方法在第一次创建时会执行,在升级钱包到 HD 时,也会执行。它被用来填充密钥池 keypool。下面我们来看下方法的执行。
- 如果标志禁止私钥,则返回假。
if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { return false;}
- 如果钱包被锁定,则返回假。
if (IsLocked()) return false;
- 如果参数 kpSize 大于0,则设置变量 nTargetSize 为 kpSize;否则,设置为用户指定的值,或默认的 1000。
unsigned int nTargetSize;if (kpSize > 0) nTargetSize = kpSize;else nTargetSize = std::max(gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t) 0);
- 计算内部、外部可用的密钥数量。
int64_t missingExternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setExternalKeyPool.size(), (int64_t) 0);int64_t missingInternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setInternalKeyPool.size(), (int64_t) 0);
- 在用户不指定的情况下,并且是第一次,因为 setExternalKeyPool、setInternalKeyPool 这两个集合都为空,所以 missingExternal、missingInternal 两个变量的值都为 1000。
- 如果不支持 HD 钱包,或者不支持 HD 分割,那么设置变量 missingInternal 为0。
if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT)){ missingInternal = 0;}
- 生成访问数据库的对象。
bool internal = false;WalletBatch batch(*database);
- 进行 for (int64_t i = missingInternal + missingExternal; i--;) 循环。
- 如果 i 小于 missingInternal ,则设置变量 internal 为真。
if (i < missingInternal) { internal = true;}
- 设置当前索引。
int64_t index = ++m_max_keypool_index;
- 生成公钥对象。
CPubKey pubkey(GenerateNewKey(batch, internal));
- GenerateNewKey 方法用来生成公钥。我们来看下这个方法的执行流程。
- 生成表示创建的公钥是否为压缩的变量 fCompressed。在 0.6 版本之后的公钥默认都是压缩的。
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY);
- 创建私钥对象和密钥元数据对象。这时候私钥和密钥元数据对象都没有经过设置,还不能成为真正的私钥和密钥元数据,只有经过下面两个方法中的某一个处理之后,才变成真正可用的对象。
CKey secret;int64_t nCreationTime = GetTime();CKeyMetadata metadata(nCreationTime);
- 如果钱包支持 HD,那么调用 DeriveNewChildKey 方法来衍生子私钥,否则,调用 MakeNewKey 方法来生成私钥。
if (IsHDEnabled()) { DeriveNewChildKey(batch, metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));} else { secret.MakeNewKey(fCompressed);}
- IsHDEnabled 是通过私钥对象的 hdChain.seed_id 对象是否为空来判断的,因为在前面生成钱包私钥之后,就设置了这个方法,所以这个方法一定返回真。MakeNewKey 这个方法,我们在前面创建钱包的私钥时已经看到过,DeriveNewChildKey 这个方法我们在下面进行重点讲解,这里暂且略过。
- 如果变量 fCompressed 为真,则调用 SetMinVersion 方法,设置最小版本为 FEATURE_COMPRPUBKEY,支持压缩公钥的版本。
if (fCompressed) { SetMinVersion(FEATURE_COMPRPUBKEY);}
- 调用私钥的 GetPubKey 方法,返回对应的公钥。方法内部使用椭圆曲线算法求得公钥。具体不细讲,读者自行看代码。
CPubKey CKey::GetPubKey() const { assert(fValid); secp256k1_pubkey pubkey; size_t clen = CPubKey::PUBLIC_KEY_SIZE; 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); assert(result.size() == clen); assert(result.IsValid()); return result;}
- 把密钥元数据加入 mapKeyMetadata 集合中。
mapKeyMetadata[pubkey.GetID()] = metadata;
- 调用 UpdateTimeFirstKey 方法,更新 nTimeFirstKey 属性。
UpdateTimeFirstKey(nCreationTime);
- 调用 AddKeyPubKeyWithDB 方法,把私钥和公钥保存到数据库中。这个方法在前面已经讲过,这里再讲了。
if (!AddKeyPubKeyWithDB(batch, secret, pubkey)) { throw std::runtime_error(std::string(__func__) + ": AddKey failed");}
- 返回公钥。
- 用公钥生成一个密钥池实体 CKeyPool 对象,并调用访问钱包数据库对象的 WritePool 方法,以 pool 为键把密钥池实体对象写入数据库。
if (!batch.WritePool(index, CKeyPool(pubkey, internal))) { throw std::runtime_error(std::string(__func__) + ": writing generated key failed");}
- 如果变量 internal 为真,则把索引保存到 setInternalKeyPool 集合中,否则,保存到 setExternalKeyPool 集合中。
if (internal) { setInternalKeyPool.insert(index);} else { setExternalKeyPool.insert(index);}
- 把索引保存到 m_pool_key_to_index 集合中。
m_pool_key_to_index[pubkey.GetID()] = index;
- 返回真。
DeriveNewChildKey 衍生新的子私钥
这个方法用来衍生子密钥。方法的逻辑如下:
- 生成相关的变量。
CKey seed; //seed (256bit)CExtKey masterKey; //hd master keyCExtKey accountKey; //key at m/0'CExtKey chainChildKey; //key at m/0'/0' (external) or m/0'/1' (internal)CExtKey childKey; //key at m/0'/0'/<n>'
- 调用 GetKey 方法,根据HD 链对象中保存的公钥对象来得到对应的私钥对象。这个私钥是根私钥,也被称为种子私钥。如果获得不到,则抛出异常。
if (!GetKey(hdChain.seed_id, seed)) throw std::runtime_error(std::string(__func__) + ": seed not found");
- GetKey 方法执行流程如下:
- 如果钱包没有加密,则调用 CBasicKeyStore::GetKey 方法,返回公钥对象的私钥。
if (!IsCrypted()) { return CBasicKeyStore::GetKey(address, keyOut);}
- CBasicKeyStore::GetKey 方法直接从 mapKeys 集合中取出对应的私钥。mapKeys 集合是我们前面分析 DeriveNewSeed 这个方法的第 6 步 AddKeyPubKey 这个方法中把私钥/公钥及元数据保存到数据库过程中设置的。
- 否则,直接从加密集合 mapCryptedKeys 中取得对应的私钥。
CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);if (mi != mapCryptedKeys.end()){ const CPubKey &vchPubKey = (*mi).second.first; const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second; return DecryptKey(vMasterKey, vchCryptedSecret, vchPubKey, keyOut);}
- 如果以上两种情况都不能返回,那么只能返回假了。
- 调用主私钥(扩展私钥)的 SetSeed 方法,设置种子。此时的主私钥只是一个对象,而不能当作真正的私钥来使用,因为内部数据不存在。只有在本方法调用之后,主私钥才能真正用来衍生密钥。我们现在来看下 SetSeed 方法的逻辑:
- 首先,生成需要的变量。
static const unsigned char hashkey[] = {'B','i','t','c','o','i','n',' ','s','e','e','d'};std::vector<unsigned char, secure_allocator<unsigned char>> vout(64);
- 其次,调用 CHMAC_SHA512 方法,使用 HMAC_SHA512 算法,根据种子私钥生成长度为 512 位的字符串。
CHMAC_SHA512(hashkey, sizeof(hashkey)).Write(seed, nSeedLen).Finalize(vout.data());
- 然后,调用私钥的 Set 方法,用 512 位的字符串的左边 256 位来初始化私钥的 keydata 属性,并且设置私钥的有效标志 fValid 属性为真,压缩标志 fCompressed 为参数指定的值,默认为真。
key.Set(vout.data(), vout.data() + 32, true);
- 调用 memcpy 方法,把 512 位的字符串的右边 256 位保存为链码。
memcpy(chaincode.begin(), vout.data() + 32, 32);
- 设置主私钥的 nDepth、nChild 为 0。
nDepth = 0;nChild = 0;
- 把 vchFingerprint 重置为 0。
memset(vchFingerprint, 0, sizeof(vchFingerprint));
- 至此,主私钥终于可用了。
- 调用主私钥(扩展私钥)的 Derive 方法,开始衍生子密钥 accountKey。
masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT);
- 注意,在 bip32 之后,采用硬化衍生子密钥。我们来看下 Derive 这个方法的执行流程:
- 设置扩展私钥 out 参数的 nDepth 为 nDepth 加 1。主私钥的 nDepth 为 0,参见上面所讲。
- 获取主公钥的 CKeyID。
CKeyID id = key.GetPubKey().GetID();
- 设置置扩展私钥 out 参数的 nChild 为参数 _nChild 的值,这里为 0x80000000,10进制为 2147483648。
- 调用根私钥的 Derive 方法,开始衍生私钥。
return key.Derive(out.key, out.chaincode, _nChild, chaincode);
- 这个方法内部执行流程如下:
- 生成一个向量。
std::vector<unsigned char, secure_allocator<unsigned char>> vout(64);
- 把变量 nChild 向右移动 31位,如果所得等于 0,那么调用 GetPubKey 方法,获得私钥对应的公钥,然后调用 BIP32Hash 方法,填充变量 vout。在 BIP32Hash 方法中使用 CHMAC_SHA512 方法,根据链码参数和子密钥数量来填充 vout 变量。如果右移所得不等于0,同样调用 BIP32Hash 方法,填充变量 vout。
if ((nChild >> 31) == 0) { CPubKey pubkey = GetPubKey(); assert(pubkey.size() == CPubKey::COMPRESSED_PUBLIC_KEY_SIZE); BIP32Hash(cc, nChild, *pubkey.begin(), pubkey.begin()+1, vout.data());} else { assert(size() == 32); BIP32Hash(cc, nChild, 0, begin(), vout.data());}
- 把变量 vout 的内容拷贝到子链码中。
memcpy(ccChild.begin(), vout.data()+32, 32);
- 把私钥的内容拷贝到子私钥中。
memcpy((unsigned char*)keyChild.begin(), begin(), 32);
- 使用椭圆曲线算法真正初始化子私钥。
bool ret = secp256k1_ec_privkey_tweak_add(secp256k1_context_sign, (unsigned char*)keyChild.begin(), vout.data());
- 设置子私钥为压缩的,是否是有效的,并返回。
keyChild.fCompressed = true;keyChild.fValid = ret;return ret;
- accountKey 私钥(扩展私钥)的 Derive 方法,开始衍生子密钥 chainChildKey。方法前面刚讲过,此处略过。
accountKey.Derive(chainChildKey, BIP32_HARDENED_KEY_LIMIT+(internal ? 1 : 0));
- 接下来衍生子私钥 childKey,与上面大致相同,可自行阅读。
do { if (internal) { chainChildKey.Derive(childKey, hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT); metadata.hdKeypath = "m/0'/1'/" + std::to_string(hdChain.nInternalChainCounter) + "'"; hdChain.nInternalChainCounter++; } else { chainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT); metadata.hdKeypath = "m/0'/0'/" + std::to_string(hdChain.nExternalChainCounter) + "'"; hdChain.nExternalChainCounter++; }} while (HaveKey(childKey.key.GetPubKey().GetID()));
- 设置变量 secret 的值为子私钥的 childKey。
secret = childKey.key;
- 设置变量 metadata 的 HD 种子为当前 HD 链的的种子。
metadata.hd_seed_id = hdChain.seed_id;
- 更新 HD 链到数据库中。
if (!batch.WriteHDChain(hdChain)) throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed");
相关内容
相关资讯
-
虚拟币平台钱包(虚拟币 钱包)
虚拟货币钱包APP哪一种比较安全好用鏍规嵁銆婂叧浜庨槻鑼冧唬甯佸彂琛岃瀺璧勯闄╃殑鍏憡銆嬶紝鎴戝浗澧冨唴娌℃湁鎵瑰噯鐨勬暟瀛楄揣甯佷氦鏄撳钩鍙般€傛牴鎹垜鍥界殑鏁板瓧璐у竵鐩戠瑙勫畾锛屾姇璧勮
-
虚拟货币上币的平台 国内正规的虚拟货币交易有哪些
br/>鐏竵鍏ㄧ悆涓撲笟绔欐槸鐏竵闆嗗洟鏃椾笅鏈嶅姟浜庡叏鐞冧笓涓氫氦鏄撶敤鎴风殑鍒涙柊鏁板瓧璧勪骇鍥介檯绔欙紝鑷村姏浜庡彂鐜颁紭璐ㄧ殑鍒涙柊鏁板瓧璧勪骇鎶曡祫鏈轰細锛岀洰鍓嶆彁渚涘洓鍗佸绉嶆暟
-
虚拟币哪个平台好 虚拟币哪个平台好用
目前国内比较好的数字货币交易平台应该属于三巨头,不管从数字货币成交量资金量来讲是从网站的安全性来讲,币安、火币、OKEX这三家大平台交易所都是非常不错的
-
股指期货对冲平仓,股指期货对冲原理
现手最近一笔的成交手数开仓是指开新的多头仓位或者新的空头仓位,也就是新买进或者新卖出一定手数的股指期货合约平仓如果你已经开了多头仓位的话,就需要卖出手上的合约来进行对冲平仓
-
2016年期货双边手续费 2016期货最新手续费
但如果你有认识好的期货客户经理,那你开的户可以只在交易所收取的标准上+0.01元每手,还是黄金,你交的总手续费只需10.01元
-
比特币大牛(比特币大牛市)
在巴比特创始人长铗看来:“中本聪在密码朋克组中是一个年轻后辈(可能30岁出头),但地位十分显赫,在这个密码朋克组中,有菲利普·希默曼(PGP技术的开发者)、约翰·吉尔摩(太阳微系统公司的明星员工)、斯
实时快讯
-
半年前黄金电子货币?电子货币 金属货币
-
半年前鼓励数字货币(数字货币有哪些)
-
半年前辐射货币代码,辐射4动力装甲代码
-
半年前国际汇兑货币,国际汇兑的两种方法
-
半年前黄金储备 基础货币?基础货币和储备货币的区别
-
半年前宏观微观货币,货币的四个职能
-
半年前黄金 货币 关系(黄金货币投资)
-
半年前国际货币基金组织份额(收益好的十大货币基金)