0%

解析比特币区块.dat文件,获得签名r以及其相关信息,并找出相同的r及恢复私钥

写在前面

先在前面说一下,我没有完全实现整个过程,因为有一个地方确实实现不了,等以后有条件再使用别的方法尝试一下。前一段时间看了一篇14年的论文,里面主要说的是比特币中随机数相同会造成私钥泄漏的问题,作者分析了当下存在很多因私钥泄漏,比特币频频被盗的现象,然后找到了问题所在,并且使用Blockchainr工具将其实现了,找出了比特币网络中存在的使用相同随机数的用户私钥。

目的

处于自身学习的角度,我要自己完成作者所提到的步骤,最终找到相同的随机数并且恢复私钥。

整体思路

先编写解析区块文件的程序,将后缀为.dat的区块文件解析出来,然后将数据存储到数据库,之后再借助14年那篇PPT的思想,利用作者提到的dabloom过滤器去找到相同的签名r,这时我们不能仅仅导出签名r,还要导出相关的信息,这样我们才可以知道是否来源于同一用户,才能去计算用户私钥。

具体过程

目前我可以实现解析区块并导出到数据库中,把.dat文件以二进制形式读取,编写区块结构并给出区块中各个字段的长度,以及通过已知字段的值,去读取某些未知长度字段的数据,如:解锁脚本可以通过脚本长度来读取,还有我们可以通过下图这样的思想去解析区块:
idear
从block入手,里面包含blockheader、tx因为一个区块有多条交易,我们定义tx为一个集合Txs,Txs中的每条交易又包含input、output当然它们也会存在多条,所以依然定义为集合,这样我们就可以通过遍历的方式将一个区块的所有数据导出。下面给出部分代码:

1
class Block:
2
    def __init__(self,blockchain):
3
        self.blockheight = 1
4
        self.continueParsing = True
5
        self.magicNum = 0
6
        self.blocksize = 0
7
        self.blockheader = ''
8
        self.txCount = 0
9
        self.Txs = []

这样的话从block入手,就可以逐层解码文件,最终得到完整的区块信息。

ScriptSig

因为我们要找到签名$r$,所以必须要解析签名脚本,我主要是通过解码$16$进制的字符串得到的,做之前我们要清楚解锁脚本的结构,这样才能正确处理,下面给出一张解锁脚本的结构图:
ScriptSig
知道这些的话我们就可以通过脚本长度,签名长度等来进一步分割字符串,并且给出判定条件当前置交易索引不为$0xffffffff$时解析scriptsig,因为相等时交易时coinbase交易,是系统给的交易而不是经过人签名得来的,所以不具备$ECDSA$签名脚本,详情可以看我的另外一篇博客:https://blog.csdn.net/qq_35324057/article/details/104072715
下面给出实现这部分的代码:

1
if 0xffffffff != self.txpreindex:
2
         scriptsig = ScriptSig(self.inscriptsig)
3
         self.sigr = scriptsig.sigR
4
         self.sigs = scriptsig.sigS
5
         self.inpubkey = scriptsig.pubkey
6
     else:
7
         self.sigr = ''
8
         self.sigs = ''
9
         self.inpubkey = ''
1
class ScriptSig:
2
    def __init__(self,blockchain):
3
        # if blockchain[8:10] == '20':
4
    #         #     self.sigR = blockchain[10:74]
5
    #         #     if blockchain[76:78] == '20':
6
    #         #         self.sigS = blockchain[78:142]
7
    #         #     else:
8
    #         #         self.sigS = blockchain[78:144]
9
    #         # else :
10
    #         #     self.sigR = blockchain[10:76]
11
    #         #     if blockchain[78:80] == '20':
12
    #         #         self.sigS = blockchain[78:142]
13
    #         #     else:
14
    #         #         self.sigS = blockchain[78:144]
15
        self.scriptLength = int(blockchain[0:2],16)*2
16
        self.script = blockchain[2:2 + self.scriptLength]
17
        self.rLength = int(blockchain[8:10],16)*2
18
        self.sigR = blockchain[10:10+self.rLength]
19
        self.sLength = int(blockchain[10+self.rLength+2:10+self.rLength+2+2],16)*2
20
        self.sigS = blockchain[10+self.rLength+2+2:10+self.rLength+2+2+self.sLength]
21
22
        if SIGHASH_ALL != int(blockchain[self.scriptLength:self.scriptLength + 2], 16):
23
            self.pubkey = "Script op_code is not SIGHASH_ALL"
24
25
        else:
26
            self.pubkey = blockchain[2 + self.scriptLength + 2:2 + self.scriptLength + 2 + 66]

