Skip to content

Commit c48fb00

Browse files
committed
ethereum/keypath: allow Ledger Live keypaths for compatibility
BitBoxApp and MyEtherWallet use `m/60'/0'/0/account`, while Ledger Live uses `m/60'/account'/0/0`. Only for the first account they match. We allow Ledger Live paths for compatibility. See also: https://blog.ledger.com/understanding-crypto-addresses-and-derivation-paths/
1 parent 8658620 commit c48fb00

File tree

3 files changed

+115
-27
lines changed

3 files changed

+115
-27
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ customers cannot upgrade their bootloader, its changes are recorded separately.
88

99
### [Unreleased]
1010
- Improved security: keep seed encrypted in RAM
11+
- Allow additional keypaths for Ethereum for compatibility with Ledger Live
1112

1213
### 9.14.0
1314
- Improved touch button positional accuracy in noisy environments

CMakeLists.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ endif()
8888
#
8989
# Versions MUST contain three parts and start with lowercase 'v'.
9090
# Example 'v1.0.0'. They MUST not contain a pre-release label such as '-beta'.
91-
set(FIRMWARE_VERSION "v9.14.1")
92-
set(FIRMWARE_BTC_ONLY_VERSION "v9.14.1")
91+
set(FIRMWARE_VERSION "v9.15.0")
92+
set(FIRMWARE_BTC_ONLY_VERSION "v9.15.0")
9393
set(BOOTLOADER_VERSION "v1.0.5")
9494

9595
find_package(PythonInterp 3.6 REQUIRED)

src/rust/bitbox02-rust/src/hww/api/ethereum/keypath.rs

+112-25
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@ use crate::workflow::confirm;
1818

1919
use util::bip32::HARDENED;
2020

21+
const PURPOSE: u32 = 44 + HARDENED;
22+
const COIN_MAINNET: u32 = 60 + HARDENED;
23+
const COIN_TESTNET: u32 = 1 + HARDENED;
24+
2125
const ACCOUNT_MAX: u32 = 99; // 100 accounts
26+
const ACCOUNT_MIN_H: u32 = 0 + HARDENED;
27+
const ACCOUNT_MAX_H: u32 = ACCOUNT_MAX + HARDENED;
2228

2329
/// If the second element of `keypath` does not match the expected bip44 coin value for the given
2430
/// coin, we warn the user about an unusual keypath.
@@ -56,29 +62,30 @@ pub async fn warn_unusual_keypath(
5662
}
5763

5864
/// Does limit checks the keypath, whitelisting bip44 purpose, account and change.
59-
/// Only allows the well-known xpubs of m'/44'/60'/0'/0 and m'/44'/1'/0'/0 for now.
60-
/// Since ethereum doesn't use the "change" path part it is always 0 and have become part of the
61-
/// xpub keypath.
65+
/// Allows the following xpubs:
66+
/// For BitBoxApp, MyEtherWalelt: m'/44'/60'/0'/0 and m'/44'/1'/0'/0.
67+
/// For Ledger Live compatibility: m/44'/60'/account' and m/44'/1'/account'
6268
/// @return true if the keypath is valid, false if it is invalid.
6369
pub fn is_valid_keypath_xpub(keypath: &[u32]) -> bool {
64-
keypath.len() == 4
65-
&& (keypath[..4] == [44 + HARDENED, 60 + HARDENED, 0 + HARDENED, 0]
66-
|| keypath[..4] == [44 + HARDENED, 1 + HARDENED, 0 + HARDENED, 0])
70+
match keypath {
71+
// BitBoxApp, MyEtherWallet
72+
[PURPOSE, COIN_MAINNET | COIN_TESTNET, HARDENED, 0] => true,
73+
// Ledger Live
74+
[PURPOSE, COIN_MAINNET | COIN_TESTNET, ACCOUNT_MIN_H..=ACCOUNT_MAX_H] => true,
75+
_ => false,
76+
}
6777
}
6878

