Skip to content

Commit 2c1a1cf

Browse files
Merge pull request #704 from LedgerHQ/develop
App release 1.14.0
2 parents e0a55ef + d28cfa0 commit 2c1a1cf

File tree

799 files changed

+8777
-414
lines changed

Some content is hidden

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

799 files changed

+8777
-414
lines changed

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,25 @@ 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.14.0](https://github.com/ledgerhq/app-ethereum/compare/1.13.0...1.14.0) - 2024-12-19
9+
10+
### Added
11+
12+
- (network) Zero
13+
- Generic clear-signing support
14+
15+
### Changed
16+
17+
- Renamed "Address" field in ERC-20 approval flow to "Approve to"
18+
- Blind-signing flow now shows transaction hash (keccak-256 of the RLP without v/r/s)
19+
- Blind-signing flow now hides the amount if it is 0
20+
21+
### Fixed
22+
23+
- Version comparison in trusted name feature
24+
- Key ID & public key used for trusted names coming from CAL
25+
- PKI key usage in trusted name feature
26+
827
## [1.13.0](https://github.com/ledgerhq/app-ethereum/compare/1.12.2...1.13.0) - 2024-11-26
928

1029
### 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 = 13
39+
APPVERSION_N = 14
4040
APPVERSION_P = 0
4141
APPVERSION = $(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)
4242

client/src/ledger_app_clients/ethereum/client.py

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ class PKIPubKeyUsage(IntEnum):
7777
PUBKEY_USAGE_SEED_ID_AUTH = 0x09
7878

7979

80+
class SignMode(IntEnum):
81+
BASIC = 0x00
82+
STORE = 0x01
83+
START_FLOW = 0x02
84+
85+
8086
class PKIClient:
8187
_CLA: int = 0xB0
8288
_INS: int = 0x06
@@ -130,6 +136,15 @@ def send_raw(self, cla: int, ins: int, p1: int, p2: int, payload: bytes):
130136
header.append(len(payload))
131137
return self._exchange(header + payload)
132138

139+
def send_raw_async(self, cla: int, ins: int, p1: int, p2: int, payload: bytes):
140+
header = bytearray()
141+
header.append(cla)
142+
header.append(ins)
143+
header.append(p1)
144+
header.append(p2)
145+
header.append(len(payload))
146+
return self._exchange_async(header + payload)
147+
133148
def eip712_send_struct_def_struct_name(self, name: str):
134149
return self._exchange_async(self._cmd_builder.eip712_send_struct_def_struct_name(name))
135150

@@ -211,7 +226,8 @@ def eip712_filtering_raw(self, name: str, sig: bytes, discarded: bool):
211226

212227
def sign(self,
213228
bip32_path: str,
214-
tx_params: dict):
229+
tx_params: dict,
230+
mode: SignMode = SignMode.BASIC):
215231
tx = Web3().eth.account.create().sign_transaction(tx_params).rawTransaction
216232
prefix = bytes()
217233
suffix = []
@@ -223,7 +239,7 @@ def sign(self,
223239
suffix = [int(tx_params["chainId"]), bytes(), bytes()]
224240
decoded = rlp.decode(tx)[:-3] # remove already computed signature
225241
tx = prefix + rlp.encode(decoded + suffix)
226-
chunks = self._cmd_builder.sign(bip32_path, tx, suffix)
242+
chunks = self._cmd_builder.sign(bip32_path, tx, mode)
227243
for chunk in chunks[:-1]:
228244
self._exchange(chunk)
229245
return self._exchange_async(chunks[-1])
@@ -255,7 +271,7 @@ def perform_privacy_operation(self,
255271
bip32_path,
256272
pubkey))
257273

258-
def _provide_trusted_name_common(self, payload: bytes) -> RAPDU:
274+
def _provide_trusted_name_common(self, payload: bytes, name_source: TrustedNameSource) -> RAPDU:
259275
if self._pki_client is None:
260276
print(f"Ledger-PKI Not supported on '{self._firmware.name}'")
261277
else:
@@ -272,10 +288,16 @@ def _provide_trusted_name_common(self, payload: bytes) -> RAPDU:
272288

