Skip to content

Commit 6e472fc

Browse files
committed
test: consolidate the 7.14.3 and 7.15 host suites onto one head
The same four test changes were cherry-picked onto two branches, and the two copies of test_protected_ping_preserves_message_presence_after_debug_read and the ripple get_address debug-capture assertion drifted apart: each copy was gated at whatever release its branch was cut for (7.14.2, 7.14.3, 7.15.0), so the gate recorded which branch the test lived on rather than which firmware carries the fix. Both fixes are present on all three products. fsm_msgPing is byte-identical between the 7.14.2 and 7.14.3 candidates, and the ripple response-arena fix is in fsm_msg_ripple.h on the 7.14.2 candidate as well. The floor is therefore 7.14.2 on both, which is what the 7.14.2 line already used before the gate was ratcheted up on each later branch. The reset-dice and storage-CRC copies are byte-identical and dedupe on merge. The two ripple sign_tx commits are complementary, not duplicated: one asserts memo rejection below 7.15 and self-skips above it, the other un-skips the THORChain memo test at 7.15.0. Also drops the claim that older release backports are separate. They are not separate: 1ce4d3961 on 7.14.3 and 885609fbe on 7.15 reach the same end state.
2 parents 5dae186 + 6268e38 commit 6e472fc

15 files changed

Lines changed: 107 additions & 106 deletions

scripts/generate-test-report.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2025,7 +2025,7 @@ def _arg_shown(a):
20252025
# lookup table cannot be resolved on-device, so today the device signs
20262026
# accounts it never showed. These four are the additive invariant
20272027
# (section F) restated for Solana, and R-4.1 of SRS-7.15. The
2028-
# protocol work first ships in firmware 7.16.
2028+
# canonical 7.15 product includes this protocol work.
20292029
('S26', 'test_msg_solana_lut_attestation',
20302030
'test_attested_accounts_are_shown_and_blind_sign_still_follows',
20312031
'Attested lookup-table accounts are shown, and the blind-sign warning survives',
@@ -3293,7 +3293,7 @@ def screenshot_test_list(fw_version):
32933293
# R-4.1. Gated on requires_message('LoadClearsignSigner'), so if provider
32943294
# loading regressed, all four would skip and the report would certify a
32953295
# feature it never exercised.
3296-
'test_msg_solana_lut_attestation': '7.16.0',
3296+
'test_msg_solana_lut_attestation': '7.15.0',
32973297
}
32983298

32993299
# These modules are mandatory only on the multi-chain product. Their handlers

tests/test_msg_ethereum_clear_signing.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,11 +1100,8 @@ def test_replay_rejected_when_digest_differs(self):
11001100
def test_advanced_mode_gate(self):
11011101
"""AdvancedMode OFF + unknown contract + no metadata → hard reject;
11021102
ON → raw-data confirm path signs; recognized ERC-20 transfer unaffected."""
1103-
# RC18 predates the rule that loading a runtime signer itself requires
1104-
# AdvancedMode. The first released firmware line carrying that complete
1105-
# gate is 7.16; the older blind-transaction gate remains covered by
1106-
# test_msg_ethereum_signtx on RC18.
1107-
self.requires_firmware("7.16.0")
1103+
# Canonical 7.15 requires AdvancedMode before runtime signer loading.
1104+
self.requires_firmware("7.15.0")
11081105
n = parse_path(DEVICE_PATH)
11091106
data = aave_supply_calldata(1000000000000000000)
11101107

tests/test_msg_ethereum_clearsign_additive.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,9 @@
8787
# METADATA_MAX_KEYS in include/keepkey/firmware/signed_metadata.h.
8888
METADATA_MAX_KEYS = 4
8989

90-
# RC18 verifies runtime metadata, but the successful-decode path did not yet
91-
# guarantee that the ordinary raw review survived byte-for-byte. That security
92-
# invariant landed after RC18 and first ships on the 7.16 line.
93-
ADDITIVE_REVIEW_FIRMWARE = "7.16.0"
90+
# The canonical 7.15 product requires additive runtime provider review.
91+
# Do not inherit the earlier RC18 candidate's capability assumptions.
92+
ADDITIVE_REVIEW_FIRMWARE = "7.15.0"
9493

9594
# The Aave V3 supply() transaction every additive test signs. Real ABI
9695
# calldata (selector + 4 x 32-byte words), so the metadata below binds a

