区块链网站|NFTS 比特币地址 科普|从零开始学习比特币开发:生成地址

科普|从零开始学习比特币开发:生成地址

广告位

科普|从零开始学习比特币开发:生成地址

生成地址

如果有人要给你寄比特币,或者你从别人那里买了几个比特币,你就得把地址给对方,让对方把币寄到你指定的地址。那么,我们怎么会有地址呢?我们来谈谈这个问题。

比特币核心提供了很多RPC供客户端调用。其中之一是getnewaddress,我们在这里将要谈到的,生成一个新地址。通过这个RPC,我们可以生成一个新的地址。有了这个地址,别人就可以给我们转账了。

Getnewaddress RPC可以接收两个参数,第一个是地址的标签,第二个是地址的类型。如果未提供标签,则默认标签为空,当前支持的地址类型为:legacy、p2sh-segwit、bech32,默认类型由-addresstype参数指定,当前为p2sh-segwit。

如果我们想查看这个RPC的帮助文档,我们可以执行以下命令:/src/bit coin-CLI-regtesthelp get new address将显示帮助信息。

这个RPC对应的方法实现位于src/wallet/rpcwallet.cpp文件中,方法名为RPC名。我们来看看这个方法。

地址生成过程

根据请求的参数来执行。STD:shared _ ptrCWallet const wallet=getwalletforjsonrprequest(request);CWallet * const pwallet=wallet . get();GetWalletForJSONRPCRequest方法的内部实现如下:调用getWalletnameFromjsonrpCrequest方法从请求对象中获取钱包的名称。如果用户指定了钱包名称,则将钱包名称保存在参数wallet_name中并返回true,否则返回false。如果可以获取用户指定的Wallet名称,则调用GetWallet方法从wallet集合vpwallets中获取指定的wallet,然后返回该wallet。如果用户未指定wallet或指定的wallet不存在,则调用GetWallets方法返回wallet集合VP wallet。如果wallet集合中只有一个wallet,或者当用户指定help时至少有一个wallet,则返回第一个wallet,即默认wallet。默认钱包是在系统启动时创建的。接下来,确保钱包可用。如果wallet不可用,则直接使用NullUniValue对象。如果(!EnsureWalletIsAvailable(pwallet,request . fhelp)){ return NullUniValue;}如果指定了帮助参数或请求的参数数量超过2个,将显示wallet的帮助信息。检查钱包是否设置了禁止的私钥,即钱包为只读watch-only/pubkeys。如果是这样,将引发异常。if(pwallet-IsWalletFlagSet(WALLET _ FLAG _ DISABLE _ PRIVATE _ KEYS)){ throw JSONRPCError(RPC _ WALLET _ Error,' ERROR:此钱包禁用了私钥');}如果指定了标签,则调用LabelFromValue方法并检查标签以确保它不是*。如果是这样,将引发异常。std:字符串标签;如果(!request.params[0]。is null())label=label from value(request . params[0]);如果指定了地址类型,则调用ParseOutputType方法来检查地址类型,以确保它是legacy、p2sh-segwit和bech32之一。如果没有指定,默认为p2sh-segwit,地址类型保存在output_type变量中。output type output _ type=p wallet-m _ default _ address _ type;如果(!request.params[1]。isNull()) { if(!parse output type(request . params[1])。get_str(),output _ type)){ throw JSONRPCError(RPC _ INVALID _ ADDRESS _ OR _ KEY,strprintf('未知地址类型' %s 'request.params[1])。get _ str()));}}如果wallet没有被锁定,调用TopUpKeyPool方法来填充密钥池。如果(!pwallet-is locked()){ pwallet-TopUpKeyPool();}TopUpKeyPool就是填充键的方法,这个我们之前已经讲过了。这里就简单解释一下,不做详细分析。因为setExternalKeyPool和setInternalKeyPool在派生该子项的过程中已经被完全填充,所以missingExternal和missingInternal这两个变量都是0,所以不会再次派生该子项。所以,其实这里基本不执行这个方法,而是直接返回true。调用wallet的GetKeyFromPool方法从密钥池生成公钥。如果无法生成,则会引发异常。CPubKey newKey如果(!pwallet-GetKeyFromPool(new key)){ throw JSONRPCError(RPC _ WALLET _ KEYPOOL _ RAN _ OUT,'错误:key pool用完了,请先调用keypoolrefill}GetKeyFromPool方法,我们下面会详细解释,这里省略。调用wallet对象的LearnRelatedScripts方法来处理公钥的脚本。方法的内部执行如下:如果公钥被压缩,地址类型为p2sh-segwit或bech32,那么如果目标参数类型为CNoDestination,则调用脚本对象的脚本方法清除脚本内容。如果目标参数类型为CKeyID,那么:首先调用脚本对象的脚本方法,清除脚本内容;然后,初始化脚本* scriptop _ dupop _ hash 160 tobytevector(keyid)op _ equaliverifyop _ check SIG。如果目标参数类型为CScriptID,那么:首先调用脚本对象的脚本方法,清除脚本内容;然后,初始化脚本* scriptop _ hash 160 tobytevector(scripted)op _ equal。如果目标参数类型为WitnessV0KeyHash,那么:首先调用脚本对象的脚本方法,清除脚本内容;然后,初始化脚本*script OP_0 ToByteVector(id)。

如果目标参数类型是WitnessV0ScriptHash,则:首先调用脚本对象的脚本方法,清除脚本内容;然后,初始化脚本*脚本OP_0 ToByteVector(id)。如果目标参数类型是未知的目击证人,则:首先调用脚本对象的脚本方法,清除脚本内容;然后,初始化脚本*脚本CScript:EncodeOP _ N(id。版本)STD:vector(id。节目,id .节目id .长度)。调用WitnessV0KeyHash方法,生成WitnessV0KeyHash对象CTX目的地wit dest=见证v0密钥哈希(key .GetID());调用GetScriptForDestination方法,获取对应的脚本CScript wit Prog=GetScriptForDestination(wit dest);GetScriptForDestination方法内部调用boost:apply _ visitor(cscript visitor(script),dest),以访问者模式来根据不同的id,获取其对应的脚本对象CScriptVisitor .对象继承自升压:静态_访问者对象,实现了访问者模式,并通过重载() 操作符来定义不同类型的身份证。调用AddCScript方法,保存脚本对象AddCScript方法,首先调用CCryptoKeyStore:AddCScript方法,把脚本保存到密钥存储的地图脚本集合中;然后,调用数据库访问对象的WriteCScript方法,以cscript为键把脚本保存到数据库中bool CWallet:AddCScript(const CScript rede script){ if(!CCryptoKeyStore:AddCScript(rede脚本))返回错误的返回wallet batch(*数据库).WriteCScript(Hash160(救赎脚本),救赎脚本);}调用GetDestinationForKey方法,获取目的地CTX目的地对象CTX目的地是一个具有特定目标的交易输出脚本模板。定义如下:typedef boost:variantCNoDestination,CKeyID,CScriptID,WitnessV0ScriptHash,WitnessV0KeyHash,WitnessUnknown CTxDestination,可能是以下几种类型之一:GetDestinationForKey方法,使用情况表达式来根据不同的地址类型,生成不同的目的CTX目的地。如果公钥不是压缩的,处理方法遗产。如果(!钥匙IsCompressed())返回键. GetID();否则,生成WitnessV0KeyHash对象,然后调用GetScriptForDestination方法,获取对应的脚本,最后根据不同的地址类型生成的目的CTX目的地wit dest=见证v0密钥哈希(key .GetID());CScript wit Prog=GetScriptForDestination(wit dest);if(type==output type:P2SH _ seg wit){ return CScriptID(wit Prog);} else { return witdest}对于默认的、不传地址类型的情况,就会返回CScriptID类型的CTX目的地,这个返回值在下面两步中都会用到。如果地址类型是遗产,则直接返回公钥的KeyID。内部把公钥的数据通过SHA256和RIPEMD160双重哈希之后,构造一个CKeyID对象。如果地址类型是p2sh-segwit,或bech32,则处理如下:编码标准化没有目的地设置CKeyIDP2PKH目的CScriptIDP2SH目的WitnessV0ScriptHashP2WSH目的WitnessV0KeyHashP2WPKH目的目击者未知未知目的P2W?调用钱包对象的SetAddressBook方法,来保存公钥地址pwallet-SetAddressBook(dest,label,' receive ');SetAddressBook方法执行如下:从地图地址簿集合中,取得对应的目的数据std:mapCTxDestination,CAddressBookData:迭代器mi=mapAddressBook.find(地址);根据集合中是否有对应的数据设置变量是否为更新fUpdated=mi!=mapaddressbook。end();把标签保存为地址对应的CAddressBookData的名字属性地图地址簿[地址]。name=strName如果参数目的不空,则更新地址对应的CAddressBookData的目的属性。如果(!str目的。empty())mapAddressBook[address].目的=strPurpose调用数据库访问对象的写入目的方法,保存参数目的到数据库中。如果(!strPurpose.empty()!wallet batch(*数据库).写目的(编码的目的地(地址),strPurpose))返回错误的调用数据库访问对象的写入目的方法,保存地址到数据库中钱包批次(*数据库)。写名字(编码目的地(地址),strName);strName为用户提供的标签。

EncodeDestination方法,我们将在下一步解释它。调用EncodeDestination方法解码目标地址并返回其结果。EncodeDestination方法也采用访问者模式return boost:apply _ visitor(destination encoder(params()),dest)。DestinationEncoder类继承boost:static_visitor,实现visitor模式,通过重载()运算符定义不同类型的id。相应的,这个方法会处理CKeyID、CScriptID、WitnessV0KeyHash、WitnessV0ScriptHash、WitnessUnknown等不同的情况。对于我们的默认情况,目的地址类型是CScriptID。我们来看看这种情况的处理,其他情况可以自己看。调用当前网络参数的Base58Prefix方法,返回脚本前缀。STD:vector unsigned char data=m _ params。base 58 prefix(CChainParams:SCRIPT _ ADDRESS);主网络的前缀为5,测试网络的前缀为196,回归测试网络的前缀为196。在前缀后添加当前20字节的数据,形成一个21字节的字符串。data.insert(data.end(),id.begin(),id . end());调用EncodeBase58Check方法,将其编码为Base58Check格式,并返回其值。返回EncodeBase58Check(数据);接下来,我们来看看这个方法的处理。其内部执行流程如下:生成一个21字节字符串的向量,调用hash方法生成一个32字节的Hash字符串;然后将前四个字节添加到21字节向量的尾部作为校验,从而生成一个长度为25字节的字符串;最后,调用EncodeBase58方法对Base58进行编码。STD:vector unsigned char vch(vchIn);uint256 hash=Hash(vch.begin()、vch . end());vch.insert(vch.end(),(unsigned char*)hash,(unsigned char *)hash 4);返回encode base 58(vch);在哈希方法中,使用双SHA256哈希算法。EncodeBase58是一个读者可以自行阅读的方法,这里就不展开了。

GetKeyFromPool从密钥库中获取公钥。

此方法从密钥池中生成公钥。第一个参数是对公钥的引用,第二个参数internal默认为false。

内部逻辑如下:

如果钱包禁止私钥,则返回false。if(IsWalletFlagSet(WALLET _ FLAG _ DISABLE _ PRIVATE _ KEYS)){返回false}调用ReserveKeyFromKeyPool方法从密钥池中获取一个密钥,并获得其公钥。如果不成功,则生成数据库访问对象,然后调用GenerateNewKey方法生成公钥。如果(!ReserveKeyFromKeyPool(nIndex,KeyPool,internal)) { if (IsLocked())返回falseWalletBatch批处理(*数据库);result=GenerateNewKey(批处理,内部);返回true在创建wallet的过程中,我们已经重点介绍了}GenerateNewKey的方法,这里就不多费口舌了。让我们来关注一下ReserveKeyFromKeyPool的方法。该方法的执行流程如下:生成一个公钥,设置为密钥池的vchPubKey属性。nIndex=-1;key pool . vchpubkey=CPubKey();如果钱包没有被锁定,则密钥池被填充。如果(!is locked())TopUpKeyPool();TopUpKeyPool是我们也讲过的一个方法,这里就略过了。如果钱包启用了HD,并且可以支持HD拆分,并且参数fRequestedInternal为true,则将变量fReturningInternal设置为true。调用此方法时,不指定此参数,但默认为false,因此变量fRequestedInternal设置为false。bool fReturningInternal=IsHDEnabled()CanSupportFeature(FEATURE _ HD _ SPLIT)fRequestedInternal;根据set_pre_split_keypool是否为空,设置变量use_split_keypool的值。变量use_split_keypool为真,因为这里use_split_keypool的集合为空。bool use _ split _ key pool=set _ pre _ split _ key pool . empty();根据变量use_split_keypool和fReturningInternal,从哪个集合中确定键池对象。根据上面的分析,我们最终会从setExternalKeyPool集合中获取数据。STD:set int 64 _ t setkey pool=use _ split _ key pool?(fReturningInternal?setInternalKeyPool:setExternalKeyPool):set _ pre _ split _ key pool;如果所需数据集为空,则返回false。if (setKeyPool.empty()) {返回false}生成数据库访问对象。WalletBatch批处理(*数据库);从setKeyPool中获取其第一个元素,并将其从集合中删除。auto it=setkey pool . begin();nIndex=* itsetkey pool . erase(it);从数据库中获取索引对应的密钥池。如果失败,将引发异常。如果(!批量。ReadPool(nIndex,key pool)){ throw STD:runtime _ error(STD:string(_ _ func _ _)'read failed ');}从密钥池中获取公钥对应的ID,并检查它是否在mapKeys或mapCryptedKeys集合之一中,如果不在,则抛出异常。我们在创建wallet的过程中说过,生成的私钥会根据是否加密存储在这两个集合中的一个。如果(!have key(key pool . vchpubkey . getid()){ throw STD:runtime _ error(STD:string(_ _ func _ _)'密钥池中的未知密钥');}如果变量use_split_keypool为true,并且keystore的fInternal属性不等于变量fReturningInternal,则会引发异常。if(use _ split _ key pool key pool . fin internal!=fReturningInternal){ throw STD:runtime _ error(STD:string(_ _ func _ _)'key pool条目分类错误');}如果存储在密钥库中的公钥无效,则会引发异常。如果(!key pool . vchpubkey . is valid()){ throw STD:runtime _ error(STD:string(_ _ func _ _)'key pool条目无效');}从m_pool_key_to_index集合中删除相应的索引。m _ pool _ key _ to _ index . erase(key pool . vchpubkey . getid());回归真实。调用KeepKey从密钥池中获取相应的公钥。

作者:区小白

资料来源:巴比特

【说明】以上内容整理自互联网。如内容侵犯您的版权,请联系邮箱:law@allwin.world,我们将在24小时内删除相关内容。

广告位
本文来自网络,不代表区块链网站|NFTS立场,转载请注明出处:https://www.qklwz.com/btb/qianbao/42949.html
上一篇
下一篇

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

返回顶部