273289
self._pki_client.send_certificate(PKIPubKeyUsage.PUBKEY_USAGE_COIN_META, bytes.fromhex(cert_apdu))
274290
payload += format_tlv(FieldTag.STRUCT_TYPE, 3) # TrustedName
275-
payload += format_tlv(FieldTag.SIGNER_KEY_ID, 0) # test key
291+
if name_source == TrustedNameSource.CAL:
292+
key_id = 6
293+
key = Key.CAL
294+
else:
295+
key_id = 3
296+
key = Key.TRUSTED_NAME
297+
payload += format_tlv(FieldTag.SIGNER_KEY_ID, key_id) # test key
276298
payload += format_tlv(FieldTag.SIGNER_ALGO, 1) # secp256k1
277299
payload += format_tlv(FieldTag.DER_SIGNATURE,
278-
sign_data(Key.TRUSTED_NAME, payload))
300+
sign_data(key, payload))
279301
chunks = self._cmd_builder.provide_trusted_name(payload)
280302
for chunk in chunks[:-1]:
281303
self._exchange(chunk)
@@ -287,7 +309,7 @@ def provide_trusted_name_v1(self, addr: bytes, name: str, challenge: int) -> RAP
287309
payload += format_tlv(FieldTag.COIN_TYPE, 0x3c) # ETH in slip-44
288310
payload += format_tlv(FieldTag.TRUSTED_NAME, name)
289311
payload += format_tlv(FieldTag.ADDRESS, addr)
290-
return self._provide_trusted_name_common(payload)
312+
return self._provide_trusted_name_common(payload, TrustedNameSource.ENS)
291313

