区块(Block)是区块链的最基本的数据单元,区块链是由一个个在逻辑上前后链接的区块构成。区块常用高度(height)来表示,从0开始(高度为0的区块是创世块),截止目前比特币区块高度是736311,并且按照每10分钟左右生成一个新块的频率在递增。为了达到高效安全的验证区块中的数据,保护区块免遭任何形式的破坏和篡改,比特币采用了哈希树(hash tree;Merkle tree:墨克树)的作为区块的数学数据结构。墨克树能够高效、安全地验证大型数据结构的内容,是哈希链的推广形式。 该概念由瑞夫·墨克于 1979 年申请专利,故亦称墨克树(Merkle tree)。在详述之前,我们先了解比特币区块的基本数据结构:
比特币区块的基本数据结构
区块主要由区块头(Block Header)和交易信息(Transaction,简称Tx)组成。区块哈希(Block Hash)由根据区块头计算出的Hash256,它是唯一的。每一个区块中有至少一条或以上的交易信息,如下图:
区块中的交易
这里从Tx0~Txn表示有n条交易信息,每个区块的首条交易,也就是Tx0固定是Coinbse交易。Coinbase交易是矿工创建的,主要是为了奖励矿工进行 POW 挖矿而付出的努力。根据前文POW工作量原理说明,也就是该奖励给那个率先计算出符合工作量要求的矿工。奖励分为两部分。一部分是出块奖励,这部分是相对固定的,每四年减半一次。另外一部分是手续费,当前区块的每个交易中都会包含一定的对矿工的奖励,也就是交易手续费。创建 Coinbase 交易的时候,矿工会把所有交易中的手续费累加到一起,然后把这笔钱转账给自己。
区块的链式结构区块的链式结构
区块头(Block Header)中的字段hashPrevBlock标记了它的上一个区块的Hash值,这样整个区块在逻辑上就组成了一个单向链表。区块哈希(Block Hash)是在区块同步过程中根据Block Header动态计算出来的,而不是一个静态的数据,因此可以防止区块在不同的节点之间同步的过程中被篡改,一旦某个节点接收的区块数据校验无法通过,将直接导致同步失败。
墨克树的组成墨克树是二叉哈希树,它的表现形式是一个二叉树。其中叶子节点就是区块中的交易信息,一个理想的墨克树如下图所示:
典型的墨克树
上图中的墨克树共有4个叶子节点,也就是说4条交易信息。每条交易信息使用Hash256根据交易的详细数据计算出对应的哈希值,对应的分别是Hash0~Hash3。然后再两两组合得到Hash01和Hash23,其中Hash01=Hash0 + Hash1,它的含义是根据Hash0和Hash1的数据计算出Hash01。最后根部的Merkle Root就是最后两个子节点哈希值合并后结算出的哈希值。
上述的交易数量是偶数,并且中间的哈希个数也是偶数。两个组合很容易理解。但是如果是奇数的话,如下图所示:
墨克树特别形式
上图有6条交易信息,当两个组合后计算出来的中间哈希值只有3个,如果再进一步计算哈希值时,两个组合后只剩下一个。根据比特币对墨克根的计算方法,它会增加一个重复的Hash45,组合后计算最终的Merkle Root。如下图所示:
墨克树特别形式
上图作为合法的交易,比特币的节点在接收到该区块数据时通过计算根墨克哈希值,很容易就验证其下面的Tx数据是否有篡改。但采用这种算法有一个致命的缺陷,如果攻击者伪造两个相同的交易,增加Tx6和Tx7分别与Tx4和Tx5完全一致,就可以与上述的结果完全一致,并且校验也能通过。如下图所示:
墨克树的重复
现在攻击者通过复制Tx4、Tx5可与上述一样的结果,理论上可欺骗其它的比特币点,将该区块数据视为合法的。但是实际上比特币节点在同步区块数据时将检查Tx的重复性,如果发现重复的数据即视为非法。
交易有效性检查为了检查交易的有效性,在同步区块过程中将对交易的有效性进行检查,在github的代码:bitcoin/tx_check.cpp at master · bitcoin/bitcoin · GitHub
中通过CheckTransaction函数对区块的交易进行检查:
bool CheckTransaction(const CTransaction& tx, TxValidationState& state)
{
// Basic checks that don't depend on any context
if (tx.vin.empty())
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-vin-empty");
if (tx.vout.empty())
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-vout-empty");
// Size limits (this doesn't take the witness into account, as that hasn't been checked for malleability)
if (::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT)
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-oversize");
// Check for negative or overflow output values (see CVE-2010-5139)
CAmount nValueOut = 0;
for (const auto& txout : tx.vout)
{
if (txout.nValue < 0)
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-vout-negative");
if (txout.nValue > MAX_MONEY)
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-vout-toolarge");
nValueOut += txout.nValue;
if (!MoneyRange(nValueOut))
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-txouttotal-toolarge");
}
// Check for duplicate inputs (see CVE-2018-17144)
// While Consensus::CheckTxInputs does check if all inputs of a tx are available, and UpdateCoins marks all inputs
// of a tx as spent, it does not check if the tx has duplicate inputs.
// Failure to run this check will result in either a crash or an inflation bug, depending on the implementation of
// the underlying coins database.
std::set vInOutPoints;
for (const auto& txin : tx.vin) {
if (!vInOutPoints.insert(txin.prevout).second)
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-inputs-duplicate");
}
if (tx.IsCoinBase())
{
if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100)
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cb-length");
}
else
{
for (const auto& txin : tx.vin)
if (txin.prevout.IsNull())
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-prevout-null");
}
return true;
}
上述代码中,就针对了重复交易进行检查,通过对交易输入(Tx in)的prevout是否重复来判断交易是否存在重复。
墨克根(Merkle Root)的计算过程在https://github.com/bitcoin/bitcoin/blob/master/src/consensus/merkle.cpp
代码中,我们可以看到墨克根的计算代码:
uint256 BlockMerkleRoot(const CBlock& block, bool* mutated)
{
std::vector leaves;
leaves.resize(block.vtx.size());
for (size_t s = 0; s < block.vtx.size(); s++) {
leaves[s] = block.vtx[s]->GetHash();
}
return ComputeMerkleRoot(std::move(leaves), mutated);
}
uint256 ComputeMerkleRoot(std::vector hashes, bool* mutated) {
bool mutation = false;
while (hashes.size() > 1) {
if (mutated) {
for (size_t pos = 0; pos + 1 < hashes.size(); pos += 2) {
if (hashes[pos] == hashes[pos + 1]) mutation = true;
}
}
if (hashes.size() & 1) {
hashes.push_back(hashes.back());
}
SHA256D64(hashes[0].begin(), hashes[0].begin(), hashes.size() / 2);
hashes.resize(hashes.size() / 2);
}
if (mutated) *mutated = mutation;
if (hashes.size() == 0) return uint256();
return hashes[0];
}
代码中的函数BlockMerkleRoot首先计算叶子节点,也就是每条交易数据的Hash值。然后根据这些Hash计算Merkle Root。其中hashes.size() & 1表示哈希的个数是奇数时,在容器中添加一个与末尾一样的hash。通过SHA256D64计算HASH256。将计算的结果保存到hashes[0],然后重复该过程最终计算出墨克根(Merkle Root)。
交易输入Tx Input和交易输出Tx Output的数据结构每笔交易(Tx) 里面包含交易输入(Tx Input)和交易输出(Tx Output)的信息。交易输入实际上包含了前面的交易的输出的信息,前面的交易是指当前交易所在的区块往前的区块,不一定是相邻的区块。交易信息通过这样的链接,就可以进行追溯资金的来源。下图是交易的链式数据结构:
比特币的链式交易数据结构
在Tx input中,有一个类型为COutPoint的prevout,它包含了一个Hash值,指向前面的区块的交易输出对应的Tx id(Tx id就是Tx hash值)。从而使该交易可以往后溯源。也就是说当前交易输入的资金来源不是凭空产生的,必须能够找到与其对应的交易输出。但是对于Coinbase矿工挖矿的奖励的部分,它的Tx Input中的prevout是空,表明该交易是挖矿奖励,是系统生成的。而不是来自其他账户的交易转账。
创世区块 创世区块(Genesis Block)是比特币区块链的第一个区块,它写死在代理里面,该区块共有50个比特币的奖励,不能用于交易,也不属于任何人。通过分析代码我们可以看出,该区块的时间是Saturday, January 3, 2009 6:15:05 PM,距今13年。区块的数据如下:
Nonce = 2083236893,
nBits = 0x1d00ffff,
nVersion = 1
Block Hash :
0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
hashMerkleRoot :
0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)