tests/test_msg_ethereum_erc20_uniswap_liquidity.py

Lines changed: 38 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -29,52 +29,45 @@ class TestMsgEthereumUniswaptxERC20(common.KeepKeyTest):
2929

3030
def setUp(self):
3131
super(TestMsgEthereumUniswaptxERC20, self).setUp()
32-
# Every test in this file approves or spends against the ETH/FOX pool,
33-
# whose contract is NOT in the token table. Approving an unknown token
34-
# contract does not complete on the emulator: the device never returns,
35-
# so these tests HANG instead of failing, and CI kills the whole run on
36-
# its no-output timeout -- taking every later test with it.
37-
#
38-
# This is a firmware-side limitation, not a gap in the tests. It is
39-
# gated here rather than deleted so the coverage returns automatically
40-
# once the firmware completes this path. Known-token approves
41-
# (test_msg_ethereum_erc20_approve) run here and pass; on real hardware
42-
# this path is exercised by the app.
43-
if self.client.features.firmware_variant[0:8] == "Emulator":
44-
self.skipTest(
45-
"Uniswap liquidity against an unknown token contract does not "
46-
"complete on the emulator")
47-
32+
# Canonical 7.15 routes unknown token contracts through explicitly
33+
# authorized raw review. Exercise that path instead of an emulator skip.
34+
self.requires_firmware("7.15.0")
35+
4836
def test_sign_uni_approve_liquidity_ETH(self):
4937
self.requires_fullFeature()
5038
self.requires_firmware("7.1.0")
5139
self.setup_mnemonic_nopin_nopassphrase()
40+
self.client.apply_policy("AdvancedMode", 1)
5241

