02 密码学原理
-
比特币用到了密码学的哪些原理?哈希和签名
-
collision resistance:没有什么高效的方法、去找到两个哈希值相同的输入
- 用途:监测文件是否被篡改
- MD5:可以人为制造碰撞,不安全了
-
hiding: 哈希函数的计算过程是单向的,不可逆的
- 哈希值没有泄露有关输入的任何信息
- 成立前提:输入的空间要足够大,同时取值要均匀
-
digital commitment: 将 collision resistance 和 hiding 结合起来
- 用途:数字签名
- 即是:预测结果不能提前公开(sealed envelope,将预测结果交给第三方保存),可以将哈希值公布出去,等到结果公布时,再公布输入
-
实践时候:X||nonce,nonce 是随机数,X 是输入,这样保证了足够随机
-
puzzle friendly:哈希值的计算是事先不可预测的
- 就是不能够知道哪些输入会产生前缀为固定的哈希值,比如预测前缀为 00000 的哈希值,不能提前知道哪些输入会产生这样的哈希值
- 应用:挖矿的过程没有捷径、所以挖矿的币可以作为工作量证明
- 但是验证挖矿的结果是很容易的,只需要验证哈希值是否满足条件即可(difficult to find, easy to verify)
-
SHA-256:比特币中使用的哈希函数(Secure Hash Algorithm 256 bits)
-
哈希讲完了、下面讲签名
-
首先讲比特币的账户管理
- 开户的时候,会生成一对公私钥,公钥是地址,私钥是签名(很简单、是去中心化的)
- 是非对称的加密(asymmetric cryptography),公钥加密私钥解密(用的是同一个人的公钥加密和私钥解密,接收方的)
- 公钥公开、私钥保存在本地就可以了
-
别人怎么知道这个发起交易的人的真实性呢、就是发起交易的时候、用私钥加密、然后其他人用公钥解密(也是同一个人的)
-
生成公私钥的过程需要有好的随机源,否则会有安全隐患
-
一般是对 message 取一个 hash、然后再对这个 hash 进行签名
03 数据结构
-
哈希指针 hash pointers
- 除了保存了数据的地址,还保存了数据的哈希值
-
区块链和普通链表的区别
- 用哈希指针代替了普通链表的指针
- 第一个区块叫做 genesis block,最近的区块叫做 most recent block
- 每个后继的区块都保存了指向前驱区块的哈希指针
-
区块链的结构能够实现 tamper-evident log
- 任何一个区块的数据被篡改,那么这个区块的哈希值就会发生变化,那么这个区块的哈希指针就会发生变化,那么这个区块的后继区块的哈希指针也会发生变化,以此类推,整个区块链的哈希指针都会发生变化
- 所以我只要保存了最后一个区块的哈希值,就可以验证整个区块链的完整性(即验证这个区块链是否发生变化)
- 怎么知道别人给你的区块是不是正确的、只需要算一下哈希值就可以了
-
merkle tree 和 binary tree 的区别
- 用哈希指针代替了普通树的指针
- 最下面的一层是数据块(data blocks)
- 中间的几层都是 hash pointers
- 根节点也可以再取一个 hash(root hash)
- 用途:只需要保存 root hash、就可以监测出树中的任何位置的数据是否被篡改(因为若修改了、hash 会从数据块一层层向上传递到根节点)
-
data blocks 每一个其实相当于一个交易 transaction(tx)
- 每一个 block 可以分为
- block header:包含了前一个区块的哈希值、merkle root
- block body:包含了多个交易(交易的列表)
-
merkle tree 的一个用途就是提供 merkle proof
-
节点一共有 2 类:
- full node(全节点):保存了 block headers 和 block bodies(有交易的具体信息)
- light node(轻节点):只保存了 block headers
-
怎么给一个 light node 提供 merkle proof
- proof 的本质:找到交易所在的位置,然后提供一条从这个位置到根节点的路径
- (这个路径上的哈希值都是已知的,所以 light node 可以验证这个路径上的哈希值是否正确,从而验证这个交易是否在这个区块中)(这个就是 merkle proof)
- 具体流程:给一个全节点发送一个请求,请求这个交易所在的位置,然后全节点返回这个路径给 light node(路径上的哈希值计算:每个全节点保存了 2 个哈希值(即左子树的哈希值和右子树的哈希值),路径上会涉及到向全节点请求的哈希值(红色),以及 SPV 本地计算的哈希值(绿色),每一层、通过当前层自己计算的哈希值结合请求来的哈希值在一起、就可以计算出父节点的哈希值、一直到根节点。(一开始的交易哈希值、是通过交易节点在本地计算的)
- 注意:只能查交易路径上的哈希值、旁边分支的哈希值是不能查的
- 原理:因为树里的任意一个位置的哈希值更改之后、都会影响到根哈希值
- 这种交易也叫做 proof of inclusion(membership)
-
如何高效地证明 proof of non-membership?
- 如果对叶节点的顺序不做一些假设、那么就没有很好的方法
- 如果有顺序的话、可以用 binary search(log(n),但是代价就是要排序)(方法:证明和其兄弟的数据节点能验证这个 root hash 的值)(sorted merkle tree)(比特币中没有用到、因为没有这个需求)
-
只要这个数据结构是无环的、都可以用哈希指针来代替(有环就不可以,因为会有循环依赖的问题)
04 比特币中的共识协议
-
不能直接地像现实世界中的一样、通过中心化的方式去发行货币、否则会有 double spending attack 的问题(就是可以复制这个发行的货币)(也可以通过央行维护一个数据库去解决,但是这个方案太麻烦了、是个中心化的方案,每一次交易都需要通过央行进行监管)
-
两个问题:
- 如何发行货币
- 如何证明交易的有效性
-
比特币的解决方案(证明交易的有效性):
- 通过所有用户来维护
- 例子:铸币交易<--花费交易
- 每个交易都包含输入和输出两部分,输入部分要说明币的来源(签名者,以及多层级的哈希指针、比如指向前面某个交易的哈希指针,证明这个币不是凭空捏造的、而且要避免 double spending,可以通过哈希指针的方式去判断是否合法)。输出部分要给出收款人公钥的哈希
-
Block header
- 包含了区块中的宏观的信息(比如哪个协议) version
- 区块链中指向前一个区块的哈希指针(取哈希的时候是把所有的 block header 都取哈希就足够了)
- 整棵 merkle tree 的根哈希值 root hash
- 挖矿的难度目标阈值(target)
- 随机数 nonce
-
Block body
- 交易列表 transaction list
-
系统中的节点分为全节点和轻节点
- 全节点:保存了所有的区块链数据(验证每一个交易的有效性)(也叫 fully validating node)
- 轻节点:只保存了区块链的头部信息(没有办法独立验证交易的有效性)(系统中的大部分节点都是轻节点)(没有参与区块链的构造与维护)
-
账本的内容要取得分布式的共识(distributed consensus)
- 例子:分布式哈希表(distributed hash table)
-
FLP impossibility theorem:在异步系统中、有一个节点失效、就不能保证达成共识
-
CAP theorem:在分布式系统中、不能同时满足一致性(consistency)、可用性(availability)、分区容忍性(partition tolerance)
-
Paxos:一个分布式共识算法(CSE 讲过)(但是与比特币的实际应用关系不大)
-
比特币中的共识协议(Consensus in BitCoin)
- 假定系统中的大多数节点都是好的,有一小部分节点是恶意的
- membership:投票权的问题
-
sybil attack(女巫攻击):恶意节点可以通过伪造身份来获得更多的投票权(所以简单投票是不行的,因为谁都能创造账户)
-
所以比特币是基于算力进行投票的
- 需要验证 H(block header) <= target
-
这里会有一个问题、就是如果在同一个节点上插入两个区块、那么就会有分叉的问题
- 为了解决这个问题、需要选择最长且合法的链(longest valid chain)
- 例子:比如说想要通过插入一个自我转账的区块(用于回滚交易),虽然这个区块在一开始检查的时候是合法的,但是不在最长的链上,所以是无效的(因为验证区块有效性的时候、只会去证明在这个分支链上的交易是否有效)(这个就是 forking attack)
-
如果有两个节点同时或者在差不多的时间找到一个 nonce、那么也会有分叉的问题
- 这时候会同时产生两个节点
- 解决方法:各个节点接受第一个收到的区块(接受:即往下扩展一个区块)(相当于各拉一帮人,然后看谁先找到下一个,通过算力竞争或者运气先算到)
- 所以会维持一段时间,直到最后有一个节点找到下一个区块,然后就会胜出,失败的就叫做 orphaned block
-
为什么要消耗很多资源去获得记账权:有记账权节点有一定权力、可以决定哪些节点能写到下一个区块里
- 但是初衷是应该所有合法的交易都应该被写到区块链上,不应该让这个成为争夺记账权的主要动力
- 解决方法:出块奖励(block reward)获得记账权的节点、在发布的区块里可以有一个特殊的交易,即铸币交易(coinbase transaction)在这个交易里,可以发布一定数量的比特币(比特币系统中发布比特币的唯一途径,其他所有交易都是转账交易)
- 最开始是 50 个比特币、每 210000 个区块减半
- 设计时平均 10 分钟会出一个区块、所以每 4 年会减半一次
-
hash rate:得到记账权的节点的算力
-
因此避免了 sybil attack ,创建账户并不会使得你有更多的记账权
-
比特币争夺记账权的过程就是挖矿(mining),争夺记账权的节点叫做矿工(miner)
05 比特币系统的具体实现
-
ledger:账本
-
系统上没有显式地记录账户余额,只有交易记录(需要通过交易记录来计算余额)
-
transaction-based ledger:比特币的全节点需要维护一个 UTXO(unspent transaction output)集合
- 就是所有未被花费的交易输出
- 注意:一个交易可能会有多个输出,但是只有被花费的输出才会从 UTXO 集合中删除(所以同一个交易中、有些币在 UTXO 集合中、有些币不在)
- 需要给出产生这个输出的交易的哈希值和它在这个交易里是第几个输出(output index),就可以定位到 UTXO 中的输出
- UTXO 的作用:为了监测 double spending(因为只有 UTXO 集合中的币才是有效的,所以只要检查这个交易的输入是否在 UTXO 集合中就可以了)
- 全节点需要在内存中维护这个 UTXO 集合,以便快速验证交易的有效性
- 交易会产生新的 UTXO,也会消耗掉一些 UTXO 的内容(如果不交易、就会永久留在 UTXO 集合中,但是足够存在一个服务器的内存中的)
- 每个服务器 total inputs = total outputs
- 同时,有记账权的节点还能获取手续费(防止只处理自己的交易)
- 这种的隐私保护性比较好(所以在交易的时候需要说明币的来源,有额外的代价)
-
还有 account-based ledger:以太坊(系统中需要显式地记录账户余额)
-
block 解读
- difficulty:每隔 210000 个区块、会调整一次难度目标(difficulty target),保证出块时间是 10 分钟左右
- nonce:随机数,这里显示的是挖矿时找到的随机数
- hash:区块头的哈希值(只包括块头)(符合难度要求的 hash,它的块头都包含一长串的 0)
- merkle root:这个区块中构成的交易的根的哈希值
- 为了找到满足要求的哈希值、需要不断地尝试不同的 nonce(挖矿的过程)
- 但是只改变 nonce 还是不够的,所以可以更改根哈希值(time 也可以在小范围内更改)
-
更改根哈希值的方法:
- 通过 CoinBase 里的交易,可以在交易中加入一些额外的信息,这个信息不会影响交易的有效性,但是会影响到根哈希值(什么都可以写)
- 最后是有两层循环的、搜索空间是 2^96,通过 nounce+coinbase 里的 extra nounce
-
Transaction 解读
- 交易是通过脚本的形式去实现的,即把 Input Scripts 和 Output Scripts 放在一起,然后执行这个脚本,如果能够配对成功、那么这个交易就是有效的
- 注意:不是同一个交易里的输入脚本和输出脚本配对,是当前的输入脚本和前一个节点的输出脚本进行配对(要能够顺利执行),当前的输出脚本和下一个节点的输入脚本进行配对
-
挖矿的分析:
- 就是不断地尝试不同的 nonce,直到找到一个满足要求的哈希值
- 每次尝试 nonce 可以看作是一个 Bernoulli trial(a random experiment with binary possible outcomes)
- 多次尝试,构成了一个 Bernoulli process(a sequence of independent Bernoulli trials)
- Bernoulli process 的特点:无记忆性(memoryless),即每次尝试的结果都是独立的,不受之前的影响
- 挖矿的场景下、可以用泊松过程(Poisson process)来模拟这个过程
- 因此,出块时间是服从指数分布的(exponential distribution)
- 所以是 progress free 的,不会因为之前做的工作越多、而对之后的工作有影响(否则算力强的矿工会有不成比例的优势,就是 10:1 的算力,可能会超过 10 倍的几率)(挖矿公平性的保证)
- 由于这个挖矿的公式,系统中总共的总量是有限的,是 2100 0000 个比特币
- 比特币的难度是人为地减少的、而不是为了解决什么数学问题,本身是没有什么实际意义的、但是挖矿的过程是对比特币的系统和机制的维护是至关重要的(Bitcoin is secured by mining)即这个过程是为了保证大部分的节点都是好的、都在诚实的人手中
-
但是这种方案也不能保证这个记账权全部都不在恶意节点上
- 所以最后相当于是,只需要诚实的节点都认这个交易(即沿着合法的区块往后挖,因为要扩展最长合法链)、如果这个交易只在恶意的节点上被认可、也是没有用的
- 同时,对攻击者来说、这种新加入非法交易的区块是不会被认可的、攻击者还会因此丢掉这些比特币
-
预防 fork attack:
- 挖矿之前就需要指定前一个区块、然后最后的 hash 就是这个区块的 hash
- 一种简单的方法就是多等几个确认(confirmation)(比如默认的是等 6 个区块确认,才认为前面那个交易是不可篡改的)
- 这种不可篡改性也是一种概率的保证
-
还有一个 zero confirmation,就是不用等到这个交易写到区块链上,就可以认为这个交易是有效的。(因为这个接受是按照时间上的最先接受的为准的,而且比特币系统以外、如果商家发现这个币是转到了无效的区块上、就可以不发货)
-
如何反制这个区块不接受交易写入呢:通过手续费的逻辑,会总归写到一个后续的诚实的节点中去
-
selfish mining:正常是挖到一个区块、就发布了、但是有一种攻击方式、就是挖到一个区块、但是不发布、等到挖了多个区块时、然后就可以做 forking attack 了(但是这么做有一个前提、就是有恶意的节点要占用很大一部分的算力)
- 还有一种 selfish mining 的得利:就是可以让别人沿着原来的区块继续挖、然后可以让挖得慢的人都白挖了(但是需要风险、而且需要很大的算力,但是回报也不是很高)
06 比特币网络的工作原理
-
The BitCoin Network:
- 应用层(application layer):比特币协议(Bitcoin protocol)
- 网络层(network layer):P2P Overlay Network(所有节点都是平等的)(有的 P2P 有 super node 或者是 master node,但是比特币没有)
- 如果要加入这个网络、需要至少知道有一个种子节点(seed node),通过 tcp 连接、有利于穿透防火墙
- 离开的时候直接退出就可以了、别的节点听不到你的消息、会自动从节点列表中删除
-
比特币网络的设计原则:
- 简单、鲁棒、而不是高效
- 消息传播在网络中采用 flooding 的方式(即一个节点收到消息之后、会广播给所有的节点),下一次收到相同的消息时、就不会再转发了
- 邻居节点的设定是随机的、没有拓扑结构的、就是不按照地理位置组合。这样可以增强系统的鲁棒性,因此给你身边的人转账和给远处的人转账是一样的)
- 等待写入区块链的交易会写入一个集合中、下一次听到这个交易的时候就不转发了、避免了无限转发
-
race condition
- 有 A->B 和 A->C 两个交易、但是用了相同的币,只能有一个能够写入区块链
- 如果一个节点听到了 A->B 或者 A->C 已经被写入其他的区块链了,那么就会把这个交易从集合中删除
-
比特币网络的特性:
- 区块的传播和交易是类似的
- 越大的区块在网络中传播的时间越慢(限制是 1MB,因为带宽是瓶颈)
- 是 best effort 的:不保证消息一定能够传播到所有的节点,顺序也不一定是一样的(而且有些节点可能不转发,就是去中心化的网络中会遇到的问题)
- 无法支持中心化的投诉
- 回滚的解决方法:通过两个不同的交易,而不是数据层面的回滚(就是转账和退款的两个交易)
07 挖矿难度
-
H(block header) <= target
- target 越小、挖矿难度越大
- 调整挖矿难度就是调整整个挖矿空间在输出空间中的比例
- 使用 SHA-256 哈希函数(整个输出空间是 2^256)
- 调整:就是要求算出来的哈希 H 前面的 0 的个数越多、难度越大
- 挖矿难度(difficulty)和 target 是反比的关系
- difficulty = difficulty_1_target / target(difficulty_1_target 指的是挖矿难度为 1 的时候的目标阈值 target,难度最小就是 1)
-
调整挖矿难度的原因:因为比特币的算力是在不断增加的,所以需要调整难度,保证出块时间是 10 分钟
- 如果出块时间太短: 因为区块在网络上传播就至少需要几十秒、如果在几秒钟同时出了多个区块、就会有分叉的问题(而且不止是二分叉,可能是多分叉)
- 分叉如果过多的话、对系统的安全性是有影响的(分叉攻击,降低了分叉攻击所需要的算力百分比),对达成共识也是有影响的
- 也不是一定要 10 分钟出块时间,只不过需要一个常数范围内的时间(比如 10 分钟左右)
- 以太坊的出块速度就很快,是 15 秒一个区块(不过也需要调整难度以保持稳定)
-
具体怎么调整挖矿难度:
- 每 2016 个区块调整一次(大概是 2 周)(2016*10/60/24 = 14 天)
- 调整公式:target = target * (actual time / expected time)(actual time 是实际的时间,就是系统中最近产生 2016 个区块实际经过的区间,expected time 是预期的时间,就是 2016*10min = 2 周)(会实际提高或降低 target,以保证实际的出块时间和预期的出块时间一致,最高最低这个倍数有 4 倍的限制)
- 如果恶意的区块不调整难度的话、那么诚实的矿工不会认这个区块节点(无法通过区块的合法性)
-
比特币是没有法币背书的,而且选择的参数也是比较保守的,也是经验公式
-
比特币系统中的实际情况
- 总算力指数增长(体现在 hash rate 和时间的关系上)
- 挖矿难度和总算力是正相关的(因为挖矿难度是根据总算力来调整的,使得出块时间是稳定的)
- 如果挖矿难度 difficulty 是不断减少的、那么这个币一般来说就要很快不行了
- 每天的出块时间都是稳定的、维持在 10 分钟左右
- 挖矿难度和目标阈值是反比的关系(公式可能会混淆)
08 比特币挖矿
-
全节点
- 一直在线
- 在本地硬盘上维护完整的区块链信息
- 在内存里维护 UTXO 集合,以便快速检验交易的正确性
- 监听比特币网络上的交易信息,验证每个交易的合法性
- 决定哪些交易会被打包到区块里
- 监听别的矿工挖出来的区块,验证其合法性
- 挖矿
- 决定沿着哪条链挖下去?
- 当出现等长的分叉的时候,选择哪一个分叉?
-
轻节点
- 不是一直在线
- 不用保存整个区块链,只要保存每个区块的块头
- 不用保存全部交易,只保存与自己相关的交易
- 无法检验大多数交易的合法性,只能检验与自己相关的那些交易的合法性
- 无法检测网上发布的区块的正确性
- 可以验证挖矿的难度
- 只能检测哪个是最长链,不知道哪个是最长合法链
- 即如果只需要转账、不需要挖矿的话、可以用轻节点
-
挖矿的设备
- 大部分内存和 cpu 中的设置都是闲置的、所以利用率太低,所以用 gpu(还是有点浪费了,因为是专门为了通用并行计算设计的)
- 所以现在基本用 asic 芯片(专门为了挖矿设计的芯片)(Application Specific Integrated Circuit)(没有多余的电路逻辑,只有挖矿的逻辑,所以效率很高)
- 而且有专门的为了设计固定种类币的芯片
- mining puzzle:挖矿的过程就是解决这个谜题的过程
- merge mining:新发布的币的规则是和比特币一样的
- alternative mining puzzle:抗 asic 的挖矿算法,为了让通用的计算机也能参与挖矿
-
挖矿的趋势:
- 大型矿池的出现:把矿工组织起来、形成一个整体
- pool manager:负责全节点的其他职责
- miner:负责挖矿,计算哈希值(因为是 asic 芯片)
- 分配奖励:用工作量证明的方式来分配奖励(按照算力来分配奖励)
- 所以分配可以基于降低难度的方法:share,almost valid block(提交给矿主,证明矿工的工作量,矿主只需要记录挖到了多少个 share 进行分配就可以了)
- 有没有可能矿工自己发布区块:没用,因为矿主的区块的收款人是矿主自己,所以矿工发布的区块是没有用的。而且如果修改了收款人、那么这个区块的哈希值就会发生变化,矿主就不认这个 share 的工作量证明
- 但是可以捣乱和破坏竞争对手,比如挖到了不发布
- 有一个矿池分布的统计
- 矿池给矿工减轻了负担
- on demand mining:可能把算力分布在不同的矿池中、然后发动攻击的时候再集中
-
Boycott:封锁禁域,就是要让某个用户的交易一上链的时候、就马上分叉
09 比特币脚本
-
比特币的交易脚本:基于栈的语言,只能访问栈空间
-
vin 和 vout
- vin:输入,包含了交易的来源
- vout:输出,包含了交易的去向
-
confirmations:交易的确认信息次数
-
vin
"vin": [{
"txid": "a3e2b1f3f7e0e4e8f2c3e1",//之前交易的哈希值
"vout": 0,//之前交易的第几个输出
"scriptSig":{ //输入脚本,给出一个前面signature,可能会有多个签名、说明多个来源
"asm": "3045022100...",
"hex": "483045022100..."
},
}],
- vout
"vout": [{
"value": 0.01,//输出的金额
"n": 0,//输出的编号
"scriptPubKey":{ //输出脚本,最简单的形式是给出一个公钥哈希
"asm": "OP_DUP OP_HASH160 404371705fa9bd789a2fcd52d2c580b65d35549d OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a914404371705fa9bd789a2fcd52d2c580b65d35549d88ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
]
}
}],
-
P2PK:pay to public key
- 交易的输出脚本是一个公钥
- 交易的输入脚本是一个签名
- 实际脚本为了安全、是分段执行的
PUSHDATA(Sig) //压栈签名
PUSHDATA(PubKey) //压栈公钥
OP_CHECKSIG //检查签名,弹栈
-
P2PKH:pay to public key hash
- 交易的输出脚本是一个公钥哈希
- 交易的输入脚本是一个签名和一个公钥
PUSHDATA(Sig) //压栈签名
PUSHDATA(PubKey) //压栈公钥
DUP //复制栈顶元素,栈顶又多了一个公钥
HASH160 //计算栈顶元素的哈希值
PUSHDATA(PubKeyHash) //压栈公钥哈希
EQUALVERIFY //比较两个栈顶元素是否相等,不相等就停止执行
CHECKSIG //检查签名,弹栈
-
P2SH:pay to script hash
- 交易的输出脚本是一个脚本哈希
- 交易的输入脚本是一个脚本
- 实际是为了支持多重签名实现的(为私钥的泄露提供保护、为私钥的丢失提供了冗余)
// 第一阶段
PUSHDATA(Sig) //压栈签名
PUSHDATA(Script) //压栈脚本(redeem script)
HASH160 //计算栈顶元素的哈希值
PUSHDATA(ScriptHash) //压栈脚本哈希
EQUALVERIFY //比较两个栈顶元素是否相等,不相等就停止执行
// 第二阶段
PUSHDATA(Pubkey) //压栈公钥
CHECKSIG //检查签名,弹栈
-
多重签名
- 最初实现上有一个 bug、会多压入一个元素
- 一开始、实际使用币购物的时候会不太方便、比如需要标出是 3of5 或者是 2of3,复杂性就交给了用户了。
- 然后用 P2SH 来实现多重签名,就可以电商公布脚本的哈希值就可以了(就相当于了一种封装)
-
Proof of Burn
- 为了证明自己有权利、可以烧掉一些币,然后就可以获得一些权利(比如要修改某个区块的内容)
- 或者有一些小的币种,要求烧掉一些比特币,然后就可以获得这个币种(AltCoin)
- 或者作 digital commitment
- 如果 output 是 0,那么全节点就不需要维护到 UTXO 中
-
这种语言就叫做比特币脚本语言(Bitcoin Script),甚至不支持循环、就不用担心死循环的问题
10 分叉
-
state fork:由于挖矿出块的不同导致的分叉
-
forking attack(deliberate fork)
-
protocal fork:由于协议的不同导致的分叉(不同意或者还没更新不同的版本)
-
hard fork:对比特币协议添加一些新的特性、没升级的节点会认为这个区块是无效的,就会导致分叉(例子:比特币中的区块大小限制,因为小的区块大小、会限制交易的 through put、而且会增加交易的延迟(因为会推迟到后续的节点))
- 这个分叉是永久性的、只要旧节点不更新软件、就会一直存在分叉
- 区块的大小也不是越大越好,因为采取 flooding 的方法、带宽是瓶颈
- 会形成社区分裂(比如 ETH 和 ETC),账户余额也是分开的
- 这个 fork 原来会有一个问题、就是一条链上的交易可以在另一条链上回放、通过 chain id 的方式解决了这个问题
-
soft fork:对比特币协议加了一些限制、原来的一些区块、在新链上不是合法的了,就会产生分叉
- 比如说把一个节点的区块大小限制为 500KB,而原来的区块大小是 1MB,那么这个旧节点挖出的区块会认为小的那个长的区块链是合法的、但是系统不会有永久性的分叉(所以叫软分叉)
-
著名的软分叉例子:P2SH
11 问答
-
转账交易的时候、如果接收者不在线会怎么办?
- 不需要接收者在线,只需要在区块链上记录就可以了
-
收款地址,有没有可能是这个发送者从来没听说过的?
- 有可能,因为比特币的地址只有在交易的时候才会被其他人知道,创建的时候、链上的人是不知道的
-
私钥丢失了怎么办?
- 没有办法了,因为没有中心化的机构可以找回(账户上的钱变成了死钱,因为没有重置密码)
- 比特币存在交易所里、那么就可以找回联系交易所
- 但是这些交易所缺乏监管,可能会被黑
- 最著名的例子是 Mt.Gox(不是个例,卷款跑路也是有可能的)
-
私钥泄露了怎么办?
- 转到另一个安全的账户里去
- 没有办法冻结账户,因为没有中心化的机构可以冻结账户(公私钥生成之后是无法改动的)
-
转账的时候写错了地址怎么办?
- 没有办法了,因为没有办法取消一个已经发布了的交易(没有提供撤销交易的机制)
-
OP_RETURN 为什么能够被写到区块链里?
- OP_RETURN 是永久返回错误的
- 但是因为他是在输出脚本里的,而不是在输入脚本里的,所以不会影响到交易的合法性检查
-
会不会有某个矿工偷答案,怎么知道是哪个矿工最先找到这个 nounce?
- 因为 head 里面有一个 coinbase tx 里的收款人的地址、如果改变的话、会导致 hash 值的改变、原来的 nounce 就作废了
-
怎么知道哪些交易费应该给哪个矿工?
- 事先不需要知道(总输入减去总输出就是手续费),差额就可以给这个矿工
-
变化和发展趋势
- Blockchain size 在不断增大
- UTXO 集合是在不断增大的(也有历史原因、比如有些账户私钥丢失了
- 矿池的百分比
- 交易量的增长,波动非常大(也是由于价格导致的)
- 交易数目的增长
- 每个区块的交易数量的增长
12 匿名性
-
跟现金相比,比特币的匿名性是有限的。但是和银行账户相比,比特币的匿名性是更好的。(因为实名,如果银行是匿名的,那么比特币还是没有银行好、因为比特币的账本是公开的,而且是不可篡改的)
-
即使每次交易都换一个地址,也是可以把很多个地址联系在一起的、所以匿名性也会被破坏
- 比如说有多个输入、可能是同一个人的(为了找零)
- 输出地址中也会有一个找零的地址(但是可以通过账本来分析出来)
- 如果要更强的隐私保护、可以多生成几个不必要的输出地址(但是现在常用的都基本是几个钱包、因此只要分析这几个生成策略就可以了)
-
同时、这个比特币账户可以同现实世界中的操作联系起来(即当实体世界中发生联系的时候,就会泄露)
- 即在资金转入和转出区块链的时候就会发生泄露(除非场外交易,即线下交易、但其实也会泄露身份)
- 为了反洗钱:就是盯紧这个转入和转出资金的过程
- 以及、比特币支付的时候(但是是 bad idea,因为延迟很大、而且交易费很大,而且会隐私泄露)
- 而且交易账户之间都是有联系的,所以也是可以追踪个人的其他账户的
- 即是是信用卡支付、也是有可能被追踪的(时空过滤的研究例子)
- 所以比特币的匿名性没有那么好
-
比特币的匿名性谁保持的最好?中本聪,因为没有和现实银行的往来
-
怎么增强比特币使用的匿名性?
- 首先在网络层的匿名性、比如使用不同的 ip、或者多路径转发(TOR 洋葱路由)、比如中间要经过很多跳
- 应用层怎么保证匿名性呢?把币混在一起:coin mixing(但是没有什么信誉比较高的 coin mixing 机构)
-
零知识证明:一方向另一方证明某个陈述时正确的、而不泄露除了陈述是正确的外的任何其他信息
- 数学基础:同态隐藏
- 如果 x,y 不同、那么算出来的加密函数值 E(x)和 E(y)也是不同的
- 给定 E(x)的值、很难算出 x 的值(不可逆的)
- 给定 E(x) 和 E(y) 的值、可以算出 E(x+y) 的值(同态加)
- 给定 E(x) 的值、可以算出 E(k*x) 的值(同态乘)
- 扩展到多项式
- 这里的例子是 CSE 中举过的例子
- 数学基础:同态隐藏
-
还有一个盲签的例子(银行签名)
- 一个盲签的例子:盲签就是签名的时候、签名者不知道签名的内容是什么
- 目的:银行不知道这个币是哪来的、银行无法把 A 和 B 联系起来
-
零币和零钞
- 零币:存在基础币和零币,通过基础币和零币的转换、消除旧地址和新地址的关联性,类似于混币
- 零钞:使用 zk-SNARKs,即零知识证明,证明自己有一定数量的币,但是不泄露具体的数量
- 但是也不是很主流,因为为了匿名性付出了性能的代价,而且可能需要强匿名性的用户也不是很多
- 而且也不是百分百安全,就是和现实世界转账的时候、与实体发生交互的时候、会留下痕迹、暴露身份
13 思考
-
指针中的地址只在本地中有意义、那么在实际中怎么做到呢?
- 其实实际上只有哈希、没有指针
- 全节点一般把这些区块存在一个 key-value 数据库里(比如 levelDB)
-
区块恋
- 如果丢失了、那么就取不出来了
- 而且截断私钥会降低安全性,因为原来是是 256 位的,剩下的可以暴力破解
- 所以对于多个人的共享账户、不要用截断私钥的方法、应该用多重签名的方法 MULTISIG(每个私钥都是独立产生的)
- 而且如果不取出来、这个币就会永久保存在 UTXO 里面、对矿工是不友好的
-
分布式共识
- 为什么比特币系统没法绕开分布式系统中的不可能结论?
- 因为并没有达到真正意义上的共识,可能会有分叉之类的
- 异步的宕机是不可能理论上解决的、但是例子给出了、可以给一个值班人员打一个电话(就不是理论上的异步模型了),进一步解决:就是再给一个服务器连一根电话线(因为电话线不会拥堵)
-
比特币的稀缺性
- 但是总量恒定的货币不太适合作为货币的
- 但是一个好的货币是需要有通货膨胀的功能的(即使黄金都是新挖出来的,但是黄金增长的速度没有货币增长的速度快)(类似于房地产)
-
量子计算
- 首先会冲击传统金融业
14 以太坊概述
-
以太坊对比特币的实际使用中的问题进行了改进(比如出块时间)
-
还改进了 mining puzzle
- 比特币是计算密集型(导致了硬件的专业化)
- 以太坊是内存密集型(memory hard)(实现了 ASIC resistance)
-
同时将工作量证明(proof of work)改成了权益证明(proof of stake)
-
同时增加了一个对智能合约的支持(smart contract)
- BitCoin:decentralized currency
- 去中心化的货币好处:跨国转账(用法币很麻烦)
- Ethereum:decentralized contract
- 去中心化合约的好处:保证合同的有效性(通过代码写好的技术手段保证不违约)
15 以太坊的账户
-
相较于比特币的交易是基于 UTXO 的,Ethereum 的是基于账户的,不用说明币的来源:
- 好处:防御 double spending 的方式更简单(不用管币的来源,只需要检查账户余额是否足够)(余额是系统中全节点维护的,难以篡改)
- 弱点:可能会出现 replay attack,因为 B 可以再广播一次这个交易(但是在比特币中不会有这个问题)
- 防御方式:加一个 nonce、每次交易都有一个 nonce+一个签名、全节点还要维护 nonce
-
账户的第一类:外部账户(external account)
- balance:账户余额
- nonce:账户的交易计数器(每次交易都会加 1)
- 由一个公钥和一个私钥组成(控制)
- 通过私钥签名交易,证明自己有权利使用这个账户
- 账户余额是系统中全节点维护的
-
账户的第二类:合约账户(contract account)
- nonce:合约的交易计数器(每次合约调用都会加 1)
- 由一个合约代码(code)和一个存储(storage)(每个变量的取值)组成
- 合约代码是用 Solidity 编写的
- 存储是一个 key-value 数据库,存储合约的状态
- 代码是不可变的,存储是可变的
- 合约账户没有私钥,不能直接主动发起交易,而是通过合约代码来执行交易(只能外部账户调用合约账户)
-
比特币的隐私保护好一点、但是以太坊要支持的是智能合约,需要明确的身份
-
financial derivatives:金融衍生品
- 比特币的交易是基于 UTXO 的,不能直接支持金融衍生品
- 以太坊的交易是基于账户的,希望保持稳定,可以直接支持金融衍生品(比如期权、期货等)
16 状态树
-
要完成账户地址 addr 到账户状态 state 的映射
-
trie 树
- trie 是一种特殊的树结构,支持高效的前缀查询
- Patricia trie 是路径压缩的 trie 树(前缀树)
-
以太坊使用的是 Merkle Patricia Trie(MPT),结合了 Merkle Tree 和 Patricia Trie 的特点
- 这里的键值是稀疏的、因为存储的是账户地址
- 账户地址是 160 位的哈希值(20 字节)(地址长是为了防止哈希碰撞,账户冲突)
- 存储是一个 key-value 数据库,key 是 256 位的哈希值(32 字节)
- merkle:把普通指针变成了哈希指针(即每个节点的指针是一个哈希值,而不是一个地址)
- MPT 的根节点是一个哈希值,表示整个状态树的状态
- 这里的键值是稀疏的、因为存储的是账户地址
-
好处:
- 根据根节点的哈希值,可以验证整个状态树的完整性(不会被篡改)
- merkle proof:可以证明某个账户的状态是合法的,而不需要遍历整个状态树
-
Modified Merkle Patricia Trie(MMPT)
- 优化:为了支持更高效的状态树更新
- leaf nodes 是存储账户状态的节点,包含账户地址和账户状态
- branch nodes 是存储子节点的节点,包含子节点的哈希值
- extension nodes 是存储路径压缩的节点,包含路径的哈希值
- 每次出现一个区块、都要新建一个状态树,包含该账户的信息
-
历史记录存储的意义:需要回滚(因为可能会有分叉冲突)(以太坊的智能合约是图灵完备的)
-
RLP(Recursive Length Prefix)
- 用于序列化和反序列化数据
- rlp 只支持 nested array of bytes(嵌套的字节数组)
- 以太坊的状态树使用 RLP 来存储节点信息
- RLP 的好处:可以高效地存储和传输数据,支持嵌套结构
- 即 value 需要序列化成 RLP 格式后再进行存储
17 交易树和收据树
-
交易树(Transaction Tree)
- 是 MPT,与比特币中的 Merkle Tree 不完全一样
- 每个交易一棵、类似比特币
-
收据树(Receipt Tree)
- 是 MPT
- 交易结束记录收据等信息
- 有利于查询交易的一些数据和结果
-
用途:
- 提供 merkle proof,证明某个交易或收据是合法的
- 快速查询符合查询条件的交易(通过 bloom filter,只需要看区块头就可以进行快速筛选(局限性:简单的过滤器不支持删除))
-
以太坊的运行过程可以看作是一个交易驱动的状态机
- 每个交易都是一个状态转换
- 交易树和收据树记录了所有的状态转换和结果
- 状态树记录了当前的状态
- 比特币中的状态树是 UTXO 集合,而以太坊中的状态树是账户状态
18 GHOST 协议
-
centralization bias
- 比特币的挖矿是基于工作量证明(PoW)的,可能会导致算力集中化
- 算力集中化会导致中心化倾向,影响去中心化的特性
-
GHOST(Greedy Heaviest Observed Subtree)
- 解决以太坊中的共识问题
- 最初版本:如果挖出了一个 uncle block(叔块),那么挖出这个区块的矿工也可以获得部分奖励(7/8 的奖励)(给予安慰,依然能得到大部分出块奖励)
- 一个区块最多可以有 2 个 uncle block
- 有利于系统出现分叉之后快速进行合并
- 最初版本的缺陷:自私的矿工可能故意不把 uncle block 包含,导致其他矿工无法获得奖励
- 扩展:隔代叔父、允许几代内的区块也可以认定 uncle block(2/8 到 7/8 的奖励)(7 代以内有共同的祖先)(这个最大限制、是为了防止节点维护的状态过多)
- uncle block 是得不到 gas fee 的
- 为了防止分叉攻击、以太坊规定只有分叉之后的第一个区块才会被认可(即只有第一个区块的 uncle block 才会被认可)
-
etherscan
- 以太坊的区块浏览器
- 可以查看区块、交易、账户等信息
- 可以查看交易的收据和状态
- 可以查看交易的 gas fee 和 uncle block 的信息
19 挖矿算法
-
设计时,目标做到 asic resistance(抗 ASIC),即不希望出现专门的挖矿芯片
- 常用做法:增加内存的使用量(memory hard),即挖矿算法需要大量的内存来计算
- 因为 asic 芯片对于内存访问的效率和通用芯片差不多
-
LiteCoin
- 使用 Scrypt 算法,内存密集型的哈希算法
- 即开设一个很大的数组、数组的前后是有前后依赖关系的、然后需要保存数组中的所有元素(否则就会降低计算效率)
- 坏的地方在于:对轻节点不友好,因为需要大量的内存来计算(应该达到目的:difficult to solve,but easy to verify)
- 但是最后还是没有对 asic 芯片起到很好的抗性,因为只用了 128K 的内存需求
- 除了一些微小的差异、比如出块速度是 2.5 分钟、而比特币是 10 分钟、其他的都差不多
-
以太坊用的是两个数据集,一开始是 16M 的 cache 和 1G 的 DAG
- DAG 是从 cache 生成的
- 轻节点只需要保存 cache,就可以验证交易的合法性
- 但是 DAG 是需要每个矿工都要保存的(因为 DAG 是每个区块都要用到的)
- cache 和 DAG 是要定期增长的(目前 DAG 大小更大了)
- 读取方法和 Scrypt 类似,根据伪随机的逻辑、读取 256 次、然后再填充到一个数组中 (从 DAG 中选取特定位置的数据)
- 区别:读取的时候还需要读取相邻的内容
- 这个是 PoW(Proof of Work)算法,但是现在以太坊已经转向了 PoS(Proof of Stake)算法,这个算法已经不再使用了
- 没有 asic 矿机、还有一个原因是要转向 PoS(Proof of Stake)权益证明
-
pre-mining
- 以太坊在发布之前就预留了一些币(为了奖励早期的开发者和支持者)
- 预挖的币是通过一个智能合约来分配的
- 预挖的币是为了支持以太坊的早期发展和推广
-
pre-sale
- 以太坊在发布之前进行了预售,允许用户购买以太币(ETH)
- 预售的目的是为了筹集资金支持以太坊的开发和推广
- 预售的以太币会在以太坊正式上线后发放给用户
20 难度调整算法
- 为了维持出块时间
以太坊的 难度调整算法(Difficulty Adjustment Algorithm, DAA)是其工作量证明(PoW)机制的核心组成部分,主要用于动态调整区块生成的难度,以维持平均出块时间稳定(以太坊的目标是 约 15 秒/区块)。以下是其核心逻辑和关键规则:
1. 基础难度调整
每个区块的难度根据 父区块的难度 和 当前出块时间 动态计算,公式简化如下:
新难度 = 父区块难度 + 难度调整项
难度调整项由两部分决定:
- 时间差调整:
如果当前区块时间戳与父区块时间戳的差值(block_time)小于目标时间(15 秒),则增加难度;反之则降低难度。 - 难度炸弹(Difficulty Bomb):
一种随时间指数增长的额外难度,旨在推动网络从 PoW 向 PoS 过渡(后文详述)。
2. 具体规则
(1)时间差调整
- 目标出块时间:15 秒。
- 调整公式:
难度调整 = 父区块难度 × (调整系数) / 2048- 如果
block_time < 15秒:调整系数为max(1 - (block_time // 10), -99),难度增加。 - 如果
block_time ≥ 15秒:调整系数为min((block_time // 10) - 1, 99),难度降低。
(注://表示整数除法)
- 如果
(2)难度炸弹(Ice Age)
- 目的:通过指数级增加的难度,迫使矿工支持 PoS 升级(如以太坊 2.0)。
- 计算公式:
难度炸弹增量 = 2^(当前区块高度 // 100000 - 2)- 每 10 万个区块(约 4.3 个月)难度炸弹的增速翻倍。
- 在多次升级中(如 "Byzantium"、"Constantinople"),以太坊通过 延迟难度炸弹 避免出块时间过长。
(3)叔块(Uncles)影响
- 如果当前区块包含叔块(即引用其他矿工的未确认区块),难度会略微降低(奖励矿工维护网络安全性)。
3. 实际效果
- 短期波动:网络算力变化时,难度会快速调整以稳定出块时间。
- 长期趋势:难度炸弹的引入使得 PoW 挖矿逐渐不可持续(最终在 "Merge" 升级后完全弃用 PoW)。
4. 以太坊 2.0 后的变化
- PoS 取代 PoW:难度调整算法被废弃,改为由验证者(Validators)通过质押 ETH 出块。
- 当前 PoW 链(如 ETC):仍沿用类似的难度调整机制,但移除了难度炸弹。
以太坊的发展历程分为四个主要阶段,按时间顺序依次为:
-
Frontier(前沿)
以太坊的最初版本(2015 年 7 月),开发者可开始测试网络、挖矿并构建去中心化应用(DApps),功能较为基础。 -
Homestead(家园)
2016 年 3 月启动,标志着以太坊进入稳定阶段,优化协议安全性并改进交易处理,为大规模应用奠定基础。 -
Metropolis(大都会)
分为两个子阶段:- Byzantium(拜占庭):2017 年 10 月,引入 zk-SNARKs 隐私技术、难度炸弹延迟等。
- Constantinople(君士坦丁堡):2019 年 2 月,进一步降低 Gas 成本,优化以太坊经济模型。
此阶段重点提升用户体验和可扩展性。
-
Serenity(宁静)
以太坊的最终阶段,核心是向**权益证明(PoS)**共识机制过渡(即以太坊 2.0),通过分片技术等实现高扩展性、低能耗的网络。
整体意义
这四个阶段展现了以太坊从实验性网络到成熟生态的演进,目标是通过分层升级解决性能、安全与去中心化问题。当前(截至 2023 年)Serenity 的部分功能已逐步落地,如信标链和合并(The Merge)。
21 权益证明
-
工作量证明缺点:浪费电
- 想法:能不能直接变成比钱的多少来进行分配
-
virtual mining:虚拟挖矿
- 优点:不需要消耗大量的电力,而且确保币的安全性不是通过挖矿来完全保证的
- 发动攻击的成本从这个币的内部得到
-
AltCoin Infanticide:扼杀新的币种在摇篮里(恶意攻击)
-
proof of deposit:存款证明
- 通过存入一定数量的币来证明自己的权益
-
nothing at stake:早期如果用权益证明的话、没有分叉的风险、即使你选择了错误的分叉、也不会损失什么(因为没有工作量证明的成本)
-
Casper the Friendly Finality Gadget(Casper FFG)
- 以太坊的权益证明机制
- 通过验证者(Validators)来维护网络安全
- 验证者需要质押一定数量的以太币(ETH)作为抵押
- 验证者可以通过验证区块和交易来获得奖励
- 如果验证者作恶(如双重签名),则会失去部分或全部质押的 ETH
- 每一轮投票都至少需要获得 2/3 的验证者同意才能达成共识
- prepare 和 commit 阶段:
- prepare 阶段:验证者对某个区块进行预投票,表示支持该区块
- commit 阶段:验证者对预投票的区块进行最终投票,表示确认该区块
- 如果验证者履行了职责,则可以获得奖励;如果验证者未履行职责,则会受到惩罚(如 slashing,削减质押的 ETH)(没收了就销毁了)
- 验证者有一定的任期
22 智能合约
-
转账的时候、费用可以是 0、但是也会有汽油费
-
智能合约运行在以太坊虚拟机(EVM)上
- EVM 是一个图灵完备的虚拟机,可以执行任意复杂的计算
- EVM 的代码是用 Solidity 编写的,Solidity 是一种类似于 JavaScript 的编程语言
- 增强可移植性
-
Halting problem:图灵完备的语言无法保证所有程序都能在有限时间内终止(停机问题)
- 因此智能合约的执行可能会出现无限循环或死锁的情况
- 为了防止这种情况,智能合约的执行需要消耗一定的 Gas(汽油费),Gas 是以太坊网络中的计费单位(同时也是为了防止恶意攻击)
- 不同指令的 Gas 费用不同,复杂度越高的指令 Gas 费用越高
-
智能合约的原子性:
- 智能合约的执行是原子的,要么全部执行成功,要么全部失败
- 如果智能合约执行失败,则会回滚所有状态变化,保证数据的一致性
- 智能合约中不存在自定义的 try-catch 机制,所有异常都会导致交易失败并回滚
- 嵌套调用是否连锁回滚、需要看情况
-
gas limit:Gas Limit 是用户为单笔交易或智能合约执行设置的最大 Gas 用量。它代表交易执行期间允许消耗的 Gas 上限,用于防止因代码错误或恶意合约导致无限循环等意外情况,避免耗尽资金。
-
合约在本地修改完后、才会发布到区块链上成为节点的共识
-
执行错误的交易也要发布、因为要形成共识之后、才能扣掉汽油费
-
智能合约不支持多线程、因为它是一个确定的状态机、但是多线程对于状态机来说是不可预测的,造成执行结果不确定(类似地、也无法产生真正意义下的随机数、不然每个全节点执行完的结果都不一样)
-
简单例子:竞价拍卖
- 需要有人去调用这个智能合约的代码
- 没有并发执行
- 第一版:循环退回出价(可能会锁仓)
- 第二版:由投标者自己取回出价(可能会发生重入攻击、递归重复取钱)(进行修改bug后可以支持)(应该先把账户清零、然后再转账)
-
Code is law
- 智能合约的代码就是法律,执行结果是不可更改的
- 代码的漏洞可能导致资金损失(如 DAO 攻击事件)
- 智能合约的代码需要经过严格的审计和测试
- 智能合约也不保险给一个用户超级权限、因为可能会跑路
23 The DAO 攻击事件
-
The DAO 是一个去中心化的自治组织(Decentralized Autonomous Organization),旨在通过智能合约来管理资金和决策
- DAO 的代码是开源的,任何人都可以参与投票和决策
- DAO 的资金是通过众筹的方式募集的,募集了超过 1 亿美元的以太币(ETH)
- 但是、DAO只存在了 3个月,就被攻击者利用智能合约的漏洞攻击了
-
DAC:Decentralized Autonomous Corporation,去中心化自治公司
-
splitDAO(拆分 DAO)
- 拆分完得到一个childDAO
- 但是黑客通过重入攻击(reentrancy attack)来获取了 childDAO 的资金
- 最终采取了追踪黑客账户然后再进行回滚、但是软分叉修复的版本、会导致汽油费为0
- 因此采用了硬分叉的解决方式
-
硬分叉之后、挖出的币就是ETC(Ethereum Classic),而不是 ETH
24 反思
-
智能合约的不可篡改性其实是一个双刃剑
- 一方面增强了合约的可信度和安全性
- 但是另一方面,如果合约代码存在漏洞或错误,可能导致资金损失且无法修复
- 修复只能通过硬分叉、因为谁都可以调用只能合约
-
状态机是为了容错、而不是为了性能更强
25 美链
-
美链(BeautyChain)BEC
- 是用来做 ICO 的(Initial Coin Offering,首次代币发行)
- 发行代币作为一个智能合约、而不是作为一个单独的链
-
但是也出现了安全性问题
- 这里是出现了一个乘法溢出的漏洞
- 应该用solidity的SafeMath库来进行安全的数学运算
26 总结
-
应该应用的场景
- 跨国支付(原有的周期很长)
-
加密货币本不该和现有的方式竞争