Skip to content

Commit 786b655

Browse files
BitHighlanderclaude
andcommitted
report: 10 fixes to the 7.15 PDF test report — release-gate coverage gaps
Audit of fork develop CI run 28679110349 (PR #284 clearsign v2 static- schema, 526 junit tests) against the who/what/why standard for every major daily-driver EVM tx format. Ten fixes: 1. Lower 8 stale requires_firmware("7.15.1") gates to 7.15.0 (signing guards x5, blind_sign_blocked/allowed, bip39-word-reject) — the behaviors ship in the 7.15.0 line under test; the gate zeroed out the EIP-1559 wrong-signer regression suite and the blind-sign policy negative path on every 7.15.0 CI run. 2. Document the ETH-section amount-unit rule (Wei below 1 gwei, scaled ETH/ticker above) so toy conformance-vector screenshots aren't misread as a "never wei" violation. 3. Disclose EIP-712 typed-data hash-signing as a known gap (E16): the device signs two host-computed hashes with zero readable domain/ message display — a daily-driver format with no who/what/why today. 4. Fix the ERC-4337 handleOps flow's overclaiming "innerCall: decoded separately, not raw" placeholder — the representative UserOp's inner callData is actually empty; the claim is now honest about what this flow does and does not prove (regenerated its frozen reference-blob snapshot to match). 5. Add 2 new on-device tests for the v2 static-schema headline security property, previously unit-tested but never proven on real device behavior: calldata/schema length-mismatch falls back to the blind-sign gate (VS6), and an unsupported dynamic arg format is independently rejected as MALFORMED by the device parser (VS7). 6. Surface the 3 silently-skipped Uniswap V2 liquidity tests (E17-E19) with their documented emulator-limitation reason instead of leaving them absent from SECTIONS entirely. 7. Surface the 3 passing-but-invisible THORChain-router EVM deposit tests (E20-E22), including the router-pin blind-sign-gate proof that closed a CRITICAL bypass. 8. Surface 6 passing-but-invisible malformed-blob rejection tests (V7a-V7f: empty/truncated/trailing-bytes/wrong-version/zero-sig/ empty-slot) — the WHY = fail-closed story had no rejection-path evidence in the release-gate document. 9. Fix the stale "7.15.1 NEW FEATURES" banner comment and the V8 context text that claimed "covered in 7.15.0+" while pointing at a test gated to 7.15.1 (a self-contradiction in the PDF's own prose, resolved by fix #1). 10. Fix print_clearsign_flows() (--flows external-signer reference dump): KeyError on flow['shows'] (no catalog flow has that key — regression from the move to the shared catalog module) and a missing required key_id arg to flow_blob(). Verified: all 6 edited files compile; SECTIONS loaded in the real firmware-checkout env (245 unique entries, 224->245, zero ID collisions); every new/changed (module, method) SECTIONS reference resolves to a real test function; all 51 REFERENCE_BLOB_SNAPSHOTS still match (only the ERC-4337 entry's hash/length changed, matching the ARG_FORMAT_STRING value edit in fix #4). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 4ceec46 commit 786b655

6 files changed

Lines changed: 192 additions & 17 deletions

keepkeylib/clearsign_catalog.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -954,10 +954,14 @@ def _bytes_tail(b):
954954
[{'name': 'sender', 'format': ARG_FORMAT_ADDRESS, 'value': addr('0x9406Cc6185a346906296840746125a0E44976454')},
955955
{'name': 'nonce', 'format': ARG_FORMAT_STRING, 'value': b'UserOperation nonce: 12'},
956956
{'name': 'beneficiary', 'format': ARG_FORMAT_ADDRESS, 'value': addr('0x' + '43' * 20)},
957-
{'name': 'innerCall', 'format': ARG_FORMAT_STRING, 'value': b'decoded separately, not raw'}],
957+
{'name': 'innerCall', 'format': ARG_FORMAT_STRING, 'value': b'empty in this representative UserOp'}],
958958
why='A bundler-submitted meta-tx: the EntryPoint singleton validates and executes a batch of '
959-
'smart-account operations; the inner callData (what the smart account will actually do) '
960-
'must be decoded and shown, never left as an opaque blob one layer inside another.',
959+
'smart-account operations. KNOWN GAP, disclosed: this representative UserOp carries an '
960+
'EMPTY inner callData (the array-of-dynamic-tuples nesting is beyond the current static '
961+
'ABI encoder), so this flow proves sender/nonce/beneficiary are decoded but does NOT '
962+
'prove the inner callData — what the smart account will actually do — is decoded. A real '
963+
'UserOp with non-empty callData would need it decoded and shown, never left as an opaque '
964+
'blob one layer inside another; that inner-decode capability is future work.',
961965
source='https://etherscan.io/address/0x0000000071727De22E5E9d8BAf0edAc6f37da032 (EntryPoint v0.7)',
962966
),
963967
)

