Skip to content

Commit 005d745

Browse files
Merge pull request #786 from LedgerHQ/rel/apa/1.17.0
App release 1.17.0
2 parents ae443cb + b5605cf commit 005d745

560 files changed

Lines changed: 1749 additions & 140 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build_and_functional_tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
uses: LedgerHQ/ledger-app-workflows/.github/workflows/reusable_build.yml@v1
3333
with:
3434
upload_app_binaries_artifact: "ragger_elfs"
35-
flags: "CAL_TEST_KEY=1 TRUSTED_NAME_TEST_KEY=1 SET_PLUGIN_TEST_KEY=1 NFT_TEST_KEY=1"
35+
flags: "CAL_TEST_KEY=1 TRUSTED_NAME_TEST_KEY=1 SET_PLUGIN_TEST_KEY=1 NFT_TEST_KEY=1 EIP7702_TEST_WHITELIST=1"
3636

3737
ragger_tests:
3838
name: Run ragger tests using the reusable workflow

.github/workflows/codeql_checks.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
strategy:
2020
fail-fast: false
2121
matrix:
22-
sdk: ["$NANOS_SDK", "$NANOX_SDK", "$NANOSP_SDK", "$STAX_SDK", "$FLEX_SDK"]
22+
sdk: ["$NANOX_SDK", "$NANOSP_SDK", "$STAX_SDK", "$FLEX_SDK"]
2323
# 'cpp' covers C and C++
2424
language: ['cpp']
2525
runs-on: ubuntu-latest

.github/workflows/lint-workflow.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ jobs:
2323
with:
2424
source: './'
2525
extensions: 'h,c'
26-
version: 12
2726

2827
yamllint:
2928
name: Check yaml files

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

8+
## [1.17.0](../../compare/1.16.0...1.17.0) - 2025-05-05
9+
10+
### Added
11+
12+
- EIP-7702 authorization signing
13+
- Type 4 transaction signing
14+
15+
### Changed
16+
17+
- Datetime formatter now supports `Unlimited` (for things that are not meant to expire)
18+
- Some TX check screens now play a sound (Flex / Stax)
19+
20+
### Removed
21+
22+
- Nano S support
23+
824
## [1.16.0](../../compare/1.15.0...1.16.0) - 2025-04-28
925

1026
### Added

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ endif
3636
include ./makefile_conf/chain/$(CHAIN).mk
3737

3838
APPVERSION_M = 1
39-
APPVERSION_N = 16
39+
APPVERSION_N = 17
4040
APPVERSION_P = 0
4141
APPVERSION = $(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)
4242

client/pyproject.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,20 @@ readme = { file = "README.md", content-type = "text/markdown" }
1616
# license = { file = "LICENSE" }
1717
classifiers = [
1818
"License :: OSI Approved :: Apache Software License",
19-
"Programming Language :: Python :: 3.7",
20-
"Programming Language :: Python :: 3.8",
2119
"Programming Language :: Python :: 3.9",
2220
"Programming Language :: Python :: 3.10",
21+
"Programming Language :: Python :: 3.11",
22+
"Programming Language :: Python :: 3.12",
23+
"Programming Language :: Python :: 3.13",
2324
"Operating System :: POSIX :: Linux",
2425
"Operating System :: Microsoft :: Windows",
2526
"Operating System :: MacOS :: MacOS X",
2627
]
2728
dynamic = [ "version" ]
28-
requires-python = ">=3.7"
29+
requires-python = ">=3.9"
2930
dependencies = [
3031
"ragger[speculos]",
31-
"web3~=6.0",
32+
"web3~=7.0",
3233
]
3334

3435
[tools.setuptools]

client/src/ledger_app_clients/ethereum/client.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import struct
22
from enum import IntEnum
3-
from typing import Optional, Tuple
3+
from typing import Optional
44
from hashlib import sha256
55
import rlp
66
from web3 import Web3
@@ -17,6 +17,7 @@
1717
from .tlv import format_tlv, FieldTag
1818
from .response_parser import pk_addr
1919
from .tx_simu import TxSimu
20+
from .tx_auth_7702 import TxAuth7702
2021

2122

2223
class StatusWord(IntEnum):
@@ -212,13 +213,13 @@ def eip712_filtering_trusted_name(self,
212213
def eip712_filtering_raw(self, name: str, sig: bytes, discarded: bool):
213214
return self._exchange_async(self._cmd_builder.eip712_filtering_raw(name, sig, discarded))
214215

215-
def serialize_tx(self, tx_params: dict) -> Tuple[bytes, bytes]:
216+
def serialize_tx(self, tx_params: dict) -> tuple[bytes, bytes]:
216217
"""Computes the serialized TX and its hash"""
217218

218-
tx = Web3().eth.account.create().sign_transaction(tx_params).rawTransaction
219+
tx = Web3().eth.account.create().sign_transaction(tx_params).raw_transaction
219220
prefix = bytes()
220221
suffix = []
221-
if tx[0] in [0x01, 0x02]:
222+
if tx[0] in [0x01, 0x02, 0x04]:
222223
prefix = tx[:1]
223224
tx = tx[len(prefix):]
224225
else: # legacy
@@ -330,7 +331,7 @@ def provide_trusted_name_v2(self,
330331
chain_id: int,
331332
nft_id: Optional[int] = None,
332333
challenge: Optional[int] = None,
333-
not_valid_after: Optional[Tuple[int]] = None) -> RAPDU:
334+
not_valid_after: Optional[tuple[int, int, int]] = None) -> RAPDU:
334335
payload = format_tlv(FieldTag.STRUCT_VERSION, 2)
335336
payload += format_tlv(FieldTag.TRUSTED_NAME, name)
336337
payload += format_tlv(FieldTag.ADDRESS, addr)
@@ -671,3 +672,9 @@ def provide_proxy_info(self, payload: bytes) -> RAPDU:
671672
for chunk in chunks[:-1]:
672673
self._exchange(chunk)
673674
return self._exchange(chunks[-1])
675+
676+
def sign_eip7702_authorization(self, bip32_path: str, auth_params: TxAuth7702) -> RAPDU:
677+
chunks = self._cmd_builder.sign_eip7702_authorization(bip32_path, auth_params.serialize())
678+
for chunk in chunks[:-1]:
679+
self._exchange(chunk)
680+
return self._exchange_async(chunks[-1])