这样就得到了计算私钥需要使用的两个值:$sigR,sigS$,但是我们还需要得到交易的哈希值$Z$,这一点真的太难了,为什么这么说呢,因为交易的哈希值并不是简单的对区块中的某个值做哈希运算,而是需要找到该交易的$prehash$所指向的交易,为什么要这样做呢,不知道的小伙伴请看我的上面提到的那篇博客,我们要是用前驱交易的交易输出部分,或者称为加密脚本,并且还需要一些删减增加字符串,然后最终得到我们的交易字符串,对其进行两次$sha256$运算得到$Z$,这一点是无法实现的。有时间的话我会单独写一篇关于计算交易哈希值的文章。

Redis

对于redis是什么我就不多说了,就是一种key-value的数据库。

安装

  • pycharm安装redis包:
1
pip install redis
  • 然后再下载安装redis数据库
    redis
  • 安装RedisDesktopManager ———-redis数据库的可视化工具

插入区块数据

这也是我实现的最大的难点,主要就是如果key相同的话,因为每个区块都有相同的字段,所以key的值就会被覆盖,如果一个.dat文件中有多个区块的话,到最后插入的数据也只是最后一个区块的数据,而不能将所有的区块数据插入,实现这一点浪费了我极大的时间和经历,希望分享出来能帮到大家!

思想

主要的思想就是用不同的key来标识不同区块不同tx不同input中的数据。
如:我们用$previousHash+$字段名,来标识不同区块的数据,用$previousHash+Txseq+$字段名,来标识一个区块中不同的tx中的数据,依次类推,可以得到插入所有数据的方法,下面给出一些代码:

1
#insert into redis DB
2
3
        #blockheader
4
        # re.set(self.previoushash + 'Version',self.version)
5
        # re.set(self.previoushash + 'PreviousHash', self.previoushash)
6
        # re.set(self.previoushash + 'MerkleRoot', self.Hash)
7
        # re.set(self.previoushash + 'Timestamp', self.time)
8
        # re.set(self.previoushash + 'Difficulty', self.difficulty)
9
        # re.set(self.previoushash + 'Nonce', self.nonce)
10
        #
11
        # # block
12
        # re.set(self.previoushash + 'MagicNum', self.magicnum)
13
        # re.set(self.previoushash + 'Blocksize', self.blocksize)
14
        # re.set(self.previoushash + 'Txcount', self.txcount)
15
        #
16
        # # Tx
17
        # re.set(self.previoushash + self.txseq + 'TxVersion', self.txversion)
18
        # re.set(self.previoushash + self.txseq + 'InCount', self.incount)
19
        # re.set(self.previoushash + self.txseq + 'Txseq', self.txseq)
20
        # re.set(self.previoushash + self.txseq + 'OutCount', self.outcount)
21
        # re.set(self.previoushash + self.txseq + 'LockTime', self.locktime)
22
        #
23
        # # Input
24
        # re.set(self.previoushash + self.txseq + self.inputseq + 'TxPrevHash', self.txprevhash)
25
        # re.set(self.previoushash + self.txseq + self.inputseq + 'TxPreIndex', self.txpreindex)
26
        # re.set(self.previoushash + self.txseq + self.inputseq + 'InScriptLen', self.inscriptlen)
27
        # re.set(self.previoushash + self.txseq + self.inputseq + 'InScriptSig', self.inscriptsig)
28
        # re.set(self.previoushash + self.txseq + self.inputseq + 'InSequence', self.sequence)

取数据

如果往里面存数据理解的话,取数据也很好理解了,主要就是利用了区块中本来就存在几个数据,$txcount$、$inputcount$、$outputcount$,利用这几个数据的话,就可以遍历它们得到数据,很简单就可以实现,如:$get(previousHash+i+’txversion’)$,其中$i$就是从0遍历到$txcount$,前面还有个东西忘了说,你查询数据用到的数据一定要存储起来,比如$previousHash$,就需要按序号存储起来,并且字段名方便提取,这样的话,提取数据的时候,用起来才会很方便。

1
for i in range(0,19972):
2
        hash = re.get('Hash' + str(i))
3
        txcount = re.get(hash + 'Txcount')
4
        # print(txcount)
5
        for j in range(0,int(txcount)):
6
            # print ("Locktime:%s" % re.get(hash + str(j) + 'InCount'))
7
            incount = re.get(hash + str(j) + 'InCount')

总结

还有一些地方没有说,就不写了,总之这个过程还是很有收获的,就是在做的过程中很难过,每天需要有点进度,我却卡在数据库上面很长时间,希望大家每天也能有所收获,无论希望多渺茫,都要继续加油!

-------------本文结束感谢您的阅读-------------
#