区块链入门 比特币技术
廖雪峰的区块链笔记。 参考
比特币解决的问题
比特币则是通过区块链技术,把整个账本全部公开,人手一份,全网相同,因此,修改账本不会被其他人承认。比特币的区块链就是一种存储了全部账本的链式数据库,通过一系列密码学理论进行防篡改,防双花。
比特币解决的是现金电子化后无需中央信任机构的交易问题,即M0如何通过网络进行价值传输。我们已经习惯了通过互联网对数字化的新闻、音乐、视频进行信息传输,因为信息传输的本质是复制,但现实世界的现金可不能复制。想象一下我们如何把100元现金通过网络发送给另一个人,同时确保交易前后两个人的现金总额保持不变。所以,中本聪的白皮书把比特币定义为“点对点的电子现金系统”。
区块链是由一个一个区块构成的有序链表,每一个区块都记录了一系列交易,并且,每个区块都指向前一个区块,从而形成一个链条:
比特币网络中是每次生成一个新区块,新区块中会打包多笔交易信息,
区块链原理
交易信息的来源:内存池(Mempool)
什么是内存池?
当用户(或钱包、交易所)发起一笔交易,例如从一个地址发送 BTC 到另一个地址,这笔交易会首先被广播到比特币网络,然后:
- 网络中的节点会接收到这笔交易
- 节点会先对其进行验证(签名是否有效、余额是否够等)
- 验证无误后,这笔交易就被加入到内存池(Mempool)
挖矿时如何生成区块?
矿工创建新区块时:
- 从内存池中选择交易(优先选择手续费高的)
- 将这些交易打包成一个候选区块(称为
block template) - 进行工作量证明(Proof of Work,解题)
- 谁先解出哈希,谁就生成新区块,广播给全网
- 其他节点验证区块,没问题就接受该区块,并加入区块链中
每个区块至少包含两笔交易:
- Coinbase 交易:矿工的奖励交易(包括区块奖励和所有交易的手续费)
- 用户交易:从 Mempool 中打包进来的普通交易
情况:两个矿工同时挖出区块,产生短暂分叉
假设当前链到高度 N,现在两个矿工分别几乎同时挖出下一个新区块。 因为网络传播延迟,不是所有节点都立刻看到同一个区块
解决机制:最长链原则(Longest Chain Rule)
比特币规定:所有节点只承认「工作量最多(最长)」的链为主链。
分叉如何被解决?
两条链并存时,网络中部分节点支持
N+1a,另一部分支持N+1b下一轮,假设某个矿工在
N+1a的链上挖出了N+2a,并成功广播给大多数节点:mathematica CopyEdit 区块 N / \ 区块 N+1a 区块 N+1b | 区块 N+2a所有节点看到
N+2a后发现这条链「更长」,就切换到这个分支N+1b就成为孤块(orphan block),被丢弃
孤块(Orphan Block)
- 是被网络短暂接受但最终被抛弃的区块
- 其中的交易 不会丢失,而是重新回到 Mempool
- 这些交易可能会被重新打包进后续区块中
举个例子:
比如:
- A矿工和B矿工分别在北京和纽约,几乎同时挖出区块高度为
850,000 - A的区块传播到亚洲快一些,B的传播到美洲快
- 某矿工在接收到 A 的区块后成功挖出了
850,001 - 这样整个网络最终选择了以 A 为主的链,B的链被回滚
初步广播并打包 ≠ 最终确认
在比特币中:
- 你的交易被打包进某个区块 ✅,并不代表最终交易成功
- 如果这个区块后来被抛弃(因为发生分叉),那么这个交易就等于没发生过
- 这时交易会回到内存池(Mempool)中等待重新打包
在区块链分叉发生时,交易是否成功,要看它是否最终出现在主链中。
分叉中交易可能经历:
| 状态 | 说明 |
|---|---|
| 被某个区块打包 | 暂时进入链中,但可能被分叉掉 |
| 区块被孤立(orphaned) | 所有交易视为未确认,回到 Mempool |
| 被新主链重新打包 | 再次等待确认,才算最终成功 |
所以为什么我们说交易要有确认数?
比特币交易通常说:
“建议等 6 个确认,再认为交易是最终不可逆的”
确认数是指:
- 某交易所在区块之后又被多少个区块所确认
| 确认数 | 安全性说明 |
|---|---|
| 0 | 仅在 Mempool,还没被打包 |
| 1 | 刚被打包,可能会被分叉 |
| 2~3 | 安全性逐渐提高 |
| 6+ | 基本不可逆(99.999% 安全) |
为什么越多确认越安全?
因为要「回滚」你的交易,攻击者必须:
- 回滚区块 N 所在的整条链
- 并从区块 N 开始挖出一个比现在主链还长的新链
- 这需要消耗大量算力(PoW)
攻击成本随着确认数指数级上升
- 确认数 = 1:攻击者只需要超过网络 50% 算力一点点,就有较高成功率
- 确认数 = 6:攻击者需要极高算力和运气,在短时间内挖出 6 个新区块赶上主链
- 确认数 > 6:攻击成本已经接近不可能,除非攻击者掌握接近全球一半的比特币算力
如果真的持续分叉,会怎样?
这种情况通常只在以下条件下出现:
- 恶意攻击(如拥有超过 50% 算力的攻击者)
- 网络严重分区(partitioning)
- 共识机制被篡改(如协议漏洞)
但一旦这样发生,整个比特币生态就会:
- 出现链重组(reorg)
- 引发重大共识危机
- 开发者/社区可能会通过硬分叉修复
解释一下什么是区块高度(Block Height)
- 区块高度是用来表示某个区块在整个区块链中的位置编号
- 创世区块(第一个区块,Genesis Block)的高度是
0 - 每新挖出一个区块,高度就 +1
| 项目 | 含义 |
|---|---|
| 区块高度 851,768 | 是比特币链上的第 851,768 个区块 |
| 作用 | 用于定位、检索、追踪、验证交易历史等 |
| 如何查看 | 区块浏览器(如 mempool.space) |
常用哈希算法
常用的哈希算法以及它们的输出长度如下:
| 哈希算法 | 输出长度(bit) | 输出长度(字节) |
|---|---|---|
| MD5 | 128 bit | 16 bytes |
| RipeMD160 | 160 bits | 20 bytes |
| SHA-1 | 160 bits | 20 bytes |
| SHA-256 | 256 bits | 32 bytes |
| SHA-512 | 512 bits | 64 bytes |
比特币使用的哈希算法有两种:SHA-256和RipeMD160
SHA-256的理论碰撞概率是:尝试2的130次方的随机输入,有99.8%的概率碰撞。注意2130是一个非常大的数字,大约是1361万亿亿亿亿。以现有的计算机的计算能力,是不可能在短期内破解的。
比特币使用两种哈希算法,一种是对数据进行两次SHA-256计算,这种算法在比特币协议中通常被称为hash256或者dhash。
另一种算法是先计算SHA-256,再计算RipeMD160,这种算法在比特币协议中通常被称为hash160。
区块链不可篡改特性
Merkle Hash
在区块的头部,有一个Merkle Hash字段,它记录了本区块所有交易的Merkle Hash:

