Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BitBox02 Simulator #741

Merged
merged 2 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/actions/install-sim/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ runs:
apt-get install -y libusb-1.0-0
tar -xvf mcu.tar.gz

- if: inputs.device == 'bitbox02'
shell: bash
run: |
apt-get update
apt-get install -y libusb-1.0-0 docker.io
tar -xvf bitbox02.tar.gz

- if: inputs.device == 'jade'
shell: bash
run: |
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ jobs:
- { name: 'jade', archive: 'jade', paths: 'test/work/jade/simulator' }
- { name: 'ledger', archive: 'speculos', paths: 'test/work/speculos' }
- { name: 'keepkey', archive: 'keepkey-firmware', paths: 'test/work/keepkey-firmware/bin' }
- { name: 'bitbox02', archive: 'bitbox02', paths: 'test/work/bitbox02-firmware/build-build/bin/simulator' }

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -219,6 +220,7 @@ jobs:
- 'ledger'
- 'ledger-legacy'
- 'keepkey'
- 'bitbox02'
script:
- name: 'Wheel'
install: 'pip install dist/*.whl'
Expand Down Expand Up @@ -289,6 +291,7 @@ jobs:
- 'ledger'
- 'ledger-legacy'
- 'keepkey'
- 'bitbox02'
interface: [ 'library', 'cli', 'stdin' ]

container: python:${{ matrix.python-version }}
Expand Down
9 changes: 9 additions & 0 deletions ci/build_bitbox02.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
docker volume rm bitbox02_volume || true
docker volume create bitbox02_volume
CONTAINER_VERSION=$(curl https://raw.githubusercontent.com/BitBoxSwiss/bitbox02-firmware/master/.containerversion)
docker pull shiftcrypto/firmware_v2:$CONTAINER_VERSION
docker run -i --rm -v bitbox02_volume:/bitbox02-firmware shiftcrypto/firmware_v2:$CONTAINER_VERSION bash -c \
"cd /bitbox02-firmware && \
git clone --recursive https://github.com/BitBoxSwiss/bitbox02-firmware.git . && \
git config --global --add safe.directory ./ && \
make -j simulator"
5 changes: 5 additions & 0 deletions ci/cirrus.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ RUN protoc --version
# docker build -f ci/cirrus.Dockerfile -t hwi_test .
# docker run -it --entrypoint /bin/bash hwi_test
# cd test; poetry run ./run_tests.py --ledger --coldcard --interface=cli --device-only
# For BitBox02:
# docker build -f ci/cirrus.Dockerfile -t hwi_test .
# ./ci/build_bitbox02.sh
# docker run -it -v bitbox02_volume:/test/work/bitbox02-firmware --name hwi --entrypoint /bin/bash hwi_test
# cd test; poetry run ./run_tests.py --bitbox02 --interface=cli --device-only
####################

####################
Expand Down
120 changes: 77 additions & 43 deletions hwilib/devices/bitbox02.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import base64
import builtins
import sys
import socket
from functools import wraps

from .._base58 import decode_check, encode_check
Expand Down Expand Up @@ -79,6 +80,8 @@
BitBoxNoiseConfig,
)

SIMULATOR_PATH = "127.0.0.1:15423"

class BitBox02Error(UnavailableActionError):
def __init__(self, msg: str):
"""
Expand Down Expand Up @@ -178,10 +181,15 @@ def enumerate(password: Optional[str] = None, expert: bool = False, chain: Chain
Enumerate all BitBox02 devices. Bootloaders excluded.
"""
result = []
for device_info in devices.get_any_bitbox02s():
path = device_info["path"].decode()
client = Bitbox02Client(path)
client.set_noise_config(SilentNoiseConfig())
devs = [device_info["path"].decode() for device_info in devices.get_any_bitbox02s()]
if allow_emulators:
devs.append(SIMULATOR_PATH)
for path in devs:
client = Bitbox02Client(path=path)
if allow_emulators and client.simulator and not client.simulator.connected:
continue
if path != SIMULATOR_PATH:
client.set_noise_config(SilentNoiseConfig())
d_data: Dict[str, object] = {}
bb02 = None
with handle_errors(common_err_msgs["enumerate"], d_data):
Expand Down Expand Up @@ -252,9 +260,31 @@ def func(*args, **kwargs): # type: ignore
raise exc
except FirmwareVersionOutdatedException as exc:
raise DeviceNotReadyError(str(exc))
except ValueError as e:
raise BadArgumentError(str(e))