scripts/generate-test-report.py

Lines changed: 113 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -727,8 +727,12 @@ def _arg_shown(a):
727727

728728
('E', 'Ethereum', '7.0.0',
729729
'Ethereum covers native ETH transfers, ERC-20 tokens, EIP-1559 gas, personal message signing '
730-
'(EIP-191), and contract interactions. The device displays checksummed addresses (EIP-55), '
731-
'values in ETH with 18-decimal precision, and gas parameters.',
730+
'(EIP-191), and contract interactions. The device displays checksummed addresses (EIP-55) and '
731+
'gas parameters. Amount UNIT rule: values below 1 gwei (1e9 wei) show as raw "Wei" (there is '
732+
'no smaller human unit to scale to); values at or above 1 gwei show 18-decimal-scaled ETH (or '
733+
'the chain-native ticker on other EVM chains). Some tests below use small conformance-vector '
734+
'amounts (e.g. 10 wei) for deterministic-signature pinning — their OLED frames legitimately '
735+
'show raw "Wei", not a display bug.',
732736
[
733737
'ETH TRANSFER: Show "Send X ETH to 0x..." -> show gas -> confirm -> sign with secp256k1',
734738
'ERC-20: Decode transfer(to,amount) from contract data -> show token name + amount',
@@ -791,6 +795,48 @@ def _arg_shown(a):
791795
'0x swap ETH to ERC-20', 'DEX aggregator swap via 0x protocol.', []),
792796
('E15', 'test_msg_ethereum_cfunc', 'test_sign_execTx',
793797
'Contract function call', 'Generic contract call signing.', []),
798+
('E16', 'test_sign_typed_data', 'test_ethereum_sign_typed_data_hash',
799+
'EIP-712 typed-data hash signing (legacy, no on-device display)',
800+
'KNOWN GAP, disclosed rather than hidden: EIP-712 (the standard behind wallet permits, '
801+
'OpenSea listings, and DAO votes — a daily-driver format) is only supported at the '
802+
'domain-separator-hash + message-hash level. The device signs two host-computed 32-byte '
803+
'hashes; it does NOT parse or display the typed-data domain or message fields, so this '
804+
'path shows the user no readable WHO/WHAT — it is effectively a blind hash-sign, not a '
805+
'clear-sign. Full structured EIP-712 display is a firmware feature, not yet built.',
806+
[]),
807+
('E17', 'test_msg_ethereum_erc20_uniswap_liquidity', 'test_sign_uni_approve_liquidity_ETH',
808+
'Uniswap V2 add-liquidity approve (pending)',
809+
'PENDING, disclosed: known emulator limitation — an approve to an unknown (non-registry) '
810+
'token contract cannot complete against the kkemu emulator (matches the sibling '
811+
'add/remove-liquidity skips below); the device-firmware path is not in question, only '
812+
'CI emulator coverage. Real-device testing is unaffected.',
813+
[]),
814+
('E18', 'test_msg_ethereum_erc20_uniswap_liquidity', 'test_sign_uni_add_liquidity_ETH',
815+
'Uniswap V2 add liquidity ETH+token (pending)',
816+
'PENDING, disclosed: same emulator limitation as E17 — a daily-driver LP-deposit flow '
817+
'with no PDF proof on this build; tracked for real-device verification.',
818+
[]),
819+
('E19', 'test_msg_ethereum_erc20_uniswap_liquidity', 'test_sign_uni_remove_liquidity_ETH',
820+
'Uniswap V2 remove liquidity ETH+token (pending)',
821+
'PENDING, disclosed: same emulator limitation as E17.',
822+
[]),
823+
('E20', 'test_msg_ethereum_thorchain_deposit', 'test_deposit_legacy_selector',
824+
'THORChain router deposit() (legacy selector)',
825+
'Cross-chain swap via the THORChain router contract — a daily-driver EVM<->THORChain '
826+
'swap path, natively decoded (asset/amount/memo) without clear-sign metadata.',
827+
[]),
828+
('E21', 'test_msg_ethereum_thorchain_deposit', 'test_deposit_with_expiry_selector',
829+
'THORChain router depositWithExpiry()',
830+
'Newer router selector variant with an expiry field; same native decode path.',
831+
[]),
832+
('E22', 'test_msg_ethereum_thorchain_deposit',
833+
'test_deposit_with_expiry_non_thor_address_blind_sign_blocked',
834+
'THORChain router call to a non-pinned address is blind-sign gated',
835+
'WHY it can be trusted: the router CONTRACT ADDRESS is pinned; a call shaped like a '
836+
'THORChain deposit but sent to an unpinned address is refused native decoding and falls '
837+
'through to the ordinary blind-sign gate instead of being silently native-decoded — the '
838+
'fix for the router-spoofing / blind-sign-bypass class of attack.',
839+
['Blind sign disabled (Blocked)']),
794840
]),
795841

