区块链学习(1)

从事C++服务器开发六年多了,主要是做并发服务器和游戏相关开发,区块链技术新兴起,自己也是很感兴趣,我是零基础学区块链的,给自己设定了一个规划,先读一读区块链相关的基础和概念,以及基本算法,然后用成熟的引擎做一个demo,接下来不断深入学习。

什么是区块链?

一两句话很难解释清楚,至少我自己还不能概括的很全面。我自己的理解是区块链技术包含了很多功能,如点对点传输,分布式数据存储,利用加密和共识算法实现数据的统一。区块链是多个技术的合理应用和创新,我觉得应该在以后的学习中不断去理解。

什么是比特币?

比特币是区块链比较成熟的应用,比特币依赖于区块链技术。

区块链的基本构成

区块链由一个个区块按照规律链接构成,每个区块中包含了很多交易。区块链的形成过程:交易是签过名的数据结构,该数据结构会在区块链网络中广播,并且被收集到区块中,然后区块会在区块链网络中广播,一个区块引用上一个区块从而形成区块链。区块链包括成千上万个区块, 而一个区块内又包含一个或多个交易, 上下关联的交易组成了一个交易链, 一个交易链内部可能又包含了多个交易。

4 比特币地址

比特币地址通常代表收款方,代表一对公钥和私钥的所有者,也可以代表其他。比特币地址是由数字和字母组成的字符串,由公钥生成的比特币地址以数字“1”开头。比特币地址有公钥经过Hash函数生成

1

交易的原理

交易实质上是包含一组输入列表和输出列表的数据结构,交易包含的结构如下:
2
一个交易的输入和输出样式:
3
Input表示交易的输入,一个交易的输入引用的是另一个交易的输出,该交易的输入从交易
f5d8ee39a430901c91a5917b9f2dc19d6d1a0e9cea205b009ca73dd04470b9a6的0号输出中导入了50个比特币, 然后该输出发送50个比特币到一个比特币地址的公钥Hash值,404371705fa9bd789a2fcd52d2c580b65d35549d。同样道理,如果接受者想花掉50比特币,需要用output作为自己交易的输入。输入是对其他交易的输出的引用,一个交易可能有很多输入,Previous tx是以前交易的Hash值,Index是被引用交易的特定输出号,
ScriptSig是脚本的签名, Scriptpubkey是脚本的公钥,公钥属于交易输出的收款人, 并且表明交易创建者允许收款人获得的输出金额; 另一个部分是ECDSA签名, 是通过对交易的Hash值进行ECDSA签名而得到的。 签名和公钥一起, 证明原地址的真正所有者创建了该支付交易。
Value表示支付的金额,1BTC=100000000聪。

找零问题

当需要支付的金额小于可用余额时, 在交易信息中必须告诉比特币网络零钱将要被发送至哪个地址,即“找零地址”。 找零地址可能是也可能不是原先的发送地址。地址A发起付款到地址B, 但此时将找零地址更改为新生成的地址C,此时能保证交易的安全性。
如果找零地址不是C而是A,很多情况会被人追踪到一笔交易的流程。
4.png
上图中交易流程很难被推断出来。

区块和区块链

数据会以文件的形式被永久记录, 我们称这些文件为区块。 一个区块是一些或所有最新比特币交易的记录集, 且未被其他先前的区块记录。 新区块会被加入到记录的最后, 一旦写上, 就再也不能改变或删除。
区块结构:
5.png

区块头包含6个数据,如下
6.png
7.png

hashPrevBlock值为前一个块的256位Hash值,也就是前一个块的hashMerkleRoot。该字段使得各个区块之间可以连接起来,形成一个巨大的“链条”。 每个区块都必须要指向前一个区块, 否则无法通过验证。 这个区块链条会一直追溯到源头, 也就是指向创世区块。 创世区块的hashPrevBlock的值为零或为空。 在区块头中, 随机数Nonce对每个块唯一。求Nonce没有固定的算法,Nonce有很多值,找到一个满足条件即可。所以要求不断尝试找到合适的Nonce为止,这个过程就是挖矿。
区块内包含许多交易, 它们通过Merkle根节点间接被散列, 以保证矿工能及时追踪一个正在打包的区块内交易的变化情况。 一旦生成Merkle根节点, 那么对包含一个交易的区块做散列所花的时间,对包含1万个交易的区块做散列所花的时间是一样的。目标Hash值的压缩格式是一个特殊的浮点编码类型, 首字节是指数 , 后3个字节是尾数, 它能表示256位的数值。
一个区块头的SHA-256(一种单向函数的算法, 可形成长度为256位的串) 值必定要小于或等于目标Hash值, 该区块才能被网络所接受。 目标Hash值越低, 产生一个新区块的难度就越大。Merkle树是Hash的二叉树。 在比特币中会两次使用SHA-256算法来生成Merkle树, 如果叶子个数为奇数, 则要重复计算最后一个叶子的两次SHA-256值, 以达到偶数叶子节点的要求。想象有3个交易, a、 b、 c, 那么Merkle根的生成过程如下所示:
计算a交易的hash值

