主页 > imtoken海外版 > 科普|从零开始学比特币开发:生成地址
科普|从零开始学比特币开发:生成地址
生成地址
如果有人要给你发比特币,或者你从别人那里买了几个比特币,你就得把地址给对方,这样对方才能把币发到你指定的地址。 那么,我们怎样才能有地址呢? 下面就来谈谈这个问题。
Bitcoin core提供了很多RPC供客户端调用,其中之一就是我们这里要说的getnewaddress,生成新地址。 通过这个RPC,我们可以生成一个新的地址。 有了这个地址,别人就可以给我们转账了。
getnewaddress RPC可以接收两个参数,第一个是地址的标签,第二个是地址的类型。 如果未提供标签,则默认标签为空。 目前支持的地址类型:legacy、p2sh-segwit、bech32。 默认类型由-addresstype 参数指定,目前是p2sh-segwit。
如果我们想查看这个RPC的帮助文档,可以执行如下命令:
./src/bitcoin-cli -regtest help getnewaddress
会显示帮助信息
这个RPC对应的方法实现位于src/wallet/rpcwallet.cpp文件中,方法名就是RPC名。 下面我们来看看这个方法。
生成地址过程
根据请求参数获取对应的钱包。
std::shared_ptrconst wallet = GetWalletForJSONRPCRequest(request); CWallet* const pwallet = wallet.get();
GetWalletForJSONRPCRequest 方法的内部实现如下: 接下来,确保钱包可用。 如果钱包不可用,直接 NullUniValue 对象。
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; }
如果指定了帮助参数或者请求参数个数大于2,会显示钱包的帮助信息。 检查钱包是否设置了禁止私钥,即钱包是read-only watch-only/pubkeys。 如果是,则抛出异常。
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); }
如果指定了标签,则调用 LabelFromValue 方法,检查标签以确保它不是 *。 如果是,则抛出异常。
std::string label; if (!request.params[0].isNull()) label = LabelFromValue(request.params[0]);
如果指定了地址类型btc地址怎么获取,则调用ParseOutputType方法检查地址类型,确保是legacy、p2sh-segwit、bech32中的一种,如果不指定,默认为p2sh-segwitbtc地址怎么获取,并将地址类型保存在output_type中多变的。
OutputType output_type = pwallet->m_default_address_type; if (!request.params[1].isNull()) { if (!ParseOutputType(request.params[1].get_str(), output_type)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str())); } }
如果钱包没有被锁定,调用TopUpKeyPool方法填充密钥池。
if (!pwallet->IsLocked()) { pwallet->TopUpKeyPool(); }
我们已经提到了TopUpKeyPool 填充密钥的方法。 这里只简单说明,不做详细分析。 因为在推导子键的过程中,setExternalKeyPool和setInternalKeyPool已经被完全填充,所以missingExternal和missingInternal这两个变量都为0,这样就不会再推导子键了,所以其实这里基本没有执行这个方法. 并直接返回true。 调用钱包的GetKeyFromPool方法从密钥池中生成公钥。 如果无法生成,则抛出异常。
CPubKey newKey; if (!pwallet->GetKeyFromPool(newKey)) { throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); }
GetKeyFromPool 方法,我们在下面详细解释,这里略过。 调用钱包对象的LearnRelatedScripts方法处理公钥的脚本。 该方法内部执行过程如下:如果公钥是压缩的,地址类型是p2sh-segwit,或者bech32,那么:
CTxDestination witdest = WitnessV0KeyHash(key.GetID());
CScript witprog = GetScriptForDestination(witdest);
bool CWallet::AddCScript(const CScript& redeemScript) { if (!CCryptoKeyStore::AddCScript(redeemScript)) return false; return WalletBatch(*database).WriteCScript(Hash160(redeemScript), redeemScript); }
调用 GetDestinationForKey 方法获取目标 CTxDestination 对象。 CTxDestination 是具有特定目的地的交易输出脚本模板。 定义如下: typedef boost::variant CTxDestination,可以是以下类型之一: GetDestinationForKey方法,使用case表达式根据不同的地址类型生成不同的目标CTxDestination。
if (!key.IsCompressed()) return key.GetID();
CTxDestination witdest = WitnessV0KeyHash(key.GetID()); CScript witprog = GetScriptForDestination(witdest); if (type == OutputType::P2SH_SEGWIT) { return CScriptID(witprog); } else { return witdest; }
调用钱包对象的SetAddressBook方法保存公钥地址。
pwallet->SetAddressBook(dest, label, "receive");
SetAddressBook 方法执行如下:
std::map::iterator mi = mapAddressBook.find(address);
fUpdated = mi != mapAddressBook.end();
mapAddressBook[address].name = strName;
if (!strPurpose.empty()) mapAddressBook[address].purpose = strPurpose;
if (!strPurpose.empty() && !WalletBatch(*database).WritePurpose(EncodeDestination(address), strPurpose)) return false;
WalletBatch(*database).WriteName(EncodeDestination(address), strName);
调用 EncodeDestination 方法解码目标地址并返回结果。 EncodeDestination 方法也使用访问者模式 return boost::apply_visitor(DestinationEncoder(Params()), dest)。 DestinationEncoder类继承boost::static_visitor,实现visitor模式,通过重载()运算符定义不同类型的id。 与前面对应,该方法会处理CKeyID、CScriptID、WitnessV0KeyHash、WitnessV0ScriptHash、WitnessUnknown。 对于我们默认的情况,目标地址类型是CScriptID,我们看看如何处理这种情况,其他情况可以自行阅读。
std::vectordata = m_params.Base58Prefix(CChainParams::SCRIPT_ADDRESS);
data.insert(data.end(), id.begin(), id.end());
return EncodeBase58Check(data);
std::vectorvch(vchIn); uint256 hash = Hash(vch.begin(), vch.end()); vch.insert(vch.end(), (unsigned char*)&hash, (unsigned char*)&hash + 4); return EncodeBase58(vch);
GetKeyFromPool 从密钥池中获取公钥
此方法从密钥池中生成公钥。 第一个参数是对公钥的引用,第二个参数是internal,默认为false。
内部逻辑如下:
如果钱包禁止私钥,则返回 false。
if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { return false; }
调用 ReserveKeyFromKeyPool 方法从密钥池中取出一个密钥,得到它的公钥。 如果不成功,生成一个数据库访问对象,然后调用GenerateNewKey方法生成公钥。
if (!ReserveKeyFromKeyPool(nIndex, keypool, internal)) { if (IsLocked()) return false; WalletBatch batch(*database); result = GenerateNewKey(batch, internal); return true; }
在创建钱包的过程中,我们已经关注到了GenerateNewKey方法。 我们不会在这里废话。 让我们关注 ReserveKeyFromKeyPool 方法。 该方法的执行流程如下:
nIndex = -1; keypool.vchPubKey = CPubKey();
if (!IsLocked()) TopUpKeyPool();
bool fReturningInternal = IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT) && fRequestedInternal;
bool use_split_keypool = set_pre_split_keypool.empty();
std::set& setKeyPool = use_split_keypool ? (fReturningInternal ? setInternalKeyPool : setExternalKeyPool) : set_pre_split_keypool;
if (setKeyPool.empty()) { return false; }
WalletBatch batch(*database);
auto it = setKeyPool.begin(); nIndex = *it; setKeyPool.erase(it);
if (!batch.ReadPool(nIndex, keypool)) { throw std::runtime_error(std::string(__func__) + ": read failed"); }
if (!HaveKey(keypool.vchPubKey.GetID())) { throw std::runtime_error(std::string(__func__) + ": unknown key in key pool"); }
if (use_split_keypool && keypool.fInternal != fReturningInternal) { throw std::runtime_error(std::string(__func__) + ": keypool entry misclassified"); }
if (!keypool.vchPubKey.IsValid()) { throw std::runtime_error(std::string(__func__) + ": keypool entry invalid"); }
m_pool_key_to_index.erase(keypool.vchPubKey.GetID());
调用KeepKey从密钥池中获取对应的公钥。