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 , 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
@@ -293,6 +294,45 @@ def stretch_backup_key(password):
293294def format_backup_filename (name ):
294295 return '{}-{}.pdf' .format (name , time .strftime ('%Y-%m-%d-%H-%M-%S' , time .localtime ()))
295296
297+ # ----------------------------------------------------------------------------------
298+ # Bootloader io
299+ #
300+
301+ def sendBoot (msg , dev ):
302+ msg = bytearray (msg ) + b'\0 ' * (boot_buf_size_send - len (msg ))
303+ serial_number = dev .get_serial_number_string ()
304+ if 'v1.' in serial_number or 'v2.' in serial_number :
305+ dev .write (b'\0 ' + msg )
306+ else :
307+ # Split `msg` into 64-byte packets
308+ n = 0
309+ while n < len (msg ):
310+ dev .write (b'\0 ' + msg [n :n + usb_report_size ])
311+ n = n + usb_report_size
312+
313+ def sendPlainBoot (msg , dev ):
314+ if type (msg ) == str :
315+ msg = msg .encode ()
316+ sendBoot (msg , dev )
317+ reply = []
318+ while len (reply ) < boot_buf_size_reply :
319+ reply = reply + dev .read (boot_buf_size_reply )
320+
321+ reply = bytearray (reply ).rstrip (b' \t \r \n \0 ' )
322+ reply = '' .join (chr (e ) for e in reply )
323+ return reply
324+
325+ def sendChunk (chunknum , data , dev ):
326+ b = bytearray (b"\x77 \x00 " )
327+ b [1 ] = chunknum % 0xFF
328+ b .extend (data )
329+ sendBoot (b , dev )
330+ reply = []
331+ while len (reply ) < boot_buf_size_reply :
332+ reply = reply + dev .read (boot_buf_size_reply )
333+ reply = bytearray (reply ).rstrip (b' \t \r \n \0 ' )
334+ reply = '' .join (chr (e ) for e in reply )
335+
296336# This class extends the HardwareWalletClient for Digital Bitbox specific things
297337class DigitalbitboxClient (HardwareWalletClient ):
298338
@@ -310,6 +350,16 @@ def __init__(self, path, password):
310350 self .device .open_path (path .encode ())
311351 self .password = password
312352
353+ # Always lock the bootloader
354+ if self .device .get_product_string () != 'bootloader' :
355+ reply = send_encrypt ('{"device":"info"}' , self .password , self .device )
356+ if 'error' in reply :
357+ raise DBBError (reply )
358+ if not reply ['device' ]['bootlock' ]:
359+ reply = send_encrypt ('{"bootloader":"lock"}' , self .password , self .device )
360+ if 'error' in reply :
361+ raise DBBError (reply )
362+
313363 # Must return a dict with the xpub
314364 # Retrieves the public key at the specified BIP 32 derivation path
315365 @digitalbitbox_exception
@@ -579,8 +629,53 @@ def send_pin(self, pin):
579629 raise UnavailableActionError ('The Digital Bitbox does not need a PIN sent from the host' )
580630
581631 # Verify firmware file then load it onto device
632+ @digitalbitbox_exception
582633 def update_firmware (self , file ):
583- raise NotImplementedError ('The Digital Bitbox does not implement this method yet' )
634+ if self .device .get_product_string () != 'bootloader' :
635+ print ('Device is not in bootloader mode. Unlocking bootloader, replugging will be required' , file = sys .stderr )
636+ print ("Touch the device for 3 seconds to unlock bootloaderr. Touch briefly to cancel" , file = sys .stderr )
637+ reply = send_encrypt ('{"bootloader":"unlock"}' , self .password , self .device )
638+ if 'error' in reply :
639+ raise DBBError (reply )
640+ 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 }
641+
642+ with open (file , "rb" ) as f :
643+ data = bytearray ()
644+ while True :
645+ d = f .read (chunksize )
646+ if len (d ) == 0 :
647+ break
648+ data = data + bytearray (d )
649+ data = data + b'\xFF ' * (applen - len (data ))
650+ firmware = data [448 :]
651+ sig = data [:448 ]
652+ print ('Hashed firmware (without signatures)' , binascii .hexlify (hash256 ((firmware ))), file = sys .stderr )
653+
654+ sendPlainBoot ("b" , self .device ) # blink led
655+ sendPlainBoot ("v" , self .device ) # bootloader version
656+ sendPlainBoot ("e" , self .device ) # erase existing firmware (required)
657+
658+ # Send firmware
659+ f = io .BytesIO (firmware )
660+ cnt = 0
661+ while True :
662+ chunk = f .read (chunksize )
663+ if len (chunk ) == 0 :
664+ break
665+ sendChunk (cnt , chunk , self .device )
666+ cnt += 1
667+
668+ # upload sigs and verify new firmware
669+ load_result = sendPlainBoot ("s" + "0" + binascii .hexlify (sig ).decode (), self .device )
670+ if load_result [1 ] == 'V' :
671+ latest_version , = struct .unpack ('>I' , binascii .unhexlify (load_result [2 + 64 :][:8 ]))
672+ app_version , = struct .unpack ('>I' , binascii .unhexlify (load_result [2 + 64 + 8 :][:8 ]))
673+ return {'error' : 'firmware downgrade not allowed. Got version %d, but must be equal or higher to %d' % (app_version , latest_version ), 'code' : BAD_ARGUMENT }
674+ elif load_result [1 ] != '0' :
675+ return {'error' : 'invalid firmware signature' , 'code' : BAD_ARGUMENT }
676+
677+ print ('Please unplug and replug your device. The bootloader will be locked next time you use HWI with it.' , file = sys .stderr )
678+ return {'success' : True }
584679
585680def enumerate (password = '' ):
586681 results = []
0 commit comments