比特币源码-一个交易的产生(一)--生成地址

交易的产生(一)–生成地址
交易的产生(二)–创建交易
交易的产生(三)–提交交易
交易的产生(四)–脚本和签名


目录

    • 0.生成比特币地址
    • 1.getnewaddress
    • 1.2 GetKeyFromPool
      • 1.2.1GenerateNewKey
    • 2.base58编码
    • 3.发送金额
      • 3.1GetBalance()
        • 3.1.1IsTrusted()
        • 3.1.2GetAvailableCredit


在这一篇里系统得讲讲客户端/钱包如何生成比特币地址,并创建一个交易。
我们知道比特币的所有权是通过数字密钥、比特币地址和数字签名来确定的。数字密钥并不存储在网络中,而是由客户端生成后保存在名为钱包的文件(wallet.dat)或者简单的数据库中。存储在用户钱包中的数字密钥完全独立于比特币协议,可由用户的钱包软件生成并管理,而无需参照区块链或访问网络。

0.生成比特币地址

从钱包的rpc指令getnewaddress开始看,先来看下调用结果
这里写图片描述
可以看到是返回一个比特币地址(测试网),如果是主网的话是以1开头的地址
然后在日志信息中会增加相应的记录
这里写图片描述
下面来看这个指定对应调用的函数

1.getnewaddress

位于/src/rpc/wallet/rpcwallet.cpp

UniValue getnewaddress(const UniValue& params, bool fHelp)
{
    if (!EnsureWalletIsAvailable(fHelp))
        return NullUniValue;

    if (fHelp || params.size() > 1)
        throw runtime_error(
            "getnewaddress ( \"account\" )\n"
            "\nReturns a new Bitcoin address for receiving payments.\n"
            "If 'account' is specified (DEPRECATED), it is added to the address book \n"
            "so payments received with the address will be credited to 'account'.\n"
            "\nArguments:\n"
            "1. \"account\"        (string, optional) DEPRECATED. The account name for the address to be linked to. If not provided, the default account \"\" is used. It can also be set to the empty string \"\" to represent the default account. The account does not need to exist, it will be created if there is no account by the given name.\n"
            "\nResult:\n"
            "\"bitcoinaddress\"    (string) The new bitcoin address\n"
            "\nExamples:\n"
            + HelpExampleCli("getnewaddress", "")
            + HelpExampleRpc("getnewaddress", "")
        );

    LOCK2(cs_main, pwalletMain->cs_wallet);

    // Parse the account first so we don't generate a key if there's an error
    string strAccount;
    if (params.size() > 0)
        strAccount = AccountFromValue(params[0]);

    if (!pwalletMain->IsLocked())
        pwalletMain->TopUpKeyPool();//充值密钥池到上限

    // Generate a new key that is added to wallet
    CPubKey newKey;
    if (!pwalletMain->GetKeyFromPool(newKey))
        throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
    CKeyID keyID = newKey.GetID();//获取的是hash160的值
//pwalletMain是CWllat类的指针
    pwalletMain->SetAddressBook(keyID, strAccount, "receive");//写入数据库,目的为“receive"
//CBitcoinAddress函数调用Base58编码转换
    return CBitcoinAddress(keyID).ToString();
}

这里的pwalletMain是指向CWallet类对象的指针。生成一个新的密钥在这里是通过调用函数GetKeyFromPool。可以看到这段代码最后是调用CBitcoinAddress函数返回比特币地址。


1.2 GetKeyFromPool

在这个函数中首先调用ReserveKeyFromKeyPool查看密钥储备池中的密钥,如果没有储备的密钥,就通过GenerateNewKey生成一个新的密钥,否则根据索引获取储备池中的下一个密钥

//src/wallet/wallet.cpp
bool CWallet::GetKeyFromPool(CPubKey& result)
{
    int64_t nIndex = 0;
    CKeyPool keypool;
    {
        LOCK(cs_wallet);
        ReserveKeyFromKeyPool(nIndex, keypool);
        if (nIndex == -1)
        {
            if (IsLocked()) return false;
            result = GenerateNewKey();
            return true;
        }
        KeepKey(nIndex);//擦除密钥
        result = keypool.vchPubKey;
    }
    return true;
}