69-
/// Does limit checks the keypath, whitelisting bip44 purpose, account and change.
79+
/// Does limit checks the keypath.
7080
/// Returns true if the keypath is valid, false if it is invalid.
7181
pub fn is_valid_keypath_address(keypath: &[u32]) -> bool {
72-
if keypath.len() != 5 {
73-
return false;
82+
match keypath {
83+
// BitBoxApp, MyEtherWallet
84+
[PURPOSE, COIN_MAINNET | COIN_TESTNET, HARDENED, 0, 0..=ACCOUNT_MAX] => true,
85+
// Ledger Live
86+
[PURPOSE, COIN_MAINNET | COIN_TESTNET, ACCOUNT_MIN_H..=ACCOUNT_MAX_H, 0, 0] => true,
87+
_ => false,
7488
}
75-
if !is_valid_keypath_xpub(&keypath[..4]) {
76-
return false;
77-
}
78-
if keypath[4] > ACCOUNT_MAX {
79-
return false;
80-
}
81-
true
8289
}
8390

8491
#[cfg(test)]
@@ -107,11 +114,7 @@ mod tests {
107114
0
108115
]));
109116
// too short
110-
assert!(!is_valid_keypath_xpub(&[
111-
44 + HARDENED,
112-
60 + HARDENED,
113-
0 + HARDENED
114-
]));
117+
assert!(!is_valid_keypath_xpub(&[44 + HARDENED, 60 + HARDENED,]));
115118
// too long
116119
assert!(!is_valid_keypath_xpub(&[
117120
44 + HARDENED,
@@ -120,6 +123,24 @@ mod tests {
120123
0,
121124
0
122125
]));
126+
127+
// Ledger Live
128+
assert!(is_valid_keypath_xpub(&[
129+
44 + HARDENED,
130+
60 + HARDENED,
131+
0 + HARDENED,
132+
]));
133+
assert!(is_valid_keypath_xpub(&[
134+
44 + HARDENED,
135+
60 + HARDENED,
136+
99 + HARDENED,
137+
]));
138+
// account too high
139+
assert!(!is_valid_keypath_xpub(&[
140+
44 + HARDENED,
141+
60 + HARDENED,
142+
100 + HARDENED,
143+
]));
123144
}
124145

125146
#[test]
@@ -175,10 +196,76 @@ mod tests {
175196
0
176197
]));
177198
// tweak keypath elements
178-
for i in 0..4 {
179-
let mut keypath = [44 + HARDENED, 60 + HARDENED, 0 + HARDENED, 0, 0];
180-
keypath[i] += 1;
181-
assert!(!is_valid_keypath_address(&keypath));
199+
assert!(!is_valid_keypath_address(&[
200+
44 + HARDENED + 1,
201+
60 + HARDENED,
202+
0 + HARDENED,
203+
0,
204+
0
205+
]));
206+
assert!(!is_valid_keypath_address(&[
207+
44 + HARDENED,
208+
60 + HARDENED + 1,
209+
0 + HARDENED,
210+
0,
211+
0
212+
]));
213+
assert!(!is_valid_keypath_address(&[
214+
44 + HARDENED,
215+
60 + HARDENED,
216+
0 + HARDENED,
217+
0 + 1,
218+
0
219+
]));
220+
221+
// Ledger Live
222+
223+
// 100 good paths.
224+
for account in 0..100 {
225+
assert!(is_valid_keypath_address(&[
226+
44 + HARDENED,
227+
60 + HARDENED,
228+
account + HARDENED,
229+
0,
230+
0
231+
]));
182232
}
233+
// account too high
234+
assert!(!is_valid_keypath_address(&[
235+
44 + HARDENED,
236+
60 + HARDENED,
237+
100 + HARDENED,
238+
0,
239+
0
240+
]));
241+
// tweak keypath elements
242+
assert!(!is_valid_keypath_address(&[
243+
44 + HARDENED + 1,
244+
60 + HARDENED,
245+
1 + HARDENED,
246+
0,
247+
0
248+
]));
249+
assert!(!is_valid_keypath_address(&[
250+
44 + HARDENED,
251+
60 + HARDENED + 1,
252+
1 + HARDENED,
253+
0,
254+
0
255+
]));
256+
assert!(!is_valid_keypath_address(&[
257+
44 + HARDENED,
258+
60 + HARDENED,
259+
1 + HARDENED,
260+
0 + 1,
261+
0
262+
]));
263+
assert!(!is_valid_keypath_address(&[
264+
44 + HARDENED,
265+
60 + HARDENED,
266+
1 + HARDENED,
267+
0,
268+
0 + 1,
269+
]));
183270
}
184271
}

0 commit comments

Comments
 (0)