BTC
自带的协议是不支持解析 USDT
,而 USDT
在 BTC
的网络中,又叫染色币,这篇博文,主要是说明如何在 BTC
网络上解析 USDT
转账记录。
参考资料
染色币
ETH
因为智能合约系统而受到了广泛地关注,大家可以用 5
分钟时间就发出自己的第一个代币,而代币的发行、转账则都依赖于智能合约,底层则是由 ETH
公链承载。事实上,远在 ETH
诞生之前,大家就想用比特币主链做点啥。
其中,一个重要的概念被提出来了:染色币。具体来说,染色币是指在普通的比特币交易中附上一些信息,借助比特币底层基础设施来记录。然而,比特币官方开发组(Core
)对这种方式颇有争议,将用于存储信息的 OP_RETURN
字段从 80
字节骤然缩小到了 40
字节 。
Omni Layer(原名 Mastercoin)
Omni Layer
也属于染色币,其核心思想是将 Omni Protocol
层的数据用某种方式写入比特币区块链中,目前在协议定义中有三种,Class A
、Class B
和 Class C
。
Class A
- 这种方法是利用了比特币每个地址本质上都是大小为20 bytes
的二进制字符串。先将数据分割成20 bytes
为一组的数据块,然后使用Base58
编码作为目标地址发送即可。当然这些被发送的UTXO
将永远丢失。使用这项技术的还有cryptograffiti
。Class B
- 这种方法利用了比特币的multisig
,即多签特性。发送一笔1 of n
的多重签名交易(即,n
个地址中,任何一个地址签名即可花费这笔UTXO
)。当前版本的Omni Layer
协议中最大支持n = 3
。Class C
- 这种方法则利用了OP_RETURN
操作码储存数据,目前来看,基本上所有的Omni layer
层的交易都采用了这种方式。使用这项技术的还有CoinSpark
。
Omni Protocol
能让用户发行自己的数字资产,其中资产编号为31
的就是被广泛使用的 USDT
。而日常中,发送 USDT
使用的交易类型是 Simple Send
。
下面是对 Simple Send
交易的定义。
举一个经典的 USDT
在 BTC
链上的 OP_RETURN
数据。
OP_RETURN 6f6d6e69000000000000001f00000002553fa2b8
符号 | 位置 | 含义 |
---|---|---|
6f6d6e69 | [10:18] | omni 对应的 ASCII 编码,因为交易备注与 omni 协议有关 |
0000 | [18:22] | 交易版本 |
0000 | [22:26] | 交易类型,代表 简单发送 |
0000001f | [26:34] | 31,代表 USDT |
00000002553fa2b8 | [34:] | USDT 转账金额,1000 00000000 最小单位 = 1000 USDT,16进制 |
Simple Send
是一项将 Omni Layer
层中特定的数字资产从原地址转移到目标地址的操作,注意原地址和目标地址均使用比特币的地址。
可以看到上面的交易并没有任何的余额信息,这就是说,原地址的 Omni Layer
层数字资产,由 Omni Core
自己维护一个额外的账本并校验。
换句话说,所谓的染色币只不过是借住了比特币的脚本功能,Omni 协议就是利用该功能在比特币区块中嵌入 Omni
协议的数据。通过 OP_RETURN
操作码区分。比特币网络并不会解析 Omni
协议对应的 OP_RETURN
数据段,对其来说只是‘备注信息’,由 Omni
所在的二级网络解析对应的交易信息。
假如说,用户 A
想要通过 Omni 协议
,转给用户 B
一定数额的 USDT
。用户 A
需要给用户 B
转 BTC
,然后,在这个转账块中,在备注上写下关于 USDT
的转账信息。所以,USDT
的转账是依附于 BTC
的,矿工费之类的也是 USDT
,因为,这笔转账的本质还是 BTC 转账
。所以,Omni 协议
下,用户的 BTC 地址
和 USDT
是相同的。
业务需求
解析 BTC
链上的 USDT
转账。
事实上正确的思路应该是根据协议的规范解析,但是,相关资料太少了,我暂时没有找到,所以,这里我用的是经验逻辑,也就是,我尽可能找了大量不同类型的块数据,找出具有普适性的逻辑,但是,这个逻辑有一个致命的缺点,就是其并没有分析全部的数据。以后,我会从协议的角度来解析数据。
转账结构
这里展示几个比较经典的数据结构,其可以在 usdt 浏览器
和 btc 浏览器
中,找到。
eb6b2ecb5056114e3da28de2f6a2e281891cff1cf5db91f33a9cbefcbb21e149
就贴着一个的数据块。
这个是非常经典的转账记录。先看一下浏览器截图。
usdt
按照我目前的经验逻辑,usdt
转账,每笔只有一个 from_address
和 一个 to_address
。
btc
这个就是标准的 btc
转账地址,里面包含的信息也就是基本 UTXO
,你可以先看我下面的博文,了解 UTXO
。
另外,看上图,地址解析失败,是因为该地址是 usdt
的标识地址,我自己取的。。。总之,那一个地址,在 btc
中是无用地址,而 usdt
的转账记录,就放在那个地址的备注信息中。
想要解析 usdt
的转账记录,需要解决下面的问题。
- 解析
usdt
的from_address
和to_address
- 解析转账金额
我们从数据块中入手。
返回的数据块
{'type': 'transaction', 'hash': 'eb6b2ecb5056114e3da28de2f6a2e281891cff1cf5db91f33a9cbefcbb21e149', 'size': 694, 'virtual_size': 694, 'version': 1, 'lock_time': 0, 'block_number': 497113, 'block_hash': '000000000000000000694c4a23986f09d70cccf4f316e25254f4d1732030b17c', 'block_timestamp': 1512172332, 'is_coinbase': False, 'index': 521, 'inputs': [{'index': 0, 'spent_transaction_hash': 'f6d6a4f82190fe9d0e6606417bb7f28e9455779b9f91d8714ef7f1f568eb67b4', 'spent_output_index': 2, 'script_asm': '0 3045022100b5470538da4374b16356d05b277bb21c8ef031f469667bdc1dd322d685bf6f870220253100900c5a5e2ca3ed8c90b5c4de7402746c7b6bb8c4578c6f8dc52c9a7145[ALL] 3044022058156d06a6f6cbffc299c22b425e3b7695d331bc9c7e660b842d09ea45530d44022036fcd61dfbdfc5f844989c417f75b1f2eb4493800208896d6788589aa71cdff9[ALL] 5221030050e567a4d4e36fea2c3e7f229f181283be92653c02af8acdb305d7bf20197b2102f4aef4731a9caf7a287ddaea6968c7a03ab3b276edc66f1a26e3bf764641a0eb2102e0dd9380c00d58af4ca2e3fe5f1fcefc8de234ec711a9f4e66a9bb9448a8c33953ae', 'script_hex': '00483045022100b5470538da4374b16356d05b277bb21c8ef031f469667bdc1dd322d685bf6f870220253100900c5a5e2ca3ed8c90b5c4de7402746c7b6bb8c4578c6f8dc52c9a714501473044022058156d06a6f6cbffc299c22b425e3b7695d331bc9c7e660b842d09ea45530d44022036fcd61dfbdfc5f844989c417f75b1f2eb4493800208896d6788589aa71cdff9014c695221030050e567a4d4e36fea2c3e7f229f181283be92653c02af8acdb305d7bf20197b2102f4aef4731a9caf7a287ddaea6968c7a03ab3b276edc66f1a26e3bf764641a0eb2102e0dd9380c00d58af4ca2e3fe5f1fcefc8de234ec711a9f4e66a9bb9448a8c33953ae', 'sequence': 4294967295, 'required_signatures': 1, 'type': 'scripthash', 'addresses': ['3Mqo31DSxsBpZj7MRNJGyaWc4jArjhMeR6'], 'value': 540}, {'index': 1, 'spent_transaction_hash': 'af81bda6612ff9bf24bbce60b067916ff5e478d9721e089870371d9602e29cac', 'spent_output_index': 0, 'script_asm': '0 30440220427092195d8dcca30eb985af4a4c2241a6d2170f8b2c9defd96de8f609c6e75002203c9354118451bdf8588aa1626d846323878a07be5d4612fce215a2407453910c[ALL] 3044022030e60d45f49af0282f5810544b6575be9953b74c4f7c2610a632eb9c0b8b723e02203da4726f72881f0347d90046433958ed6d33abd42ea7017032f96488a3362bb7[ALL] 5221035c037a36e4ec9aa1aefcef02ff742bec63c098c0a7f1c9a1b822b6294afbb7102102a1ccf15bc58870240578a2922b3f0e922260f37098adbd988e15be36943816912102af3be4803e51fef309a51f0332f399026e99dae117dc2d22a71f4eacc1f3f4f153ae', 'script_hex': '004730440220427092195d8dcca30eb985af4a4c2241a6d2170f8b2c9defd96de8f609c6e75002203c9354118451bdf8588aa1626d846323878a07be5d4612fce215a2407453910c01473044022030e60d45f49af0282f5810544b6575be9953b74c4f7c2610a632eb9c0b8b723e02203da4726f72881f0347d90046433958ed6d33abd42ea7017032f96488a3362bb7014c695221035c037a36e4ec9aa1aefcef02ff742bec63c098c0a7f1c9a1b822b6294afbb7102102a1ccf15bc58870240578a2922b3f0e922260f37098adbd988e15be36943816912102af3be4803e51fef309a51f0332f399026e99dae117dc2d22a71f4eacc1f3f4f153ae', 'sequence': 4294967295, 'required_signatures': 1, 'type': 'scripthash', 'addresses': ['3GyeFJmQynJWd8DeACm4cdEnZcckAtrfcN'], 'value': 9158882}], 'outputs': [{'index': 0, 'script_asm': 'OP_HASH160 a7aedd0d1e77300fb06aa154c9a517b74c08d245 OP_EQUAL', 'script_hex': 'a914a7aedd0d1e77300fb06aa154c9a517b74c08d24587', 'required_signatures': 1, 'type': 'scripthash', 'addresses': ['3GyeFJmQynJWd8DeACm4cdEnZcckAtrfcN'], 'value': 9068519}, {'index': 1, 'script_asm': 'OP_HASH160 a7aedd0d1e77300fb06aa154c9a517b74c08d245 OP_EQUAL', 'script_hex': 'a914a7aedd0d1e77300fb06aa154c9a517b74c08d24587', 'required_signatures': 1, 'type': 'scripthash', 'addresses': ['3GyeFJmQynJWd8DeACm4cdEnZcckAtrfcN'], 'value': 2730}, {'index': 2, 'script_asm': 'OP_RETURN 6f6d6e69000000000000001f00000032d518a03b', 'script_hex': '6a146f6d6e69000000000000001f00000032d518a03b', 'required_signatures': None, 'type': 'nonstandard', 'addresses': ['nonstandard1344ece84dae066bba791ecc885453455932e1fe'], 'value': 0}], 'input_count': 2, 'output_count': 3, 'input_value': 9159422, 'output_value': 9071249, 'fee': 88173, 'item_id': 'transaction_eb6b2ecb5056114e3da28de2f6a2e281891cff1cf5db91f33a9cbefcbb21e149'}
在这里我们看一下 inputs
,去掉多余的信息后。
'inputs': [
{'index': 0, 'addresses': ['3Mqo31DSxsBpZj7MRNJGyaWc4jArjhMeR6'], 'value': 540},
{'index': 1, 'addresses': ['3GyeFJmQynJWd8DeACm4cdEnZcckAtrfcN'], 'value': 9158882}]
outputs
去掉多余的信息后
'outputs': [
{'index': 0, 'addresses': ['3GyeFJmQynJWd8DeACm4cdEnZcckAtrfcN'], 'value': 9068519},
{'index': 1, 'addresses': ['3GyeFJmQynJWd8DeACm4cdEnZcckAtrfcN'], 'value': 2730},
{'index': 2, 'script_asm': 'OP_RETURN 6f6d6e69000000000000001f00000032d518a03b', 'addresses': ['nonstandard1344ece84dae066bba791ecc885453455932e1fe'], 'value': 0}],
上面的规律自己找一下,我们继续看其它的转账数据。
fa1bcd8ea077414608290d9e3c39b4f33a83deaaba35409c1c54710b75d85bf4
usdt
btc
e8cb9f108f6374c9686a828c6f0dc5ab74ffb6dbd9b2460c124563be17ce9e01
usdt
btc
870ccc55f0a4106cb7993acca535e9150ba7be19bbe24c7e8a0cb9264412ff2c
这个地址自己去浏览器查看吧,第一次看的时候,差点把我惊呆了。
c248c88817318078a224d8f6b5b3af807f465f92c267b37f079af24fc194bef5
这个的转账记录也是特殊表现。
从上面那些例子,还有他们返回的数据块,我找到了下面的特点。
- 无法从数据块中,直接获取
usdt
的from
和to
- 包含
usdt
的转账的那个地址,在outputs
中的位置并不固定
解析结构
直接说我找到的结论。
inputs
的第一个item
的地址,是usdt
的from
outputs
中的那些地址,减去包含信息的那个地址,还有减去找零地址,剩下的outputs
的-1
位置是usdt
的to
代码编写
- 先区分该笔交易中有没有 usdt 转账
- 按照上面的逻辑进行解析
这里只是贴一下大概的思路。
1 | def get_usdt_from_and_to(self, item): |
经过长达几个星期的校验,上述代码解析的数据可以和浏览器对的上。
祝好运!