主页 > token.im钱包下载 > 比特币源码-交易的生成(一)--生成地址

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

token.im钱包下载 2023-12-21 05:13:16

交易生成(一)--生成地址

事务生成(二)-创建事务

交易生成(三) – 提交交易

事务生成(四)——脚本和签名

目录

在这篇文章中,系统要讲客户端/钱包如何生成比特币地址并创建交易。

我们知道比特币的所有权是由数字密钥、比特币地址和数字签名决定的。数字密钥不存储在网络中,而是由客户端生成并存储在名为 wallet (wallet.dat) 的文件或简单的数据库中。存储在用户钱包中的数字密钥完全独立于比特币协议,可以由用户的钱包软件生成和管理,无需参考区块链,也无需访问网络。

0.生成比特币地址

从钱包的rpc命令getnewaddress开始,先看调用结果

这里写图片描述

可以看到它返回的是一个比特币地址(测试网),如果是主网比特币ETC单价,就是一个以1开头的地址

那么相应的记录就会被添加到日志信息中

这里写图片描述

我们看一下指定对应调用的函数

1. 获取新地址

位于/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

比特币ETC单价

在该函数中,首先调用ReserveKeyFromKeyPool查看keyreserve pool中的key,如果没有reserve key,通过GenerateNewKey生成一个新的key,否则根据index获取reserve pool中的下一个key

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

这个类的结构最好理解

-usehd:在 BIP3 之后使用分层确定性密钥生成 (HD)2. 仅在钱包创建/首次启动期间有效

这意味着使用确定性分层钱包

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

比特币ETC单价

如果未激活加密密钥

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

激活加密密钥

p>

先调用 EncryptSecret 对 secret 进行加密,然后添加加密后的私钥

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

在键值对 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 Write的父类CDB的成员函数,CDB封装了伯克利数据库的一系列操作接口。上面的代码是写伯克利数据库的key。

你需要了解加载钱包

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

2.base58 编码

代码中用到的代码如下

CBitcoinAddress(keyID).ToString();

base58 编码的比特币地址。

Public-key-hash-addresses 的版本为 0(或 111 测试网)。

比特币ETC单价

数据向量包含 RIPEMD160(SHA256(pubkey)),其中 pubkey 是序列化的公钥。

Script-hash-addresses 有版本 5(或 196 测试网)。

数据向量包含RIPEMD160(SHA256(cscript)),其中cscript是序列化的赎回脚本。

公钥哈希地址的版本为 0(或 111 测试网)。

数据向量包含 RIPEMD160 (SHA256(pubkey)),其中 pubkey 是序列化的公钥。

脚本哈希地址有版本 5(或 196 测试网)。

数据向量包含 RIPEMD160 (SHA256 (cscript))比特币ETC单价,其中 cscript 是序列化的脚本。

TypeVersion Prefix(hex)base58 结果前缀

比特币地址

0x00

1

比特币测试网地址

0x6f

米,n

脚本哈希地址

0x05

3

比特币ETC单价

脚本哈希地址测试网

0xc4

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

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

我们看代码,CBitcoinAddress类继承自CBase58Data类,CBase58Data中的vch​​Version成员变量代表版本信息。

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, 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;
}
<在CBase58Data中声明

本节的逻辑是解码验证,获取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。

这里写图片描述

接下来看看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(比如这笔交易发送给谁以及为什么它被启动)

比特币ETC单价

具有特定目的地的 txout 脚本模板。它是:

CNoDestination:没有设置目的地

CKeyID:TX_PUBKEYHASH 目的地

CScriptID:TX_SCRIPTHASH 目标

CTxDestination 是编码在 CBitcoinAddress 中的内部数据类型

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

CWalletTx:包含一堆只有所有者才关心的附加信息的交易。

它包括将其链接回区块链所需的任何未记录交易。

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

太长了,单独写一个Transaction