0%

btc | UXTO 结构

这是 BTC 解析中,必须要知道的结构,可以说,没有什么 BTC 有的只是 UTXO


参考资料



UTXO


比特币的区块链由一个个区块串联构成,而每个区块又包含一个或多个交易。

如果我们观察任何一个交易,它总是由若干个输入(Input)和若干个输出(Output)构成,一个Input指向的是前面区块的某个Output,只有Coinbase交易(矿工奖励的铸币交易)没有输入,只有凭空输出。所以,任何交易,总是可以由Input溯源到Coinbase交易。

这些交易的InputOutput总是可以串联起来:

┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│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 实际上是一个引用。在计算交易时,是通过 prevTxIdoutputIndex 属性,找到上次交易的 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

在区块链浏览器中表现如下

这个转账表明,当时一个块可以挖出 50BTC

单地址输出

{'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.1BTC 是输出地址哪一个给的,比如 15PDV2PzPcjNu7QTzdeoXK3zdmedzWp9g6 给了 3 ,然后另一个给了 2.1 还是什么情况,我们是得不到的。

找零地址

UTXO 记录的是一笔笔转账记录,所以,不是我们想象中的存储功能。

比如 A 地址有 5BTC ,它向另外一个 地址转 3BTC ,并不是,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 的染色币转账。这个,我在博客中有详细提到过,可以参考。

请我喝杯咖啡吧~