主页 > 官网imtoken钱包苹果版下载 > 比特币交易源码分析

比特币交易源码分析

交易部分可以先阅读《精通比特币》第5章

本文内容参考自

初步分析比特币源码,推荐阅读比特币源码original-bitcoin原版。 该版本源码比较简单,可以帮助您快速了解比特币各个阶段的工作流程和原理。

1.发送钱()

当比特币客户端将比特币发送到地址时调用此函数。 “src/main.cpp”第 2625 行的函数。

bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew)
{
CRITICAL_BLOCK(cs_main)
{
int64 nFeeRequired;
if (!CreateTransaction(scriptPubKey, nValue, wtxNew, nFeeRequired))
{
strig strError;
if (nValue + nFeeRequired > GetBalance())
strError = strprintf("Error: This is an oversized transaction that requires a transaction fee of %s ", FormatMoney(nFeeRequired).c_str());
else
strError = "Error: Transaction creation failed ";
wxMessageBox(strError, "Sending...");
return error("SendMoney() : %s\n", strError.c_str());
}
if (!CommitTransactionSpent(wtxNew))
{
wxMessageBox("Error finalizing transaction", "Sending...");
return error("SendMoney() : Error finalizing transaction");
}
printf("SendMoney: %s\n", wtxNew.GetHash().ToString().substr(0,6).c_str());
// Broadcast
if (!wtxNew.AcceptTransaction())
{
// This must not fail. The transaction has already been signed and recorded.
throw runtime_error("SendMoney() : wtxNew.AcceptTransaction() failed\n");
wxMessageBox("Error: Transaction not valid", "Sending...");
return error("SendMoney() : Error: Transaction not valid");
}
wtxNew.RelayWalletTransaction();
}
MainFrameRepaint();
return true;
}

该方法包含三个参数:

SendMoney()

第一次打电话

CreateTransaction()

函数,该函数的作用是构造一个新的交易,也是本文重点关注的函数。 该函数源码如下:

bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, int64& nFeeRequiredRet)
{
nFeeRequiredRet = 0;
CRITICAL_BLOCK(cs_main)
{
// txdb must be opened before the mapWallet lock
CTxDB txdb("r");
CRITICAL_BLOCK(cs_mapWallet)
{
int64 nFee = nTransactionFee;
loop
{
wtxNew.vin.clear();
wtxNew.vout.clear();
if (nValue < 0)
return false;
int64 nValueOut = nValue;
nValue += nFee;
// Choose coins to use
set setCoins;
if (!SelectCoins(nValue, setCoins))
return false;
int64 nValueIn = 0;

比特币区块和比特币的区别_比特币6位mi_比特币私钥多少位

foreach(CWalletTx* pcoin, setCoins) nValueIn += pcoin->GetCredit(); // Fill vout[0] to the payee wtxNew.vout.push_back(CTxOut(nValueOut, scriptPubKey)); // Fill vout[1] back to self with any change if (nValueIn > nValue) { // Use the same key as one of the coins vector vchPubKey; CTransaction& txFirst = *(*setCoins.begin()); foreach(const CTxOut& txout, txFirst.vout) if (txout.IsMine()) if (ExtractPubKey(txout.scriptPubKey, true, vchPubKey)) break; if (vchPubKey.empty()) return false; // Fill vout[1] to ourself CScript scriptPubKey; scriptPubKey << vchPubKey << OP_CHECKSIG; wtxNew.vout.push_back(CTxOut(nValueIn - nValue, scriptPubKey)); } // Fill vin foreach(CWalletTx* pcoin, setCoins) for (int nOut = 0; nOut < pcoin->vout.size(); nOut++) if (pcoin->vout[nOut].IsMine()) wtxNew.vin.push_back(CTxIn(pcoin->GetHash(), nOut)); // Sign int nIn = 0; foreach(CWalletTx* pcoin, setCoins) for (int nOut = 0; nOut < pcoin->vout.size(); nOut++) if (pcoin->vout[nOut].IsMine()) SignSignature(*pcoin, wtxNew, nIn++); // Check that enough fee is included if (nFee < wtxNew.GetMinFee(true)) { nFee = nFeeRequiredRet = wtxNew.GetMinFee(true); continue; } // Fill vtxPrev by copying from previous transactions vtxPrev wtxNew.AddSupportingTransactions(txdb); wtxNew.fTimeReceivedIsTxTime = true; break; } } } return true; }

调用该方法时,需要的四个参数如下:

该函数首先初始化实例wtxNew,然后计算总费用nValue=转账金额+交易手续费,并调用

SelectCoin()

寻找合适的交易输入。

事实上,没有地方可以存储比特币地址或账户余额,只有被其所有者锁定的去中心化 UTXO。 “用户的比特币余额”的概念是通过比特币钱包应用程序创建的衍生产品。 比特币钱包通过扫描区块链并汇总属于该用户的所有 UTXO 来计算用户的余额。

bool SelectCoins(int64 nTargetValue, set& setCoinsRet)
{
setCoinsRet.clear();
// List of values less than target
int64 nLowestLarger = _I64_MAX;
CWalletTx* pcoinLowestLarger = NULL;
vector > vValue;
int64 nTotalLower = 0;
...

比特币私钥多少位_比特币区块和比特币的区别_比特币6位mi

}

我们知道比特币是基于 UTXO 模型的,所以 SelectCoin 负责从属于用户的所有 UTXO 中找到一组匹配转账金额的输入。 具体的搜索算法这里就不具体分析了。

在得到一组输入后,它会计算所有输入的总量 nValueIn。 一般输入的总金额大于转账的金额,所以后面会构造一个转账到自己地址的输出进行变更。

随后打电话

wtxNew.vout.push_back(CTxOut(nValueOut, scriptPubKey))

构造第一个output,指向交易的转账地址。CTxIn和CTxOut的数据结构可以参考

如果需要更改 (nValueIn > nValue),请将另一个输出事务添加到 wtxNew 并将更改发回给我。 该过程包括以下步骤:

从setCoin中获取第一笔交易txFirst,依次检查txFirst.vout中的输出是否属于你。如果是,则从输出交易中提取公钥

ExtractPubKey

, 并放入局部变量 vchPubKey

将vchPubKey放入脚本vchPubKey OP_CHECKSIG,并使用此脚本代码为wtxNew添加一个支付给自己的输出交易。

由于 setCoins 包括支付给该人的交易,因此每笔交易必须至少包括一笔支付给该人的交易。 从第一笔交易txFirst可以查到。

至此,wtxNew的输出交易容器vout就准备好了。 现在,安装程序进入交易容器 vin。 请记住,每个输入交易列表 vin 都指向一个源交易,而 wtxNew 的每个源交易都可以在 setCoins 中找到。 对于setCoins中的每一笔交易pcoin,逐一遍历其输出交易pcoin->vout[nOut]。 如果 nOut 输出支付给我(意味着 wtxNew 从这个输出交易中获得硬币)比特币6位mi比特币6位mi,添加一个新的输入交易到 wtxNew (wtxNew.vin(wtxNew.vin.push_back(CTxIn(pcoin->GetHash(), nOut)) , line 51). 输入交易指向pcoin中的nOut输出交易,从而将wtxNew.vin与pcoin的nOut输出连接起来。

对于setCoins中的每一笔交易pcoin,将其输出的所有交易pcoin->vout[nOut]一一遍历。 如果这笔交易是我的,调用SignSignature(*pcoin,wtxNew, nIn++)为第nIn笔交易添加签名。 注意nIn是wtxNew的输入交易位置。

对于交易签名功能

SignSignature

,以下是源代码:

bool SignSignature(const CTransaction& txFrom, CTransaction& txTo, unsigned int nIn, int nHashType, CScript scriptPrereq)
{
assert(nIn < txTo.vin.size());
CTxIn& txin = txTo.vin[nIn];
assert(txin.prevout.n < txFrom.vout.size());
const CTxOut& txout = txFrom.vout[txin.prevout.n];
// Leave out the signature from the hash, since a signature can't sign itself.
// The checksig op will also drop the signatures from its hash.
uint256 hash = SignatureHash(scriptPrereq + txout.scriptPubKey, txTo, nIn, nHashType);
if (!Solver(txout.scriptPubKey, hash, nHashType, txin.scriptSig))
return false;
txin.scriptSig = scriptPrereq + txin.scriptSig;
// Test solution
if (scriptPrereq.empty())
if (!EvalScript(txin.scriptSig + CScript(OP_CODESEPARATOR) + txout.scriptPubKey, txTo, nIn))
return false;
return true;
}

首先要注意的是这个函数有5个参数,而CreateTransaction()只有3个,这是因为在script.h文件中,默认给定了最后两个参数。

SignSignature(*pcoin, wtxNew, nIn++)

这是 SignSignature() 的作用:

这些功能介绍如下:

签名哈希()

uint256 SignatureHash(CScript scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType)
{
if (nIn >= txTo.vin.size())
{
printf("ERROR: SignatureHash() : nIn=%d out of range\n", nIn);
return 1;
}
CTransaction txTmp(txTo);
// In case concatenating two scripts ends up with two codeseparators,
// or an extra one at the end, this prevents all those possible incompatibilities.
scriptCode.FindAndDelete(CScript(OP_CODESEPARATOR));
// Blank out other inputs' signatures
for (int i = 0; i < txTmp.vin.size(); i++)
txTmp.vin[i].scriptSig = CScript();
txTmp.vin[nIn].scriptSig = scriptCode;
// Blank out some of the outputs
if ((nHashType & 0x1f) == SIGHASH_NONE)
{
// Wildcard payee
txTmp.vout.clear();
// Let the others update at will

比特币私钥多少位_比特币6位mi_比特币区块和比特币的区别

for (int i = 0; i < txTmp.vin.size(); i++) if (i != nIn) txTmp.vin[i].nSequence = 0; } else if ((nHashType & 0x1f) == SIGHASH_SINGLE) { // Only lockin the txout payee at same index as txin unsigned int nOut = nIn; if (nOut >= txTmp.vout.size()) { printf("ERROR: SignatureHash() : nOut=%d out of range\n", nOut); return 1; } txTmp.vout.resize(nOut+1); for (int i = 0; i < nOut; i++) txTmp.vout[i].SetNull(); // Let the others update at will for (int i = 0; i < txTmp.vin.size(); i++) if (i != nIn) txTmp.vin[i].nSequence = 0; } // Blank out other inputs completely, not recommended for open transactions if (nHashType & SIGHASH_ANYONECANPAY) { txTmp.vin[0] = txTmp.vin[nIn]; txTmp.vin.resize(1); } // Serialize and hash CDataStream ss(SER_GETHASH); ss.reserve(10000); ss << txTmp << nHashType; return Hash(ss.begin(), ss.end()); }

SignatureHash(scriptPrereq + txout.scriptPubKey, txTo, nIn, nHashType);

下面是这个函数需要的参数:

了解了输入交易后,我们来了解一下 SignatureHash() 是如何工作的。

SignatureHash() 首先将 txTO 复制到 txTmp,然后清除 txTmp.vin 中每个输入交易的 scriptSig,除了 txTmp.vin[nIn],输入交易的 scriptSig 被设置为 scriptCode(第 14 和 15 行)。

接下来,该函数检查 nHashType 的值。 根据不同的nHAshType选择不同的消隐操作。

在最后 4 行代码中,txTmp 和 nHashType 成为 CDataStream 类型的序列化对象。 此类型包括保存数据的字符容器类型。 返回的哈希值是序列化后的数据通过Hash()方法计算得到的。

至此我们已经生成了txOut的哈希值hash,然后调用Solver()函数对刚刚生成的hash进行签名。

求解器()

Solver(txout.scriptPubKey, hash, nHashType, txin.scriptSig)

其源代码如下:

bool Solver(const CScript& scriptPubKey, uint256 hash, int nHashType, CScript& scriptSigRet)
{
scriptSigRet.clear();
vector > vSolution;
if (!Solver(scriptPubKey, vSolution))
return false;
// Compile solution
CRITICAL_BLOCK(cs_mapKeys)
{
foreach(PAIRTYPE(opcodetype, valtype)& item, vSolution)
{
if (item.first == OP_PUBKEY)
{
// Sign
const valtype& vchPubKey = item.second;
if (!mapKeys.count(vchPubKey))
return false;
if (hash != 0)
{
vector vchSig;
if (!CKey::Sign(mapKeys[vchPubKey], hash, vchSig))
return false;

比特币区块和比特币的区别_比特币6位mi_比特币私钥多少位

vchSig.push_back((unsigned char)nHashType); scriptSigRet << vchSig; } } else if (item.first == OP_PUBKEYHASH) { // Sign and give pubkey map::iterator mi = mapPubKeys.find(uint160(item.second)); if (mi == mapPubKeys.end()) return false; const vector& vchPubKey = (*mi).second; if (!mapKeys.count(vchPubKey)) return false; if (hash != 0) { vector vchSig; if (!CKey::Sign(mapKeys[vchPubKey], hash, vchSig)) return false; vchSig.push_back((unsigned char)nHashType); scriptSigRet << vchSig << vchPubKey; } } } } return true; }

以下是该方法需要的4个参数:

该函数首先清除scriptSigRet,然后调用

Solver(scriptPubKey, vSolution)

, 这个 Solver 函数有两个输入。 它的源代码是:

bool Solver(const CScript& scriptPubKey, vector >& vSolutionRet)
{
// Templates
static vector vTemplates;
if (vTemplates.empty())
{
// Standard tx, sender provides pubkey, receiver adds signature
vTemplates.push_back(CScript() << OP_PUBKEY << OP_CHECKSIG);
// Short account number tx, sender provides hash of pubkey, receiver provides signature and pubkey
vTemplates.push_back(CScript() << OP_DUP << OP_HASH160 << OP_PUBKEYHASH << OP_EQUALVERIFY << OP_CHECKSIG);
}
// Scan templates
const CScript& script1 = scriptPubKey;
foreach(const CScript& script2, vTemplates)
{
vSolutionRet.clear();
opcodetype opcode1, opcode2;
vector vch1, vch2;
// Compare
CScript::const_iterator pc1 = script1.begin();
CScript::const_iterator pc2 = script2.begin();
loop
{
bool f1 = script1.GetOp(pc1, opcode1, vch1);
bool f2 = script2.GetOp(pc2, opcode2, vch2);
if (!f1 && !f2)
{
// Success
reverse(vSolutionRet.begin(), vSolutionRet.end());
return true;
}
else if (f1 != f2)
{
break;
}
else if (opcode2 == OP_PUBKEY)

比特币私钥多少位_比特币6位mi_比特币区块和比特币的区别

{ if (vch1.size() <= sizeof(uint256)) break; vSolutionRet.push_back(make_pair(opcode2, vch1)); } else if (opcode2 == OP_PUBKEYHASH) { if (vch1.size() != sizeof(uint160)) break; vSolutionRet.push_back(make_pair(opcode2, vch1)); } else if (opcode1 != opcode2) { break; } } } vSolutionRet.clear(); return false; }

此函数的作用是将 scriptPubKey 与两个模板进行比较:

如果输入的脚本是脚本A,将模板A中的OP_PUBKEYHASH与脚本A中的OP_PUBKEYHASH配对,放入vSolutionRet中。

如果输入的脚本是脚本B,从模板B中提取算子OP_PUBKEY,从脚本B中提取操作数,配对放入vSolutionRet。

如果输入脚本与任一模板都不匹配,则返回 false。

回到带4个参数的Solver()继续分析这个函数。 现在我们清楚了这个函数是如何工作的。 它会选择两个分支中的一个来执行,这取决于从 vSolutionRet 中获得的 pair 是来自脚本 A 还是来自脚本 B。如果来自脚本 A,则 item.first == OP_PUBKEYHASH; 如果来自脚本 B,item.first == OP_PUBKEY。

评估脚本()

最后,将调用 EvalScript() 来运行一个小脚本并检查签名是否有效。

EvalScript(txin.scriptSig + CScript(OP_CODESEPARATOR) + txout.scriptPubKey, txTo, nIn)

其源代码如下:

bool EvalScript(const CScript& script, const CTransaction& txTo, unsigned int nIn, int nHashType,
vector >* pvStackRet)
{
CAutoBN_CTX pctx;
CScript::const_iterator pc = script.begin();
CScript::const_iterator pend = script.end();
CScript::const_iterator pbegincodehash = script.begin();
vector vfExec;
vector stack;
vector altstack;
if (pvStackRet)
pvStackRet->clear();
while (pc < pend)
{
bool fExec = !count(vfExec.begin(), vfExec.end(), false);
...
}
if (pvStackRet)
*pvStackRet = stack;
return (stack.empty() ? false : CastToBool(stack.back()));
}

EvalScript() 接受 3 个参数,即:

该函数会根据你输入的脚本,依次取出脚本中的操作码进行相应的操作,判断最终的仿真执行结果,并返回执行结果,如果结果为真,则大功告成

SignSignature()

, 一个新的交易被生成。

返回 SendMoney()

生成新交易后,使用函数

CommitTransactionSpent(wtxNet)

尝试将这个事务提交到数据库,然后判断事务是否提交成功,如果事务提交成功

wtxNew.AcceptTransaction()=true

, 将这笔交易广播给其他对等节点

wtxNew.RelayWalletTransaction()

.

矿工收到这笔交易的广播后,会对这笔交易进行相应的操作。 在后面的章节中,我们将详细分析新区块的处理部分。

总结

比特币产生一笔新的交易大致分为以下几个阶段:

比特币区块和比特币的区别_比特币6位mi_比特币私钥多少位

比特币区块和比特币的区别_比特币私钥多少位_比特币6位mi

轩素红发表原创文章11篇 · 点赞0 · 访问111 关注私信