d1 = dhash(a)
d2 = dhash(b)
d3 = dhash(c)
由于只有3个元素, 是奇数, 因而将最后一个元素重算一次
d4 = dhash(c)
接下来将上面产生的hash值两两计算
d5 = dhash(d1 concat d2)
d6 = dhash(d3 concat d4)
d7 = dhash(d5 concat d6)

这里的d7就是以上三个交易的Merkle根。 需要注意的是, Merkle树的Hash值是小头位序。

区块链原理和分叉

8.png
对于区块链中的任何区块来说, 只有一条通向创世块的路径。 然而, 从创世块出发, 却可能有分叉。 当两个区块产生的时间仅相差几秒时, 可能会产生包含一个区块的分叉。 当出现以上现象时, 矿工节点会根据收到区块的时间, 在先收到的区块的基础上继续挖矿。 哪个区块的后续区块先出现, 那么这个区块就会被包括进主链, 因为这条块链更长。短区块链(或有效区块链) 中的区块没有作用, 当比特币客户端转向另一个长区块链时, 短区块链中所有有效的交易都将被重新加入到交易队列池中, 并被包括到另一个区块中。 短区块链中的区块收益不会在长链中出现, 因而这些收益实际上是丢失了, 这就是比特币网络设定100个区块成熟时间的原因。

挖矿原理

比特币的挖矿和节点软件是基于对等网络、 数字签名来发起和验证交易的。 节点向网络广播交易, 这些广播出来的交易需要经过矿工的验证, 矿工们会用自己的工作证明结果来表达确认, 确认后的交易会被打包到数据块中, 数据块会串起来形成连续的数据块链。
每一个比特币的节点都会收集所有尚未确认的交易, 并且会将其归集到一个数据块中, 这个数据块将和前面一个数据块集成在一起。 矿工节点会附加一个随机调整数, 并计算前一个数据块的SHA-256 Hash运算值。 挖矿节点不断进行重复尝试, 直到它找到的随机调整数使得产生的
Hash值低于某个特定的目标为止。 由于Hash运算是不可逆的, 因此寻找到符合要求的随机调整数将会非常困难, 因此需要一个可以预计总次数的不断试错的过程。 这时, 工作量证明机制就发挥作用了。 当一个节点找到了符合要求的解, 那么它就可以向全网广播自己的结果。 其他节点就可以接收这个新解出来的数据块, 并检验其是否符合规则。 只要其他节点通过计算Hash值发现其确实满足要求(比特币要求的运算目标) ,那么该数据块就是有效的, 其他的节点就会接受该数据块, 并将其附加在自己已有的链条之后.

Nonce随机数通常都不会相同, 但是它以严格的线性方式在增长,从0开始, 每次执行散列时都会增长, 当Nonce溢出时(此事经常发生) , 挖矿交易的extraNonce项就会增长, 其将改变Merkle树的根节点.

挖矿难度

挖矿难度是对挖矿困难程度的度量, 即指计算符合给定目标的一个Hash值的困难程度。 比特币网络有一个全局的区块难度, 有效的区域必须有一个Hash值, 该Hash值必须小于给定的目标Hash值。

难度计算公式如下:
diff = diff_1_target / target
目标值是一个很大的数字,这里出现了一个 diff_1_target,这是常数,是一个很大的数字,这个数字也被称作是矿池难度,即矿池挖矿时的最大难度。这个最大难度值是标记为0x1d00ffff的数,这个标记是压缩标记,它的实际值是:

1
0x00ffff * 2**(8*(0x1d - 3)) = 0x00000000FFFF00000000000000000000000000000000000000000000000000。

计算时,后面三个字节作为底,前面一个字节1d表示的是次方数,最终得出上面这个数字,挖矿时矿池也可以保留的尾数,即
0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

