分类
科研
创建时间
Oct 7, 2024 06:09 AM
状态
已完成🤠
🎉欢迎大家来到我的博客!!🎉
『前言』
由于科研训练的需要,我趁着国庆的间隙观看了北京大学肖臻老师《区块链技术与应用》公开课 ,本页面是关于这门课程的学习笔记(暂时只有比特币部分)。
我没有看过市面上其他的区块链课程🙂↔️,但从我的观看体验来看,这门课程是我继郑益慧老师模电课程以来看过的最优秀的课程。我们常说,只有真正掌握了某个知识的人,才能够将知识以恰当简单的方式将它阐述清楚,我觉得肖老师是真正做到了这一点的(不愧是❤️北大老师)。
在课程中,肖老师有一句名言让所有人都印象深刻,“不要被学术界的思维限制了头脑🧠,不要被程序员的思维限制了想象力💡(精准空降地址)”,当我们真正步入学术领域时,只有放心大胆的去尝试,不被已有的研究圈地为牢,也许才能像中本聪那样有比特币这样伟大的创造。
对于区块链完全小白的你而言,这个笔记绝对是非常适合你的,因为我就是纯小白开始看这个视频的(一开始甚至连哈希函数是什么都不知道😅),即便你有了完全的基础,也没有问题,因为那些对大佬们来说很简单的知识点我都链接到了额外的页面去说明。
P.S. 建议大家使用电脑来查看笔记内容,会有最好的显示效果。笔记中一些概念的解释、问题的回答来源于 GPT 和网络搜索,其中也加入了我自己的理解。
请大家擅用 【Ctrl + K】 和 【Ctrl + F】 两个快捷键。
课程资源
- 课程 PPT | 来源: http://zhenxiao.com/blockchain/
(官网声明)注意:
这门课的教学以课堂讲授和板书为主,下面列出的slides只是起到辅助教学作用, 并不是对讲课内容的一个完整描述,需要结合课程视频一起看才有意义。
- 比特币教材:
- 中本聪 2008 年发布的白皮书 《比特币:一种点对点的电子现金系统》| 来源:比特币:一种P2P电子现金系统 (bitcoin.org)
有关区块链和比特币的前置知识
这部分的内容不是必须的,不阅读不影响后续的课程,但有利于我们理解比特币和区块链的概念,这些内容也是我刚接触到比特币和区块链时的一些疑问。
📔 区块链的发展史
👉🏾 关于中本聪 👈🏾
这是个传奇的人物,直到现在也没有人知道中本聪究竟是谁,据说他在比特币发布的早期囤积了大量的比特币,目前市价应该有几百个亿,但是他却从来没有花费过比特币,对于他这样做的原因:
有人说,他是不愿显山露水,静待市场变化;
有人说,他是怕显露身份后陷入危机;
也有人说,他是把自己的私钥给忘了…
总之,大家的说法有很多,也很奇特,我们不去揣测这个天才的行为,但无论处于哪种原因,他在比特币出名后就基本没有在网上活跃了,不是为了钱,也不是为了名。他现实中的身份无人得知,只知道他使用的是日本人的名字。
比特币(BTC, bitcoin)
【2024.9.29】 → 【2024.10.7】
💡 Key Words
这里的关键词只是帮助大家看完右侧的笔记后回忆内容,不是跳转链接!!
比特币两个功能
- 哈希 → 三个性质
- 签名 → 公私钥
数据结构
- 哈希指针
- 创世纪块 最新区块
- 篡改证明记录
- merkle tree
- 区块头 区块体
- 全节点 轻节点
- merkle proof
BTC 协议
- BTC 协议
- double spending attack
- 签名 来源证明
- 分布式共识
- FLP Imposibility
- CPA Theorem
- 记账权
- hash rate
- 币基奖励
BTC 实现
- 激励机制
- 区块奖励
- 具体区块分析
- 根哈希值的修改
- 输入输出脚本
- 区块产生时间
- zero confirmation
- selfish mining
BTC 网络
- 应用层
- 网络层
- 超级节点
- 种子节点
- TCP 协议
- flooding
挖矿难度
- target space
- 难度计算公式
- 出块时间的确定
- 难度调整的时间间隔
- nBits 域
BTC 挖矿
- 全节点、轻节点
- 安全性
- 挖矿设备
- 矿池
- share
- boycott
- on demand mining
BTC 脚本
- 比特币最小单位
- 交易总结构
- 交易输入
- 交易输出
- 区块链中的表示
分叉
- 状态分叉(故意分叉)
- protocol attack 协议分叉
- 硬分叉(向后不兼容)
只要还有旧节点就不会统一
- 软分叉(向后兼容)
即便一时有旧节点最终仍可以统一
匿名性
- 匿名性被破坏
- 关联账户被发现
- 账户 → 真实地址
- 提高匿名性
- 网络层
- 应用层
- 零知识证明👇🏾
零知识证明
- 概念
a → b,无需其他信息
- 同态隐藏
- 盲签方法
- 零币和零钞系统
新的匿名电子系统
🔗 Relevant Information
📝 Class Notes
笔记中引用的知识点链接可能点击后需要刷新两次才能正常显示🤒
课程大纲

比特币前置知识点
这里只是列出后面会涉及到的知识点,可以不用急着全部看完,后面涉及到相关的知识点的时候我会附上相关的链接。当然,如果你是忘记了某个知识点,也可以通过下面的链接进行查看(也可以【Ctrl + K】直接搜索)
下面的链接点击后可能需要刷新两次才能够正常打开😶🌫️🥺
比特币的两个功能
比特币被称为是加密货币(crypto-currency),但加密货币本身却又是公开的,即区块链上所有交易都是公开的,如交易的地址、交易的内容。
比特币主要用到了密码学中的两个功能,一个是哈希,一个是签名。
哈希:
性质一:Collision Resistance(抗哈希碰撞)
对于两个不同的输入 x 和 y,它们通过 【 哈希函数 】计算出来的哈希值相同 ,即 ,这个是可能出现的且不可避免的,因为输入空间是远远大于输出空间的,假如有 256 位的哈希值,它的输出空间是 ,但是它的输入空间是无穷大的,因此只要数量足够多,两个不同的输入肯定会有相同的输出(满足【 鸽笼原理 】)。
哈希碰撞是难以通过人为的方法去产生的,因为想要人为的产生两个相同的哈希值,那么就只能遍历整个哈希输出空间,若哈希值是 256 位的,那么就需要遍历 的空间,显然是低效的。
哈希碰撞在数学上是无法证明的,哈希碰撞无法通过人为的方法实现,是实践检验的成果。因此,有些哈希函数过去认为是 collision resistance 的,但是后来却又被人破译,找到了人为实现碰撞的方法,如 MD5。
那么,抗哈希碰撞有什么用处呢?
它的一个好处就是可以防止数据被篡改,由于想要人为的产生两个相同的哈希值是异常困难的,因此我们可以通过本地保存哈希值的方式辨认上传的内容是否又被篡改。这里有两个名词需要记住,一个是 message(信息),一个是 digest(摘要)【 区块链中的 message 和 digest 】。当我们本地上传了一个message 后,计算出来的哈希值是 ,如果此时有另一个人想要篡改文件,上传了另一个 message’ ,那么他计算处理的哈希值就是 , 和 通常不会相等,会被检测出来。
性质二:hiding(隐藏)
hiding 的意思是哈希函数的计算过程是单向的,不可逆的。x → H(x) 的过程是单向的。
hiding 实现的前提是输入的空间要足够的大,使得蛮力求解是无法实现的,且输入的分布要比较均匀,各种取值的可能性是差不多的,否则值可能会集中在某一个区段内,那么还是容易被破解。
digital commitment(数字承诺)/digital equivalent of a sealed envelope
将上述两个性质结合起来,就可以实现区块链中的 digital commitment 或 digital equivalent of a sealed envelope。
sealed envelope 从现实的意义上来说,比如有一个股票的预测家, 他预测了明天的股票的涨跌情况,并将这个预测情况公之于众,用于第二天验证它的说法,显然这样的做法会影响原来的结果。原来也许股票是跌的,因为他的预测,大家争先购买,股票就涨了,也许股票原来是涨的,大家为了不让他预测成功,股票就跌了。
为了不让股票的预测结果不受他预测的影响,同时又能够预测他预测的准确性,就需要先将他的预测封装到一个信封(sealed envelope)中,等第二天结果出来以后再再与信封的内容对比,判断准确性。
从区块链的角度上来说,如果股票预测师将自己的预测结果发布在区块链上作为哈希值,等到第二天结果出来以后公开,那么由于区块链的 Collision Resistance 的特点,原来上传的预测结果是不可被篡改的,那么就能够实现 sealed envelope 的功能。
上述的例子都是默认股票的分布是满足 hiding 的性质的,也就是说,他们的分布是均匀的,如果他们的分布并不均匀,可以通过将原来的输入后面拼接一个随机数以贴合条件,即 。因为对于哈希函数而言,输入即便只有微小的差异,它的输出也可能是千差万别的。
性质三:puzzel friendly
这个性质说明的是如果我们想要得到一个特殊的哈希值,我们只能通过一个一个输入来尝试,直到得到符合我们条件的哈希值。

假设我们希望我们的哈希值落在 target space 的范围内(整个长条表示 256 位的哈希值), 我们只能通过一个一个的去给输入添加不同的 nonce 来得到符合我们要求的哈希值(小于某个阈值,),上述的这个过程就是在【 挖矿 】,挖矿就是在寻找符合条件的 nonce。由于区块链中 puzzel friendly 的特性,因此挖矿是没有捷径的,只能一个一个尝试着去挖,也正因如此,它才可以用于作为工作量证明(PoW,Proof of Work)【 PoW 】 。挖矿的特性可以通过这句话来解释”Difficult to Solve, but easy to verify“(挖矿难,但验证容易)。
比特币中使用的哈希函数是 SHA-256,即 Secure Hash Algorithm - 256 ,它是满足上述的三个性质的。
签名:
比特币系统中一般是先对一个值计算哈希,然后对哈希值进行签名。
比特币系统中的账户管理
在日常生活中,如果我们需要开一个账户,我们需要银行这样的中心服务机构,而在比特币系统中创建一个账户,我们不需要任何的重心机构支持,因为区块链系统本身是去中心化的,任何人都可以创建账户,不需要任何人批准,所需的仅仅只是一个公私钥对。
在传统的账户系统中,会采用对称加密体系来进行数据的加密,即解密和加密使用的是同一个密钥;
在区块链系统中,会使用非对称的加密系统来进行数据的加密,账户的保存,即解密和加密使用的是一对公私密钥。私钥的大小是 256 位的随机数,也就是 32 字节;未压缩公钥的大小为 65 字节,压缩公钥的大小为 33 字节。
对于对称加密系统和非对称加密系统以及公私钥对的内容,参见 对称加密体系和非对称加密体系 。
在 SHA-256 的哈希函数中,由于输出空间是非常大的,因此想要破解私钥虽然理论上可行,但实行起来是非常困难的。公私钥不会被其他人破解的前提是我们有一个很好的随机源用于产生公私钥,并且要确保之后每一次的数字签名也要有好的随机源,只要有一次质量不佳,就会造成私钥被破解,数据的大量泄漏。
(公钥用于加密,私钥用于解密)与(公钥用于解密,私钥用于加密)的两种用途
“公钥用于加密,私钥用于解密” 有什么应用?
这种应用方式是我们在日常生活中常见的,比如我们想要给别人发送一条消息,而我们不希望有第三方知道我们通信的内容,我们就可以先将公钥发送给需要通讯的一方 B,B 使用我们发送的公钥加密数据后,将消息发送给我们,我们就可以使用本地的私钥解密消息,得到讯息。由于私钥始终存放在本地,未有传输,因此不会有人破解消息。
“私钥用于加密,公钥用于解密“ 有什么应用?
这种方式主要应用在区块链的数字签名,目的是验证数字签名的准确性,而不是避免消息的泄漏。
比如,我想要购买一个有 周杰lun 亲笔签名的唱片,他通过私钥加密了自己的签名,当其他人购买这款唱片的时候,会得到一个公钥,他们可以通过公钥对签名进行解密,查看是否真的是 周杰lun 本人的签字,如果是的话,他们可以成功通过公钥对签名解密,如果不是,那么就无法进行解密。
其他人无法篡改签名的内容,因为他们并不会拥有私钥,这种方式极大程度的保证了数字签名的准确性,确保了来源是可以信任的。
哈希的数据结构
哈希指针和区块的连接
要介绍哈希的数据结构,首先要了解什么是哈希指针,它是哈希值和指针的结合体,详细内容请看 【 哈希指针 】 。
区块链就是一个一个区块相互连接而成的,第一个区块称为是创世纪块(genesis block),最后一个区块被称为是最新区块(most recent block)。区块链中的连接是通过后一个区块的哈希指针指向前一个区块形成的,哈希指针中的哈希值是通过前面一个区块的所有内容【这里老师口误了,在后面的视频学习中,老师也指出哈希指针中的值是仅通过前一个区块的区块头计算出来的,因为区块头中就已经包含了该区块的关键信息,能够用于区块的验证】计算出来的(如第 5 个区块的内容就是通过图中红框部分所示的区块计算出来的),包括前一个区块的哈希指针。最新区块的内容也会通过一个哈希指针保存在本地。

