Skip to content

Commit 4d2434f

Browse files
committed
Merge commit 'c7e091ce'
2 parents b7735aa + c7e091c commit 4d2434f

File tree

15 files changed

+264
-31
lines changed

15 files changed

+264
-31
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ customers cannot upgrade their bootloader, its changes are recorded separately.
99
### [Unreleased]
1010
- Add support for deriving BIP-39 mnemonics according to BIP-85
1111

12+
### 9.17.0
13+
- Add support for deriving mnemonics for Lightning hot wallets according to BIP-85 (using a custom
14+
BIP-85 application number)
15+
1216
### 9.16.0
1317
- Disable screensaver when displaying a receive address, confirming a transaction, and other interactive actions
1418
- Add Sepolia testnet for Ethereum

CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@ endif()
9696
#
9797
# Versions MUST contain three parts and start with lowercase 'v'.
9898
# Example 'v1.0.0'. They MUST not contain a pre-release label such as '-beta'.
99-
set(FIRMWARE_VERSION "v9.16.0")
100-
set(FIRMWARE_BTC_ONLY_VERSION "v9.16.0")
99+
set(FIRMWARE_VERSION "v9.17.0")
100+
set(FIRMWARE_BTC_ONLY_VERSION "v9.17.0")
101101
set(BOOTLOADER_VERSION "v1.0.5")
102102

103103
find_package(PythonInterp 3.6 REQUIRED)

messages/keystore.proto

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
syntax = "proto3";
55
package shiftcrypto.bitbox02;
66

7+
import "google/protobuf/empty.proto";
8+
79
message ElectrumEncryptionKeyRequest {
810
repeated uint32 keypath = 1;
911
}
@@ -13,7 +15,19 @@ message ElectrumEncryptionKeyResponse {
1315
}
1416

1517
message BIP85Request {
18+
message AppLn {
19+
uint32 account_number = 1;
20+
}
21+
22+
oneof app {
23+
google.protobuf.Empty bip39 = 1;
24+
AppLn ln = 2;
25+
}
1626
}
1727

1828
message BIP85Response {
29+
oneof app {
30+
google.protobuf.Empty bip39 = 1;
31+
bytes ln = 2;
32+
}
1933
}

py/bitbox02/bitbox02/bitbox02/bitbox02.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
from bitbox02.communication.generated import common_pb2 as common
4444
from bitbox02.communication.generated import keystore_pb2 as keystore
4545
from bitbox02.communication.generated import antiklepto_pb2 as antiklepto
46+
import google.protobuf.empty_pb2
4647

4748
# pylint: disable=unused-import
4849
# We export it in __init__.py
@@ -678,14 +679,39 @@ def electrum_encryption_key(self, keypath: Sequence[int]) -> str:
678679
)
679680
return self._msg_query(request).electrum_encryption_key.key
680681

681-
def bip85(self) -> None:
682-
"""Invokes the BIP-85 workflow on the device"""
683-
self._require_atleast(semver.VersionInfo(9, 16, 0))
682+
def bip85_bip39(self) -> None:
683+
"""Invokes the BIP85-BIP39 workflow on the device"""
684+
self._require_atleast(semver.VersionInfo(9, 17, 0))
684685

685686
# pylint: disable=no-member
686687
request = hww.Request()
687-
request.bip85.CopyFrom(keystore.BIP85Request())
688-
self._msg_query(request)
688+
request.bip85.CopyFrom(
689+
keystore.BIP85Request(
690+
bip39=google.protobuf.empty_pb2.Empty(),
691+
)
692+
)
693+
response = self._msg_query(request, expected_response="bip85").bip85
694+
assert response.WhichOneof("app") == "bip39"
695+
696+
def bip85_ln(self) -> bytes:
697+
"""
698+
Generates and returns a mnemonic for a hot Lightning wallet from the device using BIP-85.
699+
"""
700+
self._require_atleast(semver.VersionInfo(9, 17, 0))
701+
702+
# Only account_number=0 is allowed for now.
703+
account_number = 0
704+
705+
# pylint: disable=no-member
706+
request = hww.Request()
707+
request.bip85.CopyFrom(
708+
keystore.BIP85Request(
709+
ln=keystore.BIP85Request.AppLn(account_number=account_number),
710+
)
711+
)
712+
response = self._msg_query(request, expected_response="bip85").bip85
713+
assert response.WhichOneof("app") == "ln"
714+
return response.ln
689715

