写在前面
先在前面说一下,我没有完全实现整个过程,因为有一个地方确实实现不了,等以后有条件再使用别的方法尝试一下。前一段时间看了一篇14年的论文,里面主要说的是比特币中随机数相同会造成私钥泄漏的问题,作者分析了当下存在很多因私钥泄漏,比特币频频被盗的现象,然后找到了问题所在,并且使用Blockchainr工具将其实现了,找出了比特币网络中存在的使用相同随机数的用户私钥。
目的
处于自身学习的角度,我要自己完成作者所提到的步骤,最终找到相同的随机数并且恢复私钥。
整体思路
先编写解析区块文件的程序,将后缀为.dat的区块文件解析出来,然后将数据存储到数据库,之后再借助14年那篇PPT的思想,利用作者提到的dabloom过滤器去找到相同的签名r,这时我们不能仅仅导出签名r,还要导出相关的信息,这样我们才可以知道是否来源于同一用户,才能去计算用户私钥。
具体过程
目前我可以实现解析区块并导出到数据库中,把.dat文件以二进制形式读取,编写区块结构并给出区块中各个字段的长度,以及通过已知字段的值,去读取某些未知长度字段的数据,如:解锁脚本可以通过脚本长度来读取,还有我们可以通过下图这样的思想去解析区块:
从block入手,里面包含blockheader、tx因为一个区块有多条交易,我们定义tx为一个集合Txs,Txs中的每条交易又包含input、output当然它们也会存在多条,所以依然定义为集合,这样我们就可以通过遍历的方式将一个区块的所有数据导出。下面给出部分代码:
class Block:
def __init__(self,blockchain):
self.blockheight = 1
self.continueParsing = True
self.magicNum = 0
self.blocksize = 0
self.blockheader = ''
self.txCount = 0
self.Txs = []
这样的话从block入手,就可以逐层解码文件,最终得到完整的区块信息。
ScriptSig
因为我们要找到签名
,所以必须要解析签名脚本,我主要是通过解码
进制的字符串得到的,做之前我们要清楚解锁脚本的结构,这样才能正确处理,下面给出一张解锁脚本的结构图:
知道这些的话我们就可以通过脚本长度,签名长度等来进一步分割字符串,并且给出判定条件当前置交易索引不为
时解析scriptsig,因为相等时交易时coinbase交易,是系统给的交易而不是经过人签名得来的,所以不具备
签名脚本,详情可以看我的另外一篇博客:https://blog.csdn.net/qq_35324057/article/details/104072715
下面给出实现这部分的代码:
if 0xffffffff != self.txpreindex:
scriptsig = ScriptSig(self.inscriptsig)
self.sigr = scriptsig.sigR
self.sigs = scriptsig.sigS
self.inpubkey = scriptsig.pubkey
else:
self.sigr = ''
self.sigs = ''
self.inpubkey = ''
class ScriptSig:
def __init__(self,blockchain):
# if blockchain[8:10] == '20':
# # self.sigR = blockchain[10:74]
# # if blockchain[76:78] == '20':
# # self.sigS = blockchain[78:142]
# # else:
# # self.sigS = blockchain[78:144]
# # else :
# # self.sigR = blockchain[10:76]
# # if blockchain[78:80] == '20':
# # self.sigS = blockchain[78:142]
# # else:
# # self.sigS = blockchain[78:144]
self.scriptLength = int(blockchain[0:2],16)*2
self.script = blockchain[2:2 + self.scriptLength]
self.rLength = int(blockchain[8:10],16)*2
self.sigR = blockchain[10:10+self.rLength]
self.sLength = int(blockchain[10+self.rLength+2:10+self.rLength+2+2],16)*2
self.sigS = blockchain[10+self.rLength+2+2:10+self.rLength+2+2+self.sLength]
if SIGHASH_ALL != int(blockchain[self.scriptLength:self.scriptLength + 2], 16):
self.pubkey = "Script op_code is not SIGHASH_ALL"
else:
self.pubkey = blockchain[2 + self.scriptLength + 2:2 + self.scriptLength + 2 + 66]
这样就得到了计算私钥需要使用的两个值: ,但是我们还需要得到交易的哈希值 ,这一点真的太难了,为什么这么说呢,因为交易的哈希值并不是简单的对区块中的某个值做哈希运算,而是需要找到该交易的 所指向的交易,为什么要这样做呢,不知道的小伙伴请看我的上面提到的那篇博客,我们要是用前驱交易的交易输出部分,或者称为加密脚本,并且还需要一些删减增加字符串,然后最终得到我们的交易字符串,对其进行两次 运算得到 ,这一点是无法实现的。有时间的话我会单独写一篇关于计算交易哈希值的文章。
Redis
对于redis是什么我就不多说了,就是一种key-value的数据库。
安装
- pycharm安装redis包:
pip install redis
- 然后再下载安装redis数据库
- 安装RedisDesktopManager -------redis数据库的可视化工具
插入区块数据
这也是我实现的最大的难点,主要就是如果key相同的话,因为每个区块都有相同的字段,所以key的值就会被覆盖,如果一个.dat文件中有多个区块的话,到最后插入的数据也只是最后一个区块的数据,而不能将所有的区块数据插入,实现这一点浪费了我极大的时间和经历,希望分享出来能帮到大家!
思想
主要的思想就是用不同的key来标识不同区块不同tx不同input中的数据。
如:我们用
字段名,来标识不同区块的数据,用
字段名,来标识一个区块中不同的tx中的数据,依次类推,可以得到插入所有数据的方法,下面给出一些代码:
#insert into redis DB
#blockheader
# re.set(self.previoushash + 'Version',self.version)
# re.set(self.previoushash + 'PreviousHash', self.previoushash)
# re.set(self.previoushash + 'MerkleRoot', self.Hash)
# re.set(self.previoushash + 'Timestamp', self.time)
# re.set(self.previoushash + 'Difficulty', self.difficulty)
# re.set(self.previoushash + 'Nonce', self.nonce)
#
# # block
# re.set(self.previoushash + 'MagicNum', self.magicnum)
# re.set(self.previoushash + 'Blocksize', self.blocksize)
# re.set(self.previoushash + 'Txcount', self.txcount)
#
# # Tx
# re.set(self.previoushash + self.txseq + 'TxVersion', self.txversion)
# re.set(self.previoushash + self.txseq + 'InCount', self.incount)
# re.set(self.previoushash + self.txseq + 'Txseq', self.txseq)
# re.set(self.previoushash + self.txseq + 'OutCount', self.outcount)
# re.set(self.previoushash + self.txseq + 'LockTime', self.locktime)
#
# # Input
# re.set(self.previoushash + self.txseq + self.inputseq + 'TxPrevHash', self.txprevhash)
# re.set(self.previoushash + self.txseq + self.inputseq + 'TxPreIndex', self.txpreindex)
# re.set(self.previoushash + self.txseq + self.inputseq + 'InScriptLen', self.inscriptlen)
# re.set(self.previoushash + self.txseq + self.inputseq + 'InScriptSig', self.inscriptsig)
# re.set(self.previoushash + self.txseq + self.inputseq + 'InSequence', self.sequence)
取数据
如果往里面存数据理解的话,取数据也很好理解了,主要就是利用了区块中本来就存在几个数据, 、 、 ,利用这几个数据的话,就可以遍历它们得到数据,很简单就可以实现,如: ,其中 就是从0遍历到 ,前面还有个东西忘了说,你查询数据用到的数据一定要存储起来,比如 ,就需要按序号存储起来,并且字段名方便提取,这样的话,提取数据的时候,用起来才会很方便。
for i in range(0,19972):
hash = re.get('Hash' + str(i))
txcount = re.get(hash + 'Txcount')
# print(txcount)
for j in range(0,int(txcount)):
# print ("Locktime:%s" % re.get(hash + str(j) + 'InCount'))
incount = re.get(hash + str(j) + 'InCount')
总结
还有一些地方没有说,就不写了,总之这个过程还是很有收获的,就是在做的过程中很难过,每天需要有点进度,我却卡在数据库上面很长时间,希望大家每天也能有所收获,无论希望多渺茫,都要继续加油!
给出我的blog主页:stride Max Zz.欢迎访问