1
1
# Digital Bitbox interaction script
2
2
3
3
import hid
4
+ import io
4
5
import struct
5
6
import json
6
7
import base64
15
16
import time
16
17
17
18
from ..hwwclient import HardwareWalletClient
18
- from ..errors import ActionCanceledError , BadArgumentError , DeviceFailureError , DeviceAlreadyInitError , DEVICE_NOT_INITIALIZED , DeviceNotReadyError , NoPasswordError , UnavailableActionError , common_err_msgs , handle_errors
19
+ from ..errors import ActionCanceledError , BAD_ARGUMENT , BadArgumentError , DeviceFailureError , DeviceAlreadyInitError , DEVICE_CONN_ERROR , DEVICE_NOT_INITIALIZED , DeviceNotReadyError , NoPasswordError , UnavailableActionError , common_err_msgs , handle_errors
19
20
from ..serializations import CTransaction , ExtendedKey , hash256 , ser_sig_der , ser_sig_compact , ser_compact_size
20
21
from ..base58 import get_xpub_fingerprint , xpub_main_2_test , get_xpub_fingerprint_hex
21
22
@@ -188,6 +189,9 @@ def close(self):
188
189
def get_serial_number_string (self ):
189
190
return 'dbb_fw:v5.0.0'
190
191
192
+ def get_product_string (self ):
193
+ return 'Digital Bitbox firmware'
194
+
191
195
def send_frame (data , device ):
192
196
data = bytearray (data )
193
197
data_len = len (data )
@@ -293,6 +297,45 @@ def stretch_backup_key(password):
293
297
def format_backup_filename (name ):
294
298
return '{}-{}.pdf' .format (name , time .strftime ('%Y-%m-%d-%H-%M-%S' , time .localtime ()))
295
299
300
+ # ----------------------------------------------------------------------------------
301
+ # Bootloader io
302
+ #
303
+
304
+ def sendBoot (msg , dev ):
305
+ msg = bytearray (msg ) + b'\0 ' * (boot_buf_size_send - len (msg ))
306
+ serial_number = dev .get_serial_number_string ()
307
+ if 'v1.' in serial_number or 'v2.' in serial_number :
308
+ dev .write (b'\0 ' + msg )
309
+ else :
310
+ # Split `msg` into 64-byte packets
311
+ n = 0
312
+ while n < len (msg ):
313
+ dev .write (b'\0 ' + msg [n :n + usb_report_size ])
314
+ n = n + usb_report_size
315
+
316
+ def sendPlainBoot (msg , dev ):
317
+ if type (msg ) == str :
318
+ msg = msg .encode ()
319
+ sendBoot (msg , dev )
320
+ reply = []
321
+ while len (reply ) < boot_buf_size_reply :
322
+ reply = reply + dev .read (boot_buf_size_reply )
323
+
324
+ reply = bytearray (reply ).rstrip (b' \t \r \n \0 ' )
325
+ reply = '' .join (chr (e ) for e in reply )
326
+ return reply
327
+
328
+ def sendChunk (chunknum , data , dev ):
329
+ b = bytearray (b"\x77 \x00 " )
330
+ b [1 ] = chunknum % 0xFF
331
+ b .extend (data )
332
+ sendBoot (b , dev )
333
+ reply = []
334
+ while len (reply ) < boot_buf_size_reply :
335
+ reply = reply + dev .read (boot_buf_size_reply )
336
+ reply = bytearray (reply ).rstrip (b' \t \r \n \0 ' )
337
+ reply = '' .join (chr (e ) for e in reply )
338
+
296
339
# This class extends the HardwareWalletClient for Digital Bitbox specific things
297
340
class DigitalbitboxClient (HardwareWalletClient ):
298
341
@@ -310,6 +353,21 @@ def __init__(self, path, password, expert=False):
310
353
self .device .open_path (path .encode ())
311
354
self .password = password
312
355
356
+ # Always lock the bootloader
357
+ if self .device .get_product_string () != 'bootloader' :
358
+ reply = send_encrypt ('{"device":"info"}' , self .password , self .device )
359
+ if 'error' not in reply :
360
+ if not reply ['device' ]['bootlock' ]:
361
+ reply = send_encrypt ('{"bootloader":"lock"}' , self .password , self .device )
362
+ if 'error' in reply :
363
+ raise DBBError (reply )
364
+ else :
365
+ # Check it isn't initialized
366
+ if reply ['error' ]['code' ] == 101 or reply ['error' ]['code' ] == '101' :
367
+ pass
368
+ else :
369
+ raise DBBError (reply )
370
+
313
371
# Must return a dict with the xpub
314
372
# Retrieves the public key at the specified BIP 32 derivation path
315
373
@digitalbitbox_exception
@@ -584,8 +642,53 @@ def send_pin(self, pin):
584
642
raise UnavailableActionError ('The Digital Bitbox does not need a PIN sent from the host' )
585
643
586
644
# Verify firmware file then load it onto device
645
+ @digitalbitbox_exception
587
646
def update_firmware (self , file ):
588
- raise NotImplementedError ('The Digital Bitbox does not implement this method yet' )
647
+ if self .device .get_product_string () != 'bootloader' :
648
+ print ('Device is not in bootloader mode. Unlocking bootloader, replugging will be required' , file = sys .stderr )
649
+ print ("Touch the device for 3 seconds to unlock bootloaderr. Touch briefly to cancel" , file = sys .stderr )
650
+ reply = send_encrypt ('{"bootloader":"unlock"}' , self .password , self .device )
651
+ if 'error' in reply :
652
+ raise DBBError (reply )
653
+ return {'error' : 'Digital Bitbox needs to be in bootloader mode. Unplug and replug the device and briefly touch the button within 3 seconds. Then try this command again' , 'code' : DEVICE_CONN_ERROR }
654
+
655
+ with open (file , "rb" ) as f :
656
+ data = bytearray ()
657
+ while True :
658
+ d = f .read (chunksize )
659
+ if len (d ) == 0 :
660
+ break
661
+ data = data + bytearray (d )
662
+ data = data + b'\xFF ' * (applen - len (data ))
663
+ firmware = data [448 :]
664
+ sig = data [:448 ]
665
+ print ('Hashed firmware (without signatures)' , binascii .hexlify (hash256 ((firmware ))), file = sys .stderr )
666
+
667
+ sendPlainBoot ("b" , self .device ) # blink led
668
+ sendPlainBoot ("v" , self .device ) # bootloader version
669
+ sendPlainBoot ("e" , self .device ) # erase existing firmware (required)
670
+
671
+ # Send firmware
672
+ f = io .BytesIO (firmware )
673
+ cnt = 0
674
+ while True :
675
+ chunk = f .read (chunksize )
676
+ if len (chunk ) == 0 :
677
+ break
678
+ sendChunk (cnt , chunk , self .device )
679
+ cnt += 1
680
+
681
+ # upload sigs and verify new firmware
682
+ load_result = sendPlainBoot ("s" + "0" + binascii .hexlify (sig ).decode (), self .device )
683
+ if load_result [1 ] == 'V' :
684
+ latest_version , = struct .unpack ('>I' , binascii .unhexlify (load_result [2 + 64 :][:8 ]))
685
+ app_version , = struct .unpack ('>I' , binascii .unhexlify (load_result [2 + 64 + 8 :][:8 ]))
686
+ return {'error' : 'firmware downgrade not allowed. Got version %d, but must be equal or higher to %d' % (app_version , latest_version ), 'code' : BAD_ARGUMENT }
687
+ elif load_result [1 ] != '0' :
688
+ return {'error' : 'invalid firmware signature' , 'code' : BAD_ARGUMENT }
689
+
690
+ print ('Please unplug and replug your device. The bootloader will be locked next time you use HWI with it.' , file = sys .stderr )
691
+ return {'success' : True }
589
692
590
693
def enumerate (password = '' ):
591
694
results = []
0 commit comments