这是 BTC
解析中,必须要知道的结构,可以说,没有什么 BTC
有的只是 UTXO
。
参考资料
- UTXO模型
- lockchain 比特币区块浏览器使用指南
- Bitcoin Transactions
- 比特币旷工费最全攻略
- 比特币BTC钱包科普——UTXO、OP_RETURN、隔离见证、找零地址等
- 一个比特币交易的完整流程
- 比特币交易
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
金额之和。
如果刚装了一个新钱包,导入了一组私钥,在钱包扫描完整个比特币区块之前,是无法得知当前管理的地址余额的。
那么,给定一个地址,要查询该地址的余额,难道要从头扫描几百GB的区块链数据?
当然不是。
要做到瞬时查询,我们知道,使用关系数据库的主键进行查询,由于用了索引,速度极快。
因此,对区块链进行查询之前,首先要扫描整个区块链,重建一个类似关系数据库的地址-余额映射表。这个表的结构如下:
address | balance | lastUpdatedAtBlock |
---|---|---|
address-1 | 50.0 | 0 |
一开始,这是一个空表。每当扫描一个区块的所有交易后,某些地址的余额增加,另一些地址的余额减少,两者之差恰好为区块奖励:
address | balance | lastUpdatedAtBlock |
---|---|---|
address-1 | 50.0 | 0 |
address-2 | 40.0 | 3 |
address-3 | 50.0 | 3 |
address-4 | 10.0 | 3 |
这样,扫描完所有区块后,我们就得到了整个区块链所有地址的完整余额记录,查询的时候,并不是从区块链查询,而是从本地数据库查询。大多数钱包程序使用LevelDB
来存储这些信息,手机钱包程序则是请求服务器,由服务器查询数据库后返回结果。
如果我们把MySQL
这样的数据库看作可修改的,那么区块链就是不可修改,只能追加的只读数据库。但是,MySQL
这样的数据库虽然其状态是可修改的,但它的状态改变却是由修改语句(INSERT/UPDATE/DELETE
)引起的。把MySQL的binlog
日志完整地记录下来,再进行重放,即可在另一台机器上完整地重建整个数据库。把区块链看作不可修改的binlog
日志,我们只要把每个区块的所有交易重放一遍,即可重建一个地址-余额的数据库。
可见,比特币的区块链记录的是修改日志,而不是当前状态。
UTXO 内部表现
我定义一下相关属性的意思以及相关概念。
satoshi: 1 BTC = 100,000,000 satoshi
transaction 的一般格式
名称 | 描述 |
---|---|
version | 比特币系统的版本号 |
hash | 本次交易的 hash 值 |
inputs 由 input 组成的数组 | |
outputs | 由 output 组成的数组 |
lockTime | 值为 0,立刻执行交易;值不为 0,在指定区块高度或时间戳执行交易。 |
output 的数据格式
名称 | 描述 |
---|---|
value | 电子货币的价值,单位 BTC |
scriptPubKey | 通常是收款人公钥等组成的锁定脚本 |
input 的数据格式
名称 | 描述 |
---|---|
prevTxId | 上一笔交易的 hash 值 |
outputIndex | 上一笔交易 outputs 的 index |
scriptSig | 通常由付款人的数字签名和收款人的公钥等组成的解锁脚本。 |
注意,input
实际上是一个引用。在计算交易时,是通过 prevTxId
和 outputIndex
属性,找到上次交易的 output
作为本次交易实际的 input
。
其实,得到的数据和区块链浏览器一对照,就知道代表啥意思了。
这一节,我们将介绍一下经典的几种 UTXO
的转账记录。
其中,数据的输出是通过 ethereum-etl
这个项目获取的。
出块转账
所谓的出块,即挖出 btc
的那个地址。
{'type': 'transaction', 'hash': 'c9dd32e63097ce23afebca154626d06f9fedfc32d5e3194c2718b45a8d0e40cb', 'size': 135, 'virtual_size': 135, 'version': 1, 'lock_time': 0, 'block_number': 55759, 'block_hash': '000000000490bca425e54c83c25eb65cc969d79e7c82b979a27bc72ce5e67140', 'block_timestamp': 1273820818, 'is_coinbase': True, 'index': 0,
'inputs': [],
'outputs': [
{'index': 0, 'script_asm': '04ed8dfac4d44eb81d5e4a372069ae5ed5a0766dfde1e93ffe43f0c142c2c7867227122a958884aeacca32ee12bf545633f8a15e5879ea48fab9c48f3cdf554f0d OP_CHECKSIG', 'script_hex': '4104ed8dfac4d44eb81d5e4a372069ae5ed5a0766dfde1e93ffe43f0c142c2c7867227122a958884aeacca32ee12bf545633f8a15e5879ea48fab9c48f3cdf554f0dac', 'required_signatures': 1, 'type': 'pubkey', 'addresses': ['181LX8WiERRNuYeaDkwcCV1TZ61reUF78k'], 'value': 5000000000}],
'input_count': 0, 'output_count': 1, 'input_value': 0, 'output_value': 5000000000, 'fee': 0, 'item_id': 'transaction_c9dd32e63097ce23afebca154626d06f9fedfc32d5e3194c2718b45a8d0e40cb'}
这个就是出块的转账,可以看出,出块的转账有以下特征
inputs
为空is_coinbase
等于true
在区块链浏览器中表现如下
这个转账表明,当时一个块可以挖出 50
个 BTC
。
单地址输出
{'type': 'transaction', 'hash': 'b109d535243b62f3b6f6f24a3d69e4f7aec568e51d4c38d3a92fb8549fce1613', 'size': 257, 'virtual_size': 257, 'version': 1, 'lock_time': 0, 'block_number': 65761, 'block_hash': '000000000090f8705e6837fa7dc8d945b7f17329794eabc7a395e594441b81a8', 'block_timestamp': 1278931908, 'is_coinbase': False, 'index': 2,
'inputs': [
{'index': 0, 'spent_transaction_hash': '177d244a7748ecf01fed9e0b9fa2cf1b4e60b15ef4839e3f39ba12097d9d5a5a', 'spent_output_index': 1, 'script_asm': '3044022008c6d2f4e26535e86bef03c72bcb93fb11b20e37c7e30f84245827211fde786902204c63f2b1249f4d9866dd9b43c6aadd2c496dcab73c26b87d9d01e21e2215da7b[ALL] 04c3fad982442c5b247a6003a95daecfef57f0fa6bcd145989a49afa08bec8a374aeedbfd4c9914d9e55d1c12cf271d8ed1bafdb9b1440d62de92c9eedef2bb92d', 'script_hex': '473044022008c6d2f4e26535e86bef03c72bcb93fb11b20e37c7e30f84245827211fde786902204c63f2b1249f4d9866dd9b43c6aadd2c496dcab73c26b87d9d01e21e2215da7b014104c3fad982442c5b247a6003a95daecfef57f0fa6bcd145989a49afa08bec8a374aeedbfd4c9914d9e55d1c12cf271d8ed1bafdb9b1440d62de92c9eedef2bb92d', 'sequence': 4294967295, 'required_signatures': 1, 'type': 'pubkeyhash', 'addresses': ['1JCz1ii3T6ZNtZ6wRgiUG3ViPkDMSqMu8k'], 'value': 335500000000}],
'outputs': [
{'index': 0, 'script_asm': 'OP_DUP OP_HASH160 e18d250777d4535db888b2836449994854a5c4dc OP_EQUALVERIFY OP_CHECKSIG', 'script_hex': '76a914e18d250777d4535db888b2836449994854a5c4dc88ac', 'required_signatures': 1, 'type': 'pubkeyhash', 'addresses': ['1MZc84hMV1ZCCRgymfc7NgGLvzDbnssZeQ'], 'value': 500000000},
{'index': 1, 'script_asm': 'OP_DUP OP_HASH160 d43124453dfca2756c0a69e280dd52b63467ac12 OP_EQUALVERIFY OP_CHECKSIG', 'script_hex': '76a914d43124453dfca2756c0a69e280dd52b63467ac1288ac', 'required_signatures': 1, 'type': 'pubkeyhash', 'addresses': ['1LLy86LHzNWbpLdygPJWaH1h1F8vzXgmiz'], 'value': 335000000000}],
'input_count': 1, 'output_count': 2, 'input_value': 335500000000, 'output_value': 335500000000, 'fee': 0, 'item_id': 'transaction_b109d535243b62f3b6f6f24a3d69e4f7aec568e51d4c38d3a92fb8549fce1613'}
这个是一个地址向两个地址进行转账,在区块链浏览器中,是这样表现的。
这个是一个地址向多个地址进行转账。
多地址输出
{'type': 'transaction', 'hash': 'd572c0293c4ec822a66c281aba5237d626c0f0a027980a9215f54ea0081dd609', 'size': 439, 'virtual_size': 439, 'version': 1, 'lock_time': 0, 'block_number': 68769, 'block_hash': '00000000002d012f512429583a72d10b8c151d79891177aa698467176f5bf9ce', 'block_timestamp': 1279406310, 'is_coinbase': False, 'index': 6,
'inputs': [
{'index': 0, 'spent_transaction_hash': '1f0aa99bd5571491be66bd93036c6ac8f8c8376fa0d3ed1dfaff3d34d09cbcd8', 'spent_output_index': 0, 'script_asm': '3046022100980430c6e49187b920bb782383010608f97b183a33f976dd3d88a877b0657f1f02210099e65a0894d0d414af093d0fb4b3e46ffb47b3b62f0b2db6fef18d26d8b2f21b[ALL] 04f07feec270ad15d698ce3e6d7a337a60bca4ee5a89e242717b322f65913b42137227ac80ce923ac3d41b6365067a4df671be43afe215bd94d4c277898e0c8a31', 'script_hex': '493046022100980430c6e49187b920bb782383010608f97b183a33f976dd3d88a877b0657f1f02210099e65a0894d0d414af093d0fb4b3e46ffb47b3b62f0b2db6fef18d26d8b2f21b014104f07feec270ad15d698ce3e6d7a337a60bca4ee5a89e242717b322f65913b42137227ac80ce923ac3d41b6365067a4df671be43afe215bd94d4c277898e0c8a31', 'sequence': 4294967295, 'required_signatures': 1, 'type': 'pubkeyhash', 'addresses': ['15PDV2PzPcjNu7QTzdeoXK3zdmedzWp9g6'], 'value': 500000000},
{'index': 1, 'spent_transaction_hash': '1f0aa99bd5571491be66bd93036c6ac8f8c8376fa0d3ed1dfaff3d34d09cbcd8', 'spent_output_index': 1, 'script_asm': '3045022100a0329d5e11729d7e5d39022081b63a8b1fb0e42fdffe05d4d2d8a68898947582022055d2367ac28b240b7970e2557d1fd219b06a0dbcfbd7575410883743a1872f68[ALL] 045dc767c57885a1e01288b120a4f6b8bf56832d443d49fab411a85d060854d5ea4838f7959f6112669b940ca6e311b77a4e9f2e90f8b913c297ffe74a12fac0ff', 'script_hex': '483045022100a0329d5e11729d7e5d39022081b63a8b1fb0e42fdffe05d4d2d8a68898947582022055d2367ac28b240b7970e2557d1fd219b06a0dbcfbd7575410883743a1872f680141045dc767c57885a1e01288b120a4f6b8bf56832d443d49fab411a85d060854d5ea4838f7959f6112669b940ca6e311b77a4e9f2e90f8b913c297ffe74a12fac0ff', 'sequence': 4294967295, 'required_signatures': 1, 'type': 'pubkeyhash', 'addresses': ['19JS6jEvpr74VGHxQgWW59pf6uCAKLhDTP'], 'value': 20005000000}],
'outputs': [
{'index': 0, 'script_asm': 'OP_DUP OP_HASH160 ff006e2dcf7f00dc36333f20199aaf3fdc810945 OP_EQUALVERIFY OP_CHECKSIG', 'script_hex': '76a914ff006e2dcf7f00dc36333f20199aaf3fdc81094588ac', 'required_signatures': 1, 'type': 'pubkeyhash', 'addresses': ['1QFKppbHTaH7RVK7aSivXEvuYyN2hi9iQL'], 'value': 510000000},
{'index': 1, 'script_asm': 'OP_DUP OP_HASH160 826c40c74590359d11c20c053b70f160f6362ce6 OP_EQUALVERIFY OP_CHECKSIG', 'script_hex': '76a914826c40c74590359d11c20c053b70f160f6362ce688ac', 'required_signatures': 1, 'type': 'pubkeyhash', 'addresses': ['1CtcXVUzex7LEZuM1z1TYkopxvKt6xfdU6'], 'value': 19995000000}],
'input_count': 2, 'output_count': 2, 'input_value': 20505000000, 'output_value': 20505000000, 'fee': 0, 'item_id': 'transaction_d572c0293c4ec822a66c281aba5237d626c0f0a027980a9215f54ea0081dd609'}
在区块链浏览器的表现是这样的
我们只能看出哪个地址输出,哪个地址接受,但是,我们得不到是什么地址给某一个具体的地址打了多少钱。
如上图,我们不知道 1QFKppbHTaH7RVK7aSivXEvuYyN2hi9iQL
接收的 5.1
个 BTC
是输出地址哪一个给的,比如 15PDV2PzPcjNu7QTzdeoXK3zdmedzWp9g6
给了 3
,然后另一个给了 2.1
还是什么情况,我们是得不到的。
找零地址
UTXO
记录的是一笔笔转账记录,所以,不是我们想象中的存储功能。
比如 A
地址有 5
个 BTC
,它向另外一个 地址转 3
个 BTC
,并不是,A
转了 3
个,还剩 2
个,而是,A
转了 3
个给另外地址,转了 2
个给自己。
如下面数据
{'type': 'transaction', 'hash': '6af56dcab10fe62c1566af74df1721ce093b9ff2a386b0d2674b445418a9e9e7', 'size': 226, 'virtual_size': 226, 'version': 1, 'lock_time': 0, 'block_number': 319785, 'block_hash': '00000000000000000235577372e7ffb7d2b63f90ca460e74bed14ceac9570021', 'block_timestamp': 1410232545, 'is_coinbase': False, 'index': 1,
'inputs': [
{'index': 0, 'spent_transaction_hash': '3eb76238445fcab02d738c6fabadb79fb9deeb05d2fbcbadeb171cee553121fa', 'spent_output_index': 1, 'script_asm': '3045022100857e8e344301dfbc8deeb3cd75ec8deedab7121915537dd57e91280fdef5762b022048606a98217e9c5192340a41ac1fdb5dea7038b392b8460b6143374985c21ed4[ALL] 02744c29c47bc9eb482a898ec4af0f90f5db02a4f47561064204a5f49b3872889a', 'script_hex': '483045022100857e8e344301dfbc8deeb3cd75ec8deedab7121915537dd57e91280fdef5762b022048606a98217e9c5192340a41ac1fdb5dea7038b392b8460b6143374985c21ed4012102744c29c47bc9eb482a898ec4af0f90f5db02a4f47561064204a5f49b3872889a', 'sequence': 4294967295, 'required_signatures': 1, 'type': 'pubkeyhash', 'addresses': ['146UrBHfkDUfFD3c4BC4AXgBxQkLUzHJri'], 'value': 7351990000}],
'outputs': [
{'index': 0, 'script_asm': 'OP_DUP OP_HASH160 c58c4c619a480e19e84a7bb7bd67322e431cb2c1 OP_EQUALVERIFY OP_CHECKSIG', 'script_hex': '76a914c58c4c619a480e19e84a7bb7bd67322e431cb2c188ac', 'required_signatures': 1, 'type': 'pubkeyhash', 'addresses': ['1K1YBtj9ejPY8XonRMZ9FbFCYjfrMx59kb'], 'value': 100000000},
{'index': 1, 'script_asm': 'OP_DUP OP_HASH160 21f1b412523e7eb07ff11b915b3b62a936b3a28f OP_EQUALVERIFY OP_CHECKSIG', 'script_hex': '76a91421f1b412523e7eb07ff11b915b3b62a936b3a28f88ac', 'required_signatures': 1, 'type': 'pubkeyhash', 'addresses': ['146UrBHfkDUfFD3c4BC4AXgBxQkLUzHJri'], 'value': 7251980000}],
'input_count': 1, 'output_count': 2, 'input_value': 7351990000, 'output_value': 7351980000, 'fee': 10000, 'item_id': 'transaction_6af56dcab10fe62c1566af74df1721ce093b9ff2a386b0d2674b445418a9e9e7'}
区块链浏览器的图如下
染色币转账
比较出名的是 usdt 的染色币转账。这个,我在博客中有详细提到过,可以参考。