这是我调用bitcoin-cli getnewaddress的日志信息,密钥池的大小是101把密钥,这次调用补上第108把密钥,本次使用第8把密钥,前7把都已经从密钥池中移除了

2018-09-26 07:12:29 keypool added key 108, size=101
2018-09-26 07:12:29 keypool reserve 8
2018-09-26 07:12:29 keypool keep 8

1.2.1GenerateNewKey

CKey这个类的结构最好了解一下

-usehd:Use hierarchical deterministic key generation (HD) after BIP32. Only has effect during wallet creation/first start
这个是表示使用确定性分层钱包

CPubKey CWallet::GenerateNewKey()
{
    AssertLockHeld(cs_wallet); // mapKeyMetadata
    bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
    // CKey是私钥的类
    CKey secret;

    // Create new metadata
    int64_t nCreationTime = GetTime();
    CKeyMetadata metadata(nCreationTime);

    // use HD key derivation if HD was enabled during wallet creation 
    //1.如果在钱包创建时使用确定分层钱包,则使用HD密钥
    if (!hdChain.masterKeyID.IsNull()) {
        // 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 externalChainChildKey; //key at m/0'/0'
        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)
        //使用硬化的派生,const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
        masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT);

        // derive m/0'/0'
        accountKey.Derive(externalChainChildKey, BIP32_HARDENED_KEY_LIMIT);

        // 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
            externalChainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
            metadata.hdKeypath     = "m/0'/0'/"+std::to_string(hdChain.nExternalChainCounter)+"'";
            metadata.hdMasterKeyID = hdChain.masterKeyID;
            // increment childkey index
            hdChain.nExternalChainCounter++;
        } while(HaveKey(childKey.key.GetPubKey().GetID()));
        secret = childKey.key;

        // update the chain model in the database
        if (!CWalletDB(strWalletFile).WriteHDChain(hdChain))
            throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed");
    } else {
    //2.如果在创建钱包的时候是使用随机钱包
        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;
    if (!nTimeFirstKey || nCreationTime < nTimeFirstKey)
        nTimeFirstKey = nCreationTime;

    if (!AddKeyPubKey(secret, pubkey))
        throw std::runtime_error(std::string(__func__) + ": AddKey failed");
    return pubkey;
}

其中的HD钱包部分是依据标准BIP32实现的,如果是随机钱包,则调用CKey类的MakeNewKey

//key.cpp
void CKey::MakeNewKey(bool fCompressedIn) {
    do {
        GetStrongRandBytes(vch, sizeof(vch));////! The actual byte data unsigned char vch[32];
    } while (!Check(vch));
    fValid = true;
    fCompressed = fCompressedIn;
}

具体实现就是新建一个CKey类型的对象,获取强随机数,私钥是32位的,知道获取的随机数通过验证,此时的私钥是有效的。
然后获取对应私钥的公钥,通过椭圆曲线算法调用库,验证公钥
最后添加公钥,调用CWallet类的AddKeyPubKey

//wallet.cpp
bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey)
{
    AssertLockHeld(cs_wallet); // mapKeyMetadata
    if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey))
        return false;

    // check if we need to remove from watch-only
    CScript script;
    script = GetScriptForDestination(pubkey.GetID());
    if (HaveWatchOnly(script))
        RemoveWatchOnly(script);
    script = GetScriptForRawPubKey(pubkey);
    if (HaveWatchOnly(script))
        RemoveWatchOnly(script);

    if (!fFileBacked)
        return true;
    if (!IsCrypted()) {
        return CWalletDB(strWalletFile).WriteKey(pubkey,
                                                 secret.GetPrivKey(),
                                                 mapKeyMetadata[pubkey.GetID()]);
    }
    return true;
}

