比特币所使用的数据结构、实际脚本以及语言。
1. 交易
交易有输入以及输出,但是输出并不是输入的旧值,虽然其中的计算会用到,但是实质我们理解的就是:我们用的旧值去创造旧值而不是新值。同时随着创造的新值还有他需要指定的地址。
1.1 地址转换
- 爱丽丝只需付给鲍勃17个币,但爱丽丝在上一交易中实际获得了25个币,为了把这些币全部消费掉,她必须再转给自己8个币。这8个币可以转到另外一个地址(不同于交易1中获得25个币的地址),但前提是该地址为爱丽丝所有;
- 爱丽丝从自己的账上转移到了另外一个也属于自己的地址——这就叫做地址转换。
1.2 有效验证
- 要核查一下爱丽丝引用的交易输出,确认她确实有25个币没有被花费掉;——好处就是不用从最开始查起
- 一直核查到账本上最新记录的交易为止即可;
1.3 多个输入或者多个输出:当存在这样一种情况的时候,就需要
- 资金合并<==鲍勃在两笔不同的交易中分别收到17个币和2个币,现在他想把这两笔钱合并起来花掉,两个输入和一个输出,输出的地址是他自己的地址
- 共同支付<==卡罗尔和鲍勃想要共同支付给戴维,两个输入一个输出
1.4 交易语法[1],[2]
-
真实的比特币交易片段:
-
从字段可以看出包含3个结构:元数据、输入、输出
- 元数据:
- :此笔交易的哈希值,也就是这个交易独一无二的ID。我们可以用哈希指针指向这个ID。
- :这笔交易的规模
- :输入的数量
- :输出的数量
- :版本号
- :最后还有一个“锁定时间”(lock_time)
- 输入:
- 可能包含多个输入,每个输入包含:
- :上一区块的 以及 值(前一个哈希指定的交易中交易输出的索引。);
- :签名,证明我们有能力去支配这个区块,也就是将我们的身份进行认证;
- 可能包含多个输入,每个输入包含:
- 输出:
- 可能包含多个输出;每个输出有以下内容:
- :输出的金额,其中金额的大小是有要求的,输出的金额之和必须 输入的金额之和
-
:可以理解为某一个脚本的公钥——指定可以声明事务输出的条件。
- 每个输出都要和一个特定的公钥(地址)对应,所以这一长串字符里面确实有一部分看上去是公钥的哈希值,但里面还有一部分看上去像指令集合的东西;
- 公钥+指令集合;它其实是一个比特币的脚本;
- 可能包含多个输出;每个输出有以下内容:
- 元数据:
-
2. 脚本
2.1 常见的比特币的交易:
2.1.1 常见的一种比特币交易场景
-
通过某人的签名去取得他在前一笔交易中获得的资金,那么对应的脚本就是:
-
凭借哈希值为X的公钥(所以这里公钥就是身份),以及这个公钥所有者的签名,才可以获得这笔资金。
-
输出脚本只是指定了一个公钥(或是公钥哈希值的地址),输入脚本指定了一个对应公钥的签名
-
2.1.2 几种内容的比特币交易
- 销毁证明
- 能做到什么?
- 如果交易代码的运行结果是将比特币转到“销毁证明”脚本,那么这笔比特币将被销毁。
- 如何实现:
- 使用OP_RETURN脚本来抛出错误;不论之前指令的运行结果是什么,OP_RETURN指令总会被执行,并相应抛出一个错误,脚本返回一个“错误”(false)值。
- 有趣的应用场景:
- 将某一些信息永远保存在区块链:由于发生错误之后的交易也不会对其内容进行增加什么,
- 实现方式:发起一笔极小额的比特币交易,在脚本中加入上述信息,并使用销毁证明脚本将币销毁,这样就可以将信息永久地存储在区块链上
- 将某一些信息永远保存在区块链:由于发生错误之后的交易也不会对其内容进行增加什么,
- 能做到什么?
- 支付给脚本的哈希值(使用
)
- 多重签名地址支付,而且还可以实现复杂的资金监管规则。
- 比特币使用的办法是(此时发送的就不是公钥的hash而是脚本的hash):
- 收款方告诉付款方“请把比特币支付给某个脚本地址,脚本的哈希值是××,在取款的时候,我会提供上述哈希值对应的脚本,同时,提供数据通过脚本的验证”
2.2 常用的指令[5]:
指令 | 指令词源 | 16进制字节码 | 功能说明 |
---|---|---|---|
duplicate | 0x76 | 复制栈顶元素 | |
hash | 0xa9 | 弹出栈顶元素,先进行SHA-256哈希,再进行RipeMD160哈希处理,将结果压入栈。 | |
equal verify | 0x88 | 弹出栈顶两个元素,如果两个内容一致,则返回1,脚本继续执行。否则返回0,脚本中断执行。 | |
check signature | 0xac | 弹出栈顶两个元素,用公钥检查输入中的签名,验证该签名是否拥有该公钥的用户用其私钥签署的。如果签名符合,则将true(true = 1) 压入栈顶。 | |
check multiple signature | 0xae | 用多个公钥检查多重签名的正确性。 | |
return | 0x6a | 标记交易无效 | |
下面多少个字节要被压入堆栈 | |||
数字1被压入堆栈 | |||
一个字节空串被压入堆栈 |
2.3 执行指令实行堆栈执行,由指令中大量使用堆栈的指令也可以看出来。
-
堆栈过程图示:
- 尖括号里的是数据指令,以OP开头的是工作码指令,指令上方对应的是指令执行之后的堆栈状态
- 尖括号里的是数据指令,以OP开头的是工作码指令,指令上方对应的是指令执行之后的堆栈状态
-
堆栈过程详解:
- 依次入栈
- 复制栈顶元素并将其置于栈顶;
- 取栈顶元素并计算其hash值,再讲计算的hash值放在栈顶;
- 还要在堆栈顶层再推送一些数据:此笔交易发送者指定的公钥的哈希值,以及对应的私钥,这样才可完成签名,取得资金。
- 此时的栈顶元素:发送者指定的公钥的哈希值+接收者想要取得资金时提交的公钥的哈希值(这两者应该是一致交易才会成功)
- 命令:用来判断栈顶两个hash值是否是一致
- :验证签名是否一致正确(签名是对交易进行的签名)
2.4 三种类型的签名[4]:
2.4.1 比特币实现花钱的方式:
- 花钱的场景:
- 假设B要花A转给他的钱:
- A在交易M的输出中,写一个脚本(输出脚本),请写明金额,表示把钱转给A。
- B在交易N的输入中,也写一个脚本(签名脚本),意思是要花A在交易M中转给他的钱。
- 当交易M在网上传播时,比特币节点验证交易M,只要签名脚本符合输出脚本的要求,节点就认可B能够执行这个花费。
- 假设B要花A转给他的钱:
2.4.2 方式分类:P2PKH、P2SH、Multisig
-
(支付到公钥模式):
-
这种支付,就是支付给某人的公钥,也就是支付给 地址
-
格式:
- 输出脚本:OP_DUP OP_HASH160 (0x14) [一个20字节的哈希值] OP_EQUALVERIFY OP_CHECKSIG
- 输入脚本:[签名的字节数][签名]0x01 [公钥的字节数] [公钥]
-
例子:
OP_DUP OP_HASH160 14 (字节数,0x14 = 20L, 网站在显示时省略了这一字节) 7232ca33e0797405a512fa872934cd922c812965 (20字节的哈希值,区块的hash值) OP_EQUALVERIFY OP_CHECKSIG
-
-
(赎回脚本):
- 其实就是相对于 的升级将1个公钥-签名对升级成了n个公钥-签名对;
- 格式:
- 输出脚本:OP_HASH160 (0x14) [一个20字节的哈希值] OP_EQUAL “签名脚本”:(输入脚本)
- 输入脚本:0x00 [字节数] [签名1] 0x01 …[字节数] [签名m] 0x01 [支付合同脚本的字节数] [m] [字节数][公钥1]… [字节数][公钥n][n] OP_CHECKSIGVERIFY
-
:
- 这样的好处就是$ \to P2SH 地址。
- 也可以实现 地址和 的功能。
3. 应用
3.1 第三方支付交易
3.1.1 场景:
爱丽丝用比特币向鲍勃买东西,爱丽丝想货到付款,而鲍勃想见款发货。该如何处理?一个好的办法是使用第三方支付交易(escrow transaction)
3.1.2 实现
3.2 绿色地址
3.2.1 应用场景
在一些交易很小并且不能忍受太长时间的等待的时候,
解决方案:
- 使用类似第三方银行的方案;
3.1.2 实现
如果爱丽丝要转账给鲍勃,爱丽丝会和她的银行联系,“我要付给鲍勃这些币,你能办理吗?”银行会回答:“好的。我会从你的账号扣钱,然后从我的绿色地址转账给鲍勃。”
3.3 高效小额支付(efficient micro-payments)
3.3.1 应用场景
鲍勃是爱丽丝的手机流量提供商,根据爱丽丝每分钟使用的流量计费。但是,每分钟支付一次是不现实的,于是实现一种一段时间累积的方案。
3.3.2 实现
爱丽丝在每分钟使用的时候,每隔小段时间就进行签名并且支付,但是这阶段是没有鲍勃的签名,所以形成不了区块,只有当爱丽丝使用完成或者爱丽丝没钱支付了这时候鲍勃进行签名,完成这笔交易形成区块。并且爱丽丝单独的交易并不会进入区块只有最后整个的交易才会进入区块。
3.4 锁定时间
3.4.1 应用场景
小额支付协议会签订一个交易,如果在一定的时间内鲍勃并没有进行最后签名,这时候的爱丽丝就会要求退还所有款项,那么这段时间内对鲍勃的退款行为就是锁定的,这里就是如何实现这个锁定的功能的。
3.4.2 实现
元数据加入一个参数:lock_time。
在此参数后面填上非零数值t, 这个值告诉矿工在记账的时候,要等待t时间之后才能把这笔交易记入区块链
3.5 智能合约
比特币系统里可以用技术手段来强制自动执行的合约,其他详细可以看智能合约
4. 区块
4.1 区块结构[6]:
-
区块头:
- 区块头里面存储着区块的头信息,包含上一个区块的哈希值( ),本区块体的哈希值( ),以及时间戳( )等等。
-
区块体:
-
区块体存储着这个区块的详细数据(Data),这个数据包含若干行记录,可以是交易信息,也可以是其他某种信息。
区块与Hash是一一对应的,Hash可以当做是区块的唯一标识。
-
4.2 区块使用的数据结构是:默克尔树
-
区块头部还要包含一个矿工可以修改的“临时随机数”、一个时间戳和一个点数(点数用来表示找到这个区块的难度)。
-
区块头部是挖矿过程中唯一哈希值化的,所以要验证一个区块的链,只要检查区块头部即可。
-
在区块头部唯一的交易数据是交易树的树根——“mrkl_root”
4.3 币基交易(奖励所产生的造币操作)
每个区块的梅克尔树上都有一个有意思的交易,叫作币基交易(奖励所产生的造币操作),与普通交易有所区别:
-
它永远只有一个单一的输入与单一的输出。
-
这个交易并不消费之前交易输出的比特币,因此,没有指针指向“上一交易”。
-
这个输出值目前大约是25个币多一点点。这个输出值就是矿工的挖矿收入。它由两部分组成:一部分是奖励的25个比特币(奖励在每生产210 000个区块——大概4年——后减半),另一部分是所有交易的交易手续费。
-
还有一个特别的地方就是“币基”参数,矿工可以放任何值进去。
5. 网络
点对点网络,在比特币网络里,所有的节点都是平等的。没有等级,也没有特殊的节点,或所谓的主节点,随意地组成拓扑,节点加入时与其他所有节点权限一样,没有规定哪个节点离开,界线就是超过3小时没有响应就会被其他节点忘记
5.1 如何加入网络?
5.2 加入比特网的好处
想让整个网络知道一个信息,由于加入了网络就可以进行“泛洪”操作,其操作流程如下:
- 交易池确保了信息不会无休止地传播下去;
5.3 加入比特网过程中遇到的问题
5.3.1 节点接收到新交易的时候如何进行检验?
- 最重要的一个是交易验证,也就是验证交易在当前的区块链中是有效的,节点会针对每个前序交易的输出运行核验脚本,确保脚本的返回值都为真;
- 检查是否有双重支付;
- 如前文所述,节点会检查这笔交易信息是不是已经被本节点接收过;
- 节点只会接收和传递在白名单上的标准脚本
注意:没有规则强制节点执行这些检查。虽然如此,每个节点还是有必要进行检查的,检验会带来更高的安全性
5.3.2 比特币网络如何解决竞态条件?
-
竞态条件指的是什么?
- 爱丽丝想把同一个比特币支付给鲍勃与查理,于是,爱丽丝几乎同时发出两笔交易。有些节点先听到爱丽丝→鲍勃交易,有些则先听到爱丽丝→查理交易,因为你听到一个就会把它放入交易池,那么另外一个就不会加入交易池,由于每一个节点的接收到的次序并不是一致,所以最后导致大家的交易池对于爱丽丝所要发起的交易是哪一笔并不一致。
-
对于比特币来说,这样的问题最终是由打包下一区块的矿工来解决,大致如下图所示:
位置很重要,如果近的话就能够在这种发生冲突的情况下先接收到某一个信息
5.3.3 大多数情况基于某一假设
基于一个假设,但是这个假设并不强制
- 不管接收到什么信息,每个节点均保留最早接收到的交易。但是比特币网络是一个对等的网络,节点并不被强制要求这么做,任何节点都有权按照其他逻辑行事,并按照所选的逻辑决定到底保留哪个交易、转播哪些交易
5.3.4 交易的传播:
- 零验证交易:一旦交易在网络中广播,接收方就立即接受交易
- 由于矿工的缺省行为是把先接收到的交易放入交易池,这样,在零验证交易里就很难实现重复支付
- 费用替代策略:节点在遇到有冲突的交易时,会把交易手续费更高的交易放进自己的交易池,把手续费更低的替换出去
- 矿工的角度,由于收益更高,因此也是理性的选择,不过使得双重支付攻击更容易了
随着发展有了“有选择权的”(opt-in)费用替代策略的做法,也就是交易可以标记自己是否适用费用替代策略
5.3.5 区块的传播:
1. 同样受到竞态条件
矿工挖到一个矿(打包一个区块),然后将区块加入区块链,这个过程与新交易的传播过程类似,也受同样竞态条件的限制
- 面临的状况:如果两个有效的区块同时被挖到(也就是有两个矿工同时获得了记账权力时),只有其中一个区块可以进入长期共识链,纳入的就被永久加入区块链,没纳入的就被丢弃
2. 核实区块?
- 确认区块头部;
- 确定里面的哈希值是在可以接受的范围内
- 确认区块里的每个交;
- 一个节点往外传播的区块必须是最长的一条区块链上新加入的区块
3. 延时
-
泛洪情况的延时:比较大的区块需要30秒左右才能传播到大部分的节点。
-
区块链设计原则:在比特币的设计里,简便是第一位的(简单的网络、节点可随时加入或退出)
4. 网络大小:
- 很难测量,随时在变化;
- 往高说,每个月可能有100万个IP地址成为比特币网络的节点(也可能是临时成为节点)。
- 往低说,大约只有5 000~10 000节点永远在线并处理交易。这个数字有点出乎意料得小
5. 存储空间需求:
- 完全有效的节点
- 目前的存储空间大约要几十个GB,一台台式机就能满足要求
- 完全有效节点必须维护在交易中产生的(交易的输出)、未被消费掉的比特币的完整列表,这个列表最好放在内存而非硬盘里
- 2014年年中,大约有4 400万的交易被纳入区块链,其中有1 200万个交易产生的比特币没有被使用。还好,这个数据不大,可以很容易地放进1G内存里。
- 轻量节点:
- 定义:轻客户端,也叫简单付款验证(Simple Payment Verification,简称SPV)客户端,只存储它们所关心的、需要进行核验的部分交易。
- 使用场景:如果你使用一个钱包软件,那里面就会有一个SPV节点,这个节点只会下载向你的账户付款的交易及区块头部。
- 优点以及不足:
- 不足的地方:
- 可以核验那些很难被挖到的区块——因为它有区块头部数据,但它不能核验一个区块里所有交易记录的有效性;
- 必须依赖那些全节点去验证网络上的其他所有交易
- 优点:
- 轻量节点依赖全节点去处理那些比较难的工作,但当某个区块由于某些原因未被矿工挖出来时(挖矿成本巨大),这些轻量节点也会做一些核验来确保这个区块不会被拒绝
- 不足的地方:
注意:比特币是一个开源协议,人们用不同的语言去实现,即使在同一时间,大家运行的客户端都略有不同。
6. 限制优化
6.1 限制的原因
初始设定的不完善,但是由于巨大的经济效应使得有一部分是没法进行改变的,比如:比特币的总体数量与记账奖励很可能永远都不会改变。
但是有些合理并且能够进行改善的,我们是有必要让其改善过来。
- 每个区块大小限定在1MB,每个交易大约是250字节,所以每块最多容纳4 000个交易。平均每隔10分钟,有一个矿工获得记账权利,所以每秒钟只能处理7个交易,这就是比特币网络的交易处理能力
- 密码算法:目前就只有几个hash函数和一个签名算法(种secp256k1的椭圆曲线数字签名算法)
6.2 如何对限制进行修订以及具有哪些相关策略
6.2.1 修订版本存在的问题
- 最大的问题是由于区块链的特性,有些节点没有在线,那么久存在一些节点已经更新修改的,但是有一些却是没有更新
6.2.2 两种修订版本的方式:硬分叉和软分叉
1. 硬分叉:
即运行新版协议的节点认定为有效的区块,会被运行旧版协议的节点认定为无效,不断随着大部分节点更新,最终的结果就是新的区块更长,那么原本那些没有更新就会被丢弃
2. 软分叉:
加入新的特性,让现有的核验规则更加严格。那样老的节点依然会接收所有的区块,而新的节点会拒绝一些——避免硬分叉所造成的永久分裂。
老节点可能会挖到一些无效的区块——因为这些区块中包含一些在新规则下无法核验通过的交易,这时候老节点意识到自己的区块分支需要进行更新,于是更新区块分支最后就与大部分节点一致了,缺点就是这种情况偶有出现,会出现比较多的分支但是解决了老节点没有更新遭到被丢弃这一情况。
参考文章
3.比特币系统的脚本(Script)——交易生成和验证的原理
4.三种类型的签名
6.区块头百度百科