796842
('R', 'Ripple (XRP)', '7.0.0',
@@ -920,7 +966,7 @@ def _arg_shown(a):
920966
'cause fund loss or invalid transactions on the block-lattice.',
921967
[])]),
922968

923-
# ===== 7.15.1 NEW FEATURES =====
969+
# ===== 7.15.0 NEW FEATURES =====
924970
('V', 'EVM Clear-Signing', '7.15.0',
925971
'The purpose of clear-signing: instead of blind-signing an opaque hash, the device screen '
926972
'answers WHO / WHAT / WHY before the user approves. WHO = the validated contract address '
@@ -963,6 +1009,24 @@ def _arg_shown(a):
9631009
'Signature verification math', 'Unit test for the metadata blob signature algorithm.', []),
9641010
('V7', 'test_msg_ethereum_clear_signing', 'test_tampered_blob_fails_verification',
9651011
'Tampered blob fails', 'Any byte change in the blob invalidates the signature.', []),
1012+
('V7a', 'test_msg_ethereum_clear_signing', 'test_empty_payload_returns_malformed',
1013+
'Empty metadata payload rejected', 'A zero-length blob classifies MALFORMED, never VERIFIED.', []),
1014+
('V7b', 'test_msg_ethereum_clear_signing', 'test_truncated_payload_returns_malformed',
1015+
'Truncated metadata payload rejected',
1016+
'A blob cut short of the minimum structural size classifies MALFORMED.', []),
1017+
('V7c', 'test_msg_ethereum_clear_signing', 'test_extra_trailing_bytes_returns_malformed',
1018+
'Trailing garbage bytes rejected',
1019+
'A blob with extra bytes appended past its declared structure classifies MALFORMED — '
1020+
'the parser cannot be tricked by appended data.', []),
1021+
('V7d', 'test_msg_ethereum_clear_signing', 'test_wrong_version_returns_malformed',
1022+
'Unknown version byte rejected', 'A blob with a version byte the firmware does not '
1023+
'recognize classifies MALFORMED rather than being guessed-parsed.', []),
1024+
('V7e', 'test_msg_ethereum_clear_signing', 'test_zero_signature_returns_malformed',
1025+
'All-zero signature rejected', 'A blob with a zeroed signature field classifies '
1026+
'MALFORMED — an attacker cannot skip signing by leaving the field blank.', []),
1027+
('V7f', 'test_msg_ethereum_clear_signing', 'test_empty_key_slot_returns_malformed',
1028+
'Metadata against an empty key slot rejected',
1029+
'A blob referencing a signer slot with no key loaded classifies MALFORMED.', []),
9661030
('V8', 'test_msg_ethereum_signtx', 'test_ethereum_blind_sign_allowed',
9671031
'Blind sign permitted (AdvancedMode ON)',
9681032
'Contract data with AdvancedMode enabled. Device allows signing. '
@@ -1014,6 +1078,37 @@ def _arg_shown(a):
10141078
'Empty, oversized, control-char and format-specifier aliases are rejected — the alias '
10151079
'is rendered on the warning screen, so it cannot carry a display-spoofing payload.',
10161080
[]),
1081+
1082+
# ── ethereum signing-path guards (the blind-sign policy negative
1083+
# half + the EIP-1559 type/fee/chain_id regression suite) ──
1084+
('VG1', 'test_msg_ethereum_signtx', 'test_ethereum_blind_sign_blocked',
1085+
'Blind sign refused (AdvancedMode OFF)',
1086+
'Unknown contract data with AdvancedMode disabled is hard-rejected before any confirm '
1087+
'screen — the negative half of the V8 policy pair.',
1088+
['Blind signing disabled (Failure)']),
1089+
('VG2', 'test_msg_ethereum_signing_guards', 'test_eip1559_requires_chain_id',
1090+
'EIP-1559 requires chain_id',
1091+
'A type-2 tx with no chain_id would hash a garbage pre-image and recover the wrong '
1092+
'signer; the device rejects it outright instead of signing an unbroadcastable tx.',
1093+
[]),
1094+
('VG3', 'test_msg_ethereum_signing_guards', 'test_eip1559_no_priority_fee_signs',
1095+
'EIP-1559 zero priority fee signs correctly',
1096+
'Regression test for the non-canonical-RLP wrong-signer bug: a type-2 tx with zero/'
1097+
'absent priority fee must still hash and sign to the correct device address.',
1098+
[]),
1099+
('VG4', 'test_msg_ethereum_signing_guards', 'test_type2_without_max_fee_rejected',
1100+
'Type-2 tx without max_fee_per_gas rejected', '', []),
1101+
('VG5', 'test_msg_ethereum_signing_guards', 'test_legacy_with_max_fee_rejected',
1102+
'Legacy tx with max_fee_per_gas rejected',
1103+
'Mixing legacy gas_price semantics with EIP-1559 fee fields is refused rather than '
1104+
'silently mis-hashed.',
1105+
[]),
1106+
('VG6', 'test_msg_ethereum_signing_guards',
1107+
'test_contract_handler_streamed_calldata_signs_full_data',
1108+
'Streamed calldata signs the full payload',
1109+
'A contract-clear-sign handler must not confirm only the first chunk while signing '
1110+
'unshown streamed bytes after it.',
1111+
[]),
10171112
] + _V_CATALOG_TESTS + [
10181113
('V%d' % (17 + len(_V_CATALOG_TESTS)),
10191114
'test_msg_ethereum_clear_signing', 'test_clearsign_batch_all_payloads',
@@ -1073,6 +1168,21 @@ def _arg_shown(a):
10731168
'with no tx_hash. The offline format tests above pin the wire format '
10741169
'the device decodes.',
10751170
['Clearsign warning', 'v2 decoded transfer to/amount', 'Sign transaction']),
1171+
('VS6', 'test_msg_ethereum_clear_signing',
1172+
'test_v2_calldata_length_mismatch_falls_back_to_blind_sign_gate',
1173+
'v2 decode-mismatch falls back to blind-sign (fail-closed)',
1174+
'THE headline v2 security property: schema says 2 words, calldata carries 3. '
1175+
'decode_v2_args\' structural completeness check fails, so the device does NOT '
1176+
'clear-sign a decode that would not match what it is about to sign — it falls '
1177+
'through to the ordinary blind-sign gate, and AdvancedMode OFF hard-rejects it.',
1178+
['Blind signing disabled (Failure)']),
1179+
('VS7', 'test_msg_ethereum_clear_signing',
1180+
'test_v2_unsupported_arg_format_returns_malformed',
1181+
'v2 unsupported arg format rejected at blob load',
1182+
'A hand-crafted v2 blob using an unsupported dynamic format (STRING) — the kind the '
1183+
'Python serializer itself refuses to build — is independently rejected by the '
1184+
'device\'s own parser as MALFORMED, before any calldata is even considered.',
1185+
[]),
10761186
]),
10771187