client/src/ledger_app_clients/ethereum/command_builder.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# https://github.com/LedgerHQ/app-ethereum/blob/develop/doc/ethapp.adoc
33

44
import struct
5+
import math
56
from enum import IntEnum
67
from typing import Optional
78
from ragger.bip import pack_derivation_path
@@ -31,6 +32,7 @@ class InsType(IntEnum):
3132
PROVIDE_PROXY_INFO = 0x2a
3233
PROVIDE_NETWORK_INFORMATION = 0x30
3334
PROVIDE_TX_SIMULATION = 0x32
35+
SIGN_EIP7702_AUTHORIZATION = 0x34
3436

3537

3638
class P1Type(IntEnum):
@@ -64,6 +66,11 @@ class P2Type(IntEnum):
6466
class CommandBuilder:
6567
_CLA: int = 0xE0
6668

69+
def _intToBytes(self, i: int) -> bytes:
70+
if i == 0:
71+
return b"\x00"
72+
return i.to_bytes(math.ceil(i.bit_length() / 8), 'big')
73+
6774
def _serialize(self,
6875
ins: InsType,
6976
p1: int,
@@ -419,11 +426,12 @@ def common_tlv_serialize(self,
419426
ins: InsType,
420427
tlv_payload: bytes,
421428
p1l: list[int] = [0x01, 0x00],
422-
p2l: list[int] = [0x00]) -> list[bytes]:
429+
p2l: list[int] = [0x00],
430+
payload: bytes = bytes()) -> list[bytes]:
423431
assert len(p1l) in [1, 2]
424432
assert len(p2l) in [1, 2]
425433
chunks = []
426-
payload = struct.pack(">H", len(tlv_payload))
434+
payload += struct.pack(">H", len(tlv_payload))
427435
payload += tlv_payload
428436
p1 = p1l[0]
429437
p2 = p2l[0]
@@ -456,6 +464,11 @@ def provide_network_information(self,
456464
p1 = P1Type.FOLLOWING_CHUNK
457465
return chunks
458466

467+
def sign_eip7702_authorization(self, bip32_path: str, tlv_payload: bytes) -> list[bytes]:
468+
return self.common_tlv_serialize(InsType.SIGN_EIP7702_AUTHORIZATION,
469+
tlv_payload,
470+
payload=pack_derivation_path(bip32_path))
471+
459472
def provide_enum_value(self, tlv_payload: bytes) -> list[bytes]:
460473
return self.common_tlv_serialize(InsType.PROVIDE_ENUM_VALUE, tlv_payload)
461474

client/src/ledger_app_clients/ethereum/response_parser.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
def signature(data: bytes) -> tuple[bytes, bytes, bytes]:
1+
def signature(data: bytes) -> tuple[int, int, int]:
22
assert len(data) == (1 + 32 + 32)
33

4-
v = data[0:1]
4+
v = int.from_bytes(data[0:1], "big")
55
data = data[1:]
6-
r = data[0:32]
6+
r = int.from_bytes(data[0:32], "big")
77
data = data[32:]
8-
s = data[0:32]
8+
s = int.from_bytes(data[0:32], "big")
99

1010
return v, r, s
1111

client/src/ledger_app_clients/ethereum/settings.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class SettingID(Enum):
1111
NONCE = auto()
1212
VERBOSE_EIP712 = auto()
1313
DEBUG_DATA = auto()
14+
EIP7702 = auto()
1415

1516

1617
def get_device_settings(firmware: Firmware) -> list[SettingID]:
@@ -28,6 +29,7 @@ def get_device_settings(firmware: Firmware) -> list[SettingID]:
2829
SettingID.NONCE,
2930
SettingID.VERBOSE_EIP712,
3031
SettingID.DEBUG_DATA,
32+
SettingID.EIP7702,
3133
]
3234
return [
3335
SettingID.WEB3_CHECK,
@@ -36,6 +38,7 @@ def get_device_settings(firmware: Firmware) -> list[SettingID]:
3638
SettingID.NONCE,
3739
SettingID.VERBOSE_EIP712,
3840
SettingID.DEBUG_DATA,
41+
SettingID.EIP7702,
3942
]
4043

4144

@@ -75,6 +78,11 @@ def get_setting_position(firmware: Firmware, setting: SettingID) -> tuple[int, i
7578
page, y = 2, 130
7679
else:
7780
page, y = 2, 315
81+
elif setting == SettingID.EIP7702:
82+
if firmware == Firmware.STAX:
83+
page, y = 2, 300
84+
else:
85+
page, y = 3, 140
7886
else:
7987
raise ValueError(f"Unknown setting: {setting}")
8088
return page, x, y

0 commit comments

Comments
 (0)