return cast(T, func)

class BitBox02Simulator():
def __init__(self) -> None:
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ip, port = SIMULATOR_PATH.split(":")
self.connected = True
try:
self.client_socket.connect((ip, int(port)))
except:
self.connected = False

def write(self, data: bytes) -> None:
# Messages from client are always prefixed with HID report ID(0x00), which is not expected by the simulator.
self.client_socket.send(data[1:])

def read(self, size: int, timeout_ms: int) -> bytes:
res = self.client_socket.recv(64)
return res

def close(self) -> None:
self.client_socket.close()

# This class extends the HardwareWalletClient for BitBox02 specific things
class Bitbox02Client(HardwareWalletClient):
Expand All @@ -267,56 +297,56 @@ def __init__(self, path: str, password: Optional[str] = None, expert: bool = Fal
"The BitBox02 does not accept a passphrase from the host. Please enable the passphrase option and enter the passphrase on the device during unlock."
)
super().__init__(path, password=password, expert=expert, chain=chain)

hid_device = hid.device()
hid_device.open_path(path.encode())
self.transport = u2fhid.U2FHid(hid_device)
self.simulator = None
self.noise_config: BitBoxNoiseConfig = BitBoxNoiseConfig()

if path != SIMULATOR_PATH:
hid_device = hid.device()
hid_device.open_path(path.encode())
self.transport = u2fhid.U2FHid(hid_device)
self.noise_config = CLINoiseConfig()
else:
self.simulator = BitBox02Simulator()
if self.simulator.connected:
self.transport = u2fhid.U2FHid(self.simulator)
self.device_path = path

# use self.init() to access self.bb02.
self.bb02: Optional[bitbox02.BitBox02] = None

self.noise_config: BitBoxNoiseConfig = CLINoiseConfig()

def set_noise_config(self, noise_config: BitBoxNoiseConfig) -> None:
self.noise_config = noise_config

def init(self, expect_initialized: Optional[bool] = True) -> bitbox02.BitBox02:
if self.bb02 is not None:
return self.bb02

for device_info in devices.get_any_bitbox02s():
if device_info["path"].decode() != self.device_path:
continue

bb02 = bitbox02.BitBox02(
transport=self.transport,
device_info=device_info,
noise_config=self.noise_config,
)
try:
bb02.check_min_version()
except FirmwareVersionOutdatedException as exc:
sys.stderr.write("WARNING: {}\n".format(exc))
raise
self.bb02 = bb02
is_initialized = bb02.device_info()["initialized"]
if expect_initialized is not None:
if expect_initialized:
if not is_initialized:
raise HWWError(
"The BitBox02 must be initialized first.",
DEVICE_NOT_INITIALIZED,
)
elif is_initialized:
raise UnavailableActionError(
"The BitBox02 must be wiped before setup."
bb02 = bitbox02.BitBox02(
transport=self.transport,
# Passing None as device_info means the device will be queried for the relevant device info.
device_info=None,
noise_config=self.noise_config,
)
try:
bb02.check_min_version()
except FirmwareVersionOutdatedException as exc:
sys.stderr.write("WARNING: {}\n".format(exc))
raise
self.bb02 = bb02
is_initialized = bb02.device_info()["initialized"]
if expect_initialized is not None:
if expect_initialized:
if not is_initialized:
raise HWWError(
"The BitBox02 must be initialized first.",
DEVICE_NOT_INITIALIZED,
)
elif is_initialized:
raise UnavailableActionError(
"The BitBox02 must be wiped before setup."
)

return bb02
raise Exception(
"Could not find the hid device info for path {}".format(self.device_path)
)
return bb02

def close(self) -> None:
self.transport.close()
Expand Down Expand Up @@ -883,9 +913,13 @@ def setup_device(

if label:
bb02.set_device_name(label)
if not bb02.set_password():
return False
return bb02.create_backup()
if self.device_path != SIMULATOR_PATH:
if not bb02.set_password():
return False
return bb02.create_backup()
else:
bb02.restore_from_mnemonic()
return True

@bitbox02_exception
def wipe_device(self) -> bool:
Expand Down
2 changes: 1 addition & 1 deletion hwilib/devices/bitbox02_lib/bitbox02/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from __future__ import print_function
import sys

__version__ = "6.2.0"
__version__ = "6.3.0"

if sys.version_info.major != 3 or sys.version_info.minor < 6:
print(
Expand Down
Loading