690716
def enable_mnemonic_passphrase(self) -> None:
691717
"""

py/bitbox02/bitbox02/communication/generated/keystore_pb2.py

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

py/bitbox02/bitbox02/communication/generated/keystore_pb2.pyi

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ isort:skip_file
44
"""
55
import builtins
66
import google.protobuf.descriptor
7+
import google.protobuf.empty_pb2
78
import google.protobuf.internal.containers
89
import google.protobuf.message
910
import typing
@@ -36,12 +37,45 @@ global___ElectrumEncryptionKeyResponse = ElectrumEncryptionKeyResponse
3637

3738
class BIP85Request(google.protobuf.message.Message):
3839
DESCRIPTOR: google.protobuf.descriptor.Descriptor
40+
class AppLn(google.protobuf.message.Message):
41+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
42+
ACCOUNT_NUMBER_FIELD_NUMBER: builtins.int
43+
account_number: builtins.int
44+
def __init__(self,
45+
*,
46+
account_number: builtins.int = ...,
47+
) -> None: ...
48+
def ClearField(self, field_name: typing_extensions.Literal["account_number",b"account_number"]) -> None: ...
49+
50+
BIP39_FIELD_NUMBER: builtins.int
51+
LN_FIELD_NUMBER: builtins.int
52+
@property
53+
def bip39(self) -> google.protobuf.empty_pb2.Empty: ...
54+
@property
55+
def ln(self) -> global___BIP85Request.AppLn: ...
3956
def __init__(self,
57+
*,
58+
bip39: typing.Optional[google.protobuf.empty_pb2.Empty] = ...,
59+
ln: typing.Optional[global___BIP85Request.AppLn] = ...,
4060
) -> None: ...
61+
def HasField(self, field_name: typing_extensions.Literal["app",b"app","bip39",b"bip39","ln",b"ln"]) -> builtins.bool: ...
62+
def ClearField(self, field_name: typing_extensions.Literal["app",b"app","bip39",b"bip39","ln",b"ln"]) -> None: ...
63+
def WhichOneof(self, oneof_group: typing_extensions.Literal["app",b"app"]) -> typing.Optional[typing_extensions.Literal["bip39","ln"]]: ...
4164
global___BIP85Request = BIP85Request
4265

4366
class BIP85Response(google.protobuf.message.Message):
4467
DESCRIPTOR: google.protobuf.descriptor.Descriptor
68+
BIP39_FIELD_NUMBER: builtins.int
69+
LN_FIELD_NUMBER: builtins.int
70+
@property
71+
def bip39(self) -> google.protobuf.empty_pb2.Empty: ...
72+
ln: builtins.bytes
4573
def __init__(self,
74+
*,
75+
bip39: typing.Optional[google.protobuf.empty_pb2.Empty] = ...,
76+
ln: builtins.bytes = ...,
4677
) -> None: ...
78+
def HasField(self, field_name: typing_extensions.Literal["app",b"app","bip39",b"bip39","ln",b"ln"]) -> builtins.bool: ...
79+
def ClearField(self, field_name: typing_extensions.Literal["app",b"app","bip39",b"bip39","ln",b"ln"]) -> None: ...
80+
def WhichOneof(self, oneof_group: typing_extensions.Literal["app",b"app"]) -> typing.Optional[typing_extensions.Literal["bip39","ln"]]: ...
4781
global___BIP85Response = BIP85Response

py/send_message.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,18 @@ def _get_electrum_encryption_key(self) -> None:
340340
),
341341
)
342342

