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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/m0_37847176/article/details/81906838

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

生成比特币地址

从钱包的rpc指令getnewaddress开始看,先来看下调用结果
这里写图片描述
可以看到是返回一个比特币地址(测试网),如果是主网的话是以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");
//CBitcoinAddress函数调用Base58编码转换
    return CBitcoinAddress(keyID).ToString();
}

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

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;
}

GenerateNewKey

CKey可以参考https://blog.csdn.net/m0_37847176/article/details/81450432#2-initsanitycheck里面有写到这个类,以及makenewkey()的实现

-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'/<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)
        //使用硬化的派生,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实现的,写在另一篇里https://blog.csdn.net/m0_37847176/article/details/82011876
如果是随机钱包,则调用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数据库。
加载钱包在https://blog.csdn.net/m0_37847176/article/details/81739999这一篇中

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

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

下面来看代码,类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;
    bool rc58 = DecodeBase58Check(psz, vchTemp);//psz是要编码的内容,编码后临时存放
    if ((!rc58) || (vchTemp.size() < nVersionBytes)) {
        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());
    memory_cleanse(&vchTemp[0], vchTemp.size());
    return true;
}

这部分代码的逻辑不是很清晰

std::string CBase58Data::ToString() const
{
    std::vector<unsigned char> vch = vchVersion;
    //在vch的末尾插入vchData
    vch.insert(vch.end(), vchData.begin(), vchData.end());
    return EncodeBase58Check(vch);
}

借用精通比特币的图,编码过程

这里写图片描述

发送金额

这个函数位于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.");
}

GetBalance()

首先调用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;
}

IsTrusted()

这里判断交易是否被信任

//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);

GetAvailableCredit

这里有几个逻辑

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;
}

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

阅读更多 登录后自动展开

更多精彩内容