Skip to content

Commit bbfcbb7

Browse files
committed
Merge branch 'many-xpubs'
2 parents 818835a + 0334a51 commit bbfcbb7

File tree

15 files changed

+526
-75
lines changed

15 files changed

+526
-75
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ customers cannot upgrade their bootloader, its changes are recorded separately.
1212
- Remove option to restore from 18 recovery words
1313
- simulator: enable Test Merchant for payment requests
1414
- simulator: simulate a Nova device
15+
- Add API call to fetch multiple xpubs at once
1516

1617
### 9.23.1
1718
- EVM: add HyperEVM (HYPE) and SONIC (S) to known networks

messages/btc.proto

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,17 @@ message BTCPubRequest {
9393
bool display = 5;
9494
}
9595

96+
message BTCXpubsRequest{
97+
enum XPubType {
98+
UNKNOWN = 0;
99+
XPUB = 1;
100+
TPUB = 2;
101+
}
102+
BTCCoin coin = 1;
103+
XPubType xpub_type = 2;
104+
repeated Keypath keypaths = 3;
105+
}
106+
96107
message BTCScriptConfigWithKeypath {
97108
BTCScriptConfig script_config = 2;
98109
repeated uint32 keypath = 3;
@@ -281,6 +292,7 @@ message BTCRequest {
281292
BTCSignMessageRequest sign_message = 6;
282293
AntiKleptoSignatureRequest antiklepto_signature = 7;
283294
BTCPaymentRequestRequest payment_request = 8;
295+
BTCXpubsRequest xpubs = 9;
284296
}
285297
}
286298

@@ -291,5 +303,6 @@ message BTCResponse {
291303
BTCSignNextResponse sign_next = 3;
292304
BTCSignMessageResponse sign_message = 4;
293305
AntiKleptoSignerCommitment antiklepto_signer_commitment = 5;
306+
PubsResponse pubs = 6;
294307
}
295308
}

messages/common.proto

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ message PubResponse {
1919
string pub = 1;
2020
}
2121

22+
message PubsResponse {
23+
repeated string pubs = 1;
24+
}
25+
2226
message RootFingerprintRequest {
2327
}
2428

py/bitbox02/bitbox02/bitbox02/bitbox02.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,26 @@ def btc_xpub(
320320
)
321321
return self._msg_query(request).pub.pub
322322

