上一篇咱们已经知道了什么是区块链,此篇说一下区块链的第一个应用——比特币。其实先有比特币,后有的区块链。比特币是中本聪提出的一种P2P去中心化的支付系统,其底层原理为:去中心化、数据不可篡改、可溯源;数据以区块的形式保存,而且像链条同样链接起来,很形象地称为区块链。比特币就是一条上文说过的公有链。web
上一节咱们说过,帐本以交易的形式记录,那交易是如何存储在区块链上呢?算法
交易存储在区块之中。区块分为区块头以及区块体,区块头包含区块的概要信息,区块体包含交易信息。安全
哈希算法就是将任意长度的数据转变为一个定长的数据(也叫哈希或者摘要)。常见的哈希算法有CRC3二、MD五、SHA一、SHA二、SHA三、Hmac等。网络
哈希算法具备如下特色:数据结构
单向不可逆性:想要经过哈希值逆推出原来的数据,只有经过暴力破解的方法实现,但这几乎没法作到,可能量子计算机出来后就能够破解了。app
肯定性:若是hash值不一样, 能够肯定原数据必定不一样。若是相同,则还须要进一步判断,由于可能hash碰撞了。编辑器
雪崩效应:原始数据任何微小的变更都会致使哈希值彻底不同分布式
不变性:同一个数据,屡次hash结果相同。ide
非对称加密有公钥和私钥两个概念,私钥本身拥有,不能给别人,公钥公开。根据应用的不一样,咱们能够选择使用不一样的密钥加密:post
私钥签名:使用私钥签名(加密),公钥验签(解密)。用于让公钥全部者验证私钥全部者的身份而且用来防止私钥全部者发布的内容被篡改,可是不用来保证内容不被他人得到。(内容自己非加密的,别人能够得到信息但没法篡改)
公钥加密:用公钥加密,私钥解密。公钥全部者可用于加密发布的信息,这个信息可能被他人篡改,可是没法被他人得到。
举例:好比小黑持有公钥,将 “我爱你” 加密后的消息 “xxx” 发送给喜欢的人小花(小花持有私钥),但小灰也喜欢小花而且也有公钥,小灰劫持了小黑的消息(消息内容已加密,小灰也不知道内容是什么),并将 “我恨你” 加密为 “xxx”组装为小黑的请求发送给小花,小花收到小黑的请求后,用私钥解密,获得内容为:“我恨你”。这下小黑就凉凉,心机boy小灰就成功了。
签名与加密能够混合使用,提升安全性与隐私性。
做用:保证信息传输的完整性、发送者的身份验证(鉴权)
原理:使用非对称加密技术及hash技术,非对称加密生成一对公私钥对,私钥用于签名,公钥用于验签。签名的对象为内容的hash。
为何签名不直接签署内容,而是签署hash呢?
答:效率问题,若是你的原内容很大,直接对原内容签名效率很低,并且签名的结果也相对很大,传输也慢。
在比特币系统中,公钥(地址)用于接收比特币,而私钥则用于比特币支付时的交易签名。
在支付比特币时,比特币的全部者须要在交易中提交本身的公钥和该交易的签名。而比特币网络中全部节点能够经过所提交的公钥和签名进行验证,从而确认支付者对交易的比特币的全部权。
Merkle树是⼀种哈希⼆叉树,它是⼀种⽤做快速概括和校验⼤规模数据完整性的数据结构。这种⼆叉树包含加密哈希值。
在⽐特币⽹络中,Merkle树被⽤来概括⼀个区块中的全部交易,同时⽣成整个交易集合的hash,且提供了⼀种校验区块是否存在某交易的⾼效途径。
当N个数据元素通过加密后插⼊Merkle树时,时间复杂度为log(N)【由于Merkle树可能为彻底二叉树或者满二叉树。而满二叉树的高度为log(N)】就能检查出任意某数据元素是否在该树中,这使得该数据结构⾮常⾼效。
计算merkleRoot时,若是交易数量为奇数,则复制最后一个交易hash进行计算。
交易数量 | 区块的近似大小 | 路径大小(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节点⼴泛使⽤。SPV节点不保存全部交易也不会下载整个区块,仅仅保存区块头。它们使⽤认证路径或者Merkle路径来验证交易存在于区块中,⽽没必要下载区块中全部交易。
区块头只有80字节,而一个区块大小为1M(比特币已扩容为2M),因此SPV节点验证交易是否存在,只须要比特币区块容量的千分之一的数据就能够,大大节省容量。
例子:使用上面的merkelTree中的交易。假设SPV节点想验证交易A是否存在与区块内。
如图所示,SPV节点只须要获取该交易所在的区块头以及验证路径:即N一、N4便可。
注意: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攻击,防止有节点建立很大且有效的区块发送到比特币网络,这样你们都会去验证并广播,形成网络拥堵。
UTXO:unspent transaction output 未花费的交易输出。若是单看比特币,其实就是UTXO,拥有多少比特币,实质是你的UTXO集合有多少。一个交易输出就是一个UTXO。
咱们再看一下交易的结构
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;
}
}
交易输出表述为:转帐给哪个地址的金额,位于交易输出列表中的哪一个位置。
挖矿是增长⽐特币货币供应的⼀个过程。挖矿同时还保护着⽐特币系统的安全,防⽌欺诈交易,避免“双花” ,“双花”是指屡次花费同⼀笔⽐特币。
**挖矿:指比特币网络中的节点将网络中收到的合法交易进行打包,生成区块的过程。**而且,交易列表的第一笔交易矿工会生成一个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个区块是估算的)
区块验证的过程就是共识的过程:验证区块是否按照比特币协议产生的,是否都打包的合法交易,是否找到了合法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%的算力,那咱们就跟着你这条链玩吧,你就是主链。
分叉的总类?
什么叫孤儿区块?
好比如今来了一个105高度的区块,使用此区块的preBlockHash没法找到父区块,则此区块就称为孤儿区块。
当收到一个区块为孤儿区块后,会将其存入孤儿区块池中。并尝试遍历孤儿区块池中的区块,看是否能连接到主链上。由于处于分布式网络中,每一个节点的网络状况、同步等有差别,可能节点会先收到高度更高的区块,而后再收到高度低一点的区块,而后再与本地的链高度连接上。
为何coinbase中的UTXO须要100个确认才能使用呢?
由于若是使用了coinbase中的UTXO的交易被打包进入了两个分叉的区块中。上面说过,从新切换主链时,旧主链的交易须要回退,产生的UTXO须要删除,使用了的UTXO须要新增。因此,致使新主链中引用了coinbase中UTXO的交易无效,由于已经没有这个UTXO了。这样就会致使区块验证不经过,主链没法从新切换。因此比特币假设没有不可能有这么长远的分叉,6个确认主链已经小于100个确认coinbase。
此篇略说了一些比特币相关的技术知识,以及比特币的一些原理。文中有错误之处,敬请各位指出。也欢迎你们评论区一块儿交流学习。
参考资料
《精通比特币》强烈推荐。
bitcoinJ源码
本文使用 mdnice 排版