53-
# Approval tx for the ETH/FOX pool
54-
sig_v, sig_r, sig_s = self.client.ethereum_sign_tx(
55-
n=[2147483692,2147483708,2147483648,0,0],
56-
nonce=0xf,
57-
gas_price=0x2980872680,
58-
gas_limit=0xbd0e,
59-
value=0x0,
60-
to=binascii.unhexlify('470e8de2ebaef52014a47cb5e6af86884947f08c'), # fox pool
61-
address_type=0,
62-
chain_id=1,
63-
# The data below is generally broken into 32-byte chunks except for the function selector (4 bytes_ and
64-
# keccak signatures (4 bytes)
65-
data=binascii.unhexlify('095ea7b3' + # approve
66-
'0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d' + # uniswap v2: router 2 contract address
67-
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff') # approve amount
42+
# Unlimited approval is deliberately disabled on canonical 7.15.
43+
# This legacy vector must be refused, not skipped or signed.
44+
with self.assertRaises(CallException) as caught:
45+
self.client.ethereum_sign_tx(
46+
n=[2147483692,2147483708,2147483648,0,0],
47+
nonce=0xf,
48+
gas_price=0x2980872680,
49+
gas_limit=0xbd0e,
50+
value=0x0,
51+
to=binascii.unhexlify('470e8de2ebaef52014a47cb5e6af86884947f08c'), # fox pool
52+
address_type=0,
53+
chain_id=1,
54+
# The data below is generally broken into 32-byte chunks except for the function selector (4 bytes_ and
55+
# keccak signatures (4 bytes)
56+
data=binascii.unhexlify('095ea7b3' + # approve
57+
'0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d' + # uniswap v2: router 2 contract address
58+
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff') # approve amount
59+
60+
)
61+
self.assertEqual(caught.exception.args[0],
62+
proto_types.Failure_ActionCancelled)
63+
self.assertIn("Unlimited ERC20 approval is disabled",
64+
str(caught.exception))
6865

69-
)
70-
self.assertEqual(sig_v, 38)
71-
self.assertEqual(binascii.hexlify(sig_r), '7f7a5ce501371a01ead394d2186385742d5fbdc3d85da98249d2a05043ac6d5a')
72-
self.assertEqual(binascii.hexlify(sig_s), '329954b284ed1df9a6242820e793b9719c0c6c21cae5f90190ce61c7f73c731e')
73-
7466
def test_sign_uni_add_liquidity_ETH(self):
7567
self.requires_fullFeature()
7668
self.requires_firmware("7.1.0")
7769
self.setup_mnemonic_nopin_nopassphrase()
70+
self.client.apply_policy("AdvancedMode", 1)
7871

7972
# Add liquidity to ETH/FOX pool
8073
sig_v, sig_r, sig_s = self.client.ethereum_sign_tx(
@@ -86,7 +79,7 @@ def test_sign_uni_add_liquidity_ETH(self):
8679
to=binascii.unhexlify('7a250d5630B4cF539739dF2C5dAcb4c659F2488D'), # UNISWAP router
8780
address_type=0,
8881
chain_id=1,
89-
# The data below is generally broken into 32-byte chunks except for the function selector (4 bytes_ and
82+
# The data below is generally broken into 32-byte chunks except for the function selector (4 bytes_ and
9083
# keccak signatures (4 bytes)
9184
data=binascii.unhexlify('f305d719' + # addLiquidityETH
9285
'000000000000000000000000c770eefad204b5180df6a14ee197d99d808ee52d' + # FOX token
@@ -95,15 +88,16 @@ def test_sign_uni_add_liquidity_ETH(self):
9588
'0000000000000000000000000000000000000000000000000000fb98b65aba40' + # min amount of eth token
9689
'0000000000000000000000003f2329C9ADFbcCd9A84f52c906E936A42dA18CB8' + # eth address (self)
9790
'00000000000000000000000000000000000000000000000000000178a9380e5f') # deadline
98-
)
91+
)
9992
self.assertEqual(sig_v, 37)
100-
self.assertEqual(binascii.hexlify(sig_r), '8547542bc74c0dcc6ca8b02a79e0dccd336856d8c48376289a2a697d864a5892')
101-
self.assertEqual(binascii.hexlify(sig_s), '0a8eec6856aef8caa234240b06862976f8e238e8b24f5c989279507dd7e51ccd')
93+
self.assertEqual(binascii.hexlify(sig_r).decode("ascii"), '8547542bc74c0dcc6ca8b02a79e0dccd336856d8c48376289a2a697d864a5892')
94+
self.assertEqual(binascii.hexlify(sig_s).decode("ascii"), '0a8eec6856aef8caa234240b06862976f8e238e8b24f5c989279507dd7e51ccd')
10295

10396
def test_sign_uni_remove_liquidity_ETH(self):
10497
self.requires_fullFeature()
10598
self.requires_firmware("7.1.0")
10699
self.setup_mnemonic_nopin_nopassphrase()
100+
self.client.apply_policy("AdvancedMode", 1)
107101

108102
# remove liquidity from the ETH/FOX pool
109103
sig_v, sig_r, sig_s = self.client.ethereum_sign_tx(
@@ -115,7 +109,7 @@ def test_sign_uni_remove_liquidity_ETH(self):
115109
to=binascii.unhexlify('7a250d5630B4cF539739dF2C5dAcb4c659F2488D'), # UNISWAP router
116110
address_type=0,
117111
chain_id=1,
118-
# The data below is generally broken into 32-byte chunks except for the function selector (4 bytes_ and
112+
# The data below is generally broken into 32-byte chunks except for the function selector (4 bytes_ and
119113
# keccak signatures (4 bytes)
120114
data=binascii.unhexlify('02751cec' + # addLiquidityETH
121115
'000000000000000000000000c770eefad204b5180df6a14ee197d99d808ee52d' + # FOX token
@@ -124,10 +118,10 @@ def test_sign_uni_remove_liquidity_ETH(self):
124118
'0000000000000000000000000000000000000000000000000000fb04c77f3e94' + # min amount of eth token
125119
'0000000000000000000000005028d647b74f12903e6d5f3969f8f624e6a9a93d' + # to address (not self)
126120
'00000000000000000000000000000000000000000000000000000178b2062f3d') # deadline
127-
)
121+
)
128122
self.assertEqual(sig_v, 37)
129-
self.assertEqual(binascii.hexlify(sig_r), '7143f0d8e5505a8cfb1df55e9c5d7433eba33a61959137c08cc5c088ec12ab5d')
130-
self.assertEqual(binascii.hexlify(sig_s), '20b456d6c13295f5abb6109d7ade2c5d5fc395963b1e45d92e6dc8c33749c517')
123+
self.assertEqual(binascii.hexlify(sig_r).decode("ascii"), '7143f0d8e5505a8cfb1df55e9c5d7433eba33a61959137c08cc5c088ec12ab5d')
124+
self.assertEqual(binascii.hexlify(sig_s).decode("ascii"), '20b456d6c13295f5abb6109d7ade2c5d5fc395963b1e45d92e6dc8c33749c517')
131125

