引言
上一篇我们已经知道了什么是区块链,此篇说一下区块链的第一个应用——比特币。其实先有比特币,后有的区块链。比特币是中本聪提出的一种P2P去中心化的支付系统,其底层原理为:去中心化、数据不可篡改、可溯源;数据以区块的形式保存,并且像链条一样连接起来,很形象地称为区块链。比特币就是一条上文说过的公有链。
比特币基本结构
上一节我们说过,账本以交易的形式记录,那交易是如何存储在区块链上呢?
交易存储在区块之中。区块分为区块头以及区块体,区块头包含区块的概要信息,区块体包含交易信息。
- 区块头
- version(版本号):用于协议区分或者升级
- height:区块高度
- hash(区块Hash):区块的唯一指纹
- preBlockHash(父区块Hash):用于向前追溯
- merkleRoot(默克尔树根Hash):可用于节点快速验证区块的交易是否被篡改或者SPV(Simplified Payment Verification 简单支付验证)客户端验证某一个交易是否存在于比特币链上。
- time(区块生成时间)
- dificultyTarget(目标难度:nBits):挖矿难度
- nonce(随机数):比特币协议中“难题的解”
- 区块体
- transactions(区块包含的交易列表):被矿工打包在区块中的交易,实际为用户的转账交易。
- version(版本号):用于升级
- Inputs:交易输入列表,指向待花费的UTXO,可以有零个至多个。coinbase(创币交易)没有输入。
- Outputs:交易输出列表:指向即将产生的UTXO
- lockTime:交易锁定时间。实际为UNIX时间戳(相对于1970年1月1日0点后流逝的秒数。)矿工打包交易时,会判断是否到达此时间,到达后才会将此交易进行打包。
- time:交易的生成时间
- memo:备注字段,用户可自由填写。
比特币基础知识
1)哈希算法
哈希算法就是将任意长度的数据转变为一个定长的数据(也叫哈希或者摘要)。常见的哈希算法有CRC32、MD5、SHA1、SHA2、SHA3、Hmac等。
哈希算法具有以下特点:
-
单向不可逆性:想要通过哈希值逆推出原来的数据,只有通过暴力破解的方法实现,但这几乎无法做到,可能量子计算机出来后就可以破解了。
-
确定性:如果hash值不同, 可以确定原数据一定不同。如果相同,则还需要进一步判断,因为可能hash碰撞了。
-
雪崩效应:原始数据任何微小的变动都会导致哈希值完全不一样
-
不变性:同一个数据,多次hash结果相同。
2)非对称加密技术
非对称加密有公钥和私钥两个概念,私钥自己拥有,不能给别人,公钥公开。根据应用的不同,我们可以选择使用不同的密钥加密:
-
私钥签名:使用私钥签名(加密),公钥验签(解密)。用于让公钥所有者验证私钥所有者的身份并且用来防止私钥所有者发布的内容被篡改,但是不用来保证内容不被他人获得。(内容本身非加密的,别人可以获得信息但无法篡改)
-
公钥加密:用公钥加密,私钥解密。公钥所有者可用于加密发布的信息,这个信息可能被他人篡改,但是无法被他人获得。
举例:比如小黑持有公钥,将 “我爱你” 加密后的消息 “xxx” 发送给喜欢的人小花(小花持有私钥),但小灰也喜欢小花并且也有公钥,小灰劫持了小黑的消息(消息内容已加密,小灰也不知道内容是什么),并将 “我恨你” 加密为 “xxx”组装为小黑的请求发送给小花,小花收到小黑的请求后,用私钥解密,得到内容为:“我恨你”。这下小黑就凉凉,心机boy小灰就成功了。
签名与加密可以混合使用,提高安全性与隐私性。
3)数字签名
作用:保证信息传输的完整性、发送者的身份验证(鉴权)
原理:使用非对称加密技术及hash技术,非对称加密生成一对公私钥对,私钥用于签名,公钥用于验签。签名的对象为内容的hash。
为什么签名不直接签署内容,而是签署hash呢?
答:效率问题,如果你的原内容很大,直接对原内容签名效率很低,而且签名的结果也相对很大,传输也慢。
签名验签过程
- 签名过程:首先获取通过哈希算法获取原文的摘要信息,然后用私钥加密,并将原文一起发送给接受者。
- 验证过程:接收者只有使用发送者的公钥才能解密被加密的摘要信息,同样通过哈希算法获取原文的摘要信息,并与解密后的摘要信息做对比。如果相同则说明信息完整,确实是对方本人的签名(因为公钥和私钥是一对的)。
在比特币系统中,公钥(地址)用于接收比特币,而私钥则用于比特币支付时的交易签名。
在支付比特币时,比特币的所有者需要在交易中提交自己的公钥和该交易的签名。而比特币网络中所有节点可以通过所提交的公钥和签名进行验证,从而确认支付者对交易的比特币的所有权。
4)MerkleTree
Merkle树是⼀种哈希⼆叉树,它是⼀种⽤作快速归纳和校验⼤规模数据完整性的数据结构。这种⼆叉树包含加密哈希值。
在⽐特币⽹络中,Merkle树被⽤来归纳⼀个区块中的所有交易,同时⽣成整个交易集合的hash,且提供了⼀种校验区块是否存在某交易的⾼效途径。
当N个数据元素经过加密后插⼊Merkle树时,时间复杂度为log(N)【因为Merkle树可能为完全二叉树或者满二叉树。而满二叉树的高度为log(N)】就能检查出任意某数据元素是否在该树中,这使得该数据结构⾮常⾼效。
计算merkleRoot时,如果交易数量为奇数,则复制最后一个交易hash进行计算。
Merkle树的效率
交易数量 | 区块的近似大小 | 路径大小(Hash数量) | 路径大小(字节) |
---|---|---|---|
16笔交易 | 4KB | 4个Hash | 128字节 |
512笔交易 | 128KB | 9个Hash | 288字节 |
2048笔交易 | 512KB | 11个Hash | 352字节 |
65535笔交易 | 16MB | 16个Hash | 512字节 |
注:单个Hash32字节
依表可得,当区块⼤⼩由16笔交易(4KB)急剧增加⾄65,535笔交易(16MB)时,为证明交易存在的Merkle路径⻓度增⻓极其缓慢,仅仅从128字节到512字节。有了Merkle树,⼀个节点能够仅下载区块头(80字节/区块),然后通过从⼀个满节点回溯⼀条⼩的Merkle路径就能认证⼀笔交易的存在,⽽不需要存储或者传输⼤量区块链中⼤多数内容,这些内容可能有⼏个G的⼤⼩。这种不需要维护⼀条完整的区块链的节点,⼜被称作简单⽀付验证(SPV)节点,它不需要下载整个区块⽽通过Merkle路径去验证交易的存在。
Merkle树和简单⽀付验证(SPV)
Merkle树被SPV节点⼴泛使⽤。SPV节点不保存所有交易也不会下载整个区块,仅仅保存区块头。它们使⽤认证路径或者Merkle路径来验证交易存在于区块中,⽽不必下载区块中所有交易。
区块头只有80字节,而一个区块大小为1M(比特币已扩容为2M),所以SPV节点验证交易是否存在,只需要比特币区块容量的千分之一的数据就可以,大大节省容量。
例子:使用上面的merkelTree中的交易。假设SPV节点想验证交易A是否存在与区块内。
如图所示,SPV节点只需要获取该交易所在的区块头以及验证路径:即N1、N4即可。
验证步骤:
- 计算交易A的Hash得到N0
- 计算出N3的Hash = Hash(N0+N1)
- 计算merkleRoot = Hash(N3+N4)
- 比较计算的merkleRoot与区块头的merkleRoot是否相同,相同则表明该交易A存在于此区块中。
注意:SPV只能验证某交易是否存在与区块中,而无法验证该交易的UTXO是否双花,需要等待该区块后是否累积了多个区块,越多证明该交易被大多数人共识确认,越无法篡改。比特币中,6个区块就可以确认该交易基本无法篡改,篡改的机率很低,并且成本昂贵。
为什么比特币的区块大小为1M?
//For now it exists as an anti-DoS measure to avoid somebody creating a titanically huge but valid block and forcing everyone to download/store it forever.
public static final int MAX_BLOCK_SIZE = 1 * 1000 * 1000;
源码里有注释:预防dos攻击,防止有节点创建很大且有效的区块发送到比特币网络,这样大家都会去验证并广播,造成网络拥堵。
5)哪有比特币,有的是UTXO
什么是UTXO?
UTXO:unspent transaction output 未花费的交易输出。如果单看比特币,其实就是UTXO,拥有多少比特币,实质是你的UTXO集合有多少。一个交易输出就是一个UTXO。
我们再看一下交易的结构
TransactionOutput
-
txHash :交易的hash
-
Index : 交易的输出列表中的下标,从0开始。
-
Value : 转账金额,比特币最小单位为聪,1Btc = 10^8聪,转账单位也为聪
-
lockScpript:锁定脚本,通常为地址。(表示转账的比特币存储的形式)
-
脚本类型
public enum ScriptType { P2PKH(1), // pay to pubkey hash (aka pay to address)通常为此形式,支付到地址 P2PK(2), // pay to pubkey P2SH(3), // pay to script hash P2WPKH(4), // pay to witness pubkey hash P2WSH(5); // pay to witness script hash public final int id; private ScriptType(int id) { this.id = id; } }
-
交易输出表述为:转账给哪一个地址的金额,位于交易输出列表中的哪个位置。
TransactionInput
- TransactionOutpoint:表示引用了哪一个UTXO。使用之前的txHash以及index找到唯一的UTXO。
- txHash :之前交易的hash
- Index : 之前交易的输出列表中的下标,从0开始。
- unlockScpript:解锁脚本:包含公钥及签名。
- pubKey:公钥:用于验签
- Signature :签名,用于比特币网络中的矿工验证该交易的发出者身份,是否有权利花费输入中引用的UTXO。
6)比特币挖矿与共识
挖矿是增加⽐特币货币供应的⼀个过程。挖矿同时还保护着⽐特币系统的安全,防⽌欺诈交易,避免“双花” ,“双花”是指多次花费同⼀笔⽐特币。
**挖矿:指比特币网络中的节点将网络中收到的合法交易进行打包,生成区块的过程。**并且,交易列表的第一笔交易矿工会生成一个coinbase交易,即为创币交易。(此交易没有输入,只有输出,输出到自己地址作为挖矿奖励)矿⼯通过创造⼀个新区块得到的⽐特币数量⼤约每四年(或准确说是每210,000个块)减少⼀半。开始时为2009年1⽉每个区块奖励50个⽐特币,然后到2012年11⽉减半为每个区块奖励25个⽐特币。之后将在2016年的某个时刻再次减半为每个新区块奖励12.5个⽐特币。基于这个公式,⽐特币挖矿奖励以指数⽅式递减,直到2140年。届时所有的⽐特币(20,999,999.98)全部发⾏完毕。换句话说在2140年之后,不会再有新的⽐特币产⽣。现在(2020/7/02)为6.25BTC。
灵魂拷问:
比特币网络中中的节点那么多,大家都可以挖矿?而最终确定的区块只有一个或少数几个,全网这么多矿工,大家怎么都承认是你的区块被接受,我的不被接受呢?
比特币中有一个挖矿难度,这个难度可以转换为一个256bit的大数,比特币的共识为:全网的矿工都去打包区块,但有一个条件,区块的hash转换的256bit的大数一定要不大于比难度转换的才行。(也就是大家常说的计算的Hash前置的0多于目标值Hash的前置0一个意思)
protected boolean checkProofOfWork(boolean throwException) throws VerificationException {
//将难度转换为一个256bit的大数
BigInteger target = getDifficultyTargetAsInteger();
//区块hash转换为一个256bit的大数
BigInteger h = getHash().toBigInteger();
//如何区块的数大于目标的,则不合法
if (h.compareTo(target) > 0) {
// Proof of work check failed!
if (throwException)
throw new VerificationException("Hash is higher than target: " + getHashAsString() + " vs "
+ target.toString(16));
else
return false;
}
return true;
}
如何计算Hash?
void writeHeader(OutputStream stream) throws IOException {
//版本
Utils.uint32ToByteStreamLE(version, stream);
//父区块Hash
stream.write(prevBlockHash.getReversedBytes());
//交易merkleTree hash
stream.write(getMerkleRoot().getReversedBytes());
//交易时间
Utils.uint32ToByteStreamLE(time, stream);
//区块难度
Utils.uint32ToByteStreamLE(difficultyTarget, stream);
//挖矿的尝试次数
Utils.uint32ToByteStreamLE(nonce, stream);
}
可以看出,区块的hash中,变化的量有交易的merkleRoot、区块时间、区块nonce。但如果挖矿时交易列表已经确定,区块时间也是确定的,那就只有尝试更改nonce来更改区块hash,以达到在该难度下的合法区块hash。
public void solve() {
while (true) {
try {
// 工作量证明
if (checkProofOfWork(false))
return;
// 增加nonce,重新计算
setNonce(getNonce() + 1);
} catch (VerificationException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
}
可以看到比特币挖矿的过程就是寻找一个合法nonce的过程。
我们再来看一看,UTXO不是只有唯一的一个吗?为什么还会产生双花?
其实双花在比特币中对应于分叉。因为比特币网络中的节点都是P2P的对等节点,每个节点打包的交易可能不同,时间也不同,nonce也不同。最终计算的区块hash也不同。这样就会在同一高度产生多个合法区块,这就是分叉。
可能你的同一笔交易被打进了高度为101的两个区块中,分别对应于两条链。只要被打进区块中的交易,证明都是被矿工确认共识过后的合法交易,所以如果是一个作恶的用户,则可以利用比特币的分叉进行双花攻击。
比特币网络为了避免双花,引入了一个确认的机制。需要6个区块才能确认一个区块中的交易是否真正有效。(6个区块是估算的)
7)区块验证(共识)
区块验证的过程就是共识的过程:验证区块是否按照比特币协议产生的,是否都打包的合法交易,是否找到了合法nonce等。
-
每个全节点依据综合标准对每个交易进⾏独⽴验证
▷交易的语法和数据结构必须正确。 ▷输⼊(coinbase交易除外)与输出列表都不能为空。
▷交易的字节⼤⼩是⼩于 MAX_BLOCK_SIZE(当前1M) 的。 ▷每⼀个输出值,以及总量,必须在规定值的范围内 (⼩于2,100万个币,⼤于0)。 ▷交易的字节⼤⼩是⼤于或等于100的。
▷交易的字节⼤⼩最大为100kb。 ▷交易中的签名数量应⼩于签名操作数量上限。 ▷解锁脚本( scriptSig )只能够将数字压⼊栈中,并且锁定脚本( scriptPubkey )必须要符合 isStandard 的格式 (该格式将会拒绝⾮标准交易)。 ▷池中或位于主分⽀区块中的⼀个匹配交易必须是存在的。 ▷对于每⼀个输⼊,如果引⽤的输出存在于池中任何的交易,该交易将被拒绝。 ▷对于每⼀个输⼊,在主分⽀和交易池中寻找引⽤的输出交易。如果输出交易缺少任何⼀个输⼊,该交易将成为⼀个孤⽴的交易。如果与其匹配的交易还没有出现在池中,那么将被加⼊到孤⽴交易池中。 ▷对于每⼀个输⼊,如果引⽤的输出交易是⼀个coinbase输出,该输⼊必须⾄少获得 COINBASE_MATURITY (100)个确认。 ▷对于每⼀个输⼊,引⽤的输出是必须存在的,并且没有被花费。 ▷使⽤引⽤的输出交易获得输⼊值,并检查每⼀个输⼊值和总值是否在规定值的范围内 (⼩于2100万个币,⼤于0)。 ▷如果输⼊值的总和⼩于输出值的总和,交易将被中⽌。 ▷如果交易费⽤太低以⾄于⽆法进⼊⼀个空的区块,交易将被拒绝。 ▷每⼀个输⼊的解锁脚本必须依据相应输出的锁定脚本来验证。
-
通过完成⼯作量证明算法的验算。
-
**判断区块高度是否为2016的倍数,因为每2016个区块会调整难度。**如果没达到2016的倍数,只需要验证目标难度是否相等。(比特币10分钟产生一个区块,2016差不多需要2周时间)
-
如果需要调整,则需要计算前2016个区块的实际产生时间。
-
往前遍历2016个区块,找到前2016个区块的开始区块
-
计算前2016个区块产生的实际时间:当前已确认的区块时间 - 前2016个区块的开始区块的时间
-
限制实际时间在调整系数(4)之内
//获取前2016个区块的实际使用时间 int timespan = (int) (prev.getTimeSeconds() - blockIntervalAgo.getTimeSeconds()); // Limit the adjustment step. final int targetTimespan = this.getTargetTimespan(); if (timespan < targetTimespan / 4) timespan = targetTimespan / 4; if (timespan > targetTimespan * 4) timespan = targetTimespan * 4;
-
根据前2016个区块的实际使用时间计算新的目标值(难度的另一种表示方式)
newTarget = timespan * preDifficulty/targetTimespan
targetTimespan = 14 * 24 * 60 * 60 两个星期
目标值压缩为4个字节的表达方式。可以节约存储。
//难度太大会限制为创世块的默认难度(大约10分钟一个区块) if (newTarget.compareTo(this.getMaxTarget()) > 0) { log.info("Difficulty hit proof of work limit: {}", newTarget.toString(16)); newTarget = this.getMaxTarget(); }
-
比较计算的难度与区块中的目标难度是否相等。因为验证的过程跟矿工挖矿的过程是一样流程。
-
-
-
每个节点对区块链进⾏独⽴选择,在⼯作量证明机制下选择累计⼯作量最⼤的区块链
-
如果该区块是接着主链打的,则处理交易。(处理时会验证交易,验证过程就是第一步)将交易输入引用的UTXO删掉;交易的输出转换为UTXO存储。
-
如果该区块不是接着主链打的,会判断区块的工作量是否大于了主链的,如果没有大于,则只是将区块链接上。
什么叫工作量呢?
工作量被定义为:平均情况下生成一个合法区块所需的尝试次数(nonce)
/** * Returns the work represented by this block.<p> * * Work is defined as the number of tries needed to solve a block in the * average case. Consider a difficulty target that covers 5% of all possible * hash values. Then the work of the block will be 20. As the target gets * lower, the amount of work goes up. */ public BigInteger getWork() throws VerificationException { //将目标难度值转换为256bit的大数,这个数值表示该难度下的目标hash值 //(假设这个hash值覆盖了所有hash范围的5%,则工作量为20) BigInteger target = getDifficultyTargetAsInteger(); //LARGEST_HASH: 该数字比最大可表示的SHA-256哈希大1 return LARGEST_HASH.divide(target.add(BigInteger.ONE)); }
链累积的工作量计算:
/** * 构建区块存储的索引。包含了这条链从创创世块到现在的所有工作量 */ public StoredBlock build(Block block) throws VerificationException { //仅代表了该链的工作量总和(这里的this指前一个区块)每个区块都如此计算,就是一个累和 BigInteger chainWork = this.chainWork.add(block.getWork()); int height = this.height + 1; return new StoredBlock(block, chainWork, height); }
如何判断是否产生新的主链?
假设现在产生了分叉,则只需要计算出分叉链的链累积工作量,与当前主链的对比,如果大于,则新产生的链为主链,旧的主链则会作废。
其实就是最长链原则,因为链越长,累积的工作量会越大。
//判断当前链是否比主链的工作量大,如果大于,则为新主链 public boolean moreWorkThan(StoredBlock other) { return chainWork.compareTo(other.chainWork) > 0; }
-
比特币处理分叉:
-
-
上图中,区块高度为103的区块到来后,新的主链产生。这时候,会用当前主链的102,与新主链的103向前遍历,找到分叉点101区块。(注意:)
-
通过最长链原则,以6个区块为确认主链,确认主链后,分叉链需要回退交易。花掉的utxo进行增加,而新增的utxo进行删除。
注意:6个区块:判断当前区块时间是否大于等于最近11个区块的区块时间的中位数(6),所以只能回退最近6个区块内的数据,反向意思为:6个之前的无法回退,即6个区块确认主链。
-
如果区块分叉,但还没有成为最长链,只是保存了区块,并没有花费UTXO(因为可能已经花费过了)
对于新区块102,不会处理交易。因为主链已经处理过了102区块的交易(可能区块中的交易不完全一样,但也没有关系,因为可能新区块102中的新交易已经被主链的103区块打包并处理了)
⽐特币将区块间隔设计为10分钟,是在更快速的交易确认和更低的分叉概率间作出的妥协。更短的区块产⽣间隔会让交易清算更快地完成,也会导致更加频繁地区块链分叉。
什么叫51%攻击?
大家可能也经常听到,比特币51%的概念。看上面分叉图。如果有人掌握了全网51%的算力,则就可以比任何人都先计算出合法nonce,所以就可以连续接着102的新区块打区块,当下面这条链的长度长于主链,那就是一条新的主链。毕竟你这么厉害,都控制了51%的算力,那我们就跟着你这条链玩吧,你就是主链。
分叉的总类?
- 软分叉:上面说的情况都是软分叉的情况。因为可能有多个矿工在同一时间找到合法的nonce,加上网络原因等。
- 硬分叉:指强制升级,可能重大bug被发现,也或者被黑客攻击,都可以使用硬分叉来修复。但需要全网很多矿工都同步升级。
什么叫孤儿区块?
比如现在来了一个105高度的区块,使用此区块的preBlockHash无法找到父区块,则此区块就称为孤儿区块。
当收到一个区块为孤儿区块后,会将其存入孤儿区块池中。并尝试遍历孤儿区块池中的区块,看是否能链接到主链上。因为处于分布式网络中,每个节点的网络情况、同步等有差异,可能节点会先收到高度更高的区块,然后再收到高度低一点的区块,然后再与本地的链高度链接上。
为什么coinbase中的UTXO需要100个确认才能使用呢?
因为如果使用了coinbase中的UTXO的交易被打包进入了两个分叉的区块中。上面说过,重新切换主链时,旧主链的交易需要回退,产生的UTXO需要删除,使用了的UTXO需要新增。所以,导致新主链中引用了coinbase中UTXO的交易无效,因为已经没有这个UTXO了。这样就会导致区块验证不通过,主链无法重新切换。所以比特币假设没有不可能有这么长远的分叉,6个确认主链已经小于100个确认coinbase。
总结
此篇略说了一些比特币相关的技术知识,以及比特币的一些原理。文中有错误之处,敬请各位指出。也欢迎大家评论区一起交流学习。
参考资料
《精通比特币》强烈推荐。
bitcoinJ源码
本文使用 mdnice 排版
来源:oschina
链接:https://my.oschina.net/u/4385718/blog/4336735