10781188
('G', 'Hive', '7.15.0',

tests/test_msg_ethereum_clear_signing.py

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,7 @@ def test_keccak256_known_vectors(self):
643643
'permit2-permit-transfer-from': ('c0fde596537a6bf1e53b98d3746638b4249a7a90d8196fe4a9f40f711729ec84', 276),
644644
'across-spokepool-depositv3': ('ab185113f0b47ef5f6e1fab6a6839df8b71bf8d48796afee64a61ba8b336ac01', 311),
645645
'safe-exectransaction': ('00a523f8e02d196db7213813edfbeee2a707679b026c6c6b6f8af88d35bf4889', 274),
646-
'erc4337-entrypoint-v0.7-handleops': ('0b44fc0f98727877a1d6bd1346300d9fe4b537e48d901002ea241d01b79c52cd', 281),
646+
'erc4337-entrypoint-v0.7-handleops': ('29ac50a7c18a4e145d058c75dc9cb6232e875af79006766be0baf5cb674ba04f', 289),
647647
'eip7702-setcode-authorization': ('0518442c7172b8c57fcbd09ded11b54e1d20076c4b5e79a7490c4ae9c2096a18', 299),
648648
}
649649

@@ -1274,6 +1274,66 @@ def test_v2_transfer_decodes_signs_and_recovers(self):
12741274
signer = recover_eth_signer(sig_r, sig_s, sig_v, tx_hash, chain_id)
12751275
self.assertEqual(signer, self.client.ethereum_get_address(n))
12761276

