forked from ethereum/btcrelay
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbtcTx.py
185 lines (130 loc) · 4.44 KB
/
btcTx.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# This file is designed to parse general Bitcoin transactions
# TODO items= syntax may need to be replaced per updated Serpent
# contains the string to be deserialized/parse (currently a tx or blockheader)
data gStr[]
# index to gStr
data pos
# contains a script, currently only used for outputScripts since input scripts are ignored
data gScript[]
def txinParse():
prevTxId = self.readUnsignedBitsLE(256)
outputIndex = readUInt32LE()
# log(outputIndex)
scriptSize = readVarintNum()
if scriptSize > 0:
dblSize = scriptSize*2
self.readSimple(scriptSize, outchars=dblSize) # return value is ignored
seqNum = readUInt32LE()
# log(seqNum)
# returns satoshis, scriptSize
def txoutParse():
satoshis = readUInt64LE()
# log(satoshis)
scriptSize = readVarintNum()
# log(scriptSize)
if scriptSize > 0:
dblSize = scriptSize * 2
scriptStr = self.readSimple(scriptSize, outchars=dblSize)
save(self.gScript[0], scriptStr, chars=dblSize)
return([satoshis, scriptSize], items=2)
# does not convert to numeric
# make sure caller uses outsz=len*2
def readSimple(len):
size = len * 2
offset = self.pos * 2
endIndex = offset + size
# TODO ideally, getting a slice of gStr would be done in 1 step, but Serpent limitation
tmpStr = load(self.gStr[0], chars=endIndex)
currStr = slice(tmpStr, chars=offset, chars=endIndex)
self.pos += len # note: len NOT size
return(currStr:str)
macro readVarintNum():
$ret = readUInt8()
if $ret == 0xfd:
$ret = readUInt16LE()
elif $ret == 0xfe:
$ret = readUInt32LE()
elif $ret == 0xff:
$ret = readUInt64LE()
$ret
# precondition: __setupForParsing() has been called
# returns an array [satoshis, outputScriptSize] and writes the
# outputScript to self.tmpScriptArr
def __getMetaForOutput(outNum):
version = readUInt32LE()
# log(version)
# log(self.pos)
numIns = readVarintNum()
# log(numIns)
# log(self.pos)
i = 0
while i < numIns:
self.txinParse()
i += 1
numOuts = readVarintNum()
i = 0
while i <= outNum:
satAndSize = self.txoutParse(outsz=2)
i += 1
return(satAndSize:arr)
# general function for getting a tx output; for something faster and
# explicit, see btcSpecialTx.py
#
# this is needed until can figure out how a dynamically sized array can be
# returned from a function instead of needing 2 functions, one that
# returns array size, then calling to get the actual array
def parseTransaction(rawTx:str, outNum):
__setupForParsing(rawTx)
meta = self.__getMetaForOutput(outNum, outsz=2)
return(meta, items=2)
macro __setupForParsing($hexStr):
self.pos = 0 # important
save(self.gStr[0], $hexStr, chars=len($hexStr))
def doCheckOutputScript(rawTx:str, size, outNum, expHashOfOutputScript):
satoshiAndScriptSize = self.parseTransaction(rawTx, outNum, outitems=2)
cnt = satoshiAndScriptSize[1] * 2 # note: *2
# TODO using load() until it can be figured out how to use gScript directly with sha256
myarr = load(self.gScript[0], items=(cnt/32)+1) # if cnt is say 50, we want 2 chunks of 32bytes
# log(data=myarr)
hash = sha256(myarr, chars=cnt) # note: chars=cnt NOT items=...
# log(hash)
return(hash == expHashOfOutputScript)
# only handles lowercase a-f
# tested via tests for readUInt8, readUInt32LE, ...
def readUnsignedBitsLE(bits):
size = bits / 4
offset = self.pos * 2
endIndex = offset + size
# TODO ideally, getting a slice of gStr would be done in 1 step, but Serpent limitation
tmpStr = load(self.gStr[0], chars=endIndex)
currStr = slice(tmpStr, chars=offset, chars=endIndex)
result = 0
j = 0
while j < size:
# "01 23 45" want it to read "10 32 54"
if j % 2 == 0:
i = j + 1
else:
i = j - 1
char = getch(currStr, i)
# log(char)
if (char >= 97 && char <= 102): # only handles lowercase a-f
numeric = char - 87
else:
numeric = char - 48
# log(numeric)
result += numeric * 16^j
# log(result)
j += 1
# important
self.pos += size / 2
return(result)
macro readUInt8():
self.readUnsignedBitsLE(8)
macro readUInt16LE():
self.readUnsignedBitsLE(16)
# only handles lowercase a-f
macro readUInt32LE():
self.readUnsignedBitsLE(32)
macro readUInt64LE():
self.readUnsignedBitsLE(64)