343-
def _bip85(self) -> None:
344-
self._device.bip85()
343+
def _bip85_bip39(self) -> None:
344+
try:
345+
self._device.bip85_bip39()
346+
except UserAbortException:
347+
print("Aborted by user")
348+
349+
def _bip85_ln(self) -> None:
350+
try:
351+
entropy = self._device.bip85_ln()
352+
print("Derived entropy for a Breez Lightning wallet:", entropy.hex())
353+
except UserAbortException:
354+
print("Aborted by user")
345355

346356
def _btc_address(self) -> None:
347357
def address(display: bool) -> str:
@@ -1391,7 +1401,8 @@ def _menu_init(self) -> None:
13911401
("Sign Ethereum Typed Message (EIP-712)", self._sign_eth_typed_message),
13921402
("Cardano", self._cardano),
13931403
("Show Electrum wallet encryption key", self._get_electrum_encryption_key),
1394-
("BIP85", self._bip85),
1404+
("BIP85 - BIP39", self._bip85_bip39),
1405+
("BIP85 - LN", self._bip85_ln),
13951406
("Reset Device", self._reset_device),
13961407
)
13971408
choice = ask_user(choices)

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ add_custom_target(rust-bindgen
330330
--allowlist-function keystore_get_bip39_word
331331
--allowlist-function keystore_get_ed25519_seed
332332
--allowlist-function keystore_bip85_bip39
333+
--allowlist-function keystore_bip85_ln
333334
--allowlist-function keystore_secp256k1_compressed_to_uncompressed
334335
--allowlist-function keystore_secp256k1_nonce_commit
335336
--allowlist-function keystore_secp256k1_sign

src/keystore.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,30 @@ bool keystore_bip85_bip39(
864864
return snprintf_result >= 0 && snprintf_result < (int)mnemonic_out_size;
865865
}
866866

867+
bool keystore_bip85_ln(uint32_t index, uint8_t* entropy_out)
868+
{
869+
if (index >= BIP32_INITIAL_HARDENED_CHILD) {
870+
return false;
871+
}
872+
873+
const uint32_t keypath[] = {
874+
83696968 + BIP32_INITIAL_HARDENED_CHILD,
875+
19534 + BIP32_INITIAL_HARDENED_CHILD,
876+
0 + BIP32_INITIAL_HARDENED_CHILD,
877+
12 + BIP32_INITIAL_HARDENED_CHILD,
878+
index + BIP32_INITIAL_HARDENED_CHILD,
879+
};
880+
881+
uint8_t entropy[64] = {0};
882+
UTIL_CLEANUP_64(entropy);
883+
if (!_bip85_entropy(keypath, sizeof(keypath) / sizeof(uint32_t), entropy)) {
884+
return false;
885+
}
886+
887+
memcpy(entropy_out, entropy, 16);
888+
return true;
889+
}
890+
867891
USE_RESULT bool keystore_encode_xpub_at_keypath(
868892
const uint32_t* keypath,
869893
size_t keypath_len,

src/keystore.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,16 @@ USE_RESULT bool keystore_bip85_bip39(
253253
char* mnemonic_out,
254254
size_t mnemonic_out_size);
255255

256+
/**
257+
* Computes a 16 byte deterministic seed specifically for Lightning hot wallets according to BIP-85.
258+
* It is the same as BIP-85 with app number 39', but instead using app number 19534' (= 0x4c4e =
259+
* 'LN'). https://github.com/bitcoin/bips/blob/master/bip-0085.mediawiki#bip39
260+
* Restricted to 16 byte output entropy.
261+
* @param[in] index must be smaller than `BIP32_INITIAL_HARDENED_CHILD`.
262+
* @param[out] entropy_out resulting entropy, must be at least 16 bytes in size.
263+
*/
264+
USE_RESULT bool keystore_bip85_ln(uint32_t index, uint8_t* entropy_out);
265+
256266
/**
257267
* Encode an xpub at the given `keypath` as 78 bytes according to BIP32. The version bytes are
258268
* the ones corresponding to `xpub`, i.e. 0x0488B21E.

0 commit comments

Comments
 (0)