1277+
def test_v2_calldata_length_mismatch_falls_back_to_blind_sign_gate(self):
1278+
"""The headline v2 security property: a blob's schema says 2 words,
1279+
but the calldata actually being signed carries 3. decode_v2_args'
1280+
structural completeness check (total calldata bytes must equal
1281+
exactly 4 + 32*num_args) fails, matches_tx returns false, and the tx
1282+
falls through to the ordinary blind-sign path — with AdvancedMode
1283+
OFF that is a hard reject, never a clear-signed-but-wrong display."""
1284+
self.client.apply_policy("AdvancedMode", 0)
1285+
self._drop_setup_screenshots()
1286+
n = parse_path(DEVICE_PATH)
1287+
chain_id, nonce, gas_price, gas_limit, value = 1, 3, 20000000000, 250000, 0
1288+
args = [
1289+
{'format': ARG_FORMAT_ADDRESS, 'address': VITALIK},
1290+
{'format': ARG_FORMAT_TOKEN_AMOUNT, 'amount': 1500000},
1291+
]
1292+
# calldata carries one EXTRA 32-byte word beyond the 2-arg schema.
1293+
data = schema_calldata(ERC20_TRANSFER_SELECTOR, args) + (b'\x00' * 32)
1294+
_, blob = _v2_transfer_blob()
1295+
1296+
resp = self.client.ethereum_send_tx_metadata(
1297+
signed_payload=blob, metadata_version=1, key_id=TEST_KEY_ID)
1298+
self.assertEqual(resp.classification, CLASSIFICATION_VERIFIED)
1299+
1300+
with self.assertRaises(CallException) as ctx:
1301+
self.client.ethereum_sign_tx(
1302+
n=n, nonce=nonce, gas_price=gas_price, gas_limit=gas_limit,
1303+
to=USDC_ADDRESS, value=value, data=data, chain_id=chain_id)
1304+
self.assertIn("Blind signing disabled", str(ctx.exception))
1305+
1306+
def test_v2_unsupported_arg_format_returns_malformed(self):
1307+
"""v2 supports only fixed single-word ADDRESS/AMOUNT/TOKEN_AMOUNT arg
1308+
formats (decode_v2_args has no dynamic-type support, by design). The
1309+
Python serializer refuses to BUILD a STRING-format v2 blob (see the
1310+
offline test_rejects_dynamic_format), but a malicious or buggy host
1311+
could still hand-craft the raw bytes — the device's own parser must
1312+
independently reject an unsupported v2 arg format as MALFORMED at
1313+
blob-load time, before any calldata is even seen."""
1314+
self._drop_setup_screenshots()
1315+
body = bytearray()
1316+
body.append(METADATA_VERSION_SCHEMA)
1317+
body.extend((1).to_bytes(4, 'big')) # chain_id
1318+
body.extend(USDC_ADDRESS)
1319+
body.extend(ERC20_TRANSFER_SELECTOR)
1320+
name = b'transfer'
1321+
body.extend(len(name).to_bytes(2, 'big'))
1322+
body.extend(name)
1323+
body.append(1) # num_args
1324+
arg_name = b'label'
1325+
body.append(len(arg_name))
1326+
body.extend(arg_name)
1327+
body.append(ARG_FORMAT_STRING) # unsupported in v2
1328+
body.append(CLASSIFICATION_VERIFIED)
1329+
body.extend((0).to_bytes(4, 'big')) # timestamp
1330+
body.append(TEST_KEY_ID)
1331+
blob = sign_metadata(bytes(body))
1332+
1333+
resp = self.client.ethereum_send_tx_metadata(
1334+
signed_payload=blob, metadata_version=1, key_id=TEST_KEY_ID)
1335+
self.assertEqual(resp.classification, CLASSIFICATION_MALFORMED)
1336+
12771337