首先调用CCryptoKeyStore的AddKeyPubKey,CCryptoKeyStore是保存加密过的私钥的密钥库,继承自CBasicKeyStore,如果没有激活加密,则使用CBasicKeyStore。

bool CCryptoKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey)
{
    {
        LOCK(cs_KeyStore);
        if (!IsCrypted())//是否激活加密
            return CBasicKeyStore::AddKeyPubKey(key, pubkey);

        if (IsLocked())
            return false;

        std::vector<unsigned char> vchCryptedSecret;
        CKeyingMaterial vchSecret(key.begin(), key.end());
        if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret))
            return false;

        if (!AddCryptedKey(pubkey, vchCryptedSecret))
            return false;
    }
    return true;
}

未激活加密私钥的话

bool CBasicKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey)
{
    LOCK(cs_KeyStore);
    mapKeys[pubkey.GetID()] = key;//私钥和公钥(hash值)保存在键值对mapKeys中
    return true;
}

激活加密私钥
首先调用EncryptSecret加密秘密,接着添加加密的私钥

bool CCryptoKeyStore::AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
{
    {
        LOCK(cs_KeyStore);
        if (!SetCrypted())
            return false;

        mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret);
    }
    return true;
}

把公钥(hash)、公钥、加密的秘密保存在键值对mapCryptedKeys中。
返回CWallet::AddKeyPubKey,最后调用 CWalletDB类的WriteKey

bool CWalletDB::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata& keyMeta)
{
    nWalletDBUpdated++;

    if (!Write(std::make_pair(std::string("keymeta"), vchPubKey),
               keyMeta, false))
        return false;

    // hash pubkey/privkey to accelerate wallet load
    std::vector<unsigned char> vchKey;
    vchKey.reserve(vchPubKey.size() + vchPrivKey.size());
    vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end());
    vchKey.insert(vchKey.end(), vchPrivKey.begin(), vchPrivKey.end());

    return Write(std::make_pair(std::string("key"), vchPubKey), std::make_pair(vchPrivKey, Hash(vchKey.begin(), vchKey.end())), false);
}

这里是调用了CWalletDB的父类CDB的成员函数Write,CDB封装了Berkeley数据库的一系列操作接口,上述代码就是把密钥写入Berkeley数据库。
加载钱包需要了解一下

Berkeley DB (DB)是一个高性能的,嵌入数据库编程库,和C语言,C++,Java,Perl,Python,PHP,Tcl以及其他很多语言都有绑定。Berkeley DB可以保存任意类型的键/值对,而且可以为一个键保存多个数据。Berkeley DB可以支持数千的并发线程同时操作数据库,支持最大256TB的数据,广泛用于各种操作系统包括大多数Unix类操作系统和Windows操作系统以及实时操作系统。

2.base58编码

代码中使用的代码如下

CBitcoinAddress(keyID).ToString();

base58-encoded Bitcoin addresses.
Public-key-hash-addresses have version 0 (or 111 testnet).
The data vector contains RIPEMD160(SHA256(pubkey)), where pubkey is the serialized public key.
Script-hash-addresses have version 5 (or 196 testnet).
The data vector contains RIPEMD160(SHA256(cscript)), where cscript is the serialized redemption script.
公钥哈希地址具有版本0(或111 测试网)。
数据向量包含RIPEMD160(SHA256(pubkey)),其中pubkey是序列化公钥。
脚本哈希地址具有版本5(或196 测试网)。
数据向量包含RIPEMD160(SHA256(cscript)),其中cscript是序列化的脚本。

Type Version Prefix(hex) base58 result prefix
bitcoin address 0x00 1
bitcoin testnet address 0x6f m、n
Script-hash-addresses 0x05 3
Script-hash-addresses testnet 0xc4

这些可以在chainparams.cpp的对应参数类中找到,如下为测试网

base58Prefixes[PUBKEY_ADDRESS] = std::vector<unsigned char>(1,111);
base58Prefixes[SCRIPT_ADDRESS] = std::vector<unsigned char>(1,196);

