bitcoin源码研读(2)——比特币钱包生成地址过程

比特币钱包生成地址过程

1 前言

经学习已知比特币地址生成有三种方式:终端命令、RPC远程调用、比特币钱包,调用的核心接口是相同的,本文就以比特币钱包为入口,通过gdb单步调试的方式,了解比特币地址的生成过程。

2 文章索引

单步调试探索比特币
├── 1.《用vim单步调试比特币》
└── 2.《比特币钱包生成地址过程》

3 小知识

3.1 公钥、私钥与地址

比特币钱包中可以生成任意多个比特币地址,每个比特币地址代表一定数量的比特币。比特币地址是一个公钥通过哈希生成的,这个公钥又是由私钥通过椭圆曲线算法生成的。可以正向进行推导,反之不可。


bitcoin源码研读(2)——比特币钱包生成地址过程_第1张图片

比特币地址生成过程示意:


bitcoin源码研读(2)——比特币钱包生成地址过程_第2张图片

详见:《精通比特币》第四章密钥和地址

比特币地址是一串字母和数字的组合,功能类似于电子邮箱,收款时将其分享给发款方。

比特币地址有几种形式:

开头数字 说明
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即生成地址,当然你也可以根据需要填写可选参数
    bitcoin源码研读(2)——比特币钱包生成地址过程_第3张图片

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);
  • 进一步分析调用关系,得到下图
bitcoin源码研读(2)——比特币钱包生成地址过程_第4张图片
  • 因是通QT钱包进入,可见入口应该是在QT中的两个方法之一

试探的挑选AddressTableModel::addRow并设置断点,运气不错,正是通过这个函数进入,接下来就从这个函数开始一步步的进行探索吧~

bitcoin源码研读(2)——比特币钱包生成地址过程_第5张图片

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
bitcoin源码研读(2)——比特币钱包生成地址过程_第6张图片

接下来就从私钥生成、公钥生成、地址生成,三块分别进行代码的探索。

5.3 比特币私钥生成

(1)函数调用示意图

bitcoin源码研读(2)——比特币钱包生成地址过程_第7张图片

(2)AddressTableModel::addRow

  • 进入该函数有4个输入参数

type:为R,表示接收

label:未填写,所以为空

address:用于返回比特币地址

address_type:为地址输出类型,这里默认是隔离见证地址

bitcoin源码研读(2)——比特币钱包生成地址过程_第8张图片
  • 因Type是R,进入Receive分支

在Receive分支中调用:wallet->GetKeyFromPool(newKey),获取密钥

bitcoin源码研读(2)——比特币钱包生成地址过程_第9张图片

(3)CWallet::GetKeyFromPool

  • 单步进入GetKeyFromPool,从这个函数名,不难看出,将会从一个密钥池中取出一个密钥返回,里面调用了2个关键函数:ReserveKeyFromKeyPoolKeepKey,这两个函数也是输出地址生成过程仅有的2条日志的函数
bitcoin源码研读(2)——比特币钱包生成地址过程_第10张图片

(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;
    
bitcoin源码研读(2)——比特币钱包生成地址过程_第11张图片
(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)函数调用示意图

比特币公钥由比特币私钥生成

bitcoin源码研读(2)——比特币钱包生成地址过程_第12张图片

(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)原理图

bitcoin源码研读(2)——比特币钱包生成地址过程_第13张图片

详见:《精通比特币》第四章密钥和地址

(2)函数调用示意图

比特币地址由比特币公钥生成

bitcoin源码研读(2)——比特币钱包生成地址过程_第14张图片

在函数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

你可能感兴趣的:(bitcoin源码研读(2)——比特币钱包生成地址过程)