哈希指针的存在可以有效避免区块中的数据被篡改,实现 tamper-evident log(篡改证明记录)。当有恶意的个体将区块的内容篡改时,该区块往后的一个区块的哈希指针就会发生变化,而该区块哈希指针的变化又造成该区块后一个区块的哈希指针发生变化,以此类推,最终造成指向最后一个区块的哈希指针发生变化。而我们本地恰好保存着最后一个哈希指针,因此通过对比哈希指针的值就可以判断区块内容是否被篡改。
在实际的应用中,有了哈希指针,在面对区块链中的大量区块时,我们可以只保存其中某些区块的哈希指针,而不用保存所有的,当某个区块中的哈希指针发生变化的时候,我们可以锁定哈希指针发生变化(被篡改)的区块范围。
Merkle tree(默克尔树)
有关 Merkle tree 和 Binary tree 的内容,可以跳转到该页面 【 Merkle tree(与 Binary tree) 】。
Merkle tree 又称为是哈希树,它与二叉树的结构类似,但是每一个节点都相当于是一个哈希指针。
叶子节点指向的是一个个交易数据,非叶子节点指向的是子节点所在的区块,每个非叶子节点都连接着两个子节点。
所有的路径最终都会指向 merkle root (根节点),这也就是说只要交易数据发生了变化(被篡改),merkle root 的哈希值就会发生改变。Merkle tree 的示例图如下所示:

图中每个 tx 表示一个交易数据。
这里需要注意一点,Merkle tree 中每个节点都存储着指向子节点的哈希指针,但也仅仅是存储着哈希指针而已。他们并不是像区块一样包含区块头和区块体,因此不能够视为是区块。Merkle tree 存在的意义就是为了检测交易数据是否又被篡改,它存在于区块体中。区块头和区块体的概念在下面会有详细的介绍。
区块链中的区块头、区块体、全节点、轻节点以及 Merkle Proof
区块链中的每一个区块都包含一个区块头(block header)和一个区块体(block body)。
区块头是一个区块的“元数据”,包含了一组有助于验证区块的关键信息,通常包括:
- 版本号(Version):区块格式的版本号,表明当前使用的协议版本。
- 哈希指针(Hash Pointer):指向前一个区块的哈希值,确保区块链的连续性。
- Merkle Root:区块中所有交易数据的哈希树根节点,它通过哈希交易数据验证交易的完整性。
- 时间戳(Timestamp):区块生成的时间,用来标记区块被打包的时间。
- 难度目标(Difficulty Target):用来调整工作量证明(PoW)难度的目标值。
- 随机数(Nonce):用来满足工作量证明(PoW)要求的随机数,通过调整该值找到合适的区块哈希。
区块头是区块链中每个区块的核心组成部分,它非常小,通常为80字节。
区块体包含了该区块中所有具体的交易数据,也包括一些额外信息。主要内容为:
- 交易列表:区块中所包含的交易记录,每个交易都有发送方、接收方和交易金额等详细信息。
- 【 Coinbase 】 交易:区块中记录的第一笔交易,是矿工打包这个区块后所获得的奖励。
区块体可以包含大量交易数据,因此区块体的大小远远大于区块头,具体大小根据网络设置和交易数量而变化。
区块体中的交易数据都是通过 merkle tree 组合起来的,merkle tree 的概念在下面会进行介绍。
那么,知道了什么是区块头和区块体后,什么又是全节点和轻节点呢?它们又和区块头和区块体有什么关系呢?
全节点(Full Node):

全节点是在区块链网络中运行的节点,保存了区块链的完整历史记录。它们存储着所有区块的区块头和区块体,并通过验证每个交易和区块的正确性来维护网络的安全和共识。
全节点的关键职责包括:
- 验证交易和区块:全节点下载所有区块的区块头和区块体,验证每个交易的合法性。
- 参与共识:全节点负责接受和传播新区块,确保网络的状态与其他节点保持一致。
- 存储完整链:全节点存储整个区块链数据,能够从创世区块一直追溯到最新区块。
轻节点(Light Node):

轻节点(也叫简化支付验证节点,Simplified Payment Verification,SPV 节点)是一种只存储区块头而不存储区块体的节点,因此它们不需要维护区块链的完整历史记录。轻节点的资源需求更低,特别适用于资源受限的设备(如手机或嵌入式设备)。网络中的大部分节点也都是轻节点。
轻节点的工作方式:
- 存储区块头:轻节点只下载区块头,而不存储区块体,从而大大减少存储和带宽需求。
- 依赖全节点:轻节点无法自行验证交易的完整性,它们依赖于全节点的验证。轻节点可以通过区块头中的 Merkle Root 进行部分验证。
Merkle Proof:
Merkle Proof 是使用轻节点验证某笔交易是否包含于某个区块中的一种证明机制。由于轻节点不存储区块体,它无法直接访问所有交易数据,但可以通过 Merkle tree 来验证某个交易是否属于特定区块。
工作过程
Merkle tree 结构:在区块链中,所有交易数据通过哈希计算形成 Merkle tree,最终生成一个根哈希(Merkle Root),这个哈希值存储在区块头中。

证明过程:如果需要验证某个交易是否存在于某个区块中,轻节点只需下载该交易所在的叶子节点以及从该叶子节点到根节点的哈希路径(如图中所示),红框部分的内容是并不需要的。然后,轻节点可以通过这些哈希值重新计算出根哈希,若计算结果与区块头中的 Merkle Root 匹配,则该交易被证明是有效的。
Merkle Proof 的好处是,轻节点只需存储少量的区块头信息,并且只需要下载少量的哈希值即可验证交易,避免了下载整个区块体数据。
区块链的理解(结合上面介绍的概念)
区块链是由大量的区块连接而成,每个区块的区块头中都存储了前一个区块头的哈希值,作为指向前一个区块的指针。这确保了每个区块与前一个区块的顺序和一致性。每个区块表示一个时间段内的交易,区块体存储了该时间段内的所有交易记录,这些交易通过 Merkle tree 组织起来。区块体中的交易数据不会直接存储在区块头中,而是通过 Merkle tree 的根节点哈希值进行概括,这个根节点哈希值被存储在区块头中,确保了交易数据的完整性和不可篡改性。同时,区块头还存储了前一个区块的哈希指针,从而链接整个区块链。

什么是 proof of membership?
Proof of Membership(又称 Proof of Inclusion) 是用于验证某个特定数据元素是否属于某个集合的方法,通常是在一个大的数据集合中查找某个元素。
- 验证内容:提供一个数据元素是否存在于某个集合的证明。
- 应用场景:常用于钱包、数据库、区块链中,以验证特定交易或数据是否存在于某个区块或账户中。
- 实现方式:
在使用 Merkle tree 的情况下,可以通过提供路径(即从叶子节点到根节点的哈希链)来证明某个交易或数据存在于区块体内。这种路径称为Merkle Proof。它的时间复杂度是 O(log2(n))
那么,什么是 proof of non-membership?
顾名思义,就是要证明某个特定交易不在某个区块体内,这种验证通常需要遍历所有的交易数据,计算的时间复杂度是 O(n)。
有一种方法可以降低时间复杂度,那就是将所有的交易数据先计算一次哈希值,然后根据哈希值的大小进行排序。将想要验证的交易计算一次哈希值,如果该哈希值是位于两个交易之间的(比如下图中两个黄色方框中间),而两个黄色方框经过 Merkle Proof 后得出的根节点值与本地保存的是一样的,则说明两个黄色方框本来就是相邻的,那么不可能会有哈希值位于这两个交易之间,因此它并不属于这个区块。

BTC 协议
Double Spending 👉🏾【 Double Spending(双花/双重支付) 】
情景假设:假设没有区块链,央行发行了自己的数字货币,使用非对称加密体系进行加密,私钥保存在央行内部,数字货币在使用时用户可以通过公钥进行验证,因此数字货币是无法伪造的。但是这样仍然会有一个问题——数字货币是可以进行复制的。虽然内容是无法伪造的,但实际上我们是不需要去伪造内容,伪造央行的签名的,我们只需要能把钱更多的复制出来并花出去就可以了,这样就造成了 double spending attack 的问题。因此,如何避免上述的 double spending attack 是研发数字货币需要考虑的一个问题。
情景改进:为了解决这个问题,央行可能会提出这样一个方法,在每个数字货币上设置一个编号,编号是唯一的,将编号与交易对象绑定在一起,存储在数据库中。如果 A 有编号为 017 的数字货币,那么在央行的数据库中可能会这么表示 (017 → A),如果 A 将数字货币交付给 B,那么 B 首先会根据编号通过央行验证该数字货币是否是属于 A 的,如果是,那么交易可以达成,数据库中会更新为 (017 → B)。这样就避免了数字货币被大量复制,但央行就需要存储大量的数据。
如果将央行的这个职能分散给所有的用户,实现去中心化,由用户共同验证数字货币的交易内容,这就形成了区块链。区块链中货币具体是由谁发行的在后面会有探讨。
double spending 还有一个表现形式(在后面也会提到),就是回滚过去的交易,因为区块链中只有最长链是有效的,当恶意节点完成了一笔交易得到某个商品后,它如果通过在之前的区块中插入新区块的方式将原先具有的比特币又转给自己,并将新区块所在链扩展为最长合法链,那么之前的那笔交易就作废了,可是它却照样得到了商品,也相当于 double spending。

比特币中数字货币交易的过程
假设有一个节点 A 挖出了 10 个比特币(拥有了铸币权),那么区块链中就会创建一个区块进行记录。A 将数字货币分发给 B 和 C 各 5 个,这又会创建一个新的记录,在这个新的记录中,首先会有 A 的签名,证明这个交易是 A 发起的,同时还有有来源证明,证明 A 确实是有 10 个比特币,比特币之前没有被复制和分发出去。如果此时 B 又将手头上的货币分发给 C 和 D,则又会产生一条新纪录,记录中会有 B 的签名,同时也会有指向 B 的来源证明。当一个个体手里的比特币有多个来源时,在进行交易时需要指向所有的来源,只有 C 经验证确实有这么多货币可以分发时,交易才能完成。
比特币系统中采用的是 transaction-based ledger,即以交易为基础的账本,这种方式可以很好的保护用户的隐私,系统中没有账户的概念,因此每一笔交易都必须要显式地说出来源。以交易为基础的账本和以账户为基础的账本区别见【 Transaction/Account-Based ledger 】。

