区块链期末复习
在以太坊中先挖矿还是先执行智能合约:先执行智能合约,在以太坊中挖矿的本质仍然是寻找一个Nonce值,使得区块头的哈希值小于难度目标,但是在以太坊的区块中包括三大树,状态树,交易树,收据树。矿工如果没有收到汽油费能不能直接不验证节点:不能,只有验证节点后执行所有的交易后更新自己本地的三大树才能继续挖矿,如果不更新三大树,与其他的节点不同,其他节点会认为该节点的挖矿产物是错误的。对于收到的交易,校验是
声明:仅供学习使用,不挖矿,不用作商业用途
区块链
一个简单的P2PKH交易是如何构建的?交易的输出脚本限制了解锁者必须提供私钥签名和公钥才能解锁
一笔交易如何成为区块链上的一部分?
广播+挖矿节点验证,添加到区块+经过六次确认,确保被整个网络接受
关于挖矿
挖矿并不在于创造新的比特币,而是一种激励机制,巩固去中心化的安全。经过这种机制,交易被验证/清算。
去中心化共识机制是由所有网络节点相互作用形成:
每个节点选择区块链都选择累积工作量最大的那条;节点收到区块验证后,会将其组装到累计工作量最大的链上。一般情况下,新区块延长的是主链,新区块的“父区块”存在于主链中;有时,父区块不在主链上而是在备用链上,如果备用链比主链积累了更多的难度,那么节点将把新区块组装到备用链上;还有一种情况是在现有的区块链中没有发现父区块,新区块将被放置孤块池中,直到其父区块找到再组装到区块链上
每个全节点依据综合标准对交易验证;对于收到的交易,校验是有一套标准的,每一个收到交易的比特币节点会验证该交易,确保只有有效的交易才会在网络中传播而无效的交易被丢弃。另外,每个节点在全网广播前会按照接收时的顺序,为有效的新交易建立一个验证池;
每个节点独立地对新区块校验并组装进区块链。
对于区块的验证也有一标准,每个节点收到一个区块先独立验证每个新区块然后再发布到网络上进行传播。检验标准如下:数据结构合法,区块头的哈希值小于目标难度,区块时间戳小于验证时刻未来两小时(允许时间错误),区块大小在限制范围内,第一个交易是coinbase交易
通过工作量证明算法验算,挖矿节点将交易记录打包进新区块;验证交易后比特币节点将交易添加到自己的内存池中。注意,内存池中一定会存放未被确认的交易,因为挖矿节点一旦收到了正在挖矿的区块,那么会立即停止挖矿,并开始挖下一个区块。这时会移出所有在内存池中的已经被确认的交易
创币交易(coinbase交易),是由挖矿节点构造并用来奖励矿工。创币交易没有输入,不消耗UTXO,只有一个输出,支付到矿工的地址
矿工们需要为区块构建区块头的信息包括:版本号,父区块哈希值,Merkle根,时间戳,Target,和Nonce;构建Merkle树的过程:创币交易作为首个交易,其余的交易添至其后,树中必须包含偶数个叶子节点,如果不够就复制最后一个交易。然后两个相邻的交易哈希拼接成字符串,做哈希运算。以此类推得到树根;Target表示难度目标值,是所需满足的工作量证明难度,由该值可以计算出求工作量证明过程中必须小于的目标值
工作量证明:计算整个区块头的哈希值,要求产生小于目标的哈希值,更高的目标就意味着找到哈希值不难,而更低的目标意味着找到哈希值更难,目标与难度成反比
整个区块链的难度是如何调整的:因为区块是10分钟产生一个,而经过2016个区块就会调整一次难度;所以难度的调整就是比较最新2016个区块的花费时长和20160分钟的大小,然后动态调整,注意:Bitcorn Core客户端是局域之前的2015个块进行调整,导致重定向偏差向高难度提高0.05%
随机数升位方案:Nonce值中的随机数随着挖矿设备的升级已经无法满足要求,于是使用coinbase交易作为额外的随机数来源,因为coninbase脚本可以存储2-100字节的数据
关于Merkle树
是一种验证交易是否存在的措施,对于一个N个数据元素的树需要2log2(N);Merkle树构建过程使用的哈希运算是双SHA256,即交易A的哈希值HA=SHA256(SHA256( TxA ))。最后计算的树根长度为32字节
认证一个交易是否存在,是从特定交易出发建立一条到根的认证路径。如果能够成功构建就说明交易存在
关于共识攻击
共识攻击只能影响整个区块链的未来的共识或者过去几个区块的共识;典型的代表:基于“51%”算力的双重支付攻击和阻止特定的交易
分叉/双重支付攻击:攻击者不承认最近的某个交易,并在这个交易之前重构了新的块,在新块的基础上计算了新的区块,这样就生成新的分叉(而且分叉链比主链更长),继而完成双重支付;注意:双重支付只能在攻击者所拥有的钱包产生的交易上进行,因为只有攻击者才拥有一个合法的签名地址
拒绝为特定的比特币地址提供服务:攻击者可以另外创建区块,然后造成分叉,在新区块中将特定比特币地址的交易移除
算力达到30%即可发起共识攻击,那怎么解决矿池的管理人员发起的恶意的共识攻击:P2P矿池
关于分叉
硬分叉:由于错误或者故意改变共识规则使得前后不兼容造成的,使用旧规则的节点没有升级,会拒绝新规则下产生的区块,一般而言,旧规则下的节点都不会传播这种区块,因为在验证阶段就认为非法而丢弃了
软分叉:增加向前兼容的共识规则,新规则在旧规则下仍然有效。比如隔离见证
关于比特币地址
由公钥生成的比特币地址以“1”开头,A = RIPEMD160(SHA256(K))
Base58Check编码过程:CheckSum = SHA256(SHA256( Prefix + Data ))
P2SH地址产生过程,Base58Check(RIPEMD160(SHA256( script )))
关于钱包
比特币钱包只包含密钥/公钥的密钥链,不包含比特币,比特币被记录在区块链上。
钱包分为非确定性钱包与确定性钱包,确定性钱包中所有的密钥都是由种子派生出来的。只要有原始种子就能恢复所有密钥。
助记词是表示编码随机数的一组有序的英文单词,用于生成确定性钱包的种子。
利用助记词生成根种子——主私钥m——主公钥M——主链码C的过程;从主私钥m到主公钥需要使用标准椭圆曲线
利用父公钥,主链码C和索引号生成子私钥的过程
关于脚本语言
脚本语言是基于堆栈的执行语言,图灵非完备性是指没有循环或复杂的流程控制;五状态验证,是指脚本执行的前后都没有状态;
锁定脚本是指设置在输出上的花费条件;解锁脚本是指解决或满足脚本放置在输出上的条件;
堆栈顶部结果显示为True或任何非零值,或者脚本执行后堆栈为空,即交易有效;
若堆栈顶部显示为Flase,或脚本执行被操作码明确禁止叫做交易无效;
关于高级脚本和交易
关于多重签名,一个标准的M/N锁定脚本的格式
M <Public Key 1> <Public Key 2> ... <Public Key N> N CHECKMULTISIG
一个典型的2/3锁定脚本的格式
2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG
对应的解锁脚本,需要提供两个数字签名
<Signature B> <Signature C>
注意由于检查多签的过程中会出现bug,CHECKMULTISIG会多弹出一个项目,为了防止堆栈错误,所以在解锁脚本中需要再添加一个额外的值(所有涉及到多签的CheckMultiSig的解锁脚本构造都需要在签名多添加一个值)
0 <Signature B> <Signature C>
关于P2SH,是为了解决多重签名的锁定脚本过长的问题,将复杂的锁定脚本转换成加密哈希。解锁时,不仅需要提供解锁脚本(数字签名)和与加密哈希匹配的脚本(兑换脚本);将复杂性从发送者转移到接收者。
工作过程如下:比如原来的一个锁定脚本是一个多重签名脚本
2 <Key1> <Key2> <Key3> <Key4> <Key5> 5 CHECKMULTISIG
对整个锁定脚本的多重签名采用SHA256哈希算法和RIPEMD160算法,转换成20字节的加密哈希
54c557e07dde5bb6cb791c7a540e0a4796f5e97e
锁定脚本就转换成了
HASH160 54c557e07dde5bb6cb791c7a540e0a4796f5e97e EQUAL
那么需要花费这笔交易就需要提供私钥签名,还有与加密哈希匹配的多重签名脚本
<Sig1> <Sig2> <2 PK1 PK2 PK3 PK4 PK5 5 CHECKMULTISIG>
验证过程就是将接收者提供的兑换脚本(即多重签名脚本)与锁定脚本中的哈希值是否匹配
<2 PK1 PK2 PK3 PK4 PK5 5 CHECKMULTISIG> HASH160 <redeem scriptHash> EQUAL
如果匹配成功,那么就匹配接收者提供的数字签名去解锁兑换脚本
<Sig1> <Sig2> 2 PK1 PK2 PK3 PK4 PK5 5 CHECKMULTISIG
解锁脚本 | 兑换脚本
注意:P2SH地址是以3开头的,这是因为加密过程为:Base58Check(RIPEMD160(SHA256( script ))),RIPEMD加密后的哈希值是以5开头的,经过Base58编码后为3开头
P2SH的优点:①将复杂脚本转换成哈希值,交易代码变短
②脚本编译成地址,不需要复杂工程即可执行
③构建脚本负担转换到接收者,存储负担转移到接收者
关于时间锁
时间锁:对交易或输出进行限制,规定了交易在指定时间之前无效。锁定时间,它定义了交易可以在网络上传输或添加到区块链中的最早时间;在大多数交易中,锁定时间为0代表着立即传播和执行。如果nLockTime低于5亿,则nLockTime就被解释为区块高度,在指定的区块高度之前交易无效,不会被传播也不会被添加到区块链上;如果nLockTime大于等于5亿,则被解释为纪元时间戳之后的秒数,规定交易在指定的时间之前无效,该交易由发起系统持有交易即使被发布出来,其他节点也会拒绝且不会传输
最初的时间锁是对交易的锁定,即没有到规定的时间之前,接收方无法花费。但是发送方却可以在这段时间创建另外一个交易把UTXO花费出去;所以解决的策略是:锁定UTXO,即检验锁定时间验证CLTV
CLTV是基于输出的时间锁,怎么实现对UTXO进行锁定的:是在输出的兑换脚本中添加CTLV操作码,从而限制输出,只能在规定的时间后花费
<now + 3 months> CHECKLOCKTIMEVERIFY DROP DUP HASH160 <public Key Hash> EQUALVERIFY CHECKSIG
接收方想要解锁这个交易,构造的解锁脚本中设置的nLockTime必须大于等于发送方设置的nLockTime
CLTV并不替换nLockTime,nLockTime是在规定时间或区块高度后交易才被发送到区块链上,而CLTV是在nLockTime的基础上对UTXO锁定,这样发送者也无法在这段时间花费这笔UTXO
相对时间锁:可以允许多个相互依赖的交易被下链处理
关于流量控制脚本
比特币脚本,条件是运行在IF操作码之前
condition
IF
code to run when condition is true
ELSE
code to run when condition is false
ENDIF
code to run in either case
第二种是任何以VERIFY结尾的操作码,只要评估的条件不为True,脚本的执行就将被立即终止,并且交易被视为无效;这个主要是起到保护条款的作用;比如普通的EQUAL会在堆栈上留下True或者False,而EqualVerify就不会留下任何的东西,以VERIFY后缀结尾的操作码都不会在堆栈上留下任何东西
比特币脚本的嵌套IF子句,如下所示;需要注意的是,脚本是堆栈语言,先压入的值后判断
IF
script A //如果要执行A脚本,那么需要给赎回脚本(解锁交易的脚本)赋值 A 1
ELSE
IF
script B // B 1 0
ELSE
script C // C 0 0
ENDIF
ENDIF
IF
IF
2
ELSE
<30 days> CHECKSEQUENCEVERIFY DROP
<Abdul the Lawyer's Pubkey> CHECKSIGVERIFY
1
ENDIF
<Mohammed's Pubkey> <Saeed's Pubkey> <Zaira's Pubkey> 3 CHECKMULTISIG
ELSE
<90 days> CHECKSEQUENCEVERIFY DROP
<Abdul the Lawyer's Pubkey> CHECKSIG
ENDIF
解释第一种路径:三方的钥匙都没出现问题,即选择True True;压入堆栈的组合兑换脚本格式如下:
压入堆栈的兑换脚本:2 <公钥1><公钥2><公钥3> 3 CheckMultiSig
提供的解锁脚本是:0 <私钥1><私钥2> True True
组合验证脚本:0 <私钥1><私钥2> 2 <公钥1><公钥2><公钥3> 3 CheckMultiSig
解释第二种路径:三方有人秘钥出现问题,在30天后通过律师和一个人的私钥解密;
压入堆栈的兑换脚本:<30 days> CHECKSEQUENCEVERIFY DROP <律师公钥> CHECKSIGVERIFY
1 <公钥1><公钥2><公钥3> 3 CheckSig
需要提供的解锁脚本:0 <Sig_三方><Sig_律师> False True
组合验证脚本:0 <Sig_三方><Sig_律师> <30 days> ......
解释第三种路径:允许90天之后律师独享
压入堆栈的兑换脚本: <90 days> CHECKSEQUENCEVERIFY DROP <律师公钥> CHECKSIG
提供的解锁脚本:<律师私钥> False
组合验证脚本:<律师公钥><90 days> .......
关于比特币网络
全节点:掌握区块链的完整交易信息,并且能够独立地建立并校验区块链。
新节点如何与网络同步:
与所有的对等节点发送一个GetBlocks消息获得对等节点的区块高度,对等节点发送可供共享的500个区块,然后新节点向与之相连的所有对等节点询问缺少的区块信息,这样以便分担工作量防止某一节点被压垮;对等节点发送对应的区块信息
关于简单支付验证SPV节点
全节点存储的是整个区块链的所有信息,而SPV节点存储的只是所有区块的区块头,而不包含其中的交易
对于一笔交易全节点和SPV节点的验证方式不同:全节点会回溯该区块到创世区块的n个区块,全部链接起来,形成一个完整的UTXO库,从而通过验证该UTXO是否被支付来决定交易是否有效
SPV节点通过参考交易在区块链的深度而不是高度,将交易与其所在的区块的所有交易建立一个Merkle树,然后等待,如果在未来后续六个区块堆叠在该交易所在区块之上,确认了该交易确实存在(但是无法知道是否是双花攻击构造的节点)
关于区块
区块的标识符是:区块头的哈希值和区块高度。区块的哈希值并不在网络上,而是由节点验证时计算出来的。区块哈希值能唯一地标识某个区块,但是区块的高度却不能够,因为有网络分叉现象
关于支付通道与状态通道
支付通道是在区块链之外的双方交换比特币的无信任机制;
双方首先建立一个状态通道,在区块链上锁定通道的初始余额(一般是P2SH多签地址的账户余额),然后双方互相交换已经签名的交易(承诺交易),可以改变通道的余额;每互相交换一个承诺交易就会废弃之前的状态,这样最新的承诺交易就是唯一可以赎回的承诺交易,以防止任何一方在对自己有利的时候关闭交易;如果有一方想要结算交易,就可以发送最终状态交易来结算
如何制造无需信任的支付通道:发送方除了创建资金交易,还需要创建一笔退款交易,让接收方签名;防止在接收方离线导致发送方的钱永久地锁定在通道中
关于闪电网络
闪电网络允许参与者穿过一个路由通道到另一个通道支付,而且不需要信任任何中间人。
节点发送支付之前必须连接到支付通道,来构建通过网络的路径。每个节点都会宣传路由信息。一旦确认构建了路径,那么会通过加密和嵌套的指令来连接每个相邻的支付通道,其中的路由节点不知道路径。中间节点仅可验证部分路由信息,并找到下一跳
关于隔离见证
将见证数据部分从解锁脚本转移到一个单独的数据结构;即一个交易的输出脚本会简化,而且解锁脚本中不需要再提供数字签名;解锁脚本中的内容全部转移到隔离见证中
对于传统的非隔离见证的节点对于一个隔离见证输出,认为任何人都能够解锁该输出;而对于升级后的隔离见证节点就知道这是一个隔离见证
一个P2PKH交易的锁定脚本
DUP HASH160 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 EQUALVERIFY CHECKSIG
对应的P2WPKH的输出脚本,"0"代表的是版本号,后面代表的是20字节公钥哈希
0 ab68025513c3dbd2f7b92a94e0581f5d50f654e7
一个P2PKH的解锁交易输出
[...]“Vin” : ["txid": "0627052b6f28912f2703066a912ea577f2ce4da4caa5a5fbd8a57286c345c2f2","vout": 0, "scriptSig": “”,][...]“witness”: “<数字签名> <公钥> Check”[...]
一个P2WPKH的脚本应该由收款人创建,而不应该由发款人通过已知的公钥,P2PKH脚本来构造一个P2WPKH见证脚本,因为不确认接收方是否有能力构建P2WPKH脚本
一个P2SH交易的锁定脚本
DUP HASH160 54c557e07dde5bb6cb791c7a540e0a4796f5e97e EQUAL
对应的P2WSH的锁定脚本,包括版本号和32字节的哈希值(是为了与P2WPKH脚本区分开)
0 9592d601848d04b172905e0ddb0adde59f1590f1e553ffc81ddc4b0ed927dd73
一个P2WSH的解锁交易如下,可以看到整个见证部分都被转移到了隔离见证的部分
[...]“Vin” : ["txid": "abcdef12345...","vout": 0, "scriptSig": “”,][...]“witness”: “<SigA> <SigB> <2 PubA PubB PubC PubD PubE 5 CHECKMULTISIG>”[...]
所以区分P2WSH脚本和P2WPKH脚本的技巧:哈希值为20字节的是P2WPKH脚本,32字节的事P2WSH脚本
以太坊
实现一种内置图灵完备编程语言的区块链,允许任何人编写智能合约和去中心化应用,并在其中设立他们自由定义的所有权规则,交易方式和状态转换函数。
关于以太坊帐户
以太坊账户共20个字节,包括以下字段;Nonce,帐户当前的以太币余额,帐户的合约代码,帐户的存储
两类帐户:私钥控制的外部帐户,外部帐户没有代码,持有者可以通过创建和签署交易从外部帐户发送信息;由合约代码控制的合约账户,合约账户收到消息后其代码就会激活,允许帐户读取或写入内部存储
“合约”不要被视为需要遵循或者履行的东西,它只是合约账户被消息触发时总是执行的代码段
关于以太坊代码
任何对状态的修改都是在本地修改数据结构,只有在修改完成且发布到主链上这个修改外部才可见。
在以太坊中先挖矿还是先执行智能合约:先执行智能合约,在以太坊中挖矿的本质仍然是寻找一个Nonce值,使得区块头的哈希值小于难度目标,但是在以太坊的区块中包括三大树,状态树,交易树,收据树。只有当执行完智能合约才能得到这三大树,才能进行挖矿
矿工如果没有收到汽油费能不能直接不验证节点:不能,只有验证节点后执行所有的交易后更新自己本地的三大树才能继续挖矿,如果不更新三大树,与其他的节点不同,其他节点会认为该节点的挖矿产物是错误的。所以每个节点都必须验证,即使没有汽油费
智能合约执行失败的交易是否会发布到区块链上:即使执行失败的交易仍然会发布到区块链上,因为即使交易失败也要支付汽油费,所以也需要发送到区块链上让其他的节点验证。
智能合约是否能够运行多线程:不能,因为多个核对内存的访问顺序不一样的话,会导致执行结果不一样,任何执行结果不确定的结果都不支持。
addr.Transfer(12345)代表的是向当前的账户addr转入了12345wei
address.call()指的是当前合约发起调用,调用的是当前这个地址
合约涉及两个函数,一个函数用于记录当前的最高出价和对应的人;另一个函数就将钱转给受益者并退还其他拍卖者的钱
存在一定的问题 ,假设一个调用情况如上,因为该账户是一个合约账户,当合约账户收到转账时没有调用任何函数就应该调用fallback函数,但是该合约中没有fallback函数导致调用失败;Transfer函数会抛出异常,引起连锁式的回滚,而在受益者方引起的回滚是回滚到收钱之前的数据状态,回滚后所有人在收益人方的本地的数据状态就根本没变,使得这个智能合约看起来似乎从来没执行过(即收益人没有收到钱)。也就导致收益方不会退钱给任何人,所有人的钱都被锁定。
针对这种情况的解决办法,采用了第二个版本,将auctionEnd函数拆分成了两个部分;第一部分是一个竞争失败者的取钱函数,由竞争失败者自己调用,第二个函数是把竞争成功者的钱转入受益者
针对第二版,如果存在如上的调用,会出现重入攻击的现象。 竞争者竞争失败,此时调用HACKV2的合约的with_draw函数取钱。在取钱的函数,由于是先执行call函数,再把该用户在受益方的数据状态清0;导致call函数执行时,调用竞拍者的合约账户时调用fallback函数,该函数又会调用with_draw函数;这样不断递归调用导致受益方的钱被取完,每次取的钱都是自己拍卖给出的价格;这样的递归调用直到以下三种情况会停止:受益方的账户余额不足;汽油费不够;调用栈溢出
针对第二版的改进,只需要改进with_draw函数,先清0再转账,而且使用transfer函数或send函数,使得能够回滚
the DAO分叉的制止措施:①禁止所有与the DAO相关的账户的交易;②想办法回滚与之相关的交易
更多推荐
所有评论(0)