11# Digital Bitbox interaction script
22
33import hid
4+ import io
45import struct
56import json
67import base64
1516import time
1617
1718from ..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
1920from ..serializations import CTransaction , ExtendedKey , hash256 , ser_sig_der , ser_sig_compact , ser_compact_size
2021from ..base58 import get_xpub_fingerprint , xpub_main_2_test , get_xpub_fingerprint_hex
2122
@@ -188,6 +189,9 @@ def close(self):
188189 def get_serial_number_string (self ):
189190 return 'dbb_fw:v5.0.0'
190191
192+ def get_product_string (self ):
193+ return 'Digital Bitbox firmware'
194+
191195def send_frame (data , device ):
192196 data = bytearray (data )
193197 data_len = len (data )
@@ -293,6 +297,45 @@ def stretch_backup_key(password):
293297def format_backup_filename (name ):
294298 return '{}-{}.pdf' .format (name , time .strftime ('%Y-%m-%d-%H-%M-%S' , time .localtime ()))
295299
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+
296339# This class extends the HardwareWalletClient for Digital Bitbox specific things
297340class DigitalbitboxClient (HardwareWalletClient ):
298341
@@ -310,6 +353,21 @@ def __init__(self, path, password, expert=False):
310353 self .device .open_path (path .encode ())
311354 self .password = password
312355
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+
313371 # Must return a dict with the xpub
314372 # Retrieves the public key at the specified BIP 32 derivation path
315373 @digitalbitbox_exception
@@ -584,8 +642,53 @@ def send_pin(self, pin):
584642 raise UnavailableActionError ('The Digital Bitbox does not need a PIN sent from the host' )
585643
586644 # Verify firmware file then load it onto device
645+ @digitalbitbox_exception
587646 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 }
589692
590693def enumerate (password = '' ):
591694 results = []
0 commit comments