Skip to content

Commit cd1c9aa

Browse files
authored
Merge pull request #9 from justjamesdev/master
Sapling - Ledger, Ledger X
2 parents 8a7a390 + ad30b46 commit cd1c9aa

File tree

4 files changed

+357
-21
lines changed

4 files changed

+357
-21
lines changed

contrib/deterministic-build/requirements-hw.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
btchip-python==0.1.26 \
2-
--hash=sha256:427d67c5b4f4709605c51dd91d5d44a2ad8f541693673817765271e4b3a1461e
1+
btchip-python==0.1.28 \
2+
--hash=sha256:da09d0d7a6180d428833795ea9a233c3b317ddfcccea8cc6f0eba59435e5dd83
33
certifi==2018.1.18 \
44
--hash=sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296 \
55
--hash=sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d

contrib/requirements/requirements-hw.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ Cython>=0.27
22
trezor>=0.9.0
33
keepkey
44
btchip-python
5+
libusb1
6+

plugins/ledger/btchip_zcash.py

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
from binascii import hexlify, unhexlify
2+
from struct import pack, unpack
3+
4+
from btchip.btchip import btchip
5+
from btchip.bitcoinTransaction import bitcoinInput, bitcoinOutput
6+
from btchip.bitcoinVarint import readVarint, writeVarint
7+
from btchip.btchipHelpers import parse_bip32_path, writeUint32BE
8+
9+
from electrum_zclassic.transaction import (OVERWINTERED_VERSION_GROUP_ID,
10+
SAPLING_VERSION_GROUP_ID)
11+
12+
13+
class zcashTransaction:
14+
15+
def __init__(self, data=None):
16+
self.version = ''
17+
self.version_group_id = ''
18+
self.inputs = []
19+
self.outputs = []
20+
self.lockTime = ''
21+
self.expiry_height = ''
22+
self.value_balance = ''
23+
self.overwintered = False
24+
self.n_version = 0
25+
if data is not None:
26+
offset = 0
27+
self.version = data[offset:offset + 4]
28+
offset += 4
29+
header = unpack('<I', self.version)[0]
30+
if header & 0x80000000:
31+
self.n_version = header & 0x7FFFFFFF
32+
self.version_group_id = data[offset:offset + 4]
33+
offset += 4
34+
version_group_id = unpack('<I', self.version_group_id)[0]
35+
if (self.n_version == 3
36+
and version_group_id == OVERWINTERED_VERSION_GROUP_ID):
37+
self.overwintered = True
38+
elif (self.n_version == 4
39+
and version_group_id == SAPLING_VERSION_GROUP_ID):
40+
self.overwintered = True
41+
else:
42+
offset -= 4
43+
self.version_group_id = ''
44+
self.n_version = header
45+
inputSize = readVarint(data, offset)
46+
offset += inputSize['size']
47+
numInputs = inputSize['value']
48+
for i in range(numInputs):
49+
tmp = { 'buffer': data, 'offset' : offset}
50+
self.inputs.append(bitcoinInput(tmp))
51+
offset = tmp['offset']
52+
outputSize = readVarint(data, offset)
53+
offset += outputSize['size']
54+
numOutputs = outputSize['value']
55+
for i in range(numOutputs):
56+
tmp = { 'buffer': data, 'offset' : offset}
57+
self.outputs.append(bitcoinOutput(tmp))
58+
offset = tmp['offset']
59+
self.lockTime = data[offset:offset + 4]
60+
if self.overwintered:
61+
offset += 4
62+
self.expiry_height = data[offset:offset + 4]
63+
if self.n_version >= 4:
64+
offset += 4
65+
self.value_balance = data[offset:offset + 8]
66+
67+
def serializeOutputs(self):
68+
result = []
69+
writeVarint(len(self.outputs), result)
70+
for troutput in self.outputs:
71+
result.extend(troutput.serialize())
72+
return result
73+
74+
75+
class btchip_zcash(btchip):
76+
77+
def startUntrustedTransaction(self, newTransaction, inputIndex, outputList,
78+
redeemScript, version=0x02,
79+
overwintered=False):
80+
# Start building a fake transaction with the passed inputs
81+
if newTransaction:
82+
if overwintered:
83+
p2 = 0x05 if version == 4 else 0x04
84+
else:
85+
p2 = 0x00
86+
else:
87+
p2 = 0x80
88+
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_HASH_INPUT_START, 0x00, p2 ]
89+
if overwintered and version == 3:
90+
params = bytearray([version, 0x00, 0x00, 0x80, 0x70, 0x82, 0xc4, 0x03])
91+
elif overwintered and version == 4:
92+
params = bytearray([version, 0x00, 0x00, 0x80, 0x85, 0x20, 0x2f, 0x89])
93+
else:
94+
params = bytearray([version, 0x00, 0x00, 0x00])
95+
writeVarint(len(outputList), params)
96+
apdu.append(len(params))
97+
apdu.extend(params)
98+
self.dongle.exchange(bytearray(apdu))
99+
# Loop for each input
100+
currentIndex = 0
101+
for passedOutput in outputList:
102+
if ('sequence' in passedOutput) and passedOutput['sequence']:
103+
sequence = bytearray(unhexlify(passedOutput['sequence']))
104+
else:
105+
sequence = bytearray([0xFF, 0xFF, 0xFF, 0xFF]) # default sequence
106+
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_HASH_INPUT_START, 0x80, 0x00 ]
107+
params = []
108+
script = bytearray(redeemScript)
109+
if overwintered:
110+
params.append(0x02)
111+
elif ('trustedInput' in passedOutput) and passedOutput['trustedInput']:
112+
params.append(0x01)
113+
else:
114+
params.append(0x00)
115+
if ('trustedInput' in passedOutput) and passedOutput['trustedInput']:
116+
params.append(len(passedOutput['value']))
117+
params.extend(passedOutput['value'])
118+
if currentIndex != inputIndex:
119+
script = bytearray()
120+
writeVarint(len(script), params)
121+
if len(script) == 0:
122+
params.extend(sequence)
123+
apdu.append(len(params))
124+
apdu.extend(params)
125+
self.dongle.exchange(bytearray(apdu))
126+
offset = 0
127+
while(offset < len(script)):
128+
blockLength = 255
129+
if ((offset + blockLength) < len(script)):
130+
dataLength = blockLength
131+
else:
132+
dataLength = len(script) - offset
133+
params = script[offset : offset + dataLength]
134+
if ((offset + dataLength) == len(script)):
135+
params.extend(sequence)
136+
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_HASH_INPUT_START, 0x80, 0x00, len(params) ]
137+
apdu.extend(params)
138+
self.dongle.exchange(bytearray(apdu))
139+
offset += blockLength
140+
currentIndex += 1
141+
142+
def finalizeInput(self, outputAddress, amount, fees, changePath, rawTx=None):
143+
alternateEncoding = False
144+
donglePath = parse_bip32_path(changePath)
145+
if self.needKeyCache:
146+
self.resolvePublicKeysInPath(changePath)
147+
result = {}
148+
outputs = None
149+
if rawTx is not None:
150+
try:
151+
fullTx = zcashTransaction(bytearray(rawTx))
152+
outputs = fullTx.serializeOutputs()
153+
if len(donglePath) != 0:
154+
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_HASH_INPUT_FINALIZE_FULL, 0xFF, 0x00 ]
155+
params = []
156+
params.extend(donglePath)
157+
apdu.append(len(params))
158+
apdu.extend(params)
159+
response = self.dongle.exchange(bytearray(apdu))
160+
offset = 0
161+
while (offset < len(outputs)):
162+
blockLength = self.scriptBlockLength
163+
if ((offset + blockLength) < len(outputs)):
164+
dataLength = blockLength
165+
p1 = 0x00
166+
else:
167+
dataLength = len(outputs) - offset
168+
p1 = 0x80
169+
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_HASH_INPUT_FINALIZE_FULL, \
170+
p1, 0x00, dataLength ]
171+
apdu.extend(outputs[offset : offset + dataLength])
172+
response = self.dongle.exchange(bytearray(apdu))
173+
offset += dataLength
174+
alternateEncoding = True
175+
except:
176+
pass
177+
if not alternateEncoding:
178+
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_HASH_INPUT_FINALIZE, 0x02, 0x00 ]
179+
params = []
180+
params.append(len(outputAddress))
181+
params.extend(bytearray(outputAddress))
182+
writeHexAmountBE(btc_to_satoshi(str(amount)), params)
183+
writeHexAmountBE(btc_to_satoshi(str(fees)), params)
184+
params.extend(donglePath)
185+
apdu.append(len(params))
186+
apdu.extend(params)
187+
response = self.dongle.exchange(bytearray(apdu))
188+
result['confirmationNeeded'] = response[1 + response[0]] != 0x00
189+
result['confirmationType'] = response[1 + response[0]]
190+
if result['confirmationType'] == 0x02:
191+
result['keycardData'] = response[1 + response[0] + 1:]
192+
if result['confirmationType'] == 0x03:
193+
offset = 1 + response[0] + 1
194+
keycardDataLength = response[offset]
195+
offset = offset + 1
196+
result['keycardData'] = response[offset : offset + keycardDataLength]
197+
offset = offset + keycardDataLength
198+
result['secureScreenData'] = response[offset:]
199+
if result['confirmationType'] == 0x04:
200+
offset = 1 + response[0] + 1
201+
keycardDataLength = response[offset]
202+
result['keycardData'] = response[offset + 1 : offset + 1 + keycardDataLength]
203+
if outputs == None:
204+
result['outputData'] = response[1 : 1 + response[0]]
205+
else:
206+
result['outputData'] = outputs
207+
return result
208+
209+
def finalizeInputFull(self, outputData):
210+
result = {}
211+
offset = 0
212+
encryptedOutputData = b""
213+
while (offset < len(outputData)):
214+
blockLength = self.scriptBlockLength
215+
if ((offset + blockLength) < len(outputData)):
216+
dataLength = blockLength
217+
p1 = 0x00
218+
else:
219+
dataLength = len(outputData) - offset
220+
p1 = 0x80
221+
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_HASH_INPUT_FINALIZE_FULL, \
222+
p1, 0x00, dataLength ]
223+
apdu.extend(outputData[offset : offset + dataLength])
224+
response = self.dongle.exchange(bytearray(apdu))
225+
encryptedOutputData = encryptedOutputData + response[1 : 1 + response[0]]
226+
offset += dataLength
227+
if len(response) > 1:
228+
result['confirmationNeeded'] = response[1 + response[0]] != 0x00
229+
result['confirmationType'] = response[1 + response[0]]
230+
if result['confirmationType'] == 0x02:
231+
result['keycardData'] = response[1 + response[0] + 1:] # legacy
232+
if result['confirmationType'] == 0x03:
233+
offset = 1 + response[0] + 1
234+
keycardDataLength = response[offset]
235+
offset = offset + 1
236+
result['keycardData'] = response[offset : offset + keycardDataLength]
237+
offset = offset + keycardDataLength
238+
result['secureScreenData'] = response[offset:]
239+
result['encryptedOutputData'] = encryptedOutputData
240+
if result['confirmationType'] == 0x04:
241+
offset = 1 + response[0] + 1
242+
keycardDataLength = response[offset]
243+
result['keycardData'] = response[offset + 1 : offset + 1 + keycardDataLength]
244+
return result
245+
246+
def untrustedHashSign(self, path, pin="", lockTime=0, sighashType=0x01,
247+
version=0x02, overwintered=False):
248+
if isinstance(pin, str):
249+
pin = pin.encode('utf-8')
250+
donglePath = parse_bip32_path(path)
251+
if self.needKeyCache:
252+
self.resolvePublicKeysInPath(path)
253+
apdu = [ self.BTCHIP_CLA, self.BTCHIP_INS_HASH_SIGN, 0x00, 0x00 ]
254+
params = []
255+
params.extend(donglePath)
256+
params.append(len(pin))
257+
params.extend(bytearray(pin))
258+
writeUint32BE(lockTime, params)
259+
params.append(sighashType)
260+
if overwintered:
261+
params.extend(bytearray([0]*4))
262+
apdu.append(len(params))
263+
apdu.extend(params)
264+
result = self.dongle.exchange(bytearray(apdu))
265+
if not result:
266+
return
267+
result[0] = 0x30
268+
return result

0 commit comments

Comments
 (0)