而比特币的挖矿难度,根据上面的公式可知,它和diff_1_target及当前网络目标值(target)有关,将diff_1_target值代入,得到:
diff = 0x1d00ffff / target
在调整难度的时候,只需要调整target大小即可,target越小,难度越大,反之,难度越小。矿池一般用后面全是FF的值来代表diff_1_target,计算出来的难度就叫做矿池难度pdiff,如果用后面全是00的值来代表diff_1_target,那么计算出来的难度值是比特币客户端难度bdiff。这里只是说明,它们有两个值,代表不同计算方式,实际上,它们计算出来的难度并不会相差很远。
网络调整难度的目的,是为了调整出块的速度保持在平均10分钟1个块,每2016个块作为一个周期调整,这样刚好2周作为一个周期,如果这2016个块中,平均出块速率快过10分钟1个块,那么难度将会增大到,维持这个难度的情况下,满足10分钟出一个块的水平。什么时候调整难度呢?由于2016的周期从来没有变过,那么该周期内还剩余的区块数量是可以计算出来的:
该周期剩余区块数量 = 2016 - (当前区块高度 % 2016)
以当前最新一个块 482017 为例
当前高度 % 2016 = 482017 % 2016 = 193
该周期剩余区块数量 = 2016 - 193 = 1823
也即该周期内才出193个区块,得等到1823个块以后才会调整难度,对于BCC来说,也是一样的。

挖矿收益计算

挖矿时,计算出来的区块哈希值,是要小于当前target值的,这个哈希值是一个范围很大的值(从0到(2^256)-1),只有靠矿机的暴力破解,才能算出这个值。
diff_1_target,即0x00000000FFFF0000000000000000000000000000000000000000000000000000 , FFFF后面有26个字节,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
即8*26 = 208位,所以diff_1_target又可以写成 0xffff*(2**208)。
如果当前难度是D的话,那么根据上面我们讲过的公式
diff = diff_1_target / target
那么
target = (0xffff * (2 ** 208)) / D
需要要搜索出这个target值,需要计算的哈希次数是
2 ** 256 / target
将D移到左边,得到
D * (2 ** 256) / (0xffff * (2 ** 208))
将2 ** 208移到左边,得到
D * (2 ** 256) / (2 ** 208) / 0xffff
化简得到
D * (2 ** 48) / 0xffff
也即10分钟(600秒)内要计算这么多次,才能得到一个块,那么平均每秒就是:
D * (2 ** 48) / 0xffff / 600
将0xffff写成十进制是2 ** 16
D * (2 ** 48) / (2 ** 16) / 600
化简得到
D * (2 ** 32) / 600
假设现在全网难度是888171856257,那么平均要计算 6357781793085713285次哈希运算才能得到一个块。我们不妨用现在的算力验证一下,现在的全网算力是5.68 EH/s, 大概接近这个值。
难度和算力的关系我们已经看出来了,难度越大,需要的算力就越大。那么以单位为1Th/s的云合约标准算力来计算,一天的收益能够达到多少呢?
一天能够计算的哈希次数 = 1T * 86400
假设当前难度为D,那么
收益 = 1T * 86400 / D / ( 2 * 32) * 600* 块收益

脚本系统

脚本操作码很多,自己去查查吧。下面简述下脚本工作原理。
以下为三个交易的关系图,一个交易的输出是下一个交易的输入。
9.png
交易的关系数据图:
10.png
交易a hash值为
11.png
交易b hash值为
12.png
交易a的输出脚本, 若干个脚本指令和转账接收方的公钥Hash
13.png
交易b的输入脚本, 这么一长串只是两个元素, 签名和公钥
14.png
首先执行的是输入脚本。 因为脚本是从左向右执行的, 那么先入栈的是签名, 随后是公钥。 接着, 执行的是输出脚本。 从左向右执行, 第
一个指令是OP_DUP——复制栈顶元素
15.png
OP_HASH160用于计算栈顶元素Hash, 得到pubkeyhash
16.png
然后将输出脚本中的公钥Hash入栈, 为了与前面计算中所得到的Hash区别开来, 这里称它为pubkeyhash’
17.png
OP_EQUALVERIFY则会检查栈顶前两个元素是否相等, 如果相等则继续执行, 否则中断执行, 返回失败
18.png
OP_CHECKSIG使用栈顶前两个元素执行签名校验操作, 如果相等, 则返回成功, 否则返回失败
19.png
没看懂?再来一遍
以著名的Pizza Transaction为例,来验证一个交易是否是有效的
在交易cca75078…4d79中,唯一的TxIn输入提供的sigScript是:

1
2
3
4
5
6
7
8b4830450221009908144ca6539e09512b9295c8
a27050d478fbb96f8addbc3d075544dc41328702
201aa528be2b907d316d2da068dd9eb1e23243d9
7e444d59290d2fddf25269ee0e0141042e930f39
ba62c6534ee98ed20ca98959d34aa9e057cda01c
fd422c6bab3667b76426529382c23f42b9b08d78
32d4fee1d6b437a8526e59667ce9c4e9dcebcabb

该sigScript实际上由两部分构成:
签名:30450221…ee0e01(71字节+1字节签名类型),实际签名是去掉最后一个字节01的30450221…ee0e,签名类型是SIGHASH_ALL(0x01)。
公钥:042e930f…cabb(65字节)
为了验证该交易是否有效,我们首先要根据TxIn所声明的Previous Output Hash:a1075db5…d48d和索引0找到上一笔交易的输出a1075db5…d48d。
这笔交易输出的脚本是:
1976a91446af3fb481837fadbb421727f9959c2d32a3682988ac
比特币的脚本由一系列指令和数据构成,每个指令占用一个字节,数据由数据头部的长度决定。上述二进制脚本翻译后的比特币指令如下
OP_DUP OP_HASH160 46af3fb4…6829 OP_EQUALVERIFY OP_CHECKSIG
现在,我们有了签名,公钥和脚本:
sig: 30450221…ee0e01
pubkey: 042e930f…cabb
script: OP_DUP OP_HASH160 46af3fb4…6829 OP_EQUALVERIFY OP_CHECKSIG
就可以运行这个脚本来验证交易是否有效。
比特币脚本被设计成以栈来运行的虚拟机指令,它只有有限的几种指令,并且故意被设计成没有循环、条件跳转,所以,比特币脚本不是图灵完备的语言。
比特币脚本的执行非常简单。我们首先要准备一个空栈,然后把签名和公钥入栈:
20.png
紧接着,我们就可以执行TxOut的脚本:
OP_DUP OP_HASH160 46af3fb4…6829 OP_EQUALVERIFY OP_CHECKSIG
首先执行OP_DUP,这条指令把栈顶的元素复制一份,所以结果变成:
21.png
紧接着执行OP_HASH160,它对栈顶元素计算SHA256/RipeMD160,实际上是计算公钥Hash,所以运行结果变成
22.png
接下来的指令实际上是一个数据,我们直接把数据入栈:
23.png
然后,执行OP_EQUALVERIFY,这条指令会比较栈顶的两个元素是否相等,如果不等,整个脚本就执行失败了,如果相等,脚本会继续执行,所以运行结果变成
24.png
最后,执行指令OP_CHECKSIG,这条指令会验证签名.
其实脚本的验证过程就是根据自己的输入将签名和公钥入栈,根据上一个交易的输出脚本指令依次执行,判断结果是否和上一个交易要求的公钥地址匹配。换言之,谁能根据我输出脚本的规则计算出符合我要求的公钥地址,谁就能使用这笔交易。

由于引入了脚本,我们可以看到,比特币实际上通过编程脚本实现了一个严格以计算机程序验证为基础的数字货币所有权的转移机制。由于计算机程序的可扩展性,比特币支付其实并不限定在必须支付给某一个公钥地址。利用脚本,我们可以构造出各种支付条件,例如,多重签名验证条件:

2 3 OP_CHECKMULTISIGN
这种提供多个公钥地址,并且需要多个签名验证的多重签名脚本,允许在M个签名种至少给出N个签名即可使用。上述脚本允许提供3个公钥地址中的任意两个有效签名。

当我们把比特币托管在某个第三方的在线钱包中时,就可以使用多重签名来保证只有自己和第三方钱包共同签名后才可动用输出,这样保证了黑客在攻击了第三方钱包后也无法花掉用户的比特币。

通过OP_CHECKLOCKTIMEVERIFY,我们可以指定一个交易的锁定时间,在此之前,该交易输出无法被花掉。这个指令其实实现了支付宝的7天资金锁定然后再支付给卖家的功能。

还有一些交易并没有指定一个公钥Hash,例如,这个交易的脚本如下:

OP_HASH256 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000 OP_EQUAL
它的意思是说,谁能够提供一个数据,它的SHA256是6fe28c0a…0000,谁就可以花费这笔交易

我的公众号:
8.jpg