Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions keepkeylib/erc7730_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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 = []
Expand Down
34 changes: 30 additions & 4 deletions tests/test_erc7730_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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():
Expand Down Expand Up @@ -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")