323+
def btc_xpubs(
324+
self,
325+
keypaths: Sequence[Sequence[int]],
326+
coin: "btc.BTCCoin.V" = btc.BTC,
327+
xpub_type: "btc.BTCXpubsRequest.XPubType.V" = btc.BTCXpubsRequest.XPUB,
328+
) -> List[str]:
329+
"""
330+
Retrieve up to 20 xpubs.
331+
"""
332+
self._require_atleast(semver.VersionInfo(9, 24, 0))
333+
btc_request = btc.BTCRequest()
334+
btc_request.xpubs.CopyFrom(
335+
btc.BTCXpubsRequest(
336+
coin=coin,
337+
keypaths=[common.Keypath(keypath=keypath) for keypath in keypaths],
338+
xpub_type=xpub_type,
339+
)
340+
)
341+
return list(self._btc_msg_query(btc_request, expected_response="pubs").pubs.pubs)
342+
323343
def btc_address(
324344
self,
325345
keypath: Sequence[int],

py/bitbox02/bitbox02/communication/generated/bitbox02_system_pb2.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class DeviceInfoResponse(google.protobuf.message.Message):
7979
firmware_hash: builtins.bytes
8080
"""Hash of the currently active Bluetooth firmware on the device."""
8181
firmware_version: builtins.str
82-
"""Firmware version, formated as "major.minor.patch"."""
82+
"""Firmware version, formated as an unsigned integer "1", "2", etc."""
8383
enabled: builtins.bool
8484
"""True if Bluetooth is enabled"""
8585
def __init__(

py/bitbox02/bitbox02/communication/generated/btc_pb2.py

Lines changed: 57 additions & 53 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

py/bitbox02/bitbox02/communication/generated/btc_pb2.pyi

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,43 @@ class BTCPubRequest(google.protobuf.message.Message):
263263

264264
global___BTCPubRequest = BTCPubRequest
265265

266+
@typing.final
267+
class BTCXpubsRequest(google.protobuf.message.Message):
268+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
269+
270+
class _XPubType:
271+
ValueType = typing.NewType("ValueType", builtins.int)
272+
V: typing_extensions.TypeAlias = ValueType
273+
274+
class _XPubTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[BTCXpubsRequest._XPubType.ValueType], builtins.type):
275+
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
276+
UNKNOWN: BTCXpubsRequest._XPubType.ValueType # 0
277+
XPUB: BTCXpubsRequest._XPubType.ValueType # 1
278+
TPUB: BTCXpubsRequest._XPubType.ValueType # 2
279+
280+
class XPubType(_XPubType, metaclass=_XPubTypeEnumTypeWrapper): ...
281+
UNKNOWN: BTCXpubsRequest.XPubType.ValueType # 0
282+
XPUB: BTCXpubsRequest.XPubType.ValueType # 1
283+
TPUB: BTCXpubsRequest.XPubType.ValueType # 2
284+
285+
COIN_FIELD_NUMBER: builtins.int
286+
XPUB_TYPE_FIELD_NUMBER: builtins.int
287+
KEYPATHS_FIELD_NUMBER: builtins.int
288+
coin: global___BTCCoin.ValueType
289+
xpub_type: global___BTCXpubsRequest.XPubType.ValueType
290+
@property
291+
def keypaths(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[common_pb2.Keypath]: ...
292+
def __init__(
293+
self,
294+
*,
295+
coin: global___BTCCoin.ValueType = ...,
296+
xpub_type: global___BTCXpubsRequest.XPubType.ValueType = ...,
297+
keypaths: collections.abc.Iterable[common_pb2.Keypath] | None = ...,
298+
) -> None: ...
299+
def ClearField(self, field_name: typing.Literal["coin", b"coin", "keypaths", b"keypaths", "xpub_type", b"xpub_type"]) -> None: ...
300+
301+
global___BTCXpubsRequest = BTCXpubsRequest
302+
266303
@typing.final
267304
class BTCScriptConfigWithKeypath(google.protobuf.message.Message):
268305
DESCRIPTOR: google.protobuf.descriptor.Descriptor
@@ -826,6 +863,7 @@ class BTCRequest(google.protobuf.message.Message):
826863
SIGN_MESSAGE_FIELD_NUMBER: builtins.int
827864
ANTIKLEPTO_SIGNATURE_FIELD_NUMBER: builtins.int
828865
PAYMENT_REQUEST_FIELD_NUMBER: builtins.int
866+
XPUBS_FIELD_NUMBER: builtins.int
829867
@property
830868
def is_script_config_registered(self) -> global___BTCIsScriptConfigRegisteredRequest: ...
831869
@property
@@ -842,6 +880,8 @@ class BTCRequest(google.protobuf.message.Message):
842880
def antiklepto_signature(self) -> antiklepto_pb2.AntiKleptoSignatureRequest: ...
843881
@property
844882
def payment_request(self) -> global___BTCPaymentRequestRequest: ...
883+
@property
884+
def xpubs(self) -> global___BTCXpubsRequest: ...
845885
def __init__(
846886
self,
847887
*,
@@ -853,10 +893,11 @@ class BTCRequest(google.protobuf.message.Message):
853893
sign_message: global___BTCSignMessageRequest | None = ...,
854894
antiklepto_signature: antiklepto_pb2.AntiKleptoSignatureRequest | None = ...,
855895
payment_request: global___BTCPaymentRequestRequest | None = ...,
896+
xpubs: global___BTCXpubsRequest | None = ...,
856897
) -> None: ...
857-
def HasField(self, field_name: typing.Literal["antiklepto_signature", b"antiklepto_signature", "is_script_config_registered", b"is_script_config_registered", "payment_request", b"payment_request", "prevtx_init", b"prevtx_init", "prevtx_input", b"prevtx_input", "prevtx_output", b"prevtx_output", "register_script_config", b"register_script_config", "request", b"request", "sign_message", b"sign_message"]) -> builtins.bool: ...
858-
def ClearField(self, field_name: typing.Literal["antiklepto_signature", b"antiklepto_signature", "is_script_config_registered", b"is_script_config_registered", "payment_request", b"payment_request", "prevtx_init", b"prevtx_init", "prevtx_input", b"prevtx_input", "prevtx_output", b"prevtx_output", "register_script_config", b"register_script_config", "request", b"request", "sign_message", b"sign_message"]) -> None: ...
859-
def WhichOneof(self, oneof_group: typing.Literal["request", b"request"]) -> typing.Literal["is_script_config_registered", "register_script_config", "prevtx_init", "prevtx_input", "prevtx_output", "sign_message", "antiklepto_signature", "payment_request"] | None: ...
898+
def HasField(self, field_name: typing.Literal["antiklepto_signature", b"antiklepto_signature", "is_script_config_registered", b"is_script_config_registered", "payment_request", b"payment_request", "prevtx_init", b"prevtx_init", "prevtx_input", b"prevtx_input", "prevtx_output", b"prevtx_output", "register_script_config", b"register_script_config", "request", b"request", "sign_message", b"sign_message", "xpubs", b"xpubs"]) -> builtins.bool: ...
899+
def ClearField(self, field_name: typing.Literal["antiklepto_signature", b"antiklepto_signature", "is_script_config_registered", b"is_script_config_registered", "payment_request", b"payment_request", "prevtx_init", b"prevtx_init", "prevtx_input", b"prevtx_input", "prevtx_output", b"prevtx_output", "register_script_config", b"register_script_config", "request", b"request", "sign_message", b"sign_message", "xpubs", b"xpubs"]) -> None: ...
900+
def WhichOneof(self, oneof_group: typing.Literal["request", b"request"]) -> typing.Literal["is_script_config_registered", "register_script_config", "prevtx_init", "prevtx_input", "prevtx_output", "sign_message", "antiklepto_signature", "payment_request", "xpubs"] | None: ...
860901

861902
global___BTCRequest = BTCRequest
862903

@@ -869,6 +910,7 @@ class BTCResponse(google.protobuf.message.Message):
869910
SIGN_NEXT_FIELD_NUMBER: builtins.int
870911
SIGN_MESSAGE_FIELD_NUMBER: builtins.int
871912
ANTIKLEPTO_SIGNER_COMMITMENT_FIELD_NUMBER: builtins.int
913+
PUBS_FIELD_NUMBER: builtins.int
872914
@property
873915
def success(self) -> global___BTCSuccess: ...
874916
@property
@@ -879,6 +921,8 @@ class BTCResponse(google.protobuf.message.Message):
879921
def sign_message(self) -> global___BTCSignMessageResponse: ...
880922
@property
881923
def antiklepto_signer_commitment(self) -> antiklepto_pb2.AntiKleptoSignerCommitment: ...
924+
@property
925+
def pubs(self) -> common_pb2.PubsResponse: ...
882926
def __init__(
883927
self,
884928
*,
@@ -887,9 +931,10 @@ class BTCResponse(google.protobuf.message.Message):
887931
sign_next: global___BTCSignNextResponse | None = ...,
888932
sign_message: global___BTCSignMessageResponse | None = ...,
889933
antiklepto_signer_commitment: antiklepto_pb2.AntiKleptoSignerCommitment | None = ...,
934+
pubs: common_pb2.PubsResponse | None = ...,
890935
) -> None: ...
891-
def HasField(self, field_name: typing.Literal["antiklepto_signer_commitment", b"antiklepto_signer_commitment", "is_script_config_registered", b"is_script_config_registered", "response", b"response", "sign_message", b"sign_message", "sign_next", b"sign_next", "success", b"success"]) -> builtins.bool: ...
892-
def ClearField(self, field_name: typing.Literal["antiklepto_signer_commitment", b"antiklepto_signer_commitment", "is_script_config_registered", b"is_script_config_registered", "response", b"response", "sign_message", b"sign_message", "sign_next", b"sign_next", "success", b"success"]) -> None: ...
893-
def WhichOneof(self, oneof_group: typing.Literal["response", b"response"]) -> typing.Literal["success", "is_script_config_registered", "sign_next", "sign_message", "antiklepto_signer_commitment"] | None: ...
936+
def HasField(self, field_name: typing.Literal["antiklepto_signer_commitment", b"antiklepto_signer_commitment", "is_script_config_registered", b"is_script_config_registered", "pubs", b"pubs", "response", b"response", "sign_message", b"sign_message", "sign_next", b"sign_next", "success", b"success"]) -> builtins.bool: ...
937+
def ClearField(self, field_name: typing.Literal["antiklepto_signer_commitment", b"antiklepto_signer_commitment", "is_script_config_registered", b"is_script_config_registered", "pubs", b"pubs", "response", b"response", "sign_message", b"sign_message", "sign_next", b"sign_next", "success", b"success"]) -> None: ...
938+
def WhichOneof(self, oneof_group: typing.Literal["response", b"response"]) -> typing.Literal["success", "is_script_config_registered", "sign_next", "sign_message", "antiklepto_signer_commitment", "pubs"] | None: ...
894939

895940
global___BTCResponse = BTCResponse

py/bitbox02/bitbox02/communication/generated/common_pb2.py

Lines changed: 13 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

py/bitbox02/bitbox02/communication/generated/common_pb2.pyi

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,22 @@ class PubResponse(google.protobuf.message.Message):
4040

4141
global___PubResponse = PubResponse
4242

43+
@typing.final
44+
class PubsResponse(google.protobuf.message.Message):
45+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
46+
47+
PUBS_FIELD_NUMBER: builtins.int
48+
@property
49+
def pubs(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ...
50+
def __init__(
51+
self,
52+
*,
53+
pubs: collections.abc.Iterable[builtins.str] | None = ...,
54+
) -> None: ...
55+
def ClearField(self, field_name: typing.Literal["pubs", b"pubs"]) -> None: ...
56+
57+
global___PubsResponse = PubsResponse
58+
4359
@typing.final
4460
class RootFingerprintRequest(google.protobuf.message.Message):
4561
DESCRIPTOR: google.protobuf.descriptor.Descriptor

py/send_message.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,14 @@ def _display_zpub(self) -> None:
331331
except UserAbortException:
332332
eprint("Aborted by user")
333333

334+
def _btc_xpubs(self) -> None:
335+
xpubs = self._device.btc_xpubs(
336+
keypaths=[[84 + HARDENED, 0 + HARDENED, i + HARDENED] for i in range(20)],
337+
)
338+
print("xpubs for m/84'/0'/{0'..19'}:")
339+
for xpub in xpubs:
340+
print(xpub)
341+
334342
def _get_electrum_encryption_key(self) -> None:
335343
print(
336344
"Electrum wallet encryption xpub at keypath m/4541509'/1112098098':",
@@ -376,7 +384,7 @@ def _btc_multisig_config(self, coin: "bitbox02.btc.BTCCoin.V") -> bitbox02.btc.B
376384
my_xpub = self._device.btc_xpub(
377385
keypath=account_keypath,
378386
coin=coin,
379-
xpub_type=bitbox02.btc.BTCPubRequest.XPUB, # pylint: disable=no-member,
387+
xpub_type=bitbox02.btc.BTCPubRequest.XPUB,
380388
display=False,
381389
)
382390
multisig_config = bitbox02.btc.BTCScriptConfig(
@@ -1503,6 +1511,7 @@ def _menu_init(self) -> None:
15031511
("Change device name", self._change_name_workflow),
15041512
("Get root fingerprint", self._get_root_fingerprint),
15051513
("Retrieve zpub of first account", self._display_zpub),
1514+
("Retrieve multiple xpubs", self._btc_xpubs),
15061515
("Retrieve a BTC address", self._btc_address),
15071516
("Retrieve a BTC Multisig address", self._btc_multisig_address),
15081517
("Retrieve a BTC policy address", self._btc_policy_address),

0 commit comments

Comments
 (0)