下面来看代码,类CBitcoinAddress继承自类CBase58Data,CBase58Data中有vchVersion成员变量表示版本信息。

class CBitcoinAddress : public CBase58Data {
public:
    bool Set(const CKeyID &id);
    bool Set(const CScriptID &id);
    bool Set(const CTxDestination &dest);
    bool IsValid() const;
    bool IsValid(const CChainParams &params) const;

    CBitcoinAddress() {}
    CBitcoinAddress(const CTxDestination &dest) { Set(dest); }
    CBitcoinAddress(const std::string& strAddress) { SetString(strAddress); }
    CBitcoinAddress(const char* pszAddress) { SetString(pszAddress); }

    CTxDestination Get() const;
    bool GetKeyID(CKeyID &keyID) const;
    bool IsScript() const;
};

根据代码推测是调用了第三个或第四个构造函数,都是调用SetString()函数,SetString()函数内部是调用父类的SetString(),也就是CBase58Data::SetString,在CBase58Data中声明的bool SetString(const char* psz, unsigned int nVersionBytes = 1);

bool CBase58Data::SetString(const char* psz, unsigned int nVersionBytes)
{
    std::vector<unsigned char> vchTemp;
    //将psz解码并且验证正确后的内容存放在vchTemp,此时的vchTemp包含版本前缀和编码前的内容
    bool rc58 = DecodeBase58Check(psz, vchTemp);
    if ((!rc58) || (vchTemp.size() < nVersionBytes)) {//如果解码验证失败,清空成员变量vchData,vchVersion
        vchData.clear();
        vchVersion.clear();
        return false;
    }
    vchVersion.assign(vchTemp.begin(), vchTemp.begin() + nVersionBytes);//获取版本信息,就是版本前缀
    vchData.resize(vchTemp.size() - nVersionBytes);
    if (!vchData.empty())
        memcpy(&vchData[0], &vchTemp[nVersionBytes], vchData.size());//编码前的数据如比特币地址,脚本地址存入vchData中
    memory_cleanse(&vchTemp[0], vchTemp.size());
    return true;
}

这一段的逻辑就是解码验证,获取Version和Payload的过程

std::string CBase58Data::ToString() const
{
    std::vector<unsigned char> vch = vchVersion;
    //在vch的末尾插入vchData
    vch.insert(vch.end(), vchData.begin(), vchData.end());
    return EncodeBase58Check(vch);//对版本前缀和内容的组合编码
}

借用精通比特币的图,编码过程如下,可以理解为vch=version+payload,在上一代码中vch=vchVersion+vchData.

比特币源码-一个交易的产生(一)--生成地址_第1张图片
那么顺便来看一下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);
}

这段很简单就是在后面加上4位的校验码,校验码就是对vch做两次sha256取前4位。然后调用EncodeBase58进行编码。

3.发送金额

这个函数位于rpcwallet.cpp中,需要传入交易目的地,交易金额,fSubtractFeeFromAmount(从交易金额中抽取费用),包含附加的交易信息CWalletTx(比如这笔交易是发送给谁,为什么发起)

A txout script template with a specific destination. It is either:
CNoDestination: no destination set
CKeyID: TX_PUBKEYHASH destination
CScriptID: TX_SCRIPTHASH destination
A CTxDestination is the internal data type encoded in a CBitcoinAddress

typedef boost::variant<CNoDestination, CKeyID, CScriptID> CTxDestination;

CWalletTx:A transaction with a bunch of additional info that only the owner cares about.
It includes any unrecorded transactions needed to link it back to the block chain.

