Updating few tests that did not respect the derivation path hardening
What changed, and why it matters
This commit only changes automated test files. It updates test cases so they use correct hardened Bitcoin derivation paths and expect the app to reject non-standard or root-level key derivation requests. There is no change to the actual app code that runs on Ledger devices, so this commit does not introduce or fix a security vulnerability by itself. It appears to be a follow-up to a real security hardening in the app firmware that happened elsewhere.
No immediate action required for this commit. Treat it as a test-only update. Review the companion firmware change that introduced the derivation-path hardening to confirm it is complete and correctly enforced in production app code, not only in tests.
Security signals we found
Hardened derivation path enforcement referenced in test expectations
Root-level key derivation now rejected unless HAVE_APPLICATION_FLAG_DERIVE_MASTER permission is present
Non-standard hardened paths now rejected as NotSupportedError
Test vectors updated from arbitrary path 1'/2'/3' to standard 44'/1'/3'
Evidence from the diff
The diff modifies tests/test_get_extended_pubkey.py and tests/test_register_wallet.py. The first file now expects get_extended_pubkey to fail with NotSupportedError for root path ‘m’ and for non-standard hardened paths like m/44’/2’/333’ or m/46’/1’/333’. It also skips the root-path case on Speculos because the simulator lacks the new enforcement. The second file updates wallet registration test vectors from arbitrary derivation path [f5acc2fd/1’/2’/3’] to a standard-looking path [f5acc2fd/44’/1’/3’] with a matching tpub. These are test-suite adjustments aligning tests with stricter derivation-path validation that was presumably implemented in a prior or companion firmware change.
Changed components
tests/test_get_extended_pubkey.pytests/test_register_wallet.pyInspect captured patch +46 / −14
diff --git a/tests/test_get_extended_pubkey.py b/tests/test_get_extended_pubkey.py
index 7bb8c00..71fcbaa 100644
--- a/tests/test_get_extended_pubkey.py
+++ b/tests/test_get_extended_pubkey.py
@@ -2,6 +2,7 @@ import pytest
from ragger_bitcoin import RaggerClient
from ragger.navigator import Navigator
+from ragger.backend import SpeculosBackend
from ragger.firmware import Firmware
from ragger.error import ExceptionRAPDU
@@ -86,15 +87,32 @@ def test_get_extended_pubkey_non_standard(navigator: Navigator, firmware: Firmwa
# Test the successful UX flow for a non-standard path (here, root path)
# (Slow test, not feasible to repeat it for many paths)
- pub_key = client.get_extended_pubkey(
- path="m", # root pubkey
- display=True,
- navigator=navigator,
- instructions=pubkey_instruction_approve(firmware),
- testname=test_name
- )
+ # The test will be re-enabled for Speculos once the installation parameters are supported
+ if isinstance(client.transport_client, SpeculosBackend):
+ pytest.skip("The test derives key at root level - now prohibited and the reinforcement is not yet implemented in Speculos.")
- assert pub_key == "tpubD6NzVbkrYhZ4YgUx2ZLNt2rLYAMTdYysCRzKoLu2BeSHKvzqPaBDvf17GeBPnExUVPkuBpx4kniP964e2MxyzzazcXLptxLXModSVCVEV1T"
+ # Deriving a key at root level without HAVE_APPLICATION_FLAG_DERIVE_MASTER permission
+ with pytest.raises(ExceptionRAPDU) as e:
+ pub_key = client.get_extended_pubkey(
+ path="m", # root pubkey
+ display=True,
+ navigator=navigator,
+ instructions=pubkey_instruction_approve(firmware),
+ testname=test_name
+ )
+ assert DeviceException.exc.get(e.value.status) == NotSupportedError
+ assert len(e.value.data) == 0
+ # Deriving a key at unauthorized path
+ with pytest.raises(ExceptionRAPDU) as e:
+ pub_key = client.get_extended_pubkey(
+ path="m/44'/2'/333'", # root pubkey
+ display=True,
+ navigator=navigator,
+ instructions=pubkey_instruction_approve(firmware),
+ testname=test_name
+ )
+ assert DeviceException.exc.get(e.value.status) == NotSupportedError
+ assert len(e.value.data) == 0
def test_get_extended_pubkey_non_standard_reject_early(navigator: Navigator, firmware: Firmware,
@@ -102,9 +120,22 @@ def test_get_extended_pubkey_non_standard_reject_early(navigator: Navigator, fir
# Test rejecting after the "Reject if you're not sure" warning
# (Slow test, not feasible to repeat it for many paths)
+ # Deriving with a suspicious path without displaying
+ with pytest.raises(ExceptionRAPDU) as e:
+ client.get_extended_pubkey(
+ path="m/46'/1'/333'",
+ display=False,
+ navigator=navigator,
+ instructions=pubkey_instruction_reject_early(firmware),
+ testname=test_name
+ )
+ assert DeviceException.exc.get(e.value.status) == NotSupportedError
+ assert len(e.value.data) == 0
+
+ # Deriving with a suspicious path with displaying, but rejecting as early as the path is displayed
with pytest.raises(ExceptionRAPDU) as e:
client.get_extended_pubkey(
- path="m/111'/222'/333'",
+ path="m/46'/1'/333'",
display=True,
navigator=navigator,
instructions=pubkey_instruction_reject_early(firmware),
@@ -119,9 +150,10 @@ def test_get_extended_pubkey_non_standard_reject(navigator: Navigator, firmware:
# Test rejecting at the end
# (Slow test, not feasible to repeat it for many paths)
+ # Deriving with a suspicious path with displaying, but rejecting on the last screen
with pytest.raises(ExceptionRAPDU) as e:
client.get_extended_pubkey(
- path="m/111'/222'/333'",
+ path="m/46'/1'/333'",
display=True,
navigator=navigator,
instructions=pubkey_reject(firmware),
diff --git a/tests/test_register_wallet.py b/tests/test_register_wallet.py
index 74c5caf..40bc91b 100644
--- a/tests/test_register_wallet.py
+++ b/tests/test_register_wallet.py
@@ -362,7 +362,7 @@ def test_register_unusual_singlesig_accounts(navigator: Navigator, firmware: Fir
run_register_test(navigator, client, speculos_globals, WalletPolicy(
name="Unusual Legacy",
descriptor_template="pkh(@0/**)",
- keys_info=["[f5acc2fd/1'/2'/3']tpubDCsHVWwqALkDzorr5zdc91Wj93zR3so1kUEH6LWsPrLtC9MVPjb8NEQwCzhPM4TEFP6KbgmTb7xAsyrbf3oEBh31Q7iAKhzMHj2FZ5YGNrr"]
+ keys_info=["[f5acc2fd/44'/1'/3']tpubDCwYjpDhUdPGW815u9VExvLRXDAHehcnkNfjqiwSvJp7NizpAGsX3Qfq9A173rES9vF17HgeqXBUvodRTyeTHCeAvg7gVgDE19mudggheN1"]
),
instructions=register_wallet_instruction_approve_unusual(firmware),
test_name=f"{test_name}_Unusual_Legacy")
@@ -370,7 +370,7 @@ def test_register_unusual_singlesig_accounts(navigator: Navigator, firmware: Fir
run_register_test(navigator, client, speculos_globals, WalletPolicy(
name="Unusual Nested SegWit",
descriptor_template="sh(wpkh(@0/**))",
- keys_info=["[f5acc2fd/1'/2'/3']tpubDCsHVWwqALkDzorr5zdc91Wj93zR3so1kUEH6LWsPrLtC9MVPjb8NEQwCzhPM4TEFP6KbgmTb7xAsyrbf3oEBh31Q7iAKhzMHj2FZ5YGNrr"]
+ keys_info=["[f5acc2fd/44'/1'/3']tpubDCwYjpDhUdPGW815u9VExvLRXDAHehcnkNfjqiwSvJp7NizpAGsX3Qfq9A173rES9vF17HgeqXBUvodRTyeTHCeAvg7gVgDE19mudggheN1"]
),
instructions=register_wallet_instruction_approve_unusual(firmware),
test_name=f"{test_name}_Unusual_Nested_Segwit")
@@ -378,7 +378,7 @@ def test_register_unusual_singlesig_accounts(navigator: Navigator, firmware: Fir
run_register_test(navigator, client, speculos_globals, WalletPolicy(
name="Unusual Native SegWit",
descriptor_template="wpkh(@0/**)",
- keys_info=["[f5acc2fd/1'/2'/3']tpubDCsHVWwqALkDzorr5zdc91Wj93zR3so1kUEH6LWsPrLtC9MVPjb8NEQwCzhPM4TEFP6KbgmTb7xAsyrbf3oEBh31Q7iAKhzMHj2FZ5YGNrr"]
+ keys_info=["[f5acc2fd/44'/1'/3']tpubDCwYjpDhUdPGW815u9VExvLRXDAHehcnkNfjqiwSvJp7NizpAGsX3Qfq9A173rES9vF17HgeqXBUvodRTyeTHCeAvg7gVgDE19mudggheN1"]
),
instructions=register_wallet_instruction_approve_unusual(firmware),
test_name=f"{test_name}_Unusual_Native_Segwit")
@@ -386,7 +386,7 @@ def test_register_unusual_singlesig_accounts(navigator: Navigator, firmware: Fir
run_register_test(navigator, client, speculos_globals, WalletPolicy(
name="Unusual Taproot",
descriptor_template="tr(@0/**)",
- keys_info=["[f5acc2fd/1'/2'/3']tpubDCsHVWwqALkDzorr5zdc91Wj93zR3so1kUEH6LWsPrLtC9MVPjb8NEQwCzhPM4TEFP6KbgmTb7xAsyrbf3oEBh31Q7iAKhzMHj2FZ5YGNrr"]
+ keys_info=["[f5acc2fd/44'/1'/3']tpubDCwYjpDhUdPGW815u9VExvLRXDAHehcnkNfjqiwSvJp7NizpAGsX3Qfq9A173rES9vF17HgeqXBUvodRTyeTHCeAvg7gVgDE19mudggheN1"]
),
instructions=register_wallet_instruction_approve_unusual(firmware),
test_name=f"{test_name}_Unusual_Taproot")
Why this scored 12/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.