深入研究比特币:(0)从零到一

介绍

比特币(Bitcoin)是一种去中心化的数字货币系统,由一个化名为中本聪(Satoshi Nakamoto)的个体或团体于2009年提出(https://bitcoin.org/bitcoin.pdf)。比特币是世界上第一个被广泛采用的区块链系统。

比特币的核心特性包括去中心化控制、供应量限制和交易透明性。去中心化意味着比特币网络不由任何单一实体控制,而是由分散的网络节点共同维护。供应量限制则体现在比特币的总量被设计为2100万枚,这一机制旨在防止通货膨胀。每笔比特币交易都被记录在一个公开的分布式账本上,即区块链,确保了交易的可追溯性和透明性。

比特币的生成过程称为挖矿(mining),涉及解决复杂的数学问题以验证交易并添加新区块到区块链中。挖矿奖励包括新生成的比特币以及交易费用,这一机制既促进了网络的安全性,也激励了网络参与者的贡献。

疑问:

  1. 为什么比特币能够去中心化?或者说为什么不同节点之间能最终记录相同的数据?

  2. 比特币的所有权是如何被区块链记录和验证的?或者说如何确认交易?

  3. 比特币的挖矿机制是如何确保网络安全性和挖矿者的公平的?

  4. 比特币的总量为何被设计为2100万,并且这种设计如何能防止通货膨胀?

去中心化原理

如何实现去中心化?

首先解释什么是去中心化,去中心化的首要特征是控制权分散在多个参与者之间。

比特币的去中心化基于区块链和分布式共识机制。区块链是一个不断增长的记录所有交易的链式数据库,而分布式共识机制则确保了网络中各个节点对交易的一致认可。最长的区块链表示得到最多参与者达成的认可状态。因此比特币能实现多数民主的去中心化。

那么,比特币为什么能达成认可呢?这就要说到共识机制。

共识机制

分布式共识是指在一个分布式系统中,各个节点通过协作达成一致的决策。

比特币节点通过解决数学难题来验证交易并创建新的区块,其他节点需要验证这些区块,并通过多数节点的同意来达成共识,从而确认交易的有效性和区块的添加。

换言之,如果我的节点真的解决了难题,那么其他节点都会认可我的工作,大家就会都把我的区块添加到末尾,相当于都选择了我的决定。

具体来说,大家是这样达成共识的:

  1. 交易广播: 在比特币网络中,任何一个用户都可以发起一笔交易,并将其广播到网络中的其他节点。这些交易被收集到一个待处理交易池中。

  2. 打包交易成区块: 矿工节点从待处理交易池中选择一些交易,并将它们打包成一个区块。

  3. 解决工作量证明问题: 矿工需要通过计算来解决一个数学难题,即寻找一个特定的哈希值,使得该区块的哈希值小于目标值。这个过程需要大量的计算能力,但是一旦找到了符合条件的哈希值,证明了这个矿工已经完成了工作量证明。

  4. 广播区块: 一旦一个矿工找到了符合条件的哈希值,他就会广播这个新的区块到网络中的其他节点。

  5. 接受和验证: 其他节点接收到新的区块后,会对其进行验证。他们会检查区块中的交易是否合法,并确认工作量证明是否有效。如果一切正常,他们就会接受这个新的区块。

  6. 选择最长链: 如果两个矿工几乎同时找到了符合条件的哈希值,网络可能会发生分叉。在这种情况下,其他节点将选择加入最长的链,并继续工作在其上。这样,整个网络将逐渐趋于一致,只有最长的链会被认可为有效的区块链。

那么,达成共识最关键的环节是什么?

工作量证明

解决工作量证明问题是最关键的一步。这个过程需要大量的计算能力,同时也是保证网络安全性的重要机制。只有完成了工作量证明,矿工才能得到挖矿奖励并广播新的区块,这也就意味着他们有足够的动力去参与并维护比特币网络。

如果我控制的节点广播一个假的交易会怎样? 假的交易会被其他节点验证为无效。比特币使用未花费交易输出(UTXO)模型来跟踪比特币的所有权。在验证交易时,需要确保每个输入引用的前一个交易的输出是存在的且未被花费过。如果一个输入引用了一个不存在或已被花费的输出,那么这个交易就是非法的。这些交易的源头是 Coinbase 输入,需要通过工作量证明获得,工作量证明也需要多数节点验证通过。因此没有一个环节可以让造假成功。

比特币网络

比特币网络是一个由全球范围内众多计算机节点组成的去中心化系统,这些节点遵循一套兼容的协议,通过互联网连接,共同维护和验证交易,确保交易的安全性和可靠性。

这些节点之间如何通信?加入了新的节点怎么处理? 新的节点加入到网络中时,会通过与已存在的节点进行通信,同步区块链的数据。新节点在接收并验证完所有区块后,会开始参与到网络的运行中,接收和转发交易,可能参与到挖矿过程中。

从实现原理而言怎么知道有哪些已存在的节点? 可以通过所谓的种子节点来发现新的节点。种子节点是网络中已知的、稳定运行的节点,新节点可以通过连接这些种子节点来发现网络中的其他节点。这些种子节点的地址通常被硬编码在比特币的软件实现中,例如 Bitcoin Core 源码的 bitcoin/src/kernel/chainparams.cpp at master · bitcoin/bitcoin 处包含一些内置的种子节点。节点控制者也可以通过 addnode 命令添加新的节点。

那么最后每个节点都会得到全网的节点,是吗? 不是完全如此。本身是去中心化的协议,没有什么保证能让它连到所有节点。

既然如此,又怎能确认交易有过半的节点认可? 简单看了下,代码里(貌似)并没有直接检查过半的逻辑,而是在这套规则下,最后在全网宏观展现出过半认可。

具体而言,通过每个节点独立地验证交易和区块的有效性,以及最终选择最长链的规则,大多数节点最终会选择相同的链作为有效链,从而确保了交易和区块得到了过半节点的认可。

临时分叉

那举一个刁钻的例子,假设节点 A 和 B 间接连接,A 和 B 同时解决了一个工作量问题,两个子网同时生成了一个长链,会怎样? 这是一个已知且偶尔会发生的分叉事件。当两个矿工几乎在同一时间解决了相同的工作量证明问题并各自广播了自己的新区块时,网络上的不同节点可能会接收到这两个区块中的任何一个作为最新的区块。这会导致网络暂时性地分裂成两个具有相同长度但内容不同的链,例子中的节点 A 和 B 分别位于这两个链中。

比特币网络是如何处理这种分叉的呢?

  1. 临时分叉:当发生分叉时,网络上的节点会暂时接受它们首先收到的区块作为有效区块,并在此基础上继续构建区块链。因此,对于一段时间,网络会存在两个有效的链,直到下一个区块被挖出。

  2. 解决分叉:分叉的解决依赖于下一个区块的挖掘。当下一个区块在其中一个分叉上被成功挖出并广播到网络时,那个分叉的长度将超过另一个,根据比特币网络的规则(最长链原则),网络上的节点将接受这个更长的链作为有效的链,并在此基础上继续构建区块链。这意味着在较短的分叉上的区块(包括其中的交易)将被丢弃,网络再次达成一致。

  3. 交易重新加入内存池:在较短链上的交易,如果没有被包含在最长链的区块中,这些交易会返回到节点的内存池中,等待被未来的区块重新包含。

那么岂不是一个节点的工作白费了?

是的,非酋节点。但这种事情对于一个节点而言,很少发生的。

还有什么情况下会发生分叉?

普通分叉

临时分叉一般不会导致全网的分叉。比特币全网出现分叉通常有两种:

  1. 硬分叉(Hard Fork):彻底的分裂。当网络中的节点无法达成共识,导致对区块链规则的重大改变时,就会发生硬分叉。这可能是由于协议的更新或改变引起的,例如改变区块大小限制或更新共识机制。在硬分叉中,旧规则和新规则之间存在不兼容,导致区块链分裂成两条独立的链,每个链上的节点可能会选择不同的规则进行验证和挖矿。

  2. 软分叉(Soft Fork):前向兼容的分裂。软分叉是对区块链规则的较小更改,这些更改对于旧规则是兼容的,因此不会导致网络的分裂。软分叉通常是由于对协议的更新或改进而引起的,但新规则被视为是向后兼容的。在软分叉中,大多数节点会升级到新规则,而那些未升级的节点仍然可以与网络中的其他节点进行通信。

去中心化实现

Bitcoin Core

Bitcoin Core 是一个参考实现(https://github.com/bitcoin/bitcoin),功能比较完整,架构如下:

本篇暂时不会涉及到技术细节,请关注后面的文章。

全节点

全节点不仅存储了所有的交易历史,还验证了这些交易的合法性,并且广播这些交易到比特币网络中。全节点能够独立自主地验证所有交易,而不需要信任任何其他节点。

那么,比特币网络存在非全节点吗?

非全节点

非全节点也是有的。非全节点可能不存储整个交易历史记录,也可能不验证所有交易的合法性。相反,非全节点可能只存储部分交易历史或仅关注特定区块链数据,以满足其特定需求。举例如下:

  1. 轻节点(Light Nodes):轻节点依赖于全节点的运行。它们只需下载比特币区块链的区块头信息,因此需要的下载和存储容量比全节点少得多。轻节点可以连接到比特币网络并进行交易,但无法独立验证比特币网络的规则,因此需要与全节点进行通信以获取区块数据。

  2. 挖矿节点(Mining Nodes):挖矿节点或者说矿工节点是负责打包交易并创建新区块的节点。它们运行特殊版本的比特币软件,包含了创建和提议区块的特殊规则。挖矿节点通过竞争来创建下一个区块,并将提议的区块广播给比特币网络上的其他节点。

  3. 归档节点(Archive Nodes):归档节点与全节点是同义词,它们存储了完整的区块链副本,并能够验证比特币网络的所有规则。归档节点之所以与全节点区分开来,是为了区分修剪节点。

  4. 修剪节点(Pruned Nodes):修剪节点存储了一定大小的完整区块链历史记录。一旦达到了存储限制,它们会开始删除或修剪早期的区块,以便能够存储新区块的完整版本。修剪节点比全节点小,但比轻节点大。

  5. 权威节点(Authority Nodes):权威节点是由比特币网络的权威机构或组织运行的节点。它们具有特殊的权限和责任,用于维护和管理比特币网络的运行。

矿工节点可以是,也可以不是全节点。矿工节点可以选择只存储和处理与挖矿相关的交易数据和区块头信息,而不必存储整个区块链的所有数据。这样可以减少存储和计算资源的需求,从而提高挖矿的效率。

在这些节点的共同努力下,能够让比特币每10分钟产生一个包含几百到几千个交易的区块。

比特币网络的交易

本章解答如下问题:

  • 比特币的交易是什么?

  • 交易是如何产生的?

  • 交易是如何验证的?

  • 交易是如何存储的?

交易记录

交易是一个被签名的数据结构。可以结合下面的伪代码来理解:

1const bitcoin = require('bitcoinjs-lib');
2const tx = new bitcoin.TransactionBuilder();
3tx.addInput('输入交易的哈希', '输入交易的索引');
4tx.addOutput('收款地址', '转账金额');
5tx.addOutput('找零地址', '找零金额');
6tx.sign(0, '私钥');
7const rawTx = tx.build().toHex();

基本和一个普通的转账记录没什么两样,但是输入交易的哈希、索引是什么?

交易哈希和交易索引

比特币交易的哈希用于唯一标识交易,而交易索引用于定位交易在区块链中的位置。

交易哈希(Transaction Hash):每笔比特币交易都有一个唯一的哈希值,用于标识该交易。交易哈希是通过对交易数据进行哈希运算得到的。

交易索引(Transaction Index):交易索引是指交易在区块链中的位置信息。每个区块都包含多笔交易,交易索引用于标识交易在区块中的位置。索引从0开始,按照交易在区块中的顺序递增。通过交易索引,可以快速定位到特定的交易。

那么,为什么要在转账时设置输入交易呢?

UTXO 模型

这是因为比特币采用 UTXO 交易模型。

让我们类比来理解,想象一下你有一个玩具商店。每当有人来买玩具时,他们会拿出一些硬币来支付。在这个类比中,你是一个玩具商店的所有者,而硬币就是UTXO(未使用的交易输出)。

每一个硬币都有不同的面值,就像每一个UTXO都有不同的价值。当有人购买一个玩具时,你会从收到的硬币中选择足够的面值来支付给他们。这就是交易的过程。

例如,如果一个玩具的价格是5个硬币,而你手中有一枚面值为10的硬币和一枚面值为1的硬币,你可以选择支付10个硬币并收回5个硬币作为找零。这样,你就使用了一枚UTXO(面值为10)来支付,并生成了两个新的UTXO(面值为5和1)。

UTXO的特点是一旦被使用,它就会被消耗掉,就像硬币一样。当你支付给顾客时,你用完了一枚硬币,它就不能再次使用了。

在比特币和其他加密货币中,UTXO是用来记录所有交易的方式。每个UTXO都有一个所有者和一个面值。当你发送加密货币时,你实际上是消耗一个或多个UTXO,并生成新的UTXO以支付给接收方。

这会引发一个思考:如果每个人都使用 UTXO 来付款,那么最初的 UTXO 从何而来?

无中生有的比特币

最初的UTXO是通过一种称为"挖矿"的过程创建的。

当一个矿工成功解决了问题并创建了一个新的区块时,他们会被奖励一定数量的比特币作为回报。这个奖励就是新生成的UTXO。这个过程被称为"挖矿奖励",它有效地引入了新的比特币进入系统,并为矿工提供了激励来参与挖矿。

初始的UTXO是在比特币网络启动时通过挖矿奖励逐步生成的。随着时间的推移,这些UTXO被交易使用,分割成更小的面值,并形成新的UTXO。这样会导致区块链网络的负担加重吗?而且许多零碎的 UTXO 就会被浪费。

UTXO 膨胀问题

上面提到的问题实际上就是 UTXO 膨胀问题。UTXO膨胀问题会增长节点的存储需求(因为需要同步整个区块链的 UTXO 集合)和验证成本(因为验证一个交易的有效性需要检查其所引用的UTXO是否已被消耗。随着UTXO集合的增长,验证交易所需的时间和计算成本也会增加。)

最近流行的 BRC20 项目就加剧了这种问题。

BRC20 代币是通过在比特币的 UTXO 中添加特定信息来实现的。每个代币都对应一个 UTXO,代币的转移就是通过转移对应的 UTXO 来实现的。

在 BRC20 协议下,每次创建或转移代币都会生成一个新的 UTXO。这意味着每个代币都对应一个新的 UTXO,而这些 UTXO 会不断增加,导致UTXO集的膨胀。

比特币社区提出了一些解决方案,包括 UTXO 合并(可以减小 UTXO)、UTXO 清理(创建特殊交易消耗掉不再用的 UTXO)

以太坊放弃了 UTXO 模型,这可能是原因之一。

交易的验证

节点接收到交易后:

  1. 检查交易格式:节点首先检查交易是否符合特定的格式和结构要求。

  2. 验证签名:节点会验证交易的签名是否有效,以确保发送者具有对所使用的资金进行交易的权限。

  3. 检查输入UTXO:节点会检查交易中引用的输入UTXO是否存在,以确保这些UTXO尚未被使用。

  4. 验证脚本:节点会执行交易中的脚本或智能合约代码,以确保满足特定的条件和规则。

之后交易会广播到网络中的其它节点。

交易的存储

交易会依次经历广播、选择、打包和添加到区块链的过程,最终成为区块链的一部分。

全网的节点收到交易后,会验证交易并暂存到交易池(或称为内存池)。交易池是节点暂时存储待处理交易的缓冲区。

在被包含在区块中之前,交易需要等待被矿工选中并打包进新区块。矿工是网络中的特殊节点,负责打包交易并创建新的区块。

当矿工准备创建新区块时,它们会从交易池中选择一些交易,并将这些交易按照特定的顺序打包进区块中。通常,矿工会优先选择手续费较高的交易,因为这样可以获得更高的奖励。

一旦交易被包含在新区块中,并且该区块被添加到区块链中,交易就被认为是已经确认。此时被永久存储在网络中。

钱包和地址

对于一般用户而言,我们直接用钱包 App 就可以创建钱包并发起转账。我们稍微深入一点,看看钱包是如何创建的。

创建钱包

引入依赖:

1[dependencies]
2bitcoin = "0.27.0"
3rand = "0.8.4"

编写代码:

 1use bitcoin::secp256k1::Secp256k1;
 2use bitcoin::util::bip32::{ExtendedPrivKey, ExtendedPubKey};
 3use bitcoin::Network;
 4use rand::rngs::OsRng;
 5use rand::Rng;
 6
 7fn main() {
 8    // 使用操作系统的 RNG
 9    let secp = Secp256k1::new();
10    let mut entropy = [0u8; 32];
11    let mut rng = OsRng;
12    rng.fill(&mut entropy);
13
14    // 生成一个新的随机私钥
15    let sk = ExtendedPrivKey::new_master(Network::Bitcoin, &entropy)
16        .expect("Failed to create private key");
17
18    // 从私钥派生公钥
19    let pk = ExtendedPubKey::from_private(&secp, &sk);
20
21    // 打印私钥和公钥
22    println!("Private Key: {}", sk);
23    println!("Public Key: {}", pk.public_key);
24
25    let address = bitcoin::Address::p2pkh(&pk.public_key, Network::Bitcoin);
26
27    // 打印地址
28    println!("Address (base58): {}", address.to_string());
29}

过程如下:

  1. 生成一个随机数作为种子。

  2. 使用种子生成一个随机私钥。

  3. 从私钥派生公钥。

  4. 使用公钥生成比特币地址。

  5. 打印私钥、公钥和地址。

生成的可能结果如下:

1Private Key: xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2
2Public Key: 03d63d9fd9fd772a989c5b90edb37716406356e98273e5f98fe07652247a3a8275
3Address (base58): 1BSzycKiqQazwiJSxz7RrNq25ADgtmA2wM

这里涉及到一些密码学的概念,将会在之后更深入探讨。

公钥和私钥

私钥(Private Key)是一个随机生成的256位数字,通常表示为一个大整数。私钥是生成钱包的核心,它是生成公钥和签名的基础。私钥必须保密,只有拥有私钥的人才能对其进行签名和解密。

公钥(Public Key)是由私钥派生而来的,它是私钥通过椭圆曲线加密算法生成的另一个256位数字。公钥可以被公开分享给其他人,用于验证签名和加密消息。它可以被用来生成比特币地址,也可以从比特币地址派生。

地址(Address)是由公钥派生而来的,它是一个人类可读的字符串,用于接收比特币。比特币地址通常以数字和字母的组合表示,如以1开头的Base58编码字符串。地址是公开可见的,任何人都可以使用地址向其发送比特币,但只有私钥的持有者才能花费这些比特币。

钱包(Wallet)是一个用于存储和管理私钥和公钥的应用程序或设备。钱包可以生成新的私钥和公钥对,签名交易并管理用户的比特币资产。钱包可以存在于不同的形式,例如软件钱包、硬件钱包、纸钱包等。

  • 软件钱包:在普通计算机(例如电脑、手机)上可以通过代码控制的钱包。

  • 硬件钱包:一种专用设备,用于安全地存储比特币私钥和执行交易。它们通常具有专门的安全芯片,用于保护私钥并离线签名交易。硬件钱包与计算机或手机通过USB或蓝牙连接,用户可以使用钱包上的按钮和显示屏来确认交易和管理资产。硬件钱包的优点是高度安全,私钥永远不会暴露在联网设备上,因此几乎不受恶意软件或网络攻击的影响。

  • 纸钱包:不使用计算机设备保存的私钥。例如写在纸上。

下一章节,我们一起更近一层探究比特币,比如为什么地址和公钥可以相互转换,交易中具体存放的内容等等。

参考