Skip to content

Commit 05b0d06

Browse files
somebodyLi424778940z
authored andcommitted
feat(devices): add onekey support
1 parent 9fbe6be commit 05b0d06

File tree

7 files changed

+759
-7
lines changed

7 files changed

+759
-7
lines changed

hwilib/devices/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
'digitalbitbox',
66
'coldcard',
77
'bitbox02',
8-
'jade'
8+
'jade',
9+
'onekey'
910
]

hwilib/devices/onekey.py

Lines changed: 362 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,362 @@
1+
# type: ignore
2+
""""
3+
OneKey Devices
4+
**************
5+
"""
6+
7+
8+
import sys
9+
from ..common import Chain
10+
from ..errors import (
11+
DEVICE_NOT_INITIALIZED,
12+
DeviceNotReadyError,
13+
common_err_msgs,
14+
handle_errors,
15+
)
16+
from .trezorlib import protobuf, debuglink
17+
from .trezorlib.transport import (
18+
udp,
19+
webusb,
20+
)
21+
from .trezor import TrezorClient
22+
from .trezorlib.mapping import DEFAULT_MAPPING
23+
from .trezorlib.messages import (
24+
BackupType,
25+
Capability,
26+
Features,
27+
SafetyCheckLevel,
28+
)
29+
from types import MethodType
30+
from .trezorlib.models import TrezorModel
31+
from typing import (
32+
Any,
33+
Dict,
34+
List,
35+
Optional,
36+
Sequence,
37+
)
38+
39+
py_enumerate = enumerate # Need to use the enumerate built-in but there's another function already named that
40+
41+
VENDORS = ("onekey.so", )
42+
43+
44+
class OnekeyFeatures(Features):
45+
MESSAGE_WIRE_TYPE = 17
46+
FIELDS = {
47+
1: protobuf.Field("vendor", "string", repeated=False, required=False),
48+
2: protobuf.Field("major_version", "uint32", repeated=False, required=True),
49+
3: protobuf.Field("minor_version", "uint32", repeated=False, required=True),
50+
4: protobuf.Field("patch_version", "uint32", repeated=False, required=True),
51+
5: protobuf.Field("bootloader_mode", "bool", repeated=False, required=False),
52+
6: protobuf.Field("device_id", "string", repeated=False, required=False),
53+
7: protobuf.Field("pin_protection", "bool", repeated=False, required=False),
54+
8: protobuf.Field(
55+
"passphrase_protection", "bool", repeated=False, required=False
56+
),
57+
9: protobuf.Field("language", "string", repeated=False, required=False),
58+
10: protobuf.Field("label", "string", repeated=False, required=False),
59+
12: protobuf.Field("initialized", "bool", repeated=False, required=False),
60+
13: protobuf.Field("revision", "bytes", repeated=False, required=False),
61+
14: protobuf.Field("bootloader_hash", "bytes", repeated=False, required=False),
62+
15: protobuf.Field("imported", "bool", repeated=False, required=False),
63+
16: protobuf.Field("unlocked", "bool", repeated=False, required=False),
64+
17: protobuf.Field(
65+
"_passphrase_cached", "bool", repeated=False, required=False
66+
),
67+
18: protobuf.Field("firmware_present", "bool", repeated=False, required=False),
68+
19: protobuf.Field("needs_backup", "bool", repeated=False, required=False),
69+
20: protobuf.Field("flags", "uint32", repeated=False, required=False),
70+
21: protobuf.Field("model", "string", repeated=False, required=False),
71+
22: protobuf.Field("fw_major", "uint32", repeated=False, required=False),
72+
23: protobuf.Field("fw_minor", "uint32", repeated=False, required=False),
73+
24: protobuf.Field("fw_patch", "uint32", repeated=False, required=False),
74+
25: protobuf.Field("fw_vendor", "string", repeated=False, required=False),
75+
27: protobuf.Field("unfinished_backup", "bool", repeated=False, required=False),
76+
28: protobuf.Field("no_backup", "bool", repeated=False, required=False),
77+
29: protobuf.Field("recovery_mode", "bool", repeated=False, required=False),
78+
30: protobuf.Field("capabilities", "Capability", repeated=True, required=False),
79+
31: protobuf.Field("backup_type", "BackupType", repeated=False, required=False),
80+
32: protobuf.Field("sd_card_present", "bool", repeated=False, required=False),
81+
33: protobuf.Field("sd_protection", "bool", repeated=False, required=False),
82+
34: protobuf.Field(
83+
"wipe_code_protection", "bool", repeated=False, required=False
84+
),
85+
35: protobuf.Field("session_id", "bytes", repeated=False, required=False),
86+
36: protobuf.Field(
87+
"passphrase_always_on_device", "bool", repeated=False, required=False
88+
),
89+
37: protobuf.Field(
90+
"safety_checks", "SafetyCheckLevel", repeated=False, required=False
91+
),
92+
38: protobuf.Field(
93+
"auto_lock_delay_ms", "uint32", repeated=False, required=False
94+
),
95+
39: protobuf.Field(
96+
"display_rotation", "uint32", repeated=False, required=False
97+
),
98+
40: protobuf.Field(
99+
"experimental_features", "bool", repeated=False, required=False
100+
),
101+
500: protobuf.Field("offset", "uint32", repeated=False, required=False),
102+
501: protobuf.Field("ble_name", "string", repeated=False, required=False),
103+
502: protobuf.Field("ble_ver", "string", repeated=False, required=False),
104+
503: protobuf.Field("ble_enable", "bool", repeated=False, required=False),
105+
504: protobuf.Field("se_enable", "bool", repeated=False, required=False),
106+
506: protobuf.Field("se_ver", "string", repeated=False, required=False),
107+
507: protobuf.Field("backup_only", "bool", repeated=False, required=False),
108+
508: protobuf.Field("onekey_version", "string", repeated=False, required=False),
109+
509: protobuf.Field("onekey_serial", "string", repeated=False, required=False),
110+
510: protobuf.Field(
111+
"bootloader_version", "string", repeated=False, required=False
112+
),
113+
511: protobuf.Field("serial_no", "string", repeated=False, required=False),
114+
519: protobuf.Field(
115+
"boardloader_version", "string", repeated=False, required=False
116+
),
117+
}
118+
119+
def __init__(
120+
self,
121+
*,
122+
major_version: "int",
123+
minor_version: "int",
124+
patch_version: "int",
125+
capabilities: Optional[Sequence["Capability"]] = None,
126+
vendor: Optional["str"] = None,
127+
bootloader_mode: Optional["bool"] = None,
128+
device_id: Optional["str"] = None,
129+
pin_protection: Optional["bool"] = None,
130+
passphrase_protection: Optional["bool"] = None,
131+
language: Optional["str"] = None,
132+
label: Optional["str"] = None,
133+
initialized: Optional["bool"] = None,
134+
revision: Optional["bytes"] = None,
135+
bootloader_hash: Optional["bytes"] = None,
136+
imported: Optional["bool"] = None,
137+
unlocked: Optional["bool"] = None,
138+
_passphrase_cached: Optional["bool"] = None,
139+
firmware_present: Optional["bool"] = None,
140+
needs_backup: Optional["bool"] = None,
141+
flags: Optional["int"] = None,
142+
model: Optional["str"] = None,
143+
fw_major: Optional["int"] = None,
144+
fw_minor: Optional["int"] = None,
145+
fw_patch: Optional["int"] = None,
146+
fw_vendor: Optional["str"] = None,
147+
unfinished_backup: Optional["bool"] = None,
148+
no_backup: Optional["bool"] = None,
149+
recovery_mode: Optional["bool"] = None,
150+
backup_type: Optional["BackupType"] = None,
151+
sd_card_present: Optional["bool"] = None,
152+
sd_protection: Optional["bool"] = None,
153+
wipe_code_protection: Optional["bool"] = None,
154+
session_id: Optional["bytes"] = None,
155+
passphrase_always_on_device: Optional["bool"] = None,
156+
safety_checks: Optional["SafetyCheckLevel"] = None,
157+
auto_lock_delay_ms: Optional["int"] = None,
158+
display_rotation: Optional["int"] = None,
159+
experimental_features: Optional["bool"] = None,
160+
offset: Optional["int"] = None,
161+
ble_name: Optional["str"] = None,
162+
ble_ver: Optional["str"] = None,
163+
ble_enable: Optional["bool"] = None,
164+
se_enable: Optional["bool"] = None,
165+
se_ver: Optional["str"] = None,
166+
backup_only: Optional["bool"] = None,
167+
onekey_version: Optional["str"] = None,
168+
onekey_serial: Optional["str"] = None,
169+
bootloader_version: Optional["str"] = None,
170+
serial_no: Optional["str"] = None,
171+
boardloader_version: Optional["str"] = None,
172+
) -> None:
173+
self.capabilities: Sequence["Capability"] = (
174+
capabilities if capabilities is not None else []
175+
)
176+
self.major_version = major_version
177+
self.minor_version = minor_version
178+
self.patch_version = patch_version
179+
self.vendor = vendor
180+
self.bootloader_mode = bootloader_mode
181+
self.device_id = device_id
182+
self.pin_protection = pin_protection
183+
self.passphrase_protection = passphrase_protection
184+
self.language = language
185+
self.label = label
186+
self.initialized = initialized
187+
self.revision = revision
188+
self.bootloader_hash = bootloader_hash
189+
self.imported = imported
190+
self.unlocked = unlocked
191+
self._passphrase_cached = _passphrase_cached
192+
self.firmware_present = firmware_present
193+
self.needs_backup = needs_backup
194+
self.flags = flags
195+
self.model = model
196+
self.fw_major = fw_major
197+
self.fw_minor = fw_minor
198+
self.fw_patch = fw_patch
199+
self.fw_vendor = fw_vendor
200+
self.unfinished_backup = unfinished_backup
201+
self.no_backup = no_backup
202+
self.recovery_mode = recovery_mode
203+
self.backup_type = backup_type
204+
self.sd_card_present = sd_card_present
205+
self.sd_protection = sd_protection
206+
self.wipe_code_protection = wipe_code_protection
207+
self.session_id = session_id
208+
self.passphrase_always_on_device = passphrase_always_on_device
209+
self.safety_checks = safety_checks
210+
self.auto_lock_delay_ms = auto_lock_delay_ms
211+
self.display_rotation = display_rotation
212+
self.experimental_features = experimental_features
213+
self.offset = offset
214+
self.ble_name = ble_name
215+
self.ble_ver = ble_ver
216+
self.ble_enable = ble_enable
217+
self.se_enable = se_enable
218+
self.se_ver = se_ver
219+
self.backup_only = backup_only
220+
self.onekey_version = onekey_version
221+
self.onekey_serial = onekey_serial
222+
self.bootloader_version = bootloader_version
223+
self.serial_no = serial_no
224+
self.boardloader_version = boardloader_version
225+
226+
227+
DEFAULT_MAPPING.register(OnekeyFeatures)
228+
229+
USB_IDS = {(0x1209, 0x4F4A), (0x1209, 0x4F4B), }
230+
231+
ONEKEY_LEGACY = TrezorModel(
232+
name="1",
233+
minimum_version=(2, 11, 0),
234+
vendors=VENDORS,
235+
usb_ids=USB_IDS,
236+
default_mapping=DEFAULT_MAPPING,
237+
)
238+
239+
ONEKEY_TOUCH = TrezorModel(
240+
name="T",
241+
minimum_version=(4, 2, 0),
242+
vendors=VENDORS,
243+
usb_ids=USB_IDS,
244+
default_mapping=DEFAULT_MAPPING,
245+
)
246+
247+
ONEKEYS = (ONEKEY_LEGACY, ONEKEY_TOUCH)
248+
249+
250+
def model_by_name(name: str) -> Optional[TrezorModel]:
251+
for model in ONEKEYS:
252+
if model.name == name:
253+
return model
254+
return None
255+
256+
257+
# ===============overwrite methods for onekey device begin============
258+
259+
260+
def _refresh_features(self: object, features: Features) -> None:
261+
"""Update internal fields based on passed-in Features message."""
262+
if not self.model:
263+
self.model = model_by_name(features.model or "1")
264+
if self.model is None:
265+
raise RuntimeError("Unsupported OneKey model")
266+
267+
if features.vendor not in self.model.vendors:
268+
raise RuntimeError("Unsupported device")
269+
self.features = features
270+
self.version = (*map(int, self.features.onekey_version.split(".")), )
271+
self.check_firmware_version(warn_only=True)
272+
if self.features.session_id is not None:
273+
self.session_id = self.features.session_id
274+
self.features.session_id = None
275+
276+
def button_request(self: object, code: Optional[int]) -> None:
277+
if not self.prompt_shown:
278+
print("Please confirm action on your OneKey device", file=sys.stderr)
279+
if not self.always_prompt:
280+
self.prompt_shown = True
281+
282+
283+
# ===============overwrite methods for onekey device end============
284+
285+
ONEKEY_EMULATOR_PATH = "127.0.0.1:54935"
286+
class OnekeyClient(TrezorClient):
287+
def __init__(
288+
self,
289+
path: str,
290+
password: Optional[str] = None,
291+
expert: bool = False,
292+
chain: Chain = Chain.MAIN,
293+
) -> None:
294+
super().__init__(path, password, expert, chain, webusb_ids=USB_IDS, sim_path=ONEKEY_EMULATOR_PATH)
295+
self.client._refresh_features = MethodType(_refresh_features, self.client)
296+
if not isinstance(self.client.ui, debuglink.DebugUI):
297+
self.client.ui.button_request = MethodType(button_request, self.client.ui)
298+
self.type = "OneKey"
299+
300+
301+
def enumerate(
302+
password: Optional[str] = None, expert: bool = False, chain: Chain = Chain.MAIN
303+
) -> List[Dict[str, Any]]:
304+
results = []
305+
devs = webusb.WebUsbTransport.enumerate(usb_ids=USB_IDS)
306+
devs.extend(udp.UdpTransport.enumerate(path=ONEKEY_EMULATOR_PATH))
307+
for dev in devs:
308+
d_data: Dict[str, Any] = {}
309+
310+
d_data["type"] = "onekey"
311+
d_data["path"] = dev.get_path()
312+
client = None
313+
with handle_errors(common_err_msgs["enumerate"], d_data):
314+
client = OnekeyClient(d_data["path"], password)
315+
try:
316+
client._prepare_device()
317+
except TypeError:
318+
continue
319+
if not client.client.features.onekey_version or client.client.features.vendor not in VENDORS:
320+
continue
321+
322+
d_data["label"] = client.client.features.label
323+
d_data["model"] = "onekey_" + client.client.features.model.lower()
324+
if d_data["path"].startswith("udp:"):
325+
d_data["model"] += "_simulator"
326+
327+
d_data["needs_pin_sent"] = (
328+
client.client.features.pin_protection
329+
and not client.client.features.unlocked
330+
)
331+
if client.client.features.model == "1":
332+
d_data[
333+
"needs_passphrase_sent"
334+
] = (
335+
client.client.features.passphrase_protection
336+
) # always need the passphrase sent for Trezor One if it has passphrase protection enabled
337+
else:
338+
d_data["needs_passphrase_sent"] = False
339+
if d_data["needs_pin_sent"]:
340+
raise DeviceNotReadyError(
341+
"OneKey is locked. Unlock by using 'promptpin' and then 'sendpin'."
342+
)
343+
if d_data["needs_passphrase_sent"] and password is None:
344+
d_data["warnings"] = [
345+
[
346+
'Passphrase protection enabled but passphrase was not provided. Using default passphrase of the empty string ("")'
347+
]
348+
]
349+
if client.client.features.initialized:
350+
d_data["fingerprint"] = client.get_master_fingerprint().hex()
351+
d_data[
352+
"needs_passphrase_sent"
353+
] = False # Passphrase is always needed for the above to have worked, so it's already sent
354+
else:
355+
d_data["error"] = "Not initialized"
356+
d_data["code"] = DEVICE_NOT_INITIALIZED
357+
358+
if client:
359+
client.close()
360+
361+
results.append(d_data)
362+
return results

hwilib/udev/51-onekey.rules

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# OneKey: Hold your own key
2+
# https://onekey.so/
3+
#
4+
# Put this file into /etc/udev/rules.d
5+
#
6+
# If you are creating a distribution package,
7+
# put this into /usr/lib/udev/rules.d or /lib/udev/rules.d
8+
# depending on your distribution
9+
10+
# OneKey
11+
# onekey boot
12+
SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="4F4A", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="onekey%n"
13+
# onekey firmware
14+
SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="4F4B", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="onekey%n"
15+
KERNEL=="hidraw*", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="4F4B", MODE="0660", GROUP="plugdev", TAG+="uaccess", TAG+="udev-acl"

0 commit comments

Comments
 (0)