12781338
# ═══════════════════════════════════════════════════════════════════════
12791339
# Dynamically generate one full-confirm device test per CLEARSIGN_FLOWS
@@ -1320,12 +1380,13 @@ def print_clearsign_flows():
13201380
for flow in CLEARSIGN_FLOWS:
13211381
print()
13221382
print('[%s] %s' % (flow['key'], flow['method']))
1323-
print(' shows : %s' % flow['shows'])
1383+
shows = ', '.join('%s=%r' % (a['name'], a.get('value')) for a in flow['args'])
1384+
print(' shows : %s' % shows)
13241385
print(' to : 0x%s' % flow['to'].hex())
13251386
print(' value : %d' % flow['value'])
13261387
print(' calldata : 0x%s' % flow['data'].hex())
13271388
print(' tx_hash : 0x%s' % flow_tx_hash(flow).hex())
1328-
print(' blob : %s' % flow_blob(flow, timestamp=REFERENCE_TIMESTAMP).hex())
1389+
print(' blob : %s' % flow_blob(flow, TEST_KEY_ID, timestamp=REFERENCE_TIMESTAMP).hex())
13291390

13301391

13311392
def print_test_vectors():

tests/test_msg_ethereum_signing_guards.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def test_eip1559_requires_chain_id(self):
2727
"""type=2 with no chain_id: Stage 1 counts chain_id as 1 byte but
2828
hash_rlp_number(0) hashes nothing -> over-declared list header ->
2929
wrong/garbage signer. The device must reject rather than sign it."""
30-
self.requires_firmware("7.15.1")
30+
self.requires_firmware("7.15.0")
3131
self.requires_fullFeature()
3232
self.setup_mnemonic_nopin_nopassphrase()
3333
self.client.apply_policy("AdvancedMode", 1)
@@ -49,7 +49,7 @@ def test_eip1559_no_priority_fee_signs(self):
4949
absent it must encode as the empty integer (0x80). Stage 1 always
5050
counts it, so Stage 2 must always hash it -- the device must still
5151
produce a valid signature (not desync the list header)."""
52-
self.requires_firmware("7.15.1")
52+
self.requires_firmware("7.15.0")
5353
self.requires_fullFeature()
5454
self.setup_mnemonic_nopin_nopassphrase()
5555
sig_v, sig_r, sig_s = self.client.ethereum_sign_tx(
@@ -69,7 +69,7 @@ def test_type2_without_max_fee_rejected(self):
6969
"""Typed prefix (0x02) is chosen from msg.type but the fee fields from
7070
has_max_fee_per_gas. A type=2 tx carrying only gas_price would sign a
7171
malformed (legacy-fee-in-1559-envelope) field list -> reject."""
72-
self.requires_firmware("7.15.1")
72+
self.requires_firmware("7.15.0")
7373
self.requires_fullFeature()
7474
self.setup_mnemonic_nopin_nopassphrase()
7575
self.client.apply_policy("AdvancedMode", 1)
@@ -88,7 +88,7 @@ def test_type2_without_max_fee_rejected(self):
8888
def test_legacy_with_max_fee_rejected(self):
8989
"""A legacy tx (type omitted) carrying max_fee_per_gas would hash two
9090
fee fields into a legacy structure -> reject the mismatch."""
91-
self.requires_firmware("7.15.1")
91+
self.requires_firmware("7.15.0")
9292
self.requires_fullFeature()
9393
self.setup_mnemonic_nopin_nopassphrase()
9494
self.client.apply_policy("AdvancedMode", 1)
@@ -117,7 +117,7 @@ def test_contract_handler_streamed_calldata_signs_full_data(self):
117117
the screen-level assertion (no 'Sablier' clear-sign summary appears for
118118
streamed calldata) is verified on-device / on the emulator via
119119
DebugLink layout."""
120-
self.requires_firmware("7.15.1")
120+
self.requires_firmware("7.15.0")
121121
self.requires_fullFeature()
122122
self.setup_mnemonic_nopin_nopassphrase()
123123
self.client.apply_policy("AdvancedMode", 1)

0 commit comments

Comments
 (0)