292314
def provide_trusted_name_v2(self,
293315
addr: bytes,
@@ -311,7 +333,7 @@ def provide_trusted_name_v2(self,
311333
if not_valid_after is not None:
312334
assert len(not_valid_after) == 3
313335
payload += format_tlv(FieldTag.NOT_VALID_AFTER, struct.pack("BBB", *not_valid_after))
314-
return self._provide_trusted_name_common(payload)
336+
return self._provide_trusted_name_common(payload, name_source)
315337

316338
def set_plugin(self,
317339
plugin_name: str,
@@ -524,3 +546,15 @@ def provide_network_information(self,
524546
assert response.status == StatusWord.OK
525547
response = self._exchange(chunks[-1])
526548
assert response.status == StatusWord.OK
549+
550+
def provide_enum_value(self, payload: bytes) -> RAPDU:
551+
chunks = self._cmd_builder.provide_enum_value(payload)
552+
for chunk in chunks[:-1]:
553+
self._exchange(chunk)
554+
return self._exchange(chunks[-1])
555+
556+
def provide_transaction_info(self, payload: bytes) -> RAPDU:
557+
chunks = self._cmd_builder.provide_transaction_info(payload)
558+
for chunk in chunks[:-1]:
559+
self._exchange(chunk)
560+
return self._exchange(chunks[-1])

client/src/ledger_app_clients/ethereum/command_builder.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class InsType(IntEnum):
1515
SIGN = 0x04
1616
PERSONAL_SIGN = 0x08
1717
PROVIDE_ERC20_TOKEN_INFORMATION = 0x0a
18+
EXTERNAL_PLUGIN_SETUP = 0x12
1819
PROVIDE_NFT_INFORMATION = 0x14
1920
SET_PLUGIN = 0x16
2021
PERFORM_PRIVACY_OPERATION = 0x18
@@ -24,7 +25,8 @@ class InsType(IntEnum):
2425
EIP712_SIGN = 0x0c
2526
GET_CHALLENGE = 0x20
2627
PROVIDE_TRUSTED_NAME = 0x22
27-
EXTERNAL_PLUGIN_SETUP = 0x12
28+
PROVIDE_ENUM_VALUE = 0x24
29+
PROVIDE_TRANSACTION_INFO = 0x26
2830
PROVIDE_NETWORK_INFORMATION = 0x30
2931

3032

@@ -261,15 +263,15 @@ def set_external_plugin(self, plugin_name: str, contract_address: bytes, selecto
261263
0x00,
262264
data)
263265

264-
def sign(self, bip32_path: str, rlp_data: bytes, vrs: list) -> list[bytes]:
266+
def sign(self, bip32_path: str, rlp_data: bytes, p2: int) -> list[bytes]:
265267
apdus = list()
266268
payload = pack_derivation_path(bip32_path)
267269
payload += rlp_data
268270
p1 = P1Type.SIGN_FIRST_CHUNK
269271
while len(payload) > 0:
270272
apdus.append(self._serialize(InsType.SIGN,
271273
p1,
272-
0x00,
274+
p2,
273275
payload[:0xff]))
274276
payload = payload[0xff:]
275277
p1 = P1Type.SIGN_SUBSQT_CHUNK
@@ -427,3 +429,23 @@ def provide_network_information(self,
427429
icon = icon[0xff:]
428430
p1 = P1Type.FOLLOWING_CHUNK
429431
return chunks
432+
433+
def common_tlv_serialize(self, tlv_payload: bytes, ins: InsType) -> list[bytes]:
434+
chunks = list()
435+
payload = struct.pack(">H", len(tlv_payload))
436+
payload += tlv_payload
437+
p1 = 1
438+
while len(payload) > 0:
439+
chunks.append(self._serialize(ins,
440+
p1,
441+
0x00,
442+
payload[:0xff]))
443+
payload = payload[0xff:]
444+
p1 = 0
445+
return chunks
446+
447+
def provide_enum_value(self, tlv_payload: bytes) -> list[bytes]:
448+
return self.common_tlv_serialize(tlv_payload, InsType.PROVIDE_ENUM_VALUE)
449+
450+
def provide_transaction_info(self, tlv_payload: bytes) -> list[bytes]:
451+
return self.common_tlv_serialize(tlv_payload, InsType.PROVIDE_TRANSACTION_INFO)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from enum import IntEnum
2+
from typing import Optional
3+
from .tlv import format_tlv
4+
from .keychain import sign_data, Key
5+
6+
7+
class Tag(IntEnum):
8+
VERSION = 0x00
9+
CHAIN_ID = 0x01
10+
CONTRACT_ADDR = 0x02
11+
SELECTOR = 0x03
12+
ID = 0x04
13+
VALUE = 0x05
14+
NAME = 0x06
15+
SIGNATURE = 0xff
16+
17+
18+
class EnumValue:
19+
version: int
20+
chain_id: int
21+
contract_addr: bytes
22+
selector: bytes
23+
id: int
24+
value: int
25+
name: str
26+
signature: Optional[bytes] = None
27+
28+
def __init__(self,
29+
version: int,
30+
chain_id: int,
31+
contract_addr: bytes,
32+
selector: bytes,
33+
id: int,
34+
value: int,
35+
name: str,
36+
signature: Optional[bytes] = None):
37+
self.version = version
38+
self.chain_id = chain_id
39+
self.contract_addr = contract_addr
40+
self.selector = selector
41+
self.id = id
42+
self.value = value
43+
self.name = name
44+
self.signature = signature
45+
46+
def serialize(self) -> bytes:
47+
payload = bytearray()
48+
payload += format_tlv(Tag.VERSION, self.version)
49+
payload += format_tlv(Tag.CHAIN_ID, self.chain_id)
50+
payload += format_tlv(Tag.CONTRACT_ADDR, self.contract_addr)
51+
payload += format_tlv(Tag.SELECTOR, self.selector)
52+
payload += format_tlv(Tag.ID, self.id)
53+
payload += format_tlv(Tag.VALUE, self.value)
54+
payload += format_tlv(Tag.NAME, self.name)
55+
sig = self.signature
56+
if sig is None:
57+
sig = sign_data(Key.CAL, payload)
58+
payload += format_tlv(Tag.SIGNATURE, sig)
59+
return payload

0 commit comments

Comments
 (0)