Merkle Hash是把一系列数据的哈希根据一个简单算法变成一个汇总的哈希。
假设一个区块有4个交易,我们对每个交易数据做dhash,得到4个哈希值a1,a2,a3和a4:
a1 = dhash(tx1)
a2 = dhash(tx2)
a3 = dhash(tx3)
a4 = dhash(tx4)
注意到哈希值也可以看做数据,所以可以把a1和a2拼起来,a3和a4拼起来,再计算出两个哈希值b1和b2:
┌───────────────┐ ┌───────────────┐
│b1=dhash(a1+a2)│ │b2=dhash(a3+a4)│
└───────────────┘ └───────────────┘
▲ ▲
┌───────┴───────┐ ┌───────┴───────┐
│ │ │ │
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│a1=dhash(tx1)│ │a2=dhash(tx2)│ │a3=dhash(tx3)│ │a4=dhash(tx4)│
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
最后,把b1和b2这两个哈希值拼起来,计算出最终的哈希值,这个哈希就是Merkle Hash:
如果交易的数量不恰好是4个怎么办?例如,只有3个交易时,第一个和第二个交易的哈希a1和a2可以拼起来算出b1,第三个交易只能算出一个哈希a3,这个时候,就把a3直接复制一份,算出b2,这样,我们也能最终计算出Merkle Hash:
如果有5个交易,我们可以看到,a5被复制了一份,以便计算出b3,随后b3也被复制了一份,以便计算出c2。总之,在每一层计算中,如果有单数,就把最后一份数据复制,最后一定能计算出Merkle Hash:
从Merkle Hash的计算方法可以得出结论:修改任意一个交易哪怕一个字节,或者交换两个交易的顺序,都会导致Merkle Hash验证失败,也就会导致这个区块本身是无效的,所以,Merkle Hash记录在区块头部,它的作用就是保证交易记录永远无法修改。
Block Hash
区块本身用Block Hash——也就是区块哈希来标识。但是,一个区块自己的区块哈希并没有记录在区块头部,而是通过计算区块头部的哈希得到的:

