Skip to content

Commit

Permalink
Merge pull request #704 from LedgerHQ/develop
Browse files Browse the repository at this point in the history
App release 1.14.0
  • Loading branch information
apaillier-ledger authored Dec 19, 2024
2 parents e0a55ef + d28cfa0 commit 2c1a1cf
Show file tree
Hide file tree
Showing 799 changed files with 8,777 additions and 414 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [1.14.0](https://github.com/ledgerhq/app-ethereum/compare/1.13.0...1.14.0) - 2024-12-19

### Added

- (network) Zero
- Generic clear-signing support

### Changed

- Renamed "Address" field in ERC-20 approval flow to "Approve to"
- Blind-signing flow now shows transaction hash (keccak-256 of the RLP without v/r/s)
- Blind-signing flow now hides the amount if it is 0

### Fixed

- Version comparison in trusted name feature
- Key ID & public key used for trusted names coming from CAL
- PKI key usage in trusted name feature

## [1.13.0](https://github.com/ledgerhq/app-ethereum/compare/1.12.2...1.13.0) - 2024-11-26

### Added
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ endif
include ./makefile_conf/chain/$(CHAIN).mk

APPVERSION_M = 1
APPVERSION_N = 13
APPVERSION_N = 14
APPVERSION_P = 0
APPVERSION = $(APPVERSION_M).$(APPVERSION_N).$(APPVERSION_P)

Expand Down
48 changes: 41 additions & 7 deletions client/src/ledger_app_clients/ethereum/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ class PKIPubKeyUsage(IntEnum):
PUBKEY_USAGE_SEED_ID_AUTH = 0x09


class SignMode(IntEnum):
BASIC = 0x00
STORE = 0x01
START_FLOW = 0x02


class PKIClient:
_CLA: int = 0xB0
_INS: int = 0x06
Expand Down Expand Up @@ -130,6 +136,15 @@ def send_raw(self, cla: int, ins: int, p1: int, p2: int, payload: bytes):
header.append(len(payload))
return self._exchange(header + payload)

def send_raw_async(self, cla: int, ins: int, p1: int, p2: int, payload: bytes):
header = bytearray()
header.append(cla)
header.append(ins)
header.append(p1)
header.append(p2)
header.append(len(payload))
return self._exchange_async(header + payload)

def eip712_send_struct_def_struct_name(self, name: str):
return self._exchange_async(self._cmd_builder.eip712_send_struct_def_struct_name(name))

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

def sign(self,
bip32_path: str,
tx_params: dict):
tx_params: dict,
mode: SignMode = SignMode.BASIC):
tx = Web3().eth.account.create().sign_transaction(tx_params).rawTransaction
prefix = bytes()
suffix = []
Expand All @@ -223,7 +239,7 @@ def sign(self,
suffix = [int(tx_params["chainId"]), bytes(), bytes()]
decoded = rlp.decode(tx)[:-3] # remove already computed signature
tx = prefix + rlp.encode(decoded + suffix)
chunks = self._cmd_builder.sign(bip32_path, tx, suffix)
chunks = self._cmd_builder.sign(bip32_path, tx, mode)
for chunk in chunks[:-1]:
self._exchange(chunk)
return self._exchange_async(chunks[-1])
Expand Down Expand Up @@ -255,7 +271,7 @@ def perform_privacy_operation(self,
bip32_path,
pubkey))

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

self._pki_client.send_certificate(PKIPubKeyUsage.PUBKEY_USAGE_COIN_META, bytes.fromhex(cert_apdu))
payload += format_tlv(FieldTag.STRUCT_TYPE, 3) # TrustedName
payload += format_tlv(FieldTag.SIGNER_KEY_ID, 0) # test key
if name_source == TrustedNameSource.CAL:
key_id = 6
key = Key.CAL
else:
key_id = 3
key = Key.TRUSTED_NAME
payload += format_tlv(FieldTag.SIGNER_KEY_ID, key_id) # test key
payload += format_tlv(FieldTag.SIGNER_ALGO, 1) # secp256k1
payload += format_tlv(FieldTag.DER_SIGNATURE,
sign_data(Key.TRUSTED_NAME, payload))
sign_data(key, payload))
chunks = self._cmd_builder.provide_trusted_name(payload)
for chunk in chunks[:-1]:
self._exchange(chunk)
Expand All @@ -287,7 +309,7 @@ def provide_trusted_name_v1(self, addr: bytes, name: str, challenge: int) -> RAP
payload += format_tlv(FieldTag.COIN_TYPE, 0x3c) # ETH in slip-44
payload += format_tlv(FieldTag.TRUSTED_NAME, name)
payload += format_tlv(FieldTag.ADDRESS, addr)
return self._provide_trusted_name_common(payload)
return self._provide_trusted_name_common(payload, TrustedNameSource.ENS)

