1919import base64
2020import builtins
2121import sys
22+ import socket
2223from functools import wraps
2324
2425from .._base58 import decode_check , encode_check
7980 BitBoxNoiseConfig ,
8081)
8182
83+ SIMULATOR_PATH = "127.0.0.1:15423"
84+
8285class BitBox02Error (UnavailableActionError ):
8386 def __init__ (self , msg : str ):
8487 """
@@ -178,10 +181,15 @@ def enumerate(password: Optional[str] = None, expert: bool = False, chain: Chain
178181 Enumerate all BitBox02 devices. Bootloaders excluded.
179182 """
180183 result = []
181- for device_info in devices .get_any_bitbox02s ():
182- path = device_info ["path" ].decode ()
183- client = Bitbox02Client (path )
184- client .set_noise_config (SilentNoiseConfig ())
184+ devs = [device_info ["path" ].decode () for device_info in devices .get_any_bitbox02s ()]
185+ if allow_emulators :
186+ devs .append (SIMULATOR_PATH )
187+ for path in devs :
188+ client = Bitbox02Client (path = path )
189+ if allow_emulators and client .simulator and not client .simulator .connected :
190+ continue
191+ if path != SIMULATOR_PATH :
192+ client .set_noise_config (SilentNoiseConfig ())
185193 d_data : Dict [str , object ] = {}
186194 bb02 = None
187195 with handle_errors (common_err_msgs ["enumerate" ], d_data ):
@@ -252,9 +260,31 @@ def func(*args, **kwargs): # type: ignore
252260 raise exc
253261 except FirmwareVersionOutdatedException as exc :
254262 raise DeviceNotReadyError (str (exc ))
263+ except ValueError as e :
264+ raise BadArgumentError (str (e ))
255265
256266 return cast (T , func )
257267
268+ class BitBox02Simulator ():
269+ def __init__ (self ) -> None :
270+ self .client_socket = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
271+ ip , port = SIMULATOR_PATH .split (":" )
272+ self .connected = True
273+ try :
274+ self .client_socket .connect ((ip , int (port )))
275+ except :
276+ self .connected = False
277+
278+ def write (self , data : bytes ) -> None :
279+ # Messages from client are always prefixed with HID report ID(0x00), which is not expected by the simulator.
280+ self .client_socket .send (data [1 :])
281+
282+ def read (self , size : int , timeout_ms : int ) -> bytes :
283+ res = self .client_socket .recv (64 )
284+ return res
285+
286+ def close (self ) -> None :
287+ self .client_socket .close ()
258288
259289# This class extends the HardwareWalletClient for BitBox02 specific things
260290class Bitbox02Client (HardwareWalletClient ):
@@ -267,56 +297,56 @@ def __init__(self, path: str, password: Optional[str] = None, expert: bool = Fal
267297 "The BitBox02 does not accept a passphrase from the host. Please enable the passphrase option and enter the passphrase on the device during unlock."
268298 )
269299 super ().__init__ (path , password = password , expert = expert , chain = chain )
270-
271- hid_device = hid .device ()
272- hid_device .open_path (path .encode ())
273- self .transport = u2fhid .U2FHid (hid_device )
300+ self .simulator = None
301+ self .noise_config : BitBoxNoiseConfig = BitBoxNoiseConfig ()
302+
303+ if path != SIMULATOR_PATH :
304+ hid_device = hid .device ()
305+ hid_device .open_path (path .encode ())
306+ self .transport = u2fhid .U2FHid (hid_device )
307+ self .noise_config = CLINoiseConfig ()
308+ else :
309+ self .simulator = BitBox02Simulator ()
310+ if self .simulator .connected :
311+ self .transport = u2fhid .U2FHid (self .simulator )
274312 self .device_path = path
275313
276314 # use self.init() to access self.bb02.
277315 self .bb02 : Optional [bitbox02 .BitBox02 ] = None
278316
279- self .noise_config : BitBoxNoiseConfig = CLINoiseConfig ()
280-
281317 def set_noise_config (self , noise_config : BitBoxNoiseConfig ) -> None :
282318 self .noise_config = noise_config
283319
284320 def init (self , expect_initialized : Optional [bool ] = True ) -> bitbox02 .BitBox02 :
285321 if self .bb02 is not None :
286322 return self .bb02
287323
288- for device_info in devices .get_any_bitbox02s ():
289- if device_info ["path" ].decode () != self .device_path :
290- continue
291-
292- bb02 = bitbox02 .BitBox02 (
293- transport = self .transport ,
294- device_info = device_info ,
295- noise_config = self .noise_config ,
296- )
297- try :
298- bb02 .check_min_version ()
299- except FirmwareVersionOutdatedException as exc :
300- sys .stderr .write ("WARNING: {}\n " .format (exc ))
301- raise
302- self .bb02 = bb02
303- is_initialized = bb02 .device_info ()["initialized" ]
304- if expect_initialized is not None :
305- if expect_initialized :
306- if not is_initialized :
307- raise HWWError (
308- "The BitBox02 must be initialized first." ,
309- DEVICE_NOT_INITIALIZED ,
310- )
311- elif is_initialized :
312- raise UnavailableActionError (
313- "The BitBox02 must be wiped before setup."
324+ bb02 = bitbox02 .BitBox02 (
325+ transport = self .transport ,
326+ # Passing None as device_info means the device will be queried for the relevant device info.
327+ device_info = None ,
328+ noise_config = self .noise_config ,
329+ )
330+ try :
331+ bb02 .check_min_version ()
332+ except FirmwareVersionOutdatedException as exc :
333+ sys .stderr .write ("WARNING: {}\n " .format (exc ))
334+ raise
335+ self .bb02 = bb02
336+ is_initialized = bb02 .device_info ()["initialized" ]
337+ if expect_initialized is not None :
338+ if expect_initialized :
339+ if not is_initialized :
340+ raise HWWError (
341+ "The BitBox02 must be initialized first." ,
342+ DEVICE_NOT_INITIALIZED ,
314343 )
344+ elif is_initialized :
345+ raise UnavailableActionError (
346+ "The BitBox02 must be wiped before setup."
347+ )
315348
316- return bb02
317- raise Exception (
318- "Could not find the hid device info for path {}" .format (self .device_path )
319- )
349+ return bb02
320350
321351 def close (self ) -> None :
322352 self .transport .close ()
@@ -883,9 +913,13 @@ def setup_device(
883913
884914 if label :
885915 bb02 .set_device_name (label )
886- if not bb02 .set_password ():
887- return False
888- return bb02 .create_backup ()
916+ if self .device_path != SIMULATOR_PATH :
917+ if not bb02 .set_password ():
918+ return False
919+ return bb02 .create_backup ()
920+ else :
921+ bb02 .restore_from_mnemonic ()
922+ return True
889923
890924 @bitbox02_exception
891925 def wipe_device (self ) -> bool :
0 commit comments