区块头部的Prev Hash记录了上一个区块的Block Hash,这样,可以通过Prev Hash追踪到上一个区块。
由于下一个区块的Prev Hash又会指向当前区块,这样,每个区块的Prev Hash都指向自己的上一个区块,这些区块串起来就形成了区块链。
区块链的第一个区块(又称创世区块)并没有上一个区块,因此,它的Prev Hash被设置为00000000...000。
如果一个恶意的攻击者修改了一个区块中的某个交易,那么Merkle Hash验证就不会通过。所以,他只能重新计算Merkle Hash,然后把区块头的Merkle Hash也修改了。这时,我们就会发现,这个区块本身的Block Hash就变了,所以,下一个区块指向它的链接就断掉了。
由于比特币区块的哈希必须满足一个难度值,因此,攻击者必须先重新计算这个区块的Block Hash,然后,再把后续所有区块全部重新计算并且伪造出来,才能够修改整个区块链。
在后面的挖矿中,我们会看到,修改一个区块的成本就已经非常非常高了,要修改后续所有区块,这个攻击者必须掌握全网51%以上的算力才行,所以,修改区块链的难度是非常非常大的,并且,由于正常的区块链在不断增长,同样一个区块,修改它的难度会随着时间的推移而不断增加。
P2P交易原理
数字签名算法
常用的数字签名算法有:RSA算法,DSA算法和ECDSA算法。比特币采用的签名算法是椭圆曲线签名算法:ECDSA,使用的椭圆曲线是一个已经定义好的标准曲线secp256k1:
比特币采用的ECDSA签名算法需要一个私钥和公钥组成的秘钥对:私钥本质上就是一个1~2256的随机数,公钥是由私钥根据ECDSA算法推算出来的,通过私钥可以很容易推算出公钥,所以不必保存公钥,但是,通过公钥无法反推私钥,只能暴力破解。
比特币的私钥是一个随机的非常大的256位整数。它的上限,确切地说,比2256要稍微小一点:
0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4140
而比特币的公钥是根据私钥推算出的两个256位整数。
如果用银行卡作比较的话,比特币的公钥相当于银行卡卡号,它是两个256位整数:

比特币的私钥相当于银行卡密码,它是一个256位整数:
18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725
比特币钱包实际上就是帮助用户管理私钥的软件。因为比特币的钱包是给普通用户使用的,它有几种分类:
- 本地钱包:是把私钥保存在本地计算机硬盘上的钱包软件,如Electrum;
- 手机钱包:和本地钱包类似,但可以直接在手机上运行,如Bitpay;
- 在线钱包:是把私钥委托给第三方在线服务商保存;
- 纸钱包:是指把私钥打印出来保存在纸上;
- 脑钱包:是指把私钥记在自己脑袋里。
对大多数普通用户来说,想要记住私钥非常困难,所以强烈不建议使用脑钱包。
交易
记录在区块链上的交易。每个区块都记录了至少一笔交易,一笔交易就是把一定金额的比特币从一个输入转移到一个输出
在实际的交易中,输入比输出要稍微大一点点,这个差额就是隐含的交易费用,交易费用会算入当前区块的矿工收入中作为矿工奖励的一部分:
比特币实际的交易记录是由一系列交易构成,每一个交易都包含一个或多个输入,以及一个或多个输出。未花费的输出被称为UTXO:Unspent Transaction Output。
当我们要简单验证某个交易的时候,例如,对于交易f36abd,它记录的输入是3f96ab,索引号是1(索引号从0开始,0表示第一个输出,1表示第二个输出,以此类推),我们就根据3f96ab找到前面已发生的交易,再根据索引号找到对应的输出是0.5个比特币,所以,这笔交易的输入总计是0.5个比特币,输出分别是0.4个比特币和0.09个比特币,隐含的交易费用是0.01个比特币:
地址
要特别注意,比特币的地址并不是公钥,而是公钥的哈希,即从公钥能推导出地址,但从地址不能反推公钥,因为哈希函数是单向函数。
比特币的公钥是根据私钥由ECDSA算法推算出来的,公钥有压缩和非压缩两种表示方法,可互相转换。
比特币的地址是公钥哈希的编码,并不是公钥本身,通过公钥可推导出地址。
挖矿原理
在比特币的P2P网络中,有一类节点,它们时刻不停地进行计算,试图把新的交易打包成新的区块并附加到区块链上,这类节点就是矿工。因为每打包一个新的区块,打包该区块的矿工就可以获得一笔比特币作为奖励。所以,打包新区块就被称为挖矿。
比特币的挖矿原理就是一种工作量证明机制。工作量证明POW是英文Proof of Work的缩写。
PoW
要让计算机实现工作量证明,必须找到一种工作量算法,让计算机无法在短时间内算出来。这种算法就是哈希算法。
通过改变区块头部的一个nonce字段的值,计算机可以计算出不同的区块哈希值:

直到计算出某个特定的哈希值的时候,计算结束。这个哈希和其他的哈希相比,它的特点是前面有好几个0:
hash256(block data, nonce=0) = 291656f37cdcf493c4bb7b926e46fee5c14f9b76aff28f9d00f5cca0e54f376f
hash256(block data, nonce=1) = f7b2c15c4de7f482edee9e8db7287a6c5def1c99354108ef33947f34d891ea8d
hash256(block data, nonce=2) = b6eebc5faa4c44d9f5232631f39ddf4211443d819208da110229b644d2a99e12
hash256(block data, nonce=3) = 00aeaaf01166a93a2217fe01021395b066dd3a81daffcd16626c308c644c5246
hash256(block data, nonce=4) = 26d33671119c9180594a91a2f1f0eb08bdd0b595e3724050acb68703dc99f9b5
hash256(block data, nonce=5) = 4e8a3dcab619a7ce5c68e8f4abdc49f98de1a71e58f0ce9a0d95e024cce7c81a
hash256(block data, nonce=6) = 185f634d50b17eba93b260a911ba6dbe9427b72f74f8248774930c0d8588c193
hash256(block data, nonce=7) = 09b19f3d32e3e5771bddc5f0e1ee3c1bac1ba4a85e7b2cc30833a120e41272ed
...
hash256(block data, nonce=124709132) = 00000000fba7277ef31c8ecd1f3fef071cf993485fe5eab08e4f7647f47be95c
比特币挖矿的工作量证明原理就是,不断尝试计算区块的哈希,直到计算出一个特定的哈希值,它比难度值要小。
对于比特币挖矿来说,就是先给定一个难度值,然后不断变换nonce,计算Block Hash,直到找到一个比给定难度值低的Block Hash,就算成功挖矿。
注意,难度值的数值越小,说明哈希值前面的0越多,计算的难度越大。
比特币网络的难度是不断变化的,它的难度保证大约每10分钟产生一个区块,而难度值在每2015个区块调整一次:如果区块平均生成时间小于10分钟,说明全网算力增加,难度也会增加,如果区块平均生成时间大于10分钟,说明全网算力减少,难度也会减少。因此,难度随着全网算力的增减会动态调整。
根据比特币每个区块的难度值和产出时间,就可以推算出整个比特币网络的全网算力。所以比特币的工作量证明被通俗地称之为挖矿。在同一时间,所有矿工都在努力计算下一个区块的哈希。而挖矿难度取决于全网总算力的百分比。举个例子,假设小明拥有全网总算力的百分之一,那么他挖到下一个区块的可能性就是1%,或者说,每挖出100个区块,大约有1个就是小明挖的。
为什么区块可以安全广播?因为Merkle Hash锁定了该区块的所有交易,而该区块的第一个coinbase交易输出地址是该矿工地址。每个矿工在挖矿时产生的区块数据都是不同的,所以无法窃取别人的工作量。
比特币总量被限制为约2100万个比特币,初始挖矿奖励为每个区块50个比特币,以后每4年减半。
分叉
如果两个矿工在同一时间各自找到了有效区块,注意,这两个区块是不同的,因为coinbase交易不同,所以Merkle Hash不同,区块哈希也不同。但它们只要符合难度值,就都是有效的。这个时候,网络上的其他矿工应该接收哪个区块并添加到区块链的末尾呢?答案是,都有可能。
但是最终,总有一个分叉首先挖到后续区块,这个时候,由于比特币网络采用最长分叉的共识算法,绿色分叉胜出,蓝色分叉被废弃,整个网络上的所有矿工又会继续在最长的链上继续挖矿。
由于区块链虽然最终会保持数据一致,但是,一个交易可能被打包到一个后续被孤立的区块中。所以,要确认一个交易被永久记录到区块链中,需要对交易进行确认。如果后续的区块被追加到区块链上,实际上就会对原有的交易进行确认,因为链越长,修改的难度越大。一般来说,经过6个区块确认的交易几乎是不可能被修改的。
可编程支付原理
锁定脚本和解锁脚本
比特币采用脚本的方式进行可编程支付:通过执行解锁脚本确认某个UTXO的资产可以被私钥持有人转移给其他人。
比特币的支付实际上并不是直接支付到对方的地址,而是一个脚本,这个脚本的意思是:谁能够提供另外一个脚本,让这两个脚本能顺利执行通过,谁就能花掉这笔钱:
所以,比特币交易的输出是一个锁定脚本,而下一个交易的输入是一个解锁脚本。必须提供一个解锁脚本,让锁定脚本正确运行,那么该输入有效,就可以花费该输出。
当小明给小红支付一笔比特币时,实际上小明创建了一个锁定脚本,该锁定脚本中引入了小红的地址。要想通过解锁脚本花费该输出,只有持有对应私钥的小红才能创建正确的解锁脚本(因为解锁脚本包含的签名只有小红的私钥才能创建),因此,小红事实上拥有了花费该输出的权利。
使用钱包软件创建的交易都是标准的支付脚本,但是,比特币的交易本质是成功执行解锁脚本和锁定脚本,所以,可以编写各种符合条件的脚本。比如,有人创建了一个交易,它的锁定脚本像这样:
OP_HASH256
DATA 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000
OP_EQUAL
这有点像一个数学谜题。它的意思是说,谁能够提供一个数据,它的hash256等于6fe28c0a...,谁就可以花费这笔输出。所以,解锁脚本实际上只需要提供一个正确的数据,就可以花费这笔输出。点这里查看谁花费了该输出。
比特币的脚本通过不同的指令还可以实现更灵活的功能。例如,多重签名可以让一笔交易只有在多数人同意的情况下才能够进行。最常见的多重签名脚本可以提供3个签名,只要任意两个签名被验证成功,这笔交易就可以成功。
FROM: UTXO Hash#index
AMOUNT: 10.5 btc
TO: P2SH: OP_2 pk1 pk2 pk3 OP_3 OP_CHECKMULTISIG
也就是说,3个人中,只要任意两个人同意用他们的私钥提供签名,就可以完成交易。这种方式也可以一定程度上防止丢失私钥的风险。3个人中如果只有一个人丢失了私钥,仍然可以保证这笔输出是可以被花费的。
比特币的脚本不含条件判断、循环等复杂结构。上述脚本就是对输入的两个数据视作签名和公钥,然后先验证公钥哈希是否与地址相同,再根据公钥验证签名,这种标准脚本称之为P2PKH(Pay to Public Key Hash)脚本。
多重签名
由比特币的签名机制可知,如果丢失了私钥,没有任何办法可以花费对应地址的资金。
这样就使得因为丢失私钥导致资金丢失的风险会很高。为了避免一个私钥的丢失导致地址的资金丢失,比特币引入了多重签名机制,可以实现分散风险的功能。
具体来说,就是假设N个人分别持有N个私钥,只要其中M个人同意签名就可以动用某个“联合地址”的资金。
发送给多重签名地址的交易创建的是P2SH脚本,而花费多重签名地址的资金需要的脚本就是M个签名+Redeem Script。
注意:从多重签名的地址本身并无法得知该多重签名使用的公钥,以及M-N的具体数值。必须将Redeem Script公示给每个私钥持有人,才能够验证多重签名地址是否正确(即包含了所有人的公钥,以及正确的M-N数值)。要花费多重签名地址的资金,除了M个私钥签名外,必须要有Redeem Script(可由所有人的公钥构造)。只有签名,没有Redeem Script是不能构造出解锁脚本来花费资金的。因此,保存多重签名地址的钱包必须同时保存Redeem Script。
利用多重签名,可以实现:
- 1-2,两人只要有一人同意即可使用资金;
- 2-2,两人必须都同意才可使用资金;
- 2-3,3人必须至少两人同意才可使用资金;
- 4-7,7人中多数人同意才可使用资金。
最常见的多重签名是2-3类型。例如,一个提供在线钱包的服务,为了防止服务商盗取用户的资金,可以使用2-3类型的多重签名地址,服务商持有1个私钥,用户持有两个私钥,一个作为常规使用,一个作为应急使用。这样,正常情况下,用户只需使用常规私钥即可配合服务商完成正常交易,服务商因为只持有1个私钥,因此无法盗取用户资金。如果服务商倒闭或者被黑客攻击,用户可使用自己掌握的两个私钥转移资金。
大型机构的比特币通常都使用多重签名地址以保证安全。
利用多重签名,可以使得私钥丢失的风险被分散到N个人手中,并且,避免了少数人窃取资金的问题。
比特币的多重签名最多允许15个私钥参与签名,即可实现1-2至15-15的任意组合(1⩽M⩽N⩽15)。
多重签名可以实现N个人持有私钥,其中M个人同意即可花费资金的功能。
多重签名降低了单个私钥丢失的风险。
支付比特币到一个多重签名地址实际上是创建一个P2SH输出。
UTXO模型
比特币的区块链由一个个区块串联构成,而每个区块又包含一个或多个交易。
如果我们观察任何一个交易,它总是由若干个输入(Input)和若干个输出(Output)构成,一个Input指向的是前面区块的某个Output,只有Coinbase交易(矿工奖励的铸币交易)没有输入,只有凭空输出。所以,任何交易,总是可以由Input溯源到Coinbase交易。
这些交易的Input和Output总是可以串联起来:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│Block #1 │ │Block #2 │ │Block #3 │ │Block #4 │
│┌──┬────┬───┐│ │┌──┬────┬───┐│ │┌──┬────┬───┐│ │┌──┬────┬───┐│
││CB│50.0│OUT├┼──┐ ││CB│50.0│OUT├┼──┐ ││CB│50.0│OUT├┼──┐ ││CB│50.0│OUT││
│└──┴────┴───┘│ │ │└──┴────┴───┘│ │ │└──┴────┴───┘│ │ │└──┴────┴───┘│
│ │ │ │┌──┬────┬───┐│ │ │┌──┬────┬───┐│ │ │┌──┬────┬───┐│
│ │ │ ││ │8.70│OUT├┼──┼──▶│IN│ │ ││ └──▶│IN│25.0│OUT││
│ │ └──▶│IN├────┼───┤│ │ │├──┤58.7│OUT││ │├──┼────┼───┤│
│ │ ││ │41.3│OUT├┼─┐└──▶│IN│ │ ││ ┌──▶│IN│66.3│OUT││
│ │ │└──┴────┴───┘│ │ │└──┴────┴───┘│ │ │└──┴────┴───┘│
└─────────────┘ └─────────────┘ │ └─────────────┘ │ └─────────────┘
└────────────────────┘
还没有被下一个交易花费的Output被称为UTXO:Unspent TX Output,即未花费交易输出。给定任何一个区块,计算当前所有的UXTO金额之和,等同于自创世区块到给定区块的挖矿奖励之和。
因此,比特币的交易模型和我们平时使用的银行账号有所不同,它并没有账户这个说法,只有UTXO。想要确定某个人拥有的比特币,并无法通过某个账户查到,必须知道此人控制的所有UTXO金额之和。
在钱包程序中,钱包管理的是一组私钥,对应的是一组公钥和地址。钱包程序必须从创世区块开始扫描每一笔交易,如果:
- 遇到某笔交易的某个Output是钱包管理的地址之一,则钱包余额增加;
- 遇到某笔交易的某个Input是钱包管理的地址之一,则钱包余额减少。
钱包的当前余额总是钱包地址关联的所有UTXO金额之和。
如果刚装了一个新钱包,导入了一组私钥,在钱包扫描完整个比特币区块之前,是无法得知当前管理的地址余额的。
- 比特币区块链本质上是不可修改的“修改日志”(类似 MySQL 的 binlog),记录的是交易变更而非账户状态。
- 要高效查询余额,需先重放所有区块交易,构建本地“地址-余额”状态数据库,钱包多使用 LevelDB 存储该状态。 重建整个地址-余额数据库需要扫描整个区块链,并按每个交易依次更新记录,即可得到当前状态。
- 实时余额查询并不读取区块链原始数据,而是查本地已构建的状态表或由服务器返回查询结果。
HD钱包
HD是Hierarchical Deterministic的缩写,意思是分层确定性。先确定根私钥root,然后根据索引计算每一层的子私钥:
派生私钥
在比特币的链上,实际上并没有账户的概念,某个用户持有的比特币,实际上是其控制的一组UTXO,而这些UTXO可能是相同的地址(对应相同的私钥),也可能是不同的地址(对应不同的私钥)。
出于保护隐私的目的,同一用户如果控制的UTXO其地址都是不同的,那么很难从地址获知某个用户的比特币持币总额。但是,管理一组成千上万的地址,意味着管理成千上万的私钥,管理起来非常麻烦。
能不能只用一个私钥管理成千上万个地址?实际上是可以的。虽然椭圆曲线算法决定了一个私钥只能对应一个公钥,但是,可以通过某种确定性算法,先确定一个私钥k1,然后计算出k2、k3、k4……等其他私钥,就相当于只需要管理一个私钥,剩下的私钥可以按需计算出来。
这种根据某种确定性算法,只需要管理一个根私钥,即可实时计算所有“子私钥”的管理方式,称为HD钱包。
HD是Hierarchical Deterministic的缩写,意思是分层确定性。先确定根私钥root,然后根据索引计算每一层的子私钥:
root
│
├─────────────┬─────────────┐
│ │ │
▼ ▼ ▼
k0 k1 k2 ...
│ │ │
├──┬──┐ ├──┬──┐ ├──┬──┐
│ │ │ │ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
k0 k1 k2 ... k0 k1 k2 ... k0 k1 k2 ...
对于任意一个私钥k,总是可以根据索引计算它的下一层私钥kn
HD钱包采用的计算子私钥的算法并不是一个简单的SHA-256,私钥也不是普通的256位ECDSA私钥,而是一个扩展的512位私钥,记作xprv,它通过SHA-512算法配合ECC计算出子扩展私钥,仍然是512位。通过扩展私钥可计算出用于签名的私钥以及公钥。
简单来说,只要给定一个根扩展私钥(随机512位整数),即可计算其任意索引的子扩展私钥。扩展私钥总是能计算出扩展公钥,记作xpub
从xprv及其对应的xpub可计算出真正用于签名的私钥和公钥。之所以要设计这种算法,是因为扩展公钥xpub也有一个特点,那就是可以直接计算其子层级的扩展公钥
因为xpub只包含公钥,不包含私钥,因此,可以安全地把xpub交给第三方(例如,一个观察钱包),它可以根据xpub计算子层级的所有地址,然后在比特币的链上监控这些地址的余额,但因为没有私钥,所以只能看,不能花。
因此,HD钱包通过分层确定性算法,实现了以下功能:
- 只要确定了扩展私钥xprv,即可根据索引计算下一层的任何扩展私钥;
- 只要确定了扩展公钥xpub,即可根据索引计算下一层的任何扩展公钥;
- 用户只需保存顶层的一个扩展私钥,即可计算出任意一层的任意索引的扩展私钥。
从理论上说,扩展私钥的层数是没有限制的,每一层的数量被限制在0~232,原因是扩展私钥中只有4字节作为索引,因此索引范围是0~232。
通常把根扩展私钥记作m,子扩展私钥按层级记作m/x/y/z等:
m
│
├──────────────────────┐
│ │
▼ ▼
m/0 m/1 ...
│ │
├─────┬─────┐ ├─────┬─────┐
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
m/0/0 m/0/1 m/0/2 ... m/1/0 m/1/1 m/1/2 ...
例如,m/0/2表示从m扩展到m/0(索引为0)再扩展到m/0/2(索引为2)。
但是HD钱包的扩展私钥算法有个潜在的安全性问题,就是如果某个层级的xprv泄露了,可反向推导出上层的xprv,继而推导出整个HD扩展私钥体系。为了避免某个子扩展私钥的泄漏导致上层扩展私钥被反向推导,HD钱包还有一种硬化的衍生计算方式(Hardened Derivation),它通过算法“切断”了母扩展私钥和子扩展私钥的反向推导。HD规范把索引0~231作为普通衍生索引,而索引231~232作为硬化衍生索引,硬化衍生索引通常记作0’、1’、2’……,即索引0’=231,1’=231+1,2’=231+2,以此类推。
因此,m/44'/0表示的子扩展私钥,它的第一层衍生索引44'是硬化衍生,实际索引是231+44=2147483692。从m/44'无法反向推导出m。
在只有扩展公钥的情况下,只能计算出普通衍生的子公钥,无法计算出硬化衍生的子扩展公钥,即可计算出的子扩展公钥索引被限制在0~231。因此,观察钱包能使用的索引是0~231。
硬化派生 vs 普通派生(BIP-32)
| 项目 | 普通派生(Normal) | 硬化派生(Hardened) |
|---|---|---|
| 索引范围 | 0 ~ 2³¹ - 1 | 2³¹ ~ 2³² - 1(如 44’ 实为 2147483692) |
| 输入 | 父 公钥 + 子索引. m/0 ← 子索引 0 m/1 ← 子索引 1 m/2 ← 子索引 2 | 父 私钥 + 子索引 |
| 是否可逆 | ❌ 子私钥可推出父公钥,可能有风险 | ✅ 子私钥无法推出父密钥或公钥 |
| 适用场景 | 创建公钥链(如观察钱包) | 增强安全性,防止私钥泄露推导父密钥 |
| 安全性 | 较低(若子私钥泄露 + 父公钥 → 推导父私钥) | 高(子私钥无父密钥信息) |
| 常用路径 | 如 m/0/1 | 如 m/44'/0'/0' |
普通派生:
ChildPubKey = f(ParentPubKey, index)
→ 可生成“观察钱包”
硬化派生:
ChildPrivKey = f(ParentPrivKey, index + 2³¹)
→ 只靠私钥能派生,防止反向推导
派生标准
比特币的BIP-44规范定义了一种如何派生私钥的标准,它本身非常简单:
m / purpose' / coin_type' / account' / change / address_index
其中,purpose总是44,coin_type在SLIP-44中定义,例如,0=BTC,2=LTC,60=ETH等。account表示用户的某个“账户”,由用户自定义索引,change=0表示外部交易,change=1表示内部交易,address_index则是真正派生的索引为0~231的地址。
例如,某个比特币钱包给用户创建的一组HD地址实际上是:
- m/44’/0’/0’/0/0
- m/44’/0’/0’/0/1
- m/44’/0’/0’/0/2
- m/44’/0’/0’/0/3
- …
如果是莱特币钱包,则用户的HD地址是:
- m/44’/2’/0’/0/0
- m/44’/2’/0’/0/1
- m/44’/2’/0’/0/2
- m/44’/2’/0’/0/3
- …
助记词
BIP-39规范通过使用助记词+口令来生成HD钱包的种子,用户只需记忆助记词和口令即可随时恢复HD钱包。
丢失助记词或者丢失口令均会导致HD钱包丢失。
助记词是一组人类可读的单词,用于表示一个随机数种子,能恢复整个 HD 钱包的所有私钥、公钥和地址。
eg: legal winner thank year wave sausage worth useful legal winner thank yellow
常见长度有:12 / 15 / 18 / 21 / 24 个单词
种子和助记词的关系
在 BIP-39 标准中:
- 助记词(mnemonic words) 是 12/15/18/24 个词的人类可读表示
- 助记词 → 通过 PBKDF2 → 派生出 512 位的种子
- 种子 → 用于生成 HD 钱包的根密钥(m)
助记词词组(24词)
→ PBKDF2 加盐派生
→ 生成种子(512位)
→ BIP-32 生成根私钥 m
→ 派生整个 HD 钱包树
地址监控
一个HD钱包管理的是一组自动计算的地址。以比特币为例,在确定了根扩展私钥m后,得到一组地址为m/44'/0'/0'/0/x,其中x=0~231。
HD钱包需要在链上监控每个TX的输入和输出,看看上述管理的一组地址是否存在与输入和输出中。如果作为输入,则钱包余额减少,如果作为输出,则钱包余额增加。
现在问题来了:如何根据TX的输入和输出地址快速判断这些地址中是否存在HD钱包管理的地址?
首先,可用的地址高达231个,这个数太大了,用户不可能用完,因此,HD钱包只会预生成前1000个地址(即索引号为0~999)并保存在本地数据库中,如果不够了,再继续扩展1000个,这样,HD钱包管理的地址数量不会太大。
要做到高效的查询和低空间占用率,可以使用布隆过滤器(Bloom Filter),它是由Burton Howard Bloom在1970年提出的,其原理是将每个元素通过若干个哈希函数映射成一个位数组的若干个点,将其置1。检索的时候,先计算给定元素对应位是否全1,如果是全1,则给定元素很可能存在,否则,元素必定不存在。
因此,Bloom Filter有个重要特点,就是判断元素时,如果不存在,那么肯定不存在,如果存在,实际上是以一定概率存在(例如,99%),还需要再次从数据库查询以确定元素真的存在。