static void SendMoney(const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew)
{
    CAmount curBalance = pwalletMain->GetBalance();

    // 1.Check amount 检查余额
    if (nValue <= 0)
        throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount");

    if (nValue > curBalance)
        throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds");

    // 2.Parse Bitcoin address 解析比目的地,获得脚本
    CScript scriptPubKey = GetScriptForDestination(address);

    // 3.Create and send the transaction 创建并发送交易
    CReserveKey reservekey(pwalletMain);
    CAmount nFeeRequired;
    std::string strError;
    vector<CRecipient> vecSend;
    int nChangePosRet = -1;
    CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount};//接收者(锁定脚本,金额,是否抽取费用)
    vecSend.push_back(recipient);
    if (!pwalletMain->CreateTransaction(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strError)) {
        if (!fSubtractFeeFromAmount && nValue + nFeeRequired > pwalletMain->GetBalance())
            strError = strprintf("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds!", FormatMoney(nFeeRequired));
        throw JSONRPCError(RPC_WALLET_ERROR, strError);
    }
    if (!pwalletMain->CommitTransaction(wtxNew, reservekey))//提交交易
        throw JSONRPCError(RPC_WALLET_ERROR, "Error: The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of the wallet and coins were spent in the copy but not marked as spent here.");
}

3.1GetBalance()

首先调用GetBalance()获取钱包的余额

CAmount CWallet::GetBalance() const
{
    CAmount nTotal = 0;
    {
        LOCK2(cs_main, cs_wallet);
        for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
        {
            const CWalletTx* pcoin = &(*it).second;//CWalletTx
            if (pcoin->IsTrusted())
                nTotal += pcoin->GetAvailableCredit();
        }
    }

    return nTotal;
}

3.1.1IsTrusted()

这里判断交易是否被信任

//wallet.cpp
bool CWalletTx::IsTrusted() const
{
    // Quick answer in most cases
    if (!CheckFinalTx(*this))//判断交易是否是最终的,函数内调用了IsFinalTx
        return false;
    int nDepth = GetDepthInMainChain();
    if (nDepth >= 1)//交易所在区块后面的区块大于等于1即视为信任
        return true;
    if (nDepth < 0)
        return false;
    if (!bSpendZeroConfChange || !IsFromMe(ISMINE_ALL)) // using wtx's cached debit
        return false;

    // Don't trust unconfirmed transactions from us unless they are in the mempool.
    if (!InMempool())//不信任不在内存池中的交易
        return false;

    // Trusted if all inputs are from us and are in the mempool:
    BOOST_FOREACH(const CTxIn& txin, vin)
    {
        // Transactions not sent by us: not trusted
        const CWalletTx* parent = pwallet->GetWalletTx(txin.prevout.hash);
        if (parent == NULL)
            return false;
        const CTxOut& parentOut = parent->vout[txin.prevout.n];
        if (pwallet->IsMine(parentOut) != ISMINE_SPENDABLE)
            return false;
    }
    return true;
}
//main.h
/**
 * Check if transaction is final and can be included in a block with the
 * specified height and time. Consensus critical.
 */
bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime);

3.1.2GetAvailableCredit

这里有几个逻辑

CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const
{  
    if (pwallet == 0)//没有钱包信息则返回0
        return 0;

    // Must wait until coinbase is safely deep enough in the chain before valuing it
    //如果是coinbase交易,必须等这笔交易足够安全,否则返回0
    if (IsCoinBase() && GetBlocksToMaturity() > 0)
        return 0;

    if (fUseCache && fAvailableCreditCached)//如果使用缓存数据
        return nAvailableCreditCached;

    CAmount nCredit = 0;
    uint256 hashTx = GetHash();
    for (unsigned int i = 0; i < vout.size(); i++)
    {
        if (!pwallet->IsSpent(hashTx, i))//如果这笔交易的输出未支出
        {
            const CTxOut &txout = vout[i];
            nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE);//我可以花费的部分
            if (!MoneyRange(nCredit))
                throw std::runtime_error("CWalletTx::GetAvailableCredit() : value out of range");
        }
    }

    nAvailableCreditCached = nCredit;
    fAvailableCreditCached = true;
    return nCredit;
}

太长了,分开写到另一篇创建一个交易

你可能感兴趣的:(比特币钱包)