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

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

作者: rzexin | 来源:发表于2018-09-30 19:22 被阅读0次

    比特币钱包生成地址过程

    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个关键函数:ReserveKeyFromKeyPoolKeepKey,这两个函数也是输出地址生成过程仅有的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'/<n>'
              
        // 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 <repeats 32 times>}}

    接下来就是调用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<unsigned char> 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 = {<base_blob<256>> = {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}}, <No data fields>}

    而后,调用CKey::Sign函数,利用私钥,计算此hash值的签名。

    (5)CKey::Sign

    bool CKey::Sign(const uint256 &hash, std::vector<unsigned char>& 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<unsigned char>& 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<CNoDestination, CKeyID, CScriptID, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessUnknown> 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<std::string>
      {
      private:
          const CChainParams& m_params;                                                                                         
      public:
          DestinationEncoder(const CChainParams& params) : m_params(params) {}
          ...
          ...
          std::string operator()(const CScriptID& id) const
          { 
    =>        std::vector<unsigned char> 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<unsigned char>(1,196);
    

    然后将它放在Payload的最前面,作为地址前缀:

    data.insert(data.end(), id.begin(), id.end());
    

    举例:

    • 拼接前缀前,id即Public Key Hash,长度20byte(160bit)

    $20 = (const CScriptID &) @0x7fffffffc6d4: {<uint160> = {<base_blob<160>> = {static WIDTH = 0x14, data = {0x30, 0x36, 0xbd, 0x49, 0xfb, 0x32, 0x49, 0xda, 0xa2, 0xb3, 0x95, 0x57, 0x93, 0x17, 0xc8, 0x81, 0x49, 0x7a, 0x29, 0x11}}, <No data fields>}, <No data fields>}

    • 增加前缀后,即增加了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<unsigned char>& vchIn)
      {    
          // add 4-byte hash check to the end
          std::vector<unsigned char> 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 = {<base_blob<256>> = {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}}, <No data fields>}

    取该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)——比特币钱包生成地址过程

          本文链接:https://www.haomeiwen.com/subject/butgoftx.html