现在我们来细看某一个交易,比如 A 将比特币分发给 B 和 C,要完成这个交易,A 首先需要知道 B 和 C 的地址(公钥)是什么,就像是银行卡转账,A 需要知道 B 和 C 的银行账户才可以切实把钱转过去。
B 需要知道 A 的交易哈希,交易哈希中通常包含以下的内容:
- 输入(Inputs):
- 交易的来源地址,即从哪个比特币地址发送。
- 输入比特币的数量。
- A 的公钥。
- 输入交易的哈希值(即上一笔交易的 TXID, trasaction ID)。
- 输入的签名(证明 A 对该比特币拥有支配权)。
- 输出(Outputs):
- 接收方的地址,即 B 的比特币地址。
- 输出比特币的数量。
- 交易金额:转账的比特币数量。
- 其他字段:
- 交易的版本号。
- 时间戳或锁定时间(Locktime)。
为了验证 A 的身份,B 以及其他所有节点也需要知道 A 的公钥。在获取 A 的公钥的过程中,当时课堂上有人提出了这样的一个问题,“如果 A 的公钥是通过 A 或是别人告知的,那么不是会存在安全隐患吗“,意思就是说如果有一个节点 冒充了 A ,并伪造了一个“ A → B 和 C” 的交易, 将交易哈希中输入的公钥说成是自己的公钥,然后用自己的私钥对此交易进行签名,并向其他人提供了自己的公钥,其他人通过公钥自然能够将交易进行解密,得到 的签名,似乎 A 的钱悄无声息的就成为了 的钱了。

解决这个问题的方法就是在输入中包含输入交易的哈希值,这个哈希值指向之前一笔未花费的输出( UTXO , Unspent Transaction Output),它会包含一个锁定脚本,锁定脚本中记录了 A 的比特币地址,由于每一个比特币的地址都是通过公钥的哈希运算计算出来的,因此 UTXO 中的地址也是由 A 的公钥计算出来的。在发生交易时,比特币网络会将 A 提供的公钥进行哈希运算,看是否会与 UTXO 中的地址一致。
区块链中的分布式共识(distributed consensus)
在区块链的分布式共识理论中,有两个理论非常的著名。第一个理论是 【 FLP Imposibility 】,它强调了在异步环境中,尤其是在某个节点可能崩溃的情况下,无法在有限的时间内实现一致性。它说明了在设计共识算法时需要考虑到节点崩溃的情况,这使得算法的设计变得复杂。
另一个理论是 【 CAP Theorem 】 ,它强调了在区块链分布式系统中,由于网络分区的不可避免性,系统设计者需要在一致性和可用性之间进行权衡。它提醒设计者考虑在不同场景下如何平衡这两者,以适应实际应用的需求。为了保证分布式系统中的一致性,可以采用分布式系统中的 【 Paxos 算法 】,它可以保证在大多数节点都同意请求的情况下保持系统的一致性(允许部分节点是故障的) 。
那么,如何实现区块链中的共识呢?
首先,我们必须要确定区块链中具有投票权的节点(membership)。
在一些场景中,投票权的分配通常是基于 hyperledger fabric 的,即联邦链,只有一些大公司才有资格加入,这可以确保大多数成员是非恶意的。
但在比特币系统中并不是如此的,它的原则是所有节点都可以创立账户,进行交易的。但这样就会出现一个问题,如果有一个恶意群体拥有一台超级计算机,它们不断的创立账户,直到超过所有账户的半数以上,那么他们就相当于对这个系统有了控制权,这种攻击被称为是【 女巫攻击(Sybil Attack) 】。
因此比特币系统中并不采用账户数目进行投票,而是采用计算力来进行投票的。
具体的过程是这样的:每个节点都可以生成一个候选区块,并将它认为合法的交易打包到区块中。接下来,节点会尝试不同的 nonce 值进行计算。如果某个节点找到一个符合要求的 nonce(通常是 4 字节长),使得满足条件 H(block header) ≤ target,那么我们就可以说这个节点赢得了记账权。
“每个节点都可以生成一个候选区块,并将它认为合法的交易打包到区块中” 这个过程有什么意义?
要想理解这个过程,首先就需要知道区块链中区块是如何产生以及交易数据是如何加入到区块链中的。
区块链中的创世纪块是是区块链中的第一个区块,通常是手动创建的,并不是通过常规的挖矿过程生成的。它包含一些特殊的信息,通常还包括创始人的地址、时间戳和特定的初始值。在比特币中,创世纪块的哈希值是特定的,由中本聪设计。
区块链中的其他区块都是通过挖矿产生的,挖矿需要有 PoW 的证明,也就是说,节点需要尝试不同的 nonce 值计算,找到符合要求的 nonce 后它就具有了加入区块的条件。区块中通常只需要包含符合条件的 nonce ,而不需要包含你尝试过的所有 nonce。
每隔一段时间,区块链中就会产生新的区块,而在这段时间内发生的交易是没有直接加入到区块链中的,它们需要通过这些新的区块插入到区块链中,这也就是为什么每个节点需要先产生一个候选区块。
那么,在候选区块加入区块链前,这些交易记录会被放在什么地方呢?
交易记录会被存放在【 内存池 】中。内存池是一个存储未确认交易的地方。每个节点在网络中接收到新的交易后,会先将这些交易存入内存池中,等待后续的处理和打包进区块。
记账权是指在区块链这个去中心化账本中,某个节点有权将下一个区块写入链中。只有拥有记账权的节点,才有权发布新的区块。
其他节点在收到该区块后,会首先验证其合法性。【 nBits 域 】代表目标难度的编码,其他节点会检查该 nBits 字段是否符合比特币网络设定的难度要求。接着,节点会验证该 nonce 生成的哈希值是否满足条件。然后,对区块头(block header)中的每个字段进行检查。如果区块头没有问题,节点会继续检查区块体(block body)中的交易列表,确保其中的每笔交易都合法。如果交易列表中存在无效交易,整个区块将被拒绝。
那么,有没有一种情况是每个交易都是合法的,但是它却不被接受的情况呢?

实际上是有的,如上图所示,上图的区块链有两条分支,在两个分支中,都包含 A 的交易过程,在第二个区块中,C 将比特币发给了 A,而在第一条分支的第四个区块中,A 又将比特币给了 B,在第二条分支的第四个区块中,A 将比特币给了 。在两条链中,A 的交易都是合法的,但是第二条分支中的交易不被区块链认可,如果按照图中 A 的方式进行操作,A 既可以将比特币分发给 B ,同时,它又可以将同一份比特币分发给 ,这样就乱套了,相当于 A 凭空多出了一份比特币。
因此在区块链中有这样一条规则——只认最长链(longest valid chain)。那么,如果有两条区块链是等长的怎么办呢?
如果两条区块链是等长的,这种状态会短暂的维持一段时间,让下一个产生新区块的节点来判断最终选择哪一个区块。假如有两条链是等长的,并且最后一个区块分别是 A 和 B,那么如果新加入的区块是跟在 A 后面的,那么就说明它接受了 A,如果接在 B 后面,就说明接受了 B。未被接受的区块被称为是 orphan block。
通过上述的过程,我们就能够大致了解,区块链中的投票权并不是看谁的计算机更多,谁的账目数量更多,而是看谁在每秒内计算的 nonce 的数目越多,这个可以通过 hash rate 来进行描述,它决定了一个节点投票的权重。
为什么大家都争着抢着想要记账权呢?
这个就涉及到比特币的来源和产生的过程了。
最初的比特币是由创世纪块产生的,创世纪块是比特币区块链的第一个区块,其中包含的比特币数量(通常为 50 个比特币)是由中本聪在比特币白皮书中设计的。这是比特币网络初始的奖励机制,目的是激励矿工进行挖矿。
创世纪块的比特币数量由协议设定,并不是通过挖矿得出的。创世纪块被手动创建时,包含了初始的比特币奖励。这个奖励在后续的区块中会逐渐减半,遵循设定的经济模型。每当矿工成功挖掘一个新区块时,他们会获得一定数量的比特币作为奖励。这个奖励在比特币网络中被称为区块奖励(coinbase reward, 币基奖励)。起初,区块奖励是 50 个比特币,但每经过 210,000 个区块(约四年),奖励会减半,成为 25 个比特币,然后是 12.5 个比特币,以此类推,直到达到预设的上限(2100 万个比特币)。
四年的时间是通过下面的方式计算出来的:

每个 21 万个区块比特币奖励数量减半,每隔 10 分钟出一个区块,按上图的方式计算大概就需要 4 年。
每个新区块中都有一个特殊的交易称为 【 Coinbase 】交易,该交易用于将新的比特币奖励发送到矿工的地址,所有其他交易都会在其后面。
BTC 实现
什么是 UTXO?
下面记录的是老师在课程中的表述,更多有关 UTXO 的内容可以跳转到页面 UTXO 。
UTXO,unspent transaction output,所有还没有被花出去的交易的输出集合就是 UTXO。
在区块链中,UTXO 集合的存在是为了避免 double spending attack。
矿工为什么要将其他人的交易纳入自己的区块中?
在区块链中,total inputs = total outputs,输入的比特币总量和输出的总量是相同的,但在输出的时候,total outputs 会包含两个去向,一个是给交易对象的,如本来区块中记录的交易就是 A → B,那么当新区块加入到区块链中后,B 会获得比特币;另一个是给矿工的,因为矿工挖到了矿,需要给他区块奖励(coinbase reward)。
那么如果只是有区块奖励,就会出现这样一个问题,矿工只会将与自己有关的交易内容纳入新区块中,而不会费心去验证其他人交易的合法性并将他们的交易内容也纳入区块中。那么,如何解决这个问题呢?
区块链系统中推出了第二个激励机制,交易费(transaction fee)。即纳入交易内容的区块在加入区块链中后需要付给矿工交易费(或者叫手续费),一般交易费的金额与区块奖励相比是比较小的,但聊胜于无嘛🤠。
区块链中具体区块的分析
原图


区块头代码:

32 位的无符号整数换一个说法就是 4 字节的整数,前面提到过。
区块头中各个参量的描述:

在上述参量中,只有根哈希值和随机数是可以修改的。以比特币现在的难度,仅仅靠修改 32 位的 nonce 的值不足以产生符合要求的 nonce,因此,需要同时修改根哈希值,那么如何修改根哈希值呢?

在新区块中,所有的交易内容(包括矿工选择的交易和 coinbase reward )都会被放置在 merkle tree 中,coinbase 的交易记录与其他的交易记录不同,其他的交易记录是通过普通的交易哈希值记录的,交易哈希在前面的内容中有介绍【ctrl+F 搜索“交易哈希”】,需要 UTXO 作为输入,而 【 Coinbase 】中交易记录的输入不需要 【 UTXO】,它在输入字段(
scriptSig
)中可以插入一些自定义数据,这个数据可以是任意内容,例如矿工的标识、区块高度、自己的挖矿感言、digital commitment等等。通过 coinbase 中的输入,就可以实现 merkle root 的更改,因为 coinbase 也是在 merkle tree 里的。什么是区块高度?
区块高度(Block Height) 是指区块链上某个区块在链中的位置或序号。它表示从创世区块(即第一个区块,通常高度为 0)开始,到当前区块之间有多少个区块。创世区块 是第一个生成的区块,其高度为 0。
也就是说,原先 32 位的 nonce 值不足以产生符合要求的 nonce,现在,由于 coinbase 添加的自定义内容使得我们可以修改 merkle root 的值,我们可以将 coinbase 中的 8 个字节(64 位)抽取出来作为 extra nonce,与 32 位的 nonce 值拼接,组成 96 位的 nonce,然后再进行哈希运算,这样就更可能产生符合要求的 nonce 了。【这里需要注意的是,哈希值的计算是将整个区块头作为输入 x,然后代入 H(x) 得到的】
实际操作时就需要设计两个循环,第一个循环修改外层的 coinbase 的内容,内层的循环遍历 32 位的 nonce。
区块中某个具体的交易:

图中表示的是非 coinbase 的交易记录,表示一个节点对另一个节点转账的过程,图中的 input scripts 和 output scripts 分别表示输入脚本和输出脚本。
输入脚本通常包含以下内容:
- 签名(Signature):交易的发送方用私钥对交易进行签名,证明他们有权花费这笔比特币。
- 公钥(Public Key):接收比特币时关联的公钥,用于验证签名是否有效。
图中,输入脚本中包含了
PUSHDATA
操作符,表示将一些数据推入堆栈。输入脚本的作用是提供解锁上一个交易输出(UTXO)的信息,这些信息包括公钥和相应的签名。比特币节点通过解锁脚本来验证交易的合法性,确保交易发起者有权花费这笔比特币。
输出脚本是交易中指定如何锁定比特币以便接收方能够使用。最常见的输出脚本形式是:
- 公钥哈希(Public Key Hash):比特币的收款地址实际上是接收方的公钥哈希。锁定脚本通常要求未来的交易必须提供与该哈希对应的公钥及其签名才能解锁这笔比特币。
区块产生时间
如果想要回忆概率论部分的内容,可以查看 【 概率与统计笔记 】 的内容。
Bernoulli trial(伯努利试验):
伯努利试验指的是一种只有两个可能结果的随机实验,通常是成功或失败。这类试验的特点是每次试验都是独立的,结果只有“是”或“否”、“1”或“0”。
Bernoulli process(伯努利过程):
伯努利过程是由一系列相互独立的伯努利试验组成的随机过程。每次试验的结果不会影响下一次试验,这称为无记忆性(memoryless),即每次试验的结果是独立的,不依赖于之前的试验。
Poisson process(泊松过程):
泊松过程描述的是在一定时间间隔内发生某一事件的次数,这些事件是相互独立的,发生的概率是恒定的。这与伯努利过程相关,它也是一种无记忆性的过程。泊松过程适用于随机事件在一段时间或空间中分布的情况。
Exponential distribution(指数分布):
指数分布描述的是两次独立事件之间的时间间隔的概率分布,常用于泊松过程。在区块链中,区块的产生就符合泊松过程中的指数分布,它的概率密度曲线如下:

将来要挖多少时间和过去挖了多少时间是没有关系的,这一性质也叫做是 progress free,也就是说整个过程是无记忆性(memoryless) 的,即每次事件的发生与前一次事件无关。当一个节点挖了 10 分钟后仍然没有挖到节点,并不意味着再过几分钟一定会挖到,在这一时刻,概率告诉它仍然需要 10 分钟才能挖到矿。这样会让算力高的节点和算力低的节点挖矿时更加公平。
如果我作为矿工挖了半天的矿,可是别人抢先一步发布了新的区块,那么我不就白挖了吗?努力就白费了,是这样的吗?
其实并不是,根据前面所说的 progress free 的特性,未来我在什么时间挖出矿跟我过去花了多少时间挖矿是没有关系的,别人挖出了新的区块,我们此时重新选择一个候选区块开始挖,挖出矿的概率和我顺着之前挖了半天的区块继续往下挖得到符合要求的 nonce 的概率是相同的。从这个角度上来说,努力其实没有白费。
好吧,如果你选择继续挖原来的区块,面临的风险还是挺大的,首先,别人发布的区块中可能已经包含了你当前候选区块中大部分的交易,其次,当你终于挖到矿并发布该区块后,可能已经有其他用户顺着前面发布的区块又往下挖了,你好不容易挖出的这个区块就成为 orphan block 了🤡。
系统中比特币的总量
比特币系统中,每生成 21 万个区块,比特币的奖励数量就会减半,初始的奖励是 50 个比特币。

什么?你忘记了等比数列的计算🥸🤡

比特币越来越难以获取并不是因为数学原因,数学问题的难度越来越大,而是人为造成的,比特币的数量被人为的减少了。
挖矿求解的 puzzel 本身是没有什么意义的,但是挖矿的过程对于维护比特币系统的安全性是至关重要的。
对于一个去中心化的系统而言,挖矿提供了一种依靠算力投票的有效手段,只要大部分的算力是掌握在诚实的节点手里,那么系统的安全性就能够得到保证。
恶意节点可能会做的坏事
- 变相 double spending

当恶意节点 m (malicious 缩写)在一个区块上进行了两次交易, M → A 和 M → M’ ,正常来说,系统中其他节点只会承认其中一条,并在其中一个区块后面添加区块,扩展最长链。但是,有一种情况是,当 M → A 的交易发生后,即 M 使用比特币向某个商家购买了某个物品,商家验证区块链上确有这个交易后,将物品交给 M,然后 M 又在原先的区块中创建了另一个分支,将钱转给了自己,并通过某种方式将这个分支扩展为最长链,那么原先的 M → A 的交易就会作废,相当于是 M 白得了一个物品。
这个可以通过什么方式解决呢?

如上图所示,我们可以通过 confirmation 的个数来判断是否接受交易,比如,设置 confirmation 为 6 个的时候选择接受交易,如果恶意节点 M 想要回滚交易内容,那么它就需要多生成七个区块来进行回滚,这一般是不会被诚实的节点接受的,而且也要求 M 付出比较大的代价。
要记住的是,区块链中的不可篡改性只是一种概率上的保证,即便很多交易已经达成了,但仍然有可能会被篡改,虽然概率比较小。
当然,一些商家也会选择使用 zero confirmation 的形式来同意交易,zero confirmation 后交易还没有被写入区块,再 one confirmation 时交易才会完成。有些货物的发货往往就是隔天发货,到那时 confirmation 已经有很多了,如果恶意节点回滚了交易,那么商家也可以选择不发货。
- 恶意节点故意不包含合法交易
这个问题其实并不大,因为合法交易即便在恶意节点写区块的回合没有被写入,在遇到诚实的节点的时候总会被包含的。
事实上,每个节点发布的区块中包含的交易的数目也是有限制的,最多只能达到 1 MB 字节。
- selfish mining
如果很多恶意节点偷偷挖了很多区块藏着,不发布,等到上面的交易由 six confirmation 后,商家交付了货物,此时恶意节点再将藏着的区块全部发布(比如有 7 个),那么此时,恶意节点的回滚链就会超过原来的最长链成为最长链。这种情况在恶意节点数量高于系统半数的时候是有用的,但是当恶意节点的比例比较小时,这个其实基本不会发生。当恶意节点数量超过半数时,这类攻击被称为是【 51% Attack 】.
BTC 网络
比特币网络分为应用层(application layer) 和网络层(network layer),应用层顾名思义,就是用户(矿工)与比特币系统交互的层,而网络层是比特币网络的底层(基础)。
应用层可以进行比特币网络中交易和区块的创建以及执行 【 智能合约 】,智能合约在这里就是矿工挖到了矿就会得到区块奖励。
网络层主要采用点对点的网络(P2P)和其他节点进行通信和数据传输,在比特币的网络中,不存在超级节点(super node)与主节点(master node),因为比特币网络是去中心化的,每个节点都是平等的,不存在哪个节点天生拥有更高的权力和特权。
新节点加入比特币网络需要依靠种子节点(seed node),它们可以帮助新节点发现并连接到其他可以信任的节点,新节点在链接到这些节点后,会获取区块链的副本,从而快速融入比特币网络,网络中的其他节点也可以借此知道新节点的加入。种子节点通常是网络中内置的,但也有一些经过验证且稳定的节点会被比特币网络设置为种子节点,成为种子节点可以提高节点自身的声誉,并被比特币网络信任,参与网络建设。
比特币网络中节点之间采用 TCP 协议进行通讯,这样可以绕过防火墙的限制,在节点想要离开比特币网络的时候,直接默默离开就行,在节点一段时间没有活跃后,该节点就会被删除。
比特币网络是简单、鲁棒的,但这样牺牲了效率(simple, robust, but not efficient),网络中某个节点在得到一个新的消息的时候,网络会随机为该节点分配一些“邻近节点”,然后该节点将消息转发给邻近节点,同时记录自己已经收到了消息(写入内存池),下一次再收到该消息时,就不会再记录并转发给邻近的节点了,邻近节点会把交易信息再次传播给邻近节点,从而实现消息的快速传播,这个过程叫做 【 Flooding(泛洪)】。“邻近节点”并不是根据网络的拓扑结构选取的,也就是不是根据位置上相近与否选取的,而是随机分配的,但是节点间的传递速度是差不多的。

一个节点 N₁ 收到一个交易(比如 A → B )并广播给邻近节点后,它会将该交易写入自己的内存池 P₁ 中。如果恰好此时又有一个冲突的交易 A → C 发给该节点,那么该节点会拒收,因为前一笔交易已经被记录了,但是也许节点 N₂ 并没有记录前一笔交易,因此它会将该交易写入自己的内存池 P₂ 中,而拒绝前面的交易 A → B。遇到这种相互冲突的情况,比特币网络只会认可真正写入区块链的区块中记录的交易,比如节点 N₁ 更快一些,挖到了新区块并将交易 A → B 写入区块体中,那么 N₁ 的内存池就会将 “A → B” 这笔交易移除,而 N₂ 的内存池中由于存在与区块链区块中冲突的交易 “A → C”,因此也会将该交易从内存池中移除,避免“双花攻击”。
线上的系统都无法解决线下的问题。当使用比特币进行商品支付时,商家不发货,但比特币已经交易成功了,这个是线下的问题,需要通过第三方来解决。
挖矿难度
比特币中挖矿是去寻找随机数 nonce ,使得 H(x|| nonce) ≤ target ,target 就是目标阈值,target 在整个输出空间中所占的比例越小,挖矿的难度就越大。

比特币中采用的是 SHA-256 的哈希函数,这意味着输出的哈希值是 256 位的,共有 中可能性。
挖矿的难度可以通过下面这个公式来进行计算:

即,挖矿的难度等于挖矿的难度为 1 时的目标阈值除以当前的阈值,挖矿难度为 1 时的阈值是当初中本聪创立比特币系统时在比特币协议中确定的,是个常量,使用哈希值来表示就是
0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
,16 进制表示,每个字符占 4 位,也就是说前 32 位字符固定为 0,目标值是 。挖矿的难度和目标阈值的大小是成反比的。
那么挖矿的难度为什么不设定为 1 不变,还要不断的增大,保持出块时间为 10 分钟呢?
我们可以看下面这个图:

假设有一条区块链已经扩展了 5 个块,如果挖矿的难度没有调整,保持比较低的值,那么每个节点的出块时间就会比较快,比如 1 s,甚至是零点几秒,这样造成的结果就是,节点们都争相在第五个块的后面进行扩展,第五个区块后一瞬间就可能多出十几个块的连接(力量是分散的),如果此时有恶意的节点想要回滚第三个块的交易,在第二个区块的位置上插入区块,并依靠自己强大的算力不断扩展这条链(集中恶意群体的算力),那么很快它就会将回滚链扩展为最长合法链,先前的交易 A → B 作废。
在这种情况下,恶意节点即便没有达到 【 51% Attack 】的条件,只有百分之十几的算力,仍然可以让整个区块链系统陷入危机。
因此,虽然出块时间短可以提高效率,但是出块时间不是越快越好。但是,比特币中保持 10 分钟的出块时间并不意味着 10 分钟的出块时间是最优的,8 分钟,5分钟实际上也是可以的。在以太坊中,出块时间就被设置为了 15 秒左右,并提出了一个 ghost 的协议来保证它的安全性。同时在比特币中,orphan block 是不会有奖励的,但在以太坊中,orphan blcok 会得到名为 uncle reward 的区块奖励(好形象是不🥸)。
难度调整的时间间隔:

目标阈值的计算

比特币中规定每隔 2016 个区块就调整一次难度,根据上面的公式,大概是 14 天调整一次难度。

难度是调高还是调低可以依据上面的公式判断,expected time 就是生成 2016 个区块期望的时间,即 2016 * 10(min) ,actual time 是上一次实际生成 2016 个区块所花费的时间,如果这个时间超过期望时间,那么比值就会大于 1,target 调高;挖矿难度下降,如果比值小于 1,target 就会调低,难度升高。难度调整也可以通过下面的公式来进行计算:
上面的公式采用的是对难度的直接调整,而前一个公式是对阈值进行的调整。这个公式与前一个公式的不同之处在于,比值的部分是颠倒的,这是因为挖矿的难度和阈值是成反比的。
阈值的调整是有上下限的,一般为 4 倍的阈值,也就是说,如果实际的时间超过了 8 个星期,那么 的比值不会超过 4,避免出现特殊的情况造成网络崩溃。
如果有恶意节点不调整自己的区块难度,还按照原来的区块难度计算阈值并发布区块怎么办?
恶意节点通过这种方式,可以更快计算出新区块的哈希值,但是这样实际上是不可行的。因为每个区块在发布时,其他的节点都会检测该区块计算的哈希值的【 nBits 域 】是否是符合条件,如果不符合条件,那么其他节点是不会承认这个区块的。
关于中本聪在设计比特币的时候参数的设置
中本聪最开始在设计比特币中的参数的时候,比如每隔 2016 个区块调整一次难度、每隔 21 万个区块比特币数量减半、区块生成时间控制在 10 分钟等等,其实并没有说明为什么将参数设置为这些值,也许是他看了一些调研报告或是一些论文,具体的原因不得而知。
BTC 挖矿
全节点、轻节点
这两个概念在前面的笔记中已经介绍过了,这里将当时的笔记搬运下来。
全节点(Full Node):

全节点是在区块链网络中运行的节点,保存了区块链的完整历史记录。它们存储着所有区块的区块头和区块体,并通过验证每个交易和区块的正确性来维护网络的安全和共识。
全节点的关键职责包括:
- 验证交易和区块:全节点下载所有区块的区块头和区块体,验证每个交易的合法性。
- 参与共识:全节点负责接受和传播新区块,确保网络的状态与其他节点保持一致。
- 存储完整链:全节点存储整个区块链数据,能够从创世区块一直追溯到最新区块。
轻节点(Light Node):

轻节点(也叫简化支付验证节点,Simplified Payment Verification,SPV 节点)是一种只存储区块头而不存储区块体的节点,因此它们不需要维护区块链的完整历史记录。轻节点的资源需求更低,特别适用于资源受限的设备(如手机或嵌入式设备)。网络中的大部分节点也都是轻节点。
轻节点的工作方式:
- 存储区块头:轻节点只下载区块头,而不存储区块体,从而大大减少存储和带宽需求。
- 依赖全节点:轻节点无法自行验证交易的完整性,它们依赖于全节点的验证。轻节点可以通过区块头中的 Merkle Root 进行部分验证。
比特币中的安全性是如何得到保证的?
比特币中的安全性是由以下两个方面来保证的:
① 根据密码学中的特性,比特币系统里节点的签名是无法伪造的,且不存在【 51% Attack 】,即网络中大多数的节点都是诚实的,它们只接受合法的交易;
② 比特币系统中的共识机制(PoW),矿工只有通过解决复杂的数学问题找到 nonce 后才能拥有记账权。
挖矿的设备
在最初的时候,挖矿的设备选择的是 cpu(第一代),也就是说,家用计算机和笔记本电脑都可以用于挖矿,但是这样的话性价比太低,无法最大程度的利用设备中的内存资源,很多部件都会闲置,不划算;
后来,出现了第二代的挖矿设备,使用 gpu 进行挖矿,相比于 cpu 而言,gpu 是通用的并行计算设备,速度更快,效率更高,因此在那段时间 gpu 几乎被挖矿用户买断,但是使用 gpu 仍然会有部件闲置,而且由于 gpu 更多的是为了深度学习算法而设计的,很多数据类型也是挖矿不需要的,如浮点数的类型,因此还是不划算;
现在(2018 年)更多的采用的是 asic 芯片进行挖矿,asic 芯片全称为 Application Specific Integrated Circuit(专用集成电路),它是专门为挖矿而设计的,性价比高,并且与加密货币间是一一映射的关系,一种 asic 芯片只能挖一种特定的矿。除非两种加密货币之间使用的是同一个种 mining puzzle(挖矿难题),它们之间是【 】 的关系。Asic 的研发周期大概是一年左右,但是随着比特币难度的不断提高,大部分的获利也许只在 Asic 芯片投入使用的前两个月中得到,之后芯片就会过时(市面上出现了算力更强的矿机),需要新的矿机支持。
购买矿机一般需要提前预定,先付费,厂家研发后再支付,在某个知名的厂家研发出一款新的矿机后,比特币系统中的算力就会有明显的提高。一些不良的厂家可能会在矿机研发后,先自己挖矿,获利后再发给用户。
ASIC 芯片的出现其实违背了比特币系统设计的初衷,它让普通的用户无法再参与到比特币的竞争当中。
现在也出现了一种新的挖矿难题叫做 alternative mining puzzel,旨在实现 asic-resistance (抗 Asic 芯片)。
矿池 ➡️【 矿池 】
单个矿工的算力小,出块的效率低,收入不稳定,因此引入矿池。矿池可以集中力量办大事,将多个矿工的算力集中在一起,共同创造收益。
矿池组织矿工一般有两种形式,一种是所有的矿机都属于某个矿主,大家在同一个区域内一起参与挖矿;
另一种是矿机是分布式的,单个的矿工可以申请加入某个矿池与其他人一起挖矿,矿工可以通过通信协议与矿主交流,最后一起参与分红。
在第二种方式下,为了避免某些矿工消极怠工,设立了 share(份额)分配的奖励机制。Share(份额) 是矿池系统内一种用于衡量矿工贡献的单位,它代表的是矿工在矿池中提交的一部分工作证明,这个用于证明的 nonce 不需要满足整个网络的区块难度要求,产生的是 almost valid block(几乎有效的区块)。
当矿工挖到符合区块链产生新区块条件的或是符合矿池产生 almost valid block 条件的 nonce 时,会将 nonce 提交给矿池,如果挖到了前一种,矿工会得到比特币的奖励,如果挖到了后一种,会得到相应的 share(份额)。
每个矿工提交的份额数量代表了其为矿池贡献的计算资源(算力),虽然这些份额本身不足以被比特币网络接受为有效区块,但矿池会根据矿工提交的份额来分配奖励。找到的区块越接近网络难度,矿工获得的份额就越多。
上述的规则看起来是比较完善的,但可能会出现一个问题,如果有矿工只提交 share 而私自发布符合区块链要求的 nonce (block withholding attack,矿工偷跑)怎么办?这样他不就相当于获得了两份收益了吗(Selfish Mining)?
这种行为造成矿池分配的不公平,造成矿池的具体损失,危害矿池的公信度。为了避免这种问题的产生,矿池在分配任务的时候会包含独特的交易信息或时间戳(写入 coinbase 中),矿工即使找到有效的区块,其区块模板(block template)与矿池不一致,会被识别为非法提交。比如,一个矿主将自己的 coinbase 中的自定义内容修改后,将尝试 nonce 的任务分配给下面的矿工,矿工挖到矿后是无法修改 coinbase 的内容的,即便偷跑了,区块的奖励还是会发给矿主的。
矿池的存在会让 【 51% Attack 】变得更加的容易,一般来说,矿池中的管理者会抽取一定比例的费用作为管理费,当矿池管理者想要发动攻击的时候,管理费会降到很低,吸引更多的矿工投奔,矿池算力在短时间内很容易达到网络的半数以上(概率上的)。
当算力达到半数以上后,首先想到的第一个问题就是 double spending attack。
还会造成的一个问题就是 boycott,即如果拥有半数的机构不想要让某个节点、机构的交易出现在区块链中,可以进行分叉攻击,只要有有关该节点的区块出现,就会对该区块进行分叉。
on demand mining
On-demand mining(按需挖矿)是一种灵活的挖矿机制,允许用户根据需求随时启动或停止挖矿活动。这种模式通常与云挖矿服务相关,具有以下特点:
- 灵活性:用户可以根据市场条件、个人需求或设备可用性随时调整挖矿活动。
- 降低门槛:用户无需购买和维护专用的挖矿设备,可以通过租用云服务来参与挖矿。
- 成本控制:用户可以按需选择挖矿的时间和规模,优化成本和收益。
- 即时启动:用户可以快速启动挖矿,无需等待硬件设置或配置。
按需挖矿使得更多人能够参与加密货币的挖掘,同时减少了资源浪费和初始投资的压力。
BTC 脚本
比特币的脚本语言相对于以太坊来说比较简单,没有循环机制,因此也不会造成停机。比特币脚本语言没有什么特殊的名字,就叫做 bitcoin script language,它的语言在某些方面功能很受限,但在密码学相关的方面功能却很强大。
比特币交易的最小单位——聪(Satoshi)
比特币中的最小单位被称为聪(Satoshi),它是比特币的最小不可分割单位。名字来源于比特币的创造者中本聪(Satoshi Nakamoto),为了纪念他的贡献。
- 1 比特币(BTC) = 100,000,000 聪(Satoshi)
- 换句话说,1 聪 是 0.00000001 BTC。
比特币的价格可能非常高,而日常交易可能需要更小的金额单位。聪作为比特币的最小单位,允许用户进行非常小额的交易和支付。这种微小的单位特别适合小额支付、手续费计算等。
常见的比特币换算:
- 1 BTC = 100,000,000 Satoshi
- 0.01 BTC = 1,000,000 Satoshi
- 0.0001 BTC = 10,000 Satoshi
- 0.00000001 BTC = 1 Satoshi
交易结构
原图


首先需要弄明白的是,上面的交易结构不是关于某个区块的代码,而是某个区块中区块体部分某个交易的代码。
图中
txid
表示的是交易 ID(Transaction ID),代表该交易的唯一标识符。它是通过对交易内容进行哈希运算生成的。第二条
hash
的值通常与 txid 相同,表示该交易的哈希值。在某些情况下,如果交易被打包在区块内后进行了更改(如使用见证数据),hash 可能会与 txid 不同。图中的
confirmations
表示的是交易所在区块被确认的次数,当交易所在区块作为候选区块时,该值为 0;当作为新区块加入区块链时,该值为 1;当区块后面跟随了 m 个区块后,该值为 (m+1) 。交易的输入
原图


上图中,
txid
的值表示的是该输入引用的前一笔未花费交易的 ID(Transaction ID
);vout
表示的是在上一笔交易中,与该交易关联的交易的索引值。可以通过下面这个简单的例子来理解,假如在上一笔交易中 A 将比特币转给了 B 和 C,在这笔交易中,B 的索引为 0,C 的索引为 1,那么在之后如果 B 又发起了交易将比特币转给 D,就要指向前一笔交易的哈希值,然后再加上 vout 的索引为 0.注意,如果一个交易有多个来源,那么每个来源都会对应一个 txid, vout 和 scriptSig 。

交易的输出
原图


区块链中的表示

