What changed, and why it matters
This commit adds a new Python client feature called BitBoxSync to the BitBox02 Python library. It introduces generated protobuf code, new API methods to request identity keys, sign various sync-related intents, and unwrap an encrypted namespace key, plus a demo menu in the developer test tool send_message.py. There is no indication in the commit that this fixes a security bug; it appears to be a normal feature addition.
No security action required; review as part of normal feature release. If auditing BitBoxSync, focus on the firmware-side implementation of intent signing and DEK unwrapping, which is not present in this commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change wires a new BitBoxSync protobuf subprotocol into the Python bitbox02 package: adds bitboxsync.proto generated pb2.py/_pb2.pyi, registers it in hww.proto’s Request/Response oneofs, and exposes helper methods on BitBox02 (bitboxsync_identity, bitboxsync_sign*_intent, bitboxsync_unwrap_namespace_dek). send_message.py gets interactive demo commands and a small HPKE-based test wrapper using pyhpke. No firmware-side implementation is shown, and the commit message/CHANGELOG describe this purely as adding support for BitBoxSync.
Changed components
py/bitbox02 Python client librarypy/send_message.py developer test toolpy/bitbox02/communication/generated/hww_pb2.pypy/bitbox02/communication/generated/bitboxsync_pb2.pyInspect captured patch +574 / −18
diff --git a/.pylintrc b/.pylintrc
index ea11f47..cd6d327 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -13,4 +13,4 @@ good-names=i,j,tx
[TYPECHECK]
# Ignore protobuf generated modules for no-member checks
-ignored-modules=bitbox02.communication.generated.btc_pb2,bitbox02.communication.generated.cardano_pb2,bitbox02.communication.generated.common_pb2,bitbox02.communication.generated.eth_pb2,bitbox02.communication.generated.hww_pb2
+ignored-modules=bitbox02.communication.generated.bitboxsync_pb2,bitbox02.communication.generated.btc_pb2,bitbox02.communication.generated.cardano_pb2,bitbox02.communication.generated.common_pb2,bitbox02.communication.generated.eth_pb2,bitbox02.communication.generated.hww_pb2
diff --git a/py/bitbox02/CHANGELOG.md b/py/bitbox02/CHANGELOG.md
index 80c8b84..83ec0c3 100644
--- a/py/bitbox02/CHANGELOG.md
+++ b/py/bitbox02/CHANGELOG.md
@@ -6,6 +6,7 @@
- Add `change_password()`
- Backups: return non-naive timestamps with UTC timezone
- Disabling anti-klepto for EIP-712 typed message signing requires firmware version v9.26.0 or newer
+- Add support for BitBoxSync
# 7.0.0
- get_info: add optional device initialized boolean to returned tuple
diff --git a/py/bitbox02/Makefile b/py/bitbox02/Makefile
index 6e25a4c..794c9a2 100644
--- a/py/bitbox02/Makefile
+++ b/py/bitbox02/Makefile
@@ -2,7 +2,7 @@
OUT_DIR=bitbox02/communication/generated
-PROTO_FILES=hww.proto common.proto backup_commands.proto bitbox02_system.proto btc.proto cardano.proto eth.proto mnemonic.proto system.proto perform_attestation.proto keystore.proto antiklepto.proto bluetooth.proto
+PROTO_FILES=hww.proto common.proto backup_commands.proto bitbox02_system.proto bitboxsync.proto btc.proto cardano.proto eth.proto mnemonic.proto system.proto perform_attestation.proto keystore.proto antiklepto.proto bluetooth.proto
TARGETS=$(addprefix ${OUT_DIR}/, $(PROTO_FILES:.proto=_pb2.py))
diff --git a/py/bitbox02/bitbox02/bitbox02/__init__.py b/py/bitbox02/bitbox02/bitbox02/__init__.py
index f046cdf..4901cd8 100644
--- a/py/bitbox02/bitbox02/bitbox02/__init__.py
+++ b/py/bitbox02/bitbox02/bitbox02/__init__.py
@@ -32,6 +32,7 @@ except AttributeError:
from .bitbox02 import (
btc_sign_needs_prevtxs,
Backup,
+ BitBoxSyncIdentity,
BitBox02,
BTCInputType,
BTCOutputExternal,
@@ -41,6 +42,7 @@ from .bitbox02 import (
BTCPrevTxOutputType,
DuplicateEntryException,
hww,
+ bitboxsync,
btc,
cardano,
common,
diff --git a/py/bitbox02/bitbox02/bitbox02/bitbox02.py b/py/bitbox02/bitbox02/bitbox02/bitbox02.py
index 3122865..07c4f60 100644
--- a/py/bitbox02/bitbox02/bitbox02/bitbox02.py
+++ b/py/bitbox02/bitbox02/bitbox02/bitbox02.py
@@ -28,6 +28,7 @@ try:
from bitbox02.communication.generated import mnemonic_pb2 as mnemonic
from bitbox02.communication.generated import bitbox02_system_pb2 as bitbox02_system
from bitbox02.communication.generated import backup_commands_pb2 as backup
+ from bitbox02.communication.generated import bitboxsync_pb2 as bitboxsync
from bitbox02.communication.generated import common_pb2 as common
from bitbox02.communication.generated import keystore_pb2 as keystore
from bitbox02.communication.generated import antiklepto_pb2 as antiklepto
@@ -142,6 +143,11 @@ class BTCOutputExternal:
BTCOutputType = Union[BTCOutputInternal, BTCOutputExternal]
+class BitBoxSyncIdentity(TypedDict):
+ auth_public_key: bytes
+ wrap_public_key: bytes
+
+
class BitBox02(BitBoxCommonAPI):
"""Class to communicate with a BitBox02"""
@@ -1403,3 +1409,122 @@ class BitBox02(BitBoxCommonAPI):
request.toggle_enabled.CopyFrom(bluetooth.BluetoothToggleEnabledRequest())
self._bluetooth_msg_query(request, expected_response="success")
+
+ def bitboxsync_msg_query(
+ self,
+ bitboxsync_request: bitboxsync.BitBoxSyncRequest,
+ expected_response: Optional[str] = None,
+ ) -> bitboxsync.BitBoxSyncResponse:
+ """
+ Same as _msg_query, but one nesting deeper for BitBoxSync messages.
+ """
+ # pylint: disable=no-member
+ request = hww.Request()
+ request.bitbox_sync.CopyFrom(bitboxsync_request)
+ bitboxsync_response = self._msg_query(request, expected_response="bitbox_sync").bitbox_sync
+ if (
+ expected_response is not None
+ and bitboxsync_response.WhichOneof("response") != expected_response
+ ):
+ raise Exception(
+ "Unexpected response: {}, expected: {}".format(
+ bitboxsync_response.WhichOneof("response"), expected_response
+ )
+ )
+ return bitboxsync_response
+
+ def bitboxsync_identity(self) -> BitBoxSyncIdentity:
+ """Return the device's BitBoxSync identity public keys."""
+ # pylint: disable=no-member
+ request = bitboxsync.BitBoxSyncRequest()
+ request.identity.CopyFrom(bitboxsync.BitBoxSyncIdentityRequest())
+ identity = self.bitboxsync_msg_query(request, expected_response="identity").identity
+ return {
+ "auth_public_key": identity.auth_public_key,
+ "wrap_public_key": identity.wrap_public_key,
+ }
+
+ def bitboxsync_sign_login_intent(self, challenge: bytes) -> bytes:
+ """Sign a BitBoxSync login intent challenge."""
+ # pylint: disable=no-member
+ request = bitboxsync.BitBoxSyncRequest()
+ request.sign_login_intent.CopyFrom(
+ bitboxsync.BitBoxSyncSignLoginIntentRequest(challenge=challenge)
+ )
+ return self.bitboxsync_msg_query(request, expected_response="signature").signature.signature
+
+ def bitboxsync_sign_refresh_intent(self, challenge: bytes) -> bytes:
+ """Sign a BitBoxSync refresh intent challenge."""
+ # pylint: disable=no-member
+ request = bitboxsync.BitBoxSyncRequest()
+ request.sign_refresh_intent.CopyFrom(
+ bitboxsync.BitBoxSyncSignRefreshIntentRequest(challenge=challenge)
+ )
+ return self.bitboxsync_msg_query(request, expected_response="signature").signature.signature
+
+ def bitboxsync_sign_revoke_all_tokens_intent(self, challenge: bytes) -> bytes:
+ """Sign a BitBoxSync revoke-all-tokens intent challenge."""
+ # pylint: disable=no-member
+ request = bitboxsync.BitBoxSyncRequest()
+ request.sign_revoke_all_tokens_intent.CopyFrom(
+ bitboxsync.BitBoxSyncSignRevokeAllTokensIntentRequest(challenge=challenge)
+ )
+ return self.bitboxsync_msg_query(request, expected_response="signature").signature.signature
+
+ def bitboxsync_sign_create_namespace_invite_intent(
+ self,
+ challenge: bytes,
+ namespace_id: bytes,
+ invite_id: bytes,
+ invite_server_secret_hash: bytes,
+ expires_at: int,
+ max_accepted: int,
+ ) -> bytes:
+ """Sign a BitBoxSync create-namespace-invite intent."""
+ # pylint: disable=no-member
+ request = bitboxsync.BitBoxSyncRequest()
+ request.sign_create_namespace_invite_intent.CopyFrom(
+ bitboxsync.BitBoxSyncSignCreateNamespaceInviteIntentRequest(
+ challenge=challenge,
+ namespace_id=namespace_id,
+ invite_id=invite_id,
+ invite_server_secret_hash=invite_server_secret_hash,
+ expires_at=expires_at,
+ max_accepted=max_accepted,
+ )
+ )
+ return self.bitboxsync_msg_query(request, expected_response="signature").signature.signature
+
+ def bitboxsync_sign_join_request_intent(
+ self,
+ namespace_id: bytes,
+ invite_id: bytes,
+ server_origin: str,
+ expires_at: int,
+ ) -> bytes:
+ """Sign a BitBoxSync join-request intent."""
+ # pylint: disable=no-member
+ request = bitboxsync.BitBoxSyncRequest()
+ request.sign_join_request_intent.CopyFrom(
+ bitboxsync.BitBoxSyncSignJoinRequestIntentRequest(
+ namespace_id=namespace_id,
+ invite_id=invite_id,
+ server_origin=server_origin,
+ expires_at=expires_at,
+ )
+ )
+ return self.bitboxsync_msg_query(request, expected_response="signature").signature.signature
+
+ def bitboxsync_unwrap_namespace_dek(self, namespace_id: bytes, wrapped_dek: bytes) -> bytes:
+ """Unwrap a BitBoxSync namespace data encryption key."""
+ # pylint: disable=no-member
+ request = bitboxsync.BitBoxSyncRequest()
+ request.unwrap_namespace_dek.CopyFrom(
+ bitboxsync.BitBoxSyncUnwrapNamespaceDEKRequest(
+ namespace_id=namespace_id,
+ wrapped_dek=wrapped_dek,
+ )
+ )
+ return self.bitboxsync_msg_query(
+ request, expected_response="unwrap_namespace_dek"
+ ).unwrap_namespace_dek.namespace_dek
diff --git a/py/bitbox02/bitbox02/communication/generated/bitboxsync_pb2.py b/py/bitbox02/bitbox02/communication/generated/bitboxsync_pb2.py
new file mode 100644
index 0000000..a8e47e7
--- /dev/null
+++ b/py/bitbox02/bitbox02/communication/generated/bitboxsync_pb2.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: bitboxsync.proto
+"""Generated protocol buffer code."""
+from google.protobuf.internal import builder as _builder
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10\x62itboxsync.proto\x12\x14shiftcrypto.bitbox02\"\x1b\n\x19\x42itBoxSyncIdentityRequest\"N\n\x1a\x42itBoxSyncIdentityResponse\x12\x17\n\x0f\x61uth_public_key\x18\x01 \x01(\x0c\x12\x17\n\x0fwrap_public_key\x18\x02 \x01(\x0c\"5\n BitBoxSyncSignLoginIntentRequest\x12\x11\n\tchallenge\x18\x01 \x01(\x0c\"7\n\"BitBoxSyncSignRefreshIntentRequest\x12\x11\n\tchallenge\x18\x01 \x01(\x0c\"?\n*BitBoxSyncSignRevokeAllTokensIntentRequest\x12\x11\n\tchallenge\x18\x01 \x01(\x0c\"\xbb\x01\n0BitBoxSyncSignCreateNamespaceInviteIntentRequest\x12\x11\n\tchallenge\x18\x01 \x01(\x0c\x12\x14\n\x0cnamespace_id\x18\x02 \x01(\x0c\x12\x11\n\tinvite_id\x18\x03 \x01(\x0c\x12!\n\x19invite_server_secret_hash\x18\x04 \x01(\x0c\x12\x12\n\nexpires_at\x18\x05 \x01(\x04\x12\x14\n\x0cmax_accepted\x18\x06 \x01(\r\"|\n&BitBoxSyncSignJoinRequestIntentRequest\x12\x14\n\x0cnamespace_id\x18\x01 \x01(\x0c\x12\x11\n\tinvite_id\x18\x02 \x01(\x0c\x12\x15\n\rserver_origin\x18\x03 \x01(\t\x12\x12\n\nexpires_at\x18\x04 \x01(\x04\"P\n#BitBoxSyncUnwrapNamespaceDEKRequest\x12\x14\n\x0cnamespace_id\x18\x01 \x01(\x0c\x12\x13\n\x0bwrapped_dek\x18\x02 \x01(\x0c\"0\n\x1b\x42itBoxSyncSignatureResponse\x12\x11\n\tsignature\x18\x01 \x01(\x0c\"=\n$BitBoxSyncUnwrapNamespaceDEKResponse\x12\x15\n\rnamespace_dek\x18\x01 \x01(\x0c\"\xb0\x05\n\x11\x42itBoxSyncRequest\x12\x43\n\x08identity\x18\x01 \x01(\x0b\x32/.shiftcrypto.bitbox02.BitBoxSyncIdentityRequestH\x00\x12S\n\x11sign_login_intent\x18\x02 \x01(\x0b\x32\x36.shiftcrypto.bitbox02.BitBoxSyncSignLoginIntentRequestH\x00\x12W\n\x13sign_refresh_intent\x18\x03 \x01(\x0b\x32\x38.shiftcrypto.bitbox02.BitBoxSyncSignRefreshIntentRequestH\x00\x12i\n\x1dsign_revoke_all_tokens_intent\x18\x04 \x01(\x0b\x32@.shiftcrypto.bitbox02.BitBoxSyncSignRevokeAllTokensIntentRequestH\x00\x12u\n#sign_create_namespace_invite_intent\x18\x05 \x01(\x0b\x32\x46.shiftcrypto.bitbox02.BitBoxSyncSignCreateNamespaceInviteIntentRequestH\x00\x12`\n\x18sign_join_request_intent\x18\x06 \x01(\x0b\x32<.shiftcrypto.bitbox02.BitBoxSyncSignJoinRequestIntentRequestH\x00\x12Y\n\x14unwrap_namespace_dek\x18\x07 \x01(\x0b\x32\x39.shiftcrypto.bitbox02.BitBoxSyncUnwrapNamespaceDEKRequestH\x00\x42\t\n\x07request\"\x8a\x02\n\x12\x42itBoxSyncResponse\x12\x44\n\x08identity\x18\x01 \x01(\x0b\x32\x30.shiftcrypto.bitbox02.BitBoxSyncIdentityResponseH\x00\x12\x46\n\tsignature\x18\x02 \x01(\x0b\x32\x31.shiftcrypto.bitbox02.BitBoxSyncSignatureResponseH\x00\x12Z\n\x14unwrap_namespace_dek\x18\x03 \x01(\x0b\x32:.shiftcrypto.bitbox02.BitBoxSyncUnwrapNamespaceDEKResponseH\x00\x42\n\n\x08responseb\x06proto3')
+
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'bitboxsync_pb2', globals())
+if _descriptor._USE_C_DESCRIPTORS == False:
+
+ DESCRIPTOR._options = None
+ _BITBOXSYNCIDENTITYREQUEST._serialized_start=42
+ _BITBOXSYNCIDENTITYREQUEST._serialized_end=69
+ _BITBOXSYNCIDENTITYRESPONSE._serialized_start=71
+ _BITBOXSYNCIDENTITYRESPONSE._serialized_end=149
+ _BITBOXSYNCSIGNLOGININTENTREQUEST._serialized_start=151
+ _BITBOXSYNCSIGNLOGININTENTREQUEST._serialized_end=204
+ _BITBOXSYNCSIGNREFRESHINTENTREQUEST._serialized_start=206
+ _BITBOXSYNCSIGNREFRESHINTENTREQUEST._serialized_end=261
+ _BITBOXSYNCSIGNREVOKEALLTOKENSINTENTREQUEST._serialized_start=263
+ _BITBOXSYNCSIGNREVOKEALLTOKENSINTENTREQUEST._serialized_end=326
+ _BITBOXSYNCSIGNCREATENAMESPACEINVITEINTENTREQUEST._serialized_start=329
+ _BITBOXSYNCSIGNCREATENAMESPACEINVITEINTENTREQUEST._serialized_end=516
+ _BITBOXSYNCSIGNJOINREQUESTINTENTREQUEST._serialized_start=518
+ _BITBOXSYNCSIGNJOINREQUESTINTENTREQUEST._serialized_end=642
+ _BITBOXSYNCUNWRAPNAMESPACEDEKREQUEST._serialized_start=644
+ _BITBOXSYNCUNWRAPNAMESPACEDEKREQUEST._serialized_end=724
+ _BITBOXSYNCSIGNATURERESPONSE._serialized_start=726
+ _BITBOXSYNCSIGNATURERESPONSE._serialized_end=774
+ _BITBOXSYNCUNWRAPNAMESPACEDEKRESPONSE._serialized_start=776
+ _BITBOXSYNCUNWRAPNAMESPACEDEKRESPONSE._serialized_end=837
+ _BITBOXSYNCREQUEST._serialized_start=840
+ _BITBOXSYNCREQUEST._serialized_end=1528
+ _BITBOXSYNCRESPONSE._serialized_start=1531
+ _BITBOXSYNCRESPONSE._serialized_end=1797
+# @@protoc_insertion_point(module_scope)
diff --git a/py/bitbox02/bitbox02/communication/generated/bitboxsync_pb2.pyi b/py/bitbox02/bitbox02/communication/generated/bitboxsync_pb2.pyi
new file mode 100644
index 0000000..0ad343e
--- /dev/null
+++ b/py/bitbox02/bitbox02/communication/generated/bitboxsync_pb2.pyi
@@ -0,0 +1,254 @@
+"""
+@generated by mypy-protobuf. Do not edit manually!
+isort:skip_file
+SPDX-License-Identifier: Apache-2.0"""
+
+import builtins
+import google.protobuf.descriptor
+import google.protobuf.message
+import typing
+
+DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
+
+@typing.final
+class BitBoxSyncIdentityRequest(google.protobuf.message.Message):
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ def __init__(
+ self,
+ ) -> None: ...
+
+global___BitBoxSyncIdentityRequest = BitBoxSyncIdentityRequest
+
+@typing.final
+class BitBoxSyncIdentityResponse(google.protobuf.message.Message):
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ AUTH_PUBLIC_KEY_FIELD_NUMBER: builtins.int
+ WRAP_PUBLIC_KEY_FIELD_NUMBER: builtins.int
+ auth_public_key: builtins.bytes
+ wrap_public_key: builtins.bytes
+ def __init__(
+ self,
+ *,
+ auth_public_key: builtins.bytes = ...,
+ wrap_public_key: builtins.bytes = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing.Literal["auth_public_key", b"auth_public_key", "wrap_public_key", b"wrap_public_key"]) -> None: ...
+
+global___BitBoxSyncIdentityResponse = BitBoxSyncIdentityResponse
+
+@typing.final
+class BitBoxSyncSignLoginIntentRequest(google.protobuf.message.Message):
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ CHALLENGE_FIELD_NUMBER: builtins.int
+ challenge: builtins.bytes
+ def __init__(
+ self,
+ *,
+ challenge: builtins.bytes = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing.Literal["challenge", b"challenge"]) -> None: ...
+
+global___BitBoxSyncSignLoginIntentRequest = BitBoxSyncSignLoginIntentRequest
+
+@typing.final
+class BitBoxSyncSignRefreshIntentRequest(google.protobuf.message.Message):
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ CHALLENGE_FIELD_NUMBER: builtins.int
+ challenge: builtins.bytes
+ def __init__(
+ self,
+ *,
+ challenge: builtins.bytes = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing.Literal["challenge", b"challenge"]) -> None: ...
+
+global___BitBoxSyncSignRefreshIntentRequest = BitBoxSyncSignRefreshIntentRequest
+
+@typing.final
+class BitBoxSyncSignRevokeAllTokensIntentRequest(google.protobuf.message.Message):
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ CHALLENGE_FIELD_NUMBER: builtins.int
+ challenge: builtins.bytes
+ def __init__(
+ self,
+ *,
+ challenge: builtins.bytes = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing.Literal["challenge", b"challenge"]) -> None: ...
+
+global___BitBoxSyncSignRevokeAllTokensIntentRequest = BitBoxSyncSignRevokeAllTokensIntentRequest
+
+@typing.final
+class BitBoxSyncSignCreateNamespaceInviteIntentRequest(google.protobuf.message.Message):
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ CHALLENGE_FIELD_NUMBER: builtins.int
+ NAMESPACE_ID_FIELD_NUMBER: builtins.int
+ INVITE_ID_FIELD_NUMBER: builtins.int
+ INVITE_SERVER_SECRET_HASH_FIELD_NUMBER: builtins.int
+ EXPIRES_AT_FIELD_NUMBER: builtins.int
+ MAX_ACCEPTED_FIELD_NUMBER: builtins.int
+ challenge: builtins.bytes
+ namespace_id: builtins.bytes
+ invite_id: builtins.bytes
+ invite_server_secret_hash: builtins.bytes
+ expires_at: builtins.int
+ max_accepted: builtins.int
+ def __init__(
+ self,
+ *,
+ challenge: builtins.bytes = ...,
+ namespace_id: builtins.bytes = ...,
+ invite_id: builtins.bytes = ...,
+ invite_server_secret_hash: builtins.bytes = ...,
+ expires_at: builtins.int = ...,
+ max_accepted: builtins.int = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing.Literal["challenge", b"challenge", "expires_at", b"expires_at", "invite_id", b"invite_id", "invite_server_secret_hash", b"invite_server_secret_hash", "max_accepted", b"max_accepted", "namespace_id", b"namespace_id"]) -> None: ...
+
+global___BitBoxSyncSignCreateNamespaceInviteIntentRequest = BitBoxSyncSignCreateNamespaceInviteIntentRequest
+
+@typing.final
+class BitBoxSyncSignJoinRequestIntentRequest(google.protobuf.message.Message):
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ NAMESPACE_ID_FIELD_NUMBER: builtins.int
+ INVITE_ID_FIELD_NUMBER: builtins.int
+ SERVER_ORIGIN_FIELD_NUMBER: builtins.int
+ EXPIRES_AT_FIELD_NUMBER: builtins.int
+ namespace_id: builtins.bytes
+ invite_id: builtins.bytes
+ server_origin: builtins.str
+ expires_at: builtins.int
+ def __init__(
+ self,
+ *,
+ namespace_id: builtins.bytes = ...,
+ invite_id: builtins.bytes = ...,
+ server_origin: builtins.str = ...,
+ expires_at: builtins.int = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing.Literal["expires_at", b"expires_at", "invite_id", b"invite_id", "namespace_id", b"namespace_id", "server_origin", b"server_origin"]) -> None: ...
+
+global___BitBoxSyncSignJoinRequestIntentRequest = BitBoxSyncSignJoinRequestIntentRequest
+
+@typing.final
+class BitBoxSyncUnwrapNamespaceDEKRequest(google.protobuf.message.Message):
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ NAMESPACE_ID_FIELD_NUMBER: builtins.int
+ WRAPPED_DEK_FIELD_NUMBER: builtins.int
+ namespace_id: builtins.bytes
+ wrapped_dek: builtins.bytes
+ def __init__(
+ self,
+ *,
+ namespace_id: builtins.bytes = ...,
+ wrapped_dek: builtins.bytes = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing.Literal["namespace_id", b"namespace_id", "wrapped_dek", b"wrapped_dek"]) -> None: ...
+
+global___BitBoxSyncUnwrapNamespaceDEKRequest = BitBoxSyncUnwrapNamespaceDEKRequest
+
+@typing.final
+class BitBoxSyncSignatureResponse(google.protobuf.message.Message):
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ SIGNATURE_FIELD_NUMBER: builtins.int
+ signature: builtins.bytes
+ def __init__(
+ self,
+ *,
+ signature: builtins.bytes = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing.Literal["signature", b"signature"]) -> None: ...
+
+global___BitBoxSyncSignatureResponse = BitBoxSyncSignatureResponse
+
+@typing.final
+class BitBoxSyncUnwrapNamespaceDEKResponse(google.protobuf.message.Message):
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ NAMESPACE_DEK_FIELD_NUMBER: builtins.int
+ namespace_dek: builtins.bytes
+ def __init__(
+ self,
+ *,
+ namespace_dek: builtins.bytes = ...,
+ ) -> None: ...
+ def ClearField(self, field_name: typing.Literal["namespace_dek", b"namespace_dek"]) -> None: ...
+
+global___BitBoxSyncUnwrapNamespaceDEKResponse = BitBoxSyncUnwrapNamespaceDEKResponse
+
+@typing.final
+class BitBoxSyncRequest(google.protobuf.message.Message):
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ IDENTITY_FIELD_NUMBER: builtins.int
+ SIGN_LOGIN_INTENT_FIELD_NUMBER: builtins.int
+ SIGN_REFRESH_INTENT_FIELD_NUMBER: builtins.int
+ SIGN_REVOKE_ALL_TOKENS_INTENT_FIELD_NUMBER: builtins.int
+ SIGN_CREATE_NAMESPACE_INVITE_INTENT_FIELD_NUMBER: builtins.int
+ SIGN_JOIN_REQUEST_INTENT_FIELD_NUMBER: builtins.int
+ UNWRAP_NAMESPACE_DEK_FIELD_NUMBER: builtins.int
+ @property
+ def identity(self) -> global___BitBoxSyncIdentityRequest: ...
+ @property
+ def sign_login_intent(self) -> global___BitBoxSyncSignLoginIntentRequest: ...
+ @property
+ def sign_refresh_intent(self) -> global___BitBoxSyncSignRefreshIntentRequest: ...
+ @property
+ def sign_revoke_all_tokens_intent(self) -> global___BitBoxSyncSignRevokeAllTokensIntentRequest: ...
+ @property
+ def sign_create_namespace_invite_intent(self) -> global___BitBoxSyncSignCreateNamespaceInviteIntentRequest: ...
+ @property
+ def sign_join_request_intent(self) -> global___BitBoxSyncSignJoinRequestIntentRequest: ...
+ @property
+ def unwrap_namespace_dek(self) -> global___BitBoxSyncUnwrapNamespaceDEKRequest: ...
+ def __init__(
+ self,
+ *,
+ identity: global___BitBoxSyncIdentityRequest | None = ...,
+ sign_login_intent: global___BitBoxSyncSignLoginIntentRequest | None = ...,
+ sign_refresh_intent: global___BitBoxSyncSignRefreshIntentRequest | None = ...,
+ sign_revoke_all_tokens_intent: global___BitBoxSyncSignRevokeAllTokensIntentRequest | None = ...,
+ sign_create_namespace_invite_intent: global___BitBoxSyncSignCreateNamespaceInviteIntentRequest | None = ...,
+ sign_join_request_intent: global___BitBoxSyncSignJoinRequestIntentRequest | None = ...,
+ unwrap_namespace_dek: global___BitBoxSyncUnwrapNamespaceDEKRequest | None = ...,
+ ) -> None: ...
+ def HasField(self, field_name: typing.Literal["identity", b"identity", "request", b"request", "sign_create_namespace_invite_intent", b"sign_create_namespace_invite_intent", "sign_join_request_intent", b"sign_join_request_intent", "sign_login_intent", b"sign_login_intent", "sign_refresh_intent", b"sign_refresh_intent", "sign_revoke_all_tokens_intent", b"sign_revoke_all_tokens_intent", "unwrap_namespace_dek", b"unwrap_namespace_dek"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing.Literal["identity", b"identity", "request", b"request", "sign_create_namespace_invite_intent", b"sign_create_namespace_invite_intent", "sign_join_request_intent", b"sign_join_request_intent", "sign_login_intent", b"sign_login_intent", "sign_refresh_intent", b"sign_refresh_intent", "sign_revoke_all_tokens_intent", b"sign_revoke_all_tokens_intent", "unwrap_namespace_dek", b"unwrap_namespace_dek"]) -> None: ...
+ def WhichOneof(self, oneof_group: typing.Literal["request", b"request"]) -> typing.Literal["identity", "sign_login_intent", "sign_refresh_intent", "sign_revoke_all_tokens_intent", "sign_create_namespace_invite_intent", "sign_join_request_intent", "unwrap_namespace_dek"] | None: ...
+
+global___BitBoxSyncRequest = BitBoxSyncRequest
+
+@typing.final
+class BitBoxSyncResponse(google.protobuf.message.Message):
+ DESCRIPTOR: google.protobuf.descriptor.Descriptor
+
+ IDENTITY_FIELD_NUMBER: builtins.int
+ SIGNATURE_FIELD_NUMBER: builtins.int
+ UNWRAP_NAMESPACE_DEK_FIELD_NUMBER: builtins.int
+ @property
+ def identity(self) -> global___BitBoxSyncIdentityResponse: ...
+ @property
+ def signature(self) -> global___BitBoxSyncSignatureResponse: ...
+ @property
+ def unwrap_namespace_dek(self) -> global___BitBoxSyncUnwrapNamespaceDEKResponse: ...
+ def __init__(
+ self,
+ *,
+ identity: global___BitBoxSyncIdentityResponse | None = ...,
+ signature: global___BitBoxSyncSignatureResponse | None = ...,
+ unwrap_namespace_dek: global___BitBoxSyncUnwrapNamespaceDEKResponse | None = ...,
+ ) -> None: ...
+ def HasField(self, field_name: typing.Literal["identity", b"identity", "response", b"response", "signature", b"signature", "unwrap_namespace_dek", b"unwrap_namespace_dek"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing.Literal["identity", b"identity", "response", b"response", "signature", b"signature", "unwrap_namespace_dek", b"unwrap_namespace_dek"]) -> None: ...
+ def WhichOneof(self, oneof_group: typing.Literal["response", b"response"]) -> typing.Literal["identity", "signature", "unwrap_namespace_dek"] | None: ...
+
+global___BitBoxSyncResponse = BitBoxSyncResponse
diff --git a/py/bitbox02/bitbox02/communication/generated/hww_pb2.py b/py/bitbox02/bitbox02/communication/generated/hww_pb2.py
index 1ea4168..a55100e 100644
--- a/py/bitbox02/bitbox02/communication/generated/hww_pb2.py
+++ b/py/bitbox02/bitbox02/communication/generated/hww_pb2.py
@@ -14,6 +14,7 @@ _sym_db = _symbol_database.Default()
from . import common_pb2 as common__pb2
from . import backup_commands_pb2 as backup__commands__pb2
from . import bitbox02_system_pb2 as bitbox02__system__pb2
+from . import bitboxsync_pb2 as bitboxsync__pb2
from . import bluetooth_pb2 as bluetooth__pb2
from . import btc_pb2 as btc__pb2
from . import cardano_pb2 as cardano__pb2
@@ -24,19 +25,19 @@ from . import system_pb2 as system__pb2
from . import perform_attestation_pb2 as perform__attestation__pb2
-DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\thww.proto\x12\x14shiftcrypto.bitbox02\x1a\x0c\x63ommon.proto\x1a\x15\x62\x61\x63kup_commands.proto\x1a\x15\x62itbox02_system.proto\x1a\x0f\x62luetooth.proto\x1a\tbtc.proto\x1a\rcardano.proto\x1a\teth.proto\x1a\x0ekeystore.proto\x1a\x0emnemonic.proto\x1a\x0csystem.proto\x1a\x19perform_attestation.proto\"&\n\x05\x45rror\x12\x0c\n\x04\x63ode\x18\x01 \x01(\x05\x12\x0f\n\x07message\x18\x02 \x01(\t\"\t\n\x07Success\"\x82\x0f\n\x07Request\x12\x41\n\x0b\x64\x65vice_name\x18\x02 \x01(\x0b\x32*.shiftcrypto.bitbox02.SetDeviceNameRequestH\x00\x12I\n\x0f\x64\x65vice_language\x18\x03 \x01(\x0b\x32..shiftcrypto.bitbox02.SetDeviceLanguageRequestH\x00\x12>\n\x0b\x64\x65vice_info\x18\x04 \x01(\x0b\x32\'.shiftcrypto.bitbox02.DeviceInfoRequestH\x00\x12@\n\x0cset_password\x18\x05 \x01(\x0b\x32(.shiftcrypto.bitbox02.SetPasswordRequestH\x00\x12\x42\n\rcreate_backup\x18\x06 \x01(\x0b\x32).shiftcrypto.bitbox02.CreateBackupRequestH\x00\x12\x42\n\rshow_mnemonic\x18\x07 \x01(\x0b\x32).shiftcrypto.bitbox02.ShowMnemonicRequestH\x00\x12\x36\n\x07\x62tc_pub\x18\x08 \x01(\x0b\x32#.shiftcrypto.bitbox02.BTCPubRequestH\x00\x12\x41\n\rbtc_sign_init\x18\t \x01(\x0b\x32(.shiftcrypto.bitbox02.BTCSignInitRequestH\x00\x12\x43\n\x0e\x62tc_sign_input\x18\n \x01(\x0b\x32).shiftcrypto.bitbox02.BTCSignInputRequestH\x00\x12\x45\n\x0f\x62tc_sign_output\x18\x0b \x01(\x0b\x32*.shiftcrypto.bitbox02.BTCSignOutputRequestH\x00\x12O\n\x14insert_remove_sdcard\x18\x0c \x01(\x0b\x32/.shiftcrypto.bitbox02.InsertRemoveSDCardRequestH\x00\x12@\n\x0c\x63heck_sdcard\x18\r \x01(\x0b\x32(.shiftcrypto.bitbox02.CheckSDCardRequestH\x00\x12\x64\n\x1fset_mnemonic_passphrase_enabled\x18\x0e \x01(\x0b\x32\x39.shiftcrypto.bitbox02.SetMnemonicPassphraseEnabledRequestH\x00\x12@\n\x0clist_backups\x18\x0f \x01(\x0b\x32(.shiftcrypto.bitbox02.ListBackupsRequestH\x00\x12\x44\n\x0erestore_backup\x18\x10 \x01(\x0b\x32*.shiftcrypto.bitbox02.RestoreBackupRequestH\x00\x12N\n\x13perform_attestation\x18\x11 \x01(\x0b\x32/.shiftcrypto.bitbox02.PerformAttestationRequestH\x00\x12\x35\n\x06reboot\x18\x12 \x01(\x0b\x32#.shiftcrypto.bitbox02.RebootRequestH\x00\x12@\n\x0c\x63heck_backup\x18\x13 \x01(\x0b\x32(.shiftcrypto.bitbox02.CheckBackupRequestH\x00\x12/\n\x03\x65th\x18\x14 \x01(\x0b\x32 .shiftcrypto.bitbox02.ETHRequestH\x00\x12\x33\n\x05reset\x18\x15 \x01(\x0b\x32\".shiftcrypto.bitbox02.ResetRequestH\x00\x12Q\n\x15restore_from_mnemonic\x18\x16 \x01(\x0b\x32\x30.shiftcrypto.bitbox02.RestoreFromMnemonicRequestH\x00\x12\x43\n\x0b\x66ingerprint\x18\x18 \x01(\x0b\x32,.shiftcrypto.bitbox02.RootFingerprintRequestH\x00\x12/\n\x03\x62tc\x18\x19 \x01(\x0b\x32 .shiftcrypto.bitbox02.BTCRequestH\x00\x12U\n\x17\x65lectrum_encryption_key\x18\x1a \x01(\x0b\x32\x32.shiftcrypto.bitbox02.ElectrumEncryptionKeyRequestH\x00\x12\x37\n\x07\x63\x61rdano\x18\x1b \x01(\x0b\x32$.shiftcrypto.bitbox02.CardanoRequestH\x00\x12\x33\n\x05\x62ip85\x18\x1c \x01(\x0b\x32\".shiftcrypto.bitbox02.BIP85RequestH\x00\x12;\n\tbluetooth\x18\x1d \x01(\x0b\x32&.shiftcrypto.bitbox02.BluetoothRequestH\x00\x12\x46\n\x0f\x63hange_password\x18\x1e \x01(\x0b\x32+.shiftcrypto.bitbox02.ChangePasswordRequestH\x00\x42\t\n\x07requestJ\x04\x08\x01\x10\x02J\x04\x08\x17\x10\x18\"\xfd\x07\n\x08Response\x12\x30\n\x07success\x18\x01 \x01(\x0b\x32\x1d.shiftcrypto.bitbox02.SuccessH\x00\x12,\n\x05\x65rror\x18\x02 \x01(\x0b\x32\x1b.shiftcrypto.bitbox02.ErrorH\x00\x12?\n\x0b\x64\x65vice_info\x18\x04 \x01(\x0b\x32(.shiftcrypto.bitbox02.DeviceInfoResponseH\x00\x12\x30\n\x03pub\x18\x05 \x01(\x0b\x32!.shiftcrypto.bitbox02.PubResponseH\x00\x12\x42\n\rbtc_sign_next\x18\x06 \x01(\x0b\x32).shiftcrypto.bitbox02.BTCSignNextResponseH\x00\x12\x41\n\x0clist_backups\x18\x07 \x01(\x0b\x32).shiftcrypto.bitbox02.ListBackupsResponseH\x00\x12\x41\n\x0c\x63heck_backup\x18\x08 \x01(\x0b\x32).shiftcrypto.bitbox02.CheckBackupResponseH\x00\x12O\n\x13perform_attestation\x18\t \x01(\x0b\x32\x30.shiftcrypto.bitbox02.PerformAttestationResponseH\x00\x12\x41\n\x0c\x63heck_sdcard\x18\n \x01(\x0b\x32).shiftcrypto.bitbox02.CheckSDCardResponseH\x00\x12\x30\n\x03\x65th\x18\x0b \x01(\x0b\x32!.shiftcrypto.bitbox02.ETHResponseH\x00\x12\x44\n\x0b\x66ingerprint\x18\x0c \x01(\x0b\x32-.shiftcrypto.bitbox02.RootFingerprintResponseH\x00\x12\x30\n\x03\x62tc\x18\r \x01(\x0b\x32!.shiftcrypto.bitbox02.BTCResponseH\x00\x12V\n\x17\x65lectrum_encryption_key\x18\x0e \x01(\x0b\x32\x33.shiftcrypto.bitbox02.ElectrumEncryptionKeyResponseH\x00\x12\x38\n\x07\x63\x61rdano\x18\x0f \x01(\x0b\x32%.shiftcrypto.bitbox02.CardanoResponseH\x00\x12\x34\n\x05\x62ip85\x18\x10 \x01(\x0b\x32#.shiftcrypto.bitbox02.BIP85ResponseH\x00\x12<\n\tbluetooth\x18\x11 \x01(\x0b\x32\'.shiftcrypto.bitbox02.BluetoothResponseH\x00\x42\n\n\x08responseJ\x04\x08\x03\x10\x04\x62\x06proto3')
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\thww.proto\x12\x14shiftcrypto.bitbox02\x1a\x0c\x63ommon.proto\x1a\x15\x62\x61\x63kup_commands.proto\x1a\x15\x62itbox02_system.proto\x1a\x10\x62itboxsync.proto\x1a\x0f\x62luetooth.proto\x1a\tbtc.proto\x1a\rcardano.proto\x1a\teth.proto\x1a\x0ekeystore.proto\x1a\x0emnemonic.proto\x1a\x0csystem.proto\x1a\x19perform_attestation.proto\"&\n\x05\x45rror\x12\x0c\n\x04\x63ode\x18\x01 \x01(\x05\x12\x0f\n\x07message\x18\x02 \x01(\t\"\t\n\x07Success\"\xc2\x0f\n\x07Request\x12\x41\n\x0b\x64\x65vice_name\x18\x02 \x01(\x0b\x32*.shiftcrypto.bitbox02.SetDeviceNameRequestH\x00\x12I\n\x0f\x64\x65vice_language\x18\x03 \x01(\x0b\x32..shiftcrypto.bitbox02.SetDeviceLanguageRequestH\x00\x12>\n\x0b\x64\x65vice_info\x18\x04 \x01(\x0b\x32\'.shiftcrypto.bitbox02.DeviceInfoRequestH\x00\x12@\n\x0cset_password\x18\x05 \x01(\x0b\x32(.shiftcrypto.bitbox02.SetPasswordRequestH\x00\x12\x42\n\rcreate_backup\x18\x06 \x01(\x0b\x32).shiftcrypto.bitbox02.CreateBackupRequestH\x00\x12\x42\n\rshow_mnemonic\x18\x07 \x01(\x0b\x32).shiftcrypto.bitbox02.ShowMnemonicRequestH\x00\x12\x36\n\x07\x62tc_pub\x18\x08 \x01(\x0b\x32#.shiftcrypto.bitbox02.BTCPubRequestH\x00\x12\x41\n\rbtc_sign_init\x18\t \x01(\x0b\x32(.shiftcrypto.bitbox02.BTCSignInitRequestH\x00\x12\x43\n\x0e\x62tc_sign_input\x18\n \x01(\x0b\x32).shiftcrypto.bitbox02.BTCSignInputRequestH\x00\x12\x45\n\x0f\x62tc_sign_output\x18\x0b \x01(\x0b\x32*.shiftcrypto.bitbox02.BTCSignOutputRequestH\x00\x12O\n\x14insert_remove_sdcard\x18\x0c \x01(\x0b\x32/.shiftcrypto.bitbox02.InsertRemoveSDCardRequestH\x00\x12@\n\x0c\x63heck_sdcard\x18\r \x01(\x0b\x32(.shiftcrypto.bitbox02.CheckSDCardRequestH\x00\x12\x64\n\x1fset_mnemonic_passphrase_enabled\x18\x0e \x01(\x0b\x32\x39.shiftcrypto.bitbox02.SetMnemonicPassphraseEnabledRequestH\x00\x12@\n\x0clist_backups\x18\x0f \x01(\x0b\x32(.shiftcrypto.bitbox02.ListBackupsRequestH\x00\x12\x44\n\x0erestore_backup\x18\x10 \x01(\x0b\x32*.shiftcrypto.bitbox02.RestoreBackupRequestH\x00\x12N\n\x13perform_attestation\x18\x11 \x01(\x0b\x32/.shiftcrypto.bitbox02.PerformAttestationRequestH\x00\x12\x35\n\x06reboot\x18\x12 \x01(\x0b\x32#.shiftcrypto.bitbox02.RebootRequestH\x00\x12@\n\x0c\x63heck_backup\x18\x13 \x01(\x0b\x32(.shiftcrypto.bitbox02.CheckBackupRequestH\x00\x12/\n\x03\x65th\x18\x14 \x01(\x0b\x32 .shiftcrypto.bitbox02.ETHRequestH\x00\x12\x33\n\x05reset\x18\x15 \x01(\x0b\x32\".shiftcrypto.bitbox02.ResetRequestH\x00\x12Q\n\x15restore_from_mnemonic\x18\x16 \x01(\x0b\x32\x30.shiftcrypto.bitbox02.RestoreFromMnemonicRequestH\x00\x12\x43\n\x0b\x66ingerprint\x18\x18 \x01(\x0b\x32,.shiftcrypto.bitbox02.RootFingerprintRequestH\x00\x12/\n\x03\x62tc\x18\x19 \x01(\x0b\x32 .shiftcrypto.bitbox02.BTCRequestH\x00\x12U\n\x17\x65lectrum_encryption_key\x18\x1a \x01(\x0b\x32\x32.shiftcrypto.bitbox02.ElectrumEncryptionKeyRequestH\x00\x12\x37\n\x07\x63\x61rdano\x18\x1b \x01(\x0b\x32$.shiftcrypto.bitbox02.CardanoRequestH\x00\x12\x33\n\x05\x62ip85\x18\x1c \x01(\x0b\x32\".shiftcrypto.bitbox02.BIP85RequestH\x00\x12;\n\tbluetooth\x18\x1d \x01(\x0b\x32&.shiftcrypto.bitbox02.BluetoothRequestH\x00\x12\x46\n\x0f\x63hange_password\x18\x1e \x01(\x0b\x32+.shiftcrypto.bitbox02.ChangePasswordRequestH\x00\x12>\n\x0b\x62itbox_sync\x18\x1f \x01(\x0b\x32\'.shiftcrypto.bitbox02.BitBoxSyncRequestH\x00\x42\t\n\x07requestJ\x04\x08\x01\x10\x02J\x04\x08\x17\x10\x18\"\xbe\x08\n\x08Response\x12\x30\n\x07success\x18\x01 \x01(\x0b\x32\x1d.shiftcrypto.bitbox02.SuccessH\x00\x12,\n\x05\x65rror\x18\x02 \x01(\x0b\x32\x1b.shiftcrypto.bitbox02.ErrorH\x00\x12?\n\x0b\x64\x65vice_info\x18\x04 \x01(\x0b\x32(.shiftcrypto.bitbox02.DeviceInfoResponseH\x00\x12\x30\n\x03pub\x18\x05 \x01(\x0b\x32!.shiftcrypto.bitbox02.PubResponseH\x00\x12\x42\n\rbtc_sign_next\x18\x06 \x01(\x0b\x32).shiftcrypto.bitbox02.BTCSignNextResponseH\x00\x12\x41\n\x0clist_backups\x18\x07 \x01(\x0b\x32).shiftcrypto.bitbox02.ListBackupsResponseH\x00\x12\x41\n\x0c\x63heck_backup\x18\x08 \x01(\x0b\x32).shiftcrypto.bitbox02.CheckBackupResponseH\x00\x12O\n\x13perform_attestation\x18\t \x01(\x0b\x32\x30.shiftcrypto.bitbox02.PerformAttestationResponseH\x00\x12\x41\n\x0c\x63heck_sdcard\x18\n \x01(\x0b\x32).shiftcrypto.bitbox02.CheckSDCardResponseH\x00\x12\x30\n\x03\x65th\x18\x0b \x01(\x0b\x32!.shiftcrypto.bitbox02.ETHResponseH\x00\x12\x44\n\x0b\x66ingerprint\x18\x0c \x01(\x0b\x32-.shiftcrypto.bitbox02.RootFingerprintResponseH\x00\x12\x30\n\x03\x62tc\x18\r \x01(\x0b\x32!.shiftcrypto.bitbox02.BTCResponseH\x00\x12V\n\x17\x65lectrum_encryption_key\x18\x0e \x01(\x0b\x32\x33.shiftcrypto.bitbox02.ElectrumEncryptionKeyResponseH\x00\x12\x38\n\x07\x63\x61rdano\x18\x0f \x01(\x0b\x32%.shiftcrypto.bitbox02.CardanoResponseH\x00\x12\x34\n\x05\x62ip85\x18\x10 \x01(\x0b\x32#.shiftcrypto.bitbox02.BIP85ResponseH\x00\x12<\n\tbluetooth\x18\x11 \x01(\x0b\x32\'.shiftcrypto.bitbox02.BluetoothResponseH\x00\x12?\n\x0b\x62itbox_sync\x18\x12 \x01(\x0b\x32(.shiftcrypto.bitbox02.BitBoxSyncResponseH\x00\x42\n\n\x08responseJ\x04\x08\x03\x10\x04\x62\x06proto3')
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'hww_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
- _ERROR._serialized_start=222
- _ERROR._serialized_end=260
- _SUCCESS._serialized_start=262
- _SUCCESS._serialized_end=271
- _REQUEST._serialized_start=274
- _REQUEST._serialized_end=2196
- _RESPONSE._serialized_start=2199
- _RESPONSE._serialized_end=3220
+ _ERROR._serialized_start=240
+ _ERROR._serialized_end=278
+ _SUCCESS._serialized_start=280
+ _SUCCESS._serialized_end=289
+ _REQUEST._serialized_start=292
+ _REQUEST._serialized_end=2278
+ _RESPONSE._serialized_start=2281
+ _RESPONSE._serialized_end=3367
# @@protoc_insertion_point(module_scope)
diff --git a/py/bitbox02/bitbox02/communication/generated/hww_pb2.pyi b/py/bitbox02/bitbox02/communication/generated/hww_pb2.pyi
index 9747a40..8dfa36c 100644
--- a/py/bitbox02/bitbox02/communication/generated/hww_pb2.pyi
+++ b/py/bitbox02/bitbox02/communication/generated/hww_pb2.pyi
@@ -5,6 +5,7 @@ SPDX-License-Identifier: Apache-2.0"""
from . import backup_commands_pb2
from . import bitbox02_system_pb2
+from . import bitboxsync_pb2
from . import bluetooth_pb2
from . import btc_pb2
import builtins
@@ -81,6 +82,7 @@ class Request(google.protobuf.message.Message):
BIP85_FIELD_NUMBER: builtins.int
BLUETOOTH_FIELD_NUMBER: builtins.int
CHANGE_PASSWORD_FIELD_NUMBER: builtins.int
+ BITBOX_SYNC_FIELD_NUMBER: builtins.int
@property
def device_name(self) -> bitbox02_system_pb2.SetDeviceNameRequest:
"""removed: RandomNumberRequest random_number = 1;"""
@@ -141,6 +143,8 @@ class Request(google.protobuf.message.Message):
def bluetooth(self) -> bluetooth_pb2.BluetoothRequest: ...
@property
def change_password(self) -> bitbox02_system_pb2.ChangePasswordRequest: ...
+ @property
+ def bitbox_sync(self) -> bitboxsync_pb2.BitBoxSyncRequest: ...
def __init__(
self,
*,
@@ -172,10 +176,11 @@ class Request(google.protobuf.message.Message):
bip85: keystore_pb2.BIP85Request | None = ...,
bluetooth: bluetooth_pb2.BluetoothRequest | None = ...,
change_password: bitbox02_system_pb2.ChangePasswordRequest | None = ...,
+ bitbox_sync: bitboxsync_pb2.BitBoxSyncRequest | None = ...,
) -> None: ...
- def HasField(self, field_name: typing.Literal["bip85", b"bip85", "bluetooth", b"bluetooth", "btc", b"btc", "btc_pub", b"btc_pub", "btc_sign_init", b"btc_sign_init", "btc_sign_input", b"btc_sign_input", "btc_sign_output", b"btc_sign_output", "cardano", b"cardano", "change_password", b"change_password", "check_backup", b"check_backup", "check_sdcard", b"check_sdcard", "create_backup", b"create_backup", "device_info", b"device_info", "device_language", b"device_language", "device_name", b"device_name", "electrum_encryption_key", b"electrum_encryption_key", "eth", b"eth", "fingerprint", b"fingerprint", "insert_remove_sdcard", b"insert_remove_sdcard", "list_backups", b"list_backups", "perform_attestation", b"perform_attestation", "reboot", b"reboot", "request", b"request", "reset", b"reset", "restore_backup", b"restore_backup", "restore_from_mnemonic", b"restore_from_mnemonic", "set_mnemonic_passphrase_enabled", b"set_mnemonic_passphrase_enabled", "set_password", b"set_password", "show_mnemonic", b"show_mnemonic"]) -> builtins.bool: ...
- def ClearField(self, field_name: typing.Literal["bip85", b"bip85", "bluetooth", b"bluetooth", "btc", b"btc", "btc_pub", b"btc_pub", "btc_sign_init", b"btc_sign_init", "btc_sign_input", b"btc_sign_input", "btc_sign_output", b"btc_sign_output", "cardano", b"cardano", "change_password", b"change_password", "check_backup", b"check_backup", "check_sdcard", b"check_sdcard", "create_backup", b"create_backup", "device_info", b"device_info", "device_language", b"device_language", "device_name", b"device_name", "electrum_encryption_key", b"electrum_encryption_key", "eth", b"eth", "fingerprint", b"fingerprint", "insert_remove_sdcard", b"insert_remove_sdcard", "list_backups", b"list_backups", "perform_attestation", b"perform_attestation", "reboot", b"reboot", "request", b"request", "reset", b"reset", "restore_backup", b"restore_backup", "restore_from_mnemonic", b"restore_from_mnemonic", "set_mnemonic_passphrase_enabled", b"set_mnemonic_passphrase_enabled", "set_password", b"set_password", "show_mnemonic", b"show_mnemonic"]) -> None: ...
- def WhichOneof(self, oneof_group: typing.Literal["request", b"request"]) -> typing.Literal["device_name", "device_language", "device_info", "set_password", "create_backup", "show_mnemonic", "btc_pub", "btc_sign_init", "btc_sign_input", "btc_sign_output", "insert_remove_sdcard", "check_sdcard", "set_mnemonic_passphrase_enabled", "list_backups", "restore_backup", "perform_attestation", "reboot", "check_backup", "eth", "reset", "restore_from_mnemonic", "fingerprint", "btc", "electrum_encryption_key", "cardano", "bip85", "bluetooth", "change_password"] | None: ...
+ def HasField(self, field_name: typing.Literal["bip85", b"bip85", "bitbox_sync", b"bitbox_sync", "bluetooth", b"bluetooth", "btc", b"btc", "btc_pub", b"btc_pub", "btc_sign_init", b"btc_sign_init", "btc_sign_input", b"btc_sign_input", "btc_sign_output", b"btc_sign_output", "cardano", b"cardano", "change_password", b"change_password", "check_backup", b"check_backup", "check_sdcard", b"check_sdcard", "create_backup", b"create_backup", "device_info", b"device_info", "device_language", b"device_language", "device_name", b"device_name", "electrum_encryption_key", b"electrum_encryption_key", "eth", b"eth", "fingerprint", b"fingerprint", "insert_remove_sdcard", b"insert_remove_sdcard", "list_backups", b"list_backups", "perform_attestation", b"perform_attestation", "reboot", b"reboot", "request", b"request", "reset", b"reset", "restore_backup", b"restore_backup", "restore_from_mnemonic", b"restore_from_mnemonic", "set_mnemonic_passphrase_enabled", b"set_mnemonic_passphrase_enabled", "set_password", b"set_password", "show_mnemonic", b"show_mnemonic"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing.Literal["bip85", b"bip85", "bitbox_sync", b"bitbox_sync", "bluetooth", b"bluetooth", "btc", b"btc", "btc_pub", b"btc_pub", "btc_sign_init", b"btc_sign_init", "btc_sign_input", b"btc_sign_input", "btc_sign_output", b"btc_sign_output", "cardano", b"cardano", "change_password", b"change_password", "check_backup", b"check_backup", "check_sdcard", b"check_sdcard", "create_backup", b"create_backup", "device_info", b"device_info", "device_language", b"device_language", "device_name", b"device_name", "electrum_encryption_key", b"electrum_encryption_key", "eth", b"eth", "fingerprint", b"fingerprint", "insert_remove_sdcard", b"insert_remove_sdcard", "list_backups", b"list_backups", "perform_attestation", b"perform_attestation", "reboot", b"reboot", "request", b"request", "reset", b"reset", "restore_backup", b"restore_backup", "restore_from_mnemonic", b"restore_from_mnemonic", "set_mnemonic_passphrase_enabled", b"set_mnemonic_passphrase_enabled", "set_password", b"set_password", "show_mnemonic", b"show_mnemonic"]) -> None: ...
+ def WhichOneof(self, oneof_group: typing.Literal["request", b"request"]) -> typing.Literal["device_name", "device_language", "device_info", "set_password", "create_backup", "show_mnemonic", "btc_pub", "btc_sign_init", "btc_sign_input", "btc_sign_output", "insert_remove_sdcard", "check_sdcard", "set_mnemonic_passphrase_enabled", "list_backups", "restore_backup", "perform_attestation", "reboot", "check_backup", "eth", "reset", "restore_from_mnemonic", "fingerprint", "btc", "electrum_encryption_key", "cardano", "bip85", "bluetooth", "change_password", "bitbox_sync"] | None: ...
global___Request = Request
@@ -199,6 +204,7 @@ class Response(google.protobuf.message.Message):
CARDANO_FIELD_NUMBER: builtins.int
BIP85_FIELD_NUMBER: builtins.int
BLUETOOTH_FIELD_NUMBER: builtins.int
+ BITBOX_SYNC_FIELD_NUMBER: builtins.int
@property
def success(self) -> global___Success: ...
@property
@@ -233,6 +239,8 @@ class Response(google.protobuf.message.Message):
def bip85(self) -> keystore_pb2.BIP85Response: ...
@property
def bluetooth(self) -> bluetooth_pb2.BluetoothResponse: ...
+ @property
+ def bitbox_sync(self) -> bitboxsync_pb2.BitBoxSyncResponse: ...
def __init__(
self,
*,
@@ -252,9 +260,10 @@ class Response(google.protobuf.message.Message):
cardano: cardano_pb2.CardanoResponse | None = ...,
bip85: keystore_pb2.BIP85Response | None = ...,
bluetooth: bluetooth_pb2.BluetoothResponse | None = ...,
+ bitbox_sync: bitboxsync_pb2.BitBoxSyncResponse | None = ...,
) -> None: ...
- def HasField(self, field_name: typing.Literal["bip85", b"bip85", "bluetooth", b"bluetooth", "btc", b"btc", "btc_sign_next", b"btc_sign_next", "cardano", b"cardano", "check_backup", b"check_backup", "check_sdcard", b"check_sdcard", "device_info", b"device_info", "electrum_encryption_key", b"electrum_encryption_key", "error", b"error", "eth", b"eth", "fingerprint", b"fingerprint", "list_backups", b"list_backups", "perform_attestation", b"perform_attestation", "pub", b"pub", "response", b"response", "success", b"success"]) -> builtins.bool: ...
- def ClearField(self, field_name: typing.Literal["bip85", b"bip85", "bluetooth", b"bluetooth", "btc", b"btc", "btc_sign_next", b"btc_sign_next", "cardano", b"cardano", "check_backup", b"check_backup", "check_sdcard", b"check_sdcard", "device_info", b"device_info", "electrum_encryption_key", b"electrum_encryption_key", "error", b"error", "eth", b"eth", "fingerprint", b"fingerprint", "list_backups", b"list_backups", "perform_attestation", b"perform_attestation", "pub", b"pub", "response", b"response", "success", b"success"]) -> None: ...
- def WhichOneof(self, oneof_group: typing.Literal["response", b"response"]) -> typing.Literal["success", "error", "device_info", "pub", "btc_sign_next", "list_backups", "check_backup", "perform_attestation", "check_sdcard", "eth", "fingerprint", "btc", "electrum_encryption_key", "cardano", "bip85", "bluetooth"] | None: ...
+ def HasField(self, field_name: typing.Literal["bip85", b"bip85", "bitbox_sync", b"bitbox_sync", "bluetooth", b"bluetooth", "btc", b"btc", "btc_sign_next", b"btc_sign_next", "cardano", b"cardano", "check_backup", b"check_backup", "check_sdcard", b"check_sdcard", "device_info", b"device_info", "electrum_encryption_key", b"electrum_encryption_key", "error", b"error", "eth", b"eth", "fingerprint", b"fingerprint", "list_backups", b"list_backups", "perform_attestation", b"perform_attestation", "pub", b"pub", "response", b"response", "success", b"success"]) -> builtins.bool: ...
+ def ClearField(self, field_name: typing.Literal["bip85", b"bip85", "bitbox_sync", b"bitbox_sync", "bluetooth", b"bluetooth", "btc", b"btc", "btc_sign_next", b"btc_sign_next", "cardano", b"cardano", "check_backup", b"check_backup", "check_sdcard", b"check_sdcard", "device_info", b"device_info", "electrum_encryption_key", b"electrum_encryption_key", "error", b"error", "eth", b"eth", "fingerprint", b"fingerprint", "list_backups", b"list_backups", "perform_attestation", b"perform_attestation", "pub", b"pub", "response", b"response", "success", b"success"]) -> None: ...
+ def WhichOneof(self, oneof_group: typing.Literal["response", b"response"]) -> typing.Literal["success", "error", "device_info", "pub", "btc_sign_next", "list_backups", "check_backup", "perform_attestation", "check_sdcard", "eth", "fingerprint", "btc", "electrum_encryption_key", "cardano", "bip85", "bluetooth", "bitbox_sync"] | None: ...
global___Response = Response
diff --git a/py/bitbox02/setup.py b/py/bitbox02/setup.py
index 8f097d1..e9de8fc 100644
--- a/py/bitbox02/setup.py
+++ b/py/bitbox02/setup.py
@@ -49,6 +49,7 @@ setuptools.setup(
"backup_commands_pb2.pyi",
"bitbox02_system_pb2.pyi",
"bitboxbase_pb2.pyi",
+ "bitboxsync_pb2.pyi",
"btc_pb2.pyi",
"common_pb2.pyi",
"eth_pb2.pyi",
diff --git a/py/requirements.txt b/py/requirements.txt
index e5abd1b..fec0af0 100644
--- a/py/requirements.txt
+++ b/py/requirements.txt
@@ -5,3 +5,4 @@ bitbox02
requests
types-requests
rlp
+pyhpke>=0.6.4,<0.7
diff --git a/py/send_message.py b/py/send_message.py
index 642ac9d..ef07354 100755
--- a/py/send_message.py
+++ b/py/send_message.py
@@ -9,7 +9,7 @@ import argparse
import socket
import pprint
import sys
-from typing import List, Any, Optional, Callable, Union, Tuple, Sequence
+from typing import List, Any, Optional, Callable, Union, Tuple, Sequence, cast
import base64
import binascii
import textwrap
@@ -76,6 +76,16 @@ def ask_user(
return choices[ans - 1][1]
+BITBOXSYNC_CHALLENGE = bytes([0x10]) * 32
+BITBOXSYNC_NAMESPACE_ID = bytes(range(0x20, 0x30))
+BITBOXSYNC_INVITE_ID = bytes(range(0x30, 0x40))
+BITBOXSYNC_INVITE_SERVER_SECRET_HASH = bytes(range(0x40, 0x60))
+BITBOXSYNC_NAMESPACE_DEK = bytes(range(0x60, 0x80))
+BITBOXSYNC_EXPIRES_AT = 1893456000 # 2030-01-01 00:00:00 UTC
+BITBOXSYNC_SERVER_ORIGIN = "https://sync.example.com"
+BITBOXSYNC_MAX_ACCEPTED = 5
+
+
def _btc_demo_inputs_outputs(
bip44_account: int,
) -> Tuple[List[bitbox02.BTCInputType], List[bitbox02.BTCOutputType]]:
@@ -1585,6 +1595,110 @@ class SendMessage:
except UserAbortException:
eprint("Aborted by user")
+ @staticmethod
+ def _bitboxsync_wrap_namespace_dek(
+ recipient_public_key: bytes,
+ namespace_id: bytes,
+ namespace_dek: bytes,
+ ) -> bytes:
+ # pylint: disable=import-outside-toplevel
+ try:
+ from pyhpke import AEADId, CipherSuite, KDFId, KEMId
+ except ModuleNotFoundError as err:
+ raise RuntimeError(
+ "pyhpke is required to generate BitBoxSync wrapped DEK test data"
+ ) from err
+
+ suite = CipherSuite.new(
+ KEMId.DHKEM_X25519_HKDF_SHA256,
+ KDFId.HKDF_SHA256,
+ AEADId.CHACHA20_POLY1305,
+ )
+ recipient_public = suite.kem.deserialize_public_key(recipient_public_key)
+ enc, sender = suite.create_sender_context(
+ recipient_public,
+ info=b"bitboxsync-wrap-dek-v1",
+ )
+ # TODO: Add pyhpke to the Dockerfile and remove these casts.
+ enc = cast(bytes, enc)
+ ciphertext = cast(bytes, sender.seal(namespace_id + namespace_dek))
+ return cast(bytes, b"\x01" + enc + ciphertext)
+
+ def _bitboxsync_print_identity(self) -> None:
+ response = self._device.bitboxsync_identity()
+ print("Auth public key:", response["auth_public_key"].hex())
+ print("Wrap public key:", response["wrap_public_key"].hex())
+
+ def _bitboxsync_sign_login_intent(self) -> None:
+ signature = self._device.bitboxsync_sign_login_intent(BITBOXSYNC_CHALLENGE)
+ print("Login signature:", signature.hex())
+
+ def _bitboxsync_sign_refresh_intent(self) -> None:
+ signature = self._device.bitboxsync_sign_refresh_intent(BITBOXSYNC_CHALLENGE)
+ print("Refresh signature:", signature.hex())
+
+ def _bitboxsync_sign_revoke_all_tokens_intent(self) -> None:
+ signature = self._device.bitboxsync_sign_revoke_all_tokens_intent(BITBOXSYNC_CHALLENGE)
+ print("Revoke all tokens signature:", signature.hex())
+
+ def _bitboxsync_sign_create_namespace_invite_intent(self) -> None:
+ signature = self._device.bitboxsync_sign_create_namespace_invite_intent(
+ BITBOXSYNC_CHALLENGE,
+ BITBOXSYNC_NAMESPACE_ID,
+ BITBOXSYNC_INVITE_ID,
+ BITBOXSYNC_INVITE_SERVER_SECRET_HASH,
+ BITBOXSYNC_EXPIRES_AT,
+ BITBOXSYNC_MAX_ACCEPTED,
+ )
+ print("Create namespace invite signature:", signature.hex())
+
+ def _bitboxsync_sign_join_request_intent(self) -> None:
+ signature = self._device.bitboxsync_sign_join_request_intent(
+ BITBOXSYNC_NAMESPACE_ID,
+ BITBOXSYNC_INVITE_ID,
+ BITBOXSYNC_SERVER_ORIGIN,
+ BITBOXSYNC_EXPIRES_AT,
+ )
+ print("Join request signature:", signature.hex())
+
+ def _bitboxsync_unwrap_namespace_dek(self) -> None:
+ identity = self._device.bitboxsync_identity()
+ wrapped_dek = self._bitboxsync_wrap_namespace_dek(
+ identity["wrap_public_key"],
+ BITBOXSYNC_NAMESPACE_ID,
+ BITBOXSYNC_NAMESPACE_DEK,
+ )
+ namespace_dek = self._device.bitboxsync_unwrap_namespace_dek(
+ BITBOXSYNC_NAMESPACE_ID,
+ wrapped_dek,
+ )
+ print("Unwrapped namespace DEK:", namespace_dek.hex())
+ if namespace_dek != BITBOXSYNC_NAMESPACE_DEK:
+ raise Exception("Unexpected namespace DEK returned")
+
+ def _bitboxsync(self) -> None:
+ choices = (
+ ("Identity", self._bitboxsync_print_identity),
+ ("Sign login intent", self._bitboxsync_sign_login_intent),
+ ("Sign refresh intent", self._bitboxsync_sign_refresh_intent),
+ (
+ "Sign revoke all tokens intent",
+ self._bitboxsync_sign_revoke_all_tokens_intent,
+ ),
+ (
+ "Sign create namespace invite intent",
+ self._bitboxsync_sign_create_namespace_invite_intent,
+ ),
+ ("Sign join request intent", self._bitboxsync_sign_join_request_intent),
+ ("Unwrap namespace DEK", self._bitboxsync_unwrap_namespace_dek),
+ )
+ choice = ask_user(choices)
+ if callable(choice):
+ try:
+ choice()
+ except UserAbortException:
+ eprint("Aborted by user")
+
def _menu_notinit(self) -> None:
"""TODO: Document
@@ -1650,6 +1764,7 @@ class SendMessage:
("BIP85 - LN", self._bip85_ln),
("Upgrade Bluetooth firmware", self._bluetooth_upgrade),
("Toggle bluetooth", self._bluetooth_toggle_enabled),
+ ("BitBoxSync", self._bitboxsync),
("Reset Device", self._reset_device),
("Change Password", self._change_password_workflow),
)
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.