132126
if __name__ == '__main__':
133127
unittest.main()

tests/test_msg_ethereum_signtx.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -442,9 +442,8 @@ def test_ethereum_signtx_omitted_chain_id_rejected(self):
442442
sibling tests in this file all now pass chain_id explicitly so they
443443
keep exercising their own subject rather than this one.
444444
"""
445-
# Explicit zero was already rejected on RC18, but an omitted field was
446-
# not. The absent-field fix landed after RC18 and first ships in 7.16.
447-
self.requires_firmware("7.16.0")
445+
# Canonical 7.15 rejects omitted chain IDs as well as explicit zero.
446+
self.requires_firmware("7.15.0")
448447
self.requires_fullFeature()
449448
self.setup_mnemonic_nopin_nopassphrase()
450449
self.client.apply_policy("AdvancedMode", 1)

tests/test_msg_getentropy.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,9 @@
3030

3131
class TestMsgGetentropy(common.KeepKeyTest):
3232

33-
@unittest.skipUnless(
34-
os.getenv('KK_EXPECT_ENTROPY_BUDGET') == '1',
35-
'requires the RC23 entropy audit budget policy')
3633
def test_entropy(self):
34+
if os.getenv("KK_EXPECT_ENTROPY_BUDGET") != "1":
35+
self.requires_firmware("7.15.0")
3736
chunk_size = 8192
3837
chunk_count = 8
3938

tests/test_msg_ping.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
class TestPing(common.KeepKeyTest):
2929

3030
def test_protected_ping_preserves_message_presence_after_debug_read(self):
31-
self.requires_firmware("7.14.3")
31+
self.requires_firmware("7.14.2")
3232
for message in (None, '', 'ping response'):
3333
with self.subTest(message=message):
3434
request = proto.Ping(button_protection=True)

tests/test_msg_recoverydevice_cipher.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,9 @@ def test_invalid_bip39_word_rejected(self):
180180
181181
With enforce_wordlist=True, completing a word that isn't in the
182182
BIP-39 wordlist must return Failure immediately.
183-
Requires firmware 7.15.1+ (per-word validation).
183+
The canonical 7.15 product includes per-word validation.
184184
"""
185-
self.requires_firmware("7.15.1")
185+
self.requires_firmware("7.15.0")
186186
ret = self.client.call_raw(proto.RecoveryDevice(word_count=12,
187187
passphrase_protection=False,
188188
pin_protection=False,

tests/test_msg_ripple_get_address.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ def test_ripple_show_address(self):
4141
self.setup_mnemonic_allallall()
4242
address = self.client.ripple_get_address(
4343
parse_path("m/44'/144'/0'/0/0"), show_display=True)
44-
if self.firmware_at_least("7.14.3"):
44+
# 7.14.2 and later preserve the response across DebugLinkGetState
45+
# requests made during screenshot capture; the same fix is on all three
46+
# products, so the floor is the release the fix first shipped in.
47+
if self.firmware_at_least("7.14.2"):
4548
self.assertEqual(address, "rNaqKtKrMSwpwZSzRckPf7S96DkimjkF4H")
4649

4750
def test_ripple_get_address_other(self):

tests/test_msg_ripple_sign_tx.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -100,23 +100,9 @@ def test_sign(self):
100100
)
101101

102102

103-
@unittest.skip(
104-
"XRP memo is not a supported feature yet. A THORChain memo cannot "
105-
"traverse hdwallet -> RippleSignTx: the protobuf has no memo field "
106-
"(RippleSignTx carries 1-6, RipplePayment carries "
107-
"amount/destination/destination_tag), and hdwallet's rippleSignTx "
108-
"never reads tx.value.memo. The firmware therefore never receives it "
109-
"and cannot serialize it. Tracked as keepkey/keepkey-vault#422.\n"
110-
"\n"
111-
"This assertion is CORRECT and is deliberately left intact: it "
112-
"describes the behaviour the product needs. Do NOT make it pass by "
113-
"asserting the memo is absent -- that would encode the bug as the "
114-
"contract. Re-enable only when the signed serialization actually "
115-
"preserves the memo."
116-
)
117103
def test_sign_with_thorchain_memo(self):
118104
self.requires_fullFeature()
119-
self.requires_firmware("7.14.2")
105+
self.requires_firmware("7.15.0")
120106

121107
self.setup_mnemonic_allallall()
122108

0 commit comments

Comments
 (0)