From b9858363c0ab087d47f7c115f46a2e0d741131fb Mon Sep 17 00:00:00 2001 From: highlander Date: Sat, 26 Sep 2026 22:18:16 -0500 Subject: [PATCH 1/2] feat(erc7730): isolate groups and visible optional fields --- keepkeylib/erc7730_compiler.py | 8 ++++++-- tests/test_erc7730_compiler.py | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/keepkeylib/erc7730_compiler.py b/keepkeylib/erc7730_compiler.py index c6bad2a6..c7199834 100644 --- a/keepkeylib/erc7730_compiler.py +++ b/keepkeylib/erc7730_compiler.py @@ -979,7 +979,7 @@ def depth(node): _PATH, _LITERAL, _STRING = frozenset((1,)), frozenset((2,)), frozenset((3,)) DEVICE_CAPABILITIES = { # 2 and 3 (interpolated intent) only as one run directly after the intent - "display_opcodes": frozenset((1, 2, 3, 4, 10)), + "display_opcodes": frozenset((1, 2, 3, 4, 5, 6, 10)), # formatter kind -> (argument role -> permitted sources, required roles) "formatters": { 1: ({1: _PATH}, frozenset((1,))), # raw @@ -998,7 +998,8 @@ def depth(node): # @.from, @.to and @.value, calldata definitions only "containers": frozenset((1, 2, 3)), "path_step_opcodes": frozenset((1,)), - "conditions": False, + "condition_opcodes": frozenset((3,)), # optional: always shown + "conditions": True, "alias_set_max": 4, "enum_max": 16, "signer_text_max": 64, @@ -1194,6 +1195,9 @@ def literal_class(index): conditions = sections.get(5, b"\0\0") if u16(conditions, 0) and not capabilities["conditions"]: return "display conditions are not executed" + for i in range(u16(conditions, 0)): + if conditions[2 + 8 * i] not in capabilities.get("condition_opcodes", ()): + return "condition opcode %d is not executed" % conditions[2 + 8 * i] formatters = sections.get(6, b"\0\0") formatter_constants = [] diff --git a/tests/test_erc7730_compiler.py b/tests/test_erc7730_compiler.py index e48a80fc..1f18bf4e 100644 --- a/tests/test_erc7730_compiler.py +++ b/tests/test_erc7730_compiler.py @@ -325,7 +325,7 @@ def test_compiles_typed_if_not_in_and_must_match_conditions(): literals = sections[4] assert int.from_bytes(literals[:2], "big") == 5 _firmware_validate(compiled, - "display conditions are not executed") + "condition opcode 7 is not executed") def test_compiles_interpolated_intent_and_metadata_enum(): @@ -383,8 +383,7 @@ def test_compiles_nested_field_group_with_balanced_links(): assert [item[0] for item in instructions] == [1, 5, 4, 4, 6, 10] assert int.from_bytes(instructions[1][6:8], "big") == 4 assert int.from_bytes(instructions[4][2:4], "big") == 1 - _firmware_validate(compiled, - "display opcode 5 is not executed") + _firmware_validate(compiled) def test_loads_bounded_includes_and_compiles_array_backed_group(tmp_path): @@ -438,7 +437,8 @@ def test_loads_bounded_includes_and_compiles_array_backed_group(tmp_path): # amount, rather than reinterpret bytes the calldata does not say are one. # Phase B adds the interpolated intent, shown as numbered parts: 954. # Phase C adds amount, nftName, date, duration, unit, enum and @.value: 1138. -REGISTRY_SIGNABLE = 1138 +# D-G enables groups and always-visible optional fields, without iteration. +REGISTRY_SIGNABLE = 1221 def test_official_registry_all_calldata_formats_reach_firmware(): @@ -554,3 +554,29 @@ def test_signed_enum_keys_are_minimal_twos_complement(): "params": {"$ref": "$.metadata.enums.side"}}]}}}} _firmware_validate(compile_calldata(descriptor, signature, 1, "0x" + "11" * 20), None) + + +def test_signer_constant_is_not_an_intent_value(): + signature = "pay(address recipient)" + descriptor = {"display": {"formats": {signature: { + "intent": "Pay", "fields": [ + {"value": "Signer claim", "label": "Claim", "format": "raw"}]}}}} + program = bytearray(_unchecked(compile_calldata, descriptor, signature, + 1, "0x" + "11" * 20)) + _firmware_validate(bytes(program)) # a signer-owned field is allowed + offset = HEADER_SIZE + while offset < len(program): + kind = program[offset] + length = struct.unpack_from(">I", program, offset + 1)[0] + if kind == 7: + instruction = offset + 5 + 2 + 8 + assert program[instruction] == 4 + formatter = struct.unpack_from(">H", program, instruction + 4)[0] + program[instruction:instruction + 8] = struct.pack( + ">BBHHH", 3, 0, formatter, 0xffff, 0xffff) + break + offset += 5 + length + else: + pytest.fail("display section missing") + _firmware_validate(bytes(program), + "a signer constant cannot be an intent value") From 345cf97a26c46d15f17233e20fae291e70342e88 Mon Sep 17 00:00:00 2001 From: highlander Date: Sat, 26 Sep 2026 22:50:30 -0500 Subject: [PATCH 2/2] ci: clone the actual project for canonical smoke --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 95f8a880..ca41ba78 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ jobs: name: Checkout current branch over HTTPS command: | git clone --depth 1 -b "$CIRCLE_BRANCH" \ - https://github.com/keepkey/python-keepkey.git . + "https://github.com/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}.git" . git submodule update --init --recursive - run: name: Validate canonical Python and EOS vector contracts