比特币钱包生成地址过程
1 前言
经学习已知比特币地址生成有三种方式:终端命令、RPC远程调用、比特币钱包,调用的核心接口是相同的,本文就以比特币钱包为入口,通过gdb单步调试的方式,了解比特币地址的生成过程。
2 文章索引
单步调试探索比特币
├── 1.《用vim单步调试比特币》
└── 2.《比特币钱包生成地址过程》
3 小知识
3.1 公钥、私钥与地址
比特币钱包中可以生成任意多个比特币地址,每个比特币地址代表一定数量的比特币。比特币地址是一个公钥通过哈希生成的,这个公钥又是由私钥通过椭圆曲线算法生成的。可以正向进行推导,反之不可。
比特币地址生成过程示意:
详见:《精通比特币》第四章密钥和地址
比特币地址是一串字母和数字的组合,功能类似于电子邮箱,收款时将其分享给发款方。
比特币地址有几种形式:
开头数字 | 说明 |
---|---|
1 | P2PKH(Pay-to-Public-Key-Hash )地址,最为常见也最为简单,用一对公私钥控制钱包 |
3 | P2SH(Pay-to-Script-Hash )地址,多重签名、隔离见证以及一些简单的智能合约采用 |
2、m、n | 用于比特币测试网络 |
5、K、L | 不是地址,而是WIF(Wallet Import Format )格式私钥,务必妥善保管,不可泄露 |
3.2 比特币钱包
钱包是用于发送和接收数字货币的客户端,其本质是私钥创建、保管和使用的工具。谁掌握了你个私钥,就相当于控制了你的数字货币。
4 三种比特币地址生成方式
4.1 终端命令
- 启动比特币服务端
$ ./bitcoind
- 通过命令获取地址
$ bitcoin-cli getnewaddress
2NEC6CdtHQj3iwWfXc77L7zPwqWTyb14iyZ
4.2 RPC远程调用
- 欲使用rpc方式调用,启动命令需添加参数,或者写入
~/.bitcoin/bitcoin.conf
$ ./bitcoind -rest -rpcuser=jason -rpcpassword=ruan -rpcport=28332
- 通过以下curl命令调用
$ curl --user jason:ruan --data-binary '{"jsonrpc": "1.0", "id":"curltest", "method": "getnewaddress", "params": [] }' http://127.0.0.1:28332/ | jq
{
"result": "2NF1Ugx4rYM7jB3kzb8JZ7yTfMFQ6YTih4v",
"error": null,
"id": "curltest"
}
4.3 比特币钱包
- 启动比特币钱包
$ ./bitcoin-qt
- 进入
Receive
页,直接单击Request payment
即生成地址,当然你也可以根据需要填写可选参数
5 钱包生成地址过程探索
5.1 代码入口
-
使用钱包生成地址后,看到如下日志
少的可怜的日志呀:(
[rzexin@RUAN:~/.bitcoin/regtest]$ tail -f debug.log
2018-09-27 21:01:23 keypool reserve 1
2018-09-27 21:01:23 keypool keep 1
- 通过grep源码目录,找到对应代码
[rzexin@RUAN:~/BlockChain/Bitcoin/bitcoin-0.16.2/src]$ grep -r "keypool reserve" *
wallet/wallet.cpp: LogPrintf("keypool reserve %d\n", nIndex);
[rzexin@RUAN:~/BlockChain/Bitcoin/bitcoin-0.16.2/src]$ grep -r "keypool keep" *
wallet/wallet.cpp: LogPrintf("keypool keep %d\n", nIndex);
- 进一步分析调用关系,得到下图
- 因是通QT钱包进入,可见入口应该是在QT中的两个方法之一
试探的挑选
AddressTableModel::addRow
并设置断点,运气不错,正是通过这个函数进入,接下来就从这个函数开始一步步的进行探索吧~
5.2 vimgdb调试代码
- 打开相关代码
[rzexin@RUAN:~/BlockChain/Bitcoin/bitcoin-0.16.2/src]$ vimgdb qt/addresstablemodel.cpp
- gdb file命令加载被调试可执行文件
(gdb) file qt/bitcoin-qt
Reading symbols from qt/bitcoin-qt...done.
- 函数入口设置断点
- start命令启动,keypool指定为0,以便每次调用都生成一对密钥,而不是直接使用已经生成好的密钥池里面的密钥,以方便调试观察
start --keypool=0
接下来就从私钥生成、公钥生成、地址生成,三块分别进行代码的探索。
5.3 比特币私钥生成
(1)函数调用示意图
(2)AddressTableModel::addRow
- 进入该函数有4个输入参数
type:为R,表示接收
label:未填写,所以为空
address:用于返回比特币地址
address_type:为地址输出类型,这里默认是隔离见证地址
- 因Type是R,进入Receive分支
在Receive分支中调用:
wallet->GetKeyFromPool(newKey)
,获取密钥
(3)CWallet::GetKeyFromPool
- 单步进入
GetKeyFromPool
,从这个函数名,不难看出,将会从一个密钥池中取出一个密钥返回,里面调用了2个关键函数:ReserveKeyFromKeyPool
、KeepKey
,这两个函数也是输出地址生成过程仅有的2条日志的函数
(4)CWallet::ReserveKeyFromKeyPool
该函数从密钥池中预定一个密钥,若获取失败,
nIndex
返回-1,这是直接调用GenerateNewKey
去创建密钥
void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal)
{
nIndex = -1;
keypool.vchPubKey = CPubKey();
{
LOCK(cs_wallet);
if (!IsLocked())
TopUpKeyPool();
...
省略大段代码,聚焦地址生成
...
}
}
通过IsLocked
检查钱包是否被锁,若未锁定,便调用TopUpKeyPool
。
(5)CWallet::TopUpKeyPool
启动时,把-keypool
设置为0,所以在这里默认会创建一个外部私钥,调用GenerateNewKey
来进行生成。
bool CWallet::TopUpKeyPool(unsigned int kpSize)
{
{
LOCK(cs_wallet);
if (IsLocked())
return false;
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);
...
...
bool internal = false;
CWalletDB walletdb(*dbw);
for (int64_t i = missingInternal + missingExternal; i--;)
{
...
CPubKey pubkey(GenerateNewKey(walletdb, internal));
...
}
return true;
}
(6)CWallet::GenerateNewKey
CPubKey CWallet::GenerateNewKey(CWalletDB &walletdb, bool internal)
{
AssertLockHeld(cs_wallet); // mapKeyMetadata
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
CKey secret;
// Create new metadata
int64_t nCreationTime = GetTime();
CKeyMetadata metadata(nCreationTime);
// use HD key derivation if HD was enabled during wallet creation
if (IsHDEnabled()) {
DeriveNewChildKey(walletdb, metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));
} else {
secret.MakeNewKey(fCompressed);
}
// Compressed public keys were introduced in version 0.6.0
if (fCompressed) {
SetMinVersion(FEATURE_COMPRPUBKEY);
}
CPubKey pubkey = secret.GetPubKey();
assert(secret.VerifyPubKey(pubkey));
mapKeyMetadata[pubkey.GetID()] = metadata;
UpdateTimeFirstKey(nCreationTime);
if (!AddKeyPubKeyWithDB(walletdb, secret, pubkey)) {
throw std::runtime_error(std::string(__func__) + ": AddKey failed");
}
return pubkey;
}
创建变量CKey secret;
,用于存储生成好的私钥,进入DeriveNewChildKey
:
DeriveNewChildKey(walletdb, metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));
(7)CWallet::DeriveNewChildKey
void CWallet::DeriveNewChildKey(CWalletDB &walletdb, CKeyMetadata& metadata, CKey& secret, bool internal)
{
// for now we use a fixed keypath scheme of m/0'/0'/k
CKey key; //master key seed (256bit)
CExtKey masterKey; //hd master key
CExtKey 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'/'
// try to get the master key
if (!GetKey(hdChain.masterKeyID, key))
throw std::runtime_error(std::string(__func__) + ": Master key not found");
masterKey.SetMaster(key.begin(), key.size());
// derive m/0'
// use hardened derivation (child keys >= 0x80000000 are hardened after bip32)
masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT);
// derive m/0'/0' (external chain) OR m/0'/1' (internal chain)
assert(internal ? CanSupportFeature(FEATURE_HD_SPLIT) : true);
accountKey.Derive(chainChildKey, BIP32_HARDENED_KEY_LIMIT+(internal ? 1 : 0));
// derive child key at next index, skip keys already known to the wallet
do {
// always derive hardened keys
// childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range
// example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649
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.key;
metadata.hdMasterKeyID = hdChain.masterKeyID;
// update the chain model in the database
if (!walletdb.WriteHDChain(hdChain))
throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed");
}
-
得到私钥
secret = childKey.key;
(gdb) p/x secret
$3 = (CKey &) @0x7fffffffc020: {static PRIVATE_KEY_SIZE = 0x117, static COMPRESSED_PRIVATE_KEY_SIZE = 0xd6, fValid = 0x1, fCompressed = 0x1, keydata = std::vector of length 32, capacity 32 = {0x40, 0x78, 0xcd, 0x3a, 0xdd, 0xf0, 0xb8, 0x35, 0x2e, 0x18, 0x2b, 0xed, 0x53, 0xad, 0x3f, 0x82, 0xed, 0xd1, 0x59, 0x2d, 0x94, 0x6, 0x87, 0xa7, 0xd0, 0xfc, 0x36, 0x65, 0x55, 0x9b, 0x6d, 0x6}}
5.4 比特币公钥生成
(1)函数调用示意图
比特币公钥由比特币私钥生成
(2)CKey::GetPubKey()
直接调用私钥对象的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;
}
通过begin()
获取到私钥的字节数组,传入secp256k1_ec_pubkey_create
生成公钥:
const unsigned char* begin() const { return keydata.data(); }
(gdb) p/x keydata
$35 = std::vector of length 32, capacity 32 = {0x17, 0x40, 0x8b, 0x66, 0x93, 0x2, 0x9e, 0xf8, 0x43, 0xb6, 0xdf, 0x3a, 0xbb, 0xfa, 0x79, 0x0, 0xc6, 0xf7, 0xc8, 0x98, 0x89, 0x62, 0x11, 0x7, 0x8f, 0x39, 0x83, 0x16, 0x14, 0x47, 0x2c, 0x80}
比特币使用的是一种经优化后的非标准的secp256k1椭圆曲线数字签名算法,来从私钥生成64字节长度公钥:
int ret = secp256k1_ec_pubkey_create(secp256k1_context_sign, &pubkey, begin());
int secp256k1_ec_pubkey_create(const secp256k1_context* ctx, secp256k1_pubkey *pubkey, const unsigned char *seckey);
生成的secp256k1_pubkey举例如下:
(gdb) p/x pubkey
$37 = {data = {0xa9, 0xfb, 0x11, 0x43, 0x8f, 0xa9, 0x2b, 0xd2, 0xe, 0x37, 0xf0, 0xc0, 0x7e, 0x2a, 0x37, 0x73, 0x50, 0x9e, 0xa0, 0xe7, 0xa5, 0x55, 0xdc, 0xa5, 0x49, 0x99, 0xa5, 0x68, 0x69, 0x5, 0x58, 0xda, 0xd0, 0xcd, 0x28, 0xa, 0x99, 0x11, 0x2e, 0x27, 0x76, 0x5e, 0x38, 0x19, 0x23, 0x41, 0xf4, 0x62, 0xe9, 0x41, 0x50, 0xcd, 0x2a, 0xc6, 0x1d, 0xa0, 0xd4, 0xb0, 0x9e, 0x9d, 0x84, 0x8f, 0xc9, 0x9e}}
接下来调用secp256k1_ec_pubkey_serialize
函数,实现压缩或非压缩公钥序列化值的计算,默然采用压缩算法:
secp256k1_ec_pubkey_serialize(secp256k1_context_sign, (unsigned char*)result.begin(), &clen, &pubkey, fCompressed ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED);
得到33字节长度压缩公钥:
(gdb) p/x result
$40 = {static PUBLIC_KEY_SIZE = 0x41, static COMPRESSED_PUBLIC_KEY_SIZE = 0x21, static SIGNATURE_SIZE = 0x48, static COMPACT_SIGNATURE_SIZE = 0x41, vch = {0x2, 0xda, 0x58, 0x5, 0x69, 0x68, 0xa5, 0x99, 0x49, 0xa5, 0xdc, 0x55, 0xa5, 0xe7, 0xa0, 0x9e, 0x50, 0x73, 0x37, 0x2a, 0x7e, 0xc0, 0xf0, 0x37, 0xe, 0xd2, 0x2b, 0xa9, 0x8f, 0x43, 0x11, 0xfb, 0xa9, 0x0}}
接下来就是调用CKey::VerifyPubKey
函数,来校验生成公私钥对是否能够正确进行签名和验签。
(3)CKey::VerifyPubKey
bool CKey::VerifyPubKey(const CPubKey& pubkey) const {
if (pubkey.IsCompressed() != fCompressed) {
return false;
}
unsigned char rnd[8];
std::string str = "Bitcoin key verification\n";
GetRandBytes(rnd, sizeof(rnd));
uint256 hash;
CHash256().Write((unsigned char*)str.data(), str.size()).Write(rnd, sizeof(rnd)).Finalize(hash.begin());
std::vector vchSig;
Sign(hash, vchSig);
return pubkey.Verify(hash, vchSig);
}
首先,调用GetRandBytes
,利用openssl提供的RAND_bytes
生成一个随机数。
(4)GetRandBytes
void GetRandBytes(unsigned char* buf, int num)
{
if (RAND_bytes(buf, num) != 1) {
RandFailure();
}
}
(gdb) p/x rnd
$48 = {0x32, 0xc2, 0x2e, 0xb2, 0x26, 0xd5, 0x25, 0x12}
而后,将该随机数拼接在字符串Bitcoin key verification\n
后,计算其Hash256值:
std::string str = "Bitcoin key verification\n";
CHash256().Write((unsigned char*)str.data(), str.size()).Write(rnd, sizeof(rnd)).Finalize(hash.begin());
(gdb) p/x hash
$49 = {> = {static WIDTH = 0x20, data = {0x21, 0xc0, 0x85, 0xa8, 0x88, 0x47, 0x2e, 0xf2, 0x89, 0x7e, 0xd5, 0x23, 0x3d, 0x40, 0x8c, 0x6c, 0xac, 0xee, 0x21, 0xbc, 0x6b, 0x26, 0x9f, 0x17, 0x20, 0x15, 0xad, 0x9, 0x23, 0xb0, 0x88, 0x8}}, }
而后,调用CKey::Sign
函数,利用私钥,计算此hash值的签名。
(5)CKey::Sign
bool CKey::Sign(const uint256 &hash, std::vector& vchSig, uint32_t test_case) const {
if (!fValid)
return false;
vchSig.resize(CPubKey::SIGNATURE_SIZE);
size_t nSigLen = CPubKey::SIGNATURE_SIZE;
unsigned char extra_entropy[32] = {0};
WriteLE32(extra_entropy, test_case);
secp256k1_ecdsa_signature sig;
int ret = secp256k1_ecdsa_sign(secp256k1_context_sign, &sig, hash.begin(), begin(), secp256k1_nonce_function_rfc6979, test_case ? extra_entropy : nullptr);
assert(ret);
secp256k1_ecdsa_signature_serialize_der(secp256k1_context_sign, (unsigned char*)vchSig.data(), &nSigLen, &sig);
vchSig.resize(nSigLen);
return true;
}
签名长度为72字节:
static const unsigned int SIGNATURE_SIZE = 72;
(gdb) p/x vchSig
$50 = std::vector of length 70, capacity 72 = {0x30, 0x44, 0x2, 0x20, 0x2c, 0x1d, 0x5e, 0xe6, 0xd7, 0xdd, 0x7b, 0x56, 0xca, 0xda, 0x2c, 0x32, 0xe2, 0x2f, 0xc, 0x6b, 0xe7, 0xc4, 0x16, 0x14, 0x84, 0xea, 0xe6, 0x96, 0x80, 0xb0, 0x55, 0xb1, 0x4b, 0x92, 0x10, 0x4e, 0x2, 0x20, 0x47, 0x28, 0x90, 0xd6, 0x19, 0xcf, 0xde, 0x81, 0x96, 0x9a, 0x8e, 0x94, 0xd0, 0x65, 0xf7, 0x1, 0xbb, 0x20, 0xbd, 0x84, 0x5, 0xa3, 0x7f, 0x4f, 0x3b, 0xb, 0x85, 0x83, 0x98, 0x16, 0x56, 0x40}
接下来调用CPubKey::Verify
,使用公钥对签名进行验证,以验证公钥的有效性。
(6)CPubKey::Verify
bool CPubKey::Verify(const uint256 &hash, const std::vector& vchSig) const
{
if (!IsValid())
return false;
secp256k1_pubkey pubkey;
secp256k1_ecdsa_signature sig;
if (!secp256k1_ec_pubkey_parse(secp256k1_context_verify, &pubkey, &(*this)[0], size())) {
return false;
}
if (!ecdsa_signature_parse_der_lax(secp256k1_context_verify, &sig, vchSig.data(), vchSig.size())) {
return false;
}
/* libsecp256k1's ECDSA verification requires lower-S signatures, which have
* not historically been enforced in Bitcoin, so normalize them first. */
secp256k1_ecdsa_signature_normalize(secp256k1_context_verify, &sig, &sig);
return secp256k1_ecdsa_verify(secp256k1_context_verify, &sig, hash.begin(), &pubkey);
}
5.5 比特币地址生成
(1)原理图
详见:《精通比特币》第四章密钥和地址
(2)函数调用示意图
比特币地址由比特币公钥生成
在函数AddressTableModel::addRow
中,通过下面代码,即得到比特币地址:
strAddress = EncodeDestination(GetDestinationForKey(newKey, address_type))
接下来看几个关键的函数,单纯聚焦地址生成,忽略隔离见证等。
(3)GetDestinationForKey
利用生成好的公钥,调用该函数,得到CScriptID对象,做为参数传入EncodeDestination。
CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type)
=>{
switch (type) {
case OUTPUT_TYPE_LEGACY: return key.GetID();
case OUTPUT_TYPE_P2SH_SEGWIT:
case OUTPUT_TYPE_BECH32: {
if (!key.IsCompressed()) return key.GetID();
CTxDestination witdest = WitnessV0KeyHash(key.GetID());
CScript witprog = GetScriptForDestination(witdest);
if (type == OUTPUT_TYPE_P2SH_SEGWIT) {
return CScriptID(witprog);
} else {
return witdest;
}
}
default: assert(false);
}
}
(4)EncodeDestination
std::string EncodeDestination(const CTxDestination& dest)
=>{
return boost::apply_visitor(DestinationEncoder(Params()), dest);
}
变量dest的CTxDestination类型使用的是boost的variant库,类似联合体,可以接受任意类型,具体定义如下:
/**
* A txout script template with a specific destination. It is either:
* * CNoDestination: no destination set
* * CKeyID: TX_PUBKEYHASH destination (P2PKH)
* * CScriptID: TX_SCRIPTHASH destination (P2SH)
* * WitnessV0ScriptHash: TX_WITNESS_V0_SCRIPTHASH destination (P2WSH)
* * WitnessV0KeyHash: TX_WITNESS_V0_KEYHASH destination (P2WPKH)
* * WitnessUnknown: TX_WITNESS_UNKNOWN destination (P2W???)
* A CTxDestination is the internal data type encoded in a bitcoin address
*/
typedef boost::variant CTxDestination;
使用boost::apply_visitor
访问,可以进行编译时类型检查,以更安全的方式进行访问:
return boost::apply_visitor(DestinationEncoder(Params()), dest);
(5)DestinationEncoder
通过Params()
构造DestinationEncoder
对象,通过Params()
拿到一个全局指针变量globalChainParams
,存储了区块链的全局参数:
const CChainParams &Params() {
assert(globalChainParams);
return *globalChainParams;
}
具体定义在chainparams.cpp
,还是已RegTest网络为例:
/**
* Regression test
*/
class CRegTestParams : public CChainParams {
public:
CRegTestParams() {
strNetworkID = "regtest";
consensus.nSubsidyHalvingInterval = 150;
consensus.BIP16Height = 0;
consensus.BIP34Height = 100000000;
consensus.BIP34Hash = uint256();
consensus.BIP65Height = 1351;
consensus.BIP66Height = 1251;
consensus.powLimit = uint256S("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks
consensus.nPowTargetSpacing = 10 * 60;
consensus.fPowAllowMinDifficultyBlocks = true;
consensus.fPowNoRetargeting = true;
...
}
利用DestinationEncoder重载的括号运算符,来进行地址生成:
class DestinationEncoder : public boost::static_visitor
{
private:
const CChainParams& m_params;
public:
DestinationEncoder(const CChainParams& params) : m_params(params) {}
...
...
std::string operator()(const CScriptID& id) const
{
=> std::vector data = m_params.Base58Prefix(CChainParams::SCRIPT_ADDRESS);
data.insert(data.end(), id.begin(), id.end());
return EncodeBase58Check(data);
}
...
...
};
首先从区块链全局变量globalChainParams中,取得Script地址前缀,这里取到的是196:
base58Prefixes[SCRIPT_ADDRESS] = std::vector(1,196);
然后将它放在Payload的最前面,作为地址前缀:
data.insert(data.end(), id.begin(), id.end());
举例:
- 拼接前缀前,id即Public Key Hash,长度20byte(160bit)
$20 = (const CScriptID &) @0x7fffffffc6d4: {
= { > = {static WIDTH = 0x14, data = {0x30, 0x36, 0xbd, 0x49, 0xfb, 0x32, 0x49, 0xda, 0xa2, 0xb3, 0x95, 0x57, 0x93, 0x17, 0xc8, 0x81, 0x49, 0x7a, 0x29, 0x11}}, }, }
- 增加前缀后,即增加了0xc4(196)在最前面
(gdb) p/x data
$21 = std::vector of length 21, capacity 21 = {0xc4, 0x30, 0x36, 0xbd, 0x49, 0xfb, 0x32, 0x49, 0xda, 0xa2, 0xb3, 0x95, 0x57, 0x93, 0x17, 0xc8, 0x81, 0x49, 0x7a, 0x29, 0x11}
接下来,让上面拼接后的整体,作为参数调用EncodeBase58Check
方法。
(6)EncodeBase58Check
std::string EncodeBase58Check(const std::vector& vchIn)
{
// add 4-byte hash check to the end
std::vector vch(vchIn);
uint256 hash = Hash(vch.begin(), vch.end());
=> vch.insert(vch.end(), (unsigned char*)&hash, (unsigned char*)&hash + 4);
return EncodeBase58(vch);
}
在EncodeBase58Check
方法中,先将加上版本号前缀,长度21字节的Payload进行一次Hash256运算:
uint256 hash = Hash(vch.begin(), vch.end());
(gdb) p/x hash
$24 = {> = {static WIDTH = 0x20, data = {0x98, 0x4, 0xb0, 0x64, 0x79, 0x53, 0x4e, 0x44, 0x99, 0xdc, 0x68, 0xea, 0x2, 0x7a, 0xb7, 0x4c, 0x24, 0xf0, 0xb7, 0x86, 0x3d, 0xf4, 0xc, 0xb1, 0x3e, 0xca, 0x94, 0xae, 0x82, 0xf, 0xab, 0x3e}}, }
取该hash值的前4个字节,拼接到21字节的Payload后,此时Payload长度达到25字节:
vch.insert(vch.end(), (unsigned char*)&hash, (unsigned char*)&hash + 4);
(gdb) p/x vch
$26 = std::vector of length 25, capacity 42 = {0xc4, 0x30, 0x36, 0xbd, 0x49, 0xfb, 0x32, 0x49, 0xda, 0xa2, 0xb3, 0x95, 0x57, 0x93, 0x17, 0xc8, 0x81, 0x49, 0x7a, 0x29, 0x11, 0x98, 0x4, 0xb0, 0x64}
此时Payload包括:Version(1)+Public Hash Key(20)+checksum(4)
,将该值做为参数传入EncodeBase58
方法进行Base58编码。
编码后,即得到比特币地址:
(gdb) p strAddress
$27 = "2Mwe9zrQkZ5aJ8yJHtotEEPkuJbPh9jGXYo"
6 后记
本文初浅的分析了下,比特币地址的生成过程,先生成私钥,通过私钥利用椭圆曲线签名算法生成公钥,再由公钥的Hash,通过添加版本和校验字段后,利用base58算法生成最终的比特币地址。其中涉及了很多自己还不懂的地方,如:隔离见证、分层确定性钱包等,还待进一步学习和掌握。
区块链研习社源码研读班第五期-rzexin