对于一笔确定的交易而言,比如 A → B,由于 A 要花费之前前一笔交易给它的资金,因此需要 A 提供输入脚本 input script,根据选择的输入输出脚本的形式(后面会介绍)提供自己的签名、公钥或其他证明;又由于 A 要将钱转给 B,因此,A 又要创建一个该交易的输出脚本,根据具体选择的输入输出脚本形式提供资金去向(B 如何解锁和花费这笔钱)的证明方式。
也就是说,在一笔交易中,输入脚本和输出脚本都是由 A 创建的,输入脚本与上一笔关于 A 交易的输出脚本有关;输出脚本与下一笔 B 创建的交易的输入脚本有关。(这一点很重要,对于后面理解多重签名有帮助)
输入输出脚本的形式
在下面的输入输出脚本形式中,以要完成 A → B 这笔交易为例,输入脚本是在 A 创建这笔交易时需要提供的(属于 A → B 这笔交易);输出脚本是 A 上一笔资金来源的交易中提供的(比如 X → A 这笔交易),并不在 A → B 这笔交易中,这点很重要。
以下 ppt 截图中的代码为了方便起见,都没有加上前缀 “OP_”,如 CHECKSIG 应该是 OP_CHECKSIG
第一种方式,P2PK(Pay to Public Key):
英文中 2 和 to 是谐音的,因此用 2 替代 to。

上图中 PUSHDATA 表示入栈操作,区块链中只有栈的存储,因此称为是基于栈的语言,不像 C 语言那样有全局变量、内存空间之类的。
输入脚本采用的是 Sig ,给出的是付款人的公钥;
输出脚本中是 PubKey ,给出的是收款人的公钥。
脚本的执行过程就是将上面的过程拼接起来,先执行
PUSHDATA(Sig)
操作将这笔交易中付款人的签名压入栈,再执行 PUSHDATA(PubKey)
操作将上一笔交易中收款人的公钥压入栈(也就是说这笔交易的付款人和上笔交易中的收款人是同一个人),最后 CheckSIG
将栈内的两个元素弹出来,用公钥检测签名是否正确。第二种方式,P2PKH(Pay to Publick Key Hash)→ 最常使用的方式:


这种方式,顾名思义,就是输入脚本采用的是付款人的签名和公钥,输出脚本采用的是上一笔交易收款人的公钥哈希值,与第一种方式不同之处在于,付款人的公钥是在输入脚本里给出的。
该操作中 DUP 和 HASH160 都是为了验证签名的正确性。
具体的执行过程如下图所示:

如果图中的两个验证过程都没有问题,那么验证就会成功,如果有任何一个环节出现错误,那么验证失败。
第二种方式可以看作是第一种方式的延伸,它在最后的验证步骤中使用了第一种方式的验证,只不过在前面多加了一步哈希比较的验证。
第三种方式,P2SH(Pay to Script Hash) → 最复杂的一种:
serialized redeemScript → 序列化的赎回脚本。 



上述的 x 表示的是区块链系统中存在的一个 bug。 

它的特殊之处在于,输出脚本中提供的是脚本的哈希,该脚本称为是“赎回脚本”。未来需要进行交易的时候,需要提供赎回脚本的具体内容,同时还要给出赎回脚本可以正确运行所需要的签名。
验证步骤如下:

包含两个步骤的验证,第一个阶段的验证如下(证明 script 是正确的):

第二个阶段的验证步骤如下(假定各个节点已经进行了反序列化的过程)(证明签名是正确的):

对于上述的简单例子而言,使用 P2SH 的过程是有些复杂了,但是对于需要多重签名进行验证的交易而言,上述的过程是比较安全的。
区块链原生的多重签名 VS. P2SH
区块链原生的多重签名采用的是
m-of-n
的验证方式(n 表示设定的总共可以签名的公钥数量,而 m 是要求验证签名的最小数量)。也就是说,如果商家要创建一笔交易,花费一笔钱,就需要最少 m 个签名的公钥,比如总共有 3 个签名者,至少需要 2 个签名者的公钥进行验证后才可以花费这笔钱。这里再次说明,下图所示的多重签名中的输入脚本和输出脚本并不在一笔交易中。
其中输入脚本是由商家提供的,用于商家创建的交易,输出脚本是用户提供的,用于证明商家的资金来源。
如果没有弄清楚这一点,很容易对后面“为什么使用 P2SH 脚本会简化用户操作”的结果产生疑问。

上面的脚本是站在商家(或其他需要多重签名验证交易的客户)的角度写的,输入脚本是商家想要创建某笔交易时需要提供的,至少需要有 M 个人提供私钥签名;输出脚本是其他用户支付商家比特币时创建的(由用户产生),用于证明商家确实有这笔钱,需要包括商家中 N 个人(所有人)的公钥,这样不管商家要消费比特币时是哪 M 个人提供了私钥,都可以得到确认。
脚本执行过程如下(从商家角度):

那么,既然区块链原生的多重签名可以完成我们的任务,为什么还要引入 P2SH 呢?
这就要从用户的角度来思考了。从用户的角度来说,在上面的过程中,用户需要在交易中提供输出脚本,要创建一个输出脚本,用户就需要了解商家采用的是何种多重签名的规则(
m-of-n
),也就是弄清楚 m 和 n 分别是多少,比如商家采用的是 6 个签名中验证 4 个商家就可以创建自己的交易(把自己的钱花出去)的方式。用户需要依据商家的不同在创建交易时提供不同的 m、n 以及 n 个公钥。但这样只是增加了用户的操作复杂度,只对未来商家的消费有好处,而对用户自身没有好处。因此,聪明的用户们想出了 P2SH 的方法。

在 P2SH 中,商家将自己的多重签名规则写入了“赎回脚本(redeemScript)”中,并在用户支付给商家比特币时提供给用户,用于创建输出脚本,用户只需要在输出脚本中写如固定化的程序
HASH160, PUSHDATA(...), EQUAL
,就可以了,只要将 PUSHDATA 括号中的脚本替换为商家提供的脚本,即可创建输出脚本,对用户来说显然更加方便。而且这种方式与 P2PKH 相比也是类似的,只不过对于只有单签名的商家,用户需要在输出脚本中提供商家的公钥哈希,对于多签名的商家,用户需要把公钥换成输出脚本哈希而已。而对商家来说,在输入脚本中需要 m、n 以及他们自己的签名,因为他们自己肯定更清楚自己的规则,所以也不会有多困难。
因此才说, P2SH 的本质是把输出脚本的复杂度转给了输入脚本。
这里还有一个点,当商家更改了赎回脚本的内容的时候,赎回脚本的哈希是会发生变化的,原来已经采用赎回脚本的交易仍会使用原来的赎回脚本哈希,而新的交易就采用新的赎回脚本的哈希。
第四种方式,Proof of Burn(燃烧证明)【 PoB(Proof of Burn) 】:

PoW 有两种应用场景,第一种是将比特币转换为其他币种【 AltCoin(Alternative Coin) 】,获取其他货币的网络权限与区块生成资格。
另一种是利用这个特性添加需要永久保存的内容,如用作 digital commitment,将想要保护的内容放入 return 语句时后面,因为 return 语句的后面不会执行,同时也是不可篡改的,相当于是花一定的比特币向区块链中烙印自己的内容了。这个方式与之前的 coinbase 语句中写内容是相似的,只不过 coinbase 中写内容只有拥有记账权的节点菜可以做,而 PoW 的方法所有的节点都可以用。
当然,比特币也可以通过另一种不用销毁的方式来完成 digital commitment,就是将交易的金额全部用于交易费,这样,相当于销毁的 比特币 为 0.
销毁比特币需要一个特殊的比特币地址,通常称为“不可花费地址”或“黑洞地址”。这是一个有效的比特币地址,但没有任何已知的私钥与其对应,意味着任何发送到该地址的比特币都将永远无法被花费。
分叉
这节课主要介绍了什么是硬分叉,什么是软分叉以及它们的区别。在此之前
state fork(状态分叉)
状态分歧就是在比特币系统中,多个节点在同一个区块后新区块的生成上产生了分叉,在这种情况下,比特币会优先选择先生成的区块作为合法区块,并等待后续最长链的出现。
当然,如果有恶意节点想要否认过去的某笔交易,可以在前面的区块中生成分叉区块,这种故意分叉(delibrate fork)的行为称为是 forking attack。
protocol attack(协议分叉)
当社区决定实施某些协议更改时,例如引入新功能或改进,可能会导致网络分裂成两个不同的版本,分别遵循新旧协议。
如果部分矿工或节点选择不同的规则进行挖矿(某些矿工不认可新的功能或改进),可能会出现链的分叉,产生两个版本的区块链,分别在不同的规则下发展。
hard fork(硬分叉)
硬分叉时只有所有的节点都更新了协议,系统才不会出现永久性的分叉。
当比特币系统中有节点认为比特币系统原有的规则不合理的时候,如区块的大小限制为 1 MB/每区块,大概每秒只能处理 7 笔交易,因此有节点想要提高区块的大小限制,就会提出升级软件系统的意见。
假设一个系统中部分节点(算力上,而非账户数目上)同意升级软件系统(我们将它们称为是“新节点”),而另一部分的节点不同意升级软件(称之为“旧节点”),那么新节点和旧节点就会产生意见分歧,在区块的生成上出现分叉。还是以区块大小为例,新节点想要生成最大为 4 MB 大小的区块,这意味着,即便区块大小在 1 MB 以内,仍然是被新节点认可的(新节点认可旧节点区块);而旧节点只认可最大为 1 MB 的区块,也就是说,超过 1 MB 大小的区块即使被新节点所认可,旧节点还是不会认可的(旧节点不认可新节点区块)。
那么,这就会出现新节点自己扩展一条链,而旧节点不跟新节点玩,自己扩展自己的链的局面。区块链中区块一旦出现这样的分叉,是永久性的,只要旧节点不更新,那么分叉就会一直存在,这样的分叉就称为是“硬分叉(hard fork)”。

硬分叉是向后不兼容的,意味着旧版本的节点无法识别新版本的区块, 硬分叉的实施意味着网络中某些节点将遵循新的规则,而其他节点将继续遵循旧的规则。这通常会导致两个不同的区块链同时存在。
可能出现的问题:
一、如果一条分叉中记录过的交易在其他分叉中也被认可,不是可以 double spending 吗?

这里的 double spending 需要分为两种情况来讨论,第一种是在分叉前存在交易 A → B,那么分叉后如果 B 只想在第一条分叉上进行交易 B → C,但是由于第二条分叉不接受第一条分叉的区块,因此也会将交易 B → C 写入自己链上的区块中,这样 B 不就无形当中花费了两笔钱吗?

老师描述了这个问题,但其实我觉得这个问题本身有点问题。因为在分叉前如果发生了交易 A → B(假设交易后 B 拥有了 10 BTC),那么分叉后由于两条链的互不相让,其实是相当于 B 在两条链上都有了一笔资产。也就是说,“A → B”这笔交易是两条链都承认的,但是 “B → C”这笔交易是两条分叉中各自承认各自的,在绿色分叉中虽然记录了 “B → C” 的交易(比如转了 5 BTC),B 帐上的钱还剩下 5 BTC,但是对于橙色分叉来说,B 仍然是拥有 10 BTC 的,因此在第二条分叉中再次记录 “B → C” 的交易实际上是没问题的,相当于是账户的同步。但是老师描述的第二种情况确实是会有大问题的:

第二种情况是说,如果某节点在绿色分叉中记录了交易 B → C(B 买商品),然后在绿色分叉之后的区块中又有其他节点记录了交易 C → B(B 申请退款)。由于两个分叉都会收到 C → B 的交易内容,且两分叉相互不承认,因此第一条分叉中即便记录过交易 C → B,第二条分叉也是不承认的,这就使得第二条分叉仍会记录 C → B 的交易。
如果 B 在两条分叉中都有钱,那还好说,因为第二条链也会把 B → C 这笔交易给记录下来,这样两条分叉中 B 的账户余额是同步减少的。但如果 B 在第二条分叉中一毛钱没有,那么根据区块链的规则,第二条分叉是不会承认 B → C 这笔交易的,而由于 C 在第二条分叉中有钱,因此 C → B 的交易可以达成,相当于 B 在第二条分叉中凭空多出了一笔钱,这就是该问题中描述的 double spending。
那么,这个问题要如何解决呢?
在以太坊中曾经出现过这个问题,它的解决方式就是为每条链都设置一个 chain ID,B 在进行第一条分叉的交易 B → C 以及 C → B 后,会被附上 chain ID 值,而第二条分叉如果收到了 C → B 的这笔交易,由于它已经有 chain ID 的标识了,它就不会被添加到第二条分叉中。
二、如果设置了 chain ID,如果分叉前有 A → B 这笔交易,那么 B 在分叉后的两条链中不是就都有一笔资金了吗,那么在新旧两条链不是也相当于是 double spending 吗?
这个问题刚开始我也很疑惑,设置了 chain ID 只能表示分叉后 B 在每条分叉上的交易不会在其他分叉中“双花”,如果把每条链都看作是一笔账户,相当于是我在 a 卡和 b 卡中都有了相同的初始资金🤔,两条链上的账户不会同步,那么这个要如何解决呢?
我当时在路上走着突然就想明白了,如果在分叉前有这样的交易存在,那么就让 B 自己选要在哪条链上存储这笔钱呗!也就是说,如果 B 选择将这笔钱记入第一条分叉中,那么就为这笔交易设置第一条分叉的 chain ID,如果 B 选择将这笔钱记入第二条分叉中,就设置第二条分叉的 chain ID 就可以了,是不是很完美🥸(不清楚还有什么解决方法,有想法的可以在评论区分享呀!!😶🌫️🤗)
soft fork(软分叉)
只要半数以上的节点更新了内容,就不会发生永久性的分叉。
软分叉也是网络中的某些规则发生了变化,通常是通过修改协议来限制某些功能或增加新的规则,而不改变原有的功能。只不过与硬分叉不同的是,软分叉是向后兼容的,意味着旧版本的节点可以识别新版本的区块,也就是说,软分叉后的新区块,旧节点是可以直接参与的。
再拿区块大小作为例子(仅作为例子,实际中不会发生),有些节点觉得区块大小太大了,想要将大小改为 0.5 MB,于是他们使用小的区块来扩展新的区块链,而主张生成大的区块(旧节点)的节点也可以直接参与到小区块的区块链中,因为旧的区块规则就是区块大小不超过 1 MB,由于 0.5 MB 比 1 MB 小,因此新节点生成的区块后面可以直接跟上旧节点的区块。只不过,如果新节点数量比较多且意见比较统一,它们就不会理会旧节点的区块,每当旧节点参与建设的时候,它们就另开一个分叉来进行扩展(如下图所示)。

相比于硬分叉来说,由于软分叉从规则上并不排斥旧节点的参与,因此不像硬分叉那样,最终造成网络分裂,但是如果旧节点就是与新节点对着干,那么,还是有可能会出现网络分裂的。
软分叉的一个应用 —— Coinbase 域:
有关 Coinbase 域的介绍在前面已经提过很多次了,详细内容可以看这篇文章 【 Coinbase 】。
由于区块链是去中心化的,区块链中大部分节点都是轻节点,因此用户基本上只能查询到自己上一笔交易的情况,而无法查看自己的账户余额,如果想要查询账户余额,只能请求全节点帮忙,而我们却无法验证全节点的回复是否是正确的。
因此用户利用软分叉增加了区块链中的 Coinbase 域存放余额的功能。具体是将 UTXO 集合使用 Merkle Tree 的形式储存,然后将 UTXO 的 Merkle Root 存放到 Coinbase 中,由于 Coinbase 的变化会引起区块头的变化(Coinbase → Merkle Root → 区块头),因此这样做相当于是将 UTXO 的根哈希与区块头间接联系了起来。
这样,未来轻节点想要查询自己的余额,它可以放心的向全节点发出一个请求,全节点将查询的结果(余额大小,UTXO 对应的叶子节点哈希位置和验证路径)返还给轻节点后,由于轻节点存储了所有区块的区块头,因此可以利用这些结果验证全节点查询的准确性。这样,轻节点就可以放心大胆的去消费了🥰。
软分叉的另一个应用 —— P2SH:
P2SH 中有两个阶段的验证,第一个阶段是验证 script 是正确的,第二个阶段是验证签名是正确的。
对于旧节点,只会进行第一阶段的验证,而新节点还会进行第二阶段的验证。因为第一阶段的验证过程新旧节点只是提交的内容不同,但都是将内容转换为哈希后进行比对。
因此,原先的系统中只需要第一个阶段的验证就可以了,而新节点扩展的链还需要多做一步验证,新节点验证成功的区块肯定第一阶段肯定是通过验证的,因此是向后兼容的(软分叉),而旧节点验证成功的区块第二阶段未必会验证成功(未必符合新节点的要求,因此新节点多半不会和旧节点一块儿玩)。
硬分叉与软分叉怎么区分?
硬分叉本质上就是新节点的协议是向后不兼容的,旧节点无法参与;软分叉本质上就是新节点的协议是向后兼容的,旧节点可以参与。
拿老师上课说的区块大小的例子来说,原本的区块大小是 1 MB,也就是说,超过 1 MB 的区块大小的区块原来的系统是不会承认的。
新节点如果采用的是 4 MB 的区块大小,那么在扩展的过程中,旧节点是无法参与的,因为旧节点要参与,就必须在 4 MB 的新区块后面进行扩展,而旧节点的协议是不允许先前的区块大小超过 1 MB 的,因此旧节点如果不更新协议只能自己扩展自己的链,这种情况就称为是向后不兼容,所以是硬分叉;

新节点如果如果采用的是 0.5 MB 的区块大小,那么在扩展的过程中,旧节点是可以参与的,因为旧节点要参与,只需要在 0.5 MB 的新区块后面进行扩展,旧节点的协议是先前的区块大小不超过 1 MB 就没有问题,新节点符合旧节点的协议,这种情况就称为是向后兼容,所以是软分叉。


匿名性(Anonymity)
与银行相比,比特币的匿名性要更好一些,因为现在的银行都需要身份证实名制,而比特币中可以使用化名(pseudonymity)进行交易,创建账户的时候不需要什么身份信息。但是,在过去的时候,银行也是可以使用化名的,银行使用化名的匿名性是比比特币要好的,因为使用比特币进行的交易是所有人都可以查询到的,而在银行中进行的交易普通人无权调查。
比特币的匿名性可能会通过哪些方式被破坏?
- 一个人可以在每次收款的时候都使用不同的地址,支付的时候再选择自己名下的某几个地址进行支付,这种情况匿名性看似非常好,但是这些账户之间的关联性容易被别人识破,从而知道某个人拥有哪些账户;
老师举的例子

这里关于找零需要提前说明,比特币系统中【 UTXO 】集合的结构决定了比特币在支付的时候只能使用区块中的金额大小进行支付,无法像我们使用现金一样精确地支付小额,因此上图在支付 9 BTC 时,商家需要找零 3 BTC。
在上面的例子中,很容易就可以推测出支付的 A₁ 和 A₂ 账户地址是属于同一个用户的,T₁ 地址是商家的收款地址,因为如果 T₂ 是收款地址,那么用户只需要一个账户用于支付就可以了。通过这一笔交易就可以推测出很多信息。
- 账户地址与真实世界中的地址之间的关联性可能会被找到;
只要使用比特币与实体世界发生交互的时候就有可能泄漏地址,最明显的就是使用现金购买比特币或是将比特币卖出得到现金,我们都需要在线下与交易所或是某个群体交互,在这个过程中就会泄漏我们的身份。同时,虽然比特币支付使用的是化名,但是只要你在某个时间地点使用比特币购买了商品,并且周围有认识你的人看到了你用比特币支付,即便交易经过了哈希处理,但是还是可以通过区块链中交易的时间地点(比如xxx某天下午在xxx咖啡厅花费了一笔比特币的交易记录)推测出化名对应的人是谁。
因此,比特币中的匿名性并不是绝对的。对于有歪心思想要用比特币从事不正当生意的人,实际上也无法使用比特币逃过制裁,比特币的匿名性并没有它看上去那么好,过去大部分利用比特币洗黑钱做黑心事的机构基本都被查封了。即便暂时安然无恙,存在账目中的钱他们也是花费不出去的。
由于比特币系统的不可篡改性,如果你的账户关联性被发现,只要你其中一个账户的信息被泄露,就会造成不可逆转的伤害,它会被永久地写入区块链中。
如何提高用户的匿名性?
比特币网络分为网络层和应用层,因此我们需要从这两个层的角度进行考虑。
从网络层的角度,网络层中用户的匿名性泄漏主要是因为用户 IP 地址与现实世界中的位置有很强的关联性,但已经有很多的研究能够实现网络层上的匿名性了,如洋葱路由(TOR)就是通过不断的转发 IP 地址来实现匿名的,一个 IP₁ 地址转发到 IP₂ 地址中间会经过多次跳转,每次跳转的中转节点只知道上一个 IP 地址,只要节点中有一个是诚实的,那么匿名性就能够得到保证。
从应用层的角度上来说,可以采用 coin mixing 的方法,将某个用户的地址与其他用户的地址混合起来,一般是通过某个机构来完成的,但是无法确保机构不会跑路,同时它们采用的 coin mixing 的方法也要非常精密才行。
也可以通过在线钱包来实现,当把币存入在线钱包时,也许再取出来后币就不是原来存入的币了,但是仍然不能保证 coin mixing。
还可以通过交易所来完成,将比特币存入交易所,然后再将卖出后取出先进,再换成其他币…再买入比特币,这样的操作也可以实现 coin mixing,因为最后买入的比特币与当初的比特币已经不是一笔钱了(怎么有点洗钱的味道🤔) 。
还可以通过交易本身来提高用户的匿名性,就是通过零知识证明。
零知识证明(从交易本身提高用户匿名性)
零知识证明是指一方(证明者)向另一方(验证者)证明一个陈述是正确的,而无需透露除该陈述是正确的外的任何信息。

关于第一条, ,用逆否命题来说明,就是 ;
第二条类似于前面介绍哈希函数的时候的 hiding 性质;
第三条指的是同态加密后的两个数进行的加法、乘法运算等价于加密前两个数的加法、乘法运算(这个对于隐私保护是非常有用的),有了同态加法和同态减法就可以扩展到多项式上了。
一个简单的例子

根据零知识证明,Allice 向 Bob 进行证明的时候,不能让 Bob 知道 x 和 y 的具体值。

最后一项利用了性质一,即只要 ,那么 x 就会等于 y。但在上面的解法中有一个缺陷,就是 Bob 可以通过暴力求解的方法迭代出 x 和 y 的值,因此 Alice 还需要对 x 和 y 增加随机数的处理。
盲签方法:
图中进行的交易是 “A → B”。 

比特币系统的好处就是没有银行这样的机构知道用户的所有交易数据,保障了用户的安全和隐私。但是如果可以在银行不知道用户交易的情况下完成用户交易的验证,那么也可以实现用户的隐私保护。

盲签法文字描述的过程其实比较好理解,需要注意的是银行返回的签名 token 并不是唯一的,多笔交易可以共用一个 token,这样当 B 去找银行验证的时候,银行不会根据 token 的值判断出 A 和 B 发生了交易。
“盲签法”设计的关键就在于银行无法直接从 Token 推测出 SerialNum,从而无法知道用户A和B之间发生的交易。
零币和零钞:

零币和零钞是专门为匿名性设计的加密货币。
零币是通过基础币进行换取的,换取后原先的基础币就不能使用了,零币在花费时只需要证明花费的币是系统中存在的某个合法的币即可,不用透露具体的币的来源(因此消除了旧地址和新地址的关联性),这样可以避免自己的关联账户被其他人知晓。
零钞系统相当于是自己直接创建了一个新的匿名电子系统,只是用零币进行交易。零钞系统其实可以看作是对原来区块链的数学抽象,将原来的区块链抽取到加密层进行交易。
这些加密货币目前(2018 年)并不是主流的货币,同时在性能上也有一定的损失,在数学原理上对初始化有比较严格的要求。
由于大部分的用户对于这种强匿名性没有很高的要求,不愿意为了这些匿名性而去转换交易货币,因此没有得到普及。
零币和零钞虽然在数学原理上有很强的匿名性,但是它仍然无法接解决线下实体交互时身份信息泄漏的问题。
课程问答
① 进行比特币交易时,是否需要接收方在线?
并不需要,只需要知道接收方的公钥地址就可以了,无需接收方在线。
② 比特币交易对象的地址是从未记录过的,是否会影响交易?
不会,比特币网络中账户的创建是不用通知其他人的,因此会有地址是从未记录过的。
③ 我太蠢了,把私钥弄丢了怎么办(非泄漏,自己不知道私钥)?
这种情况下最好去医院检查一下脑子(bushi),emmm…
私钥丢失后你账户中的钱就永远取不出来了,在现实中,常常会有类似于银行的交易所,在交易所中,可以创建账户并进行身份验证,用户可以把私钥存放到交易所里。如果忘记了交易所的账户密码是可以进行重置的。
但是交易所存在一定的风险,曾经世界上最大的交易所(门头沟,Mt. Gox)就遭遇了黑客攻击,大量用户的私钥丢失的情况。
④ 如果私钥泄漏出去怎么办(自己仍能管理账户)?
这种情况下,需要抢在别人前面赶快把钱转到一个安全的账户上。
⑤ 如果填错了交易对象的地址怎么办?
填错后无法强制交易对象将钱返还,只能看交易对象的人品和心情了。
⑥ 如果一个矿工挖到矿后泄漏了自己的 nonce 怎么办?
当别人拿到这个 nonce 并发布区块后,区块奖励其实还是会发到挖到矿的矿工的手里的,因为 nonce 是与矿工的 coinbase 绑定在一起的,【 Coinbase 】 中有矿工的收款地址,因此即便泄漏了 nonce 也没事。
⑦ 交易费在未知矿工地址的情况下如何发给矿工?
实际上并不需要知道矿工的地址,交易费的计算就是将区块生成时的 total inputs 减去 total outputs 得到的,差值会直接发给 coinbase 中记录的矿工的地址。
比特币变化趋势
详细情况见下面的 pdf 课件。
思考
① 哈希指针的深入思考;
哈希指针在区块链中并非真的是以指针的方式进行显示的,本质上是对前一个区块区块头部分的哈希值。区块链是通过 level DB (谷歌开发的一种键值对存储系统)以链表的形式串起来的,每个区块或交易数据可以通过哈希指针(哈希值)作为键,实际的数据作为值,存储在 LevelDB 中。因为区块链的数据量大,LevelDB 提供了高效的键值存储和检索机制,可以快速通过哈希指针找到对应的数据块。
② 多人共享的账户不要采用私钥截断的方式保存比特币,而要采用多重签名;
私钥截断方法有什么问题?
曾经有过一种流行的说法是——“区块恋”。即一对情侣为了证明彼此的❤️🔥爱情❤️🔥,将比特币账户的私钥分成两部分,一人保管一部分,只有当他们将私钥合在一起的时候,才可以使用比特币。
如果将上面的情形扩展为一个账户由四个人管理,将私钥分成四个部分,每个人都保管其中一部分,这样会有什么问题呢?
由于私钥是 256 位的,假如私钥没有被拆开,别人想要破解私钥,需要在 的空间中检索并破解,这个难度是巨大的;
但是如果私钥被拆成了两份,每份私钥就只有 128 位,情侣在分开后,其中一方想要破解另外的 128 位私钥,则只需要在 位的空间中进行检索,这个难度小了 倍;
如果私钥被拆成了四份,每份私钥就只有 64 位,那么在知道了其他三方私钥的情况下,就只需要在 的空间中检索,这个难度是很小的。
这告诉我们不要恋爱❤️🩹,特别是不要多人恋爱(弹幕说的,不是我说的😅)。
③ UTXO 里的死钱;
早期的时候,很多人挖矿挖着玩,挖到的比特币都丢在硬盘里,后来私钥忘记了,就造成了比特币的堆积,UTXO 集合膨胀,这对于矿工来说是不友好的。上面提到的”区块恋“也会导致这个问题,”情侣分手,矿工遭殃“。还有一个造成 UTXO 集合膨胀的原因,那就是”中某聪“ 囤积了大量的币从来没有花过🤡
④ 为什么比特币系统能够绕过分布式系统中的不可能结论?
⭐ 原因
在之前区块链分布式共识部分,曾提到过两个著名的理论 【 FLP Imposibility 】 和【 CAP Theorem 】,它们都提出了要想在分布式系统中取得公式基本上是不可能的,那么比特币系统中又是如何处理这个问题的呢?
比特币系统其实并没有从真正意义上达成分布式系统中的共识,因为比特币系统中已经创建的交易仍有可能会被推翻(这违背了分布式共识理论的要求),从理论上来说,甚至可以回滚创世纪区块以后的所有区块内容。所以比特币并没有绕过前面的不可能结论。
但是理论上的不可能结论只是在某些特定的模型上是符合的,在实际的情况中不一定都会与模型的条件一致,实际情况中只需要将模型的条件改一改结果就会有很大不同。
接下来就出现了老师的经典名言(精准空降地址):
我们说知识改变命运,这句话没有错,但是对知识的一知半解可能会使你的命运变得更差,发明比特币的中本聪应该不太可能是学术界出生的,否则不太可能设计出像比特币这样的系统来。…但是大家注意,不要被学术界的思维限制了头脑,不要被程序员的思维限制了想象力。
⑤ 比特币的稀缺性;
比特币的稀缺性在于比特币的总量是固定的,这会刺激矿工去挖矿获得收益,但从货币的角度来说,比特币的稀缺性使它无法作为货币使用。总量固定的东西是不适合用于做货币的,好的货币需要具备通货膨胀的功能,而比特币每年的发行量只会不断的减小。适度的通货膨胀可以刺激群体提前消费并提高政策的灵活性。同时,比特币的价格波动也比较大,交易费高且交易时间慢,这也是不能作为货币的原因。
⑥ 量子计算的发展会对密码学有很大影响吗,是否会使得像比特币这类加密货币变得不安全?
首先,量子计算要真正发展到能够对密码学、加密货币产生影响还有很长的一段路要走;其次,量子计算真的发展起来后,首先受到冲击的应该是传统的金融体系,与其担心量子计算对比特币的影响,不如担心它对金融体系的影响。第三点,比特币系统中的比特币地址采用的是双重加密的方法产生的,即先用私钥生成出公钥,然后在对公钥进行一次或两次哈希运算得到【 公钥哈希 】,即便是量子计算机也无法从公钥哈希中推出公钥,因此仅进行收款操作别人是无法得知你的公钥的。在发起交易时,支付者需要提供公钥本身,此时公钥会泄漏,因此从安全性的角度上来说,使用过一次的公私钥最好就不要再使用了,而是将钱转到另一个账户中生成新的公私钥地址存储(这样就不必担心量子计算的威胁了,量子计算可以从公钥 → 私钥)。
❓ My Doubts
✔️ Question 1
My Question is:
“比特币被称为是加密货币(crypto-currency),但加密货币本身却又是公开的,即区块链上所有交易都是公开的,如交易的地址、交易的内容。“ 这句话如何理解?
My Answer:
公开性指的是比特币及其他加密货币的交易信息在区块链上是可以被任何人查看的。交易信息包括交易地址和交易内容(交易的金额、时间戳)。
在比特币中,每个用户使用一个或多个地址(类似于银行账户)进行交易。每个地址是一个哈希值,不直接包含用户的个人信息,用户的隐私不会被泄漏,加密性体现在隐私的保护上。
比特币使用加密技术来保护交易的安全性和防止伪造,因此加密也体现在交易的安全性上。
✔️ Question 2
My Question is:
由于输出的哈希值只有 256 位的大小,如果一个群体专门做存储“哈希值和 nonce 映射关系”的工作,每当挖出区块链中符合难度要求的 nonce 就提交,挖出不符合要求的 nonce 就保存起来,这样不是迟早有一天会把 nonce 和 哈希值 的关系给全部测出来吗?如果这样的话,不就相当于可以从 H(x) → x?
My Answer:
如果你碰巧和我有一样的疑惑,那么恭喜你,赶紧再看看这个页面『 哈希函数 』吧,去复习复习哈希函数的输入和输出部分。哈希函数的输入空间是无穷大的,因此想要试出 H(x) 和 x 所有的一一映射关系是基本不可能的,而且比特币系统中区块头部分哈希值的计算是通过 进行的,依据不同的区块难度,本身就会有不同的 x 作为输入,nonce 只是用于改变输入产生的哈希得到满足要求的哈希值,因此你知道了 nonce 与 H(x) 的一一映射关系还是没有用的,因为 x 你是不会预先知道的。
✔️ Question 3
My Question is:
公钥和私钥都是一一对应的吗,是否可以多个公钥对应于一个私钥;公私钥对是否可以有多个;公钥只要被人知道一次,那不就相当于永远都泄露了吗,此时需要创建新的公私钥对?
My Answer:
1. 公钥和私钥的一一对应性
是的,公钥和私钥是一一对应的。每个私钥都对应一个唯一的公钥,它们是一对,通过数学算法生成。私钥用于签名和解密,而公钥用于验证签名和加密。
2. 多个公钥是否可以对应一个私钥?
不可以。一个私钥只能生成一个唯一的公钥。因此,在通常的公钥加密系统中,多个公钥不能对应一个私钥。每个私钥与其公钥都是唯一绑定的。
3. 是否可以有多个公私钥对?
可以。一个人可以拥有多个公私钥对。比如,用户可以为不同的用途或账户生成多个公私钥对,在比特币或其他加密货币中,用户可以为不同的交易地址生成多个密钥对。
4. 公钥被知道后是否需要更换?
公钥可以公开,不会泄露敏感信息。在公钥加密系统中,公钥设计为公开使用,用于加密或验证签名。因此,公钥即使被他人知道,也不会泄露私钥的安全性。
5. 公钥何时会泄漏?
比特币系统中的用户在收款时是不会泄漏公钥地址的,因为收款时给出的是公钥的哈希值,将公钥哈希作为比特币首款地址。只有在用户作为付款方时公钥才会泄漏。因此建议对隐私有高度要求的用户在每当泄漏公钥地址时就重新创建一个新的公私钥对,将原先账户的钱转入新的账户中。
✔️ Question 4
My Question is:
使用比特币向一个商家购买了某个商品后,如果想要退款怎么进行?
My Answer:
如果想要退款,不会去回滚先前购买商品时创建的交易,而是会创建一个新的交易进行退款。
以太坊
不好意思哈,大家,由于空闲时间少+科研暂时没有以太坊方面的需求,因此还没有以太坊部分的笔记,但是肖老师的课程真的让人很想听下去,把课程听完,所以等到后面我的时间充裕了,会把这部分笔记补上的。
如果对机器学习感兴趣,欢迎大家看这篇博客: 『 机器学习笔记(吴恩达) 』
如果对于论文阅读工具的选择、论文查找、论文网站选取等方面有困扰的话,欢迎阅读这篇博客:『 科研训练第一步:论文!! 』
如果还没有入门 python,欢迎大家阅读这篇博客:『 python 入门笔记』
如果本篇笔记对你有用,能否『请我吃根棒棒糖🍭 』🤠…