Skip to content

Commit 5bbd22a

Browse files
committed
coldcard: Implement update_firmware
1 parent abbb38d commit 5bbd22a

File tree

1 file changed

+63
-1
lines changed

1 file changed

+63
-1
lines changed

hwilib/devices/coldcard.py

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from .ckcc.client import ColdcardDevice, COINKITE_VID, CKCC_PID
77
from .ckcc.protocol import CCProtocolPacker, CCBusyError, CCProtoError, CCUserRefused
88
from .ckcc.constants import MAX_BLK_LEN, AF_P2WPKH, AF_CLASSIC, AF_P2WPKH_P2SH
9+
from .ckcc.sigheader import FW_HEADER_SIZE, FW_HEADER_OFFSET, FW_HEADER_MAGIC
10+
from .ckcc.utils import dfu_parse
911
from ..base58 import get_xpub_fingerprint, xpub_main_2_test
1012
from ..serializations import ExtendedKey, PSBT
1113
from hashlib import sha256
@@ -244,7 +246,67 @@ def send_pin(self, pin):
244246

245247
# Verify firmware file then load it onto device
246248
def update_firmware(self, file):
247-
raise NotImplementedError('The Coldcard does not implement this method yet')
249+
with open(file, 'rb') as fd:
250+
# learn size (portable way)
251+
offset = 0
252+
sz = fd.seek(0, 2)
253+
fd.seek(0)
254+
255+
# Unwrap DFU contents, if needed. Also handles raw binary file.
256+
try:
257+
if fd.read(5) == b'DfuSe':
258+
# expecting a DFU-wrapped file.
259+
fd.seek(0)
260+
offset, sz, *_ = dfu_parse(fd)
261+
else:
262+
# assume raw binary
263+
pass
264+
265+
assert sz % 256 == 0, "un-aligned size: %s" % sz
266+
fd.seek(offset + FW_HEADER_OFFSET)
267+
hdr = fd.read(FW_HEADER_SIZE)
268+
269+
magic = struct.unpack_from("<I", hdr)[0]
270+
except Exception:
271+
magic = None
272+
273+
if magic != FW_HEADER_MAGIC:
274+
raise BadArgumentError('{} has an invalid magic header for a firmware file.'.format(file))
275+
276+
fd.seek(offset)
277+
278+
left = sz
279+
chk = sha256()
280+
for pos in range(0, sz, MAX_BLK_LEN):
281+
here = fd.read(min(MAX_BLK_LEN, left))
282+
if not here:
283+
break
284+
left -= len(here)
285+
result = self.device.send_recv(CCProtocolPacker.upload(pos, sz, here))
286+
assert result == pos, "Got back: %r" % result
287+
chk.update(here)
288+
289+
# do a verify
290+
expect = chk.digest()
291+
result = self.device.send_recv(CCProtocolPacker.sha256())
292+
assert len(result) == 32
293+
if result != expect:
294+
raise DeviceFailureError("Wrong checksum:\nexpect: %s\n got: %s" % (b2a_hex(expect).decode('ascii'), b2a_hex(result).decode('ascii')))
295+
296+
# AFTER fully uploaded and verified, write a copy of the signature header
297+
# onto the end of flash. Bootrom uses this to check entire file uploaded.
298+
result = self.device.send_recv(CCProtocolPacker.upload(sz, sz + FW_HEADER_SIZE, hdr))
299+
assert result == sz, "failed to write trailer"
300+
301+
# check also SHA after that!
302+
chk.update(hdr)
303+
expect = chk.digest()
304+
final_chk = self.device.send_recv(CCProtocolPacker.sha256())
305+
assert expect == final_chk, "Checksum mismatch after all that?"
306+
307+
self.device.send_recv(CCProtocolPacker.reboot())
308+
309+
return {'success': True}
248310

249311
def enumerate(password=''):
250312
results = []

0 commit comments

Comments
 (0)