def provide_trusted_name_v2(self,
addr: bytes,
Expand All @@ -311,7 +333,7 @@ def provide_trusted_name_v2(self,
if not_valid_after is not None:
assert len(not_valid_after) == 3
payload += format_tlv(FieldTag.NOT_VALID_AFTER, struct.pack("BBB", *not_valid_after))
return self._provide_trusted_name_common(payload)
return self._provide_trusted_name_common(payload, name_source)

def set_plugin(self,
plugin_name: str,
Expand Down Expand Up @@ -524,3 +546,15 @@ def provide_network_information(self,
assert response.status == StatusWord.OK
response = self._exchange(chunks[-1])
assert response.status == StatusWord.OK

def provide_enum_value(self, payload: bytes) -> RAPDU:
chunks = self._cmd_builder.provide_enum_value(payload)
for chunk in chunks[:-1]:
self._exchange(chunk)
return self._exchange(chunks[-1])

def provide_transaction_info(self, payload: bytes) -> RAPDU:
chunks = self._cmd_builder.provide_transaction_info(payload)
for chunk in chunks[:-1]:
self._exchange(chunk)
return self._exchange(chunks[-1])
28 changes: 25 additions & 3 deletions client/src/ledger_app_clients/ethereum/command_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class InsType(IntEnum):
SIGN = 0x04
PERSONAL_SIGN = 0x08
PROVIDE_ERC20_TOKEN_INFORMATION = 0x0a
EXTERNAL_PLUGIN_SETUP = 0x12
PROVIDE_NFT_INFORMATION = 0x14
SET_PLUGIN = 0x16
PERFORM_PRIVACY_OPERATION = 0x18
Expand All @@ -24,7 +25,8 @@ class InsType(IntEnum):
EIP712_SIGN = 0x0c
GET_CHALLENGE = 0x20
PROVIDE_TRUSTED_NAME = 0x22
EXTERNAL_PLUGIN_SETUP = 0x12
PROVIDE_ENUM_VALUE = 0x24
PROVIDE_TRANSACTION_INFO = 0x26
PROVIDE_NETWORK_INFORMATION = 0x30


Expand Down Expand Up @@ -261,15 +263,15 @@ def set_external_plugin(self, plugin_name: str, contract_address: bytes, selecto
0x00,
data)

def sign(self, bip32_path: str, rlp_data: bytes, vrs: list) -> list[bytes]:
def sign(self, bip32_path: str, rlp_data: bytes, p2: int) -> list[bytes]:
apdus = list()
payload = pack_derivation_path(bip32_path)
payload += rlp_data
p1 = P1Type.SIGN_FIRST_CHUNK
while len(payload) > 0:
apdus.append(self._serialize(InsType.SIGN,
p1,
0x00,
p2,
payload[:0xff]))
payload = payload[0xff:]
p1 = P1Type.SIGN_SUBSQT_CHUNK
Expand Down Expand Up @@ -427,3 +429,23 @@ def provide_network_information(self,
icon = icon[0xff:]
p1 = P1Type.FOLLOWING_CHUNK
return chunks

def common_tlv_serialize(self, tlv_payload: bytes, ins: InsType) -> list[bytes]:
chunks = list()
payload = struct.pack(">H", len(tlv_payload))
payload += tlv_payload
p1 = 1
while len(payload) > 0:
chunks.append(self._serialize(ins,
p1,
0x00,
payload[:0xff]))
payload = payload[0xff:]
p1 = 0
return chunks

def provide_enum_value(self, tlv_payload: bytes) -> list[bytes]:
return self.common_tlv_serialize(tlv_payload, InsType.PROVIDE_ENUM_VALUE)

def provide_transaction_info(self, tlv_payload: bytes) -> list[bytes]:
return self.common_tlv_serialize(tlv_payload, InsType.PROVIDE_TRANSACTION_INFO)
59 changes: 59 additions & 0 deletions client/src/ledger_app_clients/ethereum/enum_value.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from enum import IntEnum
from typing import Optional
from .tlv import format_tlv
from .keychain import sign_data, Key


class Tag(IntEnum):
VERSION = 0x00
CHAIN_ID = 0x01
CONTRACT_ADDR = 0x02
SELECTOR = 0x03
ID = 0x04
VALUE = 0x05
NAME = 0x06
SIGNATURE = 0xff


class EnumValue:
version: int
chain_id: int
contract_addr: bytes
selector: bytes
id: int
value: int
name: str
signature: Optional[bytes] = None

def __init__(self,
version: int,
chain_id: int,
contract_addr: bytes,
selector: bytes,
id: int,
value: int,
name: str,
signature: Optional[bytes] = None):
self.version = version
self.chain_id = chain_id
self.contract_addr = contract_addr
self.selector = selector
self.id = id
self.value = value
self.name = name
self.signature = signature

def serialize(self) -> bytes:
payload = bytearray()
payload += format_tlv(Tag.VERSION, self.version)
payload += format_tlv(Tag.CHAIN_ID, self.chain_id)
payload += format_tlv(Tag.CONTRACT_ADDR, self.contract_addr)
payload += format_tlv(Tag.SELECTOR, self.selector)
payload += format_tlv(Tag.ID, self.id)
payload += format_tlv(Tag.VALUE, self.value)
payload += format_tlv(Tag.NAME, self.name)
sig = self.signature
if sig is None:
sig = sign_data(Key.CAL, payload)
payload += format_tlv(Tag.SIGNATURE, sig)
return payload
Loading

0 comments on commit 2c1a1cf

Please sign in to comment.